mirror of https://github.com/vuejs/core.git
fix(custom-element): properly mount multiple Teleports in custom element component w/ shadowRoot false (#13900)
close #13899
This commit is contained in:
parent
95c1975604
commit
5e1e791880
|
@ -1273,5 +1273,5 @@ export interface ComponentCustomElementInterface {
|
|||
/**
|
||||
* @internal attached by the nested Teleport when shadowRoot is false.
|
||||
*/
|
||||
_teleportTarget?: RendererElement
|
||||
_teleportTargets?: Set<RendererElement>
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
@ -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)
|
||||
|
|
|
@ -1308,6 +1308,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',
|
||||
|
|
|
@ -224,7 +224,7 @@ export class VueElement
|
|||
/**
|
||||
* @internal
|
||||
*/
|
||||
_teleportTarget?: HTMLElement
|
||||
_teleportTargets?: Set<Element>
|
||||
|
||||
private _connected = false
|
||||
private _resolved = false
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue