vue3-core/packages/runtime-vapor/src/componentSlots.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

124 lines
3.4 KiB
TypeScript
Raw Normal View History

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
}