svelte-multiselect 8.2.4 → 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
@@ -62,25 +62,30 @@ export let ulOptionsClass = ``;
62
62
  export let ulSelectedClass = ``;
63
63
  export let value = null;
64
64
  // get the label key from an option object or the option itself if it's a string or number
65
- 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
+ };
66
74
  // if maxSelect=1, value is the single item in selected (or null if selected is empty)
67
75
  // this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
68
76
  // https://github.com/janosh/svelte-multiselect/issues/136
69
77
  $: value = maxSelect === 1 ? selected[0] ?? null : selected;
70
78
  let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
71
79
  if (!(options?.length > 0)) {
72
- if (allowUserOptions || loading || disabled) {
80
+ if (allowUserOptions || loading || disabled || allowEmpty) {
73
81
  options = []; // initializing as array avoids errors when component mounts
74
82
  }
75
83
  else {
76
- // only error for empty options if user is not allowed to create custom
77
- // 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
78
86
  console.error(`MultiSelect received no options`);
79
87
  }
80
88
  }
81
- if (parseLabelsAsHtml && allowUserOptions) {
82
- console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
83
- }
84
89
  if (maxSelect !== null && maxSelect < 1) {
85
90
  console.error(`MultiSelect's maxSelect must be null or positive integer, got ${maxSelect}`);
86
91
  }
@@ -90,11 +95,14 @@ if (!Array.isArray(selected)) {
90
95
  if (maxSelect && typeof required === `number` && required > maxSelect) {
91
96
  console.error(`MultiSelect maxSelect=${maxSelect} < required=${required}, makes it impossible for users to submit a valid form`);
92
97
  }
98
+ if (parseLabelsAsHtml && allowUserOptions) {
99
+ console.warn(`Don't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
100
+ }
93
101
  if (sortSelected && selectedOptionsDraggable) {
94
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.`);
95
103
  }
96
104
  const dispatch = createEventDispatcher();
97
- 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>
98
106
  let window_width;
99
107
  // options matching the current search text
100
108
  $: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !selected.map(get_label).includes(get_label(op)) // remove already selected options from dropdown list
@@ -180,16 +188,17 @@ function add(label, event) {
180
188
  function remove(label) {
181
189
  if (selected.length === 0)
182
190
  return;
183
- selected.splice(selected.map(get_label).lastIndexOf(label), 1);
184
- selected = selected; // Svelte rerender after in-place splice
185
- const option = options.find((option) => get_label(option) === label) ??
191
+ let option = selected.find((op) => get_label(op) === label);
192
+ if (option === undefined && allowUserOptions) {
186
193
  // if option with label could not be found but allowUserOptions is truthy,
187
194
  // assume it was created by user and create corresponding option object
188
195
  // on the fly for use as event payload
189
- (allowUserOptions && { label, value: label });
190
- if (!option) {
191
- 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`);
192
200
  }
201
+ selected = selected.filter((op) => get_label(op) !== label); // remove option from selected list
193
202
  dispatch(`remove`, { option });
194
203
  dispatch(`change`, { option, type: `remove` });
195
204
  invalid = false; // reset error status whenever items are removed
@@ -298,7 +307,7 @@ const drop = (target_idx) => (event) => {
298
307
  return;
299
308
  event.dataTransfer.dropEffect = `move`;
300
309
  const start_idx = parseInt(event.dataTransfer.getData(`text/plain`));
301
- const new_selected = selected;
310
+ const new_selected = [...selected];
302
311
  if (start_idx < target_idx) {
303
312
  new_selected.splice(target_idx + 1, 0, new_selected[start_idx]);
304
313
  new_selected.splice(start_idx, 1);
@@ -393,6 +402,7 @@ const dragstart = (idx) => (event) => {
393
402
  on:keydown={if_enter_or_space(() => remove(get_label(option)))}
394
403
  type="button"
395
404
  title="{removeBtnTitle} {get_label(option)}"
405
+ class="remove"
396
406
  >
397
407
  <slot name="remove-icon">
398
408
  <CrossIcon width="15px" />
@@ -454,7 +464,7 @@ const dragstart = (idx) => (event) => {
454
464
  {#if maxSelect !== 1 && selected.length > 1}
455
465
  <button
456
466
  type="button"
457
- class="remove-all"
467
+ class="remove remove-all"
458
468
  title={removeAllTitle}
459
469
  on:mouseup|stopPropagation={remove_all}
460
470
  on:keydown={if_enter_or_space(remove_all)}
@@ -467,7 +477,7 @@ const dragstart = (idx) => (event) => {
467
477
  {/if}
468
478
 
469
479
  <!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
470
- {#if searchText || options?.length > 0}
480
+ {#if (searchText && noMatchingOptionsMsg) || options?.length > 0}
471
481
  <ul class:hidden={!open} class="options {ulOptionsClass}">
472
482
  {#each matchingOptions as option, idx}
473
483
  {@const {
@@ -513,7 +523,7 @@ const dragstart = (idx) => (event) => {
513
523
  <li
514
524
  on:mousedown|stopPropagation
515
525
  on:mouseup|stopPropagation={(event) => add(searchText, event)}
516
- title={addOptionMsg}
526
+ title={createOptionMsg}
517
527
  class:active={add_option_msg_is_active}
518
528
  on:mouseover={() => (add_option_msg_is_active = true)}
519
529
  on:focus={() => (add_option_msg_is_active = true)}
@@ -523,7 +533,7 @@ const dragstart = (idx) => (event) => {
523
533
  >
524
534
  {!duplicates && selected.some((option) => duplicateFunc(option, searchText))
525
535
  ? duplicateOptionMsg
526
- : addOptionMsg}
536
+ : createOptionMsg}
527
537
  </li>
528
538
  {:else}
529
539
  <span>{noMatchingOptionsMsg}</span>
@@ -4,8 +4,9 @@ declare class __sveltets_Render<Option extends GenericOption> {
4
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;
package/changelog.md CHANGED
@@ -2,6 +2,19 @@
2
2
 
3
3
  All notable changes to this project will be documented in this file. Dates are displayed in UTC.
4
4
 
5
+ <!-- auto-changelog-above -->
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
+
5
18
  #### [v8.2.4](https://github.com/janosh/svelte-multiselect/compare/v8.2.3...v8.2.4)
6
19
 
7
20
  > 8 January 2023
@@ -17,8 +30,6 @@ All notable changes to this project will be documented in this file. Dates are d
17
30
  - add vite alias $root to clean up package.json, readme|contributing|changelog.md imports [`c19cbe4`](https://github.com/janosh/svelte-multiselect/commit/c19cbe4e38413bbcd04d4e35eddcd4cd88c67662)
18
31
  - mv src/components src/site [`3683ed7`](https://github.com/janosh/svelte-multiselect/commit/3683ed70f19498070ffe9e95c0261c688fb2f7c7)
19
32
 
20
- <!-- auto-changelog-above -->
21
-
22
33
  #### [v8.2.3](https://github.com/janosh/svelte-multiselect/compare/v8.2.2...v8.2.3)
23
34
 
24
35
  > 28 December 2022
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;
package/index.js 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
  // Firefox lacks support for scrollIntoViewIfNeeded, see
3
5
  // https://github.com/janosh/svelte-multiselect/issues/87
4
6
  // this polyfill was copied from
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.4",
8
+ "version": "8.3.0",
9
9
  "type": "module",
10
10
  "svelte": "index.js",
11
11
  "bugs": "https://github.com/janosh/svelte-multiselect/issues",
12
12
  "devDependencies": {
13
13
  "@iconify/svelte": "^3.0.1",
14
14
  "@playwright/test": "^1.29.2",
15
- "@sveltejs/adapter-static": "1.0.1",
16
- "@sveltejs/kit": "1.0.7",
15
+ "@sveltejs/adapter-static": "^1.0.5",
16
+ "@sveltejs/kit": "^1.2.2",
17
17
  "@sveltejs/package": "1.0.2",
18
18
  "@sveltejs/vite-plugin-svelte": "^2.0.2",
19
- "@typescript-eslint/eslint-plugin": "^5.48.0",
20
- "@typescript-eslint/parser": "^5.48.0",
21
- "@vitest/coverage-c8": "^0.26.3",
22
- "eslint": "^8.31.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",
23
23
  "eslint-plugin-svelte3": "^4.0.0",
24
24
  "hastscript": "^7.2.0",
25
25
  "highlight.js": "^11.7.0",
26
26
  "jsdom": "^21.0.0",
27
27
  "mdsvex": "^0.10.6",
28
28
  "mdsvexamples": "^0.3.3",
29
- "prettier": "^2.8.2",
29
+ "prettier": "^2.8.3",
30
30
  "prettier-plugin-svelte": "^2.9.0",
31
31
  "rehype-autolink-headings": "^6.1.1",
32
32
  "rehype-slug": "^5.1.0",
33
- "svelte": "^3.55.0",
34
- "svelte-check": "^3.0.1",
35
- "svelte-github-corner": "^0.2.0",
36
- "svelte-preprocess": "^5.0.0",
37
- "svelte-toc": "^0.5.1",
38
- "svelte-zoo": "^0.1.4",
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",
39
38
  "svelte2tsx": "^0.6.0",
40
39
  "tslib": "^2.4.1",
41
40
  "typescript": "^4.9.4",
42
41
  "vite": "^4.0.4",
43
- "vitest": "^0.26.3"
42
+ "vitest": "^0.27.2"
44
43
  },
45
44
  "keywords": [
46
45
  "svelte",
package/readme.md CHANGED
@@ -42,11 +42,10 @@
42
42
 
43
43
  ## 📜 &thinsp; Breaking changes
44
44
 
45
- - **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).
46
- - **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).
47
45
  - **8.0.0**&nbsp;
48
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)
49
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).
50
49
 
51
50
  ## 🔨 &thinsp; Installation
52
51
 
@@ -91,11 +90,17 @@ Full list of props/bindable variables for this component. The `Option` type you
91
90
  Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys.
92
91
 
93
92
  1. ```ts
94
- addOptionMsg: string = `Create this option...`
93
+ createOptionMsg: string = `Create this option...`
95
94
  ```
96
95
 
97
96
  Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy.
98
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
+
99
104
  1. ```ts
100
105
  allowUserOptions: boolean | 'append' = false
101
106
  ```