From cbaa3805064cb581fc2007cf63774c91d39844fe Mon Sep 17 00:00:00 2001 From: Evan You Date: Thu, 3 Dec 2020 16:10:22 -0500 Subject: [PATCH] fix(transition): ensure manual style manipulation in transition leave hooks work ref #2720 --- .../runtime-dom/src/components/Transition.ts | 25 +-- .../src/components/TransitionGroup.ts | 8 +- packages/vue/__tests__/Transition.spec.ts | 206 +++++++++--------- .../vue/__tests__/TransitionGroup.spec.ts | 44 ++-- 4 files changed, 137 insertions(+), 146 deletions(-) diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 217f4af9de7..2c0248825e5 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -143,33 +143,23 @@ export function resolveTransitionProps( return extend(baseProps, { onBeforeEnter(el) { onBeforeEnter && onBeforeEnter(el) - addTransitionClass(el, enterActiveClass) addTransitionClass(el, enterFromClass) + addTransitionClass(el, enterActiveClass) }, onBeforeAppear(el) { onBeforeAppear && onBeforeAppear(el) - addTransitionClass(el, appearActiveClass) addTransitionClass(el, appearFromClass) + addTransitionClass(el, appearActiveClass) }, onEnter: makeEnterHook(false), onAppear: makeEnterHook(true), onLeave(el, done) { const resolve = () => finishLeave(el, done) - addTransitionClass(el, leaveActiveClass) addTransitionClass(el, leaveFromClass) - - const cachedTransition = (el as HTMLElement).style.transitionProperty - requestAnimationFrame(() => { - // ref #2531, #2593 - // disabling the transition before nextFrame ensures styles from - // *-leave-from classes are applied instantly before the transition starts. - // ref #2712 - // do this in an rAF to ensure styles from *-leave-active can trigger - // transition on the first frame when el has `transition` property itself. - ;(el as HTMLElement).style.transitionProperty = 'none' - }) + // force reflow so *-leave-from classes immediately take effect (#2593) + forceReflow() + addTransitionClass(el, leaveActiveClass) nextFrame(() => { - ;(el as HTMLElement).style.transitionProperty = cachedTransition removeTransitionClass(el, leaveFromClass) addTransitionClass(el, leaveToClass) if (!(onLeave && onLeave.length > 1)) { @@ -370,3 +360,8 @@ function getTimeout(delays: string[], durations: string[]): number { function toMs(s: string): number { return Number(s.slice(0, -1).replace(',', '.')) * 1000 } + +// synchronously force layout to put elements into a certain state +export function forceReflow() { + return document.body.offsetHeight +} diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts index 4c3fcc2d5d3..16a6aa39ee1 100644 --- a/packages/runtime-dom/src/components/TransitionGroup.ts +++ b/packages/runtime-dom/src/components/TransitionGroup.ts @@ -5,7 +5,8 @@ import { ElementWithTransition, getTransitionInfo, resolveTransitionProps, - TransitionPropsValidators + TransitionPropsValidators, + forceReflow } from './Transition' import { Fragment, @@ -172,11 +173,6 @@ function applyTranslation(c: VNode): VNode | undefined { } } -// this is put in a dedicated function to avoid the line from being treeshaken -function forceReflow() { - return document.body.offsetHeight -} - function hasCSSTransform( el: ElementWithTransition, root: Node, diff --git a/packages/vue/__tests__/Transition.spec.ts b/packages/vue/__tests__/Transition.spec.ts index ee9b8cf6515..94f973aee36 100644 --- a/packages/vue/__tests__/Transition.spec.ts +++ b/packages/vue/__tests__/Transition.spec.ts @@ -59,8 +59,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'v-leave-active', - 'v-leave-from' + 'v-leave-from', + 'v-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -74,8 +74,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'v-enter-active', - 'v-enter-from' + 'v-enter-from', + 'v-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -115,8 +115,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -130,8 +130,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -176,8 +176,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'bye-active', - 'bye-from' + 'bye-from', + 'bye-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -191,8 +191,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'hello-active', - 'hello-from' + 'hello-from', + 'hello-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -235,8 +235,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -253,8 +253,8 @@ describe('e2e: Transition', () => { }) expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'changed-enter-active', - 'changed-enter-from' + 'changed-enter-from', + 'changed-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -332,8 +332,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) // todo test event with arguments. Note: not get dom, get object. '{}' expect(beforeLeaveSpy).toBeCalled() @@ -353,8 +353,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) expect(beforeEnterSpy).toBeCalled() expect(onEnterSpy).toBeCalled() @@ -408,8 +408,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -421,8 +421,8 @@ describe('e2e: Transition', () => { // cancel (leave) expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) expect(enterCancelledSpy).toBeCalled() await nextFrame() @@ -466,8 +466,8 @@ describe('e2e: Transition', () => { // appear expect(appearClass).toStrictEqual([ 'test', - 'test-appear-active', - 'test-appear-from' + 'test-appear-from', + 'test-appear-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -481,8 +481,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -496,8 +496,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -595,8 +595,8 @@ describe('e2e: Transition', () => { // appear expect(appearClass).toStrictEqual([ 'test', - 'test-appear-active', - 'test-appear-from' + 'test-appear-from', + 'test-appear-active' ]) expect(beforeAppearSpy).toBeCalled() expect(onAppearSpy).toBeCalled() @@ -619,8 +619,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) expect(beforeLeaveSpy).toBeCalled() expect(onLeaveSpy).toBeCalled() @@ -639,8 +639,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) expect(beforeEnterSpy).toBeCalled() expect(onEnterSpy).toBeCalled() @@ -762,16 +762,16 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ - 'noop-leave-active', - 'noop-leave-from' + 'noop-leave-from', + 'noop-leave-active' ]) await nextFrame() expect(await html('#container')).toBe('') // enter expect(await classWhenTransitionStart()).toStrictEqual([ - 'noop-enter-active', - 'noop-enter-from' + 'noop-enter-from', + 'noop-enter-active' ]) await nextFrame() expect(await html('#container')).toBe('
content
') @@ -804,8 +804,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ - 'test-anim-leave-active', - 'test-anim-leave-from' + 'test-anim-leave-from', + 'test-anim-leave-active' ]) await nextFrame() expect(await classList('#container div')).toStrictEqual([ @@ -817,8 +817,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ - 'test-anim-enter-active', - 'test-anim-enter-from' + 'test-anim-enter-from', + 'test-anim-enter-active' ]) await nextFrame() expect(await classList('#container div')).toStrictEqual([ @@ -852,8 +852,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ - 'test-anim-long-leave-active', - 'test-anim-long-leave-from' + 'test-anim-long-leave-from', + 'test-anim-long-leave-active' ]) await nextFrame() expect(await classList('#container div')).toStrictEqual([ @@ -872,8 +872,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ - 'test-anim-long-enter-active', - 'test-anim-long-enter-from' + 'test-anim-long-enter-from', + 'test-anim-long-enter-active' ]) await nextFrame() expect(await classList('#container div')).toStrictEqual([ @@ -932,8 +932,8 @@ describe('e2e: Transition', () => { // leave expect(await svgTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -947,8 +947,8 @@ describe('e2e: Transition', () => { // enter expect(await svgTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -991,8 +991,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1006,8 +1006,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1063,8 +1063,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1082,8 +1082,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1149,8 +1149,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'v-leave-active', - 'v-leave-from' + 'v-leave-from', + 'v-leave-active' ]) expect(onLeaveSpy).toBeCalledTimes(1) await nextFrame() @@ -1177,8 +1177,8 @@ describe('e2e: Transition', () => { }) expect(enterClass).toStrictEqual([ 'test', - 'v-enter-active', - 'v-enter-from' + 'v-enter-from', + 'v-enter-active' ]) expect(onEnterSpy).toBeCalledTimes(2) await nextFrame() @@ -1222,8 +1222,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'v-leave-active', - 'v-leave-from' + 'v-leave-from', + 'v-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1237,8 +1237,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'v-enter-active', - 'v-enter-from' + 'v-enter-from', + 'v-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1305,7 +1305,7 @@ describe('e2e: Transition', () => { await classWhenTransitionStart() await nextFrame() expect(await html('#container')).toBe( - '
one
' + '
one
' ) await transitionFinish() await nextFrame() @@ -1347,8 +1347,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1362,8 +1362,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1443,8 +1443,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) expect(beforeLeaveSpy).toBeCalled() expect(onLeaveSpy).toBeCalled() @@ -1463,8 +1463,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) expect(beforeEnterSpy).toBeCalled() expect(onEnterSpy).toBeCalled() @@ -1516,8 +1516,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1529,8 +1529,8 @@ describe('e2e: Transition', () => { // cancel (enter) expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) expect(onLeaveCancelledSpy).toBeCalled() await nextFrame() @@ -1578,8 +1578,8 @@ describe('e2e: Transition', () => { // appear expect(appearClass).toStrictEqual([ 'test', - 'test-appear-active', - 'test-appear-from' + 'test-appear-from', + 'test-appear-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1593,8 +1593,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1608,8 +1608,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1670,8 +1670,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1685,8 +1685,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1726,8 +1726,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1741,8 +1741,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1782,8 +1782,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1797,8 +1797,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1841,8 +1841,8 @@ describe('e2e: Transition', () => { // leave expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-leave-active', - 'test-leave-from' + 'test-leave-from', + 'test-leave-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ @@ -1856,8 +1856,8 @@ describe('e2e: Transition', () => { // enter expect(await classWhenTransitionStart()).toStrictEqual([ 'test', - 'test-enter-active', - 'test-enter-from' + 'test-enter-from', + 'test-enter-active' ]) await nextFrame() expect(await classList('.test')).toStrictEqual([ diff --git a/packages/vue/__tests__/TransitionGroup.spec.ts b/packages/vue/__tests__/TransitionGroup.spec.ts index 355ad61fe7a..512a93bad57 100644 --- a/packages/vue/__tests__/TransitionGroup.spec.ts +++ b/packages/vue/__tests__/TransitionGroup.spec.ts @@ -55,8 +55,8 @@ describe('e2e: TransitionGroup', () => { `
a
` + `
b
` + `
c
` + - `
d
` + - `
e
` + `
d
` + + `
e
` ) await nextFrame() expect(await html('#container')).toBe( @@ -106,15 +106,15 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
a
` + + `
a
` + `
b
` + - `
c
` + `
c
` ) await nextFrame() expect(await html('#container')).toBe( - `
a
` + + `
a
` + `
b
` + - `
c
` + `
c
` ) await transitionFinish() expect(await html('#container')).toBe(`
b
`) @@ -150,14 +150,14 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
a
` + + `
a
` + `
b
` + `
c
` + - `
d
` + `
d
` ) await nextFrame() expect(await html('#container')).toBe( - `
a
` + + `
a
` + `
b
` + `
c
` + `
d
` @@ -202,9 +202,9 @@ describe('e2e: TransitionGroup', () => { }) // appear expect(appearHtml).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) await nextFrame() expect(await html('#container')).toBe( @@ -224,8 +224,8 @@ describe('e2e: TransitionGroup', () => { `
a
` + `
b
` + `
c
` + - `
d
` + - `
e
` + `
d
` + + `
e
` ) await nextFrame() expect(await html('#container')).toBe( @@ -275,10 +275,10 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
d
` + + `
d
` + `
b
` + `
a
` + - `
c
` + `
c
` ) await nextFrame() expect(await html('#container')).toBe( @@ -440,9 +440,9 @@ describe('e2e: TransitionGroup', () => { expect(onAppearSpy).toBeCalled() expect(afterAppearSpy).not.toBeCalled() expect(appearHtml).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) await nextFrame() expect(afterAppearSpy).not.toBeCalled() @@ -461,10 +461,10 @@ describe('e2e: TransitionGroup', () => { // enter + leave expect(await htmlWhenTransitionStart()).toBe( - `
a
` + + `
a
` + `
b
` + `
c
` + - `
d
` + `
d
` ) expect(beforeLeaveSpy).toBeCalled() expect(onLeaveSpy).toBeCalled() @@ -474,7 +474,7 @@ describe('e2e: TransitionGroup', () => { expect(afterEnterSpy).not.toBeCalled() await nextFrame() expect(await html('#container')).toBe( - `
a
` + + `
a
` + `
b
` + `
c
` + `
d
`