svelte-multiselect 4.0.0 → 4.0.3
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 +108 -69
- package/MultiSelect.svelte.d.ts +7 -1
- package/icons/ChevronExpand.svelte +1 -6
- package/icons/ChevronExpand.svelte.d.ts +13 -8
- package/icons/Cross.svelte +1 -6
- package/icons/Cross.svelte.d.ts +13 -8
- package/icons/{ReadOnly.svelte → Disabled.svelte} +1 -6
- package/icons/Disabled.svelte.d.ts +23 -0
- package/icons/index.d.ts +2 -2
- package/icons/index.js +2 -2
- package/index.d.ts +1 -0
- package/package.json +19 -14
- package/readme.md +117 -75
- package/actions.d.ts +0 -3
- package/actions.js +0 -16
- package/icons/ReadOnly.svelte.d.ts +0 -18
package/MultiSelect.svelte
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
<script >import { createEventDispatcher, onMount, tick } from 'svelte';
|
|
2
2
|
import { fly } from 'svelte/transition';
|
|
3
|
-
import { onClickOutside } from './actions';
|
|
4
3
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
5
|
-
import { CrossIcon, ExpandIcon,
|
|
4
|
+
import { CrossIcon, ExpandIcon, DisabledIcon } from './icons';
|
|
6
5
|
import Wiggle from './Wiggle.svelte';
|
|
7
6
|
export let selected = [];
|
|
8
7
|
export let selectedLabels = [];
|
|
@@ -11,9 +10,11 @@ export let searchText = ``;
|
|
|
11
10
|
export let showOptions = false;
|
|
12
11
|
export let maxSelect = null; // null means any number of options are selectable
|
|
13
12
|
export let maxSelectMsg = null;
|
|
14
|
-
export let
|
|
13
|
+
export let disabled = false;
|
|
14
|
+
export let disabledTitle = `This field is disabled`;
|
|
15
15
|
export let options;
|
|
16
16
|
export let input = null;
|
|
17
|
+
export let outerDiv = null;
|
|
17
18
|
export let placeholder = undefined;
|
|
18
19
|
export let id = undefined;
|
|
19
20
|
export let name = id;
|
|
@@ -30,14 +31,16 @@ export let liSelectedClass = ``;
|
|
|
30
31
|
export let ulOptionsClass = ``;
|
|
31
32
|
export let liOptionClass = ``;
|
|
32
33
|
export let liActiveOptionClass = ``;
|
|
34
|
+
export let inputClass = ``;
|
|
33
35
|
export let removeBtnTitle = `Remove`;
|
|
34
36
|
export let removeAllTitle = `Remove all`;
|
|
35
|
-
// https://github.com/sveltejs/svelte/issues/6964
|
|
36
37
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
37
38
|
export let allowUserOptions = false;
|
|
38
39
|
export let autoScroll = true;
|
|
39
40
|
export let loading = false;
|
|
40
41
|
export let required = false;
|
|
42
|
+
export let autocomplete = `off`;
|
|
43
|
+
export let invalid = false;
|
|
41
44
|
if (maxSelect !== null && maxSelect < 0) {
|
|
42
45
|
console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
|
|
43
46
|
}
|
|
@@ -46,12 +49,8 @@ if (!(options?.length > 0))
|
|
|
46
49
|
if (!Array.isArray(selected))
|
|
47
50
|
console.error(`selected prop must be an array`);
|
|
48
51
|
onMount(() => {
|
|
49
|
-
selected = _options.filter((op) => op?.preselected);
|
|
52
|
+
selected = _options.filter((op) => op?.preselected) ?? [];
|
|
50
53
|
});
|
|
51
|
-
let wiggle = false;
|
|
52
|
-
// formValue binds to input.form-control to prevent form submission if required
|
|
53
|
-
// prop is true and no options are selected
|
|
54
|
-
$: formValue = selectedValues.join(`,`);
|
|
55
54
|
const dispatch = createEventDispatcher();
|
|
56
55
|
function isObject(item) {
|
|
57
56
|
return typeof item === `object` && !Array.isArray(item) && item !== null;
|
|
@@ -77,16 +76,21 @@ $: labels = _options.map((op) => op.label);
|
|
|
77
76
|
$: if (new Set(labels).size !== options.length) {
|
|
78
77
|
console.error(`Option labels must be unique. Duplicates found: ${labels.filter((label, idx) => labels.indexOf(label) !== idx)}`);
|
|
79
78
|
}
|
|
79
|
+
let wiggle = false;
|
|
80
80
|
$: selectedLabels = selected.map((op) => op.label);
|
|
81
81
|
$: selectedValues = selected.map((op) => op.value);
|
|
82
|
+
// formValue binds to input.form-control to prevent form submission if required
|
|
83
|
+
// prop is true and no options are selected
|
|
84
|
+
$: formValue = selectedValues.join(`,`);
|
|
85
|
+
$: if (formValue)
|
|
86
|
+
invalid = false; // reset error status whenever component state changes
|
|
82
87
|
// options matching the current search text
|
|
83
88
|
$: matchingOptions = _options.filter((op) => filterFunc(op, searchText) && !selectedLabels.includes(op.label));
|
|
84
89
|
$: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
|
|
85
90
|
function add(label) {
|
|
86
91
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
87
92
|
wiggle = true;
|
|
88
|
-
if (!
|
|
89
|
-
!selectedLabels.includes(label) &&
|
|
93
|
+
if (!selectedLabels.includes(label) &&
|
|
90
94
|
// for maxselect = 1 we always replace current option with new selection
|
|
91
95
|
(maxSelect === null || maxSelect === 1 || selected.length < maxSelect)) {
|
|
92
96
|
searchText = ``; // reset search string on selection
|
|
@@ -108,7 +112,7 @@ function add(label) {
|
|
|
108
112
|
}
|
|
109
113
|
}
|
|
110
114
|
function remove(label) {
|
|
111
|
-
if (selected.length === 0
|
|
115
|
+
if (selected.length === 0)
|
|
112
116
|
return;
|
|
113
117
|
const option = _options.find((option) => option.label === label);
|
|
114
118
|
if (!option) {
|
|
@@ -119,18 +123,23 @@ function remove(label) {
|
|
|
119
123
|
dispatch(`change`, { option, type: `remove` });
|
|
120
124
|
}
|
|
121
125
|
function setOptionsVisible(show) {
|
|
126
|
+
if (disabled)
|
|
127
|
+
return;
|
|
122
128
|
showOptions = show;
|
|
123
|
-
if (show)
|
|
129
|
+
if (show) {
|
|
124
130
|
input?.focus();
|
|
131
|
+
dispatch(`focus`);
|
|
132
|
+
}
|
|
125
133
|
else {
|
|
126
134
|
input?.blur();
|
|
127
135
|
activeOption = null;
|
|
136
|
+
dispatch(`blur`);
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
// handle all keyboard events this component receives
|
|
131
140
|
async function handleKeydown(event) {
|
|
132
|
-
// on escape: dismiss options dropdown and reset search text
|
|
133
|
-
if (event.key === `Escape`) {
|
|
141
|
+
// on escape or tab out of input: dismiss options dropdown and reset search text
|
|
142
|
+
if (event.key === `Escape` || event.key === `Tab`) {
|
|
134
143
|
setOptionsVisible(false);
|
|
135
144
|
searchText = ``;
|
|
136
145
|
}
|
|
@@ -201,49 +210,71 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
|
|
|
201
210
|
};
|
|
202
211
|
</script>
|
|
203
212
|
|
|
213
|
+
<svelte:window
|
|
214
|
+
on:click={(event) => {
|
|
215
|
+
if (outerDiv && !outerDiv.contains(event.target)) {
|
|
216
|
+
setOptionsVisible(false)
|
|
217
|
+
}
|
|
218
|
+
}}
|
|
219
|
+
/>
|
|
220
|
+
|
|
204
221
|
<!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
|
|
205
222
|
display above those of another following shortly after it -->
|
|
206
223
|
<div
|
|
207
|
-
|
|
224
|
+
bind:this={outerDiv}
|
|
225
|
+
class:disabled
|
|
208
226
|
class:single={maxSelect === 1}
|
|
209
227
|
class:open={showOptions}
|
|
228
|
+
aria-expanded={showOptions}
|
|
229
|
+
aria-multiselectable={maxSelect === null || maxSelect > 1}
|
|
230
|
+
class:invalid
|
|
210
231
|
class="multiselect {outerDivClass}"
|
|
211
232
|
on:mouseup|stopPropagation={() => setOptionsVisible(true)}
|
|
212
|
-
|
|
213
|
-
|
|
233
|
+
title={disabled ? disabledTitle : null}
|
|
234
|
+
aria-disabled={disabled ? `true` : null}
|
|
214
235
|
>
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
236
|
+
<input
|
|
237
|
+
{required}
|
|
238
|
+
bind:value={formValue}
|
|
239
|
+
tabindex="-1"
|
|
240
|
+
aria-hidden="true"
|
|
241
|
+
aria-label="ignore this, used only to prevent form submission if select is required but empty"
|
|
242
|
+
class="form-control"
|
|
243
|
+
on:invalid={() => (invalid = true)}
|
|
244
|
+
/>
|
|
245
|
+
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt;" />
|
|
218
246
|
<ul class="selected {ulSelectedClass}">
|
|
219
247
|
{#each selected as option, idx}
|
|
220
|
-
<li class={liSelectedClass}>
|
|
248
|
+
<li class={liSelectedClass} aria-selected="true">
|
|
221
249
|
<slot name="selected" {option} {idx}>
|
|
222
250
|
{option.label}
|
|
223
251
|
</slot>
|
|
224
|
-
{#if !
|
|
252
|
+
{#if !disabled}
|
|
225
253
|
<button
|
|
226
254
|
on:mouseup|stopPropagation={() => remove(option.label)}
|
|
227
255
|
on:keydown={handleEnterAndSpaceKeys(() => remove(option.label))}
|
|
228
256
|
type="button"
|
|
229
257
|
title="{removeBtnTitle} {option.label}"
|
|
230
258
|
>
|
|
231
|
-
<CrossIcon
|
|
259
|
+
<CrossIcon width="15px" />
|
|
232
260
|
</button>
|
|
233
261
|
{/if}
|
|
234
262
|
</li>
|
|
235
263
|
{/each}
|
|
236
264
|
<li style="display: contents;">
|
|
237
265
|
<input
|
|
266
|
+
class={inputClass}
|
|
238
267
|
bind:this={input}
|
|
239
|
-
autocomplete
|
|
268
|
+
{autocomplete}
|
|
240
269
|
bind:value={searchText}
|
|
241
270
|
on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
|
|
242
271
|
on:keydown={handleKeydown}
|
|
243
272
|
on:focus={() => setOptionsVisible(true)}
|
|
244
273
|
{id}
|
|
245
274
|
{name}
|
|
275
|
+
{disabled}
|
|
246
276
|
placeholder={selectedLabels.length ? `` : placeholder}
|
|
277
|
+
aria-invalid={invalid ? `true` : null}
|
|
247
278
|
/>
|
|
248
279
|
</li>
|
|
249
280
|
</ul>
|
|
@@ -252,8 +283,10 @@ display above those of another following shortly after it -->
|
|
|
252
283
|
<CircleSpinner />
|
|
253
284
|
</slot>
|
|
254
285
|
{/if}
|
|
255
|
-
{#if
|
|
256
|
-
<
|
|
286
|
+
{#if disabled}
|
|
287
|
+
<slot name="disabled-icon">
|
|
288
|
+
<DisabledIcon width="15px" />
|
|
289
|
+
</slot>
|
|
257
290
|
{:else if selected.length > 0}
|
|
258
291
|
{#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
|
|
259
292
|
<Wiggle bind:wiggle angle={20}>
|
|
@@ -263,7 +296,7 @@ display above those of another following shortly after it -->
|
|
|
263
296
|
</span>
|
|
264
297
|
</Wiggle>
|
|
265
298
|
{/if}
|
|
266
|
-
{#if maxSelect !== 1}
|
|
299
|
+
{#if maxSelect !== 1 && selected.length > 1}
|
|
267
300
|
<button
|
|
268
301
|
type="button"
|
|
269
302
|
class="remove-all"
|
|
@@ -271,7 +304,7 @@ display above those of another following shortly after it -->
|
|
|
271
304
|
on:mouseup|stopPropagation={removeAll}
|
|
272
305
|
on:keydown={handleEnterAndSpaceKeys(removeAll)}
|
|
273
306
|
>
|
|
274
|
-
<CrossIcon
|
|
307
|
+
<CrossIcon width="15px" />
|
|
275
308
|
</button>
|
|
276
309
|
{/if}
|
|
277
310
|
{/if}
|
|
@@ -297,6 +330,17 @@ display above those of another following shortly after it -->
|
|
|
297
330
|
class:active
|
|
298
331
|
class:disabled
|
|
299
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)}
|
|
343
|
+
aria-selected="false"
|
|
300
344
|
>
|
|
301
345
|
<slot name="option" {option} {idx}>
|
|
302
346
|
{option.label}
|
|
@@ -316,11 +360,14 @@ display above those of another following shortly after it -->
|
|
|
316
360
|
align-items: center;
|
|
317
361
|
display: flex;
|
|
318
362
|
cursor: text;
|
|
319
|
-
padding: 0 3pt;
|
|
320
363
|
border: var(--sms-border, 1pt solid lightgray);
|
|
321
|
-
border-radius: var(--sms-border-radius,
|
|
322
|
-
background: var(--sms-
|
|
323
|
-
|
|
364
|
+
border-radius: var(--sms-border-radius, 3pt);
|
|
365
|
+
background: var(--sms-bg);
|
|
366
|
+
max-width: var(--sms-max-width);
|
|
367
|
+
padding: var(--sms-padding, 0 3pt);
|
|
368
|
+
color: var(--sms-text-color);
|
|
369
|
+
font-size: var(--sms-font-size, inherit);
|
|
370
|
+
min-height: var(--sms-min-height, 19pt);
|
|
324
371
|
}
|
|
325
372
|
:where(div.multiselect.open) {
|
|
326
373
|
z-index: var(--sms-open-z-index, 4);
|
|
@@ -328,8 +375,9 @@ display above those of another following shortly after it -->
|
|
|
328
375
|
:where(div.multiselect:focus-within) {
|
|
329
376
|
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
330
377
|
}
|
|
331
|
-
:where(div.multiselect.
|
|
332
|
-
background: var(--sms-
|
|
378
|
+
:where(div.multiselect.disabled) {
|
|
379
|
+
background: var(--sms-disabled-bg, lightgray);
|
|
380
|
+
cursor: not-allowed;
|
|
333
381
|
}
|
|
334
382
|
|
|
335
383
|
:where(div.multiselect > ul.selected) {
|
|
@@ -341,48 +389,51 @@ display above those of another following shortly after it -->
|
|
|
341
389
|
}
|
|
342
390
|
:where(div.multiselect > ul.selected > li) {
|
|
343
391
|
align-items: center;
|
|
344
|
-
border-radius:
|
|
392
|
+
border-radius: 3pt;
|
|
345
393
|
display: flex;
|
|
346
394
|
margin: 2pt;
|
|
347
395
|
line-height: normal;
|
|
348
|
-
padding: 1pt 2pt 1pt 5pt;
|
|
349
396
|
transition: 0.3s;
|
|
350
397
|
white-space: nowrap;
|
|
351
|
-
background: var(--sms-selected-bg,
|
|
352
|
-
|
|
398
|
+
background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
|
|
399
|
+
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
400
|
+
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
353
401
|
}
|
|
354
|
-
:where(div.multiselect
|
|
355
|
-
align-items: center;
|
|
402
|
+
:where(div.multiselect button) {
|
|
356
403
|
border-radius: 50%;
|
|
357
404
|
display: flex;
|
|
358
|
-
cursor: pointer;
|
|
359
405
|
transition: 0.2s;
|
|
360
|
-
}
|
|
361
|
-
:where(div.multiselect button) {
|
|
362
406
|
color: inherit;
|
|
363
407
|
background: transparent;
|
|
364
408
|
border: none;
|
|
365
409
|
cursor: pointer;
|
|
366
410
|
outline: none;
|
|
367
|
-
padding: 0
|
|
411
|
+
padding: 0;
|
|
412
|
+
margin: 0 0 0 3pt; /* CSS reset */
|
|
368
413
|
}
|
|
369
|
-
:where(
|
|
370
|
-
|
|
414
|
+
:where(div.multiselect button.remove-all) {
|
|
415
|
+
margin: 0 3pt;
|
|
371
416
|
}
|
|
372
|
-
:where(
|
|
373
|
-
|
|
417
|
+
:where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
|
|
418
|
+
color: var(--sms-button-hover-color, lightskyblue);
|
|
374
419
|
}
|
|
375
420
|
|
|
421
|
+
:where(div.multiselect input) {
|
|
422
|
+
margin: auto 0; /* CSS reset */
|
|
423
|
+
padding: 0; /* CSS reset */
|
|
424
|
+
}
|
|
376
425
|
:where(div.multiselect > ul.selected > li > input) {
|
|
377
426
|
border: none;
|
|
378
427
|
outline: none;
|
|
379
428
|
background: none;
|
|
380
429
|
flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
|
|
381
430
|
min-width: 2em;
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
431
|
+
color: inherit;
|
|
432
|
+
font-size: inherit;
|
|
433
|
+
cursor: inherit; /* needed for disabled state */
|
|
434
|
+
}
|
|
435
|
+
:where(div.multiselect > ul.selected > li > input)::placeholder {
|
|
436
|
+
color: var(--sms-placeholder-color);
|
|
386
437
|
}
|
|
387
438
|
:where(div.multiselect > input.form-control) {
|
|
388
439
|
width: 2em;
|
|
@@ -392,6 +443,7 @@ display above those of another following shortly after it -->
|
|
|
392
443
|
outline: none;
|
|
393
444
|
z-index: -1;
|
|
394
445
|
opacity: 0;
|
|
446
|
+
pointer-events: none;
|
|
395
447
|
}
|
|
396
448
|
|
|
397
449
|
:where(div.multiselect > ul.options) {
|
|
@@ -421,28 +473,15 @@ display above those of another following shortly after it -->
|
|
|
421
473
|
padding: 3pt 2ex;
|
|
422
474
|
}
|
|
423
475
|
:where(div.multiselect > ul.options > li.selected) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
3pt solid var(--sms-selected-color, green)
|
|
427
|
-
);
|
|
428
|
-
background: var(--sms-li-selected-bg, inherit);
|
|
429
|
-
color: var(--sms-li-selected-color, inherit);
|
|
430
|
-
}
|
|
431
|
-
:where(div.multiselect > ul.options > li:not(.selected):hover) {
|
|
432
|
-
border-left: var(
|
|
433
|
-
--sms-li-not-selected-hover-border-left,
|
|
434
|
-
3pt solid var(--sms-active-color, cornflowerblue)
|
|
435
|
-
);
|
|
476
|
+
background: var(--sms-li-selected-bg);
|
|
477
|
+
color: var(--sms-li-selected-color);
|
|
436
478
|
}
|
|
437
479
|
:where(div.multiselect > ul.options > li.active) {
|
|
438
|
-
background: var(--sms-li-active-bg, var(--sms-active-color,
|
|
480
|
+
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
439
481
|
}
|
|
440
482
|
:where(div.multiselect > ul.options > li.disabled) {
|
|
441
483
|
cursor: not-allowed;
|
|
442
484
|
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
443
485
|
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
444
486
|
}
|
|
445
|
-
:where(div.multiselect > ul.options > li.disabled:hover) {
|
|
446
|
-
border-left: unset;
|
|
447
|
-
}
|
|
448
487
|
</style>
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -9,9 +9,11 @@ declare const __propDef: {
|
|
|
9
9
|
showOptions?: boolean | undefined;
|
|
10
10
|
maxSelect?: number | null | undefined;
|
|
11
11
|
maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
|
|
12
|
-
|
|
12
|
+
disabled?: boolean | undefined;
|
|
13
|
+
disabledTitle?: string | undefined;
|
|
13
14
|
options: ProtoOption[];
|
|
14
15
|
input?: HTMLInputElement | null | undefined;
|
|
16
|
+
outerDiv?: HTMLDivElement | null | undefined;
|
|
15
17
|
placeholder?: string | undefined;
|
|
16
18
|
id?: string | undefined;
|
|
17
19
|
name?: string | undefined;
|
|
@@ -24,6 +26,7 @@ declare const __propDef: {
|
|
|
24
26
|
ulOptionsClass?: string | undefined;
|
|
25
27
|
liOptionClass?: string | undefined;
|
|
26
28
|
liActiveOptionClass?: string | undefined;
|
|
29
|
+
inputClass?: string | undefined;
|
|
27
30
|
removeBtnTitle?: string | undefined;
|
|
28
31
|
removeAllTitle?: string | undefined;
|
|
29
32
|
defaultDisabledTitle?: string | undefined;
|
|
@@ -31,6 +34,8 @@ declare const __propDef: {
|
|
|
31
34
|
autoScroll?: boolean | undefined;
|
|
32
35
|
loading?: boolean | undefined;
|
|
33
36
|
required?: boolean | undefined;
|
|
37
|
+
autocomplete?: string | undefined;
|
|
38
|
+
invalid?: boolean | undefined;
|
|
34
39
|
};
|
|
35
40
|
events: {
|
|
36
41
|
mouseup: MouseEvent;
|
|
@@ -43,6 +48,7 @@ declare const __propDef: {
|
|
|
43
48
|
idx: any;
|
|
44
49
|
};
|
|
45
50
|
spinner: {};
|
|
51
|
+
'disabled-icon': {};
|
|
46
52
|
option: {
|
|
47
53
|
option: Option;
|
|
48
54
|
idx: any;
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
<
|
|
2
|
-
export let height = width;
|
|
3
|
-
export let style = ``;
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<svg {width} {height} {style} fill="currentColor" viewBox="0 0 16 16">
|
|
1
|
+
<svg {...$$props} fill="currentColor" viewBox="0 0 16 16">
|
|
7
2
|
<path
|
|
8
3
|
d="M3.646 9.146a.5.5 0 0 1 .708 0L8 12.793l3.646-3.647a.5.5 0 0 1 .708.708l-4 4a.5.5 0 0 1-.708 0l-4-4a.5.5 0 0 1 0-.708zm0-2.292a.5.5 0 0 0 .708 0L8 3.207l3.646 3.647a.5.5 0 0 0 .708-.708l-4-4a.5.5 0 0 0-.708 0l-4 4a.5.5 0 0 0 0 .708z"
|
|
9
4
|
/>
|
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} ChevronExpandProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} ChevronExpandEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} ChevronExpandSlots */
|
|
4
|
+
export default class ChevronExpand extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type ChevronExpandProps = typeof __propDef.props;
|
|
11
|
+
export type ChevronExpandEvents = typeof __propDef.events;
|
|
12
|
+
export type ChevronExpandSlots = typeof __propDef.slots;
|
|
1
13
|
import { SvelteComponentTyped } from "svelte";
|
|
2
14
|
declare const __propDef: {
|
|
3
15
|
props: {
|
|
4
|
-
|
|
5
|
-
height?: string | number | undefined;
|
|
6
|
-
style?: string | undefined;
|
|
16
|
+
[x: string]: any;
|
|
7
17
|
};
|
|
8
18
|
events: {
|
|
9
19
|
[evt: string]: CustomEvent<any>;
|
|
10
20
|
};
|
|
11
21
|
slots: {};
|
|
12
22
|
};
|
|
13
|
-
export declare type ChevronExpandProps = typeof __propDef.props;
|
|
14
|
-
export declare type ChevronExpandEvents = typeof __propDef.events;
|
|
15
|
-
export declare type ChevronExpandSlots = typeof __propDef.slots;
|
|
16
|
-
export default class ChevronExpand extends SvelteComponentTyped<ChevronExpandProps, ChevronExpandEvents, ChevronExpandSlots> {
|
|
17
|
-
}
|
|
18
23
|
export {};
|
package/icons/Cross.svelte
CHANGED
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
<
|
|
2
|
-
export let height = width;
|
|
3
|
-
export let style = ``;
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<svg {width} {height} {style} viewBox="0 0 20 20" fill="currentColor">
|
|
1
|
+
<svg {...$$props} viewBox="0 0 20 20" fill="currentColor">
|
|
7
2
|
<path
|
|
8
3
|
d="M10 1.6a8.4 8.4 0 100 16.8 8.4 8.4 0 000-16.8zm4.789 11.461L13.06 14.79 10 11.729l-3.061 3.06L5.21 13.06 8.272 10 5.211 6.939 6.94 5.211 10 8.271l3.061-3.061 1.729 1.729L11.728 10l3.061 3.061z"
|
|
9
4
|
/>
|
package/icons/Cross.svelte.d.ts
CHANGED
|
@@ -1,18 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} CrossProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} CrossEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} CrossSlots */
|
|
4
|
+
export default class Cross extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type CrossProps = typeof __propDef.props;
|
|
11
|
+
export type CrossEvents = typeof __propDef.events;
|
|
12
|
+
export type CrossSlots = typeof __propDef.slots;
|
|
1
13
|
import { SvelteComponentTyped } from "svelte";
|
|
2
14
|
declare const __propDef: {
|
|
3
15
|
props: {
|
|
4
|
-
|
|
5
|
-
height?: string | number | undefined;
|
|
6
|
-
style?: string | undefined;
|
|
16
|
+
[x: string]: any;
|
|
7
17
|
};
|
|
8
18
|
events: {
|
|
9
19
|
[evt: string]: CustomEvent<any>;
|
|
10
20
|
};
|
|
11
21
|
slots: {};
|
|
12
22
|
};
|
|
13
|
-
export declare type CrossProps = typeof __propDef.props;
|
|
14
|
-
export declare type CrossEvents = typeof __propDef.events;
|
|
15
|
-
export declare type CrossSlots = typeof __propDef.slots;
|
|
16
|
-
export default class Cross extends SvelteComponentTyped<CrossProps, CrossEvents, CrossSlots> {
|
|
17
|
-
}
|
|
18
23
|
export {};
|
|
@@ -1,9 +1,4 @@
|
|
|
1
|
-
<
|
|
2
|
-
export let height = width;
|
|
3
|
-
export let style = ``;
|
|
4
|
-
</script>
|
|
5
|
-
|
|
6
|
-
<svg {width} {height} {style} viewBox="0 0 24 24" fill="currentColor">
|
|
1
|
+
<svg {...$$props} viewBox="0 0 24 24" fill="currentColor">
|
|
7
2
|
<path fill="none" d="M0 0h24v24H0V0z" />
|
|
8
3
|
<path
|
|
9
4
|
d="M14.48 11.95c.17.02.34.05.52.05 2.21 0 4-1.79 4-4s-1.79-4-4-4-4 1.79-4 4c0 .18.03.35.05.52l3.43 3.43zm2.21 2.21L22.53 20H23v-2c0-2.14-3.56-3.5-6.31-3.84zM0 3.12l4 4V10H1v2h3v3h2v-3h2.88l2.51 2.51C9.19 15.11 7 16.3 7 18v2h9.88l4 4 1.41-1.41L1.41 1.71 0 3.12zM6.88 10H6v-.88l.88.88z"
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} DisabledProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} DisabledEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} DisabledSlots */
|
|
4
|
+
export default class Disabled extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type DisabledProps = typeof __propDef.props;
|
|
11
|
+
export type DisabledEvents = typeof __propDef.events;
|
|
12
|
+
export type DisabledSlots = 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
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { default as CrossIcon } from './Cross.svelte';
|
|
2
1
|
export { default as ExpandIcon } from './ChevronExpand.svelte';
|
|
3
|
-
export { default as
|
|
2
|
+
export { default as CrossIcon } from './Cross.svelte';
|
|
3
|
+
export { default as DisabledIcon } from './Disabled.svelte';
|
package/icons/index.js
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
export { default as CrossIcon } from './Cross.svelte';
|
|
2
1
|
export { default as ExpandIcon } from './ChevronExpand.svelte';
|
|
3
|
-
export { default as
|
|
2
|
+
export { default as CrossIcon } from './Cross.svelte';
|
|
3
|
+
export { default as DisabledIcon } from './Disabled.svelte';
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -5,32 +5,37 @@
|
|
|
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.3",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
12
12
|
"devDependencies": {
|
|
13
|
-
"@sveltejs/adapter-static": "^1.0.0-next.
|
|
14
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
15
|
-
"@
|
|
16
|
-
"@typescript-eslint/
|
|
17
|
-
"eslint": "^
|
|
18
|
-
"
|
|
13
|
+
"@sveltejs/adapter-static": "^1.0.0-next.29",
|
|
14
|
+
"@sveltejs/kit": "^1.0.0-next.302",
|
|
15
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.40",
|
|
16
|
+
"@typescript-eslint/eslint-plugin": "^5.16.0",
|
|
17
|
+
"@typescript-eslint/parser": "^5.16.0",
|
|
18
|
+
"@vitest/ui": "^0.7.9",
|
|
19
|
+
"eslint": "^8.11.0",
|
|
20
|
+
"eslint-plugin-svelte3": "^3.4.1",
|
|
19
21
|
"hastscript": "^7.0.2",
|
|
22
|
+
"jsdom": "^19.0.0",
|
|
20
23
|
"mdsvex": "^0.10.5",
|
|
21
|
-
"
|
|
24
|
+
"playwright": "^1.20.0",
|
|
25
|
+
"prettier": "^2.6.0",
|
|
22
26
|
"prettier-plugin-svelte": "^2.6.0",
|
|
23
27
|
"rehype-autolink-headings": "^6.1.1",
|
|
24
28
|
"rehype-slug": "^5.0.1",
|
|
25
29
|
"svelte": "^3.46.4",
|
|
26
|
-
"svelte-check": "^2.4.
|
|
30
|
+
"svelte-check": "^2.4.6",
|
|
27
31
|
"svelte-github-corner": "^0.1.0",
|
|
28
|
-
"svelte-preprocess": "^4.10.
|
|
29
|
-
"svelte-toc": "^0.2.
|
|
30
|
-
"svelte2tsx": "^0.5.
|
|
32
|
+
"svelte-preprocess": "^4.10.4",
|
|
33
|
+
"svelte-toc": "^0.2.8",
|
|
34
|
+
"svelte2tsx": "^0.5.6",
|
|
31
35
|
"tslib": "^2.3.1",
|
|
32
|
-
"typescript": "^4.
|
|
33
|
-
"vite": "^2.8.
|
|
36
|
+
"typescript": "^4.6.2",
|
|
37
|
+
"vite": "^2.8.6",
|
|
38
|
+
"vitest": "^0.7.9"
|
|
34
39
|
},
|
|
35
40
|
"keywords": [
|
|
36
41
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -5,22 +5,23 @@
|
|
|
5
5
|
|
|
6
6
|
<h4 align="center">
|
|
7
7
|
|
|
8
|
+
[](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
|
|
8
9
|
[](https://app.netlify.com/sites/svelte-multiselect/deploys)
|
|
9
10
|
[](https://npmjs.com/package/svelte-multiselect)
|
|
10
11
|
[](https://results.pre-commit.ci/latest/github/janosh/svelte-multiselect/main)
|
|
11
|
-

|
|
12
|
+
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
12
13
|
|
|
13
14
|
</h4>
|
|
14
15
|
|
|
15
|
-
<div class="hide-in-docs">
|
|
16
|
-
|
|
17
|
-
**[Live demo](https://svelte-multiselect.netlify.app)**.
|
|
18
|
-
|
|
19
|
-
</div>
|
|
20
|
-
|
|
21
16
|
**Keyboard-friendly, zero-dependency multi-select Svelte component.**
|
|
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>
|
|
22
|
+
</strong>
|
|
22
23
|
|
|
23
|
-
<slot />
|
|
24
|
+
<slot name="examples" />
|
|
24
25
|
|
|
25
26
|
## Key features
|
|
26
27
|
|
|
@@ -33,21 +34,28 @@
|
|
|
33
34
|
- **No dependencies:** needs only Svelte as dev dependency
|
|
34
35
|
- **Keyboard friendly** for mouse-less form completion
|
|
35
36
|
|
|
37
|
+
<slot name="nav" />
|
|
38
|
+
|
|
36
39
|
## Recent breaking changes
|
|
37
40
|
|
|
38
41
|
- v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
|
|
39
42
|
|
|
40
43
|
```js
|
|
41
|
-
on:add={(e) => console.log(e.detail.token.label)} // v2
|
|
42
|
-
on:add={(e) => console.log(e.detail.option.label)} // v3
|
|
44
|
+
on:add={(e) => console.log(e.detail.token.label)} // v2
|
|
45
|
+
on:add={(e) => console.log(e.detail.option.label)} // v3
|
|
43
46
|
```
|
|
44
47
|
|
|
45
48
|
It also added a separate event type `removeAll` for when the user removes all currently selected options at once which previously fired a normal `remove`. The props `ulTokensClass` and `liTokenClass` were renamed to `ulSelectedClass` and `liSelectedClass`. Similarly, the CSS variable `--sms-token-bg` changed to `--sms-selected-bg`.
|
|
46
49
|
|
|
47
50
|
- v4.0.0 renamed the slots for customizing how selected options and dropdown list items are rendered:
|
|
51
|
+
|
|
48
52
|
- old: `<slot name="renderOptions" />`, new: `<slot name="option" />`
|
|
49
53
|
- old: `<slot name="renderSelected" />`, new: `<slot name="selected" />`
|
|
50
54
|
|
|
55
|
+
- 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}`.
|
|
56
|
+
|
|
57
|
+
- 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`.
|
|
58
|
+
|
|
51
59
|
## Installation
|
|
52
60
|
|
|
53
61
|
```sh
|
|
@@ -60,28 +68,16 @@ yarn add -D svelte-multiselect
|
|
|
60
68
|
<script>
|
|
61
69
|
import MultiSelect from 'svelte-multiselect'
|
|
62
70
|
|
|
63
|
-
const
|
|
64
|
-
`Svelte`,
|
|
65
|
-
`React`,
|
|
66
|
-
`Vue`,
|
|
67
|
-
`Angular`,
|
|
68
|
-
`Polymer`,
|
|
69
|
-
`Ruby on Rails`,
|
|
70
|
-
`ASP.net`,
|
|
71
|
-
`Laravel`,
|
|
72
|
-
`Django`,
|
|
73
|
-
`Express`,
|
|
74
|
-
`Spring`,
|
|
75
|
-
]
|
|
71
|
+
const ui_libs = [`Svelte`, `React`, `Vue`, `Angular`, `...`]
|
|
76
72
|
|
|
77
|
-
let selected
|
|
73
|
+
let selected = []
|
|
78
74
|
</script>
|
|
79
75
|
|
|
80
|
-
Favorite
|
|
76
|
+
Favorite Frontend Frameworks?
|
|
81
77
|
|
|
82
78
|
<code>selected = {JSON.stringify(selected)}</code>
|
|
83
79
|
|
|
84
|
-
<MultiSelect bind:selected options={
|
|
80
|
+
<MultiSelect bind:selected options={ui_libs} />
|
|
85
81
|
```
|
|
86
82
|
|
|
87
83
|
## Props
|
|
@@ -91,26 +87,33 @@ Full list of props/bindable variables for this component:
|
|
|
91
87
|
<div class="table">
|
|
92
88
|
|
|
93
89
|
<!-- prettier-ignore -->
|
|
94
|
-
| name
|
|
95
|
-
|
|
|
96
|
-
| `options`
|
|
97
|
-
| `showOptions`
|
|
98
|
-
| `searchText`
|
|
99
|
-
| `activeOption`
|
|
100
|
-
| `maxSelect`
|
|
101
|
-
| `selected`
|
|
102
|
-
| `selectedLabels`
|
|
103
|
-
| `selectedValues`
|
|
104
|
-
| `noOptionsMsg`
|
|
105
|
-
| `
|
|
106
|
-
| `
|
|
107
|
-
| `
|
|
108
|
-
| `
|
|
109
|
-
| `
|
|
110
|
-
| `
|
|
111
|
-
| `
|
|
112
|
-
| `
|
|
113
|
-
| `
|
|
90
|
+
| name | default | description |
|
|
91
|
+
| :--------------------- | :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
92
|
+
| `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. |
|
|
93
|
+
| `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
|
|
94
|
+
| `searchText` | `` | Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text. |
|
|
95
|
+
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
96
|
+
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
97
|
+
| `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
|
|
98
|
+
| `selectedLabels` | `[]` | Labels of currently selected options. |
|
|
99
|
+
| `selectedValues` | `[]` | Values of currently selected options. |
|
|
100
|
+
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
|
|
101
|
+
| `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
102
|
+
| `disabledTitle` | `This field is disabled` | Tooltip text to display on hover when the component is in `disabled` state. |
|
|
103
|
+
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
|
|
104
|
+
| `input` | `null` | Handle to the `<input>` DOM node. Only available after component mounts (`null` before then). |
|
|
105
|
+
| `outerDiv` | `null` | Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then). |
|
|
106
|
+
| `id` | `undefined` | Applied to the `<input>` element for associating HTML form `<label>`s with this component for accessibility. Also, clicking a `<label>` with same `for` attribute as `id` will focus this component. |
|
|
107
|
+
| `name` | `id` | Applied to the `<input>` element. If not provided, will be set to the value of `id`. Sets the key of this field in a submitted form data object. Not useful at the moment since the value is stored in Svelte state, not on the `<input>`. |
|
|
108
|
+
| `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
|
+
| `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
|
|
110
|
+
| `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. |
|
|
111
|
+
| `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
|
+
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (cross icon) to remove selected option. |
|
|
113
|
+
| `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
|
|
114
|
+
| `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. |
|
|
115
|
+
| `autocomplete` | `'off'` | Applied to the `<input>`. Specifies if browser is permitted to auto-fill this form field. See [MDN docs](https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/autocomplete) for other admissible values. |
|
|
116
|
+
| `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. |
|
|
114
117
|
|
|
115
118
|
</div>
|
|
116
119
|
|
|
@@ -138,19 +141,22 @@ Full list of props/bindable variables for this component:
|
|
|
138
141
|
- `slot="option"`: Customize rendering of dropdown options. Receives as props the `option` object and the zero-indexed position (`idx`) it has in the dropdown.
|
|
139
142
|
- `slot="selected"`: Customize rendering selected tags. Receives as props the `option` object and the zero-indexed position (`idx`) it has in the list of selected items.
|
|
140
143
|
- `slot="spinner"`: Custom spinner component to display when in `loading` state. Receives no props.
|
|
144
|
+
- `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.
|
|
141
145
|
|
|
142
146
|
Example:
|
|
143
147
|
|
|
144
148
|
```svelte
|
|
145
|
-
<MultiSelect options={[`
|
|
149
|
+
<MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]}>
|
|
146
150
|
<span let:idx let:option slot="option">
|
|
147
|
-
{idx + 1}
|
|
148
|
-
{option.label
|
|
151
|
+
{idx + 1}
|
|
152
|
+
{option.label}
|
|
153
|
+
<span style:background={option.label} style=" width: 1em; height: 1em;" />
|
|
149
154
|
</span>
|
|
150
155
|
|
|
151
156
|
<span let:idx let:option slot="selected">
|
|
152
|
-
|
|
157
|
+
{idx + 1}
|
|
153
158
|
{option.label}
|
|
159
|
+
<span style:background={option.label} style=" width: 1em; height: 1em;" />
|
|
154
160
|
</span>
|
|
155
161
|
|
|
156
162
|
<CustomSpinner slot="spinner">
|
|
@@ -161,13 +167,13 @@ Example:
|
|
|
161
167
|
|
|
162
168
|
`MultiSelect.svelte` dispatches the following events:
|
|
163
169
|
|
|
164
|
-
| name | detail
|
|
165
|
-
| ----------- |
|
|
166
|
-
| `add` | `{ option: Option }`
|
|
167
|
-
| `remove` | `{ option: Option }`
|
|
168
|
-
| `removeAll` | `options: Option[]`
|
|
169
|
-
| `change` | `
|
|
170
|
-
| `blur` | none
|
|
170
|
+
| name | detail | description |
|
|
171
|
+
| ----------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
172
|
+
| `add` | `{ option: Option }` | Triggers when a new option is selected. |
|
|
173
|
+
| `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
|
|
174
|
+
| `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
|
|
175
|
+
| `change` | `type: 'add' \| 'remove' \| 'removeAll'` | Triggers when a option is either added or removed, or all options are removed at once. Payload will be a single or an aarray of `Option` objects, respectively. |
|
|
176
|
+
| `blur` | none | Triggers when the input field looses focus. |
|
|
171
177
|
|
|
172
178
|
### Examples
|
|
173
179
|
|
|
@@ -225,22 +231,26 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
225
231
|
|
|
226
232
|
- `div.multiselect`
|
|
227
233
|
- `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.
|
|
228
|
-
- `border-radius: var(--sms-border-radius,
|
|
229
|
-
- `
|
|
230
|
-
- `
|
|
234
|
+
- `border-radius: var(--sms-border-radius, 3pt)`
|
|
235
|
+
- `padding: var(--sms-padding, 0 3pt)`
|
|
236
|
+
- `background: var(--sms-bg)`
|
|
237
|
+
- `color: var(--sms-text-color)`
|
|
238
|
+
- `min-height: var(--sms-min-height)`
|
|
239
|
+
- `max-width: var(--sms-max-width)`
|
|
231
240
|
- `div.multiselect.open`
|
|
232
241
|
- `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
|
|
233
242
|
- `div.multiselect:focus-within`
|
|
234
243
|
- `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` if not set which defaults to `cornflowerblue`.
|
|
235
|
-
- `div.multiselect.
|
|
236
|
-
- `background: var(--sms-
|
|
237
|
-
- `div.multiselect
|
|
238
|
-
- `color: var(--sms-
|
|
244
|
+
- `div.multiselect.disabled`
|
|
245
|
+
- `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
|
|
246
|
+
- `div.multiselect input::placeholder`
|
|
247
|
+
- `color: var(--sms-placeholder-color)`
|
|
239
248
|
- `div.multiselect > ul.selected > li`
|
|
240
|
-
- `background: var(--sms-selected-bg,
|
|
241
|
-
- `
|
|
249
|
+
- `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
|
|
250
|
+
- `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
|
|
251
|
+
- `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
|
|
242
252
|
- `ul.selected > li button:hover, button.remove-all:hover, button:focus`
|
|
243
|
-
- `color: var(--sms-
|
|
253
|
+
- `color: var(--sms-button-hover-color, lightskyblue)`: Color of the cross-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
|
|
244
254
|
- `div.multiselect > ul.options`
|
|
245
255
|
- `background: var(--sms-options-bg, white)`: Background of dropdown list.
|
|
246
256
|
- `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
|
|
@@ -249,13 +259,10 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
249
259
|
- `div.multiselect > ul.options > li`
|
|
250
260
|
- `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.
|
|
251
261
|
- `div.multiselect > ul.options > li.selected`
|
|
252
|
-
- `
|
|
253
|
-
- `
|
|
254
|
-
- `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
|
|
255
|
-
- `div.multiselect > ul.options > li:not(.selected):hover`
|
|
256
|
-
- `border-left: var(--sms-li-not-selected-hover-border-left, 3pt solid var(--sms-active-color, cornflowerblue))`
|
|
262
|
+
- `background: var(--sms-li-selected-bg)`: Background of selected list items in options pane.
|
|
263
|
+
- `color: var(--sms-li-selected-color)`: Text color of selected list items in options pane.
|
|
257
264
|
- `div.multiselect > ul.options > li.active`
|
|
258
|
-
- `background: var(--sms-li-active-bg, var(--sms-active-color,
|
|
265
|
+
- `background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)))`: Background of active dropdown item. Items become active either by mouseover or by navigating to them with arrow keys.
|
|
259
266
|
- `div.multiselect > ul.options > li.disabled`
|
|
260
267
|
- `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
|
|
261
268
|
- `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
|
|
@@ -281,6 +288,7 @@ This simplified version of the DOM structure of this component shows where these
|
|
|
281
288
|
|
|
282
289
|
```svelte
|
|
283
290
|
<div class="multiselect {outerDivClass}">
|
|
291
|
+
<input class={inputClass} />
|
|
284
292
|
<ul class="selected {ulSelectedClass}">
|
|
285
293
|
<li class={liSelectedClass}>Selected 1</li>
|
|
286
294
|
<li class={liSelectedClass}>Selected 2</li>
|
|
@@ -305,8 +313,8 @@ You can alternatively style every part of this component with more fine-grained
|
|
|
305
313
|
:global(div.multiselect.open) {
|
|
306
314
|
/* top-level wrapper div when dropdown open */
|
|
307
315
|
}
|
|
308
|
-
:global(div.multiselect.
|
|
309
|
-
/* top-level wrapper div when in
|
|
316
|
+
:global(div.multiselect.disabled) {
|
|
317
|
+
/* top-level wrapper div when in disabled state */
|
|
310
318
|
}
|
|
311
319
|
:global(div.multiselect > ul.selected) {
|
|
312
320
|
/* selected list */
|
|
@@ -344,6 +352,40 @@ You can alternatively style every part of this component with more fine-grained
|
|
|
344
352
|
}
|
|
345
353
|
```
|
|
346
354
|
|
|
355
|
+
## Downstream testing
|
|
356
|
+
|
|
357
|
+
To test a Svelte component which imports `svelte-multiselect`, you need to configure your test runner to avoid [transpiling issues](https://github.com/janosh/svelte-multiselect/issues/48).
|
|
358
|
+
|
|
359
|
+
For Jest, exclude `svelte-multiselect` from `transformIgnorePatterns` in your `jest.config.json`:
|
|
360
|
+
|
|
361
|
+
```json
|
|
362
|
+
{
|
|
363
|
+
"transformIgnorePatterns": ["node_modules/?!(svelte-multiselect)"],
|
|
364
|
+
"transform": {
|
|
365
|
+
"^.+\\.[t|j]s?$": "esbuild-jest",
|
|
366
|
+
"^.+\\.svelte$": ["svelte-jester", { "preprocess": true }]
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
For Vitest, include `svelte-multiselect` in `deps.inline`:
|
|
372
|
+
|
|
373
|
+
```ts
|
|
374
|
+
// vite.config.ts
|
|
375
|
+
import { svelte } from '@sveltejs/vite-plugin-svelte'
|
|
376
|
+
|
|
377
|
+
export default {
|
|
378
|
+
plugins: [svelte({ hot: !process.env.VITEST })],
|
|
379
|
+
test: {
|
|
380
|
+
deps: {
|
|
381
|
+
inline: [/svelte-multiselect/],
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
}
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
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
|
+
|
|
347
389
|
## Want to contribute?
|
|
348
390
|
|
|
349
391
|
To submit a PR, clone the repo, install dependencies and start the dev server to try out your changes.
|
package/actions.d.ts
DELETED
package/actions.js
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
export function onClickOutside(node, cb) {
|
|
2
|
-
const dispatchOnClickOutside = (event) => {
|
|
3
|
-
const clickWasOutside = node && !node.contains(event.target);
|
|
4
|
-
if (clickWasOutside && !event.defaultPrevented) {
|
|
5
|
-
node.dispatchEvent(new CustomEvent(`clickOutside`));
|
|
6
|
-
if (cb)
|
|
7
|
-
cb();
|
|
8
|
-
}
|
|
9
|
-
};
|
|
10
|
-
document.addEventListener(`click`, dispatchOnClickOutside);
|
|
11
|
-
return {
|
|
12
|
-
destroy() {
|
|
13
|
-
document.removeEventListener(`click`, dispatchOnClickOutside);
|
|
14
|
-
},
|
|
15
|
-
};
|
|
16
|
-
}
|
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
import { SvelteComponentTyped } from "svelte";
|
|
2
|
-
declare const __propDef: {
|
|
3
|
-
props: {
|
|
4
|
-
width?: string | number | undefined;
|
|
5
|
-
height?: string | number | undefined;
|
|
6
|
-
style?: string | undefined;
|
|
7
|
-
};
|
|
8
|
-
events: {
|
|
9
|
-
[evt: string]: CustomEvent<any>;
|
|
10
|
-
};
|
|
11
|
-
slots: {};
|
|
12
|
-
};
|
|
13
|
-
export declare type ReadOnlyProps = typeof __propDef.props;
|
|
14
|
-
export declare type ReadOnlyEvents = typeof __propDef.events;
|
|
15
|
-
export declare type ReadOnlySlots = typeof __propDef.slots;
|
|
16
|
-
export default class ReadOnly extends SvelteComponentTyped<ReadOnlyProps, ReadOnlyEvents, ReadOnlySlots> {
|
|
17
|
-
}
|
|
18
|
-
export {};
|