Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion packages/runtime-core/src/component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1273,5 +1273,5 @@ export interface ComponentCustomElementInterface {
/**
* @internal attached by the nested Teleport when shadowRoot is false.
*/
_teleportTarget?: RendererElement
_teleportTargets?: RendererElement[]
}
7 changes: 6 additions & 1 deletion packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,12 @@ export const TeleportImpl = {
// compiler and vnode children normalization.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (parentComponent && parentComponent.isCE) {
parentComponent.ce!._teleportTarget = container
const _teleportTargets = parentComponent.ce!._teleportTargets
if (!_teleportTargets) {
parentComponent.ce!._teleportTargets = [container]
} else {
_teleportTargets.push(container)
}
}
mountChildren(
children as VNodeArrayChildren,
Expand Down
38 changes: 38 additions & 0 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1281,6 +1281,44 @@ describe('defineCustomElement', () => {
app.unmount()
})

test('render nested tow Teleports w/ shadowRoot false', async () => {
const target1 = document.createElement('div')
const target2 = document.createElement('span')
const Child = defineCustomElement(
{
render() {
return [
h(Teleport, { to: target1 }, [renderSlot(this.$slots, 'header')]),
h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]),
]
},
},
{ shadowRoot: false },
)
customElements.define('my-el-tow-teleport-child', Child)

const App = {
render() {
return h('my-el-tow-teleport-child', null, {
default: () => [
h('div', { slot: 'header' }, 'header'),
h('span', { slot: 'body' }, 'body'),
],
})
},
}
const app = createApp(App)
app.mount(container)
await nextTick()
expect(target1.outerHTML).toBe(
`<div><div slot="header">header</div></div>`,
)
expect(target2.outerHTML).toBe(
`<span><span slot="body">body</span></span>`,
)
app.unmount()
})

test('toggle nested custom element with shadowRoot: false', async () => {
customElements.define(
'my-el-child-shadow-false',
Expand Down
12 changes: 10 additions & 2 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -224,7 +224,7 @@ export class VueElement
/**
* @internal
*/
_teleportTarget?: HTMLElement
_teleportTargets?: HTMLElement[]

private _connected = false
private _resolved = false
Expand Down Expand Up @@ -635,7 +635,7 @@ export class VueElement
* Only called when shadowRoot is false
*/
private _renderSlots() {
const outlets = (this._teleportTarget || this).querySelectorAll('slot')
const outlets = [...getSlots(this._teleportTargets), ...getSlots([this])]
const scopeId = this._instance!.type.__scopeId
for (let i = 0; i < outlets.length; i++) {
const o = outlets[i] as HTMLSlotElement
Expand Down Expand Up @@ -716,3 +716,11 @@ export function useShadowRoot(): ShadowRoot | null {
const el = __DEV__ ? useHost('useShadowRoot') : useHost()
return el && el.shadowRoot
}

function getSlots(roots: HTMLElement[] | undefined): HTMLSlotElement[] {
if (!roots) return []
return roots.reduce<HTMLSlotElement[]>((res, i) => {
res.push(...Array.from(i.querySelectorAll('slot')))
return res
}, [])
}
Loading