import { warn } from 'core/util/debug'
|
import { extend, once, noop } from 'shared/util'
|
import { activeInstance } from 'core/instance/lifecycle'
|
import { resolveTransition } from 'web/runtime/transition-util'
|
|
export default {
|
create: enter,
|
activate: enter,
|
remove: leave
|
}
|
|
function enter (_, vnode) {
|
const el = 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) {
|
return
|
}
|
|
const {
|
enterClass,
|
enterToClass,
|
enterActiveClass,
|
appearClass,
|
appearToClass,
|
appearActiveClass,
|
beforeEnter,
|
enter,
|
afterEnter,
|
enterCancelled,
|
beforeAppear,
|
appear,
|
afterAppear,
|
appearCancelled
|
} = data
|
|
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 toClass = isAppear ? appearToClass : enterToClass
|
const activeClass = isAppear ? appearActiveClass : enterActiveClass
|
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 userWantsControl =
|
enterHook &&
|
// enterHook may be a bound method which exposes
|
// the length of original fn as _length
|
(enterHook._length || enterHook.length) > 1
|
|
const stylesheet = vnode.context.$options.style || {}
|
const startState = stylesheet[startClass]
|
const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][activeClass]) || {}
|
const endState = getEnterTargetState(el, stylesheet, startClass, toClass, activeClass)
|
const needAnimation = Object.keys(endState).length > 0
|
|
const cb = el._enterCb = once(() => {
|
if (cb.cancelled) {
|
enterCancelledHook && enterCancelledHook(el)
|
} else {
|
afterEnterHook && afterEnterHook(el)
|
}
|
el._enterCb = null
|
})
|
|
// We need to wait until the native element has been inserted, but currently
|
// there's no API to do that. So we have to wait "one frame" - not entirely
|
// sure if this is guaranteed to be enough (e.g. on slow devices?)
|
setTimeout(() => {
|
const parent = el.parentNode
|
const pendingNode = parent && parent._pending && parent._pending[vnode.key]
|
if (pendingNode &&
|
pendingNode.context === vnode.context &&
|
pendingNode.tag === vnode.tag &&
|
pendingNode.elm._leaveCb
|
) {
|
pendingNode.elm._leaveCb()
|
}
|
enterHook && enterHook(el, cb)
|
|
if (needAnimation) {
|
const animation = vnode.context.$requireWeexModule('animation')
|
animation.transition(el.ref, {
|
styles: endState,
|
duration: transitionProperties.duration || 0,
|
delay: transitionProperties.delay || 0,
|
timingFunction: transitionProperties.timingFunction || 'linear'
|
}, userWantsControl ? noop : cb)
|
} else if (!userWantsControl) {
|
cb()
|
}
|
}, 16)
|
|
// start enter transition
|
beforeEnterHook && beforeEnterHook(el)
|
|
if (startState) {
|
if (typeof el.setStyles === 'function') {
|
el.setStyles(startState)
|
} else {
|
for (const key in startState) {
|
el.setStyle(key, startState[key])
|
}
|
}
|
}
|
|
if (!needAnimation && !userWantsControl) {
|
cb()
|
}
|
}
|
|
function leave (vnode, rm) {
|
const el = 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()
|
}
|
|
if (el._leaveCb) {
|
return
|
}
|
|
const {
|
leaveClass,
|
leaveToClass,
|
leaveActiveClass,
|
beforeLeave,
|
leave,
|
afterLeave,
|
leaveCancelled,
|
delayLeave
|
} = data
|
|
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 stylesheet = vnode.context.$options.style || {}
|
const startState = stylesheet[leaveClass]
|
const endState = stylesheet[leaveToClass] || stylesheet[leaveActiveClass]
|
const transitionProperties = (stylesheet['@TRANSITION'] && stylesheet['@TRANSITION'][leaveActiveClass]) || {}
|
|
const cb = el._leaveCb = once(() => {
|
if (el.parentNode && el.parentNode._pending) {
|
el.parentNode._pending[vnode.key] = null
|
}
|
if (cb.cancelled) {
|
leaveCancelled && leaveCancelled(el)
|
} else {
|
rm()
|
afterLeave && afterLeave(el)
|
}
|
el._leaveCb = null
|
})
|
|
if (delayLeave) {
|
delayLeave(performLeave)
|
} else {
|
performLeave()
|
}
|
|
function performLeave () {
|
const animation = vnode.context.$requireWeexModule('animation')
|
// 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 (startState) {
|
animation.transition(el.ref, {
|
styles: startState
|
}, next)
|
} else {
|
next()
|
}
|
|
function next () {
|
animation.transition(el.ref, {
|
styles: endState,
|
duration: transitionProperties.duration || 0,
|
delay: transitionProperties.delay || 0,
|
timingFunction: transitionProperties.timingFunction || 'linear'
|
}, userWantsControl ? noop : cb)
|
}
|
|
leave && leave(el, cb)
|
if (!endState && !userWantsControl) {
|
cb()
|
}
|
}
|
}
|
|
// determine the target animation style for an entering transition.
|
function getEnterTargetState (el, stylesheet, startClass, endClass, activeClass) {
|
const targetState = {}
|
const startState = stylesheet[startClass]
|
const endState = stylesheet[endClass]
|
const activeState = stylesheet[activeClass]
|
// 1. fallback to element's default styling
|
if (startState) {
|
for (const key in startState) {
|
targetState[key] = el.style[key]
|
if (
|
process.env.NODE_ENV !== 'production' &&
|
targetState[key] == null &&
|
(!activeState || activeState[key] == null) &&
|
(!endState || endState[key] == null)
|
) {
|
warn(
|
`transition property "${key}" is declared in enter starting class (.${startClass}), ` +
|
`but not declared anywhere in enter ending class (.${endClass}), ` +
|
`enter active cass (.${activeClass}) or the element's default styling. ` +
|
`Note in Weex, CSS properties need explicit values to be transitionable.`
|
)
|
}
|
}
|
}
|
// 2. if state is mixed in active state, extract them while excluding
|
// transition properties
|
if (activeState) {
|
for (const key in activeState) {
|
if (key.indexOf('transition') !== 0) {
|
targetState[key] = activeState[key]
|
}
|
}
|
}
|
// 3. explicit endState has highest priority
|
if (endState) {
|
extend(targetState, endState)
|
}
|
return targetState
|
}
|