diff --git a/packages/runtime-core/src/components/Transition.ts b/packages/runtime-core/src/components/Transition.ts index 1d4d3feb..e620aaf6 100644 --- a/packages/runtime-core/src/components/Transition.ts +++ b/packages/runtime-core/src/components/Transition.ts @@ -17,6 +17,12 @@ import { ShapeFlags } from '../shapeFlags' export interface TransitionProps { mode?: 'in-out' | 'out-in' | 'default' appear?: boolean + // If true, indicates this is a transition that doesn't actually insert/remove + // the element, but toggles the show / hidden status instead. + // The transition hooks are injected, but will be skipped by the renderer. + // Instead, a custom directive can control the transition by calling the + // injected hooks (e.g. v-show). + persisted?: boolean // enter onBeforeEnter?: (el: any) => void onEnter?: (el: any, done: () => void) => void @@ -139,6 +145,7 @@ if (__DEV__) { ;(TransitionImpl as ComponentOptions).props = { mode: String, appear: Boolean, + persisted: Boolean, // enter onBeforeEnter: Function, onEnter: Function, @@ -161,6 +168,7 @@ export const Transition = (TransitionImpl as any) as { } export interface TransitionHooks { + persisted: boolean beforeEnter(el: object): void enter(el: object): void leave(el: object, remove: () => void): void @@ -173,6 +181,7 @@ export interface TransitionHooks { function resolveTransitionHooks( { appear, + persisted = false, onBeforeEnter, onEnter, onAfterEnter, @@ -188,6 +197,7 @@ function resolveTransitionHooks( performDelayedLeave: () => void ): TransitionHooks { return { + persisted, beforeEnter(el) { if (!isMounted && !appear) { return @@ -202,7 +212,10 @@ function resolveTransitionHooks( if (!isMounted && !appear) { return } + let called = false const afterEnter = (pendingCallbacks.enter = (cancelled?) => { + if (called) return + called = true if (cancelled) { callHook(onEnterCancelled, [el]) } else { @@ -223,7 +236,10 @@ function resolveTransitionHooks( pendingCallbacks.enter(true /* cancelled */) } callHook(onBeforeLeave, [el]) + let called = false const afterLeave = (pendingCallbacks.leave = (cancelled?) => { + if (called) return + called = true remove() if (cancelled) { callHook(onLeaveCancelled, [el]) diff --git a/packages/runtime-core/src/renderer.ts b/packages/runtime-core/src/renderer.ts index da3e777c..258c70ac 100644 --- a/packages/runtime-core/src/renderer.ts +++ b/packages/runtime-core/src/renderer.ts @@ -367,7 +367,7 @@ export function createRenderer< invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode) } } - if (transition != null) { + if (transition != null && !transition.persisted) { transition.beforeEnter(el) } if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { @@ -385,11 +385,14 @@ export function createRenderer< } hostInsert(el, container, anchor) const vnodeMountedHook = props && props.onVnodeMounted - if (vnodeMountedHook != null || transition != null) { + if ( + vnodeMountedHook != null || + (transition != null && !transition.persisted) + ) { queuePostRenderEffect(() => { vnodeMountedHook && invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode) - transition && transition.enter(el) + transition && !transition.persisted && transition.enter(el) }, parentSuspense) } } @@ -1468,11 +1471,19 @@ export function createRenderer< const remove = () => { hostRemove(vnode.el!) if (anchor != null) hostRemove(anchor) - if (transition != null && transition.afterLeave) { + if ( + transition != null && + !transition.persisted && + transition.afterLeave + ) { transition.afterLeave() } } - if (vnode.shapeFlag & ShapeFlags.ELEMENT && transition != null) { + if ( + vnode.shapeFlag & ShapeFlags.ELEMENT && + transition != null && + !transition.persisted + ) { const { leave, delayLeave } = transition const performLeave = () => leave(el!, remove) if (delayLeave) { diff --git a/packages/runtime-dom/src/components/CSSTransition.ts b/packages/runtime-dom/src/components/CSSTransition.ts index c3a858f8..b5436c15 100644 --- a/packages/runtime-dom/src/components/CSSTransition.ts +++ b/packages/runtime-dom/src/components/CSSTransition.ts @@ -6,6 +6,7 @@ import { FunctionalComponent } from '@vue/runtime-core' import { isObject } from '@vue/shared' +import { currentRenderingInstance } from 'packages/runtime-core/src/componentRenderUtils' const TRANSITION = 'transition' const ANIMATION = 'animation' @@ -18,12 +19,12 @@ export interface CSSTransitionProps extends TransitionProps { enterFromClass?: string enterActiveClass?: string enterToClass?: string + appearFromClass?: string + appearActiveClass?: string + appearToClass?: string leaveFromClass?: string leaveActiveClass?: string leaveToClass?: string - // if present, indicates this is a v-show transition by toggling the - // CSS display property instead of actually removing the element. - show?: boolean } export const CSSTransition: FunctionalComponent = ( @@ -36,10 +37,13 @@ if (__DEV__) { ...(BaseTransition as any).props, name: String, type: String, - enterClass: String, + enterFromClass: String, enterActiveClass: String, enterToClass: String, - leaveClass: String, + appearFromClass: String, + appearActiveClass: String, + appearToClass: String, + leaveFromClass: String, leaveActiveClass: String, leaveToClass: String, duration: Object @@ -53,6 +57,9 @@ function resolveCSSTransitionProps({ enterFromClass = `${name}-enter-from`, enterActiveClass = `${name}-enter-active`, enterToClass = `${name}-enter-to`, + appearFromClass = enterFromClass, + appearActiveClass = enterActiveClass, + appearToClass = enterToClass, leaveFromClass = `${name}-leave-from`, leaveActiveClass = `${name}-leave-active`, leaveToClass = `${name}-leave-to`, @@ -61,7 +68,26 @@ function resolveCSSTransitionProps({ const durations = normalizeDuration(duration) const enterDuration = durations && durations[0] const leaveDuration = durations && durations[1] - const { onBeforeEnter, onEnter, onLeave } = baseProps + const { appear, onBeforeEnter, onEnter, onLeave } = baseProps + + // is appearing + if (appear && !currentRenderingInstance!.subTree) { + enterFromClass = appearFromClass + enterActiveClass = appearActiveClass + enterToClass = appearToClass + } + + function finishEnter(el: Element, done?: () => void) { + removeTransitionClass(el, enterToClass) + removeTransitionClass(el, enterActiveClass) + done && done() + } + + function finishLeave(el: Element, done?: () => void) { + removeTransitionClass(el, leaveToClass) + removeTransitionClass(el, leaveActiveClass) + done && done() + } return { ...baseProps, @@ -72,11 +98,7 @@ function resolveCSSTransitionProps({ }, onEnter(el, done) { nextFrame(() => { - const resolve = () => { - removeTransitionClass(el, enterToClass) - removeTransitionClass(el, enterActiveClass) - done() - } + const resolve = () => finishEnter(el, done) onEnter && onEnter(el, resolve) removeTransitionClass(el, enterFromClass) addTransitionClass(el, enterToClass) @@ -93,11 +115,7 @@ function resolveCSSTransitionProps({ addTransitionClass(el, leaveActiveClass) addTransitionClass(el, leaveFromClass) nextFrame(() => { - const resolve = () => { - removeTransitionClass(el, leaveToClass) - removeTransitionClass(el, leaveActiveClass) - done() - } + const resolve = () => finishLeave(el, done) onLeave && onLeave(el, resolve) removeTransitionClass(el, leaveFromClass) addTransitionClass(el, leaveToClass) @@ -109,7 +127,9 @@ function resolveCSSTransitionProps({ } } }) - } + }, + onEnterCancelled: finishEnter, + onLeaveCancelled: finishLeave } } @@ -161,9 +181,11 @@ function addTransitionClass(el: ElementWithTransition, cls: string) { function removeTransitionClass(el: ElementWithTransition, cls: string) { el.classList.remove(cls) - el._vtc!.delete(cls) - if (!el._vtc!.size) { - el._vtc = undefined + if (el._vtc) { + el._vtc.delete(cls) + if (!el._vtc!.size) { + el._vtc = undefined + } } } diff --git a/packages/runtime-dom/src/index.ts b/packages/runtime-dom/src/index.ts index ea736067..bec21093 100644 --- a/packages/runtime-dom/src/index.ts +++ b/packages/runtime-dom/src/index.ts @@ -66,7 +66,7 @@ export { export { withModifiers, withKeys } from './directives/vOn' // DOM-only components -export { CSSTransition } from './components/CSSTransition' +export { CSSTransition, CSSTransitionProps } from './components/CSSTransition' // re-export everything from core // h, Component, reactivity API, nextTick, flags & types