b9d998a306f48639ce21b09b3f929447dcd606f0eafb55c066ed723aade23256c968343d6233958d40c9df4359135b108d3cfaefda23da2718916ac0a2b565 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343
  1. /* @flow */
  2. import { inBrowser, isIE9, warn } from 'core/util/index'
  3. import { mergeVNodeHook } from 'core/vdom/helpers/index'
  4. import { activeInstance } from 'core/instance/lifecycle'
  5. import {
  6. once,
  7. isDef,
  8. isUndef,
  9. isObject,
  10. toNumber
  11. } from 'shared/util'
  12. import {
  13. nextFrame,
  14. resolveTransition,
  15. whenTransitionEnds,
  16. addTransitionClass,
  17. removeTransitionClass
  18. } from '../transition-util'
  19. export function enter (vnode: VNodeWithData, toggleDisplay: ?() => void) {
  20. const el: any = vnode.elm
  21. // call leave callback now
  22. if (isDef(el._leaveCb)) {
  23. el._leaveCb.cancelled = true
  24. el._leaveCb()
  25. }
  26. const data = resolveTransition(vnode.data.transition)
  27. if (isUndef(data)) {
  28. return
  29. }
  30. /* istanbul ignore if */
  31. if (isDef(el._enterCb) || el.nodeType !== 1) {
  32. return
  33. }
  34. const {
  35. css,
  36. type,
  37. enterClass,
  38. enterToClass,
  39. enterActiveClass,
  40. appearClass,
  41. appearToClass,
  42. appearActiveClass,
  43. beforeEnter,
  44. enter,
  45. afterEnter,
  46. enterCancelled,
  47. beforeAppear,
  48. appear,
  49. afterAppear,
  50. appearCancelled,
  51. duration
  52. } = data
  53. // activeInstance will always be the <transition> component managing this
  54. // transition. One edge case to check is when the <transition> is placed
  55. // as the root node of a child component. In that case we need to check
  56. // <transition>'s parent for appear check.
  57. let context = activeInstance
  58. let transitionNode = activeInstance.$vnode
  59. while (transitionNode && transitionNode.parent) {
  60. context = transitionNode.context
  61. transitionNode = transitionNode.parent
  62. }
  63. const isAppear = !context._isMounted || !vnode.isRootInsert
  64. if (isAppear && !appear && appear !== '') {
  65. return
  66. }
  67. const startClass = isAppear && appearClass
  68. ? appearClass
  69. : enterClass
  70. const activeClass = isAppear && appearActiveClass
  71. ? appearActiveClass
  72. : enterActiveClass
  73. const toClass = isAppear && appearToClass
  74. ? appearToClass
  75. : enterToClass
  76. const beforeEnterHook = isAppear
  77. ? (beforeAppear || beforeEnter)
  78. : beforeEnter
  79. const enterHook = isAppear
  80. ? (typeof appear === 'function' ? appear : enter)
  81. : enter
  82. const afterEnterHook = isAppear
  83. ? (afterAppear || afterEnter)
  84. : afterEnter
  85. const enterCancelledHook = isAppear
  86. ? (appearCancelled || enterCancelled)
  87. : enterCancelled
  88. const explicitEnterDuration: any = toNumber(
  89. isObject(duration)
  90. ? duration.enter
  91. : duration
  92. )
  93. if (process.env.NODE_ENV !== 'production' && explicitEnterDuration != null) {
  94. checkDuration(explicitEnterDuration, 'enter', vnode)
  95. }
  96. const expectsCSS = css !== false && !isIE9
  97. const userWantsControl = getHookArgumentsLength(enterHook)
  98. const cb = el._enterCb = once(() => {
  99. if (expectsCSS) {
  100. removeTransitionClass(el, toClass)
  101. removeTransitionClass(el, activeClass)
  102. }
  103. if (cb.cancelled) {
  104. if (expectsCSS) {
  105. removeTransitionClass(el, startClass)
  106. }
  107. enterCancelledHook && enterCancelledHook(el)
  108. } else {
  109. afterEnterHook && afterEnterHook(el)
  110. }
  111. el._enterCb = null
  112. })
  113. if (!vnode.data.show) {
  114. // remove pending leave element on enter by injecting an insert hook
  115. mergeVNodeHook(vnode, 'insert', () => {
  116. const parent = el.parentNode
  117. const pendingNode = parent && parent._pending && parent._pending[vnode.key]
  118. if (pendingNode &&
  119. pendingNode.tag === vnode.tag &&
  120. pendingNode.elm._leaveCb
  121. ) {
  122. pendingNode.elm._leaveCb()
  123. }
  124. enterHook && enterHook(el, cb)
  125. })
  126. }
  127. // start enter transition
  128. beforeEnterHook && beforeEnterHook(el)
  129. if (expectsCSS) {
  130. addTransitionClass(el, startClass)
  131. addTransitionClass(el, activeClass)
  132. nextFrame(() => {
  133. removeTransitionClass(el, startClass)
  134. if (!cb.cancelled) {
  135. addTransitionClass(el, toClass)
  136. if (!userWantsControl) {
  137. if (isValidDuration(explicitEnterDuration)) {
  138. setTimeout(cb, explicitEnterDuration)
  139. } else {
  140. whenTransitionEnds(el, type, cb)
  141. }
  142. }
  143. }
  144. })
  145. }
  146. if (vnode.data.show) {
  147. toggleDisplay && toggleDisplay()
  148. enterHook && enterHook(el, cb)
  149. }
  150. if (!expectsCSS && !userWantsControl) {
  151. cb()
  152. }
  153. }
  154. export function leave (vnode: VNodeWithData, rm: Function) {
  155. const el: any = vnode.elm
  156. // call enter callback now
  157. if (isDef(el._enterCb)) {
  158. el._enterCb.cancelled = true
  159. el._enterCb()
  160. }
  161. const data = resolveTransition(vnode.data.transition)
  162. if (isUndef(data) || el.nodeType !== 1) {
  163. return rm()
  164. }
  165. /* istanbul ignore if */
  166. if (isDef(el._leaveCb)) {
  167. return
  168. }
  169. const {
  170. css,
  171. type,
  172. leaveClass,
  173. leaveToClass,
  174. leaveActiveClass,
  175. beforeLeave,
  176. leave,
  177. afterLeave,
  178. leaveCancelled,
  179. delayLeave,
  180. duration
  181. } = data
  182. const expectsCSS = css !== false && !isIE9
  183. const userWantsControl = getHookArgumentsLength(leave)
  184. const explicitLeaveDuration: any = toNumber(
  185. isObject(duration)
  186. ? duration.leave
  187. : duration
  188. )
  189. if (process.env.NODE_ENV !== 'production' && isDef(explicitLeaveDuration)) {
  190. checkDuration(explicitLeaveDuration, 'leave', vnode)
  191. }
  192. const cb = el._leaveCb = once(() => {
  193. if (el.parentNode && el.parentNode._pending) {
  194. el.parentNode._pending[vnode.key] = null
  195. }
  196. if (expectsCSS) {
  197. removeTransitionClass(el, leaveToClass)
  198. removeTransitionClass(el, leaveActiveClass)
  199. }
  200. if (cb.cancelled) {
  201. if (expectsCSS) {
  202. removeTransitionClass(el, leaveClass)
  203. }
  204. leaveCancelled && leaveCancelled(el)
  205. } else {
  206. rm()
  207. afterLeave && afterLeave(el)
  208. }
  209. el._leaveCb = null
  210. })
  211. if (delayLeave) {
  212. delayLeave(performLeave)
  213. } else {
  214. performLeave()
  215. }
  216. function performLeave () {
  217. // the delayed leave may have already been cancelled
  218. if (cb.cancelled) {
  219. return
  220. }
  221. // record leaving element
  222. if (!vnode.data.show && el.parentNode) {
  223. (el.parentNode._pending || (el.parentNode._pending = {}))[(vnode.key: any)] = vnode
  224. }
  225. beforeLeave && beforeLeave(el)
  226. if (expectsCSS) {
  227. addTransitionClass(el, leaveClass)
  228. addTransitionClass(el, leaveActiveClass)
  229. nextFrame(() => {
  230. removeTransitionClass(el, leaveClass)
  231. if (!cb.cancelled) {
  232. addTransitionClass(el, leaveToClass)
  233. if (!userWantsControl) {
  234. if (isValidDuration(explicitLeaveDuration)) {
  235. setTimeout(cb, explicitLeaveDuration)
  236. } else {
  237. whenTransitionEnds(el, type, cb)
  238. }
  239. }
  240. }
  241. })
  242. }
  243. leave && leave(el, cb)
  244. if (!expectsCSS && !userWantsControl) {
  245. cb()
  246. }
  247. }
  248. }
  249. // only used in dev mode
  250. function checkDuration (val, name, vnode) {
  251. if (typeof val !== 'number') {
  252. warn(
  253. `<transition> explicit ${name} duration is not a valid number - ` +
  254. `got ${JSON.stringify(val)}.`,
  255. vnode.context
  256. )
  257. } else if (isNaN(val)) {
  258. warn(
  259. `<transition> explicit ${name} duration is NaN - ` +
  260. 'the duration expression might be incorrect.',
  261. vnode.context
  262. )
  263. }
  264. }
  265. function isValidDuration (val) {
  266. return typeof val === 'number' && !isNaN(val)
  267. }
  268. /**
  269. * Normalize a transition hook's argument length. The hook may be:
  270. * - a merged hook (invoker) with the original in .fns
  271. * - a wrapped component method (check ._length)
  272. * - a plain function (.length)
  273. */
  274. function getHookArgumentsLength (fn: Function): boolean {
  275. if (isUndef(fn)) {
  276. return false
  277. }
  278. const invokerFns = fn.fns
  279. if (isDef(invokerFns)) {
  280. // invoker
  281. return getHookArgumentsLength(
  282. Array.isArray(invokerFns)
  283. ? invokerFns[0]
  284. : invokerFns
  285. )
  286. } else {
  287. return (fn._length || fn.length) > 1
  288. }
  289. }
  290. function _enter (_: any, vnode: VNodeWithData) {
  291. if (vnode.data.show !== true) {
  292. enter(vnode)
  293. }
  294. }
  295. export default inBrowser ? {
  296. create: _enter,
  297. activate: _enter,
  298. remove (vnode: VNode, rm: Function) {
  299. /* istanbul ignore else */
  300. if (vnode.data.show !== true) {
  301. leave(vnode, rm)
  302. } else {
  303. rm()
  304. }
  305. }
  306. } : {}