mirror of https://github.com/vuejs/core.git
feat(runtime-vapor): implement vModelCheckbox & vModelDynamic
This commit is contained in:
parent
f4f467811a
commit
4be349ebc4
|
@ -7,13 +7,17 @@ import {
|
||||||
looseToNumber,
|
looseToNumber,
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
import type { ComponentInternalInstance } from '../component'
|
import type { ComponentInternalInstance } from '../component'
|
||||||
import type { ObjectDirective } from '../directive'
|
import type {
|
||||||
|
DirectiveBinding,
|
||||||
|
DirectiveHook,
|
||||||
|
DirectiveHookName,
|
||||||
|
ObjectDirective,
|
||||||
|
} from '../directive'
|
||||||
import { addEventListener } from '../dom/event'
|
import { addEventListener } from '../dom/event'
|
||||||
import { nextTick } from '../scheduler'
|
import { nextTick } from '../scheduler'
|
||||||
import { warn } from '../warning'
|
import { warn } from '../warning'
|
||||||
|
|
||||||
type AssignerFn = (value: any) => void
|
type AssignerFn = (value: any) => void
|
||||||
|
|
||||||
function getModelAssigner(
|
function getModelAssigner(
|
||||||
el: Element,
|
el: Element,
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
|
@ -117,10 +121,7 @@ export const vModelRadio = {}
|
||||||
export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
|
export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
|
||||||
// <select multiple> value need to be deep traversed
|
// <select multiple> value need to be deep traversed
|
||||||
deep: true,
|
deep: true,
|
||||||
beforeMount(
|
beforeMount(el, { value, instance, modifiers: { number = false } = {} }) {
|
||||||
el,
|
|
||||||
{ value, oldValue, instance, modifiers: { number = false } = {} },
|
|
||||||
) {
|
|
||||||
const isSetModel = isSet(value)
|
const isSetModel = isSet(value)
|
||||||
addEventListener(el, 'change', () => {
|
addEventListener(el, 'change', () => {
|
||||||
const selectedVal = Array.prototype.filter
|
const selectedVal = Array.prototype.filter
|
||||||
|
@ -142,7 +143,7 @@ export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
assignFnMap.set(el, getModelAssigner(el, instance))
|
assignFnMap.set(el, getModelAssigner(el, instance))
|
||||||
setSelected(el, instance, value, oldValue, number)
|
setSelected(el, instance, value, number)
|
||||||
},
|
},
|
||||||
beforeUpdate(el, { instance }) {
|
beforeUpdate(el, { instance }) {
|
||||||
assignFnMap.set(el, getModelAssigner(el, instance))
|
assignFnMap.set(el, getModelAssigner(el, instance))
|
||||||
|
@ -152,7 +153,7 @@ export const vModelSelect: ObjectDirective<HTMLSelectElement, any, 'number'> = {
|
||||||
{ value, oldValue, instance, modifiers: { number = false } = {} },
|
{ value, oldValue, instance, modifiers: { number = false } = {} },
|
||||||
) {
|
) {
|
||||||
if (!assigningMap.get(el)) {
|
if (!assigningMap.get(el)) {
|
||||||
setSelected(el, instance, value, oldValue, number)
|
setSelected(el, instance, value, number)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
@ -161,7 +162,6 @@ function setSelected(
|
||||||
el: HTMLSelectElement,
|
el: HTMLSelectElement,
|
||||||
instance: ComponentInternalInstance,
|
instance: ComponentInternalInstance,
|
||||||
value: any,
|
value: any,
|
||||||
oldValue: any,
|
|
||||||
number: boolean,
|
number: boolean,
|
||||||
) {
|
) {
|
||||||
const isMultiple = el.multiple
|
const isMultiple = el.multiple
|
||||||
|
@ -213,6 +213,125 @@ function getValue(
|
||||||
return (metadata && metadata.props.value) || el.value
|
return (metadata && metadata.props.value) || el.value
|
||||||
}
|
}
|
||||||
|
|
||||||
export const vModelCheckbox = {}
|
// retrieve raw value for true-value and false-value set via :true-value or :false-value bindings
|
||||||
|
function getCheckboxValue(
|
||||||
|
el: HTMLInputElement,
|
||||||
|
instance: ComponentInternalInstance,
|
||||||
|
checked: boolean,
|
||||||
|
) {
|
||||||
|
const metadata = instance.metadata.get(el)
|
||||||
|
const props = metadata && metadata.props
|
||||||
|
const key = checked ? 'true-value' : 'false-value'
|
||||||
|
if (props && key in props) {
|
||||||
|
return props[key]
|
||||||
|
}
|
||||||
|
if (el.hasAttribute(key)) {
|
||||||
|
return el.getAttribute(key)
|
||||||
|
}
|
||||||
|
return checked
|
||||||
|
}
|
||||||
|
|
||||||
export const vModelDynamic = {}
|
const setChecked: DirectiveHook<HTMLInputElement> = (
|
||||||
|
el,
|
||||||
|
{ value, oldValue, instance },
|
||||||
|
) => {
|
||||||
|
if (isArray(value)) {
|
||||||
|
el.checked = looseIndexOf(value, getValue(el, instance)) > -1
|
||||||
|
} else if (isSet(value)) {
|
||||||
|
el.checked = value.has(getValue(el, instance))
|
||||||
|
} else if (value !== oldValue) {
|
||||||
|
el.checked = looseEqual(value, getCheckboxValue(el, instance, true))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vModelCheckbox: ObjectDirective<HTMLInputElement> = {
|
||||||
|
// #4096 array checkboxes need to be deep traversed
|
||||||
|
deep: true,
|
||||||
|
beforeMount(el, binding) {
|
||||||
|
const { instance } = binding
|
||||||
|
assignFnMap.set(el, getModelAssigner(el, binding.instance))
|
||||||
|
|
||||||
|
addEventListener(el, 'change', () => {
|
||||||
|
const modelValue = binding.value
|
||||||
|
const elementValue = getValue(el, instance)
|
||||||
|
const checked = el.checked
|
||||||
|
const assigner = assignFnMap.get(el)!
|
||||||
|
if (isArray(modelValue)) {
|
||||||
|
const index = looseIndexOf(modelValue, elementValue)
|
||||||
|
const found = index !== -1
|
||||||
|
if (checked && !found) {
|
||||||
|
assigner(modelValue.concat(elementValue))
|
||||||
|
} else if (!checked && found) {
|
||||||
|
const filtered = [...modelValue]
|
||||||
|
filtered.splice(index, 1)
|
||||||
|
assigner(filtered)
|
||||||
|
}
|
||||||
|
} else if (isSet(modelValue)) {
|
||||||
|
const cloned = new Set(modelValue)
|
||||||
|
if (checked) {
|
||||||
|
cloned.add(elementValue)
|
||||||
|
} else {
|
||||||
|
cloned.delete(elementValue)
|
||||||
|
}
|
||||||
|
assigner(cloned)
|
||||||
|
} else {
|
||||||
|
assigner(getCheckboxValue(el, instance, checked))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
},
|
||||||
|
// set initial checked on mount to wait for true-value/false-value
|
||||||
|
mounted: setChecked,
|
||||||
|
beforeUpdate(el, binding) {
|
||||||
|
assignFnMap.set(el, getModelAssigner(el, binding.instance))
|
||||||
|
setChecked(el, binding)
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
export const vModelDynamic: ObjectDirective<
|
||||||
|
HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement
|
||||||
|
> = {
|
||||||
|
beforeMount(el, binding) {
|
||||||
|
callModelHook(el, binding, 'beforeMount')
|
||||||
|
},
|
||||||
|
mounted(el, binding) {
|
||||||
|
callModelHook(el, binding, 'mounted')
|
||||||
|
},
|
||||||
|
beforeUpdate(el, binding) {
|
||||||
|
callModelHook(el, binding, 'beforeUpdate')
|
||||||
|
},
|
||||||
|
updated(el, binding) {
|
||||||
|
callModelHook(el, binding, 'updated')
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveDynamicModel(
|
||||||
|
tagName: string,
|
||||||
|
type: string | null,
|
||||||
|
): ObjectDirective {
|
||||||
|
switch (tagName) {
|
||||||
|
case 'SELECT':
|
||||||
|
return vModelSelect
|
||||||
|
case 'TEXTAREA':
|
||||||
|
return vModelText
|
||||||
|
default:
|
||||||
|
switch (type) {
|
||||||
|
case 'checkbox':
|
||||||
|
return vModelCheckbox
|
||||||
|
case 'radio':
|
||||||
|
return vModelRadio
|
||||||
|
default:
|
||||||
|
return vModelText
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function callModelHook(
|
||||||
|
el: HTMLInputElement | HTMLSelectElement | HTMLTextAreaElement,
|
||||||
|
binding: DirectiveBinding,
|
||||||
|
hook: DirectiveHookName,
|
||||||
|
) {
|
||||||
|
const type = el.getAttribute('type')
|
||||||
|
const modelToUse = resolveDynamicModel(el.tagName, type)
|
||||||
|
const fn = modelToUse[hook]
|
||||||
|
fn && fn(el, binding)
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue