transition.js 3.0 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. import { warn } from 'core/util/index'
  2. import { noop, camelize } from 'shared/util'
  3. import { leave } from 'web/runtime/modules/transition'
  4. import { getRealChild, mergeVNodeHook } from 'core/vdom/helpers'
  5. export default {
  6. name: 'transition',
  7. props: {
  8. name: String,
  9. appear: Boolean,
  10. css: Boolean,
  11. mode: String,
  12. enterClass: String,
  13. leaveClass: String,
  14. enterActiveClass: String,
  15. leaveActiveClass: String,
  16. appearClass: String,
  17. appearActiveClass: String
  18. },
  19. _abstract: true,
  20. render (h) {
  21. const children = this.$slots.default && this.$slots.default.filter(c => c.tag)
  22. if (!children || !children.length) {
  23. return
  24. }
  25. if (process.env.NODE_ENV !== 'production' && children.length > 1) {
  26. warn(
  27. '<transition> can only be used on a single element. Use ' +
  28. '<transition-group> for lists.'
  29. )
  30. }
  31. const rawChild = children[0]
  32. // if this is a component root node and the compoennt's
  33. // parent container node also has transition, skip.
  34. if (this.$vnode.parent && this.$vnode.parent.data.transition) {
  35. return rawChild
  36. }
  37. // apply transition data to child
  38. // use getRealChild() to ignore abstract components e.g. keep-alive
  39. const child = getRealChild(rawChild)
  40. child.key = child.key || `__v${child.tag + this._uid}__`
  41. const data = (child.data || (child.data = {})).transition = {}
  42. // props
  43. for (const key in this.$options.propsData) {
  44. data[key] = this[key]
  45. }
  46. // events.
  47. // extract listeners and pass them directly to the transition methods
  48. const listeners = this.$options._parentListeners
  49. for (const key in listeners) {
  50. data[camelize(key)] = listeners[key].fn
  51. }
  52. // handle transition mode
  53. const mode = this.mode
  54. const oldRawChild = this._vnode
  55. const oldChild = getRealChild(oldRawChild)
  56. if (mode && oldChild && oldChild.data && oldChild.key !== child.key) {
  57. if (mode === 'out-in') {
  58. if (
  59. !oldChild.elm._leaveCb && // not already leaving
  60. oldChild.data.transition // not already left
  61. ) {
  62. leave(oldChild, () => {
  63. // mark left & avoid triggering leave transition again
  64. oldChild.data.transition = null
  65. this.$forceUpdate()
  66. })
  67. }
  68. // return old node if not left yet
  69. if (oldChild.data.transition) {
  70. return oldRawChild
  71. }
  72. } else if (mode === 'in-out') {
  73. let delayedLeave
  74. const performLeave = () => { delayedLeave() }
  75. mergeVNodeHook(data, 'afterEnter', performLeave)
  76. mergeVNodeHook(data, 'enterCancelled', performLeave)
  77. const oldData = oldChild.data.transition
  78. mergeVNodeHook(oldData, 'delayLeave', leave => {
  79. delayedLeave = leave
  80. })
  81. mergeVNodeHook(oldData, 'leaveCancelled', () => {
  82. delayedLeave = noop
  83. })
  84. } else if (process.env.NODE_ENV !== 'production') {
  85. warn('invalid <transition> mode: ' + mode)
  86. }
  87. }
  88. return rawChild
  89. }
  90. }