From: daiwei Date: Sat, 12 Oct 2024 03:42:30 +0000 (+0800) Subject: fix(vModel): avoid updates caused by side effects of the click X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=3ee62bae0e5712cb4302cd0402de5acb685dd9df;p=thirdparty%2Fvuejs%2Fcore.git fix(vModel): avoid updates caused by side effects of the click --- diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 5057e16d47..7d93b705d7 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -122,6 +122,9 @@ export const vModelCheckbox: ModelDirective = { deep: true, created(el, _, vnode) { el[assignKey] = getModelAssigner(vnode) + addEventListener(el, 'mousedown', () => { + ;(el as any)._willChange = true + }) addEventListener(el, 'change', () => { const modelValue = (el as any)._modelValue const elementValue = getValue(el) @@ -153,6 +156,10 @@ export const vModelCheckbox: ModelDirective = { // set initial checked on mount to wait for true-value/false-value mounted: setChecked, beforeUpdate(el, binding, vnode) { + if ((el as any)._willChange) { + ;(el as any)._willChange = false + return + } el[assignKey] = getModelAssigner(vnode) setChecked(el, binding, vnode) }, @@ -160,7 +167,7 @@ export const vModelCheckbox: ModelDirective = { function setChecked( el: HTMLInputElement, - { value, oldValue }: DirectiveBinding, + { value }: DirectiveBinding, vnode: VNode, ) { // store the v-model value on the element so it can be accessed by the @@ -173,7 +180,6 @@ function setChecked( } else if (isSet(value)) { checked = value.has(vnode.props!.value) } else { - if (value === oldValue) return checked = looseEqual(value, getCheckboxValue(el, true)) } @@ -204,6 +210,9 @@ export const vModelSelect: ModelDirective = { deep: true, created(el, { value, modifiers: { number } }, vnode) { const isSetModel = isSet(value) + addEventListener(el, 'mousedown', () => { + ;(el as any)._willChange = true + }) addEventListener(el, 'change', () => { const selectedVal = Array.prototype.filter .call(el.options, (o: HTMLOptionElement) => o.selected) @@ -234,6 +243,10 @@ export const vModelSelect: ModelDirective = { }, updated(el, { value }) { if (!el._assigning) { + if ((el as any)._willChange) { + ;(el as any)._willChange = false + return + } setSelected(el, value) } }, diff --git a/packages/vue/__tests__/e2e/vModel.spec.ts b/packages/vue/__tests__/e2e/vModel.spec.ts index e1a06bda53..598379d714 100644 --- a/packages/vue/__tests__/e2e/vModel.spec.ts +++ b/packages/vue/__tests__/e2e/vModel.spec.ts @@ -1,7 +1,7 @@ import path from 'node:path' import { setupPuppeteer } from './e2eUtils' -const { page, click, isChecked } = setupPuppeteer() +const { page, click, isChecked, html, value } = setupPuppeteer() import { nextTick } from 'vue' beforeEach(async () => { @@ -55,3 +55,94 @@ test('checkbox click with v-model', async () => { expect(await isChecked('#first')).toBe(false) expect(await isChecked('#second')).toBe(true) }) + +// #8638 +test('checkbox click with v-model array value', async () => { + await page().evaluate(() => { + const { createApp, ref } = (window as any).Vue + createApp({ + template: ` + {{cls}} + + `, + setup() { + const inputModel = ref([]) + const count = ref(0) + const change = () => { + count.value++ + } + return { + inputModel, + change, + cls: count, + } + }, + }).mount('#app') + }) + + expect(await isChecked('#checkEl')).toBe(false) + expect(await html('#app')).toMatchInlineSnapshot( + `"0 "`, + ) + + await click('#checkEl') + await nextTick() + expect(await isChecked('#checkEl')).toBe(true) + expect(await html('#app')).toMatchInlineSnapshot( + `"1 "`, + ) + + await click('#checkEl') + await nextTick() + expect(await isChecked('#checkEl')).toBe(false) + expect(await html('#app')).toMatchInlineSnapshot( + `"2 "`, + ) +}) + +// #8579 +test('select click with v-model', async () => { + await page().evaluate(() => { + const { createApp } = (window as any).Vue + createApp({ + template: ` +

+ Changed: {{changed}} +

+

+ Chosen: {{chosen}} +

+
+ +
+ `, + data() { + return { + choices: ['A', 'B'], + chosen: 'A', + changed: false, + } + }, + }).mount('#app') + }) + + expect(await value('#selectEl')).toBe('A') + expect(await html('#app')).toMatchInlineSnapshot( + `"

Changed: false

Chosen: A

"`, + ) + + await page().select('#selectEl', 'B') + await nextTick() + expect(await value('#selectEl')).toBe('B') + expect(await html('#app')).toMatchInlineSnapshot( + `"

Changed: true

Chosen: B

"`, + ) +})