feat(transition): handle persisted mode

This commit is contained in:
Evan You 2019-11-22 23:21:39 -05:00
parent cbc2044752
commit 1b8236615e
4 changed files with 75 additions and 26 deletions

View File

@ -17,6 +17,12 @@ import { ShapeFlags } from '../shapeFlags'
export interface TransitionProps { export interface TransitionProps {
mode?: 'in-out' | 'out-in' | 'default' mode?: 'in-out' | 'out-in' | 'default'
appear?: boolean 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 // enter
onBeforeEnter?: (el: any) => void onBeforeEnter?: (el: any) => void
onEnter?: (el: any, done: () => void) => void onEnter?: (el: any, done: () => void) => void
@ -139,6 +145,7 @@ if (__DEV__) {
;(TransitionImpl as ComponentOptions).props = { ;(TransitionImpl as ComponentOptions).props = {
mode: String, mode: String,
appear: Boolean, appear: Boolean,
persisted: Boolean,
// enter // enter
onBeforeEnter: Function, onBeforeEnter: Function,
onEnter: Function, onEnter: Function,
@ -161,6 +168,7 @@ export const Transition = (TransitionImpl as any) as {
} }
export interface TransitionHooks { export interface TransitionHooks {
persisted: boolean
beforeEnter(el: object): void beforeEnter(el: object): void
enter(el: object): void enter(el: object): void
leave(el: object, remove: () => void): void leave(el: object, remove: () => void): void
@ -173,6 +181,7 @@ export interface TransitionHooks {
function resolveTransitionHooks( function resolveTransitionHooks(
{ {
appear, appear,
persisted = false,
onBeforeEnter, onBeforeEnter,
onEnter, onEnter,
onAfterEnter, onAfterEnter,
@ -188,6 +197,7 @@ function resolveTransitionHooks(
performDelayedLeave: () => void performDelayedLeave: () => void
): TransitionHooks { ): TransitionHooks {
return { return {
persisted,
beforeEnter(el) { beforeEnter(el) {
if (!isMounted && !appear) { if (!isMounted && !appear) {
return return
@ -202,7 +212,10 @@ function resolveTransitionHooks(
if (!isMounted && !appear) { if (!isMounted && !appear) {
return return
} }
let called = false
const afterEnter = (pendingCallbacks.enter = (cancelled?) => { const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
if (called) return
called = true
if (cancelled) { if (cancelled) {
callHook(onEnterCancelled, [el]) callHook(onEnterCancelled, [el])
} else { } else {
@ -223,7 +236,10 @@ function resolveTransitionHooks(
pendingCallbacks.enter(true /* cancelled */) pendingCallbacks.enter(true /* cancelled */)
} }
callHook(onBeforeLeave, [el]) callHook(onBeforeLeave, [el])
let called = false
const afterLeave = (pendingCallbacks.leave = (cancelled?) => { const afterLeave = (pendingCallbacks.leave = (cancelled?) => {
if (called) return
called = true
remove() remove()
if (cancelled) { if (cancelled) {
callHook(onLeaveCancelled, [el]) callHook(onLeaveCancelled, [el])

View File

@ -367,7 +367,7 @@ export function createRenderer<
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode) invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
} }
} }
if (transition != null) { if (transition != null && !transition.persisted) {
transition.beforeEnter(el) transition.beforeEnter(el)
} }
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) { if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
@ -385,11 +385,14 @@ export function createRenderer<
} }
hostInsert(el, container, anchor) hostInsert(el, container, anchor)
const vnodeMountedHook = props && props.onVnodeMounted const vnodeMountedHook = props && props.onVnodeMounted
if (vnodeMountedHook != null || transition != null) { if (
vnodeMountedHook != null ||
(transition != null && !transition.persisted)
) {
queuePostRenderEffect(() => { queuePostRenderEffect(() => {
vnodeMountedHook && vnodeMountedHook &&
invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode) invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
transition && transition.enter(el) transition && !transition.persisted && transition.enter(el)
}, parentSuspense) }, parentSuspense)
} }
} }
@ -1468,11 +1471,19 @@ export function createRenderer<
const remove = () => { const remove = () => {
hostRemove(vnode.el!) hostRemove(vnode.el!)
if (anchor != null) hostRemove(anchor) if (anchor != null) hostRemove(anchor)
if (transition != null && transition.afterLeave) { if (
transition != null &&
!transition.persisted &&
transition.afterLeave
) {
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 { leave, delayLeave } = transition
const performLeave = () => leave(el!, remove) const performLeave = () => leave(el!, remove)
if (delayLeave) { if (delayLeave) {

View File

@ -6,6 +6,7 @@ import {
FunctionalComponent FunctionalComponent
} from '@vue/runtime-core' } from '@vue/runtime-core'
import { isObject } from '@vue/shared' import { isObject } from '@vue/shared'
import { currentRenderingInstance } from 'packages/runtime-core/src/componentRenderUtils'
const TRANSITION = 'transition' const TRANSITION = 'transition'
const ANIMATION = 'animation' const ANIMATION = 'animation'
@ -18,12 +19,12 @@ export interface CSSTransitionProps extends TransitionProps {
enterFromClass?: string enterFromClass?: string
enterActiveClass?: string enterActiveClass?: string
enterToClass?: string enterToClass?: string
appearFromClass?: string
appearActiveClass?: string
appearToClass?: string
leaveFromClass?: string leaveFromClass?: string
leaveActiveClass?: string leaveActiveClass?: string
leaveToClass?: 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 = ( export const CSSTransition: FunctionalComponent = (
@ -36,10 +37,13 @@ if (__DEV__) {
...(BaseTransition as any).props, ...(BaseTransition as any).props,
name: String, name: String,
type: String, type: String,
enterClass: String, enterFromClass: String,
enterActiveClass: String, enterActiveClass: String,
enterToClass: String, enterToClass: String,
leaveClass: String, appearFromClass: String,
appearActiveClass: String,
appearToClass: String,
leaveFromClass: String,
leaveActiveClass: String, leaveActiveClass: String,
leaveToClass: String, leaveToClass: String,
duration: Object duration: Object
@ -53,6 +57,9 @@ function resolveCSSTransitionProps({
enterFromClass = `${name}-enter-from`, enterFromClass = `${name}-enter-from`,
enterActiveClass = `${name}-enter-active`, enterActiveClass = `${name}-enter-active`,
enterToClass = `${name}-enter-to`, enterToClass = `${name}-enter-to`,
appearFromClass = enterFromClass,
appearActiveClass = enterActiveClass,
appearToClass = enterToClass,
leaveFromClass = `${name}-leave-from`, leaveFromClass = `${name}-leave-from`,
leaveActiveClass = `${name}-leave-active`, leaveActiveClass = `${name}-leave-active`,
leaveToClass = `${name}-leave-to`, leaveToClass = `${name}-leave-to`,
@ -61,7 +68,26 @@ function resolveCSSTransitionProps({
const durations = normalizeDuration(duration) const durations = normalizeDuration(duration)
const enterDuration = durations && durations[0] const enterDuration = durations && durations[0]
const leaveDuration = durations && durations[1] 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 { return {
...baseProps, ...baseProps,
@ -72,11 +98,7 @@ function resolveCSSTransitionProps({
}, },
onEnter(el, done) { onEnter(el, done) {
nextFrame(() => { nextFrame(() => {
const resolve = () => { const resolve = () => finishEnter(el, done)
removeTransitionClass(el, enterToClass)
removeTransitionClass(el, enterActiveClass)
done()
}
onEnter && onEnter(el, resolve) onEnter && onEnter(el, resolve)
removeTransitionClass(el, enterFromClass) removeTransitionClass(el, enterFromClass)
addTransitionClass(el, enterToClass) addTransitionClass(el, enterToClass)
@ -93,11 +115,7 @@ function resolveCSSTransitionProps({
addTransitionClass(el, leaveActiveClass) addTransitionClass(el, leaveActiveClass)
addTransitionClass(el, leaveFromClass) addTransitionClass(el, leaveFromClass)
nextFrame(() => { nextFrame(() => {
const resolve = () => { const resolve = () => finishLeave(el, done)
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
done()
}
onLeave && onLeave(el, resolve) onLeave && onLeave(el, resolve)
removeTransitionClass(el, leaveFromClass) removeTransitionClass(el, leaveFromClass)
addTransitionClass(el, leaveToClass) addTransitionClass(el, leaveToClass)
@ -109,7 +127,9 @@ function resolveCSSTransitionProps({
} }
} }
}) })
} },
onEnterCancelled: finishEnter,
onLeaveCancelled: finishLeave
} }
} }
@ -161,11 +181,13 @@ function addTransitionClass(el: ElementWithTransition, cls: string) {
function removeTransitionClass(el: ElementWithTransition, cls: string) { function removeTransitionClass(el: ElementWithTransition, cls: string) {
el.classList.remove(cls) el.classList.remove(cls)
el._vtc!.delete(cls) if (el._vtc) {
el._vtc.delete(cls)
if (!el._vtc!.size) { if (!el._vtc!.size) {
el._vtc = undefined el._vtc = undefined
} }
} }
}
function nextFrame(cb: () => void) { function nextFrame(cb: () => void) {
requestAnimationFrame(() => { requestAnimationFrame(() => {

View File

@ -66,7 +66,7 @@ export {
export { withModifiers, withKeys } from './directives/vOn' export { withModifiers, withKeys } from './directives/vOn'
// DOM-only components // DOM-only components
export { CSSTransition } from './components/CSSTransition' export { CSSTransition, CSSTransitionProps } from './components/CSSTransition'
// re-export everything from core // re-export everything from core
// h, Component, reactivity API, nextTick, flags & types // h, Component, reactivity API, nextTick, flags & types