type Hook<T = () => void> = T | T[]
-const leaveCbKey: unique symbol = Symbol('_leaveCb')
+export const leaveCbKey: unique symbol = Symbol('_leaveCb')
const enterCbKey: unique symbol = Symbol('_enterCb')
export interface BaseTransitionProps<HostElement = RendererElement> {
isUnmounting: boolean
// Track pending leave callbacks for children of the same key.
// This is used to force remove leaving a child when a new copy is entering.
- leavingVNodes: Map<any, Record<string, VNode>>
+ leavingVNodes: Map<any, Record<string, any>>
}
export interface TransitionElement {
return leavingVNodesCache
}
+export interface TransitionHooksContext {
+ setLeavingNodeCache: () => void
+ unsetLeavingNodeCache: () => void
+ earlyRemove: () => void
+ cloneHooks: (node: any) => TransitionHooks
+}
+
// The transition hooks are attached to the vnode as vnode.transition
// and will be called at appropriate timing in the renderer.
export function resolveTransitionHooks(
instance: GenericComponentInstance,
postClone?: (hooks: TransitionHooks) => void,
): TransitionHooks {
+ const key = String(vnode.key)
+ const leavingVNodesCache = getLeavingNodesForType(state, vnode)
+ const context: TransitionHooksContext = {
+ setLeavingNodeCache: () => {
+ leavingVNodesCache[key] = vnode
+ },
+ unsetLeavingNodeCache: () => {
+ if (leavingVNodesCache[key] === vnode) {
+ delete leavingVNodesCache[key]
+ }
+ },
+ earlyRemove: () => {
+ const leavingVNode = leavingVNodesCache[key]
+ if (
+ leavingVNode &&
+ isSameVNodeType(vnode, leavingVNode) &&
+ (leavingVNode.el as TransitionElement)[leaveCbKey]
+ ) {
+ // force early removal (not cancelled)
+ ;(leavingVNode.el as TransitionElement)[leaveCbKey]!()
+ }
+ },
+ cloneHooks: vnode => {
+ const hooks = resolveTransitionHooks(
+ vnode,
+ props,
+ state,
+ instance,
+ postClone,
+ )
+ if (postClone) postClone(hooks)
+ return hooks
+ },
+ }
+
+ return baseResolveTransitionHooks(context, props, state, instance)
+}
+
+export function baseResolveTransitionHooks(
+ context: TransitionHooksContext,
+ props: BaseTransitionProps<any>,
+ state: TransitionState,
+ instance: GenericComponentInstance,
+): TransitionHooks {
+ const {
+ setLeavingNodeCache,
+ unsetLeavingNodeCache,
+ earlyRemove,
+ cloneHooks,
+ } = context
+
const {
appear,
mode,
onAfterAppear,
onAppearCancelled,
} = props
- const key = String(vnode.key)
- const leavingVNodesCache = getLeavingNodesForType(state, vnode)
const callHook: TransitionHookCaller = (hook, args) => {
hook &&
el[leaveCbKey](true /* cancelled */)
}
// for toggled element with same key (v-if)
- const leavingVNode = leavingVNodesCache[key]
- if (
- leavingVNode &&
- isSameVNodeType(vnode, leavingVNode) &&
- // TODO refactor
- ((leavingVNode.el || leavingVNode) as TransitionElement)[leaveCbKey]
- ) {
- // force early removal (not cancelled)
- ;((leavingVNode.el || leavingVNode) as TransitionElement)[leaveCbKey]!()
- }
+ earlyRemove()
callHook(hook, [el])
},
},
leave(el, remove) {
- const key = String(vnode.key)
+ // const key = String(vnode.key)
if (el[enterCbKey]) {
el[enterCbKey](true /* cancelled */)
}
callHook(onAfterLeave, [el])
}
el[leaveCbKey] = undefined
- if (leavingVNodesCache[key] === vnode) {
- delete leavingVNodesCache[key]
- }
+ unsetLeavingNodeCache()
})
- leavingVNodesCache[key] = vnode
+ setLeavingNodeCache()
if (onLeave) {
callAsyncHook(onLeave, [el, done])
} else {
}
},
- clone(vnode) {
- const hooks = resolveTransitionHooks(
- vnode,
- props,
- state,
- instance,
- postClone,
- )
- if (postClone) postClone(hooks)
- return hooks
+ clone(node) {
+ return cloneHooks(node)
},
}
import {
+ type GenericComponentInstance,
+ type TransitionElement,
type TransitionHooks,
+ type TransitionHooksContext,
type TransitionProps,
+ type TransitionState,
type VaporTransitionInterface,
+ baseResolveTransitionHooks,
currentInstance,
+ leaveCbKey,
registerVaporTransition,
- resolveTransitionHooks,
useTransitionState,
} from '@vue/runtime-dom'
import type { Block } from '../block'
},
}
+function resolveTransitionHooks(
+ block: Block & { key: string },
+ props: TransitionProps,
+ state: TransitionState,
+ instance: GenericComponentInstance,
+ postClone?: (hooks: TransitionHooks) => void,
+): TransitionHooks {
+ const key = String(block.key)
+ const leavingNodeCache = getLeavingNodesForBlock(state, block)
+ const context: TransitionHooksContext = {
+ setLeavingNodeCache: () => {
+ leavingNodeCache[key] = block
+ },
+ unsetLeavingNodeCache: () => {
+ if (leavingNodeCache[key] === block) {
+ delete leavingNodeCache[key]
+ }
+ },
+ earlyRemove: () => {
+ const leavingNode = leavingNodeCache[key]
+ if (leavingNode && (leavingNode as TransitionElement)[leaveCbKey]) {
+ // force early removal (not cancelled)
+ ;(leavingNode as TransitionElement)[leaveCbKey]!()
+ }
+ },
+ cloneHooks: block => {
+ const hooks = resolveTransitionHooks(
+ block,
+ props,
+ state,
+ instance,
+ postClone,
+ )
+ if (postClone) postClone(hooks)
+ return hooks
+ },
+ }
+ return baseResolveTransitionHooks(context, props, state, instance)
+}
+
+function getLeavingNodesForBlock(
+ state: TransitionState,
+ block: Block,
+): Record<string, Block> {
+ const { leavingVNodes } = state
+ let leavingNodesCache = leavingVNodes.get(block)!
+ if (!leavingNodesCache) {
+ leavingNodesCache = Object.create(null)
+ leavingVNodes.set(block, leavingNodesCache)
+ }
+ return leavingNodesCache
+}
+
function setTransitionHooks(block: Block, hooks: TransitionHooks) {
if (isVaporComponent(block)) {
setTransitionHooks(block.block, hooks)