diff --git a/src/components/Select.vue b/src/components/Select.vue index a80efdd58..2d8c5f358 100644 --- a/src/components/Select.vue +++ b/src/components/Select.vue @@ -57,9 +57,9 @@ v-for="(option, index) in filteredOptions" :key="getOptionKey(option)" class="vs__dropdown-option" - :class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer }" - @mouseover="typeAheadPointer = index" - @mousedown.prevent.stop="select(option)" + :class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option) }" + @mouseover="selectable(option) ? typeAheadPointer = index : null" + @mousedown.prevent.stop="selectable(option) ? select(option) : null" > {{ getOptionLabel(option) }} @@ -224,6 +224,19 @@ default: option => option, }, + /** + * Decides wether an option is selectable or not. Not selectable options + * are displayed but disabled and cannot be selected. + * + * @type {Function} + * @param {Object|String} option + * @return {Boolean} + */ + selectable: { + type: Function, + default: option => true, + }, + /** * Callback to generate the label text. If {option} * is an object, returns option[this.label] by default. diff --git a/src/mixins/typeAheadPointer.js b/src/mixins/typeAheadPointer.js index f6f946770..4eb2636d3 100644 --- a/src/mixins/typeAheadPointer.js +++ b/src/mixins/typeAheadPointer.js @@ -7,35 +7,46 @@ export default { watch: { filteredOptions() { - this.typeAheadPointer = 0 + for (let i = 0; i < this.filteredOptions.length; i++) { + if (this.selectable(this.filteredOptions[i])) { + this.typeAheadPointer = i; + break; + } + } } }, methods: { /** * Move the typeAheadPointer visually up the list by - * subtracting the current index by one. + * setting it to the previous selectable option. * @return {void} */ typeAheadUp() { - if (this.typeAheadPointer > 0) { - this.typeAheadPointer-- - if( this.maybeAdjustScroll ) { - this.maybeAdjustScroll() + for (let i = this.typeAheadPointer - 1; i >= 0; i--) { + if (this.selectable(this.filteredOptions[i])) { + this.typeAheadPointer = i; + if( this.maybeAdjustScroll ) { + this.maybeAdjustScroll() + } + break; } } }, /** * Move the typeAheadPointer visually down the list by - * adding the current index by one. + * setting it to the next selectable option. * @return {void} */ typeAheadDown() { - if (this.typeAheadPointer < this.filteredOptions.length - 1) { - this.typeAheadPointer++ - if( this.maybeAdjustScroll ) { - this.maybeAdjustScroll() + for (let i = this.typeAheadPointer + 1; i < this.filteredOptions.length; i++) { + if (this.selectable(this.filteredOptions[i])) { + this.typeAheadPointer = i; + if( this.maybeAdjustScroll ) { + this.maybeAdjustScroll() + } + break; } } }, diff --git a/src/scss/modules/_dropdown-option.scss b/src/scss/modules/_dropdown-option.scss index e25de00a2..474746dd3 100644 --- a/src/scss/modules/_dropdown-option.scss +++ b/src/scss/modules/_dropdown-option.scss @@ -16,3 +16,12 @@ background: $vs-state-active-bg; color: $vs-state-active-color; } + +.vs__dropdown-option--disabled { + background: inherit; + color: $vs-state-disabled-color; + + &:hover { + cursor: inherit; + } +} diff --git a/tests/unit/Selectable.spec.js b/tests/unit/Selectable.spec.js new file mode 100644 index 000000000..881283469 --- /dev/null +++ b/tests/unit/Selectable.spec.js @@ -0,0 +1,53 @@ +import { selectWithProps } from "../helpers"; + +describe("Selectable prop", () => { + it("should select selectable option if clicked", () => { + const Select = selectWithProps({ + options: ["one", "two", "three"], + selectable: (option) => option == "one" + }); + + Select.vm.$data.open = true; + + Select.find(".vs__dropdown-menu li:first-child").trigger("mousedown"); + expect(Select.vm.selectedValue).toEqual(["one"]); + }) + + it("should not select not selectable option if clicked", () => { + const Select = selectWithProps({ + options: ["one", "two", "three"], + selectable: (option) => option == "one" + }); + + Select.vm.$data.open = true; + + Select.find(".vs__dropdown-menu li:last-child").trigger("mousedown"); + expect(Select.vm.selectedValue).toEqual([]); + }); + + it("should skip non-selectable option on down arrow keyUp", () => { + const Select = selectWithProps({ + options: ["one", "two", "three"], + selectable: (option) => option !== "two" + }); + + Select.vm.typeAheadPointer = 1; + + Select.find({ ref: "search" }).trigger("keyup.down"); + + expect(Select.vm.typeAheadPointer).toEqual(2); + }) + + it("should skip non-selectable option on up arrow keyUp", () => { + const Select = selectWithProps({ + options: ["one", "two", "three"], + selectable: (option) => option !== "two" + }); + + Select.vm.typeAheadPointer = 2; + + Select.find({ ref: "search" }).trigger("keyup.up"); + + expect(Select.vm.typeAheadPointer).toEqual(0); + }) +}) diff --git a/tests/unit/TypeAhead.spec.js b/tests/unit/TypeAhead.spec.js index 31a2f5777..6dcc6ab5d 100755 --- a/tests/unit/TypeAhead.spec.js +++ b/tests/unit/TypeAhead.spec.js @@ -18,7 +18,7 @@ describe("Moving the Typeahead Pointer", () => { expect(Select.vm.typeAheadPointer).toEqual(0); }); - it("should move the pointer visually up the list on up arrow keyDown", () => { + it("should move the pointer visually up the list on up arrow keyUp", () => { const Select = mountDefault(); Select.vm.typeAheadPointer = 1; @@ -28,7 +28,7 @@ describe("Moving the Typeahead Pointer", () => { expect(Select.vm.typeAheadPointer).toEqual(0); }); - it("should move the pointer visually down the list on down arrow keyDown", () => { + it("should move the pointer visually down the list on down arrow keyUp", () => { const Select = mountDefault(); Select.vm.typeAheadPointer = 1; @@ -47,7 +47,7 @@ describe("Moving the Typeahead Pointer", () => { }); describe("Automatic Scrolling", () => { - it("should check if the scroll position needs to be adjusted on up arrow keyDown", () => { + it("should check if the scroll position needs to be adjusted on up arrow keyUp", () => { const Select = mountDefault(); const spy = jest.spyOn(Select.vm, "maybeAdjustScroll"); @@ -57,7 +57,7 @@ describe("Moving the Typeahead Pointer", () => { expect(spy).toHaveBeenCalled(); }); - it("should check if the scroll position needs to be adjusted on down arrow keyDown", () => { + it("should check if the scroll position needs to be adjusted on down arrow keyUp", () => { const Select = mountDefault(); const spy = jest.spyOn(Select.vm, "maybeAdjustScroll");