diff --git a/packages/runtime-core/src/components/BaseTransition.ts b/packages/runtime-core/src/components/BaseTransition.ts index 865432ab109..84ac530a24c 100644 --- a/packages/runtime-core/src/components/BaseTransition.ts +++ b/packages/runtime-core/src/components/BaseTransition.ts @@ -13,7 +13,6 @@ import { import { warn } from '../warning' import { isKeepAlive } from './KeepAlive' import { toRaw } from '@vue/reactivity' -import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling' import { ShapeFlags } from '@vue/shared' import { onBeforeUnmount, onMounted } from '../apiLifecycle' import { RendererElement } from '../renderer' @@ -32,17 +31,30 @@ export interface BaseTransitionProps { // Hooks. Using camel case for easier usage in render functions & JSX. // In templates these can be written as @before-enter="xxx" as prop names // are camelized. - onBeforeEnter?: (el: HostElement) => void - onEnter?: (el: HostElement, done: () => void) => void - onAfterEnter?: (el: HostElement) => void - onEnterCancelled?: (el: HostElement) => void + onBeforeEnter?: TransitionOtherHook + onEnter?: TransitionActiveHook + onAfterEnter?: TransitionOtherHook + onEnterCancelled?: TransitionOtherHook // leave - onBeforeLeave?: (el: HostElement) => void - onLeave?: (el: HostElement, done: () => void) => void - onAfterLeave?: (el: HostElement) => void - onLeaveCancelled?: (el: HostElement) => void // only fired in persisted mode + onBeforeLeave?: TransitionOtherHook + onLeave?: TransitionActiveHook + onAfterLeave?: TransitionOtherHook + onLeaveCancelled?: TransitionOtherHook // only fired in persisted mode + // appear + onBeforeAppear?: TransitionOtherHook + onAppear?: TransitionActiveHook + onAfterAppear?: TransitionOtherHook + onAppearCancelled?: TransitionOtherHook } +export type TransitionActiveHook = ( + el: HostElement, + done: () => void +) => void +export type TransitionOtherHook = ( + el: HostElement +) => void + export interface TransitionHooks { persisted: boolean beforeEnter(el: RendererElement): void @@ -57,11 +69,6 @@ export interface TransitionHooks { delayedLeave?(): void } -type TransitionHookCaller = ( - hook: ((el: any) => void) | undefined, - args?: any[] -) => void - export type PendingCallback = (cancelled?: boolean) => void export interface TransitionState { @@ -113,7 +120,12 @@ const BaseTransitionImpl = { onBeforeLeave: Function, onLeave: Function, onAfterLeave: Function, - onLeaveCancelled: Function + onLeaveCancelled: Function, + // appear + onBeforeAppear: Function, + onAppear: Function, + onAfterAppear: Function, + onAppearCancelled: Function }, setup(props: BaseTransitionProps, { slots }: SetupContext) { @@ -260,16 +272,6 @@ export function resolveTransitionHooks( const key = String(vnode.key) const leavingVNodesCache = getLeavingNodesForType(state, vnode) - const callHook: TransitionHookCaller = (hook, args) => { - hook && - callWithAsyncErrorHandling( - hook, - instance, - ErrorCodes.TRANSITION_HOOK, - args - ) - } - const hooks: TransitionHooks = { persisted, beforeEnter(el: TransitionElement) { @@ -290,7 +292,7 @@ export function resolveTransitionHooks( // force early removal (not cancelled) leavingVNode.el!._leaveCb() } - callHook(onBeforeEnter, [el]) + onBeforeEnter && onBeforeEnter(el) }, enter(el: TransitionElement) { @@ -302,9 +304,9 @@ export function resolveTransitionHooks( if (called) return called = true if (cancelled) { - callHook(onEnterCancelled, [el]) + onEnterCancelled && onEnterCancelled(el) } else { - callHook(onAfterEnter, [el]) + onAfterEnter && onAfterEnter(el) } if (hooks.delayedLeave) { hooks.delayedLeave() @@ -326,16 +328,16 @@ export function resolveTransitionHooks( if (state.isUnmounting) { return remove() } - callHook(onBeforeLeave, [el]) + onBeforeLeave && onBeforeLeave(el) let called = false const afterLeave = (el._leaveCb = (cancelled?) => { if (called) return called = true remove() if (cancelled) { - callHook(onLeaveCancelled, [el]) + onLeaveCancelled && onLeaveCancelled(el) } else { - callHook(onAfterLeave, [el]) + onAfterLeave && onAfterLeave(el) } el._leaveCb = undefined if (leavingVNodesCache[key] === vnode) { diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 5f8055520e8..0cc93da09ed 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -196,7 +196,12 @@ export { DirectiveArguments } from './directives' export { SuspenseBoundary } from './components/Suspense' -export { TransitionState, TransitionHooks } from './components/BaseTransition' +export { + TransitionState, + TransitionHooks, + TransitionActiveHook, + TransitionOtherHook +} from './components/BaseTransition' export { AsyncComponentOptions, AsyncComponentLoader diff --git a/packages/runtime-dom/src/components/Transition.ts b/packages/runtime-dom/src/components/Transition.ts index c2d52dc0201..68c9ab5abb5 100644 --- a/packages/runtime-dom/src/components/Transition.ts +++ b/packages/runtime-dom/src/components/Transition.ts @@ -5,7 +5,10 @@ import { warn, FunctionalComponent, getCurrentInstance, - callWithAsyncErrorHandling + callWithAsyncErrorHandling, + RendererElement, + TransitionActiveHook, + TransitionOtherHook } from '@vue/runtime-core' import { isObject } from '@vue/shared' import { ErrorCodes } from 'packages/runtime-core/src/errorHandling' @@ -30,6 +33,11 @@ export interface TransitionProps extends BaseTransitionProps { leaveToClass?: string } +export type TransitionHookCaller = ( + hook?: TransitionOtherHook | TransitionActiveHook, + args?: any[] +) => void + // DOM Transition is a higher-order-component based on the platform-agnostic // base Transition component, with DOM-specific logic. export const Transition: FunctionalComponent = ( @@ -71,24 +79,50 @@ export function resolveTransitionProps({ leaveFromClass = `${name}-leave-from`, leaveActiveClass = `${name}-leave-active`, leaveToClass = `${name}-leave-to`, + appear, + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled, + onBeforeLeave, + onLeave, + onAfterLeave, + onLeaveCancelled, + onBeforeAppear = onBeforeEnter, + onAppear = onEnter, + onAfterAppear = onAfterEnter, + onAppearCancelled = onEnterCancelled, ...baseProps }: TransitionProps): BaseTransitionProps { if (!css) { return baseProps } - const originEnterClass = [enterFromClass, enterActiveClass, enterToClass] const instance = getCurrentInstance()! const durations = normalizeDuration(duration) const enterDuration = durations && durations[0] const leaveDuration = durations && durations[1] - const { appear, onBeforeEnter, onEnter, onLeave } = baseProps // is appearing + const originEnterClass = [enterFromClass, enterActiveClass, enterToClass] + const originEnterHook: any[] = [ + onBeforeEnter, + onEnter, + onAfterEnter, + onEnterCancelled + ] if (appear && !instance.isMounted) { - enterFromClass = appearFromClass - enterActiveClass = appearActiveClass - enterToClass = appearToClass + ;[enterFromClass, enterActiveClass, enterToClass] = [ + appearFromClass, + appearActiveClass, + appearToClass + ] + ;[onBeforeEnter, onEnter, onBeforeEnter, onEnterCancelled] = [ + onBeforeAppear, + onAppear, + onAfterAppear, + onAppearCancelled + ] } type Hook = (el: Element, done?: () => void) => void @@ -97,9 +131,15 @@ export function resolveTransitionProps({ removeTransitionClass(el, enterToClass) removeTransitionClass(el, enterActiveClass) done && done() - // reset enter class + // reset enter class and hooks if (appear) { ;[enterFromClass, enterActiveClass, enterToClass] = originEnterClass + ;[ + onBeforeEnter, + onEnter, + onBeforeEnter, + onEnterCancelled + ] = originEnterHook } } @@ -109,23 +149,26 @@ export function resolveTransitionProps({ done && done() } - // only needed for user hooks called in nextFrame - // sync errors are already handled by BaseTransition - function callHookWithErrorHandling(hook: Hook, args: any[]) { - callWithAsyncErrorHandling(hook, instance, ErrorCodes.TRANSITION_HOOK, args) + const callHook: TransitionHookCaller = (hook, args) => { + hook && + callWithAsyncErrorHandling( + hook, + instance, + ErrorCodes.TRANSITION_HOOK, + args + ) } - return { ...baseProps, onBeforeEnter(el) { - onBeforeEnter && onBeforeEnter(el) + callHook(onBeforeEnter, [el]) addTransitionClass(el, enterActiveClass) addTransitionClass(el, enterFromClass) }, onEnter(el, done) { nextFrame(() => { const resolve = () => finishEnter(el, done) - onEnter && callHookWithErrorHandling(onEnter as Hook, [el, resolve]) + callHook(onEnter, [el, resolve]) removeTransitionClass(el, enterFromClass) addTransitionClass(el, enterToClass) if (!(onEnter && onEnter.length > 1)) { @@ -137,12 +180,18 @@ export function resolveTransitionProps({ } }) }, + onAfterEnter(el) { + callHook(onAfterEnter, [el]) + }, + onBeforeLeave(el) { + callHook(onBeforeLeave, [el]) + }, onLeave(el, done) { addTransitionClass(el, leaveActiveClass) addTransitionClass(el, leaveFromClass) nextFrame(() => { const resolve = () => finishLeave(el, done) - onLeave && callHookWithErrorHandling(onLeave as Hook, [el, resolve]) + callHook(onLeave, [el, resolve]) removeTransitionClass(el, leaveFromClass) addTransitionClass(el, leaveToClass) if (!(onLeave && onLeave.length > 1)) { @@ -154,8 +203,17 @@ export function resolveTransitionProps({ } }) }, - onEnterCancelled: finishEnter, - onLeaveCancelled: finishLeave + onAfterLeave(el) { + callHook(onAfterLeave, [el]) + }, + onEnterCancelled(el) { + finishEnter(el) + callHook(onEnterCancelled, [el]) + }, + onLeaveCancelled(el) { + finishLeave(el) + callHook(onLeaveCancelled, [el]) + } } }