vue
Version:
Reactive, component-oriented view layer for modern web interfaces.
290 lines (259 loc) • 7.24 kB
JavaScript
/* @flow */
import { inBrowser, isIE9 } from 'core/util/index'
import { cached, extend } from 'shared/util'
import { mergeVNodeHook } from 'core/vdom/helpers/index'
import { activeInstance } from 'core/instance/lifecycle'
import {
nextFrame,
addTransitionClass,
removeTransitionClass,
whenTransitionEnds
} from '../transition-util'
export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
const el: any = vnode.elm
// call leave callback now
if (el._leaveCb) {
el._leaveCb.cancelled = true
el._leaveCb()
}
const data = resolveTransition(vnode.data.transition)
if (!data) {
return
}
/* istanbul ignore if */
if (el._enterCb || el.nodeType !== 1) {
return
}
const {
css,
type,
enterClass,
enterToClass,
enterActiveClass,
appearClass,
appearToClass,
appearActiveClass,
beforeEnter,
enter,
afterEnter,
enterCancelled,
beforeAppear,
appear,
afterAppear,
appearCancelled
} = data
// activeInstance will always be the <transition> component managing this
// transition. One edge case to check is when the <transition> is placed
// as the root node of a child component. In that case we need to check
// <transition>'s parent for appear check.
let context = activeInstance
let transitionNode = activeInstance.$vnode
while (transitionNode && transitionNode.parent) {
transitionNode = transitionNode.parent
context = transitionNode.context
}
const isAppear = !context._isMounted || !vnode.isRootInsert
if (isAppear && !appear && appear !== '') {
return
}
const startClass = isAppear ? appearClass : enterClass
const activeClass = isAppear ? appearActiveClass : enterActiveClass
const toClass = isAppear ? appearToClass : enterToClass
const beforeEnterHook = isAppear ? (beforeAppear || beforeEnter) : beforeEnter
const enterHook = isAppear ? (typeof appear === 'function' ? appear : enter) : enter
const afterEnterHook = isAppear ? (afterAppear || afterEnter) : afterEnter
const enterCancelledHook = isAppear ? (appearCancelled || enterCancelled) : enterCancelled
const expectsCSS = css !== false && !isIE9
const userWantsControl =
enterHook &&
// enterHook may be a bound method which exposes
// the length of original fn as _length
(enterHook._length || enterHook.length) > 1
const cb = el._enterCb = once(() => {
if (expectsCSS) {
removeTransitionClass(el, toClass)
removeTransitionClass(el, activeClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, startClass)
}
enterCancelledHook && enterCancelledHook(el)
} else {
afterEnterHook && afterEnterHook(el)
}
el._enterCb = null
})
if (!vnode.data.show) {
// remove pending leave element on enter by injecting an insert hook
mergeVNodeHook(vnode.data.hook || (vnode.data.hook = {}), 'insert', () => {
const parent = el.parentNode
const pendingNode = parent && parent._pending && parent._pending[vnode.key]
if (pendingNode &&
pendingNode.tag === vnode.tag &&
pendingNode.elm._leaveCb) {
pendingNode.elm._leaveCb()
}
enterHook && enterHook(el, cb)
}, 'transition-insert')
}
// start enter transition
beforeEnterHook && beforeEnterHook(el)
if (expectsCSS) {
addTransitionClass(el, startClass)
addTransitionClass(el, activeClass)
nextFrame(() => {
addTransitionClass(el, toClass)
removeTransitionClass(el, startClass)
if (!cb.cancelled && !userWantsControl) {
whenTransitionEnds(el, type, cb)
}
})
}
if (vnode.data.show) {
toggleDisplay && toggleDisplay()
enterHook && enterHook(el, cb)
}
if (!expectsCSS && !userWantsControl) {
cb()
}
}
export function leave (vnode: VNodeWithData, rm: Function) {
const el: any = vnode.elm
// call enter callback now
if (el._enterCb) {
el._enterCb.cancelled = true
el._enterCb()
}
const data = resolveTransition(vnode.data.transition)
if (!data) {
return rm()
}
/* istanbul ignore if */
if (el._leaveCb || el.nodeType !== 1) {
return
}
const {
css,
type,
leaveClass,
leaveToClass,
leaveActiveClass,
beforeLeave,
leave,
afterLeave,
leaveCancelled,
delayLeave
} = data
const expectsCSS = css !== false && !isIE9
const userWantsControl =
leave &&
// leave hook may be a bound method which exposes
// the length of original fn as _length
(leave._length || leave.length) > 1
const cb = el._leaveCb = once(() => {
if (el.parentNode && el.parentNode._pending) {
el.parentNode._pending[vnode.key] = null
}
if (expectsCSS) {
removeTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveActiveClass)
}
if (cb.cancelled) {
if (expectsCSS) {
removeTransitionClass(el, leaveClass)
}
leaveCancelled && leaveCancelled(el)
} else {
rm()
afterLeave && afterLeave(el)
}
el._leaveCb = null
})
if (delayLeave) {
delayLeave(performLeave)
} else {
performLeave()
}
function performLeave () {
// the delayed leave may have already been cancelled
if (cb.cancelled) {
return
}
// record leaving element
if (!vnode.data.show) {
(el.parentNode._pending || (el.parentNode._pending = {}))[vnode.key] = vnode
}
beforeLeave && beforeLeave(el)
if (expectsCSS) {
addTransitionClass(el, leaveClass)
addTransitionClass(el, leaveActiveClass)
nextFrame(() => {
addTransitionClass(el, leaveToClass)
removeTransitionClass(el, leaveClass)
if (!cb.cancelled && !userWantsControl) {
whenTransitionEnds(el, type, cb)
}
})
}
leave && leave(el, cb)
if (!expectsCSS && !userWantsControl) {
cb()
}
}
}
function resolveTransition (def?: string | Object): ?Object {
if (!def) {
return
}
/* istanbul ignore else */
if (typeof def === 'object') {
const res = {}
if (def.css !== false) {
extend(res, autoCssTransition(def.name || 'v'))
}
extend(res, def)
return res
} else if (typeof def === 'string') {
return autoCssTransition(def)
}
}
const autoCssTransition: (name: string) => Object = cached(name => {
return {
enterClass: `${name}-enter`,
leaveClass: `${name}-leave`,
appearClass: `${name}-enter`,
enterToClass: `${name}-enter-to`,
leaveToClass: `${name}-leave-to`,
appearToClass: `${name}-enter-to`,
enterActiveClass: `${name}-enter-active`,
leaveActiveClass: `${name}-leave-active`,
appearActiveClass: `${name}-enter-active`
}
})
function once (fn: Function): Function {
let called = false
return () => {
if (!called) {
called = true
fn()
}
}
}
function _enter (_: any, vnode: VNodeWithData) {
if (!vnode.data.show) {
enter(vnode)
}
}
export default inBrowser ? {
create: _enter,
activate: _enter,
remove (vnode: VNode, rm: Function) {
/* istanbul ignore else */
if (!vnode.data.show) {
leave(vnode, rm)
} else {
rm()
}
}
} : {}