Remove deprecated scripts for adding head-matter to wt_config.xml, including Python and Bash implementations, to streamline configuration management.

This commit is contained in:
Torsten Schulz (local)
2025-12-04 16:34:45 +01:00
parent 4b674c7c60
commit 6e9116e819
13187 changed files with 1493219 additions and 337 deletions

View File

@@ -0,0 +1,297 @@
import { VNode, defineComponent } from 'vue';
interface ClassList {
assist: string;
caret: Array<string>;
clear: string;
clearIcon: string;
container: Array<string>;
dropdown: Array<string>;
fakeInput: string;
group: string;
groupLabel: (g: any) => any;
groupOptions: string;
inifinite: string;
inifiniteSpinner: string;
multipleLabel: string;
noOptions: string;
noResults: string;
option: (o:any, g:any) => any;
options: Array<string>;
placeholder: string;
search: string;
singleLabel: string;
singleLabelText: string;
spacer: string;
spinner: string;
tag: Array<string>
tagDisabled: string;
tagRemove: string;
tagRemoveIcon: string;
tags: string;
tagsSearch: string;
tagsSearchCopy: string;
tagsSearchWrapper: string;
wrapper: string;
}
interface MultiselectProps {
modelValue?: any;
value?: any;
mode?: 'single' | 'multiple' | 'tags';
options?: any[] | object | Function;
searchable?: boolean;
valueProp?: string;
trackBy?: string | string[];
label?: string;
placeholder?: string | null;
multipleLabel?: any; // Function
disabled?: boolean;
max?: number;
limit?: number;
loading?: boolean;
id?: string;
caret?: boolean;
maxHeight?: string | number;
noOptionsText?: string | object;
noResultsText?: string | object;
canDeselect?: boolean;
canClear?: boolean;
clearOnSearch?: boolean;
clearOnSelect?: boolean;
delay?: number;
filterResults?: boolean;
minChars?: number;
resolveOnLoad?: boolean;
appendNewTag?: boolean;
appendNewOption?: boolean;
createTag?: boolean;
createOption?: boolean;
addTagOn?: string[];
addOptionOn?: string[];
hideSelected?: boolean;
showOptions?: boolean;
object?: boolean;
required?: boolean;
openDirection?: 'top' | 'bottom';
nativeSupport?: boolean;
classes?: object;
strict?: boolean;
closeOnSelect?: boolean;
closeOnDeselect?: boolean;
autocomplete?: string;
groups?: boolean;
groupLabel?: string;
groupOptions?: string;
groupHideEmpty?: boolean;
groupSelect?: boolean;
inputType?: string;
attrs?: object;
onCreate?: Function;
searchStart?: boolean;
reverse?: boolean;
regex?: string | object;
rtl?: boolean;
infinite?: boolean;
aria?: object;
clearOnBlur?: boolean;
locale?: string;
fallbackLocale?: string;
searchFilter?: Function;
allowAbsent?: boolean;
appendToBody?: boolean;
closeOnScroll?: boolean;
breakTags?: boolean;
appendTo?: string;
}
declare class Multiselect implements ReturnType<typeof defineComponent> {
modelValue: MultiselectProps['modelValue'];
value: MultiselectProps['value'];
mode: MultiselectProps['mode'];
options: MultiselectProps['options'];
searchable: MultiselectProps['searchable'];
valueProp: MultiselectProps['valueProp'];
trackBy: MultiselectProps['trackBy'];
label: MultiselectProps['label'];
placeholder: MultiselectProps['placeholder'];
multipleLabel: MultiselectProps['multipleLabel'];
disabled: MultiselectProps['disabled'];
max: MultiselectProps['max'];
limit: MultiselectProps['limit'];
loading: MultiselectProps['loading'];
id: MultiselectProps['id'];
caret: MultiselectProps['caret'];
maxHeight: MultiselectProps['maxHeight'];
noOptionsText: MultiselectProps['noOptionsText'];
noResultsText: MultiselectProps['noResultsText'];
canDeselect: MultiselectProps['canDeselect'];
canClear: MultiselectProps['canClear'];
clearOnSearch: MultiselectProps['clearOnSearch'];
clearOnSelect: MultiselectProps['clearOnSelect'];
delay: MultiselectProps['delay'];
filterResults: MultiselectProps['filterResults'];
minChars: MultiselectProps['minChars'];
resolveOnLoad: MultiselectProps['resolveOnLoad'];
appendNewTag: MultiselectProps['appendNewTag'];
appendNewOption: MultiselectProps['appendNewOption'];
createTag: MultiselectProps['createTag'];
createOption: MultiselectProps['createOption'];
addTagOn: MultiselectProps['addTagOn'];
addOptionOn: MultiselectProps['addOptionOn'];
hideSelected: MultiselectProps['hideSelected'];
showOptions: MultiselectProps['showOptions'];
object: MultiselectProps['object'];
required: MultiselectProps['required'];
openDirection: MultiselectProps['openDirection'];
nativeSupport: MultiselectProps['nativeSupport'];
classes: MultiselectProps['classes'];
strict: MultiselectProps['strict'];
closeOnSelect: MultiselectProps['closeOnSelect'];
closeOnDeselect: MultiselectProps['closeOnDeselect'];
autocomplete: MultiselectProps['autocomplete'];
groups: MultiselectProps['groups'];
groupLabel: MultiselectProps['groupLabel'];
groupOptions: MultiselectProps['groupOptions'];
groupHideEmpty: MultiselectProps['groupHideEmpty'];
groupSelect: MultiselectProps['groupSelect'];
inputType: MultiselectProps['inputType'];
attrs: MultiselectProps['attrs'];
onCreate: MultiselectProps['onCreate'];
searchStart: MultiselectProps['searchStart'];
reverse: MultiselectProps['reverse'];
regex: MultiselectProps['regex'];
rtl: MultiselectProps['rtl'];
infinite: MultiselectProps['infinite'];
aria: MultiselectProps['aria'];
clearOnBlur: MultiselectProps['clearOnBlur'];
locale: MultiselectProps['locale'];
fallbackLocale: MultiselectProps['fallbackLocale'];
searchFilter: MultiselectProps['searchFilter'];
allowAbsent: MultiselectProps['allowAbsent'];
appendToBody: MultiselectProps['appendToBody'];
closeOnScroll: MultiselectProps['closeOnScroll'];
breakTags: MultiselectProps['breakTags'];
appendTo: MultiselectProps['appendTo'];
$props: MultiselectProps;
$emit(eventName: 'change', value: any, instance: this): this | void;
$emit(eventName: 'select', value: any, option: any, instance:this): this | void;
$emit(eventName: 'deselect', value: any, option: any, instance:this): this | void;
$emit(eventName: 'search-change', query: string, instance: this): this | void;
$emit(eventName: 'tag', option: any, instance: this): this | void;
$emit(eventName: 'option', option: any, instance: this): this | void;
$emit(eventName: 'create', option: any, instance: this): this | void;
$emit(eventName: 'paste', e: Event, instance: this): this | void;
$emit(eventName: 'keydown', e: Event, instance: this): this | void;
$emit(eventName: 'keyup', e: Event, instance: this): this | void;
$emit(eventName: 'open', instance: this): this | void;
$emit(eventName: 'close', instance: this): this | void;
$emit(eventName: 'clear', instance: this): this | void;
$emit(eventName: 'max', instance: this): this | void;
$slots: {
placeholder: VNode[];
afterlist: (props: { options: any[] }) => VNode[];
beforelist: (props: { options: any[] }) => VNode[];
multiplelabel: (props: { values: any[] | object }) => VNode[];
singlelabel: (props: { value: any }) => VNode[];
option: (props: { option: any, isSelected: (option: any) => boolean, isPointed: (option: any) => boolean, search: null | string }) => VNode[];
grouplabel: (props: { group: any, isSelected: (option: any) => boolean, isPointed: (option: any) => boolean }) => VNode[];
tag: (props: { option: any, handleTagRemove: (option: any, e: Event) => void, disabled: boolean, }) => VNode[];
infinite: VNode[];
nooptions: VNode[];
noresults: VNode[];
caret: (props: { handleCaretClick: () => void, isOpen: boolean, }) => VNode[];
clear: (props: { clear: () => void }) => VNode[];
spinner: VNode[];
};
activate: (shouldOpen?: boolean) => void;
ariaActiveDescendant: string | undefined;
ariaAssist: string;
ariaControls: string;
ariaGroupId: (option : any) => string;
ariaGroupLabel: (label: any) => string;
ariaLabel: string;
ariaMultiselectable: boolean;
ariaOptionId: (option: any) => string;
ariaOptionLabel: (label: any) => string;
ariaPlaceholder: any;
ariaTagLabel: (label: any) => string;
arias: object;
backwardPointer: any;
blur: () => void;
busy: boolean;
canPointGroups: boolean;
classList: ClassList;
clear: () => void;
clearPointer: () => void;
clearSearch: () => void;
close: () => void;
deactivate: () => void;
deselect: (option: any) => void;
disabledProp?: string;
extendedGroups: Array<any>
extendedOptions: Array<any>
externalValue: any;
filteredGroups: Array<any>
filteredOptions: any;
focus: () => void;
forwardPointer: () => void;
getOption: (val: any) => any
handleCaretClick: () => void;
handleFocusIn: (e: any) => void;
handleFocusOut: () => void;
handleGroupClick: (group: any) => void;
handleKeydown: (e: Event) => void;
handleKeypress: (e: Event) => void;
handleKeyup: (e: Event) => void;
handleMousedown: (e: Event) => void;
handleOptionClic: (option: any) => void;
handlePaste: (e: Event) => void;
handleSearchInput: (e: Event) => void;
handleTagRemove: (option: any, e: Event) => void;
hasMore: boolean;
hasSelected: boolean;
infiniteLoader: any;
input: HTMLInputElement;
internalValue: any;
isActive: boolean;
isDisabled: boolean;
isMax: () => boolean;
isOpen: boolean;
isPointed: (option: any) => boolean | undefined;
isSelected: (option: any) => boolean;
localize: (target: any) => any;
mouseClicked: boolean;
multipleLabelText: string;
multiselect: any;
offset: number;
open: () => void;
plainValue: any;
pointer: any;
placement: string;
popper: object;
preparePointer: () => void;
refreshLabels: () => void;
refreshOptions: (callback: any) => void;
remove: (option: any) => void;
resolveOptions: (callback: any) => void;
resolving: boolean;
search: any;
select: (option: any) => void;
selectAll: () => void;
selectPointer: () => void;
setPointer: (option: any) => void;
setPointerFirst: () => void;
showDropdown: boolean;
tabindex: number;
tags: any;
textValue: any;
update: (val: any, triggerInput?: boolean) => void;
updatePopper: () => void;
}
export default Multiselect;

