diff --git a/packages/runtime-dom/__tests__/customElement.spec.ts b/packages/runtime-dom/__tests__/customElement.spec.ts index 976213f3783..c0474768266 100644 --- a/packages/runtime-dom/__tests__/customElement.spec.ts +++ b/packages/runtime-dom/__tests__/customElement.spec.ts @@ -147,10 +147,12 @@ describe('defineCustomElement', () => { }) // #12412 + const contextElStyle = ':host { color: red }' const ContextEl = defineCustomElement({ props: { msg: String, }, + styles: [contextElStyle], setup(props, { expose }) { expose({ text: () => props.msg, @@ -171,27 +173,33 @@ describe('defineCustomElement', () => { await nextTick() await nextTick() // wait two ticks for disconnect expect('text' in parent).toBe(false) + expect(child.shadowRoot!.querySelectorAll('style').length).toBe(1) container.appendChild(parent) // should not throw Error await nextTick() expect(parent.text()).toBe('msg1') - expect(parent.shadowRoot!.textContent).toBe('msg1') - expect(child.shadowRoot!.textContent).toBe('msg1') + expect(parent.shadowRoot!.textContent).toBe(contextElStyle + 'msg1') + expect(child.shadowRoot!.textContent).toBe(contextElStyle + 'msg1') parent.setAttribute('msg', 'msg2') await nextTick() - expect(parent.shadowRoot!.textContent).toBe('msg2') + expect(parent.shadowRoot!.textContent).toBe(contextElStyle + 'msg2') await nextTick() - expect(child.shadowRoot!.textContent).toBe('msg2') + expect(child.shadowRoot!.textContent).toBe(contextElStyle + 'msg2') + expect(child.shadowRoot!.querySelectorAll('style').length).toBe(1) }) - test('move element to change parent and context', async () => { + test('move element to new parent', async () => { container.innerHTML = `` const first = container.children[0] as VueElement, - second = container.children[1] as VueElement + second = container.children[1] as VueElement & { text: () => string } await nextTick() - expect(second.shadowRoot!.textContent).toBe('msg2') + expect(second.shadowRoot!.textContent).toBe(contextElStyle + 'msg2') first.append(second) await nextTick() - expect(second.shadowRoot!.textContent).toBe('msg1') + expect(second.shadowRoot!.textContent).toBe(contextElStyle + 'msg1') + expect(second.shadowRoot!.querySelectorAll('style').length).toBe(1) + second.setAttribute('msg', 'msg3') + await nextTick() + expect(second.text()).toBe('msg3') }) }) diff --git a/packages/runtime-dom/src/apiCustomElement.ts b/packages/runtime-dom/src/apiCustomElement.ts index 0e72fa0adb5..b2b2d3f2011 100644 --- a/packages/runtime-dom/src/apiCustomElement.ts +++ b/packages/runtime-dom/src/apiCustomElement.ts @@ -298,12 +298,13 @@ export class VueElement } } - // unmount if parent changed and previously mounted, should keep parent + // unmount if parent changed and previously mounted, should keep parent and observer if (this._instance && parentChanged) this._unmount(true) - if (!this._instance) { + if (!this._instance || parentChanged) { if (this._resolved) { - this._setParent() - this._update() + // no instance means no observer + if (!this._instance) this._observe() + this._mount(this._def) } else { if (parent && parent._pendingResolve) { this._pendingResolve = parent._pendingResolve.then(() => { @@ -324,10 +325,13 @@ export class VueElement } } - private _unmount(keepParent?: boolean) { - if (this._ob) { - this._ob.disconnect() - this._ob = null + private _unmount(keepParentAndOb?: boolean) { + if (!keepParentAndOb) { + this._parent = undefined + if (this._ob) { + this._ob.disconnect() + this._ob = null + } } this._app && this._app.unmount() if (this._instance) { @@ -340,8 +344,6 @@ export class VueElement this._instance.ce = undefined } this._app = this._instance = null - if (!keepParent) this._parent = undefined - this._resolved = false } disconnectedCallback(): void { @@ -353,6 +355,17 @@ export class VueElement }) } + private _observe() { + if (!this._ob) { + this._ob = new MutationObserver(mutations => { + for (const m of mutations) { + this._setAttr(m.attributeName!) + } + }) + } + this._ob.observe(this, { attributes: true }) + } + /** * resolve inner component definition (handle possible async component) */ @@ -367,13 +380,7 @@ export class VueElement } // watch future attr changes - this._ob = new MutationObserver(mutations => { - for (const m of mutations) { - this._setAttr(m.attributeName!) - } - }) - - this._ob.observe(this, { attributes: true }) + this._observe() const resolve = (def: InnerComponentDef, isAsync = false) => { this._resolved = true @@ -534,7 +541,7 @@ export class VueElement } else if (!val) { this.removeAttribute(hyphenate(key)) } - ob && ob.observe(this, { attributes: true }) + this._observe() } } }