Skip to content

Commit a3fbf21

Browse files
authored
fix(suspense): fix anchor for suspense with transition out-in (#9999)
close #9996
1 parent 7220c58 commit a3fbf21

File tree

2 files changed

+84
-6
lines changed

2 files changed

+84
-6
lines changed

packages/runtime-core/src/components/Suspense.ts

+13-6
Original file line numberDiff line numberDiff line change
@@ -400,7 +400,6 @@ export interface SuspenseBoundary {
400400
namespace: ElementNamespace
401401
container: RendererElement
402402
hiddenContainer: RendererElement
403-
anchor: RendererNode | null
404403
activeBranch: VNode | null
405404
pendingBranch: VNode | null
406405
deps: number
@@ -473,14 +472,14 @@ function createSuspenseBoundary(
473472
assertNumber(timeout, `Suspense timeout`)
474473
}
475474

475+
const initialAnchor = anchor
476476
const suspense: SuspenseBoundary = {
477477
vnode,
478478
parent: parentSuspense,
479479
parentComponent,
480480
namespace,
481481
container,
482482
hiddenContainer,
483-
anchor,
484483
deps: 0,
485484
pendingId: suspenseId++,
486485
timeout: typeof timeout === 'number' ? timeout : -1,
@@ -529,20 +528,28 @@ function createSuspenseBoundary(
529528
move(
530529
pendingBranch!,
531530
container,
532-
next(activeBranch!),
531+
anchor === initialAnchor ? next(activeBranch!) : anchor,
533532
MoveType.ENTER,
534533
)
535534
queuePostFlushCb(effects)
536535
}
537536
}
538537
}
539-
// this is initial anchor on mount
540-
let { anchor } = suspense
541538
// unmount current active tree
542539
if (activeBranch) {
543540
// if the fallback tree was mounted, it may have been moved
544541
// as part of a parent suspense. get the latest anchor for insertion
545-
anchor = next(activeBranch)
542+
// #8105 if `delayEnter` is true, it means that the mounting of
543+
// `activeBranch` will be delayed. if the branch switches before
544+
// transition completes, both `activeBranch` and `pendingBranch` may
545+
// coexist in the `hiddenContainer`. This could result in
546+
// `next(activeBranch!)` obtaining an incorrect anchor
547+
// (got `pendingBranch.el`).
548+
// Therefore, after the mounting of activeBranch is completed,
549+
// it is necessary to get the latest anchor.
550+
if (parentNode(activeBranch.el!) !== suspense.hiddenContainer) {
551+
anchor = next(activeBranch)
552+
}
546553
unmount(activeBranch, parentComponent, suspense, true)
547554
}
548555
if (!delayEnter) {

packages/vue/__tests__/e2e/Transition.spec.ts

+71
Original file line numberDiff line numberDiff line change
@@ -1652,6 +1652,77 @@ describe('e2e: Transition', () => {
16521652
},
16531653
E2E_TIMEOUT,
16541654
)
1655+
1656+
// #9996
1657+
test(
1658+
'trigger again when transition is not finished & correctly anchor',
1659+
async () => {
1660+
await page().evaluate(duration => {
1661+
const { createApp, shallowRef, h } = (window as any).Vue
1662+
const One = {
1663+
async setup() {
1664+
return () => h('div', { class: 'test' }, 'one')
1665+
},
1666+
}
1667+
const Two = {
1668+
async setup() {
1669+
return () => h('div', { class: 'test' }, 'two')
1670+
},
1671+
}
1672+
createApp({
1673+
template: `
1674+
<div id="container">
1675+
<div>Top</div>
1676+
<transition name="test" mode="out-in" :duration="${duration}">
1677+
<Suspense>
1678+
<component :is="view"/>
1679+
</Suspense>
1680+
</transition>
1681+
<div>Bottom</div>
1682+
</div>
1683+
<button id="toggleBtn" @click="click">button</button>
1684+
`,
1685+
setup: () => {
1686+
const view = shallowRef(One)
1687+
const click = () => {
1688+
view.value = view.value === One ? Two : One
1689+
}
1690+
return { view, click }
1691+
},
1692+
}).mount('#app')
1693+
}, duration)
1694+
1695+
await nextFrame()
1696+
expect(await html('#container')).toBe(
1697+
'<div>Top</div><div class="test test-enter-active test-enter-to">one</div><div>Bottom</div>',
1698+
)
1699+
1700+
await transitionFinish()
1701+
expect(await html('#container')).toBe(
1702+
'<div>Top</div><div class="test">one</div><div>Bottom</div>',
1703+
)
1704+
1705+
// trigger twice
1706+
classWhenTransitionStart()
1707+
await nextFrame()
1708+
expect(await html('#container')).toBe(
1709+
'<div>Top</div><div class="test test-leave-active test-leave-to">one</div><div>Bottom</div>',
1710+
)
1711+
1712+
await transitionFinish()
1713+
await nextFrame()
1714+
expect(await html('#container')).toBe(
1715+
'<div>Top</div><div class="test test-enter-active test-enter-to">two</div><div>Bottom</div>',
1716+
)
1717+
1718+
await transitionFinish()
1719+
await nextFrame()
1720+
expect(await html('#container')).toBe(
1721+
'<div>Top</div><div class="test">two</div><div>Bottom</div>',
1722+
)
1723+
},
1724+
E2E_TIMEOUT,
1725+
)
16551726
})
16561727

16571728
describe('transition with v-show', () => {

0 commit comments

Comments
 (0)