View File

@@ -0,0 +1,691 @@
<template>
<div
ref="multiselect"
:class="classList.container"
:id="searchable ? undefined : id"
:dir="rtl ? 'rtl' : undefined"
@focusin="handleFocusIn"
@focusout="handleFocusOut"
@keyup="handleKeyup"
@keydown="handleKeydown"
>
<div
:class="classList.wrapper"
@mousedown="handleMousedown"
ref="wrapper"
:tabindex="tabindex"
:aria-controls="!searchable ? ariaControls : undefined"
:aria-placeholder="!searchable ? ariaPlaceholder : undefined"
:aria-expanded="!searchable ? isOpen : undefined"
:aria-activedescendant="!searchable ? ariaActiveDescendant : undefined"
:aria-multiselectable="!searchable ? ariaMultiselectable : undefined"
:role="!searchable ? 'combobox' : undefined"
v-bind="!searchable ? arias : {}"
>
<!-- Search -->
<template v-if="mode !== 'tags' && searchable && !disabled">
<input
:type="inputType"
:modelValue="search"
:value="search"
:class="classList.search"
:autocomplete="autocomplete"
:id="searchable ? id : undefined"
@input="handleSearchInput"
@keypress="handleKeypress"
@paste.stop="handlePaste"
ref="input"
:aria-controls="ariaControls"
:aria-placeholder="ariaPlaceholder"
:aria-expanded="isOpen"
:aria-activedescendant="ariaActiveDescendant"
:aria-multiselectable="ariaMultiselectable"
role="combobox"
v-bind="{
...attrs,
...arias,
}"
/>
</template>
<!-- Tags (with search) -->
<template v-if="mode == 'tags'">
<div :class="classList.tags" data-tags>
<slot
v-for="(option, i, key) in iv"
name="tag"
:option="option"
:handleTagRemove="handleTagRemove"
:disabled="disabled"
>
<span
:class="[
classList.tag,
option.disabled ? classList.tagDisabled : null,
]"
tabindex="-1"
@keyup.enter="handleTagRemove(option, $event)"
:key="key"
:aria-label="ariaTagLabel(localize(option[label]))"
>
<span :class="classList.tagWrapper">{{ localize(option[label]) }}</span>
<span
v-if="!disabled && !option.disabled"
:class="classList.tagRemove"
@click.stop="handleTagRemove(option, $event)"
>
<span :class="classList.tagRemoveIcon"></span>
</span>
</span>
</slot>
<div :class="classList.tagsSearchWrapper" ref="tags">
<!-- Used for measuring search width -->
<span :class="classList.tagsSearchCopy">{{ search }}</span>
<!-- Actual search input -->
<input
v-if="searchable && !disabled"
:type="inputType"
:modelValue="search"
:value="search"
:class="classList.tagsSearch"
:id="searchable ? id : undefined"
:autocomplete="autocomplete"
@input="handleSearchInput"
@keypress="handleKeypress"
@paste.stop="handlePaste"
ref="input"
:aria-controls="ariaControls"
:aria-placeholder="ariaPlaceholder"
:aria-expanded="isOpen"
:aria-activedescendant="ariaActiveDescendant"
:aria-multiselectable="ariaMultiselectable"
role="combobox"
v-bind="{
...attrs,
...arias,
}"
/>
</div>
</div>
</template>
<!-- Single label -->
<template v-if="mode == 'single' && hasSelected && !search && iv">
<slot name="singlelabel" :value="iv">
<div :class="classList.singleLabel">
<span :class="classList.singleLabelText">{{ localize(iv[label]) }}</span>
</div>
</slot>
</template>
<!-- Multiple label -->
<template v-if="mode == 'multiple' && hasSelected && !search">
<slot name="multiplelabel" :values="iv">
<div :class="classList.multipleLabel" v-html="multipleLabelText"></div>
</slot>
</template>
<!-- Placeholder -->
<template v-if="placeholder && !hasSelected && !search">
<slot name="placeholder">
<div :class="classList.placeholder" aria-hidden="true">
{{ placeholder }}
</div>
</slot>
</template>
<!-- Spinner -->
<slot v-if="loading || resolving" name="spinner">
<span :class="classList.spinner" aria-hidden="true"></span>
</slot>
<!-- Clear -->
<slot v-if="hasSelected && !disabled && canClear && !busy" name="clear" :clear="clear">
<span
aria-hidden="true"
tabindex="0"
role="button"
data-clear
aria-roledescription="❎"
:class="classList.clear"
@click="clear"
@keyup.enter="clear"
><span :class="classList.clearIcon"></span></span>
</slot>
<!-- Caret -->
<slot v-if="caret && showOptions" name="caret" :handle-caret-click="handleCaretClick" :is-open="isOpen">
<span :class="classList.caret" @click="handleCaretClick" aria-hidden="true"></span>
</slot>
</div>
<!-- Options -->
<Teleport :to="appendTo || 'body'" :disabled="!appendToBody && !appendTo">
<div
:id="id ? `${id}-dropdown` : undefined"
:class="classList.dropdown"
tabindex="-1"
ref="dropdown"
@focusin="handleFocusIn"
@focusout="handleFocusOut"
>
<slot name="beforelist" :options="fo"></slot>
<ul :class="classList.options" :id="ariaControls" role="listbox">
<template v-if="groups">
<li
v-for="(group, i, key) in fg"
:class="classList.group"
:key="key"
:id="ariaGroupId(group)"
:aria-label="ariaGroupLabel(localize(group[groupLabel]))"
:aria-selected="isSelected(group)"
role="option"
>
<div
v-if="!group.__CREATE__"
:class="classList.groupLabel(group)"
:data-pointed="isPointed(group)"
@mouseenter="setPointer(group, i)"
@click="handleGroupClick(group)"
>
<slot name="grouplabel" :group="group" :is-selected="isSelected" :is-pointed="isPointed">
<span v-html="localize(group[groupLabel])"></span>
</slot>
</div>
<ul
:class="classList.groupOptions"
:aria-label="ariaGroupLabel(localize(group[groupLabel]))"
role="group"
>
<li
v-for="(option, i, key) in group.__VISIBLE__"
:class="classList.option(option, group)"
:data-pointed="isPointed(option)"
:data-selected="isSelected(option) || undefined"
:key="key"
@mouseenter="setPointer(option)"
@click="handleOptionClick(option)"
:id="ariaOptionId(option)"
:aria-selected="isSelected(option)"
:aria-label="ariaOptionLabel(localize(option[label]))"
role="option"
>
<slot name="option" :option="option" :is-selected="isSelected" :is-pointed="isPointed" :search="search">
<span>{{ localize(option[label]) }}</span>
</slot>
</li>
</ul>
</li>
</template>
<template v-else>
<li
v-for="(option, i, key) in fo"
:class="classList.option(option)"
:data-pointed="isPointed(option)"
:data-selected="isSelected(option) || undefined"
:key="key"
@mouseenter="setPointer(option)"
@click="handleOptionClick(option)"
:id="ariaOptionId(option)"
:aria-selected="isSelected(option)"
:aria-label="ariaOptionLabel(localize(option[label]))"
role="option"
>
<slot name="option" :option="option" :isSelected="isSelected" :is-pointed="isPointed" :search="search">
<span>{{ localize(option[label]) }}</span>
</slot>
</li>
</template>
</ul>
<slot v-if="noOptions" name="nooptions">
<div :class="classList.noOptions" v-html="localize(noOptionsText)"></div>
</slot>
<slot v-if="noResults" name="noresults">
<div :class="classList.noResults" v-html="localize(noResultsText)"></div>
</slot>
<div v-if="infinite && hasMore" :class="classList.inifinite" ref="infiniteLoader">
<slot name="infinite">
<span :class="classList.inifiniteSpinner"></span>
</slot>
</div>
<slot name="afterlist" :options="fo"></slot>
</div>
</Teleport>
<!-- Hacky input element to show HTML5 required warning -->
<input v-if="required" :class="classList.fakeInput" tabindex="-1" :value="textValue" required/>
<!-- Native input support -->
<template v-if="nativeSupport">
<input v-if="mode == 'single'" type="hidden" :name="name" :value="plainValue !== undefined ? plainValue : ''" />
<template v-else>
<input v-for="(v, i) in plainValue" type="hidden" :name="`${name}[]`" :value="v" :key="i" />
</template>
</template>
<!-- Screen reader assistive text -->
<div v-if="searchable && hasSelected" :class="classList.assist" :id="ariaAssist" aria-hidden="true">
{{ ariaLabel }}
</div>
<!-- Create height for empty input -->
<div :class="classList.spacer"></div>
</div>
</template>
<script>
/* istanbul ignore file */
import useData from './composables/useData'
import useValue from './composables/useValue'
import useSearch from './composables/useSearch'
import usePointer from './composables/usePointer'
import useOptions from './composables/useOptions'
import usePointerAction from './composables/usePointerAction'
import useDropdown from './composables/useDropdown'
import useMultiselect from './composables/useMultiselect'
import useKeyboard from './composables/useKeyboard'
import useClasses from './composables/useClasses'
import useScroll from './composables/useScroll'
import useA11y from './composables/useA11y'
import useI18n from './composables/useI18n'
import useRefs from './composables/useRefs'
import resolveDeps from './utils/resolveDeps'
export default {
name: 'Multiselect',
emits: [
'paste', 'open', 'close', 'select', 'deselect',
'input', 'search-change', 'tag', 'option', 'update:modelValue',
'change', 'clear', 'keydown', 'keyup', 'max', 'create',
],
props: {
value: {
required: false,
},
modelValue: {
required: false,
},
options: {
type: [Array, Object, Function],
required: false,
default: () => ([])
},
id: {
type: [String, Number],
required: false,
default: undefined,
},
name: {
type: [String, Number],
required: false,
default: 'multiselect',
},
disabled: {
type: Boolean,
required: false,
default: false,
},
label: {
type: String,
required: false,
default: 'label',
},
trackBy: {
type: [String, Array],
required: false,
default: undefined,
},
valueProp: {
type: String,
required: false,
default: 'value',
},
placeholder: {
type: String,
required: false,
default: null,
},
mode: {
type: String,
required: false,
default: 'single', // single|multiple|tags
},
searchable: {
type: Boolean,
required: false,
default: false,
},
limit: {
type: Number,
required: false,
default: -1,
},
hideSelected: {
type: Boolean,
required: false,
default: true,
},
createTag: {
type: Boolean,
required: false,
default: undefined,
},
createOption: {
type: Boolean,
required: false,
default: undefined,
},
appendNewTag: {
type: Boolean,
required: false,
default: undefined,
},
appendNewOption: {
type: Boolean,
required: false,
default: undefined,
},
addTagOn: {
type: Array,
required: false,
default: undefined,
},
addOptionOn: {
type: Array,
required: false,
default: undefined,
},
caret: {
type: Boolean,
required: false,
default: true,
},
loading: {
type: Boolean,
required: false,
default: false,
},
noOptionsText: {
type: [String, Object],
required: false,
default: 'The list is empty',
},
noResultsText: {
type: [String, Object],
required: false,
default: 'No results found',
},
multipleLabel: {
type: Function,
required: false,
default: undefined,
},
object: {
type: Boolean,
required: false,
default: false,
},
delay: {
type: Number,
required: false,
default: -1,
},
minChars: {
type: Number,
required: false,
default: 0,
},
resolveOnLoad: {
type: Boolean,
required: false,
default: true,
},
filterResults: {
type: Boolean,
required: false,
default: true,
},
clearOnSearch: {
type: Boolean,
required: false,
default: false,
},
clearOnSelect: {
type: Boolean,
required: false,
default: true,
},
canDeselect: {
type: Boolean,
required: false,
default: true,
},
canClear: {
type: Boolean,
required: false,
default: true,
},
max: {
type: Number,
required: false,
default: -1,
},
showOptions: {
type: Boolean,
required: false,
default: true,
},
required: {
type: Boolean,
required: false,
default: false,
},
openDirection: {
type: String,
required: false,
default: 'bottom',
},
nativeSupport: {
type: Boolean,
required: false,
default: false,
},
classes: {
type: Object,
required: false,
default: () => ({})
},
strict: {
type: Boolean,
required: false,
default: true,
},
closeOnSelect: {
type: Boolean,
required: false,
default: true,
},
closeOnDeselect: {
type: Boolean,
required: false,
default: false,
},
autocomplete: {
type: String,
required: false,
default: undefined,
},
groups: {
type: Boolean,
required: false,
default: false,
},
groupLabel: {
type: String,
required: false,
default: 'label',
},
groupOptions: {
type: String,
required: false,
default: 'options',
},
groupHideEmpty: {
type: Boolean,
required: false,
default: false,
},
groupSelect: {
type: Boolean,
required: false,
default: true,
},
inputType: {
type: String,
required: false,
default: 'text',
},
attrs: {
required: false,
type: Object,
default: () => ({}),
},
onCreate: {
required: false,
type: Function,
default: undefined,
},
disabledProp: {
type: String,
required: false,
default: 'disabled',
},
searchStart: {
type: Boolean,
required: false,
default: false,
},
reverse: {
type: Boolean,
required: false,
default: false,
},
regex: {
type: [Object, String, RegExp],
required: false,
default: undefined,
},
rtl: {
type: Boolean,
required: false,
default: false,
},
infinite: {
type: Boolean,
required: false,
default: false,
},
aria: {
required: false,
type: Object,
default: () => ({}),
},
clearOnBlur: {
required: false,
type: Boolean,
default: true,
},
locale: {
required: false,
type: String,
default: null,
},
fallbackLocale: {
required: false,
type: String,
default: 'en',
},
searchFilter: {
required: false,
type: Function,
default: null,
},
allowAbsent: {
required: false,
type: Boolean,
default: false,
},
appendToBody: {
required: false,
type: Boolean,
default: false,
},
closeOnScroll: {
required: false,
type: Boolean,
default: false,
},
breakTags: {
required: false,
type: Boolean,
default: false,
},
appendTo: {
required: false,
type: String,
default: undefined,
},
},
setup(props, context)
{
return resolveDeps(props, context, [
useRefs,
useI18n,
useValue,
usePointer,
useDropdown,
useSearch,
useData,
useMultiselect,
useOptions,
useScroll,
usePointerAction,
useKeyboard,
useClasses,
useA11y,
])
},
beforeMount() {
if ((this.$root.constructor && this.$root.constructor.version && this.$root.constructor.version.match(/^2\./)) || this.vueVersionMs === 2) {
if (!this.$options.components.Teleport) {
this.$options.components.Teleport = {
render() {
return this.$slots.default ? this.$slots.default[0] : null
}
}
}
}
}
}
</script>

