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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user