diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index 46924117b1d..49ed2d4d5f9 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -27,6 +27,14 @@ export interface TransitionProps extends BaseTransitionProps { leaveToClass?: string } +export interface ElementWithTransition extends HTMLElement { + // _vtc = Vue Transition Classes. + // Store the temporarily-added transition classes on the element + // so that we can avoid overwriting them if the element's class is patched + // during the transition. + _vtc?: Set +} + // DOM Transition is a higher-order-component based on the platform-agnostic // base Transition component, with DOM-specific logic. export const Transition: FunctionalComponent = ( @@ -123,7 +131,6 @@ export function resolveTransitionProps( const resolve = () => finishEnter(el, isAppear, done) hook && hook(el, resolve) nextFrame(() => { - enableTransition(el) removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass) addTransitionClass(el, isAppear ? appearToClass : enterToClass) if (!(hook && hook.length > 1)) { @@ -133,30 +140,16 @@ export function resolveTransitionProps( } } - // see issue #2531, #2593 - let cacheTransition: string - const disableTransition = (el: Element) => { - const style = (el as HTMLElement).style - cacheTransition = style.transition - style.transitionProperty = 'none' - } - - const enableTransition = (el: Element) => { - ;(el as HTMLElement).style.transition = cacheTransition - } - return extend(baseProps, { onBeforeEnter(el) { onBeforeEnter && onBeforeEnter(el) addTransitionClass(el, enterActiveClass) addTransitionClass(el, enterFromClass) - disableTransition(el) }, onBeforeAppear(el) { onBeforeAppear && onBeforeAppear(el) addTransitionClass(el, appearActiveClass) addTransitionClass(el, appearFromClass) - disableTransition(el) }, onEnter: makeEnterHook(false), onAppear: makeEnterHook(true), @@ -164,9 +157,15 @@ export function resolveTransitionProps( const resolve = () => finishLeave(el, done) addTransitionClass(el, leaveActiveClass) addTransitionClass(el, leaveFromClass) - disableTransition(el) + // ref #2531, #2593 + // disabling the transition before nextFrame ensures styles from + // *-leave-from and *-enter-from classes are applied instantly before + // the transition starts. This is applied for enter transition as well + // so that it accounts for `visibility: hidden` cases. + const cachedTransition = (el as HTMLElement).style.transitionProperty + ;(el as HTMLElement).style.transitionProperty = 'none' nextFrame(() => { - enableTransition(el) + ;(el as HTMLElement).style.transitionProperty = cachedTransition removeTransitionClass(el, leaveFromClass) addTransitionClass(el, leaveToClass) if (!(onLeave && onLeave.length > 1)) { @@ -223,14 +222,6 @@ function validateDuration(val: unknown) { } } -export interface ElementWithTransition extends HTMLElement { - // _vtc = Vue Transition Classes. - // Store the temporarily-added transition classes on the element - // so that we can avoid overwriting them if the element's class is patched - // during the transition. - _vtc?: Set -} - export function addTransitionClass(el: Element, cls: string) { cls.split(/\s+/).forEach(c => c && el.classList.add(c)) ;( diff --git a/packages/vue/__tests__/Transition.spec.ts b/packages/vue/__tests__/Transition.spec.ts index 2d3efcc595e..ee9b8cf6515 100644 --- a/packages/vue/__tests__/Transition.spec.ts +++ b/packages/vue/__tests__/Transition.spec.ts @@ -84,9 +84,7 @@ describe('e2e: Transition', () => { 'v-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -142,9 +140,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -205,9 +201,7 @@ describe('e2e: Transition', () => { 'hello-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -269,9 +263,7 @@ describe('e2e: Transition', () => { 'changed-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -375,9 +367,7 @@ describe('e2e: Transition', () => { ]) expect(afterEnterSpy).not.toBeCalled() await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') expect(afterEnterSpy).toBeCalled() }, E2E_TIMEOUT @@ -486,9 +476,7 @@ describe('e2e: Transition', () => { 'test-appear-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') // leave expect(await classWhenTransitionStart()).toStrictEqual([ @@ -518,9 +506,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -623,9 +609,7 @@ describe('e2e: Transition', () => { ]) expect(afterAppearSpy).not.toBeCalled() await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') expect(afterAppearSpy).toBeCalled() expect(beforeEnterSpy).not.toBeCalled() @@ -669,9 +653,7 @@ describe('e2e: Transition', () => { ]) expect(afterEnterSpy).not.toBeCalled() await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') expect(afterEnterSpy).toBeCalled() }, E2E_TIMEOUT @@ -792,9 +774,7 @@ describe('e2e: Transition', () => { 'noop-enter-from' ]) await nextFrame() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -846,9 +826,7 @@ describe('e2e: Transition', () => { 'test-anim-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -910,9 +888,7 @@ describe('e2e: Transition', () => { 'test-anim-long-enter-to' ]) await transitionFinish(duration * 2) - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -982,7 +958,7 @@ describe('e2e: Transition', () => { ]) await transitionFinish() expect(await html('#container')).toBe( - '' + '' ) }, E2E_TIMEOUT @@ -1040,9 +1016,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1099,9 +1073,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
two
' - ) + expect(await html('#container')).toBe('
two
') // change view -> 'one' await page().evaluate(() => { @@ -1169,12 +1141,10 @@ describe('e2e: Transition', () => { expect(onEnterSpy).toBeCalledTimes(1) await nextFrame() expect(await html('#container')).toBe( - '
content
' + '
content
' ) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') // leave expect(await classWhenTransitionStart()).toStrictEqual([ @@ -1218,9 +1188,7 @@ describe('e2e: Transition', () => { 'v-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1279,9 +1247,7 @@ describe('e2e: Transition', () => { 'v-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1330,12 +1296,10 @@ describe('e2e: Transition', () => { await nextFrame() expect(await html('#container')).toBe( - '
one
' + '
one
' ) await transitionFinish() - expect(await html('#container')).toBe( - '
one
' - ) + expect(await html('#container')).toBe('
one
') // leave await classWhenTransitionStart() @@ -1346,12 +1310,10 @@ describe('e2e: Transition', () => { await transitionFinish() await nextFrame() expect(await html('#container')).toBe( - '
two
' + '
two
' ) await transitionFinish() - expect(await html('#container')).toBe( - '
two
' - ) + expect(await html('#container')).toBe('
two
') }, E2E_TIMEOUT ) @@ -1626,9 +1588,7 @@ describe('e2e: Transition', () => { 'test-appear-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') // leave expect(await classWhenTransitionStart()).toStrictEqual([ @@ -1735,9 +1695,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish(duration * 2) - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1793,9 +1751,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish(duration * 2) - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1851,9 +1807,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish() - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) @@ -1912,9 +1866,7 @@ describe('e2e: Transition', () => { 'test-enter-to' ]) await transitionFinish(200) - expect(await html('#container')).toBe( - '
content
' - ) + expect(await html('#container')).toBe('
content
') }, E2E_TIMEOUT ) diff --git a/packages/vue/__tests__/TransitionGroup.spec.ts b/packages/vue/__tests__/TransitionGroup.spec.ts index 9c82d6700c2..c3d2aa28ec4 100644 --- a/packages/vue/__tests__/TransitionGroup.spec.ts +++ b/packages/vue/__tests__/TransitionGroup.spec.ts @@ -8,6 +8,7 @@ describe('e2e: TransitionGroup', () => { const duration = 50 const buffer = 5 + const transitionDisableProp = `style="transition-property: none;"` const htmlWhenTransitionStart = () => page().evaluate(() => { @@ -55,24 +56,24 @@ describe('e2e: TransitionGroup', () => { `
a
` + `
b
` + `
c
` + - `
d
` + - `
e
` + `
d
` + + `
e
` ) await nextFrame() expect(await html('#container')).toBe( `
a
` + `
b
` + `
c
` + - `
d
` + - `
e
` + `
d
` + + `
e
` ) await transitionFinish() expect(await html('#container')).toBe( `
a
` + `
b
` + `
c
` + - `
d
` + - `
e
` + `
d
` + + `
e
` ) }, E2E_TIMEOUT @@ -106,9 +107,9 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
a
` + + `
a
` + `
b
` + - `
c
` + `
c
` ) await nextFrame() expect(await html('#container')).toBe( @@ -150,23 +151,23 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
a
` + + `
a
` + `
b
` + `
c
` + - `
d
` + `
d
` ) await nextFrame() expect(await html('#container')).toBe( `
a
` + `
b
` + `
c
` + - `
d
` + `
d
` ) await transitionFinish() expect(await html('#container')).toBe( `
b
` + `
c
` + - `
d
` + `
d
` ) }, E2E_TIMEOUT @@ -202,46 +203,46 @@ describe('e2e: TransitionGroup', () => { }) // appear expect(appearHtml).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) await nextFrame() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) await transitionFinish() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) // enter expect(await htmlWhenTransitionStart()).toBe( - `
a
` + - `
b
` + - `
c
` + - `
d
` + - `
e
` + `
a
` + + `
b
` + + `
c
` + + `
d
` + + `
e
` ) await nextFrame() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + - `
d
` + - `
e
` + `
a
` + + `
b
` + + `
c
` + + `
d
` + + `
e
` ) await transitionFinish() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + - `
d
` + - `
e
` + `
a
` + + `
b
` + + `
c
` + + `
d
` + + `
e
` ) }, E2E_TIMEOUT @@ -275,21 +276,21 @@ describe('e2e: TransitionGroup', () => { ) expect(await htmlWhenTransitionStart()).toBe( - `
d
` + + `
d
` + `
b
` + `
a
` + - `
c
` + `
c
` ) await nextFrame() expect(await html('#container')).toBe( - `
d
` + + `
d
` + `
b
` + `
a
` + `
c
` ) await transitionFinish(duration * 2) expect(await html('#container')).toBe( - `
d
` + + `
d
` + `
b
` + `
a
` ) @@ -440,31 +441,31 @@ 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() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) await transitionFinish() expect(afterAppearSpy).toBeCalled() expect(await html('#container')).toBe( - `
a
` + - `
b
` + - `
c
` + `
a
` + + `
b
` + + `
c
` ) // enter + leave expect(await htmlWhenTransitionStart()).toBe( - `
a
` + - `
b
` + - `
c
` + - `
d
` + `
a
` + + `
b
` + + `
c
` + + `
d
` ) expect(beforeLeaveSpy).toBeCalled() expect(onLeaveSpy).toBeCalled() @@ -475,17 +476,17 @@ describe('e2e: TransitionGroup', () => { await nextFrame() expect(await html('#container')).toBe( `
a
` + - `
b
` + - `
c
` + - `
d
` + `
b
` + + `
c
` + + `
d
` ) expect(afterLeaveSpy).not.toBeCalled() expect(afterEnterSpy).not.toBeCalled() await transitionFinish() expect(await html('#container')).toBe( - `
b
` + - `
c
` + - `
d
` + `
b
` + + `
c
` + + `
d
` ) expect(afterLeaveSpy).toBeCalled() expect(afterEnterSpy).toBeCalled()