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') 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 () => { test('props via hyphen property', async () => {
const Comp = defineCustomElement({ const Comp = defineCustomElement({
props: { 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) * resolve inner component definition (handle possible async component)
*/ */
@ -360,11 +366,7 @@ export class VueElement
} }
// watch future attr changes // watch future attr changes
this._ob = new MutationObserver(mutations => { this._ob = new MutationObserver(this._processMutations.bind(this))
for (const m of mutations) {
this._setAttr(m.attributeName!)
}
})
this._ob.observe(this, { attributes: true }) this._ob.observe(this, { attributes: true })
@ -514,7 +516,10 @@ export class VueElement
// reflect // reflect
if (shouldReflect) { if (shouldReflect) {
const ob = this._ob const ob = this._ob
ob && ob.disconnect() if (ob) {
this._processMutations(ob.takeRecords())
ob.disconnect()
}
if (val === true) { if (val === true) {
this.setAttribute(hyphenate(key), '') this.setAttribute(hyphenate(key), '')
} else if (typeof val === 'string' || typeof val === 'number') { } else if (typeof val === 'string' || typeof val === 'number') {