mirror of https://github.com/vuejs/core.git
Merge 748686a3f9
into bb4ae25793
This commit is contained in:
commit
d544cd2362
|
@ -103,6 +103,97 @@ export function render(_ctx) {
|
||||||
}"
|
}"
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > forwarded slots > <slot w/ nested component> 1`] = `
|
||||||
|
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _createForwardedSlot = _forwardedSlotCreator()
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n2 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n1 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n0 = _createForwardedSlot("default", null)
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
})
|
||||||
|
return n1
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n2
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > forwarded slots > <slot> tag only 1`] = `
|
||||||
|
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _createForwardedSlot = _forwardedSlotCreator()
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n1 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n0 = _createForwardedSlot("default", null)
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n1
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ template 1`] = `
|
||||||
|
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _createForwardedSlot = _forwardedSlotCreator()
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n2 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n0 = _createForwardedSlot("default", null)
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n2
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ v-for 1`] = `
|
||||||
|
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createFor as _createFor, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _createForwardedSlot = _forwardedSlotCreator()
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n3 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n0 = _createFor(() => (_ctx.b), (_for_item0) => {
|
||||||
|
const n2 = _createForwardedSlot("default", null)
|
||||||
|
return n2
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n3
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
|
exports[`compiler: transform slot > forwarded slots > <slot> tag w/ v-if 1`] = `
|
||||||
|
"import { forwardedSlotCreator as _forwardedSlotCreator, resolveComponent as _resolveComponent, createIf as _createIf, createComponentWithFallback as _createComponentWithFallback } from 'vue';
|
||||||
|
|
||||||
|
export function render(_ctx) {
|
||||||
|
const _createForwardedSlot = _forwardedSlotCreator()
|
||||||
|
const _component_Comp = _resolveComponent("Comp")
|
||||||
|
const n3 = _createComponentWithFallback(_component_Comp, null, {
|
||||||
|
"default": () => {
|
||||||
|
const n0 = _createIf(() => (_ctx.ok), () => {
|
||||||
|
const n2 = _createForwardedSlot("default", null)
|
||||||
|
return n2
|
||||||
|
})
|
||||||
|
return n0
|
||||||
|
}
|
||||||
|
}, true)
|
||||||
|
return n3
|
||||||
|
}"
|
||||||
|
`;
|
||||||
|
|
||||||
exports[`compiler: transform slot > implicit default slot 1`] = `
|
exports[`compiler: transform slot > implicit default slot 1`] = `
|
||||||
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
"import { resolveComponent as _resolveComponent, createComponentWithFallback as _createComponentWithFallback, template as _template } from 'vue';
|
||||||
const t0 = _template("<div></div>")
|
const t0 = _template("<div></div>")
|
||||||
|
|
|
@ -420,6 +420,35 @@ describe('compiler: transform slot', () => {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('forwarded slots', () => {
|
||||||
|
test('<slot> tag only', () => {
|
||||||
|
const { code } = compileWithSlots(`<Comp><slot/></Comp>`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<slot> tag w/ v-if', () => {
|
||||||
|
const { code } = compileWithSlots(`<Comp><slot v-if="ok"/></Comp>`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<slot> tag w/ v-for', () => {
|
||||||
|
const { code } = compileWithSlots(`<Comp><slot v-for="a in b"/></Comp>`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<slot> tag w/ template', () => {
|
||||||
|
const { code } = compileWithSlots(
|
||||||
|
`<Comp><template #default><slot/></template></Comp>`,
|
||||||
|
)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('<slot w/ nested component>', () => {
|
||||||
|
const { code } = compileWithSlots(`<Comp><Comp><slot/></Comp></Comp>`)
|
||||||
|
expect(code).toMatchSnapshot()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
describe('errors', () => {
|
describe('errors', () => {
|
||||||
test('error on extraneous children w/ named default slot', () => {
|
test('error on extraneous children w/ named default slot', () => {
|
||||||
const onError = vi.fn()
|
const onError = vi.fn()
|
||||||
|
|
|
@ -18,6 +18,7 @@ import {
|
||||||
genCall,
|
genCall,
|
||||||
} from './generators/utils'
|
} from './generators/utils'
|
||||||
import { setTemplateRefIdent } from './generators/templateRef'
|
import { setTemplateRefIdent } from './generators/templateRef'
|
||||||
|
import { createForwardedSlotIdent } from './generators/slotOutlet'
|
||||||
|
|
||||||
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
|
export type CodegenOptions = Omit<BaseCodegenOptions, 'optimizeImports'>
|
||||||
|
|
||||||
|
@ -129,6 +130,12 @@ export function generate(
|
||||||
`const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
|
`const ${setTemplateRefIdent} = ${context.helper('createTemplateRefSetter')}()`,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
if (ir.hasForwardedSlot) {
|
||||||
|
push(
|
||||||
|
NEWLINE,
|
||||||
|
`const ${createForwardedSlotIdent} = ${context.helper('forwardedSlotCreator')}()`,
|
||||||
|
)
|
||||||
|
}
|
||||||
push(...genBlockContent(ir.block, context, true))
|
push(...genBlockContent(ir.block, context, true))
|
||||||
push(INDENT_END, NEWLINE)
|
push(INDENT_END, NEWLINE)
|
||||||
|
|
||||||
|
|
|
@ -5,12 +5,14 @@ import { genExpression } from './expression'
|
||||||
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
|
import { type CodeFragment, NEWLINE, buildCodeFragment, genCall } from './utils'
|
||||||
import { genRawProps } from './component'
|
import { genRawProps } from './component'
|
||||||
|
|
||||||
|
export const createForwardedSlotIdent = `_createForwardedSlot`
|
||||||
|
|
||||||
export function genSlotOutlet(
|
export function genSlotOutlet(
|
||||||
oper: SlotOutletIRNode,
|
oper: SlotOutletIRNode,
|
||||||
context: CodegenContext,
|
context: CodegenContext,
|
||||||
): CodeFragment[] {
|
): CodeFragment[] {
|
||||||
const { helper } = context
|
const { helper } = context
|
||||||
const { id, name, fallback } = oper
|
const { id, name, fallback, forwarded } = oper
|
||||||
const [frag, push] = buildCodeFragment()
|
const [frag, push] = buildCodeFragment()
|
||||||
|
|
||||||
const nameExpr = name.isStatic
|
const nameExpr = name.isStatic
|
||||||
|
@ -26,7 +28,7 @@ export function genSlotOutlet(
|
||||||
NEWLINE,
|
NEWLINE,
|
||||||
`const n${id} = `,
|
`const n${id} = `,
|
||||||
...genCall(
|
...genCall(
|
||||||
helper('createSlot'),
|
forwarded ? createForwardedSlotIdent : helper('createSlot'),
|
||||||
nameExpr,
|
nameExpr,
|
||||||
genRawProps(oper.props, context) || 'null',
|
genRawProps(oper.props, context) || 'null',
|
||||||
fallbackArg,
|
fallbackArg,
|
||||||
|
|
|
@ -65,6 +65,7 @@ export interface RootIRNode {
|
||||||
directive: Set<string>
|
directive: Set<string>
|
||||||
block: BlockIRNode
|
block: BlockIRNode
|
||||||
hasTemplateRef: boolean
|
hasTemplateRef: boolean
|
||||||
|
hasForwardedSlot: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IfIRNode extends BaseIRNode {
|
export interface IfIRNode extends BaseIRNode {
|
||||||
|
@ -208,6 +209,7 @@ export interface SlotOutletIRNode extends BaseIRNode {
|
||||||
name: SimpleExpressionNode
|
name: SimpleExpressionNode
|
||||||
props: IRProps[]
|
props: IRProps[]
|
||||||
fallback?: BlockIRNode
|
fallback?: BlockIRNode
|
||||||
|
forwarded?: boolean
|
||||||
parent?: number
|
parent?: number
|
||||||
anchor?: number
|
anchor?: number
|
||||||
}
|
}
|
||||||
|
|
|
@ -78,6 +78,7 @@ export class TransformContext<T extends AllNode = AllNode> {
|
||||||
|
|
||||||
inVOnce: boolean = false
|
inVOnce: boolean = false
|
||||||
inVFor: number = 0
|
inVFor: number = 0
|
||||||
|
inSlot: boolean = false
|
||||||
|
|
||||||
comment: CommentNode[] = []
|
comment: CommentNode[] = []
|
||||||
component: Set<string> = this.ir.component
|
component: Set<string> = this.ir.component
|
||||||
|
@ -219,6 +220,7 @@ export function transform(
|
||||||
directive: new Set(),
|
directive: new Set(),
|
||||||
block: newBlock(node),
|
block: newBlock(node),
|
||||||
hasTemplateRef: false,
|
hasTemplateRef: false,
|
||||||
|
hasForwardedSlot: false,
|
||||||
}
|
}
|
||||||
|
|
||||||
const context = new TransformContext(ir, node, options)
|
const context = new TransformContext(ir, node, options)
|
||||||
|
|
|
@ -99,6 +99,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||||
}
|
}
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
|
if (context.inSlot) context.ir.hasForwardedSlot = true
|
||||||
exitBlock && exitBlock()
|
exitBlock && exitBlock()
|
||||||
context.dynamic.operation = {
|
context.dynamic.operation = {
|
||||||
type: IRNodeTypes.SLOT_OUTLET_NODE,
|
type: IRNodeTypes.SLOT_OUTLET_NODE,
|
||||||
|
@ -106,6 +107,7 @@ export const transformSlotOutlet: NodeTransform = (node, context) => {
|
||||||
name: slotName,
|
name: slotName,
|
||||||
props: irProps,
|
props: irProps,
|
||||||
fallback,
|
fallback,
|
||||||
|
forwarded: context.inSlot,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -248,7 +248,14 @@ function createSlotBlock(
|
||||||
const block: SlotBlockIRNode = newBlock(slotNode)
|
const block: SlotBlockIRNode = newBlock(slotNode)
|
||||||
block.props = dir && dir.exp
|
block.props = dir && dir.exp
|
||||||
const exitBlock = context.enterBlock(block)
|
const exitBlock = context.enterBlock(block)
|
||||||
return [block, exitBlock]
|
context.inSlot = true
|
||||||
|
return [
|
||||||
|
block,
|
||||||
|
() => {
|
||||||
|
context.inSlot = false
|
||||||
|
exitBlock()
|
||||||
|
},
|
||||||
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
function isNonWhitespaceContent(node: TemplateChildNode): boolean {
|
function isNonWhitespaceContent(node: TemplateChildNode): boolean {
|
||||||
|
|
|
@ -81,6 +81,10 @@ export function renderSlot(
|
||||||
}
|
}
|
||||||
openBlock()
|
openBlock()
|
||||||
const validSlotContent = slot && ensureValidVNode(slot(props))
|
const validSlotContent = slot && ensureValidVNode(slot(props))
|
||||||
|
|
||||||
|
// handle forwarded vapor slot fallback
|
||||||
|
ensureVaporSlotFallback(validSlotContent, fallback)
|
||||||
|
|
||||||
const slotKey =
|
const slotKey =
|
||||||
props.key ||
|
props.key ||
|
||||||
// slot content array of a dynamic conditional slot may have a branch
|
// slot content array of a dynamic conditional slot may have a branch
|
||||||
|
@ -124,3 +128,20 @@ export function ensureValidVNode(
|
||||||
? vnodes
|
? vnodes
|
||||||
: null
|
: null
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function ensureVaporSlotFallback(
|
||||||
|
vnodes: VNodeArrayChildren | null | undefined,
|
||||||
|
fallback?: () => VNodeArrayChildren,
|
||||||
|
): void {
|
||||||
|
let vaporSlot: any
|
||||||
|
if (
|
||||||
|
vnodes &&
|
||||||
|
vnodes.length === 1 &&
|
||||||
|
isVNode(vnodes[0]) &&
|
||||||
|
(vaporSlot = vnodes[0].vs)
|
||||||
|
) {
|
||||||
|
if (!vaporSlot.fallback && fallback) {
|
||||||
|
vaporSlot.fallback = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -557,6 +557,10 @@ export { startMeasure, endMeasure } from './profiling'
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
export { initFeatureFlags } from './featureFlags'
|
export { initFeatureFlags } from './featureFlags'
|
||||||
|
/**
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export { ensureVaporSlotFallback } from './helpers/renderSlot'
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -2648,7 +2648,7 @@ export function traverseStaticChildren(
|
||||||
function locateNonHydratedAsyncRoot(
|
function locateNonHydratedAsyncRoot(
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
): ComponentInternalInstance | undefined {
|
): ComponentInternalInstance | undefined {
|
||||||
const subComponent = instance.subTree.component
|
const subComponent = instance.vapor ? null : instance.subTree.component
|
||||||
if (subComponent) {
|
if (subComponent) {
|
||||||
if (subComponent.asyncDep && !subComponent.asyncResolved) {
|
if (subComponent.asyncDep && !subComponent.asyncResolved) {
|
||||||
return subComponent
|
return subComponent
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -23,6 +23,7 @@ export class VaporFragment {
|
||||||
anchor?: Node
|
anchor?: Node
|
||||||
insert?: (parent: ParentNode, anchor: Node | null) => void
|
insert?: (parent: ParentNode, anchor: Node | null) => void
|
||||||
remove?: (parent?: ParentNode) => void
|
remove?: (parent?: ParentNode) => void
|
||||||
|
fallback?: BlockFn
|
||||||
|
|
||||||
constructor(nodes: Block) {
|
constructor(nodes: Block) {
|
||||||
this.nodes = nodes
|
this.nodes = nodes
|
||||||
|
@ -33,7 +34,6 @@ export class DynamicFragment extends VaporFragment {
|
||||||
anchor: Node
|
anchor: Node
|
||||||
scope: EffectScope | undefined
|
scope: EffectScope | undefined
|
||||||
current?: BlockFn
|
current?: BlockFn
|
||||||
fallback?: BlockFn
|
|
||||||
|
|
||||||
constructor(anchorLabel?: string) {
|
constructor(anchorLabel?: string) {
|
||||||
super([])
|
super([])
|
||||||
|
@ -124,6 +124,10 @@ export function insert(
|
||||||
insert(b, parent, anchor)
|
insert(b, parent, anchor)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
if (block.anchor) {
|
||||||
|
insert(block.anchor, parent, anchor)
|
||||||
|
anchor = block.anchor
|
||||||
|
}
|
||||||
// fragment
|
// fragment
|
||||||
if (block.insert) {
|
if (block.insert) {
|
||||||
// TODO handle hydration for vdom interop
|
// TODO handle hydration for vdom interop
|
||||||
|
@ -131,7 +135,6 @@ export function insert(
|
||||||
} else {
|
} else {
|
||||||
insert(block.nodes, parent, anchor)
|
insert(block.nodes, parent, anchor)
|
||||||
}
|
}
|
||||||
if (block.anchor) insert(block.anchor, parent, anchor)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,12 @@
|
||||||
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
|
import { EMPTY_OBJ, NO, hasOwn, isArray, isFunction } from '@vue/shared'
|
||||||
import { type Block, type BlockFn, DynamicFragment, insert } from './block'
|
import {
|
||||||
|
type Block,
|
||||||
|
type BlockFn,
|
||||||
|
DynamicFragment,
|
||||||
|
type VaporFragment,
|
||||||
|
insert,
|
||||||
|
isFragment,
|
||||||
|
} from './block'
|
||||||
import { rawPropsProxyHandlers } from './componentProps'
|
import { rawPropsProxyHandlers } from './componentProps'
|
||||||
import { currentInstance, isRef } from '@vue/runtime-dom'
|
import { currentInstance, isRef } from '@vue/runtime-dom'
|
||||||
import type { LooseRawProps, VaporComponentInstance } from './component'
|
import type { LooseRawProps, VaporComponentInstance } from './component'
|
||||||
|
@ -91,10 +98,21 @@ export function getSlot(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function forwardedSlotCreator(): (
|
||||||
|
name: string | (() => string),
|
||||||
|
rawProps?: LooseRawProps | null,
|
||||||
|
fallback?: VaporSlot,
|
||||||
|
) => Block {
|
||||||
|
const instance = currentInstance as VaporComponentInstance
|
||||||
|
return (name, rawProps, fallback) =>
|
||||||
|
createSlot(name, rawProps, fallback, instance)
|
||||||
|
}
|
||||||
|
|
||||||
export function createSlot(
|
export function createSlot(
|
||||||
name: string | (() => string),
|
name: string | (() => string),
|
||||||
rawProps?: LooseRawProps | null,
|
rawProps?: LooseRawProps | null,
|
||||||
fallback?: VaporSlot,
|
fallback?: VaporSlot,
|
||||||
|
i?: VaporComponentInstance,
|
||||||
): Block {
|
): Block {
|
||||||
const _insertionParent = insertionParent
|
const _insertionParent = insertionParent
|
||||||
const _insertionAnchor = insertionAnchor
|
const _insertionAnchor = insertionAnchor
|
||||||
|
@ -104,7 +122,7 @@ export function createSlot(
|
||||||
resetInsertionState()
|
resetInsertionState()
|
||||||
}
|
}
|
||||||
|
|
||||||
const instance = currentInstance as VaporComponentInstance
|
const instance = i || (currentInstance as VaporComponentInstance)
|
||||||
const rawSlots = instance.rawSlots
|
const rawSlots = instance.rawSlots
|
||||||
const slotProps = rawProps
|
const slotProps = rawProps
|
||||||
? new Proxy(rawProps, rawPropsProxyHandlers)
|
? new Proxy(rawProps, rawPropsProxyHandlers)
|
||||||
|
@ -133,8 +151,27 @@ export function createSlot(
|
||||||
(slot._bound = () => {
|
(slot._bound = () => {
|
||||||
const slotContent = slot(slotProps)
|
const slotContent = slot(slotProps)
|
||||||
if (slotContent instanceof DynamicFragment) {
|
if (slotContent instanceof DynamicFragment) {
|
||||||
slotContent.fallback = fallback
|
let nodes = slotContent.nodes
|
||||||
|
if (
|
||||||
|
(slotContent.fallback = fallback) &&
|
||||||
|
isArray(nodes) &&
|
||||||
|
nodes.length === 0
|
||||||
|
) {
|
||||||
|
// use fallback if the slot content is invalid
|
||||||
|
slotContent.update(fallback)
|
||||||
|
} else {
|
||||||
|
while (isFragment(nodes)) {
|
||||||
|
ensureVaporSlotFallback(nodes, fallback)
|
||||||
|
nodes = nodes.nodes
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
// forwarded vdom slot, if there is no fallback provide, try use the fallback
|
||||||
|
// provided by the slot outlet.
|
||||||
|
else if (isFragment(slotContent)) {
|
||||||
|
ensureVaporSlotFallback(slotContent, fallback)
|
||||||
|
}
|
||||||
|
|
||||||
return slotContent
|
return slotContent
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
|
@ -157,3 +194,12 @@ export function createSlot(
|
||||||
|
|
||||||
return fragment
|
return fragment
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureVaporSlotFallback(
|
||||||
|
block: VaporFragment,
|
||||||
|
fallback?: VaporSlot,
|
||||||
|
): void {
|
||||||
|
if (block.insert && !block.fallback && fallback) {
|
||||||
|
block.fallback = fallback
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ export { insert, prepend, remove, isFragment, VaporFragment } from './block'
|
||||||
export { setInsertionState } from './insertionState'
|
export { setInsertionState } from './insertionState'
|
||||||
export { createComponent, createComponentWithFallback } from './component'
|
export { createComponent, createComponentWithFallback } from './component'
|
||||||
export { renderEffect } from './renderEffect'
|
export { renderEffect } from './renderEffect'
|
||||||
export { createSlot } from './componentSlots'
|
export { createSlot, forwardedSlotCreator } from './componentSlots'
|
||||||
export { template } from './dom/template'
|
export { template } from './dom/template'
|
||||||
export { createTextNode, child, nthChild, next } from './dom/node'
|
export { createTextNode, child, nthChild, next } from './dom/node'
|
||||||
export {
|
export {
|
||||||
|
|
|
@ -4,7 +4,9 @@ import {
|
||||||
type ConcreteComponent,
|
type ConcreteComponent,
|
||||||
MoveType,
|
MoveType,
|
||||||
type Plugin,
|
type Plugin,
|
||||||
|
type RendererElement,
|
||||||
type RendererInternals,
|
type RendererInternals,
|
||||||
|
type RendererNode,
|
||||||
type ShallowRef,
|
type ShallowRef,
|
||||||
type Slots,
|
type Slots,
|
||||||
type VNode,
|
type VNode,
|
||||||
|
@ -13,7 +15,9 @@ import {
|
||||||
createVNode,
|
createVNode,
|
||||||
currentInstance,
|
currentInstance,
|
||||||
ensureRenderer,
|
ensureRenderer,
|
||||||
|
ensureVaporSlotFallback,
|
||||||
isEmitListener,
|
isEmitListener,
|
||||||
|
isVNode,
|
||||||
onScopeDispose,
|
onScopeDispose,
|
||||||
renderSlot,
|
renderSlot,
|
||||||
shallowReactive,
|
shallowReactive,
|
||||||
|
@ -29,8 +33,15 @@ import {
|
||||||
mountComponent,
|
mountComponent,
|
||||||
unmountComponent,
|
unmountComponent,
|
||||||
} from './component'
|
} from './component'
|
||||||
import { type Block, VaporFragment, insert, remove } from './block'
|
import {
|
||||||
import { EMPTY_OBJ, extend, isFunction } from '@vue/shared'
|
type Block,
|
||||||
|
DynamicFragment,
|
||||||
|
VaporFragment,
|
||||||
|
insert,
|
||||||
|
isFragment,
|
||||||
|
remove,
|
||||||
|
} from './block'
|
||||||
|
import { EMPTY_OBJ, extend, isArray, isFunction } from '@vue/shared'
|
||||||
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
|
import { type RawProps, rawPropsProxyHandlers } from './componentProps'
|
||||||
import type { RawSlots, VaporSlot } from './componentSlots'
|
import type { RawSlots, VaporSlot } from './componentSlots'
|
||||||
import { renderEffect } from './renderEffect'
|
import { renderEffect } from './renderEffect'
|
||||||
|
@ -94,15 +105,28 @@ const vaporInteropImpl: Omit<
|
||||||
slot(n1: VNode, n2: VNode, container, anchor) {
|
slot(n1: VNode, n2: VNode, container, anchor) {
|
||||||
if (!n1) {
|
if (!n1) {
|
||||||
// mount
|
// mount
|
||||||
const selfAnchor = (n2.el = n2.anchor = createTextNode())
|
let selfAnchor: Node | undefined
|
||||||
insert(selfAnchor, container, anchor)
|
|
||||||
const { slot, fallback } = n2.vs!
|
const { slot, fallback } = n2.vs!
|
||||||
const propsRef = (n2.vs!.ref = shallowRef(n2.props))
|
const propsRef = (n2.vs!.ref = shallowRef(n2.props))
|
||||||
const slotBlock = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler))
|
const slotBlock = slot(new Proxy(propsRef, vaporSlotPropsProxyHandler))
|
||||||
// TODO fallback for slot with v-if content
|
// forwarded vdom slot without its own fallback, use the fallback provided by
|
||||||
// fallback is a vnode slot function here, and slotBlock, if a DynamicFragment,
|
// the slot outlet
|
||||||
// expects a Vapor BlockFn as fallback
|
if (slotBlock instanceof DynamicFragment) {
|
||||||
fallback
|
// vapor slot's nodes is a forwarded vdom slot
|
||||||
|
let nodes = slotBlock.nodes
|
||||||
|
while (isFragment(nodes)) {
|
||||||
|
ensureVDOMSlotFallback(nodes, fallback)
|
||||||
|
nodes = nodes.nodes
|
||||||
|
}
|
||||||
|
// use fragment's anchor when possible
|
||||||
|
selfAnchor = slotBlock.anchor
|
||||||
|
} else if (isFragment(slotBlock)) {
|
||||||
|
ensureVDOMSlotFallback(slotBlock, fallback)
|
||||||
|
selfAnchor = slotBlock.anchor!
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!selfAnchor) selfAnchor = createTextNode()
|
||||||
|
insert((n2.el = n2.anchor = selfAnchor), container, anchor)
|
||||||
insert((n2.vb = slotBlock), container, selfAnchor)
|
insert((n2.vb = slotBlock), container, selfAnchor)
|
||||||
} else {
|
} else {
|
||||||
// update
|
// update
|
||||||
|
@ -233,34 +257,56 @@ function renderVDOMSlot(
|
||||||
let fallbackNodes: Block | undefined
|
let fallbackNodes: Block | undefined
|
||||||
let oldVNode: VNode | null = null
|
let oldVNode: VNode | null = null
|
||||||
|
|
||||||
|
frag.fallback = fallback
|
||||||
frag.insert = (parentNode, anchor) => {
|
frag.insert = (parentNode, anchor) => {
|
||||||
if (!isMounted) {
|
if (!isMounted) {
|
||||||
renderEffect(() => {
|
renderEffect(() => {
|
||||||
const vnode = renderSlot(
|
let vnode: VNode | undefined
|
||||||
slotsRef.value,
|
let isValidSlot = false
|
||||||
isFunction(name) ? name() : name,
|
// only render slot if rawSlots is defined and slot nodes are not empty
|
||||||
props,
|
// otherwise, render fallback
|
||||||
)
|
if (slotsRef.value) {
|
||||||
if ((vnode.children as any[]).length) {
|
vnode = renderSlot(
|
||||||
|
slotsRef.value,
|
||||||
|
isFunction(name) ? name() : name,
|
||||||
|
props,
|
||||||
|
)
|
||||||
|
|
||||||
|
let children = vnode.children as any[]
|
||||||
|
// handle forwarded vapor slot without its own fallback
|
||||||
|
// use the fallback provided by the slot outlet
|
||||||
|
ensureVaporSlotFallback(children, fallback as any)
|
||||||
|
isValidSlot = children.length > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isValidSlot) {
|
||||||
if (fallbackNodes) {
|
if (fallbackNodes) {
|
||||||
remove(fallbackNodes, parentNode)
|
remove(fallbackNodes, parentNode)
|
||||||
fallbackNodes = undefined
|
fallbackNodes = undefined
|
||||||
}
|
}
|
||||||
internals.p(
|
internals.p(
|
||||||
oldVNode,
|
oldVNode,
|
||||||
vnode,
|
vnode!,
|
||||||
parentNode,
|
parentNode,
|
||||||
anchor,
|
anchor,
|
||||||
parentComponent as any,
|
parentComponent as any,
|
||||||
)
|
)
|
||||||
oldVNode = vnode
|
oldVNode = vnode!
|
||||||
} else {
|
} else {
|
||||||
|
// for forwarded slot without its own fallback, use the fallback
|
||||||
|
// provided by the slot outlet.
|
||||||
|
// re-fetch `frag.fallback` as it may have been updated at `createSlot`
|
||||||
|
fallback = frag.fallback
|
||||||
if (fallback && !fallbackNodes) {
|
if (fallback && !fallbackNodes) {
|
||||||
// mount fallback
|
// mount fallback
|
||||||
if (oldVNode) {
|
if (oldVNode) {
|
||||||
internals.um(oldVNode, parentComponent as any, null, true)
|
internals.um(oldVNode, parentComponent as any, null, true)
|
||||||
}
|
}
|
||||||
insert((fallbackNodes = fallback(props)), parentNode, anchor)
|
insert(
|
||||||
|
(fallbackNodes = fallback(internals, parentComponent)),
|
||||||
|
parentNode,
|
||||||
|
anchor,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
oldVNode = null
|
oldVNode = null
|
||||||
}
|
}
|
||||||
|
@ -302,3 +348,37 @@ export const vaporInteropPlugin: Plugin = app => {
|
||||||
return mount(...args)
|
return mount(...args)
|
||||||
}) satisfies App['mount']
|
}) satisfies App['mount']
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureVDOMSlotFallback(block: VaporFragment, fallback?: () => any) {
|
||||||
|
if (block.insert && !block.fallback && fallback) {
|
||||||
|
block.fallback = createFallback(fallback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const createFallback =
|
||||||
|
(fallback: () => any) =>
|
||||||
|
(
|
||||||
|
internals: RendererInternals<RendererNode, RendererElement>,
|
||||||
|
parentComponent: ComponentInternalInstance | null,
|
||||||
|
) => {
|
||||||
|
const fallbackNodes = fallback()
|
||||||
|
|
||||||
|
// vnode slot, wrap it as a VaporFragment
|
||||||
|
if (isArray(fallbackNodes) && fallbackNodes.every(isVNode)) {
|
||||||
|
const frag = new VaporFragment([])
|
||||||
|
frag.insert = (parentNode, anchor) => {
|
||||||
|
fallbackNodes.forEach(vnode => {
|
||||||
|
internals.p(null, vnode, parentNode, anchor, parentComponent)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
frag.remove = parentNode => {
|
||||||
|
fallbackNodes.forEach(vnode => {
|
||||||
|
internals.um(vnode, parentComponent, null, true)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
return frag
|
||||||
|
}
|
||||||
|
|
||||||
|
// vapor slot
|
||||||
|
return fallbackNodes as Block
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue