svelte-multiselect 5.0.2 → 5.0.5
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/MultiSelect.svelte +98 -67
- package/MultiSelect.svelte.d.ts +4 -2
- package/icons/Octocat.svelte +5 -0
- package/icons/Octocat.svelte.d.ts +23 -0
- package/icons/index.d.ts +1 -0
- package/icons/index.js +1 -0
- package/index.d.ts +1 -1
- package/package.json +14 -13
- package/readme.md +16 -14
package/MultiSelect.svelte
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
<script>import
|
|
1
|
+
<script>import scrollIntoView from 'scroll-into-view-if-needed';
|
|
2
|
+
import { createEventDispatcher } from 'svelte';
|
|
2
3
|
import { get_label, get_value } from './';
|
|
3
4
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
5
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
@@ -10,6 +11,7 @@ export let maxSelectMsg = null;
|
|
|
10
11
|
export let disabled = false;
|
|
11
12
|
export let disabledTitle = `This field is disabled`;
|
|
12
13
|
export let options;
|
|
14
|
+
export let matchingOptions = [];
|
|
13
15
|
export let selected = [];
|
|
14
16
|
export let selectedLabels = [];
|
|
15
17
|
export let selectedValues = [];
|
|
@@ -44,15 +46,24 @@ export let required = false;
|
|
|
44
46
|
export let autocomplete = `off`;
|
|
45
47
|
export let invalid = false;
|
|
46
48
|
export let sortSelected = false;
|
|
47
|
-
if (!(options?.length > 0))
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
49
|
+
if (!(options?.length > 0)) {
|
|
50
|
+
if (allowUserOptions) {
|
|
51
|
+
options = []; // initializing as array avoids errors when component mounts
|
|
52
|
+
}
|
|
53
|
+
else {
|
|
54
|
+
// only error for empty options if user is not allowed to create custom options
|
|
55
|
+
console.error(`MultiSelect received no options`);
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (parseLabelsAsHtml && allowUserOptions) {
|
|
59
|
+
console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
|
|
60
|
+
}
|
|
51
61
|
if (maxSelect !== null && maxSelect < 1) {
|
|
52
62
|
console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
|
|
53
63
|
}
|
|
54
|
-
if (!Array.isArray(selected))
|
|
55
|
-
console.error(`selected prop must be an array`);
|
|
64
|
+
if (!Array.isArray(selected)) {
|
|
65
|
+
console.error(`selected prop must be an array, got ${selected}`);
|
|
66
|
+
}
|
|
56
67
|
const dispatch = createEventDispatcher();
|
|
57
68
|
let activeMsg = false; // controls active state of <li>{addOptionMsg}</li>
|
|
58
69
|
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
@@ -68,6 +79,9 @@ $: matchingOptions = options.filter((op) => filterFunc(op, searchText) &&
|
|
|
68
79
|
!(op instanceof Object && op.disabled) &&
|
|
69
80
|
!selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
|
|
70
81
|
);
|
|
82
|
+
// reset activeOption if it's no longer in the matchingOptions list
|
|
83
|
+
$: if (activeOption && !matchingOptions.includes(activeOption))
|
|
84
|
+
activeOption = null;
|
|
71
85
|
// add an option to selected list
|
|
72
86
|
function add(label) {
|
|
73
87
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
@@ -82,10 +96,19 @@ function add(label) {
|
|
|
82
96
|
searchText.length > 0) {
|
|
83
97
|
// user entered text but no options match, so if allowUserOptions=true | 'append', we create
|
|
84
98
|
// a new option from the user-entered text
|
|
85
|
-
if (typeof options[0] === `
|
|
86
|
-
option
|
|
87
|
-
else
|
|
99
|
+
if (typeof options[0] === `object`) {
|
|
100
|
+
// if 1st option is an object, we create new option as object to keep type homogeneity
|
|
88
101
|
option = { label: searchText, value: searchText };
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
if ([`number`, `undefined`].includes(typeof options[0]) &&
|
|
105
|
+
!isNaN(Number(searchText))) {
|
|
106
|
+
// create new option as number if it parses to a number and 1st option is also number or missing
|
|
107
|
+
option = Number(searchText);
|
|
108
|
+
}
|
|
109
|
+
else
|
|
110
|
+
option = searchText; // else create custom option as string
|
|
111
|
+
}
|
|
89
112
|
if (allowUserOptions === `append`)
|
|
90
113
|
options = [...options, option];
|
|
91
114
|
}
|
|
@@ -210,7 +233,8 @@ async function handleKeydown(event) {
|
|
|
210
233
|
// around start/end of option list. Find a better solution than waiting 10 ms to.
|
|
211
234
|
setTimeout(() => {
|
|
212
235
|
const li = document.querySelector(`ul.options > li.active`);
|
|
213
|
-
li
|
|
236
|
+
if (li)
|
|
237
|
+
scrollIntoView(li, { scrollMode: `if-needed` });
|
|
214
238
|
}, 10);
|
|
215
239
|
}
|
|
216
240
|
}
|
|
@@ -225,7 +249,7 @@ function remove_all() {
|
|
|
225
249
|
selected = [];
|
|
226
250
|
searchText = ``;
|
|
227
251
|
}
|
|
228
|
-
$:
|
|
252
|
+
$: is_selected = (label) => selectedLabels.includes(label);
|
|
229
253
|
const if_enter_or_space = (handler) => (event) => {
|
|
230
254
|
if ([`Enter`, `Space`].includes(event.code)) {
|
|
231
255
|
event.preventDefault();
|
|
@@ -282,7 +306,7 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
282
306
|
type="button"
|
|
283
307
|
title="{removeBtnTitle} {get_label(option)}"
|
|
284
308
|
>
|
|
285
|
-
<CrossIcon width="15px"
|
|
309
|
+
<slot name="remove-icon"><CrossIcon width="15px" /></slot>
|
|
286
310
|
</button>
|
|
287
311
|
{/if}
|
|
288
312
|
</li>
|
|
@@ -330,69 +354,74 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
330
354
|
on:mouseup|stopPropagation={remove_all}
|
|
331
355
|
on:keydown={if_enter_or_space(remove_all)}
|
|
332
356
|
>
|
|
333
|
-
<CrossIcon width="15px"
|
|
357
|
+
<slot name="remove-icon"><CrossIcon width="15px" /></slot>
|
|
334
358
|
</button>
|
|
335
359
|
{/if}
|
|
336
360
|
{/if}
|
|
337
361
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
on:mouseup|stopPropagation={() => {
|
|
351
|
-
if (!disabled) isSelected(label) ? remove(label) : add(label)
|
|
352
|
-
}}
|
|
353
|
-
title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
|
|
354
|
-
class:selected={isSelected(label)}
|
|
355
|
-
class:active
|
|
356
|
-
class:disabled
|
|
357
|
-
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
358
|
-
on:mouseover={() => {
|
|
359
|
-
if (!disabled) activeOption = option
|
|
360
|
-
}}
|
|
361
|
-
on:focus={() => {
|
|
362
|
-
if (!disabled) activeOption = option
|
|
363
|
-
}}
|
|
364
|
-
on:mouseout={() => (activeOption = null)}
|
|
365
|
-
on:blur={() => (activeOption = null)}
|
|
366
|
-
aria-selected="false"
|
|
367
|
-
>
|
|
368
|
-
<slot name="option" {option} {idx}>
|
|
369
|
-
{#if parseLabelsAsHtml}
|
|
370
|
-
{@html get_label(option)}
|
|
371
|
-
{:else}
|
|
372
|
-
{get_label(option)}
|
|
373
|
-
{/if}
|
|
374
|
-
</slot>
|
|
375
|
-
</li>
|
|
376
|
-
{:else}
|
|
377
|
-
{#if allowUserOptions && searchText}
|
|
362
|
+
<!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
|
|
363
|
+
{#if searchText || options?.length > 0}
|
|
364
|
+
<ul class:hidden={!showOptions} class="options {ulOptionsClass}">
|
|
365
|
+
{#each matchingOptions as option, idx}
|
|
366
|
+
{@const {
|
|
367
|
+
label,
|
|
368
|
+
disabled = null,
|
|
369
|
+
title = null,
|
|
370
|
+
selectedTitle = null,
|
|
371
|
+
disabledTitle = defaultDisabledTitle,
|
|
372
|
+
} = option instanceof Object ? option : { label: option }}
|
|
373
|
+
{@const active = activeOption && get_label(activeOption) === label}
|
|
378
374
|
<li
|
|
379
375
|
on:mousedown|stopPropagation
|
|
380
|
-
on:mouseup|stopPropagation={() =>
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
376
|
+
on:mouseup|stopPropagation={() => {
|
|
377
|
+
if (!disabled) is_selected(label) ? remove(label) : add(label)
|
|
378
|
+
}}
|
|
379
|
+
title={disabled
|
|
380
|
+
? disabledTitle
|
|
381
|
+
: (is_selected(label) && selectedTitle) || title}
|
|
382
|
+
class:selected={is_selected(label)}
|
|
383
|
+
class:active
|
|
384
|
+
class:disabled
|
|
385
|
+
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
386
|
+
on:mouseover={() => {
|
|
387
|
+
if (!disabled) activeOption = option
|
|
388
|
+
}}
|
|
389
|
+
on:focus={() => {
|
|
390
|
+
if (!disabled) activeOption = option
|
|
391
|
+
}}
|
|
392
|
+
on:mouseout={() => (activeOption = null)}
|
|
393
|
+
on:blur={() => (activeOption = null)}
|
|
387
394
|
aria-selected="false"
|
|
388
395
|
>
|
|
389
|
-
{
|
|
396
|
+
<slot name="option" {option} {idx}>
|
|
397
|
+
{#if parseLabelsAsHtml}
|
|
398
|
+
{@html get_label(option)}
|
|
399
|
+
{:else}
|
|
400
|
+
{get_label(option)}
|
|
401
|
+
{/if}
|
|
402
|
+
</slot>
|
|
390
403
|
</li>
|
|
391
404
|
{:else}
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
405
|
+
{#if allowUserOptions && searchText}
|
|
406
|
+
<li
|
|
407
|
+
on:mousedown|stopPropagation
|
|
408
|
+
on:mouseup|stopPropagation={() => add(searchText)}
|
|
409
|
+
title={addOptionMsg}
|
|
410
|
+
class:active={activeMsg}
|
|
411
|
+
on:mouseover={() => (activeMsg = true)}
|
|
412
|
+
on:focus={() => (activeMsg = true)}
|
|
413
|
+
on:mouseout={() => (activeMsg = false)}
|
|
414
|
+
on:blur={() => (activeMsg = false)}
|
|
415
|
+
aria-selected="false"
|
|
416
|
+
>
|
|
417
|
+
{addOptionMsg}
|
|
418
|
+
</li>
|
|
419
|
+
{:else}
|
|
420
|
+
<span>{noOptionsMsg}</span>
|
|
421
|
+
{/if}
|
|
422
|
+
{/each}
|
|
423
|
+
</ul>
|
|
424
|
+
{/if}
|
|
396
425
|
</div>
|
|
397
426
|
|
|
398
427
|
<style>
|
|
@@ -472,9 +501,11 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
472
501
|
background: none;
|
|
473
502
|
flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
|
|
474
503
|
min-width: 2em;
|
|
475
|
-
color
|
|
504
|
+
/* ensure input uses text color and not --sms-selected-text-color */
|
|
505
|
+
color: var(--sms-text-color);
|
|
476
506
|
font-size: inherit;
|
|
477
507
|
cursor: inherit; /* needed for disabled state */
|
|
508
|
+
border-radius: 0; /* reset ul.selected > li */
|
|
478
509
|
}
|
|
479
510
|
:where(div.multiselect > ul.selected > li > input)::placeholder {
|
|
480
511
|
padding-left: 5pt;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
import {
|
|
2
|
+
import type { MultiSelectEvents, Option } from './';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
5
|
searchText?: string | undefined;
|
|
@@ -9,6 +9,7 @@ declare const __propDef: {
|
|
|
9
9
|
disabled?: boolean | undefined;
|
|
10
10
|
disabledTitle?: string | undefined;
|
|
11
11
|
options: Option[];
|
|
12
|
+
matchingOptions?: Option[] | undefined;
|
|
12
13
|
selected?: Option[] | undefined;
|
|
13
14
|
selectedLabels?: (string | number)[] | undefined;
|
|
14
15
|
selectedValues?: unknown[] | undefined;
|
|
@@ -45,6 +46,7 @@ declare const __propDef: {
|
|
|
45
46
|
option: Option;
|
|
46
47
|
idx: any;
|
|
47
48
|
};
|
|
49
|
+
'remove-icon': {};
|
|
48
50
|
spinner: {};
|
|
49
51
|
'disabled-icon': {};
|
|
50
52
|
option: {
|
|
@@ -53,7 +55,7 @@ declare const __propDef: {
|
|
|
53
55
|
};
|
|
54
56
|
};
|
|
55
57
|
getters: {};
|
|
56
|
-
events:
|
|
58
|
+
events: MultiSelectEvents;
|
|
57
59
|
};
|
|
58
60
|
export declare type MultiSelectProps = typeof __propDef.props;
|
|
59
61
|
export declare type MultiSelectEvents = typeof __propDef.events;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg {...$$props} viewBox="0 0 24 24" fill="currentColor">
|
|
2
|
+
<path
|
|
3
|
+
d="M8.422 20.081c0 .896.01 1.753.016 2.285a.617.617 0 0 0 .422.58c2.078.686 4.317.718 6.414.091l.292-.087a.67.67 0 0 0 .478-.638c.005-.733.017-2.017.017-3.53c0-1.372-.477-2.25-1.031-2.707c3.399-.366 6.97-1.61 6.97-7.227c0-1.61-.592-2.91-1.566-3.934c.153-.366.688-1.866-.153-3.878c0 0-1.28-.403-4.201 1.5a14.76 14.76 0 0 0-3.82-.494c-1.298 0-2.597.165-3.819.494C5.52.65 4.24 1.036 4.24 1.036c-.84 2.012-.306 3.512-.153 3.878a5.565 5.565 0 0 0-1.566 3.934c0 5.598 3.552 6.86 6.951 7.227c-.439.366-.84 1.006-.973 1.957c-.879.384-3.075 1.006-4.45-1.207c-.286-.44-1.146-1.519-2.349-1.5c-1.28.018-.516.695.02.97c.648.347 1.393 1.646 1.565 2.067c.306.823 1.299 2.396 5.137 1.72Z"
|
|
4
|
+
/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} OctocatProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} OctocatEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} OctocatSlots */
|
|
4
|
+
export default class Octocat extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type OctocatProps = typeof __propDef.props;
|
|
11
|
+
export type OctocatEvents = typeof __propDef.events;
|
|
12
|
+
export type OctocatSlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponentTyped } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
[x: string]: any;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
package/icons/index.d.ts
CHANGED
package/icons/index.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -28,7 +28,7 @@ export declare type DispatchEvents = {
|
|
|
28
28
|
focus: undefined;
|
|
29
29
|
blur: undefined;
|
|
30
30
|
};
|
|
31
|
-
export declare type
|
|
31
|
+
export declare type MultiSelectEvents = {
|
|
32
32
|
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
33
33
|
};
|
|
34
34
|
export declare const get_label: (op: Option) => string | number;
|
package/package.json
CHANGED
|
@@ -5,37 +5,38 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "5.0.
|
|
8
|
+
"version": "5.0.5",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"main": "index.js",
|
|
12
12
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@
|
|
15
|
-
"@sveltejs/
|
|
16
|
-
"@sveltejs/
|
|
17
|
-
"@
|
|
18
|
-
"@typescript-eslint/
|
|
19
|
-
"eslint": "^
|
|
14
|
+
"@playwright/test": "^1.24.1",
|
|
15
|
+
"@sveltejs/adapter-static": "^1.0.0-next.38",
|
|
16
|
+
"@sveltejs/kit": "^1.0.0-next.396",
|
|
17
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.1",
|
|
18
|
+
"@typescript-eslint/eslint-plugin": "^5.31.0",
|
|
19
|
+
"@typescript-eslint/parser": "^5.31.0",
|
|
20
|
+
"eslint": "^8.20.0",
|
|
20
21
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
21
22
|
"hastscript": "^7.0.2",
|
|
22
23
|
"jsdom": "^20.0.0",
|
|
23
24
|
"mdsvex": "^0.10.6",
|
|
24
|
-
"playwright": "^1.22.2",
|
|
25
25
|
"prettier": "^2.7.1",
|
|
26
26
|
"prettier-plugin-svelte": "^2.7.0",
|
|
27
27
|
"rehype-autolink-headings": "^6.1.1",
|
|
28
28
|
"rehype-slug": "^5.0.1",
|
|
29
|
-
"
|
|
29
|
+
"scroll-into-view-if-needed": "^2.2.29",
|
|
30
|
+
"svelte": "^3.49.0",
|
|
30
31
|
"svelte-check": "^2.8.0",
|
|
31
32
|
"svelte-github-corner": "^0.1.0",
|
|
32
33
|
"svelte-preprocess": "^4.10.6",
|
|
33
|
-
"svelte-toc": "^0.2.
|
|
34
|
-
"svelte2tsx": "^0.5.
|
|
34
|
+
"svelte-toc": "^0.2.10",
|
|
35
|
+
"svelte2tsx": "^0.5.13",
|
|
35
36
|
"tslib": "^2.4.0",
|
|
36
37
|
"typescript": "^4.7.4",
|
|
37
|
-
"vite": "^
|
|
38
|
-
"vitest": "^0.
|
|
38
|
+
"vite": "^3.0.4",
|
|
39
|
+
"vitest": "^0.19.1"
|
|
39
40
|
},
|
|
40
41
|
"keywords": [
|
|
41
42
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
|
|
14
14
|
</h4>
|
|
15
15
|
|
|
16
|
-
**Keyboard-friendly, accessible multi-select
|
|
16
|
+
**Keyboard-friendly, accessible and highly customizable multi-select component.**
|
|
17
17
|
<strong class="hide-in-docs">
|
|
18
18
|
<a href="https://svelte-multiselect.netlify.app">Docs</a>
|
|
19
19
|
</strong>
|
|
@@ -24,7 +24,7 @@
|
|
|
24
24
|
|
|
25
25
|
- **Bindable:** `bind:selected` gives you an array of the currently selected options. Thanks to Svelte's 2-way binding, it can also control the component state externally through assignment `selected = ['foo', 42]`.
|
|
26
26
|
- **Keyboard friendly** for mouse-less form completion
|
|
27
|
-
- **No
|
|
27
|
+
- **No run-time deps:** needs only Svelte as dev dependency
|
|
28
28
|
- **Dropdowns:** scrollable lists for large numbers of options
|
|
29
29
|
- **Searchable:** start typing to filter options
|
|
30
30
|
- **Tagging:** selected options are listed as tags within the input
|
|
@@ -35,20 +35,17 @@
|
|
|
35
35
|
|
|
36
36
|
## Recent breaking changes
|
|
37
37
|
|
|
38
|
-
- v4.0.
|
|
39
|
-
|
|
40
|
-
- old: `<slot name="renderOptions" />`, new: `<slot name="option" />`
|
|
41
|
-
- old: `<slot name="renderSelected" />`, new: `<slot name="selected" />`
|
|
42
|
-
|
|
43
|
-
- v4.0.1 renamed the `readonly` prop to `disabled` which now prevents all form or user interaction with this component including opening the dropdown list which was still possible before. See [#45](https://github.com/janosh/svelte-multiselect/issues/45) for details. The associated CSS class applied to the outer `div` was likewise renamed to `div.multiselect.{readonly=>disabled}`.
|
|
38
|
+
- v4.0.1 renamed the `readonly` prop to `disabled` which now prevents all form of user interaction with this component including opening the dropdown list which was still possible before. See [#45](https://github.com/janosh/svelte-multiselect/issues/45) for details. The associated CSS class applied to the outer `div` was likewise renamed `div.multiselect.{readonly=>disabled}`.
|
|
44
39
|
|
|
45
40
|
- v4.0.3 CSS variables starting with `--sms-input-<attr>` were renamed to just `--sms-<attr>`. E.g. `--sms-input-min-height` is now `--sms-min-height`.
|
|
46
41
|
|
|
47
|
-
- v5.0.0 Support both simple and object options. Previously
|
|
42
|
+
- v5.0.0 Support both simple and object options. Previously strings and numbers were converted to `{ value, label }` objects internally and returned by `bind:selected`. Now, if you pass in `string[]`, that's exactly what you'll get from `bind:selected`.
|
|
48
43
|
|
|
49
44
|
## Installation
|
|
50
45
|
|
|
51
46
|
```sh
|
|
47
|
+
npm install -D svelte-multiselect
|
|
48
|
+
pnpm install -D svelte-multiselect
|
|
52
49
|
yarn add -D svelte-multiselect
|
|
53
50
|
```
|
|
54
51
|
|
|
@@ -87,6 +84,7 @@ Full list of props/bindable variables for this component:
|
|
|
87
84
|
| `selected` | `[]` | Array of currently selected options. Can be bound to `bind:selected={[1, 2, 3]}` to control component state externally or passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction. |
|
|
88
85
|
| `selectedLabels` | `[]` | Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings, `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`. |
|
|
89
86
|
| `selectedValues` | `[]` | Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`. |
|
|
87
|
+
| `matchingOptions` | `Option[]` | List of options currently displayed to the user. Same as `options` unless the user entered `searchText` in which case this array contains only those options for which `filterFunc = (op: Option, searchText: string) => boolean` returned `true` (see [exposed methods](#exposed-methods) below for details on `filterFunc`). |
|
|
90
88
|
| `sortSelected` | `boolean \| ((op1, op2) => number)` | Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://svelte-multiselect.netlify.app/sort-selected) example. |
|
|
91
89
|
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
|
|
92
90
|
| `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
@@ -102,19 +100,21 @@ Full list of props/bindable variables for this component:
|
|
|
102
100
|
| `parseLabelsAsHtml` | `false` | Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting). |
|
|
103
101
|
| `addOptionMsg` | `'Create this option...'` | Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy. |
|
|
104
102
|
| `loading` | `false` | Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner. |
|
|
105
|
-
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (
|
|
103
|
+
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button to remove selected option (which defaults to a cross icon). |
|
|
106
104
|
| `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
|
|
107
105
|
| `defaultDisabledTitle` | `'This option is disabled'` | Title text to display when user hovers over a disabled option. Each option can override this through its `disabledTitle` attribute. |
|
|
108
|
-
| `autocomplete` | `'off'` | Applied to the `<input>`. Specifies if browser is permitted to auto-fill this form field. See [MDN docs](https://developer.mozilla.org/
|
|
106
|
+
| `autocomplete` | `'off'` | Applied to the `<input>`. Specifies if browser is permitted to auto-fill this form field. See [MDN docs](https://developer.mozilla.org/docs/Web/HTML/Attributes/autocomplete) for other admissible values. |
|
|
109
107
|
| `invalid` | `false` | If `required=true` and user tries to submit but `selected = []` is empty, `invalid` is automatically set to `true` and CSS class `invalid` applied to the top-level `div.multiselect`. `invalid` class is removed again as soon as the user selects an option. `invalid` can also be controlled externally by binding to it `<MultiSelect bind:invalid />` and setting it to `true` based on outside events or custom validation. |
|
|
110
108
|
|
|
111
109
|
</div>
|
|
112
110
|
|
|
113
111
|
## Exposed methods
|
|
114
112
|
|
|
115
|
-
1. `filterFunc = (op: Option, searchText: string) => boolean`:
|
|
113
|
+
1. `filterFunc = (op: Option, searchText: string) => boolean`: Customize how dropdown options are filtered when user enters search string into `<MultiSelect />`. Defaults to:
|
|
116
114
|
|
|
117
115
|
```ts
|
|
116
|
+
import type { Option } from 'svelte-multiselect'
|
|
117
|
+
|
|
118
118
|
filterFunc = (op: Option, searchText: string) => {
|
|
119
119
|
if (!searchText) return true
|
|
120
120
|
return `${op.label}`.toLowerCase().includes(searchText.toLowerCase())
|
|
@@ -135,6 +135,7 @@ Full list of props/bindable variables for this component:
|
|
|
135
135
|
- `slot="selected"`: Customize rendering of selected items. Receives as props an `option` and the zero-indexed position (`idx`) it has in the list of selected items.
|
|
136
136
|
- `slot="spinner"`: Custom spinner component to display when in `loading` state. Receives no props.
|
|
137
137
|
- `slot="disabled-icon"`: Custom icon to display inside the input when in `disabled` state. Receives no props. Use an empty `<span slot="disabled-icon" />` or `div` to remove the default disabled icon.
|
|
138
|
+
- `slot="remove-icon"`: Custom icon to display as remove button. Will be used both by buttons to remove individual selected options and the 'remove all' button that clears all options at once. Receives no props.
|
|
138
139
|
|
|
139
140
|
Example:
|
|
140
141
|
|
|
@@ -153,6 +154,7 @@ Example:
|
|
|
153
154
|
</span>
|
|
154
155
|
|
|
155
156
|
<CustomSpinner slot="spinner">
|
|
157
|
+
<strong slot="remove-icon">X</strong>
|
|
156
158
|
</MultiSelect>
|
|
157
159
|
```
|
|
158
160
|
|
|
@@ -246,11 +248,11 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
246
248
|
- `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
|
|
247
249
|
- `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
|
|
248
250
|
- `ul.selected > li button:hover, button.remove-all:hover, button:focus`
|
|
249
|
-
- `color: var(--sms-button-hover-color, lightskyblue)`: Color of the
|
|
251
|
+
- `color: var(--sms-button-hover-color, lightskyblue)`: Color of the remove-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
|
|
250
252
|
- `div.multiselect > ul.options`
|
|
251
253
|
- `background: var(--sms-options-bg, white)`: Background of dropdown list.
|
|
252
254
|
- `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
|
|
253
|
-
- `overscroll-behavior: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/
|
|
255
|
+
- `overscroll-behavior: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/docs/Web/CSS/overscroll-behavior).
|
|
254
256
|
- `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);`: Box shadow of dropdown list.
|
|
255
257
|
- `div.multiselect > ul.options > li`
|
|
256
258
|
- `scroll-margin: var(--sms-options-scroll-margin, 100px)`: Top/bottom margin to keep between dropdown list items and top/bottom screen edge when auto-scrolling list to keep items in view.
|