@kws3/ui 1.1.0 → 1.2.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/buttons/SubmitButton.svelte +4 -4
- package/controls/NumberInput.svelte +4 -1
- package/datagrid/DataSearch/DataSearch.svelte +45 -11
- package/datagrid/DataSearch/SearchFilter.svelte +82 -19
- package/datagrid/DataSort/DataSort.svelte +16 -6
- package/datagrid/GridView/GridCell.svelte +23 -7
- package/datagrid/GridView/GridRow.svelte +60 -15
- package/datagrid/GridView/GridView.svelte +79 -19
- package/datagrid/Pagination/Pagination.svelte +8 -2
- package/datagrid/TileView/TileView.svelte +64 -16
- package/datagrid/TileView/TileViewItem.svelte +36 -9
- package/forms/Datepicker.svelte +4 -4
- package/forms/select/MultiSelect.svelte +581 -0
- package/forms/select/SearchableSelect.svelte +149 -0
- package/helpers/ClipboardCopier.svelte +2 -2
- package/helpers/FloatingUI/Floatie.svelte +141 -0
- package/helpers/FloatingUI/FloatingUIOutput.svelte +34 -0
- package/helpers/FloatingUI/index.js +156 -0
- package/helpers/Icon.svelte +2 -2
- package/index.js +8 -1
- package/package.json +2 -2
- package/settings.js +15 -3
- package/styles/FloatingUI.scss +163 -0
- package/styles/Panel.scss +2 -3
- package/styles/Select.scss +203 -0
- package/forms/SearchableSelect.svelte +0 -438
- package/styles/SearchableSelect.scss +0 -128
|
@@ -0,0 +1,581 @@
|
|
|
1
|
+
<!--
|
|
2
|
+
@component
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@param {array} [value=[]] - Value of the Input
|
|
6
|
+
|
|
7
|
+
This property can be bound to, to fetch the current value, Default: `[]`
|
|
8
|
+
@param {object} [max=null] - Maximum number of selectable items from dropdown list.
|
|
9
|
+
|
|
10
|
+
Accepts a `null` value for unlimited selected items.
|
|
11
|
+
Or a number value, Default: `null`
|
|
12
|
+
@param {string} [placeholder="Please select..."] - Placeholder text for the input, Default: `"Please select..."`
|
|
13
|
+
@param {array} [options=[]] - Array of strings, or objects.
|
|
14
|
+
Used to populate the list of options in the dropdown, Default: `[]`
|
|
15
|
+
@param {string} [search_key="name"] - If `options` is an array of objects,
|
|
16
|
+
this property of each object will be searched, Default: `"name"`
|
|
17
|
+
@param {string} [value_key="id"] - If `options` is an array of objects,
|
|
18
|
+
this property of each object will be returned as the value, Default: `"id"`
|
|
19
|
+
@param {''|'small'|'medium'|'large'} [size=""] - Size of the input, Default: `""`
|
|
20
|
+
@param {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'} [color=""] - Color of the input, Default: `""`
|
|
21
|
+
@param {string} [style=""] - Inline CSS for input container, Default: `""`
|
|
22
|
+
@param {boolean} [readonly=false] - Marks component as read-only, Default: `false`
|
|
23
|
+
@param {boolean} [disabled=false] - Disables the component, Default: `false`
|
|
24
|
+
@param {string} [selected_icon="check"] - Icon used to mark selected items in dropdown list, Default: `"check"`
|
|
25
|
+
@param {boolean} [summary_mode=false] - When activated, it will show the number of selected items.
|
|
26
|
+
|
|
27
|
+
Instead of listing all the selected items inside the input., Default: `false`
|
|
28
|
+
@param {string} [no_options_msg="No matching options"] - Message to display when no matching options are found, Default: `"No matching options"`
|
|
29
|
+
@param {string} [remove_btn_tip="Remove"] - Tooltip text for Remove Item button. This `string` will precede the selected Item Name in the tooltip., Default: `"Remove"`
|
|
30
|
+
@param {string} [remove_all_tip="Remove all"] - Tooltip text for the Clear All button, Default: `"Remove all"`
|
|
31
|
+
@param {string} [class=""] - CSS classes for input container, Default: `""`
|
|
32
|
+
|
|
33
|
+
### Events
|
|
34
|
+
- `change` - Triggered when the value changes
|
|
35
|
+
- `add` - Triggered when an item is added from dropdown list
|
|
36
|
+
- `remove` - Triggered when an item is removed from selected Items
|
|
37
|
+
- `blur` - Triggered when the input loses focus
|
|
38
|
+
|
|
39
|
+
### Slots
|
|
40
|
+
- `<slot name="default" {search_key} {option} />` - Slot containing text for each selectable item
|
|
41
|
+
|
|
42
|
+
Default value: `<span>{option[search_key] || option}</span>`
|
|
43
|
+
|
|
44
|
+
-->
|
|
45
|
+
<div
|
|
46
|
+
bind:this={el}
|
|
47
|
+
class="
|
|
48
|
+
searchableselect input
|
|
49
|
+
{disabled ? 'is-disabled' : ''}
|
|
50
|
+
{readonly ? 'is-readonly' : ''}
|
|
51
|
+
is-{size} is-{color} {klass}
|
|
52
|
+
"
|
|
53
|
+
class:readonly
|
|
54
|
+
class:single
|
|
55
|
+
{style}
|
|
56
|
+
on:click|stopPropagation={() => setOptionsVisible(true)}>
|
|
57
|
+
<ul class="tokens tags {summary_mode ? 'has-addons' : ''}">
|
|
58
|
+
{#if !single && selectedOptions && selectedOptions.length > 0}
|
|
59
|
+
{#if summary_mode}
|
|
60
|
+
<li class="tag summary-count is-{size} is-{color || 'primary'}">
|
|
61
|
+
{selectedOptions.length}
|
|
62
|
+
</li>
|
|
63
|
+
<li
|
|
64
|
+
class="tag is-{size} summary-text is-{color || 'primary'} is-light">
|
|
65
|
+
Item{selectedOptions.length == 1 ? "" : "s"} selected
|
|
66
|
+
</li>
|
|
67
|
+
{:else}
|
|
68
|
+
{#each selectedOptions as tag}
|
|
69
|
+
<li
|
|
70
|
+
class="tag is-{size} is-{color || 'primary'} is-light"
|
|
71
|
+
on:click|self|stopPropagation={() => setOptionsVisible(true)}>
|
|
72
|
+
{tag[used_search_key]}
|
|
73
|
+
{#if !readonly && !disabled}
|
|
74
|
+
<button
|
|
75
|
+
on:click|self|stopPropagation={() => remove(tag)}
|
|
76
|
+
type="button"
|
|
77
|
+
class="delete is-small"
|
|
78
|
+
data-tooltip="{remove_btn_tip} {tag[used_search_key]}" />
|
|
79
|
+
{/if}
|
|
80
|
+
</li>
|
|
81
|
+
{/each}
|
|
82
|
+
{/if}
|
|
83
|
+
{/if}
|
|
84
|
+
<input
|
|
85
|
+
class="input is-{size}"
|
|
86
|
+
bind:this={input}
|
|
87
|
+
autocomplete="off"
|
|
88
|
+
{disabled}
|
|
89
|
+
{readonly}
|
|
90
|
+
bind:value={searchText}
|
|
91
|
+
on:click|self|stopPropagation={() => setOptionsVisible(true)}
|
|
92
|
+
on:keydown={handleKeydown}
|
|
93
|
+
on:focus={() => setOptionsVisible(true)}
|
|
94
|
+
on:blur={blurEvent}
|
|
95
|
+
on:blur={() => setOptionsVisible(false)}
|
|
96
|
+
placeholder={_placeholder} />
|
|
97
|
+
</ul>
|
|
98
|
+
{#if !readonly && !disabled}
|
|
99
|
+
<button
|
|
100
|
+
type="button"
|
|
101
|
+
class="remove-all delete is-small"
|
|
102
|
+
data-tooltip={remove_all_tip}
|
|
103
|
+
on:click|stopPropagation={removeAll}
|
|
104
|
+
style={shouldShowClearAll ? "" : "display: none;"} />
|
|
105
|
+
{/if}
|
|
106
|
+
|
|
107
|
+
<ul
|
|
108
|
+
bind:this={dropdown}
|
|
109
|
+
class="options {single ? 'is-single' : 'is-multi'}"
|
|
110
|
+
class:hidden={!showOptions}>
|
|
111
|
+
{#each filteredOptions as option}
|
|
112
|
+
<li
|
|
113
|
+
on:mousedown|preventDefault|stopPropagation={() =>
|
|
114
|
+
handleOptionMouseDown(option)}
|
|
115
|
+
on:mouseenter|preventDefault|stopPropagation={() => {
|
|
116
|
+
activeOption = option;
|
|
117
|
+
}}
|
|
118
|
+
class:selected={isSelected(option)}
|
|
119
|
+
class:active={activeOption === option}>
|
|
120
|
+
<span class="kws-selected-icon"
|
|
121
|
+
><Icon icon={selected_icon} size="small" /></span
|
|
122
|
+
><!--
|
|
123
|
+
Slot containing text for each selectable item
|
|
124
|
+
|
|
125
|
+
Default value: `<span>{option[search_key] || option}</span>`
|
|
126
|
+
--><slot
|
|
127
|
+
search_key={used_search_key}
|
|
128
|
+
{option}>{option[used_search_key] || option}</slot>
|
|
129
|
+
</li>
|
|
130
|
+
{:else}
|
|
131
|
+
<li class="no-options">{no_options_msg}</li>
|
|
132
|
+
{/each}
|
|
133
|
+
</ul>
|
|
134
|
+
</div>
|
|
135
|
+
|
|
136
|
+
<script>
|
|
137
|
+
import { Icon } from "@kws3/ui";
|
|
138
|
+
import { createEventDispatcher, onMount } from "svelte";
|
|
139
|
+
import { createPopper } from "@popperjs/core";
|
|
140
|
+
|
|
141
|
+
const sameWidthPopperModifier = {
|
|
142
|
+
name: "sameWidth",
|
|
143
|
+
enabled: true,
|
|
144
|
+
phase: "beforeWrite",
|
|
145
|
+
requires: ["computeStyles"],
|
|
146
|
+
fn: ({ state }) => {
|
|
147
|
+
state.styles.popper.width = `${Math.max(
|
|
148
|
+
200,
|
|
149
|
+
state.rects.reference.width
|
|
150
|
+
)}px`;
|
|
151
|
+
},
|
|
152
|
+
effect: ({ state }) => {
|
|
153
|
+
state.elements.popper.style.width = `${state.elements.reference.offsetWidth}px`;
|
|
154
|
+
},
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Value of the Input
|
|
159
|
+
*
|
|
160
|
+
* This property can be bound to, to fetch the current value
|
|
161
|
+
*/
|
|
162
|
+
export let value = [];
|
|
163
|
+
/**
|
|
164
|
+
* Maximum number of selectable items from dropdown list.
|
|
165
|
+
*
|
|
166
|
+
* Accepts a `null` value for unlimited selected items.
|
|
167
|
+
* Or a number value
|
|
168
|
+
*/
|
|
169
|
+
export let max = null;
|
|
170
|
+
/**
|
|
171
|
+
* Placeholder text for the input
|
|
172
|
+
*/
|
|
173
|
+
export let placeholder = "Please select...";
|
|
174
|
+
/**
|
|
175
|
+
* Array of strings, or objects.
|
|
176
|
+
* Used to populate the list of options in the dropdown
|
|
177
|
+
*/
|
|
178
|
+
export let options = [];
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* If `options` is an array of objects,
|
|
182
|
+
* this property of each object will be searched
|
|
183
|
+
*/
|
|
184
|
+
export let search_key = "name";
|
|
185
|
+
/**
|
|
186
|
+
* If `options` is an array of objects,
|
|
187
|
+
* this property of each object will be returned as the value
|
|
188
|
+
*/
|
|
189
|
+
export let value_key = "id";
|
|
190
|
+
/**
|
|
191
|
+
* Size of the input
|
|
192
|
+
* @type {''|'small'|'medium'|'large'}
|
|
193
|
+
*/
|
|
194
|
+
export let size = "";
|
|
195
|
+
/**
|
|
196
|
+
* Color of the input
|
|
197
|
+
* @type {''|'primary'|'success'|'warning'|'info'|'danger'|'dark'|'light'}
|
|
198
|
+
*/
|
|
199
|
+
export let color = "";
|
|
200
|
+
/**
|
|
201
|
+
* Inline CSS for input container
|
|
202
|
+
*/
|
|
203
|
+
export let style = "";
|
|
204
|
+
/**
|
|
205
|
+
* Marks component as read-only
|
|
206
|
+
*/
|
|
207
|
+
export let readonly = false;
|
|
208
|
+
/**
|
|
209
|
+
* Disables the component
|
|
210
|
+
*/
|
|
211
|
+
export let disabled = false;
|
|
212
|
+
/**
|
|
213
|
+
* Icon used to mark selected items in dropdown list
|
|
214
|
+
*/
|
|
215
|
+
export let selected_icon = "check";
|
|
216
|
+
/**
|
|
217
|
+
* When activated, it will show the number of selected items.
|
|
218
|
+
*
|
|
219
|
+
* Instead of listing all the selected items inside the input.
|
|
220
|
+
*/
|
|
221
|
+
export let summary_mode = false;
|
|
222
|
+
/**
|
|
223
|
+
* Message to display when no matching options are found
|
|
224
|
+
*/
|
|
225
|
+
export let no_options_msg = "No matching options";
|
|
226
|
+
/**
|
|
227
|
+
* Tooltip text for Remove Item button. This `string` will precede the selected Item Name in the tooltip.
|
|
228
|
+
* */
|
|
229
|
+
export let remove_btn_tip = "Remove";
|
|
230
|
+
/**
|
|
231
|
+
* Tooltip text for the Clear All button
|
|
232
|
+
*/
|
|
233
|
+
export let remove_all_tip = "Remove all";
|
|
234
|
+
|
|
235
|
+
/**
|
|
236
|
+
* CSS classes for input container
|
|
237
|
+
*/
|
|
238
|
+
let klass = "";
|
|
239
|
+
export { klass as class };
|
|
240
|
+
|
|
241
|
+
if (!options || !options.length) console.error(`Missing options`);
|
|
242
|
+
|
|
243
|
+
if (max !== null && max < 0) {
|
|
244
|
+
throw new TypeError(`max must be null or positive integer, got ${max}`);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
//ensure we have a root container for all our hoisitng related stuff
|
|
248
|
+
let rootContainerId = "kws-overlay-root";
|
|
249
|
+
let rootContainer = document.getElementById(rootContainerId);
|
|
250
|
+
if (!rootContainer) {
|
|
251
|
+
rootContainer = document.createElement("div");
|
|
252
|
+
rootContainer.id = rootContainerId;
|
|
253
|
+
document.body.appendChild(rootContainer);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
//this is the container that will hold the dropdown
|
|
257
|
+
let container;
|
|
258
|
+
|
|
259
|
+
const fire = createEventDispatcher();
|
|
260
|
+
|
|
261
|
+
let el, //whole wrapping element
|
|
262
|
+
dropdown, //dropdown ul
|
|
263
|
+
input, //the textbox to type in
|
|
264
|
+
POPPER,
|
|
265
|
+
activeOption = "",
|
|
266
|
+
searchText = "",
|
|
267
|
+
searching = false,
|
|
268
|
+
showOptions = false,
|
|
269
|
+
filteredOptions = [], //list of options filtered by search query
|
|
270
|
+
normalisedOptions = [], //list of options normalised
|
|
271
|
+
selectedOptions = []; //list of options that are selected
|
|
272
|
+
|
|
273
|
+
$: single = max === 1;
|
|
274
|
+
$: hasValue = single
|
|
275
|
+
? value !== null && typeof value != "undefined"
|
|
276
|
+
? true
|
|
277
|
+
: false
|
|
278
|
+
: value && value.length
|
|
279
|
+
? true
|
|
280
|
+
: false;
|
|
281
|
+
$: _placeholder = hasValue ? "" : placeholder;
|
|
282
|
+
|
|
283
|
+
//ensure search_key and value_key are no empty strings
|
|
284
|
+
$: used_search_key = search_key && search_key != "" ? search_key : "name";
|
|
285
|
+
$: used_value_key = value_key && value_key != "" ? value_key : "id";
|
|
286
|
+
|
|
287
|
+
$: shouldShowClearAll = hasValue;
|
|
288
|
+
|
|
289
|
+
$: options, normaliseOptions();
|
|
290
|
+
$: normalisedOptions,
|
|
291
|
+
searchText,
|
|
292
|
+
searching,
|
|
293
|
+
used_search_key,
|
|
294
|
+
used_value_key,
|
|
295
|
+
updateFilteredOptions();
|
|
296
|
+
|
|
297
|
+
$: value, single, fillSelectedOptions();
|
|
298
|
+
|
|
299
|
+
$: if (
|
|
300
|
+
(activeOption && !filteredOptions.includes(activeOption)) ||
|
|
301
|
+
(!activeOption && searchText)
|
|
302
|
+
)
|
|
303
|
+
activeOption = filteredOptions[0];
|
|
304
|
+
|
|
305
|
+
//TODO: optimise isSelected function
|
|
306
|
+
$: isSelected = (option) => {
|
|
307
|
+
if (single) return matchesValue(value, option);
|
|
308
|
+
if (!(value && value.length > 0) || value == "") return false;
|
|
309
|
+
// nothing is selected if `value` is the empty array or string
|
|
310
|
+
else return value.some((v) => matchesValue(v, option));
|
|
311
|
+
};
|
|
312
|
+
|
|
313
|
+
//convert arrays of strings into normalised arrays of objects
|
|
314
|
+
function normaliseOptions() {
|
|
315
|
+
let _items = options || [];
|
|
316
|
+
if (!_items || !(_items instanceof Array)) {
|
|
317
|
+
normalisedOptions = [];
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
normalisedOptions = _items.slice().map((item) => {
|
|
322
|
+
if (typeof item === "object") {
|
|
323
|
+
return item;
|
|
324
|
+
}
|
|
325
|
+
let __obj = {};
|
|
326
|
+
__obj[used_search_key] = item;
|
|
327
|
+
__obj[used_value_key] = item;
|
|
328
|
+
return __obj;
|
|
329
|
+
});
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
function updateFilteredOptions() {
|
|
333
|
+
let filter;
|
|
334
|
+
|
|
335
|
+
//when in single mode, searchText contains the selected value
|
|
336
|
+
//so we need to check if we are actually searching
|
|
337
|
+
if (single && !searching) {
|
|
338
|
+
filter = "";
|
|
339
|
+
} else {
|
|
340
|
+
filter = searchText.toLowerCase();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
filteredOptions = normalisedOptions.slice().filter((item) => {
|
|
344
|
+
// filter out items that don't match `filter`
|
|
345
|
+
if (typeof item === "object") {
|
|
346
|
+
if (used_search_key) {
|
|
347
|
+
if (
|
|
348
|
+
typeof item[used_search_key] === "string" &&
|
|
349
|
+
item[used_search_key].toLowerCase().indexOf(filter) > -1
|
|
350
|
+
)
|
|
351
|
+
return true;
|
|
352
|
+
} else {
|
|
353
|
+
for (var key in item) {
|
|
354
|
+
if (
|
|
355
|
+
typeof item[key] === "string" &&
|
|
356
|
+
item[key].toLowerCase().indexOf(filter) > -1
|
|
357
|
+
)
|
|
358
|
+
return true;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
} else {
|
|
362
|
+
return item.toLowerCase().indexOf(filter) > -1;
|
|
363
|
+
}
|
|
364
|
+
});
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function fillSelectedOptions() {
|
|
368
|
+
if (single) {
|
|
369
|
+
selectedOptions = normalisedOptions.filter(
|
|
370
|
+
(v) => `${v[used_value_key]}` == `${value}`
|
|
371
|
+
);
|
|
372
|
+
setSingleVisibleValue();
|
|
373
|
+
} else {
|
|
374
|
+
selectedOptions = normalisedOptions
|
|
375
|
+
.filter(
|
|
376
|
+
(v) => value && value.some((vl) => `${v[used_value_key]}` == `${vl}`)
|
|
377
|
+
)
|
|
378
|
+
.sort(
|
|
379
|
+
(a, b) =>
|
|
380
|
+
value.indexOf(a[used_value_key]) - value.indexOf(b[used_value_key])
|
|
381
|
+
);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
POPPER && POPPER.update();
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
/**
|
|
388
|
+
* Moves dropdown to rootContainer, so that
|
|
389
|
+
* overflows etc do not mess with it
|
|
390
|
+
*/
|
|
391
|
+
function hoistDropdown() {
|
|
392
|
+
if (!container) {
|
|
393
|
+
container = document.createElement("div");
|
|
394
|
+
container.className = "searchableselect";
|
|
395
|
+
rootContainer.appendChild(container);
|
|
396
|
+
container.appendChild(dropdown);
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
onMount(() => {
|
|
401
|
+
hoistDropdown();
|
|
402
|
+
|
|
403
|
+
POPPER = createPopper(el, dropdown, {
|
|
404
|
+
strategy: "fixed",
|
|
405
|
+
placement: "bottom-start",
|
|
406
|
+
modifiers: [sameWidthPopperModifier],
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
//normalize value for single versus multiselect
|
|
410
|
+
if (value === null || typeof value == "undefined")
|
|
411
|
+
value = single ? null : [];
|
|
412
|
+
|
|
413
|
+
setSingleVisibleValue();
|
|
414
|
+
|
|
415
|
+
return () => {
|
|
416
|
+
POPPER.destroy();
|
|
417
|
+
//remove hoisted items
|
|
418
|
+
container &&
|
|
419
|
+
container.parentNode &&
|
|
420
|
+
container.parentNode.removeChild(container);
|
|
421
|
+
};
|
|
422
|
+
});
|
|
423
|
+
|
|
424
|
+
function add(token) {
|
|
425
|
+
if (readonly || disabled) {
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
let isAlreadySelected = isSelected(token);
|
|
430
|
+
|
|
431
|
+
if (single) {
|
|
432
|
+
if (isAlreadySelected) {
|
|
433
|
+
setSingleVisibleValue();
|
|
434
|
+
} else {
|
|
435
|
+
value = token[used_value_key];
|
|
436
|
+
input && input.blur();
|
|
437
|
+
setOptionsVisible(false);
|
|
438
|
+
fire("change", { token, type: `add` });
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
if (!isAlreadySelected && !single && (max === null || value.length < max)) {
|
|
443
|
+
//attach to value array while filtering out invalid values
|
|
444
|
+
value = [...value, token[used_value_key]].filter((v) => {
|
|
445
|
+
return normalisedOptions.filter((nv) => nv[used_value_key] == v).length;
|
|
446
|
+
});
|
|
447
|
+
searchText = ""; // reset search string on selection
|
|
448
|
+
|
|
449
|
+
if (value && value.length && value.length === max) {
|
|
450
|
+
input && input.blur();
|
|
451
|
+
setOptionsVisible(false);
|
|
452
|
+
}
|
|
453
|
+
/**
|
|
454
|
+
* Triggered when an item is added from dropdown list
|
|
455
|
+
*/
|
|
456
|
+
fire("add", { token });
|
|
457
|
+
|
|
458
|
+
fire("change", { token, type: `add` });
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
function remove(token) {
|
|
463
|
+
if (readonly || disabled || single) return;
|
|
464
|
+
value = value.filter
|
|
465
|
+
? value.filter((item) => !matchesValue(item, token))
|
|
466
|
+
: value;
|
|
467
|
+
/**
|
|
468
|
+
* Triggered when an item is removed from selected Items
|
|
469
|
+
*/
|
|
470
|
+
fire("remove", { token });
|
|
471
|
+
/**
|
|
472
|
+
* Triggered when the value changes
|
|
473
|
+
*/
|
|
474
|
+
fire("change", { token, type: `remove` });
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
function blurEvent() {
|
|
478
|
+
/**
|
|
479
|
+
* Triggered when the input loses focus
|
|
480
|
+
*/
|
|
481
|
+
fire("blur");
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
function setOptionsVisible(show) {
|
|
485
|
+
// nothing to do if visibility is already as intended
|
|
486
|
+
if (readonly || disabled || show === showOptions) return;
|
|
487
|
+
showOptions = show;
|
|
488
|
+
if (show) {
|
|
489
|
+
input && input.focus();
|
|
490
|
+
}
|
|
491
|
+
POPPER && POPPER.update();
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
function setSingleVisibleValue() {
|
|
495
|
+
if (single && hasValue) {
|
|
496
|
+
searchText =
|
|
497
|
+
selectedOptions && selectedOptions[0]
|
|
498
|
+
? selectedOptions[0][used_search_key]
|
|
499
|
+
: "";
|
|
500
|
+
searching = false;
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function handleKeydown(event) {
|
|
505
|
+
if (event.key === `Escape`) {
|
|
506
|
+
if (!single) {
|
|
507
|
+
searchText = "";
|
|
508
|
+
}
|
|
509
|
+
} else {
|
|
510
|
+
setOptionsVisible(true);
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (event.key === `Enter`) {
|
|
514
|
+
if (activeOption) {
|
|
515
|
+
handleOptionMouseDown(activeOption);
|
|
516
|
+
if (!single) {
|
|
517
|
+
searchText = "";
|
|
518
|
+
}
|
|
519
|
+
} else {
|
|
520
|
+
// no active option means the options are closed in which case enter means open
|
|
521
|
+
setOptionsVisible(true);
|
|
522
|
+
}
|
|
523
|
+
} else if ([`ArrowDown`, `ArrowUp`].includes(event.key)) {
|
|
524
|
+
const increment = event.key === `ArrowUp` ? -1 : 1;
|
|
525
|
+
const newActiveIdx = filteredOptions.indexOf(activeOption) + increment;
|
|
526
|
+
|
|
527
|
+
if (newActiveIdx < 0) {
|
|
528
|
+
activeOption = filteredOptions[filteredOptions.length - 1];
|
|
529
|
+
} else {
|
|
530
|
+
if (newActiveIdx === filteredOptions.length)
|
|
531
|
+
activeOption = filteredOptions[0];
|
|
532
|
+
else activeOption = filteredOptions[newActiveIdx];
|
|
533
|
+
}
|
|
534
|
+
} else if (event.key === `Backspace`) {
|
|
535
|
+
// only remove selected tags on backspace if there are any and no searchText characters remain
|
|
536
|
+
if (searchText.length === 0) {
|
|
537
|
+
if (single) {
|
|
538
|
+
if (value) {
|
|
539
|
+
value = null;
|
|
540
|
+
}
|
|
541
|
+
} else {
|
|
542
|
+
if (value && value.length > 0) {
|
|
543
|
+
value = value.slice(0, value.length - 1);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
} else {
|
|
547
|
+
if (single) {
|
|
548
|
+
searching = true;
|
|
549
|
+
}
|
|
550
|
+
}
|
|
551
|
+
} else {
|
|
552
|
+
if (single) {
|
|
553
|
+
searching = true;
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
function handleOptionMouseDown(option) {
|
|
559
|
+
if (single) {
|
|
560
|
+
add(option);
|
|
561
|
+
} else {
|
|
562
|
+
isSelected(option) ? remove(option) : add(option);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const removeAll = () => {
|
|
567
|
+
fire("remove", { token: value });
|
|
568
|
+
fire("change", { token: value, type: `remove` });
|
|
569
|
+
value = single ? null : [];
|
|
570
|
+
searchText = "";
|
|
571
|
+
};
|
|
572
|
+
|
|
573
|
+
const matchesValue = (_value, _option) => {
|
|
574
|
+
if (_value === null || typeof _value == "undefined") {
|
|
575
|
+
return false;
|
|
576
|
+
}
|
|
577
|
+
return (
|
|
578
|
+
`${_value[used_value_key] || _value}` === `${_option[used_value_key]}`
|
|
579
|
+
);
|
|
580
|
+
};
|
|
581
|
+
</script>
|