h,
inject,
nextTick,
+ provide,
ref,
render,
renderSlot,
).toHaveBeenWarned()
})
})
+
+ test('async & nested custom elements', async () => {
+ let fooVal: string | undefined = ''
+ const E = defineCustomElement(
+ defineAsyncComponent(() => {
+ return Promise.resolve({
+ setup(props) {
+ provide('foo', 'foo')
+ },
+ render(this: any) {
+ return h('div', null, [renderSlot(this.$slots, 'default')])
+ },
+ })
+ }),
+ )
+
+ const EChild = defineCustomElement({
+ setup(props) {
+ fooVal = inject('foo')
+ },
+ render(this: any) {
+ return h('div', null, 'child')
+ },
+ })
+ customElements.define('my-el-async-nested-ce', E)
+ customElements.define('slotted-child', EChild)
+ container.innerHTML = `<my-el-async-nested-ce><div><slotted-child></slotted-child></div></my-el-async-nested-ce>`
+
+ await new Promise(r => setTimeout(r))
+ const e = container.childNodes[0] as VueElement
+ expect(e.shadowRoot!.innerHTML).toBe(`<div><slot></slot></div>`)
+ expect(fooVal).toBe('foo')
+ })
+
+ test('async & multiple levels of nested custom elements', async () => {
+ let fooVal: string | undefined = ''
+ let barVal: string | undefined = ''
+ const E = defineCustomElement(
+ defineAsyncComponent(() => {
+ return Promise.resolve({
+ setup(props) {
+ provide('foo', 'foo')
+ },
+ render(this: any) {
+ return h('div', null, [renderSlot(this.$slots, 'default')])
+ },
+ })
+ }),
+ )
+
+ const EChild = defineCustomElement({
+ setup(props) {
+ provide('bar', 'bar')
+ },
+ render(this: any) {
+ return h('div', null, [renderSlot(this.$slots, 'default')])
+ },
+ })
+
+ const EChild2 = defineCustomElement({
+ setup(props) {
+ fooVal = inject('foo')
+ barVal = inject('bar')
+ },
+ render(this: any) {
+ return h('div', null, 'child')
+ },
+ })
+ customElements.define('my-el-async-nested-m-ce', E)
+ customElements.define('slotted-child-m', EChild)
+ customElements.define('slotted-child2-m', EChild2)
+ container.innerHTML =
+ `<my-el-async-nested-m-ce>` +
+ `<div><slotted-child-m>` +
+ `<slotted-child2-m></slotted-child2-m>` +
+ `</slotted-child-m></div>` +
+ `</my-el-async-nested-m-ce>`
+
+ await new Promise(r => setTimeout(r))
+ const e = container.childNodes[0] as VueElement
+ expect(e.shadowRoot!.innerHTML).toBe(`<div><slot></slot></div>`)
+ expect(fooVal).toBe('foo')
+ expect(barVal).toBe('bar')
+ })
})
private _resolved = false
private _numberProps: Record<string, true> | null = null
private _styleChildren = new WeakSet()
+ private _pendingResolve: Promise<void> | undefined
+ private _parent: VueElement | undefined
/**
* dev only
*/
this._parseSlots()
}
this._connected = true
+
+ // locate nearest Vue custom element parent for provide/inject
+ let parent: Node | null = this
+ while (
+ (parent = parent && (parent.parentNode || (parent as ShadowRoot).host))
+ ) {
+ if (parent instanceof VueElement) {
+ this._parent = parent
+ break
+ }
+ }
+
if (!this._instance) {
if (this._resolved) {
+ this._setParent()
this._update()
} else {
- this._resolveDef()
+ if (parent && parent._pendingResolve) {
+ this._pendingResolve = parent._pendingResolve.then(() => {
+ this._pendingResolve = undefined
+ this._resolveDef()
+ })
+ } else {
+ this._resolveDef()
+ }
}
}
}
+ private _setParent(parent = this._parent) {
+ if (parent) {
+ this._instance!.parent = parent._instance
+ this._instance!.provides = parent._instance!.provides
+ }
+ }
+
disconnectedCallback() {
this._connected = false
nextTick(() => {
* resolve inner component definition (handle possible async component)
*/
private _resolveDef() {
- this._resolved = true
+ if (this._pendingResolve) {
+ return
+ }
// set initial attrs
for (let i = 0; i < this.attributes.length; i++) {
this._ob.observe(this, { attributes: true })
const resolve = (def: InnerComponentDef, isAsync = false) => {
+ this._resolved = true
+ this._pendingResolve = undefined
+
const { props, styles } = def
// cast Number-type props set before resolve
const asyncDef = (this._def as ComponentOptions).__asyncLoader
if (asyncDef) {
- asyncDef().then(def => resolve((this._def = def), true))
+ this._pendingResolve = asyncDef().then(def =>
+ resolve((this._def = def), true),
+ )
} else {
resolve(this._def)
}
}
}
- // locate nearest Vue custom element parent for provide/inject
- let parent: Node | null = this
- while (
- (parent =
- parent && (parent.parentNode || (parent as ShadowRoot).host))
- ) {
- if (parent instanceof VueElement) {
- instance.parent = parent._instance
- instance.provides = parent._instance!.provides
- break
- }
- }
+ this._setParent()
}
}
return vnode