Skip to content

Commit

Permalink
fix(custom-element): change solution
Browse files Browse the repository at this point in the history
  • Loading branch information
lejunyang committed Nov 22, 2024
1 parent 53077a5 commit 3c23328
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 26 deletions.
24 changes: 16 additions & 8 deletions packages/runtime-dom/__tests__/customElement.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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 = `<my-context-el msg="msg1"></my-context-el><my-context-el msg="msg2"></my-context-el>`
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')
})
})

Expand Down
43 changes: 25 additions & 18 deletions packages/runtime-dom/src/apiCustomElement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand All @@ -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) {
Expand All @@ -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 {
Expand All @@ -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)
*/
Expand All @@ -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
Expand Down Expand Up @@ -534,7 +541,7 @@ export class VueElement
} else if (!val) {
this.removeAttribute(hyphenate(key))
}
ob && ob.observe(this, { attributes: true })
this._observe()
}
}
}
Expand Down

0 comments on commit 3c23328

Please sign in to comment.