feat(runtime-dom): allow native Set as v-model checkbox source (#1957)
This commit is contained in:
parent
542680e478
commit
cf1b6c666f
@ -433,6 +433,76 @@ describe('vModel', () => {
|
|||||||
expect(bar.checked).toEqual(false)
|
expect(bar.checked).toEqual(false)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it(`should support Set as a checkbox model`, async () => {
|
||||||
|
const component = defineComponent({
|
||||||
|
data() {
|
||||||
|
return { value: new Set() }
|
||||||
|
},
|
||||||
|
render() {
|
||||||
|
return [
|
||||||
|
withVModel(
|
||||||
|
h('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
class: 'foo',
|
||||||
|
value: 'foo',
|
||||||
|
'onUpdate:modelValue': setValue.bind(this)
|
||||||
|
}),
|
||||||
|
this.value
|
||||||
|
),
|
||||||
|
withVModel(
|
||||||
|
h('input', {
|
||||||
|
type: 'checkbox',
|
||||||
|
class: 'bar',
|
||||||
|
value: 'bar',
|
||||||
|
'onUpdate:modelValue': setValue.bind(this)
|
||||||
|
}),
|
||||||
|
this.value
|
||||||
|
)
|
||||||
|
]
|
||||||
|
}
|
||||||
|
})
|
||||||
|
render(h(component), root)
|
||||||
|
|
||||||
|
const foo = root.querySelector('.foo')
|
||||||
|
const bar = root.querySelector('.bar')
|
||||||
|
const data = root._vnode.component.data
|
||||||
|
|
||||||
|
foo.checked = true
|
||||||
|
triggerEvent('change', foo)
|
||||||
|
await nextTick()
|
||||||
|
expect(data.value).toMatchObject(new Set(['foo']))
|
||||||
|
|
||||||
|
bar.checked = true
|
||||||
|
triggerEvent('change', bar)
|
||||||
|
await nextTick()
|
||||||
|
expect(data.value).toMatchObject(new Set(['foo', 'bar']))
|
||||||
|
|
||||||
|
bar.checked = false
|
||||||
|
triggerEvent('change', bar)
|
||||||
|
await nextTick()
|
||||||
|
expect(data.value).toMatchObject(new Set(['foo']))
|
||||||
|
|
||||||
|
foo.checked = false
|
||||||
|
triggerEvent('change', foo)
|
||||||
|
await nextTick()
|
||||||
|
expect(data.value).toMatchObject(new Set())
|
||||||
|
|
||||||
|
data.value = new Set(['foo'])
|
||||||
|
await nextTick()
|
||||||
|
expect(bar.checked).toEqual(false)
|
||||||
|
expect(foo.checked).toEqual(true)
|
||||||
|
|
||||||
|
data.value = new Set(['bar'])
|
||||||
|
await nextTick()
|
||||||
|
expect(foo.checked).toEqual(false)
|
||||||
|
expect(bar.checked).toEqual(true)
|
||||||
|
|
||||||
|
data.value = new Set()
|
||||||
|
await nextTick()
|
||||||
|
expect(foo.checked).toEqual(false)
|
||||||
|
expect(bar.checked).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
it('should work with radio', async () => {
|
it('should work with radio', async () => {
|
||||||
const component = defineComponent({
|
const component = defineComponent({
|
||||||
data() {
|
data() {
|
||||||
|
@ -11,7 +11,9 @@ import {
|
|||||||
looseEqual,
|
looseEqual,
|
||||||
looseIndexOf,
|
looseIndexOf,
|
||||||
invokeArrayFns,
|
invokeArrayFns,
|
||||||
toNumber
|
toNumber,
|
||||||
|
isSet,
|
||||||
|
looseHas
|
||||||
} from '@vue/shared'
|
} from '@vue/shared'
|
||||||
|
|
||||||
type AssignerFn = (value: any) => void
|
type AssignerFn = (value: any) => void
|
||||||
@ -111,6 +113,14 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
|
|||||||
filtered.splice(index, 1)
|
filtered.splice(index, 1)
|
||||||
assign(filtered)
|
assign(filtered)
|
||||||
}
|
}
|
||||||
|
} else if (isSet(modelValue)) {
|
||||||
|
const found = modelValue.has(elementValue)
|
||||||
|
if (checked && !found) {
|
||||||
|
assign(modelValue.add(elementValue))
|
||||||
|
} else if (!checked && found) {
|
||||||
|
modelValue.delete(elementValue)
|
||||||
|
assign(modelValue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
assign(getCheckboxValue(el, checked))
|
assign(getCheckboxValue(el, checked))
|
||||||
}
|
}
|
||||||
@ -132,6 +142,8 @@ function setChecked(
|
|||||||
;(el as any)._modelValue = value
|
;(el as any)._modelValue = value
|
||||||
if (isArray(value)) {
|
if (isArray(value)) {
|
||||||
el.checked = looseIndexOf(value, vnode.props!.value) > -1
|
el.checked = looseIndexOf(value, vnode.props!.value) > -1
|
||||||
|
} else if (isSet(value)) {
|
||||||
|
el.checked = looseHas(value, vnode.props!.value)
|
||||||
} else if (value !== oldValue) {
|
} else if (value !== oldValue) {
|
||||||
el.checked = looseEqual(value, getCheckboxValue(el, true))
|
el.checked = looseEqual(value, getCheckboxValue(el, true))
|
||||||
}
|
}
|
||||||
@ -178,10 +190,10 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
|
|||||||
|
|
||||||
function setSelected(el: HTMLSelectElement, value: any) {
|
function setSelected(el: HTMLSelectElement, value: any) {
|
||||||
const isMultiple = el.multiple
|
const isMultiple = el.multiple
|
||||||
if (isMultiple && !isArray(value)) {
|
if (isMultiple && !isArray(value) && !isSet(value)) {
|
||||||
__DEV__ &&
|
__DEV__ &&
|
||||||
warn(
|
warn(
|
||||||
`<select multiple v-model> expects an Array value for its binding, ` +
|
`<select multiple v-model> expects an Array or Set 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
|
||||||
@ -190,7 +202,11 @@ function setSelected(el: HTMLSelectElement, value: any) {
|
|||||||
const option = el.options[i]
|
const option = el.options[i]
|
||||||
const optionValue = getValue(option)
|
const optionValue = getValue(option)
|
||||||
if (isMultiple) {
|
if (isMultiple) {
|
||||||
option.selected = looseIndexOf(value, optionValue) > -1
|
if (isArray(value)) {
|
||||||
|
option.selected = looseIndexOf(value, optionValue) > -1
|
||||||
|
} else {
|
||||||
|
option.selected = looseHas(value, optionValue)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
if (looseEqual(getValue(option), value)) {
|
if (looseEqual(getValue(option), value)) {
|
||||||
el.selectedIndex = i
|
el.selectedIndex = i
|
||||||
@ -280,6 +296,10 @@ if (__NODE_JS__) {
|
|||||||
if (vnode.props && looseIndexOf(value, vnode.props.value) > -1) {
|
if (vnode.props && looseIndexOf(value, vnode.props.value) > -1) {
|
||||||
return { checked: true }
|
return { checked: true }
|
||||||
}
|
}
|
||||||
|
} else if (isSet(value)) {
|
||||||
|
if (vnode.props && looseHas(value, vnode.props.value)) {
|
||||||
|
return { checked: true }
|
||||||
|
}
|
||||||
} else if (value) {
|
} else if (value) {
|
||||||
return { checked: true }
|
return { checked: true }
|
||||||
}
|
}
|
||||||
|
@ -58,6 +58,9 @@ export const hasOwn = (
|
|||||||
): key is keyof typeof val => hasOwnProperty.call(val, key)
|
): key is keyof typeof val => hasOwnProperty.call(val, key)
|
||||||
|
|
||||||
export const isArray = Array.isArray
|
export const isArray = Array.isArray
|
||||||
|
export const isSet = (val: any): boolean => {
|
||||||
|
return toRawType(val) === 'Set'
|
||||||
|
}
|
||||||
export const isDate = (val: unknown): val is Date => val instanceof Date
|
export const isDate = (val: unknown): val is Date => val instanceof Date
|
||||||
export const isFunction = (val: unknown): val is Function =>
|
export const isFunction = (val: unknown): val is Function =>
|
||||||
typeof val === 'function'
|
typeof val === 'function'
|
||||||
|
@ -51,3 +51,10 @@ export function looseEqual(a: any, b: any): boolean {
|
|||||||
export function looseIndexOf(arr: any[], val: any): number {
|
export function looseIndexOf(arr: any[], val: any): number {
|
||||||
return arr.findIndex(item => looseEqual(item, val))
|
return arr.findIndex(item => looseEqual(item, val))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function looseHas(set: Set<any>, val: any): boolean {
|
||||||
|
for (let item of set) {
|
||||||
|
if (looseEqual(item, val)) return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user