export type TransitionBlock = {
key?: any
transition?: TransitionHooks
- applyLeavingHooks?: (
+ applyTransitionLeavingHooks?: (
block: Block,
afterLeaveCb: () => void,
- ) => TransitionHooks
+ ) => TransitionHooks | undefined
}
export type BlockFn = (...args: any[]) => Block
insert?: (parent: ParentNode, anchor: Node | null) => void
remove?: (parent?: ParentNode) => void
transition?: TransitionHooks
- applyLeavingHooks?: (
+ applyTransitionLeavingHooks?: (
block: Block,
afterLeaveCb: () => void,
- ) => TransitionHooks
+ ) => TransitionHooks | undefined
constructor(nodes: Block) {
this.nodes = nodes
pauseTracking()
const parent = this.anchor.parentNode
- const renderNewBranch = () => {
+ const renderBranch = () => {
if (render) {
this.scope = new EffectScope()
this.nodes = this.scope.run(render) || []
this.scope = undefined
this.nodes = []
}
-
- if (this.fallback && !isValidBlock(this.nodes)) {
- parent && remove(this.nodes, parent, this.transition)
- this.nodes =
- (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
- []
- parent && insert(this.nodes, parent, this.anchor, this.transition)
- }
}
// teardown previous branch
this.scope.stop()
const mode = this.transition && this.transition.mode
if (mode) {
- const transition = this.applyLeavingHooks!(this.nodes, renderNewBranch)
+ const transition = this.applyTransitionLeavingHooks!(
+ this.nodes,
+ renderBranch,
+ )
parent && remove(this.nodes, parent, transition)
if (mode === 'out-in') {
resetTracking()
}
}
- renderNewBranch()
+ renderBranch()
+
+ if (this.fallback && !isValidBlock(this.nodes)) {
+ parent && remove(this.nodes, parent, this.transition)
+ this.nodes =
+ (this.scope || (this.scope = new EffectScope())).run(this.fallback) ||
+ []
+ parent && insert(this.nodes, parent, this.anchor, this.transition)
+ }
+
resetTracking()
}
}
): void {
anchor = anchor === 0 ? parent.firstChild : anchor
if (block instanceof Node) {
- if (transition) {
+ // don't apply transition on text or comment nodes
+ if (transition && block instanceof Element) {
applyTransitionEnter(
block,
transition,
transition: TransitionHooks | undefined = block.transition,
): void {
if (block instanceof Node) {
- if (transition) {
+ if (transition && block instanceof Element) {
applyTransitionLeave(
block,
transition,
leaveCbKey,
registerVaporTransition,
useTransitionState,
+ warn,
} from '@vue/runtime-dom'
import type { Block } from '../block'
import { isVaporComponent } from '../component'
slots: { default: () => Block },
) => {
const children = slots.default && slots.default()
- if (!children) {
- return
- }
+ if (!children) return
- // TODO find non-comment node
- const child = children
+ const child = findElementChild(children)
+ if (!child) return
const state = useTransitionState()
let enterHooks = resolveTransitionHooks(
setTransitionHooks(child, enterHooks)
const { mode } = props
- // TODO check mode
+ if (
+ __DEV__ &&
+ mode &&
+ mode !== 'in-out' &&
+ mode !== 'out-in' &&
+ mode !== 'default'
+ ) {
+ warn(`invalid <transition> mode: ${mode}`)
+ }
- child.applyLeavingHooks = (
- leavingBlock: Block,
+ child.applyTransitionLeavingHooks = (
+ block: Block,
afterLeaveCb: () => void,
) => {
+ const leavingBlock = findElementChild(block)
+ if (!leavingBlock) return undefined
+
let leavingHooks = resolveTransitionHooks(
leavingBlock as any,
props,
},
}
-function resolveTransitionHooks(
+const getTransitionHooksContext = (
+ leavingNodeCache: Record<string, Block>,
+ key: string,
block: Block,
props: TransitionProps,
state: TransitionState,
instance: GenericComponentInstance,
- postClone?: (hooks: TransitionHooks) => void,
-): TransitionHooks {
- const key = String(block.key)
- const leavingNodeCache = getLeavingNodesForBlock(state, block)
+ postClone: ((hooks: TransitionHooks) => void) | undefined,
+) => {
const context: TransitionHooksContext = {
setLeavingNodeCache: () => {
leavingNodeCache[key] = block
return hooks
},
}
+ return context
+}
+
+function resolveTransitionHooks(
+ block: Block,
+ props: TransitionProps,
+ state: TransitionState,
+ instance: GenericComponentInstance,
+ postClone?: (hooks: TransitionHooks) => void,
+): TransitionHooks {
+ const key = String(block.key)
+ const leavingNodeCache = getLeavingNodesForBlock(state, block)
+ const context = getTransitionHooksContext(
+ leavingNodeCache,
+ key,
+ block,
+ props,
+ state,
+ instance,
+ postClone,
+ )
return baseResolveTransitionHooks(context, props, state, instance)
}
}
function setTransitionHooks(block: Block, hooks: TransitionHooks) {
- if (isVaporComponent(block)) {
- setTransitionHooks(block.block, hooks)
+ block.transition = hooks
+}
+
+function findElementChild(block: Block): Block | undefined {
+ let child: Block | undefined
+ // transition can only be applied on Element child
+ if (block instanceof Element) {
+ child = block
+ } else if (isVaporComponent(block)) {
+ child = findElementChild(block.block)
+ } else if (Array.isArray(block)) {
+ child = block[0]
+ let hasFound = false
+ for (const c of block) {
+ const item = findElementChild(c)
+ if (item instanceof Element) {
+ if (__DEV__ && hasFound) {
+ // warn more than one non-comment child
+ warn(
+ '<transition> can only be used on a single element or component. ' +
+ 'Use <transition-group> for lists.',
+ )
+ break
+ }
+ child = item
+ hasFound = true
+ if (!__DEV__) break
+ }
+ }
} else {
- block.transition = hooks
+ // fragment
+ // store transition hooks on fragment itself, so it can apply to both
+ // previous and new branch during updates.
+ child = block
}
+
+ if (__DEV__ && !child) {
+ warn('Transition component has no valid child element')
+ }
+
+ return child
}
let registered = false