}
}
-// 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
export function createSlot(
name: string | (() => string),
rawProps?: LooseRawProps | null,
): Block {
const instance = currentInstance as VaporComponentInstance
const rawSlots = instance.rawSlots
- const isDynamicName = isFunction(name)
- const fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
const slotProps = rawProps
? new Proxy(rawProps, rawPropsProxyHandlers)
: EMPTY_OBJ
+ if (rawSlots._) {
+ return instance.appContext.vapor!.vdomSlot(
+ rawSlots._,
+ name,
+ slotProps,
+ instance,
+ fallback,
+ )
+ }
+
+ const isDynamicName = isFunction(name)
+ const fragment = __DEV__ ? new DynamicFragment('slot') : new DynamicFragment()
const renderSlot = () => {
const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
if (slot) {
type ConcreteComponent,
type Plugin,
type RendererInternals,
+ type ShallowRef,
+ type Slots,
+ type VNode,
type VaporInteropInterface,
createVNode,
currentInstance,
ensureRenderer,
+ renderSlot,
shallowRef,
simpleSetCurrentInstance,
} from '@vue/runtime-dom'
import {
type LooseRawProps,
type LooseRawSlots,
+ type VaporComponent,
VaporComponentInstance,
createComponent,
mountComponent,
unmountComponent,
} from './component'
-import { VaporFragment, insert } from './block'
-import { extend, remove } from '@vue/shared'
+import { type Block, VaporFragment, insert, remove } from './block'
+import { extend, isFunction, remove as removeItem } from '@vue/shared'
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
-import type { RawSlots } from './componentSlots'
+import type { RawSlots, VaporSlot } from './componentSlots'
+import { renderEffect } from './renderEffect'
const vaporInteropImpl: Omit<
VaporInteropInterface,
- 'vdomMount' | 'vdomUnmount'
+ 'vdomMount' | 'vdomUnmount' | 'vdomSlot'
> = {
mount(vnode, container, anchor, parentComponent) {
const selfAnchor = (vnode.anchor = document.createComment('vapor'))
container.insertBefore(selfAnchor, anchor)
const prev = currentInstance
simpleSetCurrentInstance(parentComponent)
+
const propsRef = shallowRef(vnode.props)
+ const slotsRef = shallowRef(vnode.children)
+
// @ts-expect-error
- const instance = (vnode.component = createComponent(vnode.type, {
- $: [() => propsRef.value],
- }))
+ const instance = (vnode.component = createComponent(
+ vnode.type as any as VaporComponent,
+ {
+ $: [() => propsRef.value],
+ } as RawProps,
+ {
+ _: slotsRef, // pass the slots ref
+ } as any as RawSlots,
+ ))
instance.rawPropsRef = propsRef
+ instance.rawSlotsRef = slotsRef
mountComponent(instance, container, selfAnchor)
simpleSetCurrentInstance(prev)
return instance
update(n1, n2, shouldUpdate) {
n2.component = n1.component
if (shouldUpdate) {
- ;(n2.component as any as VaporComponentInstance).rawPropsRef!.value =
- n2.props
+ const instance = n2.component as any as VaporComponentInstance
+ instance.rawPropsRef!.value = n2.props
+ instance.rawSlotsRef!.value = n2.children
}
},
}
frag.remove = () => {
internals.umt(vnode.component!, null, true)
- remove(parentInstance.vdomChildren!, vnode.component)
- isMounted = false
+ removeItem(parentInstance.vdomChildren!, vnode.component)
+ }
+
+ return frag
+}
+
+function renderVDOMSlot(
+ internals: RendererInternals,
+ slotsRef: ShallowRef<Slots>,
+ name: string | (() => string),
+ props: Record<string, any>,
+ parentComponent: VaporComponentInstance,
+ fallback?: VaporSlot,
+): VaporFragment {
+ const frag = new VaporFragment([])
+
+ let isMounted = false
+ let fallbackNodes: Block | undefined
+ let parentNode: ParentNode
+ let oldVNode: VNode | null = null
+
+ frag.insert = (parent, anchor) => {
+ parentNode = parent
+ if (!isMounted) {
+ renderEffect(() => {
+ const vnode = renderSlot(
+ slotsRef.value,
+ isFunction(name) ? name() : name,
+ props,
+ )
+ if ((vnode.children as any[]).length) {
+ if (fallbackNodes) {
+ remove(fallbackNodes, parentNode)
+ fallbackNodes = undefined
+ }
+ internals.p(oldVNode, vnode, parent, anchor, parentComponent as any)
+ oldVNode = vnode
+ } else {
+ if (fallback && !fallbackNodes) {
+ // mount fallback
+ if (oldVNode) {
+ internals.um(oldVNode, parentComponent as any, null, true)
+ }
+ insert((fallbackNodes = fallback(props)), parent, anchor)
+ }
+ oldVNode = null
+ }
+ })
+ isMounted = true
+ } else {
+ // TODO move
+ }
+
+ frag.remove = () => {
+ if (fallbackNodes) {
+ remove(fallbackNodes, parentNode)
+ } else if (oldVNode) {
+ internals.um(oldVNode, parentComponent as any, null)
+ }
+ }
}
return frag
app._context.vapor = extend(vaporInteropImpl, {
vdomMount: createVDOMComponent.bind(null, internals),
vdomUnmount: internals.umt,
+ vdomSlot: renderVDOMSlot.bind(null, internals),
})
}