componentSlots.ts 2.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import { NO, hasOwn, isArray, isFunction } from '@vue/shared'
  2. import { type Block, Fragment, isValidBlock } from './block'
  3. import { type RawProps, resolveDynamicProps } from './componentProps'
  4. import { currentInstance } from '@vue/runtime-core'
  5. import type { VaporComponentInstance } from './component'
  6. import { renderEffect } from './renderEffect'
  7. export type RawSlots = Record<string, Slot> & {
  8. $?: (StaticSlots | DynamicSlotFn)[]
  9. }
  10. export type StaticSlots = Record<string, Slot>
  11. export type Slot = (...args: any[]) => Block
  12. export type DynamicSlot = { name: string; fn: Slot }
  13. export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
  14. export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
  15. get: getSlot,
  16. has: (target, key: string) => !!getSlot(target, key),
  17. getOwnPropertyDescriptor(target, key: string) {
  18. const slot = getSlot(target, key)
  19. if (slot) {
  20. return {
  21. configurable: true,
  22. enumerable: true,
  23. value: slot,
  24. }
  25. }
  26. },
  27. ownKeys(target) {
  28. const keys = Object.keys(target)
  29. const dynamicSources = target.$
  30. if (dynamicSources) {
  31. for (const source of dynamicSources) {
  32. if (isFunction(source)) {
  33. const slot = source()
  34. if (isArray(slot)) {
  35. for (const s of slot) keys.push(s.name)
  36. } else {
  37. keys.push(slot.name)
  38. }
  39. } else {
  40. keys.push(...Object.keys(source))
  41. }
  42. }
  43. }
  44. return keys
  45. },
  46. set: NO,
  47. deleteProperty: NO,
  48. }
  49. export function getSlot(target: RawSlots, key: string): Slot | undefined {
  50. if (key === '$') return
  51. const dynamicSources = target.$
  52. if (dynamicSources) {
  53. let i = dynamicSources.length
  54. let source
  55. while (i--) {
  56. source = dynamicSources[i]
  57. if (isFunction(source)) {
  58. const slot = source()
  59. if (isArray(slot)) {
  60. for (const s of slot) {
  61. if (s.name === key) return s.fn
  62. }
  63. } else if (slot.name === key) {
  64. return slot.fn
  65. }
  66. } else if (hasOwn(source, key)) {
  67. return source[key]
  68. }
  69. }
  70. }
  71. if (hasOwn(target, key)) {
  72. return target[key]
  73. }
  74. }
  75. export function createSlot(
  76. name: string | (() => string),
  77. props?: RawProps,
  78. fallback?: Slot,
  79. ): Block {
  80. const slots = (currentInstance as VaporComponentInstance)!.rawSlots
  81. if (isFunction(name) || slots.$) {
  82. // dynamic slot name, or dynamic slot sources
  83. // TODO togglable fragment class
  84. const fragment = new Fragment([], 'slot')
  85. return fragment
  86. } else {
  87. // static
  88. return renderSlot(name)
  89. }
  90. function renderSlot(name: string) {
  91. const slot = getSlot(slots, name)
  92. if (slot) {
  93. const block = slot(props ? resolveDynamicProps(props) : {})
  94. if (isValidBlock(block)) {
  95. return block
  96. }
  97. }
  98. return fallback ? fallback() : []
  99. }
  100. }