]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(transition): support in templates
authorEvan You <yyx990803@gmail.com>
Sun, 24 Nov 2019 23:37:59 +0000 (18:37 -0500)
committerEvan You <yyx990803@gmail.com>
Sun, 24 Nov 2019 23:37:59 +0000 (18:37 -0500)
packages/compiler-core/src/runtimeHelpers.ts
packages/compiler-core/src/transforms/transformElement.ts
packages/runtime-core/src/components/BaseTransition.ts
packages/runtime-dom/src/components/Transition.ts

index a6d679ef9d262c82eddef79cd28d909316f0574f..9ee8a4526944bef808324f7a8b41d82db8e6cfc8 100644 (file)
@@ -2,6 +2,8 @@ export const FRAGMENT = Symbol(__DEV__ ? `Fragment` : ``)
 export const PORTAL = Symbol(__DEV__ ? `Portal` : ``)
 export const SUSPENSE = Symbol(__DEV__ ? `Suspense` : ``)
 export const KEEP_ALIVE = Symbol(__DEV__ ? `KeepAlive` : ``)
+export const TRANSITION = Symbol(__DEV__ ? `Transition` : ``)
+export const BASE_TRANSITION = Symbol(__DEV__ ? `BaseTransition` : ``)
 export const OPEN_BLOCK = Symbol(__DEV__ ? `openBlock` : ``)
 export const CREATE_BLOCK = Symbol(__DEV__ ? `createBlock` : ``)
 export const CREATE_VNODE = Symbol(__DEV__ ? `createVNode` : ``)
