componentSlots.ts 4.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151
  1. import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
  2. import { type Block, type BlockFn, DynamicFragment, insert } from './block'
  3. import { rawPropsProxyHandlers } from './componentProps'
  4. import { currentInstance, isRef } from '@vue/runtime-dom'
  5. import type { LooseRawProps, VaporComponentInstance } from './component'
  6. import { renderEffect } from './renderEffect'
  7. import {
  8. insertionAnchor,
  9. insertionParent,
  10. resetInsertionState,
  11. } from './insertionState'
  12. import { isHydrating, locateHydrationNode } from './dom/hydration'
  13. export type RawSlots = Record<string, VaporSlot> & {
  14. $?: DynamicSlotSource[]
  15. }
  16. export type StaticSlots = Record<string, VaporSlot>
  17. export type VaporSlot = BlockFn
  18. export type DynamicSlot = { name: string; fn: VaporSlot }
  19. export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
  20. export type DynamicSlotSource = StaticSlots | DynamicSlotFn
  21. export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
  22. get: getSlot,
  23. has: (target, key: string) => !!getSlot(target, key),
  24. getOwnPropertyDescriptor(target, key: string) {
  25. const slot = getSlot(target, key)
  26. if (slot) {
  27. return {
  28. configurable: true,
  29. enumerable: true,
  30. value: slot,
  31. }
  32. }
  33. },
  34. ownKeys(target) {
  35. let keys = Object.keys(target)
  36. const dynamicSources = target.$
  37. if (dynamicSources) {
  38. keys = keys.filter(k => k !== '$')
  39. for (const source of dynamicSources) {
  40. if (isFunction(source)) {
  41. const slot = source()
  42. if (isArray(slot)) {
  43. for (const s of slot) keys.push(String(s.name))
  44. } else {
  45. keys.push(String(slot.name))
  46. }
  47. } else {
  48. keys.push(...Object.keys(source))
  49. }
  50. }
  51. }
  52. return keys
  53. },
  54. set: NO,
  55. deleteProperty: NO,
  56. }
  57. export function getSlot(
  58. target: RawSlots,
  59. key: string,
  60. ): (VaporSlot & { _bound?: VaporSlot }) | undefined {
  61. if (key === '$') return
  62. const dynamicSources = target.$
  63. if (dynamicSources) {
  64. let i = dynamicSources.length
  65. let source
  66. while (i--) {
  67. source = dynamicSources[i]
  68. if (isFunction(source)) {
  69. const slot = source()
  70. if (slot) {
  71. if (isArray(slot)) {
  72. for (const s of slot) {
  73. if (String(s.name) === key) return s.fn
  74. }
  75. } else if (String(slot.name) === key) {
  76. return slot.fn
  77. }
  78. }
  79. } else if (hasOwn(source, key)) {
  80. return source[key]
  81. }
  82. }
  83. }
  84. if (hasOwn(target, key)) {
  85. return target[key]
  86. }
  87. }
  88. export function createSlot(
  89. name: string | (() => string),
  90. rawProps?: LooseRawProps | null,
  91. fallback?: VaporSlot,
  92. ): Block {
  93. const _insertionParent = insertionParent
  94. const _insertionAnchor = insertionAnchor
  95. if (isHydrating) {
  96. locateHydrationNode()
  97. } else {
  98. resetInsertionState()
  99. }
  100. const instance = currentInstance as VaporComponentInstance
  101. const rawSlots = instance.rawSlots
  102. const slotProps = rawProps
  103. ? new Proxy(rawProps, rawPropsProxyHandlers)
  104. : EMPTY_OBJ
  105. let fragment: DynamicFragment
  106. if (isRef(rawSlots._)) {
  107. fragment = instance.appContext.vapor!.vdomSlot(
  108. rawSlots._,
  109. name,
  110. slotProps,
  111. instance,
  112. fallback,
  113. )
  114. } else {
  115. fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
  116. const isDynamicName = isFunction(name)
  117. const renderSlot = () => {
  118. const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
  119. if (slot) {
  120. fragment.fallback = fallback
  121. // create and cache bound version of the slot to make it stable
  122. // so that we avoid unnecessary updates if it resolves to the same slot
  123. fragment.update(slot._bound || (slot._bound = () => slot(slotProps)))
  124. } else {
  125. fragment.update(fallback)
  126. }
  127. }
  128. // dynamic slot name or has dynamicSlots
  129. if (isDynamicName || rawSlots.$) {
  130. renderEffect(renderSlot)
  131. } else {
  132. renderSlot()
  133. }
  134. }
  135. if (!isHydrating && _insertionParent) {
  136. insert(fragment, _insertionParent, _insertionAnchor)
  137. }
  138. return fragment
  139. }