svelte-multiselect 11.3.0 → 11.4.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/dist/utils.js CHANGED
@@ -1,10 +1,12 @@
1
+ // Type guard for checking if a value is a non-null object
2
+ export const is_object = (val) => typeof val === `object` && val !== null;
1
3
  // Get the label key from an option object or the option itself
2
4
  // if it's a string or number
3
5
  export const get_label = (opt) => {
4
- if (opt instanceof Object) {
6
+ if (is_object(opt)) {
5
7
  if (opt.label === undefined) {
6
8
  const opt_str = JSON.stringify(opt);
7
- console.error(`MultiSelect option ${opt_str} is an object but has no label key`);
9
+ console.error(`MultiSelect: option is an object but has no label key`, opt_str);
8
10
  }
9
11
  return opt.label;
10
12
  }
@@ -27,7 +29,7 @@ export function get_style(option, key = null) {
27
29
  if (key && key in option.style)
28
30
  return option.style[key] ?? ``;
29
31
  else {
30
- console.error(`Invalid style object for option=${JSON.stringify(option)}`);
32
+ console.error(`MultiSelect: invalid style object for option`, option);
31
33
  }
32
34
  }
33
35
  }
package/package.json CHANGED
@@ -5,39 +5,46 @@
5
5
  "homepage": "https://janosh.github.io/svelte-multiselect",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "11.3.0",
8
+ "version": "11.4.0",
9
9
  "type": "module",
10
+ "scripts": {
11
+ "dev": "vite dev",
12
+ "build": "vite build",
13
+ "preview": "vite preview",
14
+ "test": "vitest --run && playwright test",
15
+ "check": "svelte-check"
16
+ },
10
17
  "svelte": "./dist/index.js",
11
18
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
12
19
  "peerDependencies": {
13
20
  "svelte": "^5.35.6"
14
21
  },
15
22
  "devDependencies": {
16
- "@playwright/test": "^1.56.1",
17
- "@stylistic/eslint-plugin": "^5.5.0",
23
+ "@playwright/test": "^1.57.0",
24
+ "@stylistic/eslint-plugin": "^5.6.1",
18
25
  "@sveltejs/adapter-static": "^3.0.10",
19
- "@sveltejs/kit": "^2.48.4",
20
- "@sveltejs/package": "2.5.4",
26
+ "@sveltejs/kit": "^2.49.1",
27
+ "@sveltejs/package": "2.5.7",
21
28
  "@sveltejs/vite-plugin-svelte": "^6.2.1",
22
- "@types/node": "^24.10.0",
23
- "@vitest/coverage-v8": "^4.0.8",
29
+ "@types/node": "^24.10.1",
30
+ "@vitest/coverage-v8": "^4.0.15",
24
31
  "eslint": "^9.39.1",
25
- "eslint-plugin-svelte": "^3.13.0",
26
- "happy-dom": "^20.0.10",
32
+ "eslint-plugin-svelte": "^3.13.1",
33
+ "happy-dom": "^20.0.11",
27
34
  "hastscript": "^9.0.1",
28
35
  "mdsvex": "^0.12.6",
29
36
  "mdsvexamples": "^0.5.0",
30
37
  "rehype-autolink-headings": "^7.1.0",
31
38
  "rehype-slug": "^6.0.0",
32
- "svelte": "^5.43.5",
33
- "svelte-check": "^4.3.3",
39
+ "svelte": "^5.45.6",
40
+ "svelte-check": "^4.3.4",
34
41
  "svelte-preprocess": "^6.0.3",
35
42
  "svelte-toc": "^0.6.2",
36
43
  "svelte2tsx": "^0.7.45",
37
44
  "typescript": "5.9.3",
38
- "typescript-eslint": "^8.46.3",
39
- "vite": "^7.2.2",
40
- "vitest": "^4.0.8"
45
+ "typescript-eslint": "^8.48.1",
46
+ "vite": "^7.2.7",
47
+ "vitest": "^4.0.15"
41
48
  },
42
49
  "keywords": [
43
50
  "svelte",
package/readme.md CHANGED
@@ -9,7 +9,7 @@
9
9
  [![GitHub Pages](https://github.com/janosh/svelte-multiselect/actions/workflows/gh-pages.yml/badge.svg)](https://github.com/janosh/svelte-multiselect/actions/workflows/gh-pages.yml)
10
10
  [![NPM version](https://img.shields.io/npm/v/svelte-multiselect?logo=NPM&color=purple)](https://npmjs.com/package/svelte-multiselect)
11
11
  [![Needs Svelte version](https://img.shields.io/npm/dependency-version/svelte-multiselect/peer/svelte?color=teal&logo=Svelte&label=Svelte)](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md)
12
- [![REPL](https://img.shields.io/badge/Svelte-REPL-blue?label=Try%20it!)](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
12
+ [![Playground](https://img.shields.io/badge/Svelte-Playground-blue?label=Try%20it!)](https://svelte.dev/playground/a5a14b8f15d64cb083b567292480db05)
13
13
  [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-darkblue?logo=stackblitz)](https://stackblitz.com/github/janosh/svelte-multiselect)
14
14
 
15
15
  </h4>
@@ -172,10 +172,18 @@ These are the core props you'll use in most cases:
172
172
  ```
173
173
 
174
174
  1. ```ts
175
- placeholder: string | null = null
175
+ placeholder: string | { text: string; persistent?: boolean } | null = null
176
176
  ```
177
177
 
178
- Text shown when no options are selected.
178
+ Text shown when no options are selected. Can be a simple string or an object with extended options:
179
+
180
+ ```svelte
181
+ <!-- Simple string -->
182
+ <MultiSelect placeholder="Choose..." />
183
+
184
+ <!-- Object with persistent option (stays visible even when options selected) -->
185
+ <MultiSelect placeholder={{ text: 'Add items...', persistent: true }} />
186
+ ```
179
187
 
180
188
  1. ```ts
181
189
  disabled: boolean = false
@@ -229,6 +237,41 @@ These are the core props you'll use in most cases:
229
237
 
230
238
  ### Advanced Props
231
239
 
240
+ 1. ```ts
241
+ loadOptions: LoadOptionsFn | LoadOptionsConfig = undefined
242
+ ```
243
+
244
+ **Dynamic loading for large datasets.** Enables lazy loading / infinite scroll instead of passing static `options`. Pass either a function or an object with config:
245
+
246
+ ```svelte
247
+ <!-- Simple: just a function -->
248
+ <MultiSelect loadOptions={myFetchFn} />
249
+
250
+ <!-- With config -->
251
+ <MultiSelect loadOptions={{ fetch: myFetchFn, debounceMs: 500, batchSize: 20 }} />
252
+ ```
253
+
254
+ The function receives `{ search, offset, limit }` and must return `{ options, hasMore }`:
255
+
256
+ ```ts
257
+ async function load_options({ search, offset, limit }) {
258
+ const response = await fetch(`/api/items?q=${search}&skip=${offset}&take=${limit}`)
259
+ const { items, total } = await response.json()
260
+ return { options: items, hasMore: offset + limit < total }
261
+ }
262
+ ```
263
+
264
+ Config options (when passing an object):
265
+
266
+ | Key | Type | Default | Description |
267
+ | ------------ | --------- | ------- | ------------------------------------------- |
268
+ | `fetch` | `fn` | — | Async function to load options (required) |
269
+ | `debounceMs` | `number` | `300` | Debounce delay for search queries |
270
+ | `batchSize` | `number` | `50` | Number of options to load per batch |
271
+ | `onOpen` | `boolean` | `true` | Whether to load options when dropdown opens |
272
+
273
+ Features automatic state management, debounced search, infinite scroll pagination, and loading indicators. See the [infinite-scroll demo](https://janosh.github.io/svelte-multiselect/infinite-scroll) for live examples.
274
+
232
275
  1. ```ts
233
276
  activeIndex: number | null = null // bindable
234
277
  ```
@@ -268,10 +311,10 @@ These are the core props you'll use in most cases:
268
311
  Function to determine option equality. Default compares by lowercased label.
269
312
 
270
313
  1. ```ts
271
- closeDropdownOnSelect: boolean | 'if-mobile' | 'retain-focus' = 'if-mobile'
314
+ closeDropdownOnSelect: boolean | 'if-mobile' | 'retain-focus' = false
272
315
  ```
273
316
 
274
- Whether to close dropdown after selection. `'if-mobile'` closes dropdown on mobile devices only (responsive). `'retain-focus'` closes dropdown but keeps input focused for rapid typing to create custom options from text input (see `allowUserOptions`).
317
+ Whether to close dropdown after selection. `false` (default) keeps dropdown open for rapid multi-selection. `true` closes after each selection. `'if-mobile'` closes on mobile devices only (screen width below `breakpoint`). `'retain-focus'` closes dropdown but keeps input focused for rapid typing to create custom options from text input (see `allowUserOptions`).
275
318
 
276
319
  1. ```ts
277
320
  resetFilterOnAdd: boolean = true
@@ -349,12 +392,36 @@ These are the core props you'll use in most cases:
349
392
 
350
393
  Screen width (px) that separates 'mobile' from 'desktop' behavior.
351
394
 
395
+ 1. ```ts
396
+ fuzzy: boolean = true
397
+ ```
398
+
399
+ Whether to use fuzzy matching for filtering options. When `true` (default), matches non-consecutive characters (e.g., "ga" matches "Grapes" and "Green Apple"). When `false`, uses substring matching only.
400
+
352
401
  1. ```ts
353
402
  highlightMatches: boolean = true
354
403
  ```
355
404
 
356
405
  Whether to highlight matching text in dropdown options.
357
406
 
407
+ 1. ```ts
408
+ keepSelectedInDropdown: false | 'plain' | 'checkboxes' = false
409
+ ```
410
+
411
+ Controls whether selected options remain visible in dropdown. `false` (default) hides selected options. `'plain'` shows them with visual distinction. `'checkboxes'` prefixes each option with a checkbox.
412
+
413
+ 1. ```ts
414
+ selectAllOption: boolean | string = false
415
+ ```
416
+
417
+ Adds a "Select All" option at the top of the dropdown. `true` shows default label, or pass a custom string label.
418
+
419
+ 1. ```ts
420
+ liSelectAllClass: string = ''
421
+ ```
422
+
423
+ CSS class applied to the "Select All" `<li>` element.
424
+
358
425
  1. ```ts
359
426
  parseLabelsAsHtml: boolean = false
360
427
  ```
@@ -367,6 +434,12 @@ These are the core props you'll use in most cases:
367
434
 
368
435
  Whether selected options can be reordered by dragging.
369
436
 
437
+ 1. ```ts
438
+ selectedFlipParams: FlipParams = { duration: 100 }
439
+ ```
440
+
441
+ Animation parameters for the [Svelte flip animation](https://svelte.dev/docs/svelte/svelte-animate) when reordering selected options via drag-and-drop. Set `{ duration: 0 }` to disable animation. Accepts `duration`, `delay`, and `easing` properties.
442
+
370
443
  ### Message Props
371
444
 
372
445
  1. ```ts
@@ -682,7 +755,12 @@ You can also import [the types this component uses](https://github.com/janosh/sv
682
755
 
683
756
  ```ts
684
757
  import {
685
- DispatchEvents,
758
+ LoadOptions, // Dynamic option loading callback
759
+ LoadOptionsConfig,
760
+ LoadOptionsFn,
761
+ LoadOptionsParams,
762
+ LoadOptionsResult,
763
+ MultiSelectEvents,
686
764
  MultiSelectEvents,
687
765
  ObjectOption,
688
766
  Option,