svelte-multiselect 10.1.0 → 10.3.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.
@@ -28,8 +28,8 @@ function close_if_outside(event) {
28
28
  open = false;
29
29
  }
30
30
  }
31
- function run_and_close(event) {
32
- event.detail.option.action();
31
+ function trigger_action_and_close(event) {
32
+ event.detail.option.action(event.detail.option.label);
33
33
  open = false;
34
34
  }
35
35
  </script>
@@ -42,7 +42,7 @@ function run_and_close(event) {
42
42
  options={actions}
43
43
  bind:input
44
44
  {placeholder}
45
- on:add={run_and_close}
45
+ on:add={trigger_action_and_close}
46
46
  on:keydown={toggle}
47
47
  {...$$restProps}
48
48
  let:option
@@ -26,17 +26,22 @@ export let filterFunc = (opt, searchText) => {
26
26
  return true;
27
27
  return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase());
28
28
  };
29
- export let focusInputOnSelect = `desktop`;
29
+ export let closeDropdownOnSelect = `desktop`;
30
30
  export let form_input = null;
31
31
  export let highlightMatches = true;
32
32
  export let id = null;
33
33
  export let input = null;
34
34
  export let inputClass = ``;
35
+ export let inputStyle = null;
35
36
  export let inputmode = null;
36
37
  export let invalid = false;
37
38
  export let liActiveOptionClass = ``;
39
+ export let liActiveUserMsgClass = ``;
38
40
  export let liOptionClass = ``;
41
+ export let liOptionStyle = null;
39
42
  export let liSelectedClass = ``;
43
+ export let liSelectedStyle = null;
44
+ export let liUserMsgClass = ``;
40
45
  export let loading = false;
41
46
  export let matchingOptions = [];
42
47
  export let maxOptions = undefined;
@@ -63,8 +68,11 @@ export let selected = options
63
68
  .slice(0, maxSelect ?? undefined) ?? []; // don't allow more than maxSelect preselected options
64
69
  export let sortSelected = false;
65
70
  export let selectedOptionsDraggable = !sortSelected;
71
+ export let style = null;
66
72
  export let ulOptionsClass = ``;
67
73
  export let ulSelectedClass = ``;
74
+ export let ulSelectedStyle = null;
75
+ export let ulOptionsStyle = null;
68
76
  export let value = null;
69
77
  const selected_to_value = (selected) => {
70
78
  value = maxSelect === 1 ? selected[0] ?? null : selected;
@@ -186,10 +194,13 @@ function add(option, event) {
186
194
  selected = selected.sort(sortSelected);
187
195
  }
188
196
  }
189
- if (selected.length === maxSelect)
197
+ const reached_max_select = selected.length === maxSelect;
198
+ const dropdown_should_close = closeDropdownOnSelect === true ||
199
+ (closeDropdownOnSelect === `desktop` && window_width < breakpoint);
200
+ if (reached_max_select || dropdown_should_close) {
190
201
  close_dropdown(event);
191
- else if (focusInputOnSelect === true ||
192
- (focusInputOnSelect === `desktop` && window_width > breakpoint)) {
202
+ }
203
+ else if (!dropdown_should_close) {
193
204
  input?.focus();
194
205
  }
195
206
  dispatch(`add`, { option });
@@ -302,10 +313,10 @@ async function handle_keydown(event) {
302
313
  }
303
314
  }
304
315
  function remove_all() {
305
- selected = [];
306
- searchText = ``;
307
316
  dispatch(`removeAll`, { options: selected });
308
317
  dispatch(`change`, { options: selected, type: `removeAll` });
318
+ selected = [];
319
+ searchText = ``;
309
320
  }
310
321
  $: is_selected = (label) => selected.map(get_label).includes(label);
311
322
  const if_enter_or_space = (handler) => (event) => {
@@ -350,7 +361,7 @@ let ul_options;
350
361
  // highlight text matching user-entered search text in available options
351
362
  function highlight_matching_options(event) {
352
363
  if (!highlightMatches || typeof CSS == `undefined` || !CSS.highlights)
353
- return; // don't try if CSS highlight API not supported
364
+ return; // abort if CSS highlight API not supported
354
365
  // clear previous ranges from HighlightRegistry
355
366
  CSS.highlights.clear();
356
367
  // get input's search query
@@ -392,9 +403,11 @@ function highlight_matching_options(event) {
392
403
  });
393
404
  });
