svelte-multiselect 8.2.3 โ†’ 8.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.
@@ -1,12 +1,12 @@
1
1
  <script>import { createEventDispatcher, tick } from 'svelte';
2
2
  import { flip } from 'svelte/animate';
3
- import CircleSpinner from './CircleSpinner.svelte';
3
+ import { CircleSpinner, Wiggle } from '.';
4
4
  import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
5
- import Wiggle from './Wiggle.svelte';
6
5
  export let activeIndex = null;
7
6
  export let activeOption = null;
8
- export let addOptionMsg = `Create this option...`;
7
+ export let createOptionMsg = `Create this option...`;
9
8
  export let allowUserOptions = false;
9
+ export let allowEmpty = false; // added for https://github.com/janosh/svelte-multiselect/issues/192
10
10
  export let autocomplete = `off`;
11
11
  export let autoScroll = true;
12
12
  export let breakpoint = 800; // any screen with more horizontal pixels is considered desktop, below is mobile
@@ -14,6 +14,7 @@ export let defaultDisabledTitle = `This option is disabled`;
14
14
  export let disabled = false;
15
15
  export let disabledInputTitle = `This input is disabled`;
16
16
  // case-insensitive equality comparison after string coercion (looking only at the `label` key of object options)
17
+ // prettier-ignore
17
18
  export let duplicateFunc = (op1, op2) => `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase();
18
19
  export let duplicateOptionMsg = `This option is already selected`;
19
20
  export let duplicates = false; // whether to allow duplicate options
@@ -53,7 +54,7 @@ export let required = false;
53
54
  export let resetFilterOnAdd = true;
54
55
  export let searchText = ``;
55
56
  export let selected = options
56
- ?.filter((op) => op?.preselected)
57
+ ?.filter((op) => op instanceof Object && op?.preselected)
57
58
  .slice(0, maxSelect ?? undefined) ?? [];
58
59
  export let sortSelected = false;
59
60
  export let selectedOptionsDraggable = !sortSelected;
@@ -61,25 +62,30 @@ export let ulOptionsClass = ``;
61
62
  export let ulSelectedClass = ``;
62
63
  export let value = null;
63
64
  // get the label key from an option object or the option itself if it's a string or number
64
- const get_label = (op) => (op instanceof Object ? op.label : op);
65
+ const get_label = (op) => {
66
+ if (op instanceof Object) {
67
+ if (op.label === undefined) {
68
+ console.error(`MultiSelect option ${JSON.stringify(op)} is an object but has no label key`);
69
+ }
70
+ return op.label;
71
+ }
72
+ return op;
73
+ };
65
74
  // if maxSelect=1, value is the single item in selected (or null if selected is empty)
66
75
  // this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
67
76
  // https://github.com/janosh/svelte-multiselect/issues/136
68
77
  $: value = maxSelect === 1 ? selected[0] ?? null : selected;
69
78
  let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
70
79
  if (!(options?.length > 0)) {
71
- if (allowUserOptions || loading || disabled) {
80
+ if (allowUserOptions || loading || disabled || allowEmpty) {
72
81
  options = []; // initializing as array avoids errors when component mounts
73
82
  }
74
83
  else {
75
- // only error for empty options if user is not allowed to create custom
76
- // options and loading is false
84
+ // error on empty options if user is not allowed to create custom options and loading is false
85
+ // and component is not disabled and allowEmpty is false
77
86
  console.error(`MultiSelect received no options`);
78
87
  }
79
88
  }
80
- if (parseLabelsAsHtml && allowUserOptions) {
81
- console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
82
- }
83
89
  if (maxSelect !== null && maxSelect < 1) {
84
90
  console.error(`MultiSelect's maxSelect must be null or positive integer, got ${maxSelect}`);
85
91
  }
