]> git.ipfire.org Git - thirdparty/vuejs/core.git/commitdiff
feat(runtime-dom): allow native Set as v-model checkbox source (#1957)
authorPick <picknight@foxmail.com>
Mon, 14 Sep 2020 15:16:50 +0000 (23:16 +0800)
committerGitHub <noreply@github.com>
Mon, 14 Sep 2020 15:16:50 +0000 (11:16 -0400)
packages/runtime-dom/__tests__/directives/vModel.spec.ts
packages/runtime-dom/src/directives/vModel.ts
packages/shared/src/index.ts
packages/shared/src/looseEqual.ts

index c8b3531ffb92ee3e6b147704fb4f9b2d93af191e..33d48e5fc6cb6fba64f005e11947be5d4e752a71 100644 (file)
@@ -433,6 +433,76 @@ describe('vModel', () => {
     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 () => {
     const component = defineComponent({
       data() {
index 23ebb07b3594017951009156de1b2597fdc922ac..ea1098dd19b9469abc4474caef64709aa4ce00c0 100644 (file)
@@ -11,7 +11,9 @@ import {
   looseEqual,
   looseIndexOf,
   invokeArrayFns,
-  toNumber
+  toNumber,
+  isSet,
+  looseHas
 } from '@vue/shared'
 
 type AssignerFn = (value: any) => void
@@ -111,6 +113,14 @@ export const vModelCheckbox: ModelDirective<HTMLInputElement> = {
           filtered.splice(index, 1)
           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 {
         assign(getCheckboxValue(el, checked))
       }
@@ -132,6 +142,8 @@ function setChecked(
   ;(el as any)._modelValue = value
   if (isArray(value)) {
     el.checked = looseIndexOf(value, vnode.props!.value) > -1
+  } else if (isSet(value)) {
+    el.checked = looseHas(value, vnode.props!.value)
   } else if (value !== oldValue) {
     el.checked = looseEqual(value, getCheckboxValue(el, true))
   }
@@ -178,10 +190,10 @@ export const vModelSelect: ModelDirective<HTMLSelectElement> = {
 
 function setSelected(el: HTMLSelectElement, value: any) {
   const isMultiple = el.multiple
-  if (isMultiple && !isArray(value)) {
+  if (isMultiple && !isArray(value) && !isSet(value)) {
     __DEV__ &&
       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)}.`
       )
     return
@@ -190,7 +202,11 @@ function setSelected(el: HTMLSelectElement, value: any) {
     const option = el.options[i]
     const optionValue = getValue(option)
     if (isMultiple) {
-      option.selected = looseIndexOf(value, optionValue) > -1
+      if (isArray(value)) {
+        option.selected = looseIndexOf(value, optionValue) > -1
+      } else {
+        option.selected = looseHas(value, optionValue)
+      }
     } else {
       if (looseEqual(getValue(option), value)) {
         el.selectedIndex = i
@@ -280,6 +296,10 @@ if (__NODE_JS__) {
       if (vnode.props && looseIndexOf(value, vnode.props.value) > -1) {
         return { checked: true }
       }
+    } else if (isSet(value)) {
+      if (vnode.props && looseHas(value, vnode.props.value)) {
+        return { checked: true }
+      }
     } else if (value) {
       return { checked: true }
     }
index 69739cae2518ae6fe9b364c6e57495ed081b2164..2178c5f1ee76cc533c0ee0fc993331136ea7d590 100644 (file)
@@ -58,6 +58,9 @@ export const hasOwn = (
 ): key is keyof typeof val => hasOwnProperty.call(val, key)
 
 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 isFunction = (val: unknown): val is Function =>
   typeof val === 'function'
index 030f0338b30e395e0496ba0dff42546e05c27e1a..076ea7b556952e8db0aabd60eebacf5fb77253be 100644 (file)
@@ -51,3 +51,10 @@ export function looseEqual(a: any, b: any): boolean {
 export function looseIndexOf(arr: any[], val: any): number {
   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
+}