import { type ComponentInternalInstance, currentInstance } from './component' import { type VNode, type VNodeChild, type VNodeNormalizedChildren, normalizeVNode, } from './vnode' import { EMPTY_OBJ, type IfAny, type Prettify, ShapeFlags, SlotFlags, def, isArray, isFunction, } from '@vue/shared' import { warn } from './warning' import { isKeepAlive } from './components/KeepAlive' import { type ContextualRenderFn, currentRenderingInstance, withCtx, } from './componentRenderContext' import { isHmrUpdating } from './hmr' import { DeprecationTypes, isCompatEnabled } from './compat/compatConfig' import { TriggerOpTypes, trigger } from '@vue/reactivity' import { createInternalObject } from './internalObject' export type Slot = ( ...args: IfAny ) => VNode[] export type InternalSlots = { [name: string]: Slot | undefined } export type Slots = Readonly declare const SlotSymbol: unique symbol export type SlotsType = Record> = { [SlotSymbol]?: T } export type StrictUnwrapSlotsType< S extends SlotsType, T = NonNullable, > = [keyof S] extends [never] ? Slots : Readonly & T export type UnwrapSlotsType< S extends SlotsType, T = NonNullable, > = [keyof S] extends [never] ? Slots : Readonly< Prettify<{ [K in keyof T]: NonNullable extends (...args: any[]) => any ? T[K] : Slot }> > export type RawSlots = { [name: string]: unknown // manual render fn hint to skip forced children updates $stable?: boolean /** * for tracking slot owner instance. This is attached during * normalizeChildren when the component vnode is created. * @internal */ _ctx?: ComponentInternalInstance | null /** * indicates compiler generated slots * we use a reserved property instead of a vnode patchFlag because the slots * object may be directly passed down to a child component in a manual * render function, and the optimization hint need to be on the slot object * itself to be preserved. * @internal */ _?: SlotFlags } const isInternalKey = (key: string) => key === '_' || key === '_ctx' || key === '$stable' const normalizeSlotValue = (value: unknown): VNode[] => isArray(value) ? value.map(normalizeVNode) : [normalizeVNode(value as VNodeChild)] const normalizeSlot = ( key: string, rawSlot: Function, ctx: ComponentInternalInstance | null | undefined, ): Slot => { if ((rawSlot as any)._n) { // already normalized - #5353 return rawSlot as Slot } const normalized = withCtx((...args: any[]) => { if ( __DEV__ && currentInstance && !currentInstance.vapor && !(ctx === null && currentRenderingInstance) && !(ctx && ctx.root !== currentInstance.root) ) { warn( `Slot "${key}" invoked outside of the render function: ` + `this will not track dependencies used in the slot. ` + `Invoke the slot function inside the render function instead.`, ) } return normalizeSlotValue(rawSlot(...args)) }, ctx) as Slot // NOT a compiled slot ;(normalized as ContextualRenderFn)._c = false return normalized } const normalizeObjectSlots = ( rawSlots: RawSlots, slots: InternalSlots, instance: ComponentInternalInstance, ) => { const ctx = rawSlots._ctx for (const key in rawSlots) { if (isInternalKey(key)) continue const value = rawSlots[key] if (isFunction(value)) { slots[key] = normalizeSlot(key, value, ctx) } else if (value != null) { if ( __DEV__ && !( __COMPAT__ && isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance) ) ) { warn( `Non-function value encountered for slot "${key}". ` + `Prefer function slots for better performance.`, ) } const normalized = normalizeSlotValue(value) slots[key] = () => normalized } } } const normalizeVNodeSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren, ) => { if ( __DEV__ && !isKeepAlive(instance.vnode) && !(__COMPAT__ && isCompatEnabled(DeprecationTypes.RENDER_FUNCTION, instance)) ) { warn( `Non-function value encountered for default slot. ` + `Prefer function slots for better performance.`, ) } const normalized = normalizeSlotValue(children) instance.slots.default = () => normalized } const assignSlots = ( slots: InternalSlots, children: Slots, optimized: boolean, ) => { for (const key in children) { // #2893 // when rendering the optimized slots by manually written render function, // do not copy the `slots._` compiler flag so that `renderSlot` creates // slot Fragment with BAIL patchFlag to force full updates if (optimized || !isInternalKey(key)) { slots[key] = children[key] } } } export const initSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren, optimized: boolean, ): void => { const slots = (instance.slots = createInternalObject()) if (instance.vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = (children as RawSlots)._ if (type) { assignSlots(slots, children as Slots, optimized) // make compiler marker non-enumerable if (optimized) { def(slots, '_', type, true) } } else { normalizeObjectSlots(children as RawSlots, slots, instance) } } else if (children) { normalizeVNodeSlots(instance, children) } } export const updateSlots = ( instance: ComponentInternalInstance, children: VNodeNormalizedChildren, optimized: boolean, ): void => { const { vnode, slots } = instance let needDeletionCheck = true let deletionComparisonTarget = EMPTY_OBJ if (vnode.shapeFlag & ShapeFlags.SLOTS_CHILDREN) { const type = (children as RawSlots)._ if (type) { // compiled slots. if (__DEV__ && isHmrUpdating) { // Parent was HMR updated so slot content may have changed. // force update slots and mark instance for hmr as well assignSlots(slots, children as Slots, optimized) trigger(instance, TriggerOpTypes.SET, '$slots') } else if (optimized && type === SlotFlags.STABLE) { // compiled AND stable. // no need to update, and skip stale slots removal. needDeletionCheck = false } else { // compiled but dynamic (v-if/v-for on slots) - update slots, but skip // normalization. assignSlots(slots, children as Slots, optimized) } } else { needDeletionCheck = !(children as RawSlots).$stable normalizeObjectSlots(children as RawSlots, slots, instance) } deletionComparisonTarget = children as RawSlots } else if (children) { // non slot object children (direct value) passed to a component normalizeVNodeSlots(instance, children) deletionComparisonTarget = { default: 1 } } // delete stale slots if (needDeletionCheck) { for (const key in slots) { if (!isInternalKey(key) && deletionComparisonTarget[key] == null) { delete slots[key] } } } }