feat(v-model): number/trim modifier + array checkbox support

This commit is contained in:
Evan You 2019-10-11 20:35:25 -04:00
parent acf406b779
commit 14aabf0f98

View File

@ -23,16 +23,32 @@ function trigger(el: HTMLElement, type: string) {
el.dispatchEvent(e) el.dispatchEvent(e)
} }
function toNumber(val: string): number | string {
const n = parseFloat(val)
return isNaN(n) ? val : n
}
// We are exporting the v-model runtime directly as vnode hooks so that it can // 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. // be tree-shaken in case v-model is never used.
export const vModelText: Directive<HTMLInputElement | HTMLTextAreaElement> = { export const vModelText: Directive<HTMLInputElement | HTMLTextAreaElement> = {
beforeMount(el, { value, modifiers: { lazy } }, vnode) { beforeMount(el, { value, modifiers: { lazy, trim, number } }, vnode) {
el.value = value el.value = value
const assign = getModelAssigner(vnode) const assign = getModelAssigner(vnode)
const castToNumber = number || el.type === 'number'
addEventListener(el, lazy ? 'change' : 'input', () => { addEventListener(el, lazy ? 'change' : 'input', () => {
// TODO number & trim modifiers let domValue: string | number = el.value
assign(el.value) if (trim) {
domValue = domValue.trim()
} else if (castToNumber) {
domValue = toNumber(domValue)
}
assign(domValue)
}) })
if (trim) {
addEventListener(el, 'change', () => {
el.value = el.value.trim()
})
}
if (!lazy) { if (!lazy) {
addEventListener(el, 'compositionstart', onCompositionStart) addEventListener(el, 'compositionstart', onCompositionStart)
addEventListener(el, 'compositionend', onCompositionEnd) addEventListener(el, 'compositionend', onCompositionEnd)
@ -43,29 +59,56 @@ export const vModelText: Directive<HTMLInputElement | HTMLTextAreaElement> = {
addEventListener(el, 'change', onCompositionEnd) addEventListener(el, 'change', onCompositionEnd)
} }
}, },
beforeUpdate(el, { value }) { beforeUpdate(el, { value, modifiers: { trim, number } }) {
// TODO number & trim handling if (document.activeElement === el) {
if (trim && el.value.trim() === value) {
return
}
if ((number || el.type === 'number') && toNumber(el.value) === value) {
return
}
}
el.value = value el.value = value
} }
} }
export const vModelCheckbox: Directive<HTMLInputElement> = { export const vModelCheckbox: Directive<HTMLInputElement> = {
beforeMount(el, { value }, vnode) { beforeMount(el, binding, vnode) {
// TODO handle array checkbox & number modifier setChecked(el, binding, vnode)
el.checked = !!value
const assign = getModelAssigner(vnode) const assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => { addEventListener(el, 'change', () => {
const modelValue = (el as any)._modelValue
const elementValue = getValue(el)
if (isArray(modelValue)) {
const i = looseIndexOf(modelValue, elementValue)
if (i > -1) {
assign([...modelValue.slice(0, i), ...modelValue.slice(i + 1)])
} else {
assign(modelValue.concat(elementValue))
}
} else {
assign(el.checked) assign(el.checked)
}
}) })
}, },
beforeUpdate(el, { value }) { beforeUpdate: setChecked
el.checked = !!value }
}
function setChecked(
el: HTMLInputElement,
{ value }: DirectiveBinding,
vnode: VNode
) {
// store the v-model value on the element so it can be accessed by the
// change listener.
;(el as any)._modelValue = value
el.checked = isArray(value)
? looseIndexOf(value, vnode.props!.value) > -1
: !!value
} }
export const vModelRadio: Directive<HTMLInputElement> = { export const vModelRadio: Directive<HTMLInputElement> = {
beforeMount(el, { value }, vnode) { beforeMount(el, { value }, vnode) {
// TODO number modifier
el.checked = looseEqual(value, vnode.props!.value) el.checked = looseEqual(value, vnode.props!.value)
const assign = getModelAssigner(vnode) const assign = getModelAssigner(vnode)
addEventListener(el, 'change', () => { addEventListener(el, 'change', () => {
@ -73,7 +116,6 @@ export const vModelRadio: Directive<HTMLInputElement> = {
}) })
}, },
beforeUpdate(el, { value }, vnode) { beforeUpdate(el, { value }, vnode) {
// TODO number modifier
el.checked = looseEqual(value, vnode.props!.value) el.checked = looseEqual(value, vnode.props!.value)
} }
} }
@ -101,23 +143,18 @@ function setSelected(el: HTMLSelectElement, value: any) {
__DEV__ && __DEV__ &&
warn( warn(
`<select multiple v-model> expects an Array value for its binding, ` + `<select multiple v-model> expects an Array value for its binding, ` +
`but got ${Object.prototype.toString.call(value).slice(8, -1)}` `but got ${Object.prototype.toString.call(value).slice(8, -1)}.`
) )
return return
} }
let selected, option
for (let i = 0, l = el.options.length; i < l; i++) { for (let i = 0, l = el.options.length; i < l; i++) {
option = el.options[i] const option = el.options[i]
const optionValue = getValue(option)
if (isMultiple) { if (isMultiple) {
selected = looseIndexOf(value, getValue(option)) > -1 option.selected = looseIndexOf(value, optionValue) > -1
if (option.selected !== selected) {
option.selected = selected
}
} else { } else {
if (looseEqual(getValue(option), value)) { if (looseEqual(getValue(option), value)) {
if (el.selectedIndex !== i) {
el.selectedIndex = i el.selectedIndex = i
}
return return
} }
} }
@ -127,7 +164,7 @@ function setSelected(el: HTMLSelectElement, value: any) {
} }
} }
function looseIndexOf(arr: Array<any>, val: any): number { function looseIndexOf(arr: any[], val: any): number {
for (let i = 0; i < arr.length; i++) { for (let i = 0; i < arr.length; i++) {
if (looseEqual(arr[i], val)) return i if (looseEqual(arr[i], val)) return i
} }