svelte-multiselect 8.6.0 → 8.6.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/dist/CmdPalette.svelte +2 -2
- package/dist/CmdPalette.svelte.d.ts +2 -2
- package/dist/MultiSelect.svelte +69 -54
- package/dist/MultiSelect.svelte.d.ts +10 -8
- package/dist/index.d.ts +2 -52
- package/dist/index.js +8 -7
- package/dist/types.d.ts +51 -0
- package/dist/types.js +1 -0
- package/package.json +22 -22
- package/readme.md +15 -11
package/dist/CmdPalette.svelte
CHANGED
|
@@ -10,8 +10,8 @@ export let style = ``; // for dialog
|
|
|
10
10
|
// for span in option slot, has no effect when passing slot="option"
|
|
11
11
|
export let span_style = ``;
|
|
12
12
|
export let open = false;
|
|
13
|
-
export let dialog;
|
|
14
|
-
export let input;
|
|
13
|
+
export let dialog = null;
|
|
14
|
+
export let input = null;
|
|
15
15
|
export let placeholder = `Filter actions...`;
|
|
16
16
|
async function toggle(event) {
|
|
17
17
|
if (event.key === trigger && event.metaKey && !open) {
|
|
@@ -11,8 +11,8 @@ declare const __propDef: {
|
|
|
11
11
|
style?: string | undefined;
|
|
12
12
|
span_style?: string | undefined;
|
|
13
13
|
open?: boolean | undefined;
|
|
14
|
-
dialog
|
|
15
|
-
input
|
|
14
|
+
dialog?: HTMLDialogElement | null | undefined;
|
|
15
|
+
input?: HTMLInputElement | null | undefined;
|
|
16
16
|
placeholder?: string | undefined;
|
|
17
17
|
};
|
|
18
18
|
events: {
|
package/dist/MultiSelect.svelte
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
<script>import { createEventDispatcher, tick } from 'svelte';
|
|
2
2
|
import { flip } from 'svelte/animate';
|
|
3
|
-
import
|
|
3
|
+
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
|
+
import Wiggle from './Wiggle.svelte';
|
|
4
5
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
5
6
|
export let activeIndex = null;
|
|
6
7
|
export let activeOption = null;
|
|
@@ -63,7 +64,7 @@ export let ulOptionsClass = ``;
|
|
|
63
64
|
export let ulSelectedClass = ``;
|
|
64
65
|
export let value = null;
|
|
65
66
|
// get the label key from an option object or the option itself if it's a string or number
|
|
66
|
-
const get_label = (op) => {
|
|
67
|
+
export const get_label = (op) => {
|
|
67
68
|
if (op instanceof Object) {
|
|
68
69
|
if (op.label === undefined) {
|
|
69
70
|
console.error(`MultiSelect option ${JSON.stringify(op)} is an object but has no label key`);
|
|
@@ -100,13 +101,18 @@ if (parseLabelsAsHtml && allowUserOptions) {
|
|
|
100
101
|
console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
|
|
101
102
|
}
|
|
102
103
|
if (sortSelected && selectedOptionsDraggable) {
|
|
103
|
-
console.warn(`MultiSelect's sortSelected and selectedOptionsDraggable should not be combined as any
|
|
104
|
+
console.warn(`MultiSelect's sortSelected and selectedOptionsDraggable should not be combined as any ` +
|
|
105
|
+
`user re-orderings of selected options will be undone by sortSelected on component re-renders.`);
|
|
106
|
+
}
|
|
107
|
+
if (allowUserOptions && !createOptionMsg && createOptionMsg !== null) {
|
|
108
|
+
console.error(`MultiSelect has allowUserOptions=${allowUserOptions} but createOptionMsg=${createOptionMsg} is falsy. ` +
|
|
109
|
+
`This prevents the "Add option" <span> from showing up, resulting in a confusing user experience.`);
|
|
104
110
|
}
|
|
105
111
|
const dispatch = createEventDispatcher();
|
|
106
|
-
let
|
|
112
|
+
let option_msg_is_active = false; // controls active state of <li>{createOptionMsg}</li>
|
|
107
113
|
let window_width;
|
|
108
114
|
// options matching the current search text
|
|
109
|
-
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.
|
|
115
|
+
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.includes(op) // remove already selected options from dropdown list
|
|
110
116
|
);
|
|
111
117
|
// raise if matchingOptions[activeIndex] does not yield a value
|
|
112
118
|
if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
@@ -115,17 +121,17 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
|
115
121
|
// update activeOption when activeIndex changes
|
|
116
122
|
$: activeOption = matchingOptions[activeIndex ?? -1] ?? null;
|
|
117
123
|
// add an option to selected list
|
|
118
|
-
function add(
|
|
124
|
+
function add(option, event) {
|
|
119
125
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
120
126
|
wiggle = true;
|
|
121
|
-
if (!isNaN(Number(
|
|
122
|
-
|
|
123
|
-
|
|
127
|
+
if (!isNaN(Number(option)) && typeof selected.map(get_label)[0] === `number`) {
|
|
128
|
+
option = Number(option); // convert to number if possible
|
|
129
|
+
}
|
|
130
|
+
const is_duplicate = selected.some((op) => duplicateFunc(op, option));
|
|
124
131
|
if ((maxSelect === null || maxSelect === 1 || selected.length < maxSelect) &&
|
|
125
132
|
(duplicates || !is_duplicate)) {
|
|
126
|
-
// first check if we find option in the options list
|
|
127
|
-
|
|
128
|
-
if (!option && // this has the side-effect of not allowing to user to add the same
|
|
133
|
+
if (!options.includes(option) && // first check if we find option in the options list
|
|
134
|
+
// this has the side-effect of not allowing to user to add the same
|
|
129
135
|
// custom option twice in append mode
|
|
130
136
|
[true, `append`].includes(allowUserOptions) &&
|
|
131
137
|
searchText.length > 0) {
|
|
@@ -149,13 +155,10 @@ function add(label, event) {
|
|
|
149
155
|
if (allowUserOptions === `append`)
|
|
150
156
|
options = [...options, option];
|
|
151
157
|
}
|
|
152
|
-
if (option === undefined) {
|
|
153
|
-
throw `Run time error, option with label ${label} not found in options list`;
|
|
154
|
-
}
|
|
155
158
|
if (resetFilterOnAdd)
|
|
156
159
|
searchText = ``; // reset search string on selection
|
|
157
160
|
if ([``, undefined, null].includes(option)) {
|
|
158
|
-
console.error(`MultiSelect: encountered
|
|
161
|
+
console.error(`MultiSelect: encountered falsy option ${option}`);
|
|
159
162
|
return;
|
|
160
163
|
}
|
|
161
164
|
if (maxSelect === 1) {
|
|
@@ -188,20 +191,22 @@ function add(label, event) {
|
|
|
188
191
|
}
|
|
189
192
|
}
|
|
190
193
|
// remove an option from selected list
|
|
191
|
-
function remove(
|
|
194
|
+
function remove(to_remove) {
|
|
192
195
|
if (selected.length === 0)
|
|
193
196
|
return;
|
|
194
|
-
|
|
197
|
+
const idx = selected.findIndex((op) => JSON.stringify(op) === JSON.stringify(to_remove));
|
|
198
|
+
let [option] = selected.splice(idx, 1); // remove option from selected list
|
|
195
199
|
if (option === undefined && allowUserOptions) {
|
|
196
200
|
// if option with label could not be found but allowUserOptions is truthy,
|
|
197
201
|
// assume it was created by user and create corresponding option object
|
|
198
202
|
// on the fly for use as event payload
|
|
199
|
-
|
|
203
|
+
const other_ops_type = typeof options[0];
|
|
204
|
+
option = (other_ops_type ? { label: to_remove } : to_remove);
|
|
200
205
|
}
|
|
201
206
|
if (option === undefined) {
|
|
202
|
-
return console.error(`Multiselect can't remove selected option ${
|
|
207
|
+
return console.error(`Multiselect can't remove selected option ${JSON.stringify(to_remove)}, not found in selected list`);
|
|
203
208
|
}
|
|
204
|
-
selected = selected
|
|
209
|
+
selected = [...selected]; // trigger Svelte rerender
|
|
205
210
|
invalid = false; // reset error status whenever items are removed
|
|
206
211
|
form_input?.setCustomValidity(``);
|
|
207
212
|
dispatch(`remove`, { option });
|
|
@@ -234,8 +239,7 @@ async function handle_keydown(event) {
|
|
|
234
239
|
else if (event.key === `Enter`) {
|
|
235
240
|
event.preventDefault(); // prevent enter key from triggering form submission
|
|
236
241
|
if (activeOption) {
|
|
237
|
-
|
|
238
|
-
selected.map(get_label).includes(label) ? remove(label) : add(label, event);
|
|
242
|
+
selected.includes(activeOption) ? remove(activeOption) : add(activeOption, event);
|
|
239
243
|
searchText = ``;
|
|
240
244
|
}
|
|
241
245
|
else if (allowUserOptions && searchText.length > 0) {
|
|
@@ -257,7 +261,7 @@ async function handle_keydown(event) {
|
|
|
257
261
|
else if (allowUserOptions && !matchingOptions.length && searchText.length > 0) {
|
|
258
262
|
// if allowUserOptions is truthy and user entered text but no options match, we make
|
|
259
263
|
// <li>{addUserMsg}</li> active on keydown (or toggle it if already active)
|
|
260
|
-
|
|
264
|
+
option_msg_is_active = !option_msg_is_active;
|
|
261
265
|
return;
|
|
262
266
|
}
|
|
263
267
|
else if (activeIndex === null) {
|
|
@@ -282,7 +286,7 @@ async function handle_keydown(event) {
|
|
|
282
286
|
}
|
|
283
287
|
// on backspace key: remove last selected option
|
|
284
288
|
else if (event.key === `Backspace` && selected.length > 0 && !searchText) {
|
|
285
|
-
remove(selected.
|
|
289
|
+
remove(selected.at(-1));
|
|
286
290
|
}
|
|
287
291
|
// make first matching option active on any keypress (if none of the above special cases match)
|
|
288
292
|
else if (matchingOptions.length > 0) {
|
|
@@ -344,7 +348,14 @@ function highlight_matching_options(event) {
|
|
|
344
348
|
const query = event?.target?.value.trim().toLowerCase();
|
|
345
349
|
if (!query)
|
|
346
350
|
return;
|
|
347
|
-
const tree_walker = document.createTreeWalker(ul_options, NodeFilter.SHOW_TEXT
|
|
351
|
+
const tree_walker = document.createTreeWalker(ul_options, NodeFilter.SHOW_TEXT, {
|
|
352
|
+
acceptNode: (node) => {
|
|
353
|
+
// don't highlight text in the "no matching options" message
|
|
354
|
+
if (node?.textContent === noMatchingOptionsMsg)
|
|
355
|
+
return NodeFilter.FILTER_REJECT;
|
|
356
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
357
|
+
},
|
|
358
|
+
});
|
|
348
359
|
const text_nodes = [];
|
|
349
360
|
let current_node = tree_walker.nextNode();
|
|
350
361
|
while (current_node) {
|
|
@@ -353,10 +364,10 @@ function highlight_matching_options(event) {
|
|
|
353
364
|
}
|
|
354
365
|
// iterate over all text nodes and find matches
|
|
355
366
|
const ranges = text_nodes.map((el) => {
|
|
356
|
-
const text = el.textContent
|
|
367
|
+
const text = el.textContent?.toLowerCase();
|
|
357
368
|
const indices = [];
|
|
358
369
|
let start_pos = 0;
|
|
359
|
-
while (start_pos < text.length) {
|
|
370
|
+
while (text && start_pos < text.length) {
|
|
360
371
|
const index = text.indexOf(query, start_pos);
|
|
361
372
|
if (index === -1)
|
|
362
373
|
break;
|
|
@@ -373,7 +384,7 @@ function highlight_matching_options(event) {
|
|
|
373
384
|
});
|
|
374
385
|
// create Highlight object from ranges and add to registry
|
|
375
386
|
// eslint-disable-next-line no-undef
|
|
376
|
-
CSS.highlights.set(`search-
|
|
387
|
+
CSS.highlights.set(`sms-search-matches`, new Highlight(...ranges.flat()));
|
|
377
388
|
}
|
|
378
389
|
</script>
|
|
379
390
|
|
|
@@ -398,7 +409,7 @@ function highlight_matching_options(event) {
|
|
|
398
409
|
<input
|
|
399
410
|
{name}
|
|
400
411
|
required={Boolean(required)}
|
|
401
|
-
value={selected.length >= required ? JSON.stringify(selected) : null}
|
|
412
|
+
value={selected.length >= Number(required) ? JSON.stringify(selected) : null}
|
|
402
413
|
tabindex="-1"
|
|
403
414
|
aria-hidden="true"
|
|
404
415
|
aria-label="ignore this, used only to prevent form submission if select is required but empty"
|
|
@@ -407,9 +418,9 @@ function highlight_matching_options(event) {
|
|
|
407
418
|
on:invalid={() => {
|
|
408
419
|
invalid = true
|
|
409
420
|
let msg
|
|
410
|
-
if (maxSelect && maxSelect > 1 && required > 1) {
|
|
421
|
+
if (maxSelect && maxSelect > 1 && Number(required) > 1) {
|
|
411
422
|
msg = `Please select between ${required} and ${maxSelect} options`
|
|
412
|
-
} else if (required > 1) {
|
|
423
|
+
} else if (Number(required) > 1) {
|
|
413
424
|
msg = `Please select at least ${required} options`
|
|
414
425
|
} else {
|
|
415
426
|
msg = `Please select an option`
|
|
@@ -421,7 +432,7 @@ function highlight_matching_options(event) {
|
|
|
421
432
|
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt; cursor: pointer;" />
|
|
422
433
|
</slot>
|
|
423
434
|
<ul class="selected {ulSelectedClass}" aria-label="selected options">
|
|
424
|
-
{#each selected as option, idx (
|
|
435
|
+
{#each selected as option, idx (option)}
|
|
425
436
|
<li
|
|
426
437
|
class={liSelectedClass}
|
|
427
438
|
animate:flip={{ duration: 100 }}
|
|
@@ -441,8 +452,8 @@ function highlight_matching_options(event) {
|
|
|
441
452
|
</slot>
|
|
442
453
|
{#if !disabled && (minSelect === null || selected.length > minSelect)}
|
|
443
454
|
<button
|
|
444
|
-
on:mouseup|stopPropagation={() => remove(
|
|
445
|
-
on:keydown={if_enter_or_space(() => remove(
|
|
455
|
+
on:mouseup|stopPropagation={() => remove(option)}
|
|
456
|
+
on:keydown={if_enter_or_space(() => remove(option))}
|
|
446
457
|
type="button"
|
|
447
458
|
title="{removeBtnTitle} {get_label(option)}"
|
|
448
459
|
class="remove"
|
|
@@ -533,7 +544,7 @@ function highlight_matching_options(event) {
|
|
|
533
544
|
<li
|
|
534
545
|
on:mousedown|stopPropagation
|
|
535
546
|
on:mouseup|stopPropagation={(event) => {
|
|
536
|
-
if (!disabled) add(
|
|
547
|
+
if (!disabled) add(option, event)
|
|
537
548
|
}}
|
|
538
549
|
title={disabled
|
|
539
550
|
? disabledTitle
|
|
@@ -560,24 +571,30 @@ function highlight_matching_options(event) {
|
|
|
560
571
|
</slot>
|
|
561
572
|
</li>
|
|
562
573
|
{:else}
|
|
563
|
-
{
|
|
574
|
+
{@const search_is_duplicate = selected.some((option) =>
|
|
575
|
+
duplicateFunc(option, searchText)
|
|
576
|
+
)}
|
|
577
|
+
{@const msg =
|
|
578
|
+
!duplicates && search_is_duplicate ? duplicateOptionMsg : createOptionMsg}
|
|
579
|
+
{#if allowUserOptions && searchText && msg}
|
|
564
580
|
<li
|
|
565
581
|
on:mousedown|stopPropagation
|
|
566
582
|
on:mouseup|stopPropagation={(event) => add(searchText, event)}
|
|
567
583
|
title={createOptionMsg}
|
|
568
|
-
class:active={
|
|
569
|
-
on:mouseover={() => (
|
|
570
|
-
on:focus={() => (
|
|
571
|
-
on:mouseout={() => (
|
|
572
|
-
on:blur={() => (
|
|
584
|
+
class:active={option_msg_is_active}
|
|
585
|
+
on:mouseover={() => (option_msg_is_active = true)}
|
|
586
|
+
on:focus={() => (option_msg_is_active = true)}
|
|
587
|
+
on:mouseout={() => (option_msg_is_active = false)}
|
|
588
|
+
on:blur={() => (option_msg_is_active = false)}
|
|
589
|
+
class="user-msg"
|
|
573
590
|
>
|
|
574
|
-
{
|
|
575
|
-
? duplicateOptionMsg
|
|
576
|
-
: createOptionMsg}
|
|
591
|
+
{msg}
|
|
577
592
|
</li>
|
|
578
|
-
{:else}
|
|
579
|
-
|
|
593
|
+
{:else if noMatchingOptionsMsg}
|
|
594
|
+
<!-- use span to not have cursor: pointer -->
|
|
595
|
+
<span class="user-msg">{noMatchingOptionsMsg}</span>
|
|
580
596
|
{/if}
|
|
597
|
+
<!-- Show nothing if all messages are empty -->
|
|
581
598
|
{/each}
|
|
582
599
|
</ul>
|
|
583
600
|
{/if}
|
|
@@ -721,8 +738,9 @@ function highlight_matching_options(event) {
|
|
|
721
738
|
cursor: pointer;
|
|
722
739
|
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
723
740
|
}
|
|
724
|
-
|
|
725
|
-
|
|
741
|
+
:where(div.multiselect > ul.options .user-msg) {
|
|
742
|
+
/* block needed so vertical padding applies to span */
|
|
743
|
+
display: block;
|
|
726
744
|
padding: 3pt 2ex;
|
|
727
745
|
}
|
|
728
746
|
:where(div.multiselect > ul.options > li.selected) {
|
|
@@ -741,10 +759,7 @@ function highlight_matching_options(event) {
|
|
|
741
759
|
:where(span.max-select-msg) {
|
|
742
760
|
padding: 0 3pt;
|
|
743
761
|
}
|
|
744
|
-
::highlight(search-
|
|
745
|
-
color:
|
|
746
|
-
background: var(--sms-highlight-bg);
|
|
747
|
-
text-decoration: var(--sms-highlight-text-decoration);
|
|
748
|
-
text-decoration-color: var(--sms-highlight-text-decoration-color);
|
|
762
|
+
::highlight(sms-search-matches) {
|
|
763
|
+
color: mediumaquamarine;
|
|
749
764
|
}
|
|
750
765
|
</style>
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import type { MultiSelectEvents, Option as
|
|
3
|
-
declare class __sveltets_Render<Option extends
|
|
2
|
+
import type { MultiSelectEvents, Option as T } from './types';
|
|
3
|
+
declare class __sveltets_Render<Option extends T> {
|
|
4
4
|
props(): {
|
|
5
5
|
activeIndex?: number | null | undefined;
|
|
6
6
|
activeOption?: Option | null | undefined;
|
|
7
|
-
createOptionMsg?: string | undefined;
|
|
7
|
+
createOptionMsg?: string | null | undefined;
|
|
8
8
|
allowUserOptions?: boolean | "append" | undefined;
|
|
9
9
|
allowEmpty?: boolean | undefined;
|
|
10
10
|
autocomplete?: string | undefined;
|
|
@@ -13,7 +13,7 @@ declare class __sveltets_Render<Option extends GenericOption> {
|
|
|
13
13
|
defaultDisabledTitle?: string | undefined;
|
|
14
14
|
disabled?: boolean | undefined;
|
|
15
15
|
disabledInputTitle?: string | undefined;
|
|
16
|
-
duplicateFunc?: ((op1:
|
|
16
|
+
duplicateFunc?: ((op1: T, op2: T) => boolean) | undefined;
|
|
17
17
|
duplicateOptionMsg?: string | undefined;
|
|
18
18
|
duplicates?: boolean | undefined;
|
|
19
19
|
filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
|
|
@@ -54,6 +54,7 @@ declare class __sveltets_Render<Option extends GenericOption> {
|
|
|
54
54
|
ulOptionsClass?: string | undefined;
|
|
55
55
|
ulSelectedClass?: string | undefined;
|
|
56
56
|
value?: Option | Option[] | null | undefined;
|
|
57
|
+
get_label?: ((op: T) => string | number) | undefined;
|
|
57
58
|
};
|
|
58
59
|
events(): MultiSelectEvents;
|
|
59
60
|
slots(): {
|
|
@@ -73,9 +74,10 @@ declare class __sveltets_Render<Option extends GenericOption> {
|
|
|
73
74
|
};
|
|
74
75
|
};
|
|
75
76
|
}
|
|
76
|
-
export type MultiSelectProps<Option extends
|
|
77
|
-
export type MultiSelectEvents<Option extends
|
|
78
|
-
export type MultiSelectSlots<Option extends
|
|
79
|
-
export default class MultiSelect<Option extends
|
|
77
|
+
export type MultiSelectProps<Option extends T> = ReturnType<__sveltets_Render<Option>['props']>;
|
|
78
|
+
export type MultiSelectEvents<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
|
|
79
|
+
export type MultiSelectSlots<Option extends T> = ReturnType<__sveltets_Render<Option>['slots']>;
|
|
80
|
+
export default class MultiSelect<Option extends T> extends SvelteComponentTyped<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
|
|
81
|
+
get get_label(): (op: T) => string | number;
|
|
80
82
|
}
|
|
81
83
|
export {};
|
package/dist/index.d.ts
CHANGED
|
@@ -1,56 +1,6 @@
|
|
|
1
1
|
export { default as CircleSpinner } from './CircleSpinner.svelte';
|
|
2
2
|
export { default as CmdPalette } from './CmdPalette.svelte';
|
|
3
|
-
export { default
|
|
3
|
+
export { default as MultiSelect, default } from './MultiSelect.svelte';
|
|
4
4
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
5
|
-
export
|
|
6
|
-
export type ObjectOption = {
|
|
7
|
-
label: string | number;
|
|
8
|
-
value?: unknown;
|
|
9
|
-
title?: string;
|
|
10
|
-
disabled?: boolean;
|
|
11
|
-
preselected?: boolean;
|
|
12
|
-
disabledTitle?: string;
|
|
13
|
-
selectedTitle?: string;
|
|
14
|
-
[key: string]: unknown;
|
|
15
|
-
};
|
|
16
|
-
export type DispatchEvents<T = Option> = {
|
|
17
|
-
add: {
|
|
18
|
-
option: T;
|
|
19
|
-
};
|
|
20
|
-
create: {
|
|
21
|
-
option: T;
|
|
22
|
-
};
|
|
23
|
-
remove: {
|
|
24
|
-
option: T;
|
|
25
|
-
};
|
|
26
|
-
removeAll: {
|
|
27
|
-
options: T[];
|
|
28
|
-
};
|
|
29
|
-
change: {
|
|
30
|
-
option?: T;
|
|
31
|
-
options?: T[];
|
|
32
|
-
type: 'add' | 'remove' | 'removeAll';
|
|
33
|
-
};
|
|
34
|
-
open: {
|
|
35
|
-
event: Event;
|
|
36
|
-
};
|
|
37
|
-
close: {
|
|
38
|
-
event: Event;
|
|
39
|
-
};
|
|
40
|
-
};
|
|
41
|
-
export type MultiSelectEvents = {
|
|
42
|
-
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
43
|
-
} & {
|
|
44
|
-
blur: FocusEvent;
|
|
45
|
-
click: MouseEvent;
|
|
46
|
-
focus: FocusEvent;
|
|
47
|
-
keydown: KeyboardEvent;
|
|
48
|
-
keyup: KeyboardEvent;
|
|
49
|
-
mouseenter: MouseEvent;
|
|
50
|
-
mouseleave: MouseEvent;
|
|
51
|
-
touchcancel: TouchEvent;
|
|
52
|
-
touchend: TouchEvent;
|
|
53
|
-
touchmove: TouchEvent;
|
|
54
|
-
touchstart: TouchEvent;
|
|
55
|
-
};
|
|
5
|
+
export * from './types';
|
|
56
6
|
export declare function scroll_into_view_if_needed_polyfill(centerIfNeeded?: boolean): IntersectionObserver;
|
package/dist/index.js
CHANGED
|
@@ -1,26 +1,27 @@
|
|
|
1
1
|
export { default as CircleSpinner } from './CircleSpinner.svelte';
|
|
2
2
|
export { default as CmdPalette } from './CmdPalette.svelte';
|
|
3
|
-
export { default
|
|
3
|
+
export { default as MultiSelect, default } from './MultiSelect.svelte';
|
|
4
4
|
export { default as Wiggle } from './Wiggle.svelte';
|
|
5
|
-
|
|
6
|
-
// https://
|
|
7
|
-
//
|
|
5
|
+
export * from './types';
|
|
6
|
+
// Firefox lacks support for scrollIntoViewIfNeeded (https://caniuse.com/scrollintoviewifneeded).
|
|
7
|
+
// See https://github.com/janosh/svelte-multiselect/issues/87
|
|
8
|
+
// Polyfill copied from
|
|
8
9
|
// https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
|
|
9
10
|
// exported for testing
|
|
10
11
|
export function scroll_into_view_if_needed_polyfill(centerIfNeeded = true) {
|
|
11
|
-
const
|
|
12
|
+
const elem = this;
|
|
12
13
|
const observer = new IntersectionObserver(function ([entry]) {
|
|
13
14
|
const ratio = entry.intersectionRatio;
|
|
14
15
|
if (ratio < 1) {
|
|
15
16
|
const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`;
|
|
16
|
-
|
|
17
|
+
elem.scrollIntoView({
|
|
17
18
|
block: place,
|
|
18
19
|
inline: place,
|
|
19
20
|
});
|
|
20
21
|
}
|
|
21
22
|
this.disconnect();
|
|
22
23
|
});
|
|
23
|
-
observer.observe(
|
|
24
|
+
observer.observe(elem);
|
|
24
25
|
return observer; // return for testing
|
|
25
26
|
}
|
|
26
27
|
if (typeof Element !== `undefined` &&
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
export type Option = string | number | ObjectOption;
|
|
2
|
+
export type ObjectOption = {
|
|
3
|
+
label: string | number;
|
|
4
|
+
value?: unknown;
|
|
5
|
+
title?: string;
|
|
6
|
+
disabled?: boolean;
|
|
7
|
+
preselected?: boolean;
|
|
8
|
+
disabledTitle?: string;
|
|
9
|
+
selectedTitle?: string;
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
};
|
|
12
|
+
export type DispatchEvents<T = Option> = {
|
|
13
|
+
add: {
|
|
14
|
+
option: T;
|
|
15
|
+
};
|
|
16
|
+
create: {
|
|
17
|
+
option: T;
|
|
18
|
+
};
|
|
19
|
+
remove: {
|
|
20
|
+
option: T;
|
|
21
|
+
};
|
|
22
|
+
removeAll: {
|
|
23
|
+
options: T[];
|
|
24
|
+
};
|
|
25
|
+
change: {
|
|
26
|
+
option?: T;
|
|
27
|
+
options?: T[];
|
|
28
|
+
type: 'add' | 'remove' | 'removeAll';
|
|
29
|
+
};
|
|
30
|
+
open: {
|
|
31
|
+
event: Event;
|
|
32
|
+
};
|
|
33
|
+
close: {
|
|
34
|
+
event: Event;
|
|
35
|
+
};
|
|
36
|
+
};
|
|
37
|
+
export type MultiSelectEvents = {
|
|
38
|
+
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
39
|
+
} & {
|
|
40
|
+
blur: FocusEvent;
|
|
41
|
+
click: MouseEvent;
|
|
42
|
+
focus: FocusEvent;
|
|
43
|
+
keydown: KeyboardEvent;
|
|
44
|
+
keyup: KeyboardEvent;
|
|
45
|
+
mouseenter: MouseEvent;
|
|
46
|
+
mouseleave: MouseEvent;
|
|
47
|
+
touchcancel: TouchEvent;
|
|
48
|
+
touchend: TouchEvent;
|
|
49
|
+
touchmove: TouchEvent;
|
|
50
|
+
touchstart: TouchEvent;
|
|
51
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"homepage": "https://janosh.github.io/svelte-multiselect",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "8.6.
|
|
8
|
+
"version": "8.6.2",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "./dist/index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
@@ -23,37 +23,37 @@
|
|
|
23
23
|
"update-coverage": "vitest tests/unit --run --coverage && npx istanbul-badges-readme"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"svelte": "^3.
|
|
26
|
+
"svelte": "^3.59.1"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@iconify/svelte": "^3.1.
|
|
30
|
-
"@playwright/test": "^1.
|
|
31
|
-
"@sveltejs/adapter-static": "^2.0.
|
|
32
|
-
"@sveltejs/kit": "^1.
|
|
29
|
+
"@iconify/svelte": "^3.1.3",
|
|
30
|
+
"@playwright/test": "^1.33.0",
|
|
31
|
+
"@sveltejs/adapter-static": "^2.0.2",
|
|
32
|
+
"@sveltejs/kit": "^1.16.3",
|
|
33
33
|
"@sveltejs/package": "2.0.2",
|
|
34
|
-
"@sveltejs/vite-plugin-svelte": "^2.0
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
36
|
-
"@typescript-eslint/parser": "^5.
|
|
37
|
-
"@vitest/coverage-c8": "^0.
|
|
38
|
-
"eslint": "^8.
|
|
34
|
+
"@sveltejs/vite-plugin-svelte": "^2.2.0",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^5.59.5",
|
|
36
|
+
"@typescript-eslint/parser": "^5.59.5",
|
|
37
|
+
"@vitest/coverage-c8": "^0.31.0",
|
|
38
|
+
"eslint": "^8.40.0",
|
|
39
39
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
40
40
|
"hastscript": "^7.2.0",
|
|
41
|
-
"highlight.js": "^11.
|
|
42
|
-
"jsdom": "^
|
|
41
|
+
"highlight.js": "^11.8.0",
|
|
42
|
+
"jsdom": "^22.0.0",
|
|
43
43
|
"mdsvex": "^0.10.6",
|
|
44
44
|
"mdsvexamples": "^0.3.3",
|
|
45
|
-
"prettier": "^2.8.
|
|
46
|
-
"prettier-plugin-svelte": "^2.
|
|
45
|
+
"prettier": "^2.8.8",
|
|
46
|
+
"prettier-plugin-svelte": "^2.10.0",
|
|
47
47
|
"rehype-autolink-headings": "^6.1.1",
|
|
48
48
|
"rehype-slug": "^5.1.0",
|
|
49
|
-
"svelte-check": "^3.
|
|
49
|
+
"svelte-check": "^3.3.2",
|
|
50
50
|
"svelte-preprocess": "^5.0.3",
|
|
51
|
-
"svelte-toc": "^0.5.
|
|
52
|
-
"svelte-zoo": "^0.4.
|
|
53
|
-
"svelte2tsx": "^0.6.
|
|
54
|
-
"typescript": "5.0.
|
|
55
|
-
"vite": "^4.
|
|
56
|
-
"vitest": "^0.
|
|
51
|
+
"svelte-toc": "^0.5.5",
|
|
52
|
+
"svelte-zoo": "^0.4.5",
|
|
53
|
+
"svelte2tsx": "^0.6.14",
|
|
54
|
+
"typescript": "5.0.4",
|
|
55
|
+
"vite": "^4.3.6",
|
|
56
|
+
"vitest": "^0.31.0"
|
|
57
57
|
},
|
|
58
58
|
"keywords": [
|
|
59
59
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
|
|
52
52
|
```sh
|
|
53
53
|
npm install --dev svelte-multiselect
|
|
54
|
-
pnpm add
|
|
54
|
+
pnpm add -D svelte-multiselect
|
|
55
55
|
yarn add --dev svelte-multiselect
|
|
56
56
|
```
|
|
57
57
|
|
|
@@ -90,10 +90,10 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
90
90
|
Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
|
|
91
91
|
|
|
92
92
|
1. ```ts
|
|
93
|
-
createOptionMsg: string = `Create this option...`
|
|
93
|
+
createOptionMsg: string | null = `Create this option...`
|
|
94
94
|
```
|
|
95
95
|
|
|
96
|
-
|
|
96
|
+
The message shown to users when `allowUserOptions` is truthy and they entered text that doesn't match any existing options to suggest creating a new option from the entered text. Emits `console.error` if `allowUserOptions` is `true` or `'append'` and `createOptionMsg=''` to since users might be unaware they can create new option. The error can be silenced by setting `createOptionMsg=null` indicating developer intent is to e.g. use MultiSelect as a tagging component where a user message might be unwanted.
|
|
97
97
|
|
|
98
98
|
1. ```ts
|
|
99
99
|
allowEmpty: boolean = false
|
|
@@ -146,7 +146,7 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
146
146
|
|
|
147
147
|
<!-- prettier-ignore -->
|
|
148
148
|
1. ```ts
|
|
149
|
-
duplicateFunc: (op1:
|
|
149
|
+
duplicateFunc: (op1: T, op2: T) => boolean = (op1, op2) =>
|
|
150
150
|
`${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
|
|
151
151
|
```
|
|
152
152
|
|
|
@@ -189,7 +189,7 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
189
189
|
highlightMatches: boolean = true
|
|
190
190
|
```
|
|
191
191
|
|
|
192
|
-
Whether to highlight text in the dropdown options that matches the current user-entered search query. Uses the [CSS Custom Highlight API](https://developer.mozilla.org/docs/Web/API/CSS_Custom_Highlight_API) with limited browser support and [styling options](https://developer.mozilla.org/docs/Web/CSS/::highlight). See `::highlight(search-
|
|
192
|
+
Whether to highlight text in the dropdown options that matches the current user-entered search query. Uses the [CSS Custom Highlight API](https://developer.mozilla.org/docs/Web/API/CSS_Custom_Highlight_API) with limited browser support and [styling options](https://developer.mozilla.org/docs/Web/CSS/::highlight). See `::highlight(sms-search-matches)` below for available CSS variables.
|
|
193
193
|
|
|
194
194
|
1. ```ts
|
|
195
195
|
id: string | null = null
|
|
@@ -526,7 +526,7 @@ Minimal example that changes the background color of the options dropdown:
|
|
|
526
526
|
- `div.multiselect.open`
|
|
527
527
|
- `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
|
|
528
528
|
- `div.multiselect:focus-within`
|
|
529
|
-
- `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color`
|
|
529
|
+
- `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` which in turn defaults to `cornflowerblue`.
|
|
530
530
|
- `div.multiselect.disabled`
|
|
531
531
|
- `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
|
|
532
532
|
- `div.multiselect input::placeholder`
|
|
@@ -559,11 +559,15 @@ Minimal example that changes the background color of the options dropdown:
|
|
|
559
559
|
- `div.multiselect > ul.options > li.disabled`
|
|
560
560
|
- `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
|
|
561
561
|
- `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
|
|
562
|
-
- `::highlight(search-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
562
|
+
- `::highlight(sms-search-matches)`: applies to search results in dropdown list that match the current search query if `highlightMatches=true`. These styles [cannot be set via CSS variables](https://stackoverflow.com/a/56799215). Instead, use a new rule set. For example:
|
|
563
|
+
|
|
564
|
+
```css
|
|
565
|
+
::highlight(sms-search-matches) {
|
|
566
|
+
color: orange;
|
|
567
|
+
background: rgba(0, 0, 0, 0.15);
|
|
568
|
+
text-decoration: underline;
|
|
569
|
+
}
|
|
570
|
+
```
|
|
567
571
|
|
|
568
572
|
### With CSS frameworks
|
|
569
573
|
|