]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(transition): handle cancel hooks
authorEvan You <yyx990803@gmail.com>
Fri, 22 Nov 2019 22:10:17 +0000 (17:10 -0500)
committerEvan You <yyx990803@gmail.com>
Fri, 22 Nov 2019 22:10:17 +0000 (17:10 -0500)
packages/runtime-core/src/components/Transition.ts
packages/runtime-core/src/vnode.ts

index 501ffc3c33b8b2032a99a733da379c113e00c134..34d9e5f199d3049c446c72f42730c307bf78d295 100644 (file)
@@ -1,6 +1,5 @@
 import {
   getCurrentInstance,
-  ComponentInternalInstance,
   SetupContext,
   ComponentOptions
 } from '../component'
@@ -30,17 +29,38 @@ export interface TransitionProps {
   onLeaveCancelled?: (el: any) => void
 }
 
+type TransitionHookCaller = (
+  hook: ((el: any) => void) | undefined,
+  args?: any[]
+) => void
+
+interface PendingCallbacks {
+  enter?: (cancelled?: boolean) => void
+  leave?: (cancelled?: boolean) => void
+}
+
 const TransitionImpl = {
   name: `BaseTransition`,
   setup(props: TransitionProps, { slots }: SetupContext) {
     const instance = getCurrentInstance()!
     let isLeaving = false
     let isMounted = false
+    const pendingCallbacks: PendingCallbacks = {}
 
     onMounted(() => {
       isMounted = true
     })
 
+    const callTransitionHook: TransitionHookCaller = (hook, args) => {
+      hook &&
+        callWithAsyncErrorHandling(
+          hook,
+          instance,
+          ErrorCodes.TRANSITION_HOOK,
+          args
+        )
+    }
+
     return () => {
       const children = slots.default && slots.default()
       if (!children || !children.length) {
@@ -72,10 +92,11 @@ const TransitionImpl = {
 
       let delayedLeave: (() => void) | undefined
       const performDelayedLeave = () => delayedLeave && delayedLeave()
-      const transitionData = (child.transition = resolveTransitionData(
-        instance,
+      const transitionHooks = (child.transition = resolveTransitionHooks(
         rawProps,
+        callTransitionHook,
         isMounted,
+        pendingCallbacks,
         performDelayedLeave
       ))
 
@@ -92,18 +113,18 @@ const TransitionImpl = {
       ) {
         // update old tree's hooks in case of dynamic transition
         // need to do this recursively in case of HOCs
-        updateHOCTransitionData(oldChild, transitionData)
+        updateHOCTransitionData(oldChild, transitionHooks)
         // switching between different views
         if (mode === 'out-in') {
           isLeaving = true
           // return placeholder node and queue update when leave finishes
-          transitionData.afterLeave = () => {
+          transitionHooks.afterLeave = () => {
             isLeaving = false
             instance.update()
           }
           return placeholder(child)
         } else if (mode === 'in-out') {
-          transitionData.delayLeave = performLeave => {
+          transitionHooks.delayLeave = performLeave => {
             delayedLeave = performLeave
           }
         }
@@ -139,7 +160,7 @@ export const Transition = (TransitionImpl as any) as {
   }
 }
 
-export interface TransitionData {
+export interface TransitionHooks {
   beforeEnter(el: object): void
   enter(el: object): void
   leave(el: object, remove: () => void): void
@@ -147,8 +168,7 @@ export interface TransitionData {
   delayLeave?(performLeave: () => void): void
 }
 
-function resolveTransitionData(
-  instance: ComponentInternalInstance,
+function resolveTransitionHooks(
   {
     appear,
     onBeforeEnter,
@@ -160,66 +180,59 @@ function resolveTransitionData(
     onAfterLeave,
     onLeaveCancelled
   }: TransitionProps,
+  callHook: TransitionHookCaller,
   isMounted: boolean,
+  pendingCallbacks: PendingCallbacks,
   performDelayedLeave: () => void
-): TransitionData {
-  // TODO handle cancel hooks
+): TransitionHooks {
   return {
     beforeEnter(el) {
       if (!isMounted && !appear) {
         return
       }
-      onBeforeEnter &&
-        callWithAsyncErrorHandling(
-          onBeforeEnter,
-          instance,
-          ErrorCodes.TRANSITION_HOOK,
-          [el]
-        )
+      if (pendingCallbacks.leave) {
+        pendingCallbacks.leave(true /* cancelled */)
+      }
+      callHook(onBeforeEnter, [el])
     },
+
     enter(el) {
       if (!isMounted && !appear) {
         return
       }
-      const done = () => {
-        onAfterEnter &&
-          callWithAsyncErrorHandling(
-            onAfterEnter,
-            instance,
-            ErrorCodes.TRANSITION_HOOK,
-            [el]
-          )
-        performDelayedLeave()
-      }
+      const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
+        if (cancelled) {
+          callHook(onEnterCancelled, [el])
+        } else {
+          callHook(onAfterEnter, [el])
+          performDelayedLeave()
+        }
+        pendingCallbacks.enter = undefined
+      })
       if (onEnter) {
-        onEnter(el, done)
+        onEnter(el, afterEnter)
       } else {
-        done()
+        afterEnter()
       }
     },
+
     leave(el, remove) {
-      onBeforeLeave &&
-        callWithAsyncErrorHandling(
-          onBeforeLeave,
-          instance,
-          ErrorCodes.TRANSITION_HOOK,
-          [el]
-        )
-      const afterLeave = () =>
-        onAfterLeave &&
-        callWithAsyncErrorHandling(
-          onAfterLeave,
-          instance,
-          ErrorCodes.TRANSITION_HOOK,
-          [el]
-        )
+      if (pendingCallbacks.enter) {
+        pendingCallbacks.enter(true /* cancelled */)
+      }
+      callHook(onBeforeLeave, [el])
+      const afterLeave = (pendingCallbacks.leave = (cancelled?) => {
+        remove()
+        if (cancelled) {
+          callHook(onLeaveCancelled, [el])
+        } else {
+          callHook(onAfterLeave, [el])
+        }
+        pendingCallbacks.leave = undefined
+      })
       if (onLeave) {
-        onLeave(el, () => {
-          remove()
-          afterLeave()
-        })
+        onLeave(el, afterLeave)
       } else {
-        remove()
         afterLeave()
       }
     }
@@ -238,7 +251,7 @@ function placeholder(vnode: VNode): VNode | undefined {
   }
 }
 
-function updateHOCTransitionData(vnode: VNode, data: TransitionData) {
+function updateHOCTransitionData(vnode: VNode, data: TransitionHooks) {
   if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
     updateHOCTransitionData(vnode.component!.subTree, data)
   } else {
index c7b32bdfe4f46413a8095e78db664b4ea56b19de..a56cc976426381defe59aa9962f93c04b68a52ec 100644 (file)
@@ -19,7 +19,7 @@ import { AppContext } from './apiApp'
 import { SuspenseBoundary } from './components/Suspense'
 import { DirectiveBinding } from './directives'
 import { SuspenseImpl } from './components/Suspense'
-import { TransitionData } from './components/Transition'
+import { TransitionHooks } from './components/Transition'
 
 export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
   __isFragment: true
@@ -93,7 +93,7 @@ export interface VNode<HostNode = any, HostElement = any> {
   component: ComponentInternalInstance | null
   suspense: SuspenseBoundary<HostNode, HostElement> | null
   dirs: DirectiveBinding[] | null
-  transition: TransitionData | null
+  transition: TransitionHooks | null
 
   // DOM
   el: HostNode | null