text: /* @flow */
  
  import { inBrowser, isIE9, warn } from 'core/util/index'
  import { mergeVNodeHook } from 'core/vdom/helpers/index'
  import { activeInstance } from 'core/instance/lifecycle'
  
  import {
      once,
      isDef,
      isUndef,
      isObject,
      toNumber
  } from 'shared/util'
  
  import {
      nextFrame,
      resolveTransition,
      whenTransitionEnds,
      addTransitionClass,
      removeTransitionClass
  } from '../transition-util'
  
  export function enter(vnode: VNodeWithData, toggleDisplay: ?() => void) {
      const el: any = vnode.elm
  
      // call leave callback now
      if (isDef(el._leaveCb)) {
          el._leaveCb.cancelled = true
          el._leaveCb()
      }
  
      const data = resolveTransition(vnode.data.transition)
      if (isUndef(data)) {
          return
      }
  
      /* istanbul ignore if */
      if (isDef(el._enterCb) || el.nodeType !== 1) {
          return
      }
  
      const {
          css,
          type,
          enterClass,
          enterToClass,
          enterActiveClass,
          appearClass,
          appearToClass,
          appearActiveClass,
          beforeEnter,
          enter,
          afterEnter,
          enterCancelled,
          beforeAppear,
          appear,
          afterAppear,
          appearCancelled,
          duration
      } = 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
          ? appearClass
          : enterClass
      const activeClass = isAppear && appearActiveClass
          ? appearActiveClass
          : enterActiveClass
      const toClass = isAppear && appearToClass
          ? 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 explicitEnterDuration: any = toNumber(
          isObject(duration)
              ? duration.enter
              : duration
      )
  
      if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {
          checkDuration(explicitEnterDuration, 'enter', vnode)
      }
  
      const expectsCSS = css !== false && !isIE9
      const userWantsControl = getHookArgumentsLength(enterHook)
  
      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, '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)
          })
      }
  
      // start enter transition
      beforeEnterHook && beforeEnterHook(el)
      if (expectsCSS) {
          addTransitionClass(el, startClass)
          addTransitionClass(el, activeClass)
          nextFrame(() => {
              removeTransitionClass(el, startClass)
              if (!cb.cancelled) {
                  addTransitionClass(el, toClass)
                  if (!userWantsControl) {
                      if (isValidDuration(explicitEnterDuration)) {
                          setTimeout(cb, explicitEnterDuration)
                      } else {
                          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 (isDef(el._enterCb)) {
          el._enterCb.cancelled = true
          el._enterCb()
      }
  
      const data = resolveTransition(vnode.data.transition)
      if (isUndef(data) || el.nodeType !== 1) {
          return rm()
      }
  
      /* istanbul ignore if */
      if (isDef(el._leaveCb)) {
          return
      }
  
      const {
          css,
          type,
          leaveClass,
          leaveToClass,
          leaveActiveClass,
          beforeLeave,
          leave,
          afterLeave,
          leaveCancelled,
          delayLeave,
          duration
      } = data
  
      const expectsCSS = css !== false && !isIE9
      const userWantsControl = getHookArgumentsLength(leave)
  
      const explicitLeaveDuration: any = toNumber(
          isObject(duration)
              ? duration.leave
              : duration
      )
  
      if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {
          checkDuration(explicitLeaveDuration, 'leave', vnode)
      }
  
      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: any)] = vnode
          }
          beforeLeave && beforeLeave(el)
          if (expectsCSS) {
              addTransitionClass(el, leaveClass)
              addTransitionClass(el, leaveActiveClass)
              nextFrame(() => {
                  removeTransitionClass(el, leaveClass)
                  if (!cb.cancelled) {
                      addTransitionClass(el, leaveToClass)
                      if (!userWantsControl) {
                          if (isValidDuration(explicitLeaveDuration)) {
                              setTimeout(cb, explicitLeaveDuration)
                          } else {
                              whenTransitionEnds(el, type, cb)
                          }
                      }
                  }
              })
          }
          leave && leave(el, cb)
          if (!expectsCSS && !userWantsControl) {
              cb()
          }
      }
  }
  
  // only used in dev mode
  function checkDuration(val, name, vnode) {
      if (typeof val !== 'number') {
          warn(
              `<transition> explicit ${name} duration is not a valid number - ` +
              `got ${JSON.stringify(val)}.`,
              vnode.context
          )
      } else if (isNaN(val)) {
          warn(
              `<transition> explicit ${name} duration is NaN - ` +
              'the duration expression might be incorrect.',
              vnode.context
          )
      }
  }
  
  function isValidDuration(val) {
      return typeof val === 'number' && !isNaN(val)
  }
  
  /**
   * Normalize a transition hook's argument length. The hook may be:
   * - a merged hook (invoker) with the original in .fns
   * - a wrapped component method (check ._length)
   * - a plain function (.length)
   */
  function getHookArgumentsLength(fn: Function): boolean {
      if (isUndef(fn)) {
          return false
      }
      const invokerFns = fn.fns
      if (isDef(invokerFns)) {
          // invoker
          return getHookArgumentsLength(
              Array.isArray(invokerFns)
                  ? invokerFns[0]
                  : invokerFns
          )
      } else {
          return (fn._length || fn.length) > 1
      }
  }
  
  function _enter(_: any, vnode: VNodeWithData) {
      if (vnode.data.show !== true) {
          enter(vnode)
      }
  }
  
  export default inBrowser ? {
      create: _enter,
      activate: _enter,
      remove(vnode: VNode, rm: Function) {
          /* istanbul ignore else */
          if (vnode.data.show !== true) {
              leave(vnode, rm)
          } else {
              rm()
          }
      }
  } : {}
  