svelte-multiselect 5.0.4 → 6.0.0
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 +55 -39
- package/MultiSelect.svelte.d.ts +4 -1
- package/icons/Octocat.svelte +5 -0
- package/icons/Octocat.svelte.d.ts +23 -0
- package/icons/index.d.ts +1 -0
- package/icons/index.js +1 -0
- package/index.d.ts +1 -1
- package/index.js +21 -0
- package/package.json +17 -15
- package/readme.md +21 -18
package/MultiSelect.svelte
CHANGED
|
@@ -4,7 +4,7 @@ import CircleSpinner from './CircleSpinner.svelte';
|
|
|
4
4
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
5
5
|
import Wiggle from './Wiggle.svelte';
|
|
6
6
|
export let searchText = ``;
|
|
7
|
-
export let
|
|
7
|
+
export let open = false;
|
|
8
8
|
export let maxSelect = null; // null means any number of options are selectable
|
|
9
9
|
export let maxSelectMsg = null;
|
|
10
10
|
export let disabled = false;
|
|
@@ -21,11 +21,14 @@ export let id = undefined;
|
|
|
21
21
|
export let name = id;
|
|
22
22
|
export let noOptionsMsg = `No matching options`;
|
|
23
23
|
export let activeOption = null;
|
|
24
|
+
export let activeIndex = null;
|
|
24
25
|
export let filterFunc = (op, searchText) => {
|
|
25
26
|
if (!searchText)
|
|
26
27
|
return true;
|
|
27
28
|
return `${get_label(op)}`.toLowerCase().includes(searchText.toLowerCase());
|
|
28
29
|
};
|
|
30
|
+
export let focusInputOnSelect = `desktop`;
|
|
31
|
+
export let breakpoint = 800; // any screen with more horizontal pixels is considered desktop, below is mobile
|
|
29
32
|
export let outerDivClass = ``;
|
|
30
33
|
export let ulSelectedClass = ``;
|
|
31
34
|
export let liSelectedClass = ``;
|
|
@@ -65,6 +68,7 @@ if (!Array.isArray(selected)) {
|
|
|
65
68
|
}
|
|
66
69
|
const dispatch = createEventDispatcher();
|
|
67
70
|
let activeMsg = false; // controls active state of <li>{addOptionMsg}</li>
|
|
71
|
+
let window_width;
|
|
68
72
|
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
69
73
|
$: selectedLabels = selected.map(get_label);
|
|
70
74
|
$: selectedValues = selected.map(get_value);
|
|
@@ -78,9 +82,12 @@ $: matchingOptions = options.filter((op) => filterFunc(op, searchText) &&
|
|
|
78
82
|
!(op instanceof Object && op.disabled) &&
|
|
79
83
|
!selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
|
|
80
84
|
);
|
|
81
|
-
//
|
|
82
|
-
|
|
83
|
-
|
|
85
|
+
// raise if matchingOptions[activeIndex] does not yield a value
|
|
86
|
+
if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
87
|
+
throw `Run time error, activeIndex=${activeIndex} is out of bounds, matchingOptions.length=${matchingOptions.length}`;
|
|
88
|
+
}
|
|
89
|
+
// update activeOption when activeIndex changes
|
|
90
|
+
$: activeOption = activeIndex ? matchingOptions[activeIndex] : null;
|
|
84
91
|
// add an option to selected list
|
|
85
92
|
function add(label) {
|
|
86
93
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
@@ -134,9 +141,11 @@ function add(label) {
|
|
|
134
141
|
}
|
|
135
142
|
}
|
|
136
143
|
if (selected.length === maxSelect)
|
|
137
|
-
|
|
138
|
-
else
|
|
144
|
+
close_dropdown();
|
|
145
|
+
else if (focusInputOnSelect === true ||
|
|
146
|
+
(focusInputOnSelect === `desktop` && window_width > breakpoint)) {
|
|
139
147
|
input?.focus();
|
|
148
|
+
}
|
|
140
149
|
dispatch(`add`, { option });
|
|
141
150
|
dispatch(`change`, { option, type: `add` });
|
|
142
151
|
}
|
|
@@ -158,25 +167,24 @@ function remove(label) {
|
|
|
158
167
|
dispatch(`remove`, { option });
|
|
159
168
|
dispatch(`change`, { option, type: `remove` });
|
|
160
169
|
}
|
|
161
|
-
function
|
|
170
|
+
function open_dropdown() {
|
|
162
171
|
if (disabled)
|
|
163
172
|
return;
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
173
|
+
open = true;
|
|
174
|
+
input?.focus();
|
|
175
|
+
dispatch(`focus`);
|
|
176
|
+
}
|
|
177
|
+
function close_dropdown() {
|
|
178
|
+
open = false;
|
|
179
|
+
input?.blur();
|
|
180
|
+
activeOption = null;
|
|
181
|
+
dispatch(`blur`);
|
|
174
182
|
}
|
|
175
183
|
// handle all keyboard events this component receives
|
|
176
|
-
async function
|
|
184
|
+
async function handle_keydown(event) {
|
|
177
185
|
// on escape or tab out of input: dismiss options dropdown and reset search text
|
|
178
186
|
if (event.key === `Escape` || event.key === `Tab`) {
|
|
179
|
-
|
|
187
|
+
close_dropdown();
|
|
180
188
|
searchText = ``;
|
|
181
189
|
}
|
|
182
190
|
// on enter key: toggle active option and reset search text
|
|
@@ -194,7 +202,7 @@ async function handleKeydown(event) {
|
|
|
194
202
|
// no active option and no search text means the options dropdown is closed
|
|
195
203
|
// in which case enter means open it
|
|
196
204
|
else
|
|
197
|
-
|
|
205
|
+
open_dropdown();
|
|
198
206
|
}
|
|
199
207
|
// on up/down arrow keys: update active option
|
|
200
208
|
else if ([`ArrowDown`, `ArrowUp`].includes(event.key)) {
|
|
@@ -232,7 +240,10 @@ async function handleKeydown(event) {
|
|
|
232
240
|
// around start/end of option list. Find a better solution than waiting 10 ms to.
|
|
233
241
|
setTimeout(() => {
|
|
234
242
|
const li = document.querySelector(`ul.options > li.active`);
|
|
235
|
-
li
|
|
243
|
+
if (li) {
|
|
244
|
+
li.parentNode?.scrollIntoView({ block: `center` });
|
|
245
|
+
li.scrollIntoViewIfNeeded();
|
|
246
|
+
}
|
|
236
247
|
}, 10);
|
|
237
248
|
}
|
|
238
249
|
}
|
|
@@ -254,26 +265,29 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
254
265
|
handler();
|
|
255
266
|
}
|
|
256
267
|
};
|
|
268
|
+
function on_click_outside(event) {
|
|
269
|
+
if (outerDiv && !outerDiv.contains(event.target)) {
|
|
270
|
+
close_dropdown();
|
|
271
|
+
}
|
|
272
|
+
}
|
|
257
273
|
</script>
|
|
258
274
|
|
|
259
275
|
<svelte:window
|
|
260
|
-
on:click={
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
}
|
|
264
|
-
}}
|
|
276
|
+
on:click={on_click_outside}
|
|
277
|
+
on:touchstart={on_click_outside}
|
|
278
|
+
bind:innerWidth={window_width}
|
|
265
279
|
/>
|
|
266
280
|
|
|
267
281
|
<div
|
|
268
282
|
bind:this={outerDiv}
|
|
269
283
|
class:disabled
|
|
270
284
|
class:single={maxSelect === 1}
|
|
271
|
-
class:open
|
|
272
|
-
aria-expanded={
|
|
285
|
+
class:open
|
|
286
|
+
aria-expanded={open}
|
|
273
287
|
aria-multiselectable={maxSelect === null || maxSelect > 1}
|
|
274
288
|
class:invalid
|
|
275
289
|
class="multiselect {outerDivClass}"
|
|
276
|
-
on:mouseup|stopPropagation={
|
|
290
|
+
on:mouseup|stopPropagation={open_dropdown}
|
|
277
291
|
title={disabled ? disabledTitle : null}
|
|
278
292
|
aria-disabled={disabled ? `true` : null}
|
|
279
293
|
>
|
|
@@ -315,9 +329,9 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
315
329
|
bind:this={input}
|
|
316
330
|
{autocomplete}
|
|
317
331
|
bind:value={searchText}
|
|
318
|
-
on:mouseup|self|stopPropagation={
|
|
319
|
-
on:keydown={
|
|
320
|
-
on:focus={
|
|
332
|
+
on:mouseup|self|stopPropagation={open_dropdown}
|
|
333
|
+
on:keydown={handle_keydown}
|
|
334
|
+
on:focus={open_dropdown}
|
|
321
335
|
{id}
|
|
322
336
|
{name}
|
|
323
337
|
{disabled}
|
|
@@ -359,7 +373,7 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
359
373
|
|
|
360
374
|
<!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
|
|
361
375
|
{#if searchText || options?.length > 0}
|
|
362
|
-
<ul class:hidden={!
|
|
376
|
+
<ul class:hidden={!open} class="options {ulOptionsClass}">
|
|
363
377
|
{#each matchingOptions as option, idx}
|
|
364
378
|
{@const {
|
|
365
379
|
label,
|
|
@@ -368,7 +382,7 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
368
382
|
selectedTitle = null,
|
|
369
383
|
disabledTitle = defaultDisabledTitle,
|
|
370
384
|
} = option instanceof Object ? option : { label: option }}
|
|
371
|
-
{@const active =
|
|
385
|
+
{@const active = activeIndex === idx}
|
|
372
386
|
<li
|
|
373
387
|
on:mousedown|stopPropagation
|
|
374
388
|
on:mouseup|stopPropagation={() => {
|
|
@@ -382,13 +396,13 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
382
396
|
class:disabled
|
|
383
397
|
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
384
398
|
on:mouseover={() => {
|
|
385
|
-
if (!disabled)
|
|
399
|
+
if (!disabled) activeIndex = idx
|
|
386
400
|
}}
|
|
387
401
|
on:focus={() => {
|
|
388
|
-
if (!disabled)
|
|
402
|
+
if (!disabled) activeIndex = idx
|
|
389
403
|
}}
|
|
390
|
-
on:mouseout={() => (
|
|
391
|
-
on:blur={() => (
|
|
404
|
+
on:mouseout={() => (activeIndex = null)}
|
|
405
|
+
on:blur={() => (activeIndex = null)}
|
|
392
406
|
aria-selected="false"
|
|
393
407
|
>
|
|
394
408
|
<slot name="option" {option} {idx}>
|
|
@@ -499,9 +513,11 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
499
513
|
background: none;
|
|
500
514
|
flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
|
|
501
515
|
min-width: 2em;
|
|
502
|
-
color
|
|
516
|
+
/* ensure input uses text color and not --sms-selected-text-color */
|
|
517
|
+
color: var(--sms-text-color);
|
|
503
518
|
font-size: inherit;
|
|
504
519
|
cursor: inherit; /* needed for disabled state */
|
|
520
|
+
border-radius: 0; /* reset ul.selected > li */
|
|
505
521
|
}
|
|
506
522
|
:where(div.multiselect > ul.selected > li > input)::placeholder {
|
|
507
523
|
padding-left: 5pt;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -3,7 +3,7 @@ import type { MultiSelectEvents, Option } from './';
|
|
|
3
3
|
declare const __propDef: {
|
|
4
4
|
props: {
|
|
5
5
|
searchText?: string | undefined;
|
|
6
|
-
|
|
6
|
+
open?: boolean | undefined;
|
|
7
7
|
maxSelect?: number | null | undefined;
|
|
8
8
|
maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
|
|
9
9
|
disabled?: boolean | undefined;
|
|
@@ -20,7 +20,10 @@ declare const __propDef: {
|
|
|
20
20
|
name?: string | undefined;
|
|
21
21
|
noOptionsMsg?: string | undefined;
|
|
22
22
|
activeOption?: Option | null | undefined;
|
|
23
|
+
activeIndex?: number | null | undefined;
|
|
23
24
|
filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
|
|
25
|
+
focusInputOnSelect?: boolean | "desktop" | undefined;
|
|
26
|
+
breakpoint?: number | undefined;
|
|
24
27
|
outerDivClass?: string | undefined;
|
|
25
28
|
ulSelectedClass?: string | undefined;
|
|
26
29
|
liSelectedClass?: string | undefined;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
<svg {...$$props} viewBox="0 0 24 24" fill="currentColor">
|
|
2
|
+
<path
|
|
3
|
+
d="M8.422 20.081c0 .896.01 1.753.016 2.285a.617.617 0 0 0 .422.58c2.078.686 4.317.718 6.414.091l.292-.087a.67.67 0 0 0 .478-.638c.005-.733.017-2.017.017-3.53c0-1.372-.477-2.25-1.031-2.707c3.399-.366 6.97-1.61 6.97-7.227c0-1.61-.592-2.91-1.566-3.934c.153-.366.688-1.866-.153-3.878c0 0-1.28-.403-4.201 1.5a14.76 14.76 0 0 0-3.82-.494c-1.298 0-2.597.165-3.819.494C5.52.65 4.24 1.036 4.24 1.036c-.84 2.012-.306 3.512-.153 3.878a5.565 5.565 0 0 0-1.566 3.934c0 5.598 3.552 6.86 6.951 7.227c-.439.366-.84 1.006-.973 1.957c-.879.384-3.075 1.006-4.45-1.207c-.286-.44-1.146-1.519-2.349-1.5c-1.28.018-.516.695.02.97c.648.347 1.393 1.646 1.565 2.067c.306.823 1.299 2.396 5.137 1.72Z"
|
|
4
|
+
/>
|
|
5
|
+
</svg>
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/** @typedef {typeof __propDef.props} OctocatProps */
|
|
2
|
+
/** @typedef {typeof __propDef.events} OctocatEvents */
|
|
3
|
+
/** @typedef {typeof __propDef.slots} OctocatSlots */
|
|
4
|
+
export default class Octocat extends SvelteComponentTyped<{
|
|
5
|
+
[x: string]: any;
|
|
6
|
+
}, {
|
|
7
|
+
[evt: string]: CustomEvent<any>;
|
|
8
|
+
}, {}> {
|
|
9
|
+
}
|
|
10
|
+
export type OctocatProps = typeof __propDef.props;
|
|
11
|
+
export type OctocatEvents = typeof __propDef.events;
|
|
12
|
+
export type OctocatSlots = typeof __propDef.slots;
|
|
13
|
+
import { SvelteComponentTyped } from "svelte";
|
|
14
|
+
declare const __propDef: {
|
|
15
|
+
props: {
|
|
16
|
+
[x: string]: any;
|
|
17
|
+
};
|
|
18
|
+
events: {
|
|
19
|
+
[evt: string]: CustomEvent<any>;
|
|
20
|
+
};
|
|
21
|
+
slots: {};
|
|
22
|
+
};
|
|
23
|
+
export {};
|
package/icons/index.d.ts
CHANGED
package/icons/index.js
CHANGED
package/index.d.ts
CHANGED
|
@@ -32,4 +32,4 @@ export declare type MultiSelectEvents = {
|
|
|
32
32
|
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
33
33
|
};
|
|
34
34
|
export declare const get_label: (op: Option) => string | number;
|
|
35
|
-
export declare const get_value: (op: Option) =>
|
|
35
|
+
export declare const get_value: (op: Option) => {};
|
package/index.js
CHANGED
|
@@ -3,3 +3,24 @@ export { default } from './MultiSelect.svelte';
|
|
|
3
3
|
export const get_label = (op) => (op instanceof Object ? op.label : op);
|
|
4
4
|
// fallback on label if option is object and value is undefined
|
|
5
5
|
export const get_value = (op) => op instanceof Object ? op.value ?? op.label : op;
|
|
6
|
+
// Firefox lacks support for scrollIntoViewIfNeeded, see
|
|
7
|
+
// https://github.com/janosh/svelte-multiselect/issues/87
|
|
8
|
+
// this polyfill was copied from
|
|
9
|
+
// https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
|
|
10
|
+
if (typeof Element !== `undefined` &&
|
|
11
|
+
!Element.prototype?.scrollIntoViewIfNeeded) {
|
|
12
|
+
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded = true) {
|
|
13
|
+
const el = this;
|
|
14
|
+
new IntersectionObserver(function ([entry]) {
|
|
15
|
+
const ratio = entry.intersectionRatio;
|
|
16
|
+
if (ratio < 1) {
|
|
17
|
+
const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`;
|
|
18
|
+
el.scrollIntoView({
|
|
19
|
+
block: place,
|
|
20
|
+
inline: place,
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
this.disconnect();
|
|
24
|
+
}).observe(this);
|
|
25
|
+
};
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -5,19 +5,21 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "
|
|
8
|
+
"version": "6.0.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"main": "index.js",
|
|
12
12
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@playwright/test": "^1.
|
|
15
|
-
"@sveltejs/adapter-
|
|
16
|
-
"@sveltejs/
|
|
17
|
-
"@sveltejs/
|
|
18
|
-
"@
|
|
19
|
-
"@
|
|
20
|
-
"eslint": "^
|
|
14
|
+
"@playwright/test": "^1.25.1",
|
|
15
|
+
"@sveltejs/adapter-netlify": "^1.0.0-next.76",
|
|
16
|
+
"@sveltejs/adapter-static": "^1.0.0-next.41",
|
|
17
|
+
"@sveltejs/kit": "^1.0.0-next.465",
|
|
18
|
+
"@sveltejs/package": "^1.0.0-next.3",
|
|
19
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.4",
|
|
20
|
+
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
|
21
|
+
"@typescript-eslint/parser": "^5.36.1",
|
|
22
|
+
"eslint": "^8.23.0",
|
|
21
23
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
22
24
|
"hastscript": "^7.0.2",
|
|
23
25
|
"jsdom": "^20.0.0",
|
|
@@ -26,16 +28,16 @@
|
|
|
26
28
|
"prettier-plugin-svelte": "^2.7.0",
|
|
27
29
|
"rehype-autolink-headings": "^6.1.1",
|
|
28
30
|
"rehype-slug": "^5.0.1",
|
|
29
|
-
"svelte": "^3.
|
|
30
|
-
"svelte-check": "^2.
|
|
31
|
+
"svelte": "^3.50.0",
|
|
32
|
+
"svelte-check": "^2.9.0",
|
|
31
33
|
"svelte-github-corner": "^0.1.0",
|
|
32
34
|
"svelte-preprocess": "^4.10.6",
|
|
33
|
-
"svelte-toc": "^0.2.
|
|
34
|
-
"svelte2tsx": "^0.5.
|
|
35
|
+
"svelte-toc": "^0.2.10",
|
|
36
|
+
"svelte2tsx": "^0.5.16",
|
|
35
37
|
"tslib": "^2.4.0",
|
|
36
|
-
"typescript": "^4.
|
|
37
|
-
"vite": "^
|
|
38
|
-
"vitest": "^0.
|
|
38
|
+
"typescript": "^4.8.2",
|
|
39
|
+
"vite": "^3.1.0-beta.2",
|
|
40
|
+
"vitest": "^0.22.1"
|
|
39
41
|
},
|
|
40
42
|
"keywords": [
|
|
41
43
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -5,18 +5,19 @@
|
|
|
5
5
|
|
|
6
6
|
<h4 align="center">
|
|
7
7
|
|
|
8
|
-
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
9
8
|
[](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
|
|
10
9
|
[](https://app.netlify.com/sites/svelte-multiselect/deploys)
|
|
11
10
|
[](https://npmjs.com/package/svelte-multiselect)
|
|
12
11
|
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
12
|
+
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
13
|
+
[](https://stackblitz.com/github/janosh/svelte-multiselect)
|
|
13
14
|
|
|
14
15
|
</h4>
|
|
15
16
|
|
|
16
17
|
**Keyboard-friendly, accessible and highly customizable multi-select component.**
|
|
17
|
-
<
|
|
18
|
-
<a href="https://svelte-multiselect.netlify.app">
|
|
19
|
-
</
|
|
18
|
+
<span class="hide-in-docs">
|
|
19
|
+
<a href="https://svelte-multiselect.netlify.app">View the docs</a>
|
|
20
|
+
</span>
|
|
20
21
|
|
|
21
22
|
<slot name="examples" />
|
|
22
23
|
|
|
@@ -35,15 +36,14 @@
|
|
|
35
36
|
|
|
36
37
|
## Recent breaking changes
|
|
37
38
|
|
|
38
|
-
-
|
|
39
|
-
|
|
40
|
-
- v4.0.3 CSS variables starting with `--sms-input-<attr>` were renamed to just `--sms-<attr>`. E.g. `--sms-input-min-height` is now `--sms-min-height`.
|
|
41
|
-
|
|
42
|
-
- v5.0.0 Support both simple and object options. Previously string or number options were converted to objects internally and returned by `bind:selected`. Now, if you pass in `string[]`, that's what you'll get from `bind:selected`.
|
|
39
|
+
- v5.0.0 Supports both simple and object options. Previously strings and numbers were converted to `{ value, label }` objects internally and returned by `bind:selected`. Now, if you pass in `string[]`, that's exactly what you'll get from `bind:selected`.
|
|
40
|
+
- v6.0.0 The prop `showOptions` which controls whether the list of dropdown options is currently open or closed was renamed to just `open`.
|
|
43
41
|
|
|
44
42
|
## Installation
|
|
45
43
|
|
|
46
44
|
```sh
|
|
45
|
+
npm install -D svelte-multiselect
|
|
46
|
+
pnpm install -D svelte-multiselect
|
|
47
47
|
yarn add -D svelte-multiselect
|
|
48
48
|
```
|
|
49
49
|
|
|
@@ -78,6 +78,7 @@ Full list of props/bindable variables for this component:
|
|
|
78
78
|
| `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
|
|
79
79
|
| `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. |
|
|
80
80
|
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
81
|
+
| `activeIndex` | `null` | Zero-based index of currently active option in the array of currently matching options, i.e. if the user typed a search string into the input and only a subset of options match, this index refers to the array position of the matching subset of options |
|
|
81
82
|
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
82
83
|
| `selected` | `[]` | Array of currently selected options. Can be bound to `bind:selected={[1, 2, 3]}` to control component state externally or passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction. |
|
|
83
84
|
| `selectedLabels` | `[]` | Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings, `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`. |
|
|
@@ -103,14 +104,18 @@ Full list of props/bindable variables for this component:
|
|
|
103
104
|
| `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. |
|
|
104
105
|
| `autocomplete` | `'off'` | Applied to the `<input>`. Specifies if browser is permitted to auto-fill this form field. See [MDN docs](https://developer.mozilla.org/docs/Web/HTML/Attributes/autocomplete) for other admissible values. |
|
|
105
106
|
| `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. |
|
|
107
|
+
| `focusInputOnSelect` | `'desktop'` | One of `true`, `false` or `'desktop'`. Whether to set the cursor back to the input element after selecting an element. 'desktop' means only do so if current window width is larger than the current value of `breakpoint` prop (default 800). |
|
|
108
|
+
| `breakpoint` | `800` | Screens wider than `breakpoint` in pixels will be considered `'desktop'`, everything narrower as `'mobile'`. |
|
|
106
109
|
|
|
107
110
|
</div>
|
|
108
111
|
|
|
109
112
|
## Exposed methods
|
|
110
113
|
|
|
111
|
-
1. `filterFunc = (op: Option, searchText: string) => boolean`:
|
|
114
|
+
1. `filterFunc = (op: Option, searchText: string) => boolean`: Customize how dropdown options are filtered when user enters search string into `<MultiSelect />`. Defaults to:
|
|
112
115
|
|
|
113
116
|
```ts
|
|
117
|
+
import type { Option } from 'svelte-multiselect'
|
|
118
|
+
|
|
114
119
|
filterFunc = (op: Option, searchText: string) => {
|
|
115
120
|
if (!searchText) return true
|
|
116
121
|
return `${op.label}`.toLowerCase().includes(searchText.toLowerCase())
|
|
@@ -188,16 +193,14 @@ TypeScript users can import the types used for internal type safety:
|
|
|
188
193
|
|
|
189
194
|
```svelte
|
|
190
195
|
<script lang="ts">
|
|
191
|
-
import MultiSelect, {
|
|
192
|
-
Option,
|
|
193
|
-
Primitive,
|
|
194
|
-
ProtoOption,
|
|
195
|
-
} from 'svelte-multiselect'
|
|
196
|
+
import MultiSelect, { Option, ObjectOption } from 'svelte-multiselect'
|
|
196
197
|
|
|
197
|
-
const myOptions:
|
|
198
|
+
const myOptions: ObjectOption[] = [
|
|
198
199
|
{ label: 'foo', value: 42 },
|
|
199
200
|
{ label: 'bar', value: 69 },
|
|
200
201
|
]
|
|
202
|
+
// an Option can be string | number | ObjectOption
|
|
203
|
+
const myNumbers: Option[] = [42, 69]
|
|
201
204
|
</script>
|
|
202
205
|
```
|
|
203
206
|
|
|
@@ -387,6 +390,6 @@ To submit a PR, clone the repo, install dependencies and start the dev server to
|
|
|
387
390
|
```sh
|
|
388
391
|
git clone https://github.com/janosh/svelte-multiselect
|
|
389
392
|
cd svelte-multiselect
|
|
390
|
-
|
|
391
|
-
|
|
393
|
+
npm install
|
|
394
|
+
npm run dev
|
|
392
395
|
```
|