View File

@@ -0,0 +1,127 @@
import { toRefs, onMounted, ref, computed } from 'vue'
import toRef from './../utils/toRef'
export default function useA11y (props, context, dep)
{
const {
placeholder, id, valueProp, label: labelProp, mode, groupLabel, aria, searchable ,
} = toRefs(props)
// ============ DEPENDENCIES ============
const pointer = dep.pointer
const iv = dep.iv
const hasSelected = dep.hasSelected
const multipleLabelText = dep.multipleLabelText
// ================ DATA ================
const label = ref(null)
// ============== COMPUTED ==============
const ariaAssist = toRef(() => (
`${id.value ? id.value + '-' : ''}assist`
))
const ariaControls = toRef(() => (
`${id.value ? id.value + '-' : ''}multiselect-options`
))
const ariaActiveDescendant = toRef(() => {
if (pointer.value) {
let texts = id.value
? `${id.value}-`
: '';
texts += `${pointer.value.group ? 'multiselect-group' : 'multiselect-option'}-`
texts += pointer.value.group ? pointer.value.index : pointer.value[valueProp.value]
return texts
}
})
const ariaPlaceholder = toRef(() => {
return placeholder.value
})
const ariaMultiselectable = toRef(() => {
return mode.value !== 'single'
})
const ariaLabel = computed(() => {
if (mode.value === 'single' && hasSelected.value) {
return iv.value[labelProp.value]
}
if (mode.value === 'multiple' && hasSelected.value) {
return multipleLabelText.value
}
if (mode.value === 'tags' && hasSelected.value) {
return iv.value.map(v => v[labelProp.value]).join(', ')
}
return ''
})
const arias = computed(() => {
let arias = { ...aria.value }
// Need to add manually because focusing
// the input won't read the selected value
if (searchable.value) {
arias['aria-labelledby'] = arias['aria-labelledby']
? `${ariaAssist.value} ${arias['aria-labelledby']}`
: ariaAssist.value
if (ariaLabel.value && arias['aria-label']) {
arias['aria-label'] = `${ariaLabel.value}, ${arias['aria-label']}`
}
}
return arias
})
// =============== METHODS ==============
const ariaOptionId = (option) => (
`${id.value ? id.value + '-' : ''}multiselect-option-${option[valueProp.value]}`
)
const ariaGroupId = (option) => (
`${id.value ? id.value + '-' : ''}multiselect-group-${option.index}`
)
const ariaOptionLabel = (label) => `${label}`
const ariaGroupLabel = (label) => `${label}`
const ariaTagLabel = (label) => `${label}`
// =============== HOOKS ================
onMounted(() => {
/* istanbul ignore next */
if (id.value && document && document.querySelector) {
let forTag = document.querySelector(`[for="${id.value}"]`)
label.value = forTag ? forTag.innerText : null
}
})
return {
arias,
ariaLabel,
ariaAssist,
ariaControls,
ariaPlaceholder,
ariaMultiselectable,
ariaActiveDescendant,
ariaOptionId,
ariaOptionLabel,
ariaGroupId,
ariaGroupLabel,
ariaTagLabel,
}
}

View File

