From f41d16e6c73b2d8133563ae9c80cafb11ef17a51 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sat, 4 Oct 2025 10:52:21 +0800 Subject: [PATCH 1/3] fix(custom-element): optimize slot retrieval to avoid duplicates --- packages/runtime-dom/src/apiCustomElement.ts | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 6b1c8f0cae8..6c6ab395e04 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -685,11 +685,23 @@ export class VueElement if (this._teleportTargets) { roots.push(...this._teleportTargets) } - return roots.reduce((res, i) => { - res.push(...Array.from(i.querySelectorAll('slot'))) - return res - }, []) + + const seen = new Set() + const slots: HTMLSlotElement[] = [] + for (const root of roots) { + const found = root.querySelectorAll('slot') + for (let i = 0; i < found.length; i++) { + const slot = found[i] + if (!seen.has(slot)) { + seen.add(slot) + slots.push(slot) + } + } + } + + return slots } + /** * @internal */ From b661cd340281e277da3f32d1f3fff2fbb5a18509 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sat, 4 Oct 2025 11:02:41 +0800 Subject: [PATCH 2/3] test: add test --- .../__tests__/customElement.spec.ts | 38 +++++++++++++++++++ 1 file changed, 38 insertions(+) diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 36c9e918c9a..3e48edffe6d 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -1430,6 +1430,44 @@ describe('defineCustomElement', () => { app.unmount() }) + test('teleport target is ancestor of custom element host', async () => { + const Child = defineCustomElement( + { + render() { + return [ + h(Teleport, { to: '#t1' }, [renderSlot(this.$slots, 'header')]), + ] + }, + }, + { shadowRoot: false }, + ) + customElements.define('my-el-teleport-child-target', Child) + + const App = { + render() { + return h('div', { id: 't1' }, [ + h('my-el-teleport-child-target', null, { + default: () => [h('div', { slot: 'header' }, 'header')], + }), + ]) + }, + } + const app = createApp(App) + app.mount(container) + + const target1 = document.getElementById('t1')! + expect(target1.outerHTML).toBe( + `
` + + `` + + `` + + `` + + `
header
` + + `
`, + ) + + app.unmount() + }) + test('toggle nested custom element with shadowRoot: false', async () => { customElements.define( 'my-el-child-shadow-false', From 72b5019416168b674d6459b89befe0a26d1ed6c1 Mon Sep 17 00:00:00 2001 From: daiwei Date: Sat, 4 Oct 2025 16:57:20 +0800 Subject: [PATCH 3/3] chore: tweaks --- packages/runtime-dom/src/apiCustomElement.ts | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 6c6ab395e04..912523cb410 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -686,20 +686,15 @@ export class VueElement roots.push(...this._teleportTargets) } - const seen = new Set() - const slots: HTMLSlotElement[] = [] + const slots = new Set() for (const root of roots) { const found = root.querySelectorAll('slot') for (let i = 0; i < found.length; i++) { - const slot = found[i] - if (!seen.has(slot)) { - seen.add(slot) - slots.push(slot) - } + slots.add(found[i]) } } - return slots + return Array.from(slots) } /**