svelte-multiselect 7.0.1 → 7.1.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 +31 -10
- package/MultiSelect.svelte.d.ts +3 -0
- package/icons/Cross.svelte +3 -2
- package/index.d.ts +0 -2
- package/index.js +0 -4
- package/package.json +1 -1
- package/readme.md +24 -3
package/MultiSelect.svelte
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
<script>import { createEventDispatcher, tick } from 'svelte';
|
|
2
|
-
import { get_label, get_value } from './';
|
|
3
2
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
3
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
5
4
|
import Wiggle from './Wiggle.svelte';
|
|
@@ -13,6 +12,10 @@ export let breakpoint = 800; // any screen with more horizontal pixels is consid
|
|
|
13
12
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
14
13
|
export let disabled = false;
|
|
15
14
|
export let disabledInputTitle = `This input is disabled`;
|
|
15
|
+
// case-insensitive equality comparison after string coercion (looking only at the `label` key of object options)
|
|
16
|
+
export let duplicateFunc = (op1, op2) => `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase();
|
|
17
|
+
export let duplicateOptionMsg = `This option is already selected`;
|
|
18
|
+
export let duplicates = false; // whether to allow duplicate options
|
|
16
19
|
export let filterFunc = (op, searchText) => {
|
|
17
20
|
if (!searchText)
|
|
18
21
|
return true;
|
|
@@ -52,6 +55,10 @@ export let selectedValues = [];
|
|
|
52
55
|
export let sortSelected = false;
|
|
53
56
|
export let ulOptionsClass = ``;
|
|
54
57
|
export let ulSelectedClass = ``;
|
|
58
|
+
// get the label key from an option object or the option itself if it's a string or number
|
|
59
|
+
const get_label = (op) => (op instanceof Object ? op.label : op);
|
|
60
|
+
// fallback on label if option is object and value is undefined
|
|
61
|
+
const get_value = (op) => (op instanceof Object ? op.value ?? op.label : op);
|
|
55
62
|
// selected and _selected are identical except if maxSelect=1, selected will be the single item (or null)
|
|
56
63
|
// in _selected which will always be an array for easier component internals. selected then solves
|
|
57
64
|
// https://github.com/janosh/svelte-multiselect/issues/86
|
|
@@ -101,8 +108,11 @@ $: activeOption = activeIndex !== null ? matchingOptions[activeIndex] : null;
|
|
|
101
108
|
function add(label, event) {
|
|
102
109
|
if (maxSelect && maxSelect > 1 && _selected.length >= maxSelect)
|
|
103
110
|
wiggle = true;
|
|
104
|
-
|
|
105
|
-
|
|
111
|
+
if (!isNaN(Number(label)) && typeof _selectedLabels[0] === `number`)
|
|
112
|
+
label = Number(label); // convert to number if possible
|
|
113
|
+
const is_duplicate = _selected.some((option) => duplicateFunc(option, label));
|
|
114
|
+
if ((maxSelect === null || maxSelect === 1 || _selected.length < maxSelect) &&
|
|
115
|
+
(duplicates || !is_duplicate)) {
|
|
106
116
|
// first check if we find option in the options list
|
|
107
117
|
let option = options.find((op) => get_label(op) === label);
|
|
108
118
|
if (!option && // this has the side-effect of not allowing to user to add the same
|
|
@@ -127,9 +137,12 @@ function add(label, event) {
|
|
|
127
137
|
if (allowUserOptions === `append`)
|
|
128
138
|
options = [...options, option];
|
|
129
139
|
}
|
|
140
|
+
if (option === undefined) {
|
|
141
|
+
throw `Run time error, option with label ${label} not found in options list`;
|
|
142
|
+
}
|
|
130
143
|
searchText = ``; // reset search string on selection
|
|
131
|
-
if (
|
|
132
|
-
console.error(`MultiSelect: option with label ${label}
|
|
144
|
+
if ([``, undefined, null].includes(option)) {
|
|
145
|
+
console.error(`MultiSelect: encountered missing option with label ${label} (or option is poorly labeled)`);
|
|
133
146
|
return;
|
|
134
147
|
}
|
|
135
148
|
if (maxSelect === 1) {
|
|
@@ -322,7 +335,9 @@ function on_click_outside(event) {
|
|
|
322
335
|
type="button"
|
|
323
336
|
title="{removeBtnTitle} {get_label(option)}"
|
|
324
337
|
>
|
|
325
|
-
<slot name="remove-icon"
|
|
338
|
+
<slot name="remove-icon">
|
|
339
|
+
<CrossIcon width="15px" />
|
|
340
|
+
</slot>
|
|
326
341
|
</button>
|
|
327
342
|
{/if}
|
|
328
343
|
</li>
|
|
@@ -386,7 +401,9 @@ function on_click_outside(event) {
|
|
|
386
401
|
on:mouseup|stopPropagation={remove_all}
|
|
387
402
|
on:keydown={if_enter_or_space(remove_all)}
|
|
388
403
|
>
|
|
389
|
-
<slot name="remove-icon"
|
|
404
|
+
<slot name="remove-icon">
|
|
405
|
+
<CrossIcon width="15px" />
|
|
406
|
+
</slot>
|
|
390
407
|
</button>
|
|
391
408
|
{/if}
|
|
392
409
|
{/if}
|
|
@@ -446,7 +463,9 @@ function on_click_outside(event) {
|
|
|
446
463
|
on:blur={() => (add_option_msg_is_active = false)}
|
|
447
464
|
aria-selected="false"
|
|
448
465
|
>
|
|
449
|
-
{
|
|
466
|
+
{!duplicates && _selected.some((option) => duplicateFunc(option, searchText))
|
|
467
|
+
? duplicateOptionMsg
|
|
468
|
+
: addOptionMsg}
|
|
450
469
|
</li>
|
|
451
470
|
{:else}
|
|
452
471
|
<span>{noOptionsMsg}</span>
|
|
@@ -520,7 +539,8 @@ function on_click_outside(event) {
|
|
|
520
539
|
:where(div.multiselect) ul.selected > li button:hover,
|
|
521
540
|
:where(div.multiselect) button.remove-all:hover,
|
|
522
541
|
:where(div.multiselect) button:focus {
|
|
523
|
-
color: var(--sms-
|
|
542
|
+
color: var(--sms-remove-btn-hover-color, lightskyblue);
|
|
543
|
+
background: var(--sms-remove-btn-hover-bg, rgba(0, 0, 0, 0.2));
|
|
524
544
|
}
|
|
525
545
|
:where(div.multiselect) input {
|
|
526
546
|
margin: auto 0; /* CSS reset */
|
|
@@ -659,7 +679,8 @@ function on_click_outside(event) {
|
|
|
659
679
|
div.multiselect ul.selected > li button:hover,
|
|
660
680
|
div.multiselect button.remove-all:hover,
|
|
661
681
|
div.multiselect button:focus {
|
|
662
|
-
color: var(--sms-
|
|
682
|
+
color: var(--sms-remove-btn-hover-color, lightskyblue);
|
|
683
|
+
background: var(--sms-remove-btn-hover-bg, rgba(0, 0, 0, 0.2));
|
|
663
684
|
}
|
|
664
685
|
div.multiselect input {
|
|
665
686
|
margin: auto 0; /* CSS reset */
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -12,6 +12,9 @@ declare const __propDef: {
|
|
|
12
12
|
defaultDisabledTitle?: string | undefined;
|
|
13
13
|
disabled?: boolean | undefined;
|
|
14
14
|
disabledInputTitle?: string | undefined;
|
|
15
|
+
duplicateFunc?: ((op1: Option, op2: Option) => boolean) | undefined;
|
|
16
|
+
duplicateOptionMsg?: string | undefined;
|
|
17
|
+
duplicates?: boolean | undefined;
|
|
15
18
|
filterFunc?: ((op: Option, searchText: string) => boolean) | undefined;
|
|
16
19
|
focusInputOnSelect?: boolean | "desktop" | undefined;
|
|
17
20
|
id?: string | null | undefined;
|
package/icons/Cross.svelte
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
|
-
<svg {...$$props} viewBox="0 0
|
|
1
|
+
<svg {...$$props} viewBox="0 0 24 24" fill="currentColor">
|
|
2
2
|
<path
|
|
3
|
-
d="
|
|
3
|
+
d="M18.3 5.71a.996.996 0 0 0-1.41 0L12 10.59L7.11 5.7A.996.996 0 1 0 5.7 7.11L10.59 12L5.7 16.89a.996.996 0 1 0 1.41 1.41L12 13.41l4.89 4.89a.996.996 0 1 0 1.41-1.41L13.41 12l4.89-4.89c.38-.38.38-1.02 0-1.4z"
|
|
4
4
|
/>
|
|
5
5
|
</svg>
|
|
6
|
+
<!-- https://api.iconify.design/ic:round-clear.svg -->
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,8 +1,4 @@
|
|
|
1
1
|
export { default } from './MultiSelect.svelte';
|
|
2
|
-
// get the label key from an option object or the option itself if it's a string or number
|
|
3
|
-
export const get_label = (op) => (op instanceof Object ? op.label : op);
|
|
4
|
-
// fallback on label if option is object and value is undefined
|
|
5
|
-
export const get_value = (op) => op instanceof Object ? op.value ?? op.label : op;
|
|
6
2
|
// Firefox lacks support for scrollIntoViewIfNeeded, see
|
|
7
3
|
// https://github.com/janosh/svelte-multiselect/issues/87
|
|
8
4
|
// this polyfill was copied from
|
package/package.json
CHANGED
package/readme.md
CHANGED
|
@@ -138,6 +138,26 @@ import type { Option } from 'svelte-multiselect'
|
|
|
138
138
|
|
|
139
139
|
Tooltip text to display on hover when the component is in `disabled` state.
|
|
140
140
|
|
|
141
|
+
<!-- prettier-ignore -->
|
|
142
|
+
1. ```ts
|
|
143
|
+
duplicateFunc: (op1: Option, op2: Option) => boolean = (op1, op2) =>
|
|
144
|
+
`${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
This option determines when two options are considered duplicates. Defaults to case-insensitive equality comparison after string coercion (looking only at the `label` key of object options). I.e. the default `duplicateFunc` considers `'Foo' == 'foo'`, `'42' == 42` and ``{ label: `Foo`, value: 0 } == { label: `foo`, value: 42 }``.
|
|
148
|
+
|
|
149
|
+
1. ```ts
|
|
150
|
+
duplicates: boolean = false
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
Whether to allow users to select duplicate options. Applies only to the selected item list, not the options dropdown. Keeping that free of duplicates is left to developer. The selected item list can have duplicates if `allowUserOptions` is truthy, `duplicates` is ` true` and users create the same option multiple times. Use `duplicateOptionMsg` to customize the message shown to user if `duplicates` is `false` and users attempt this and `duplicateFunc` to customize when a pair of options is considered a duplicate.
|
|
154
|
+
|
|
155
|
+
1. ```ts
|
|
156
|
+
duplicateOptionMsg: string = `This option is already selected`
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
Text to display to users when `allowUserOptions` is truthy and they try to create a new option that's already selected.
|
|
160
|
+
|
|
141
161
|
1. ```ts
|
|
142
162
|
filterFunc = (op: Option, searchText: string): boolean => {
|
|
143
163
|
if (!searchText) return true
|
|
@@ -290,13 +310,13 @@ import type { Option } from 'svelte-multiselect'
|
|
|
290
310
|
selectedLabels: (string | number)[] | string | number | null = []
|
|
291
311
|
```
|
|
292
312
|
|
|
293
|
-
Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings, `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`. If `maxSelect={1}`, selectedLabels will not be an array but a single `string | number` or `null` if no options are selected.
|
|
313
|
+
Labels of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.label)` when options are objects. If options are simple strings (or numbers), `selected === selectedLabels`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedLabels`. If `maxSelect={1}`, selectedLabels will not be an array but a single `string | number` or `null` if no options are selected.
|
|
294
314
|
|
|
295
315
|
1. ```ts
|
|
296
316
|
selectedValues: unknown[] | unknown | null = []
|
|
297
317
|
```
|
|
298
318
|
|
|
299
|
-
Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings, `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`. If `maxSelect={1}`, selectedLabels will not be an array but a single value or `null` if no options are selected.
|
|
319
|
+
Values of currently selected options. Exposed just for convenience, equivalent to `selected.map(op => op.value)` when options are objects. If options are simple strings (or numbers), `selected === selectedValues`. Supports binding but is read-only, i.e. since this value is reactive to `selected`, you cannot control `selected` by changing `bind:selectedValues`. If `maxSelect={1}`, selectedLabels will not be an array but a single value or `null` if no options are selected.
|
|
300
320
|
|
|
301
321
|
1. ```ts
|
|
302
322
|
sortSelected: boolean | ((op1: Option, op2: Option) => number) = false
|
|
@@ -460,7 +480,8 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
460
480
|
- `padding: var(--sms-selected-li-padding, 1pt 5pt)`: Height of selected options.
|
|
461
481
|
- `color: var(--sms-selected-text-color, var(--sms-text-color))`: Text color for selected options.
|
|
462
482
|
- `ul.selected > li button:hover, button.remove-all:hover, button:focus`
|
|
463
|
-
- `color: var(--sms-
|
|
483
|
+
- `color: var(--sms-remove-btn-hover-color, lightskyblue)`: Color of the remove-icon buttons for removing all or individual selected options when in `:focus` or `:hover` state.
|
|
484
|
+
- `background: var(--sms-remove-btn-hover-bg, rgba(0, 0, 0, 0.2))`: Background for hovered remove buttons.
|
|
464
485
|
- `div.multiselect > ul.options`
|
|
465
486
|
- `background: var(--sms-options-bg, white)`: Background of dropdown list.
|
|
466
487
|
- `max-height: var(--sms-options-max-height, 50vh)`: Maximum height of options dropdown.
|