@@ -0,0 +1,165 @@
import { computed, toRefs } from 'vue'
import toRef from './../utils/toRef'
export default function useClasses (props, context, dependencies)
{const {
classes: classes_, disabled, showOptions, breakTags
} = toRefs(props)
// ============ DEPENDENCIES ============
const isOpen = dependencies.isOpen
const isPointed = dependencies.isPointed
const isSelected = dependencies.isSelected
const isDisabled = dependencies.isDisabled
const isActive = dependencies.isActive
const canPointGroups = dependencies.canPointGroups
const resolving = dependencies.resolving
const fo = dependencies.fo
const placement = dependencies.placement
// ============== COMPUTED ==============
const classes = toRef(() => ({
container: 'multiselect',
containerDisabled: 'is-disabled',
containerOpen: 'is-open',
containerOpenTop: 'is-open-top',
containerActive: 'is-active',
wrapper: 'multiselect-wrapper',
singleLabel: 'multiselect-single-label',
singleLabelText: 'multiselect-single-label-text',
multipleLabel: 'multiselect-multiple-label',
search: 'multiselect-search',
tags: 'multiselect-tags',
tag: 'multiselect-tag',
tagWrapper: 'multiselect-tag-wrapper',
tagWrapperBreak: 'multiselect-tag-wrapper-break',
tagDisabled: 'is-disabled',
tagRemove: 'multiselect-tag-remove',
tagRemoveIcon: 'multiselect-tag-remove-icon',
tagsSearchWrapper: 'multiselect-tags-search-wrapper',
tagsSearch: 'multiselect-tags-search',
tagsSearchCopy: 'multiselect-tags-search-copy',
placeholder: 'multiselect-placeholder',
caret: 'multiselect-caret',
caretOpen: 'is-open',
clear: 'multiselect-clear',
clearIcon: 'multiselect-clear-icon',
spinner: 'multiselect-spinner',
inifinite: 'multiselect-inifite',
inifiniteSpinner: 'multiselect-inifite-spinner',
dropdown: 'multiselect-dropdown',
dropdownTop: 'is-top',
dropdownHidden: 'is-hidden',
options: 'multiselect-options',
optionsTop: 'is-top',
group: 'multiselect-group',
groupLabel: 'multiselect-group-label',
groupLabelPointable: 'is-pointable',
groupLabelPointed: 'is-pointed',
groupLabelSelected: 'is-selected',
groupLabelDisabled: 'is-disabled',
groupLabelSelectedPointed: 'is-selected is-pointed',
groupLabelSelectedDisabled: 'is-selected is-disabled',
groupOptions: 'multiselect-group-options',
option: 'multiselect-option',
optionPointed: 'is-pointed',
optionSelected: 'is-selected',
optionDisabled: 'is-disabled',
optionSelectedPointed: 'is-selected is-pointed',
optionSelectedDisabled: 'is-selected is-disabled',
noOptions: 'multiselect-no-options',
noResults: 'multiselect-no-results',
fakeInput: 'multiselect-fake-input',
assist: 'multiselect-assistive-text',
spacer: 'multiselect-spacer',
...classes_.value,
}))
const showDropdown = toRef(() => {
return !!(isOpen.value && showOptions.value && (!resolving.value || (resolving.value && fo.value.length)))
})
const classList = computed(() => {
const c = classes.value
return {
container: [c.container]
.concat(disabled.value ? c.containerDisabled : [])
.concat(showDropdown.value && placement.value === 'top' ? c.containerOpenTop : [])
.concat(showDropdown.value && placement.value !== 'top' ? c.containerOpen : [])
.concat(isActive.value ? c.containerActive : []),
wrapper: c.wrapper,
spacer: c.spacer,
singleLabel: c.singleLabel,
singleLabelText: c.singleLabelText,
multipleLabel: c.multipleLabel,
search: c.search,
tags: c.tags,
tag: [c.tag]
.concat(disabled.value ? c.tagDisabled : []),
tagWrapper: [c.tagWrapper, breakTags.value ? c.tagWrapperBreak : null],
tagDisabled: c.tagDisabled,
tagRemove: c.tagRemove,
tagRemoveIcon: c.tagRemoveIcon,
tagsSearchWrapper: c.tagsSearchWrapper,
tagsSearch: c.tagsSearch,
tagsSearchCopy: c.tagsSearchCopy,
placeholder: c.placeholder,
caret: [c.caret]
.concat(isOpen.value ? c.caretOpen : []),
clear: c.clear,
clearIcon: c.clearIcon,
spinner: c.spinner,
inifinite: c.inifinite,
inifiniteSpinner: c.inifiniteSpinner,
dropdown: [c.dropdown]
.concat(placement.value === 'top' ? c.dropdownTop : [])
.concat(!isOpen.value || !showOptions.value || !showDropdown.value ? c.dropdownHidden : []),
options: [c.options]
.concat(placement.value === 'top' ? c.optionsTop : []),
group: c.group,
groupLabel: (g) => {
let groupLabel = [c.groupLabel]
if (isPointed(g)) {
groupLabel.push(isSelected(g) ? c.groupLabelSelectedPointed : c.groupLabelPointed)
} else if (isSelected(g) && canPointGroups.value) {
groupLabel.push(isDisabled(g) ? c.groupLabelSelectedDisabled : c.groupLabelSelected)
} else if (isDisabled(g)) {
groupLabel.push(c.groupLabelDisabled)
}
if (canPointGroups.value) {
groupLabel.push(c.groupLabelPointable)
}
return groupLabel
},
groupOptions: c.groupOptions,
option: (o, g) => {
let option = [c.option]
if (isPointed(o)) {
option.push(isSelected(o) ? c.optionSelectedPointed : c.optionPointed)
} else if (isSelected(o)) {
option.push(isDisabled(o) ? c.optionSelectedDisabled : c.optionSelected)
} else if (isDisabled(o) || (g && isDisabled(g))) {
option.push(c.optionDisabled)
}
return option
},
noOptions: c.noOptions,
noResults: c.noResults,
assist: c.assist,
fakeInput: c.fakeInput,
}
})
return {
classList,
showDropdown,
}
}

View File

@@ -0,0 +1,62 @@
import { toRefs, getCurrentInstance } from 'vue'
import isNullish from './../utils/isNullish'
export default function useData (props, context, dep)
{
const { object, valueProp, mode } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
// =============== METHODS ==============
const update = (val, triggerInput = true) => {
// Setting object(s) as internal value
iv.value = makeInternal(val)
// Setting object(s) or plain value as external
// value based on `option` setting
const externalVal = makeExternal(val)
context.emit('change', externalVal, $this)
if (triggerInput) {
context.emit('input', externalVal)
context.emit('update:modelValue', externalVal)
}
}
// no export
const makeExternal = (val) => {
// If external value should be object
// no transformation is required
if (object.value) {
return val
}
// No need to transform if empty value
if (isNullish(val)) {
return val
}
// If external should be plain transform
// value object to plain values
return !Array.isArray(val) ? val[valueProp.value] : val.map(v => v[valueProp.value])
}
// no export
const makeInternal = (val) => {
if (isNullish(val)) {
return mode.value === 'single' ? {} : []
}
return val
}
return {
update,
}
}

View File

@@ -0,0 +1,158 @@
import { ref, toRefs, getCurrentInstance, onMounted, onBeforeUnmount, nextTick } from 'vue'
import { createPopper } from '@popperjs/core/lib/popper-lite'
import preventOverflow from '@popperjs/core/lib/modifiers/preventOverflow'
import flip from '@popperjs/core/lib/modifiers/flip'
import toRef from './../utils/toRef'
export default function useDropdown (props, context, dep)
{
const { disabled, appendTo, appendToBody, openDirection } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const multiselect = dep.multiselect
const dropdown = dep.dropdown
// ================ DATA ================
const isOpen = ref(false)
const popper = ref(null)
const forcedPlacement = ref(null)
// ============== COMPUTED ==============
const appended = toRef(() => {
return appendTo.value || appendToBody.value
})
const placement = toRef(() => {
return (openDirection.value === 'top' && forcedPlacement.value === 'bottom') ||
(openDirection.value === 'bottom' && forcedPlacement.value !== 'top')
? 'bottom'
: 'top'
})
// =============== METHODS ==============
const open = () => {
if (isOpen.value || disabled.value) {
return
}
isOpen.value = true
context.emit('open', $this)
if (appended.value) {
nextTick(() => {
updatePopper()
})
}
}
const close = () => {
if (!isOpen.value) {
return
}
isOpen.value = false
context.emit('close', $this)
}
const updatePopper = () => {
if (!popper.value) {
return
}
let borderTopWidth = parseInt(window.getComputedStyle(dropdown.value).borderTopWidth.replace('px', ''))
let borderBottomWidth = parseInt(window.getComputedStyle(dropdown.value).borderBottomWidth.replace('px', ''))
popper.value.setOptions((options) => ({
...options,
modifiers: [
...options.modifiers,
{
name: 'offset',
options: {
offset: [0, (placement.value === 'top' ? borderTopWidth : borderBottomWidth) * -1],
},
},
]
}))
popper.value.update()
}
/* istanbul ignore next: UI feature */
const hasFixedParent = (element) => {
while (element && element !== document.body) {
const style = getComputedStyle(element)
if (style.position === 'fixed') {
return true
}
element = element.parentElement
}
return false
}
onMounted(() => {
if (!appended.value) {
return
}
/* istanbul ignore next: popper mock */
popper.value = createPopper(multiselect.value, dropdown.value, {
strategy: hasFixedParent(multiselect.value) ? /* istanbul ignore next: UI feature */ 'fixed' : undefined,
placement: openDirection.value,
modifiers: [
preventOverflow,
flip,
{
name: 'sameWidth',
enabled: true,
phase: 'beforeWrite',
requires: ['computeStyles'],
fn: ({ state }) => {
state.styles.popper.width = `${state.rects.reference.width}px`
},
effect: ({ state }) => {
state.elements.popper.style.width = `${
state.elements.reference.offsetWidth
}px`
}
},
{
name: 'toggleClass',
enabled: true,
phase: 'write',
fn({ state }) {
forcedPlacement.value = state.placement
},
},
]
})
})
onBeforeUnmount(() => {
if (!appended.value || !popper.value) {
return
}
popper.value.destroy()
popper.value = null
})
return {
popper,
isOpen,
open,
close,
placement,
updatePopper,
}
}

View File

@@ -0,0 +1,34 @@
import { toRefs } from 'vue'
export default function useI18n (props, context, dep)
{
const {
locale, fallbackLocale,
} = toRefs(props)
// =============== METHODS ==============
const localize = (target) => {
if (!target || typeof target !== 'object') {
return target
}
if (target && target[locale.value]) {
return target[locale.value]
} else if (target && locale.value && target[locale.value.toUpperCase()]) {
return target[locale.value.toUpperCase()]
} else if (target && target[fallbackLocale.value]) {
return target[fallbackLocale.value]
} else if (target && fallbackLocale.value && target[fallbackLocale.value.toUpperCase()]) {
return target[fallbackLocale.value.toUpperCase()]
} else if (target && Object.keys(target)[0]) {
return target[Object.keys(target)[0]]
} else {
return ''
}
}
return {
localize,
}
}

View File

