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:
+1
-1
@@ -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>
|
||||
@@ -0,0 +1,4 @@
|
||||
<template>
|
||||
<!-- tag on 188/comma & 13/return -->
|
||||
<v-select no-drop taggable multiple :select-on-key-codes="[188, 13]" />
|
||||
</template>
|
||||
@@ -123,6 +123,13 @@ module.exports = {
|
||||
['guide/loops', 'Using in Loops'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'Customizing',
|
||||
collapsable: false,
|
||||
children: [
|
||||
['guide/keydown', 'Keydown Events'],
|
||||
],
|
||||
},
|
||||
{
|
||||
title: 'API',
|
||||
collapsable: false,
|
||||
|
||||
@@ -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 no–op 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
@@ -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: {
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
});
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user