|
|
|
@@ -8,12 +8,12 @@
|
|
|
|
|
<div
|
|
|
|
|
:id="`vs${uid}__combobox`"
|
|
|
|
|
ref="toggle"
|
|
|
|
|
@mousedown="toggleDropdown($event)"
|
|
|
|
|
class="vs__dropdown-toggle"
|
|
|
|
|
role="combobox"
|
|
|
|
|
:aria-expanded="dropdownOpen.toString()"
|
|
|
|
|
:aria-owns="`vs${uid}__listbox`"
|
|
|
|
|
:aria-label="i18n.search.ariaLabel"
|
|
|
|
|
aria-label="Search for option"
|
|
|
|
|
@mousedown="toggleDropdown($event)"
|
|
|
|
|
>
|
|
|
|
|
<div ref="selectedOptions" class="vs__selected-options">
|
|
|
|
|
<slot
|
|
|
|
@@ -33,15 +33,13 @@
|
|
|
|
|
</slot>
|
|
|
|
|
<button
|
|
|
|
|
v-if="multiple"
|
|
|
|
|
ref="deselectButtons"
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
@click="deselect(option)"
|
|
|
|
|
type="button"
|
|
|
|
|
class="vs__deselect"
|
|
|
|
|
:title="i18n.deselectButton.ariaLabel(getOptionLabel(option))"
|
|
|
|
|
:aria-label="
|
|
|
|
|
i18n.deselectButton.ariaLabel(getOptionLabel(option))
|
|
|
|
|
"
|
|
|
|
|
ref="deselectButtons"
|
|
|
|
|
:title="`Deselect ${getOptionLabel(option)}`"
|
|
|
|
|
:aria-label="`Deselect ${getOptionLabel(option)}`"
|
|
|
|
|
@click="deselect(option)"
|
|
|
|
|
>
|
|
|
|
|
<component :is="childComponents.Deselect" />
|
|
|
|
|
</button>
|
|
|
|
@@ -60,13 +58,13 @@
|
|
|
|
|
<div ref="actions" class="vs__actions">
|
|
|
|
|
<button
|
|
|
|
|
v-show="showClearButton"
|
|
|
|
|
ref="clearButton"
|
|
|
|
|
:disabled="disabled"
|
|
|
|
|
type="button"
|
|
|
|
|
@click="clearSelection"
|
|
|
|
|
class="vs__clear"
|
|
|
|
|
title="i18n.clearButton.ariaLabel"
|
|
|
|
|
aria-label="i18n.clearButton.ariaLabel"
|
|
|
|
|
ref="clearButton"
|
|
|
|
|
title="Clear Selected"
|
|
|
|
|
aria-label="Clear Selected"
|
|
|
|
|
@click="clearSelection"
|
|
|
|
|
>
|
|
|
|
|
<component :is="childComponents.Deselect" />
|
|
|
|
|
</button>
|
|
|
|
@@ -80,31 +78,29 @@
|
|
|
|
|
</slot>
|
|
|
|
|
|
|
|
|
|
<slot name="spinner" v-bind="scope.spinner">
|
|
|
|
|
<div v-show="mutableLoading" class="vs__spinner">
|
|
|
|
|
{{ i18n.spinner.text }}
|
|
|
|
|
</div>
|
|
|
|
|
<div v-show="mutableLoading" class="vs__spinner">Loading...</div>
|
|
|
|
|
</slot>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
<transition :name="transition">
|
|
|
|
|
<ul
|
|
|
|
|
ref="dropdownMenu"
|
|
|
|
|
v-if="dropdownOpen"
|
|
|
|
|
:id="`vs${uid}__listbox`"
|
|
|
|
|
ref="dropdownMenu"
|
|
|
|
|
:key="`vs${uid}__listbox`"
|
|
|
|
|
v-append-to-body
|
|
|
|
|
class="vs__dropdown-menu"
|
|
|
|
|
role="listbox"
|
|
|
|
|
tabindex="-1"
|
|
|
|
|
@mousedown.prevent="onMousedown"
|
|
|
|
|
@mouseup="onMouseUp"
|
|
|
|
|
tabindex="-1"
|
|
|
|
|
v-append-to-body
|
|
|
|
|
>
|
|
|
|
|
<slot name="list-header" v-bind="scope.listHeader" />
|
|
|
|
|
<li
|
|
|
|
|
v-for="(option, index) in filteredOptions"
|
|
|
|
|
role="option"
|
|
|
|
|
:key="getOptionKey(option)"
|
|
|
|
|
:id="`vs${uid}__option-${index}`"
|
|
|
|
|
:key="getOptionKey(option)"
|
|
|
|
|
role="option"
|
|
|
|
|
class="vs__dropdown-option"
|
|
|
|
|
:class="{
|
|
|
|
|
'vs__dropdown-option--selected': isOptionSelected(option),
|
|
|
|
@@ -120,9 +116,9 @@
|
|
|
|
|
</slot>
|
|
|
|
|
</li>
|
|
|
|
|
<li v-if="filteredOptions.length === 0" class="vs__no-options">
|
|
|
|
|
<slot name="no-options" v-bind="scope.noOptions">{{
|
|
|
|
|
i18n.noOptions.text
|
|
|
|
|
}}</slot>
|
|
|
|
|
<slot name="no-options" v-bind="scope.noOptions"
|
|
|
|
|
>Sorry, no matching options.</slot
|
|
|
|
|
>
|
|
|
|
|
</li>
|
|
|
|
|
<slot name="list-footer" v-bind="scope.listFooter" />
|
|
|
|
|
</ul>
|
|
|
|
@@ -137,13 +133,10 @@
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<script type="text/babel">
|
|
|
|
|
import {
|
|
|
|
|
ajax,
|
|
|
|
|
pointerScroll,
|
|
|
|
|
i18n,
|
|
|
|
|
pointer as typeAheadPointer,
|
|
|
|
|
} from '../mixins'
|
|
|
|
|
<script>
|
|
|
|
|
import pointerScroll from '../mixins/pointerScroll'
|
|
|
|
|
import typeAheadPointer from '../mixins/typeAheadPointer'
|
|
|
|
|
import ajax from '../mixins/ajax'
|
|
|
|
|
import childComponents from './childComponents'
|
|
|
|
|
import appendToBody from '../directives/appendToBody'
|
|
|
|
|
import sortAndStringify from '../utility/sortAndStringify'
|
|
|
|
@@ -155,9 +148,9 @@ import uniqueId from '../utility/uniqueId'
|
|
|
|
|
export default {
|
|
|
|
|
components: { ...childComponents },
|
|
|
|
|
|
|
|
|
|
directives: {appendToBody},
|
|
|
|
|
directives: { appendToBody },
|
|
|
|
|
|
|
|
|
|
mixins: [pointerScroll, typeAheadPointer, ajax, i18n],
|
|
|
|
|
mixins: [pointerScroll, typeAheadPointer, ajax],
|
|
|
|
|
|
|
|
|
|
props: {
|
|
|
|
|
/**
|
|
|
|
@@ -166,6 +159,7 @@ export default {
|
|
|
|
|
* using 'change' event using v-on
|
|
|
|
|
* @type {Object||String||null}
|
|
|
|
|
*/
|
|
|
|
|
// eslint-disable-next-line vue/require-default-prop,vue/require-prop-types
|
|
|
|
|
value: {},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -529,6 +523,7 @@ export default {
|
|
|
|
|
* @type {String}
|
|
|
|
|
* @default {null}
|
|
|
|
|
*/
|
|
|
|
|
// eslint-disable-next-line vue/require-default-prop
|
|
|
|
|
inputId: {
|
|
|
|
|
type: String,
|
|
|
|
|
},
|
|
|
|
@@ -582,6 +577,7 @@ export default {
|
|
|
|
|
* for the search input. Can be used to implement
|
|
|
|
|
* custom behaviour for key presses.
|
|
|
|
|
*/
|
|
|
|
|
|
|
|
|
|
mapKeydown: {
|
|
|
|
|
type: Function,
|
|
|
|
|
/**
|
|
|
|
@@ -653,6 +649,7 @@ export default {
|
|
|
|
|
open: false,
|
|
|
|
|
isComposing: false,
|
|
|
|
|
pushedTags: [],
|
|
|
|
|
// eslint-disable-next-line vue/no-reserved-keys
|
|
|
|
|
_value: [], // Internal value managed by Vue Select if no `value` prop is passed
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
@@ -663,26 +660,29 @@ export default {
|
|
|
|
|
* track the state of values internally.
|
|
|
|
|
* @return {boolean}
|
|
|
|
|
*/
|
|
|
|
|
isTrackingValues () {
|
|
|
|
|
return typeof this.value === 'undefined' || this.$options.propsData.hasOwnProperty('reduce');
|
|
|
|
|
isTrackingValues() {
|
|
|
|
|
return (
|
|
|
|
|
typeof this.value === 'undefined' ||
|
|
|
|
|
this.$options.propsData.hasOwnProperty('reduce')
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The options that are currently selected.
|
|
|
|
|
* @return {Array}
|
|
|
|
|
*/
|
|
|
|
|
selectedValue () {
|
|
|
|
|
let value = this.value;
|
|
|
|
|
selectedValue() {
|
|
|
|
|
let value = this.value
|
|
|
|
|
if (this.isTrackingValues) {
|
|
|
|
|
// Vue select has to manage value internally
|
|
|
|
|
value = this.$data._value;
|
|
|
|
|
value = this.$data._value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value) {
|
|
|
|
|
return [].concat(value);
|
|
|
|
|
return [].concat(value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return [];
|
|
|
|
|
return []
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -692,61 +692,65 @@ export default {
|
|
|
|
|
*
|
|
|
|
|
* @return {Array}
|
|
|
|
|
*/
|
|
|
|
|
optionList () {
|
|
|
|
|
return this.options.concat(this.pushTags ? this.pushedTags : []);
|
|
|
|
|
optionList() {
|
|
|
|
|
return this.options.concat(this.pushTags ? this.pushedTags : [])
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Find the search input DOM element.
|
|
|
|
|
* @returns {HTMLInputElement}
|
|
|
|
|
*/
|
|
|
|
|
searchEl () {
|
|
|
|
|
searchEl() {
|
|
|
|
|
return !!this.$scopedSlots['search']
|
|
|
|
|
? this.$refs.selectedOptions.querySelector(this.searchInputQuerySelector)
|
|
|
|
|
: this.$refs.search;
|
|
|
|
|
? this.$refs.selectedOptions.querySelector(
|
|
|
|
|
this.searchInputQuerySelector
|
|
|
|
|
)
|
|
|
|
|
: this.$refs.search
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* The object to be bound to the $slots.search scoped slot.
|
|
|
|
|
* @returns {Object}
|
|
|
|
|
*/
|
|
|
|
|
scope () {
|
|
|
|
|
scope() {
|
|
|
|
|
const listSlot = {
|
|
|
|
|
search: this.search,
|
|
|
|
|
loading: this.loading,
|
|
|
|
|
searching: this.searching,
|
|
|
|
|
filteredOptions: this.filteredOptions
|
|
|
|
|
};
|
|
|
|
|
filteredOptions: this.filteredOptions,
|
|
|
|
|
}
|
|
|
|
|
return {
|
|
|
|
|
search: {
|
|
|
|
|
attributes: {
|
|
|
|
|
'disabled': this.disabled,
|
|
|
|
|
'placeholder': this.searchPlaceholder,
|
|
|
|
|
'tabindex': this.tabindex,
|
|
|
|
|
'readonly': !this.searchable,
|
|
|
|
|
'id': this.inputId,
|
|
|
|
|
disabled: this.disabled,
|
|
|
|
|
placeholder: this.searchPlaceholder,
|
|
|
|
|
tabindex: this.tabindex,
|
|
|
|
|
readonly: !this.searchable,
|
|
|
|
|
id: this.inputId,
|
|
|
|
|
'aria-autocomplete': 'list',
|
|
|
|
|
'aria-labelledby': `vs${this.uid}__combobox`,
|
|
|
|
|
'aria-controls': `vs${this.uid}__listbox`,
|
|
|
|
|
'ref': 'search',
|
|
|
|
|
'type': 'search',
|
|
|
|
|
'autocomplete': this.autocomplete,
|
|
|
|
|
'value': this.search,
|
|
|
|
|
...(this.dropdownOpen && this.filteredOptions[this.typeAheadPointer] ? {
|
|
|
|
|
'aria-activedescendant': `vs${this.uid}__option-${this.typeAheadPointer}`
|
|
|
|
|
} : {}),
|
|
|
|
|
ref: 'search',
|
|
|
|
|
type: 'search',
|
|
|
|
|
autocomplete: this.autocomplete,
|
|
|
|
|
value: this.search,
|
|
|
|
|
...(this.dropdownOpen && this.filteredOptions[this.typeAheadPointer]
|
|
|
|
|
? {
|
|
|
|
|
'aria-activedescendant': `vs${this.uid}__option-${this.typeAheadPointer}`,
|
|
|
|
|
}
|
|
|
|
|
: {}),
|
|
|
|
|
},
|
|
|
|
|
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,
|
|
|
|
|
compositionstart: () => (this.isComposing = true),
|
|
|
|
|
compositionend: () => (this.isComposing = false),
|
|
|
|
|
keydown: this.onSearchKeyDown,
|
|
|
|
|
blur: this.onSearchBlur,
|
|
|
|
|
focus: this.onSearchFocus,
|
|
|
|
|
input: (e) => (this.search = e.target.value),
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
spinner: {
|
|
|
|
|
loading: this.mutableLoading
|
|
|
|
|
loading: this.mutableLoading,
|
|
|
|
|
},
|
|
|
|
|
noOptions: {
|
|
|
|
|
search: this.search,
|
|
|
|
@@ -755,16 +759,16 @@ export default {
|
|
|
|
|
},
|
|
|
|
|
openIndicator: {
|
|
|
|
|
attributes: {
|
|
|
|
|
'ref': 'openIndicator',
|
|
|
|
|
'role': 'presentation',
|
|
|
|
|
'class': 'vs__open-indicator',
|
|
|
|
|
ref: 'openIndicator',
|
|
|
|
|
role: 'presentation',
|
|
|
|
|
class: 'vs__open-indicator',
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
listHeader: listSlot,
|
|
|
|
|
listFooter: listSlot,
|
|
|
|
|
header: { ...listSlot, deselect: this.deselect },
|
|
|
|
|
footer: { ...listSlot, deselect: this.deselect }
|
|
|
|
|
};
|
|
|
|
|
footer: { ...listSlot, deselect: this.deselect },
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -774,11 +778,11 @@ export default {
|
|
|
|
|
*
|
|
|
|
|
* @return {Object}
|
|
|
|
|
*/
|
|
|
|
|
childComponents () {
|
|
|
|
|
childComponents() {
|
|
|
|
|
return {
|
|
|
|
|
...childComponents,
|
|
|
|
|
...this.components
|
|
|
|
|
};
|
|
|
|
|
...this.components,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -793,7 +797,7 @@ export default {
|
|
|
|
|
'vs--searchable': this.searchable && !this.noDrop,
|
|
|
|
|
'vs--unsearchable': !this.searchable,
|
|
|
|
|
'vs--loading': this.mutableLoading,
|
|
|
|
|
'vs--disabled': this.disabled
|
|
|
|
|
'vs--disabled': this.disabled,
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -803,7 +807,7 @@ export default {
|
|
|
|
|
* @return {Boolean} True if non empty value
|
|
|
|
|
*/
|
|
|
|
|
searching() {
|
|
|
|
|
return !! this.search
|
|
|
|
|
return !!this.search
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -812,7 +816,7 @@ export default {
|
|
|
|
|
* @return {Boolean} True if open
|
|
|
|
|
*/
|
|
|
|
|
dropdownOpen() {
|
|
|
|
|
return this.dropdownShouldOpen(this);
|
|
|
|
|
return this.dropdownShouldOpen(this)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -821,9 +825,9 @@ export default {
|
|
|
|
|
* @return {String} Placeholder text
|
|
|
|
|
*/
|
|
|
|
|
searchPlaceholder() {
|
|
|
|
|
if (this.isValueEmpty && this.placeholder) {
|
|
|
|
|
return this.placeholder;
|
|
|
|
|
}
|
|
|
|
|
return this.isValueEmpty && this.placeholder
|
|
|
|
|
? this.placeholder
|
|
|
|
|
: undefined
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -835,20 +839,22 @@ export default {
|
|
|
|
|
* @return {array}
|
|
|
|
|
*/
|
|
|
|
|
filteredOptions() {
|
|
|
|
|
const optionList = [].concat(this.optionList);
|
|
|
|
|
const optionList = [].concat(this.optionList)
|
|
|
|
|
|
|
|
|
|
if (!this.filterable && !this.taggable) {
|
|
|
|
|
return optionList;
|
|
|
|
|
return optionList
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
let options = this.search.length ? this.filter(optionList, this.search, this) : optionList;
|
|
|
|
|
let options = this.search.length
|
|
|
|
|
? this.filter(optionList, this.search, this)
|
|
|
|
|
: optionList
|
|
|
|
|
if (this.taggable && this.search.length) {
|
|
|
|
|
const createdOption = this.createOption(this.search);
|
|
|
|
|
const createdOption = this.createOption(this.search)
|
|
|
|
|
if (!this.optionExists(createdOption)) {
|
|
|
|
|
options.unshift(createdOption);
|
|
|
|
|
options.unshift(createdOption)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return options;
|
|
|
|
|
return options
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -856,7 +862,7 @@ export default {
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
isValueEmpty() {
|
|
|
|
|
return this.selectedValue.length === 0;
|
|
|
|
|
return this.selectedValue.length === 0
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -864,7 +870,9 @@ export default {
|
|
|
|
|
* @return {Boolean}
|
|
|
|
|
*/
|
|
|
|
|
showClearButton() {
|
|
|
|
|
return !this.multiple && this.clearable && !this.open && !this.isValueEmpty
|
|
|
|
|
return (
|
|
|
|
|
!this.multiple && this.clearable && !this.open && !this.isValueEmpty
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -876,17 +884,22 @@ export default {
|
|
|
|
|
* is correct.
|
|
|
|
|
* @return {[type]} [description]
|
|
|
|
|
*/
|
|
|
|
|
options (newOptions, oldOptions) {
|
|
|
|
|
let shouldReset = () => typeof this.resetOnOptionsChange === 'function'
|
|
|
|
|
? this.resetOnOptionsChange(newOptions, oldOptions, this.selectedValue)
|
|
|
|
|
: this.resetOnOptionsChange;
|
|
|
|
|
options(newOptions, oldOptions) {
|
|
|
|
|
let shouldReset = () =>
|
|
|
|
|
typeof this.resetOnOptionsChange === 'function'
|
|
|
|
|
? this.resetOnOptionsChange(
|
|
|
|
|
newOptions,
|
|
|
|
|
oldOptions,
|
|
|
|
|
this.selectedValue
|
|
|
|
|
)
|
|
|
|
|
: this.resetOnOptionsChange
|
|
|
|
|
|
|
|
|
|
if (!this.taggable && shouldReset()) {
|
|
|
|
|
this.clearSelection();
|
|
|
|
|
this.clearSelection()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.value && this.isTrackingValues) {
|
|
|
|
|
this.setInternalValueFromOptions(this.value);
|
|
|
|
|
this.setInternalValueFromOptions(this.value)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -903,7 +916,6 @@ export default {
|
|
|
|
|
/**
|
|
|
|
|
* Always reset the value when
|
|
|
|
|
* the multiple prop changes.
|
|
|
|
|
* @param {Boolean} isMultiple
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
multiple() {
|
|
|
|
@@ -911,14 +923,14 @@ export default {
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
open(isOpen) {
|
|
|
|
|
this.$emit(isOpen ? 'open' : 'close');
|
|
|
|
|
}
|
|
|
|
|
this.$emit(isOpen ? 'open' : 'close')
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
created() {
|
|
|
|
|
this.mutableLoading = this.loading;
|
|
|
|
|
this.mutableLoading = this.loading
|
|
|
|
|
|
|
|
|
|
if (typeof this.value !== "undefined" && this.isTrackingValues) {
|
|
|
|
|
if (typeof this.value !== 'undefined' && this.isTrackingValues) {
|
|
|
|
|
this.setInternalValueFromOptions(this.value)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -934,9 +946,11 @@ export default {
|
|
|
|
|
*/
|
|
|
|
|
setInternalValueFromOptions(value) {
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
this.$data._value = value.map(val => this.findOptionFromReducedValue(val));
|
|
|
|
|
this.$data._value = value.map((val) =>
|
|
|
|
|
this.findOptionFromReducedValue(val)
|
|
|
|
|
)
|
|
|
|
|
} else {
|
|
|
|
|
this.$data._value = this.findOptionFromReducedValue(value);
|
|
|
|
|
this.$data._value = this.findOptionFromReducedValue(value)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -946,16 +960,16 @@ export default {
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
select(option) {
|
|
|
|
|
this.$emit('option:selecting', option);
|
|
|
|
|
this.$emit('option:selecting', option)
|
|
|
|
|
if (!this.isOptionSelected(option)) {
|
|
|
|
|
if (this.taggable && !this.optionExists(option)) {
|
|
|
|
|
this.$emit('option:created', option);
|
|
|
|
|
this.$emit('option:created', option)
|
|
|
|
|
}
|
|
|
|
|
if (this.multiple) {
|
|
|
|
|
option = this.selectedValue.concat(option)
|
|
|
|
|
}
|
|
|
|
|
this.updateValue(option);
|
|
|
|
|
this.$emit('option:selected', option);
|
|
|
|
|
this.updateValue(option)
|
|
|
|
|
this.$emit('option:selected', option)
|
|
|
|
|
}
|
|
|
|
|
this.onAfterSelect(option)
|
|
|
|
|
},
|
|
|
|
@@ -965,12 +979,14 @@ export default {
|
|
|
|
|
* @param {Object|String} option
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
deselect (option) {
|
|
|
|
|
this.$emit('option:deselecting', option);
|
|
|
|
|
this.updateValue(this.selectedValue.filter(val => {
|
|
|
|
|
return !this.optionComparator(val, option);
|
|
|
|
|
}));
|
|
|
|
|
this.$emit('option:deselected', option);
|
|
|
|
|
deselect(option) {
|
|
|
|
|
this.$emit('option:deselecting', option)
|
|
|
|
|
this.updateValue(
|
|
|
|
|
this.selectedValue.filter((val) => {
|
|
|
|
|
return !this.optionComparator(val, option)
|
|
|
|
|
})
|
|
|
|
|
)
|
|
|
|
|
this.$emit('option:deselected', option)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -988,7 +1004,7 @@ export default {
|
|
|
|
|
*/
|
|
|
|
|
onAfterSelect(option) {
|
|
|
|
|
if (this.closeOnSelect) {
|
|
|
|
|
this.open = !this.open;
|
|
|
|
|
this.open = !this.open
|
|
|
|
|
this.searchEl.blur()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
@@ -1005,21 +1021,21 @@ export default {
|
|
|
|
|
* @emits input
|
|
|
|
|
* @param value
|
|
|
|
|
*/
|
|
|
|
|
updateValue (value) {
|
|
|
|
|
updateValue(value) {
|
|
|
|
|
if (typeof this.value === 'undefined') {
|
|
|
|
|
// Vue select has to manage value
|
|
|
|
|
this.$data._value = value;
|
|
|
|
|
this.$data._value = value
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (value !== null) {
|
|
|
|
|
if (Array.isArray(value)) {
|
|
|
|
|
value = value.map(val => this.reduce(val));
|
|
|
|
|
value = value.map((val) => this.reduce(val))
|
|
|
|
|
} else {
|
|
|
|
|
value = this.reduce(value);
|
|
|
|
|
value = this.reduce(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.$emit('input', value);
|
|
|
|
|
this.$emit('input', value)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1027,10 +1043,10 @@ export default {
|
|
|
|
|
* @param {Event} event
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
toggleDropdown (event) {
|
|
|
|
|
const targetIsNotSearch = event.target !== this.searchEl;
|
|
|
|
|
toggleDropdown(event) {
|
|
|
|
|
const targetIsNotSearch = event.target !== this.searchEl
|
|
|
|
|
if (targetIsNotSearch) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// don't react to click on deselect/clear buttons,
|
|
|
|
@@ -1038,18 +1054,23 @@ export default {
|
|
|
|
|
const ignoredButtons = [
|
|
|
|
|
...(this.$refs['deselectButtons'] || []),
|
|
|
|
|
...([this.$refs['clearButton']] || []),
|
|
|
|
|
];
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
if (this.searchEl === undefined || ignoredButtons.filter(Boolean).some(ref => ref.contains(event.target) || ref === event.target)) {
|
|
|
|
|
event.preventDefault();
|
|
|
|
|
return;
|
|
|
|
|
if (
|
|
|
|
|
this.searchEl === undefined ||
|
|
|
|
|
ignoredButtons
|
|
|
|
|
.filter(Boolean)
|
|
|
|
|
.some((ref) => ref.contains(event.target) || ref === event.target)
|
|
|
|
|
) {
|
|
|
|
|
event.preventDefault()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (this.open && targetIsNotSearch) {
|
|
|
|
|
this.searchEl.blur();
|
|
|
|
|
this.searchEl.blur()
|
|
|
|
|
} else if (!this.disabled) {
|
|
|
|
|
this.open = true;
|
|
|
|
|
this.searchEl.focus();
|
|
|
|
|
this.open = true
|
|
|
|
|
this.searchEl.focus()
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
@@ -1059,7 +1080,9 @@ export default {
|
|
|
|
|
* @return {Boolean} True when selected | False otherwise
|
|
|
|
|
*/
|
|
|
|
|
isOptionSelected(option) {
|
|
|
|
|
return this.selectedValue.some(value => this.optionComparator(value, option))
|
|
|
|
|
return this.selectedValue.some((value) =>
|
|
|
|
|
this.optionComparator(value, option)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1070,7 +1093,7 @@ export default {
|
|
|
|
|
* @returns {boolean}
|
|
|
|
|
*/
|
|
|
|
|
optionComparator(a, b) {
|
|
|
|
|
return this.getOptionKey(a) === this.getOptionKey(b);
|
|
|
|
|
return this.getOptionKey(a) === this.getOptionKey(b)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1081,16 +1104,14 @@ export default {
|
|
|
|
|
* @param value {Object}
|
|
|
|
|
* @returns {*}
|
|
|
|
|
*/
|
|
|
|
|
findOptionFromReducedValue (value) {
|
|
|
|
|
const predicate = option => JSON.stringify(this.reduce(option)) === JSON.stringify(value);
|
|
|
|
|
findOptionFromReducedValue(value) {
|
|
|
|
|
const predicate = (option) =>
|
|
|
|
|
JSON.stringify(this.reduce(option)) === JSON.stringify(value)
|
|
|
|
|
|
|
|
|
|
const matches = [
|
|
|
|
|
...this.options,
|
|
|
|
|
...this.pushedTags,
|
|
|
|
|
].filter(predicate);
|
|
|
|
|
const matches = [...this.options, ...this.pushedTags].filter(predicate)
|
|
|
|
|
|
|
|
|
|
if (matches.length === 1) {
|
|
|
|
|
return matches[0];
|
|
|
|
|
return matches[0]
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1099,7 +1120,11 @@ export default {
|
|
|
|
|
* unique reduced value.
|
|
|
|
|
* @see https://github.com/sagalbot/vue-select/issues/1089#issuecomment-597238735
|
|
|
|
|
*/
|
|
|
|
|
return matches.find(match => this.optionComparator(match, this.$data._value)) || value;
|
|
|
|
|
return (
|
|
|
|
|
matches.find((match) =>
|
|
|
|
|
this.optionComparator(match, this.$data._value)
|
|
|
|
|
) || value
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1107,7 +1132,7 @@ export default {
|
|
|
|
|
* @emits {search:blur}
|
|
|
|
|
* @returns {void}
|
|
|
|
|
*/
|
|
|
|
|
closeSearchOptions(){
|
|
|
|
|
closeSearchOptions() {
|
|
|
|
|
this.open = false
|
|
|
|
|
this.$emit('search:blur')
|
|
|
|
|
},
|
|
|
|
@@ -1118,10 +1143,17 @@ export default {
|
|
|
|
|
* @return {this.value}
|
|
|
|
|
*/
|
|
|
|
|
maybeDeleteValue() {
|
|
|
|
|
if (!this.searchEl.value.length && this.selectedValue && this.selectedValue.length && this.clearable) {
|
|
|
|
|
let value = null;
|
|
|
|
|
if (
|
|
|
|
|
!this.searchEl.value.length &&
|
|
|
|
|
this.selectedValue &&
|
|
|
|
|
this.selectedValue.length &&
|
|
|
|
|
this.clearable
|
|
|
|
|
) {
|
|
|
|
|
let value = null
|
|
|
|
|
if (this.multiple) {
|
|
|
|
|
value = [...this.selectedValue.slice(0, this.selectedValue.length - 1)]
|
|
|
|
|
value = [
|
|
|
|
|
...this.selectedValue.slice(0, this.selectedValue.length - 1),
|
|
|
|
|
]
|
|
|
|
|
}
|
|
|
|
|
this.updateValue(value)
|
|
|
|
|
}
|
|
|
|
@@ -1135,7 +1167,9 @@ export default {
|
|
|
|
|
* @return {boolean}
|
|
|
|
|
*/
|
|
|
|
|
optionExists(option) {
|
|
|
|
|
return this.optionList.some(_option => this.optionComparator(_option, option))
|
|
|
|
|
return this.optionList.some((_option) =>
|
|
|
|
|
this.optionComparator(_option, option)
|
|
|
|
|
)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1144,8 +1178,8 @@ export default {
|
|
|
|
|
* @param option
|
|
|
|
|
* @return {*}
|
|
|
|
|
*/
|
|
|
|
|
normalizeOptionForSlot (option) {
|
|
|
|
|
return (typeof option === 'object') ? option : {[this.label]: option};
|
|
|
|
|
normalizeOptionForSlot(option) {
|
|
|
|
|
return typeof option === 'object' ? option : { [this.label]: option }
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1155,8 +1189,8 @@ export default {
|
|
|
|
|
* @param {Object || String} option
|
|
|
|
|
* @return {void}
|
|
|
|
|
*/
|
|
|
|
|
pushTag (option) {
|
|
|
|
|
this.pushedTags.push(option);
|
|
|
|
|
pushTag(option) {
|
|
|
|
|
this.pushedTags.push(option)
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
@@ -1181,7 +1215,7 @@ export default {
|
|
|
|
|
if (this.mousedown && !this.searching) {
|
|
|
|
|
this.mousedown = false
|
|
|
|
|
} else {
|
|
|
|
|
const { clearSearchOnSelect, multiple } = this;
|
|
|
|
|
const { clearSearchOnSelect, multiple } = this
|
|
|
|
|
if (this.clearSearchOnBlur({ clearSearchOnSelect, multiple })) {
|
|
|
|
|
this.search = ''
|
|
|
|
|
}
|
|
|
|
@@ -1189,7 +1223,7 @@ export default {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
// Fixed bug where no-options message could not be closed
|
|
|
|
|
if (this.search.length === 0 && this.options.length === 0){
|
|
|
|
|
if (this.search.length === 0 && this.options.length === 0) {
|
|
|
|
|
this.closeSearchOptions()
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
@@ -1231,38 +1265,39 @@ export default {
|
|
|
|
|
* @param e {KeyboardEvent}
|
|
|
|
|
* @return {Function}
|
|
|
|
|
*/
|
|
|
|
|
onSearchKeyDown (e) {
|
|
|
|
|
const preventAndSelect = e => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return !this.isComposing && this.typeAheadSelect();
|
|
|
|
|
};
|
|
|
|
|
onSearchKeyDown(e) {
|
|
|
|
|
const preventAndSelect = (e) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
return !this.isComposing && this.typeAheadSelect()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
const defaults = {
|
|
|
|
|
// backspace
|
|
|
|
|
8: e => this.maybeDeleteValue(),
|
|
|
|
|
8: (e) => this.maybeDeleteValue(),
|
|
|
|
|
// tab
|
|
|
|
|
9: e => this.onTab(),
|
|
|
|
|
9: (e) => this.onTab(),
|
|
|
|
|
// esc
|
|
|
|
|
27: e => this.onEscape(),
|
|
|
|
|
27: (e) => this.onEscape(),
|
|
|
|
|
// up.prevent
|
|
|
|
|
38: e => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return this.typeAheadUp();
|
|
|
|
|
38: (e) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
return this.typeAheadUp()
|
|
|
|
|
},
|
|
|
|
|
// down.prevent
|
|
|
|
|
40: e => {
|
|
|
|
|
e.preventDefault();
|
|
|
|
|
return this.typeAheadDown();
|
|
|
|
|
40: (e) => {
|
|
|
|
|
e.preventDefault()
|
|
|
|
|
return this.typeAheadDown()
|
|
|
|
|
},
|
|
|
|
|
};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
this.selectOnKeyCodes.forEach(keyCode => defaults[keyCode] = preventAndSelect);
|
|
|
|
|
this.selectOnKeyCodes.forEach(
|
|
|
|
|
(keyCode) => (defaults[keyCode] = preventAndSelect)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
const handlers = this.mapKeydown(defaults, this);
|
|
|
|
|
const handlers = this.mapKeydown(defaults, this)
|
|
|
|
|
|
|
|
|
|
if (typeof handlers[e.keyCode] === 'function') {
|
|
|
|
|
return handlers[e.keyCode](e);
|
|
|
|
|
}
|
|
|
|
|
return handlers[e.keyCode](e)
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
},
|
|
|
|
|