2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-19 09:50:33 +03:00

Allow to disable options with selectable function (#921)

* allow to disable options with selectable function

* add simple spec for new selectable option

* Prevent non-selectable options from being keyboard navigatable
This commit is contained in:
Markus
2019-10-25 22:11:50 +02:00
committed by Jeff Sagal
parent 17c1d3db97
commit aea81a6f5c
5 changed files with 104 additions and 18 deletions
+16 -3
View File
@@ -57,9 +57,9 @@
v-for="(option, index) in filteredOptions" v-for="(option, index) in filteredOptions"
:key="getOptionKey(option)" :key="getOptionKey(option)"
class="vs__dropdown-option" class="vs__dropdown-option"
:class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer }" :class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option) }"
@mouseover="typeAheadPointer = index" @mouseover="selectable(option) ? typeAheadPointer = index : null"
@mousedown.prevent.stop="select(option)" @mousedown.prevent.stop="selectable(option) ? select(option) : null"
> >
<slot name="option" v-bind="normalizeOptionForSlot(option)"> <slot name="option" v-bind="normalizeOptionForSlot(option)">
{{ getOptionLabel(option) }} {{ getOptionLabel(option) }}
@@ -224,6 +224,19 @@
default: option => option, 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} * Callback to generate the label text. If {option}
* is an object, returns option[this.label] by default. * is an object, returns option[this.label] by default.
+22 -11
View File
@@ -7,35 +7,46 @@ export default {
watch: { watch: {
filteredOptions() { filteredOptions() {
this.typeAheadPointer = 0 for (let i = 0; i < this.filteredOptions.length; i++) {
if (this.selectable(this.filteredOptions[i])) {
this.typeAheadPointer = i;
break;
}
}
} }
}, },
methods: { methods: {
/** /**
* Move the typeAheadPointer visually up the list by * Move the typeAheadPointer visually up the list by
* subtracting the current index by one. * setting it to the previous selectable option.
* @return {void} * @return {void}
*/ */
typeAheadUp() { typeAheadUp() {
if (this.typeAheadPointer > 0) { for (let i = this.typeAheadPointer - 1; i >= 0; i--) {
this.typeAheadPointer-- if (this.selectable(this.filteredOptions[i])) {
if( this.maybeAdjustScroll ) { this.typeAheadPointer = i;
this.maybeAdjustScroll() if( this.maybeAdjustScroll ) {
this.maybeAdjustScroll()
}
break;
} }
} }
}, },
/** /**
* Move the typeAheadPointer visually down the list by * Move the typeAheadPointer visually down the list by
* adding the current index by one. * setting it to the next selectable option.
* @return {void} * @return {void}
*/ */
typeAheadDown() { typeAheadDown() {
if (this.typeAheadPointer < this.filteredOptions.length - 1) { for (let i = this.typeAheadPointer + 1; i < this.filteredOptions.length; i++) {
this.typeAheadPointer++ if (this.selectable(this.filteredOptions[i])) {
if( this.maybeAdjustScroll ) { this.typeAheadPointer = i;
this.maybeAdjustScroll() if( this.maybeAdjustScroll ) {
this.maybeAdjustScroll()
}
break;
} }
} }
}, },
+9
View File
@@ -16,3 +16,12 @@
background: $vs-state-active-bg; background: $vs-state-active-bg;
color: $vs-state-active-color; color: $vs-state-active-color;
} }
.vs__dropdown-option--disabled {
background: inherit;
color: $vs-state-disabled-color;
&:hover {
cursor: inherit;
}
}
+53
View File
@@ -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);
})
})
+4 -4
View File
@@ -18,7 +18,7 @@ describe("Moving the Typeahead Pointer", () => {
expect(Select.vm.typeAheadPointer).toEqual(0); 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(); const Select = mountDefault();
Select.vm.typeAheadPointer = 1; Select.vm.typeAheadPointer = 1;
@@ -28,7 +28,7 @@ describe("Moving the Typeahead Pointer", () => {
expect(Select.vm.typeAheadPointer).toEqual(0); 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(); const Select = mountDefault();
Select.vm.typeAheadPointer = 1; Select.vm.typeAheadPointer = 1;
@@ -47,7 +47,7 @@ describe("Moving the Typeahead Pointer", () => {
}); });
describe("Automatic Scrolling", () => { 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 Select = mountDefault();
const spy = jest.spyOn(Select.vm, "maybeAdjustScroll"); const spy = jest.spyOn(Select.vm, "maybeAdjustScroll");
@@ -57,7 +57,7 @@ describe("Moving the Typeahead Pointer", () => {
expect(spy).toHaveBeenCalled(); 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 Select = mountDefault();
const spy = jest.spyOn(Select.vm, "maybeAdjustScroll"); const spy = jest.spyOn(Select.vm, "maybeAdjustScroll");