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

Merge commit '99f2dfdc0a70a2a6e1e2fe4d1370f8d4b728a3ad' into reduce-bug-fix-options-watcher

This commit is contained in:
Jeff
2019-11-07 19:38:20 -08:00
14 changed files with 202 additions and 50 deletions
-5
View File
@@ -14,11 +14,6 @@ module.exports = merge(baseWebpackConfig, {
minimizer: [
new TerserPlugin({
sourceMap: true,
terserOptions: {
compress: {
drop_console: true,
},
},
}),
],
}
@@ -0,0 +1,43 @@
<template>
<table>
<tr>
<th>Name</th>
<th>Country</th>
</tr>
<tr v-for="person in people">
<td>{{ person.name }}</td>
<td>
<v-select
:options="options"
:value="person.country"
@input="country => updateCountry(person, country)"
/>
</td>
</tr>
</table>
</template>
<script>
import countries from '../data/countries';
export default {
data: () => ({
people: [{name: 'John', country: ''}, {name: 'Jane', country: ''}],
}),
methods: {
updateCountry (person, country) {
person.country = country;
},
},
computed: {
options: () => countries,
},
};
</script>
<style scoped>
table {
display: table;
width: 100%;
}
</style>
+2 -1
View File
@@ -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'],
],
},
{
+21
View File
@@ -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
<v-select transition="" />
```
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;
}
```
+17
View File
@@ -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.
<LoopedSelect />
<<< @/.vuepress/components/LoopedSelect.vue
+2 -3
View File
@@ -10,14 +10,13 @@ vue-select provides the scoped `option` slot in order to create custom dropdown
```html
<v-select :options="options" label="title">
<template slot="option" slot-scope="option">
<template v-slot:option="option">
<span :class="option.icon"></span>
{{ option.title }}
</template>
</v-select>
```
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.
<CodePen url="NXBwYG" height="500"/>
+1
View File
@@ -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",
+21 -19
View File
@@ -51,15 +51,15 @@
</div>
<transition :name="transition">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown="onMousedown" @mouseup="onMouseUp">
<ul ref="dropdownMenu" v-if="dropdownOpen" class="vs__dropdown-menu" role="listbox" @mousedown.prevent="onMousedown" @mouseup="onMouseUp">
<li
role="option"
v-for="(option, index) in filteredOptions"
:key="getOptionKey(option)"
class="vs__dropdown-option"
:class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer }"
@mouseover="typeAheadPointer = index"
@mousedown.prevent.stop="select(option)"
:class="{ 'vs__dropdown-option--selected': isOptionSelected(option), 'vs__dropdown-option--highlight': index === typeAheadPointer, 'vs__dropdown-option--disabled': !selectable(option) }"
@mouseover="selectable(option) ? typeAheadPointer = index : null"
@mousedown.prevent.stop="selectable(option) ? select(option) : null"
>
<slot name="option" v-bind="normalizeOptionForSlot(option)">
{{ 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
+22 -11
View File
@@ -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;
}
}
},
+9
View File
@@ -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;
}
}
+1 -1
View File
@@ -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")
};
/**
+2 -2
View File
@@ -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("");
});
+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 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);
})
})
+8 -8
View File
@@ -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();
});