Skip to content

Commit bbb5be2

Browse files
authored
fix(custom-element): disconnect MutationObserver in nextTick in case that custom elements are moved (#10613)
Closes #10610
1 parent 392bd9b commit bbb5be2

File tree

2 files changed

+53
-4
lines changed

2 files changed

+53
-4
lines changed

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

+49
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import {
22
type Ref,
33
type VueElement,
4+
createApp,
45
defineAsyncComponent,
56
defineComponent,
67
defineCustomElement,
@@ -60,6 +61,54 @@ describe('defineCustomElement', () => {
6061
expect(e.shadowRoot!.innerHTML).toBe('')
6162
})
6263

64+
// #10610
65+
test('When elements move, avoid prematurely disconnecting MutationObserver', async () => {
66+
const CustomInput = defineCustomElement({
67+
props: ['value'],
68+
emits: ['update'],
69+
setup(props, { emit }) {
70+
return () =>
71+
h('input', {
72+
type: 'number',
73+
value: props.value,
74+
onInput: (e: InputEvent) => {
75+
const num = (e.target! as HTMLInputElement).valueAsNumber
76+
emit('update', Number.isNaN(num) ? null : num)
77+
},
78+
})
79+
},
80+
})
81+
customElements.define('my-el-input', CustomInput)
82+
const num = ref('12')
83+
const containerComp = defineComponent({
84+
setup() {
85+
return () => {
86+
return h('div', [
87+
h('my-el-input', {
88+
value: num.value,
89+
onUpdate: ($event: CustomEvent) => {
90+
num.value = $event.detail[0]
91+
},
92+
}),
93+
h('div', { id: 'move' }),
94+
])
95+
}
96+
},
97+
})
98+
const app = createApp(containerComp)
99+
app.mount(container)
100+
const myInputEl = container.querySelector('my-el-input')!
101+
const inputEl = myInputEl.shadowRoot!.querySelector('input')!
102+
await nextTick()
103+
expect(inputEl.value).toBe('12')
104+
const moveEl = container.querySelector('#move')!
105+
moveEl.append(myInputEl)
106+
await nextTick()
107+
myInputEl.removeAttribute('value')
108+
await nextTick()
109+
expect(inputEl.value).toBe('')
110+
})
111+
63112
test('should not unmount on move', async () => {
64113
container.innerHTML = `<div><my-element></my-element></div>`
65114
const e = container.childNodes[0].childNodes[0] as VueElement

packages/runtime-dom/src/apiCustomElement.ts

+4-4
Original file line numberDiff line numberDiff line change
@@ -215,12 +215,12 @@ export class VueElement extends BaseClass {
215215

216216
disconnectedCallback() {
217217
this._connected = false
218-
if (this._ob) {
219-
this._ob.disconnect()
220-
this._ob = null
221-
}
222218
nextTick(() => {
223219
if (!this._connected) {
220+
if (this._ob) {
221+
this._ob.disconnect()
222+
this._ob = null
223+
}
224224
render(null, this.shadowRoot!)
225225
this._instance = null
226226
}

0 commit comments

Comments
 (0)