apiTemplateRef.ts 4.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169
  1. import { type Ref, isRef, onScopeDispose } from '@vue/reactivity'
  2. import {
  3. type VaporComponentInstance,
  4. currentInstance,
  5. getExposed,
  6. isVaporComponent,
  7. } from './component'
  8. import {
  9. ErrorCodes,
  10. type SchedulerJob,
  11. callWithErrorHandling,
  12. isAsyncWrapper,
  13. queuePostFlushCb,
  14. warn,
  15. } from '@vue/runtime-dom'
  16. import {
  17. EMPTY_OBJ,
  18. hasOwn,
  19. isArray,
  20. isFunction,
  21. isString,
  22. remove,
  23. } from '@vue/shared'
  24. import { DynamicFragment } from './fragment'
  25. export type NodeRef = string | Ref | ((ref: Element) => void)
  26. export type RefEl = Element | VaporComponentInstance
  27. export type setRefFn = (
  28. el: RefEl,
  29. ref: NodeRef,
  30. oldRef?: NodeRef,
  31. refFor?: boolean,
  32. ) => NodeRef | undefined
  33. export function createTemplateRefSetter(): setRefFn {
  34. const instance = currentInstance as VaporComponentInstance
  35. return (...args) => setRef(instance, ...args)
  36. }
  37. /**
  38. * Function for handling a template ref
  39. */
  40. export function setRef(
  41. instance: VaporComponentInstance,
  42. el: RefEl,
  43. ref: NodeRef,
  44. oldRef?: NodeRef,
  45. refFor = false,
  46. ): NodeRef | undefined {
  47. if (!instance || instance.isUnmounted) return
  48. const isVaporComp = isVaporComponent(el)
  49. if (isVaporComp && isAsyncWrapper(el as VaporComponentInstance)) {
  50. const i = el as VaporComponentInstance
  51. const frag = i.block as DynamicFragment
  52. // async component not resolved yet
  53. if (!i.type.__asyncResolved) {
  54. frag.setRef = i => setRef(instance, i, ref, oldRef, refFor)
  55. return
  56. }
  57. // set ref to the inner component instead
  58. el = frag.nodes as VaporComponentInstance
  59. }
  60. const setupState: any = __DEV__ ? instance.setupState || {} : null
  61. const refValue = getRefValue(el)
  62. const refs =
  63. instance.refs === EMPTY_OBJ ? (instance.refs = {}) : instance.refs
  64. // dynamic ref changed. unset old ref
  65. if (oldRef != null && oldRef !== ref) {
  66. if (isString(oldRef)) {
  67. refs[oldRef] = null
  68. if (__DEV__ && hasOwn(setupState, oldRef)) {
  69. setupState[oldRef] = null
  70. }
  71. } else if (isRef(oldRef)) {
  72. oldRef.value = null
  73. }
  74. }
  75. if (isFunction(ref)) {
  76. const invokeRefSetter = (value?: Element | Record<string, any>) => {
  77. callWithErrorHandling(ref, currentInstance, ErrorCodes.FUNCTION_REF, [
  78. value,
  79. refs,
  80. ])
  81. }
  82. invokeRefSetter(refValue)
  83. // TODO this gets called repeatedly in renderEffect when it's dynamic ref?
  84. onScopeDispose(() => invokeRefSetter())
  85. } else {
  86. const _isString = isString(ref)
  87. const _isRef = isRef(ref)
  88. let existing: unknown
  89. if (_isString || _isRef) {
  90. const doSet: SchedulerJob = () => {
  91. if (refFor) {
  92. existing = _isString
  93. ? __DEV__ && hasOwn(setupState, ref)
  94. ? setupState[ref]
  95. : refs[ref]
  96. : ref.value
  97. if (!isArray(existing)) {
  98. existing = [refValue]
  99. if (_isString) {
  100. refs[ref] = existing
  101. if (__DEV__ && hasOwn(setupState, ref)) {
  102. setupState[ref] = refs[ref]
  103. // if setupState[ref] is a reactivity ref,
  104. // the existing will also become reactivity too
  105. // need to get the Proxy object by resetting
  106. existing = setupState[ref]
  107. }
  108. } else {
  109. ref.value = existing
  110. }
  111. } else if (!existing.includes(refValue)) {
  112. existing.push(refValue)
  113. }
  114. } else if (_isString) {
  115. refs[ref] = refValue
  116. if (__DEV__ && hasOwn(setupState, ref)) {
  117. setupState[ref] = refValue
  118. }
  119. } else if (_isRef) {
  120. ref.value = refValue
  121. } else if (__DEV__) {
  122. warn('Invalid template ref type:', ref, `(${typeof ref})`)
  123. }
  124. }
  125. queuePostFlushCb(doSet, -1)
  126. // TODO this gets called repeatedly in renderEffect when it's dynamic ref?
  127. onScopeDispose(() => {
  128. queuePostFlushCb(() => {
  129. if (isArray(existing)) {
  130. remove(existing, refValue)
  131. } else if (_isString) {
  132. refs[ref] = null
  133. if (__DEV__ && hasOwn(setupState, ref)) {
  134. setupState[ref] = null
  135. }
  136. } else if (_isRef) {
  137. ref.value = null
  138. }
  139. })
  140. })
  141. } else if (__DEV__) {
  142. warn('Invalid template ref type:', ref, `(${typeof ref})`)
  143. }
  144. }
  145. return ref
  146. }
  147. const getRefValue = (el: RefEl) => {
  148. if (isVaporComponent(el)) {
  149. return getExposed(el) || el
  150. } else if (el instanceof DynamicFragment) {
  151. return getRefValue(el.nodes as RefEl)
  152. }
  153. return el
  154. }