svelte-multiselect 4.0.3 → 4.0.6
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 +102 -75
- package/MultiSelect.svelte.d.ts +3 -2
- package/package.json +16 -15
- package/readme.md +10 -12
package/MultiSelect.svelte
CHANGED
|
@@ -1,9 +1,7 @@
|
|
|
1
|
-
<script >import { createEventDispatcher,
|
|
2
|
-
import { fly } from 'svelte/transition';
|
|
1
|
+
<script >import { createEventDispatcher, tick } from 'svelte';
|
|
3
2
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
3
|
import { CrossIcon, ExpandIcon, DisabledIcon } from './icons';
|
|
5
4
|
import Wiggle from './Wiggle.svelte';
|
|
6
|
-
export let selected = [];
|
|
7
5
|
export let selectedLabels = [];
|
|
8
6
|
export let selectedValues = [];
|
|
9
7
|
export let searchText = ``;
|
|
@@ -13,6 +11,7 @@ export let maxSelectMsg = null;
|
|
|
13
11
|
export let disabled = false;
|
|
14
12
|
export let disabledTitle = `This field is disabled`;
|
|
15
13
|
export let options;
|
|
14
|
+
export let selected = options.filter((op) => op?.preselected) ?? [];
|
|
16
15
|
export let input = null;
|
|
17
16
|
export let outerDiv = null;
|
|
18
17
|
export let placeholder = undefined;
|
|
@@ -36,6 +35,7 @@ export let removeBtnTitle = `Remove`;
|
|
|
36
35
|
export let removeAllTitle = `Remove all`;
|
|
37
36
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
38
37
|
export let allowUserOptions = false;
|
|
38
|
+
export let addOptionMsg = `Create this option...`;
|
|
39
39
|
export let autoScroll = true;
|
|
40
40
|
export let loading = false;
|
|
41
41
|
export let required = false;
|
|
@@ -48,34 +48,27 @@ if (!(options?.length > 0))
|
|
|
48
48
|
console.error(`MultiSelect missing options`);
|
|
49
49
|
if (!Array.isArray(selected))
|
|
50
50
|
console.error(`selected prop must be an array`);
|
|
51
|
-
onMount(() => {
|
|
52
|
-
selected = _options.filter((op) => op?.preselected) ?? [];
|
|
53
|
-
});
|
|
54
51
|
const dispatch = createEventDispatcher();
|
|
55
|
-
|
|
52
|
+
let activeMsg = false; // controls active state of <li>{addOptionMsg}</li>
|
|
53
|
+
function is_object(item) {
|
|
56
54
|
return typeof item === `object` && !Array.isArray(item) && item !== null;
|
|
57
55
|
}
|
|
58
56
|
// process proto options to full ones with mandatory labels
|
|
59
|
-
$: _options = options.map((
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
return op;
|
|
57
|
+
$: _options = options.map((raw_op) => {
|
|
58
|
+
if (is_object(raw_op)) {
|
|
59
|
+
const option = { ...raw_op };
|
|
60
|
+
if (option.value === undefined)
|
|
61
|
+
option.value = option.label;
|
|
62
|
+
return option;
|
|
66
63
|
}
|
|
67
64
|
else {
|
|
68
|
-
if (![`string`, `number`].includes(typeof
|
|
69
|
-
console.
|
|
65
|
+
if (![`string`, `number`].includes(typeof raw_op)) {
|
|
66
|
+
console.warn(`MultiSelect options must be objects, strings or numbers, got ${typeof raw_op}`);
|
|
70
67
|
}
|
|
71
68
|
// even if we logged error above, try to proceed hoping user knows what they're doing
|
|
72
|
-
return { label:
|
|
69
|
+
return { label: raw_op, value: raw_op };
|
|
73
70
|
}
|
|
74
71
|
});
|
|
75
|
-
$: labels = _options.map((op) => op.label);
|
|
76
|
-
$: if (new Set(labels).size !== options.length) {
|
|
77
|
-
console.error(`Option labels must be unique. Duplicates found: ${labels.filter((label, idx) => labels.indexOf(label) !== idx)}`);
|
|
78
|
-
}
|
|
79
72
|
let wiggle = false;
|
|
80
73
|
$: selectedLabels = selected.map((op) => op.label);
|
|
81
74
|
$: selectedValues = selected.map((op) => op.value);
|
|
@@ -87,38 +80,57 @@ $: if (formValue)
|
|
|
87
80
|
// options matching the current search text
|
|
88
81
|
$: matchingOptions = _options.filter((op) => filterFunc(op, searchText) && !selectedLabels.includes(op.label));
|
|
89
82
|
$: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
|
|
83
|
+
// add an option to selected list
|
|
90
84
|
function add(label) {
|
|
91
85
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
92
86
|
wiggle = true;
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
87
|
+
// to prevent duplicate selection, we could add `&& !selectedLabels.includes(label)`
|
|
88
|
+
if (maxSelect === null || maxSelect === 1 || selected.length < maxSelect) {
|
|
89
|
+
// first check if we find option in the options list
|
|
90
|
+
let option = _options.find((op) => op.label === label);
|
|
91
|
+
if (!option && // this has the side-effect of not allowing to user to add the same
|
|
92
|
+
// custom option twice in append mode
|
|
93
|
+
[true, `append`].includes(allowUserOptions) &&
|
|
94
|
+
searchText.length > 0) {
|
|
95
|
+
// user entered text but no options match, so if allowUserOptions=true | 'append', we create new option
|
|
96
|
+
option = { label: searchText, value: searchText };
|
|
97
|
+
if (allowUserOptions === `append`)
|
|
98
|
+
_options = [..._options, option];
|
|
99
|
+
}
|
|
96
100
|
searchText = ``; // reset search string on selection
|
|
97
|
-
const option = _options.find((op) => op.label === label);
|
|
98
101
|
if (!option) {
|
|
99
102
|
console.error(`MultiSelect: option with label ${label} not found`);
|
|
100
103
|
return;
|
|
101
104
|
}
|
|
102
105
|
if (maxSelect === 1) {
|
|
106
|
+
// for maxselect = 1 we always replace current option with new one
|
|
103
107
|
selected = [option];
|
|
104
108
|
}
|
|
105
109
|
else {
|
|
106
|
-
selected = [
|
|
110
|
+
selected = [...selected, option];
|
|
107
111
|
}
|
|
108
112
|
if (selected.length === maxSelect)
|
|
109
113
|
setOptionsVisible(false);
|
|
114
|
+
else
|
|
115
|
+
input?.focus();
|
|
110
116
|
dispatch(`add`, { option });
|
|
111
117
|
dispatch(`change`, { option, type: `add` });
|
|
112
118
|
}
|
|
113
119
|
}
|
|
120
|
+
// remove an option from selected list
|
|
114
121
|
function remove(label) {
|
|
115
122
|
if (selected.length === 0)
|
|
116
123
|
return;
|
|
117
|
-
|
|
124
|
+
selected.splice(selectedLabels.lastIndexOf(label), 1);
|
|
125
|
+
selected = selected; // Svelte rerender after in-place splice
|
|
126
|
+
const option = _options.find((option) => option.label === label) ??
|
|
127
|
+
// if option with label could not be found but allowUserOptions is truthy,
|
|
128
|
+
// assume it was created by user and create correspondidng option object
|
|
129
|
+
// on the fly for use as event payload
|
|
130
|
+
(allowUserOptions && { label, value: label });
|
|
118
131
|
if (!option) {
|
|
119
132
|
return console.error(`MultiSelect: option with label ${label} not found`);
|
|
120
133
|
}
|
|
121
|
-
selected = selected.filter((option) => label !== option.label);
|
|
122
134
|
dispatch(`remove`, { option });
|
|
123
135
|
dispatch(`change`, { option, type: `remove` });
|
|
124
136
|
}
|
|
@@ -145,16 +157,15 @@ async function handleKeydown(event) {
|
|
|
145
157
|
}
|
|
146
158
|
// on enter key: toggle active option and reset search text
|
|
147
159
|
else if (event.key === `Enter`) {
|
|
160
|
+
event.preventDefault(); // prevent enter key from triggering form submission
|
|
148
161
|
if (activeOption) {
|
|
149
162
|
const { label } = activeOption;
|
|
150
163
|
selectedLabels.includes(label) ? remove(label) : add(label);
|
|
151
164
|
searchText = ``;
|
|
152
165
|
}
|
|
153
|
-
else if (
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
options = [...options, { label: searchText, value: searchText }];
|
|
157
|
-
searchText = ``;
|
|
166
|
+
else if (allowUserOptions && searchText.length > 0) {
|
|
167
|
+
// user entered text but no options match, so if allowUserOptions is truthy, we create new option
|
|
168
|
+
add(searchText);
|
|
158
169
|
}
|
|
159
170
|
// no active option and no search text means the options dropdown is closed
|
|
160
171
|
// in which case enter means open it
|
|
@@ -163,11 +174,17 @@ async function handleKeydown(event) {
|
|
|
163
174
|
}
|
|
164
175
|
// on up/down arrow keys: update active option
|
|
165
176
|
else if ([`ArrowDown`, `ArrowUp`].includes(event.key)) {
|
|
166
|
-
if
|
|
167
|
-
|
|
177
|
+
// if no option is active yet, but there are matching options, make first one active
|
|
178
|
+
if (activeOption === null && matchingEnabledOptions.length > 0) {
|
|
168
179
|
activeOption = matchingEnabledOptions[0];
|
|
169
180
|
return;
|
|
170
181
|
}
|
|
182
|
+
else if (allowUserOptions && searchText.length > 0) {
|
|
183
|
+
// if allowUserOptions is truthy and user entered text but no options match, we make
|
|
184
|
+
// <li>{addUserMsg}</li> active on keydown (or toggle it if already active)
|
|
185
|
+
activeMsg = !activeMsg;
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
171
188
|
const increment = event.key === `ArrowUp` ? -1 : 1;
|
|
172
189
|
const newActiveIdx = matchingEnabledOptions.indexOf(activeOption) + increment;
|
|
173
190
|
if (newActiveIdx < 0) {
|
|
@@ -189,10 +206,8 @@ async function handleKeydown(event) {
|
|
|
189
206
|
}
|
|
190
207
|
}
|
|
191
208
|
// on backspace key: remove last selected option
|
|
192
|
-
else if (event.key === `Backspace`) {
|
|
193
|
-
|
|
194
|
-
if (label && !searchText)
|
|
195
|
-
remove(label);
|
|
209
|
+
else if (event.key === `Backspace` && selectedLabels.length > 0 && !searchText) {
|
|
210
|
+
remove(selectedLabels.at(-1));
|
|
196
211
|
}
|
|
197
212
|
}
|
|
198
213
|
const removeAll = () => {
|
|
@@ -309,48 +324,55 @@ display above those of another following shortly after it -->
|
|
|
309
324
|
{/if}
|
|
310
325
|
{/if}
|
|
311
326
|
|
|
312
|
-
{
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
{
|
|
320
|
-
|
|
321
|
-
|
|
327
|
+
<ul class:hidden={!showOptions} class="options {ulOptionsClass}">
|
|
328
|
+
{#each matchingOptions as option, idx}
|
|
329
|
+
{@const { label, disabled, title = null, selectedTitle } = option}
|
|
330
|
+
{@const { disabledTitle = defaultDisabledTitle } = option}
|
|
331
|
+
{@const active = activeOption?.label === label}
|
|
332
|
+
<li
|
|
333
|
+
on:mousedown|stopPropagation
|
|
334
|
+
on:mouseup|stopPropagation={() => {
|
|
335
|
+
if (!disabled) isSelected(label) ? remove(label) : add(label)
|
|
336
|
+
}}
|
|
337
|
+
title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
|
|
338
|
+
class:selected={isSelected(label)}
|
|
339
|
+
class:active
|
|
340
|
+
class:disabled
|
|
341
|
+
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
342
|
+
on:mouseover={() => {
|
|
343
|
+
if (!disabled) activeOption = option
|
|
344
|
+
}}
|
|
345
|
+
on:focus={() => {
|
|
346
|
+
if (!disabled) activeOption = option
|
|
347
|
+
}}
|
|
348
|
+
on:mouseout={() => (activeOption = null)}
|
|
349
|
+
on:blur={() => (activeOption = null)}
|
|
350
|
+
aria-selected="false"
|
|
351
|
+
>
|
|
352
|
+
<slot name="option" {option} {idx}>
|
|
353
|
+
{option.label}
|
|
354
|
+
</slot>
|
|
355
|
+
</li>
|
|
356
|
+
{:else}
|
|
357
|
+
{#if allowUserOptions && searchText}
|
|
322
358
|
<li
|
|
323
|
-
on:
|
|
324
|
-
on:
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
}
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
class:disabled
|
|
332
|
-
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
333
|
-
on:mouseover={() => {
|
|
334
|
-
if (disabled) return
|
|
335
|
-
activeOption = option
|
|
336
|
-
}}
|
|
337
|
-
on:focus={() => {
|
|
338
|
-
if (disabled) return
|
|
339
|
-
activeOption = option
|
|
340
|
-
}}
|
|
341
|
-
on:mouseout={() => (activeOption = null)}
|
|
342
|
-
on:blur={() => (activeOption = null)}
|
|
359
|
+
on:mousedown|stopPropagation
|
|
360
|
+
on:mouseup|stopPropagation={() => add(searchText)}
|
|
361
|
+
title={addOptionMsg}
|
|
362
|
+
class:active={activeMsg}
|
|
363
|
+
on:mouseover={() => (activeMsg = true)}
|
|
364
|
+
on:focus={() => (activeMsg = true)}
|
|
365
|
+
on:mouseout={() => (activeMsg = false)}
|
|
366
|
+
on:blur={() => (activeMsg = false)}
|
|
343
367
|
aria-selected="false"
|
|
344
368
|
>
|
|
345
|
-
|
|
346
|
-
{option.label}
|
|
347
|
-
</slot>
|
|
369
|
+
{addOptionMsg}
|
|
348
370
|
</li>
|
|
349
371
|
{:else}
|
|
350
372
|
<span>{noOptionsMsg}</span>
|
|
351
|
-
{/
|
|
352
|
-
|
|
353
|
-
|
|
373
|
+
{/if}
|
|
374
|
+
{/each}
|
|
375
|
+
</ul>
|
|
354
376
|
</div>
|
|
355
377
|
|
|
356
378
|
<style>
|
|
@@ -459,9 +481,14 @@ display above those of another following shortly after it -->
|
|
|
459
481
|
max-height: var(--sms-options-max-height, 50vh);
|
|
460
482
|
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
461
483
|
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
484
|
+
transition: all 0.2s;
|
|
485
|
+
opacity: 1;
|
|
486
|
+
transform: translateY(0);
|
|
462
487
|
}
|
|
463
488
|
:where(div.multiselect > ul.options.hidden) {
|
|
464
489
|
visibility: hidden;
|
|
490
|
+
opacity: 0;
|
|
491
|
+
transform: translateY(50px);
|
|
465
492
|
}
|
|
466
493
|
:where(div.multiselect > ul.options > li) {
|
|
467
494
|
padding: 3pt 2ex;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -2,7 +2,6 @@ import { SvelteComponentTyped } from "svelte";
|
|
|
2
2
|
import type { Option, Primitive, ProtoOption } from './';
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
|
-
selected?: Option[] | undefined;
|
|
6
5
|
selectedLabels?: Primitive[] | undefined;
|
|
7
6
|
selectedValues?: Primitive[] | undefined;
|
|
8
7
|
searchText?: string | undefined;
|
|
@@ -12,6 +11,7 @@ declare const __propDef: {
|
|
|
12
11
|
disabled?: boolean | undefined;
|
|
13
12
|
disabledTitle?: string | undefined;
|
|
14
13
|
options: ProtoOption[];
|
|
14
|
+
selected?: Option[] | undefined;
|
|
15
15
|
input?: HTMLInputElement | null | undefined;
|
|
16
16
|
outerDiv?: HTMLDivElement | null | undefined;
|
|
17
17
|
placeholder?: string | undefined;
|
|
@@ -31,6 +31,7 @@ declare const __propDef: {
|
|
|
31
31
|
removeAllTitle?: string | undefined;
|
|
32
32
|
defaultDisabledTitle?: string | undefined;
|
|
33
33
|
allowUserOptions?: boolean | "append" | undefined;
|
|
34
|
+
addOptionMsg?: string | undefined;
|
|
34
35
|
autoScroll?: boolean | undefined;
|
|
35
36
|
loading?: boolean | undefined;
|
|
36
37
|
required?: boolean | undefined;
|
|
@@ -38,7 +39,7 @@ declare const __propDef: {
|
|
|
38
39
|
invalid?: boolean | undefined;
|
|
39
40
|
};
|
|
40
41
|
events: {
|
|
41
|
-
|
|
42
|
+
mousedown: MouseEvent;
|
|
42
43
|
} & {
|
|
43
44
|
[evt: string]: CustomEvent<any>;
|
|
44
45
|
};
|
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": "4.0.
|
|
8
|
+
"version": "4.0.6",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
12
12
|
"devDependencies": {
|
|
13
13
|
"@sveltejs/adapter-static": "^1.0.0-next.29",
|
|
14
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
15
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.
|
|
16
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
17
|
-
"@typescript-eslint/parser": "^5.
|
|
18
|
-
"@vitest/ui": "^0.
|
|
19
|
-
"
|
|
14
|
+
"@sveltejs/kit": "^1.0.0-next.308",
|
|
15
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.41",
|
|
16
|
+
"@typescript-eslint/eslint-plugin": "^5.18.0",
|
|
17
|
+
"@typescript-eslint/parser": "^5.18.0",
|
|
18
|
+
"@vitest/ui": "^0.9.0",
|
|
19
|
+
"c8": "^7.11.0",
|
|
20
|
+
"eslint": "^8.12.0",
|
|
20
21
|
"eslint-plugin-svelte3": "^3.4.1",
|
|
21
22
|
"hastscript": "^7.0.2",
|
|
22
23
|
"jsdom": "^19.0.0",
|
|
23
24
|
"mdsvex": "^0.10.5",
|
|
24
|
-
"playwright": "^1.20.
|
|
25
|
-
"prettier": "^2.6.
|
|
25
|
+
"playwright": "^1.20.2",
|
|
26
|
+
"prettier": "^2.6.2",
|
|
26
27
|
"prettier-plugin-svelte": "^2.6.0",
|
|
27
28
|
"rehype-autolink-headings": "^6.1.1",
|
|
28
29
|
"rehype-slug": "^5.0.1",
|
|
29
|
-
"svelte": "^3.46.
|
|
30
|
+
"svelte": "^3.46.6",
|
|
30
31
|
"svelte-check": "^2.4.6",
|
|
31
32
|
"svelte-github-corner": "^0.1.0",
|
|
32
|
-
"svelte-preprocess": "^4.10.
|
|
33
|
-
"svelte-toc": "^0.2.
|
|
33
|
+
"svelte-preprocess": "^4.10.5",
|
|
34
|
+
"svelte-toc": "^0.2.9",
|
|
34
35
|
"svelte2tsx": "^0.5.6",
|
|
35
36
|
"tslib": "^2.3.1",
|
|
36
|
-
"typescript": "^4.6.
|
|
37
|
-
"vite": "^2.
|
|
38
|
-
"vitest": "^0.
|
|
37
|
+
"typescript": "^4.6.3",
|
|
38
|
+
"vite": "^2.9.1",
|
|
39
|
+
"vitest": "^0.9.0"
|
|
39
40
|
},
|
|
40
41
|
"keywords": [
|
|
41
42
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -1,24 +1,21 @@
|
|
|
1
1
|
<h1 align="center">
|
|
2
|
-
<img src="https://raw.githubusercontent.com/janosh/svelte-
|
|
3
|
-
<br> Svelte MultiSelect
|
|
2
|
+
<img src="https://raw.githubusercontent.com/janosh/svelte-multiselect/main/static/favicon.svg" alt="Svelte MultiSelect" height="60" width="60">
|
|
3
|
+
<br class="hide-in-docs"> Svelte MultiSelect
|
|
4
4
|
</h1>
|
|
5
5
|
|
|
6
6
|
<h4 align="center">
|
|
7
7
|
|
|
8
|
+
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
8
9
|
[](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
|
|
9
10
|
[](https://app.netlify.com/sites/svelte-multiselect/deploys)
|
|
10
|
-
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
11
|
+
[](https://npmjs.com/package/svelte-multiselect)
|
|
12
|
+
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
13
13
|
|
|
14
14
|
</h4>
|
|
15
15
|
|
|
16
|
-
**Keyboard-friendly,
|
|
16
|
+
**Keyboard-friendly, accessible multi-select Svelte component.**
|
|
17
17
|
<strong class="hide-in-docs">
|
|
18
|
-
<a href="https://svelte-multiselect.netlify.app">Docs</a>
|
|
19
|
-
</strong>
|
|
20
|
-
<strong>
|
|
21
|
-
<a href="https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05">REPL</a>
|
|
18
|
+
<a href="https://svelte-multiselect.netlify.app">Docs</a>
|
|
22
19
|
</strong>
|
|
23
20
|
|
|
24
21
|
<slot name="examples" />
|
|
@@ -108,6 +105,7 @@ Full list of props/bindable variables for this component:
|
|
|
108
105
|
| `required` | `false` | Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected. |
|
|
109
106
|
| `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
|
|
110
107
|
| `allowUserOptions` | `false` | Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected. |
|
|
108
|
+
| `addOptionMsg` | `'Create this option...'` | Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy. |
|
|
111
109
|
| `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. |
|
|
112
110
|
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (cross icon) to remove selected option. |
|
|
113
111
|
| `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
|
|
@@ -284,7 +282,7 @@ The second method allows you to pass in custom classes to the important DOM elem
|
|
|
284
282
|
- `liOptionClass`
|
|
285
283
|
- `liActiveOptionClass`
|
|
286
284
|
|
|
287
|
-
This simplified version of the DOM structure of
|
|
285
|
+
This simplified version of the DOM structure of the component shows where these classes are inserted:
|
|
288
286
|
|
|
289
287
|
```svelte
|
|
290
288
|
<div class="multiselect {outerDivClass}">
|
|
@@ -384,7 +382,7 @@ export default {
|
|
|
384
382
|
}
|
|
385
383
|
```
|
|
386
384
|
|
|
387
|
-
Here's a [Stackblitz example](https://stackblitz.com/fork/github/davipon/test-svelte-multiselect?initialPath=__vitest__) that also uses [`
|
|
385
|
+
Here's a [Stackblitz example](https://stackblitz.com/fork/github/davipon/test-svelte-multiselect?initialPath=__vitest__) that also uses [`vitest-svelte-kit`](https://github.com/nickbreaton/vitest-svelte-kit).
|
|
388
386
|
|
|
389
387
|
## Want to contribute?
|
|
390
388
|
|