diff --git a/packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts b/packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts
index ba050f0f263..80563244733 100644
--- a/packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts
+++ b/packages-private/vapor-e2e-test/__tests__/transition-group.spec.ts
@@ -369,6 +369,44 @@ describe('vapor transition-group', () => {
expect(calls).toContain('afterEnter')
})
+ test(
+ 'reusable transition group',
+ async () => {
+ const btnSelector = '.reusable-transition-group > button'
+ const containerSelector = '.reusable-transition-group > div'
+
+ expect(await html(containerSelector)).toBe(
+ `
a
` +
+ `b
` +
+ `c
`,
+ )
+
+ expect(
+ (await transitionStart(btnSelector, containerSelector)).innerHTML,
+ ).toBe(
+ `d
` +
+ `b
` +
+ `a
` +
+ `c
`,
+ )
+
+ await nextFrame()
+ expect(await html(containerSelector)).toBe(
+ `d
` +
+ `b
` +
+ `a
` +
+ `c
`,
+ )
+ await transitionFinish(duration * 2)
+ expect(await html(containerSelector)).toBe(
+ `d
` +
+ `b
` +
+ `a
`,
+ )
+ },
+ E2E_TIMEOUT,
+ )
+
test('interop: render vdom component', async () => {
const btnSelector = '.interop > button'
const containerSelector = '.interop > div'
diff --git a/packages-private/vapor-e2e-test/transition-group/App.vue b/packages-private/vapor-e2e-test/transition-group/App.vue
index 55775743c56..5cc6903a985 100644
--- a/packages-private/vapor-e2e-test/transition-group/App.vue
+++ b/packages-private/vapor-e2e-test/transition-group/App.vue
@@ -1,6 +1,7 @@
+
+
+
+
+
+
diff --git a/packages/compiler-vapor/src/generators/component.ts b/packages/compiler-vapor/src/generators/component.ts
index c2da7a3f22e..cd842a91887 100644
--- a/packages/compiler-vapor/src/generators/component.ts
+++ b/packages/compiler-vapor/src/generators/component.ts
@@ -40,12 +40,7 @@ import { genEventHandler } from './event'
import { genDirectiveModifiers, genDirectivesForElement } from './directive'
import { genBlock } from './block'
import { genModelHandler } from './vModel'
-import {
- isBuiltInComponent,
- isKeepAliveTag,
- isTeleportTag,
- isTransitionGroupTag,
-} from '../utils'
+import { isBuiltInComponent } from '../utils'
export function genCreateComponent(
operation: CreateComponentIRNode,
@@ -465,15 +460,7 @@ function genSlotBlockWithProps(oper: SlotBlockIRNode, context: CodegenContext) {
]
}
- if (
- node.type === NodeTypes.ELEMENT &&
- // Not a real component
- !isTeleportTag(node.tag) &&
- // Needs to determine whether to activate/deactivate based on instance.parent being KeepAlive
- !isKeepAliveTag(node.tag) &&
- // Slot updates need to trigger TransitionGroup's onBeforeUpdate/onUpdated hook
- !isTransitionGroupTag(node.tag)
- ) {
+ if (node.type === NodeTypes.ELEMENT) {
// wrap with withVaporCtx to ensure correct currentInstance inside slot
blockFn = [`${context.helper('withVaporCtx')}(`, ...blockFn, `)`]
}
diff --git a/packages/runtime-vapor/src/apiCreateFor.ts b/packages/runtime-vapor/src/apiCreateFor.ts
index 5c4e598b910..5bc4473997c 100644
--- a/packages/runtime-vapor/src/apiCreateFor.ts
+++ b/packages/runtime-vapor/src/apiCreateFor.ts
@@ -39,6 +39,7 @@ import {
isLastInsertion,
resetInsertionState,
} from './insertionState'
+import { triggerTransitionGroupUpdate } from './components/TransitionGroup'
class ForBlock extends VaporFragment {
scope: EffectScope | undefined
@@ -130,6 +131,12 @@ export const createFor = (
newBlocks = new Array(newLength)
let isFallback = false
+ // trigger TransitionGroup update hooks
+ const transitionHooks = frag.$transition
+ if (transitionHooks && transitionHooks.group) {
+ triggerTransitionGroupUpdate(transitionHooks)
+ }
+
const prevSub = setActiveSub()
if (!isMounted) {
diff --git a/packages/runtime-vapor/src/block.ts b/packages/runtime-vapor/src/block.ts
index 628e6b61c7b..76a5edb2f69 100644
--- a/packages/runtime-vapor/src/block.ts
+++ b/packages/runtime-vapor/src/block.ts
@@ -29,6 +29,9 @@ export interface VaporTransitionHooks extends TransitionHooks {
// mark transition hooks as disabled so that it skips during
// inserting
disabled?: boolean
+ // mark transition hooks as group so that it triggers TransitionGroup update hooks
+ // in vFor renderList function
+ group?: boolean
}
export interface TransitionOptions {
diff --git a/packages/runtime-vapor/src/components/Transition.ts b/packages/runtime-vapor/src/components/Transition.ts
index 131154e2b6c..3f945e838f5 100644
--- a/packages/runtime-vapor/src/components/Transition.ts
+++ b/packages/runtime-vapor/src/components/Transition.ts
@@ -233,7 +233,7 @@ export function applyTransitionHooks(
return hooks
}
- const { props, instance, state, delayedLeave } = hooks
+ const { props, instance, state, delayedLeave, group } = hooks
let resolvedHooks = resolveTransitionHooks(
child,
props,
@@ -242,6 +242,7 @@ export function applyTransitionHooks(
hooks => (resolvedHooks = hooks as VaporTransitionHooks),
)
resolvedHooks.delayedLeave = delayedLeave
+ resolvedHooks.group = group
child.$transition = resolvedHooks
if (isFrag) setTransitionHooksOnFragment(block, resolvedHooks)
@@ -365,6 +366,9 @@ export function setTransitionHooksOnFragment(
): void {
if (isFragment(block)) {
block.$transition = hooks
+ if (block.nodes && isFragment(block.nodes)) {
+ setTransitionHooksOnFragment(block.nodes, hooks)
+ }
} else if (isArray(block)) {
for (let i = 0; i < block.length; i++) {
setTransitionHooksOnFragment(block[i], hooks)
diff --git a/packages/runtime-vapor/src/components/TransitionGroup.ts b/packages/runtime-vapor/src/components/TransitionGroup.ts
index b1e86c87b24..48dab603211 100644
--- a/packages/runtime-vapor/src/components/TransitionGroup.ts
+++ b/packages/runtime-vapor/src/components/TransitionGroup.ts
@@ -10,11 +10,12 @@ import {
hasCSSTransform,
onBeforeUpdate,
onUpdated,
+ queuePostFlushCb,
resolveTransitionProps,
useTransitionState,
warn,
} from '@vue/runtime-dom'
-import { extend, isArray } from '@vue/shared'
+import { extend, invokeArrayFns, isArray } from '@vue/shared'
import {
type Block,
type TransitionBlock,
@@ -126,6 +127,7 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({
props: cssTransitionProps,
state,
instance,
+ group: true,
} as VaporTransitionHooks)
children = getTransitionBlocks(slottedBlock)
@@ -133,10 +135,14 @@ export const VaporTransitionGroup: ObjectVaporComponent = decorate({
const child = children[i]
if (isValidTransitionBlock(child)) {
if (child.$key != null) {
- setTransitionHooks(
+ const hooks = resolveTransitionHooks(
child,
- resolveTransitionHooks(child, cssTransitionProps, state, instance!),
+ cssTransitionProps,
+ state,
+ instance!,
)
+ hooks.group = true
+ setTransitionHooks(child, hooks)
} else if (__DEV__ && child.$key == null) {
warn(` children must be keyed`)
}
@@ -221,3 +227,23 @@ function getFirstConnectedChild(
if (el.isConnected) return el
}
}
+
+/**
+ * The implementation of TransitionGroup relies on the onBeforeUpdate and onUpdated hooks.
+ * However, when the slot content of TransitionGroup updates, it does not trigger the
+ * onBeforeUpdate and onUpdated hooks. Therefore, it is necessary to manually trigger
+ * the TransitionGroup update hooks to ensure its proper work.
+ */
+export function triggerTransitionGroupUpdate(
+ transition: VaporTransitionHooks,
+): void {
+ const { instance } = transition
+ if (!instance.isUpdating) {
+ instance.isUpdating = true
+ if (instance.bu) invokeArrayFns(instance.bu)
+ queuePostFlushCb(() => {
+ instance.isUpdating = false
+ if (instance.u) invokeArrayFns(instance.u)
+ })
+ }
+}