diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 070418a19a0..d1751f4955a 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -198,11 +198,13 @@ const BaseTransitionImpl: ComponentOptions = { return emptyPlaceholder(child) } - const enterHooks = resolveTransitionHooks( + let enterHooks = resolveTransitionHooks( innerChild, rawProps, state, instance, + // #11061, ensure enterHooks is fresh after clone + hooks => (enterHooks = hooks), ) setTransitionHooks(innerChild, enterHooks) @@ -299,6 +301,7 @@ export function resolveTransitionHooks( props: BaseTransitionProps, state: TransitionState, instance: ComponentInternalInstance, + postClone?: (hooks: TransitionHooks) => void, ): TransitionHooks { const { appear, @@ -439,7 +442,15 @@ export function resolveTransitionHooks( }, clone(vnode) { - return resolveTransitionHooks(vnode, props, state, instance) + const hooks = resolveTransitionHooks( + vnode, + props, + state, + instance, + postClone, + ) + if (postClone) postClone(hooks) + return hooks }, } diff --git a/packages/runtime-core/src/vnode.ts b/packages/runtime-core/src/vnode.ts index 0e2a4bafcc5..3d30503e20a 100644 --- a/packages/runtime-core/src/vnode.ts +++ b/packages/runtime-core/src/vnode.ts @@ -36,7 +36,10 @@ import { isSuspense, } from './components/Suspense' import type { DirectiveBinding } from './directives' -import type { TransitionHooks } from './components/BaseTransition' +import { + type TransitionHooks, + setTransitionHooks, +} from './components/BaseTransition' import { warn } from './warning' import { type Teleport, @@ -691,7 +694,10 @@ export function cloneVNode( // to clone the transition to ensure that the vnode referenced within // the transition hooks is fresh. if (transition && cloneTransition) { - cloned.transition = transition.clone(cloned as VNode) + setTransitionHooks( + cloned as VNode, + transition.clone(cloned as VNode) as TransitionHooks, + ) } if (__COMPAT__) { diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index 4fe78ae8ab0..59754bb97b3 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -1263,6 +1263,98 @@ describe('e2e: Transition', () => { E2E_TIMEOUT, ) + // #11061 + test( + 'transition + fallthrough attrs (in-out mode)', + async () => { + const beforeLeaveSpy = vi.fn() + const onLeaveSpy = vi.fn() + const afterLeaveSpy = vi.fn() + const beforeEnterSpy = vi.fn() + const onEnterSpy = vi.fn() + const afterEnterSpy = vi.fn() + + await page().exposeFunction('onLeaveSpy', onLeaveSpy) + await page().exposeFunction('onEnterSpy', onEnterSpy) + await page().exposeFunction('beforeLeaveSpy', beforeLeaveSpy) + await page().exposeFunction('beforeEnterSpy', beforeEnterSpy) + await page().exposeFunction('afterLeaveSpy', afterLeaveSpy) + await page().exposeFunction('afterEnterSpy', afterEnterSpy) + + await page().evaluate(() => { + const { onEnterSpy, onLeaveSpy } = window as any + const { createApp, ref } = (window as any).Vue + createApp({ + components: { + one: { + template: '
one
', + }, + two: { + template: '
two
', + }, + }, + template: ` +
+ + + +
+ + `, + setup: () => { + const view = ref('one') + const click = () => + (view.value = view.value === 'one' ? 'two' : 'one') + return { + view, + click, + beforeEnterSpy, + onEnterSpy, + afterEnterSpy, + beforeLeaveSpy, + onLeaveSpy, + afterLeaveSpy, + } + }, + }).mount('#app') + }) + expect(await html('#container')).toBe('
one
') + + // toggle + await click('#toggleBtn') + await nextTick() + await transitionFinish() + expect(beforeEnterSpy).toBeCalledTimes(1) + expect(onEnterSpy).toBeCalledTimes(1) + expect(afterEnterSpy).toBeCalledTimes(1) + expect(beforeLeaveSpy).toBeCalledTimes(1) + expect(onLeaveSpy).toBeCalledTimes(1) + expect(afterLeaveSpy).toBeCalledTimes(1) + + expect(await html('#container')).toBe('
two
') + + // toggle back + await click('#toggleBtn') + await nextTick() + await transitionFinish() + expect(beforeEnterSpy).toBeCalledTimes(2) + expect(onEnterSpy).toBeCalledTimes(2) + expect(afterEnterSpy).toBeCalledTimes(2) + expect(beforeLeaveSpy).toBeCalledTimes(2) + expect(onLeaveSpy).toBeCalledTimes(2) + expect(afterLeaveSpy).toBeCalledTimes(2) + + expect(await html('#container')).toBe('
one
') + }, + E2E_TIMEOUT, + ) + test( 'w/ KeepAlive + unmount innerChild', async () => {