2
0
mirror of https://github.com/tenrok/vue-select.git synced 2026-06-10 07:52:23 +03:00
Files
vue-select/src/components/Select.vue
T
2020-02-08 11:32:44 -08:00

1223 lines
33 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div :dir="dir" class="v-select" :class="stateClasses">
<div ref="toggle" @mousedown.prevent="toggleDropdown" class="vs__dropdown-toggle">
<div class="vs__selected-options" ref="selectedOptions">
<slot name="selected-option" v-for="selected in scopedValues" v-bind="selected">
<span :class="selected.bindings.class">
{{ selected.label }}
<component
ref="deselectButtons"
:is="selected.deselect.component"
v-if="selected.deselect.bindings.multiple"
v-bind="selected.deselect.bindings"
v-on="selected.deselect.events"
/>
</span>
</slot>
<slot name="search" v-bind="scope.search">
<input v-bind="scope.search.attributes" v-on="scope.search.events">
</slot>
</div>
<div class="vs__actions" ref="actions">
<slot name="clear">
<component
ref="clearButton"
:is="scope.clear.component"
v-bind="scope.clear.bindings"
v-on="scope.clear.events"
/>
</slot>
<slot name="open-indicator" v-bind="scope.openIndicator">
<component
v-if="scope.openIndicator.shouldDisplay"
:is="scope.openIndicator.component"
v-bind="scope.openIndicator.attributes"
/>
</slot>
<slot name="spinner" v-bind="scope.spinner">
<div :class="scope.spinner.bindings.class" v-show="mutableLoading">Loading...</div>
</slot>
</div>
</div>
<transition :name="transition">
<ul
ref="dropdownMenu"
v-if="dropdownOpen"
class="vs__dropdown-menu"
role="listbox"
@mousedown.prevent="onMousedown"
@mouseup="onMouseUp"
>
<slot name="option" v-for="option in scopedOptions" v-bind="option">
<li v-bind="option.attributes" v-on="option.events">{{ option.label }}</li>
</slot>
<li v-if="!filteredOptions.length" class="vs__no-options" @mousedown.stop="">
<slot name="no-options">Sorry, no matching options.</slot>
</li>
</ul>
</transition>
</div>
</template>
<script type="text/babel">
import pointerScroll from '../mixins/pointerScroll'
import typeAheadPointer from '../mixins/typeAheadPointer'
import ajax from '../mixins/ajax'
import childComponents from './childComponents';
const spreadableOptionProperties = (option) => typeof option === 'object' ? {...option} : {};
/**
* @name VueSelect
*/
export default {
components: {...childComponents},
mixins: [pointerScroll, typeAheadPointer, ajax],
props: {
/**
* Contains the currently selected value. Very similar to a
* `value` attribute on an <input>. You can listen for changes
* using 'change' event using v-on
* @type {Object||String||null}
*/
value: {},
/**
* An object with any custom components that you'd like to overwrite
* the default implementation of in your app. The keys in this object
* will be merged with the defaults.
* @see https://vue-select.org/guide/components.html
* @type {Function}
*/
components: {
type: Object,
default: () => ({}),
},
/**
* An array of strings or objects to be used as dropdown choices.
* If you are using an array of objects, vue-select will look for
* a `label` key (ex. [{label: 'This is Foo', value: 'foo'}]). A
* custom label key can be set with the `label` prop.
* @type {Array}
*/
options: {
type: Array,
default() {
return []
},
},
/**
* Disable the entire component.
* @type {Boolean}
*/
disabled: {
type: Boolean,
default: false
},
/**
* Can the user clear the selected property.
* @type {Boolean}
*/
clearable: {
type: Boolean,
default: true
},
/**
* Enable/disable filtering the options.
* @type {Boolean}
*/
searchable: {
type: Boolean,
default: true
},
/**
* Equivalent to the `multiple` attribute on a `<select>` input.
* @type {Boolean}
*/
multiple: {
type: Boolean,
default: false
},
/**
* Equivalent to the `placeholder` attribute on an `<input>`.
* @type {String}
*/
placeholder: {
type: String,
default: ''
},
/**
* Sets a Vue transition property on the `.vs__dropdown-menu`.
* @type {String}
*/
transition: {
type: String,
default: 'vs__fade'
},
/**
* Enables/disables clearing the search text when an option is selected.
* @type {Boolean}
*/
clearSearchOnSelect: {
type: Boolean,
default: true
},
/**
* Close a dropdown when an option is chosen. Set to false to keep the dropdown
* open (useful when combined with multi-select, for example)
* @type {Boolean}
*/
closeOnSelect: {
type: Boolean,
default: true
},
/**
* Tells vue-select what key to use when generating option
* labels when each `option` is an object.
* @type {String}
*/
label: {
type: String,
default: 'label'
},
/**
* Value of the 'autocomplete' field of the input
* element.
* @type {String}
*/
autocomplete: {
type: String,
default: 'off'
},
/**
* When working with objects, the reduce
* prop allows you to transform a given
* object to only the information you
* want passed to a v-model binding
* or @input event.
*/
reduce: {
type: Function,
default: option => option,
},
/**
* Decides whether an option is selectable or not. Not selectable options
* are displayed but disabled and cannot be selected.
*
* @type {Function}
* @since 3.3.0
* @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.
*
* Label text is used for filtering comparison and
* displaying. If you only need to adjust the
* display, you should use the `option` and
* `selected-option` slots.
*
* @type {Function}
* @param {Object || String} option
* @return {String}
*/
getOptionLabel: {
type: Function,
default(option) {
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` +
'https://vue-select.org/api/props.html#getoptionlabel'
)
}
return option[this.label]
}
return option;
}
},
/**
* Callback to get an option key. If {option}
* is an object and has an {id}, returns {option.id}
* by default, otherwise tries to serialize {option}
* to JSON.
*
* The key must be unique for an option.
*
* @type {Function}
* @param {Object || String} option
* @return {String}
*/
getOptionKey: {
type: Function,
default (option) {
if (typeof option === 'object' && option.id) {
return option.id;
} else {
try {
return JSON.stringify(option);
} catch (e) {
return console.warn(
`[vue-select warn]: Could not stringify option ` +
`to generate unique key. Please provide'getOptionKey' prop ` +
`to return a unique key for each option.\n` +
'https://vue-select.org/api/props.html#getoptionkey'
);
}
}
},
},
getOptionScope: {
type: Function,
default (option, index) {
return {
...spreadableOptionProperties(option),
label: this.getOptionLabel(option),
attributes: {
key: this.getOptionKey(option),
class: {
'vs__dropdown-option': true,
'vs__dropdown-option--selected': this.isOptionSelected(option),
'vs__dropdown-option--highlight': index === this.typeAheadPointer,
'vs__dropdown-option--disabled': !this.selectable(option),
},
},
events: {
'mouseover': () => this.selectable(option) ? this.typeAheadPointer = index : null,
'mousedown': e => {
e.preventDefault();
e.stopPropagation();
return this.selectable(option) ? this.select(option) : null;
},
},
};
},
},
getSelectedOptionScope: {
type: Function,
default (option, index) {
return {
...spreadableOptionProperties(option),
label: this.getOptionLabel(option),
deselect: this.getOptionDeselectScope(option),
bindings: {
key: this.getOptionKey(option),
option: this.normalizeOptionForSlot(option),
deselect: this.deselect,
multiple: this.multiple,
class: 'vs__selected',
},
events: {
'mouseover': () => this.selectable(option) ? this.typeAheadPointer = index : null,
'mousedown': e => {
e.preventDefault();
e.stopPropagation();
return this.selectable(option) ? this.select(option) : null;
},
},
};
},
},
getOptionDeselectScope: {
type: Function,
default (option) {
return {
component: childComponents.Deselect,
bindings: {
'type': 'button',
'class': 'vs__deselect',
'aria-label': `Deselect ${this.getOptionLabel(option)}`,
'disabled': this.disabled,
'multiple': this.multiple,
'ref': 'deselectButtons'
},
events: {
'click': () => this.deselect(option),
},
};
},
},
/**
* Select the current value if selectOnTab is enabled
* @deprecated since 3.3
*/
onTab: {
type: Function,
default: function () {
if (this.selectOnTab && !this.isComposing) {
this.typeAheadSelect();
}
},
},
/**
* Enable/disable creating options from searchEl.
* @type {Boolean}
*/
taggable: {
type: Boolean,
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
* the options list.
* @type {Boolean}
*/
pushTags: {
type: Boolean,
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
},
/**
* Callback to determine if the provided option should
* match the current search text. Used to determine
* if the option should be displayed.
* @type {Function}
* @param {Object || String} option
* @param {String} label
* @param {String} search
* @return {Boolean}
*/
filterBy: {
type: Function,
default(option, label, search) {
return (label || '').toLowerCase().indexOf(search.toLowerCase()) > -1
}
},
/**
* Callback to filter results when search text
* is provided. Default implementation loops
* each option, and returns the result of
* this.filterBy.
* @type {Function}
* @param {Array} list of options
* @param {String} search text
* @param {Object} vSelect instance
* @return {Boolean}
*/
filter: {
"type": Function,
default(options, search) {
return options.filter((option) => {
let label = this.getOptionLabel(option)
if (typeof label === 'number') {
label = label.toString()
}
return this.filterBy(option, label, search)
});
}
},
/**
* User defined function for adding Options
* @type {Function}
*/
createOption: {
type: Function,
default (option) {
return (typeof this.optionList[0] === 'object') ? {[this.label]: option} : option;
},
},
/**
* When false, updating the options will not reset the selected value. Accepts
* a `boolean` or `function` that returns a `boolean`. If defined as a function,
* it will receive the params listed below.
*
* @since 3.4 - Type changed to {Boolean|Function}
*
* @type {Boolean|Function}
* @param {Array} newOptions
* @param {Array} oldOptions
* @param {Array} selectedValue
*/
resetOnOptionsChange: {
default: false,
validator: (value) => ['function', 'boolean'].includes(typeof value)
},
/**
* Disable the dropdown entirely.
* @type {Boolean}
*/
noDrop: {
type: Boolean,
default: false
},
/**
* Sets the id of the input element.
* @type {String}
* @default {null}
*/
inputId: {
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'
},
/**
* 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.
*
* Must be a valid CSS selector string.
*
* @see https://developer.mozilla.org/en-US/docs/Web/API/Document/querySelector
* @type {String}
*/
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,
}
},
data() {
return {
search: '',
open: false,
isComposing: false,
pushedTags: [],
_value: [] // Internal value managed by Vue Select if no `value` prop is passed
}
},
watch: {
/**
* Maybe reset the value
* when options change.
* Make sure selected option
* is correct.
* @return {[type]} [description]
*/
options (newOptions, oldOptions) {
let shouldReset = () => typeof this.resetOnOptionsChange === 'function'
? this.resetOnOptionsChange(newOptions, oldOptions, this.selectedValue)
: this.resetOnOptionsChange;
if (!this.taggable && shouldReset()) {
this.clearSelection();
}
if (this.value && this.isTrackingValues) {
this.setInternalValueFromOptions(this.value);
}
},
/**
* Make sure to update internal
* value if prop changes outside
*/
value(val) {
if (this.isTrackingValues) {
this.setInternalValueFromOptions(val)
}
},
/**
* Always reset the value when
* the multiple prop changes.
* @param {Boolean} isMultiple
* @return {void}
*/
multiple() {
this.clearSelection()
}
},
created() {
this.mutableLoading = this.loading;
if (typeof this.value !== "undefined" && this.isTrackingValues) {
this.setInternalValueFromOptions(this.value)
}
this.$on('option:created', this.maybePushTag)
},
methods: {
/**
* Make sure tracked value is
* one option if possible.
* @param {Object|String} value
* @return {void}
*/
setInternalValueFromOptions(value) {
if (Array.isArray(value)) {
this.$data._value = value.map(val => this.findOptionFromReducedValue(val));
} else {
this.$data._value = this.findOptionFromReducedValue(value);
}
},
/**
* Select a given option.
* @param {Object|String} option
* @return {void}
*/
select(option) {
if (!this.isOptionSelected(option)) {
if (this.taggable && !this.optionExists(option)) {
option = this.createOption(option);
this.$emit('option:created', option);
}
if (this.multiple) {
option = this.selectedValue.concat(option)
}
this.updateValue(option);
}
this.onAfterSelect(option)
},
/**
* De-select a given option.
* @param {Object|String} option
* @return {void}
*/
deselect (option) {
this.updateValue(this.selectedValue.filter(val => {
return !this.optionComparator(val, option);
}));
},
/**
* Clears the currently selected value(s)
* @return {void}
*/
clearSelection() {
this.updateValue(this.multiple ? [] : null)
},
/**
* Called from this.select after each selection.
* @param {Object|String} option
* @return {void}
*/
onAfterSelect(option) {
if (this.closeOnSelect) {
this.open = !this.open
this.searchEl.blur()
}
if (this.clearSearchOnSelect) {
this.search = ''
}
},
/**
* Accepts a selected value, updates local
* state when required, and triggers the
* input event.
*
* @emits input
* @param value
*/
updateValue (value) {
if (this.isTrackingValues) {
// Vue select has to manage value
this.$data._value = value;
}
if (value !== null) {
if (Array.isArray(value)) {
value = value.map(val => this.reduce(val));
} else {
value = this.reduce(value);
}
}
this.$emit('input', value);
},
/**
* Toggle the visibility of the dropdown menu.
* @param {Event} e
* @return {void}
*/
toggleDropdown ({target}) {
// don't react to click on deselect/clear buttons,
// they dropdown state will be set in their click handlers
const ignoredButtons = [
...(this.$refs['deselectButtons'] || []),
...([this.$refs['clearButton']] || [])
];
if (ignoredButtons.some(ref => ref.contains(target) || ref === target)) {
return;
}
if (this.open) {
this.searchEl.blur();
} else if (!this.disabled) {
this.open = true;
this.searchEl.focus();
}
},
/**
* Check if the given option is currently selected.
* @param {Object|String} option
* @return {Boolean} True when selected | False otherwise
*/
isOptionSelected(option) {
return this.selectedValue.some(value => {
return this.optionComparator(value, option)
})
},
/**
* Determine if two option objects are matching.
*
* @param value {Object}
* @param option {Object}
* @returns {boolean}
*/
optionComparator(value, option) {
if (typeof value !== 'object' && typeof option !== 'object') {
// Comparing primitives
if (value === option) {
return true
}
} else {
// Comparing objects
if (value === this.reduce(option)) {
return true
}
if ((this.getOptionLabel(value) === this.getOptionLabel(option)) || (this.getOptionLabel(value) === option)) {
return true
}
if (this.reduce(value) === this.reduce(option)) {
return true
}
}
return false;
},
/**
* Finds an option from this.options
* where a reduced value matches
* the passed in value.
*
* @param value {Object}
* @returns {*}
*/
findOptionFromReducedValue (value) {
return this.options.find(option => JSON.stringify(this.reduce(option)) === JSON.stringify(value)) || value;
},
/**
* 'Private' function to close the search options
* @emits {search:blur}
* @returns {void}
*/
closeSearchOptions(){
this.open = false
this.$emit('search:blur')
},
/**
* Delete the value on Delete keypress when there is no
* text in the search input, & there's tags to delete
* @return {this.value}
*/
maybeDeleteValue() {
if (!this.searchEl.value.length && this.selectedValue && this.clearable) {
let value = null;
if (this.multiple) {
value = [...this.selectedValue.slice(0, this.selectedValue.length - 1)]
}
this.updateValue(value)
}
},
/**
* Determine if an option exists
* within this.optionList array.
*
* @param {Object || String} option
* @return {boolean}
*/
optionExists(option) {
return this.optionList.some(opt => {
if (typeof opt === 'object' && this.getOptionLabel(opt) === option) {
return true
} else if (opt === option) {
return true
}
return false
})
},
/**
* Ensures that options are always
* passed as objects to scoped slots.
* @param option
* @return {*}
*/
normalizeOptionForSlot (option) {
return (typeof option === 'object') ? option : {[this.label]: option};
},
/**
* If push-tags is true, push the
* given option to `this.pushedTags`.
*
* @param {Object || String} option
* @return {void}
*/
maybePushTag(option) {
if (this.pushTags) {
this.pushedTags.push(option)
}
},
/**
* If there is any text in the search input, remove it.
* Otherwise, blur the search input to close the dropdown.
* @return {void}
*/
onEscape() {
if (!this.search.length) {
this.searchEl.blur()
} else {
this.search = ''
}
},
/**
* Close the dropdown on blur.
* @emits {search:blur}
* @return {void}
*/
onSearchBlur() {
if (this.mousedown && !this.searching) {
this.mousedown = false
} else {
if (this.clearSearchOnBlur) {
this.search = ''
}
this.closeSearchOptions()
return
}
// Fixed bug where no-options message could not be closed
if (this.search.length === 0 && this.options.length === 0){
this.closeSearchOptions()
return
}
},
/**
* Open the dropdown on focus.
* @emits {search:focus}
* @return {void}
*/
onSearchFocus() {
this.open = true
this.$emit('search:focus')
},
/**
* Event-Handler to help workaround IE11 (probably fixes 10 as well)
* firing a `blur` event when clicking
* the dropdown's scrollbar, causing it
* to collapse abruptly.
* @see https://github.com/sagalbot/vue-select/issues/106
* @return {void}
*/
onMousedown() {
this.mousedown = true
},
/**
* Event-Handler to help workaround IE11 (probably fixes 10 as well)
* @see https://github.com/sagalbot/vue-select/issues/106
* @return {void}
*/
onMouseUp() {
this.mousedown = false
},
/**
* Search <input> KeyBoardEvent handler.
* @param e {KeyboardEvent}
* @return {Function}
*/
onSearchKeyDown (e) {
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();
},
// 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.
* @return {boolean}
*/
isTrackingValues () {
return typeof this.value === 'undefined' || this.$options.propsData.hasOwnProperty('reduce');
},
/**
* The options that are currently selected.
* @return {Array}
*/
selectedValue () {
let value = this.value;
if (this.isTrackingValues) {
// Vue select has to manage value internally
value = this.$data._value;
}
if (value) {
return [].concat(value);
}
return [];
},
scopedValues() {
return this.selectedValue.map(option => this.getSelectedOptionScope(option));
},
/**
* The options available to be chosen
* from the dropdown, including any
* tags that have been pushed.
*
* @return {Array}
*/
optionList () {
return this.options.concat(this.pushedTags);
},
/**
* Find the search input DOM element.
* @returns {HTMLInputElement}
*/
searchEl () {
return !!this.$scopedSlots['search']
? this.$refs.selectedOptions.querySelector(this.searchInputQuerySelector)
: this.$refs.search;
},
/**
* Each key of this object is the name of a scoped slot
* within the component. The value of that key is the
* object that will be passed as the scope for the
* slot. There are a few slots that take place in
* v-for loops these slots can't be captured in
* a computed prop, so there's specific methods
* for each of those slots.
*
* @returns {Object}
*/
scope () {
return {
search: {
attributes: {
'class': 'vs__search',
'disabled': this.disabled,
'placeholder': this.searchPlaceholder,
'tabindex': this.tabindex,
'readonly': !this.searchable,
'id': this.inputId,
'aria-expanded': this.dropdownOpen,
'aria-label': 'Search for an option',
'ref': 'search',
'role': 'combobox',
'type': 'search',
'autocomplete': 'off',
'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,
},
},
clear: {
component: this.childComponents.Deselect,
bindings: {
'ref': 'clearButton',
'disabled': this.disabled,
'type': 'button',
'class': 'vs__clear',
'title': 'Clear Selection',
'style': { 'display': this.showClearButton ? 'block': 'none' }
},
events: {
'click': () => this.clearSelection(),
},
},
noOptions: {
text: 'Sorry, no options',
options: this.scopedOptions,
search: this.search,
attributes: {
class: 'vs__no-options',
},
events: {
mousedown: e => e.stopPropagation(),
},
},
spinner: {
loading: this.mutableLoading,
bindings: {
'class': 'vs__spinner',
},
},
openIndicator: {
shouldDisplay: ! this.noDrop,
component: this.childComponents.OpenIndicator,
attributes: {
'ref': 'openIndicator',
'role': 'presentation',
'class': 'vs__open-indicator',
},
},
};
},
/**
* Returns an object containing the child components
* that will be used throughout the component. The
* `component` prop can be used to overwrite the defaults.
*
* @return {Object}
*/
childComponents () {
return {
...childComponents,
...this.components
};
},
/**
* Holds the current state of the component.
* @return {Object}
*/
stateClasses() {
return {
'vs--open': this.dropdownOpen,
'vs--single': !this.multiple,
'vs--searching': this.searching && !this.noDrop,
'vs--searchable': this.searchable && !this.noDrop,
'vs--unsearchable': !this.searchable,
'vs--loading': this.mutableLoading,
'vs--disabled': this.disabled
}
},
/**
* If search text should clear on blur
* @return {Boolean} True when single and clearSearchOnSelect
*/
clearSearchOnBlur() {
return this.clearSearchOnSelect && !this.multiple
},
/**
* Return the current state of the
* search input
* @return {Boolean} True if non empty value
*/
searching() {
return !! this.search
},
/**
* Return the current state of the
* dropdown menu.
* @return {Boolean} True if open
*/
dropdownOpen() {
return this.noDrop ? false : this.open && !this.mutableLoading
},
/**
* Return the placeholder string if it's set
* & there is no value selected.
* @return {String} Placeholder text
*/
searchPlaceholder() {
if (this.isValueEmpty && this.placeholder) {
return this.placeholder;
}
},
/**
* The currently displayed options, filtered
* by the search elements value. If tagging
* true, the search text will be prepended
* if it doesn't already exist.
*
* @return {array}
*/
filteredOptions() {
const optionList = [].concat(this.optionList);
if (!this.filterable && !this.taggable) {
return optionList;
}
let options = this.search.length ? this.filter(optionList, this.search, this) : optionList;
if (this.taggable && this.search.length && !this.optionExists(this.search)) {
options.unshift(this.search)
}
return options
},
scopedOptions() {
return this.filteredOptions.map((option, index) => this.getOptionScope(option, index));
},
/**
* Check if there aren't any options selected.
* @return {Boolean}
*/
isValueEmpty() {
return this.selectedValue.length === 0;
},
/**
* Determines if the clear button should be displayed.
* @return {Boolean}
*/
showClearButton() {
return !this.multiple && this.clearable && !this.open && !this.isValueEmpty
}
},
}
</script>