await nextTick()
expect(data.value).toMatchObject([fooValue, barValue])
+ // reset
foo.selected = false
bar.selected = false
+ triggerEvent('change', input)
+ await nextTick()
+ expect(data.value).toMatchObject([])
+
data.value = [fooValue, barValue]
await nextTick()
expect(foo.selected).toEqual(true)
expect(bar.selected).toEqual(true)
+ // reset
foo.selected = false
bar.selected = false
+ triggerEvent('change', input)
+ await nextTick()
+ expect(data.value).toMatchObject([])
+
data.value = [{ foo: 1 }, { bar: 1 }]
await nextTick()
// looseEqual
type DirectiveHook,
type ObjectDirective,
type VNode,
+ nextTick,
warn,
} from '@vue/runtime-core'
import { addEventListener } from '../modules/events'
const assignKey = Symbol('_assign')
-type ModelDirective<T> = ObjectDirective<T & { [assignKey]: AssignerFn }>
+type ModelDirective<T> = ObjectDirective<
+ T & { [assignKey]: AssignerFn; _assigning?: boolean }
+>
// We are exporting the v-model runtime directly as vnode hooks so that it can
// be tree-shaken in case v-model is never used.
: selectedVal
: selectedVal[0],
)
+ el._assigning = true
+ nextTick(() => {
+ el._assigning = false
+ })
})
el[assignKey] = getModelAssigner(vnode)
},
// set value in mounted & updated because <select> relies on its children
// <option>s.
- mounted(el, { value }) {
- setSelected(el, value)
+ mounted(el, { value, oldValue, modifiers: { number } }) {
+ setSelected(el, value, oldValue, number)
},
beforeUpdate(el, _binding, vnode) {
el[assignKey] = getModelAssigner(vnode)
},
- updated(el, { value }) {
- setSelected(el, value)
+ updated(el, { value, oldValue, modifiers: { number } }) {
+ if (!el._assigning) {
+ setSelected(el, value, oldValue, number)
+ }
},
}
-function setSelected(el: HTMLSelectElement, value: any) {
+function setSelected(
+ el: HTMLSelectElement,
+ value: any,
+ oldValue: any,
+ number: boolean,
+) {
const isMultiple = el.multiple
- if (isMultiple && !isArray(value) && !isSet(value)) {
+ const isArrayValue = isArray(value)
+ if (isMultiple && !isArrayValue && !isSet(value)) {
__DEV__ &&
warn(
`<select multiple v-model> expects an Array or Set value for its binding, ` +
)
return
}
+
+ // fast path for updates triggered by other changes
+ if (isArrayValue && looseEqual(value, oldValue)) {
+ return
+ }
+
for (let i = 0, l = el.options.length; i < l; i++) {
const option = el.options[i]
const optionValue = getValue(option)
if (isMultiple) {
- if (isArray(value)) {
- option.selected = looseIndexOf(value, optionValue) > -1
+ if (isArrayValue) {
+ const optionType = typeof optionValue
+ // fast path for string / number values
+ if (optionType === 'string' || optionType === 'number') {
+ option.selected = value.includes(
+ number ? looseToNumber(optionValue) : optionValue,
+ )
+ } else {
+ option.selected = looseIndexOf(value, optionValue) > -1
+ }
} else {
option.selected = value.has(optionValue)
}