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.
- package/MultiSelect.svelte +32 -22
- package/MultiSelect.svelte.d.ts +2 -1
- package/changelog.md +13 -2
- package/index.d.ts +3 -1
- package/index.js +3 -1
- package/package.json +14 -15
- package/readme.md +8 -3
package/MultiSelect.svelte
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
<script>import { createEventDispatcher, tick } from 'svelte';
|
|
2
2
|
import { flip } from 'svelte/animate';
|
|
3
|
-
import CircleSpinner from '
|
|
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
|
|
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) =>
|
|
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
|
-
//
|
|
77
|
-
//
|
|
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>{
|
|
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.
|
|
184
|
-
|
|
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
|
-
(
|
|
190
|
-
|
|
191
|
-
|
|
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={
|
|
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
|
-
:
|
|
536
|
+
: createOptionMsg}
|
|
527
537
|
</li>
|
|
528
538
|
{:else}
|
|
529
539
|
<span>{noMatchingOptionsMsg}</span>
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -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
|
-
|
|
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 './
|
|
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 './
|
|
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.
|
|
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.
|
|
16
|
-
"@sveltejs/kit": "1.
|
|
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.
|
|
20
|
-
"@typescript-eslint/parser": "^5.48.
|
|
21
|
-
"@vitest/coverage-c8": "^0.
|
|
22
|
-
"eslint": "^8.
|
|
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.
|
|
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.
|
|
34
|
-
"svelte-check": "^3.0.
|
|
35
|
-
"svelte-
|
|
36
|
-
"svelte-
|
|
37
|
-
"svelte-
|
|
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.
|
|
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
|
## 📜   Breaking changes
|
|
44
44
|
|
|
45
|
-
- **6.1.0** 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** `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**
|
|
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** `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
|
## 🔨   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
|
-
|
|
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
|
```
|