mirror of
https://github.com/tenrok/vue-select.git
synced 2026-06-22 10:30:34 +03:00
Merge branch 'master' into gitbook
# Conflicts: # docs/md/OnChange.md
This commit is contained in:
@@ -9,17 +9,15 @@
|
|||||||
<!--<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">-->
|
<!--<link href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.6/css/bootstrap.min.css" rel="stylesheet">-->
|
||||||
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"> -->
|
<!-- <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-alpha.6/css/bootstrap.min.css"> -->
|
||||||
<style>
|
<style>
|
||||||
html,
|
|
||||||
body,
|
|
||||||
#app {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#app {
|
#app {
|
||||||
|
height: 95vh;
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: flex-start;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-content: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-select {
|
.v-select {
|
||||||
@@ -32,13 +30,25 @@
|
|||||||
<body>
|
<body>
|
||||||
<div id="app">
|
<div id="app">
|
||||||
<v-select placeholder="default" :options="options"></v-select>
|
<v-select placeholder="default" :options="options"></v-select>
|
||||||
|
<v-select placeholder="default, RTL" :options="options" dir="rtl"></v-select>
|
||||||
<v-select placeholder="multiple" multiple :options="options"></v-select>
|
<v-select placeholder="multiple" multiple :options="options"></v-select>
|
||||||
<v-select placeholder="multiple, taggable" multiple taggable :options="options" no-drop></v-select>
|
<v-select placeholder="multiple, taggable" multiple taggable :options="options" no-drop></v-select>
|
||||||
<v-select placeholder="multiple, taggable, push-tags" multiple push-tags taggable :options="[{label: 'Foo', value: 'foo'}]"></v-select>
|
<v-select placeholder="multiple, taggable, push-tags" multiple push-tags taggable :options="[{label: 'Foo', value: 'foo'}]"></v-select>
|
||||||
<v-select placeholder="multiple, closeOnSelect=true" multiple :options="['cat', 'dog', 'bear']"></v-select>
|
<v-select placeholder="multiple, closeOnSelect=true" multiple :options="['cat', 'dog', 'bear']"></v-select>
|
||||||
<v-select placeholder="multiple, closeOnSelect=false" multiple :close-on-select="false" :options="['cat', 'dog', 'bear']"></v-select>
|
<v-select placeholder="multiple, closeOnSelect=false" multiple :close-on-select="false" :options="['cat', 'dog', 'bear']"></v-select>
|
||||||
<v-select placeholder="unsearchable" :options="options" :searchable="false"></v-select>
|
<v-select placeholder="searchable=false" :options="options" :searchable="false"></v-select>
|
||||||
<v-select placeholder="search github.." label="full_name" @search="search" :options="ajaxRes"></v-select>
|
<v-select placeholder="search github.." label="full_name" @search="search" :options="ajaxRes"></v-select>
|
||||||
|
<v-select placeholder="custom option template" :options="options" multiple>
|
||||||
|
<template slot="selected-option" slot-scope="option">
|
||||||
|
{{option.label}}
|
||||||
|
</template>
|
||||||
|
<template slot="option" slot-scope="option">
|
||||||
|
{{option.label}} ({{option.value}})
|
||||||
|
</template>
|
||||||
|
</v-select>
|
||||||
|
<v-select placeholder="disabled" disabled value="disabled"></v-select>
|
||||||
|
<v-select placeholder="disabled multiple" disabled multiple :value="['disabled', 'multiple']"></v-select>
|
||||||
|
<v-select placeholder="filterable=false, @search=searchPeople" label="first_name" :filterable="false" @search="searchPeople" :options="people"></v-select>
|
||||||
</div>
|
</div>
|
||||||
</body>
|
</body>
|
||||||
|
|
||||||
|
|||||||
Vendored
+2
-2
File diff suppressed because one or more lines are too long
Vendored
+1
-1
File diff suppressed because one or more lines are too long
@@ -122,7 +122,7 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="col-md-6">
|
<div class="col-md-6">
|
||||||
<pre><v-code lang="markup"><v-select v-on:change="consoleCallback" :options="countries"></v-select></v-code></pre>
|
<pre><v-code lang="markup"><v-select :on-change="consoleCallback" :options="countries"></v-select></v-code></pre>
|
||||||
<pre><v-code lang="javascript">methods: {
|
<pre><v-code lang="javascript">methods: {
|
||||||
consoleCallback(val) {
|
consoleCallback(val) {
|
||||||
console.dir(JSON.stringify(val))
|
console.dir(JSON.stringify(val))
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
### Change Event <small>Vuex Compatibility</small>
|
||||||
|
|
||||||
|
vue-select provides a `change` event. This function is passed the currently selected value(s) as it's only parameter.
|
||||||
|
|
||||||
|
This is very useful when integrating with Vuex, as it will allow your to trigger an action to update your vuex state object. Choose a callback and see it in action.
|
||||||
|
|
||||||
|
<div class="form-inline">
|
||||||
|
<div class="radio"><label><input type="radio" v-model="callback" value="console"> `console.log(val)`</label> </div>
|
||||||
|
<div class="radio"><label><input type="radio" v-model="callback" value="alert"> `alert(val)`</label> </div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
```html
|
||||||
|
<v-select :on-change="consoleCallback" :options="countries"></v-select>
|
||||||
|
```
|
||||||
|
|
||||||
|
```js
|
||||||
|
methods: {
|
||||||
|
consoleCallback(val) {
|
||||||
|
console.dir(JSON.stringify(val))
|
||||||
|
},
|
||||||
|
|
||||||
|
alertCallback(val) {
|
||||||
|
alert(JSON.stringify(val))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
+2
-7
@@ -1,12 +1,13 @@
|
|||||||
{
|
{
|
||||||
"name": "vue-select",
|
"name": "vue-select",
|
||||||
"version": "2.2.0",
|
"version": "2.4.0",
|
||||||
"description": "A native Vue.js component that provides similar functionality to Select2 without the overhead of jQuery.",
|
"description": "A native Vue.js component that provides similar functionality to Select2 without the overhead of jQuery.",
|
||||||
"author": "Jeff Sagal <sagalbot@gmail.com>",
|
"author": "Jeff Sagal <sagalbot@gmail.com>",
|
||||||
"private": false,
|
"private": false,
|
||||||
"main": "dist/vue-select.js",
|
"main": "dist/vue-select.js",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"start": "npm run dev",
|
||||||
"dev": "node build/dev-server.js",
|
"dev": "node build/dev-server.js",
|
||||||
"docs": "gitbook serve docs",
|
"docs": "gitbook serve docs",
|
||||||
"build": "node build/build.js",
|
"build": "node build/build.js",
|
||||||
@@ -14,12 +15,6 @@
|
|||||||
"test": "karma start test/unit/karma.conf.js --single-run",
|
"test": "karma start test/unit/karma.conf.js --single-run",
|
||||||
"test-watch": "karma start test/unit/karma.conf.js --single-run=false --auto-watch=true"
|
"test-watch": "karma start test/unit/karma.conf.js --single-run=false --auto-watch=true"
|
||||||
},
|
},
|
||||||
"browserify": {
|
|
||||||
"transform": [
|
|
||||||
"vueify",
|
|
||||||
"babelify"
|
|
||||||
]
|
|
||||||
},
|
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/sagalbot/vue-select.git"
|
"url": "https://github.com/sagalbot/vue-select.git"
|
||||||
|
|||||||
+179
-30
@@ -3,12 +3,30 @@
|
|||||||
position: relative;
|
position: relative;
|
||||||
font-family: sans-serif;
|
font-family: sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
.v-select,
|
.v-select,
|
||||||
.v-select * {
|
.v-select * {
|
||||||
-webkit-box-sizing: border-box;
|
-webkit-box-sizing: border-box;
|
||||||
-moz-box-sizing: border-box;
|
-moz-box-sizing: border-box;
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
}
|
}
|
||||||
|
/* Rtl support */
|
||||||
|
.v-select.rtl .open-indicator {
|
||||||
|
left: 10px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
.v-select.rtl .selected-tag {
|
||||||
|
float: right;
|
||||||
|
margin-right: 3px;
|
||||||
|
margin-left: 1px;
|
||||||
|
}
|
||||||
|
.v-select.rtl .dropdown-menu {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.v-select.rtl .dropdown-toggle .clear {
|
||||||
|
left: 30px;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
/* Open Indicator */
|
/* Open Indicator */
|
||||||
.v-select .open-indicator {
|
.v-select .open-indicator {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
@@ -20,7 +38,6 @@
|
|||||||
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
|
transition: all 150ms cubic-bezier(1.000, -0.115, 0.975, 0.855);
|
||||||
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
|
transition-timing-function: cubic-bezier(1.000, -0.115, 0.975, 0.855);
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
transition: opacity .1s;
|
|
||||||
height: 20px; width: 10px;
|
height: 20px; width: 10px;
|
||||||
}
|
}
|
||||||
.v-select .open-indicator:before {
|
.v-select .open-indicator:before {
|
||||||
@@ -58,7 +75,6 @@
|
|||||||
border: 1px solid rgba(60, 60, 60, .26);
|
border: 1px solid rgba(60, 60, 60, .26);
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
white-space: normal;
|
white-space: normal;
|
||||||
transition: border-radius .25s;
|
|
||||||
}
|
}
|
||||||
.v-select .dropdown-toggle:after {
|
.v-select .dropdown-toggle:after {
|
||||||
visibility: hidden;
|
visibility: hidden;
|
||||||
@@ -68,6 +84,22 @@
|
|||||||
clear: both;
|
clear: both;
|
||||||
height: 0;
|
height: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Clear Button */
|
||||||
|
.v-select .dropdown-toggle .clear {
|
||||||
|
position: absolute;
|
||||||
|
bottom: 9px;
|
||||||
|
right: 30px;
|
||||||
|
font-size: 23px;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
color: rgba(60, 60, 60, .5);
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
/* Dropdown Toggle States */
|
/* Dropdown Toggle States */
|
||||||
.v-select.searchable .dropdown-toggle {
|
.v-select.searchable .dropdown-toggle {
|
||||||
cursor: text;
|
cursor: text;
|
||||||
@@ -174,14 +206,14 @@
|
|||||||
background: none;
|
background: none;
|
||||||
position: relative;
|
position: relative;
|
||||||
box-shadow: none;
|
box-shadow: none;
|
||||||
float: left;
|
|
||||||
clear: none;
|
|
||||||
}
|
}
|
||||||
/* Search Input States */
|
|
||||||
.v-select.unsearchable input[type="search"] {
|
.v-select.unsearchable input[type="search"] {
|
||||||
max-width: 1px;
|
opacity: 0;
|
||||||
}
|
}
|
||||||
/* List Items */
|
.v-select.unsearchable input[type="search"]:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/* List Items */
|
||||||
.v-select li {
|
.v-select li {
|
||||||
line-height: 1.42857143; /* Normalize line height */
|
line-height: 1.42857143; /* Normalize line height */
|
||||||
}
|
}
|
||||||
@@ -233,6 +265,17 @@
|
|||||||
width: 5em;
|
width: 5em;
|
||||||
height: 5em;
|
height: 5em;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Disabled state */
|
||||||
|
.v-select.disabled .dropdown-toggle,
|
||||||
|
.v-select.disabled .dropdown-toggle .clear,
|
||||||
|
.v-select.disabled .dropdown-toggle input,
|
||||||
|
.v-select.disabled .selected-tag .close,
|
||||||
|
.v-select.disabled .open-indicator {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: rgb(248, 248, 248);
|
||||||
|
}
|
||||||
|
|
||||||
/* Loading Spinner States */
|
/* Loading Spinner States */
|
||||||
.v-select.loading .spinner {
|
.v-select.loading .spinner {
|
||||||
opacity: 1;
|
opacity: 1;
|
||||||
@@ -266,15 +309,20 @@
|
|||||||
</style>
|
</style>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div class="dropdown v-select" :class="dropdownClasses">
|
<div :dir="dir" class="dropdown v-select" :class="dropdownClasses">
|
||||||
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="dropdown-toggle">
|
<div ref="toggle" @mousedown.prevent="toggleDropdown" :class="['dropdown-toggle', 'clearfix']">
|
||||||
|
|
||||||
<span class="selected-tag" v-for="option in valueAsArray" v-bind:key="option.index">
|
<slot v-for="option in valueAsArray" name="selected-option-container"
|
||||||
{{ getOptionLabel(option) }}
|
:option="option" :deselect="deselect">
|
||||||
<button v-if="multiple" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
|
<span class="selected-tag" v-bind:key="option.index">
|
||||||
<span aria-hidden="true">×</span>
|
<slot name="selected-option" v-bind="option">
|
||||||
</button>
|
{{ getOptionLabel(option) }}
|
||||||
</span>
|
</slot>
|
||||||
|
<button v-if="multiple" :disabled="disabled" @click="deselect(option)" type="button" class="close" aria-label="Remove option">
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
</span>
|
||||||
|
</slot>
|
||||||
|
|
||||||
<input
|
<input
|
||||||
ref="search"
|
ref="search"
|
||||||
@@ -283,18 +331,32 @@
|
|||||||
@keyup.esc="onEscape"
|
@keyup.esc="onEscape"
|
||||||
@keydown.up.prevent="typeAheadUp"
|
@keydown.up.prevent="typeAheadUp"
|
||||||
@keydown.down.prevent="typeAheadDown"
|
@keydown.down.prevent="typeAheadDown"
|
||||||
@keyup.enter.prevent="typeAheadSelect"
|
@keydown.enter.prevent="typeAheadSelect"
|
||||||
@blur="onSearchBlur"
|
@blur="onSearchBlur"
|
||||||
@focus="onSearchFocus"
|
@focus="onSearchFocus"
|
||||||
type="search"
|
type="search"
|
||||||
class="form-control"
|
class="form-control"
|
||||||
|
autocomplete="false"
|
||||||
|
:disabled="disabled"
|
||||||
:placeholder="searchPlaceholder"
|
:placeholder="searchPlaceholder"
|
||||||
|
:tabindex="tabindex"
|
||||||
:readonly="!searchable"
|
:readonly="!searchable"
|
||||||
:style="{ width: isValueEmpty ? '100%' : 'auto' }"
|
:style="{ width: isValueEmpty ? '100%' : 'auto' }"
|
||||||
:id="inputId"
|
:id="inputId"
|
||||||
aria-label="Search for option"
|
aria-label="Search for option"
|
||||||
>
|
>
|
||||||
|
|
||||||
|
<button
|
||||||
|
v-show="showClearButton"
|
||||||
|
:disabled="disabled"
|
||||||
|
@click="clearSelection"
|
||||||
|
type="button"
|
||||||
|
class="clear"
|
||||||
|
title="Clear selection"
|
||||||
|
>
|
||||||
|
<span aria-hidden="true">×</span>
|
||||||
|
</button>
|
||||||
|
|
||||||
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
|
<i v-if="!noDrop" ref="openIndicator" role="presentation" class="open-indicator"></i>
|
||||||
|
|
||||||
<slot name="spinner">
|
<slot name="spinner">
|
||||||
@@ -306,7 +368,9 @@
|
|||||||
<ul ref="dropdownMenu" v-if="dropdownOpen" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
|
<ul ref="dropdownMenu" v-if="dropdownOpen" class="dropdown-menu" :style="{ 'max-height': maxHeight }">
|
||||||
<li v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
|
<li v-for="(option, index) in filteredOptions" v-bind:key="index" :class="{ active: isOptionSelected(option), highlight: index === typeAheadPointer }" @mouseover="typeAheadPointer = index">
|
||||||
<a @mousedown.prevent="select(option)">
|
<a @mousedown.prevent="select(option)">
|
||||||
|
<slot name="option" v-bind="option">
|
||||||
{{ getOptionLabel(option) }}
|
{{ getOptionLabel(option) }}
|
||||||
|
</slot>
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
<li v-if="!filteredOptions.length" class="no-options">
|
<li v-if="!filteredOptions.length" class="no-options">
|
||||||
@@ -341,7 +405,7 @@
|
|||||||
* If you are using an array of objects, vue-select will look for
|
* If you are using an array of objects, vue-select will look for
|
||||||
* a `label` key (ex. [{label: 'This is Foo', value: 'foo'}]). A
|
* a `label` key (ex. [{label: 'This is Foo', value: 'foo'}]). A
|
||||||
* custom label key can be set with the `label` prop.
|
* custom label key can be set with the `label` prop.
|
||||||
* @type {Object}
|
* @type {Array}
|
||||||
*/
|
*/
|
||||||
options: {
|
options: {
|
||||||
type: Array,
|
type: Array,
|
||||||
@@ -350,6 +414,15 @@
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Disable the entire component.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
disabled: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the max-height property on the dropdown list.
|
* Sets the max-height property on the dropdown list.
|
||||||
* @deprecated
|
* @deprecated
|
||||||
@@ -371,7 +444,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to the `multiple` attribute on a `<select>` input.
|
* Equivalent to the `multiple` attribute on a `<select>` input.
|
||||||
* @type {Object}
|
* @type {Boolean}
|
||||||
*/
|
*/
|
||||||
multiple: {
|
multiple: {
|
||||||
type: Boolean,
|
type: Boolean,
|
||||||
@@ -380,7 +453,7 @@
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Equivalent to the `placeholder` attribute on an `<input>`.
|
* Equivalent to the `placeholder` attribute on an `<input>`.
|
||||||
* @type {Object}
|
* @type {String}
|
||||||
*/
|
*/
|
||||||
placeholder: {
|
placeholder: {
|
||||||
type: String,
|
type: String,
|
||||||
@@ -429,6 +502,7 @@
|
|||||||
/**
|
/**
|
||||||
* Callback to generate the label text. If {option}
|
* Callback to generate the label text. If {option}
|
||||||
* is an object, returns option[this.label] by default.
|
* is an object, returns option[this.label] by default.
|
||||||
|
* @type {Function}
|
||||||
* @param {Object || String} option
|
* @param {Object || String} option
|
||||||
* @return {String}
|
* @return {String}
|
||||||
*/
|
*/
|
||||||
@@ -436,6 +510,13 @@
|
|||||||
type: Function,
|
type: Function,
|
||||||
default(option) {
|
default(option) {
|
||||||
if (typeof option === 'object') {
|
if (typeof option === 'object') {
|
||||||
|
if (!option.hasOwnProperty(this.label)) {
|
||||||
|
return console.warn(
|
||||||
|
`[vue-select warn]: Label key "option.${this.label}" does not` +
|
||||||
|
` exist in options object ${JSON.stringify(option)}.\n` +
|
||||||
|
'http://sagalbot.github.io/vue-select/#ex-labels'
|
||||||
|
)
|
||||||
|
}
|
||||||
if (this.label && option[this.label]) {
|
if (this.label && option[this.label]) {
|
||||||
return option[this.label]
|
return option[this.label]
|
||||||
}
|
}
|
||||||
@@ -444,12 +525,27 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callback to filter the search result the label text.
|
||||||
|
* @type {Function}
|
||||||
|
* @param {Object || String} option
|
||||||
|
* @param {String} label
|
||||||
|
* @param {String} search
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
filterFunction: {
|
||||||
|
type: Function,
|
||||||
|
default(option, label, search) {
|
||||||
|
return (label || '').toLowerCase().indexOf(search.toLowerCase()) > -1
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An optional callback function that is called each time the selected
|
* An optional callback function that is called each time the selected
|
||||||
* value(s) change. When integrating with Vuex, use this callback to trigger
|
* value(s) change. When integrating with Vuex, use this callback to trigger
|
||||||
* an action, rather than using :value.sync to retreive the selected value.
|
* an action, rather than using :value.sync to retreive the selected value.
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
* @default {null}
|
* @param {Object || String} val
|
||||||
*/
|
*/
|
||||||
onChange: {
|
onChange: {
|
||||||
type: Function,
|
type: Function,
|
||||||
@@ -467,6 +563,15 @@
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the tabindex for the input field.
|
||||||
|
* @type {Number}
|
||||||
|
*/
|
||||||
|
tabindex: {
|
||||||
|
type: Number,
|
||||||
|
default: null
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When true, newly created tags will be added to
|
* When true, newly created tags will be added to
|
||||||
* the options list.
|
* the options list.
|
||||||
@@ -477,6 +582,17 @@
|
|||||||
default: false
|
default: false
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When true, existing options will be filtered
|
||||||
|
* by the search text. Should not be used in conjunction
|
||||||
|
* with taggable.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
filterable: {
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* User defined function for adding Options
|
* User defined function for adding Options
|
||||||
* @type {Function}
|
* @type {Function}
|
||||||
@@ -517,7 +633,18 @@
|
|||||||
*/
|
*/
|
||||||
inputId: {
|
inputId: {
|
||||||
type: String
|
type: String
|
||||||
}
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets RTL support. Accepts 'ltr', 'rtl', 'auto'.
|
||||||
|
* @see https://developer.mozilla.org/en-US/docs/Web/HTML/Global_attributes/dir
|
||||||
|
* @type {String}
|
||||||
|
* @default 'auto'
|
||||||
|
*/
|
||||||
|
dir: {
|
||||||
|
type: String,
|
||||||
|
default: 'auto'
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
data() {
|
data() {
|
||||||
@@ -645,6 +772,14 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the currently selected value(s)
|
||||||
|
* @return {void}
|
||||||
|
*/
|
||||||
|
clearSelection() {
|
||||||
|
this.mutableValue = this.multiple ? [] : null
|
||||||
|
},
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called from this.select after each selection.
|
* Called from this.select after each selection.
|
||||||
* @param {Object|String} option
|
* @param {Object|String} option
|
||||||
@@ -671,8 +806,10 @@
|
|||||||
if (this.open) {
|
if (this.open) {
|
||||||
this.$refs.search.blur() // dropdown will close on blur
|
this.$refs.search.blur() // dropdown will close on blur
|
||||||
} else {
|
} else {
|
||||||
this.open = true
|
if (!this.disabled) {
|
||||||
this.$refs.search.focus()
|
this.open = true
|
||||||
|
this.$refs.search.focus()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -796,7 +933,9 @@
|
|||||||
searching: this.searching,
|
searching: this.searching,
|
||||||
searchable: this.searchable,
|
searchable: this.searchable,
|
||||||
unsearchable: !this.searchable,
|
unsearchable: !this.searchable,
|
||||||
loading: this.mutableLoading
|
loading: this.mutableLoading,
|
||||||
|
rtl: this.dir === 'rtl',
|
||||||
|
disabled: this.disabled
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -846,13 +985,15 @@
|
|||||||
* @return {array}
|
* @return {array}
|
||||||
*/
|
*/
|
||||||
filteredOptions() {
|
filteredOptions() {
|
||||||
|
if (!this.filterable && !this.taggable) {
|
||||||
|
return this.mutableOptions.slice()
|
||||||
|
}
|
||||||
let options = this.mutableOptions.filter((option) => {
|
let options = this.mutableOptions.filter((option) => {
|
||||||
if (typeof option === 'object' && option.hasOwnProperty(this.label)) {
|
let label = this.getOptionLabel(option)
|
||||||
return option[this.label].toLowerCase().indexOf(this.search.toLowerCase()) > -1
|
if (typeof label === 'number') {
|
||||||
} else if (typeof option === 'object' && !option.hasOwnProperty(this.label)) {
|
label = label.toString()
|
||||||
return console.warn(`[vue-select warn]: Label key "option.${this.label}" does not exist in options object.\nhttp://sagalbot.github.io/vue-select/#ex-labels`)
|
|
||||||
}
|
}
|
||||||
return option.toLowerCase().indexOf(this.search.toLowerCase()) > -1
|
return this.filterFunction(option, label, this.search)
|
||||||
})
|
})
|
||||||
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
|
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
|
||||||
options.unshift(this.search)
|
options.unshift(this.search)
|
||||||
@@ -883,10 +1024,18 @@
|
|||||||
if (this.multiple) {
|
if (this.multiple) {
|
||||||
return this.mutableValue
|
return this.mutableValue
|
||||||
} else if (this.mutableValue) {
|
} else if (this.mutableValue) {
|
||||||
return [this.mutableValue]
|
return [].concat(this.mutableValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determines if the clear button should be displayed.
|
||||||
|
* @return {Boolean}
|
||||||
|
*/
|
||||||
|
showClearButton() {
|
||||||
|
return !this.multiple && !this.open && this.mutableValue != null
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -17,13 +17,24 @@ new Vue({
|
|||||||
placeholder: "placeholder",
|
placeholder: "placeholder",
|
||||||
value: null,
|
value: null,
|
||||||
options: countries,
|
options: countries,
|
||||||
ajaxRes: []
|
ajaxRes: [],
|
||||||
|
people: []
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
search(search, loading) {
|
search(search, loading) {
|
||||||
loading(true)
|
loading(true)
|
||||||
this.getRepositories(search, loading, this)
|
this.getRepositories(search, loading, this)
|
||||||
},
|
},
|
||||||
|
searchPeople(search, loading) {
|
||||||
|
loading(true)
|
||||||
|
this.getPeople(loading, this)
|
||||||
|
},
|
||||||
|
getPeople: debounce((loading, vm) => {
|
||||||
|
vm.$http.get(`https://reqres.in/api/users?per_page=10`).then(res => {
|
||||||
|
vm.people = res.data.data
|
||||||
|
loading(false)
|
||||||
|
})
|
||||||
|
}, 250),
|
||||||
getRepositories: debounce((search, loading, vm) => {
|
getRepositories: debounce((search, loading, vm) => {
|
||||||
vm.$http.get(`https://api.github.com/search/repositories?q=${search}`).then(res => {
|
vm.$http.get(`https://api.github.com/search/repositories?q=${search}`).then(res => {
|
||||||
vm.ajaxRes = res.data.items
|
vm.ajaxRes = res.data.items
|
||||||
|
|||||||
@@ -4,7 +4,7 @@
|
|||||||
import Vue from 'vue'
|
import Vue from 'vue'
|
||||||
import vSelect from 'src/components/Select.vue'
|
import vSelect from 'src/components/Select.vue'
|
||||||
import pointerScroll from 'src/mixins/pointerScroll.js'
|
import pointerScroll from 'src/mixins/pointerScroll.js'
|
||||||
|
Vue.config.productionTip = false
|
||||||
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
|
// http://vue-loader.vuejs.org/en/workflow/testing-with-mocks.html
|
||||||
const Mock = require('!!vue?inject!src/components/Select.vue')
|
const Mock = require('!!vue?inject!src/components/Select.vue')
|
||||||
|
|
||||||
@@ -65,7 +65,7 @@ function searchSubmit(vm, search = false) {
|
|||||||
vm.$children[0].search = search
|
vm.$children[0].search = search
|
||||||
}
|
}
|
||||||
|
|
||||||
trigger(vm.$children[0].$refs.search, 'keyup', function (e) {
|
trigger(vm.$children[0].$refs.search, 'keydown', function (e) {
|
||||||
e.keyCode = 13
|
e.keyCode = 13
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -295,6 +295,15 @@ describe('Select.vue', () => {
|
|||||||
expect(vm.$refs.select.filteredOptions).toEqual(['bar','baz'])
|
expect(vm.$refs.select.filteredOptions).toEqual(['bar','baz'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not filter the array of strings if filterable is false', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `<div><v-select ref="select" :filterable="false" :options="['foo','bar','baz']" v-model="value"></v-select></div>`,
|
||||||
|
data: {value: 'foo'}
|
||||||
|
}).$mount()
|
||||||
|
vm.$refs.select.search = 'ba'
|
||||||
|
expect(vm.$refs.select.filteredOptions).toEqual(['foo','bar','baz'])
|
||||||
|
})
|
||||||
|
|
||||||
it('should filter without case-sensitivity', () => {
|
it('should filter without case-sensitivity', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `<div><v-select ref="select" :options="['Foo','Bar','Baz']" v-model="value"></v-select></div>`,
|
template: `<div><v-select ref="select" :options="['Foo','Bar','Baz']" v-model="value"></v-select></div>`,
|
||||||
@@ -312,9 +321,40 @@ describe('Select.vue', () => {
|
|||||||
vm.$refs.select.search = 'ba'
|
vm.$refs.select.search = 'ba'
|
||||||
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]))
|
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]))
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('can use a custom filterFunction passed via props', ()=>{
|
||||||
|
const vm = new Vue({
|
||||||
|
template: `<div><v-select ref="select" :filterFunction="customFn" :options="[{label: 'Aoo', value: 'foo'}, {label: 'Bar', value: 'bar'}, {label: 'Baz', value: 'baz'}]" v-model="value"></v-select></div>`,
|
||||||
|
data: {value: 'foo'},
|
||||||
|
methods:{
|
||||||
|
customFn: (option, label, search) => label.match(new RegExp('^' + search, 'i'))
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
vm.$refs.select.search = 'a'
|
||||||
|
expect(JSON.stringify(vm.$refs.select.filteredOptions)).toEqual(JSON.stringify([{label: 'Aoo', value: 'foo'}]))
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('Toggling Dropdown', () => {
|
describe('Toggling Dropdown', () => {
|
||||||
|
it('should not open the dropdown when the el is clicked but the component is disabled', (done) => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options" :value="value" disabled></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
value: [{label: 'one'}],
|
||||||
|
options: [{label: 'one'}]
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
vm.$children[0].toggleDropdown({target: vm.$children[0].$refs.search})
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(vm.$children[0].open).toEqual(false)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should open the dropdown when the el is clicked', (done) => {
|
it('should open the dropdown when the el is clicked', (done) => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
template: '<div><v-select :options="options" :value="value"></v-select></div>',
|
||||||
@@ -674,6 +714,22 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not remove tag when close icon is clicked and component is disabled', (done) => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select disabled multiple :options="options" v-model="value"></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
value: ['one'],
|
||||||
|
options: ['one', 'two', 'three']
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
vm.$children[0].$refs.toggle.querySelector('.close').click()
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(vm.$children[0].mutableValue).toEqual(['one'])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should remove the last item in the value array on delete keypress when multiple is true', () => {
|
it('should remove the last item in the value array on delete keypress when multiple is true', () => {
|
||||||
|
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
@@ -726,7 +782,7 @@ describe('Select.vue', () => {
|
|||||||
}).$mount()
|
}).$mount()
|
||||||
Vue.nextTick(() => {
|
Vue.nextTick(() => {
|
||||||
expect(console.warn).toHaveBeenCalledWith(
|
expect(console.warn).toHaveBeenCalledWith(
|
||||||
'[vue-select warn]: Label key "option.label" does not exist in options object.' +
|
'[vue-select warn]: Label key "option.label" does not exist in options object {}.' +
|
||||||
'\nhttp://sagalbot.github.io/vue-select/#ex-labels'
|
'\nhttp://sagalbot.github.io/vue-select/#ex-labels'
|
||||||
)
|
)
|
||||||
done()
|
done()
|
||||||
@@ -853,6 +909,21 @@ describe('Select.vue', () => {
|
|||||||
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three'])
|
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should add a freshly created option/tag to the options list when pushTags is true and filterable is false', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options" push-tags :value="value" :filterable="false" :multiple="true" :taggable="true"></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
value: ['one'],
|
||||||
|
options: ['one', 'two']
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
searchSubmit(vm, 'three')
|
||||||
|
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two', 'three'])
|
||||||
|
expect(vm.$children[0].filteredOptions).toEqual(['one', 'two', 'three'])
|
||||||
|
})
|
||||||
|
|
||||||
it('wont add a freshly created option/tag to the options list when pushTags is false', () => {
|
it('wont add a freshly created option/tag to the options list when pushTags is false', () => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options" :value="value" :multiple="true" :taggable="true"></v-select></div>',
|
template: '<div><v-select :options="options" :value="value" :multiple="true" :taggable="true"></v-select></div>',
|
||||||
@@ -867,6 +938,21 @@ describe('Select.vue', () => {
|
|||||||
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two'])
|
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two'])
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('wont add a freshly created option/tag to the options list when pushTags is false and filterable is false', () => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options" :value="value" :multiple="true" :filterable="false" :taggable="true"></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
value: ['one'],
|
||||||
|
options: ['one', 'two']
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
searchSubmit(vm, 'three')
|
||||||
|
expect(vm.$children[0].mutableOptions).toEqual(['one', 'two'])
|
||||||
|
expect(vm.$children[0].filteredOptions).toEqual(['one', 'two'])
|
||||||
|
})
|
||||||
|
|
||||||
it('should select an existing option if the search string matches a string from options', (done) => {
|
it('should select an existing option if the search string matches a string from options', (done) => {
|
||||||
let two = 'two'
|
let two = 'two'
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
@@ -908,6 +994,28 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should select an existing option if the search string matches an objects label from options when filter-options is false', (done) => {
|
||||||
|
let two = {label: 'two'}
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options" taggable :filterable="false"></v-select></div>',
|
||||||
|
data: {
|
||||||
|
options: [{label: 'one'}, two]
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
vm.$children[0].search = 'two'
|
||||||
|
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
searchSubmit(vm)
|
||||||
|
// This needs to be wrapped in nextTick() twice so that filteredOptions can
|
||||||
|
// calculate after setting the search text, and move the typeAheadPointer index to 0.
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(vm.$children[0].mutableValue.label).toBe(two.label)
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should not reset the selected value when the options property changes', (done) => {
|
it('should not reset the selected value when the options property changes', (done) => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
template: '<div><v-select :options="options" :value="value" :multiple="true" taggable></v-select></div>',
|
||||||
@@ -924,6 +1032,22 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('should not reset the selected value when the options property changes when filterable is false', (done) => {
|
||||||
|
const vm = new Vue({
|
||||||
|
template: '<div><v-select :options="options" :value="value" :multiple="true" :filterable="false" taggable></v-select></div>',
|
||||||
|
components: {vSelect},
|
||||||
|
data: {
|
||||||
|
value: [{label: 'one'}],
|
||||||
|
options: [{label: 'one'}]
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
vm.$children[0].mutableOptions = [{label: 'two'}]
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
expect(vm.$children[0].mutableValue).toEqual([{label: 'one'}])
|
||||||
|
done()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
it('should not allow duplicate tags when using string options', (done) => {
|
it('should not allow duplicate tags when using string options', (done) => {
|
||||||
const vm = new Vue({
|
const vm = new Vue({
|
||||||
template: `<div><v-select ref="select" taggable multiple></v-select></div>`,
|
template: `<div><v-select ref="select" taggable multiple></v-select></div>`,
|
||||||
@@ -1187,4 +1311,61 @@ describe('Select.vue', () => {
|
|||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe( 'Clear button', () => {
|
||||||
|
|
||||||
|
it( 'should be displayed on single select when value is selected', () => {
|
||||||
|
const VueSelect = Vue.extend( vSelect )
|
||||||
|
const vm = new VueSelect({
|
||||||
|
propsData: {
|
||||||
|
options: ['foo','bar'],
|
||||||
|
value: 'foo'
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
expect(vm.showClearButton).toEqual(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
it( 'should not be displayed on multiple select', () => {
|
||||||
|
const VueSelect = Vue.extend( vSelect )
|
||||||
|
const vm = new VueSelect({
|
||||||
|
propsData: {
|
||||||
|
options: ['foo','bar'],
|
||||||
|
value: 'foo',
|
||||||
|
multiple: true
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
expect(vm.showClearButton).toEqual(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it( 'should remove selected value when clicked', () => {
|
||||||
|
const VueSelect = Vue.extend( vSelect )
|
||||||
|
const vm = new VueSelect({
|
||||||
|
propsData: {
|
||||||
|
options: ['foo','bar'],
|
||||||
|
value: 'foo'
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
expect(vm.mutableValue).toEqual('foo')
|
||||||
|
vm.$el.querySelector( 'button.clear' ).click()
|
||||||
|
expect(vm.mutableValue).toEqual(null)
|
||||||
|
})
|
||||||
|
|
||||||
|
it( 'should be disabled when component is disabled', () => {
|
||||||
|
const VueSelect = Vue.extend( vSelect )
|
||||||
|
const vm = new VueSelect({
|
||||||
|
propsData: {
|
||||||
|
options: ['foo','bar'],
|
||||||
|
value: 'foo',
|
||||||
|
disabled: true
|
||||||
|
}
|
||||||
|
}).$mount()
|
||||||
|
|
||||||
|
const buttonEl = vm.$el.querySelector( 'button.clear' )
|
||||||
|
expect(buttonEl.disabled).toEqual(true);
|
||||||
|
})
|
||||||
|
|
||||||
|
});
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user