fix(custom-element): ensure proper remount and prevent redundant slot parsing with shadowRoot false (#13201)

close #13199
This commit is contained in:
edison 2025-05-22 08:05:39 +08:00 committed by GitHub
parent 5179d328d9
commit 1d41d4de7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 89 additions and 3 deletions

View File

@ -1226,6 +1226,92 @@ describe('defineCustomElement', () => {
expect(target.innerHTML).toBe(`<span>default</span>`)
app.unmount()
})
test('toggle nested custom element with shadowRoot: false', async () => {
customElements.define(
'my-el-child-shadow-false',
defineCustomElement(
{
render(ctx: any) {
return h('div', null, [renderSlot(ctx.$slots, 'default')])
},
},
{ shadowRoot: false },
),
)
const ChildWrapper = {
render() {
return h('my-el-child-shadow-false', null, 'child')
},
}
customElements.define(
'my-el-parent-shadow-false',
defineCustomElement(
{
props: {
isShown: { type: Boolean, required: true },
},
render(ctx: any, _: any, $props: any) {
return $props.isShown
? h('div', { key: 0 }, [renderSlot(ctx.$slots, 'default')])
: null
},
},
{ shadowRoot: false },
),
)
const ParentWrapper = {
props: {
isShown: { type: Boolean, required: true },
},
render(ctx: any, _: any, $props: any) {
return h('my-el-parent-shadow-false', { isShown: $props.isShown }, [
renderSlot(ctx.$slots, 'default'),
])
},
}
const isShown = ref(true)
const App = {
render() {
return h(ParentWrapper, { isShown: isShown.value } as any, {
default: () => [h(ChildWrapper)],
})
},
}
const container = document.createElement('div')
document.body.appendChild(container)
const app = createApp(App)
app.mount(container)
expect(container.innerHTML).toBe(
`<my-el-parent-shadow-false is-shown="" data-v-app="">` +
`<div>` +
`<my-el-child-shadow-false data-v-app="">` +
`<div>child</div>` +
`</my-el-child-shadow-false>` +
`</div>` +
`</my-el-parent-shadow-false>`,
)
isShown.value = false
await nextTick()
expect(container.innerHTML).toBe(
`<my-el-parent-shadow-false data-v-app=""><!----></my-el-parent-shadow-false>`,
)
isShown.value = true
await nextTick()
expect(container.innerHTML).toBe(
`<my-el-parent-shadow-false data-v-app="" is-shown="">` +
`<div>` +
`<my-el-child-shadow-false data-v-app="">` +
`<div>child</div>` +
`</my-el-child-shadow-false>` +
`</div>` +
`</my-el-parent-shadow-false>`,
)
})
})
describe('helpers', () => {

View File

@ -280,7 +280,8 @@ export class VueElement
// avoid resolving component if it's not connected
if (!this.isConnected) return
if (!this.shadowRoot) {
// avoid re-parsing slots if already resolved
if (!this.shadowRoot && !this._resolved) {
this._parseSlots()
}
this._connected = true
@ -298,8 +299,7 @@ export class VueElement
if (!this._instance) {
if (this._resolved) {
this._setParent()
this._update()
this._mount(this._def)
} else {
if (parent && parent._pendingResolve) {
this._pendingResolve = parent._pendingResolve.then(() => {