@@ -0,0 +1,247 @@
import { toRefs, getCurrentInstance } from 'vue'
import toRef from './../utils/toRef'
export default function useKeyboard (props, context, dep)
{
const {
mode, addTagOn, openDirection, searchable,
showOptions, valueProp, groups: groupped,
addOptionOn: addOptionOn_, createTag, createOption: createOption_,
reverse,
} = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
const update = dep.update
const deselect = dep.deselect
const search = dep.search
const setPointer = dep.setPointer
const selectPointer = dep.selectPointer
const backwardPointer = dep.backwardPointer
const forwardPointer = dep.forwardPointer
const multiselect = dep.multiselect
const wrapper = dep.wrapper
const tags = dep.tags
const isOpen = dep.isOpen
const open = dep.open
const blur = dep.blur
const fo = dep.fo
// ============== COMPUTED ==============
// no export
const createOption = toRef(() => {
return createTag.value || createOption_.value || false
})
// no export
const addOptionOn = toRef(() => {
if (addTagOn.value !== undefined) {
return addTagOn.value
}
else if (addOptionOn_.value !== undefined) {
return addOptionOn_.value
}
return ['enter']
})
// =============== METHODS ==============
// no export
const preparePointer = () => {
// When options are hidden and creating tags is allowed
// no pointer will be set (because options are hidden).
// In such case we need to set the pointer manually to the
// first option, which equals to the option created from
// the search value.
if (mode.value === 'tags' && !showOptions.value && createOption.value && searchable.value && !groupped.value) {
setPointer(fo.value[fo.value.map(o => o[valueProp.value]).indexOf(search.value)])
}
}
const handleKeydown = (e) => {
context.emit('keydown', e, $this)
let tagList
let activeIndex
if (['ArrowLeft', 'ArrowRight', 'Enter'].indexOf(e.key) !== -1 && mode.value === 'tags') {
tagList = [...(multiselect.value.querySelectorAll(`[data-tags] > *`))].filter(e => e !== tags.value)
activeIndex = tagList.findIndex(e => e === document.activeElement)
}
switch (e.key) {
case 'Backspace':
if (mode.value === 'single') {
return
}
if (searchable.value && [null, ''].indexOf(search.value) === -1) {
return
}
if (iv.value.length === 0) {
return
}
let deselectables = iv.value.filter(v=>!v.disabled && v.remove !== false)
if (deselectables.length) {
deselect(deselectables[deselectables.length - 1])
}
break
case 'Enter':
e.preventDefault()
if (e.keyCode === 229) {
// ignore IME confirmation
return
}
if (activeIndex !== -1 && activeIndex !== undefined) {
update([...iv.value].filter((v, k) => k !== activeIndex))
if (activeIndex === tagList.length - 1) {
if (tagList.length - 1) {
tagList[tagList.length - 2].focus()
} else if (searchable.value) {
tags.value.querySelector('input').focus()
} else {
wrapper.value.focus()
}
}
return
}
if (addOptionOn.value.indexOf('enter') === -1 && createOption.value) {
return
}
preparePointer()
selectPointer()
break
case ' ':
if (!createOption.value && !searchable.value) {
e.preventDefault()
preparePointer()
selectPointer()
return
}
if (!createOption.value) {
return false
}
if (addOptionOn.value.indexOf('space') === -1 && createOption.value) {
return
}
e.preventDefault()
preparePointer()
selectPointer()
break
case 'Tab':
case ';':
case ',':
if (addOptionOn.value.indexOf(e.key.toLowerCase()) === -1 || !createOption.value) {
return
}
preparePointer()
selectPointer()
e.preventDefault()
break
case 'Escape':
blur()
break
case 'ArrowUp':
e.preventDefault()
if (!showOptions.value) {
return
}
/* istanbul ignore else */
if (!isOpen.value) {
open()
}
backwardPointer()
break
case 'ArrowDown':
e.preventDefault()
if (!showOptions.value) {
return
}
/* istanbul ignore else */
if (!isOpen.value) {
open()
}
forwardPointer()
break
case 'ArrowLeft':
if (
(searchable.value && tags.value && tags.value.querySelector('input').selectionStart)
|| e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length
) {
return
}
e.preventDefault()
if (activeIndex === -1) {
tagList[tagList.length-1].focus()
}
else if (activeIndex > 0) {
tagList[activeIndex-1].focus()
}
break
case 'ArrowRight':
if (activeIndex === -1 || e.shiftKey || mode.value !== 'tags' || !iv.value || !iv.value.length) {
return
}
e.preventDefault()
/* istanbul ignore else */
if (tagList.length > activeIndex + 1) {
tagList[activeIndex+1].focus()
}
else if (searchable.value) {
tags.value.querySelector('input').focus()
}
else if (!searchable.value) {
wrapper.value.focus()
}
break
}
}
const handleKeyup = (e) => {
context.emit('keyup', e, $this)
}
return {
handleKeydown,
handleKeyup,
preparePointer,
}
}

View File

@@ -0,0 +1,121 @@
import { ref, toRefs } from 'vue'
import toRef from './../utils/toRef'
export default function useMultiselect (props, context, dep)
{
const { searchable, disabled, clearOnBlur } = toRefs(props)
// ============ DEPENDENCIES ============
const input = dep.input
const open = dep.open
const close = dep.close
const clearSearch = dep.clearSearch
const isOpen = dep.isOpen
const wrapper = dep.wrapper
const tags = dep.tags
// ================ DATA ================
const isActive = ref(false)
const mouseClicked = ref(false)
// ============== COMPUTED ==============
const tabindex = toRef(() => {
return searchable.value || disabled.value ? -1 : 0
})
// =============== METHODS ==============
const blur = () => {
if (searchable.value) {
input.value.blur()
}
wrapper.value.blur()
}
const focus = () => {
if (searchable.value && !disabled.value) {
input.value.focus()
}
}
const activate = (shouldOpen = true) => {
if (disabled.value) {
return
}
isActive.value = true
if (shouldOpen) {
open()
}
}
const deactivate = () => {
isActive.value = false
setTimeout(() => {
if (!isActive.value) {
close()
if (clearOnBlur.value) {
clearSearch()
}
}
}, 1)
}
const handleFocusIn = (e) => {
if ((e.target.closest('[data-tags]') && e.target.nodeName !== 'INPUT') || e.target.closest('[data-clear]')) {
return
}
activate(mouseClicked.value)
}
const handleFocusOut = () => {
deactivate()
}
const handleCaretClick = () => {
deactivate()
blur()
}
/* istanbul ignore next */
const handleMousedown = (e) => {
mouseClicked.value = true
if (isOpen.value && (e.target.isEqualNode(wrapper.value) || e.target.isEqualNode(tags.value))) {
setTimeout(() => {
deactivate()
}, 0)
} else if (!isOpen.value
&& (document.activeElement.isEqualNode(wrapper.value)
|| document.activeElement.isEqualNode(input.value))) {
activate()
}
setTimeout(() => {
mouseClicked.value = false
}, 0)
}
return {
tabindex,
isActive,
mouseClicked,
blur,
focus,
activate,
deactivate,
handleFocusIn,
handleFocusOut,
handleCaretClick,
handleMousedown,
}
}

View File