@@ -30,6 +32,8 @@ export const helperNameMap: any = {
   [PORTAL]: `Portal`,
   [SUSPENSE]: `Suspense`,
   [KEEP_ALIVE]: `KeepAlive`,
+  [TRANSITION]: `Transition`,
+  [BASE_TRANSITION]: `BaseTransition`,
   [OPEN_BLOCK]: `openBlock`,
   [CREATE_BLOCK]: `createBlock`,
   [CREATE_VNODE]: `createVNode`,
index e7935a636ea3b75e6179d6dbf923d3e22a0ad317..e70588bbe2e9b3fbeb8f66e8e705a81568bc8720 100644 (file)
@@ -15,7 +15,7 @@ import {
   createObjectExpression,
   Property
 } from '../ast'
-import { PatchFlags, PatchFlagNames, isSymbol } from '@vue/shared'
+import { PatchFlags, PatchFlagNames, isSymbol, hyphenate } from '@vue/shared'
 import { createCompilerError, ErrorCodes } from '../errors'
 import {
   CREATE_VNODE,
@@ -27,7 +27,8 @@ import {
   TO_HANDLERS,
   PORTAL,
   SUSPENSE,
-  KEEP_ALIVE
+  KEEP_ALIVE,
+  TRANSITION
 } from '../runtimeHelpers'
 import { getInnerRange, isVSlot, toValidAssetId, findProp } from '../utils'
 import { buildSlots } from './vSlot'
@@ -37,6 +38,9 @@ import { isStaticNode } from './hoistStatic'
 // import, which should be used instead of a resolveDirective call.
 const directiveImportMap = new WeakMap<DirectiveNode, symbol>()
 
+const isBuiltInType = (tag: string, expected: string): boolean =>
+  tag === expected || tag === hyphenate(expected)
+
 // generate a JavaScript AST for this element's codegen
 export const transformElement: NodeTransform = (node, context) => {
   if (
@@ -53,9 +57,10 @@ export const transformElement: NodeTransform = (node, context) => {
   // processed and merged.
   return function postTransformElement() {
     const { tag, tagType, props } = node
-    const isPortal = tag === 'portal' || tag === 'Portal'
-    const isSuspense = tag === 'suspense' || tag === 'Suspense'
-    const isKeepAlive = tag === 'keep-alive' || tag === 'KeepAlive'
+    const isPortal = isBuiltInType(tag, 'Portal')
+    const isSuspense = isBuiltInType(tag, 'Suspense')
+    const isKeepAlive = isBuiltInType(tag, 'KeepAlive')
+    const isTransition = isBuiltInType(tag, 'Transition')
     const isComponent = tagType === ElementTypes.COMPONENT
 
     let hasProps = props.length > 0
@@ -96,6 +101,8 @@ export const transformElement: NodeTransform = (node, context) => {
       nodeType = context.helper(SUSPENSE)
     } else if (isKeepAlive) {
       nodeType = context.helper(KEEP_ALIVE)
+    } else if (isTransition) {
+      nodeType = context.helper(TRANSITION)
     } else if (isComponent) {
       // user component w/ resolve
       context.helper(RESOLVE_COMPONENT)
index 9ffa165c63be3b99ccf104684464d564dd1295be..528e9e9069b1ed0edd62fae49874cc4daa5e1b87 100644 (file)
@@ -9,6 +9,7 @@ import { isKeepAlive } from './KeepAlive'
 import { toRaw } from '@vue/reactivity'
 import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
 import { ShapeFlags } from '../shapeFlags'
+import { onBeforeUnmount, onMounted } from '../apiLifecycle'
 
 export interface BaseTransitionProps {
   mode?: 'in-out' | 'out-in' | 'default'
@@ -22,9 +23,8 @@ export interface BaseTransitionProps {
   persisted?: boolean
 
   // Hooks. Using camel casef for easier usage in render functions & JSX.
-  // In templates these will be written as @before-enter="xxx"
-  // The compiler has special handling to convert them into the proper cases.
-  // enter
+  // In templates these can be written as @before-enter="xxx" as prop names
+  // are camelized
   onBeforeEnter?: (el: any) => void
   onEnter?: (el: any, done: () => void) => void
   onAfterEnter?: (el: any) => void
@@ -41,17 +41,29 @@ type TransitionHookCaller = (
   args?: any[]
 ) => void
 
-interface PendingCallbacks {
-  enter?: (cancelled?: boolean) => void
-  leave?: (cancelled?: boolean) => void
+interface TransitionState {
+  isMounted: boolean
+  isLeaving: boolean
+  isUnmounting: boolean
+  pendingEnter?: (cancelled?: boolean) => void
+  pendingLeave?: (cancelled?: boolean) => void
 }
 
 const BaseTransitionImpl = {
   name: `BaseTransition`,
   setup(props: BaseTransitionProps, { slots }: SetupContext) {
     const instance = getCurrentInstance()!
-    const pendingCallbacks: PendingCallbacks = {}
-    let isLeaving = false
+    const state: TransitionState = {
+      isMounted: false,
+      isLeaving: false,
+      isUnmounting: false
+    }
+    onMounted(() => {
+      state.isMounted = true
+    })
+    onBeforeUnmount(() => {
+      state.isUnmounting = true
+    })
 
     const callTransitionHook: TransitionHookCaller = (hook, args) => {
       hook &&
@@ -88,17 +100,17 @@ const BaseTransitionImpl = {
 
       // at this point children has a guaranteed length of 1.
       const child = children[0]
-      if (isLeaving) {
+      if (state.isLeaving) {
         return placeholder(child)
       }
 
       let delayedLeave: (() => void) | undefined
       const performDelayedLeave = () => delayedLeave && delayedLeave()
+
       const transitionHooks = (child.transition = resolveTransitionHooks(
         rawProps,
+        state,
         callTransitionHook,
-        instance.isMounted,
-        pendingCallbacks,
         performDelayedLeave
       ))
 
@@ -118,10 +130,10 @@ const BaseTransitionImpl = {
         updateHOCTransitionData(oldChild, transitionHooks)
         // switching between different views
         if (mode === 'out-in') {
-          isLeaving = true
+          state.isLeaving = true
           // return placeholder node and queue update when leave finishes
           transitionHooks.afterLeave = () => {
-            isLeaving = false
+            state.isLeaving = false
             instance.update()
           }
           return placeholder(child)
@@ -187,29 +199,28 @@ function resolveTransitionHooks(
     onAfterLeave,
     onLeaveCancelled
   }: BaseTransitionProps,
+  state: TransitionState,
   callHook: TransitionHookCaller,
-  isMounted: boolean,
-  pendingCallbacks: PendingCallbacks,
   performDelayedLeave: () => void
 ): TransitionHooks {
   return {
     persisted,
     beforeEnter(el) {
-      if (!isMounted && !appear) {
-        return
+      if (state.pendingLeave) {
+        state.pendingLeave(true /* cancelled */)
       }
-      if (pendingCallbacks.leave) {
-        pendingCallbacks.leave(true /* cancelled */)
+      if (!appear && !state.isMounted) {
+        return
       }
       callHook(onBeforeEnter, [el])
     },
 
     enter(el) {
-      if (!isMounted && !appear) {
+      if (!appear && !state.isMounted) {
         return
       }
       let called = false
-      const afterEnter = (pendingCallbacks.enter = (cancelled?) => {
+      const afterEnter = (state.pendingEnter = (cancelled?) => {
         if (called) return
         called = true
         if (cancelled) {
@@ -218,7 +229,7 @@ function resolveTransitionHooks(
           callHook(onAfterEnter, [el])
           performDelayedLeave()
         }
-        pendingCallbacks.enter = undefined
+        state.pendingEnter = undefined
       })
       if (onEnter) {
         onEnter(el, afterEnter)
@@ -228,12 +239,15 @@ function resolveTransitionHooks(
     },
 
     leave(el, remove) {
-      if (pendingCallbacks.enter) {
-        pendingCallbacks.enter(true /* cancelled */)
+      if (state.pendingEnter) {
+        state.pendingEnter(true /* cancelled */)
+      }
+      if (state.isUnmounting) {
+        return remove()
       }
       callHook(onBeforeLeave, [el])
       let called = false
-      const afterLeave = (pendingCallbacks.leave = (cancelled?) => {
+      const afterLeave = (state.pendingLeave = (cancelled?) => {
         if (called) return
         called = true
         remove()
@@ -242,7 +256,7 @@ function resolveTransitionHooks(
         } else {
           callHook(onAfterLeave, [el])
         }
-        pendingCallbacks.leave = undefined
+        state.pendingLeave = undefined
       })
       if (onLeave) {
         onLeave(el, afterLeave)
index b61fe25b405444c2882d68f769c9e24112627f16..18f15ddcedb555b51e2a8836f134f4a11b9c6210 100644 (file)
@@ -16,6 +16,7 @@ const ANIMATION = 'animation'
 export interface TransitionProps extends BaseTransitionProps {
   name?: string
   type?: typeof TRANSITION | typeof ANIMATION
+  css?: boolean
   duration?: number | { enter: number; leave: number }
   // custom transition classes
   enterFromClass?: string
@@ -41,6 +42,10 @@ if (__DEV__) {
     ...(BaseTransition as any).props,
     name: String,
     type: String,
+    // Cannot use Boolean otherwise it will be force casted to false when
+    // omitted
+    css: null,
+    duration: Object,
     enterFromClass: String,
     enterActiveClass: String,
     enterToClass: String,
@@ -49,14 +54,14 @@ if (__DEV__) {
     appearToClass: String,
     leaveFromClass: String,
     leaveActiveClass: String,
-    leaveToClass: String,
-    duration: Object
+    leaveToClass: String
   }
 }
 
 function resolveTransitionProps({
   name = 'v',
   type,
+  css = true,
   duration,
   enterFromClass = `${name}-enter-from`,
   enterActiveClass = `${name}-enter-active`,
@@ -69,6 +74,10 @@ function resolveTransitionProps({
   leaveToClass = `${name}-leave-to`,
   ...baseProps
 }: TransitionProps): BaseTransitionProps {
+  if (!css) {
+    return baseProps
+  }
+
   const instance = getCurrentInstance()!
   const durations = normalizeDuration(duration)
   const enterDuration = durations && durations[0]