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.
|
* @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
|
// Teleport *always* has Array children. This is enforced in both the
|
||||||
// compiler and vnode children normalization.
|
// compiler and vnode children normalization.
|
||||||
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
|
||||||
if (parentComponent && parentComponent.isCE) {
|
|
||||||
parentComponent.ce!._teleportTarget = container
|
|
||||||
}
|
|
||||||
mountChildren(
|
mountChildren(
|
||||||
children as VNodeArrayChildren,
|
children as VNodeArrayChildren,
|
||||||
container,
|
container,
|
||||||
|
@ -145,6 +142,15 @@ export const TeleportImpl = {
|
||||||
} else if (namespace !== 'mathml' && isTargetMathML(target)) {
|
} else if (namespace !== 'mathml' && isTargetMathML(target)) {
|
||||||
namespace = 'mathml'
|
namespace = 'mathml'
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// track CE teleport targets
|
||||||
|
if (parentComponent && parentComponent.isCE) {
|
||||||
|
;(
|
||||||
|
parentComponent.ce!._teleportTargets ||
|
||||||
|
(parentComponent.ce!._teleportTargets = new Set())
|
||||||
|
).add(target)
|
||||||
|
}
|
||||||
|
|
||||||
if (!disabled) {
|
if (!disabled) {
|
||||||
mount(target, targetAnchor)
|
mount(target, targetAnchor)
|
||||||
updateCssVars(n2, false)
|
updateCssVars(n2, false)
|
||||||
|
|
|
@ -1308,6 +1308,83 @@ describe('defineCustomElement', () => {
|
||||||
app.unmount()
|
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 () => {
|
test('toggle nested custom element with shadowRoot: false', async () => {
|
||||||
customElements.define(
|
customElements.define(
|
||||||
'my-el-child-shadow-false',
|
'my-el-child-shadow-false',
|
||||||
|
|
|
@ -224,7 +224,7 @@ export class VueElement
|
||||||
/**
|
/**
|
||||||
* @internal
|
* @internal
|
||||||
*/
|
*/
|
||||||
_teleportTarget?: HTMLElement
|
_teleportTargets?: Set<Element>
|
||||||
|
|
||||||
private _connected = false
|
private _connected = false
|
||||||
private _resolved = false
|
private _resolved = false
|
||||||
|
@ -338,6 +338,10 @@ export class VueElement
|
||||||
this._app && this._app.unmount()
|
this._app && this._app.unmount()
|
||||||
if (this._instance) this._instance.ce = undefined
|
if (this._instance) this._instance.ce = undefined
|
||||||
this._app = this._instance = null
|
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
|
* Only called when shadowRoot is false
|
||||||
*/
|
*/
|
||||||
private _renderSlots() {
|
private _renderSlots() {
|
||||||
const outlets = (this._teleportTarget || this).querySelectorAll('slot')
|
const outlets = this._getSlots()
|
||||||
const scopeId = this._instance!.type.__scopeId
|
const scopeId = this._instance!.type.__scopeId
|
||||||
for (let i = 0; i < outlets.length; i++) {
|
for (let i = 0; i < outlets.length; i++) {
|
||||||
const o = outlets[i] as HTMLSlotElement
|
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
|
* @internal
|
||||||
*/
|
*/
|
||||||
|
|
Loading…
Reference in New Issue