fix(transition): fix higher order transition components with merged listeners
fix #3227
This commit is contained in:
parent
d6607c9864
commit
071986a2c6
@ -69,8 +69,8 @@ export interface TransitionHooks<
|
|||||||
delayedLeave?(): void
|
delayedLeave?(): void
|
||||||
}
|
}
|
||||||
|
|
||||||
type TransitionHookCaller = (
|
export type TransitionHookCaller = (
|
||||||
hook: ((el: any) => void) | undefined,
|
hook: ((el: any) => void) | Array<(el: any) => void> | undefined,
|
||||||
args?: any[]
|
args?: any[]
|
||||||
) => void
|
) => void
|
||||||
|
|
||||||
|
@ -7,7 +7,7 @@ import {
|
|||||||
compatUtils,
|
compatUtils,
|
||||||
DeprecationTypes
|
DeprecationTypes
|
||||||
} from '@vue/runtime-core'
|
} from '@vue/runtime-core'
|
||||||
import { isObject, toNumber, extend } from '@vue/shared'
|
import { isObject, toNumber, extend, isArray } from '@vue/shared'
|
||||||
|
|
||||||
const TRANSITION = 'transition'
|
const TRANSITION = 'transition'
|
||||||
const ANIMATION = 'animation'
|
const ANIMATION = 'animation'
|
||||||
@ -75,6 +75,35 @@ export const TransitionPropsValidators = (Transition.props = /*#__PURE__*/ exten
|
|||||||
DOMTransitionPropsValidators
|
DOMTransitionPropsValidators
|
||||||
))
|
))
|
||||||
|
|
||||||
|
/**
|
||||||
|
* #3227 Incoming hooks may be merged into arrays when wrapping Transition
|
||||||
|
* with custom HOCs.
|
||||||
|
*/
|
||||||
|
const callHook = (
|
||||||
|
hook: Function | Function[] | undefined,
|
||||||
|
args: any[] = []
|
||||||
|
) => {
|
||||||
|
if (isArray(hook)) {
|
||||||
|
hook.forEach(h => h(...args))
|
||||||
|
} else if (hook) {
|
||||||
|
hook(...args)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if a hook expects a callback (2nd arg), which means the user
|
||||||
|
* intends to explicitly control the end of the transition.
|
||||||
|
*/
|
||||||
|
const hasExplicitCallback = (
|
||||||
|
hook: Function | Function[] | undefined
|
||||||
|
): boolean => {
|
||||||
|
return hook
|
||||||
|
? isArray(hook)
|
||||||
|
? hook.some(h => h.length > 1)
|
||||||
|
: hook.length > 1
|
||||||
|
: false
|
||||||
|
}
|
||||||
|
|
||||||
export function resolveTransitionProps(
|
export function resolveTransitionProps(
|
||||||
rawProps: TransitionProps
|
rawProps: TransitionProps
|
||||||
): BaseTransitionProps<Element> {
|
): BaseTransitionProps<Element> {
|
||||||
@ -154,7 +183,7 @@ export function resolveTransitionProps(
|
|||||||
return (el: Element, done: () => void) => {
|
return (el: Element, done: () => void) => {
|
||||||
const hook = isAppear ? onAppear : onEnter
|
const hook = isAppear ? onAppear : onEnter
|
||||||
const resolve = () => finishEnter(el, isAppear, done)
|
const resolve = () => finishEnter(el, isAppear, done)
|
||||||
hook && hook(el, resolve)
|
callHook(hook, [el, resolve])
|
||||||
nextFrame(() => {
|
nextFrame(() => {
|
||||||
removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
|
removeTransitionClass(el, isAppear ? appearFromClass : enterFromClass)
|
||||||
if (__COMPAT__ && legacyClassEnabled) {
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
@ -164,7 +193,7 @@ export function resolveTransitionProps(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
addTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
addTransitionClass(el, isAppear ? appearToClass : enterToClass)
|
||||||
if (!(hook && hook.length > 1)) {
|
if (!hasExplicitCallback(hook)) {
|
||||||
whenTransitionEnds(el, type, enterDuration, resolve)
|
whenTransitionEnds(el, type, enterDuration, resolve)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -173,7 +202,7 @@ export function resolveTransitionProps(
|
|||||||
|
|
||||||
return extend(baseProps, {
|
return extend(baseProps, {
|
||||||
onBeforeEnter(el) {
|
onBeforeEnter(el) {
|
||||||
onBeforeEnter && onBeforeEnter(el)
|
callHook(onBeforeEnter, [el])
|
||||||
addTransitionClass(el, enterFromClass)
|
addTransitionClass(el, enterFromClass)
|
||||||
if (__COMPAT__ && legacyClassEnabled) {
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
addTransitionClass(el, legacyEnterFromClass)
|
addTransitionClass(el, legacyEnterFromClass)
|
||||||
@ -181,7 +210,7 @@ export function resolveTransitionProps(
|
|||||||
addTransitionClass(el, enterActiveClass)
|
addTransitionClass(el, enterActiveClass)
|
||||||
},
|
},
|
||||||
onBeforeAppear(el) {
|
onBeforeAppear(el) {
|
||||||
onBeforeAppear && onBeforeAppear(el)
|
callHook(onBeforeAppear, [el])
|
||||||
addTransitionClass(el, appearFromClass)
|
addTransitionClass(el, appearFromClass)
|
||||||
if (__COMPAT__ && legacyClassEnabled) {
|
if (__COMPAT__ && legacyClassEnabled) {
|
||||||
addTransitionClass(el, legacyAppearFromClass)
|
addTransitionClass(el, legacyAppearFromClass)
|
||||||
@ -205,23 +234,23 @@ export function resolveTransitionProps(
|
|||||||
removeTransitionClass(el, legacyLeaveFromClass)
|
removeTransitionClass(el, legacyLeaveFromClass)
|
||||||
}
|
}
|
||||||
addTransitionClass(el, leaveToClass)
|
addTransitionClass(el, leaveToClass)
|
||||||
if (!(onLeave && onLeave.length > 1)) {
|
if (!hasExplicitCallback(onLeave)) {
|
||||||
whenTransitionEnds(el, type, leaveDuration, resolve)
|
whenTransitionEnds(el, type, leaveDuration, resolve)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
onLeave && onLeave(el, resolve)
|
callHook(onLeave, [el, resolve])
|
||||||
},
|
},
|
||||||
onEnterCancelled(el) {
|
onEnterCancelled(el) {
|
||||||
finishEnter(el, false)
|
finishEnter(el, false)
|
||||||
onEnterCancelled && onEnterCancelled(el)
|
callHook(onEnterCancelled, [el])
|
||||||
},
|
},
|
||||||
onAppearCancelled(el) {
|
onAppearCancelled(el) {
|
||||||
finishEnter(el, true)
|
finishEnter(el, true)
|
||||||
onAppearCancelled && onAppearCancelled(el)
|
callHook(onAppearCancelled, [el])
|
||||||
},
|
},
|
||||||
onLeaveCancelled(el) {
|
onLeaveCancelled(el) {
|
||||||
finishLeave(el)
|
finishLeave(el)
|
||||||
onLeaveCancelled && onLeaveCancelled(el)
|
callHook(onLeaveCancelled, [el])
|
||||||
}
|
}
|
||||||
} as BaseTransitionProps<Element>)
|
} as BaseTransitionProps<Element>)
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
|
import { E2E_TIMEOUT, setupPuppeteer } from './e2eUtils'
|
||||||
import path from 'path'
|
import path from 'path'
|
||||||
import { h, createApp, Transition } from 'vue'
|
import { h, createApp, Transition, ref, nextTick } from 'vue'
|
||||||
|
|
||||||
describe('e2e: Transition', () => {
|
describe('e2e: Transition', () => {
|
||||||
const {
|
const {
|
||||||
@ -1634,23 +1634,6 @@ describe('e2e: Transition', () => {
|
|||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
test(
|
|
||||||
'warn when used on multiple elements',
|
|
||||||
async () => {
|
|
||||||
createApp({
|
|
||||||
render() {
|
|
||||||
return h(Transition, null, {
|
|
||||||
default: () => [h('div'), h('div')]
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}).mount(document.createElement('div'))
|
|
||||||
expect(
|
|
||||||
'<transition> can only be used on a single element or component'
|
|
||||||
).toHaveBeenWarned()
|
|
||||||
},
|
|
||||||
E2E_TIMEOUT
|
|
||||||
)
|
|
||||||
|
|
||||||
describe('explicit durations', () => {
|
describe('explicit durations', () => {
|
||||||
test(
|
test(
|
||||||
'single value',
|
'single value',
|
||||||
@ -1916,4 +1899,59 @@ describe('e2e: Transition', () => {
|
|||||||
E2E_TIMEOUT
|
E2E_TIMEOUT
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('warn when used on multiple elements', async () => {
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(Transition, null, {
|
||||||
|
default: () => [h('div'), h('div')]
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}).mount(document.createElement('div'))
|
||||||
|
expect(
|
||||||
|
'<transition> can only be used on a single element or component'
|
||||||
|
).toHaveBeenWarned()
|
||||||
|
})
|
||||||
|
|
||||||
|
// #3227
|
||||||
|
test(`HOC w/ merged hooks`, async () => {
|
||||||
|
const innerSpy = jest.fn()
|
||||||
|
const outerSpy = jest.fn()
|
||||||
|
|
||||||
|
const MyTransition = {
|
||||||
|
render(this: any) {
|
||||||
|
return h(
|
||||||
|
Transition,
|
||||||
|
{
|
||||||
|
onLeave(el, end) {
|
||||||
|
innerSpy()
|
||||||
|
end()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
this.$slots.default
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggle = ref(true)
|
||||||
|
|
||||||
|
const root = document.createElement('div')
|
||||||
|
createApp({
|
||||||
|
render() {
|
||||||
|
return h(
|
||||||
|
MyTransition,
|
||||||
|
{ onLeave: () => outerSpy() },
|
||||||
|
() => (toggle.value ? h('div') : null)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}).mount(root)
|
||||||
|
|
||||||
|
expect(root.innerHTML).toBe(`<div></div>`)
|
||||||
|
|
||||||
|
toggle.value = false
|
||||||
|
await nextTick()
|
||||||
|
expect(innerSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(outerSpy).toHaveBeenCalledTimes(1)
|
||||||
|
expect(root.innerHTML).toBe(`<!---->`)
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
Loading…
Reference in New Issue
Block a user