@@ -0,0 +1,888 @@
import { ref, toRefs, computed, watch, getCurrentInstance } from 'vue'
import normalize from './../utils/normalize'
import isObject from './../utils/isObject'
import isNullish from './../utils/isNullish'
import arraysEqual from './../utils/arraysEqual'
import objectsEqual from './../utils/objectsEqual'
import toRef from './../utils/toRef'
export default function useOptions (props, context, dep)
{
const {
options, mode, trackBy: trackBy_, limit, hideSelected, createTag, createOption: createOption_, label,
appendNewTag, appendNewOption: appendNewOption_, multipleLabel, object, loading, delay, resolveOnLoad,
minChars, filterResults, clearOnSearch, clearOnSelect, valueProp, allowAbsent, groupLabel,
canDeselect, max, strict, closeOnSelect, closeOnDeselect, groups: groupped, reverse, infinite,
groupOptions, groupHideEmpty, groupSelect, onCreate, disabledProp, searchStart, searchFilter,
} = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const iv = dep.iv
const ev = dep.ev
const search = dep.search
const clearSearch = dep.clearSearch
const update = dep.update
const pointer = dep.pointer
const setPointer = dep.setPointer
const clearPointer = dep.clearPointer
const focus = dep.focus
const deactivate = dep.deactivate
const close = dep.close
const localize = dep.localize
// ================ DATA ================
// no export
// appendedOptions
const ap = ref([])
// no export
// resolvedOptions
const ro = ref([])
const resolving = ref(false)
// no export
const searchWatcher = ref(null)
const offset = ref(infinite.value && limit.value === -1 ? 10 : limit.value)
// ============== COMPUTED ==============
const resolvedOptions = computed({
get: () => ro.value,
set: (v) => ro.value = v
})
// no export
const createOption = toRef(() => {
return createTag.value || createOption_.value || false
})
// no export
const appendNewOption = toRef(() => {
if (appendNewTag.value !== undefined) {
return appendNewTag.value
} else if (appendNewOption_.value !== undefined) {
return appendNewOption_.value
}
return true
})
// no export
// extendedOptions
const eo = computed(() => {
if (groupped.value) {
let groups = eg.value || /* istanbul ignore next */ []
let eo = []
groups.forEach((group) => {
optionsToArray(group[groupOptions.value]).forEach((option) => {
eo.push(Object.assign({}, option, group[disabledProp.value] ? { [disabledProp.value]: true } : {}))
})
})
return eo
} else {
let eo = optionsToArray(ro.value || /* istanbul ignore next */ [])
if (ap.value.length) {
eo = eo.concat(ap.value)
}
return eo
}
})
// preFilteredOptions
const pfo = computed(() => {
let options = eo.value
if (reverse.value) {
options = options.reverse()
}
if (createdOption.value.length) {
options = createdOption.value.concat(options)
}
return filterOptions(options)
})
// filteredOptions
const fo = computed(() => {
let options = pfo.value
if (offset.value > 0) {
options = options.slice(0, offset.value)
}
return options
})
// no export
// extendedGroups
const eg = computed(() => {
if (!groupped.value) {
return []
}
let eg = []
let groups = ro.value || /* istanbul ignore next */ []
if (ap.value.length) {
eg.push({
[groupLabel.value]: ' ',
[groupOptions.value]: [...ap.value],
__CREATE__: true
})
}
return eg.concat(groups)
})
// preFilteredGroups
const pfg = computed(() => {
let groups = [...eg.value].map(g => ({...g}))
if (createdOption.value.length) {
if (groups[0] && groups[0].__CREATE__) {
groups[0][groupOptions.value] = [...createdOption.value, ...groups[0][groupOptions.value]]
} else {
groups = [{
[groupLabel.value]: ' ',
[groupOptions.value]: [...createdOption.value],
__CREATE__: true
}].concat(groups)
}
}
return groups
})
// filteredGroups
const fg = computed(() => {
if (!groupped.value) {
return []
}
let options = pfg.value
return filterGroups((options || /* istanbul ignore next */ []).map((group, index) => {
const arrayOptions = optionsToArray(group[groupOptions.value])
return {
...group,
index,
group: true,
[groupOptions.value]: filterOptions(arrayOptions, false).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
__VISIBLE__: filterOptions(arrayOptions).map(o => Object.assign({}, o, group[disabledProp.value] ? { [disabledProp.value]: true } : {})),
}
// Difference between __VISIBLE__ and {groupOptions}: visible does not contain selected options when hideSelected=true
}))
})
const hasSelected = computed(() => {
switch (mode.value) {
case 'single':
return !isNullish(iv.value[valueProp.value])
case 'multiple':
case 'tags':
return !isNullish(iv.value) && iv.value.length > 0
}
})
const multipleLabelText = computed(() => {
return multipleLabel.value !== undefined
? multipleLabel.value(iv.value, $this)
: (iv.value && iv.value.length > 1 ? `${iv.value.length} options selected` : `1 option selected`)
})
const noOptions = toRef(() => {
return !eo.value.length && !resolving.value && !createdOption.value.length
})
const noResults = toRef(() => {
return eo.value.length > 0 && fo.value.length == 0 && ((search.value && groupped.value) || !groupped.value)
})
// no export
const createdOption = computed(() => {
if (createOption.value === false || !search.value) {
return []
}
if (getOptionByTrackBy(search.value) !== -1) {
return []
}
return [{
[valueProp.value]: search.value,
[trackBy.value[0]]: search.value,
[label.value]: search.value,
__CREATE__: true,
}]
})
const trackBy = computed(() => {
return trackBy_.value ? (Array.isArray(trackBy_.value) ? trackBy_.value : [trackBy_.value]) : [label.value]
})
// no export
const nullValue = toRef(() => {
switch (mode.value) {
case 'single':
return null
case 'multiple':
case 'tags':
return []
}
})
const busy = toRef(() => {
return loading.value || resolving.value
})
// =============== METHODS ==============
/**
* @param {array|object|string|number} option
*/
const select = (option) => {
if (typeof option !== 'object') {
option = getOption(option)
}
switch (mode.value) {
case 'single':
update(option)
break
case 'multiple':
case 'tags':
update((iv.value).concat(option))
break
}
context.emit('select', finalValue(option), option, $this)
}
const deselect = (option) => {
if (typeof option !== 'object') {
option = getOption(option)
}
switch (mode.value) {
case 'single':
clear()
break
case 'tags':
case 'multiple':
update(Array.isArray(option)
? iv.value.filter(v => option.map(o => o[valueProp.value]).indexOf(v[valueProp.value]) === -1)
: iv.value.filter(v => v[valueProp.value] != option[valueProp.value]))
break
}
context.emit('deselect', finalValue(option), option, $this)
}
// no export
const finalValue = (option) => {
return object.value ? option : option[valueProp.value]
}
const remove = (option) => {
deselect(option)
}
const handleTagRemove = (option, e) => {
if (e.button !== 0) {
e.preventDefault()
return
}
remove(option)
}
const clear = () => {
update(nullValue.value)
context.emit('clear', $this)
}
const isSelected = (option) => {
if (option.group !== undefined) {
return mode.value === 'single' ? false : areAllSelected(option[groupOptions.value]) && option[groupOptions.value].length
}
switch (mode.value) {
case 'single':
return !isNullish(iv.value) && (
iv.value[valueProp.value] == option[valueProp.value] ||
(typeof iv.value[valueProp.value] === 'object' && typeof option[valueProp.value] === 'object' && objectsEqual(iv.value[valueProp.value], option[valueProp.value]))
)
case 'tags':
case 'multiple':
return !isNullish(iv.value) && iv.value.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
}
}
const isDisabled = (option) => {
return option[disabledProp.value] === true
}
const isMax = () => {
if (max === undefined || max.value === -1 || (!hasSelected.value && max.value > 0)) {
return false
}
return iv.value.length >= max.value
}
const handleOptionClick = (option) => {
if (isDisabled(option)) {
return
}
if (onCreate.value && !isSelected(option) && option.__CREATE__) {
option = { ...option }
delete option.__CREATE__
option = onCreate.value(option, $this)
if (option instanceof Promise) {
resolving.value = true
option.then((result) => {
resolving.value = false
handleOptionSelect(result)
})
return
}
}
handleOptionSelect(option)
}
const handleOptionSelect = (option) => {
if (option.__CREATE__) {
option = { ...option }
delete option.__CREATE__
}
switch (mode.value) {
case 'single':
if (option && isSelected(option)) {
if (canDeselect.value) {
deselect(option)
}
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (option) {
handleOptionAppend(option)
}
/* istanbul ignore else */
if (clearOnSelect.value) {
clearSearch()
}
if (closeOnSelect.value) {
clearPointer()
close()
}
if (option) {
select(option)
}
break
case 'multiple':
if (option && isSelected(option)) {
deselect(option)
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (isMax()) {
context.emit('max', $this)
return
}
if (option) {
handleOptionAppend(option)
select(option)
}
if (clearOnSelect.value) {
clearSearch()
}
if (hideSelected.value) {
clearPointer()
}
if (closeOnSelect.value) {
close()
}
break
case 'tags':
if (option && isSelected(option)) {
deselect(option)
if (closeOnDeselect.value) {
clearPointer()
close()
}
return
}
if (isMax()) {
context.emit('max', $this)
return
}
if (option) {
handleOptionAppend(option)
}
if (clearOnSelect.value) {
clearSearch()
}
if (option) {
select(option)
}
if (hideSelected.value) {
clearPointer()
}
if (closeOnSelect.value) {
close()
}
break
}
if (!closeOnSelect.value) {
focus()
}
}
const handleGroupClick = (group) => {
if (isDisabled(group) || mode.value === 'single' || !groupSelect.value) {
return
}
switch (mode.value) {
case 'multiple':
case 'tags':
if (areAllEnabledSelected(group[groupOptions.value])) {
deselect(group[groupOptions.value])
} else {
select(group[groupOptions.value]
.filter(o => iv.value.map(v => v[valueProp.value]).indexOf(o[valueProp.value]) === -1)
.filter(o => !o[disabledProp.value])
.filter((o, k) => iv.value.length + 1 + k <= max.value || max.value === -1)
)
}
if (hideSelected.value && pointer.value) {
// Refresh pointer because pointer.__VISIBLE__ are not reactive #354
setPointer(fg.value.filter(g => !g[disabledProp.value])[pointer.value.index])
}
break
}
if (closeOnSelect.value) {
deactivate()
}
}
const handleOptionAppend = (option) => {
if (getOption(option[valueProp.value]) === undefined && createOption.value) {
context.emit('tag', option[valueProp.value], $this)
context.emit('option', option[valueProp.value], $this)
context.emit('create', option[valueProp.value], $this)
if (appendNewOption.value) {
appendOption(option)
}
clearSearch()
}
}
const selectAll = () => {
if (mode.value === 'single') {
return
}
select(fo.value.filter(o => !o.disabled && !isSelected(o)))
}
// no export
const areAllEnabledSelected = (options) => {
return options.find(o => !isSelected(o) && !o[disabledProp.value]) === undefined
}
// no export
const areAllSelected = (options) => {
return options.find(o => !isSelected(o)) === undefined
}
const getOption = (val) => {
return eo.value[eo.value.map(o => String(o[valueProp.value])).indexOf(String(val))]
}
// no export
const getOptionByTrackBy = (val) => {
return eo.value.findIndex((o) => {
return trackBy.value.some((track) => {
return (parseInt(o[track]) == o[track] ? parseInt(o[track]) : o[track]) === (parseInt(val) == val ? parseInt(val) : val)
})
})
}
// no export
const shouldHideOption = (option) => {
return ['tags', 'multiple'].indexOf(mode.value) !== -1 && hideSelected.value && isSelected(option)
}
// no export
const appendOption = (option) => {
ap.value.push(option)
}
// no export
const filterGroups = (groups) => {
// If the search has value we need to filter among
// the ones that are visible to the user to avoid
// displaying groups which technically have options
// based on search but that option is already selected.
return groupHideEmpty.value
? groups.filter(g => search.value
? g.__VISIBLE__.length
: g[groupOptions.value].length
)
: groups.filter(g => search.value ? g.__VISIBLE__.length : true)
}
// no export
const filterOptions = (options, excludeHideSelected = true) => {
let fo = options
if (search.value && filterResults.value) {
let filter = searchFilter.value
if (!filter) {
filter = (option, query, $this) => {
return trackBy.value.some(track => {
let target = normalize(localize(option[track]), strict.value);
return searchStart.value
? target.startsWith(normalize(query, strict.value))
: target.indexOf(normalize(query, strict.value)) !== -1;
})
}
}
fo = fo.filter((o) => {
return filter(o, search.value, $this)
})
}
if (hideSelected.value && excludeHideSelected) {
fo = fo.filter((option) => !shouldHideOption(option))
}
return fo
}
// no export
const optionsToArray = (options) => {
let uo = options
// Transforming an object to an array of objects
if (isObject(uo)) {
uo = Object.keys(uo).map((key) => {
let val = uo[key]
return { [valueProp.value]: key, [trackBy.value[0]]: val, [label.value]: val}
})
}
// Transforming an plain arrays to an array of objects
/* istanbul ignore else */
if (uo && Array.isArray(uo)) {
uo = uo.map((val) => {
return typeof val === 'object' ? val : { [valueProp.value]: val, [trackBy.value[0]]: val, [label.value]: val}
})
} else {
uo = []
}
return uo
}
// no export
const initInternalValue = () => {
if (!isNullish(ev.value)) {
iv.value = makeInternal(ev.value)
}
}
const resolveOptions = (callback) => {
resolving.value = true
return new Promise((resolve, reject) => {
options.value(search.value, $this).then((response) => {
ro.value = response || []
if (typeof callback == 'function') {
callback(response)
}
resolving.value = false
}).catch((e) => {
console.error(e)
ro.value = []
resolving.value = false
}).finally(() => {
resolve()
})
})
}
// no export
const refreshLabels = () => {
if (!hasSelected.value) {
return
}
if (mode.value === 'single') {
let option = getOption(iv.value[valueProp.value])
/* istanbul ignore else */
if (option !== undefined) {
let newLabel = option[label.value]
iv.value[label.value] = newLabel
if (object.value) {
ev.value[label.value] = newLabel
}
}
} else {
iv.value.forEach((val, i) => {
let option = getOption(iv.value[i][valueProp.value])
/* istanbul ignore else */
if (option !== undefined) {
let newLabel = option[label.value]
iv.value[i][label.value] = newLabel
if (object.value) {
ev.value[i][label.value] = newLabel
}
}
})
}
}
const refreshOptions = (callback) => {
resolveOptions(callback)
}
// no export
const makeInternal = (val) => {
if (isNullish(val)) {
return mode.value === 'single' ? {} : []
}
if (object.value) {
return val
}
// If external should be plain transform value object to plain values
return mode.value === 'single' ? getOption(val) || (allowAbsent.value ? {
[label.value]: val,
[valueProp.value]: val,
[trackBy.value[0]]: val,
} : {}) : val.filter(v => !!getOption(v) || allowAbsent.value).map(v => getOption(v) || {
[label.value]: v,
[valueProp.value]: v,
[trackBy.value[0]]: v,
})
}
// no export
const initSearchWatcher = () => {
searchWatcher.value = watch(search, (query) => {
if (query.length < minChars.value || (!query && minChars.value !== 0)) {
return
}
resolving.value = true
if (clearOnSearch.value) {
ro.value = []
}
setTimeout(() => {
if (query != search.value) {
return
}
options.value(search.value, $this).then((response) => {
if (query == search.value || !search.value) {
ro.value = response
pointer.value = fo.value.filter(o => o[disabledProp.value] !== true)[0] || null
resolving.value = false
}
}).catch( /* istanbul ignore next */ (e) => {
console.error(e)
})
}, delay.value)
}, { flush: 'sync' })
}
// ================ HOOKS ===============
if (mode.value !== 'single' && !isNullish(ev.value) && !Array.isArray(ev.value)) {
throw new Error(`v-model must be an array when using "${mode.value}" mode`)
}
if (options && typeof options.value == 'function') {
if (resolveOnLoad.value) {
resolveOptions(initInternalValue)
} else if (object.value == true) {
initInternalValue()
}
}
else {
ro.value = options.value
initInternalValue()
}
// ============== WATCHERS ==============
if (delay.value > -1) {
initSearchWatcher()
}
watch(delay, (value, old) => {
/* istanbul ignore else */
if (searchWatcher.value) {
searchWatcher.value()
}
if (value >= 0) {
initSearchWatcher()
}
})
watch(ev, (newValue) => {
if (isNullish(newValue)) {
update(makeInternal(newValue), false)
return
}
switch (mode.value) {
case 'single':
if (object.value ? newValue[valueProp.value] != iv.value[valueProp.value] : newValue != iv.value[valueProp.value]) {
update(makeInternal(newValue), false)
}
break
case 'multiple':
case 'tags':
if (!arraysEqual(object.value ? newValue.map(o => o[valueProp.value]) : newValue, iv.value.map(o => o[valueProp.value]))) {
update(makeInternal(newValue), false)
}
break
}
}, { deep: true })
watch(options, (n, o) => {
if (typeof props.options === 'function') {
if (resolveOnLoad.value && (!o || (n && n.toString() !== o.toString()))) {
resolveOptions()
}
} else {
ro.value = props.options
if (!Object.keys(iv.value).length) {
initInternalValue()
}
refreshLabels()
}
})
watch(label, refreshLabels)
watch(limit, (n,o) => {
offset.value = infinite.value && n === -1 ? 10 : n
})
return {
resolvedOptions,
pfo,
fo,
filteredOptions: fo,
hasSelected,
multipleLabelText,
eo,
extendedOptions: eo,
eg,
extendedGroups: eg,
fg,
filteredGroups: fg,
noOptions,
noResults,
resolving,
busy,
offset,
select,
deselect,
remove,
selectAll,
clear,
isSelected,
isDisabled,
isMax,
getOption,
handleOptionClick,
handleGroupClick,
handleTagRemove,
refreshOptions,
resolveOptions,
refreshLabels,
}
}

View File

@@ -0,0 +1,34 @@
import { ref, toRefs } from 'vue'
export default function usePointer (props, context, dep)
{
const { groupSelect, mode, groups, disabledProp } = toRefs(props)
// ================ DATA ================
const pointer = ref(null)
// =============== METHODS ==============
const setPointer = (option) => {
if (option === undefined || (option !== null && option[disabledProp.value])) {
return
}
if (groups.value && option && option.group && (mode.value === 'single' || !groupSelect.value)) {
return
}
pointer.value = option
}
const clearPointer = () => {
setPointer(null)
}
return {
pointer,
setPointer,
clearPointer,
}
}

View File

@@ -0,0 +1,274 @@
import { toRefs, watch, nextTick, computed } from 'vue'
import toRef from './../utils/toRef'
export default function usePointer (props, context, dep)
{
const {
valueProp, showOptions, searchable, groupLabel,
groups: groupped, mode, groupSelect, disabledProp,
groupOptions,
} = toRefs(props)
// ============ DEPENDENCIES ============
const fo = dep.fo
const fg = dep.fg
const handleOptionClick = dep.handleOptionClick
const handleGroupClick = dep.handleGroupClick
const search = dep.search
const pointer = dep.pointer
const setPointer = dep.setPointer
const clearPointer = dep.clearPointer
const multiselect = dep.multiselect
const isOpen = dep.isOpen
// ============== COMPUTED ==============
// no export
const options = computed(() => {
return fo.value.filter(o => !o[disabledProp.value])
})
const groups = computed(() => {
return fg.value.filter(g => !g[disabledProp.value])
})
const canPointGroups = toRef(() => {
return mode.value !== 'single' && groupSelect.value
})
const isPointerGroup = toRef(() => {
return pointer.value && pointer.value.group
})
const currentGroup = computed(() => {
return getParentGroup(pointer.value)
})
const prevGroup = computed(() => {
const group = isPointerGroup.value ? pointer.value : /* istanbul ignore next */ getParentGroup(pointer.value)
const groupIndex = groups.value.map(g => g[groupLabel.value]).indexOf(group[groupLabel.value])
let prevGroup = groups.value[groupIndex - 1]
if (prevGroup === undefined) {
prevGroup = lastGroup.value
}
return prevGroup
})
const nextGroup = computed(() => {
let nextIndex = groups.value.map(g => g.label).indexOf(isPointerGroup.value
? pointer.value[groupLabel.value]
: getParentGroup(pointer.value)[groupLabel.value]) + 1
if (groups.value.length <= nextIndex) {
nextIndex = 0
}
return groups.value[nextIndex]
})
const lastGroup = computed(() => {
return [...groups.value].slice(-1)[0]
})
const currentGroupFirstEnabledOption = computed(() => {
return pointer.value.__VISIBLE__.filter(o => !o[disabledProp.value])[0]
})
const currentGroupPrevEnabledOption = computed(() => {
const options = currentGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])
return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1]
})
const currentGroupNextEnabledOption = computed(() => {
const options = getParentGroup(pointer.value).__VISIBLE__.filter(o => !o[disabledProp.value])
return options[options.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1]
})
const prevGroupLastEnabledOption = computed(() => {
return [...prevGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
})
const lastGroupLastEnabledOption = computed(() => {
return [...lastGroup.value.__VISIBLE__.filter(o => !o[disabledProp.value])].slice(-1)[0]
})
// =============== METHODS ==============
const isPointed = (option) => {
return (!!pointer.value && (
(!option.group && pointer.value[valueProp.value] === option[valueProp.value]) ||
(option.group !== undefined && pointer.value[groupLabel.value] === option[groupLabel.value])
)) ? true : undefined
}
const setPointerFirst = () => {
setPointer(options.value[0] || null)
}
const selectPointer = () => {
if (!pointer.value || pointer.value[disabledProp.value] === true) {
return
}
if (isPointerGroup.value) {
handleGroupClick(pointer.value)
} else {
handleOptionClick(pointer.value)
}
}
const forwardPointer = () => {
if (pointer.value === null) {
setPointer((groupped.value && canPointGroups.value ? (!groups.value[0].__CREATE__ ? groups.value[0] : options.value[0]) : options.value[0]) || null)
}
else if (groupped.value && canPointGroups.value) {
let nextPointer = isPointerGroup.value ? currentGroupFirstEnabledOption.value : currentGroupNextEnabledOption.value
if (nextPointer === undefined) {
nextPointer = nextGroup.value
if (nextPointer.__CREATE__) {
nextPointer = nextPointer[groupOptions.value][0]
}
}
setPointer(nextPointer || /* istanbul ignore next */ null)
} else {
let next = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) + 1
if (options.value.length <= next) {
next = 0
}
setPointer(options.value[next] || null)
}
nextTick(() => {
adjustWrapperScrollToPointer()
})
}
const backwardPointer = () => {
if (pointer.value === null) {
let prevPointer = options.value[options.value.length - 1]
if (groupped.value && canPointGroups.value) {
prevPointer = lastGroupLastEnabledOption.value
if (prevPointer === undefined) {
prevPointer = lastGroup.value
}
}
setPointer(prevPointer || null)
}
else if (groupped.value && canPointGroups.value) {
let prevPointer = isPointerGroup.value ? prevGroupLastEnabledOption.value : currentGroupPrevEnabledOption.value
if (prevPointer === undefined) {
prevPointer = isPointerGroup.value ? prevGroup.value : currentGroup.value
if (prevPointer.__CREATE__) {
prevPointer = prevGroupLastEnabledOption.value
if (prevPointer === undefined) {
prevPointer = prevGroup.value
}
}
}
setPointer(prevPointer || /* istanbul ignore next */ null)
} else {
let prevIndex = options.value.map(o => o[valueProp.value]).indexOf(pointer.value[valueProp.value]) - 1
if (prevIndex < 0) {
prevIndex = options.value.length - 1
}
setPointer(options.value[prevIndex] || null)
}
nextTick(() => {
adjustWrapperScrollToPointer()
})
}
const getParentGroup = (option) => {
return groups.value.find((group) => {
return group.__VISIBLE__.map(o => o[valueProp.value]).indexOf(option[valueProp.value]) !== -1
})
}
// no export
/* istanbul ignore next */
const adjustWrapperScrollToPointer = () => {
let pointedOption = multiselect.value.querySelector(`[data-pointed]`)
if (!pointedOption) {
return
}
let wrapper = pointedOption.parentElement.parentElement
if (groupped.value) {
wrapper = isPointerGroup.value
? pointedOption.parentElement.parentElement.parentElement
: pointedOption.parentElement.parentElement.parentElement.parentElement
}
if (pointedOption.offsetTop + pointedOption.offsetHeight > wrapper.clientHeight + wrapper.scrollTop) {
wrapper.scrollTop = pointedOption.offsetTop + pointedOption.offsetHeight - wrapper.clientHeight
}
if (pointedOption.offsetTop < wrapper.scrollTop) {
wrapper.scrollTop = pointedOption.offsetTop
}
}
// ============== WATCHERS ==============
watch(search, (val) => {
if (searchable.value) {
if (val.length && showOptions.value) {
setPointerFirst()
} else {
clearPointer()
}
}
})
watch(isOpen, (val) => {
if (val && multiselect && multiselect.value) {
let firstSelected = multiselect.value.querySelectorAll(`[data-selected]`)[0]
if (!firstSelected) {
return
}
let wrapper = firstSelected.parentElement.parentElement
nextTick(() => {
// Removed because of #406
/* istanbul ignore next */
// if (wrapper.scrollTop > 0) {
// return
// }
wrapper.scrollTop = firstSelected.offsetTop
})
}
})
return {
pointer,
canPointGroups,
isPointed,
setPointerFirst,
selectPointer,
forwardPointer,
backwardPointer,
}
}

