svelte-multiselect 7.1.0 → 8.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.
@@ -33,9 +33,9 @@ export let liSelectedClass = ``;
33
33
  export let loading = false;
34
34
  export let matchingOptions = [];
35
35
  export let maxSelect = null; // null means any number of options are selectable
36
- export let maxSelectMsg = null;
36
+ export let maxSelectMsg = (current, max) => (max > 1 ? `${current}/${max}` : ``);
37
37
  export let name = null;
38
- export let noOptionsMsg = `No matching options`;
38
+ export let noMatchingOptionsMsg = `No matching options`;
39
39
  export let open = false;
40
40
  export let options;
41
41
  export let outerDiv = null;
@@ -46,29 +46,22 @@ export let placeholder = null;
46
46
  export let removeAllTitle = `Remove all`;
47
47
  export let removeBtnTitle = `Remove`;
48
48
  export let required = false;
49
+ export let resetFilterOnAdd = true;
49
50
  export let searchText = ``;
50
51
  export let selected = options
51
52
  ?.filter((op) => op?.preselected)
52
53
  .slice(0, maxSelect ?? undefined) ?? [];
53
- export let selectedLabels = [];
54
- export let selectedValues = [];
55
54
  export let sortSelected = false;
56
55
  export let ulOptionsClass = ``;
57
56
  export let ulSelectedClass = ``;
57
+ export let value = null;
58
58
  // get the label key from an option object or the option itself if it's a string or number
59
59
  const get_label = (op) => (op instanceof Object ? op.label : op);
60
- // fallback on label if option is object and value is undefined
61
- const get_value = (op) => (op instanceof Object ? op.value ?? op.label : op);
62
- // selected and _selected are identical except if maxSelect=1, selected will be the single item (or null)
63
- // in _selected which will always be an array for easier component internals. selected then solves
64
- // https://github.com/janosh/svelte-multiselect/issues/86
65
- let _selected = (selected ?? []);
66
- $: selected = maxSelect === 1 ? _selected[0] ?? null : _selected;
60
+ // if maxSelect=1, value is the single item in selected (or null if selected is empty)
61
+ // this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
62
+ // https://github.com/janosh/svelte-multiselect/issues/136
63
+ $: value = maxSelect === 1 ? selected[0] ?? null : selected;
67
64
  let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
