svelte-multiselect 8.5.0 → 8.6.0
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/MultiSelect.svelte +57 -23
- package/dist/MultiSelect.svelte.d.ts +1 -0
- package/dist/index.d.ts +3 -0
- package/package.json +16 -16
- package/readme.md +17 -6
package/dist/MultiSelect.svelte
CHANGED
|
@@ -25,6 +25,7 @@ export let filterFunc = (op, searchText) => {
|
|
|
25
25
|
};
|
|
26
26
|
export let focusInputOnSelect = `desktop`;
|
|
27
27
|
export let form_input = null;
|
|
28
|
+
export let highlightMatches = true;
|
|
28
29
|
export let id = null;
|
|
29
30
|
export let input = null;
|
|
30
31
|
export let inputClass = ``;
|
|
@@ -128,7 +129,7 @@ function add(label, event) {
|
|
|
128
129
|
// custom option twice in append mode
|
|
129
130
|
[true, `append`].includes(allowUserOptions) &&
|
|
130
131
|
searchText.length > 0) {
|
|
131
|
-
// user entered text but no options match, so if allowUserOptions=true | 'append', we create
|
|
132
|
+
// user entered text but no options match, so if allowUserOptions = true | 'append', we create
|
|
132
133
|
// a new option from the user-entered text
|
|
133
134
|
if (typeof options[0] === `object`) {
|
|
134
135
|
// if 1st option is an object, we create new option as object to keep type homogeneity
|
|
@@ -140,8 +141,10 @@ function add(label, event) {
|
|
|
140
141
|
// create new option as number if it parses to a number and 1st option is also number or missing
|
|
141
142
|
option = Number(searchText);
|
|
142
143
|
}
|
|
143
|
-
else
|
|
144
|
+
else {
|
|
144
145
|
option = searchText; // else create custom option as string
|
|
146
|
+
}
|
|
147
|
+
dispatch(`create`, { option });
|
|
145
148
|
}
|
|
146
149
|
if (allowUserOptions === `append`)
|
|
147
150
|
options = [...options, option];
|
|
@@ -199,10 +202,10 @@ function remove(label) {
|
|
|
199
202
|
return console.error(`Multiselect can't remove selected option ${label}, not found in selected list`);
|
|
200
203
|
}
|
|
201
204
|
selected = selected.filter((op) => get_label(op) !== label); // remove option from selected list
|
|
202
|
-
dispatch(`remove`, { option });
|
|
203
|
-
dispatch(`change`, { option, type: `remove` });
|
|
204
205
|
invalid = false; // reset error status whenever items are removed
|
|
205
206
|
form_input?.setCustomValidity(``);
|
|
207
|
+
dispatch(`remove`, { option });
|
|
208
|
+
dispatch(`change`, { option, type: `remove` });
|
|
206
209
|
}
|
|
207
210
|
function open_dropdown(event) {
|
|
208
211
|
if (disabled)
|
|
@@ -331,6 +334,47 @@ const dragstart = (idx) => (event) => {
|
|
|
331
334
|
event.dataTransfer.dropEffect = `move`;
|
|
332
335
|
event.dataTransfer.setData(`text/plain`, `${idx}`);
|
|
333
336
|
};
|
|
337
|
+
let ul_options;
|
|
338
|
+
function highlight_matching_options(event) {
|
|
339
|
+
if (!highlightMatches || typeof CSS == `undefined` || !CSS.highlights)
|
|
340
|
+
return; // don't try if CSS highlight API not supported
|
|
341
|
+
// clear previous ranges from HighlightRegistry
|
|
342
|
+
CSS.highlights.clear();
|
|
343
|
+
// get input's search query
|
|
344
|
+
const query = event?.target?.value.trim().toLowerCase();
|
|
345
|
+
if (!query)
|
|
346
|
+
return;
|
|
347
|
+
const tree_walker = document.createTreeWalker(ul_options, NodeFilter.SHOW_TEXT);
|
|
348
|
+
const text_nodes = [];
|
|
349
|
+
let current_node = tree_walker.nextNode();
|
|
350
|
+
while (current_node) {
|
|
351
|
+
text_nodes.push(current_node);
|
|
352
|
+
current_node = tree_walker.nextNode();
|
|
353
|
+
}
|
|
354
|
+
// iterate over all text nodes and find matches
|
|
355
|
+
const ranges = text_nodes.map((el) => {
|
|
356
|
+
const text = el.textContent.toLowerCase();
|
|
357
|
+
const indices = [];
|
|
358
|
+
let start_pos = 0;
|
|
359
|
+
while (start_pos < text.length) {
|
|
360
|
+
const index = text.indexOf(query, start_pos);
|
|
361
|
+
if (index === -1)
|
|
362
|
+
break;
|
|
363
|
+
indices.push(index);
|
|
364
|
+
start_pos = index + query.length;
|
|
365
|
+
}
|
|
366
|
+
// create range object for each str found in the text node
|
|
367
|
+
return indices.map((index) => {
|
|
368
|
+
const range = new Range();
|
|
369
|
+
range.setStart(el, index);
|
|
370
|
+
range.setEnd(el, index + query.length);
|
|
371
|
+
return range;
|
|
372
|
+
});
|
|
373
|
+
});
|
|
374
|
+
// create Highlight object from ranges and add to registry
|
|
375
|
+
// eslint-disable-next-line no-undef
|
|
376
|
+
CSS.highlights.set(`search-results`, new Highlight(...ranges.flat()));
|
|
377
|
+
}
|
|
334
378
|
</script>
|
|
335
379
|
|
|
336
380
|
<svelte:window
|
|
@@ -376,17 +420,10 @@ const dragstart = (idx) => (event) => {
|
|
|
376
420
|
<slot name="expand-icon" {open}>
|
|
377
421
|
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt; cursor: pointer;" />
|
|
378
422
|
</slot>
|
|
379
|
-
<ul
|
|
380
|
-
class="selected {ulSelectedClass}"
|
|
381
|
-
role="listbox"
|
|
382
|
-
aria-multiselectable={maxSelect === null || maxSelect > 1}
|
|
383
|
-
aria-label="selected options"
|
|
384
|
-
>
|
|
423
|
+
<ul class="selected {ulSelectedClass}" aria-label="selected options">
|
|
385
424
|
{#each selected as option, idx (get_label(option))}
|
|
386
425
|
<li
|
|
387
426
|
class={liSelectedClass}
|
|
388
|
-
role="option"
|
|
389
|
-
aria-selected="true"
|
|
390
427
|
animate:flip={{ duration: 100 }}
|
|
391
428
|
draggable={selectedOptionsDraggable && !disabled && selected.length > 1}
|
|
392
429
|
on:dragstart={dragstart(idx)}
|
|
@@ -425,6 +462,7 @@ const dragstart = (idx) => (event) => {
|
|
|
425
462
|
on:keydown|stopPropagation={handle_keydown}
|
|
426
463
|
on:focus
|
|
427
464
|
on:focus={open_dropdown}
|
|
465
|
+
on:input={highlight_matching_options}
|
|
428
466
|
{id}
|
|
429
467
|
{disabled}
|
|
430
468
|
{autocomplete}
|
|
@@ -482,14 +520,7 @@ const dragstart = (idx) => (event) => {
|
|
|
482
520
|
|
|
483
521
|
<!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
|
|
484
522
|
{#if (searchText && noMatchingOptionsMsg) || options?.length > 0}
|
|
485
|
-
<ul
|
|
486
|
-
class:hidden={!open}
|
|
487
|
-
class="options {ulOptionsClass}"
|
|
488
|
-
role="listbox"
|
|
489
|
-
aria-multiselectable={maxSelect === null || maxSelect > 1}
|
|
490
|
-
aria-expanded={open}
|
|
491
|
-
aria-disabled={disabled ? `true` : null}
|
|
492
|
-
>
|
|
523
|
+
<ul class:hidden={!open} class="options {ulOptionsClass}" bind:this={ul_options}>
|
|
493
524
|
{#each matchingOptions as option, idx}
|
|
494
525
|
{@const {
|
|
495
526
|
label,
|
|
@@ -519,8 +550,6 @@ const dragstart = (idx) => (event) => {
|
|
|
519
550
|
}}
|
|
520
551
|
on:mouseout={() => (activeIndex = null)}
|
|
521
552
|
on:blur={() => (activeIndex = null)}
|
|
522
|
-
role="option"
|
|
523
|
-
aria-selected="false"
|
|
524
553
|
>
|
|
525
554
|
<slot name="option" {option} {idx}>
|
|
526
555
|
{#if parseLabelsAsHtml}
|
|
@@ -541,7 +570,6 @@ const dragstart = (idx) => (event) => {
|
|
|
541
570
|
on:focus={() => (add_option_msg_is_active = true)}
|
|
542
571
|
on:mouseout={() => (add_option_msg_is_active = false)}
|
|
543
572
|
on:blur={() => (add_option_msg_is_active = false)}
|
|
544
|
-
aria-selected="false"
|
|
545
573
|
>
|
|
546
574
|
{!duplicates && selected.some((option) => duplicateFunc(option, searchText))
|
|
547
575
|
? duplicateOptionMsg
|
|
@@ -713,4 +741,10 @@ const dragstart = (idx) => (event) => {
|
|
|
713
741
|
:where(span.max-select-msg) {
|
|
714
742
|
padding: 0 3pt;
|
|
715
743
|
}
|
|
744
|
+
::highlight(search-results) {
|
|
745
|
+
color: var(--sms-highlight-color, orange);
|
|
746
|
+
background: var(--sms-highlight-bg);
|
|
747
|
+
text-decoration: var(--sms-highlight-text-decoration);
|
|
748
|
+
text-decoration-color: var(--sms-highlight-text-decoration-color);
|
|
749
|
+
}
|
|
716
750
|
</style>
|
|
@@ -19,6 +19,7 @@ declare class __sveltets_Render<Option extends GenericOption> {
|
|
|
19
19
|
filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
|
|
20
20
|
focusInputOnSelect?: boolean | "desktop" | undefined;
|
|
21
21
|
form_input?: HTMLInputElement | null | undefined;
|
|
22
|
+
highlightMatches?: boolean | undefined;
|
|
22
23
|
id?: string | null | undefined;
|
|
23
24
|
input?: HTMLInputElement | null | undefined;
|
|
24
25
|
inputClass?: string | undefined;
|
package/dist/index.d.ts
CHANGED
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.
|
|
8
|
+
"version": "8.6.0",
|
|
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.57.0"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
29
|
"@iconify/svelte": "^3.1.0",
|
|
30
30
|
"@playwright/test": "^1.31.2",
|
|
31
31
|
"@sveltejs/adapter-static": "^2.0.1",
|
|
32
|
-
"@sveltejs/kit": "^1.
|
|
32
|
+
"@sveltejs/kit": "^1.12.0",
|
|
33
33
|
"@sveltejs/package": "2.0.2",
|
|
34
34
|
"@sveltejs/vite-plugin-svelte": "^2.0.3",
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
36
|
-
"@typescript-eslint/parser": "^5.
|
|
37
|
-
"@vitest/coverage-c8": "^0.29.
|
|
38
|
-
"eslint": "^8.
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^5.55.0",
|
|
36
|
+
"@typescript-eslint/parser": "^5.55.0",
|
|
37
|
+
"@vitest/coverage-c8": "^0.29.3",
|
|
38
|
+
"eslint": "^8.36.0",
|
|
39
39
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
40
40
|
"hastscript": "^7.2.0",
|
|
41
41
|
"highlight.js": "^11.7.0",
|
|
42
|
-
"jsdom": "^21.1.
|
|
42
|
+
"jsdom": "^21.1.1",
|
|
43
43
|
"mdsvex": "^0.10.6",
|
|
44
44
|
"mdsvexamples": "^0.3.3",
|
|
45
45
|
"prettier": "^2.8.4",
|
|
46
46
|
"prettier-plugin-svelte": "^2.9.0",
|
|
47
47
|
"rehype-autolink-headings": "^6.1.1",
|
|
48
48
|
"rehype-slug": "^5.1.0",
|
|
49
|
-
"svelte-check": "^3.1.
|
|
50
|
-
"svelte-preprocess": "^5.0.
|
|
51
|
-
"svelte-toc": "^0.5.
|
|
52
|
-
"svelte-zoo": "^0.3
|
|
53
|
-
"svelte2tsx": "^0.6.
|
|
54
|
-
"typescript": "
|
|
55
|
-
"vite": "^4.
|
|
56
|
-
"vitest": "^0.29.
|
|
49
|
+
"svelte-check": "^3.1.4",
|
|
50
|
+
"svelte-preprocess": "^5.0.3",
|
|
51
|
+
"svelte-toc": "^0.5.4",
|
|
52
|
+
"svelte-zoo": "^0.4.3",
|
|
53
|
+
"svelte2tsx": "^0.6.10",
|
|
54
|
+
"typescript": "5.0.2",
|
|
55
|
+
"vite": "^4.2.0",
|
|
56
|
+
"vitest": "^0.29.3"
|
|
57
57
|
},
|
|
58
58
|
"keywords": [
|
|
59
59
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -185,6 +185,12 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
185
185
|
|
|
186
186
|
Handle to the `<input>` DOM node that's responsible for form validity checks and passing selected options to form submission handlers. Only available after component mounts (`null` before then).
|
|
187
187
|
|
|
188
|
+
1. ```ts
|
|
189
|
+
highlightMatches: boolean = true
|
|
190
|
+
```
|
|
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-results)` below for available CSS variables.
|
|
193
|
+
|
|
188
194
|
1. ```ts
|
|
189
195
|
id: string | null = null
|
|
190
196
|
```
|
|
@@ -500,6 +506,12 @@ There are 3 ways to style this component. To understand which options do what, i
|
|
|
500
506
|
|
|
501
507
|
If you only want to make small adjustments, you can pass the following CSS variables directly to the component as props or define them in a `:global()` CSS context. See [`app.css`](https://github.com/janosh/svelte-multiselect/blob/main/src/app.css) for how these variables are set on the demo site of this component.
|
|
502
508
|
|
|
509
|
+
Minimal example that changes the background color of the options dropdown:
|
|
510
|
+
|
|
511
|
+
```svelte
|
|
512
|
+
<MultiSelect --sms-options-bg="white" />
|
|
513
|
+
```
|
|
514
|
+
|
|
503
515
|
- `div.multiselect`
|
|
504
516
|
- `border: var(--sms-border, 1pt solid lightgray)`: Change this to e.g. to `1px solid red` to indicate this form field is in an invalid state.
|
|
505
517
|
- `border-radius: var(--sms-border-radius, 3pt)`
|
|
@@ -547,12 +559,11 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
547
559
|
- `div.multiselect > ul.options > li.disabled`
|
|
548
560
|
- `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
|
|
549
561
|
- `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
```
|
|
562
|
+
- `::highlight(search-results)`: applies to search results in dropdown list that match the current search query if `highlightMatches=true`
|
|
563
|
+
- `color: var(--sms-highlight-color, orange)`
|
|
564
|
+
- `background: var(--sms-highlight-bg)`
|
|
565
|
+
- `text-decoration: var(--sms-highlight-text-decoration)`
|
|
566
|
+
- `text-decoration-color: var(--sms-highlight-text-decoration-color)`
|
|
556
567
|
|
|
557
568
|
### With CSS frameworks
|
|
558
569
|
|