diff --git a/packages/runtime-core/src/index.ts b/packages/runtime-core/src/index.ts index 94b2985040a..b281da1e717 100644 --- a/packages/runtime-core/src/index.ts +++ b/packages/runtime-core/src/index.ts @@ -354,6 +354,8 @@ export { normalizeStyle, } from '@vue/shared' +export { filterSingleRoot } from './componentRenderUtils' + // For test-utils export { transformVNodeArgs } from './vnode' diff --git a/packages/runtime-dom/src/components/TransitionGroup.ts b/packages/runtime-dom/src/components/TransitionGroup.ts index f98e82b2734..97a5592ce8e 100644 --- a/packages/runtime-dom/src/components/TransitionGroup.ts +++ b/packages/runtime-dom/src/components/TransitionGroup.ts @@ -15,8 +15,10 @@ import { Fragment, type SetupContext, type VNode, + type VNodeArrayChildren, compatUtils, createVNode, + filterSingleRoot, getCurrentInstance, getTransitionRawChildren, onUpdated, @@ -26,7 +28,7 @@ import { useTransitionState, warn, } from '@vue/runtime-core' -import { extend } from '@vue/shared' +import { PatchFlags, ShapeFlags, extend } from '@vue/shared' const positionMap = new WeakMap() const newPositionMap = new WeakMap() @@ -113,6 +115,28 @@ const TransitionGroupImpl: ComponentOptions = { } prevChildren = children + + // In dev mode, comments are preserved, and it's possible for a template + // to have comments alongside the root element which makes it a fragment. + // In that case we re-assign `el` so DOM operations access the actual + // root element instead of the fragment root. (#6745) + if (__DEV__ && prevChildren) { + prevChildren.forEach(child => { + if ( + child.shapeFlag & ShapeFlags.COMPONENT && + child.component && + child.component.subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT + ) { + const elementRoot = filterSingleRoot( + child.component.subTree.children as VNodeArrayChildren, + ) + if (elementRoot) { + child.el = elementRoot.el + } + } + }) + } + children = slots.default ? getTransitionRawChildren(slots.default()) : [] for (let i = 0; i < children.length; i++) { diff --git a/packages/vue/__tests__/e2e/TransitionGroup.spec.ts b/packages/vue/__tests__/e2e/TransitionGroup.spec.ts index febc9d3c20a..4f52d4a8e06 100644 --- a/packages/vue/__tests__/e2e/TransitionGroup.spec.ts +++ b/packages/vue/__tests__/e2e/TransitionGroup.spec.ts @@ -508,4 +508,45 @@ describe('e2e: TransitionGroup', () => { expect(` children must be keyed`).toHaveBeenWarned() }) + + test( + 'works when child component has single root + comments', + async () => { + const onErrorSpy = vi.fn() + await page().exposeFunction('onErrorSpy', onErrorSpy) + + await page().evaluate(() => { + const { onErrorSpy } = window as any + const { createApp, ref, onErrorCaptured } = (window as any).Vue + + const app = createApp({ + template: ` +
+ + + +
+ + `, + setup: () => { + onErrorCaptured(() => onErrorSpy()) + const show = ref(true) + const click = () => (show.value = false) + return { show, click } + }, + }) + app.component('a-component', { template: `
` }) + app.mount('#app') + }) + + expect(await html('#container')).toBe('
') + + await htmlWhenTransitionStart() + await transitionFinish() + + expect(onErrorSpy).not.toBeCalled() + expect(await html('#container')).toBe('') + }, + E2E_TIMEOUT, + ) })