vue3-core/packages/runtime-vapor/src/components/TransitionGroup.ts

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

225 lines
6.0 KiB
TypeScript
Raw Normal View History

import {
type ElementWithTransition,
type TransitionGroupProps,
TransitionPropsValidators,
baseApplyTranslation,
callPendingCbs,
currentInstance,
forceReflow,
handleMovedChildren,
hasCSSTransform,
onBeforeUpdate,
onUpdated,
resolveTransitionProps,
useTransitionState,
warn,
} from '@vue/runtime-dom'
import { extend, isArray } from '@vue/shared'
import {
type Block,
DynamicFragment,
type TransitionBlock,
type VaporTransitionHooks,
insert,
isFragment,
} from '../block'
import {
resolveTransitionHooks,
setTransitionHooks,
2025-03-16 10:00:19 +08:00
setTransitionHooksOnFragment,
} from './Transition'
2025-03-13 21:46:40 +08:00
import {
type ObjectVaporComponent,
type VaporComponentInstance,
2025-03-14 11:44:04 +08:00
applyFallthroughProps,
2025-03-13 21:46:40 +08:00
isVaporComponent,
} from '../component'
import { isForBlock } from '../apiCreateFor'
2025-03-14 11:44:04 +08:00
import { renderEffect } from '../renderEffect'
2025-03-17 11:40:10 +08:00
import { createElement } from '../dom/node'
const positionMap = new WeakMap<TransitionBlock, DOMRect>()
const newPositionMap = new WeakMap<TransitionBlock, DOMRect>()
const decorate = (t: typeof VaporTransitionGroup) => {
delete (t.props! as any).mode
t.__vapor = true
return t
}
export const VaporTransitionGroup: ObjectVaporComponent = decorate({
name: 'VaporTransitionGroup',
props: /*@__PURE__*/ extend({}, TransitionPropsValidators, {
tag: String,
moveClass: String,
}),
2025-03-13 17:35:25 +08:00
setup(props: TransitionGroupProps, { slots }) {
2025-03-13 21:46:40 +08:00
const instance = currentInstance as VaporComponentInstance
const state = useTransitionState()
const cssTransitionProps = resolveTransitionProps(props)
let prevChildren: TransitionBlock[]
let children: TransitionBlock[]
let slottedBlock: Block
onBeforeUpdate(() => {
prevChildren = []
children = getTransitionBlocks(slottedBlock)
if (children) {
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isValidTransitionBlock(child)) {
prevChildren.push(child)
2025-03-12 11:47:20 +08:00
// disabled transition during enter, so the children will be
// inserted into the correct position immediately. this prevents
// `recordPosition` from getting incorrect positions in `onUpdated`
2025-03-12 11:47:20 +08:00
child.$transition!.disabled = true
2025-03-12 09:12:39 +08:00
positionMap.set(
child,
getTransitionElement(child).getBoundingClientRect(),
)
}
}
}
})
onUpdated(() => {
if (!prevChildren.length) {
return
}
const moveClass = props.moveClass || `${props.name || 'v'}-move`
2025-03-12 09:12:39 +08:00
const firstChild = getFirstConnectedChild(prevChildren)
if (
!firstChild ||
!hasCSSTransform(
firstChild as ElementWithTransition,
firstChild.parentNode as Node,
moveClass,
)
) {
return
}
prevChildren.forEach(callPendingCbs)
prevChildren.forEach(child => {
2025-03-17 11:40:10 +08:00
child.$transition!.disabled = false
recordPosition(child)
})
const movedChildren = prevChildren.filter(applyTranslation)
// force reflow to put everything in position
forceReflow()
movedChildren.forEach(c =>
2025-03-12 09:12:39 +08:00
handleMovedChildren(
getTransitionElement(c) as ElementWithTransition,
moveClass,
),
)
})
slottedBlock = slots.default && slots.default()
// store props and state on fragment for reusing during insert new items
2025-03-16 10:00:19 +08:00
setTransitionHooksOnFragment(slottedBlock, {
props: cssTransitionProps,
state,
2025-03-17 14:29:20 +08:00
instance,
} as VaporTransitionHooks)
children = getTransitionBlocks(slottedBlock)
for (let i = 0; i < children.length; i++) {
const child = children[i]
if (isValidTransitionBlock(child)) {
2025-03-12 11:47:20 +08:00
if (child.$key != null) {
setTransitionHooks(
child,
resolveTransitionHooks(child, cssTransitionProps, state, instance!),
)
2025-03-12 11:47:20 +08:00
} else if (__DEV__ && child.$key == null) {
warn(`<transition-group> children must be keyed`)
}
}
}
const tag = props.tag
if (tag) {
2025-03-17 11:40:10 +08:00
const container = createElement(tag)
2025-03-12 11:09:46 +08:00
insert(slottedBlock, container)
// fallthrough attrs
2025-03-13 21:46:40 +08:00
if (instance!.hasFallthrough) {
2025-03-14 11:44:04 +08:00
renderEffect(() => applyFallthroughProps(container, instance!.attrs))
2025-03-13 21:46:40 +08:00
}
2025-03-12 11:09:46 +08:00
return container
} else {
const frag = __DEV__
? new DynamicFragment('transitionGroup')
: new DynamicFragment()
renderEffect(() => frag.update(() => slottedBlock))
return frag
}
},
})
function getTransitionBlocks(block: Block) {
let children: TransitionBlock[] = []
if (block instanceof Node) {
children.push(block)
} else if (isVaporComponent(block)) {
children.push(...getTransitionBlocks(block.block))
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
const b = block[i]
const blocks = getTransitionBlocks(b)
if (isForBlock(b)) blocks.forEach(block => (block.$key = b.key))
children.push(...blocks)
}
} else if (isFragment(block)) {
if (block.insert) {
// vdom component
children.push(block)
} else {
children.push(...getTransitionBlocks(block.nodes))
}
}
return children
}
function isValidTransitionBlock(block: Block): boolean {
return !!(block instanceof Element || (isFragment(block) && block.insert))
}
2025-03-12 09:12:39 +08:00
function getTransitionElement(c: TransitionBlock): Element {
return (isFragment(c) ? (c.nodes as Element[])[0] : c) as Element
}
function recordPosition(c: TransitionBlock) {
2025-03-12 09:12:39 +08:00
newPositionMap.set(c, getTransitionElement(c).getBoundingClientRect())
}
function applyTranslation(c: TransitionBlock): TransitionBlock | undefined {
if (
baseApplyTranslation(
positionMap.get(c)!,
newPositionMap.get(c)!,
2025-03-12 09:12:39 +08:00
getTransitionElement(c) as ElementWithTransition,
)
) {
return c
}
}
2025-03-12 09:12:39 +08:00
function getFirstConnectedChild(
children: TransitionBlock[],
): Element | undefined {
for (let i = 0; i < children.length; i++) {
const child = children[i]
2025-03-12 09:12:39 +08:00
const el = getTransitionElement(child)
if (el.isConnected) return el
}
}