]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(custom-element): set prop runs pending mutations before disconnect (#13897)
authorAlex Snezhko <alexsnezhko89@gmail.com>
Wed, 24 Sep 2025 09:42:11 +0000 (02:42 -0700)
committerGitHub <noreply@github.com>
Wed, 24 Sep 2025 09:42:11 +0000 (17:42 +0800)
close #13315

packages/runtime-dom/__tests__/customElement.spec.ts
packages/runtime-dom/src/apiCustomElement.ts

index 9db0de9cf74e117b629a70c4c4c309700016e2dd..8b3b440f24f2f31c05a8a5452475aee7d9680c88 100644 (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: {
index d99a9341bd99f47085edba8de66ce16cabb4c5b9..6d6dccc6e908edc1439efe7f5f9217e9ae25a17f 100644 (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') {