transition.js 4.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /* @flow */
  2. // Provides transition support for a single element/component.
  3. // supports transition mode (out-in / in-out)
  4. import { warn } from 'core/util/index'
  5. import { camelize, extend } from 'shared/util'
  6. import { mergeVNodeHook, getFirstComponentChild } from 'core/vdom/helpers'
  7. export const transitionProps = {
  8. name: String,
  9. appear: Boolean,
  10. css: Boolean,
  11. mode: String,
  12. type: String,
  13. enterClass: String,
  14. leaveClass: String,
  15. enterActiveClass: String,
  16. leaveActiveClass: String,
  17. appearClass: String,
  18. appearActiveClass: String
  19. }
  20. // in case the child is also an abstract component, e.g. <keep-alive>
  21. // we want to recrusively retrieve the real component to be rendered
  22. function getRealChild (vnode: ?VNode): ?VNode {
  23. const compOptions = vnode && vnode.componentOptions
  24. if (compOptions && compOptions.Ctor.options.abstract) {
  25. return getRealChild(getFirstComponentChild(compOptions.children))
  26. } else {
  27. return vnode
  28. }
  29. }
  30. export function extractTransitionData (comp: Component): Object {
  31. const data = {}
  32. const options = comp.$options
  33. // props
  34. for (const key in options.propsData) {
  35. data[key] = comp[key]
  36. }
  37. // events.
  38. // extract listeners and pass them directly to the transition methods
  39. const listeners = options._parentListeners
  40. for (const key in listeners) {
  41. data[camelize(key)] = listeners[key].fn
  42. }
  43. return data
  44. }
  45. function placeholder (h, rawChild) {
  46. return /\d-keep-alive$/.test(rawChild.tag)
  47. ? h('keep-alive')
  48. : null
  49. }
  50. function hasParentTransition (vnode) {
  51. while ((vnode = vnode.parent)) {
  52. if (vnode.data.transition) {
  53. return true
  54. }
  55. }
  56. }
  57. export default {
  58. name: 'transition',
  59. props: transitionProps,
  60. abstract: true,
  61. render (h: Function) {
  62. let children = this.$slots.default
  63. if (!children) {
  64. return
  65. }
  66. // filter out text nodes (possible whitespaces)
  67. children = children.filter(c => c.tag)
  68. /* istanbul ignore if */
  69. if (!children.length) {
  70. return
  71. }
  72. // warn multiple elements
  73. if (process.env.NODE_ENV !== 'production' && children.length > 1) {
  74. warn(
  75. '<transition> can only be used on a single element. Use ' +
  76. '<transition-group> for lists.',
  77. this.$parent
  78. )
  79. }
  80. const mode = this.mode
  81. // warn invalid mode
  82. if (process.env.NODE_ENV !== 'production' &&
  83. mode && mode !== 'in-out' && mode !== 'out-in') {
  84. warn(
  85. 'invalid <transition> mode: ' + mode,
  86. this.$parent
  87. )
  88. }
  89. const rawChild = children[0]
  90. // if this is a component root node and the component's
  91. // parent container node also has transition, skip.
  92. if (hasParentTransition(this.$vnode)) {
  93. return rawChild
  94. }
  95. // apply transition data to child
  96. // use getRealChild() to ignore abstract components e.g. keep-alive
  97. const child = getRealChild(rawChild)
  98. /* istanbul ignore if */
  99. if (!child) {
  100. return rawChild
  101. }
  102. if (this._leaving) {
  103. return placeholder(h, rawChild)
  104. }
  105. child.key = child.key == null
  106. ? `__v${child.tag + this._uid}__`
  107. : child.key
  108. const data = (child.data || (child.data = {})).transition = extractTransitionData(this)
  109. const oldRawChild = this._vnode
  110. const oldChild: any = getRealChild(oldRawChild)
  111. // mark v-show
  112. // so that the transition module can hand over the control to the directive
  113. if (child.data.directives && child.data.directives.some(d => d.name === 'show')) {
  114. child.data.show = true
  115. }
  116. if (oldChild && oldChild.data && oldChild.key !== child.key) {
  117. // replace old child transition data with fresh one
  118. // important for dynamic transitions!
  119. const oldData = oldChild.data.transition = extend({}, data)
  120. // handle transition mode
  121. if (mode === 'out-in') {
  122. // return placeholder node and queue update when leave finishes
  123. this._leaving = true
  124. mergeVNodeHook(oldData, 'afterLeave', () => {
  125. this._leaving = false
  126. this.$forceUpdate()
  127. })
  128. return placeholder(h, rawChild)
  129. } else if (mode === 'in-out') {
  130. var delayedLeave
  131. var performLeave = () => { delayedLeave() }
  132. mergeVNodeHook(data, 'afterEnter', performLeave)
  133. mergeVNodeHook(data, 'enterCancelled', performLeave)
  134. mergeVNodeHook(oldData, 'delayLeave', leave => {
  135. delayedLeave = leave
  136. })
  137. }
  138. }
  139. return rawChild
  140. }
  141. }