+
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index d8d80e0..583ba3d 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -114,12 +114,13 @@ module.exports = {
],
},
{
- title: 'Digging Deeper',
+ title: 'Use Cases',
collapsable: false,
children: [
['guide/validation', 'Validation'],
['guide/vuex', 'Vuex'],
['guide/ajax', 'AJAX'],
+ ['guide/loops', 'Using in Loops'],
],
},
{
diff --git a/docs/guide/css.md b/docs/guide/css.md
index 8c16a1c..20f28e5 100644
--- a/docs/guide/css.md
+++ b/docs/guide/css.md
@@ -34,4 +34,25 @@ all instances of Vue Select, or add your own classname if you just want to affec
<<< @/.vuepress/components/CssSpecificity.vue
+## Dropdown Transition
+By default, the dropdown transitions with a `.15s` cubic-bezier opacity fade in/out. The component
+uses the [VueJS transition system](https://vuejs.org/v2/guide/transitions.html). By default, the
+ transition name is `vs__fade`. There's a couple ways to override or change this transition.
+
+1. Use the `transition` prop. Applying this prop will change the name of the animation classes and
+negate the default CSS. If you want to remove it entirely, you can set it to an empty string.
+
+```html
+
+```
+
+2. You can also override the default CSS for the `vs__fade` transition. Again, if you
+wanted to eliminate the transition entirely:
+
+```css
+.vs__fade-enter-active,
+.vs__fade-leave-active {
+ transition: none;
+}
+```
diff --git a/docs/guide/loops.md b/docs/guide/loops.md
new file mode 100644
index 0000000..9c7e223
--- /dev/null
+++ b/docs/guide/loops.md
@@ -0,0 +1,17 @@
+### Using Vue Select in v-for Loops
+---
+
+There may be times that you are including Vue Select within loops of data, such as a table. This can
+pose some challenges when emitting events from the component, as you won't know which Vue Select
+instance emitted it. This can make it difficult to wire up with things like Vuex.
+
+Fortunately, you can solve this problem with an anonymous function. The example below doesn't use
+Vuex just to keep things succinct, but the same solution would apply. The `@input` is handled
+with an inline anonymous function, allowing the selected country to be passed to the `updateCountry`
+method with the `country` and the `person` object.
+
+
+
+<<< @/.vuepress/components/LoopedSelect.vue
+
+
diff --git a/docs/guide/slots.md b/docs/guide/slots.md
index 10d3c50..7bfe9b1 100644
--- a/docs/guide/slots.md
+++ b/docs/guide/slots.md
@@ -10,14 +10,13 @@ vue-select provides the scoped `option` slot in order to create custom dropdown
```html
-
+
{{ option.title }}
```
-Using the `option` slot with `slot-scope="option"` gives the
-provides the current option variable to the template.
+Using the `option` slot with props `"option"` provides the current option variable to the template.
diff --git a/package.json b/package.json
index a194899..deefd80 100644
--- a/package.json
+++ b/package.json
@@ -11,6 +11,7 @@
"private": false,
"main": "dist/vue-select.js",
"license": "MIT",
+ "prepare": "npm run build",
"scripts": {
"serve": "webpack-dev-server --config build/webpack.dev.conf.js --hot --progress -d",
"serve:docs": "cd docs && yarn serve",
diff --git a/src/components/Select.vue b/src/components/Select.vue
index ebcef8e..91e5c7f 100644
--- a/src/components/Select.vue
+++ b/src/components/Select.vue
@@ -51,15 +51,15 @@
-
+
{{ 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.
@@ -838,16 +851,10 @@
case 9:
// tab
return this.onTab();
- }
- },
-
- /**
- * Search 'input' KeyBoardEvent handler.
- * @param e {KeyboardEvent}
- * @return {Function}
- */
- onSearchKeyUp (e) {
- switch (e.keyCode) {
+ case 13:
+ // enter.prevent
+ e.preventDefault();
+ return this.typeAheadSelect();
case 27:
// esc
return this.onEscape();
@@ -859,10 +866,6 @@
// down.prevent
e.preventDefault();
return this.typeAheadDown();
- case 13:
- // enter.prevent
- e.preventDefault();
- return this.typeAheadSelect();
}
}
},
@@ -941,7 +944,6 @@
},
events: {
'keydown': this.onSearchKeyDown,
- 'keyup': this.onSearchKeyUp,
'blur': this.onSearchBlur,
'focus': this.onSearchFocus,
'input': (e) => this.search = e.target.value
diff --git a/src/mixins/typeAheadPointer.js b/src/mixins/typeAheadPointer.js
index f6f9467..4eb2636 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 e25de00..474746d 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/helpers.js b/tests/helpers.js
index fd45c88..77c5988 100755
--- a/tests/helpers.js
+++ b/tests/helpers.js
@@ -13,7 +13,7 @@ export const searchSubmit = (Wrapper, searchText = false) => {
if (searchText) {
Wrapper.vm.search = searchText;
}
- Wrapper.find({ ref: "search" }).trigger("keyup.enter")
+ Wrapper.find({ ref: "search" }).trigger("keydown.enter")
};
/**
diff --git a/tests/unit/Dropdown.spec.js b/tests/unit/Dropdown.spec.js
index 58b01e2..6ae22a4 100755
--- a/tests/unit/Dropdown.spec.js
+++ b/tests/unit/Dropdown.spec.js
@@ -109,14 +109,14 @@ describe("Toggling Dropdown", () => {
expect(spy).toHaveBeenCalled();
});
- it("should remove existing search text on escape keyup", () => {
+ it("should remove existing search text on escape keydown", () => {
const Select = selectWithProps({
value: [{ label: "one" }],
options: [{ label: "one" }]
});
Select.vm.search = "foo";
- Select.find('.vs__search').trigger('keyup.esc')
+ Select.find('.vs__search').trigger('keydown.esc')
expect(Select.vm.search).toEqual("");
});
diff --git a/tests/unit/Selectable.spec.js b/tests/unit/Selectable.spec.js
new file mode 100644
index 0000000..b8158eb
--- /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 keyDown", () => {
+ const Select = selectWithProps({
+ options: ["one", "two", "three"],
+ selectable: (option) => option !== "two"
+ });
+
+ Select.vm.typeAheadPointer = 1;
+
+ Select.find({ ref: "search" }).trigger("keydown.down");
+
+ expect(Select.vm.typeAheadPointer).toEqual(2);
+ })
+
+ it("should skip non-selectable option on up arrow keyDown", () => {
+ const Select = selectWithProps({
+ options: ["one", "two", "three"],
+ selectable: (option) => option !== "two"
+ });
+
+ Select.vm.typeAheadPointer = 2;
+
+ Select.find({ ref: "search" }).trigger("keydown.up");
+
+ expect(Select.vm.typeAheadPointer).toEqual(0);
+ })
+})
diff --git a/tests/unit/TypeAhead.spec.js b/tests/unit/TypeAhead.spec.js
index 31a2f57..a1a4755 100755
--- a/tests/unit/TypeAhead.spec.js
+++ b/tests/unit/TypeAhead.spec.js
@@ -18,22 +18,22 @@ 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;
- Select.find({ ref: "search" }).trigger("keyup.up");
+ Select.find({ ref: "search" }).trigger("keydown.up");
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;
- Select.find({ ref: "search" }).trigger("keyup.down");
+ Select.find({ ref: "search" }).trigger("keydown.down");
expect(Select.vm.typeAheadPointer).toEqual(2);
});
@@ -47,23 +47,23 @@ 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");
Select.vm.typeAheadPointer = 1;
- Select.find({ ref: "search" }).trigger("keyup.up");
+ Select.find({ ref: "search" }).trigger("keydown.up");
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");
Select.vm.typeAheadPointer = 1;
- Select.find({ ref: "search" }).trigger("keyup.down");
+ Select.find({ ref: "search" }).trigger("keydown.down");
expect(spy).toHaveBeenCalled();
});