mirror of
https://github.com/crater-invoice/crater.git
synced 2025-10-29 12:41:10 -04:00
build version 400
This commit is contained in:
@ -1,9 +1,8 @@
|
||||
<template>
|
||||
<div
|
||||
:tabindex="searchable ? -1 : tabindex"
|
||||
:class="{'multiselect--active': isOpen, 'multiselect--disabled': disabled, 'multiselect--above': isAbove }"
|
||||
:aria-owns="'listbox-'+id"
|
||||
class="base-select multiselect"
|
||||
:class="multiSelectStyle"
|
||||
:aria-owns="'listbox-' + id"
|
||||
role="combobox"
|
||||
@focus="activate()"
|
||||
@blur="searchable ? false : deactivate()"
|
||||
@ -13,10 +12,10 @@
|
||||
@keyup.esc="deactivate()"
|
||||
>
|
||||
<slot :toggle="toggle" name="caret">
|
||||
<div class="multiselect__select" @mousedown.prevent.stop="toggle()" />
|
||||
<div :class="multiselectSelectStyle" @mousedown.prevent.stop="toggle()" />
|
||||
</slot>
|
||||
<!-- <slot name="clear" :search="search"></slot> -->
|
||||
<div ref="tags" :class="{'in-valid': invalid}" class="multiselect__tags">
|
||||
<div ref="tags" :class="multiSelectTagsStyle">
|
||||
<slot
|
||||
:search="search"
|
||||
:remove="removeElement"
|
||||
@ -24,25 +23,41 @@
|
||||
:is-open="isOpen"
|
||||
name="selection"
|
||||
>
|
||||
<div v-show="visibleValues.length > 0" class="multiselect__tags-wrap">
|
||||
<div
|
||||
v-show="visibleValues.length > 0"
|
||||
:class="multiselectTagsWrapStyle"
|
||||
>
|
||||
<template v-for="(option, index) of visibleValues" @mousedown.prevent>
|
||||
<slot :option="option" :search="search" :remove="removeElement" name="tag">
|
||||
<span :key="index" class="multiselect__tag">
|
||||
<span v-text="getOptionLabel(option)"/>
|
||||
<i class="multiselect__tag-icon" tabindex="1" @keypress.enter.prevent="removeElement(option)" @mousedown.prevent="removeElement(option)"/>
|
||||
<slot
|
||||
:option="option"
|
||||
:search="search"
|
||||
:remove="removeElement"
|
||||
name="tag"
|
||||
>
|
||||
<span :key="index" :class="multiselectTagStyle">
|
||||
<span v-text="getOptionLabel(option)" />
|
||||
<i
|
||||
:class="multiselectTagIconStyle"
|
||||
tabindex="1"
|
||||
@keypress.enter.prevent="removeElement(option)"
|
||||
@mousedown.prevent="removeElement(option)"
|
||||
/>
|
||||
</span>
|
||||
</slot>
|
||||
</template>
|
||||
</div>
|
||||
<template v-if="internalValue && internalValue.length > limit">
|
||||
<slot name="limit">
|
||||
<strong class="multiselect__strong" v-text="limitText(internalValue.length - limit)"/>
|
||||
<strong
|
||||
:class="multiselectStrongStyle"
|
||||
v-text="limitText(internalValue.length - limit)"
|
||||
/>
|
||||
</slot>
|
||||
</template>
|
||||
</slot>
|
||||
<transition name="multiselect__loading">
|
||||
<slot name="loading">
|
||||
<div v-show="loading" class="multiselect__spinner"/>
|
||||
<div v-show="loading" :class="multiselectSpinnerStyle" />
|
||||
</slot>
|
||||
</transition>
|
||||
<input
|
||||
@ -54,8 +69,8 @@
|
||||
:value="search"
|
||||
:disabled="disabled"
|
||||
:tabindex="tabindex"
|
||||
:aria-controls="'listbox-'+id"
|
||||
:class="['multiselect__input']"
|
||||
:aria-controls="'listbox-' + id"
|
||||
:class="multiselectInputStyle"
|
||||
type="text"
|
||||
autocomplete="off"
|
||||
spellcheck="false"
|
||||
@ -67,10 +82,10 @@
|
||||
@keydown.up.prevent="pointerBackward()"
|
||||
@keypress.enter.prevent.stop.self="addPointerElement($event)"
|
||||
@keydown.delete.stop="removeLastElement()"
|
||||
>
|
||||
/>
|
||||
<span
|
||||
v-if="isSingleLabelVisible"
|
||||
class="multiselect__single"
|
||||
:class="multiselectSingleStyle"
|
||||
@mousedown.prevent="toggle"
|
||||
>
|
||||
<slot :option="singleValue" name="singleLabel">
|
||||
@ -83,16 +98,23 @@
|
||||
v-show="isOpen"
|
||||
ref="list"
|
||||
:style="{ maxHeight: optimizedHeight + 'px' }"
|
||||
class="multiselect__content-wrapper"
|
||||
:class="multiselectContentWrapperStyle"
|
||||
tabindex="-1"
|
||||
@focus="activate"
|
||||
@mousedown.prevent
|
||||
>
|
||||
<ul :style="contentStyle" :id="'listbox-'+id" class="multiselect__content" role="listbox">
|
||||
<slot name="beforeList"/>
|
||||
<ul
|
||||
:style="contentStyle"
|
||||
:id="'listbox-' + id"
|
||||
:class="multiselectContentStyle"
|
||||
role="listbox"
|
||||
>
|
||||
<slot name="beforeList" />
|
||||
<li v-if="multiple && max === internalValue.length">
|
||||
<span class="multiselect__option">
|
||||
<slot name="maxElements"> {{ $t('validation.maximum_options_error', { max: max }) }} </slot>
|
||||
<span :class="multiselectOptionStyle">
|
||||
<slot name="maxElements">
|
||||
{{ $t('validation.maximum_options_error', { max: max }) }}
|
||||
</slot>
|
||||
</span>
|
||||
</li>
|
||||
<template v-if="!max || internalValue.length < max">
|
||||
@ -100,16 +122,21 @@
|
||||
v-for="(option, index) of filteredOptions"
|
||||
:key="index"
|
||||
:id="id + '-' + index"
|
||||
:role="!(option && (option.$isLabel || option.$isDisabled)) ? 'option' : null"
|
||||
class="multiselect__element"
|
||||
:role="
|
||||
!(option && (option.$isLabel || option.$isDisabled))
|
||||
? 'option'
|
||||
: null
|
||||
"
|
||||
:class="multiselectElementStyle"
|
||||
>
|
||||
<span
|
||||
v-if="!(option && (option.$isLabel || option.$isDisabled))"
|
||||
:class="optionHighlight(index, option)"
|
||||
:data-select="option && option.isTag ? tagPlaceholder : selectLabelText"
|
||||
:data-select="
|
||||
option && option.isTag ? tagPlaceholder : selectLabelText
|
||||
"
|
||||
:data-selected="selectedLabelText"
|
||||
:data-deselect="deselectLabelText"
|
||||
class="multiselect__option"
|
||||
@click.stop="select(option)"
|
||||
@mouseenter.self="pointerSet(index)"
|
||||
>
|
||||
@ -122,7 +149,6 @@
|
||||
:data-select="groupSelect && selectGroupLabelText"
|
||||
:data-deselect="groupSelect && deselectGroupLabelText"
|
||||
:class="groupHighlight(index, option)"
|
||||
class="multiselect__option"
|
||||
@mouseenter.self="groupSelect && pointerSet(index)"
|
||||
@mousedown.prevent="selectGroup(option)"
|
||||
>
|
||||
@ -132,24 +158,50 @@
|
||||
</span>
|
||||
</li>
|
||||
</template>
|
||||
<li v-if="showNoOptions && (options.length === 0 && !search && !loading)">
|
||||
<span class="multiselect__option">
|
||||
<li
|
||||
v-if="showNoOptions && (options.length === 0 && !search && !loading)"
|
||||
>
|
||||
<span :class="multiselectOptionStyle">
|
||||
<slot name="noOptions">{{ $t('general.list_is_empty') }}</slot>
|
||||
</span>
|
||||
</li>
|
||||
</ul>
|
||||
<slot name="afterList"/>
|
||||
<slot name="afterList" />
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import CraterTheme from '../theme/index'
|
||||
import multiselectMixin from './multiselectMixin'
|
||||
import pointerMixin from './pointerMixin'
|
||||
|
||||
const {
|
||||
activeBaseSelectContainer,
|
||||
disabledBaseSelectContainer,
|
||||
baseSelectContainer,
|
||||
multiSelect,
|
||||
disabledMultiSelect,
|
||||
multiSelectTags,
|
||||
multiSelectTagsInvalid,
|
||||
multiSelectTagsDefaultColor,
|
||||
disabledMultiSelectTags,
|
||||
multiselectTagsWrap,
|
||||
multiselectTag,
|
||||
multiselectTagIcon,
|
||||
multiselectStrong,
|
||||
multiselectSpinner,
|
||||
multiselectInput,
|
||||
multiselectSingle,
|
||||
multiselectContentWrapper,
|
||||
multiselectContent,
|
||||
multiselectOption,
|
||||
multiselectElement,
|
||||
} = CraterTheme.BaseSelect
|
||||
|
||||
export default {
|
||||
name: 'vue-multiselect',
|
||||
name: 'VueMultiselect',
|
||||
mixins: [multiselectMixin, pointerMixin],
|
||||
props: {
|
||||
/**
|
||||
@ -159,7 +211,7 @@ export default {
|
||||
*/
|
||||
name: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an option
|
||||
@ -168,7 +220,7 @@ export default {
|
||||
*/
|
||||
selectLabel: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an option
|
||||
@ -177,7 +229,7 @@ export default {
|
||||
*/
|
||||
selectGroupLabel: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* String to show next to selected option
|
||||
@ -186,7 +238,7 @@ export default {
|
||||
*/
|
||||
selectedLabel: {
|
||||
type: String,
|
||||
default: 'Selected'
|
||||
default: 'Selected',
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an already selected option
|
||||
@ -195,7 +247,7 @@ export default {
|
||||
*/
|
||||
deselectLabel: {
|
||||
type: String,
|
||||
default: 'Press enter to remove'
|
||||
default: 'Press enter to remove',
|
||||
},
|
||||
/**
|
||||
* String to show when pointing to an already selected option
|
||||
@ -204,7 +256,7 @@ export default {
|
||||
*/
|
||||
deselectGroupLabel: {
|
||||
type: String,
|
||||
default: 'Press enter to deselect group'
|
||||
default: 'Press enter to deselect group',
|
||||
},
|
||||
/**
|
||||
* Decide whether to show pointer labels
|
||||
@ -213,7 +265,7 @@ export default {
|
||||
*/
|
||||
showLabels: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Limit the display of selected options. The rest will be hidden within the limitText string.
|
||||
@ -222,7 +274,7 @@ export default {
|
||||
*/
|
||||
limit: {
|
||||
type: Number,
|
||||
default: 99999
|
||||
default: 99999,
|
||||
},
|
||||
/**
|
||||
* Sets maxHeight style value of the dropdown
|
||||
@ -231,7 +283,7 @@ export default {
|
||||
*/
|
||||
maxHeight: {
|
||||
type: Number,
|
||||
default: 300
|
||||
default: 300,
|
||||
},
|
||||
/**
|
||||
* Function that process the message shown when selected
|
||||
@ -242,7 +294,7 @@ export default {
|
||||
*/
|
||||
limitText: {
|
||||
type: Function,
|
||||
default: count => `and ${count} more`
|
||||
default: (count) => `and ${count} more`,
|
||||
},
|
||||
/**
|
||||
* Set true to trigger the loading spinner.
|
||||
@ -251,7 +303,7 @@ export default {
|
||||
*/
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Disables the multiselect if true.
|
||||
@ -260,7 +312,7 @@ export default {
|
||||
*/
|
||||
disabled: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Fixed opening direction
|
||||
@ -269,7 +321,7 @@ export default {
|
||||
*/
|
||||
openDirection: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* Shows slot with message about empty options
|
||||
@ -278,54 +330,54 @@ export default {
|
||||
*/
|
||||
showNoOptions: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
showNoResults: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
tabindex: {
|
||||
type: Number,
|
||||
default: 0
|
||||
default: 0,
|
||||
},
|
||||
invalid: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
isSingleLabelVisible () {
|
||||
isSingleLabelVisible() {
|
||||
return (
|
||||
(this.singleValue || this.singleValue === 0) &&
|
||||
(!this.isOpen || !this.searchable) &&
|
||||
!this.visibleValues.length
|
||||
)
|
||||
},
|
||||
isPlaceholderVisible () {
|
||||
isPlaceholderVisible() {
|
||||
return !this.internalValue.length && (!this.searchable || !this.isOpen)
|
||||
},
|
||||
visibleValues () {
|
||||
visibleValues() {
|
||||
return this.multiple ? this.internalValue.slice(0, this.limit) : []
|
||||
},
|
||||
singleValue () {
|
||||
singleValue() {
|
||||
return this.internalValue[0]
|
||||
},
|
||||
deselectLabelText () {
|
||||
deselectLabelText() {
|
||||
return this.showLabels ? this.deselectLabel : ''
|
||||
},
|
||||
deselectGroupLabelText () {
|
||||
deselectGroupLabelText() {
|
||||
return this.showLabels ? this.deselectGroupLabel : ''
|
||||
},
|
||||
selectLabelText () {
|
||||
selectLabelText() {
|
||||
return this.showLabels ? this.selectLabel : ''
|
||||
},
|
||||
selectGroupLabelText () {
|
||||
selectGroupLabelText() {
|
||||
return this.showLabels ? this.selectGroupLabel : ''
|
||||
},
|
||||
selectedLabelText () {
|
||||
selectedLabelText() {
|
||||
return this.showLabels ? this.selectedLabel : ''
|
||||
},
|
||||
inputStyle () {
|
||||
inputStyle() {
|
||||
if (
|
||||
this.searchable ||
|
||||
(this.multiple && this.value && this.value.length)
|
||||
@ -334,15 +386,17 @@ export default {
|
||||
|
||||
return this.isOpen
|
||||
? { width: '100%' }
|
||||
: ((this.value) ? { width: '0', position: 'absolute', padding: '0' } : '')
|
||||
: this.value
|
||||
? { width: '0', position: 'absolute', padding: '0' }
|
||||
: ''
|
||||
}
|
||||
},
|
||||
contentStyle () {
|
||||
contentStyle() {
|
||||
return this.options.length
|
||||
? { display: 'inline-block' }
|
||||
: { display: 'block' }
|
||||
},
|
||||
isAbove () {
|
||||
isAbove() {
|
||||
if (this.openDirection === 'above' || this.openDirection === 'top') {
|
||||
return true
|
||||
} else if (
|
||||
@ -354,7 +408,7 @@ export default {
|
||||
return this.preferredOpenDirection === 'above'
|
||||
}
|
||||
},
|
||||
showSearchInput () {
|
||||
showSearchInput() {
|
||||
return (
|
||||
this.searchable &&
|
||||
(this.hasSingleSelectedSlot &&
|
||||
@ -362,7 +416,466 @@ export default {
|
||||
? this.isOpen
|
||||
: true)
|
||||
)
|
||||
}
|
||||
}
|
||||
},
|
||||
multiSelectStyle() {
|
||||
let style = ['multiselect--active', baseSelectContainer]
|
||||
if (this.isOpen) {
|
||||
style.push(activeBaseSelectContainer)
|
||||
}
|
||||
if (this.disabled) {
|
||||
style.push(disabledBaseSelectContainer)
|
||||
}
|
||||
if (this.isAbove) {
|
||||
style.push('multiselect--above')
|
||||
}
|
||||
return style
|
||||
},
|
||||
multiselectSelectStyle() {
|
||||
let style = [multiSelect]
|
||||
|
||||
if (this.disabled) {
|
||||
style.push(disabledMultiSelect)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
multiSelectTagsStyle() {
|
||||
let style = [multiSelectTags]
|
||||
|
||||
if (this.invalid) {
|
||||
style.push(multiSelectTagsInvalid)
|
||||
} else {
|
||||
style.push(multiSelectTagsDefaultColor)
|
||||
}
|
||||
|
||||
if (this.disabled) {
|
||||
style.push(disabledMultiSelectTags)
|
||||
}
|
||||
|
||||
return style
|
||||
},
|
||||
multiselectTagsWrapStyle() {
|
||||
return [multiselectTagsWrap]
|
||||
},
|
||||
multiselectTagStyle() {
|
||||
return [multiselectTag]
|
||||
},
|
||||
multiselectTagIconStyle() {
|
||||
return [multiselectTagIcon]
|
||||
},
|
||||
multiselectStrongStyle() {
|
||||
return [multiselectStrong]
|
||||
},
|
||||
multiselectSpinnerStyle() {
|
||||
return [multiselectSpinner]
|
||||
},
|
||||
multiselectInputStyle() {
|
||||
return [multiselectInput]
|
||||
},
|
||||
multiselectSingleStyle() {
|
||||
return [multiselectSingle]
|
||||
},
|
||||
multiselectContentWrapperStyle() {
|
||||
return [multiselectContentWrapper]
|
||||
},
|
||||
multiselectContentStyle() {
|
||||
return [multiselectContent]
|
||||
},
|
||||
multiselectOptionStyle() {
|
||||
return [multiselectOption]
|
||||
},
|
||||
multiselectElementStyle() {
|
||||
return [multiselectElement]
|
||||
},
|
||||
},
|
||||
}
|
||||
</script>
|
||||
<style lang="scss">
|
||||
fieldset[disabled] .multiselect {
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
min-height: 40px;
|
||||
}
|
||||
|
||||
.multiselect__spinner {
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
}
|
||||
|
||||
.multiselect__spinner:before,
|
||||
.multiselect__spinner:after {
|
||||
position: absolute;
|
||||
content: '';
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
margin: -8px 0 0 -8px;
|
||||
z-index: 5;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
border-radius: 100%;
|
||||
border-color: #41b883 transparent transparent;
|
||||
border-style: solid;
|
||||
border-width: 2px;
|
||||
box-shadow: 0 0 0 1px transparent;
|
||||
}
|
||||
|
||||
.multiselect__spinner:before {
|
||||
animation: spinning 2.4s cubic-bezier(0.41, 0.26, 0.2, 0.62);
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.multiselect__spinner:after {
|
||||
animation: spinning 2.4s cubic-bezier(0.51, 0.09, 0.21, 0.8);
|
||||
animation-iteration-count: infinite;
|
||||
}
|
||||
|
||||
.multiselect__loading-enter-active,
|
||||
.multiselect__loading-leave-active {
|
||||
transition: opacity 0.4s ease-in-out;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.multiselect__loading-enter,
|
||||
.multiselect__loading-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.multiselect,
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
font-family: inherit;
|
||||
// font-size: 14px;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
box-sizing: content-box;
|
||||
display: block;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 40px;
|
||||
text-align: left;
|
||||
color: #35495e;
|
||||
}
|
||||
|
||||
.multiselect * {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.multiselect:focus {
|
||||
border: 1px solid #817ae3 !important;
|
||||
}
|
||||
|
||||
.multiselect--disabled {
|
||||
pointer-events: none;
|
||||
opacity: 0.6;
|
||||
}
|
||||
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__current,
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__input,
|
||||
.multiselect--active:not(.multiselect--above) .multiselect__tags {
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.multiselect--active .multiselect__select {
|
||||
transform: rotateZ(180deg);
|
||||
}
|
||||
.multiselect--above.multiselect--active .multiselect__current,
|
||||
.multiselect--above.multiselect--active .multiselect__input,
|
||||
.multiselect--above.multiselect--active .multiselect__tags {
|
||||
border-top-left-radius: 0;
|
||||
border-top-right-radius: 0;
|
||||
}
|
||||
|
||||
.multiselect__input,
|
||||
.multiselect__single {
|
||||
min-height: 20px;
|
||||
transition: border 0.1s ease;
|
||||
}
|
||||
|
||||
.multiselect__input::placeholder {
|
||||
color: #b9c1d1;
|
||||
}
|
||||
|
||||
.multiselect__tag ~ .multiselect__input,
|
||||
.multiselect__tag ~ .multiselect__single {
|
||||
width: auto;
|
||||
}
|
||||
.multiselect__input:hover,
|
||||
.multiselect__single:hover {
|
||||
border-color: #cfcfcf;
|
||||
}
|
||||
.multiselect__input:focus,
|
||||
.multiselect__single:focus {
|
||||
border-color: #a8a8a8;
|
||||
outline: none;
|
||||
}
|
||||
.multiselect__tag {
|
||||
background: #41b883;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.multiselect__tag-icon {
|
||||
font-style: initial;
|
||||
}
|
||||
.multiselect__tag-icon:after {
|
||||
content: '×';
|
||||
color: #266d4d;
|
||||
font-size: 14px;
|
||||
}
|
||||
.multiselect__tag-icon:focus,
|
||||
.multiselect__tag-icon:hover {
|
||||
background: #369a6e;
|
||||
}
|
||||
.multiselect__tag-icon:focus:after,
|
||||
.multiselect__tag-icon:hover:after {
|
||||
color: white;
|
||||
}
|
||||
.multiselect__current {
|
||||
line-height: 16px;
|
||||
min-height: 40px;
|
||||
box-sizing: border-box;
|
||||
display: block;
|
||||
overflow: hidden;
|
||||
padding: 8px 12px 0;
|
||||
padding-right: 30px;
|
||||
white-space: nowrap;
|
||||
margin: 0;
|
||||
text-decoration: none;
|
||||
border-radius: 5px;
|
||||
border: 1px solid #ebf1fa;
|
||||
cursor: pointer;
|
||||
}
|
||||
.multiselect__select {
|
||||
right: 1px;
|
||||
top: 1px;
|
||||
transition: transform 0.2s;
|
||||
}
|
||||
.multiselect__select:before {
|
||||
position: relative;
|
||||
right: 0;
|
||||
top: 65%;
|
||||
color: #a5acc1;
|
||||
margin-top: 4px;
|
||||
border-style: solid;
|
||||
border-width: 5px 5px 0 5px;
|
||||
border-color: #a5acc1 transparent transparent transparent;
|
||||
content: '';
|
||||
}
|
||||
.multiselect__placeholder {
|
||||
color: #b9c1d1;
|
||||
display: inline-block;
|
||||
margin-bottom: 10px;
|
||||
padding-top: 2px;
|
||||
}
|
||||
.multiselect--active .multiselect__placeholder {
|
||||
display: none;
|
||||
}
|
||||
.multiselect__content-wrapper {
|
||||
max-height: 240px;
|
||||
-webkit-overflow-scrolling: touch;
|
||||
}
|
||||
.multiselect--above .multiselect__content-wrapper {
|
||||
bottom: 100%;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
border-top-left-radius: 5px;
|
||||
border-top-right-radius: 5px;
|
||||
border-bottom: none;
|
||||
border-top: 1px solid #e8e8e8;
|
||||
}
|
||||
.multiselect__content::webkit-scrollbar {
|
||||
display: none;
|
||||
}
|
||||
.multiselect__option {
|
||||
min-height: 40px;
|
||||
}
|
||||
.multiselect__option:after {
|
||||
top: 0;
|
||||
right: 0;
|
||||
position: absolute;
|
||||
line-height: 40px;
|
||||
padding-right: 12px;
|
||||
padding-left: 20px;
|
||||
font-size: 13px;
|
||||
}
|
||||
.multiselect__option--highlight {
|
||||
background: #41b883;
|
||||
outline: none;
|
||||
color: white;
|
||||
}
|
||||
.multiselect__option--highlight:after {
|
||||
content: attr(data-select);
|
||||
background: #41b883;
|
||||
color: white;
|
||||
}
|
||||
.multiselect__option--selected {
|
||||
background: #f3f3f3;
|
||||
color: #35495e;
|
||||
font-weight: bold;
|
||||
}
|
||||
.multiselect__option--selected:after {
|
||||
content: attr(data-selected);
|
||||
color: silver;
|
||||
}
|
||||
.multiselect__option--selected.multiselect__option--highlight {
|
||||
background: #ff6a6a;
|
||||
color: #fff;
|
||||
}
|
||||
.multiselect__option--selected.multiselect__option--highlight:after {
|
||||
background: #ff6a6a;
|
||||
content: attr(data-deselect);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect--disabled .multiselect__current,
|
||||
.multiselect--disabled .multiselect__select {
|
||||
background: #ebf1fa;
|
||||
color: #b9c1d1;
|
||||
}
|
||||
|
||||
.multiselect--disabled .multiselect__input,
|
||||
.multiselect--disabled .multiselect__single {
|
||||
background: #ebf1fa;
|
||||
color: #b9c1d1;
|
||||
}
|
||||
|
||||
.multiselect__option--disabled {
|
||||
background: transparent !important;
|
||||
color: #dddddd !important;
|
||||
cursor: text;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.multiselect__option--group {
|
||||
background: #ededed;
|
||||
color: #35495e;
|
||||
}
|
||||
|
||||
.multiselect__option--group.multiselect__option--highlight {
|
||||
background: #35495e;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect__option--group.multiselect__option--highlight:after {
|
||||
background: #35495e;
|
||||
}
|
||||
|
||||
.multiselect__option--disabled.multiselect__option--highlight {
|
||||
background: #dedede;
|
||||
}
|
||||
|
||||
.multiselect__option--group-selected.multiselect__option--highlight {
|
||||
background: #ff6a6a;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect__option--group-selected.multiselect__option--highlight:after {
|
||||
background: #ff6a6a;
|
||||
content: attr(data-deselect);
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.multiselect-enter-active,
|
||||
.multiselect-leave-active {
|
||||
transition: all 0.15s ease;
|
||||
}
|
||||
|
||||
.multiselect-enter,
|
||||
.multiselect-leave-active {
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__select {
|
||||
right: auto;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__tags {
|
||||
padding: 8px 8px 0px 40px;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__content {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__option:after {
|
||||
right: auto;
|
||||
left: 0;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__clear {
|
||||
right: auto;
|
||||
left: 12px;
|
||||
}
|
||||
|
||||
*[dir='rtl'] .multiselect__spinner {
|
||||
right: auto;
|
||||
left: 1px;
|
||||
}
|
||||
|
||||
@keyframes spinning {
|
||||
from {
|
||||
transform: rotate(0);
|
||||
}
|
||||
to {
|
||||
transform: rotate(2turn);
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect {
|
||||
.multiselect__option--highlight {
|
||||
background: #5851d8;
|
||||
color: #040405;
|
||||
font-weight: normal !important;
|
||||
|
||||
&.multiselect__option--selected {
|
||||
background: #ebf1fa;
|
||||
color: #040405;
|
||||
font-size: 1rem;
|
||||
font-weight: normal !important;
|
||||
|
||||
&::after {
|
||||
background: #040405;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
&::after {
|
||||
background: #040405;
|
||||
color: #fff;
|
||||
}
|
||||
}
|
||||
|
||||
.multiselect__option--selected {
|
||||
font-weight: normal !important;
|
||||
background: #ebf1fa;
|
||||
}
|
||||
|
||||
.multiselect__tags-wrap .multiselect__tag {
|
||||
background: #5851d8;
|
||||
color: #040405;
|
||||
|
||||
.multiselect__tag-icon {
|
||||
&:hover {
|
||||
background: #5851d8;
|
||||
}
|
||||
|
||||
&::after {
|
||||
color: #040405;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&.error {
|
||||
border: 1px solid #fb7178;
|
||||
border-radius: 5px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -1,14 +1,14 @@
|
||||
function isEmpty (opt) {
|
||||
function isEmpty(opt) {
|
||||
if (opt === 0) return false
|
||||
if (Array.isArray(opt) && opt.length === 0) return true
|
||||
return !opt
|
||||
}
|
||||
|
||||
function not (fun) {
|
||||
function not(fun) {
|
||||
return (...params) => !fun(...params)
|
||||
}
|
||||
|
||||
function includes (str, query) {
|
||||
function includes(str, query) {
|
||||
/* istanbul ignore else */
|
||||
if (str === undefined) str = 'undefined'
|
||||
if (str === null) str = 'null'
|
||||
@ -17,22 +17,24 @@ function includes (str, query) {
|
||||
return text.indexOf(query.trim()) !== -1
|
||||
}
|
||||
|
||||
function filterOptions (options, search, label, customLabel) {
|
||||
return options.filter(option => includes(customLabel(option, label), search))
|
||||
function filterOptions(options, search, label, customLabel) {
|
||||
return options.filter((option) =>
|
||||
includes(customLabel(option, label), search)
|
||||
)
|
||||
}
|
||||
|
||||
function stripGroups (options) {
|
||||
return options.filter(option => !option.$isLabel)
|
||||
function stripGroups(options) {
|
||||
return options.filter((option) => !option.$isLabel)
|
||||
}
|
||||
|
||||
function flattenOptions (values, label) {
|
||||
function flattenOptions(values, label) {
|
||||
return (options) =>
|
||||
options.reduce((prev, curr) => {
|
||||
/* istanbul ignore else */
|
||||
if (curr[values] && curr[values].length) {
|
||||
prev.push({
|
||||
$groupLabel: curr[label],
|
||||
$isLabel: true
|
||||
$isLabel: true,
|
||||
})
|
||||
return prev.concat(curr[values])
|
||||
}
|
||||
@ -40,40 +42,47 @@ function flattenOptions (values, label) {
|
||||
}, [])
|
||||
}
|
||||
|
||||
function filterGroups (search, label, values, groupLabel, customLabel) {
|
||||
function filterGroups(search, label, values, groupLabel, customLabel) {
|
||||
return (groups) =>
|
||||
groups.map(group => {
|
||||
groups.map((group) => {
|
||||
/* istanbul ignore else */
|
||||
if (!group[values]) {
|
||||
console.warn(`Options passed to vue-multiselect do not contain groups, despite the config.`)
|
||||
console.warn(
|
||||
`Options passed to vue-multiselect do not contain groups, despite the config.`
|
||||
)
|
||||
return []
|
||||
}
|
||||
const groupOptions = filterOptions(group[values], search, label, customLabel)
|
||||
const groupOptions = filterOptions(
|
||||
group[values],
|
||||
search,
|
||||
label,
|
||||
customLabel
|
||||
)
|
||||
|
||||
return groupOptions.length
|
||||
? {
|
||||
[groupLabel]: group[groupLabel],
|
||||
[values]: groupOptions
|
||||
}
|
||||
[groupLabel]: group[groupLabel],
|
||||
[values]: groupOptions,
|
||||
}
|
||||
: []
|
||||
})
|
||||
}
|
||||
|
||||
const flow = (...fns) => x => fns.reduce((v, f) => f(v), x)
|
||||
const flow = (...fns) => (x) => fns.reduce((v, f) => f(v), x)
|
||||
|
||||
export default {
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
search: '',
|
||||
isOpen: false,
|
||||
preferredOpenDirection: 'below',
|
||||
optimizedHeight: this.maxHeight
|
||||
optimizedHeight: this.maxHeight,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
initialSearch: {
|
||||
type: String,
|
||||
default: ''
|
||||
default: '',
|
||||
},
|
||||
/**
|
||||
* Decide whether to filter the results based on search query.
|
||||
@ -82,7 +91,7 @@ export default {
|
||||
*/
|
||||
internalSearch: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Array of available options: Objects, Strings or Integers.
|
||||
@ -92,7 +101,7 @@ export default {
|
||||
*/
|
||||
options: {
|
||||
type: Array,
|
||||
required: true
|
||||
required: true,
|
||||
},
|
||||
/**
|
||||
* Equivalent to the `multiple` attribute on a `<select>` input.
|
||||
@ -101,7 +110,7 @@ export default {
|
||||
*/
|
||||
multiple: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Presets the selected options value.
|
||||
@ -109,9 +118,9 @@ export default {
|
||||
*/
|
||||
value: {
|
||||
type: null,
|
||||
default () {
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Key to compare objects
|
||||
@ -119,7 +128,7 @@ export default {
|
||||
* @type {String}
|
||||
*/
|
||||
trackBy: {
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
/**
|
||||
* Label to look for in option Object
|
||||
@ -127,7 +136,7 @@ export default {
|
||||
* @type {String}
|
||||
*/
|
||||
label: {
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
/**
|
||||
* Enable/disable search in options
|
||||
@ -136,7 +145,7 @@ export default {
|
||||
*/
|
||||
searchable: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Clear the search input after `)
|
||||
@ -145,7 +154,7 @@ export default {
|
||||
*/
|
||||
clearOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Hide already selected options
|
||||
@ -154,7 +163,7 @@ export default {
|
||||
*/
|
||||
hideSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Equivalent to the `placeholder` attribute on a `<select>` input.
|
||||
@ -163,7 +172,7 @@ export default {
|
||||
*/
|
||||
placeholder: {
|
||||
type: String,
|
||||
default: 'Select option'
|
||||
default: 'Select option',
|
||||
},
|
||||
/**
|
||||
* Allow to remove all selected values
|
||||
@ -172,7 +181,7 @@ export default {
|
||||
*/
|
||||
allowEmpty: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Reset this.internalValue, this.search after this.internalValue changes.
|
||||
@ -182,7 +191,7 @@ export default {
|
||||
*/
|
||||
resetAfter: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Enable/disable closing after selecting an option
|
||||
@ -191,7 +200,7 @@ export default {
|
||||
*/
|
||||
closeOnSelect: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
/**
|
||||
* Function to interpolate the custom label
|
||||
@ -200,10 +209,10 @@ export default {
|
||||
*/
|
||||
customLabel: {
|
||||
type: Function,
|
||||
default (option, label) {
|
||||
default(option, label) {
|
||||
if (isEmpty(option)) return ''
|
||||
return label ? option[label] : option
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Disable / Enable tagging
|
||||
@ -212,16 +221,16 @@ export default {
|
||||
*/
|
||||
taggable: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* String to show when highlighting a potential tag
|
||||
* @default 'Press enter to create a tag'
|
||||
* @type {String}
|
||||
*/
|
||||
*/
|
||||
tagPlaceholder: {
|
||||
type: String,
|
||||
default: 'Press enter to create a tag'
|
||||
default: 'Press enter to create a tag',
|
||||
},
|
||||
/**
|
||||
* By default new tags will appear above the search results.
|
||||
@ -229,56 +238,56 @@ export default {
|
||||
* and will proritize the search results
|
||||
* @default 'top'
|
||||
* @type {String}
|
||||
*/
|
||||
*/
|
||||
tagPosition: {
|
||||
type: String,
|
||||
default: 'top'
|
||||
default: 'top',
|
||||
},
|
||||
/**
|
||||
* Number of allowed selected options. No limit if 0.
|
||||
* @default 0
|
||||
* @type {Number}
|
||||
*/
|
||||
*/
|
||||
max: {
|
||||
type: [Number, Boolean],
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Will be passed with all events as second param.
|
||||
* Useful for identifying events origin.
|
||||
* @default null
|
||||
* @type {String|Integer}
|
||||
*/
|
||||
*/
|
||||
id: {
|
||||
default: null
|
||||
default: null,
|
||||
},
|
||||
/**
|
||||
* Limits the options displayed in the dropdown
|
||||
* to the first X options.
|
||||
* @default 1000
|
||||
* @type {Integer}
|
||||
*/
|
||||
*/
|
||||
optionsLimit: {
|
||||
type: Number,
|
||||
default: 1000
|
||||
default: 1000,
|
||||
},
|
||||
/**
|
||||
* Name of the property containing
|
||||
* the group values
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
*/
|
||||
groupValues: {
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
/**
|
||||
* Name of the property containing
|
||||
* the group label
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
*/
|
||||
groupLabel: {
|
||||
type: String
|
||||
type: String,
|
||||
},
|
||||
/**
|
||||
* Allow to select all group values
|
||||
@ -288,43 +297,45 @@ export default {
|
||||
*/
|
||||
groupSelect: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Array of keyboard keys to block
|
||||
* when selecting
|
||||
* @default 1000
|
||||
* @type {String}
|
||||
*/
|
||||
*/
|
||||
blockKeys: {
|
||||
type: Array,
|
||||
default () {
|
||||
default() {
|
||||
return []
|
||||
}
|
||||
},
|
||||
},
|
||||
/**
|
||||
* Prevent from wiping up the search value
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
*/
|
||||
preserveSearch: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
default: false,
|
||||
},
|
||||
/**
|
||||
* Select 1st options if value is empty
|
||||
* @default false
|
||||
* @type {Boolean}
|
||||
*/
|
||||
*/
|
||||
preselectFirst: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
default: false,
|
||||
},
|
||||
},
|
||||
mounted () {
|
||||
mounted() {
|
||||
/* istanbul ignore else */
|
||||
if (!this.multiple && this.max) {
|
||||
console.warn('[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.')
|
||||
console.warn(
|
||||
'[Vue-Multiselect warn]: Max prop should not be used when prop Multiple equals false.'
|
||||
)
|
||||
}
|
||||
if (
|
||||
this.preselectFirst &&
|
||||
@ -339,12 +350,14 @@ export default {
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
internalValue () {
|
||||
internalValue() {
|
||||
return this.value || this.value === 0
|
||||
? Array.isArray(this.value) ? this.value : [this.value]
|
||||
? Array.isArray(this.value)
|
||||
? this.value
|
||||
: [this.value]
|
||||
: []
|
||||
},
|
||||
filteredOptions () {
|
||||
filteredOptions() {
|
||||
const search = this.search || ''
|
||||
const normalizedSearch = search.toLowerCase().trim()
|
||||
|
||||
@ -354,9 +367,16 @@ export default {
|
||||
if (this.internalSearch) {
|
||||
options = this.groupValues
|
||||
? this.filterAndFlat(options, normalizedSearch, this.label)
|
||||
: filterOptions(options, normalizedSearch, this.label, this.customLabel)
|
||||
: filterOptions(
|
||||
options,
|
||||
normalizedSearch,
|
||||
this.label,
|
||||
this.customLabel
|
||||
)
|
||||
} else {
|
||||
options = this.groupValues ? flattenOptions(this.groupValues, this.groupLabel)(options) : options
|
||||
options = this.groupValues
|
||||
? flattenOptions(this.groupValues, this.groupLabel)(options)
|
||||
: options
|
||||
}
|
||||
|
||||
options = this.hideSelected
|
||||
@ -364,7 +384,11 @@ export default {
|
||||
: options
|
||||
|
||||
/* istanbul ignore else */
|
||||
if (this.taggable && normalizedSearch.length && !this.isExistingOption(normalizedSearch)) {
|
||||
if (
|
||||
this.taggable &&
|
||||
normalizedSearch.length &&
|
||||
!this.isExistingOption(normalizedSearch)
|
||||
) {
|
||||
if (this.tagPosition === 'bottom') {
|
||||
options.push({ isTag: true, label: search })
|
||||
} else {
|
||||
@ -374,57 +398,71 @@ export default {
|
||||
|
||||
return options.slice(0, this.optionsLimit)
|
||||
},
|
||||
valueKeys () {
|
||||
valueKeys() {
|
||||
if (this.trackBy) {
|
||||
return this.internalValue.map(element => element[this.trackBy])
|
||||
return this.internalValue.map((element) => element[this.trackBy])
|
||||
} else {
|
||||
return this.internalValue
|
||||
}
|
||||
},
|
||||
optionKeys () {
|
||||
const options = this.groupValues ? this.flatAndStrip(this.options) : this.options
|
||||
return options.map(element => this.customLabel(element, this.label).toString().toLowerCase())
|
||||
optionKeys() {
|
||||
const options = this.groupValues
|
||||
? this.flatAndStrip(this.options)
|
||||
: this.options
|
||||
return options.map((element) =>
|
||||
this.customLabel(element, this.label).toString().toLowerCase()
|
||||
)
|
||||
},
|
||||
currentOptionLabel () {
|
||||
currentOptionLabel() {
|
||||
return this.multiple
|
||||
? this.searchable ? '' : this.placeholder
|
||||
? this.searchable
|
||||
? ''
|
||||
: this.placeholder
|
||||
: this.internalValue.length
|
||||
? this.getOptionLabel(this.internalValue[0])
|
||||
: this.searchable ? '' : this.placeholder
|
||||
}
|
||||
? this.getOptionLabel(this.internalValue[0])
|
||||
: this.searchable
|
||||
? ''
|
||||
: this.placeholder
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
internalValue () {
|
||||
internalValue() {
|
||||
/* istanbul ignore else */
|
||||
if (this.resetAfter && this.internalValue.length) {
|
||||
this.search = ''
|
||||
this.$emit('input', this.multiple ? [] : null)
|
||||
}
|
||||
},
|
||||
search () {
|
||||
search() {
|
||||
this.$emit('search-change', this.search, this.id)
|
||||
}
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
/**
|
||||
* Returns the internalValue in a way it can be emited to the parent
|
||||
* @returns {Object||Array||String||Integer}
|
||||
*/
|
||||
getValue () {
|
||||
getValue() {
|
||||
return this.multiple
|
||||
? this.internalValue
|
||||
: this.internalValue.length === 0
|
||||
? null
|
||||
: this.internalValue[0]
|
||||
? null
|
||||
: this.internalValue[0]
|
||||
},
|
||||
/**
|
||||
* Filters and then flattens the options list
|
||||
* @param {Array}
|
||||
* @returns {Array} returns a filtered and flat options list
|
||||
*/
|
||||
filterAndFlat (options, search, label) {
|
||||
filterAndFlat(options, search, label) {
|
||||
return flow(
|
||||
filterGroups(search, label, this.groupValues, this.groupLabel, this.customLabel),
|
||||
filterGroups(
|
||||
search,
|
||||
label,
|
||||
this.groupValues,
|
||||
this.groupLabel,
|
||||
this.customLabel
|
||||
),
|
||||
flattenOptions(this.groupValues, this.groupLabel)
|
||||
)(options)
|
||||
},
|
||||
@ -433,7 +471,7 @@ export default {
|
||||
* @param {Array}
|
||||
* @returns {Array} returns a flat options list without group labels
|
||||
*/
|
||||
flatAndStrip (options) {
|
||||
flatAndStrip(options) {
|
||||
return flow(
|
||||
flattenOptions(this.groupValues, this.groupLabel),
|
||||
stripGroups
|
||||
@ -443,7 +481,7 @@ export default {
|
||||
* Updates the search value
|
||||
* @param {String}
|
||||
*/
|
||||
updateSearch (query) {
|
||||
updateSearch(query) {
|
||||
this.search = query
|
||||
this.$emit('value', this.search)
|
||||
},
|
||||
@ -453,10 +491,8 @@ export default {
|
||||
* @param {String}
|
||||
* @returns {Boolean} returns true if element is available
|
||||
*/
|
||||
isExistingOption (query) {
|
||||
return !this.options
|
||||
? false
|
||||
: this.optionKeys.indexOf(query) > -1
|
||||
isExistingOption(query) {
|
||||
return !this.options ? false : this.optionKeys.indexOf(query) > -1
|
||||
},
|
||||
/**
|
||||
* Finds out if the given element is already present
|
||||
@ -464,10 +500,8 @@ export default {
|
||||
* @param {Object||String||Integer} option passed element to check
|
||||
* @returns {Boolean} returns true if element is selected
|
||||
*/
|
||||
isSelected (option) {
|
||||
const opt = this.trackBy
|
||||
? option[this.trackBy]
|
||||
: option
|
||||
isSelected(option) {
|
||||
const opt = this.trackBy ? option[this.trackBy] : option
|
||||
return this.valueKeys.indexOf(opt) > -1
|
||||
},
|
||||
/**
|
||||
@ -475,7 +509,7 @@ export default {
|
||||
* @param {Object||String||Integer} option passed element to check
|
||||
* @returns {Boolean} returns true if element is disabled
|
||||
*/
|
||||
isOptionDisabled (option) {
|
||||
isOptionDisabled(option) {
|
||||
return !!option.$isDisabled
|
||||
},
|
||||
/**
|
||||
@ -486,7 +520,7 @@ export default {
|
||||
* @param {Object||String||Integer} Passed option
|
||||
* @returns {Object||String}
|
||||
*/
|
||||
getOptionLabel (option) {
|
||||
getOptionLabel(option) {
|
||||
if (isEmpty(option)) return ''
|
||||
/* istanbul ignore else */
|
||||
if (option.isTag) return option.label
|
||||
@ -506,19 +540,22 @@ export default {
|
||||
* @param {Object||String||Integer} option to select/deselect
|
||||
* @param {Boolean} block removing
|
||||
*/
|
||||
select (option, key) {
|
||||
select(option, key) {
|
||||
/* istanbul ignore else */
|
||||
if (option.$isLabel && this.groupSelect) {
|
||||
this.selectGroup(option)
|
||||
return
|
||||
}
|
||||
if (this.blockKeys.indexOf(key) !== -1 ||
|
||||
if (
|
||||
this.blockKeys.indexOf(key) !== -1 ||
|
||||
this.disabled ||
|
||||
option.$isDisabled ||
|
||||
option.$isLabel
|
||||
) return
|
||||
)
|
||||
return
|
||||
/* istanbul ignore else */
|
||||
if (this.max && this.multiple && this.internalValue.length === this.max) return
|
||||
if (this.max && this.multiple && this.internalValue.length === this.max)
|
||||
return
|
||||
/* istanbul ignore else */
|
||||
if (key === 'Tab' && !this.pointerDirty) return
|
||||
if (option.isTag) {
|
||||
@ -553,8 +590,8 @@ export default {
|
||||
*
|
||||
* @param {Object||String||Integer} group to select/deselect
|
||||
*/
|
||||
selectGroup (selectedGroup) {
|
||||
const group = this.options.find(option => {
|
||||
selectGroup(selectedGroup) {
|
||||
const group = this.options.find((option) => {
|
||||
return option[this.groupLabel] === selectedGroup.$groupLabel
|
||||
})
|
||||
|
||||
@ -564,21 +601,18 @@ export default {
|
||||
this.$emit('remove', group[this.groupValues], this.id)
|
||||
|
||||
const newValue = this.internalValue.filter(
|
||||
option => group[this.groupValues].indexOf(option) === -1
|
||||
(option) => group[this.groupValues].indexOf(option) === -1
|
||||
)
|
||||
|
||||
this.$emit('input', newValue, this.id)
|
||||
} else {
|
||||
const optionsToAdd = group[this.groupValues].filter(
|
||||
option => !(this.isOptionDisabled(option) || this.isSelected(option))
|
||||
(option) =>
|
||||
!(this.isOptionDisabled(option) || this.isSelected(option))
|
||||
)
|
||||
|
||||
this.$emit('select', optionsToAdd, this.id)
|
||||
this.$emit(
|
||||
'input',
|
||||
this.internalValue.concat(optionsToAdd),
|
||||
this.id
|
||||
)
|
||||
this.$emit('input', this.internalValue.concat(optionsToAdd), this.id)
|
||||
}
|
||||
},
|
||||
/**
|
||||
@ -586,8 +620,9 @@ export default {
|
||||
*
|
||||
* @param {Object} group to validated selected values against
|
||||
*/
|
||||
wholeGroupSelected (group) {
|
||||
return group[this.groupValues].every(option => this.isSelected(option) || this.isOptionDisabled(option)
|
||||
wholeGroupSelected(group) {
|
||||
return group[this.groupValues].every(
|
||||
(option) => this.isSelected(option) || this.isOptionDisabled(option)
|
||||
)
|
||||
},
|
||||
/**
|
||||
@ -595,7 +630,7 @@ export default {
|
||||
*
|
||||
* @param {Object} group to check for disabled values
|
||||
*/
|
||||
wholeGroupDisabled (group) {
|
||||
wholeGroupDisabled(group) {
|
||||
return group[this.groupValues].every(this.isOptionDisabled)
|
||||
},
|
||||
/**
|
||||
@ -606,7 +641,7 @@ export default {
|
||||
* @param {type} option description
|
||||
* @returns {type} description
|
||||
*/
|
||||
removeElement (option, shouldClose = true) {
|
||||
removeElement(option, shouldClose = true) {
|
||||
/* istanbul ignore else */
|
||||
if (this.disabled) return
|
||||
/* istanbul ignore else */
|
||||
@ -617,13 +652,16 @@ export default {
|
||||
return
|
||||
}
|
||||
|
||||
const index = typeof option === 'object'
|
||||
? this.valueKeys.indexOf(option[this.trackBy])
|
||||
: this.valueKeys.indexOf(option)
|
||||
const index =
|
||||
typeof option === 'object'
|
||||
? this.valueKeys.indexOf(option[this.trackBy])
|
||||
: this.valueKeys.indexOf(option)
|
||||
|
||||
this.$emit('remove', option, this.id)
|
||||
if (this.multiple) {
|
||||
const newValue = this.internalValue.slice(0, index).concat(this.internalValue.slice(index + 1))
|
||||
const newValue = this.internalValue
|
||||
.slice(0, index)
|
||||
.concat(this.internalValue.slice(index + 1))
|
||||
this.$emit('input', newValue, this.id)
|
||||
} else {
|
||||
this.$emit('input', null, this.id)
|
||||
@ -638,25 +676,36 @@ export default {
|
||||
*
|
||||
* @fires this#removeElement
|
||||
*/
|
||||
removeLastElement () {
|
||||
removeLastElement() {
|
||||
/* istanbul ignore else */
|
||||
if (this.blockKeys.indexOf('Delete') !== -1) return
|
||||
/* istanbul ignore else */
|
||||
if (this.search.length === 0 && Array.isArray(this.internalValue) && this.internalValue.length) {
|
||||
this.removeElement(this.internalValue[this.internalValue.length - 1], false)
|
||||
if (
|
||||
this.search.length === 0 &&
|
||||
Array.isArray(this.internalValue) &&
|
||||
this.internalValue.length
|
||||
) {
|
||||
this.removeElement(
|
||||
this.internalValue[this.internalValue.length - 1],
|
||||
false
|
||||
)
|
||||
}
|
||||
},
|
||||
/**
|
||||
* Opens the multiselect’s dropdown.
|
||||
* Sets this.isOpen to TRUE
|
||||
*/
|
||||
activate () {
|
||||
activate() {
|
||||
/* istanbul ignore else */
|
||||
if (this.isOpen || this.disabled) return
|
||||
|
||||
this.adjustPosition()
|
||||
/* istanbul ignore else */
|
||||
if (this.groupValues && this.pointer === 0 && this.filteredOptions.length) {
|
||||
if (
|
||||
this.groupValues &&
|
||||
this.pointer === 0 &&
|
||||
this.filteredOptions.length
|
||||
) {
|
||||
this.pointer = 1
|
||||
}
|
||||
|
||||
@ -674,7 +723,7 @@ export default {
|
||||
* Closes the multiselect’s dropdown.
|
||||
* Sets this.isOpen to FALSE
|
||||
*/
|
||||
deactivate () {
|
||||
deactivate() {
|
||||
/* istanbul ignore else */
|
||||
if (!this.isOpen) return
|
||||
this.isOpen = false
|
||||
@ -694,29 +743,33 @@ export default {
|
||||
* @fires this#activate || this#deactivate
|
||||
* @property {Boolean} isOpen indicates if dropdown is open
|
||||
*/
|
||||
toggle () {
|
||||
this.isOpen
|
||||
? this.deactivate()
|
||||
: this.activate()
|
||||
toggle() {
|
||||
this.isOpen ? this.deactivate() : this.activate()
|
||||
},
|
||||
/**
|
||||
* Updates the hasEnoughSpace variable used for
|
||||
* detecting where to expand the dropdown
|
||||
*/
|
||||
adjustPosition () {
|
||||
adjustPosition() {
|
||||
if (typeof window === 'undefined') return
|
||||
|
||||
const spaceAbove = this.$el.getBoundingClientRect().top
|
||||
const spaceBelow = window.innerHeight - this.$el.getBoundingClientRect().bottom
|
||||
const spaceBelow =
|
||||
window.innerHeight - this.$el.getBoundingClientRect().bottom
|
||||
const hasEnoughSpaceBelow = spaceBelow > this.maxHeight
|
||||
|
||||
if (hasEnoughSpaceBelow || spaceBelow > spaceAbove || this.openDirection === 'below' || this.openDirection === 'bottom') {
|
||||
if (
|
||||
hasEnoughSpaceBelow ||
|
||||
spaceBelow > spaceAbove ||
|
||||
this.openDirection === 'below' ||
|
||||
this.openDirection === 'bottom'
|
||||
) {
|
||||
this.preferredOpenDirection = 'below'
|
||||
this.optimizedHeight = Math.min(spaceBelow - 40, this.maxHeight)
|
||||
} else {
|
||||
this.preferredOpenDirection = 'above'
|
||||
this.optimizedHeight = Math.min(spaceAbove - 40, this.maxHeight)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
@ -1,8 +1,10 @@
|
||||
import CraterTheme from '../theme/index'
|
||||
const { multiselectOption } = CraterTheme.BaseSelect
|
||||
export default {
|
||||
data () {
|
||||
data() {
|
||||
return {
|
||||
pointer: 0,
|
||||
pointerDirty: false
|
||||
pointerDirty: false,
|
||||
}
|
||||
},
|
||||
props: {
|
||||
@ -13,79 +15,106 @@ export default {
|
||||
*/
|
||||
showPointer: {
|
||||
type: Boolean,
|
||||
default: true
|
||||
default: true,
|
||||
},
|
||||
optionHeight: {
|
||||
type: Number,
|
||||
default: 40
|
||||
}
|
||||
default: 40,
|
||||
},
|
||||
},
|
||||
computed: {
|
||||
pointerPosition () {
|
||||
pointerPosition() {
|
||||
return this.pointer * this.optionHeight
|
||||
},
|
||||
visibleElements () {
|
||||
visibleElements() {
|
||||
return this.optimizedHeight / this.optionHeight
|
||||
}
|
||||
},
|
||||
},
|
||||
watch: {
|
||||
filteredOptions () {
|
||||
filteredOptions() {
|
||||
this.pointerAdjust()
|
||||
},
|
||||
isOpen () {
|
||||
isOpen() {
|
||||
this.pointerDirty = false
|
||||
},
|
||||
pointer () {
|
||||
this.$refs.search.setAttribute('aria-activedescendant', this.id + '-' + this.pointer.toString())
|
||||
}
|
||||
pointer() {
|
||||
this.$refs.search.setAttribute(
|
||||
'aria-activedescendant',
|
||||
this.id + '-' + this.pointer.toString()
|
||||
)
|
||||
},
|
||||
},
|
||||
methods: {
|
||||
optionHighlight (index, option) {
|
||||
return {
|
||||
'multiselect__option--highlight': index === this.pointer && this.showPointer,
|
||||
'multiselect__option--selected': this.isSelected(option)
|
||||
}
|
||||
optionHighlight(index, option) {
|
||||
return [
|
||||
{
|
||||
'multiselect__option--highlight':
|
||||
index === this.pointer && this.showPointer,
|
||||
'multiselect__option--selected': this.isSelected(option),
|
||||
},
|
||||
multiselectOption,
|
||||
]
|
||||
},
|
||||
groupHighlight (index, selectedGroup) {
|
||||
groupHighlight(index, selectedGroup) {
|
||||
if (!this.groupSelect) {
|
||||
return ['multiselect__option--group', 'multiselect__option--disabled']
|
||||
return [
|
||||
'multiselect__option--group',
|
||||
'multiselect__option--disabled',
|
||||
multiselectOption,
|
||||
]
|
||||
}
|
||||
|
||||
const group = this.options.find(option => {
|
||||
const group = this.options.find((option) => {
|
||||
return option[this.groupLabel] === selectedGroup.$groupLabel
|
||||
})
|
||||
|
||||
return group && !this.wholeGroupDisabled(group) ? [
|
||||
'multiselect__option--group',
|
||||
{ 'multiselect__option--highlight': index === this.pointer && this.showPointer },
|
||||
{ 'multiselect__option--group-selected': this.wholeGroupSelected(group) }
|
||||
] : 'multiselect__option--disabled'
|
||||
return group && !this.wholeGroupDisabled(group)
|
||||
? [
|
||||
'multiselect__option--group',
|
||||
{
|
||||
'multiselect__option--highlight':
|
||||
index === this.pointer && this.showPointer,
|
||||
},
|
||||
{
|
||||
'multiselect__option--group-selected': this.wholeGroupSelected(
|
||||
group
|
||||
),
|
||||
},
|
||||
multiselectOption,
|
||||
]
|
||||
: ['multiselect__option--disabled', multiselectOption]
|
||||
},
|
||||
addPointerElement ({ key } = 'Enter') {
|
||||
addPointerElement({ key } = 'Enter') {
|
||||
/* istanbul ignore else */
|
||||
if (this.filteredOptions.length > 0) {
|
||||
this.select(this.filteredOptions[this.pointer], key)
|
||||
}
|
||||
this.pointerReset()
|
||||
},
|
||||
pointerForward () {
|
||||
pointerForward() {
|
||||
/* istanbul ignore else */
|
||||
if (this.pointer < this.filteredOptions.length - 1) {
|
||||
this.pointer++
|
||||
/* istanbul ignore next */
|
||||
if (this.$refs.list.scrollTop <= this.pointerPosition - (this.visibleElements - 1) * this.optionHeight) {
|
||||
this.$refs.list.scrollTop = this.pointerPosition - (this.visibleElements - 1) * this.optionHeight
|
||||
if (
|
||||
this.$refs.list.scrollTop <=
|
||||
this.pointerPosition - (this.visibleElements - 1) * this.optionHeight
|
||||
) {
|
||||
this.$refs.list.scrollTop =
|
||||
this.pointerPosition -
|
||||
(this.visibleElements - 1) * this.optionHeight
|
||||
}
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerForward()
|
||||
)
|
||||
this.pointerForward()
|
||||
}
|
||||
this.pointerDirty = true
|
||||
},
|
||||
pointerBackward () {
|
||||
pointerBackward() {
|
||||
if (this.pointer > 0) {
|
||||
this.pointer--
|
||||
/* istanbul ignore else */
|
||||
@ -97,18 +126,20 @@ export default {
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerBackward()
|
||||
)
|
||||
this.pointerBackward()
|
||||
} else {
|
||||
/* istanbul ignore else */
|
||||
if (
|
||||
this.filteredOptions[this.pointer] &&
|
||||
this.filteredOptions[0].$isLabel &&
|
||||
!this.groupSelect
|
||||
) this.pointerForward()
|
||||
)
|
||||
this.pointerForward()
|
||||
}
|
||||
this.pointerDirty = true
|
||||
},
|
||||
pointerReset () {
|
||||
pointerReset() {
|
||||
/* istanbul ignore else */
|
||||
if (!this.closeOnSelect) return
|
||||
this.pointer = 0
|
||||
@ -117,7 +148,7 @@ export default {
|
||||
this.$refs.list.scrollTop = 0
|
||||
}
|
||||
},
|
||||
pointerAdjust () {
|
||||
pointerAdjust() {
|
||||
/* istanbul ignore else */
|
||||
if (this.pointer >= this.filteredOptions.length - 1) {
|
||||
this.pointer = this.filteredOptions.length
|
||||
@ -125,16 +156,17 @@ export default {
|
||||
: 0
|
||||
}
|
||||
|
||||
if (this.filteredOptions.length > 0 &&
|
||||
if (
|
||||
this.filteredOptions.length > 0 &&
|
||||
this.filteredOptions[this.pointer].$isLabel &&
|
||||
!this.groupSelect
|
||||
) {
|
||||
this.pointerForward()
|
||||
}
|
||||
},
|
||||
pointerSet (index) {
|
||||
pointerSet(index) {
|
||||
this.pointer = index
|
||||
this.pointerDirty = true
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user