Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(transition): reflow before leave-active class after leave-from (#2593), while fixing (#10688) #12288

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 21 additions & 7 deletions packages/runtime-dom/src/components/Transition.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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)
Expand All @@ -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
Expand All @@ -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) {
Expand Down
51 changes: 50 additions & 1 deletion packages/vue/__tests__/e2e/Transition.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')}`

Expand Down Expand Up @@ -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() {
Expand Down
15 changes: 15 additions & 0 deletions packages/vue/__tests__/e2e/e2eUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -195,6 +209,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
value,
html,
classList,
style,
children,
isVisible,
isChecked,
Expand Down
10 changes: 10 additions & 0 deletions packages/vue/__tests__/e2e/transition.html
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down