]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(transition): reflow before leave-active class after leave-from (#12288)
authorTadas <35614452+Tadehz@users.noreply.github.com>
Fri, 15 Nov 2024 13:36:21 +0000 (15:36 +0200)
committerGitHub <noreply@github.com>
Fri, 15 Nov 2024 13:36:21 +0000 (21:36 +0800)
re-fix #2593

packages/runtime-dom/src/components/Transition.ts
packages/vue/__tests__/e2e/Transition.spec.ts
packages/vue/__tests__/e2e/e2eUtils.ts
packages/vue/__tests__/e2e/transition.html

index 3a1a661d90b934fb78b2467e5794a1bbaa5f44f9..6c6344bfcacbc8dc84961131dbca8d49f813634f 100644 (file)
@@ -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) {
index 60274ad134cfccb1ae4f402a87744205323478c1..1315259f07578f63fa03a53c925ca68b06f7c3ad 100644 (file)
@@ -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: `
+          <div id="container">
+            <transition name="test-reflow">
+              <div v-if="toggle" class="test-reflow">content</div>
+            </transition>
+          </div>
+          <button id="toggleBtn" @click="click">button</button>
+        `,
+        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('<!--v-if-->')
+  })
+
   test('warn when used on multiple elements', async () => {
     createApp({
       render() {
index 8512dcaae474198681a085beebdfdf2d50f407b5..8a63e0a3530aaf3e98403e8e307be916b02d2e4e 100644 (file)
@@ -39,6 +39,7 @@ interface PuppeteerUtils {
   value(selector: string): Promise<string>
   html(selector: string): Promise<string>
   classList(selector: string): Promise<string[]>
+  style(selector: string, property: keyof CSSStyleDeclaration): Promise<any>
   children(selector: string): Promise<any[]>
   isVisible(selector: string): Promise<boolean>
   isChecked(selector: string): Promise<boolean>
@@ -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<any> {
+    return await page.$eval(
+      selector,
+      (node, property) => {
+        return window.getComputedStyle(node)[property]
+      },
+      property,
+    )
+  }
+
   async function isVisible(selector: string): Promise<boolean> {
     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,
index c44da2f78a1a08ad53566db71bc1bf0e965a6aef..ab404d67dc7286fbc05e20c7bf47c9a5bd5529da 100644 (file)
   .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;