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:
@@ -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>
|
||||
@@ -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'],
|
||||
],
|
||||
},
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
```
|
||||
|
||||
@@ -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
@@ -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"/>
|
||||
|
||||
@@ -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
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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
@@ -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")
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -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("");
|
||||
});
|
||||
|
||||
|
||||
@@ -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);
|
||||
})
|
||||
})
|
||||
@@ -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();
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user