|  | 
| 3 | 3 |  type DirectiveHook, | 
| 4 | 4 |  type ObjectDirective, | 
| 5 | 5 |  type VNode, | 
|  | 6 | + nextTick, | 
| 6 | 7 |  warn, | 
| 7 | 8 | } from '@vue/runtime-core' | 
| 8 | 9 | import { addEventListener } from '../modules/events' | 
| @@ -38,7 +39,9 @@ function onCompositionEnd(e: Event) { | 
| 38 | 39 | 
 | 
| 39 | 40 | const assignKey = Symbol('_assign') | 
| 40 | 41 | 
 | 
| 41 |  | -type ModelDirective<T> = ObjectDirective<T & { [assignKey]: AssignerFn }> | 
|  | 42 | +type ModelDirective<T> = ObjectDirective< | 
|  | 43 | + T & { [assignKey]: AssignerFn; _assigning?: boolean } | 
|  | 44 | +> | 
| 42 | 45 | 
 | 
| 43 | 46 | // We are exporting the v-model runtime directly as vnode hooks so that it can | 
| 44 | 47 | // be tree-shaken in case v-model is never used. | 
| @@ -197,38 +200,64 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = { | 
| 197 | 200 |  : selectedVal | 
| 198 | 201 |  : selectedVal[0], | 
| 199 | 202 |  ) | 
|  | 203 | + el._assigning = true | 
|  | 204 | + nextTick(() => { | 
|  | 205 | + el._assigning = false | 
|  | 206 | + }) | 
| 200 | 207 |  }) | 
| 201 | 208 |  el[assignKey] = getModelAssigner(vnode) | 
| 202 | 209 |  }, | 
| 203 | 210 |  // set value in mounted & updated because <select> relies on its children | 
| 204 | 211 |  // <option>s. | 
| 205 |  | - mounted(el, { value }) { | 
| 206 |  | - setSelected(el, value) | 
|  | 212 | + mounted(el, { value, oldValue, modifiers: { number } }) { | 
|  | 213 | + setSelected(el, value, oldValue, number) | 
| 207 | 214 |  }, | 
| 208 | 215 |  beforeUpdate(el, _binding, vnode) { | 
| 209 | 216 |  el[assignKey] = getModelAssigner(vnode) | 
| 210 | 217 |  }, | 
| 211 |  | - updated(el, { value }) { | 
| 212 |  | - setSelected(el, value) | 
|  | 218 | + updated(el, { value, oldValue, modifiers: { number } }) { | 
|  | 219 | + if (!el._assigning) { | 
|  | 220 | + setSelected(el, value, oldValue, number) | 
|  | 221 | + } | 
| 213 | 222 |  }, | 
| 214 | 223 | } | 
| 215 | 224 | 
 | 
| 216 |  | -function setSelected(el: HTMLSelectElement, value: any) { | 
|  | 225 | +function setSelected( | 
|  | 226 | + el: HTMLSelectElement, | 
|  | 227 | + value: any, | 
|  | 228 | + oldValue: any, | 
|  | 229 | + number: boolean, | 
|  | 230 | +) { | 
| 217 | 231 |  const isMultiple = el.multiple | 
| 218 |  | - if (isMultiple && !isArray(value) && !isSet(value)) { | 
|  | 232 | + const isArrayValue = isArray(value) | 
|  | 233 | + if (isMultiple && !isArrayValue && !isSet(value)) { | 
| 219 | 234 |  __DEV__ && | 
| 220 | 235 |  warn( | 
| 221 | 236 |  `<select multiple v-model> expects an Array or Set value for its binding, ` + | 
| 222 | 237 |  `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`, | 
| 223 | 238 |  ) | 
| 224 | 239 |  return | 
| 225 | 240 |  } | 
|  | 241 | + | 
|  | 242 | + // fast path for updates triggered by other changes | 
|  | 243 | + if (isArrayValue && looseEqual(value, oldValue)) { | 
|  | 244 | + return | 
|  | 245 | + } | 
|  | 246 | + | 
| 226 | 247 |  for (let i = 0, l = el.options.length; i < l; i++) { | 
| 227 | 248 |  const option = el.options[i] | 
| 228 | 249 |  const optionValue = getValue(option) | 
| 229 | 250 |  if (isMultiple) { | 
| 230 |  | - if (isArray(value)) { | 
| 231 |  | - option.selected = looseIndexOf(value, optionValue) > -1 | 
|  | 251 | + if (isArrayValue) { | 
|  | 252 | + const optionType = typeof optionValue | 
|  | 253 | + // fast path for string / number values | 
|  | 254 | + if (optionType === 'string' || optionType === 'number') { | 
|  | 255 | + option.selected = value.includes( | 
|  | 256 | + number ? looseToNumber(optionValue) : optionValue, | 
|  | 257 | + ) | 
|  | 258 | + } else { | 
|  | 259 | + option.selected = looseIndexOf(value, optionValue) > -1 | 
|  | 260 | + } | 
| 232 | 261 |  } else { | 
| 233 | 262 |  option.selected = value.has(optionValue) | 
| 234 | 263 |  } | 
|  | 
0 commit comments