View File

@@ -0,0 +1,24 @@
import { shallowRef } from 'vue'
export default function useRefs (props, context, dep)
{
// ================ DATA ================
const multiselect = shallowRef(null)
const wrapper = shallowRef(null)
const tags = shallowRef(null)
const input = shallowRef(null)
const dropdown = shallowRef(null)
return {
multiselect,
wrapper,
tags,
input,
dropdown,
}
}

View File

@@ -0,0 +1,100 @@
import { toRefs, watch, nextTick, onMounted, ref, shallowRef, computed } from 'vue'
import toRef from '../utils/toRef'
export default function useScroll (props, context, dep)
{
const {
limit, infinite,
} = toRefs(props)
// ============ DEPENDENCIES ============
const isOpen = dep.isOpen
const offset = dep.offset
const search = dep.search
const pfo = dep.pfo
const eo = dep.eo
// ================ DATA ================
// no export
const observer = ref(null)
const infiniteLoader = shallowRef(null)
// ============== COMPUTED ==============
const hasMore = toRef(() => {
return offset.value < pfo.value.length
})
// =============== METHODS ==============
// no export
/* istanbul ignore next */
const handleIntersectionObserver = (entries) => {
const { isIntersecting, target } = entries[0]
if (isIntersecting) {
const parent = target.offsetParent
const scrollTop = parent.scrollTop
offset.value += limit.value == -1 ? 10 : limit.value
nextTick(() => {
parent.scrollTop = scrollTop
})
}
}
const observe = () => {
/* istanbul ignore else */
if (isOpen.value && offset.value < pfo.value.length) {
observer.value.observe(infiniteLoader.value)
} else if (!isOpen.value && observer.value) {
observer.value.disconnect()
}
}
// ============== WATCHERS ==============
watch(isOpen, () => {
if (!infinite.value) {
return
}
observe()
})
watch(search, () => {
if (!infinite.value) {
return
}
offset.value = limit.value
observe()
}, { flush: 'post' })
watch(eo, () => {
if (!infinite.value) {
return
}
observe()
}, { immediate: false, flush: 'post' })
// ================ HOOKS ===============
onMounted(() => {
/* istanbul ignore else */
if (window && window.IntersectionObserver) {
observer.value = new IntersectionObserver(handleIntersectionObserver)
}
})
return {
hasMore,
infiniteLoader,
}
}

