svelte-multiselect 5.0.1 → 5.0.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.
- package/CircleSpinner.svelte +1 -1
- package/MultiSelect.svelte +32 -17
- package/MultiSelect.svelte.d.ts +1 -0
- package/Wiggle.svelte +1 -1
- package/index.d.ts +2 -0
- package/index.js +4 -0
- package/package.json +21 -23
- package/readme.md +9 -7
package/CircleSpinner.svelte
CHANGED
package/MultiSelect.svelte
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
<script
|
|
2
|
-
import './';
|
|
1
|
+
<script>import { createEventDispatcher } from 'svelte';
|
|
2
|
+
import { get_label, get_value } from './';
|
|
3
3
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
4
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
5
5
|
import Wiggle from './Wiggle.svelte';
|
|
@@ -36,6 +36,7 @@ export let removeBtnTitle = `Remove`;
|
|
|
36
36
|
export let removeAllTitle = `Remove all`;
|
|
37
37
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
38
38
|
export let allowUserOptions = false;
|
|
39
|
+
export let parseLabelsAsHtml = false; // should not be combined with allowUserOptions!
|
|
39
40
|
export let addOptionMsg = `Create this option...`;
|
|
40
41
|
export let autoScroll = true;
|
|
41
42
|
export let loading = false;
|
|
@@ -43,18 +44,17 @@ export let required = false;
|
|
|
43
44
|
export let autocomplete = `off`;
|
|
44
45
|
export let invalid = false;
|
|
45
46
|
export let sortSelected = false;
|
|
47
|
+
if (!(options?.length > 0))
|
|
48
|
+
console.error(`MultiSelect received no options`);
|
|
49
|
+
if (parseLabelsAsHtml && allowUserOptions)
|
|
50
|
+
console.warn(`You shouldn't combine parseLabelsAsHtml and allowUserOptions. It's susceptible to XSS attacks!`);
|
|
46
51
|
if (maxSelect !== null && maxSelect < 1) {
|
|
47
52
|
console.error(`maxSelect must be null or positive integer, got ${maxSelect}`);
|
|
48
53
|
}
|
|
49
|
-
if (!(options?.length > 0))
|
|
50
|
-
console.error(`MultiSelect is missing options`);
|
|
51
54
|
if (!Array.isArray(selected))
|
|
52
55
|
console.error(`selected prop must be an array`);
|
|
53
56
|
const dispatch = createEventDispatcher();
|
|
54
57
|
let activeMsg = false; // controls active state of <li>{addOptionMsg}</li>
|
|
55
|
-
const get_label = (op) => (op instanceof Object ? op.label : op);
|
|
56
|
-
// fallback on label if option is object and value is undefined
|
|
57
|
-
const get_value = (op) => (op instanceof Object ? op.value ?? op.label : op);
|
|
58
58
|
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
59
59
|
$: selectedLabels = selected.map(get_label);
|
|
60
60
|
$: selectedValues = selected.map(get_value);
|
|
@@ -80,8 +80,12 @@ function add(label) {
|
|
|
80
80
|
// custom option twice in append mode
|
|
81
81
|
[true, `append`].includes(allowUserOptions) &&
|
|
82
82
|
searchText.length > 0) {
|
|
83
|
-
// user entered text but no options match, so if allowUserOptions=true | 'append', we create
|
|
84
|
-
|
|
83
|
+
// user entered text but no options match, so if allowUserOptions=true | 'append', we create
|
|
84
|
+
// a new option from the user-entered text
|
|
85
|
+
if (typeof options[0] === `string`)
|
|
86
|
+
option = searchText;
|
|
87
|
+
else
|
|
88
|
+
option = { label: searchText, value: searchText };
|
|
85
89
|
if (allowUserOptions === `append`)
|
|
86
90
|
options = [...options, option];
|
|
87
91
|
}
|
|
@@ -202,9 +206,12 @@ async function handleKeydown(event) {
|
|
|
202
206
|
activeOption = matchingOptions[newActiveIdx];
|
|
203
207
|
}
|
|
204
208
|
if (autoScroll) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
209
|
+
// TODO This ugly timeout hack is needed to properly scroll element into view when wrapping
|
|
210
|
+
// around start/end of option list. Find a better solution than waiting 10 ms to.
|
|
211
|
+
setTimeout(() => {
|
|
212
|
+
const li = document.querySelector(`ul.options > li.active`);
|
|
213
|
+
li?.scrollIntoView();
|
|
214
|
+
}, 10);
|
|
208
215
|
}
|
|
209
216
|
}
|
|
210
217
|
// on backspace key: remove last selected option
|
|
@@ -262,7 +269,11 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
262
269
|
{#each selected as option, idx}
|
|
263
270
|
<li class={liSelectedClass} aria-selected="true">
|
|
264
271
|
<slot name="selected" {option} {idx}>
|
|
265
|
-
{
|
|
272
|
+
{#if parseLabelsAsHtml}
|
|
273
|
+
{@html get_label(option)}
|
|
274
|
+
{:else}
|
|
275
|
+
{get_label(option)}
|
|
276
|
+
{/if}
|
|
266
277
|
</slot>
|
|
267
278
|
{#if !disabled}
|
|
268
279
|
<button
|
|
@@ -355,7 +366,11 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
355
366
|
aria-selected="false"
|
|
356
367
|
>
|
|
357
368
|
<slot name="option" {option} {idx}>
|
|
358
|
-
{
|
|
369
|
+
{#if parseLabelsAsHtml}
|
|
370
|
+
{@html get_label(option)}
|
|
371
|
+
{:else}
|
|
372
|
+
{get_label(option)}
|
|
373
|
+
{/if}
|
|
359
374
|
</slot>
|
|
360
375
|
</li>
|
|
361
376
|
{:else}
|
|
@@ -462,7 +477,9 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
462
477
|
cursor: inherit; /* needed for disabled state */
|
|
463
478
|
}
|
|
464
479
|
:where(div.multiselect > ul.selected > li > input)::placeholder {
|
|
480
|
+
padding-left: 5pt;
|
|
465
481
|
color: var(--sms-placeholder-color);
|
|
482
|
+
opacity: var(--sms-placeholder-opacity);
|
|
466
483
|
}
|
|
467
484
|
:where(div.multiselect > input.form-control) {
|
|
468
485
|
width: 2em;
|
|
@@ -477,7 +494,7 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
477
494
|
|
|
478
495
|
:where(div.multiselect > ul.options) {
|
|
479
496
|
list-style: none;
|
|
480
|
-
padding: 0;
|
|
497
|
+
padding: 4pt 0;
|
|
481
498
|
top: 100%;
|
|
482
499
|
left: 0;
|
|
483
500
|
width: 100%;
|
|
@@ -489,8 +506,6 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
489
506
|
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
490
507
|
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
491
508
|
transition: all 0.2s;
|
|
492
|
-
opacity: 1;
|
|
493
|
-
transform: translateY(0);
|
|
494
509
|
}
|
|
495
510
|
:where(div.multiselect > ul.options.hidden) {
|
|
496
511
|
visibility: hidden;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ declare const __propDef: {
|
|
|
31
31
|
removeAllTitle?: string | undefined;
|
|
32
32
|
defaultDisabledTitle?: string | undefined;
|
|
33
33
|
allowUserOptions?: boolean | "append" | undefined;
|
|
34
|
+
parseLabelsAsHtml?: boolean | undefined;
|
|
34
35
|
addOptionMsg?: string | undefined;
|
|
35
36
|
autoScroll?: boolean | undefined;
|
|
36
37
|
loading?: boolean | undefined;
|
package/Wiggle.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script
|
|
1
|
+
<script>import { spring } from 'svelte/motion';
|
|
2
2
|
// bind to this state and set it to true from parent
|
|
3
3
|
export let wiggle = false;
|
|
4
4
|
// intended use case: set max value during wiggle for one of angle, scale, dx, dy through props
|
package/index.d.ts
CHANGED
|
@@ -31,3 +31,5 @@ export declare type DispatchEvents = {
|
|
|
31
31
|
export declare type CustomEvents = {
|
|
32
32
|
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
33
33
|
};
|
|
34
|
+
export declare const get_label: (op: Option) => string | number;
|
|
35
|
+
export declare const get_value: (op: Option) => unknown;
|
package/index.js
CHANGED
|
@@ -1 +1,5 @@
|
|
|
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;
|
package/package.json
CHANGED
|
@@ -5,39 +5,37 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "5.0.
|
|
8
|
+
"version": "5.0.2",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"main": "index.js",
|
|
12
12
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
13
13
|
"devDependencies": {
|
|
14
|
-
"@sveltejs/adapter-static": "^1.0.0-next.
|
|
15
|
-
"@sveltejs/kit": "
|
|
16
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.
|
|
17
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
18
|
-
"@typescript-eslint/parser": "^5.
|
|
19
|
-
"
|
|
20
|
-
"
|
|
21
|
-
"eslint": "^8.12.0",
|
|
22
|
-
"eslint-plugin-svelte3": "^3.4.1",
|
|
14
|
+
"@sveltejs/adapter-static": "^1.0.0-next.34",
|
|
15
|
+
"@sveltejs/kit": "1.0.0-next.345",
|
|
16
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.0-next.49",
|
|
17
|
+
"@typescript-eslint/eslint-plugin": "^5.29.0",
|
|
18
|
+
"@typescript-eslint/parser": "^5.29.0",
|
|
19
|
+
"eslint": "^8.18.0",
|
|
20
|
+
"eslint-plugin-svelte3": "^4.0.0",
|
|
23
21
|
"hastscript": "^7.0.2",
|
|
24
|
-
"jsdom": "^
|
|
25
|
-
"mdsvex": "^0.10.
|
|
26
|
-
"playwright": "^1.
|
|
27
|
-
"prettier": "^2.
|
|
28
|
-
"prettier-plugin-svelte": "^2.
|
|
22
|
+
"jsdom": "^20.0.0",
|
|
23
|
+
"mdsvex": "^0.10.6",
|
|
24
|
+
"playwright": "^1.22.2",
|
|
25
|
+
"prettier": "^2.7.1",
|
|
26
|
+
"prettier-plugin-svelte": "^2.7.0",
|
|
29
27
|
"rehype-autolink-headings": "^6.1.1",
|
|
30
28
|
"rehype-slug": "^5.0.1",
|
|
31
|
-
"svelte": "^3.
|
|
32
|
-
"svelte-check": "^2.
|
|
29
|
+
"svelte": "^3.48.0",
|
|
30
|
+
"svelte-check": "^2.8.0",
|
|
33
31
|
"svelte-github-corner": "^0.1.0",
|
|
34
|
-
"svelte-preprocess": "^4.10.
|
|
32
|
+
"svelte-preprocess": "^4.10.6",
|
|
35
33
|
"svelte-toc": "^0.2.9",
|
|
36
|
-
"svelte2tsx": "^0.5.
|
|
37
|
-
"tslib": "^2.
|
|
38
|
-
"typescript": "^4.
|
|
39
|
-
"vite": "^2.9.
|
|
40
|
-
"vitest": "^0.
|
|
34
|
+
"svelte2tsx": "^0.5.11",
|
|
35
|
+
"tslib": "^2.4.0",
|
|
36
|
+
"typescript": "^4.7.4",
|
|
37
|
+
"vite": "^2.9.12",
|
|
38
|
+
"vitest": "^0.16.0"
|
|
41
39
|
},
|
|
42
40
|
"keywords": [
|
|
43
41
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -22,14 +22,14 @@
|
|
|
22
22
|
|
|
23
23
|
## Key features
|
|
24
24
|
|
|
25
|
-
- **
|
|
25
|
+
- **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]`.
|
|
26
|
+
- **Keyboard friendly** for mouse-less form completion
|
|
27
|
+
- **No 3rd-party dependencies:** needs only Svelte as dev dependency
|
|
26
28
|
- **Dropdowns:** scrollable lists for large numbers of options
|
|
27
29
|
- **Searchable:** start typing to filter options
|
|
28
|
-
- **Tagging:** selected options are
|
|
29
|
-
- **
|
|
30
|
+
- **Tagging:** selected options are listed as tags within the input
|
|
31
|
+
- **Single / multiple select:** pass `maxSelect={1, 2, 3, ...}` prop to restrict the number of selectable options
|
|
30
32
|
- **Configurable:** see [props](#props)
|
|
31
|
-
- **No dependencies:** needs only Svelte as dev dependency
|
|
32
|
-
- **Keyboard friendly** for mouse-less form completion
|
|
33
33
|
|
|
34
34
|
<slot name="nav" />
|
|
35
35
|
|
|
@@ -84,13 +84,13 @@ Full list of props/bindable variables for this component:
|
|
|
84
84
|
| `searchText` | `` | Text the user-entered to filter down on the list of options. Binds both ways, i.e. can also be used to set the input text. |
|
|
85
85
|
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
86
86
|
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
87
|
-
| `selected` | `[]` | Array of currently
|
|
87
|
+
| `selected` | `[]` | Array of currently selected options. Can be bound to `bind:selected={[1, 2, 3]}` to control component state externally or passed as prop to set pre-selected options that will already be populated when component mounts before any user interaction. |
|
|
88
88
|
| `selectedLabels` | `[]` | 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`. |
|
|
89
89
|
| `selectedValues` | `[]` | 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`. |
|
|
90
90
|
| `sortSelected` | `boolean \| ((op1, op2) => number)` | Default behavior is to render selected items in the order they were chosen. `sortSelected={true}` uses default JS array sorting. A compare function enables custom logic for sorting selected options. See the [`/sort-selected`](https://svelte-multiselect.netlify.app/sort-selected) example. |
|
|
91
91
|
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
|
|
92
92
|
| `disabled` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
93
|
-
| `disabledTitle` | `This field is disabled`
|
|
93
|
+
| `disabledTitle` | `'This field is disabled'` | Tooltip text to display on hover when the component is in `disabled` state. |
|
|
94
94
|
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
|
|
95
95
|
| `input` | `null` | Handle to the `<input>` DOM node. Only available after component mounts (`null` before then). |
|
|
96
96
|
| `outerDiv` | `null` | Handle to outer `<div class="multiselect">` that wraps the whole component. Only available after component mounts (`null` before then). |
|
|
@@ -99,6 +99,7 @@ Full list of props/bindable variables for this component:
|
|
|
99
99
|
| `required` | `false` | Whether forms can be submitted without selecting any options. Aborts submission, is scrolled into view and shows help "Please fill out" message when true and user tries to submit with no options selected. |
|
|
100
100
|
| `autoScroll` | `true` | `false` disables keeping the active dropdown items in view when going up/down the list of options with arrow keys. |
|
|
101
101
|
| `allowUserOptions` | `false` | Whether users are allowed to enter values not in the dropdown list. `true` means add user-defined options to the selected list only, `'append'` means add to both options and selected. |
|
|
102
|
+
| `parseLabelsAsHtml` | `false` | Whether option labels should be passed to [Svelte's `@html` directive](https://svelte.dev/tutorial/html-tags) or inserted into the DOM as plain text. `true` will raise an error if `allowUserOptions` is also truthy as it makes your site susceptible to [cross-site scripting (XSS) attacks](https://wikipedia.org/wiki/Cross-site_scripting). |
|
|
102
103
|
| `addOptionMsg` | `'Create this option...'` | Message shown to users after entering text when no options match their query and `allowUserOptions` is truthy. |
|
|
103
104
|
| `loading` | `false` | Whether the component should display a spinner to indicate it's in loading state. Use `<slot name='spinner'>` to specify a custom spinner. |
|
|
104
105
|
| `removeBtnTitle` | `'Remove'` | Title text to display when user hovers over button (cross icon) to remove selected option. |
|
|
@@ -239,6 +240,7 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
239
240
|
- `background: var(--sms-disabled-bg, lightgray)`: Background when in disabled state.
|
|
240
241
|
- `div.multiselect input::placeholder`
|
|
241
242
|
- `color: var(--sms-placeholder-color)`
|
|
243
|
+
- `color: var(--sms-placeholder-opacity)`
|
|
242
244
|
- `div.multiselect > ul.selected > li`
|
|
243
245
|
- `background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15))`: Background of selected options.
|
|
244
246
|
- `padding: var(--sms-selected-li-padding, 5pt 1pt)`: Height of selected options.
|