fix(v-model): handle dynamic assigners and array assigners

close #923
This commit is contained in:
Evan You
2020-04-04 20:51:42 -04:00
parent c1d5928f3b
commit f42d11e8e1
5 changed files with 122 additions and 40 deletions

View File

@@ -6,10 +6,14 @@ import {
warn
} from '@vue/runtime-core'
import { addEventListener } from '../modules/events'
import { isArray, looseEqual, looseIndexOf } from '@vue/shared'
import { isArray, looseEqual, looseIndexOf, invokeArrayFns } from '@vue/shared'
const getModelAssigner = (vnode: VNode): ((value: any) => void) =>
vnode.props!['onUpdate:modelValue']
type AssignerFn = (value: any) => void
const getModelAssigner = (vnode: VNode): AssignerFn => {
const fn = vnode.props!['onUpdate:modelValue']
return isArray(fn) ? value => invokeArrayFns(fn, value) : fn
}
function onCompositionStart(e: Event) {
;(e.target as any).composing = true
@@ -34,14 +38,16 @@ function toNumber(val: string): number | string {
return isNaN(n) ? val : n
}
type ModelDirective<T> = ObjectDirective<T & { _assign: AssignerFn }>
// 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.
export const vModelText: ObjectDirective<
export const vModelText: ModelDirective<
HTMLInputElement | HTMLTextAreaElement
> = {
beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el.value = value
const assign = getModelAssigner(vnode)
el._assign = getModelAssigner(vnode)
const castToNumber = number || el.type === 'number'
addEventListener(el, lazy ? 'change' : 'input', () => {
let domValue: string | number = el.value
@@ -50,7 +56,7 @@ export const vModelText: ObjectDirective<
} else if (castToNumber) {
domValue = toNumber(domValue)
}
assign(domValue)
el._assign(domValue)
})
if (trim) {
addEventListener(el, 'change', () => {
@@ -67,7 +73,8 @@ export const vModelText: ObjectDirective<
addEventListener(el, 'change', onCompositionEnd)
}
},
beforeUpdate(el, { value, oldValue, modifiers: { trim, number } }) {
beforeUpdate(el, { value, oldValue, modifiers: { trim, number } }, vnode) {
el._assign = getModelAssigner(vnode)
if (value === oldValue) {
return
}
@@ -83,14 +90,15 @@ export const vModelText: ObjectDirective<
}
}
export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
beforeMount(el, binding, vnode) {
setChecked(el, binding, vnode)
const assign = getModelAssigner(vnode)
el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => {
const modelValue = (el as any)._modelValue
const elementValue = getValue(el)
const checked = el.checked
const assign = el._assign
if (isArray(modelValue)) {
const index = looseIndexOf(modelValue, elementValue)
const found = index !== -1
@@ -106,7 +114,10 @@ export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
}
})
},
beforeUpdate: setChecked
beforeUpdate(el, binding, vnode) {
setChecked(el, binding, vnode)
el._assign = getModelAssigner(vnode)
}
}
function setChecked(
@@ -124,33 +135,37 @@ function setChecked(
}
}
export const vModelRadio: ObjectDirective<HTMLInputElement> = {
export const vModelRadio: ModelDirective<HTMLInputElement> = {
beforeMount(el, { value }, vnode) {
el.checked = looseEqual(value, vnode.props!.value)
const assign = getModelAssigner(vnode)
el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => {
assign(getValue(el))
el._assign(getValue(el))
})
},
beforeUpdate(el, { value, oldValue }, vnode) {
el._assign = getModelAssigner(vnode)
if (value !== oldValue) {
el.checked = looseEqual(value, vnode.props!.value)
}
}
}
export const vModelSelect: ObjectDirective<HTMLSelectElement> = {
export const vModelSelect: ModelDirective<HTMLSelectElement> = {
// use mounted & updated because <select> relies on its children <option>s.
mounted(el, { value }, vnode) {
setSelected(el, value)
const assign = getModelAssigner(vnode)
el._assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => {
const selectedVal = Array.prototype.filter
.call(el.options, (o: HTMLOptionElement) => o.selected)
.map(getValue)
assign(el.multiple ? selectedVal : selectedVal[0])
el._assign(el.multiple ? selectedVal : selectedVal[0])
})
},
beforeUpdate(el, _binding, vnode) {
el._assign = getModelAssigner(vnode)
},
updated(el, { value }) {
setSelected(el, value)
}