2024-12-07 21:43:08 +08:00
|
|
|
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
|
2024-12-07 22:05:11 +08:00
|
|
|
import { type Block, type BlockFn, DynamicFragment } from './block'
|
2024-12-08 10:16:25 +08:00
|
|
|
import {
|
|
|
|
type RawProps,
|
|
|
|
getAttrFromRawProps,
|
|
|
|
hasAttrFromRawProps,
|
|
|
|
} from './componentProps'
|
2024-12-07 15:12:32 +08:00
|
|
|
import { currentInstance } from '@vue/runtime-core'
|
|
|
|
import type { VaporComponentInstance } from './component'
|
2024-12-07 21:43:08 +08:00
|
|
|
import { renderEffect } from './renderEffect'
|
2024-12-06 22:45:45 +08:00
|
|
|
|
|
|
|
export type RawSlots = Record<string, Slot> & {
|
|
|
|
$?: (StaticSlots | DynamicSlotFn)[]
|
|
|
|
}
|
|
|
|
|
|
|
|
export type StaticSlots = Record<string, Slot>
|
|
|
|
|
2024-12-07 22:05:11 +08:00
|
|
|
export type Slot = BlockFn
|
2024-12-06 22:45:45 +08:00
|
|
|
export type DynamicSlot = { name: string; fn: Slot }
|
|
|
|
export type DynamicSlotFn = () => DynamicSlot | DynamicSlot[]
|
|
|
|
|
2024-12-06 23:10:41 +08:00
|
|
|
export const dynamicSlotsProxyHandlers: ProxyHandler<RawSlots> = {
|
2024-12-06 22:45:45 +08:00
|
|
|
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) {
|
|
|
|
const keys = Object.keys(target)
|
|
|
|
const dynamicSources = target.$
|
|
|
|
if (dynamicSources) {
|
|
|
|
for (const source of dynamicSources) {
|
|
|
|
if (isFunction(source)) {
|
|
|
|
const slot = source()
|
|
|
|
if (isArray(slot)) {
|
|
|
|
for (const s of slot) keys.push(s.name)
|
|
|
|
} else {
|
|
|
|
keys.push(slot.name)
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
keys.push(...Object.keys(source))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return keys
|
|
|
|
},
|
|
|
|
set: NO,
|
|
|
|
deleteProperty: NO,
|
|
|
|
}
|
|
|
|
|
2024-12-07 15:12:32 +08:00
|
|
|
export function getSlot(target: RawSlots, key: string): Slot | undefined {
|
|
|
|
if (key === '$') return
|
2024-12-06 22:45:45 +08:00
|
|
|
const dynamicSources = target.$
|
|
|
|
if (dynamicSources) {
|
|
|
|
let i = dynamicSources.length
|
|
|
|
let source
|
|
|
|
while (i--) {
|
|
|
|
source = dynamicSources[i]
|
|
|
|
if (isFunction(source)) {
|
|
|
|
const slot = source()
|
|
|
|
if (isArray(slot)) {
|
|
|
|
for (const s of slot) {
|
|
|
|
if (s.name === key) return s.fn
|
|
|
|
}
|
|
|
|
} else if (slot.name === key) {
|
|
|
|
return slot.fn
|
|
|
|
}
|
|
|
|
} else if (hasOwn(source, key)) {
|
|
|
|
return source[key]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasOwn(target, key)) {
|
|
|
|
return target[key]
|
|
|
|
}
|
|
|
|
}
|
2024-12-07 15:12:32 +08:00
|
|
|
|
2024-12-07 21:43:08 +08:00
|
|
|
const dynamicSlotsPropsProxyHandlers: ProxyHandler<RawProps> = {
|
2024-12-08 10:16:25 +08:00
|
|
|
get: getAttrFromRawProps,
|
|
|
|
has: hasAttrFromRawProps,
|
|
|
|
ownKeys: target => Object.keys(target).filter(k => k !== '$'),
|
2024-12-07 21:43:08 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
// TODO how to handle empty slot return blocks?
|
|
|
|
// e.g. a slot renders a v-if node that may toggle inside.
|
|
|
|
// we may need special handling by passing the fallback into the slot
|
|
|
|
// and make the v-if use it as fallback
|
2024-12-07 15:12:32 +08:00
|
|
|
export function createSlot(
|
|
|
|
name: string | (() => string),
|
2024-12-07 21:43:08 +08:00
|
|
|
rawProps?: RawProps,
|
2024-12-07 15:12:32 +08:00
|
|
|
fallback?: Slot,
|
|
|
|
): Block {
|
2024-12-08 10:16:25 +08:00
|
|
|
const fragment = new DynamicFragment('slot')
|
2024-12-07 21:43:08 +08:00
|
|
|
const rawSlots = (currentInstance as VaporComponentInstance)!.rawSlots
|
|
|
|
const slotProps = rawProps
|
2024-12-08 10:16:25 +08:00
|
|
|
? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers)
|
2024-12-07 21:43:08 +08:00
|
|
|
: EMPTY_OBJ
|
|
|
|
|
2024-12-08 10:16:25 +08:00
|
|
|
// always create effect because a slot may contain dynamic root inside
|
|
|
|
// which affects fallback
|
|
|
|
renderEffect(() => {
|
|
|
|
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
|
2024-12-07 15:12:32 +08:00
|
|
|
if (slot) {
|
2024-12-08 10:16:25 +08:00
|
|
|
fragment.update(
|
|
|
|
() => slot(slotProps) || (fallback && fallback()),
|
|
|
|
// TODO this key needs to account for possible fallback (v-if)
|
|
|
|
// inside the slot
|
|
|
|
slot,
|
|
|
|
)
|
|
|
|
} else {
|
|
|
|
fragment.update(fallback)
|
2024-12-07 15:12:32 +08:00
|
|
|
}
|
2024-12-08 10:16:25 +08:00
|
|
|
})
|
|
|
|
|
|
|
|
return fragment
|
2024-12-07 15:12:32 +08:00
|
|
|
}
|