@@ -89,11 +95,14 @@ if (!Array.isArray(selected)) {
89
95
  if (maxSelect && typeof required === `number` && required > maxSelect) {
90
96
  console.error(`MultiSelect maxSelect=${maxSelect} < required=${required}, makes it impossible for users to submit a valid form`);
91
97
  }
98
+ if (parseLabelsAsHtml && allowUserOptions) {
99
+ console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
100
+ }
92
101
  if (sortSelected && selectedOptionsDraggable) {
93
102
  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.`);
94
103
  }
95
104
  const dispatch = createEventDispatcher();
96
- let add_option_msg_is_active = false; // controls active state of <li>{addOptionMsg}</li>
105
+ let add_option_msg_is_active = false; // controls active state of <li>{createOptionMsg}</li>
97
106
  let window_width;
98
107
  // options matching the current search text
99
108
  $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.map(get_label).includes(get_label(op)) // remove already selected options from dropdown list
@@ -123,7 +132,7 @@ function add(label, event) {
123
132
  // a new option from the user-entered text
124
133
  if (typeof options[0] === `object`) {
125
134
  // if 1st option is an object, we create new option as object to keep type homogeneity
126
- option = { label: searchText, value: searchText };
135
+ option = { label: searchText };
127
136
  }
128
137
  else {
129
138
  if ([`number`, `undefined`].includes(typeof options[0]) &&
@@ -147,7 +156,7 @@ function add(label, event) {
147
156
  return;
148
157
  }
149
158
  if (maxSelect === 1) {
150
- // for maxselect = 1 we always replace current option with new one
159
+ // for maxSelect = 1 we always replace current option with new one
151
160
  selected = [option];
152
161
  }
153
162
  else {
@@ -179,16 +188,17 @@ function add(label, event) {
179
188
  function remove(label) {
180
189
  if (selected.length === 0)
181
190
  return;
182
- selected.splice(selected.map(get_label).lastIndexOf(label), 1);
183
- selected = selected; // Svelte rerender after in-place splice
184
- const option = options.find((option) => get_label(option) === label) ??
191
+ let option = selected.find((op) => get_label(op) === label);
192
+ if (option === undefined && allowUserOptions) {
185
193
  // if option with label could not be found but allowUserOptions is truthy,
186
- // assume it was created by user and create correspondidng option object
194
+ // assume it was created by user and create corresponding option object
187
195
  // on the fly for use as event payload
188
- (allowUserOptions && { label, value: label });
189
- if (!option) {
190
- return console.error(`MultiSelect: option with label ${label} not found`);
196
+ option = (typeof options[0] == `object` ? { label } : label);
197
+ }
198
+ if (option === undefined) {
199
+ return console.error(`Multiselect can't remove selected option ${label}, not found in selected list`);
191
200
  }
201
+ selected = selected.filter((op) => get_label(op) !== label); // remove option from selected list
192
202
  dispatch(`remove`, { option });
193
203
  dispatch(`change`, { option, type: `remove` });
194
204
  invalid = false; // reset error status whenever items are removed
@@ -212,7 +222,7 @@ function close_dropdown(event) {
212
222
  }
213
223
  // handle all keyboard events this component receives
214
224
  async function handle_keydown(event) {
215
- // on escape or tab out of input: dismiss options dropdown and reset search text
225
+ // on escape or tab out of input: close options dropdown and reset search text
216
226
  if (event.key === `Escape` || event.key === `Tab`) {
217
227
  close_dropdown(event);
218
228
  searchText = ``;
@@ -297,7 +307,7 @@ const drop = (target_idx) => (event) => {
297
307
  return;
298
308
  event.dataTransfer.dropEffect = `move`;
299
309
  const start_idx = parseInt(event.dataTransfer.getData(`text/plain`));
300
- const new_selected = selected;
310
+ const new_selected = [...selected];
301
311
  if (start_idx < target_idx) {
302
312
  new_selected.splice(target_idx + 1, 0, new_selected[start_idx]);
303
313
  new_selected.splice(start_idx, 1);
@@ -375,9 +385,10 @@ const dragstart = (idx) => (event) => {
375
385
  on:dragstart={dragstart(idx)}
376
386
  on:drop|preventDefault={drop(idx)}
377
387
  on:dragenter={() => (drag_idx = idx)}
388
+ on:dragover|preventDefault
378
389
  class:active={drag_idx === idx}
379
- ondragover="return false"
380
390
  >
391
+ <!-- on:dragover|preventDefault needed for the drop to succeed https://stackoverflow.com/a/31085796 -->
381
392
  <slot name="selected" {option} {idx}>
382
393
  {#if parseLabelsAsHtml}
383
394
  {@html get_label(option)}
@@ -391,6 +402,7 @@ const dragstart = (idx) => (event) => {
391
402
  on:keydown={if_enter_or_space(() => remove(get_label(option)))}
392
403
  type="button"
393
404
  title="{removeBtnTitle} {get_label(option)}"
405
+ class="remove"
394
406
  >
395
407
  <slot name="remove-icon">
396
408
  <CrossIcon width="15px" />
@@ -452,7 +464,7 @@ const dragstart = (idx) => (event) => {
452
464
  {#if maxSelect !== 1 && selected.length > 1}
453
465
  <button
454
466
  type="button"
455
- class="remove-all"
467
+ class="remove remove-all"
456
468
  title={removeAllTitle}
457
469
  on:mouseup|stopPropagation={remove_all}
458
470
  on:keydown={if_enter_or_space(remove_all)}
@@ -465,7 +477,7 @@ const dragstart = (idx) => (event) => {
465
477
  {/if}
466
478
 
467
479
  <!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
468
- {#if searchText || options?.length > 0}
480
+ {#if (searchText && noMatchingOptionsMsg) || options?.length > 0}
469
481
  <ul class:hidden={!open} class="options {ulOptionsClass}">
470
482
  {#each matchingOptions as option, idx}
471
483
  {@const {
@@ -511,7 +523,7 @@ const dragstart = (idx) => (event) => {
511
523
  <li
512
524
  on:mousedown|stopPropagation
513
525
  on:mouseup|stopPropagation={(event) => add(searchText, event)}
514
- title={addOptionMsg}
526
+ title={createOptionMsg}
515
527
  class:active={add_option_msg_is_active}
516
528
  on:mouseover={() => (add_option_msg_is_active = true)}
517
529
  on:focus={() => (add_option_msg_is_active = true)}
@@ -521,7 +533,7 @@ const dragstart = (idx) => (event) => {
521
533
  >
522
534
  {!duplicates && selected.some((option) => duplicateFunc(option, searchText))
523
535
  ? duplicateOptionMsg
524
- : addOptionMsg}
536
+ : createOptionMsg}
525
537
  </li>
526
538
  {:else}
527
539
  <span>{noMatchingOptionsMsg}</span>
@@ -1,18 +1,19 @@
1
1
  import { SvelteComponentTyped } from "svelte";
2
- import type { MultiSelectEvents, Option } from './';
3
- declare const __propDef: {
4
- props: {
2
+ import type { MultiSelectEvents, Option as GenericOption } from './';
3
+ declare class __sveltets_Render<Option extends GenericOption> {
4
+ props(): {
5
5
  activeIndex?: number | null | undefined;
6
6
  activeOption?: Option | null | undefined;
7
- addOptionMsg?: string | undefined;
7
+ createOptionMsg?: string | undefined;
8
8
  allowUserOptions?: boolean | "append" | undefined;
9
+ allowEmpty?: boolean | undefined;
9
10
  autocomplete?: string | undefined;
10
11
  autoScroll?: boolean | undefined;
11
12
  breakpoint?: number | undefined;
12
13
  defaultDisabledTitle?: string | undefined;
13
14
  disabled?: boolean | undefined;
14
15
  disabledInputTitle?: string | undefined;
15
- duplicateFunc?: ((op1: Option, op2: Option) => boolean) | undefined;
16
+ duplicateFunc?: ((op1: GenericOption, op2: GenericOption) => boolean) | undefined;
16
17
  duplicateOptionMsg?: string | undefined;
17
18
  duplicates?: boolean | undefined;
18
19
  filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
@@ -53,7 +54,8 @@ declare const __propDef: {
53
54
  ulSelectedClass?: string | undefined;
54
55
  value?: Option | Option[] | null | undefined;
55
56
  };
56
- slots: {
57
+ events(): MultiSelectEvents;
58
+ slots(): {
57
59
  'expand-icon': {
58
60
  open: boolean;
59
61
  };
@@ -69,11 +71,10 @@ declare const __propDef: {
69
71
  idx: any;
70
72
  };
71
73
  };
72
- events: MultiSelectEvents;
73
- };
74
- export type MultiSelectProps = typeof __propDef.props;
75
- export type MultiSelectEvents = typeof __propDef.events;
76
- export type MultiSelectSlots = typeof __propDef.slots;
77
- export default class MultiSelect extends SvelteComponentTyped<MultiSelectProps, MultiSelectEvents, MultiSelectSlots> {
74
+ }
75
+ export type MultiSelectProps<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['props']>;
76
+ export type MultiSelectEvents<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['events']>;
77
+ export type MultiSelectSlots<Option extends GenericOption> = ReturnType<__sveltets_Render<Option>['slots']>;
78
+ export default class MultiSelect<Option extends GenericOption> extends SvelteComponentTyped<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
78
79
  }
79
80
  export {};
package/changelog.md CHANGED
@@ -4,6 +4,32 @@ All notable changes to this project will be documented in this file. Dates are d
4
4
 
5
5
  <!-- auto-changelog-above -->
6
6
 
7
+ #### [v8.3.0](https://github.com/janosh/svelte-multiselect/compare/v8.2.4...v8.3.0)
8
+
9
+ > 25 January 2023
10
+
11
+ - Don't error on removing options that are in `selected` but not in `options` array [`#204`](https://github.com/janosh/svelte-multiselect/pull/204)
12
+ - Add class 'remove' to buttons that remove selected options [`#202`](https://github.com/janosh/svelte-multiselect/pull/202)
13
+ - Add prop allowEmpty: boolean = false [`#198`](https://github.com/janosh/svelte-multiselect/pull/198)
14
+ - Support `immutable` Svelte compiler option [`#197`](https://github.com/janosh/svelte-multiselect/pull/197)
15
+ - group demo routes [`e813e48`](https://github.com/janosh/svelte-multiselect/commit/e813e480716f29ab4bdd53f90afe56485507fb1c)
16
+ - breaking: rename addOptionMsg to createOptionMsg [`f24e025`](https://github.com/janosh/svelte-multiselect/commit/f24e0256fcdc32c90ed798edbb663a6be18ebe00)
17
+
18
+ #### [v8.2.4](https://github.com/janosh/svelte-multiselect/compare/v8.2.3...v8.2.4)
19
+
20
+ > 8 January 2023
21
+
22
+ - Coverage badges [`#190`](https://github.com/janosh/svelte-multiselect/pull/190)
23
+ - feat: add type inference for the `options` prop [`#189`](https://github.com/janosh/svelte-multiselect/pull/189)
24
+ - feat: add type inference for the `options` prop (#189) [`#78`](https://github.com/janosh/svelte-multiselect/issues/78)
25
+ - merge ExampleCode.svelte with CollapsibleCode.svelte [`56ff99b`](https://github.com/janosh/svelte-multiselect/commit/56ff99bcc378c5582b303aa1c03302cdbceb3076)
26
+ - pnpm add -D svelte-zoo to outsource some site components and icons [`6ee64f3`](https://github.com/janosh/svelte-multiselect/commit/6ee64f376dfe166b993c94a36d376d1dce5f44f5)
27
+ - restore reactive searchText block in loading example [`846da66`](https://github.com/janosh/svelte-multiselect/commit/846da66af058ac1f448c8aaa513d12fb4c2ac4cc)
28
+ - fix bunch of TS errors, add playwright test for dragging selected options to reorder [`a483217`](https://github.com/janosh/svelte-multiselect/commit/a4832176f6fceb5346af2d4cd8ecc01a5626ab43)
29
+ - add update-coverage package.json script [`1094f08`](https://github.com/janosh/svelte-multiselect/commit/1094f08cec9d6fd2f54b058af05022ab35ec4ac9)
30
+ - add vite alias $root to clean up package.json, readme|contributing|changelog.md imports [`c19cbe4`](https://github.com/janosh/svelte-multiselect/commit/c19cbe4e38413bbcd04d4e35eddcd4cd88c67662)
31
+ - mv src/components src/site [`3683ed7`](https://github.com/janosh/svelte-multiselect/commit/3683ed70f19498070ffe9e95c0261c688fb2f7c7)
32
+
7
33
  #### [v8.2.3](https://github.com/janosh/svelte-multiselect/compare/v8.2.2...v8.2.3)
8
34
 
9
35
  > 28 December 2022
@@ -116,7 +142,7 @@ All notable changes to this project will be documented in this file. Dates are d
116
142
 
117
143
  - Fix single select with arrow and enter keys [`#128`](https://github.com/janosh/svelte-multiselect/pull/128)
118
144
  - Add SCSS preprocessing [`#126`](https://github.com/janosh/svelte-multiselect/pull/126)
119
- - [pre-commit.ci] pre-commit autoupdate [`#124`](https://github.com/janosh/svelte-multiselect/pull/124)
145
+ - pre-commit autoupdate [`#124`](https://github.com/janosh/svelte-multiselect/pull/124)
120
146
  - more unit tests [`1adbc99`](https://github.com/janosh/svelte-multiselect/commit/1adbc994b746b39c4ad081dc2573bf37f27c96c0)
121
147
  - test required but empty MultiSelect fails form validity check (i.e. causes unsubmittable form) and filled one passes it [`fd8b377`](https://github.com/janosh/svelte-multiselect/commit/fd8b37782cd508aacfc8125c6647cefe56144b80)
122
148
 
@@ -186,7 +212,7 @@ All notable changes to this project will be documented in this file. Dates are d
186
212
  - Convert E2E tests from`vitest` to `@playwright/test` [`#95`](https://github.com/janosh/svelte-multiselect/pull/95)
187
213
  - Allow empty Multiselect [`#94`](https://github.com/janosh/svelte-multiselect/pull/94)
188
214
  - Add new slot `'remove-icon'` [`#93`](https://github.com/janosh/svelte-multiselect/pull/93)
189
- - [pre-commit.ci] pre-commit autoupdate [`#92`](https://github.com/janosh/svelte-multiselect/pull/92)
215
+ - pre-commit autoupdate [`#92`](https://github.com/janosh/svelte-multiselect/pull/92)
190
216
 
191
217
  #### [v5.0.3](https://github.com/janosh/svelte-multiselect/compare/v5.0.2...v5.0.3)
192
218
 
@@ -224,7 +250,7 @@ All notable changes to this project will be documented in this file. Dates are d
224
250
 
225
251
  - Fix backspace deleting multiple selected options if identical labels [`#72`](https://github.com/janosh/svelte-multiselect/pull/72)
226
252
  - Several fixes for `allowUserOptions` [`#69`](https://github.com/janosh/svelte-multiselect/pull/69)
227
- - [pre-commit.ci] pre-commit autoupdate [`#70`](https://github.com/janosh/svelte-multiselect/pull/70)
253
+ - pre-commit autoupdate [`#70`](https://github.com/janosh/svelte-multiselect/pull/70)
228
254
 
229
255
  #### [v4.0.5](https://github.com/janosh/svelte-multiselect/compare/v4.0.4...v4.0.5)
230
256
 
@@ -347,7 +373,7 @@ All notable changes to this project will be documented in this file. Dates are d
347
373
  - favorite web framework show Confetti.svelte on:add Svelte [`8d109ee`](https://github.com/janosh/svelte-multiselect/commit/8d109ee5c7755e447fcb72419f3b7ecc19cac0b2)
348
374
  - bump svelte@3.45.0 to silence warning: MultiSelect has unused export property 'defaultDisabledTitle' (sveltejs/svelte#6964) [`f80a7a6`](https://github.com/janosh/svelte-multiselect/commit/f80a7a622310005407585298f2611597c0941990)
349
375
  - update readme + svelte-toc@0.2.0 [`40013ba`](https://github.com/janosh/svelte-multiselect/commit/40013badd61dd0fcade7ab295dabd26693e3cc51)
350
- - [pre-commit.ci] pre-commit autoupdate [`0d05864`](https://github.com/janosh/svelte-multiselect/commit/0d05864d19987460dd30d667eb22deb91a520668)
376
+ - pre-commit autoupdate [`0d05864`](https://github.com/janosh/svelte-multiselect/commit/0d05864d19987460dd30d667eb22deb91a520668)
351
377
  - iOS Safari prevent zoom into page on focus MultiSelect input [`44f17be`](https://github.com/janosh/svelte-multiselect/commit/44f17be53378e38f4a8748b815737d25cdebc85f)
352
378
 
353
379
  ### [v3.0.0](https://github.com/janosh/svelte-multiselect/compare/v2.0.0...v3.0.0)
@@ -398,7 +424,7 @@ All notable changes to this project will be documented in this file. Dates are d
398
424
 
399
425
  - Add new prop disabledOptions [`#9`](https://github.com/janosh/svelte-multiselect/pull/9)
400
426
  - add pre-commit hooks [`dfb6399`](https://github.com/janosh/svelte-multiselect/commit/dfb6399a77b705f8e5979eb887d345a5f52ff929)
401
- - [pre-commit.ci] pre-commit autoupdate [`b69425d`](https://github.com/janosh/svelte-multiselect/commit/b69425d18473122f1af889d2f48c60d02e43b99f)
427
+ - pre-commit autoupdate [`b69425d`](https://github.com/janosh/svelte-multiselect/commit/b69425d18473122f1af889d2f48c60d02e43b99f)
402
428
 
403
429
  #### [v1.1.11](https://github.com/janosh/svelte-multiselect/compare/v1.1.10...v1.1.11)
404
430
 
@@ -490,7 +516,7 @@ All notable changes to this project will be documented in this file. Dates are d
490
516
 
491
517
  - remove hidden input for storing currently selected options as JSON [`802a219`](https://github.com/janosh/svelte-multiselect/commit/802a2195a28986c219298d7d9e7ca47f2aaf7db6)
492
518
 
493
- #### v1.0.0
519
+ ### v1.0.0
494
520
 
495
521
  > 7 May 2021
496
522
 
@@ -2,7 +2,7 @@
2
2
  /** @typedef {typeof __propDef.events} ChevronExpandEvents */
3
3
  /** @typedef {typeof __propDef.slots} ChevronExpandSlots */
4
4
  export default class ChevronExpand extends SvelteComponentTyped<{
5
- [x: string]: never;
5
+ [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
8
8
  }, {}> {
@@ -13,7 +13,7 @@ export type ChevronExpandSlots = typeof __propDef.slots;
13
13
  import { SvelteComponentTyped } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
- [x: string]: never;
16
+ [x: string]: any;
17
17
  };
18
18
  events: {
19
19
  [evt: string]: CustomEvent<any>;
@@ -2,7 +2,7 @@
2
2
  /** @typedef {typeof __propDef.events} CrossEvents */
3
3
  /** @typedef {typeof __propDef.slots} CrossSlots */
4
4
  export default class Cross extends SvelteComponentTyped<{
5
- [x: string]: never;
5
+ [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
8
8
  }, {}> {
@@ -13,7 +13,7 @@ export type CrossSlots = typeof __propDef.slots;
13
13
  import { SvelteComponentTyped } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
- [x: string]: never;
16
+ [x: string]: any;
17
17
  };
18
18
  events: {
19
19
  [evt: string]: CustomEvent<any>;
@@ -2,7 +2,7 @@
2
2
  /** @typedef {typeof __propDef.events} DisabledEvents */
3
3
  /** @typedef {typeof __propDef.slots} DisabledSlots */
4
4
  export default class Disabled extends SvelteComponentTyped<{
5
- [x: string]: never;
5
+ [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
8
8
  }, {}> {
@@ -13,7 +13,7 @@ export type DisabledSlots = typeof __propDef.slots;
13
13
  import { SvelteComponentTyped } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
- [x: string]: never;
16
+ [x: string]: any;
17
17
  };
18
18
  events: {
19
19
  [evt: string]: CustomEvent<any>;
@@ -2,7 +2,7 @@
2
2
  /** @typedef {typeof __propDef.events} OctocatEvents */
3
3
  /** @typedef {typeof __propDef.slots} OctocatSlots */
4
4
  export default class Octocat extends SvelteComponentTyped<{
5
- [x: string]: never;
5
+ [x: string]: any;
6
6
  }, {
7
7
  [evt: string]: CustomEvent<any>;
8
8
  }, {}> {
@@ -13,7 +13,7 @@ export type OctocatSlots = typeof __propDef.slots;
13
13
  import { SvelteComponentTyped } from "svelte";
14
14
  declare const __propDef: {
15
15
  props: {
16
- [x: string]: never;
16
+ [x: string]: any;
17
17
  };
18
18
  events: {
19
19
  [evt: string]: CustomEvent<any>;
package/index.d.ts CHANGED
@@ -1,4 +1,6 @@
1
- export { default } from './MultiSelect.svelte';
1
+ export { default as CircleSpinner } from './CircleSpinner.svelte';
2
+ export { default, default as MultiSelect } from './MultiSelect.svelte';
3
+ export { default as Wiggle } from './Wiggle.svelte';
2
4
  export type Option = string | number | ObjectOption;
3
5
  export type ObjectOption = {
4
6
  label: string | number;
@@ -47,3 +49,4 @@ export type MultiSelectEvents = {
47
49
  touchmove: TouchEvent;
48
50
  touchstart: TouchEvent;
49
51
  };
52
+ export declare function scroll_into_view_if_needed_polyfill(centerIfNeeded?: boolean): IntersectionObserver;
package/index.js CHANGED
@@ -1,23 +1,29 @@
1
- export { default } from './MultiSelect.svelte';
1
+ export { default as CircleSpinner } from './CircleSpinner.svelte';
2
+ export { default, default as MultiSelect } from './MultiSelect.svelte';
3
+ export { default as Wiggle } from './Wiggle.svelte';
2
4
  // Firefox lacks support for scrollIntoViewIfNeeded, see
3
5
  // https://github.com/janosh/svelte-multiselect/issues/87
4
6
  // this polyfill was copied from
5
7
  // https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
8
+ // exported for testing
9
+ export function scroll_into_view_if_needed_polyfill(centerIfNeeded = true) {
10
+ const el = this;
11
+ const observer = new IntersectionObserver(function ([entry]) {
12
+ const ratio = entry.intersectionRatio;
13
+ if (ratio < 1) {
14
+ const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`;
15
+ el.scrollIntoView({
16
+ block: place,
17
+ inline: place,
18
+ });
19
+ }
20
+ this.disconnect();
21
+ });
22
+ observer.observe(this);
23
+ return observer; // return for testing
24
+ }
6
25
  if (typeof Element !== `undefined` &&
7
26
  !Element.prototype?.scrollIntoViewIfNeeded &&
8
27
  typeof IntersectionObserver !== `undefined`) {
9
- Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded = true) {
10
- const el = this;
11
- new IntersectionObserver(function ([entry]) {
12
- const ratio = entry.intersectionRatio;
13
- if (ratio < 1) {
14
- const place = ratio <= 0 && centerIfNeeded ? `center` : `nearest`;
15
- el.scrollIntoView({
16
- block: place,
17
- inline: place,
18
- });
19
- }
20
- this.disconnect();
21
- }).observe(this);
22
- };
28
+ Element.prototype.scrollIntoViewIfNeeded = scroll_into_view_if_needed_polyfill;
23
29
  }
package/package.json CHANGED
@@ -5,42 +5,41 @@
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.2.3",
8
+ "version": "8.3.0",
9
9
  "type": "module",
10
10
  "svelte": "index.js",
11
- "main": "index.js",
12
11
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
13
12
  "devDependencies": {
14
13
  "@iconify/svelte": "^3.0.1",
15
- "@playwright/test": "^1.29.0",
16
- "@sveltejs/adapter-static": "1.0.0",
17
- "@sveltejs/kit": "1.0.1",
18
- "@sveltejs/package": "1.0.1",
14
+ "@playwright/test": "^1.29.2",
15
+ "@sveltejs/adapter-static": "^1.0.5",
16
+ "@sveltejs/kit": "^1.2.2",
17
+ "@sveltejs/package": "1.0.2",
19
18
  "@sveltejs/vite-plugin-svelte": "^2.0.2",
20
- "@typescript-eslint/eslint-plugin": "^5.46.1",
21
- "@typescript-eslint/parser": "^5.46.1",
22
- "@vitest/coverage-c8": "^0.25.8",
23
- "eslint": "^8.30.0",
19
+ "@typescript-eslint/eslint-plugin": "^5.48.2",
20
+ "@typescript-eslint/parser": "^5.48.2",
21
+ "@vitest/coverage-c8": "^0.27.2",
22
+ "eslint": "^8.32.0",
24
23
  "eslint-plugin-svelte3": "^4.0.0",
25
- "hastscript": "^7.1.0",
24
+ "hastscript": "^7.2.0",
26
25
  "highlight.js": "^11.7.0",
27
- "jsdom": "^20.0.3",
26
+ "jsdom": "^21.0.0",
28
27
  "mdsvex": "^0.10.6",
29
28
  "mdsvexamples": "^0.3.3",
30
- "prettier": "^2.8.1",
29
+ "prettier": "^2.8.3",
31
30
  "prettier-plugin-svelte": "^2.9.0",
32
31
  "rehype-autolink-headings": "^6.1.1",
33
32
  "rehype-slug": "^5.1.0",
34
- "svelte": "^3.55.0",
35
- "svelte-check": "^2.10.2",
36
- "svelte-github-corner": "^0.1.0",
37
- "svelte-preprocess": "^5.0.0",
38
- "svelte-toc": "^0.5.0",
39
- "svelte2tsx": "^0.5.22",
33
+ "svelte": "^3.55.1",
34
+ "svelte-check": "^3.0.2",
35
+ "svelte-preprocess": "^5.0.1",
36
+ "svelte-toc": "^0.5.2",
37
+ "svelte-zoo": "^0.2.3",
38
+ "svelte2tsx": "^0.6.0",
40
39
  "tslib": "^2.4.1",
41
40
  "typescript": "^4.9.4",
42
- "vite": "^4.0.1",
43
- "vitest": "^0.25.8"
41
+ "vite": "^4.0.4",
42
+ "vitest": "^0.27.2"
44
43
  },
45
44
  "keywords": [
46
45
  "svelte",
package/readme.md CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  <slot name="examples" />
23
23
 
24
- ## ๐Ÿ’ก &nbsp; Features
24
+ ## ๐Ÿ’ก &thinsp; Features
25
25
 
26
26
  - **Bindable:** `bind:selected` gives you an array of the currently selected options. Thanks to Svelte's 2-way binding, it can also control the component state externally through assignment `selected = ['foo', 42]`.
27
27
  - **Keyboard friendly** for mouse-less form completion
@@ -34,23 +34,28 @@
34
34
 
35
35
  <slot name="nav" />
36
36
 
37
- ## ๐Ÿ“œ &nbsp; Breaking changes
37
+ ## ๐Ÿงช &thinsp; Coverage
38
+
39
+ | Statements | Branches | Lines |
40
+ | ------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------ | -------------------------------------------------------------------------------- |
41
+ | ![Statements](https://img.shields.io/badge/statements-92.95%25-brightgreen.svg?style=flat) | ![Branches](https://img.shields.io/badge/branches-100%25-brightgreen.svg?style=flat) | ![Lines](https://img.shields.io/badge/lines-92.95%25-brightgreen.svg?style=flat) |
42
+
43
+ ## ๐Ÿ“œ &thinsp; Breaking changes
38
44
 
39
- - **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. [PR 120](https://github.com/janosh/svelte-multiselect/pull/120).
40
- - **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. [PR 123](https://github.com/janosh/svelte-multiselect/pull/123).
41
45
  - **8.0.0**&nbsp;
42
46
  - 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. [PR 138](https://github.com/janosh/svelte-multiselect/pull/138)
43
47
  - Prop `noOptionsMsg` was renamed to `noMatchingOptionsMsg`. [PR 133](https://github.com/janosh/svelte-multiselect/pull/133).
48
+ - **v8.3.0**&nbsp; `addOptionMsg` was renamed to `createOptionMsg` (no major since version since it's rarely used) [sha](https://github.com/janosh/svelte-multiselect/commits).
44
49
 
45
- ## ๐Ÿ”จ &nbsp; Installation
50
+ ## ๐Ÿ”จ &thinsp; Installation
46
51
 
47
52
  ```sh
48
- npm install -D svelte-multiselect
49
- pnpm add -D svelte-multiselect
50
- yarn add -D svelte-multiselect
53
+ npm install --dev svelte-multiselect
54
+ pnpm add --dev svelte-multiselect
55
+ yarn add --dev svelte-multiselect
51
56
  ```
52
57
 
53
- ## ๐Ÿ“™ &nbsp; Usage
58
+ ## ๐Ÿ“™ &thinsp; Usage
54
59
 
55
60
  ```svelte
56
61
  <script>
@@ -68,7 +73,7 @@ Favorite Frontend Tools?
68
73
  <MultiSelect bind:selected options={ui_libs} />
69
74
  ```
70
75
 
71
- ## ๐Ÿ”ฃ &nbsp; Props
76
+ ## ๐Ÿ”ฃ &thinsp; Props
72
77
 
73
78
  Full list of props/bindable variables for this component. The `Option` type you see below is defined in [`src/lib/index.ts`](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/index.ts) and can be imported as `import { type Option } from 'svelte-multiselect'`.
74
79
 
@@ -85,11 +90,17 @@ Full list of props/bindable variables for this component. The `Option` type you
85
90
  Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
86
91
 
87
92
  1. ```ts
88
- addOptionMsg: string = `Create this option...`
93
+ createOptionMsg: string = `Create this option...`
89
94
  ```
90
95
 
91
96
  Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy.
92
97
 
98
+ 1. ```ts
99
+ allowEmpty: boolean = false
100
+ ```
101
+
102
+ Whether to `console.error` if dropdown list of options is empty. `allowEmpty={false}` will suppress errors. `allowEmpty={true}` will report a console error if component is not `disabled`, not in `loading` state and doesn't `allowUserOptions`.
103
+
93
104
  1. ```ts
94
105
  allowUserOptions: boolean | 'append' = false
95
106
  ```
@@ -135,7 +146,7 @@ Full list of props/bindable variables for this component. The `Option` type you
135
146
 
136
147
  <!-- prettier-ignore -->
137
148
  1. ```ts
138
- duplicateFunc: (op1: Option, op2: Option) => boolean = (op1, op2) =>
149
+ duplicateFunc: (op1: GenericOption, op2: GenericOption) => boolean = (op1, op2) =>
139
150
  `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
140
151
  ```
141
152
 
@@ -344,7 +355,7 @@ Full list of props/bindable variables for this component. The `Option` type you
344
355
 
345
356
  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!
346
357
 
347
- ## ๐ŸŽฐ &nbsp; Slots
358
+ ## ๐ŸŽฐ &thinsp; Slots
348
359
 
349
360
  `MultiSelect.svelte` accepts the following named slots:
350
361
 
@@ -376,7 +387,7 @@ Example:
376
387
  </MultiSelect>
377
388
  ```
378
389
 
379
- ## ๐ŸŽฌ &nbsp; Events
390
+ ## ๐ŸŽฌ &thinsp; Events
380
391
 
381
392
  `MultiSelect.svelte` dispatches the following events:
382
393
 
@@ -439,25 +450,36 @@ The above list of events are [Svelte `dispatch` events](https://svelte.dev/tutor
439
450
  />
440
451
  ```
441
452
 
442
- ## ๐Ÿฆบ &nbsp; TypeScript
453
+ ## ๐Ÿฆบ &thinsp; TypeScript
443
454
 
444
- TypeScript users can import the types used for internal type safety:
455
+ The type of `options` is inferred automatically from the data you pass. E.g.
445
456
 
446
- ```svelte
447
- <script lang="ts">
448
- import MultiSelect from 'svelte-multiselect'
449
- import type { Option, ObjectOption } from 'svelte-multiselect'
450
-
451
- const myOptions: ObjectOption[] = [
452
- { label: 'foo', value: 42 },
453
- { label: 'bar', value: 69 },
454
- ]
455
- // an Option can be string | number | ObjectOption
456
- const myNumbers: Option[] = [42, 69]
457
- </script>
457
+ ```ts
458
+ const options = [
459
+ { label: `foo`, value: 42 }
460
+ { label: `bar`, value: 69 }
461
+ ]
462
+ // type Option = { label: string, value: number }
463
+ const options = [`foo`, `bar`]
464
+ // type Option = string
465
+ const options = [42, 69]
466
+ // type Option = number
467
+ ```
468
+
469
+ The inferred type of `Option` is used to enforce type-safety on derived props like `selected` as well as slot components. E.g. you'll get an error when trying to use a slot component that expects a string if your options are objects (see [this comment](https://github.com/janosh/svelte-multiselect/pull/189/files#r1058853697) for example screenshots).
470
+
471
+ You can also import [the types this component uses](https://github.com/janosh/svelte-multiselect/blob/main/src/lib/index.ts) for downstream applications:
472
+
473
+ ```ts
474
+ import {
475
+ Option,
476
+ ObjectOption,
477
+ DispatchEvents,
478
+ MultiSelectEvents,
479
+ } from 'svelte-multiselect'
458
480
  ```
459
481
 
460
- ## โœจ &nbsp; Styling
482
+ ## โœจ &thinsp; Styling
461
483
 
462
484
  There are 3 ways to style this component. To understand which options do what, it helps to keep in mind this simplified DOM structure of the component:
463
485
 
@@ -608,10 +630,10 @@ Odd as it may seem, you get the most fine-grained control over the styling of ev
608
630
  }
609
631
  ```
610
632
 
611
- ## ๐Ÿ†• &nbsp; Changelog
633
+ ## ๐Ÿ†• &thinsp; Changelog
612
634
 
613
635
  [View the changelog](changelog.md).
614
636
 
615
- ## ๐Ÿ™ &nbsp; Contributing
637
+ ## ๐Ÿ™ &thinsp; Contributing
616
638
 
617
639
  Here are some steps to [get you started](contributing.md) if you'd like to contribute to this project!