| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 |
- import { type IfAny, isArray, isFunction } from '@vue/shared'
- import {
- type EffectScope,
- effectScope,
- isReactive,
- shallowReactive,
- } from '@vue/reactivity'
- import {
- type ComponentInternalInstance,
- currentInstance,
- setCurrentInstance,
- } from './component'
- import { type Block, type Fragment, fragmentKey } from './apiRender'
- import { firstEffect, renderEffect } from './renderEffect'
- import { createComment, createTextNode, insert, remove } from './dom/element'
- import { VaporErrorCodes, callWithAsyncErrorHandling } from './errorHandling'
- // TODO: SSR
- export type Slot<T extends any = any> = (
- ...args: IfAny<T, any[], [T] | (T extends undefined ? [] : never)>
- ) => Block
- export type InternalSlots = {
- [name: string]: Slot | undefined
- }
- export type Slots = Readonly<InternalSlots>
- export interface DynamicSlot {
- name: string
- fn: Slot
- key?: string
- }
- export type DynamicSlots = () => (DynamicSlot | DynamicSlot[])[]
- export function initSlots(
- instance: ComponentInternalInstance,
- rawSlots: InternalSlots | null = null,
- dynamicSlots: DynamicSlots | null = null,
- ) {
- let slots: InternalSlots = {}
- for (const key in rawSlots) {
- const slot = rawSlots[key]
- if (slot) {
- slots[key] = withCtx(slot)
- }
- }
- if (dynamicSlots) {
- slots = shallowReactive(slots)
- const dynamicSlotKeys: Record<string, true> = {}
- firstEffect(instance, () => {
- const _dynamicSlots = callWithAsyncErrorHandling(
- dynamicSlots,
- instance,
- VaporErrorCodes.RENDER_FUNCTION,
- )
- for (let i = 0; i < _dynamicSlots.length; i++) {
- const slot = _dynamicSlots[i]
- // array of dynamic slot generated by <template v-for="..." #[...]>
- if (isArray(slot)) {
- for (let j = 0; j < slot.length; j++) {
- slots[slot[j].name] = withCtx(slot[j].fn)
- dynamicSlotKeys[slot[j].name] = true
- }
- } else if (slot) {
- // conditional single slot generated by <template v-if="..." #foo>
- slots[slot.name] = withCtx(
- slot.key
- ? (...args: any[]) => {
- const res = slot.fn(...args)
- // attach branch key so each conditional branch is considered a
- // different fragment
- if (res) (res as any).key = slot.key
- return res
- }
- : slot.fn,
- )
- dynamicSlotKeys[slot.name] = true
- }
- }
- // delete stale slots
- for (const key in dynamicSlotKeys) {
- if (
- !_dynamicSlots.some(slot =>
- slot && isArray(slot)
- ? slot.some(s => s.name === key)
- : slot.name === key,
- )
- ) {
- delete slots[key]
- }
- }
- })
- }
- instance.slots = slots
- function withCtx(fn: Slot): Slot {
- return (...args: any[]) => {
- const reset = setCurrentInstance(instance.parent!)
- try {
- return fn(...args)
- } finally {
- reset()
- }
- }
- }
- }
- export function createSlot(
- name: string | (() => string),
- binds?: Record<string, (() => unknown) | undefined>,
- fallback?: () => Block,
- ): Block {
- let block: Block | undefined
- let branch: Slot | undefined
- let oldBranch: Slot | undefined
- let parent: ParentNode | undefined | null
- let scope: EffectScope | undefined
- const isDynamicName = isFunction(name)
- const instance = currentInstance!
- const { slots } = instance
- // When not using dynamic slots, simplify the process to improve performance
- if (!isDynamicName && !isReactive(slots)) {
- if ((branch = slots[name] || fallback)) {
- return branch(binds)
- } else {
- return []
- }
- }
- const getSlot = isDynamicName ? () => slots[name()] : () => slots[name]
- const anchor = __DEV__ ? createComment('slot') : createTextNode()
- const fragment: Fragment = {
- nodes: [],
- anchor,
- [fragmentKey]: true,
- }
- // TODO lifecycle hooks
- renderEffect(() => {
- if ((branch = getSlot() || fallback) !== oldBranch) {
- parent ||= anchor.parentNode
- if (block) {
- scope!.stop()
- remove(block, parent!)
- }
- if ((oldBranch = branch)) {
- scope = effectScope()
- fragment.nodes = block = scope.run(() => branch!(binds))!
- parent && insert(block, parent, anchor)
- } else {
- scope = block = undefined
- fragment.nodes = []
- }
- }
- })
- return fragment
- }
|