svelte-multiselect 8.6.0 → 8.6.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.
@@ -10,8 +10,8 @@ export let style = ``; // for dialog
10
10
  // for span in option slot, has no effect when passing slot="option"
11
11
  export let span_style = ``;
12
12
  export let open = false;
13
- export let dialog;
14
- export let input;
13
+ export let dialog = null;
14
+ export let input = null;
15
15
  export let placeholder = `Filter actions...`;
16
16
  async function toggle(event) {
17
17
  if (event.key === trigger && event.metaKey && !open) {
@@ -11,8 +11,8 @@ declare const __propDef: {
11
11
  style?: string | undefined;
12
12
  span_style?: string | undefined;
13
13
  open?: boolean | undefined;
14
- dialog: HTMLDialogElement;
15
- input: HTMLInputElement;
14
+ dialog?: HTMLDialogElement | null | undefined;
15
+ input?: HTMLInputElement | null | undefined;
16
16
  placeholder?: string | undefined;
17
17
  };
18
18
  events: {
@@ -1,6 +1,7 @@
1
1
  <script>import { createEventDispatcher, tick } from 'svelte';
2
2
  import { flip } from 'svelte/animate';
3
- import { CircleSpinner, Wiggle } from '.';
3
+ import CircleSpinner from './CircleSpinner.svelte';
4
+ import Wiggle from './Wiggle.svelte';
4
5
  import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
5
6
  export let activeIndex = null;
6
7
  export let activeOption = null;
@@ -63,7 +64,7 @@ export let ulOptionsClass = ``;
63
64
  export let ulSelectedClass = ``;
64
65
  export let value = null;
65
66
  // get the label key from an option object or the option itself if it's a string or number
66
- const get_label = (op) => {
67
+ export const get_label = (op) => {
67
68
  if (op instanceof Object) {
68
69
  if (op.label === undefined) {
69
70
  console.error(`MultiSelect option ${JSON.stringify(op)} is an object but has no label key`);
@@ -100,13 +101,18 @@ if (parseLabelsAsHtml && allowUserOptions) {
100
101
  console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
101
102
  }
102
103
  if (sortSelected && selectedOptionsDraggable) {
103
- console.warn(`MultiSelect's sortSelected and selectedOptionsDraggable should not be combined as any user re-orderings of selected options will be undone by sortSelected on component re-renders.`);
104
+ console.warn(`MultiSelect's sortSelected and selectedOptionsDraggable should not be combined as any ` +
105
+ `user re-orderings of selected options will be undone by sortSelected on component re-renders.`);
106
+ }
107
+ if (allowUserOptions && !createOptionMsg && createOptionMsg !== null) {
108
+ console.error(`MultiSelect has allowUserOptions=${allowUserOptions} but createOptionMsg=${createOptionMsg} is falsy. ` +
109
+ `This prevents the "Add option" <span> from showing up, resulting in a confusing user experience.`);
104
110
  }
105
111
  const dispatch = createEventDispatcher();
106
- let add_option_msg_is_active = false; // controls active state of <li>{createOptionMsg}</li>
112
+ let option_msg_is_active = false; // controls active state of <li>{createOptionMsg}</li>
107
113
  let window_width;
108
114
  // options matching the current search text
109
- $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.map(get_label).includes(get_label(op)) // remove already selected options from dropdown list
115
+ $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.includes(op) // remove already selected options from dropdown list
110
116
  );
111
117
  // raise if matchingOptions[activeIndex] does not yield a value
112
118
  if (activeIndex !== null && !matchingOptions[activeIndex]) {
@@ -115,17 +121,17 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
115
121
  // update activeOption when activeIndex changes
116
122
  $: activeOption = matchingOptions[activeIndex ?? -1] ?? null;
117
123
  // add an option to selected list
118
- function add(label, event) {
124
+ function add(option, event) {
119
125
  if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
120
126
  wiggle = true;
121
- if (!isNaN(Number(label)) && typeof selected.map(get_label)[0] === `number`)
122
- label = Number(label); // convert to number if possible
123
- const is_duplicate = selected.some((option) => duplicateFunc(option, label));
127
+ if (!isNaN(Number(option)) && typeof selected.map(get_label)[0] === `number`) {
128
+ option = Number(option); // convert to number if possible
129
+ }
130
+ const is_duplicate = selected.some((op) => duplicateFunc(op, option));
124
131
  if ((maxSelect === null || maxSelect === 1 || selected.length < maxSelect) &&
125
132
  (duplicates || !is_duplicate)) {
126
- // first check if we find option in the options list
127
- let option = options.find((op) => get_label(op) === label);
128
- if (!option && // this has the side-effect of not allowing to user to add the same
133
+ if (!options.includes(option) && // first check if we find option in the options list
134
+ // this has the side-effect of not allowing to user to add the same
129
135
  // custom option twice in append mode
130
136
  [true, `append`].includes(allowUserOptions) &&
131
137
  searchText.length > 0) {
@@ -149,13 +155,10 @@ function add(label, event) {
149
155
  if (allowUserOptions === `append`)
150
156
  options = [...options, option];
151
157
  }
152
- if (option === undefined) {
153
- throw `Run time error, option with label ${label} not found in options list`;
154
- }
155
158
  if (resetFilterOnAdd)
156
159
  searchText = ``; // reset search string on selection
157
160
  if ([``, undefined, null].includes(option)) {
158
- console.error(`MultiSelect: encountered missing option with label ${label} (or option is poorly labeled)`);
161
+ console.error(`MultiSelect: encountered falsy option ${option}`);
159
162
  return;
160
163
  }
161
164
  if (maxSelect === 1) {
@@ -188,20 +191,22 @@ function add(label, event) {
188
191
  }
189
192
  }
190
193
  // remove an option from selected list
191
- function remove(label) {
194
+ function remove(to_remove) {
192
195
  if (selected.length === 0)
193
196
  return;
194
- let option = selected.find((op) => get_label(op) === label);
197
+ const idx = selected.findIndex((op) => JSON.stringify(op) === JSON.stringify(to_remove));
198
+ let [option] = selected.splice(idx, 1); // remove option from selected list
195
199
  if (option === undefined && allowUserOptions) {
196
200
  // if option with label could not be found but allowUserOptions is truthy,
197
201
  // assume it was created by user and create corresponding option object
198
202
  // on the fly for use as event payload
199
- option = (typeof options[0] == `object` ? { label } : label);
203
+ const other_ops_type = typeof options[0];
204
+ option = (other_ops_type ? { label: to_remove } : to_remove);
200
205
  }
201
206
  if (option === undefined) {
202
- return console.error(`Multiselect can't remove selected option ${label}, not found in selected list`);
207
+ return console.error(`Multiselect can't remove selected option ${JSON.stringify(to_remove)}, not found in selected list`);
203
208
  }
204
- selected = selected.filter((op) => get_label(op) !== label); // remove option from selected list
209
+ selected = [...selected]; // trigger Svelte rerender
205
210
  invalid = false; // reset error status whenever items are removed
206
211
  form_input?.setCustomValidity(``);
207
212
  dispatch(`remove`, { option });
@@ -234,8 +239,7 @@ async function handle_keydown(event) {
234
239
  else if (event.key === `Enter`) {
235
240
  event.preventDefault(); // prevent enter key from triggering form submission
236
241
  if (activeOption) {
237
- const label = get_label(activeOption);
238
- selected.map(get_label).includes(label) ? remove(label) : add(label, event);
242
+ selected.includes(activeOption) ? remove(activeOption) : add(activeOption, event);
239
243
  searchText = ``;
240
244
  }
241
245
  else if (allowUserOptions && searchText.length > 0) {
@@ -257,7 +261,7 @@ async function handle_keydown(event) {
257
261
  else if (allowUserOptions && !matchingOptions.length && searchText.length > 0) {
258
262
  // if allowUserOptions is truthy and user entered text but no options match, we make
259
263
  // <li>{addUserMsg}</li> active on keydown (or toggle it if already active)
260
- add_option_msg_is_active = !add_option_msg_is_active;
264
+ option_msg_is_active = !option_msg_is_active;
261
265
  return;
262
266
  }
263
267
  else if (activeIndex === null) {
@@ -282,7 +286,7 @@ async function handle_keydown(event) {
282
286
  }
283
287
  // on backspace key: remove last selected option
284
288
  else if (event.key === `Backspace` && selected.length > 0 && !searchText) {
285
- remove(selected.map(get_label).at(-1));
289
+ remove(selected.at(-1));
286
290
  }
287
291
  // make first matching option active on any keypress (if none of the above special cases match)
288
292
  else if (matchingOptions.length > 0) {
@@ -344,7 +348,14 @@ function highlight_matching_options(event) {
344
348
  const query = event?.target?.value.trim().toLowerCase();
345
349
  if (!query)
346
350
  return;
347
- const tree_walker = document.createTreeWalker(ul_options, NodeFilter.SHOW_TEXT);
351
+ const tree_walker = document.createTreeWalker(ul_options, NodeFilter.SHOW_TEXT, {
352
+ acceptNode: (node) => {
353
+ // don't highlight text in the "no matching options" message
354
+ if (node?.textContent === noMatchingOptionsMsg)
355
+ return NodeFilter.FILTER_REJECT;
356
+ return NodeFilter.FILTER_ACCEPT;
357
+ },
358
+ });
348
359
  const text_nodes = [];
349
360
  let current_node = tree_walker.nextNode();
350
361
  while (current_node) {
@@ -353,10 +364,10 @@ function highlight_matching_options(event) {
353
364
  }
354
365
  // iterate over all text nodes and find matches
355
366
  const ranges = text_nodes.map((el) => {
356
- const text = el.textContent.toLowerCase();
367
+ const text = el.textContent?.toLowerCase();
357
368
  const indices = [];
358
369
  let start_pos = 0;
359
- while (start_pos < text.length) {
370
+ while (text && start_pos < text.length) {
360
371
  const index = text.indexOf(query, start_pos);
361
372
  if (index === -1)
362
373
  break;
@@ -373,7 +384,7 @@ function highlight_matching_options(event) {
373
384
  });
374
385
  // create Highlight object from ranges and add to registry
375
386
  // eslint-disable-next-line no-undef
376
- CSS.highlights.set(`search-results`, new Highlight(...ranges.flat()));
387
+ CSS.highlights.set(`sms-search-matches`, new Highlight(...ranges.flat()));
377
388
  }
378
389
  </script>
379
390
 
@@ -398,7 +409,7 @@ function highlight_matching_options(event) {
398
409
  <input
399
410
  {name}
400
411
  required={Boolean(required)}
401
- value={selected.length >= required ? JSON.stringify(selected) : null}
412
+ value={selected.length >= Number(required) ? JSON.stringify(selected) : null}
402
413
  tabindex="-1"
403
414
  aria-hidden="true"
404
415
  aria-label="ignore this, used only to prevent form submission if select is required but empty"
@@ -407,9 +418,9 @@ function highlight_matching_options(event) {
407
418
  on:invalid={() => {
408
419
  invalid = true
409
420
  let msg
410
- if (maxSelect && maxSelect > 1 && required > 1) {
421
+ if (maxSelect && maxSelect > 1 && Number(required) > 1) {
411
422
  msg = `Please select between ${required} and ${maxSelect} options`
412
- } else if (required > 1) {
423
+ } else if (Number(required) > 1) {
413
424
  msg = `Please select at least ${required} options`
414
425
  } else {
415
426
  msg = `Please select an option`
@@ -421,7 +432,7 @@ function highlight_matching_options(event) {
421
432
  <ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt; cursor: pointer;" />
422
433
  </slot>
423
434
  <ul class="selected {ulSelectedClass}" aria-label="selected options">
424
- {#each selected as option, idx (get_label(option))}
435
+ {#each selected as option, idx (option)}
425
436
  <li
426
437
  class={liSelectedClass}
427
438
  animate:flip={{ duration: 100 }}
@@ -441,8 +452,8 @@ function highlight_matching_options(event) {
441
452
  </slot>
442
453
  {#if !disabled && (minSelect === null || selected.length > minSelect)}
443
454
  <button
444
- on:mouseup|stopPropagation={() => remove(get_label(option))}
445
- on:keydown={if_enter_or_space(() => remove(get_label(option)))}
455
+ on:mouseup|stopPropagation={() => remove(option)}
456
+ on:keydown={if_enter_or_space(() => remove(option))}
446
457
  type="button"
447
458
  title="{removeBtnTitle} {get_label(option)}"
448
459
  class="remove"
@@ -533,7 +544,7 @@ function highlight_matching_options(event) {
533
544
  <li
534
545
  on:mousedown|stopPropagation
535
546
  on:mouseup|stopPropagation={(event) => {
536
- if (!disabled) add(label, event)
547
+ if (!disabled) add(option, event)
537
548
  }}
538
549
  title={disabled
539
550
  ? disabledTitle
@@ -560,24 +571,30 @@ function highlight_matching_options(event) {
560
571
  </slot>
561
572
  </li>
562
573
  {:else}
563
- {#if allowUserOptions && searchText}
574
+ {@const search_is_duplicate = selected.some((option) =>
575
+ duplicateFunc(option, searchText)
576
+ )}
577
+ {@const msg =
578
+ !duplicates && search_is_duplicate ? duplicateOptionMsg : createOptionMsg}
579
+ {#if allowUserOptions && searchText && msg}
564
580
  <li
565
581
  on:mousedown|stopPropagation
566
582
  on:mouseup|stopPropagation={(event) => add(searchText, event)}
567
583
  title={createOptionMsg}
568
- class:active={add_option_msg_is_active}
569
- on:mouseover={() => (add_option_msg_is_active = true)}
570
- on:focus={() => (add_option_msg_is_active = true)}
571
- on:mouseout={() => (add_option_msg_is_active = false)}
572
- on:blur={() => (add_option_msg_is_active = false)}
584
+ class:active={option_msg_is_active}
585
+ on:mouseover={() => (option_msg_is_active = true)}
586
+ on:focus={() => (option_msg_is_active = true)}
587
+ on:mouseout={() => (option_msg_is_active = false)}
588
+ on:blur={() => (option_msg_is_active = false)}
589
+ class="user-msg"
573
590
  >
574
- {!duplicates && selected.some((option) => duplicateFunc(option, searchText))
575
- ? duplicateOptionMsg
576
- : createOptionMsg}
591
+ {msg}
577
592
  </li>
578
- {:else}
579
- <span>{noMatchingOptionsMsg}</span>
593
+ {:else if noMatchingOptionsMsg}
594
+ <!-- use span to not have cursor: pointer -->
595
+ <span class="user-msg">{noMatchingOptionsMsg}</span>
580
596
  {/if}
597
+ <!-- Show nothing if all messages are empty -->
581
598
  {/each}
582
599
  </ul>
583
600
  {/if}
@@ -721,8 +738,9 @@ function highlight_matching_options(event) {
721
738
  cursor: pointer;
722
739
  scroll-margin: var(--sms-options-scroll-margin, 100px);
723
740
  }
724
- /* for noOptionsMsg */
725
- :where(div.multiselect > ul.options span) {
741
+ :where(div.multiselect > ul.options .user-msg) {
742
+ /* block needed so vertical padding applies to span */
743
+ display: block;
726
744
  padding: 3pt 2ex;
727
745
  }
728
746
  :where(div.multiselect > ul.options > li.selected) {
@@ -741,10 +759,7 @@ function highlight_matching_options(event) {
741
759
  :where(span.max-select-msg) {
742
760
  padding: 0 3pt;
743
761
  }
744
- ::highlight(search-results) {
745
- color: var(--sms-highlight-color, orange);
746
- background: var(--sms-highlight-bg);
747
- text-decoration: var(--sms-highlight-text-decoration);
748
- text-decoration-color: var(--sms-highlight-text-decoration-color);
762
+ ::highlight(sms-search-matches) {
763
+ color: mediumaquamarine;
749
764
  }
750
765
  </style>
@@ -1,10 +1,10 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
- import type { MultiSelectEvents, Option as GenericOption } from './';
3
- declare class __sveltets_Render<Option extends GenericOption> {
2
+ import type { MultiSelectEvents, Option as T } from './types';
3
+ declare class __sveltets_Render<Option extends T> {
4
4
  props(): {
5
5
  activeIndex?: number | null | undefined;
6
6
  activeOption?: Option | null | undefined;
7
- createOptionMsg?: string | undefined;
7
+ createOptionMsg?: string | null | undefined;
8
8
  allowUserOptions?: boolean | "append" | undefined;
9
9
  allowEmpty?: boolean | undefined;
10
10
  autocomplete?: string | undefined;
@@ -13,7 +13,7 @@ declare class __sveltets_Render<Option extends GenericOption> {
13
13
  defaultDisabledTitle?: string | undefined;
14
14
  disabled?: boolean | undefined;
15
15
  disabledInputTitle?: string | undefined;
16
- duplicateFunc?: ((op1: GenericOption, op2: GenericOption) => boolean) | undefined;
16
+ duplicateFunc?: ((op1: T, op2: T) => boolean) | undefined;
17
17
  duplicateOptionMsg?: string | undefined;
18
18
  duplicates?: boolean | undefined;
19
19
  filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
@@ -54,6 +54,7 @@ declare class __sveltets_Render<Option extends GenericOption> {
54
54
  ulOptionsClass?: string | undefined;
55
55
  ulSelectedClass?: string | undefined;
56
56
  value?: Option | Option[] | null | undefined;
57
+ get_label?: ((op: T) => string | number) | undefined;
57
58
  };
58
59
  events(): MultiSelectEvents;
59
60
  slots(): {
@@ -73,9 +74,10 @@ declare class __sveltets_Render<Option extends GenericOption> {
73
74
  };
74
75
  };
75
76
  }
76
- export type MultiSelectProps<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['props']>;
77
- export type MultiSelectEvents<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['events']>;
78
- export type MultiSelectSlots<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['slots']>;
79
- export default class MultiSelect<Option extends GenericOption> extends SvelteComponentTyped<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
77
+ export type MultiSelectProps<Option extends T> = ReturnType<__sveltets_Render<Option>['props']>;
78
+ export type MultiSelectEvents<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
79
+ export type MultiSelectSlots<Option extends T> = ReturnType<__sveltets_Render<Option>['slots']>;
80
+ export default class MultiSelect<Option extends T> extends SvelteComponentTyped<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
81
+ get get_label(): (op: T) => string | number;
80
82
  }
81
83
  export {};
package/dist/index.d.ts CHANGED
@@ -1,56 +1,6 @@
1
1
  export { default as CircleSpinner } from './CircleSpinner.svelte';
2
2
  export { default as CmdPalette } from './CmdPalette.svelte';
3
- export { default, default as MultiSelect } from './MultiSelect.svelte';
3
+ export { default as MultiSelect, default } from './MultiSelect.svelte';
4
4
  export { default as Wiggle } from './Wiggle.svelte';
5
- export type Option = string | number | ObjectOption;
6
- export type ObjectOption = {
7
- label: string | number;
8
- value?: unknown;
9
- title?: string;
10
- disabled?: boolean;
11
- preselected?: boolean;
12
- disabledTitle?: string;
13
- selectedTitle?: string;
14
- [key: string]: unknown;
15
- };
16
- export type DispatchEvents<T = Option> = {
17
- add: {
18
- option: T;
19
- };
20
- create: {
21
- option: T;
22
- };
23
- remove: {
24
- option: T;
25
- };
26
- removeAll: {
27
- options: T[];
28
- };
29
- change: {
30
- option?: T;
31
- options?: T[];
32
- type: 'add' | 'remove' | 'removeAll';
33
- };
34
- open: {
35
- event: Event;
36
- };
37
- close: {
38
- event: Event;
39
- };
40
- };
41
- export type MultiSelectEvents = {
42
- [key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
43
- } & {
44
- blur: FocusEvent;
45
- click: MouseEvent;
46
- focus: FocusEvent;
47
- keydown: KeyboardEvent;
48
- keyup: KeyboardEvent;
49
- mouseenter: MouseEvent;
50
- mouseleave: MouseEvent;
51
- touchcancel: TouchEvent;
52
- touchend: TouchEvent;
53
- touchmove: TouchEvent;
54
- touchstart: TouchEvent;
55
- };
5
+ export * from './types';
56
6
  export declare function scroll_into_view_if_needed_polyfill(centerIfNeeded?: boolean): IntersectionObserver;
package/dist/index.js CHANGED
@@ -1,26 +1,27 @@
1
1
  export { default as CircleSpinner } from './CircleSpinner.svelte';
2
2
  export { default as CmdPalette } from './CmdPalette.svelte';
3
- export { default, default as MultiSelect } from './MultiSelect.svelte';
3
+ export { default as MultiSelect, default } from './MultiSelect.svelte';
4
4
  export { default as Wiggle } from './Wiggle.svelte';
5
- // Firefox lacks support for scrollIntoViewIfNeeded, see
6
- // https://github.com/janosh/svelte-multiselect/issues/87
7
- // this polyfill was copied from
5
+ export * from './types';
6
+ // Firefox lacks support for scrollIntoViewIfNeeded (https://caniuse.com/scrollintoviewifneeded).
7
+ // See https://github.com/janosh/svelte-multiselect/issues/87
8
+ // Polyfill copied from
8
9
  // https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
9
10
  // exported for testing
10
11
  export function scroll_into_view_if_needed_polyfill(centerIfNeeded = true) {
11
- const el = this;
12
+ const elem = this;
12
13
  const observer = new IntersectionObserver(function ([entry]) {
13
14
  const ratio = entry.intersectionRatio;
14
15
  if (ratio < 1) {
15
16
  const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`;
16
- el.scrollIntoView({
17
+ elem.scrollIntoView({
17
18
  block: place,
18
19
  inline: place,
19
20
  });
20
21
  }
21
22
  this.disconnect();
22
23
  });
23
- observer.observe(this);
24
+ observer.observe(elem);
24
25
  return observer; // return for testing
25
26
  }
26
27
  if (typeof Element !== `undefined` &&
@@ -0,0 +1,51 @@
1
+ export type Option = string | number | ObjectOption;
2
+ export type ObjectOption = {
3
+ label: string | number;
4
+ value?: unknown;
5
+ title?: string;
6
+ disabled?: boolean;
7
+ preselected?: boolean;
8
+ disabledTitle?: string;
9
+ selectedTitle?: string;
10
+ [key: string]: unknown;
11
+ };
12
+ export type DispatchEvents<T = Option> = {
13
+ add: {
14
+ option: T;
15
+ };
16
+ create: {
17
+ option: T;
18
+ };
19
+ remove: {
20
+ option: T;
21
+ };
22
+ removeAll: {
23
+ options: T[];
24
+ };
25
+ change: {
26
+ option?: T;
27
+ options?: T[];
28
+ type: 'add' | 'remove' | 'removeAll';
29
+ };
30
+ open: {
31
+ event: Event;
32
+ };
33
+ close: {
34
+ event: Event;
35
+ };
36
+ };
37
+ export type MultiSelectEvents = {
38
+ [key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
39
+ } & {
40
+ blur: FocusEvent;
41
+ click: MouseEvent;
42
+ focus: FocusEvent;
43
+ keydown: KeyboardEvent;
44
+ keyup: KeyboardEvent;
45
+ mouseenter: MouseEvent;
46
+ mouseleave: MouseEvent;
47
+ touchcancel: TouchEvent;
48
+ touchend: TouchEvent;
49
+ touchmove: TouchEvent;
50
+ touchstart: TouchEvent;
51
+ };
package/dist/types.js ADDED
@@ -0,0 +1 @@
1
+ export {};
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": "8.6.0",
8
+ "version": "8.6.2",
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": "^3.57.0"
26
+ "svelte": "^3.59.1"
27
27
  },
28
28
  "devDependencies": {
29
- "@iconify/svelte": "^3.1.0",
30
- "@playwright/test": "^1.31.2",
31
- "@sveltejs/adapter-static": "^2.0.1",
32
- "@sveltejs/kit": "^1.12.0",
29
+ "@iconify/svelte": "^3.1.3",
30
+ "@playwright/test": "^1.33.0",
31
+ "@sveltejs/adapter-static": "^2.0.2",
32
+ "@sveltejs/kit": "^1.16.3",
33
33
  "@sveltejs/package": "2.0.2",
34
- "@sveltejs/vite-plugin-svelte": "^2.0.3",
35
- "@typescript-eslint/eslint-plugin": "^5.55.0",
36
- "@typescript-eslint/parser": "^5.55.0",
37
- "@vitest/coverage-c8": "^0.29.3",
38
- "eslint": "^8.36.0",
34
+ "@sveltejs/vite-plugin-svelte": "^2.2.0",
35
+ "@typescript-eslint/eslint-plugin": "^5.59.5",
36
+ "@typescript-eslint/parser": "^5.59.5",
37
+ "@vitest/coverage-c8": "^0.31.0",
38
+ "eslint": "^8.40.0",
39
39
  "eslint-plugin-svelte3": "^4.0.0",
40
40
  "hastscript": "^7.2.0",
41
- "highlight.js": "^11.7.0",
42
- "jsdom": "^21.1.1",
41
+ "highlight.js": "^11.8.0",
42
+ "jsdom": "^22.0.0",
43
43
  "mdsvex": "^0.10.6",
44
44
  "mdsvexamples": "^0.3.3",
45
- "prettier": "^2.8.4",
46
- "prettier-plugin-svelte": "^2.9.0",
45
+ "prettier": "^2.8.8",
46
+ "prettier-plugin-svelte": "^2.10.0",
47
47
  "rehype-autolink-headings": "^6.1.1",
48
48
  "rehype-slug": "^5.1.0",
49
- "svelte-check": "^3.1.4",
49
+ "svelte-check": "^3.3.2",
50
50
  "svelte-preprocess": "^5.0.3",
51
- "svelte-toc": "^0.5.4",
52
- "svelte-zoo": "^0.4.3",
53
- "svelte2tsx": "^0.6.10",
54
- "typescript": "5.0.2",
55
- "vite": "^4.2.0",
56
- "vitest": "^0.29.3"
51
+ "svelte-toc": "^0.5.5",
52
+ "svelte-zoo": "^0.4.5",
53
+ "svelte2tsx": "^0.6.14",
54
+ "typescript": "5.0.4",
55
+ "vite": "^4.3.6",
56
+ "vitest": "^0.31.0"
57
57
  },
58
58
  "keywords": [
59
59
  "svelte",
package/readme.md CHANGED
@@ -51,7 +51,7 @@
51
51
 
52
52
  ```sh
53
53
  npm install --dev svelte-multiselect
54
- pnpm add --dev svelte-multiselect
54
+ pnpm add -D svelte-multiselect
55
55
  yarn add --dev svelte-multiselect
56
56
  ```
57
57
 
@@ -90,10 +90,10 @@ Full list of props/bindable variables for this component. The `Option` type you
90
90
  Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
91
91
 
92
92
  1. ```ts
93
- createOptionMsg: string = `Create this option...`
93
+ createOptionMsg: string | null = `Create this option...`
94
94
  ```
95
95
 
96
- Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy.
96
+ The message shown to users when `allowUserOptions` is truthy and they entered text that doesn't match any existing options to suggest creating a new option from the entered text. Emits `console.error` if `allowUserOptions` is `true` or `'append'` and `createOptionMsg=''` to since users might be unaware they can create new option. The error can be silenced by setting `createOptionMsg=null` indicating developer intent is to e.g. use MultiSelect as a tagging component where a user message might be unwanted.
97
97
 
98
98
  1. ```ts
99
99
  allowEmpty: boolean = false
@@ -146,7 +146,7 @@ Full list of props/bindable variables for this component. The `Option` type you
146
146
 
147
147
  <!-- prettier-ignore -->
148
148
  1. ```ts
149
- duplicateFunc: (op1: GenericOption, op2: GenericOption) => boolean = (op1, op2) =>
149
+ duplicateFunc: (op1: T, op2: T) => boolean = (op1, op2) =>
150
150
  `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
151
151
  ```
152
152
 
@@ -189,7 +189,7 @@ Full list of props/bindable variables for this component. The `Option` type you
189
189
  highlightMatches: boolean = true
190
190
  ```
191
191
 
192
- Whether to highlight text in the dropdown options that matches the current user-entered search query. Uses the [CSS Custom Highlight API](https://developer.mozilla.org/docs/Web/API/CSS_Custom_Highlight_API) with limited browser support and [styling options](https://developer.mozilla.org/docs/Web/CSS/::highlight). See `::highlight(search-results)` below for available CSS variables.
192
+ Whether to highlight text in the dropdown options that matches the current user-entered search query. Uses the [CSS Custom Highlight API](https://developer.mozilla.org/docs/Web/API/CSS_Custom_Highlight_API) with limited browser support and [styling options](https://developer.mozilla.org/docs/Web/CSS/::highlight). See `::highlight(sms-search-matches)` below for available CSS variables.
193
193
 
194
194
  1. ```ts
195
195
  id: string | null = null
@@ -526,7 +526,7 @@ Minimal example that changes the background color of the options dropdown:
526
526
  - `div.multiselect.open`
527
527
  - `z-index: var(--sms-open-z-index, 4)`: Increase this if needed to ensure the dropdown list is displayed atop all other page elements.
528
528
  - `div.multiselect:focus-within`
529
- - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` if not set which defaults to `cornflowerblue`.
529
+ - `border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue))`: Border when component has focus. Defaults to `--sms-active-color` which in turn defaults to `cornflowerblue`.
530
530
  - `div.multiselect.disabled`
531
531
  - `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
532
532
  - `div.multiselect input::placeholder`
@@ -559,11 +559,15 @@ Minimal example that changes the background color of the options dropdown:
559
559
  - `div.multiselect > ul.options > li.disabled`
560
560
  - `background: var(--sms-li-disabled-bg, #f5f5f6)`: Background of disabled options in the dropdown list.
561
561
  - `color: var(--sms-li-disabled-text, #b8b8b8)`: Text color of disabled option in the dropdown list.
562
- - `::highlight(search-results)`: applies to search results in dropdown list that match the current search query if `highlightMatches=true`
563
- - `color: var(--sms-highlight-color, orange)`
564
- - `background: var(--sms-highlight-bg)`
565
- - `text-decoration: var(--sms-highlight-text-decoration)`
566
- - `text-decoration-color: var(--sms-highlight-text-decoration-color)`
562
+ - `::highlight(sms-search-matches)`: applies to search results in dropdown list that match the current search query if `highlightMatches=true`. These styles [cannot be set via CSS variables](https://stackoverflow.com/a/56799215). Instead, use a new rule set. For example:
563
+
564
+ ```css
565
+ ::highlight(sms-search-matches) {
566
+ color: orange;
567
+ background: rgba(0, 0, 0, 0.15);
568
+ text-decoration: underline;
569
+ }
570
+ ```
567
571
 
568
572
  ### With CSS frameworks
569
573