svelte-multiselect 4.0.1 → 4.0.4
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 +66 -38
- package/MultiSelect.svelte.d.ts +3 -0
- 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/Disabled.svelte +1 -6
- package/icons/Disabled.svelte.d.ts +13 -8
- package/index.d.ts +1 -0
- package/package.json +16 -16
- package/readme.md +67 -69
package/MultiSelect.svelte
CHANGED
|
@@ -14,6 +14,7 @@ export let disabled = false;
|
|
|
14
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,6 +31,7 @@ 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
37
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
@@ -38,6 +40,7 @@ export let autoScroll = true;
|
|
|
38
40
|
export let loading = false;
|
|
39
41
|
export let required = false;
|
|
40
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
|
}
|
|
@@ -48,26 +51,21 @@ if (!Array.isArray(selected))
|
|
|
48
51
|
onMount(() => {
|
|
49
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;
|
|
58
57
|
}
|
|
59
58
|
// process proto options to full ones with mandatory labels
|
|
60
59
|
$: _options = options.map((rawOp) => {
|
|
61
|
-
// convert to objects internally if user passed list of strings or numbers as options
|
|
62
60
|
if (isObject(rawOp)) {
|
|
63
|
-
const
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
return
|
|
61
|
+
const option = { ...rawOp };
|
|
62
|
+
if (option.value === undefined)
|
|
63
|
+
option.value = option.label;
|
|
64
|
+
return option;
|
|
67
65
|
}
|
|
68
66
|
else {
|
|
69
67
|
if (![`string`, `number`].includes(typeof rawOp)) {
|
|
70
|
-
console.
|
|
68
|
+
console.warn(`MultiSelect options must be objects, strings or numbers, got ${typeof rawOp}`);
|
|
71
69
|
}
|
|
72
70
|
// even if we logged error above, try to proceed hoping user knows what they're doing
|
|
73
71
|
return { label: rawOp, value: rawOp };
|
|
@@ -75,10 +73,16 @@ $: _options = options.map((rawOp) => {
|
|
|
75
73
|
});
|
|
76
74
|
$: labels = _options.map((op) => op.label);
|
|
77
75
|
$: if (new Set(labels).size !== options.length) {
|
|
78
|
-
console.
|
|
76
|
+
console.warn(`Option labels should be unique. Duplicates found: ${labels.filter((label, idx) => labels.indexOf(label) !== idx)}`);
|
|
79
77
|
}
|
|
78
|
+
let wiggle = false;
|
|
80
79
|
$: selectedLabels = selected.map((op) => op.label);
|
|
81
80
|
$: selectedValues = selected.map((op) => op.value);
|
|
81
|
+
// formValue binds to input.form-control to prevent form submission if required
|
|
82
|
+
// prop is true and no options are selected
|
|
83
|
+
$: formValue = selectedValues.join(`,`);
|
|
84
|
+
$: if (formValue)
|
|
85
|
+
invalid = false; // reset error status whenever component state changes
|
|
82
86
|
// options matching the current search text
|
|
83
87
|
$: matchingOptions = _options.filter((op) => filterFunc(op, searchText) && !selectedLabels.includes(op.label));
|
|
84
88
|
$: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
|
|
@@ -121,17 +125,20 @@ function setOptionsVisible(show) {
|
|
|
121
125
|
if (disabled)
|
|
122
126
|
return;
|
|
123
127
|
showOptions = show;
|
|
124
|
-
if (show)
|
|
128
|
+
if (show) {
|
|
125
129
|
input?.focus();
|
|
130
|
+
dispatch(`focus`);
|
|
131
|
+
}
|
|
126
132
|
else {
|
|
127
133
|
input?.blur();
|
|
128
134
|
activeOption = null;
|
|
135
|
+
dispatch(`blur`);
|
|
129
136
|
}
|
|
130
137
|
}
|
|
131
138
|
// handle all keyboard events this component receives
|
|
132
139
|
async function handleKeydown(event) {
|
|
133
|
-
// on escape: dismiss options dropdown and reset search text
|
|
134
|
-
if (event.key === `Escape`) {
|
|
140
|
+
// on escape or tab out of input: dismiss options dropdown and reset search text
|
|
141
|
+
if (event.key === `Escape` || event.key === `Tab`) {
|
|
135
142
|
setOptionsVisible(false);
|
|
136
143
|
searchText = ``;
|
|
137
144
|
}
|
|
@@ -202,26 +209,42 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
|
|
|
202
209
|
};
|
|
203
210
|
</script>
|
|
204
211
|
|
|
212
|
+
<svelte:window
|
|
213
|
+
on:click={(event) => {
|
|
214
|
+
if (outerDiv && !outerDiv.contains(event.target)) {
|
|
215
|
+
setOptionsVisible(false)
|
|
216
|
+
}
|
|
217
|
+
}}
|
|
218
|
+
/>
|
|
219
|
+
|
|
205
220
|
<!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
|
|
206
221
|
display above those of another following shortly after it -->
|
|
207
222
|
<div
|
|
223
|
+
bind:this={outerDiv}
|
|
208
224
|
class:disabled
|
|
209
225
|
class:single={maxSelect === 1}
|
|
210
226
|
class:open={showOptions}
|
|
227
|
+
aria-expanded={showOptions}
|
|
228
|
+
aria-multiselectable={maxSelect === null || maxSelect > 1}
|
|
229
|
+
class:invalid
|
|
211
230
|
class="multiselect {outerDivClass}"
|
|
212
231
|
on:mouseup|stopPropagation={() => setOptionsVisible(true)}
|
|
213
|
-
on:focusout={() => {
|
|
214
|
-
setOptionsVisible(false)
|
|
215
|
-
dispatch(`blur`)
|
|
216
|
-
}}
|
|
217
232
|
title={disabled ? disabledTitle : null}
|
|
233
|
+
aria-disabled={disabled ? `true` : null}
|
|
218
234
|
>
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
235
|
+
<input
|
|
236
|
+
{required}
|
|
237
|
+
bind:value={formValue}
|
|
238
|
+
tabindex="-1"
|
|
239
|
+
aria-hidden="true"
|
|
240
|
+
aria-label="ignore this, used only to prevent form submission if select is required but empty"
|
|
241
|
+
class="form-control"
|
|
242
|
+
on:invalid={() => (invalid = true)}
|
|
243
|
+
/>
|
|
244
|
+
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt;" />
|
|
222
245
|
<ul class="selected {ulSelectedClass}">
|
|
223
246
|
{#each selected as option, idx}
|
|
224
|
-
<li class={liSelectedClass}>
|
|
247
|
+
<li class={liSelectedClass} aria-selected="true">
|
|
225
248
|
<slot name="selected" {option} {idx}>
|
|
226
249
|
{option.label}
|
|
227
250
|
</slot>
|
|
@@ -232,24 +255,25 @@ display above those of another following shortly after it -->
|
|
|
232
255
|
type="button"
|
|
233
256
|
title="{removeBtnTitle} {option.label}"
|
|
234
257
|
>
|
|
235
|
-
<CrossIcon
|
|
258
|
+
<CrossIcon width="15px" />
|
|
236
259
|
</button>
|
|
237
260
|
{/if}
|
|
238
261
|
</li>
|
|
239
262
|
{/each}
|
|
240
263
|
<li style="display: contents;">
|
|
241
264
|
<input
|
|
265
|
+
class={inputClass}
|
|
242
266
|
bind:this={input}
|
|
243
267
|
{autocomplete}
|
|
244
268
|
bind:value={searchText}
|
|
245
269
|
on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
|
|
246
270
|
on:keydown={handleKeydown}
|
|
247
271
|
on:focus={() => setOptionsVisible(true)}
|
|
248
|
-
on:blur={() => setOptionsVisible(false)}
|
|
249
272
|
{id}
|
|
250
273
|
{name}
|
|
251
274
|
{disabled}
|
|
252
275
|
placeholder={selectedLabels.length ? `` : placeholder}
|
|
276
|
+
aria-invalid={invalid ? `true` : null}
|
|
253
277
|
/>
|
|
254
278
|
</li>
|
|
255
279
|
</ul>
|
|
@@ -260,7 +284,7 @@ display above those of another following shortly after it -->
|
|
|
260
284
|
{/if}
|
|
261
285
|
{#if disabled}
|
|
262
286
|
<slot name="disabled-icon">
|
|
263
|
-
<DisabledIcon
|
|
287
|
+
<DisabledIcon width="15px" />
|
|
264
288
|
</slot>
|
|
265
289
|
{:else if selected.length > 0}
|
|
266
290
|
{#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
|
|
@@ -271,7 +295,7 @@ display above those of another following shortly after it -->
|
|
|
271
295
|
</span>
|
|
272
296
|
</Wiggle>
|
|
273
297
|
{/if}
|
|
274
|
-
{#if maxSelect !== 1}
|
|
298
|
+
{#if maxSelect !== 1 && selected.length > 1}
|
|
275
299
|
<button
|
|
276
300
|
type="button"
|
|
277
301
|
class="remove-all"
|
|
@@ -279,7 +303,7 @@ display above those of another following shortly after it -->
|
|
|
279
303
|
on:mouseup|stopPropagation={removeAll}
|
|
280
304
|
on:keydown={handleEnterAndSpaceKeys(removeAll)}
|
|
281
305
|
>
|
|
282
|
-
<CrossIcon
|
|
306
|
+
<CrossIcon width="15px" />
|
|
283
307
|
</button>
|
|
284
308
|
{/if}
|
|
285
309
|
{/if}
|
|
@@ -315,6 +339,7 @@ display above those of another following shortly after it -->
|
|
|
315
339
|
}}
|
|
316
340
|
on:mouseout={() => (activeOption = null)}
|
|
317
341
|
on:blur={() => (activeOption = null)}
|
|
342
|
+
aria-selected="false"
|
|
318
343
|
>
|
|
319
344
|
<slot name="option" {option} {idx}>
|
|
320
345
|
{option.label}
|
|
@@ -334,13 +359,14 @@ display above those of another following shortly after it -->
|
|
|
334
359
|
align-items: center;
|
|
335
360
|
display: flex;
|
|
336
361
|
cursor: text;
|
|
337
|
-
padding: 0 3pt;
|
|
338
362
|
border: var(--sms-border, 1pt solid lightgray);
|
|
339
363
|
border-radius: var(--sms-border-radius, 3pt);
|
|
340
|
-
background: var(--sms-
|
|
341
|
-
|
|
364
|
+
background: var(--sms-bg);
|
|
365
|
+
max-width: var(--sms-max-width);
|
|
366
|
+
padding: var(--sms-padding, 0 3pt);
|
|
342
367
|
color: var(--sms-text-color);
|
|
343
368
|
font-size: var(--sms-font-size, inherit);
|
|
369
|
+
min-height: var(--sms-min-height, 19pt);
|
|
344
370
|
}
|
|
345
371
|
:where(div.multiselect.open) {
|
|
346
372
|
z-index: var(--sms-open-z-index, 4);
|
|
@@ -362,15 +388,14 @@ display above those of another following shortly after it -->
|
|
|
362
388
|
}
|
|
363
389
|
:where(div.multiselect > ul.selected > li) {
|
|
364
390
|
align-items: center;
|
|
365
|
-
border-radius:
|
|
391
|
+
border-radius: 3pt;
|
|
366
392
|
display: flex;
|
|
367
393
|
margin: 2pt;
|
|
368
394
|
line-height: normal;
|
|
369
|
-
padding: 1pt 5pt;
|
|
370
395
|
transition: 0.3s;
|
|
371
396
|
white-space: nowrap;
|
|
372
397
|
background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
|
|
373
|
-
|
|
398
|
+
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
374
399
|
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
375
400
|
}
|
|
376
401
|
:where(div.multiselect button) {
|
|
@@ -383,13 +408,13 @@ display above those of another following shortly after it -->
|
|
|
383
408
|
cursor: pointer;
|
|
384
409
|
outline: none;
|
|
385
410
|
padding: 0;
|
|
386
|
-
margin: 0 0 0
|
|
411
|
+
margin: 0 0 0 3pt; /* CSS reset */
|
|
387
412
|
}
|
|
388
|
-
:where(
|
|
389
|
-
|
|
413
|
+
:where(div.multiselect button.remove-all) {
|
|
414
|
+
margin: 0 3pt;
|
|
390
415
|
}
|
|
391
|
-
:where(
|
|
392
|
-
|
|
416
|
+
:where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
|
|
417
|
+
color: var(--sms-button-hover-color, lightskyblue);
|
|
393
418
|
}
|
|
394
419
|
|
|
395
420
|
:where(div.multiselect input) {
|
|
@@ -406,6 +431,9 @@ display above those of another following shortly after it -->
|
|
|
406
431
|
font-size: inherit;
|
|
407
432
|
cursor: inherit; /* needed for disabled state */
|
|
408
433
|
}
|
|
434
|
+
:where(div.multiselect > ul.selected > li > input)::placeholder {
|
|
435
|
+
color: var(--sms-placeholder-color);
|
|
436
|
+
}
|
|
409
437
|
:where(div.multiselect > input.form-control) {
|
|
410
438
|
width: 2em;
|
|
411
439
|
position: absolute;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -13,6 +13,7 @@ declare const __propDef: {
|
|
|
13
13
|
disabledTitle?: string | undefined;
|
|
14
14
|
options: ProtoOption[];
|
|
15
15
|
input?: HTMLInputElement | null | undefined;
|
|
16
|
+
outerDiv?: HTMLDivElement | null | undefined;
|
|
16
17
|
placeholder?: string | undefined;
|
|
17
18
|
id?: string | undefined;
|
|
18
19
|
name?: string | undefined;
|
|
@@ -25,6 +26,7 @@ declare const __propDef: {
|
|
|
25
26
|
ulOptionsClass?: string | undefined;
|
|
26
27
|
liOptionClass?: string | undefined;
|
|
27
28
|
liActiveOptionClass?: string | undefined;
|
|
29
|
+
inputClass?: string | undefined;
|
|
28
30
|
removeBtnTitle?: string | undefined;
|
|
29
31
|
removeAllTitle?: string | undefined;
|
|
30
32
|
defaultDisabledTitle?: string | undefined;
|
|
@@ -33,6 +35,7 @@ declare const __propDef: {
|
|
|
33
35
|
loading?: boolean | undefined;
|
|
34
36
|
required?: boolean | undefined;
|
|
35
37
|
autocomplete?: string | undefined;
|
|
38
|
+
invalid?: boolean | undefined;
|
|
36
39
|
};
|
|
37
40
|
events: {
|
|
38
41
|
mouseup: MouseEvent;
|
|
@@ -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 {};
|
package/icons/Disabled.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 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"
|
|
@@ -1,18 +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;
|
|
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 DisabledProps = typeof __propDef.props;
|
|
14
|
-
export declare type DisabledEvents = typeof __propDef.events;
|
|
15
|
-
export declare type DisabledSlots = typeof __propDef.slots;
|
|
16
|
-
export default class Disabled extends SvelteComponentTyped<DisabledProps, DisabledEvents, DisabledSlots> {
|
|
17
|
-
}
|
|
18
23
|
export {};
|
package/index.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -5,37 +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.4",
|
|
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
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.
|
|
16
|
-
"@
|
|
17
|
-
"@typescript-eslint/
|
|
18
|
-
"@
|
|
19
|
-
"
|
|
20
|
-
"eslint": "^8.9.0",
|
|
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",
|
|
21
20
|
"eslint-plugin-svelte3": "^3.4.1",
|
|
22
21
|
"hastscript": "^7.0.2",
|
|
23
22
|
"jsdom": "^19.0.0",
|
|
24
23
|
"mdsvex": "^0.10.5",
|
|
25
|
-
"
|
|
24
|
+
"playwright": "^1.20.0",
|
|
25
|
+
"prettier": "^2.6.0",
|
|
26
26
|
"prettier-plugin-svelte": "^2.6.0",
|
|
27
27
|
"rehype-autolink-headings": "^6.1.1",
|
|
28
28
|
"rehype-slug": "^5.0.1",
|
|
29
29
|
"svelte": "^3.46.4",
|
|
30
|
-
"svelte-check": "^2.4.
|
|
30
|
+
"svelte-check": "^2.4.6",
|
|
31
31
|
"svelte-github-corner": "^0.1.0",
|
|
32
32
|
"svelte-preprocess": "^4.10.4",
|
|
33
|
-
"svelte-toc": "^0.2.
|
|
34
|
-
"svelte2tsx": "^0.5.
|
|
33
|
+
"svelte-toc": "^0.2.8",
|
|
34
|
+
"svelte2tsx": "^0.5.6",
|
|
35
35
|
"tslib": "^2.3.1",
|
|
36
|
-
"typescript": "^4.
|
|
37
|
-
"vite": "^2.8.
|
|
38
|
-
"vitest": "^0.
|
|
36
|
+
"typescript": "^4.6.2",
|
|
37
|
+
"vite": "^2.8.6",
|
|
38
|
+
"vitest": "^0.7.9"
|
|
39
39
|
},
|
|
40
40
|
"keywords": [
|
|
41
41
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -1,27 +1,24 @@
|
|
|
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
|
-
[
|
|
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
|
-
|
|
16
|
+
**Keyboard-friendly, accessible multi-select Svelte component.**
|
|
17
|
+
<strong class="hide-in-docs">
|
|
18
|
+
<a href="https://svelte-multiselect.netlify.app">Docs</a>
|
|
19
|
+
</strong>
|
|
17
20
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
</div>
|
|
21
|
-
|
|
22
|
-
**Keyboard-friendly, zero-dependency multi-select Svelte component.**
|
|
23
|
-
|
|
24
|
-
<slot />
|
|
21
|
+
<slot name="examples" />
|
|
25
22
|
|
|
26
23
|
## Key features
|
|
27
24
|
|
|
@@ -34,6 +31,8 @@
|
|
|
34
31
|
- **No dependencies:** needs only Svelte as dev dependency
|
|
35
32
|
- **Keyboard friendly** for mouse-less form completion
|
|
36
33
|
|
|
34
|
+
<slot name="nav" />
|
|
35
|
+
|
|
37
36
|
## Recent breaking changes
|
|
38
37
|
|
|
39
38
|
- v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
|
|
@@ -52,6 +51,8 @@
|
|
|
52
51
|
|
|
53
52
|
- 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}`.
|
|
54
53
|
|
|
54
|
+
- 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`.
|
|
55
|
+
|
|
55
56
|
## Installation
|
|
56
57
|
|
|
57
58
|
```sh
|
|
@@ -64,28 +65,16 @@ yarn add -D svelte-multiselect
|
|
|
64
65
|
<script>
|
|
65
66
|
import MultiSelect from 'svelte-multiselect'
|
|
66
67
|
|
|
67
|
-
const
|
|
68
|
-
`Svelte`,
|
|
69
|
-
`React`,
|
|
70
|
-
`Vue`,
|
|
71
|
-
`Angular`,
|
|
72
|
-
`Polymer`,
|
|
73
|
-
`Ruby on Rails`,
|
|
74
|
-
`ASP.net`,
|
|
75
|
-
`Laravel`,
|
|
76
|
-
`Django`,
|
|
77
|
-
`Express`,
|
|
78
|
-
`Spring`,
|
|
79
|
-
]
|
|
68
|
+
const ui_libs = [`Svelte`, `React`, `Vue`, `Angular`, `...`]
|
|
80
69
|
|
|
81
70
|
let selected = []
|
|
82
71
|
</script>
|
|
83
72
|
|
|
84
|
-
Favorite
|
|
73
|
+
Favorite Frontend Frameworks?
|
|
85
74
|
|
|
86
75
|
<code>selected = {JSON.stringify(selected)}</code>
|
|
87
76
|
|
|
88
|
-
<MultiSelect bind:selected options={
|
|
77
|
+
<MultiSelect bind:selected options={ui_libs} />
|
|
89
78
|
```
|
|
90
79
|
|
|
91
80
|
## Props
|
|
@@ -95,31 +84,33 @@ Full list of props/bindable variables for this component:
|
|
|
95
84
|
<div class="table">
|
|
96
85
|
|
|
97
86
|
<!-- prettier-ignore -->
|
|
98
|
-
| name | default | description
|
|
99
|
-
| :--------------------- | :-------------------------- |
|
|
100
|
-
| `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.
|
|
101
|
-
| `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible.
|
|
102
|
-
| `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.
|
|
103
|
-
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
|
|
104
|
-
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit.
|
|
105
|
-
| `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively.
|
|
106
|
-
| `selectedLabels` | `[]` | Labels of currently selected options.
|
|
107
|
-
| `selectedValues` | `[]` | Values of currently selected options.
|
|
108
|
-
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string.
|
|
109
|
-
| `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it.
|
|
110
|
-
| `disabledTitle` | `This field is disabled` | Tooltip text to display on hover when the component is in `disabled` state.
|
|
111
|
-
| `placeholder` | `undefined` | String shown in the text input when no option is selected.
|
|
112
|
-
| `input` | `
|
|
113
|
-
| `
|
|
114
|
-
| `
|
|
115
|
-
| `
|
|
116
|
-
| `
|
|
117
|
-
| `
|
|
118
|
-
| `
|
|
119
|
-
| `
|
|
120
|
-
| `
|
|
121
|
-
| `
|
|
122
|
-
| `
|
|
87
|
+
| name | default | description |
|
|
88
|
+
| :--------------------- | :-------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
89
|
+
| `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. |
|
|
90
|
+
| `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
|
|
91
|
+
| `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. |
|
|
92
|
+
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
93
|
+
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
94
|
+
| `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
|
|
95
|
+
| `selectedLabels` | `[]` | Labels of currently selected options. |
|
|
96
|
+
| `selectedValues` | `[]` | Values of currently selected options. |
|
|
97
|
+
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
|
|
98
|
+
| `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
99
|
+
| `disabledTitle` | `This field is disabled` | Tooltip text to display on hover when the component is in `disabled` state. |
|
|
100
|
+
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
|
|
101
|
+
| `input` | `null` | Handle to the `<input>` DOM node. Only available after component mounts (`null` before then). |
|
|
102
|
+
| `outerDiv` | `null` | Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then). |
|
|
103
|
+
| `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. |
|
|
104
|
+
| `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>`. |
|
|
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. |
|
|
106
|
+
| `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
|
|
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
|
+
| `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. |
|
|
109
|
+
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (cross icon) to remove selected option. |
|
|
110
|
+
| `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
|
|
111
|
+
| `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. |
|
|
112
|
+
| `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. |
|
|
113
|
+
| `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. |
|
|
123
114
|
|
|
124
115
|
</div>
|
|
125
116
|
|
|
@@ -152,15 +143,17 @@ Full list of props/bindable variables for this component:
|
|
|
152
143
|
Example:
|
|
153
144
|
|
|
154
145
|
```svelte
|
|
155
|
-
<MultiSelect options={[`
|
|
146
|
+
<MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]}>
|
|
156
147
|
<span let:idx let:option slot="option">
|
|
157
|
-
{idx + 1}
|
|
158
|
-
{option.label
|
|
148
|
+
{idx + 1}
|
|
149
|
+
{option.label}
|
|
150
|
+
<span style:background={option.label} style=" width: 1em; height: 1em;" />
|
|
159
151
|
</span>
|
|
160
152
|
|
|
161
153
|
<span let:idx let:option slot="selected">
|
|
162
|
-
|
|
154
|
+
{idx + 1}
|
|
163
155
|
{option.label}
|
|
156
|
+
<span style:background={option.label} style=" width: 1em; height: 1em;" />
|
|
164
157
|
</span>
|
|
165
158
|
|
|
166
159
|
<CustomSpinner slot="spinner">
|
|
@@ -171,13 +164,13 @@ Example:
|
|
|
171
164
|
|
|
172
165
|
`MultiSelect.svelte` dispatches the following events:
|
|
173
166
|
|
|
174
|
-
| name | detail
|
|
175
|
-
| ----------- |
|
|
176
|
-
| `add` | `{ option: Option }`
|
|
177
|
-
| `remove` | `{ option: Option }`
|
|
178
|
-
| `removeAll` | `options: Option[]`
|
|
179
|
-
| `change` | `
|
|
180
|
-
| `blur` | none
|
|
167
|
+
| name | detail | description |
|
|
168
|
+
| ----------- | ---------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
169
|
+
| `add` | `{ option: Option }` | Triggers when a new option is selected. |
|
|
170
|
+
| `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
|
|
171
|
+
| `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
|
|
172
|
+
| `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. |
|
|
173
|
+
| `blur` | none | Triggers when the input field looses focus. |
|
|
181
174
|
|
|
182
175
|
### Examples
|
|
183
176
|
|
|
@@ -236,21 +229,25 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
236
229
|
- `div.multiselect`
|
|
237
230
|
- `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.
|
|
238
231
|
- `border-radius: var(--sms-border-radius, 3pt)`
|
|
239
|
-
- `
|
|
240
|
-
- `
|
|
232
|
+
- `padding: var(--sms-padding, 0 3pt)`
|
|
233
|
+
- `background: var(--sms-bg)`
|
|
241
234
|
- `color: var(--sms-text-color)`
|
|
235
|
+
- `min-height: var(--sms-min-height)`
|
|
236
|
+
- `max-width: var(--sms-max-width)`
|
|
242
237
|
- `div.multiselect.open`
|
|
243
238
|
- `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
|
|
244
239
|
- `div.multiselect:focus-within`
|
|
245
240
|
- `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`.
|
|
246
241
|
- `div.multiselect.disabled`
|
|
247
242
|
- `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
|
|
243
|
+
- `div.multiselect input::placeholder`
|
|
244
|
+
- `color: var(--sms-placeholder-color)`
|
|
248
245
|
- `div.multiselect > ul.selected > li`
|
|
249
246
|
- `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
|
|
250
|
-
- `
|
|
247
|
+
- `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
|
|
251
248
|
- `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
|
|
252
249
|
- `ul.selected > li button:hover, button.remove-all:hover, button:focus`
|
|
253
|
-
- `color: var(--sms-
|
|
250
|
+
- `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.
|
|
254
251
|
- `div.multiselect > ul.options`
|
|
255
252
|
- `background: var(--sms-options-bg, white)`: Background of dropdown list.
|
|
256
253
|
- `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
|
|
@@ -288,6 +285,7 @@ This simplified version of the DOM structure of this component shows where these
|
|
|
288
285
|
|
|
289
286
|
```svelte
|
|
290
287
|
<div class="multiselect {outerDivClass}">
|
|
288
|
+
<input class={inputClass} />
|
|
291
289
|
<ul class="selected {ulSelectedClass}">
|
|
292
290
|
<li class={liSelectedClass}>Selected 1</li>
|
|
293
291
|
<li class={liSelectedClass}>Selected 2</li>
|
|
@@ -353,7 +351,7 @@ You can alternatively style every part of this component with more fine-grained
|
|
|
353
351
|
|
|
354
352
|
## Downstream testing
|
|
355
353
|
|
|
356
|
-
To test a Svelte component which imports `svelte-multiselect`, you need to configure your test runner to avoid [transpiling issues](https://github.com/
|
|
354
|
+
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).
|
|
357
355
|
|
|
358
356
|
For Jest, exclude `svelte-multiselect` from `transformIgnorePatterns` in your `jest.config.json`:
|
|
359
357
|
|