svelte-multiselect 3.0.1 → 3.2.1
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 -57
- package/MultiSelect.svelte.d.ts +11 -2
- package/Wiggle.svelte +24 -0
- package/Wiggle.svelte.d.ts +25 -0
- package/actions.js +0 -23
- package/package.json +19 -20
- package/readme.md +116 -49
package/MultiSelect.svelte
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import { fly } from 'svelte/transition';
|
|
3
3
|
import { onClickOutside } from './actions';
|
|
4
4
|
import { CrossIcon, ExpandIcon, ReadOnlyIcon } from './icons';
|
|
5
|
+
import Wiggle from './Wiggle.svelte';
|
|
5
6
|
export let selected = [];
|
|
6
7
|
export let selectedLabels = [];
|
|
7
8
|
export let selectedValues = [];
|
|
@@ -11,8 +12,8 @@ export let readonly = false;
|
|
|
11
12
|
export let options;
|
|
12
13
|
export let input = null;
|
|
13
14
|
export let placeholder = undefined;
|
|
14
|
-
export let name = undefined;
|
|
15
15
|
export let id = undefined;
|
|
16
|
+
export let name = id;
|
|
16
17
|
export let noOptionsMsg = `No matching options`;
|
|
17
18
|
export let activeOption = null;
|
|
18
19
|
export let outerDivClass = ``;
|
|
@@ -34,6 +35,7 @@ if (!Array.isArray(selected))
|
|
|
34
35
|
onMount(() => {
|
|
35
36
|
selected = _options.filter((op) => op?.preselected);
|
|
36
37
|
});
|
|
38
|
+
let wiggle = false;
|
|
37
39
|
function isObject(item) {
|
|
38
40
|
return typeof item === `object` && !Array.isArray(item) && item !== null;
|
|
39
41
|
}
|
|
@@ -79,6 +81,8 @@ $: if (
|
|
|
79
81
|
// make the first filtered option active
|
|
80
82
|
activeOption = matchingEnabledOptions[0];
|
|
81
83
|
function add(label) {
|
|
84
|
+
if (selected.length - (maxSelect ?? 0) < 1)
|
|
85
|
+
wiggle = true;
|
|
82
86
|
if (!readonly &&
|
|
83
87
|
!selectedLabels.includes(label) &&
|
|
84
88
|
// for maxselect = 1 we always replace current option with new selection
|
|
@@ -196,57 +200,52 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
|
|
|
196
200
|
<!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
|
|
197
201
|
display above those of another following shortly after it -->
|
|
198
202
|
<div
|
|
199
|
-
{id}
|
|
200
203
|
class="multiselect {outerDivClass}"
|
|
201
204
|
class:readonly
|
|
202
205
|
class:single={maxSelect == 1}
|
|
203
|
-
|
|
206
|
+
class:open={showOptions}
|
|
204
207
|
on:mouseup|stopPropagation={() => setOptionsVisible(true)}
|
|
205
208
|
use:onClickOutside={() => setOptionsVisible(false)}
|
|
206
209
|
use:onClickOutside={() => dispatch(`blur`)}
|
|
207
210
|
>
|
|
208
211
|
<ExpandIcon height="14pt" style="padding: 0 3pt 0 1pt;" />
|
|
209
212
|
<ul class="selected {ulSelectedClass}">
|
|
210
|
-
{#
|
|
211
|
-
<
|
|
212
|
-
{
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
<
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
>
|
|
228
|
-
<CrossIcon height="12pt" />
|
|
229
|
-
</button>
|
|
230
|
-
{/if}
|
|
231
|
-
</li>
|
|
232
|
-
{/each}
|
|
233
|
-
{/if}
|
|
234
|
-
<input
|
|
235
|
-
bind:this={input}
|
|
236
|
-
autocomplete="off"
|
|
237
|
-
bind:value={searchText}
|
|
238
|
-
on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
|
|
239
|
-
on:keydown={handleKeydown}
|
|
240
|
-
on:focus={() => setOptionsVisible(true)}
|
|
241
|
-
{name}
|
|
242
|
-
placeholder={selectedLabels.length ? `` : placeholder}
|
|
243
|
-
/>
|
|
213
|
+
{#each selected as option, idx}
|
|
214
|
+
<li class={liSelectedClass}>
|
|
215
|
+
<slot name="renderSelected" {option} {idx}>
|
|
216
|
+
{option.label}
|
|
217
|
+
</slot>
|
|
218
|
+
{#if !readonly}
|
|
219
|
+
<button
|
|
220
|
+
on:mouseup|stopPropagation={() => remove(option.label)}
|
|
221
|
+
on:keydown={handleEnterAndSpaceKeys(() => remove(option.label))}
|
|
222
|
+
type="button"
|
|
223
|
+
title="{removeBtnTitle} {option.label}"
|
|
224
|
+
>
|
|
225
|
+
<CrossIcon height="12pt" />
|
|
226
|
+
</button>
|
|
227
|
+
{/if}
|
|
228
|
+
</li>
|
|
229
|
+
{/each}
|
|
244
230
|
</ul>
|
|
231
|
+
<input
|
|
232
|
+
bind:this={input}
|
|
233
|
+
autocomplete="off"
|
|
234
|
+
bind:value={searchText}
|
|
235
|
+
on:mouseup|self|stopPropagation={() => setOptionsVisible(true)}
|
|
236
|
+
on:keydown={handleKeydown}
|
|
237
|
+
on:focus={() => setOptionsVisible(true)}
|
|
238
|
+
{id}
|
|
239
|
+
{name}
|
|
240
|
+
placeholder={selectedLabels.length ? `` : placeholder}
|
|
241
|
+
/>
|
|
245
242
|
{#if readonly}
|
|
246
243
|
<ReadOnlyIcon height="14pt" />
|
|
247
244
|
{:else if selected.length > 0}
|
|
248
|
-
{#if maxSelect !== null &&
|
|
249
|
-
<
|
|
245
|
+
{#if maxSelect !== null && maxSelectMsg !== null}
|
|
246
|
+
<Wiggle bind:wiggle angle={20}>
|
|
247
|
+
<span style="padding: 0 3pt;">{maxSelectMsg(selected.length, maxSelect)}</span>
|
|
248
|
+
</Wiggle>
|
|
250
249
|
{/if}
|
|
251
250
|
<button
|
|
252
251
|
type="button"
|
|
@@ -265,7 +264,9 @@ display above those of another following shortly after it -->
|
|
|
265
264
|
class:hidden={!showOptions}
|
|
266
265
|
transition:fly|local={{ duration: 300, y: 40 }}
|
|
267
266
|
>
|
|
268
|
-
{#each matchingOptions as
|
|
267
|
+
{#each matchingOptions as option, idx}
|
|
268
|
+
{@const { label, disabled, title = null, selectedTitle } = option}
|
|
269
|
+
{@const { disabledTitle = defaultDisabledTitle } = option}
|
|
269
270
|
<li
|
|
270
271
|
on:mouseup|preventDefault|stopPropagation
|
|
271
272
|
on:mousedown|preventDefault|stopPropagation={() => {
|
|
@@ -278,34 +279,47 @@ display above those of another following shortly after it -->
|
|
|
278
279
|
title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
|
|
279
280
|
class={liOptionClass}
|
|
280
281
|
>
|
|
281
|
-
{
|
|
282
|
+
<slot name="renderOptions" {option} {idx}>
|
|
283
|
+
{option.label}
|
|
284
|
+
</slot>
|
|
282
285
|
</li>
|
|
283
286
|
{:else}
|
|
284
|
-
{noOptionsMsg}
|
|
287
|
+
<span>{noOptionsMsg}</span>
|
|
285
288
|
{/each}
|
|
286
289
|
</ul>
|
|
287
290
|
{/key}
|
|
288
291
|
</div>
|
|
289
292
|
|
|
290
293
|
<style>
|
|
291
|
-
:where(.multiselect) {
|
|
294
|
+
:where(div.multiselect) {
|
|
292
295
|
position: relative;
|
|
293
296
|
margin: 1em 0;
|
|
294
297
|
border: var(--sms-border, 1pt solid lightgray);
|
|
295
298
|
border-radius: var(--sms-border-radius, 5pt);
|
|
299
|
+
background: var(--sms-input-bg);
|
|
300
|
+
height: var(--sms-input-height, 2em);
|
|
296
301
|
align-items: center;
|
|
297
302
|
min-height: 18pt;
|
|
298
303
|
display: flex;
|
|
299
304
|
cursor: text;
|
|
300
305
|
padding: 0 3pt;
|
|
301
306
|
}
|
|
302
|
-
:where(.multiselect
|
|
307
|
+
:where(div.multiselect.open) {
|
|
308
|
+
z-index: var(--sms-open-z-index, 4);
|
|
309
|
+
}
|
|
310
|
+
:where(div.multiselect:focus-within) {
|
|
303
311
|
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
304
312
|
}
|
|
305
|
-
:where(.multiselect.readonly) {
|
|
313
|
+
:where(div.multiselect.readonly) {
|
|
306
314
|
background: var(--sms-readonly-bg, lightgray);
|
|
307
315
|
}
|
|
308
316
|
|
|
317
|
+
:where(ul.selected) {
|
|
318
|
+
display: flex;
|
|
319
|
+
padding: 0;
|
|
320
|
+
margin: 0;
|
|
321
|
+
flex-wrap: wrap;
|
|
322
|
+
}
|
|
309
323
|
:where(ul.selected > li) {
|
|
310
324
|
background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue));
|
|
311
325
|
align-items: center;
|
|
@@ -332,15 +346,14 @@ display above those of another following shortly after it -->
|
|
|
332
346
|
outline: none;
|
|
333
347
|
padding: 0 2pt;
|
|
334
348
|
}
|
|
335
|
-
:where(ul.selected > li button:hover, button.remove-all:hover) {
|
|
349
|
+
:where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
|
|
336
350
|
color: var(--sms-remove-x-hover-focus-color, lightskyblue);
|
|
337
351
|
}
|
|
338
352
|
:where(button:focus) {
|
|
339
|
-
color: var(--sms-remove-x-hover-focus-color, lightskyblue);
|
|
340
353
|
transform: scale(1.04);
|
|
341
354
|
}
|
|
342
355
|
|
|
343
|
-
:where(.multiselect input) {
|
|
356
|
+
:where(div.multiselect > input) {
|
|
344
357
|
border: none;
|
|
345
358
|
outline: none;
|
|
346
359
|
background: none;
|
|
@@ -352,15 +365,6 @@ display above those of another following shortly after it -->
|
|
|
352
365
|
font-size: calc(16px + 0.1vw);
|
|
353
366
|
}
|
|
354
367
|
|
|
355
|
-
:where(ul.selected) {
|
|
356
|
-
display: flex;
|
|
357
|
-
padding: 0;
|
|
358
|
-
margin: 0;
|
|
359
|
-
flex-wrap: wrap;
|
|
360
|
-
flex: 1;
|
|
361
|
-
overscroll-behavior: none;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
368
|
:where(ul.options) {
|
|
365
369
|
list-style: none;
|
|
366
370
|
max-height: 50vh;
|
|
@@ -371,6 +375,7 @@ display above those of another following shortly after it -->
|
|
|
371
375
|
border-radius: 1ex;
|
|
372
376
|
overflow: auto;
|
|
373
377
|
background: var(--sms-options-bg, white);
|
|
378
|
+
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
374
379
|
}
|
|
375
380
|
:where(ul.options.hidden) {
|
|
376
381
|
visibility: hidden;
|
|
@@ -379,6 +384,10 @@ display above those of another following shortly after it -->
|
|
|
379
384
|
padding: 3pt 2ex;
|
|
380
385
|
cursor: pointer;
|
|
381
386
|
}
|
|
387
|
+
/* for noOptionsMsg */
|
|
388
|
+
:where(ul.options span) {
|
|
389
|
+
padding: 3pt 2ex;
|
|
390
|
+
}
|
|
382
391
|
:where(ul.options li.selected) {
|
|
383
392
|
border-left: var(
|
|
384
393
|
--sms-li-selected-border-left,
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -11,8 +11,8 @@ declare const __propDef: {
|
|
|
11
11
|
options: ProtoOption[];
|
|
12
12
|
input?: HTMLInputElement | null | undefined;
|
|
13
13
|
placeholder?: string | undefined;
|
|
14
|
-
name?: string | undefined;
|
|
15
14
|
id?: string | undefined;
|
|
15
|
+
name?: string | undefined;
|
|
16
16
|
noOptionsMsg?: string | undefined;
|
|
17
17
|
activeOption?: Option | null | undefined;
|
|
18
18
|
outerDivClass?: string | undefined;
|
|
@@ -29,7 +29,16 @@ declare const __propDef: {
|
|
|
29
29
|
} & {
|
|
30
30
|
[evt: string]: CustomEvent<any>;
|
|
31
31
|
};
|
|
32
|
-
slots: {
|
|
32
|
+
slots: {
|
|
33
|
+
renderSelected: {
|
|
34
|
+
option: Option;
|
|
35
|
+
idx: any;
|
|
36
|
+
};
|
|
37
|
+
renderOptions: {
|
|
38
|
+
option: Option;
|
|
39
|
+
idx: any;
|
|
40
|
+
};
|
|
41
|
+
};
|
|
33
42
|
};
|
|
34
43
|
export declare type MultiSelectProps = typeof __propDef.props;
|
|
35
44
|
export declare type MultiSelectEvents = typeof __propDef.events;
|
package/Wiggle.svelte
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
<script >import { spring } from 'svelte/motion';
|
|
2
|
+
// bind to this state and set it to true from parent
|
|
3
|
+
export let wiggle = false;
|
|
4
|
+
// intended use case: set max value during wiggle for one of angle, scale, dx, dy through props
|
|
5
|
+
export let angle = 0; // try 20
|
|
6
|
+
export let scale = 1; // try 1.2
|
|
7
|
+
export let dx = 0; // try 10
|
|
8
|
+
export let dy = 0; // try 10
|
|
9
|
+
export let duration = 200;
|
|
10
|
+
export let stiffness = 0.05;
|
|
11
|
+
export let damping = 0.1;
|
|
12
|
+
let restState = { angle: 0, scale: 1, dx: 0, dy: 0 };
|
|
13
|
+
let store = spring(restState, { stiffness, damping });
|
|
14
|
+
$: store.set(wiggle ? { scale, angle, dx, dy } : restState);
|
|
15
|
+
$: if (wiggle)
|
|
16
|
+
setTimeout(() => (wiggle = false), duration);
|
|
17
|
+
</script>
|
|
18
|
+
|
|
19
|
+
<span
|
|
20
|
+
style:transform="rotate({$store.angle}deg) scale({$store.scale}) translate({$store.dx}px,
|
|
21
|
+
{$store.dy}px)"
|
|
22
|
+
>
|
|
23
|
+
<slot />
|
|
24
|
+
</span>
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { SvelteComponentTyped } from "svelte";
|
|
2
|
+
declare const __propDef: {
|
|
3
|
+
props: {
|
|
4
|
+
wiggle?: boolean | undefined;
|
|
5
|
+
angle?: number | undefined;
|
|
6
|
+
scale?: number | undefined;
|
|
7
|
+
dx?: number | undefined;
|
|
8
|
+
dy?: number | undefined;
|
|
9
|
+
duration?: number | undefined;
|
|
10
|
+
stiffness?: number | undefined;
|
|
11
|
+
damping?: number | undefined;
|
|
12
|
+
};
|
|
13
|
+
events: {
|
|
14
|
+
[evt: string]: CustomEvent<any>;
|
|
15
|
+
};
|
|
16
|
+
slots: {
|
|
17
|
+
default: {};
|
|
18
|
+
};
|
|
19
|
+
};
|
|
20
|
+
export declare type WiggleProps = typeof __propDef.props;
|
|
21
|
+
export declare type WiggleEvents = typeof __propDef.events;
|
|
22
|
+
export declare type WiggleSlots = typeof __propDef.slots;
|
|
23
|
+
export default class Wiggle extends SvelteComponentTyped<WiggleProps, WiggleEvents, WiggleSlots> {
|
|
24
|
+
}
|
|
25
|
+
export {};
|
package/actions.js
CHANGED
|
@@ -14,26 +14,3 @@ export function onClickOutside(node, cb) {
|
|
|
14
14
|
},
|
|
15
15
|
};
|
|
16
16
|
}
|
|
17
|
-
// import { spring } from 'svelte/motion'
|
|
18
|
-
// export default function boop(node: HTMLElement, params = {}) {
|
|
19
|
-
// const { setter } = params
|
|
20
|
-
// const springyRotation = spring(
|
|
21
|
-
// { x: 0, y: 0, rotation: 0, scale: 1 },
|
|
22
|
-
// { stiffness: 0.1, damping: 0.15 }
|
|
23
|
-
// )
|
|
24
|
-
// node.style.display = `inline-block`
|
|
25
|
-
// const unsubscribe = springyRotation.subscribe(({ x, y, rotation, scale }) => {
|
|
26
|
-
// node.style.transform = `translate(${x}px, ${y}px) rotate(${rotation}deg) scale(${scale})`
|
|
27
|
-
// })
|
|
28
|
-
// return {
|
|
29
|
-
// update({ isBooped: x = 0, y = 0, rotation = 0, scale = 1, timing }) {
|
|
30
|
-
// springyRotation.set(
|
|
31
|
-
// isBooped
|
|
32
|
-
// ? { x, y, rotation, scale }
|
|
33
|
-
// : { x: 0, y: 0, rotation: 0, scale: 1 }
|
|
34
|
-
// )
|
|
35
|
-
// if (isBooped) window.setTimeout(() => setter(false), timing)
|
|
36
|
-
// },
|
|
37
|
-
// destroy: unsubscribe,
|
|
38
|
-
// }
|
|
39
|
-
// }
|
package/package.json
CHANGED
|
@@ -5,33 +5,32 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "3.
|
|
8
|
+
"version": "3.2.1",
|
|
9
9
|
"type": "module",
|
|
10
|
-
"svelte": "
|
|
11
|
-
"bugs":
|
|
12
|
-
"url": "https://github.com/janosh/svelte-multiselect/issues"
|
|
13
|
-
},
|
|
10
|
+
"svelte": "index.js",
|
|
11
|
+
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
14
12
|
"devDependencies": {
|
|
15
|
-
"@sveltejs/adapter-static": "^1.0.0-next.
|
|
16
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
17
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
18
|
-
"@typescript-eslint/parser": "^5.
|
|
19
|
-
"eslint": "^8.
|
|
20
|
-
"eslint-plugin-svelte3": "^3.
|
|
13
|
+
"@sveltejs/adapter-static": "^1.0.0-next.26",
|
|
14
|
+
"@sveltejs/kit": "^1.0.0-next.259",
|
|
15
|
+
"@typescript-eslint/eslint-plugin": "^5.10.2",
|
|
16
|
+
"@typescript-eslint/parser": "^5.10.2",
|
|
17
|
+
"eslint": "^8.8.0",
|
|
18
|
+
"eslint-plugin-svelte3": "^3.4.0",
|
|
21
19
|
"hastscript": "^7.0.2",
|
|
22
|
-
"mdsvex": "^0.
|
|
20
|
+
"mdsvex": "^0.10.5",
|
|
23
21
|
"prettier": "^2.5.1",
|
|
24
|
-
"prettier-plugin-svelte": "^2.
|
|
22
|
+
"prettier-plugin-svelte": "^2.6.0",
|
|
25
23
|
"rehype-autolink-headings": "^6.1.1",
|
|
26
24
|
"rehype-slug": "^5.0.1",
|
|
27
|
-
"svelte": "^3.
|
|
28
|
-
"svelte-check": "^2.2
|
|
29
|
-
"svelte-
|
|
30
|
-
"svelte-
|
|
31
|
-
"
|
|
25
|
+
"svelte": "^3.46.3",
|
|
26
|
+
"svelte-check": "^2.4.2",
|
|
27
|
+
"svelte-github-corner": "^0.1.0",
|
|
28
|
+
"svelte-preprocess": "^4.10.2",
|
|
29
|
+
"svelte-toc": "^0.2.3",
|
|
30
|
+
"svelte2tsx": "^0.5.2",
|
|
32
31
|
"tslib": "^2.3.1",
|
|
33
|
-
"typescript": "^4.5.
|
|
34
|
-
"vite": "^2.7.
|
|
32
|
+
"typescript": "^4.5.5",
|
|
33
|
+
"vite": "^2.7.13"
|
|
35
34
|
},
|
|
36
35
|
"keywords": [
|
|
37
36
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -1,33 +1,33 @@
|
|
|
1
|
-
<
|
|
2
|
-
|
|
3
|
-
<
|
|
4
|
-
|
|
5
|
-
</p>
|
|
6
|
-
|
|
7
|
-
<h1 align="center">Svelte MultiSelect</h1>
|
|
1
|
+
<h1 align="center">
|
|
2
|
+
<img src="https://raw.githubusercontent.com/janosh/svelte-toc/main/static/favicon.svg" alt="Svelte MultiSelect" height=60>
|
|
3
|
+
<br> Svelte MultiSelect
|
|
4
|
+
</h1>
|
|
8
5
|
|
|
9
6
|
<h4 align="center">
|
|
10
7
|
|
|
11
8
|
[](https://app.netlify.com/sites/svelte-multiselect/deploys)
|
|
12
9
|
[](https://npmjs.com/package/svelte-multiselect)
|
|
13
10
|
[](https://results.pre-commit.ci/latest/github/janosh/svelte-multiselect/main)
|
|
11
|
+

|
|
14
12
|
|
|
15
13
|
</h4>
|
|
16
14
|
|
|
15
|
+
<div class="hide-in-docs">
|
|
16
|
+
|
|
17
17
|
**[Live demo](https://svelte-multiselect.netlify.app)**.
|
|
18
18
|
|
|
19
19
|
</div>
|
|
20
20
|
|
|
21
|
-
<!-- remove above in docs -->
|
|
22
|
-
|
|
23
21
|
**Keyboard-friendly, zero-dependency multi-select Svelte component.**
|
|
24
22
|
|
|
23
|
+
<slot />
|
|
24
|
+
|
|
25
25
|
## Key Features
|
|
26
26
|
|
|
27
27
|
- **Single / multiple select:** pass `maxSelect={1}` prop to only allow one selection
|
|
28
28
|
- **Dropdowns:** scrollable lists for large numbers of options
|
|
29
29
|
- **Searchable:** start typing to filter options
|
|
30
|
-
- **Tagging:** selected options are recorded as tags
|
|
30
|
+
- **Tagging:** selected options are recorded as tags in the input
|
|
31
31
|
- **Server-side rendering:** no reliance on browser objects like `window` or `document`
|
|
32
32
|
- **Configurable:** see [props](#props)
|
|
33
33
|
- **No dependencies:** needs only Svelte as dev dependency
|
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
|
|
36
36
|
## Recent breaking changes
|
|
37
37
|
|
|
38
|
-
- v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects
|
|
38
|
+
- v2.0.0 added the ability to pass options as objects. As a result, `bind:selected` no longer returns simple strings but objects, even if you still pass in `options` as strings. To get the same stuff you would have gotten from `bind:selected` before, there's now `bind:selectedLabels` (and `bind:selectedValues`).
|
|
39
39
|
- v3.0.0 changed the `event.detail` payload for `'add'`, `'remove'` and `'change'` events from `token` to `option`, e.g.
|
|
40
40
|
|
|
41
41
|
```js
|
|
@@ -88,23 +88,46 @@ Full list of props/bindable variables for this component:
|
|
|
88
88
|
<div class="table">
|
|
89
89
|
|
|
90
90
|
<!-- prettier-ignore -->
|
|
91
|
-
| name | default | description
|
|
92
|
-
| :--------------- | :--------------------------------------------------------- |
|
|
93
|
-
| `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.
|
|
94
|
-
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
|
|
95
|
-
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit.
|
|
96
|
-
| `maxSelectMsg` | ``(current: number, max: number) => `${current}/${max}` `` | Function that returns a string informing the user how many of the maximum allowed options they have currently selected. Return empty string to disable, i.e. `() => ''`.
|
|
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
|
-
| `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it.
|
|
101
|
-
| `placeholder` | `undefined` | String shown in the text input when no option is selected.
|
|
102
|
-
| `input` | `undefined` | Handle to the `<input>` DOM node.
|
|
103
|
-
| `
|
|
104
|
-
| `
|
|
91
|
+
| name | default | description |
|
|
92
|
+
| :--------------- | :--------------------------------------------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `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. |
|
|
94
|
+
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
95
|
+
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
96
|
+
| `maxSelectMsg` | ``(current: number, max: number) => `${current}/${max}` `` | Function that returns a string informing the user how many of the maximum allowed options they have currently selected. Return empty string to disable, i.e. `() => ''`. |
|
|
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
|
+
| `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
101
|
+
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
|
|
102
|
+
| `input` | `undefined` | Handle to the `<input>` DOM node. |
|
|
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
105
|
|
|
106
106
|
</div>
|
|
107
107
|
|
|
108
|
+
## Slots
|
|
109
|
+
|
|
110
|
+
`MultiSelect.svelte` accepts two named slots
|
|
111
|
+
|
|
112
|
+
- `slot="renderOptions"`
|
|
113
|
+
- `slot="renderSelected"`
|
|
114
|
+
|
|
115
|
+
to customize rendering individual options in the dropdown and the list of selected tags, respectively. Each renderer receives the full `option` object along with the zero-indexed position (`idx`) in its list, both available via the `let:` directive:
|
|
116
|
+
|
|
117
|
+
```svelte
|
|
118
|
+
<MultiSelect options={[`Banana`, `Apple`, `Mango`]}>
|
|
119
|
+
<span let:idx let:option slot="renderOptions">
|
|
120
|
+
{idx + 1}. {option.label}
|
|
121
|
+
{option.label === `Mango` ? `🎉` : ``}
|
|
122
|
+
</span>
|
|
123
|
+
|
|
124
|
+
<span let:idx let:option slot="renderSelected">
|
|
125
|
+
#{idx + 1}
|
|
126
|
+
{option.label}
|
|
127
|
+
</span>
|
|
128
|
+
</MultiSelect>
|
|
129
|
+
```
|
|
130
|
+
|
|
108
131
|
## Events
|
|
109
132
|
|
|
110
133
|
`MultiSelect.svelte` dispatches the following events:
|
|
@@ -131,28 +154,72 @@ Full list of props/bindable variables for this component:
|
|
|
131
154
|
/>
|
|
132
155
|
```
|
|
133
156
|
|
|
157
|
+
## TypeScript
|
|
158
|
+
|
|
159
|
+
TypeScript users can import the types used for internal type safety:
|
|
160
|
+
|
|
161
|
+
```svelte
|
|
162
|
+
<script lang="ts">
|
|
163
|
+
import MultiSelect, {
|
|
164
|
+
Option,
|
|
165
|
+
Primitive,
|
|
166
|
+
ProtoOption,
|
|
167
|
+
} from 'svelte-multiselect'
|
|
168
|
+
|
|
169
|
+
const myOptions: Option[] = [
|
|
170
|
+
{ label: 'foo', value: 42 },
|
|
171
|
+
{ label: 'bar', value: 69 },
|
|
172
|
+
]
|
|
173
|
+
</script>
|
|
174
|
+
```
|
|
175
|
+
|
|
134
176
|
## Styling
|
|
135
177
|
|
|
136
|
-
There are 3 ways to style this component.
|
|
178
|
+
There are 3 ways to style this component. To understand which options do what, it helps to keep in mind this simplified DOM structure of the component:
|
|
179
|
+
|
|
180
|
+
```svelte
|
|
181
|
+
<div class="multiselect">
|
|
182
|
+
<ul class="selected">
|
|
183
|
+
<li>Selected 1</li>
|
|
184
|
+
<li>Selected 2</li>
|
|
185
|
+
</ul>
|
|
186
|
+
<ul class="options">
|
|
187
|
+
<li>Option 1</li>
|
|
188
|
+
<li>Option 2</li>
|
|
189
|
+
</ul>
|
|
190
|
+
</div>
|
|
191
|
+
```
|
|
137
192
|
|
|
138
193
|
### With CSS variables
|
|
139
194
|
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
- `
|
|
143
|
-
- `border
|
|
144
|
-
- `
|
|
145
|
-
- `
|
|
146
|
-
- `
|
|
147
|
-
- `
|
|
148
|
-
- `
|
|
149
|
-
- `
|
|
150
|
-
- `
|
|
151
|
-
- `
|
|
152
|
-
- `color: var(--sms-
|
|
153
|
-
- `
|
|
154
|
-
- `background: var(--sms-
|
|
155
|
-
- `
|
|
195
|
+
If you only want to make small adjustments, you can pass the following CSS variables directly to the component as props or define them in a `:global()` CSS context.
|
|
196
|
+
|
|
197
|
+
- `div.multiselect`:
|
|
198
|
+
- `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.
|
|
199
|
+
- `border-radius: var(--sms-border-radius, 5pt)`: Input border radius.
|
|
200
|
+
- `background: var(--sms-input-bg)`: Input background.
|
|
201
|
+
- `height: var(--sms-input-height, 2em)`: Input height.
|
|
202
|
+
- `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when focused. Falls back to `--sms-active-color` if not set which in turn falls back on `cornflowerblue`.
|
|
203
|
+
- `background: var(--sms-readonly-bg, lightgray)`: Background when in readonly state.
|
|
204
|
+
- `div.multiselect.open`:
|
|
205
|
+
- `z-index: var(--sms-open-z-index, 4)`: Useful to ensure the dropdown list of options is displayed on top of other page elements of increased `z-index`.
|
|
206
|
+
- `div.multiselect > input`
|
|
207
|
+
- `color: var(--sms-text-color, inherit)`: Input text color.
|
|
208
|
+
- `ul.selected > li`:
|
|
209
|
+
- `background: var(--sms-selected-bg, var(--sms-active-color, cornflowerblue))`: Background of selected options.
|
|
210
|
+
- `ul.selected > li button:hover, button.remove-all:hover`
|
|
211
|
+
- `color: var(--sms-remove-x-hover-focus-color, lightskyblue)`: Color of the cross-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
|
|
212
|
+
- `ul.options`
|
|
213
|
+
- `background: var(--sms-options-bg, white)`: Background of options list.
|
|
214
|
+
- `background: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior).
|
|
215
|
+
- `ul.options > li.selected`
|
|
216
|
+
- `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
|
|
217
|
+
- `color: var(--sms-li-selected-color, inherit)`: Text color of selected list items in options pane.
|
|
218
|
+
- `ul.options > li.active`
|
|
219
|
+
- `background: var(--sms-li-active-bg, var(--sms-active-color, cornflowerblue))`: Background of active (currently with arrow keys highlighted) list item.
|
|
220
|
+
- `ul.options > li.disabled`
|
|
221
|
+
- `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
|
|
222
|
+
- `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
|
|
156
223
|
|
|
157
224
|
For example, to change the background color of the options dropdown:
|
|
158
225
|
|
|
@@ -173,14 +240,14 @@ The second method allows you to pass in custom classes to the important DOM elem
|
|
|
173
240
|
This simplified version of the DOM structure of this component shows where these classes are inserted:
|
|
174
241
|
|
|
175
242
|
```svelte
|
|
176
|
-
<div class={outerDivClass}>
|
|
177
|
-
<ul class={ulSelectedClass}>
|
|
178
|
-
<li class={liSelectedClass}>
|
|
179
|
-
<li class={liSelectedClass}>
|
|
243
|
+
<div class="multiselect {outerDivClass}">
|
|
244
|
+
<ul class="selected {ulSelectedClass}">
|
|
245
|
+
<li class={liSelectedClass}>Selected 1</li>
|
|
246
|
+
<li class={liSelectedClass}>Selected 2</li>
|
|
180
247
|
</ul>
|
|
181
|
-
<ul class={ulOptionsClass}>
|
|
182
|
-
<li class={liOptionClass}>
|
|
183
|
-
<li class={liOptionClass}>
|
|
248
|
+
<ul class="options {ulOptionsClass}">
|
|
249
|
+
<li class={liOptionClass}>Option 1</li>
|
|
250
|
+
<li class={liOptionClass}>Option 2</li>
|
|
184
251
|
</ul>
|
|
185
252
|
</div>
|
|
186
253
|
```
|