diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 3a1a661d90b..6c6344bfcac 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -181,7 +181,13 @@ export function resolveTransitionProps( onAppearCancelled = onEnterCancelled, } = baseProps - const finishEnter = (el: Element, isAppear: boolean, done?: () => void) => { + const finishEnter = ( + el: Element & { _enterCancelled?: boolean }, + isAppear: boolean, + done?: () => void, + isCancelled?: boolean, + ) => { + el._enterCancelled = isCancelled removeTransitionClass(el, isAppear ? appearToClass : enterToClass) removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass) done && done() @@ -240,7 +246,10 @@ export function resolveTransitionProps( }, onEnter: makeEnterHook(false), onAppear: makeEnterHook(true), - onLeave(el: Element & { _isLeaving?: boolean }, done) { + onLeave( + el: Element & { _isLeaving?: boolean; _enterCancelled?: boolean }, + done, + ) { el._isLeaving = true const resolve = () => finishLeave(el, done) addTransitionClass(el, leaveFromClass) @@ -249,9 +258,14 @@ export function resolveTransitionProps( } // add *-leave-active class before reflow so in the case of a cancelled enter transition // the css will not get the final state (#10677) - addTransitionClass(el, leaveActiveClass) - // force reflow so *-leave-from classes immediately take effect (#2593) - forceReflow() + if (!el._enterCancelled) { + // force reflow so *-leave-from classes immediately take effect (#2593) + forceReflow() + addTransitionClass(el, leaveActiveClass) + } else { + addTransitionClass(el, leaveActiveClass) + forceReflow() + } nextFrame(() => { if (!el._isLeaving) { // cancelled @@ -269,11 +283,11 @@ export function resolveTransitionProps( callHook(onLeave, [el, resolve]) }, onEnterCancelled(el) { - finishEnter(el, false) + finishEnter(el, false, undefined, true) callHook(onEnterCancelled, [el]) }, onAppearCancelled(el) { - finishEnter(el, true) + finishEnter(el, true, undefined, true) callHook(onAppearCancelled, [el]) }, onLeaveCancelled(el) { diff --git a/packages/vue/__tests__/e2e/Transition.spec.ts b/packages/vue/__tests__/e2e/Transition.spec.ts index c0863a75991..66be3a4af57 100644 --- a/packages/vue/__tests__/e2e/Transition.spec.ts +++ b/packages/vue/__tests__/e2e/Transition.spec.ts @@ -3,7 +3,7 @@ import path from 'node:path' import { Transition, createApp, h, nextTick, ref } from 'vue' describe('e2e: Transition', () => { - const { page, html, classList, isVisible, timeout, nextFrame, click } = + const { page, html, classList, style, isVisible, timeout, nextFrame, click } = setupPuppeteer() const baseUrl = `file://${path.resolve(__dirname, './transition.html')}` @@ -2986,6 +2986,55 @@ describe('e2e: Transition', () => { ) }) + test('reflow after *-leave-from before *-leave-active', async () => { + await page().evaluate(() => { + const { createApp, ref } = (window as any).Vue + createApp({ + template: ` +
+ +
content
+
+
+ + `, + setup: () => { + const toggle = ref(false) + const click = () => (toggle.value = !toggle.value) + return { + toggle, + click, + } + }, + }).mount('#app') + }) + + // if transition starts while there's v-leave-active added along with v-leave-from, its bad, it has to start when it doesnt have the v-leave-from + + // enter + await classWhenTransitionStart() + await transitionFinish() + + // leave + expect(await classWhenTransitionStart()).toStrictEqual([ + 'test-reflow', + 'test-reflow-leave-from', + 'test-reflow-leave-active', + ]) + + expect(await style('.test-reflow', 'opacity')).toStrictEqual('0.9') + + await nextFrame() + expect(await classList('.test-reflow')).toStrictEqual([ + 'test-reflow', + 'test-reflow-leave-active', + 'test-reflow-leave-to', + ]) + + await transitionFinish() + expect(await html('#container')).toBe('') + }) + test('warn when used on multiple elements', async () => { createApp({ render() { diff --git a/packages/vue/__tests__/e2e/e2eUtils.ts b/packages/vue/__tests__/e2e/e2eUtils.ts index 8512dcaae47..8a63e0a3530 100644 --- a/packages/vue/__tests__/e2e/e2eUtils.ts +++ b/packages/vue/__tests__/e2e/e2eUtils.ts @@ -39,6 +39,7 @@ interface PuppeteerUtils { value(selector: string): Promise html(selector: string): Promise classList(selector: string): Promise + style(selector: string, property: keyof CSSStyleDeclaration): Promise children(selector: string): Promise isVisible(selector: string): Promise isChecked(selector: string): Promise @@ -120,6 +121,19 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils { return page.$eval(selector, (node: any) => [...node.children]) } + async function style( + selector: string, + property: keyof CSSStyleDeclaration, + ): Promise { + return await page.$eval( + selector, + (node, property) => { + return window.getComputedStyle(node)[property] + }, + property, + ) + } + async function isVisible(selector: string): Promise { const display = await page.$eval(selector, node => { return window.getComputedStyle(node).display @@ -195,6 +209,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils { value, html, classList, + style, children, isVisible, isChecked, diff --git a/packages/vue/__tests__/e2e/transition.html b/packages/vue/__tests__/e2e/transition.html index c44da2f78a1..ab404d67dc7 100644 --- a/packages/vue/__tests__/e2e/transition.html +++ b/packages/vue/__tests__/e2e/transition.html @@ -16,11 +16,21 @@ .test-appear, .test-enter, .test-leave-active, + .test-reflow-enter, + .test-reflow-leave-to, .hello, .bye.active, .changed-enter { opacity: 0; } + .test-reflow-leave-active, + .test-reflow-enter-active { + -webkit-transition: opacity 50ms ease; + transition: opacity 50ms ease; + } + .test-reflow-leave-from { + opacity: 0.9; + } .test-anim-enter-active { animation: test-enter 50ms; -webkit-animation: test-enter 50ms;