diff --git a/packages/runtime-dom/__tests__/directives/vModel.spec.ts b/packages/runtime-dom/__tests__/directives/vModel.spec.ts index 534e5dfe98e..969a637af95 100644 --- a/packages/runtime-dom/__tests__/directives/vModel.spec.ts +++ b/packages/runtime-dom/__tests__/directives/vModel.spec.ts @@ -1,5 +1,6 @@ import { type VNode, + computed, defineComponent, h, nextTick, @@ -1409,6 +1410,51 @@ describe('vModel', () => { expect(bar.selected).toEqual(true) }) + it('multiple select (v-model has custom setter)', async () => { + const selected = ref([]) + + const multipleSelected = computed({ + get() { + return selected.value + }, + set(newVal) { + selected.value = newVal.slice(0, 1) + }, + }) + const setValue = (v: any) => { + multipleSelected.value = v + } + const component = defineComponent({ + render() { + return [ + withVModel( + h( + 'select', + { + multiple: true, + 'onUpdate:modelValue': setValue, + }, + [h('option', { value: '1' }), h('option', { value: '2' })], + ), + multipleSelected.value, + ), + ] + }, + }) + render(h(component), root) + + await nextTick() + const select = root.querySelector('select') + const [foo, bar] = root.querySelectorAll('option') + foo.selected = true + bar.selected = true + triggerEvent('change', select) + await nextTick() + expect(selected.value).toEqual(['1']) + expect(foo.selected).toEqual(true) + expect(bar.selected).toEqual(false) + }) + // #10503 test('equal value with a leading 0 should trigger update.', async () => { const setNum = function (this: any, value: any) { diff --git a/packages/runtime-dom/src/directives/vModel.ts b/packages/runtime-dom/src/directives/vModel.ts index 5057e16d472..01357c85ee4 100644 --- a/packages/runtime-dom/src/directives/vModel.ts +++ b/packages/runtime-dom/src/directives/vModel.ts @@ -4,6 +4,7 @@ import { type ObjectDirective, type VNode, nextTick, + toRaw, warn, } from '@vue/runtime-core' import { addEventListener } from '../modules/events' @@ -38,9 +39,15 @@ function onCompositionEnd(e: Event) { } const assignKey: unique symbol = Symbol('_assign') +const assignValueKey: unique symbol = Symbol('_value') +const assigningKey: unique symbol = Symbol('_assigning') type ModelDirective = ObjectDirective< - T & { [assignKey]: AssignerFn; _assigning?: boolean }, + T & { + [assignKey]: AssignerFn + [assignValueKey]: any + [assigningKey]?: boolean + }, any, Modifiers > @@ -210,16 +217,15 @@ export const vModelSelect: ModelDirective = { .map((o: HTMLOptionElement) => number ? looseToNumber(getValue(o)) : getValue(o), ) - el[assignKey]( - el.multiple - ? isSetModel - ? new Set(selectedVal) - : selectedVal - : selectedVal[0], - ) - el._assigning = true + const value = (el[assignValueKey] = el.multiple + ? isSetModel + ? new Set(selectedVal) + : selectedVal + : selectedVal[0]) + el[assignKey](value) + el[assigningKey] = true nextTick(() => { - el._assigning = false + el[assigningKey] = false }) }) el[assignKey] = getModelAssigner(vnode) @@ -233,7 +239,7 @@ export const vModelSelect: ModelDirective = { el[assignKey] = getModelAssigner(vnode) }, updated(el, { value }) { - if (!el._assigning) { + if (!el[assigningKey] || toRaw(value) !== el[assignValueKey]) { setSelected(el, value) } },