mirror of https://github.com/vuejs/core.git
wip(vapor): vdom slots in vapor component
This commit is contained in:
parent
a770a83de6
commit
99d70ddd31
|
@ -194,6 +194,13 @@ export interface VaporInteropInterface {
|
|||
move(vnode: VNode, container: any, anchor: any): void
|
||||
vdomMount: (component: ConcreteComponent, props?: any, slots?: any) => any
|
||||
vdomUnmount: UnmountComponentFn
|
||||
vdomSlot: (
|
||||
slots: any,
|
||||
name: string | (() => string),
|
||||
props: Record<string, any>,
|
||||
parentComponent: any, // VaporComponentInstance
|
||||
fallback?: any, // VaporSlot
|
||||
) => any
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -32,10 +32,11 @@ export function renderSlot(
|
|||
noSlotted?: boolean,
|
||||
): VNode {
|
||||
if (
|
||||
currentRenderingInstance!.ce ||
|
||||
(currentRenderingInstance!.parent &&
|
||||
isAsyncWrapper(currentRenderingInstance!.parent) &&
|
||||
currentRenderingInstance!.parent.ce)
|
||||
currentRenderingInstance &&
|
||||
(currentRenderingInstance.ce ||
|
||||
(currentRenderingInstance.parent &&
|
||||
isAsyncWrapper(currentRenderingInstance.parent) &&
|
||||
currentRenderingInstance.parent.ce))
|
||||
) {
|
||||
// in custom element mode, render <slot/> as actual slot outlets
|
||||
// wrap it with a fragment because in shadowRoot: false mode the slot
|
||||
|
|
|
@ -7,7 +7,7 @@ import {
|
|||
type GenericComponentInstance,
|
||||
isClassComponent,
|
||||
} from './component'
|
||||
import { queueJob, queuePostFlushCb } from './scheduler'
|
||||
import { nextTick, queueJob, queuePostFlushCb } from './scheduler'
|
||||
import { extend, getGlobalThis } from '@vue/shared'
|
||||
|
||||
type HMRComponent = ComponentOptions | ClassComponent
|
||||
|
@ -102,7 +102,9 @@ function rerender(id: string, newRender?: Function): void {
|
|||
i.renderCache = []
|
||||
i.update()
|
||||
}
|
||||
isHmrUpdating = false
|
||||
nextTick(() => {
|
||||
isHmrUpdating = false
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -160,7 +162,9 @@ function reload(id: string, newComp: HMRComponent): void {
|
|||
} else {
|
||||
;(parent as ComponentInternalInstance).update()
|
||||
}
|
||||
isHmrUpdating = false
|
||||
nextTick(() => {
|
||||
isHmrUpdating = false
|
||||
})
|
||||
// #6930, #11248 avoid infinite recursion
|
||||
dirtyInstances.delete(instance)
|
||||
})
|
||||
|
|
|
@ -2530,15 +2530,17 @@ function resolveChildrenNamespace(
|
|||
}
|
||||
|
||||
function toggleRecurse(
|
||||
{ effect, job }: ComponentInternalInstance,
|
||||
{ effect, job, vapor }: ComponentInternalInstance,
|
||||
allowed: boolean,
|
||||
) {
|
||||
if (allowed) {
|
||||
effect.flags |= EffectFlags.ALLOW_RECURSE
|
||||
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
|
||||
} else {
|
||||
effect.flags &= ~EffectFlags.ALLOW_RECURSE
|
||||
job.flags! &= ~SchedulerJobFlags.ALLOW_RECURSE
|
||||
if (!vapor) {
|
||||
if (allowed) {
|
||||
effect.flags |= EffectFlags.ALLOW_RECURSE
|
||||
job.flags! |= SchedulerJobFlags.ALLOW_RECURSE
|
||||
} else {
|
||||
effect.flags &= ~EffectFlags.ALLOW_RECURSE
|
||||
job.flags! &= ~SchedulerJobFlags.ALLOW_RECURSE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -313,10 +313,13 @@ export class VaporComponentInstance implements GenericComponentInstance {
|
|||
props: Record<string, any>
|
||||
attrs: Record<string, any>
|
||||
propsDefaults: Record<string, any> | null
|
||||
rawPropsRef?: ShallowRef<any> // to hold vnode props in vdom interop mode
|
||||
|
||||
slots: StaticSlots
|
||||
|
||||
// to hold vnode props / slots in vdom interop mode
|
||||
rawPropsRef?: ShallowRef<any>
|
||||
rawSlotsRef?: ShallowRef<any>
|
||||
|
||||
emit: EmitFn
|
||||
emitted: Record<string, boolean> | null
|
||||
|
||||
|
|
|
@ -85,10 +85,6 @@ export function getSlot(
|
|||
}
|
||||
}
|
||||
|
||||
// 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,
|
||||
|
@ -96,12 +92,22 @@ export function createSlot(
|
|||
): 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) {
|
||||
|
|
|
@ -3,41 +3,57 @@ import {
|
|||
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
|
||||
|
@ -46,8 +62,9 @@ const vaporInteropImpl: Omit<
|
|||
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
|
||||
}
|
||||
},
|
||||
|
||||
|
@ -109,8 +126,66 @@ function createVDOMComponent(
|
|||
}
|
||||
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
|
||||
|
@ -121,5 +196,6 @@ export const vaporInteropPlugin: Plugin = app => {
|
|||
app._context.vapor = extend(vaporInteropImpl, {
|
||||
vdomMount: createVDOMComponent.bind(null, internals),
|
||||
vdomUnmount: internals.umt,
|
||||
vdomSlot: renderVDOMSlot.bind(null, internals),
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue