Skip to content

Commit 79aa9db

Browse files
authored
fix(runtime-vapor): track and restore slot owner context for DynamicFragment rendering (#14193)
close #14192
1 parent 13320fd commit 79aa9db

File tree

2 files changed

+81
-6
lines changed

2 files changed

+81
-6
lines changed

packages/runtime-vapor/__tests__/componentSlots.spec.ts

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -909,6 +909,72 @@ describe('component: slots', () => {
909909
expect(html()).toBe('child fallback<!--slot--><!--slot-->')
910910
})
911911

912+
test('named forwarded slot with v-if', async () => {
913+
const Child = defineVaporComponent({
914+
setup() {
915+
return createSlot('default', null)
916+
},
917+
})
918+
919+
const Parent = defineVaporComponent({
920+
props: {
921+
show: Boolean,
922+
},
923+
setup(props) {
924+
const n6 = createComponent(
925+
Child,
926+
null,
927+
{
928+
default: withVaporCtx(() => {
929+
const n0 = createIf(
930+
() => props.show,
931+
() => {
932+
const n5 = template('<div></div>')() as any
933+
setInsertionState(n5, null, true)
934+
createSlot('header', null, () => {
935+
const n4 = template('default header')()
936+
return n4
937+
})
938+
return n5
939+
},
940+
)
941+
return n0
942+
}),
943+
},
944+
true,
945+
)
946+
return n6
947+
},
948+
})
949+
950+
const show = ref(false)
951+
const { html } = define({
952+
setup() {
953+
return createComponent(
954+
Parent,
955+
{
956+
show: () => show.value,
957+
},
958+
{
959+
header: () => template('custom header')(),
960+
},
961+
)
962+
},
963+
}).render()
964+
965+
expect(html()).toBe('<!--if--><!--slot-->')
966+
967+
show.value = true
968+
await nextTick()
969+
expect(html()).toBe(
970+
'<div>custom header<!--slot--></div><!--if--><!--slot-->',
971+
)
972+
973+
show.value = false
974+
await nextTick()
975+
expect(html()).toBe('<!--if--><!--slot-->')
976+
})
977+
912978
test('forwarded slot with fallback (v-if)', async () => {
913979
const Child = defineVaporComponent({
914980
setup() {

packages/runtime-vapor/src/fragment.ts

Lines changed: 15 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ import {
3434
} from './dom/hydration'
3535
import { isArray } from '@vue/shared'
3636
import { renderEffect } from './renderEffect'
37+
import { currentSlotOwner, setCurrentSlotOwner } from './componentSlots'
3738

3839
export class VaporFragment<T extends Block = Block>
3940
implements TransitionOptions
@@ -97,8 +98,11 @@ export class DynamicFragment extends VaporFragment {
9798
) => boolean)[]
9899
onBeforeMount?: ((newKey: any, nodes: Block, scope: EffectScope) => void)[]
99100

101+
slotOwner: VaporComponentInstance | null
102+
100103
constructor(anchorLabel?: string) {
101104
super([])
105+
this.slotOwner = currentSlotOwner
102106
if (isHydrating) {
103107
this.anchorLabel = anchorLabel
104108
locateHydrationNode()
@@ -204,12 +208,14 @@ export class DynamicFragment extends VaporFragment {
204208
this.scope = new EffectScope()
205209
}
206210

211+
// restore slot owner
212+
const prevOwner = setCurrentSlotOwner(this.slotOwner)
207213
// switch current instance to parent instance during update
208214
// ensure that the parent instance is correct for nested components
209-
let prev
210-
if (parent && instance) prev = setCurrentInstance(instance)
215+
const prev = parent && instance ? setCurrentInstance(instance) : undefined
211216
this.nodes = this.scope.run(render) || []
212-
if (parent && instance) setCurrentInstance(...prev!)
217+
if (prev !== undefined) setCurrentInstance(...prev)
218+
setCurrentSlotOwner(prevOwner)
213219

214220
if (transition) {
215221
this.$transition = applyTransitionHooks(this.nodes, transition)
@@ -225,9 +231,12 @@ export class DynamicFragment extends VaporFragment {
225231
// apply fallthrough props during update
226232
if (this.attrs) {
227233
if (this.nodes instanceof Element) {
228-
renderEffect(() =>
229-
applyFallthroughProps(this.nodes as Element, this.attrs!),
230-
)
234+
// ensure render effect is cleaned up when scope is stopped
235+
this.scope.run(() => {
236+
renderEffect(() =>
237+
applyFallthroughProps(this.nodes as Element, this.attrs!),
238+
)
239+
})
231240
} else if (
232241
__DEV__ &&
233242
// preventing attrs fallthrough on slots

0 commit comments

Comments
 (0)