394
405
  // create Highlight object from ranges and add to registry
395
- // eslint-disable-next-line no-undef
396
406
  CSS.highlights.set(`sms-search-matches`, new Highlight(...ranges.flat()));
397
407
  }
408
+ // reset form validation when required prop changes
409
+ // https://github.com/janosh/svelte-multiselect/issues/285
410
+ $: required, form_input?.setCustomValidity(``);
398
411
  </script>
399
412
 
400
413
  <svelte:window
@@ -415,12 +428,13 @@ function highlight_matching_options(event) {
415
428
  data-id={id}
416
429
  role="searchbox"
417
430
  tabindex="-1"
431
+ {style}
418
432
  >
419
433
  <!-- form control input invisible to the user, only purpose is to abort form submission if this component fails data validation -->
420
434
  <!-- bind:value={selected} prevents form submission if required prop is true and no options are selected -->
421
435
  <input
422
436
  {name}
423
- required={Boolean(required)}
437
+ {required}
424
438
  value={selected.length >= Number(required) ? JSON.stringify(selected) : null}
425
439
  tabindex="-1"
426
440
  aria-hidden="true"
@@ -443,7 +457,11 @@ function highlight_matching_options(event) {
443
457
  <slot name="expand-icon" {open}>
444
458
  <ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt; cursor: pointer;" />
445
459
  </slot>
446
- <ul class="selected {ulSelectedClass}" aria-label="selected options">
460
+ <ul
461
+ class="selected {ulSelectedClass}"
462
+ aria-label="selected options"
463
+ style={ulSelectedStyle}
464
+ >
447
465
  {#each selected as option, idx (duplicates ? [key(option), idx] : key(option))}
448
466
  <li
449
467
  class={liSelectedClass}
@@ -456,7 +474,7 @@ function highlight_matching_options(event) {
456
474
  on:dragenter={() => (drag_idx = idx)}
457
475
  on:dragover|preventDefault
458
476
  class:active={drag_idx === idx}
459
- style={get_style(option, `selected`)}
477
+ style="{get_style(option, `selected`)} {liSelectedStyle}"
460
478
  >
461
479
  <!-- on:dragover|preventDefault needed for the drop to succeed https://stackoverflow.com/a/31085796 -->
462
480
  <slot name="selected" {option} {idx}>
@@ -485,6 +503,7 @@ function highlight_matching_options(event) {
485
503
  {/each}
486
504
  <input
487
505
  class={inputClass}
506
+ style={inputStyle}
488
507
  bind:this={input}
489
508
  bind:value={searchText}
490
509
  on:mouseup|self|stopPropagation={open_dropdown}
@@ -567,6 +586,7 @@ function highlight_matching_options(event) {
567
586
  aria-expanded={open}
568
587
  aria-disabled={disabled ? `true` : null}
569
588
  bind:this={ul_options}
589
+ style={ulOptionsStyle}
570
590
  >
571
591
  {#each matchingOptions.slice(0, Math.max(0, maxOptions ?? 0) || Infinity) as option, idx}
572
592
  {@const {
@@ -599,7 +619,7 @@ function highlight_matching_options(event) {
599
619
  on:blur={() => (activeIndex = null)}
600
620
  role="option"
601
621
  aria-selected="false"
602
- style={get_style(option, `option`)}
622
+ style="{get_style(option, `option`)} {liOptionStyle}"
603
623
  >
604
624
  <slot name="option" {option} {idx}>
605
625
  <slot {option} {idx}>
@@ -615,9 +635,9 @@ function highlight_matching_options(event) {
615
635
  {#if searchText}
616
636
  {@const text_input_is_duplicate = selected.map(get_label).includes(searchText)}
617
637
  {@const is_dupe = !duplicates && text_input_is_duplicate && `dupe`}
618
- {@const can_create = allowUserOptions && createOptionMsg && `create`}
638
+ {@const can_create = Boolean(allowUserOptions && createOptionMsg) && `create`}
619
639
  {@const no_match =
620
- matchingOptions?.length == 0 && noMatchingOptionsMsg && `no-match`}
640
+ Boolean(matchingOptions?.length == 0 && noMatchingOptionsMsg) && `no-match`}
621
641
  {@const msgType = is_dupe || can_create || no_match}
622
642
  {#if msgType}
623
643
  {@const msg = {
@@ -638,7 +658,9 @@ function highlight_matching_options(event) {
638
658
  on:blur={() => (option_msg_is_active = false)}
639
659
  role="option"
640
660
  aria-selected="false"
641
- class="user-msg"
661
+ class="user-msg {liUserMsgClass} {option_msg_is_active
662
+ ? liActiveUserMsgClass
663
+ : ``}"
642
664
  style:cursor={{
643
665
  dupe: `not-allowed`,
644
666
  create: `pointer`,
@@ -17,17 +17,22 @@ declare class __sveltets_Render<Option extends T> {
17
17
  duplicates?: boolean | undefined;
18
18
  key?: ((opt: T) => unknown) | undefined;
19
19
  filterFunc?: ((opt: Option, searchText: string) => boolean) | undefined;
20
- focusInputOnSelect?: boolean | "desktop" | undefined;
20
+ closeDropdownOnSelect?: boolean | "desktop" | undefined;
21
21
  form_input?: HTMLInputElement | null | undefined;
22
22
  highlightMatches?: boolean | undefined;
23
23
  id?: string | null | undefined;
24
24
  input?: HTMLInputElement | null | undefined;
25
25
  inputClass?: string | undefined;
26
+ inputStyle?: string | null | undefined;
26
27
  inputmode?: string | null | undefined;
27
28
  invalid?: boolean | undefined;
28
29
  liActiveOptionClass?: string | undefined;
30
+ liActiveUserMsgClass?: string | undefined;
29
31
  liOptionClass?: string | undefined;
32
+ liOptionStyle?: string | null | undefined;
30
33
  liSelectedClass?: string | undefined;
34
+ liSelectedStyle?: string | null | undefined;
35
+ liUserMsgClass?: string | undefined;
31
36
  loading?: boolean | undefined;
32
37
  matchingOptions?: Option[] | undefined;
33
38
  maxOptions?: number | undefined;
@@ -52,8 +57,11 @@ declare class __sveltets_Render<Option extends T> {
52
57
  selected?: Option[] | undefined;
53
58
  sortSelected?: boolean | ((op1: Option, op2: Option) => number) | undefined;
54
59
  selectedOptionsDraggable?: boolean | undefined;
60
+ style?: string | null | undefined;
55
61
  ulOptionsClass?: string | undefined;
56
62
  ulSelectedClass?: string | undefined;
63
+ ulSelectedStyle?: string | null | undefined;
64
+ ulOptionsStyle?: string | null | undefined;
57
65
  value?: Option | Option[] | null | undefined;
58
66
  };
59
67
  events(): MultiSelectEvents;
@@ -93,8 +101,8 @@ declare class __sveltets_Render<Option extends T> {
93
101
  };
94
102
  }
95
103
  export type MultiSelectProps<Option extends T> = ReturnType<__sveltets_Render<Option>['props']>;
96
- export type MultiSelectEvents<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
104
+ export type MultiSelectEvents_<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
97
105
  export type MultiSelectSlots<Option extends T> = ReturnType<__sveltets_Render<Option>['slots']>;
98
- export default class MultiSelect<Option extends T> extends SvelteComponent<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
106
+ export default class MultiSelect<Option extends T> extends SvelteComponent<MultiSelectProps<Option>, MultiSelectEvents_<Option>, MultiSelectSlots<Option>> {
99
107
  }
100
108
  export {};
package/dist/utils.d.ts CHANGED
@@ -3,4 +3,4 @@ export declare const get_label: (opt: Option) => string | number;
3
3
  export declare function get_style(option: {
4
4
  style?: OptionStyle;
5
5
  [key: string]: unknown;
6
- } | string | number, key?: 'selected' | 'option' | null): string | null | undefined;
6
+ } | string | number, key?: 'selected' | 'option' | null): string;
package/dist/utils.js CHANGED
@@ -8,23 +8,27 @@ export const get_label = (opt) => {
8
8
  }
9
9
  return `${opt}`;
10
10
  };
11
+ // this function is used extract CSS strings from a {selected, option} style object to be used in the style attribute of the option
12
+ // if the style is a string, it will be returned as is
11
13
  export function get_style(option, key = null) {
12
- if (!option?.style)
13
- return null;
14
+ let css_str = ``;
14
15
  if (![`selected`, `option`, null].includes(key)) {
15
16
  console.error(`MultiSelect: Invalid key=${key} for get_style`);
16
- return;
17
17
  }
18
18
  if (typeof option == `object` && option.style) {
19
19
  if (typeof option.style == `string`) {
20
- return option.style;
20
+ css_str = option.style;
21
21
  }
22
22
  if (typeof option.style == `object`) {
23
23
  if (key && key in option.style)
24
- return option.style[key];
24
+ return option.style[key] ?? ``;
25
25
  else {
26
26
  console.error(`Invalid style object for option=${JSON.stringify(option)}`);
27
27
  }
28
28
  }
29
29
  }
30
+ // ensure css_str ends with a semicolon
31
+ if (css_str.trim() && !css_str.trim().endsWith(`;`))
32
+ css_str += `;`;
33
+ return css_str;
30
34
  }
package/package.json CHANGED
@@ -5,7 +5,7 @@
5
5
  "homepage": "https://janosh.github.io/svelte-multiselect",
6
6
  "repository": "https://github.com/janosh/svelte-multiselect",
7
7
  "license": "MIT",
8
- "version": "10.1.0",
8
+ "version": "10.3.0",
9
9
  "type": "module",
10
10
  "svelte": "./dist/index.js",
11
11
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
@@ -23,37 +23,38 @@
23
23
  "update-coverage": "vitest tests/unit --run --coverage && npx istanbul-badges-readme"
24
24
  },
25
25
  "dependencies": {
26
- "svelte": "^4.0.5"
26
+ "svelte": "4.2.12"
27
27
  },
28
28
  "devDependencies": {
29
- "@iconify/svelte": "^3.1.4",
30
- "@playwright/test": "^1.36.1",
31
- "@sveltejs/adapter-static": "^2.0.2",
32
- "@sveltejs/kit": "^1.22.3",
33
- "@sveltejs/package": "2.2.0",
34
- "@sveltejs/vite-plugin-svelte": "2.4.2",
35
- "@typescript-eslint/eslint-plugin": "^6.1.0",
36
- "@typescript-eslint/parser": "^6.1.0",
37
- "@vitest/coverage-v8": "^0.33.0",
38
- "eslint": "^8.45.0",
39
- "eslint-plugin-svelte": "^2.32.2",
40
- "hastscript": "^7.2.0",
41
- "highlight.js": "^11.8.0",
42
- "jsdom": "^22.1.0",
29
+ "@iconify/svelte": "^3.1.6",
30
+ "@playwright/test": "^1.43.0",
31
+ "@sveltejs/adapter-static": "^3.0.1",
32
+ "@sveltejs/kit": "^2.5.5",
33
+ "@sveltejs/package": "2.3.1",
34
+ "@sveltejs/vite-plugin-svelte": "3.0.2",
35
+ "@vitest/coverage-v8": "^1.4.0",
36
+ "eslint": "^9.0.0",
37
+ "eslint-plugin-svelte": "^2.36.0",
38
+ "globals": "^15.0.0",
39
+ "hastscript": "^9.0.0",
40
+ "highlight.js": "^11.9.0",
41
+ "jsdom": "^24.0.0",
43
42
  "mdsvex": "^0.11.0",
44
- "mdsvexamples": "^0.3.3",
45
- "prettier": "^2.8.8",
46
- "prettier-plugin-svelte": "^2.10.1",
47
- "rehype-autolink-headings": "^6.1.1",
48
- "rehype-slug": "^5.1.0",
49
- "svelte-check": "^3.4.6",
50
- "svelte-preprocess": "^5.0.4",
51
- "svelte-toc": "^0.5.5",
52
- "svelte-zoo": "^0.4.9",
53
- "svelte2tsx": "^0.6.19",
54
- "typescript": "5.1.6",
55
- "vite": "^4.4.4",
56
- "vitest": "^0.33.0"
43
+ "mdsvexamples": "^0.4.1",
44
+ "prettier": "^3.2.5",
45
+ "prettier-plugin-svelte": "^3.2.2",
46
+ "rehype-autolink-headings": "^7.1.0",
47
+ "rehype-slug": "^6.0.0",
48
+ "svelte-check": "^3.6.9",
49
+ "svelte-multiselect": "^10.2.0",
50
+ "svelte-preprocess": "^5.1.3",
51
+ "svelte-toc": "^0.5.8",
52
+ "svelte-zoo": "^0.4.10",
53
+ "svelte2tsx": "^0.7.6",
54
+ "typescript": "5.4.4",
55
+ "typescript-eslint": "^7.5.0",
56
+ "vite": "^5.1.5",
57
+ "vitest": "^1.4.0"
57
58
  },
58
59
  "keywords": [
59
60
  "svelte",
package/readme.md CHANGED
@@ -194,10 +194,10 @@ Full list of props/bindable variables for this component. The `Option` type you
194
194
  Customize how dropdown options are filtered when user enters search string into `<MultiSelect />`. Defaults to:
195
195
 
196
196
  1. ```ts
197
- focusInputOnSelect: boolean | 'desktop' = `desktop`
197
+ closeDropdownOnSelect: boolean | 'desktop' = `desktop`
198
198
  ```
199
199
 
200
- 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).
200
+ One of `true`, `false` or `'desktop'`. Whether to close the dropdown list after selecting a dropdown item. If `true`, component will loose focus and `dropdown` is closed. `'desktop'` means `false` if current window width is larger than the current value of `breakpoint` prop (default is 800, meaning screen width in pixels). This is to align with the default behavior of many mobile browsers like Safari which close dropdowns after selecting an option while desktop browsers facilitate multi-selection by leaving dropdowns open.
201
201
 
202
202
  1. ```ts
203
203
  form_input: HTMLInputElement | null = null
@@ -229,12 +229,30 @@ Full list of props/bindable variables for this component. The `Option` type you
229
229
 
230
230
  The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow mobile browsers to display an appropriate virtual on-screen keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details. If you want to suppress the on-screen keyboard to leave full-screen real estate for the dropdown list of options, set `inputmode="none"`.
231
231
 
232
+ 1. ```ts
233
+ inputStyle: string | null = null
234
+ ```
235
+
236
+ One-off CSS rules applied to the `<input>` element.
237
+
232
238
  1. ```ts
233
239
  invalid: boolean = false
234
240
  ```
235
241
 
236
242
  If `required = true, 1, 2, ...` and user tries to submit form but `selected = []` is empty/`selected.length < required`, `invalid` is automatically set to `true` and CSS class `invalid` applied to the top-level `div.multiselect`. `invalid` class is removed as soon as any change to `selected` is registered. `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.
237
243
 
244
+ 1. ```ts
245
+ liOptionStyle: string | null = null
246
+ ```
247
+
248
+ One-off CSS rules applied to the `<li>` elements that wrap the dropdown options.
249
+
250
+ 1. ```ts
251
+ liSelectedStyle: string | null = null
252
+ ```
253
+
254
+ One-off CSS rules applied to the `<li>` elements that wrap the selected options.
255
+
238
256
  1. ```ts
239
257
  loading: boolean = false
240
258
  ```
@@ -304,7 +322,7 @@ Full list of props/bindable variables for this component. The `Option` type you
304
322
  options: Option[]
305
323
  ```
306
324
 
307
- **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 type `ObjectOption` in [`src/lib/index.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/index.ts) for all special keys and their purpose.
325
+ **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 type `ObjectOption` in [`/src/lib/types.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/types.ts) for all special keys and their purpose.
308
326
 
309
327
  1. ```ts
310
328
  outerDiv: HTMLDivElement | null = null
@@ -381,6 +399,24 @@ Full list of props/bindable variables for this component. The `Option` type you
381
399
 
382
400
  Whether selected options are draggable so users can change their order.
383
401
 
402
+ 1. ```ts
403
+ style: string | null = null
404
+ ```
405
+
406
+ One-off CSS rules applied to the outer `<div class="multiselect">` that wraps the whole component for passing one-off CSS.
407
+
408
+ 1. ```ts
409
+ ulSelectedStyle: string | null = null
410
+ ```
411
+
412
+ One-off CSS rules applied to the `<ul class="selected">` that wraps the list of selected options.
413
+
414
+ 1. ```ts
415
+ ulOptionsStyle: string | null = null
416
+ ```
417
+
418
+ One-off CSS rules applied to the `<ul class="options">` that wraps the list of selected options.
419
+
384
420
  1. ```ts
385
421
  value: Option | Option[] | null = null
386
422
  ```
@@ -397,13 +433,11 @@ Full list of props/bindable variables for this component. The `Option` type you
397
433
  1. `slot="disabled-icon"`: Custom icon to display inside the input when in `disabled` state. Receives no props. Use an empty `<span slot="disabled-icon" />` or `div` to remove the default disabled icon.
398
434
  1. `slot="expand-icon"`: 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.
399
435
  1. `slot="remove-icon"`: 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.
400
- 1. `slot="user-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). `let:props`:
401
- - `duplicateOptionMsg: string`: See [props](#🔣-props).
402
- - `createOptionMsg: string`: See [props](#🔣-props).
403
- - `textInputIsDuplicate: boolean`: Whether user has typed text that matches an already existing option.
404
- - `searchText: string`: The text user typed into search input.
405
- - `msg: string`: `duplicateOptionMsg` if user input is a duplicate else `createOptionMsg`.
406
- 1. `slot='after-input'`: ForPlaced after the search input. For arbitrary content like icons or temporary messages. Receives props `selected`, `disabled`, `invalid`, `id`, `placeholder`, `open`, `required`.
436
+ 1. `slot="user-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:
437
+ - `searchText`: The text user typed into search input.
438
+ - `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.
439
+ - `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 slot replaces the default UI for displaying these messages so the slot needs to render them instead (unless purposely not showing a message).
440
+ 1. `slot='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.
407
441
 
408
442
  Example using several slots:
409
443
 
@@ -429,13 +463,13 @@ Example using several slots:
429
463
  on:add={(event) => console.log(event.detail.option)}
430
464
  ```
431
465
 
432
- Triggers when a new option is selected.
466
+ Triggers when a new option is selected. The newly selected option is provided as `event.detail.option`.
433
467
 
434
468
  1. ```ts
435
469
  on:remove={(event) => console.log(event.detail.option)}`
436
470
  ```
437
471
 
438
- Triggers when one selected option provided as `event.detail.option` is removed.
472
+ Triggers when a single selected option is removed. The removed option is provided as `event.detail.option`.
439
473
 
440
474
  1. ```ts
441
475
  on:removeAll={(event) => console.log(event.detail.options)}`
@@ -447,7 +481,7 @@ Example using several slots:
447
481
  on:change={(event) => console.log(`${event.detail.type}: '${event.detail.option}'`)}
448
482
  ```
449
483
 
450
- 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.
484
+ 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.
451
485
 
452
486
  1. ```ts
453
487
  on:open={(event) => console.log(`Multiselect dropdown was opened by ${event}`)}
@@ -607,6 +641,8 @@ The second method allows you to pass in custom classes to the important DOM elem
607
641
  - `ulOptionsClass`: available options listed in the dropdown when component is in `open` state
608
642
  - `liOptionClass`: list items selectable from dropdown list
609
643
  - `liActiveOptionClass`: the currently active dropdown list item (i.e. hovered or navigated to with arrow keys)
644
+ - `liUserMsgClass`: user message (last child of dropdown list when no options match user input)
645
+ - `liActiveUserMsgClass`: user message when active (i.e. hovered or navigated to with arrow keys)
610
646
  - `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
611
647
 
612
648
  This simplified version of the DOM structure of the component shows where these classes are inserted:
@@ -624,6 +660,10 @@ This simplified version of the DOM structure of the component shows where these
624
660
  <li class="{liOptionClass} {liActiveOptionClass}">
625
661
  Option 2 (currently active)
626
662
  </li>
663
+ ...
664
+ <li class="{liUserMsgClass} {liActiveUserMsgClass}">
665
+ Create this option...
666
+ </li>
627
667
  </ul>
628
668
  </div>
629
669
  ```