svelte-multiselect 11.3.0 โ†’ 11.5.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/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.5.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.2",
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",
24
- "eslint": "^9.39.1",
25
- "eslint-plugin-svelte": "^3.13.0",
26
- "happy-dom": "^20.0.10",
29
+ "@types/node": "^25.0.3",
30
+ "@vitest/coverage-v8": "^4.0.16",
31
+ "eslint": "^9.39.2",
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.46.1",
40
+ "svelte-check": "^4.3.5",
34
41
  "svelte-preprocess": "^6.0.3",
35
42
  "svelte-toc": "^0.6.2",
36
- "svelte2tsx": "^0.7.45",
43
+ "svelte2tsx": "^0.7.46",
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.51.0",
46
+ "vite": "^7.3.0",
47
+ "vitest": "^4.0.16"
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>
@@ -34,9 +34,9 @@
34
34
 
35
35
  ## ๐Ÿงช &thinsp; Coverage
36
36
 
37
- | Statements | Branches | Lines |
38
- | ------------------------------------------------------------------------------------------ | --------------------------------------------------------------------------------- | -------------------------------------------------------------------------------- |
39
- | ![Statements](https://img.shields.io/badge/statements-92.81%25-brightgreen.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-81.57%25-yellow.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-92.81%25-brightgreen.svg?style=flat) |
37
+ | Statements | Branches | Lines |
38
+ | ------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------- | --------------------------------------------------------------------------- |
39
+ | ![Statements](https://img.shields.io/badge/statements-85.65%25-yellow.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-82.47%25-yellow.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-85.51%25-yellow.svg?style=flat) |
40
40
 
41
41
  ## ๐Ÿ”จ &thinsp; Installation
42
42
 
@@ -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
@@ -291,6 +334,85 @@ These are the core props you'll use in most cases:
291
334
 
292
335
  Configuration for portal rendering. When `active: true`, the dropdown is rendered at document.body level with fixed positioning. Useful for avoiding z-index and overflow issues.
293
336
 
337
+ ### Grouping Props
338
+
339
+ Group related options together with visual headers. Add a `group` key to your option objects:
340
+
341
+ ```svelte
342
+ <script>
343
+ const options = [
344
+ { label: `JavaScript`, group: `Frontend` },
345
+ { label: `TypeScript`, group: `Frontend` },
346
+ { label: `Python`, group: `Backend` },
347
+ { label: `Go`, group: `Backend` },
348
+ ]
349
+ </script>
350
+
351
+ <MultiSelect {options} collapsibleGroups groupSelectAll />
352
+ ```
353
+
354
+ See the [grouping demo](https://janosh.github.io/svelte-multiselect/grouping) for live examples.
355
+
356
+ 1. ```ts
357
+ collapsibleGroups: boolean = false
358
+ ```
359
+
360
+ Enable click-to-collapse groups. When `true`, users can click group headers to hide/show options in that group.
361
+
362
+ 1. ```ts
363
+ collapsedGroups: Set<string> = new Set()
364
+ ```
365
+
366
+ Bindable set of collapsed group names. Use `bind:collapsedGroups` to control which groups are collapsed externally or to persist collapse state.
367
+
368
+ 1. ```ts
369
+ groupSelectAll: boolean = false
370
+ ```
371
+
372
+ Add a "Select all" button to each group header, allowing users to select all options in a specific group at once.
373
+
374
+ 1. ```ts
375
+ ungroupedPosition: 'first' | 'last' = 'first'
376
+ ```
377
+
378
+ Where to render options that don't have a `group` key. `'first'` places them at the top, `'last'` at the bottom.
379
+
380
+ 1. ```ts
381
+ groupSortOrder: 'none' | 'asc' | 'desc' | ((a: string, b: string) => number) = 'none'
382
+ ```
383
+
384
+ Sort groups alphabetically (`'asc'` or `'desc'`) or with a custom comparator function. Default `'none'` preserves order of first occurrence.
385
+
386
+ 1. ```ts
387
+ searchExpandsCollapsedGroups: boolean = false
388
+ ```
389
+
390
+ When `true`, collapsed groups automatically expand when the search query matches options within them.
391
+
392
+ 1. ```ts
393
+ liGroupHeaderClass: string = ''
394
+ ```
395
+
396
+ CSS class applied to group header `<li>` elements.
397
+
398
+ 1. ```ts
399
+ liGroupHeaderStyle: string | null = null
400
+ ```
401
+
402
+ Inline style for group header elements.
403
+
404
+ 1. ```ts
405
+ groupHeader: Snippet<[{ group: string; options: T[]; collapsed: boolean }]>
406
+ ```
407
+
408
+ Custom snippet for rendering group headers. Receives the group name, array of options in that group, and whether the group is collapsed.
409
+
410
+ 1. ```ts
411
+ ongroupToggle: (data: { group: string; collapsed: boolean }) => void
412
+ ```
413
+
414
+ Callback fired when a group is collapsed or expanded. Receives the group name and its new collapsed state.
415
+
294
416
  ### Form & Accessibility Props
295
417
 
296
418
  1. ```ts
@@ -349,12 +471,36 @@ These are the core props you'll use in most cases:
349
471
 
350
472
  Screen width (px) that separates 'mobile' from 'desktop' behavior.
351
473
 
474
+ 1. ```ts
475
+ fuzzy: boolean = true
476
+ ```
477
+
478
+ 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.
479
+
352
480
  1. ```ts
353
481
  highlightMatches: boolean = true
354
482
  ```
355
483
 
356
484
  Whether to highlight matching text in dropdown options.
357
485
 
486
+ 1. ```ts
487
+ keepSelectedInDropdown: false | 'plain' | 'checkboxes' = false
488
+ ```
489
+
490
+ 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.
491
+
492
+ 1. ```ts
493
+ selectAllOption: boolean | string = false
494
+ ```
495
+
496
+ Adds a "Select All" option at the top of the dropdown. `true` shows default label, or pass a custom string label.
497
+
498
+ 1. ```ts
499
+ liSelectAllClass: string = ''
500
+ ```
501
+
502
+ CSS class applied to the "Select All" `<li>` element.
503
+
358
504
  1. ```ts
359
505
  parseLabelsAsHtml: boolean = false
360
506
  ```
@@ -367,6 +513,12 @@ These are the core props you'll use in most cases:
367
513
 
368
514
  Whether selected options can be reordered by dragging.
369
515
 
516
+ 1. ```ts
517
+ selectedFlipParams: FlipParams = { duration: 100 }
518
+ ```
519
+
520
+ 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.
521
+
370
522
  ### Message Props
371
523
 
372
524
  1. ```ts
@@ -558,15 +710,16 @@ These reflect internal component state:
558
710
 
559
711
  1. `#snippet option({ option, idx })`: Customize rendering of dropdown options. Receives as props an `option` and the zero-indexed position (`idx`) it has in the dropdown.
560
712
  1. `#snippet selectedItem({ option, idx })`: Customize rendering of selected items. Receives as props an `option` and the zero-indexed position (`idx`) it has in the list of selected items.
713
+ 1. `#snippet children({ option, idx })`: Convenience snippet that applies to both dropdown options AND selected items. Use this when you want the same custom rendering for both. Takes precedence if `option` or `selectedItem` are not provided.
561
714
  1. `#snippet spinner()`: Custom spinner component to display when in `loading` state. Receives no props.
562
715
  1. `#snippet disabledIcon()`: Custom icon to display inside the input when in `disabled` state. Receives no props. Use an empty `{#snippet disabledIcon()}{/snippet}` to remove the default disabled icon.
563
- 1. `#snippet expandIcon()`: Allows setting a custom icon to indicate to users that the Multiselect text input field is expandable into a dropdown list. Receives prop `open: boolean` which is true if the Multiselect dropdown is visible and false if it's hidden.
716
+ 1. `#snippet expandIcon({ open })`: Allows setting a custom icon to indicate to users that the Multiselect text input field is expandable into a dropdown list. `open` is `true` if the dropdown is visible and `false` if hidden.
564
717
  1. `#snippet removeIcon()`: Custom icon to display as remove button. Will be used both by buttons to remove individual selected options and the 'remove all' button that clears all options at once. Receives no props.
565
718
  1. `#snippet userMsg({ searchText, msgType, msg })`: Displayed like a dropdown item when the list is empty and user is allowed to create custom options based on text input (or if the user's text input clashes with an existing option). Receives props:
566
719
  - `searchText`: The text user typed into search input.
567
720
  - `msgType: false | 'create' | 'dupe' | 'no-match'`: `'dupe'` means user input is a duplicate of an existing option. `'create'` means user is allowed to convert their input into a new option not previously in the dropdown. `'no-match'` means user input doesn't match any dropdown items and users are not allowed to create new options. `false` means none of the above.
568
721
  - `msg`: Will be `duplicateOptionMsg` or `createOptionMsg` (see [props](#๐Ÿ”ฃ-props)) based on whether user input is a duplicate or can be created as new option. Note this snippet replaces the default UI for displaying these messages so the snippet needs to render them instead (unless purposely not showing a message).
569
- 1. `snippet='after-input'`: Placed after the search input. For arbitrary content like icons or temporary messages. Receives props `selected: Option[]`, `disabled: boolean`, `invalid: boolean`, `id: string | null`, `placeholder: string`, `open: boolean`, `required: boolean`. Can serve as a more dynamic, more customizable alternative to the `placeholder` prop.
722
+ 1. `#snippet afterInput({ selected, disabled, invalid, id, placeholder, open, required })`: Placed after the search input. For arbitrary content like icons or temporary messages. Can serve as a more dynamic, more customizable alternative to the `placeholder` prop.
570
723
 
571
724
  Example using several snippets:
572
725
 
@@ -593,65 +746,79 @@ Example using several snippets:
593
746
 
594
747
  ## ๐ŸŽฌ &thinsp; Events
595
748
 
596
- `MultiSelect.svelte` dispatches the following events:
749
+ `MultiSelect.svelte` provides the following event callback props:
750
+
751
+ 1. ```ts
752
+ onadd={({ option }) => console.log(option)}
753
+ ```
754
+
755
+ Triggers when a new option is selected. The newly selected option is provided as `option`.
597
756
 
598
757
  1. ```ts
599
- onadd={(event) => console.log(event.detail.option)}
758
+ oncreate={({ option }) => console.log(option)}
600
759
  ```
601
760
 
602
- Triggers when a new option is selected. The newly selected option is provided as `event.detail.option`.
761
+ Triggers when a user creates a new option (when `allowUserOptions` is enabled). The created option is provided as `option`.
603
762
 
604
763
  1. ```ts
605
- oncreate={(event) => console.log(event.detail.option)}
764
+ onremove={({ option }) => console.log(option)}
606
765
  ```
607
766
 
608
- Triggers when a user creates a new option (when `allowUserOptions` is enabled). The created option is provided as `event.detail.option`.
767
+ Triggers when a single selected option is removed. The removed option is provided as `option`.
609
768
 
610
769
  1. ```ts
611
- onremove={(event) => console.log(event.detail.option)}`
770
+ onremoveAll={({ options }) => console.log(options)}
612
771
  ```
613
772
 
614
- Triggers when a single selected option is removed. The removed option is provided as `event.detail.option`.
773
+ Triggers when all selected options are removed. The `options` payload gives the options that were removed (might not be all if `minSelect` is set).
615
774
 
616
775
  1. ```ts
617
- onremoveAll={(event) => console.log(event.detail.options)}`
776
+ onselectAll={({ options }) => console.log(options)}
618
777
  ```
619
778
 
620
- Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were removed (might not be all if `minSelect` is set).
779
+ Triggers when the "Select All" option is clicked (requires `selectAllOption` to be enabled). The `options` payload contains the options that were added.
621
780
 
622
781
  1. ```ts
623
- onchange={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
782
+ onreorder={({ options }) => console.log(options)}
624
783
  ```
625
784
 
626
- Triggers when an option is either added (selected) or removed from selected, or all selected options are removed at once. `type` is one of `'add' | 'remove' | 'removeAll'` and payload will be `option: Option` or `options: Option[]`, respectively.
785
+ Triggers when selected options are reordered via drag-and-drop (enabled by default when `sortSelected` is false). The `options` payload is the newly ordered array of selected options.
627
786
 
628
787
  1. ```ts
629
- onopen={(event) => console.log(`Multiselect dropdown was opened by ${event}`)}
788
+ onchange={({ type, option, options }) => console.log(type, option ?? options)}
630
789
  ```
631
790
 
632
- Triggers when the dropdown list of options appears. Event is the DOM's `FocusEvent`,`KeyboardEvent` or `ClickEvent` that initiated this Svelte `dispatch` event.
791
+ Triggers when an option is either added (selected) or removed from selected, all selected options are removed at once, or selected options are reordered via drag-and-drop. `type` is one of `'add' | 'remove' | 'removeAll' | 'selectAll' | 'reorder'` and payload will be `option: Option` or `options: Option[]`, respectively.
633
792
 
634
793
  1. ```ts
635
- onclose={(event) => console.log(`Multiselect dropdown was closed by ${event}`)}
794
+ onopen={({ event }) => console.log(`Dropdown opened by`, event)}
636
795
  ```
637
796
 
638
- Triggers when the dropdown list of options disappears. Event is the DOM's `FocusEvent`, `KeyboardEvent` or `ClickEvent` that initiated this Svelte `dispatch` event.
797
+ Triggers when the dropdown list of options appears. `event` is the DOM's `FocusEvent`, `KeyboardEvent` or `ClickEvent` that triggered the open.
798
+
799
+ 1. ```ts
800
+ onclose={({ event }) => console.log(`Dropdown closed by`, event)}
801
+ ```
802
+
803
+ Triggers when the dropdown list of options disappears. `event` is the DOM's `FocusEvent`, `KeyboardEvent` or `ClickEvent` that triggered the close.
639
804
 
640
805
  For example, here's how you might annoy your users with an alert every time one or more options are added or removed:
641
806
 
642
807
  ```svelte
643
808
  <MultiSelect
644
- onchange={(event) => {
645
- if (event.detail.type === 'add') alert(`You added ${event.detail.option}`)
646
- if (event.detail.type === 'remove') alert(`You removed ${event.detail.option}`)
647
- if (event.detail.type === 'removeAll') alert(`You removed ${event.detail.options}`)
809
+ onchange={({ type, option, options }) => {
810
+ if (type === 'add') alert(`You added ${option}`)
811
+ if (type === 'remove') alert(`You removed ${option}`)
812
+ if (type === 'removeAll') alert(`You removed ${options}`)
813
+ if (type === 'selectAll') alert(`You selected all: ${options}`)
814
+ if (type === 'reorder') alert(`New order: ${options}`)
648
815
  }}
649
816
  />
650
817
  ```
651
818
 
652
- > Note: Depending on the data passed to the component the `options(s)` payload will either be objects or simple strings/numbers.
819
+ > Note: Depending on the data passed to the component the `option(s)` payload will either be objects or simple strings/numbers.
653
820
 
654
- The above list of events are [Svelte `dispatch` events](https://svelte.dev/tutorial/svelte/component-events). This component also forwards many DOM events from the `<input>` node: `blur`, `change`, `click`, `keydown`, `keyup`, `mousedown`, `mouseenter`, `mouseleave`, `touchcancel`, `touchend`, `touchmove`, `touchstart`. Registering listeners for these events works the same:
821
+ This component also forwards many DOM events from the `<input>` node: `blur`, `change`, `click`, `keydown`, `keyup`, `mousedown`, `mouseenter`, `mouseleave`, `touchcancel`, `touchend`, `touchmove`, `touchstart`. Registering listeners for these events works the same:
655
822
 
656
823
  ```svelte
657
824
  <MultiSelect
@@ -682,7 +849,12 @@ You can also import [the types this component uses](https://github.com/janosh/sv
682
849
 
683
850
  ```ts
684
851
  import {
685
- DispatchEvents,
852
+ LoadOptions, // Dynamic option loading callback
853
+ LoadOptionsConfig,
854
+ LoadOptionsFn,
855
+ LoadOptionsParams,
856
+ LoadOptionsResult,
857
+ MultiSelectEvents,
686
858
  MultiSelectEvents,
687
859
  ObjectOption,
688
860
  Option,
@@ -717,10 +889,10 @@ Minimal example that changes the background color of the options dropdown:
717
889
  ```
718
890
 
719
891
  - `div.multiselect`
720
- - `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.
892
+ - `border: var(--sms-border, 1pt solid light-dark(lightgray, #555))`: Change this to e.g. to `1px solid red` to indicate this form field is in an invalid state.
721
893
  - `border-radius: var(--sms-border-radius, 3pt)`
722
894
  - `padding: var(--sms-padding, 0 3pt)`
723
- - `background: var(--sms-bg)`
895
+ - `background: var(--sms-bg, light-dark(white, #1a1a1a))`
724
896
  - `color: var(--sms-text-color)`
725
897
  - `min-height: var(--sms-min-height, 22pt)`
726
898
  - `width: var(--sms-width)`
@@ -732,23 +904,23 @@ Minimal example that changes the background color of the options dropdown:
732
904
  - `div.multiselect:focus-within`
733
905
  - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` which in turn defaults to `cornflowerblue`.
734
906
  - `div.multiselect.disabled`
735
- - `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
907
+ - `background: var(--sms-disabled-bg, light-dark(lightgray, #444))`: Background when in disabled state.
736
908
  - `div.multiselect input::placeholder`
737
909
  - `color: var(--sms-placeholder-color)`
738
910
  - `opacity: var(--sms-placeholder-opacity)`
739
911
  - `div.multiselect > ul.selected > li`
740
- - `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
912
+ - `background: var(--sms-selected-bg, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15)))`: Background of selected options.
741
913
  - `padding: var(--sms-selected-li-padding, 1pt 5pt)`: Height of selected options.
742
914
  - `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
743
915
  - `ul.selected > li button:hover, button.remove-all:hover, button:focus`
744
- - `color: var(--sms-remove-btn-hover-color, lightskyblue)`: Color of the remove-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
745
- - `background: var(--sms-remove-btn-hover-bg, rgba(0, 0, 0, 0.2))`: Background for hovered remove buttons.
916
+ - `color: var(--sms-remove-btn-hover-color, light-dark(#0088cc, lightskyblue))`: Color of the remove-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
917
+ - `background: var(--sms-remove-btn-hover-bg, light-dark(rgba(0, 0, 0, 0.2), rgba(255, 255, 255, 0.2)))`: Background for hovered remove buttons.
746
918
  - `div.multiselect > ul.options`
747
- - `background: var(--sms-options-bg, white)`: Background of dropdown list.
919
+ - `background: var(--sms-options-bg, light-dark(#fafafa, #1a1a1a))`: Background of dropdown list.
748
920
  - `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
749
921
  - `overscroll-behavior: 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/docs/Web/CSS/overscroll-behavior).
750
922
  - `z-index: var(--sms-options-z-index, 3)`: Z-index for the dropdown options list.
751
- - `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black)`: Box shadow of dropdown list.
923
+ - `box-shadow: var(--sms-options-shadow, light-dark(0 0 14pt -8pt black, 0 0 14pt -4pt rgba(0, 0, 0, 0.8)))`: Box shadow of dropdown list.
752
924
  - `border: var(--sms-options-border)`
753
925
  - `border-width: var(--sms-options-border-width)`
754
926
  - `border-radius: var(--sms-options-border-radius, 1ex)`
@@ -757,13 +929,13 @@ Minimal example that changes the background color of the options dropdown:
757
929
  - `div.multiselect > ul.options > li`
758
930
  - `scroll-margin: var(--sms-options-scroll-margin, 100px)`: Top/bottom margin to keep between dropdown list items and top/bottom screen edge when auto-scrolling list to keep items in view.
759
931
  - `div.multiselect > ul.options > li.selected`
760
- - `background: var(--sms-li-selected-bg)`: Background of selected list items in options pane.
761
- - `color: var(--sms-li-selected-color)`: Text color of selected list items in options pane.
932
+ - `background: var(--sms-li-selected-plain-bg, light-dark(rgba(0, 123, 255, 0.1), rgba(100, 180, 255, 0.2)))`: Background of selected list items in options pane.
933
+ - `border-left: var(--sms-li-selected-plain-border, 3px solid var(--sms-active-color, cornflowerblue))`: Left border of selected list items in options pane.
762
934
  - `div.multiselect > ul.options > li.active`
763
- - `background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)))`: Background of active options. Options in the dropdown list become active either by mouseover or by navigating to them with arrow keys. Selected options become active when `selectedOptionsDraggable=true` and an option is being dragged to a new position. Note the active option in that case is not the dragged option but the option under it whose place it will take on drag end.
935
+ - `background: var(--sms-li-active-bg, var(--sms-active-color, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15))))`: Background of active options. Options in the dropdown list become active either by mouseover or by navigating to them with arrow keys. Selected options become active when `selectedOptionsDraggable=true` and an option is being dragged to a new position. Note the active option in that case is not the dragged option but the option under it whose place it will take on drag end.
764
936
  - `div.multiselect > ul.options > li.disabled`
765
- - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
766
- - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
937
+ - `background: var(--sms-li-disabled-bg, light-dark(#f5f5f6, #2a2a2a))`: Background of disabled options in the dropdown list.
938
+ - `color: var(--sms-li-disabled-text, light-dark(#b8b8b8, #666))`: Text color of disabled option in the dropdown list.
767
939
  - `::highlight(sms-search-matches)`: applies to search results in dropdown list that match the current search query if `highlightMatches=true`. These styles [cannot be set via CSS variables](https://stackoverflow.com/a/56799215). Instead, use a new rule set. For example:
768
940
 
769
941
  ```css
@@ -784,6 +956,7 @@ The second method allows you to pass in custom classes to the important DOM elem
784
956
  - `ulOptionsClass`: available options listed in the dropdown when component is in `open` state
785
957
  - `liOptionClass`: list items selectable from dropdown list
786
958
  - `liActiveOptionClass`: the currently active dropdown list item (i.e. hovered or navigated to with arrow keys)
959
+ - `liSelectAllClass`: the "Select All" option at the top of the dropdown (when `selectAllOption` is enabled)
787
960
  - `liUserMsgClass`: user message (last child of dropdown list when no options match user input)
788
961
  - `liActiveUserMsgClass`: user message when active (i.e. hovered or navigated to with arrow keys)
789
962
  - `maxSelectMsgClass`: small span towards the right end of the input field displaying to the user how many of the allowed number of options they've already selected
@@ -799,6 +972,7 @@ This simplified version of the DOM structure of the component shows where these
799
972
  </ul>
800
973
  <span class="maxSelectMsgClass">2/5 selected</span>
801
974
  <ul class="options {ulOptionsClass}">
975
+ <li class="select-all {liSelectAllClass}">Select all</li>
802
976
  <li class={liOptionClass}>Option 1</li>
803
977
  <li class="{liOptionClass} {liActiveOptionClass}">
804
978
  Option 2 (currently active)
@@ -859,6 +1033,9 @@ Odd as it may seem, you get the most fine-grained control over the styling of ev
859
1033
  :global(div.multiselect > ul.options > li.disabled) {
860
1034
  /* options with disabled key set to true (see props above) */
861
1035
  }
1036
+ :global(div.multiselect > ul.options > li.select-all) {
1037
+ /* the "Select All" option at the top of the dropdown */
1038
+ }
862
1039
  ```
863
1040
 
864
1041
  ## ๐Ÿ†• &thinsp; Changelog
@@ -1,29 +0,0 @@
1
- <script lang="ts">"use strict";
2
- let { color = `cornflowerblue`, duration = `1.5s`, size = `1em` } = $props();
3
- </script>
4
-
5
- <div
6
- style="--duration: {duration}"
7
- style:border-color="{color} transparent {color}
8
- {color}"
9
- style:width={size}
10
- style:height={size}
11
- >
12
- </div>
13
-
14
- <style>
15
- div {
16
- display: inline-block;
17
- vertical-align: middle;
18
- margin: 0 3pt;
19
- border-width: calc(1em / 5);
20
- border-style: solid;
21
- border-radius: 50%;
22
- animation: var(--duration) infinite rotate;
23
- }
24
- @keyframes rotate {
25
- 100% {
26
- transform: rotate(360deg);
27
- }
28
- }
29
- </style>
@@ -1,8 +0,0 @@
1
- type $$ComponentProps = {
2
- color?: string;
3
- duration?: string;
4
- size?: string;
5
- };
6
- declare const CircleSpinner: import("svelte").Component<$$ComponentProps, {}, "">;
7
- type CircleSpinner = ReturnType<typeof CircleSpinner>;
8
- export default CircleSpinner;
@@ -1,74 +0,0 @@
1
- <script
2
- lang="ts"
3
- generics="Action extends { label: string; action: (label: string) => void } & Record<string, unknown> = { label: string; action: (label: string) => void }"
4
- >import { fade } from 'svelte/transition';
5
- import MultiSelect from './MultiSelect.svelte';
6
- let { actions, triggers = [`k`], close_keys = [`Escape`], fade_duration = 200, dialog_style = ``, open = $bindable(false), dialog = $bindable(null), input = $bindable(null), placeholder = `Filter actions...`, dialog_props, ...rest } = $props();
7
- $effect(() => {
8
- if (open && input && document.activeElement !== input)
9
- input.focus();
10
- });
11
- async function toggle(event) {
12
- const is_trigger = triggers.includes(event.key) &&
13
- (event.metaKey || event.ctrlKey);
14
- if (is_trigger && !open)
15
- open = true;
16
- else if (close_keys.includes(event.key) && open)
17
- open = false;
18
- }
19
- function close_if_outside(event) {
20
- const target = event.target;
21
- if (!target || !(target instanceof HTMLElement))
22
- return;
23
- if (open && !dialog?.contains(target) && !target.closest(`ul.options`)) {
24
- open = false;
25
- }
26
- }
27
- function trigger_action_and_close({ option }) {
28
- if (!option?.action)
29
- return;
30
- option.action(option.label);
31
- open = false;
32
- }
33
- </script>
34
-
35
- <svelte:window onkeydown={toggle} onclick={close_if_outside} />
36
-
37
- {#if open}
38
- <dialog
39
- open
40
- bind:this={dialog}
41
- transition:fade={{ duration: fade_duration }}
42
- style={dialog_style}
43
- {...dialog_props}
44
- >
45
- <MultiSelect
46
- options={actions}
47
- bind:input
48
- {placeholder}
49
- onadd={trigger_action_and_close}
50
- onkeydown={toggle}
51
- {...rest}
52
- --sms-bg="var(--sms-options-bg)"
53
- --sms-width="min(20em, 90vw)"
54
- --sms-max-width="none"
55
- --sms-placeholder-color="lightgray"
56
- --sms-options-margin="1px 0"
57
- --sms-options-border-radius="0 0 1ex 1ex"
58
- />
59
- </dialog>
60
- {/if}
61
-
62
- <style>
63
- :where(dialog) {
64
- position: fixed;
65
- top: 30%;
66
- border: none;
67
- padding: 0;
68
- background-color: transparent;
69
- display: flex;
70
- color: white;
71
- z-index: 10;
72
- font-size: 2.4ex;
73
- }
74
- </style>