diff --git a/dev/Dev.vue b/dev/Dev.vue
index d180b1a..880c8c2 100644
--- a/dev/Dev.vue
+++ b/dev/Dev.vue
@@ -1,7 +1,7 @@
-
+
diff --git a/docs/.vuepress/components/CustomHandlers.vue b/docs/.vuepress/components/CustomHandlers.vue
new file mode 100644
index 0000000..8fa43e8
--- /dev/null
+++ b/docs/.vuepress/components/CustomHandlers.vue
@@ -0,0 +1,25 @@
+
+
+
+
+
diff --git a/docs/.vuepress/components/TagOnComma.vue b/docs/.vuepress/components/TagOnComma.vue
new file mode 100644
index 0000000..59e1d6a
--- /dev/null
+++ b/docs/.vuepress/components/TagOnComma.vue
@@ -0,0 +1,4 @@
+
+
+
+
diff --git a/docs/.vuepress/config.js b/docs/.vuepress/config.js
index 583ba3d..1f2e6a0 100644
--- a/docs/.vuepress/config.js
+++ b/docs/.vuepress/config.js
@@ -123,6 +123,13 @@ module.exports = {
['guide/loops', 'Using in Loops'],
],
},
+ {
+ title: 'Customizing',
+ collapsable: false,
+ children: [
+ ['guide/keydown', 'Keydown Events'],
+ ],
+ },
{
title: 'API',
collapsable: false,
diff --git a/docs/guide/keydown.md b/docs/guide/keydown.md
new file mode 100644
index 0000000..b353960
--- /dev/null
+++ b/docs/guide/keydown.md
@@ -0,0 +1,73 @@
+### Customizing Keydown Behaviour
+---
+
+## selectOnKeyCodes
+
+`selectOnKeyCodes {Array}` is an array of keyCodes that will trigger a typeAheadSelect. Any keyCodes
+ in this array will prevent the default event action and trigger a typeahead select. By default,
+ it's just `[13]` for return. For example, maybe you want to tag on a comma keystroke:
+
+
+
+<<< @/.vuepress/components/TagOnComma.vue
+
+## mapKeyDown
+
+Vue Select provides the `map-keydown` Function prop to allow for customizing the components response to
+keydown events while the search input has focus.
+
+```js
+/**
+ * @param map {Object} Mapped keyCode to handlers { : }
+ * @param vm {VueSelect}
+ * @return {Object}
+ */
+(map, vm) => map,
+```
+
+By default, the prop is a no–op returning the same object `map` object it receives. This object
+maps keyCodes to handlers: `{ : }`. Modifying this object can override default
+functionality, or add handlers for different keys that the component doesn't normally listen for.
+
+Note that any keyCodes you've added to `selectOnKeyCodes` will be passed to `map-keydown` as well,
+so `map-keydown` will always take precedence.
+
+**Default Handlers**
+
+```js
+// delete
+8: e => this.maybeDeleteValue()
+
+// tab
+9: e => this.onTab()
+
+// enter
+13: e => {
+ e.preventDefault();
+ return this.typeAheadSelect();
+}
+
+// esc
+27: e => this.onEscape()
+
+// up
+38: e => {
+ e.preventDefault();
+ return this.typeAheadUp();
+}
+
+// down
+40: e => {
+ e.preventDefault();
+ return this.typeAheadDown();
+}
+```
+
+### Example: Autocomplete Email Addresses
+
+This is example listens for the `@` key, and autocompletes an email address with `@gmail.com`.
+
+
+
+<<< @/.vuepress/components/CustomHandlers.vue
+
diff --git a/src/components/Select.vue b/src/components/Select.vue
index f2aba4e..ba34341 100644
--- a/src/components/Select.vue
+++ b/src/components/Select.vue
@@ -79,6 +79,9 @@
import ajax from '../mixins/ajax'
import childComponents from './childComponents';
+ /**
+ * @name VueSelect
+ */
export default {
components: {...childComponents},
@@ -302,11 +305,12 @@
/**
* Select the current value if selectOnTab is enabled
+ * @deprecated since 3.3
*/
onTab: {
type: Function,
default: function () {
- if (this.selectOnTab) {
+ if (this.selectOnTab && !this.isComposing) {
this.typeAheadSelect();
}
},
@@ -449,12 +453,22 @@
/**
* When true, hitting the 'tab' key will select the current select value
* @type {Boolean}
+ * @deprecated since 3.3 - use selectOnKeyCodes instead
*/
selectOnTab: {
type: Boolean,
default: false
},
+ /**
+ * Keycodes that will select the current option.
+ * @type Array
+ */
+ selectOnKeyCodes: {
+ type: Array,
+ default: () => [13],
+ },
+
/**
* Query Selector used to find the search input
* when the 'search' scoped slot is used.
@@ -467,6 +481,21 @@
searchInputQuerySelector: {
type: String,
default: '[type=search]'
+ },
+
+ /**
+ * Used to modify the default keydown events map
+ * for the search input. Can be used to implement
+ * custom behaviour for key presses.
+ */
+ mapKeydown: {
+ type: Function,
+ /**
+ * @param map {Object}
+ * @param vm {VueSelect}
+ * @return {Object}
+ */
+ default: (map, vm) => map,
}
},
@@ -474,6 +503,7 @@
return {
search: '',
open: false,
+ isComposing: false,
pushedTags: [],
_value: [] // Internal value managed by Vue Select if no `value` prop is passed
}
@@ -840,39 +870,46 @@
},
/**
- * Search 'input' KeyBoardEvent handler.
+ * Search KeyBoardEvent handler.
* @param e {KeyboardEvent}
* @return {Function}
*/
onSearchKeyDown (e) {
- switch (e.keyCode) {
- case 8:
- // delete
- return this.maybeDeleteValue();
- case 9:
- // tab
- return this.onTab();
- case 13:
- // enter.prevent
- e.preventDefault();
- return this.typeAheadSelect();
- case 27:
- // esc
- return this.onEscape();
- case 38:
- // up.prevent
+ const preventAndSelect = e => {
+ e.preventDefault();
+ return !this.isComposing && this.typeAheadSelect();
+ };
+
+ const defaults = {
+ // delete
+ 8: e => this.maybeDeleteValue(),
+ // tab
+ 9: e => this.onTab(),
+ // esc
+ 27: e => this.onEscape(),
+ // up.prevent
+ 38: e => {
e.preventDefault();
return this.typeAheadUp();
- case 40:
- // down.prevent
+ },
+ // down.prevent
+ 40: e => {
e.preventDefault();
return this.typeAheadDown();
+ },
+ };
+
+ this.selectOnKeyCodes.forEach(keyCode => defaults[keyCode] = preventAndSelect);
+
+ const handlers = this.mapKeydown(defaults, this);
+
+ if (typeof handlers[e.keyCode] === 'function') {
+ return handlers[e.keyCode](e);
}
}
},
computed: {
-
/**
* Determine if the component needs to
* track the state of values internally.
@@ -944,10 +981,12 @@
'value': this.search,
},
events: {
+ 'compositionstart': () => this.isComposing = true,
+ 'compositionend': () => this.isComposing = false,
'keydown': this.onSearchKeyDown,
'blur': this.onSearchBlur,
'focus': this.onSearchFocus,
- 'input': (e) => this.search = e.target.value
+ 'input': (e) => this.search = e.target.value,
},
},
spinner: {
diff --git a/tests/unit/Keydown.spec.js b/tests/unit/Keydown.spec.js
new file mode 100644
index 0000000..4721719
--- /dev/null
+++ b/tests/unit/Keydown.spec.js
@@ -0,0 +1,74 @@
+import { mountDefault } from '../helpers';
+
+describe('Custom Keydown Handlers', () => {
+
+ it('can use the map-keydown prop to trigger custom behaviour', () => {
+ const onKeyDown = jest.fn();
+ const Select = mountDefault({
+ mapKeydown: (defaults, vm) => ({...defaults, 32: onKeyDown}),
+ });
+
+ Select.find({ref: 'search'}).trigger('keydown.space');
+
+ expect(onKeyDown.mock.calls.length).toBe(1);
+ });
+
+ it('selectOnKeyCodes should trigger a selection for custom keycodes', () => {
+ const Select = mountDefault({
+ selectOnKeyCodes: [32],
+ });
+
+ const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
+
+ Select.find({ref: 'search'}).trigger('keydown.space');
+
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('even works when combining selectOnKeyCodes with map-keydown', () => {
+ const onKeyDown = jest.fn();
+ const Select = mountDefault({
+ mapKeydown: (defaults, vm) => ({...defaults, 32: onKeyDown}),
+ selectOnKeyCodes: [9],
+ });
+
+ const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
+
+ Select.find({ref: 'search'}).trigger('keydown.space');
+ expect(onKeyDown.mock.calls.length).toBe(1);
+
+ Select.find({ref: 'search'}).trigger('keydown.tab');
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ describe('CompositionEvent support', () => {
+
+ it('will not select a value with enter if the user is composing', () => {
+ const Select = mountDefault();
+ const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
+
+ Select.find({ref: 'search'}).trigger('compositionstart');
+ Select.find({ref: 'search'}).trigger('keydown.enter');
+ expect(spy).toHaveBeenCalledTimes(0);
+
+ Select.find({ref: 'search'}).trigger('compositionend');
+ Select.find({ref: 'search'}).trigger('keydown.enter');
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ it('will not select a value with tab if the user is composing', () => {
+ const Select = mountDefault({selectOnTab: true});
+ const spy = jest.spyOn(Select.vm, 'typeAheadSelect');
+
+ Select.find({ref: 'search'}).trigger('compositionstart');
+ Select.find({ref: 'search'}).trigger('keydown.tab');
+ expect(spy).toHaveBeenCalledTimes(0);
+
+ Select.find({ref: 'search'}).trigger('compositionend');
+ Select.find({ref: 'search'}).trigger('keydown.tab');
+ expect(spy).toHaveBeenCalledTimes(1);
+ });
+
+ });
+
+});
diff --git a/tests/unit/Selectable.spec.js b/tests/unit/Selectable.spec.js
index b8158eb..11d7663 100644
--- a/tests/unit/Selectable.spec.js
+++ b/tests/unit/Selectable.spec.js
@@ -4,7 +4,7 @@ describe("Selectable prop", () => {
it("should select selectable option if clicked", () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
- selectable: (option) => option == "one"
+ selectable: (option) => option === "one"
});
Select.vm.$data.open = true;
@@ -16,7 +16,7 @@ describe("Selectable prop", () => {
it("should not select not selectable option if clicked", () => {
const Select = selectWithProps({
options: ["one", "two", "three"],
- selectable: (option) => option == "one"
+ selectable: (option) => option === "one"
});
Select.vm.$data.open = true;