]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
fix(custom-element): disconnect MutationObserver in nextTick in case that custom...
author白雾三语 <32354856+baiwusanyu-c@users.noreply.github.com>
Tue, 21 May 2024 16:14:02 +0000 (00:14 +0800)
committerGitHub <noreply@github.com>
Tue, 21 May 2024 16:14:02 +0000 (00:14 +0800)
Closes #10610

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

index fb746f72c4a2ac85ebd602b1ef4e1f6d24c0acc4..a8bdec77512b39d94a16b1b9bf8dc09bec9ef555 100644 (file)
@@ -1,6 +1,7 @@
 import {
   type Ref,
   type VueElement,
+  createApp,
   defineAsyncComponent,
   defineComponent,
   defineCustomElement,
@@ -60,6 +61,54 @@ describe('defineCustomElement', () => {
       expect(e.shadowRoot!.innerHTML).toBe('')
     })
 
+    // #10610
+    test('When elements move, avoid prematurely disconnecting MutationObserver', async () => {
+      const CustomInput = defineCustomElement({
+        props: ['value'],
+        emits: ['update'],
+        setup(props, { emit }) {
+          return () =>
+            h('input', {
+              type: 'number',
+              value: props.value,
+              onInput: (e: InputEvent) => {
+                const num = (e.target! as HTMLInputElement).valueAsNumber
+                emit('update', Number.isNaN(num) ? null : num)
+              },
+            })
+        },
+      })
+      customElements.define('my-el-input', CustomInput)
+      const num = ref('12')
+      const containerComp = defineComponent({
+        setup() {
+          return () => {
+            return h('div', [
+              h('my-el-input', {
+                value: num.value,
+                onUpdate: ($event: CustomEvent) => {
+                  num.value = $event.detail[0]
+                },
+              }),
+              h('div', { id: 'move' }),
+            ])
+          }
+        },
+      })
+      const app = createApp(containerComp)
+      app.mount(container)
+      const myInputEl = container.querySelector('my-el-input')!
+      const inputEl = myInputEl.shadowRoot!.querySelector('input')!
+      await nextTick()
+      expect(inputEl.value).toBe('12')
+      const moveEl = container.querySelector('#move')!
+      moveEl.append(myInputEl)
+      await nextTick()
+      myInputEl.removeAttribute('value')
+      await nextTick()
+      expect(inputEl.value).toBe('')
+    })
+
     test('should not unmount on move', async () => {
       container.innerHTML = `<div><my-element></my-element></div>`
       const e = container.childNodes[0].childNodes[0] as VueElement
index 01ce2bad4644681273a2ba6b51ec4008f05c40b5..938230277075d1db9223e506fa9ff1bacfc3d095 100644 (file)
@@ -215,12 +215,12 @@ export class VueElement extends BaseClass {
 
   disconnectedCallback() {
     this._connected = false
-    if (this._ob) {
-      this._ob.disconnect()
-      this._ob = null
-    }
     nextTick(() => {
       if (!this._connected) {
+        if (this._ob) {
+          this._ob.disconnect()
+          this._ob = null
+        }
         render(null, this.shadowRoot!)
         this._instance = null
       }