Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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?: Set<RendererElement>
}
12 changes: 9 additions & 3 deletions packages/runtime-core/src/components/Teleport.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,9 +119,6 @@ export const TeleportImpl = {
// Teleport *always* has Array children. This is enforced in both the
// compiler and vnode children normalization.
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
if (parentComponent && parentComponent.isCE) {
parentComponent.ce!._teleportTarget = container
}
mountChildren(
children as VNodeArrayChildren,
container,
Expand All @@ -145,6 +142,15 @@ export const TeleportImpl = {
} else if (namespace !== 'mathml' && isTargetMathML(target)) {
namespace = 'mathml'
}

// track CE teleport targets
if (parentComponent && parentComponent.isCE) {
;(
parentComponent.ce!._teleportTargets ||
(parentComponent.ce!._teleportTargets = new Set())
).add(target)
}

if (!disabled) {
mount(target, targetAnchor)
updateCssVars(n2, false)
Expand Down
77 changes: 77 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,83 @@ describe('defineCustomElement', () => {
app.unmount()
})

test('render two 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-two-teleport-child', Child)

const App = {
render() {
return h('my-el-two-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('render two Teleports w/ shadowRoot false (with disabled)', async () => {
const target1 = document.createElement('div')
const target2 = document.createElement('span')
const Child = defineCustomElement(
{
render() {
return [
// with disabled: true
h(Teleport, { to: target1, disabled: true }, [
renderSlot(this.$slots, 'header'),
]),
h(Teleport, { to: target2 }, [renderSlot(this.$slots, 'body')]),
]
},
},
{ shadowRoot: false },
)
customElements.define('my-el-two-teleport-child-0', Child)

const App = {
render() {
return h('my-el-two-teleport-child-0', 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>`)
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
21 changes: 19 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?: Set<Element>

private _connected = false
private _resolved = false
Expand Down Expand Up @@ -338,6 +338,10 @@ export class VueElement
this._app && this._app.unmount()
if (this._instance) this._instance.ce = undefined
this._app = this._instance = null
if (this._teleportTargets) {
this._teleportTargets.clear()
this._teleportTargets = undefined
}
}
})
}
Expand Down Expand Up @@ -635,7 +639,7 @@ export class VueElement
* Only called when shadowRoot is false
*/
private _renderSlots() {
const outlets = (this._teleportTarget || this).querySelectorAll('slot')
const outlets = this._getSlots()
const scopeId = this._instance!.type.__scopeId
for (let i = 0; i < outlets.length; i++) {
const o = outlets[i] as HTMLSlotElement
Expand Down Expand Up @@ -663,6 +667,19 @@ export class VueElement
}
}

/**
* @internal
*/
private _getSlots(): HTMLSlotElement[] {
const roots: Element[] = [this]
if (this._teleportTargets) {
roots.push(...this._teleportTargets)
}
return roots.reduce<HTMLSlotElement[]>((res, i) => {
res.push(...Array.from(i.querySelectorAll('slot')))
return res
}, [])
}
/**
* @internal
*/
Expand Down
Loading