From 68da1c172e9dd304eb593e6674056ba17df51328 Mon Sep 17 00:00:00 2001 From: andreas Date: Sun, 17 Oct 2021 19:47:06 +0300 Subject: [PATCH] feat: add `deselectFromDropdown` boolean prop (#1033) --- docs/api/props.md | 11 ++++++ src/components/Select.vue | 27 ++++++++++++- src/scss/global/_variables.scss | 4 ++ src/scss/modules/_dropdown-option.scss | 5 +++ tests/unit/Deselecting.spec.js | 53 ++++++++++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/docs/api/props.md b/docs/api/props.md index b205d14..35799d8 100644 --- a/docs/api/props.md +++ b/docs/api/props.md @@ -163,6 +163,17 @@ createOption: { }, ``` +## deselectFromDropdown + +Determines whether the user can deselect an option by clicking +it from within the dropdown menu. + +```js +deselectFromDropdown: { + type: Boolean, + default: false +}, +``` ## dir diff --git a/src/components/Select.vue b/src/components/Select.vue index 06c0417..803dce5 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -103,6 +103,8 @@ role="option" class="vs__dropdown-option" :class="{ + 'vs__dropdown-option--deselect': + isOptionDeselectable(option) && index === typeAheadPointer, 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option), @@ -206,6 +208,16 @@ export default { default: true, }, + /** + * Can the user deselect an option by clicking it from + * within the dropdown. + * @type {Boolean} + */ + deselectFromDropdown: { + type: Boolean, + default: false, + }, + /** * Enable/disable filtering the options. * @type {Boolean} @@ -960,7 +972,8 @@ export default { }, /** - * Select a given option. + * Select or deselect a given option. + * Allow deselect if clearable or if not the only selected option. * @param {Object|String} option * @return {void} */ @@ -975,6 +988,11 @@ export default { } this.updateValue(option) this.$emit('option:selected', option) + } else if ( + this.deselectFromDropdown && + (this.clearable || (this.multiple && this.selectedValue.length > 1)) + ) { + this.deselect(option) } this.onAfterSelect(option) }, @@ -1090,6 +1108,13 @@ export default { ) }, + /** + * Can the current option be removed via the dropdown? + */ + isOptionDeselectable(option) { + return this.isOptionSelected(option) && this.deselectFromDropdown + }, + /** * Determine if two option objects are matching. * diff --git a/src/scss/global/_variables.scss b/src/scss/global/_variables.scss index 248dd48..b4772fb 100644 --- a/src/scss/global/_variables.scss +++ b/src/scss/global/_variables.scss @@ -13,6 +13,10 @@ $vs-component-placeholder-color: inherit !default; $vs-state-active-bg: #5897fb !default; $vs-state-active-color: #fff !default; +// Deselect State +$vs-state-deselect-bg: #fb5858 !default; +$vs-state-deselect-color: #fff !default; + // Disabled State $vs-state-disabled-bg: rgb(248, 248, 248) !default; $vs-state-disabled-color: map_get($vs-colors, 'light') !default; diff --git a/src/scss/modules/_dropdown-option.scss b/src/scss/modules/_dropdown-option.scss index aa195c9..02a7685 100644 --- a/src/scss/modules/_dropdown-option.scss +++ b/src/scss/modules/_dropdown-option.scss @@ -14,6 +14,11 @@ color: $vs-state-active-color; } +.vs__dropdown-option--deselect { + background: $vs-state-deselect-bg; + color: $vs-state-deselect-color; +} + .vs__dropdown-option--disabled { background: inherit; color: $vs-state-disabled-color; diff --git a/tests/unit/Deselecting.spec.js b/tests/unit/Deselecting.spec.js index ee2625d..1a1497f 100755 --- a/tests/unit/Deselecting.spec.js +++ b/tests/unit/Deselecting.spec.js @@ -51,6 +51,7 @@ describe('Removing values', () => { it('will not emit input event if value has not changed with backspace', () => { const Select = mountDefault() Select.vm.$data._value = 'one' + Select.findComponent({ ref: 'search' }).trigger('keydown.backspace') expect(Select.emitted().input.length).toBe(1) @@ -59,6 +60,58 @@ describe('Removing values', () => { expect(Select.emitted().input.length).toBe(1) }) + it('should deselect a selected option when clicked and deselectFromDropdown is true', async () => { + const Select = selectWithProps({ + value: 'one', + options: ['one', 'two', 'three'], + deselectFromDropdown: true, + }) + const deselect = spyOn(Select.vm, 'deselect') + + Select.vm.open = true + await Select.vm.$nextTick() + + Select.find('.vs__dropdown-option--selected').trigger('click') + await Select.vm.$nextTick() + + expect(deselect).toHaveBeenCalledWith('one') + }) + + it('should not deselect a selected option when clicked if clearable is false', async () => { + const Select = selectWithProps({ + value: 'one', + options: ['one', 'two', 'three'], + clearable: false, + deselectFromDropdown: true, + }) + const deselect = spyOn(Select.vm, 'deselect') + + Select.vm.open = true + await Select.vm.$nextTick() + + Select.find('.vs__dropdown-option--selected').trigger('click') + await Select.vm.$nextTick() + + expect(deselect).not.toHaveBeenCalledWith('one') + }) + + it('should not deselect a selected option when clicked if deselectFromDropdown is false', async () => { + const Select = selectWithProps({ + value: 'one', + options: ['one', 'two', 'three'], + deselectFromDropdown: false, + }) + const deselect = spyOn(Select.vm, 'deselect') + + Select.vm.open = true + await Select.vm.$nextTick() + + Select.find('.vs__dropdown-option--selected').trigger('click') + await Select.vm.$nextTick() + + expect(deselect).not.toHaveBeenCalledWith('one') + }) + describe('Clear button', () => { it('should be displayed on single select when value is selected', () => { const Select = selectWithProps({