svelte-multiselect 6.0.0 → 6.0.2

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.
@@ -3,51 +3,51 @@ import { get_label, get_value } from './';
3
3
  import CircleSpinner from './CircleSpinner.svelte';
4
4
  import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
5
5
  import Wiggle from './Wiggle.svelte';
6
- export let searchText = ``;
7
- export let open = false;
8
- export let maxSelect = null; // null means any number of options are selectable
9
- export let maxSelectMsg = null;
10
- export let disabled = false;
11
- export let disabledTitle = `This field is disabled`;
12
- export let options;
13
- export let matchingOptions = [];
14
- export let selected = [];
15
- export let selectedLabels = [];
16
- export let selectedValues = [];
17
- export let input = null;
18
- export let outerDiv = null;
19
- export let placeholder = undefined;
20
- export let id = undefined;
21
- export let name = id;
22
- export let noOptionsMsg = `No matching options`;
23
- export let activeOption = null;
24
6
  export let activeIndex = null;
7
+ export let activeOption = null;
8
+ export let addOptionMsg = `Create this option...`;
9
+ export let allowUserOptions = false;
10
+ export let autocomplete = `off`;
11
+ export let autoScroll = true;
12
+ export let breakpoint = 800; // any screen with more horizontal pixels is considered desktop, below is mobile
13
+ export let defaultDisabledTitle = `This option is disabled`;
14
+ export let disabled = false;
15
+ export let disabledInputTitle = `This input is disabled`;
25
16
  export let filterFunc = (op, searchText) => {
26
17
  if (!searchText)
27
18
  return true;
28
19
  return `${get_label(op)}`.toLowerCase().includes(searchText.toLowerCase());
29
20
  };
30
21
  export let focusInputOnSelect = `desktop`;
31
- export let breakpoint = 800; // any screen with more horizontal pixels is considered desktop, below is mobile
32
- export let outerDivClass = ``;
33
- export let ulSelectedClass = ``;
34
- export let liSelectedClass = ``;
35
- export let ulOptionsClass = ``;
36
- export let liOptionClass = ``;
37
- export let liActiveOptionClass = ``;
22
+ export let id = null;
23
+ export let input = null;
38
24
  export let inputClass = ``;
39
- export let removeBtnTitle = `Remove`;
40
- export let removeAllTitle = `Remove all`;
41
- export let defaultDisabledTitle = `This option is disabled`;
42
- export let allowUserOptions = false;
43
- export let parseLabelsAsHtml = false; // should not be combined with allowUserOptions!
44
- export let addOptionMsg = `Create this option...`;
45
- export let autoScroll = true;
25
+ export let invalid = false;
26
+ export let liActiveOptionClass = ``;
27
+ export let liOptionClass = ``;
28
+ export let liSelectedClass = ``;
46
29
  export let loading = false;
30
+ export let matchingOptions = [];
31
+ export let maxSelect = null; // null means any number of options are selectable
32
+ export let maxSelectMsg = null;
33
+ export let name = null;
34
+ export let noOptionsMsg = `No matching options`;
35
+ export let open = false;
36
+ export let options;
37
+ export let outerDiv = null;
38
+ export let outerDivClass = ``;
39
+ export let parseLabelsAsHtml = false; // should not be combined with allowUserOptions!
40
+ export let placeholder = undefined;
41
+ export let removeAllTitle = `Remove all`;
42
+ export let removeBtnTitle = `Remove`;
47
43
  export let required = false;
48
- export let autocomplete = `off`;
49
- export let invalid = false;
44
+ export let searchText = ``;
45
+ export let selected = options?.filter((op) => op?.preselected) ?? [];
46
+ export let selectedLabels = [];
47
+ export let selectedValues = [];
50
48
  export let sortSelected = false;