68
- $: _selectedLabels = _selected?.map(get_label) ?? [];
69
- $: selectedLabels = maxSelect === 1 ? _selectedLabels[0] ?? null : _selectedLabels;
70
- $: _selectedValues = _selected?.map(get_value) ?? [];
71
- $: selectedValues = maxSelect === 1 ? _selectedValues[0] ?? null : _selectedValues;
72
65
  if (!(options?.length > 0)) {
73
66
  if (allowUserOptions) {
74
67
  options = []; // initializing as array avoids errors when component mounts
@@ -84,19 +77,14 @@ if (parseLabelsAsHtml && allowUserOptions) {
84
77
  if (maxSelect !== null && maxSelect < 1) {
85
78
  console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
86
79
  }
87
- if (!Array.isArray(_selected)) {
88
- console.error(`internal variable _selected prop should always be an array, got ${_selected}`);
80
+ if (!Array.isArray(selected)) {
81
+ console.error(`internal variable selected prop should always be an array, got ${selected}`);
89
82
  }
90
83
  const dispatch = createEventDispatcher();
91
84
  let add_option_msg_is_active = false; // controls active state of <li>{addOptionMsg}</li>
92
85
  let window_width;
93
- // formValue binds to input.form-control to prevent form submission if required
94
- // prop is true and no options are selected
95
- $: form_value = _selectedValues.join(`,`);
96
- $: if (form_value)
97
- invalid = false; // reset error status whenever component state changes
98
86
  // options matching the current search text
99
- $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !_selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
87
+ $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.map(get_label).includes(get_label(op)) // remove already selected options from dropdown list
100
88
  );
101
89
  // raise if matchingOptions[activeIndex] does not yield a value
102
90
  if (activeIndex !== null && !matchingOptions[activeIndex]) {
@@ -106,12 +94,12 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
106
94
  $: activeOption = activeIndex !== null ? matchingOptions[activeIndex] : null;
107
95
  // add an option to selected list
108
96
  function add(label, event) {
109
- if (maxSelect && maxSelect > 1 && _selected.length >= maxSelect)
97
+ if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
110
98
  wiggle = true;
111
- if (!isNaN(Number(label)) && typeof _selectedLabels[0] === `number`)
99
+ if (!isNaN(Number(label)) && typeof selected.map(get_label)[0] === `number`)
112
100
  label = Number(label); // convert to number if possible
113
- const is_duplicate = _selected.some((option) => duplicateFunc(option, label));
114
- if ((maxSelect === null || maxSelect === 1 || _selected.length < maxSelect) &&
101
+ const is_duplicate = selected.some((option) => duplicateFunc(option, label));
102
+ if ((maxSelect === null || maxSelect === 1 || selected.length < maxSelect) &&
115
103
  (duplicates || !is_duplicate)) {
116
104
  // first check if we find option in the options list
117
105
  let option = options.find((op) => get_label(op) === label);
@@ -140,29 +128,30 @@ function add(label, event) {
140
128
  if (option === undefined) {
141
129
  throw `Run time error, option with label ${label} not found in options list`;
142
130
  }
143
- searchText = ``; // reset search string on selection
131
+ if (resetFilterOnAdd)
132
+ searchText = ``; // reset search string on selection
144
133
  if ([``, undefined, null].includes(option)) {
145
134
  console.error(`MultiSelect: encountered missing option with label ${label} (or option is poorly labeled)`);
146
135
  return;
147
136
  }
148
137
  if (maxSelect === 1) {
149
138
  // for maxselect = 1 we always replace current option with new one
150
- _selected = [option];
139
+ selected = [option];
151
140
  }
152
141
  else {
153
- _selected = [..._selected, option];
142
+ selected = [...selected, option];
154
143
  if (sortSelected === true) {
155
- _selected = _selected.sort((op1, op2) => {
144
+ selected = selected.sort((op1, op2) => {
156
145
  const [label1, label2] = [get_label(op1), get_label(op2)];
157
146
  // coerce to string if labels are numbers
158
147
  return `${label1}`.localeCompare(`${label2}`);
159
148
  });
160
149
  }
161
150
  else if (typeof sortSelected === `function`) {
162
- _selected = _selected.sort(sortSelected);
151
+ selected = selected.sort(sortSelected);
163
152
  }
164
153
  }
165
- if (_selected.length === maxSelect)
154
+ if (selected.length === maxSelect)
166
155
  close_dropdown(event);
167
156
  else if (focusInputOnSelect === true ||
168
157
  (focusInputOnSelect === `desktop` && window_width > breakpoint)) {
@@ -170,14 +159,15 @@ function add(label, event) {
170
159
  }
171
160
  dispatch(`add`, { option });
172
161
  dispatch(`change`, { option, type: `add` });
162
+ invalid = false; // reset error status whenever new items are selected
173
163
  }
174
164
  }
175
165
  // remove an option from selected list
176
166
  function remove(label) {
177
- if (_selected.length === 0)
167
+ if (selected.length === 0)
178
168
  return;
179
- _selected.splice(_selectedLabels.lastIndexOf(label), 1);
180
- _selected = _selected; // Svelte rerender after in-place splice
169
+ selected.splice(selected.map(get_label).lastIndexOf(label), 1);
170
+ selected = selected; // Svelte rerender after in-place splice
181
171
  const option = options.find((option) => get_label(option) === label) ??
182
172
  // if option with label could not be found but allowUserOptions is truthy,
183
173
  // assume it was created by user and create correspondidng option object
@@ -188,6 +178,7 @@ function remove(label) {
188
178
  }
189
179
  dispatch(`remove`, { option });
190
180
  dispatch(`change`, { option, type: `remove` });
181
+ invalid = false; // reset error status whenever items are removed
191
182
  }
192
183
  function open_dropdown(event) {
193
184
  if (disabled)
@@ -217,7 +208,7 @@ async function handle_keydown(event) {
217
208
  event.preventDefault(); // prevent enter key from triggering form submission
218
209
  if (activeOption) {
219
210
  const label = get_label(activeOption);
220
- selectedLabels?.includes(label) ? remove(label) : add(label, event);
211
+ selected.map(get_label).includes(label) ? remove(label) : add(label, event);
221
212
  searchText = ``;
222
213
  }
223
214
  else if (allowUserOptions && searchText.length > 0) {
@@ -264,17 +255,17 @@ async function handle_keydown(event) {
264
255
  }
265
256
  }
266
257
  // on backspace key: remove last selected option
267
- else if (event.key === `Backspace` && _selectedLabels.length > 0 && !searchText) {
268
- remove(_selectedLabels.at(-1));
258
+ else if (event.key === `Backspace` && selected.length > 0 && !searchText) {
259
+ remove(selected.map(get_label).at(-1));
269
260
  }
270
261
  }
271
262
  function remove_all() {
272
- dispatch(`removeAll`, { options: _selected });
273
- dispatch(`change`, { options: _selected, type: `removeAll` });
274
- _selected = [];
263
+ dispatch(`removeAll`, { options: selected });
264
+ dispatch(`change`, { options: selected, type: `removeAll` });
265
+ selected = [];
275
266
  searchText = ``;
276
267
  }
277
- $: is_selected = (label) => _selectedLabels.includes(label);
268
+ $: is_selected = (label) => selected.map(get_label).includes(label);
278
269
  const if_enter_or_space = (handler) => (event) => {
279
270
  if ([`Enter`, `Space`].includes(event.code)) {
280
271
  event.preventDefault();
@@ -307,10 +298,10 @@ function on_click_outside(event) {
307
298
  title={disabled ? disabledInputTitle : null}
308
299
  aria-disabled={disabled ? `true` : null}
309
300
  >
310
- <!-- formValue binds to input.form-control to prevent form submission if required prop is true and no options are selected -->
301
+ <!-- bind:value={selected} prevents form submission if required prop is true and no options are selected -->
311
302
  <input
312
303
  {required}
313
- bind:value={form_value}
304
+ bind:value={selected}
314
305
  tabindex="-1"
315
306
  aria-hidden="true"
316
307
  aria-label="ignore this, used only to prevent form submission if select is required but empty"
@@ -319,7 +310,7 @@ function on_click_outside(event) {
319
310
  />
320
311
  <ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt;" />
321
312
  <ul class="selected {ulSelectedClass}">
322
- {#each _selected as option, idx}
313
+ {#each selected as option, idx}
323
314
  <li class={liSelectedClass} aria-selected="true">
324
315
  <slot name="selected" {option} {idx}>
325
316
  {#if parseLabelsAsHtml}
@@ -357,7 +348,7 @@ function on_click_outside(event) {
357
348
  {disabled}
358
349
  {inputmode}
359
350
  {pattern}
360
- placeholder={_selected.length == 0 ? placeholder : null}
351
+ placeholder={selected.length == 0 ? placeholder : null}
361
352
  aria-invalid={invalid ? `true` : null}
362
353
  on:blur
363
354
  on:change
@@ -384,16 +375,15 @@ function on_click_outside(event) {
384
375
  <slot name="disabled-icon">
385
376
  <DisabledIcon width="15px" />
386
377
  </slot>
387
- {:else if _selected.length > 0}
378
+ {:else if selected.length > 0}
388
379
  {#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
389
380
  <Wiggle bind:wiggle angle={20}>
390
381
  <span style="padding: 0 3pt;">
391
- {maxSelectMsg?.(_selected.length, maxSelect) ??
392
- (maxSelect > 1 ? `${_selected.length}/${maxSelect}` : ``)}
382
+ {maxSelectMsg?.(selected.length, maxSelect)}
393
383
  </span>
394
384
  </Wiggle>
395
385
  {/if}
396
- {#if maxSelect !== 1 && _selected.length > 1}
386
+ {#if maxSelect !== 1 && selected.length > 1}
397
387
  <button
398
388
  type="button"
399
389
  class="remove-all"
@@ -423,7 +413,7 @@ function on_click_outside(event) {
423
413
  <li
424
414
  on:mousedown|stopPropagation
425
415
  on:mouseup|stopPropagation={(event) => {
426
- if (!disabled) is_selected(label) ? remove(label) : add(label, event)
416
+ if (!disabled) add(label, event)
427
417
  }}
428
418
  title={disabled
429
419
  ? disabledTitle
@@ -463,12 +453,12 @@ function on_click_outside(event) {
463
453
  on:blur={() => (add_option_msg_is_active = false)}
464
454
  aria-selected="false"
465
455
  >
466
- {!duplicates && _selected.some((option) => duplicateFunc(option, searchText))
456
+ {!duplicates && selected.some((option) => duplicateFunc(option, searchText))
467
457
  ? duplicateOptionMsg
468
458
  : addOptionMsg}
469
459
  </li>
470
460
  {:else}
471
- <span>{noOptionsMsg}</span>
461
+ <span>{noMatchingOptionsMsg}</span>
472
462
  {/if}
473
463
  {/each}
474
464
  </ul>
@@ -30,7 +30,7 @@ declare const __propDef: {
30
30
  maxSelect?: number | null | undefined;
31
31
  maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
32
32
  name?: string | null | undefined;
33
- noOptionsMsg?: string | undefined;
33
+ noMatchingOptionsMsg?: string | undefined;
34
34
  open?: boolean | undefined;
35
35
  options: Option[];
36
36
  outerDiv?: HTMLDivElement | null | undefined;
@@ -41,13 +41,13 @@ declare const __propDef: {
41
41
  removeAllTitle?: string | undefined;
42
42
  removeBtnTitle?: string | undefined;
43
43
  required?: boolean | undefined;
44
+ resetFilterOnAdd?: boolean | undefined;
44
45
  searchText?: string | undefined;
45
- selected?: Option | Option[] | null | undefined;
46
- selectedLabels?: string | number | (string | number)[] | null | undefined;
47
- selectedValues?: unknown[] | unknown | null;
46
+ selected?: Option[] | undefined;
48
47
  sortSelected?: boolean | ((op1: Option, op2: Option) => number) | undefined;
49
48
  ulOptionsClass?: string | undefined;
50
49
  ulSelectedClass?: string | undefined;
50
+ value?: Option | Option[] | null | undefined;
51
51
  };
52
52
  slots: {
53
53
  selected: {
package/package.json CHANGED
@@ -5,39 +5,40 @@
5
5
  "homepage": "https://svelte-multiselect.netlify.app",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "7.1.0",
8
+ "version": "8.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.26.0",
15
- "@sveltejs/adapter-static": "^1.0.0-next.43",
16
- "@sveltejs/kit": "^1.0.0-next.502",
14
+ "@iconify/svelte": "^3.0.0",
15
+ "@playwright/test": "^1.27.1",
16
+ "@sveltejs/adapter-static": "^1.0.0-next.44",
17
+ "@sveltejs/kit": "^1.0.0-next.516",
17
18
  "@sveltejs/package": "^1.0.0-next.5",
18
- "@sveltejs/vite-plugin-svelte": "^1.0.8",
19
- "@typescript-eslint/eslint-plugin": "^5.38.0",
20
- "@typescript-eslint/parser": "^5.38.0",
21
- "eslint": "^8.24.0",
19
+ "@sveltejs/vite-plugin-svelte": "^1.0.9",
20
+ "@typescript-eslint/eslint-plugin": "^5.40.0",
21
+ "@typescript-eslint/parser": "^5.40.0",
22
+ "eslint": "^8.25.0",
22
23
  "eslint-plugin-svelte3": "^4.0.0",
23
- "hastscript": "^7.0.2",
24
- "jsdom": "^20.0.0",
24
+ "hastscript": "^7.1.0",
25
+ "jsdom": "^20.0.1",
25
26
  "mdsvex": "^0.10.6",
26
27
  "prettier": "^2.7.1",
27
- "prettier-plugin-svelte": "^2.7.0",
28
+ "prettier-plugin-svelte": "^2.8.0",
28
29
  "rehype-autolink-headings": "^6.1.1",
29
30
  "rehype-slug": "^5.0.1",
30
31
  "sass": "^1.55.0",
31
- "svelte": "^3.50.1",
32
- "svelte-check": "^2.9.0",
32
+ "svelte": "^3.52.0",
33
+ "svelte-check": "^2.9.2",
33
34
  "svelte-github-corner": "^0.1.0",
34
35
  "svelte-preprocess": "^4.10.6",
35
36
  "svelte-toc": "^0.4.0",
36
- "svelte2tsx": "^0.5.18",
37
+ "svelte2tsx": "^0.5.20",
37
38
  "tslib": "^2.4.0",
38
- "typescript": "^4.8.3",
39
- "vite": "^3.1.3",
40
- "vitest": "^0.23.4"
39
+ "typescript": "^4.8.4",
40
+ "vite": "^3.1.8",
41
+ "vitest": "^0.24.3"
41
42
  },
42
43
  "keywords": [
43
44
  "svelte",
package/readme.md CHANGED
@@ -8,8 +8,8 @@
8
8
  [![Tests](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml/badge.svg)](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
9
9
  [![Netlify Status](https://api.netlify.com/api/v1/badges/a45b62c3-ea45-4cfd-9912-77ec4fc8d7e8/deploy-status)](https://app.netlify.com/sites/svelte-multiselect/deploys)
10
10
  [![NPM version](https://img.shields.io/npm/v/svelte-multiselect?logo=NPM&color=purple)](https://npmjs.com/package/svelte-multiselect)
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
- [![REPL](https://img.shields.io/badge/Svelte-REPL-blue?logo=Svelte)](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
11
+ [![Needs Svelte version](https://img.shields.io/npm/dependency-version/svelte-multiselect/dev/svelte?color=teal&logo=Svelte&label=Svelte)](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
12
+ [![REPL](https://img.shields.io/badge/Svelte-REPL-blue?label=Try%20it!)](https://svelte.dev/repl/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>
@@ -36,12 +36,14 @@
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`. 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).
43
- - **6.1.0** The `dispatch` events `focus` and `blur` were renamed to `open` and `close`, respectively. These actions refer to the dropdown list, i.e. `<MultiSelect on:open={(event) => console.log(event)}>` will trigger when the dropdown list opens. The focus and blur events are now regular DOM (not Svelte `dispatch`) events emitted by the `<input>` node. See [PR 120](https://github.com/janosh/svelte-multiselect/pull/120).
44
- - **v7.0.0** `selected` (as well `selectedLabels` and `selectedValues`) used to be arrays always. Now, if `maxSelect=1`, they will no longer be a length-1 array but simply a single a option (label/value respectively) or `null` if no option is selected. See [PR 123](https://github.com/janosh/svelte-multiselect/pull/123).
39
+ - **v6.0.0**&nbsp; 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).
40
+ - **v6.0.1**&nbsp; 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).
41
+ - **v6.0.1**&nbsp; 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).
42
+ - **6.1.0**&nbsp; The `dispatch` events `focus` and `blur` were renamed to `open` and `close`, respectively. These actions refer to the dropdown list, i.e. `<MultiSelect on:open={(event) => console.log(event)}>` will trigger when the dropdown list opens. The focus and blur events are now regular DOM (not Svelte `dispatch`) events emitted by the `<input>` node. See [PR 120](https://github.com/janosh/svelte-multiselect/pull/120).
43
+ - **v7.0.0**&nbsp; `selected` (as well `selectedLabels` and `selectedValues`) used to be arrays always. Now, if `maxSelect=1`, they will no longer be a length-1 array but simply a single a option (label/value respectively) or `null` if no option is selected. See [PR 123](https://github.com/janosh/svelte-multiselect/pull/123).
44
+ - **8.0.0**&nbsp;
45
+ - Props `selectedLabels` and `selectedValues` were removed. If you were using them, they were equivalent to assigning `bind:selected` to a local variable and then running `selectedLabels = selected.map(option => option.label)` and `selectedValues = selected.map(option => option.value)` if your options were objects with `label` and `value` keys. If they were simple strings/numbers, there was no point in using `selected{Labels,Values}` anyway. See [PR 138](https://github.com/janosh/svelte-multiselect/pull/138)
46
+ - Prop `noOptionsMsg` was renamed to `noMatchingOptionsMsg`. See [PR 133](https://github.com/janosh/svelte-multiselect/pull/133).
45
47
 
46
48
  ## Installation
47
49
 
@@ -216,7 +218,10 @@ import type { Option } from 'svelte-multiselect'
216
218
  Positive integer to limit the number of options users can pick. `null` means no limit. `maxSelect={1}` will change the type of `selected` to be a single `Option` (or `null`) (not a length-1 array). Likewise, the type of `selectedLabels` changes from `(string | number)[]` to `string | number | null` and `selectedValues` from `unknown[]` to `unknown | null`. `maxSelect={1}` will also give `div.multiselect` a class of `single`. I.e. you can target the selector `div.multiselect.single` to give single selects a different appearance from multi selects.
217
219
 
218
220
  1. ```ts
219
- maxSelectMsg: ((current: number, max: number) => string) | null = null
221
+ maxSelectMsg: ((current: number, max: number) => string) | null = (
222
+ current: number,
223
+ max: number
224
+ ) => (max > 1 ? `${current}/${max}` : ``)
220
225
  ```
221
226
 
222
227
  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:
@@ -232,7 +237,7 @@ import type { Option } from 'svelte-multiselect'
232
237
  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.
233
238
 
234
239
  1. ```ts
235
- noOptionsMsg: string = `No matching options`
240
+ noMatchingOptionsMsg: string = `No matching options`
236
241
  ```
237
242
 
238
243
  What message to show if no options match the user-entered search string.
@@ -291,6 +296,12 @@ import type { Option } from 'svelte-multiselect'
291
296
 
292
297
  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.
293
298
 
299
+ 1. ```ts
300
+ resetFilterOnAdd: boolean = true
301
+ ```
302
+
303
+ Whether text entered into the input to filter options in the dropdown list is reset to empty string when user selects an option.
304
+
294
305
  1. ```ts
295
306
  searchText: string = ``
296
307
  ```
@@ -298,31 +309,25 @@ import type { Option } from 'svelte-multiselect'
298
309
  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.
299
310
 
300
311
  1. ```ts
301
- selected: Option[] | Option | null =
312
+ selected: Option[] =
302
313
  options
303
314
  ?.filter((op) => (op as ObjectOption)?.preselected)
304
315
  .slice(0, maxSelect ?? undefined) ?? []
305
316
  ```
306
317
 
307
- Array of currently selected options. Supports 2-way binding `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. If `maxSelect={1}`, selected will not be an array but a single `Option` or `null` if no options are selected.
308
-
309
- 1. ```ts
310
- selectedLabels: (string | number)[] | string | number | null = []
311
- ```
312
-
313
- 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 (or numbers), `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`. If `maxSelect={1}`, selectedLabels will not be an array but a single `string | number` or `null` if no options are selected.
318
+ Array of currently selected options. Supports 2-way binding `bind:selected={[1, 2, 3]}` to control component state externally. Can be passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction.
314
319
 
315
320
  1. ```ts
316
- selectedValues: unknown[] | unknown | null = []
321
+ sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
317
322
  ```
318
323
 
319
- 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 (or numbers), `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`. If `maxSelect={1}`, selectedLabels will not be an array but a single value or `null` if no options are selected.
324
+ 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.
320
325
 
321
326
  1. ```ts
322
- sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
327
+ value: Option | Option[] | null = null
323
328
  ```
324
329
 
325
- 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.
330
+ If `maxSelect={1}`, `value` will be the single item in `selected` (or `null` if `selected` is empty). If `maxSelect != 1`, `maxSelect` and `selected` are equal. Warning: `value` supports 1-way binding only, meaning `bind:value` will update `value` when internal component state changes but changing `value` externally will not update internal component state. This is because `value` is already reactive to `selected` and making `selected` reactive to `value` would be cyclic. Suggestions for better solutions that solve both [#86](https://github.com/janosh/svelte-multiselect/issues/86) and [#136](https://github.com/janosh/svelte-multiselect/issues/136) welcome!
326
331
 
327
332
  ## Slots
328
333