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