Skip to content

Commit b1b30d6

Browse files
committed
fix(transition): get root el of DEV_FRAGMENT_ROOT
fix #6745
1 parent f66a75e commit b1b30d6

File tree

3 files changed

+68
-1
lines changed

3 files changed

+68
-1
lines changed

packages/runtime-core/src/index.ts

+2
Original file line numberDiff line numberDiff line change
@@ -354,6 +354,8 @@ export {
354354
normalizeStyle,
355355
} from '@vue/shared'
356356

357+
export { filterSingleRoot } from './componentRenderUtils'
358+
357359
// For test-utils
358360
export { transformVNodeArgs } from './vnode'
359361

packages/runtime-dom/src/components/TransitionGroup.ts

+25-1
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,10 @@ import {
1515
Fragment,
1616
type SetupContext,
1717
type VNode,
18+
type VNodeArrayChildren,
1819
compatUtils,
1920
createVNode,
21+
filterSingleRoot,
2022
getCurrentInstance,
2123
getTransitionRawChildren,
2224
onUpdated,
@@ -26,7 +28,7 @@ import {
2628
useTransitionState,
2729
warn,
2830
} from '@vue/runtime-core'
29-
import { extend } from '@vue/shared'
31+
import { PatchFlags, ShapeFlags, extend } from '@vue/shared'
3032

3133
const positionMap = new WeakMap<VNode, DOMRect>()
3234
const newPositionMap = new WeakMap<VNode, DOMRect>()
@@ -113,6 +115,28 @@ const TransitionGroupImpl: ComponentOptions = {
113115
}
114116

115117
prevChildren = children
118+
119+
// In dev mode, comments are preserved, and it's possible for a template
120+
// to have comments alongside the root element which makes it a fragment.
121+
// In that case we re-assign `el` so DOM operations access the actual
122+
// root element instead of the fragment root. (#6745)
123+
if (__DEV__ && prevChildren) {
124+
prevChildren.forEach(child => {
125+
if (
126+
child.shapeFlag & ShapeFlags.COMPONENT &&
127+
child.component &&
128+
child.component.subTree.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT
129+
) {
130+
const elementRoot = filterSingleRoot(
131+
child.component.subTree.children as VNodeArrayChildren
132+
)
133+
if (elementRoot) {
134+
child.el = elementRoot.el
135+
}
136+
}
137+
})
138+
}
139+
116140
children = slots.default ? getTransitionRawChildren(slots.default()) : []
117141

118142
for (let i = 0; i < children.length; i++) {

packages/vue/__tests__/e2e/TransitionGroup.spec.ts

+41
Original file line numberDiff line numberDiff line change
@@ -508,4 +508,45 @@ describe('e2e: TransitionGroup', () => {
508508

509509
expect(`<TransitionGroup> children must be keyed`).toHaveBeenWarned()
510510
})
511+
512+
test(
513+
'works when child component has single root + comments',
514+
async () => {
515+
const onErrorSpy = vi.fn()
516+
await page().exposeFunction('onErrorSpy', onErrorSpy)
517+
518+
await page().evaluate(() => {
519+
const { onErrorSpy } = window as any
520+
const { createApp, ref, onErrorCaptured } = (window as any).Vue
521+
522+
const app = createApp({
523+
template: `
524+
<div id="container">
525+
<transition-group>
526+
<a-component v-if="show"></a-component>
527+
</transition-group>
528+
</div>
529+
<button id="toggleBtn" @click="click">button</button>
530+
`,
531+
setup: () => {
532+
onErrorCaptured(() => onErrorSpy())
533+
const show = ref(true)
534+
const click = () => (show.value = false)
535+
return { show, click }
536+
}
537+
})
538+
app.component('a-component', { template: `<!----><div></div>` })
539+
app.mount('#app')
540+
})
541+
542+
expect(await html('#container')).toBe('<!----><div></div>')
543+
544+
await htmlWhenTransitionStart()
545+
await transitionFinish()
546+
547+
expect(onErrorSpy).not.toBeCalled()
548+
expect(await html('#container')).toBe('')
549+
},
550+
E2E_TIMEOUT
551+
)
511552
})

0 commit comments

Comments
 (0)