fix(custom-element): set prop runs pending mutations before disconnect (#13897)

close #13315
This commit is contained in:
Alex Snezhko 2025-09-24 02:42:11 -07:00 committed by GitHub
parent e388f1a09f
commit c4a88cdd0d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
2 changed files with 36 additions and 6 deletions

View File

@ -223,6 +223,31 @@ describe('defineCustomElement', () => {
expect(e.getAttribute('baz-qux')).toBe('four')
})
test('props via attributes and properties changed together', async () => {
const e = new E()
e.foo = 'foo1'
e.bar = { x: 'bar1' }
container.appendChild(e)
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe('<div>foo1</div><div>bar1</div>')
// change attr then property
e.setAttribute('foo', 'foo2')
e.bar = { x: 'bar2' }
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe('<div>foo2</div><div>bar2</div>')
expect(e.getAttribute('foo')).toBe('foo2')
expect(e.hasAttribute('bar')).toBe(false)
// change prop then attr
e.bar = { x: 'bar3' }
e.setAttribute('foo', 'foo3')
await nextTick()
expect(e.shadowRoot!.innerHTML).toBe('<div>foo3</div><div>bar3</div>')
expect(e.getAttribute('foo')).toBe('foo3')
expect(e.hasAttribute('bar')).toBe(false)
})
test('props via hyphen property', async () => {
const Comp = defineCustomElement({
props: {

View File

@ -346,6 +346,12 @@ export class VueElement
})
}
private _processMutations(mutations: MutationRecord[]) {
for (const m of mutations) {
this._setAttr(m.attributeName!)
}
}
/**
* resolve inner component definition (handle possible async component)
*/
@ -360,11 +366,7 @@ export class VueElement
}
// watch future attr changes
this._ob = new MutationObserver(mutations => {
for (const m of mutations) {
this._setAttr(m.attributeName!)
}
})
this._ob = new MutationObserver(this._processMutations.bind(this))
this._ob.observe(this, { attributes: true })
@ -514,7 +516,10 @@ export class VueElement
// reflect
if (shouldReflect) {
const ob = this._ob
ob && ob.disconnect()
if (ob) {
this._processMutations(ob.takeRecords())
ob.disconnect()
}
if (val === true) {
this.setAttribute(hyphenate(key), '')
} else if (typeof val === 'string' || typeof val === 'number') {