import {
createComponent,
createForSlots,
+ createIf,
createSlot,
createVaporApp,
defineVaporComponent,
expect(host.innerHTML).toBe('<p><!--slot--></p>')
})
+
+ test('use fallback when inner content changes', async () => {
+ const Child = {
+ setup() {
+ return createSlot('default', null, () =>
+ document.createTextNode('fallback'),
+ )
+ },
+ }
+
+ const toggle = ref(true)
+
+ const { html } = define({
+ setup() {
+ return createComponent(Child, null, {
+ default: () => {
+ return createIf(
+ () => toggle.value,
+ () => {
+ return document.createTextNode('content')
+ },
+ )
+ },
+ })
+ },
+ }).render()
+
+ expect(html()).toBe('content<!--if--><!--slot-->')
+
+ toggle.value = false
+ await nextTick()
+ expect(html()).toBe('fallback<!--if--><!--slot-->')
+
+ toggle.value = true
+ await nextTick()
+ expect(html()).toBe('content<!--if--><!--slot-->')
+ })
})
})
anchor: Node
scope: EffectScope | undefined
current?: BlockFn
+ fallback?: BlockFn
constructor(anchorLabel?: string) {
super([])
this.scope = undefined
this.nodes = []
}
+
+ if (this.fallback && !isValidBlock(this.nodes)) {
+ parent && remove(this.nodes, parent)
+ this.nodes =
+ (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
+ []
+ parent && insert(this.nodes, parent, this.anchor)
+ }
+
resetTracking()
}
}
)
}
-/*! #__NO_SIDE_EFFECTS__ */
-// TODO this should be optimized away
-export function normalizeBlock(block: Block): Node[] {
- const nodes: Node[] = []
+export function isValidBlock(block: Block): boolean {
if (block instanceof Node) {
- nodes.push(block)
- } else if (isArray(block)) {
- block.forEach(child => nodes.push(...normalizeBlock(child)))
+ return !(block instanceof Comment)
} else if (isVaporComponent(block)) {
- nodes.push(...normalizeBlock(block.block!))
- } else if (block) {
- nodes.push(...normalizeBlock(block.nodes))
- block.anchor && nodes.push(block.anchor)
+ return isValidBlock(block.block)
+ } else if (isArray(block)) {
+ return block.length > 0 && block.every(isValidBlock)
+ } else {
+ // fragment
+ return isValidBlock(block.nodes)
}
- return nodes
-}
-
-// TODO optimize
-export function isValidBlock(block: Block): boolean {
- return (
- normalizeBlock(block).filter(node => !(node instanceof Comment)).length > 0
- )
}
export function insert(
parentsWithUnmountedChildren = null
}
}
+
+/**
+ * dev / test only
+ */
+export function normalizeBlock(block: Block): Node[] {
+ if (!__DEV__ && !__TEST__) {
+ throw new Error(
+ 'normalizeBlock should not be used in production code paths',
+ )
+ }
+ const nodes: Node[] = []
+ if (block instanceof Node) {
+ nodes.push(block)
+ } else if (isArray(block)) {
+ block.forEach(child => nodes.push(...normalizeBlock(child)))
+ } else if (isVaporComponent(block)) {
+ nodes.push(...normalizeBlock(block.block!))
+ } else {
+ nodes.push(...normalizeBlock(block.nodes))
+ block.anchor && nodes.push(block.anchor)
+ }
+ return nodes
+}
deleteProperty: NO,
}
-export function getSlot(target: RawSlots, key: string): Slot | undefined {
+export function getSlot(
+ target: RawSlots,
+ key: string,
+): (Slot & { _bound?: Slot }) | undefined {
if (key === '$') return
const dynamicSources = target.$
if (dynamicSources) {
? new Proxy(rawProps, dynamicSlotsPropsProxyHandlers)
: EMPTY_OBJ
- const renderSlot = (name: string) => {
- const slot = getSlot(rawSlots, name)
+ const renderSlot = () => {
+ const slot = getSlot(rawSlots, isFunction(name) ? name() : name)
if (slot) {
- fragment.update(() => slot(slotProps) || (fallback && fallback()))
+ // create and cache bound version of the slot to make it stable
+ // so that we avoid unnecessary updates if it resolves to the same slot
+ fragment.update(
+ slot._bound ||
+ (slot._bound = () => {
+ const slotContent = slot(slotProps)
+ if (slotContent instanceof DynamicFragment) {
+ slotContent.fallback = fallback
+ }
+ return slotContent
+ }),
+ )
} else {
fragment.update(fallback)
}
// dynamic slot name or has dynamicSlots
if (isDynamicName || rawSlots.$) {
- renderEffect(() => renderSlot(isFunction(name) ? name() : name))
+ renderEffect(renderSlot)
} else {
- renderSlot(name)
+ renderSlot()
}
return fragment