svelte-multiselect 10.0.0 → 10.2.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.
@@ -1,4 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
1
+ import { SvelteComponent } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  color?: string | undefined;
@@ -13,6 +13,6 @@ declare const __propDef: {
13
13
  export type CircleSpinnerProps = typeof __propDef.props;
14
14
  export type CircleSpinnerEvents = typeof __propDef.events;
15
15
  export type CircleSpinnerSlots = typeof __propDef.slots;
16
- export default class CircleSpinner extends SvelteComponentTyped<CircleSpinnerProps, CircleSpinnerEvents, CircleSpinnerSlots> {
16
+ export default class CircleSpinner extends SvelteComponent<CircleSpinnerProps, CircleSpinnerEvents, CircleSpinnerSlots> {
17
17
  }
18
18
  export {};
@@ -1,4 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
1
+ import { SvelteComponent } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  [x: string]: any;
@@ -26,6 +26,6 @@ declare const __propDef: {
26
26
  export type CmdPaletteProps = typeof __propDef.props;
27
27
  export type CmdPaletteEvents = typeof __propDef.events;
28
28
  export type CmdPaletteSlots = typeof __propDef.slots;
29
- export default class CmdPalette extends SvelteComponentTyped<CmdPaletteProps, CmdPaletteEvents, CmdPaletteSlots> {
29
+ export default class CmdPalette extends SvelteComponent<CmdPaletteProps, CmdPaletteEvents, CmdPaletteSlots> {
30
30
  }
31
31
  export {};
@@ -3,6 +3,7 @@ import { flip } from 'svelte/animate';
3
3
  import CircleSpinner from './CircleSpinner.svelte';
4
4
  import Wiggle from './Wiggle.svelte';
5
5
  import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
6
+ import { get_label, get_style } from './utils';
6
7
  export let activeIndex = null;
7
8
  export let activeOption = null;
8
9
  export let createOptionMsg = `Create this option...`;
@@ -25,7 +26,7 @@ export let filterFunc = (opt, searchText) => {
25
26
  return true;
26
27
  return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase());
27
28
  };
28
- export let focusInputOnSelect = `desktop`;
29
+ export let closeDropdownOnSelect = `desktop`;
29
30
  export let form_input = null;
30
31
  export let highlightMatches = true;
31
32
  export let id = null;
@@ -59,26 +60,26 @@ export let resetFilterOnAdd = true;
59
60
  export let searchText = ``;
60
61
  export let selected = options
61
62
  ?.filter((opt) => opt instanceof Object && opt?.preselected)
62
- .slice(0, maxSelect ?? undefined) ?? [];
63
+ .slice(0, maxSelect ?? undefined) ?? []; // don't allow more than maxSelect preselected options
63
64
  export let sortSelected = false;
64
65
  export let selectedOptionsDraggable = !sortSelected;
65
66
  export let ulOptionsClass = ``;
66
67
  export let ulSelectedClass = ``;
67
68
  export let value = null;
68
- // get the label key from an option object or the option itself if it's a string or number
69
- export const get_label = (opt) => {
70
- if (opt instanceof Object) {
71
- if (opt.label === undefined) {
72
- console.error(`MultiSelect option ${JSON.stringify(opt)} is an object but has no label key`);
73
- }
74
- return opt.label;
75
- }
76
- return `${opt}`;
69
+ const selected_to_value = (selected) => {
70
+ value = maxSelect === 1 ? selected[0] ?? null : selected;
71
+ };
72
+ const value_to_selected = (value) => {
73
+ if (maxSelect === 1)
74
+ selected = value ? [value] : [];
75
+ else
76
+ selected = value ?? [];
77
77
  };
78
78
  // if maxSelect=1, value is the single item in selected (or null if selected is empty)
79
79
  // this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
80
80
  // https://github.com/janosh/svelte-multiselect/issues/136
81
- $: value = maxSelect === 1 ? selected[0] ?? null : selected;
81
+ $: selected_to_value(selected);
82
+ $: value_to_selected(value);
82
83
  let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
83
84
  if (!(options?.length > 0)) {
84
85
  if (allowUserOptions || loading || disabled || allowEmpty) {
@@ -185,10 +186,13 @@ function add(option, event) {
185
186
  selected = selected.sort(sortSelected);
186
187
  }
187
188
  }
188
- if (selected.length === maxSelect)
189
+ const reached_max_select = selected.length === maxSelect;
190
+ const dropdown_should_close = closeDropdownOnSelect === true ||
191
+ (closeDropdownOnSelect === `desktop` && window_width < breakpoint);
192
+ if (reached_max_select || dropdown_should_close) {
189
193
  close_dropdown(event);
190
- else if (focusInputOnSelect === true ||
191
- (focusInputOnSelect === `desktop` && window_width > breakpoint)) {
194
+ }
195
+ else if (!dropdown_should_close) {
192
196
  input?.focus();
193
197
  }
194
198
  dispatch(`add`, { option });
@@ -301,10 +305,10 @@ async function handle_keydown(event) {
301
305
  }
302
306
  }
303
307
  function remove_all() {
304
- selected = [];
305
- searchText = ``;
306
308
  dispatch(`removeAll`, { options: selected });
307
309
  dispatch(`change`, { options: selected, type: `removeAll` });
310
+ selected = [];
311
+ searchText = ``;
308
312
  }
309
313
  $: is_selected = (label) => selected.map(get_label).includes(label);
310
314
  const if_enter_or_space = (handler) => (event) => {
@@ -455,6 +459,7 @@ function highlight_matching_options(event) {
455
459
  on:dragenter={() => (drag_idx = idx)}
456
460
  on:dragover|preventDefault
457
461
  class:active={drag_idx === idx}
462
+ style={get_style(option, `selected`)}
458
463
  >
459
464
  <!-- on:dragover|preventDefault needed for the drop to succeed https://stackoverflow.com/a/31085796 -->
460
465
  <slot name="selected" {option} {idx}>
@@ -597,6 +602,7 @@ function highlight_matching_options(event) {
597
602
  on:blur={() => (activeIndex = null)}
598
603
  role="option"
599
604
  aria-selected="false"
605
+ style={get_style(option, `option`)}
600
606
  >
601
607
  <slot name="option" {option} {idx}>
602
608
  <slot {option} {idx}>
@@ -608,16 +614,25 @@ function highlight_matching_options(event) {
608
614
  </slot>
609
615
  </slot>
610
616
  </li>
611
- {:else}
612
- {@const textInputIsDuplicate = selected.map(get_label).includes(searchText)}
613
- <!-- set msg to duplicateOptionMsg if duplicates are not allowed and the user-entered
614
- searchText is a duplicate, else set to createOptionMsg -->
615
- {@const msg =
616
- !duplicates && textInputIsDuplicate ? duplicateOptionMsg : createOptionMsg}
617
- {#if allowUserOptions && searchText && msg}
617
+ {/each}
618
+ {#if searchText}
619
+ {@const text_input_is_duplicate = selected.map(get_label).includes(searchText)}
620
+ {@const is_dupe = !duplicates && text_input_is_duplicate && `dupe`}
621
+ {@const can_create = Boolean(allowUserOptions && createOptionMsg) && `create`}
622
+ {@const no_match =
623
+ Boolean(matchingOptions?.length == 0 && noMatchingOptionsMsg) && `no-match`}
624
+ {@const msgType = is_dupe || can_create || no_match}
625
+ {#if msgType}
626
+ {@const msg = {
627
+ dupe: duplicateOptionMsg,
628
+ create: createOptionMsg,
629
+ 'no-match': noMatchingOptionsMsg,
630
+ }[msgType]}
618
631
  <li
619
632
  on:mousedown|stopPropagation
620
- on:mouseup|stopPropagation={(event) => add(searchText, event)}
633
+ on:mouseup|stopPropagation={(event) => {
634
+ if (allowUserOptions) add(searchText, event)
635
+ }}
621
636
  title={createOptionMsg}
622
637
  class:active={option_msg_is_active}
623
638
  on:mouseover={() => (option_msg_is_active = true)}
@@ -627,24 +642,18 @@ function highlight_matching_options(event) {
627
642
  role="option"
628
643
  aria-selected="false"
629
644
  class="user-msg"
645
+ style:cursor={{
646
+ dupe: `not-allowed`,
647
+ create: `pointer`,
648
+ 'no-match': `default`,
649
+ }[msgType]}
630
650
  >
631
- <slot
632
- name="user-msg"
633
- {duplicateOptionMsg}
634
- {createOptionMsg}
635
- {textInputIsDuplicate}
636
- {searchText}
637
- {msg}
638
- >
651
+ <slot name="user-msg" {searchText} {msgType} {msg}>
639
652
  {msg}
640
653
  </slot>
641
654
  </li>
642
- {:else if noMatchingOptionsMsg}
643
- <!-- use span to not have cursor: pointer -->
644
- <span class="user-msg">{noMatchingOptionsMsg}</span>
645
655
  {/if}
646
- <!-- Show nothing if all messages are empty -->
647
- {/each}
656
+ {/if}
648
657
  </ul>
649
658
  {/if}
650
659
  </div>
@@ -1,4 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
1
+ import { SvelteComponent } from "svelte";
2
2
  import type { MultiSelectEvents, Option as T } from './types';
3
3
  declare class __sveltets_Render<Option extends T> {
4
4
  props(): {
@@ -17,7 +17,7 @@ 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;
@@ -55,7 +55,6 @@ declare class __sveltets_Render<Option extends T> {
55
55
  ulOptionsClass?: string | undefined;
56
56
  ulSelectedClass?: string | undefined;
57
57
  value?: Option | Option[] | null | undefined;
58
- get_label?: ((opt: T) => string | number) | undefined;
59
58
  };
60
59
  events(): MultiSelectEvents;
61
60
  slots(): {
@@ -87,10 +86,8 @@ declare class __sveltets_Render<Option extends T> {
87
86
  idx: any;
88
87
  };
89
88
  'user-msg': {
90
- duplicateOptionMsg: string;
91
- createOptionMsg: string | null;
92
- textInputIsDuplicate: any;
93
89
  searchText: string;
90
+ msgType: any;
94
91
  msg: any;
95
92
  };
96
93
  };
@@ -98,7 +95,6 @@ declare class __sveltets_Render<Option extends T> {
98
95
  export type MultiSelectProps<Option extends T> = ReturnType<__sveltets_Render<Option>['props']>;
99
96
  export type MultiSelectEvents<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
100
97
  export type MultiSelectSlots<Option extends T> = ReturnType<__sveltets_Render<Option>['slots']>;
101
- export default class MultiSelect<Option extends T> extends SvelteComponentTyped<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
102
- get get_label(): (opt: T) => string | number;
98
+ export default class MultiSelect<Option extends T> extends SvelteComponent<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
103
99
  }
104
100
  export {};
@@ -1,4 +1,4 @@
1
- import { SvelteComponentTyped } from "svelte";
1
+ import { SvelteComponent } from "svelte";
2
2
  declare const __propDef: {
3
3
  props: {
4
4
  wiggle?: boolean | undefined;
@@ -20,6 +20,6 @@ declare const __propDef: {
20
20
  export type WiggleProps = typeof __propDef.props;
21
21
  export type WiggleEvents = typeof __propDef.events;
22
22
  export type WiggleSlots = typeof __propDef.slots;
23
- export default class Wiggle extends SvelteComponentTyped<WiggleProps, WiggleEvents, WiggleSlots> {
23
+ export default class Wiggle extends SvelteComponent<WiggleProps, WiggleEvents, WiggleSlots> {
24
24
  }
25
25
  export {};
@@ -1,7 +1,7 @@
1
1
  /** @typedef {typeof __propDef.props} ChevronExpandProps */
2
2
  /** @typedef {typeof __propDef.events} ChevronExpandEvents */
3
3
  /** @typedef {typeof __propDef.slots} ChevronExpandSlots */
4
- export default class ChevronExpand extends SvelteComponentTyped<{
4
+ export default class ChevronExpand extends SvelteComponent<{
5
5
  [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
@@ -10,7 +10,7 @@ export default class ChevronExpand extends SvelteComponentTyped<{
10
10
  export type ChevronExpandProps = typeof __propDef.props;
11
11
  export type ChevronExpandEvents = typeof __propDef.events;
12
12
  export type ChevronExpandSlots = typeof __propDef.slots;
13
- import { SvelteComponentTyped } from "svelte";
13
+ import { SvelteComponent } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
16
  [x: string]: any;
@@ -1,7 +1,7 @@
1
1
  /** @typedef {typeof __propDef.props} CrossProps */
2
2
  /** @typedef {typeof __propDef.events} CrossEvents */
3
3
  /** @typedef {typeof __propDef.slots} CrossSlots */
4
- export default class Cross extends SvelteComponentTyped<{
4
+ export default class Cross extends SvelteComponent<{
5
5
  [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
@@ -10,7 +10,7 @@ export default class Cross extends SvelteComponentTyped<{
10
10
  export type CrossProps = typeof __propDef.props;
11
11
  export type CrossEvents = typeof __propDef.events;
12
12
  export type CrossSlots = typeof __propDef.slots;
13
- import { SvelteComponentTyped } from "svelte";
13
+ import { SvelteComponent } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
16
  [x: string]: any;
@@ -1,7 +1,7 @@
1
1
  /** @typedef {typeof __propDef.props} DisabledProps */
2
2
  /** @typedef {typeof __propDef.events} DisabledEvents */
3
3
  /** @typedef {typeof __propDef.slots} DisabledSlots */
4
- export default class Disabled extends SvelteComponentTyped<{
4
+ export default class Disabled extends SvelteComponent<{
5
5
  [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
@@ -10,7 +10,7 @@ export default class Disabled extends SvelteComponentTyped<{
10
10
  export type DisabledProps = typeof __propDef.props;
11
11
  export type DisabledEvents = typeof __propDef.events;
12
12
  export type DisabledSlots = typeof __propDef.slots;
13
- import { SvelteComponentTyped } from "svelte";
13
+ import { SvelteComponent } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
16
  [x: string]: any;
@@ -1,7 +1,7 @@
1
1
  /** @typedef {typeof __propDef.props} OctocatProps */
2
2
  /** @typedef {typeof __propDef.events} OctocatEvents */
3
3
  /** @typedef {typeof __propDef.slots} OctocatSlots */
4
- export default class Octocat extends SvelteComponentTyped<{
4
+ export default class Octocat extends SvelteComponent<{
5
5
  [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
@@ -10,7 +10,7 @@ export default class Octocat extends SvelteComponentTyped<{
10
10
  export type OctocatProps = typeof __propDef.props;
11
11
  export type OctocatEvents = typeof __propDef.events;
12
12
  export type OctocatSlots = typeof __propDef.slots;
13
- import { SvelteComponentTyped } from "svelte";
13
+ import { SvelteComponent } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
16
  [x: string]: any;
package/dist/types.d.ts CHANGED
@@ -1,4 +1,8 @@
1
1
  export type Option = string | number | ObjectOption;
2
+ export type OptionStyle = string | {
3
+ option: string;
4
+ selected: string;
5
+ };
2
6
  export type ObjectOption = {
3
7
  label: string | number;
4
8
  value?: unknown;
@@ -7,6 +11,7 @@ export type ObjectOption = {
7
11
  preselected?: boolean;
8
12
  disabledTitle?: string;
9
13
  selectedTitle?: string;
14
+ style?: OptionStyle;
10
15
  [key: string]: unknown;
11
16
  };
12
17
  export type DispatchEvents<T = Option> = {
@@ -0,0 +1,6 @@
1
+ import type { Option, OptionStyle } from './types';
2
+ export declare const get_label: (opt: Option) => string | number;
3
+ export declare function get_style(option: {
4
+ style?: OptionStyle;
5
+ [key: string]: unknown;
6
+ } | string | number, key?: 'selected' | 'option' | null): string | null | undefined;
package/dist/utils.js ADDED
@@ -0,0 +1,30 @@
1
+ // get the label key from an option object or the option itself if it's a string or number
2
+ export const get_label = (opt) => {
3
+ if (opt instanceof Object) {
4
+ if (opt.label === undefined) {
5
+ console.error(`MultiSelect option ${JSON.stringify(opt)} is an object but has no label key`);
6
+ }
7
+ return opt.label;
8
+ }
9
+ return `${opt}`;
10
+ };
11
+ export function get_style(option, key = null) {
12
+ if (!option?.style)
13
+ return null;
14
+ if (![`selected`, `option`, null].includes(key)) {
15
+ console.error(`MultiSelect: Invalid key=${key} for get_style`);
16
+ return;
17
+ }
18
+ if (typeof option == `object` && option.style) {
19
+ if (typeof option.style == `string`) {
20
+ return option.style;
21
+ }
22
+ if (typeof option.style == `object`) {
23
+ if (key && key in option.style)
24
+ return option.style[key];
25
+ else {
26
+ console.error(`Invalid style object for option=${JSON.stringify(option)}`);
27
+ }
28
+ }
29
+ }
30
+ }
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.0.0",
8
+ "version": "10.2.0",
9
9
  "type": "module",
10
10
  "svelte": "./dist/index.js",
11
11
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
@@ -23,37 +23,37 @@
23
23
  "update-coverage": "vitest tests/unit --run --coverage && npx istanbul-badges-readme"
24
24
  },
25
25
  "dependencies": {
26
- "svelte": "^4.0.1"
26
+ "svelte": "^4.2.0"
27
27
  },
28
28
  "devDependencies": {
29
29
  "@iconify/svelte": "^3.1.4",
30
- "@playwright/test": "^1.35.1",
31
- "@sveltejs/adapter-static": "^2.0.2",
32
- "@sveltejs/kit": "^1.21.0",
33
- "@sveltejs/package": "2.1.0",
34
- "@sveltejs/vite-plugin-svelte": "2.4.2",
35
- "@typescript-eslint/eslint-plugin": "^5.60.1",
36
- "@typescript-eslint/parser": "^5.60.1",
37
- "@vitest/coverage-v8": "^0.32.2",
38
- "eslint": "^8.44.0",
39
- "eslint-plugin-svelte": "^2.32.2",
40
- "hastscript": "^7.2.0",
30
+ "@playwright/test": "^1.37.1",
31
+ "@sveltejs/adapter-static": "^2.0.3",
32
+ "@sveltejs/kit": "^1.24.1",
33
+ "@sveltejs/package": "2.2.2",
34
+ "@sveltejs/vite-plugin-svelte": "2.4.5",
35
+ "@typescript-eslint/eslint-plugin": "^6.7.0",
36
+ "@typescript-eslint/parser": "^6.7.0",
37
+ "@vitest/coverage-v8": "^0.34.4",
38
+ "eslint": "^8.49.0",
39
+ "eslint-plugin-svelte": "^2.33.1",
40
+ "hastscript": "^8.0.0",
41
41
  "highlight.js": "^11.8.0",
42
42
  "jsdom": "^22.1.0",
43
43
  "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.4",
44
+ "mdsvexamples": "^0.4.1",
45
+ "prettier": "^3.0.3",
46
+ "prettier-plugin-svelte": "^3.0.3",
47
+ "rehype-autolink-headings": "^7.0.0",
48
+ "rehype-slug": "^6.0.0",
49
+ "svelte-check": "^3.5.1",
50
50
  "svelte-preprocess": "^5.0.4",
51
- "svelte-toc": "^0.5.5",
52
- "svelte-zoo": "^0.4.8",
53
- "svelte2tsx": "^0.6.16",
54
- "typescript": "5.1.6",
55
- "vite": "^4.3.9",
56
- "vitest": "^0.32.2"
51
+ "svelte-toc": "^0.5.6",
52
+ "svelte-zoo": "^0.4.9",
53
+ "svelte2tsx": "^0.6.21",
54
+ "typescript": "5.2.2",
55
+ "vite": "^4.4.9",
56
+ "vitest": "^0.34.4"
57
57
  },
58
58
  "keywords": [
59
59
  "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
@@ -385,7 +385,7 @@ Full list of props/bindable variables for this component. The `Option` type you
385
385
  value: Option | Option[] | null = null
386
386
  ```
387
387
 
388
- 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!
388
+ 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: Setting `value` does not rendered state on initial mount, meaning `bind:value` will update local variable `value` whenever internal component state changes but passing a `value` when component first mounts won't be reflected in UI. This is because the source of truth for rendering is `bind:selected`. `selected` is reactive to `value` internally but only on reassignment from initial value. Suggestions for better solutions than [#249](https://github.com/janosh/svelte-multiselect/issues/249) welcome!
389
389
 
390
390
  ## 🎰 &thinsp; Slots
391
391
 
@@ -397,13 +397,11 @@ Full list of props/bindable variables for this component. The `Option` type you
397
397
  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
398
  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
399
  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`.
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). Receives props:
401
+ - `searchText`: The text user typed into search input.
402
+ - `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.
403
+ - `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).
404
+ 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
405
 
408
406
  Example using several slots:
409
407