View File

@@ -0,0 +1,78 @@
import { ref, getCurrentInstance, watch, toRefs } from 'vue'
export default function useSearch (props, context, dep)
{
const { regex } = toRefs(props)
const $this = getCurrentInstance().proxy
// ============ DEPENDENCIES ============
const isOpen = dep.isOpen
const open = dep.open
// ================ DATA ================
const search = ref(null)
// =============== METHODS ==============
const clearSearch = () => {
search.value = ''
}
const handleSearchInput = (e) => {
search.value = e.target.value
}
const handleKeypress = (e) => {
if (regex.value) {
let regexp = regex.value
if (typeof regexp === 'string') {
regexp = new RegExp(regexp)
}
if (!e.key.match(regexp)) {
e.preventDefault()
}
}
}
const handlePaste = (e) => {
if (regex.value) {
let clipboardData = e.clipboardData || /* istanbul ignore next */ window.clipboardData
let pastedData = clipboardData.getData('Text')
let regexp = regex.value
if (typeof regexp === 'string') {
regexp = new RegExp(regexp)
}
if (!pastedData.split('').every(c => !!c.match(regexp))) {
e.preventDefault()
}
}
context.emit('paste', e, $this)
}
// ============== WATCHERS ==============
watch(search, (val) => {
if (!isOpen.value && val) {
open()
}
context.emit('search-change', val, $this)
})
return {
search,
clearSearch,
handleSearchInput,
handleKeypress,
handlePaste,
}
}

View File

@@ -0,0 +1,37 @@
import { computed, toRefs, ref } from 'vue'
import toRef from '../utils/toRef'
export default function useValue (props, context)
{
const { value, modelValue, mode, valueProp } = toRefs(props)
// ================ DATA ================
// internalValue
const iv = ref(mode.value !== 'single' ? [] : {})
// ============== COMPUTED ==============
/* istanbul ignore next */
// externalValue
const ev = toRef(() => {
return modelValue.value !== undefined ? modelValue.value : value.value
})
const plainValue = computed(() => {
return mode.value === 'single' ? iv.value[valueProp.value] : iv.value.map(v=>v[valueProp.value])
})
const textValue = toRef(() => {
return mode.value !== 'single' ? iv.value.map(v=>v[valueProp.value]).join(',') : iv.value[valueProp.value]
})
return {
iv,
internalValue: iv,
ev,
externalValue: ev,
textValue,
plainValue,
}
}

View File

@@ -0,0 +1 @@
export { default } from './Multiselect';

View File

@@ -0,0 +1,11 @@
export default function arraysEqual (array1, array2) {
if (array1.length !== array2.length) {
return false;
}
const array2Sorted = array2.slice().sort()
return array1.slice().sort().every(function(value, index) {
return value === array2Sorted[index];
})
}

View File

@@ -0,0 +1,3 @@
export default function isNullish (val) {
return val === null || val === undefined
}

View File

@@ -0,0 +1,3 @@
export default function isObject (variable) {
return Object.prototype.toString.call(variable) === '[object Object]'
}

View File

@@ -0,0 +1,11 @@
export default function normalize (str, strict = true) {
return strict
? String(str).toLowerCase().trim()
: String(str).toLowerCase()
.normalize('NFD')
.trim()
.replace(/æ/g, 'ae')
.replace(/œ/g, 'oe')
.replace(/ø/g, 'o')
.replace(/\p{Diacritic}/gu, '')
}

View File

@@ -0,0 +1,38 @@
/* istanbul ignore next */
const objectsEqual = (obj1, obj2) => {
// If both are strictly equal, return true
if (obj1 === obj2) {
return true
}
// If either is not an object or is null, return false (handles primitive types and null)
if (typeof obj1 !== 'object' || obj1 === null || typeof obj2 !== 'object' || obj2 === null) {
return false
}
// Get the keys of both objects
const keys1 = Object.keys(obj1)
const keys2 = Object.keys(obj2)
// If they have a different number of keys, they're not equal
if (keys1.length !== keys2.length) {
return false
}
// Compare each key-value pair recursively
for (let key of keys1) {
// Check if both objects have the same key
if (!keys2.includes(key)) {
return false
}
// Recursively compare the values
if (!objectsEqual(obj1[key], obj2[key])) {
return false
}
}
return true
}
export default objectsEqual

View File

@@ -0,0 +1,10 @@
export default function (props, context, features, deps = {}) {
features.forEach((composable) => {
deps = {
...deps,
...composable(props, context, deps)
}
})
return deps
}

View File

@@ -0,0 +1,7 @@
import { customRef } from 'vue'
// Polyfill for Vue <3.3 for getters only
// https://vuejs.org/api/reactivity-utilities.html#toref
export default function toRef (get) {
return customRef(() => ({ get, set: /* istanbul ignore next */ () => { } }))
}