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
;(TransitionImpl as ComponentOptions).props = {
mode: String,
appear: Boolean,
+ persisted: Boolean,
// enter
onBeforeEnter: Function,
onEnter: Function,
}
export interface TransitionHooks {
+ persisted: boolean
beforeEnter(el: object): void
enter(el: object): void
leave(el: object, remove: () => void): void
function resolveTransitionHooks(
{
appear,
+ persisted = false,
onBeforeEnter,
onEnter,
onAfterEnter,
performDelayedLeave: () => void
): TransitionHooks {
return {
+ persisted,
beforeEnter(el) {
if (!isMounted && !appear) {
return
if (!isMounted && !appear) {
return
}
+ let called = false
const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
+ if (called) return
+ called = true
if (cancelled) {
callHook(onEnterCancelled, [el])
} else {
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])
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
}
}
- if (transition != null) {
+ if (transition != null && !transition.persisted) {
transition.beforeEnter(el)
}
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
}
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)
}
}
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) {
FunctionalComponent
} from '@vue/runtime-core'
import { isObject } from '@vue/shared'
+import { currentRenderingInstance } from 'packages/runtime-core/src/componentRenderUtils'
const TRANSITION = 'transition'
const ANIMATION = 'animation'
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 = (
...(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
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`,
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,
},
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)
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)
}
}
})
- }
+ },
+ onEnterCancelled: finishEnter,
+ onLeaveCancelled: finishLeave
}
}
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
+ }
}
}