mirror of https://github.com/vuejs/core.git
fix(transition): reflow before leave-active class after leave-from (#12288)
re-fix #2593
This commit is contained in:
parent
a20a4cb36a
commit
4b479db61d
|
@ -181,7 +181,13 @@ export function resolveTransitionProps(
|
||||||
onAppearCancelled = onEnterCancelled,
|
onAppearCancelled = onEnterCancelled,
|
||||||
} = baseProps
|
} = 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 ? appearToClass : enterToClass)
|
||||||
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
removeTransitionClass(el, isAppear ? appearActiveClass : enterActiveClass)
|
||||||
done && done()
|
done && done()
|
||||||
|
@ -240,7 +246,10 @@ export function resolveTransitionProps(
|
||||||
},
|
},
|
||||||
onEnter: makeEnterHook(false),
|
onEnter: makeEnterHook(false),
|
||||||
onAppear: makeEnterHook(true),
|
onAppear: makeEnterHook(true),
|
||||||
onLeave(el: Element & { _isLeaving?: boolean }, done) {
|
onLeave(
|
||||||
|
el: Element & { _isLeaving?: boolean; _enterCancelled?: boolean },
|
||||||
|
done,
|
||||||
|
) {
|
||||||
el._isLeaving = true
|
el._isLeaving = true
|
||||||
const resolve = () => finishLeave(el, done)
|
const resolve = () => finishLeave(el, done)
|
||||||
addTransitionClass(el, leaveFromClass)
|
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
|
// add *-leave-active class before reflow so in the case of a cancelled enter transition
|
||||||
// the css will not get the final state (#10677)
|
// the css will not get the final state (#10677)
|
||||||
addTransitionClass(el, leaveActiveClass)
|
if (!el._enterCancelled) {
|
||||||
// force reflow so *-leave-from classes immediately take effect (#2593)
|
// force reflow so *-leave-from classes immediately take effect (#2593)
|
||||||
forceReflow()
|
forceReflow()
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
} else {
|
||||||
|
addTransitionClass(el, leaveActiveClass)
|
||||||
|
forceReflow()
|
||||||
|
}
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
if (!el._isLeaving) {
|
if (!el._isLeaving) {
|
||||||
// cancelled
|
// cancelled
|
||||||
|
@ -269,11 +283,11 @@ export function resolveTransitionProps(
|
||||||
callHook(onLeave, [el, resolve])
|
callHook(onLeave, [el, resolve])
|
||||||
},
|
},
|
||||||
onEnterCancelled(el) {
|
onEnterCancelled(el) {
|
||||||
finishEnter(el, false)
|
finishEnter(el, false, undefined, true)
|
||||||
callHook(onEnterCancelled, [el])
|
callHook(onEnterCancelled, [el])
|
||||||
},
|
},
|
||||||
onAppearCancelled(el) {
|
onAppearCancelled(el) {
|
||||||
finishEnter(el, true)
|
finishEnter(el, true, undefined, true)
|
||||||
callHook(onAppearCancelled, [el])
|
callHook(onAppearCancelled, [el])
|
||||||
},
|
},
|
||||||
onLeaveCancelled(el) {
|
onLeaveCancelled(el) {
|
||||||
|
|
|
@ -3,7 +3,7 @@ import path from 'node:path'
|
||||||
import { Transition, createApp, h, nextTick, ref } from 'vue'
|
import { Transition, createApp, h, nextTick, ref } from 'vue'
|
||||||
|
|
||||||
describe('e2e: Transition', () => {
|
describe('e2e: Transition', () => {
|
||||||
const { page, html, classList, isVisible, timeout, nextFrame, click } =
|
const { page, html, classList, style, isVisible, timeout, nextFrame, click } =
|
||||||
setupPuppeteer()
|
setupPuppeteer()
|
||||||
const baseUrl = `file://${path.resolve(__dirname, './transition.html')}`
|
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 () => {
|
test('warn when used on multiple elements', async () => {
|
||||||
createApp({
|
createApp({
|
||||||
render() {
|
render() {
|
||||||
|
|
|
@ -39,6 +39,7 @@ interface PuppeteerUtils {
|
||||||
value(selector: string): Promise<string>
|
value(selector: string): Promise<string>
|
||||||
html(selector: string): Promise<string>
|
html(selector: string): Promise<string>
|
||||||
classList(selector: string): Promise<string[]>
|
classList(selector: string): Promise<string[]>
|
||||||
|
style(selector: string, property: keyof CSSStyleDeclaration): Promise<any>
|
||||||
children(selector: string): Promise<any[]>
|
children(selector: string): Promise<any[]>
|
||||||
isVisible(selector: string): Promise<boolean>
|
isVisible(selector: string): Promise<boolean>
|
||||||
isChecked(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])
|
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> {
|
async function isVisible(selector: string): Promise<boolean> {
|
||||||
const display = await page.$eval(selector, node => {
|
const display = await page.$eval(selector, node => {
|
||||||
return window.getComputedStyle(node).display
|
return window.getComputedStyle(node).display
|
||||||
|
@ -195,6 +209,7 @@ export function setupPuppeteer(args?: string[]): PuppeteerUtils {
|
||||||
value,
|
value,
|
||||||
html,
|
html,
|
||||||
classList,
|
classList,
|
||||||
|
style,
|
||||||
children,
|
children,
|
||||||
isVisible,
|
isVisible,
|
||||||
isChecked,
|
isChecked,
|
||||||
|
|
|
@ -16,11 +16,21 @@
|
||||||
.test-appear,
|
.test-appear,
|
||||||
.test-enter,
|
.test-enter,
|
||||||
.test-leave-active,
|
.test-leave-active,
|
||||||
|
.test-reflow-enter,
|
||||||
|
.test-reflow-leave-to,
|
||||||
.hello,
|
.hello,
|
||||||
.bye.active,
|
.bye.active,
|
||||||
.changed-enter {
|
.changed-enter {
|
||||||
opacity: 0;
|
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 {
|
.test-anim-enter-active {
|
||||||
animation: test-enter 50ms;
|
animation: test-enter 50ms;
|
||||||
-webkit-animation: test-enter 50ms;
|
-webkit-animation: test-enter 50ms;
|
||||||
|
|
Loading…
Reference in New Issue