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