49
+ export let ulOptionsClass = ``;
50
+ export let ulSelectedClass = ``;
51
51
  if (!(options?.length > 0)) {
52
52
  if (allowUserOptions) {
53
53
  options = []; // initializing as array avoids errors when component mounts
@@ -78,9 +78,7 @@ $: formValue = selectedValues.join(`,`);
78
78
  $: if (formValue)
79
79
  invalid = false; // reset error status whenever component state changes
80
80
  // options matching the current search text
81
- $: matchingOptions = options.filter((op) => filterFunc(op, searchText) &&
82
- !(op instanceof Object && op.disabled) &&
83
- !selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
81
+ $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
84
82
  );
85
83
  // raise if matchingOptions[activeIndex] does not yield a value
86
84
  if (activeIndex !== null && !matchingOptions[activeIndex]) {
@@ -288,7 +286,7 @@ function on_click_outside(event) {
288
286
  class:invalid
289
287
  class="multiselect {outerDivClass}"
290
288
  on:mouseup|stopPropagation={open_dropdown}
291
- title={disabled ? disabledTitle : null}
289
+ title={disabled ? disabledInputTitle : null}
292
290
  aria-disabled={disabled ? `true` : null}
293
291
  >
294
292
  <input
@@ -439,7 +437,6 @@ function on_click_outside(event) {
439
437
  <style>
440
438
  :where(div.multiselect) {
441
439
  position: relative;
442
- margin: 1em 0;
443
440
  align-items: center;
444
441
  display: flex;
445
442
  cursor: text;
@@ -451,6 +448,7 @@ function on_click_outside(event) {
451
448
  color: var(--sms-text-color);
452
449
  font-size: var(--sms-font-size, inherit);
453
450
  min-height: var(--sms-min-height, 19pt);
451
+ margin: var(--sms-margin);
454
452
  }
455
453
  :where(div.multiselect.open) {
456
454
  /* increase z-index when open to ensure the dropdown of one <MultiSelect />
@@ -2,47 +2,47 @@ import { SvelteComponentTyped } from "svelte";
2
2
  import type { MultiSelectEvents, Option } from './';
3
3
  declare const __propDef: {
4
4
  props: {
5
- searchText?: string | undefined;
6
- open?: boolean | undefined;
5
+ activeIndex?: number | null | undefined;
6
+ activeOption?: Option | null | undefined;
7
+ addOptionMsg?: string | undefined;
8
+ allowUserOptions?: boolean | "append" | undefined;
9
+ autocomplete?: string | undefined;
10
+ autoScroll?: boolean | undefined;
11
+ breakpoint?: number | undefined;
12
+ defaultDisabledTitle?: string | undefined;
13
+ disabled?: boolean | undefined;
14
+ disabledInputTitle?: string | undefined;
15
+ filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
16
+ focusInputOnSelect?: boolean | "desktop" | undefined;
17
+ id?: string | null | undefined;
18
+ input?: HTMLInputElement | null | undefined;
19
+ inputClass?: string | undefined;
20
+ invalid?: boolean | undefined;
21
+ liActiveOptionClass?: string | undefined;
22
+ liOptionClass?: string | undefined;
23
+ liSelectedClass?: string | undefined;
24
+ loading?: boolean | undefined;
25
+ matchingOptions?: Option[] | undefined;
7
26
  maxSelect?: number | null | undefined;
8
27
  maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
9
- disabled?: boolean | undefined;
10
- disabledTitle?: string | undefined;
28
+ name?: string | null | undefined;
29
+ noOptionsMsg?: string | undefined;
30
+ open?: boolean | undefined;
11
31
  options: Option[];
12
- matchingOptions?: Option[] | undefined;
13
- selected?: Option[] | undefined;
14
- selectedLabels?: (string | number)[] | undefined;
15
- selectedValues?: unknown[] | undefined;
16
- input?: HTMLInputElement | null | undefined;
17
32
  outerDiv?: HTMLDivElement | null | undefined;
18
- placeholder?: string | undefined;
19
- id?: string | undefined;
20
- name?: string | undefined;
21
- noOptionsMsg?: string | undefined;
22
- activeOption?: Option | null | undefined;
23
- activeIndex?: number | null | undefined;
24
- filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
25
- focusInputOnSelect?: boolean | "desktop" | undefined;
26
- breakpoint?: number | undefined;
27
33
  outerDivClass?: string | undefined;
28
- ulSelectedClass?: string | undefined;
29
- liSelectedClass?: string | undefined;
30
- ulOptionsClass?: string | undefined;
31
- liOptionClass?: string | undefined;
32
- liActiveOptionClass?: string | undefined;
33
- inputClass?: string | undefined;
34
- removeBtnTitle?: string | undefined;
35
- removeAllTitle?: string | undefined;
36
- defaultDisabledTitle?: string | undefined;
37
- allowUserOptions?: boolean | "append" | undefined;
38
34
  parseLabelsAsHtml?: boolean | undefined;
39
- addOptionMsg?: string | undefined;
40
- autoScroll?: boolean | undefined;
41
- loading?: boolean | undefined;
35
+ placeholder?: string | undefined;
36
+ removeAllTitle?: string | undefined;
37
+ removeBtnTitle?: string | undefined;
42
38
  required?: boolean | undefined;
43
- autocomplete?: string | undefined;
44
- invalid?: boolean | undefined;
39
+ searchText?: string | undefined;
40
+ selected?: Option[] | undefined;
41
+ selectedLabels?: (string | number)[] | undefined;
42
+ selectedValues?: unknown[] | undefined;
45
43
  sortSelected?: boolean | ((op1: Option, op2: Option) => number) | undefined;
44
+ ulOptionsClass?: string | undefined;
45
+ ulSelectedClass?: string | undefined;
46
46
  };
47
47
  slots: {
48
48
  selected: {
package/package.json CHANGED
@@ -5,21 +5,20 @@
5
5
  "homepage": "https://svelte-multiselect.netlify.app",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "6.0.0",
8
+ "version": "6.0.2",
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.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",
14
+ "@playwright/test": "^1.25.2",
15
+ "@sveltejs/adapter-static": "^1.0.0-next.43",
16
+ "@sveltejs/kit": "^1.0.0-next.481",
18
17
  "@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",
18
+ "@sveltejs/vite-plugin-svelte": "^1.0.5",
19
+ "@typescript-eslint/eslint-plugin": "^5.37.0",
20
+ "@typescript-eslint/parser": "^5.37.0",
21
+ "eslint": "^8.23.1",
23
22
  "eslint-plugin-svelte3": "^4.0.0",
24
23
  "hastscript": "^7.0.2",
25
24
  "jsdom": "^20.0.0",
@@ -28,16 +27,16 @@
28
27
  "prettier-plugin-svelte": "^2.7.0",
29
28
  "rehype-autolink-headings": "^6.1.1",
30
29
  "rehype-slug": "^5.0.1",
31
- "svelte": "^3.50.0",
30
+ "svelte": "^3.50.1",
32
31
  "svelte-check": "^2.9.0",
33
32
  "svelte-github-corner": "^0.1.0",
34
33
  "svelte-preprocess": "^4.10.6",
35
- "svelte-toc": "^0.2.10",
36
- "svelte2tsx": "^0.5.16",
34
+ "svelte-toc": "^0.4.0",
35
+ "svelte2tsx": "^0.5.17",
37
36
  "tslib": "^2.4.0",
38
- "typescript": "^4.8.2",
39
- "vite": "^3.1.0-beta.2",
40
- "vitest": "^0.22.1"
37
+ "typescript": "^4.8.3",
38
+ "vite": "^3.1.0",
39
+ "vitest": "^0.23.2"
41
40
  },
42
41
  "keywords": [
43
42
  "svelte",
package/readme.md CHANGED
@@ -10,7 +10,7 @@
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/dev/svelte?color=teal)](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
12
12
  [![REPL](https://img.shields.io/badge/Svelte-REPL-blue)](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
13
- [![Open in StackBlitz](https://img.shields.io/badge/Open%20in-StackBlitz-darkblue?logo=pytorchlightning)](https://stackblitz.com/github/janosh/svelte-multiselect)
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>
16
16
 
@@ -36,8 +36,10 @@
36
36
 
37
37
  ## Recent breaking changes
38
38
 
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`.
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`. See [PR 76](https://github.com/janosh/svelte-multiselect/pull/76).
40
+ - **v6.0.0** The prop `showOptions` which controls whether the list of dropdown options is currently open or closed was renamed to `open`. See [PR 103](https://github.com/janosh/svelte-multiselect/pull/103).
41
+ - **v6.0.1** The prop `disabledTitle` which sets the title of the `<MultiSelect>` `<input>` node if in `disabled` mode was renamed to `disabledInputTitle`. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
42
+ - **v6.0.1** The default margin of `1em 0` on the wrapper `div.multiselect` was removed. Instead, there is now a new CSS variable `--sms-margin`. Set it to `--sms-margin: 1em 0;` to restore the old appearance. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
41
43
 
42
44
  ## Installation
43
45
 
@@ -67,67 +69,230 @@ Favorite Frontend Frameworks?
67
69
 
68
70
  ## Props
69
71
 
70
- Full list of props/bindable variables for this component:
71
-
72
- <div class="table">
73
-
74
- <!-- prettier-ignore -->
75
- | name | default | description |
76
- | :--------------------- | :---------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
77
- | `options` | required prop | Array of strings/numbers or `Option` objects to be listed in the dropdown. The only required key on objects is `label` which must also be unique. An object's `value` defaults to `label` if `undefined`. You can add arbitrary additional keys to your option objects. MultiSelect A few keys like `preselected` and `title` have special meaning though. See `src/lib/index.ts` for all special keys and their purpose. |
78
- | `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
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
- | `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 |
82
- | `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
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. |
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`. |
85
- | `selectedValues` | `[]` | Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`. |
86
- | `matchingOptions` | `Option[]` | List of options currently displayed to the user. Same as `options` unless the user entered `searchText` in which case this array contains only those options for which `filterFunc = (op: Option, searchText: string) => boolean` returned `true` (see [exposed methods](#exposed-methods) below for details on `filterFunc`). |
87
- | `sortSelected` | `boolean \| ((op1, op2) => number)` | Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://svelte-multiselect.netlify.app/sort-selected) example. |
88
- | `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
89
- | `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
90
- | `disabledTitle` | `'This field is disabled'` | Tooltip text to display on hover when the component is in `disabled` state. |
91
- | `placeholder` | `undefined` | String shown in the text input when no option is selected. |
92
- | `input` | `null` | Handle to the `<input>` DOM node. Only available after component mounts (`null` before then). |
93
- | `outerDiv` | `null` | Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then). |
94
- | `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. |
95
- | `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>`. |
96
- | `required` | `false` | Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected. |
97
- | `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
98
- | `allowUserOptions` | `false` | Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected. |
99
- | `parseLabelsAsHtml` | `false` | Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting). |
100
- | `addOptionMsg` | `'Create this option...'` | Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy. |
101
- | `loading` | `false` | Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner. |
102
- | `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button to remove selected option (which defaults to a cross icon). |
103
- | `removeAllTitle` | `'Remove all'` | Title text to display when user hovers over remove-all button. |
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. |
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. |
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'`. |
72
+ Full list of props/bindable variables for this component. In the type hints below, `Option` is:
109
73
 
110
- </div>
74
+ ```ts
75
+ import type { Option } from 'svelte-multiselect'
76
+ ```
77
+
78
+ 1. ```ts
79
+ activeIndex: integer | null = null
80
+ ```
81
+
82
+ 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
111
83
 
112
- ## Exposed methods
84
+ 1. ```ts
85
+ activeOption: Option | null = null
86
+ ```
113
87
 
114
- 1. `filterFunc = (op: Option, searchText: string) => boolean`: Customize how dropdown options are filtered when user enters search string into `<MultiSelect />`. Defaults to:
88
+ Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
115
89
 
116
- ```ts
117
- import type { Option } from 'svelte-multiselect'
90
+ 1. ```ts
91
+ addOptionMsg: string = 'Create this option...'
92
+ ```
93
+
94
+ Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy.
95
+
96
+ 1. ```ts
97
+ allowUserOptions: boolean = false
98
+ ```
99
+
100
+ Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected.
101
+
102
+ 1. ```ts
103
+ autocomplete: string = 'off'
104
+ ```
105
+
106
+ 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.
107
+
108
+ 1. ```ts
109
+ autoScroll: boolean = true
110
+ ```
111
+
112
+ `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys.
113
+
114
+ 1. ```ts
115
+ breakpoint: integer = 800
116
+ ```
117
+
118
+ Screens wider than `breakpoint` in pixels will be considered `'desktop'`, everything narrower as `'mobile'`.
119
+
120
+ 1. ```ts
121
+ defaultDisabledTitle: string = 'This option is disabled'
122
+ ```
123
+
124
+ Title text to display when user hovers over a disabled option. Each option can override this through its `disabledTitle` attribute.
125
+
126
+ 1. ```ts
127
+ disabled: boolean = false
128
+ ```
129
+
130
+ Disable the component. It will still be rendered but users won't be able to interact with it.
131
+
132
+ 1. ```ts
133
+ disabledInputTitle: string = 'This input is disabled'
134
+ ```
135
+
136
+ Tooltip text to display on hover when the component is in `disabled` state.
118
137
 
119
- filterFunc = (op: Option, searchText: string) => {
138
+ 1. ```ts
139
+ filterFunc: = (op: Option, searchText: string): boolean => {
120
140
  if (!searchText) return true
121
- return `${op.label}`.toLowerCase().includes(searchText.toLowerCase())
141
+ return `${get_label(op)}`.toLowerCase().includes(searchText.toLowerCase())
122
142
  }
123
143
  ```
124
144
 
125
- 2. `maxSelectMsg = (current: number, max: number) => string`: Inform users how many of the maximum allowed options they have already selected. Set `maxSelectMsg={null}` to not show a message. Defaults to `null` when `maxSelect={1}` or `maxSelect={null}`. Else if `maxSelect > 1`, defaults to:
145
+ Customize how dropdown options are filtered when user enters search string into `<MultiSelect />`. Defaults to:
146
+
147
+ 1. ```ts
148
+ focusInputOnSelect: boolean | 'desktop' = `desktop`
149
+ ```
150
+
151
+ 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).
152
+
153
+ 1. ```ts
154
+ id: string | null = null
155
+ ```
156
+
157
+ 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.
158
+
159
+ 1. ```ts
160
+ input: HTMLInputElement | null = null
161
+ ```
162
+
163
+ Handle to the `<input>` DOM node. Only available after component mounts (`null` before then).
164
+
165
+ 1. ```ts
166
+ invalid: boolean = false
167
+ ```
168
+
169
+ 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.
170
+
171
+ 1. ```ts
172
+ loading: boolean = false
173
+ ```
174
+
175
+ Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner.
176
+
177
+ 1. ```ts
178
+ matchingOptions: Option[] = []
179
+ ```
180
+
181
+ List of options currently displayed to the user. Same as `options` unless the user entered `searchText` in which case this array contains only those options for which `filterFunc = (op: Option, searchText: string) => boolean` returned `true`.
182
+
183
+ 1. ```ts
184
+ maxSelect: number | null = null
185
+ ```
186
+
187
+ Positive integer to limit the number of options users can pick. `null` means no limit.
188
+
189
+ 1. ```ts
190
+ maxSelectMsg: (current: number, max: number): string = null
191
+ ```
192
+
193
+ Inform users how many of the maximum allowed options they have already selected. Set `maxSelectMsg={null}` to not show a message. Defaults to `null` when `maxSelect={1}` or `maxSelect={null}`. Else if `maxSelect > 1`, defaults to:
126
194
 
127
195
  ```ts
128
196
  maxSelectMsg = (current: number, max: number) => `${current}/${max}`
129
197
  ```
130
198
 
199
+ 1. ```ts
200
+ name: string | null = null
201
+ ```
202
+
203
+ Applied to the `<input>` element. 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>` node.
204
+
205
+ 1. ```ts
206
+ noOptionsMsg: string = 'No matching options'
207
+ ```
208
+
209
+ What message to show if no options match the user-entered search string.
210
+
211
+ 1. ```ts
212
+ open: boolean = false
213
+ ```
214
+
215
+ Whether the dropdown list is currently visible. Is two-way bindable, i.e. can be used for external control of when the options are visible.
216
+
217
+ 1. ```ts
218
+ options: Option[]
219
+ ```
220
+
221
+ **The only required prop** (no default). Array of strings/numbers or `Option` objects to be listed in the dropdown. The only required key on objects is `label` which must also be unique. An object's `value` defaults to `label` if `undefined`. You can add arbitrary additional keys to your option objects. A few keys like `preselected` and `title` have special meaning though. See `src/lib/index.ts` for all special keys and their purpose.
222
+
223
+ 1. ```ts
224
+ outerDiv: HTMLDivElement | null = null
225
+ ```
226
+
227
+ Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then).
228
+
229
+ 1. ```ts
230
+ parseLabelsAsHtml: boolean = false
231
+ ```
232
+
233
+ Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting).
234
+
235
+ 1. ```ts
236
+ placeholder: string | null = null
237
+ ```
238
+
239
+ String shown in the text input when no option is selected.
240
+
241
+ 1. ```ts
242
+ removeAllTitle: string = 'Remove all'
243
+ ```
244
+
245
+ Title text to display when user hovers over remove-all button.
246
+
247
+ 1. ```ts
248
+ removeBtnTitle: string = 'Remove'
249
+ ```
250
+
251
+ Title text to display when user hovers over button to remove selected option (which defaults to a cross icon).
252
+
253
+ 1. ```ts
254
+ required: boolean = false
255
+ ```
256
+
257
+ Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected.
258
+
259
+ 1. ```ts
260
+ searchText: string = ''
261
+ ```
262
+
263
+ 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.
264
+
265
+ 1. ```ts
266
+ selected: Option[] = options?.filter((op) => op?.preselected) ?? []
267
+ ```
268
+
269
+ 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.
270
+
271
+ 1. ```ts
272
+ selectedLabels: (string | number)[] = []
273
+ ```
274
+
275
+ 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`.
276
+
277
+ 1. ```ts
278
+ selectedValues: unknown[] = []
279
+ ```
280
+
281
+ Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`.
282
+
283
+ 1. ```ts
284
+ sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
285
+ ```
286
+
287
+ Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://svelte-multiselect.netlify.app/sort-selected) example.
288
+
289
+ 1. ```ts
290
+ userInputAs: 'string' | 'number' | 'object' =
291
+ options.length > 0 ? (typeof options[0] as 'object' | 'string' | 'number') : 'string'
292
+ ```
293
+
294
+ What type new options created from user text input should be. Only relevant if `allowUserOptions=true | 'append'`. If not explicitly set, we default `userInputAs` to the type of the 1st option (if available, else `string`) to keep type homogeneity. E.g. if MultiSelect already contains at least one option and it's an object, new options from user-entered text will take the shape `{label: userText, value: userText}`. Likewise if the 1st existing option is a number of string. If MultiSelect starts out empty but you still want user-created custom options to be objects, pass `userInputAs='object'`.
295
+
131
296
  ## Slots
132
297
 
133
298
  `MultiSelect.svelte` has 3 named slots:
@@ -163,30 +328,50 @@ Example:
163
328
 
164
329
  `MultiSelect.svelte` dispatches the following events:
165
330
 
166
- | name | detail | description |
167
- | ----------- | ---------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
168
- | `add` | `{ option: Option }` | Triggers when a new option is selected. |
169
- | `remove` | `{ option: Option }` | Triggers when one selected option provided as `event.detail.option` is removed. |
170
- | `removeAll` | `options: Option[]` | Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected. |
171
- | `change` | `type: 'add' \| 'remove' \| 'removeAll'` | Triggers when a option is either added or removed, or all options are removed at once. Payload will be a single or an array of `Option`s, respectively. |
172
- | `blur` | none | Triggers when the input field looses focus. |
331
+ 1. ```ts
332
+ on:add={(event) => console.log(event.detail.option)}
333
+ ```
334
+
335
+ Triggers when a new option is selected.
336
+
337
+ 1. ```ts
338
+ on:remove={(event) => console.log(event.detail.option)}`
339
+ ```
340
+
341
+ Triggers when one selected option provided as `event.detail.option` is removed.
342
+
343
+ 1. ```ts
344
+ on:removeAll={(event) => console.log(event.detail.options)}`
345
+ ```
346
+
347
+ Triggers when all selected options are removed. The payload `event.detail.options` gives the options that were previously selected.
173
348
 
174
- Depending on the data passed to the component the `options(s)` payload will either be objects or simple strings/numbers.
349
+ 1. ```ts
350
+ on:change={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
351
+ ```
175
352
 
176
- ### Examples
353
+ Triggers when an option is either added or removed, or all options are removed at once. `type` is one of `'add' | 'remove' | 'removeAll'` and payload will be `option: Option` or `options: Option[]`, respectively.
177
354
 
178
- <!-- prettier-ignore -->
179
- - `on:add={(event) => console.log(event.detail.option)}`
180
- - `on:remove={(event) => console.log(event.detail.option)}`.
181
- - ``on:change={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}``
182
- - `on:blur={myFunction}`
355
+ 1. ```ts
356
+ on:blur={() => console.log('Multiselect input lost focus')}
357
+ ```
358
+
359
+ Triggers when the input field looses focus.
360
+
361
+ For example, here's how you might annoy your users with an alert every time one or more options are added or removed:
183
362
 
184
363
  ```svelte
185
364
  <MultiSelect
186
- on:change={(e) => alert(`You ${e.detail.type}ed '${e.detail.option}'`)}
365
+ on:change={(e) => {
366
+ if (e.detail.type === 'add') alert(`You added ${e.detail.option}`)
367
+ if (e.detail.type === 'remove') alert(`You removed ${e.detail.option}`)
368
+ if (e.detail.type === 'removeAll') alert(`You removed ${e.detail.options}`)
369
+ }}
187
370
  />
188
371
  ```
189
372
 
373
+ > Note: Depending on the data passed to the component the `options(s)` payload will either be objects or simple strings/numbers.
374
+
190
375
  ## TypeScript
191
376
 
192
377
  TypeScript users can import the types used for internal type safety:
@@ -223,7 +408,7 @@ There are 3 ways to style this component. To understand which options do what, i
223
408
 
224
409
  ### With CSS variables
225
410
 
226
- 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.
411
+ 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. See [`app.css`](https://github.com/janosh/svelte-multiselect/blob/main/src/app.css) for how these variables are set on the demo site of this component.
227
412
 
228
413
  - `div.multiselect`
229
414
  - `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.
@@ -231,8 +416,10 @@ If you only want to make small adjustments, you can pass the following CSS varia
231
416
  - `padding: var(--sms-padding, 0 3pt)`
232
417
  - `background: var(--sms-bg)`
233
418
  - `color: var(--sms-text-color)`
234
- - `min-height: var(--sms-min-height)`
419
+ - `min-height: var(--sms-min-height, 19pt)`
235
420
  - `max-width: var(--sms-max-width)`
421
+ - `margin: var(--sms-margin)`
422
+ - `font-size: var(--sms-font-size, inherit)`
236
423
  - `div.multiselect.open`
237
424
  - `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
238
425
  - `div.multiselect:focus-within`
@@ -241,10 +428,10 @@ If you only want to make small adjustments, you can pass the following CSS varia
241
428
  - `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
242
429
  - `div.multiselect input::placeholder`
243
430
  - `color: var(--sms-placeholder-color)`
244
- - `color: var(--sms-placeholder-opacity)`
431
+ - `opacity: var(--sms-placeholder-opacity)`
245
432
  - `div.multiselect > ul.selected > li`
246
433
  - `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
247
- - `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
434
+ - `padding: var(--sms-selected-li-padding, 1pt 5pt)`: Height of selected options.
248
435
  - `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
249
436
  - `ul.selected > li button:hover, button.remove-all:hover, button:focus`
250
437
  - `color: var(--sms-button-hover-color, lightskyblue)`: Color of the remove-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
@@ -252,7 +439,7 @@ If you only want to make small adjustments, you can pass the following CSS varia
252
439
  - `background: var(--sms-options-bg, white)`: Background of dropdown list.
253
440
  - `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
254
441
  - `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).
255
- - `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);`: Box shadow of dropdown list.
442
+ - `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black)`: Box shadow of dropdown list.
256
443
  - `div.multiselect > ul.options > li`
257
444
  - `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.
258
445
  - `div.multiselect > ul.options > li.selected`