2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-05-17 02:29:37 +03:00

Merge pull request #971 from sagalbot/fix-956-event-delegation

WIP Event delegation for #956
This commit is contained in:
Jeff Sagal
2019-11-08 10:51:22 -08:00
committed by GitHub
8 changed files with 247 additions and 25 deletions
+1 -1
View File
@@ -1,7 +1,7 @@
<template>
<div id="app">
<sandbox hide-help v-slot="config">
<v-select v-bind="config" />
<v-select v-bind="config"/>
</sandbox>
</div>
</template>
@@ -0,0 +1,25 @@
<template>
<v-select
taggable
multiple
no-drop
:map-keydown="handlers"
placeholder="enter an email"
/>
</template>
<script>
export default {
name: 'CustomHandlers',
methods: {
handlers: (map, vm) => ({
...map, 50: e => {
e.preventDefault();
if( e.key === '@' && vm.search.length > 0 ) {
vm.search = `${vm.search}@gmail.com`;
}
},
}),
},
};
</script>
+4
View File
@@ -0,0 +1,4 @@
<template>
<!-- tag on 188/comma & 13/return -->
<v-select no-drop taggable multiple :select-on-key-codes="[188, 13]" />
</template>
+7
View File
@@ -123,6 +123,13 @@ module.exports = {
['guide/loops', 'Using in Loops'],
],
},
{
title: 'Customizing',
collapsable: false,
children: [
['guide/keydown', 'Keydown Events'],
],
},
{
title: 'API',
collapsable: false,
+73
View File
@@ -0,0 +1,73 @@
### Customizing Keydown Behaviour
---
## selectOnKeyCodes <Badge text="v3.3.0+" />
`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:
<TagOnComma />
<<< @/.vuepress/components/TagOnComma.vue
## mapKeyDown <Badge text="v3.3.0+" />
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 { <keyCode>:<callback> }
* @param vm {VueSelect}
* @return {Object}
*/
(map, vm) => map,
```
By default, the prop is a noop returning the same object `map` object it receives. This object
maps keyCodes to handlers: `{ <keyCode>: <callback> }`. 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`.
<CustomHandlers />
<<< @/.vuepress/components/CustomHandlers.vue
+61 -22
View File
@@ -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 <input> 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: {
+74
View File
@@ -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);
});
});
});
+2 -2
View File
@@ -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;