13e0d3c687c0413284e3a4667ef0b31cc9546ff7fd67f3187b2b79adb22c1c29dc38632dc9a29e9d05bc335aecdafd9186e10142b04eb9bceaca8d9b8f648d 5.6 KB

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