|
|
@@ -3,6 +3,7 @@ import {
|
|
|
type DirectiveHook,
|
|
|
type ObjectDirective,
|
|
|
type VNode,
|
|
|
+ nextTick,
|
|
|
warn,
|
|
|
} from '@vue/runtime-core'
|
|
|
import { addEventListener } from '../modules/events'
|
|
|
@@ -38,7 +39,9 @@ function onCompositionEnd(e: Event) {
|
|
|
|
|
|
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.
|
|
|
@@ -197,25 +200,37 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|
|
: 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, ` +
|
|
|
@@ -223,12 +238,26 @@ function setSelected(el: HTMLSelectElement, value: any) {
|
|
|
)
|
|
|
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)
|
|
|
}
|