From: edison Date: Thu, 22 May 2025 00:05:39 +0000 (+0800) Subject: fix(custom-element): ensure proper remount and prevent redundant slot parsing with... X-Git-Tag: v3.5.15~10 X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1d41d4de7f64a37160c8171d0137fd8d35c346c9;p=thirdparty%2Fvuejs%2Fcore.git fix(custom-element): ensure proper remount and prevent redundant slot parsing with shadowRoot false (#13201) close #13199 --- diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 907b299b82..cf7b94ed00 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1226,6 +1226,92 @@ describe('defineCustomElement', () => { expect(target.innerHTML).toBe(`default`) 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( + `` + + `
` + + `` + + `
child
` + + `
` + + `
` + + `
`, + ) + + isShown.value = false + await nextTick() + expect(container.innerHTML).toBe( + ``, + ) + + isShown.value = true + await nextTick() + expect(container.innerHTML).toBe( + `` + + `
` + + `` + + `
child
` + + `
` + + `
` + + `
`, + ) + }) }) describe('helpers', () => { diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index f072466309..3a4a341297 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -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(() => {