isFunction,
isArray,
isObject,
- isReservedProp,
hasOwn,
toRawType,
PatchFlags,
if (rawProps != null) {
for (const key in rawProps) {
- // key, ref are reserved
- if (isReservedProp(key)) continue
+ // key, ref are reserved and never passed down
+ if (key === 'key' || key === 'ref') continue
// prop option names are camelized during normalization, so to support
// kebab -> camel conversion here we need to camelize the key.
const camelKey = camelize(key)
)
}
}
+
+ // inherit transition data
+ if (vnode.transition != null) {
+ // TODO warn if component has transition data but root is a fragment
+ result.transition = vnode.transition
+ }
} catch (err) {
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
result = createVNode(Comment)
import { createComponent } from '../apiCreateComponent'
-import { getCurrentInstance } from '../component'
-import {
- cloneVNode,
- Comment,
- isSameVNodeType,
- VNodeProps,
- VNode,
- mergeProps
-} from '../vnode'
+import { getCurrentInstance, ComponentInternalInstance } from '../component'
+import { cloneVNode, Comment, isSameVNodeType, VNode } from '../vnode'
import { warn } from '../warning'
import { isKeepAlive } from './KeepAlive'
import { toRaw } from '@vue/reactivity'
import { onMounted } from '../apiLifecycle'
+import { callWithAsyncErrorHandling, ErrorCodes } from '../errorHandling'
+import { ShapeFlags } from '../shapeFlags'
// Using camel case here makes it easier to use in render functions & JSX.
// In templates these will be written as @before-enter="xxx"
}
// at this point children has a guaranteed length of 1.
- const rawChild = children[0]
+ const child = children[0]
if (isLeaving) {
- return placeholder(rawChild)
+ return placeholder(child)
}
- rawChild.transition = rawProps
+ let delayedLeave: (() => void) | undefined
+ const performDelayedLeave = () => delayedLeave && delayedLeave()
+ const transitionData = (child.transition = resolveTransitionData(
+ instance,
+ rawProps,
+ isMounted,
+ performDelayedLeave
+ ))
+
// clone old subTree because we need to modify it
const oldChild = instance.subTree
? (instance.subTree = cloneVNode(instance.subTree))
: null
// handle mode
- let performDelayedLeave: (() => void) | undefined
if (
oldChild &&
- !isSameVNodeType(rawChild, oldChild) &&
+ !isSameVNodeType(child, oldChild) &&
oldChild.type !== Comment
) {
// update old tree's hooks in case of dynamic transition
- oldChild.transition = rawProps
+ // need to do this recursively in case of HOCs
+ updateHOCTransitionData(oldChild, transitionData)
// switching between different views
if (mode === 'out-in') {
isLeaving = true
// return placeholder node and queue update when leave finishes
- oldChild.props = mergeProps(oldChild.props!, {
- onVnodeRemoved() {
- isLeaving = false
- instance.update()
- }
- })
- return placeholder(rawChild)
+ transitionData.afterLeave = () => {
+ isLeaving = false
+ instance.update()
+ }
+ return placeholder(child)
} else if (mode === 'in-out') {
- let delayedLeave: () => void
- performDelayedLeave = () => delayedLeave()
- oldChild.props = mergeProps(oldChild.props!, {
- onVnodeDelayLeave(performLeave) {
- delayedLeave = performLeave
- }
- })
+ transitionData.delayLeave = performLeave => {
+ delayedLeave = performLeave
+ }
}
}
- return cloneVNode(
- rawChild,
- resolveTransitionInjections(rawProps, isMounted, performDelayedLeave)
- )
+ return child
}
}
})
}
}
-function resolveTransitionInjections(
+export interface TransitionData {
+ beforeEnter(el: object): void
+ enter(el: object): void
+ leave(el: object, remove: () => void): void
+ afterLeave?(): void
+ delayLeave?(performLeave: () => void): void
+}
+
+function resolveTransitionData(
+ instance: ComponentInternalInstance,
{
appear,
onBeforeEnter,
onLeaveCancelled
}: TransitionProps,
isMounted: boolean,
- performDelayedLeave?: () => void
-): VNodeProps {
- // TODO handle appear
+ performDelayedLeave: () => void
+): TransitionData {
// TODO handle cancel hooks
return {
- onVnodeBeforeMount(vnode) {
+ beforeEnter(el) {
if (!isMounted && !appear) {
return
}
- onBeforeEnter && onBeforeEnter(vnode.el)
+ onBeforeEnter &&
+ callWithAsyncErrorHandling(
+ onBeforeEnter,
+ instance,
+ ErrorCodes.TRANSITION_HOOK,
+ [el]
+ )
},
- onVnodeMounted({ el }) {
+ enter(el) {
if (!isMounted && !appear) {
return
}
const done = () => {
- onAfterEnter && onAfterEnter(el)
- performDelayedLeave && performDelayedLeave()
+ onAfterEnter &&
+ callWithAsyncErrorHandling(
+ onAfterEnter,
+ instance,
+ ErrorCodes.TRANSITION_HOOK,
+ [el]
+ )
+ performDelayedLeave()
}
if (onEnter) {
onEnter(el, done)
done()
}
},
- onVnodeBeforeRemove({ el }, remove) {
- onBeforeLeave && onBeforeLeave(el)
+ leave(el, remove) {
+ onBeforeLeave &&
+ callWithAsyncErrorHandling(
+ onBeforeLeave,
+ instance,
+ ErrorCodes.TRANSITION_HOOK,
+ [el]
+ )
+ const afterLeave = () =>
+ onAfterLeave &&
+ callWithAsyncErrorHandling(
+ onAfterLeave,
+ instance,
+ ErrorCodes.TRANSITION_HOOK,
+ [el]
+ )
if (onLeave) {
onLeave(el, () => {
remove()
- onAfterLeave && onAfterLeave(el)
+ afterLeave()
})
} else {
remove()
- onAfterLeave && onAfterLeave(el)
+ afterLeave()
}
}
}
return vnode
}
}
+
+function updateHOCTransitionData(vnode: VNode, data: TransitionData) {
+ if (vnode.shapeFlag & ShapeFlags.COMPONENT) {
+ updateHOCTransitionData(vnode.component!.subTree, data)
+ } else {
+ vnode.transition = data
+ }
+}
NATIVE_EVENT_HANDLER,
COMPONENT_EVENT_HANDLER,
DIRECTIVE_HOOK,
+ TRANSITION_HOOK,
APP_ERROR_HANDLER,
APP_WARN_HANDLER,
FUNCTION_REF,
[ErrorCodes.NATIVE_EVENT_HANDLER]: 'native event handler',
[ErrorCodes.COMPONENT_EVENT_HANDLER]: 'component event handler',
[ErrorCodes.DIRECTIVE_HOOK]: 'directive hook',
+ [ErrorCodes.TRANSITION_HOOK]: 'transition hook',
[ErrorCodes.APP_ERROR_HANDLER]: 'app errorHandler',
[ErrorCodes.APP_WARN_HANDLER]: 'app warnHandler',
[ErrorCodes.FUNCTION_REF]: 'ref function',
EMPTY_ARR,
isReservedProp,
isFunction,
- PatchFlags,
- isArray
+ PatchFlags
} from '@vue/shared'
import { queueJob, queuePostFlushCb, flushPostFlushCbs } from './scheduler'
import {
queueEffectWithSuspense,
SuspenseImpl
} from './components/Suspense'
-import {
- ErrorCodes,
- callWithErrorHandling,
- callWithAsyncErrorHandling
-} from './errorHandling'
+import { ErrorCodes, callWithErrorHandling } from './errorHandling'
import { KeepAliveSink, isKeepAlive } from './components/KeepAlive'
export interface RendererOptions<HostNode = any, HostElement = any> {
const tag = vnode.type as string
isSVG = isSVG || tag === 'svg'
const el = (vnode.el = hostCreateElement(tag, isSVG))
- const { props, shapeFlag } = vnode
+ const { props, shapeFlag, transition } = vnode
if (props != null) {
for (const key in props) {
if (isReservedProp(key)) continue
invokeDirectiveHook(props.onVnodeBeforeMount, parentComponent, vnode)
}
}
+ if (transition != null) {
+ transition.beforeEnter(el)
+ }
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, vnode.children as string)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
)
}
hostInsert(el, container, anchor)
- if (props != null && props.onVnodeMounted != null) {
+ const vnodeMountedHook = props && props.onVnodeMounted
+ if (vnodeMountedHook != null || transition != null) {
queuePostRenderEffect(() => {
- invokeDirectiveHook(props.onVnodeMounted!, parentComponent, vnode)
+ vnodeMountedHook &&
+ invokeDirectiveHook(vnodeMountedHook, parentComponent, vnode)
+ transition && transition.enter(el)
}, parentSuspense)
}
}
doRemove?: boolean
) {
const {
+ el,
props,
ref,
type,
children,
dynamicChildren,
shapeFlag,
- anchor
+ anchor,
+ transition
} = vnode
// unset ref
}
if (doRemove) {
- const beforeRemoveHooks = props && props.onVnodeBeforeRemove
const remove = () => {
hostRemove(vnode.el!)
if (anchor != null) hostRemove(anchor)
- const removedHook = props && props.onVnodeRemoved
- removedHook && removedHook()
- }
- if (vnode.shapeFlag & ShapeFlags.ELEMENT && beforeRemoveHooks != null) {
- const delayLeave = props && props.onVnodeDelayLeave
- const performLeave = () => {
- invokeBeforeRemoveHooks(
- beforeRemoveHooks,
- parentComponent,
- vnode,
- remove
- )
+ if (transition != null && transition.afterLeave) {
+ transition.afterLeave()
}
+ }
+ if (vnode.shapeFlag & ShapeFlags.ELEMENT && transition != null) {
+ const { leave, delayLeave } = transition
+ const performLeave = () => leave(el!, remove)
if (delayLeave) {
delayLeave(performLeave)
} else {
}
}
- function invokeBeforeRemoveHooks(
- hooks: ((...args: any[]) => any) | ((...args: any[]) => any)[],
- instance: ComponentInternalInstance | null,
- vnode: HostVNode,
- done: () => void
- ) {
- if (!isArray(hooks)) {
- hooks = [hooks]
- }
- let delayedRemoveCount = hooks.length
- const doneRemove = () => {
- delayedRemoveCount--
- if (allHooksCalled && !delayedRemoveCount) {
- done()
- }
- }
- let allHooksCalled = false
- for (let i = 0; i < hooks.length; i++) {
- callWithAsyncErrorHandling(
- hooks[i],
- instance,
- ErrorCodes.DIRECTIVE_HOOK,
- [vnode, doneRemove]
- )
- }
- allHooksCalled = true
- if (!delayedRemoveCount) {
- done()
- }
- }
-
function unmountComponent(
instance: ComponentInternalInstance,
parentSuspense: HostSuspenseBoundary | null,
import { SuspenseBoundary } from './components/Suspense'
import { DirectiveBinding } from './directives'
import { SuspenseImpl } from './components/Suspense'
-import { TransitionProps } from './components/Transition'
+import { TransitionData } from './components/Transition'
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
onVnodeUpdated?: (vnode: VNode, oldVNode: VNode) => void
onVnodeBeforeUnmount?: (vnode: VNode) => void
onVnodeUnmounted?: (vnode: VNode) => void
-
- // transition hooks, internal.
- onVnodeDelayLeave?: (performLeave: () => void) => void
- onVnodeBeforeRemove?: (vnode: VNode, remove: () => void) => void
- onVnodeRemoved?: () => void
}
type VNodeChildAtom<HostNode, HostElement> =
component: ComponentInternalInstance | null
suspense: SuspenseBoundary<HostNode, HostElement> | null
dirs: DirectiveBinding[] | null
- transition: TransitionProps | null
+ transition: TransitionData | null
// DOM
el: HostNode | null