svelte-multiselect 3.2.3 → 3.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 +31 -22
- package/MultiSelect.svelte.d.ts +4 -1
- package/package.json +6 -6
- package/readme.md +22 -15
package/MultiSelect.svelte
CHANGED
|
@@ -6,8 +6,10 @@ import Wiggle from './Wiggle.svelte';
|
|
|
6
6
|
export let selected = [];
|
|
7
7
|
export let selectedLabels = [];
|
|
8
8
|
export let selectedValues = [];
|
|
9
|
+
export let searchText = ``;
|
|
10
|
+
export let showOptions = false;
|
|
9
11
|
export let maxSelect = null; // null means any number of options are selectable
|
|
10
|
-
export let maxSelectMsg =
|
|
12
|
+
export let maxSelectMsg = null;
|
|
11
13
|
export let readonly = false;
|
|
12
14
|
export let options;
|
|
13
15
|
export let input = null;
|
|
@@ -26,6 +28,7 @@ export let ulSelectedClass = ``;
|
|
|
26
28
|
export let liSelectedClass = ``;
|
|
27
29
|
export let ulOptionsClass = ``;
|
|
28
30
|
export let liOptionClass = ``;
|
|
31
|
+
export let liActiveOptionClass = ``;
|
|
29
32
|
export let removeBtnTitle = `Remove`;
|
|
30
33
|
export let removeAllTitle = `Remove all`;
|
|
31
34
|
// https://github.com/sveltejs/svelte/issues/6964
|
|
@@ -41,6 +44,7 @@ onMount(() => {
|
|
|
41
44
|
selected = _options.filter((op) => op?.preselected);
|
|
42
45
|
});
|
|
43
46
|
let wiggle = false;
|
|
47
|
+
const dispatch = createEventDispatcher();
|
|
44
48
|
function isObject(item) {
|
|
45
49
|
return typeof item === `object` && !Array.isArray(item) && item !== null;
|
|
46
50
|
}
|
|
@@ -67,9 +71,6 @@ $: if (new Set(labels).size !== options.length) {
|
|
|
67
71
|
}
|
|
68
72
|
$: selectedLabels = selected.map((op) => op.label);
|
|
69
73
|
$: selectedValues = selected.map((op) => op.value);
|
|
70
|
-
const dispatch = createEventDispatcher();
|
|
71
|
-
let searchText = ``;
|
|
72
|
-
let showOptions = false;
|
|
73
74
|
// options matching the current search text
|
|
74
75
|
$: matchingOptions = _options.filter((op) => filterFunc(op, searchText));
|
|
75
76
|
$: matchingEnabledOptions = matchingOptions.filter((op) => !op.disabled);
|
|
@@ -87,7 +88,7 @@ function add(label) {
|
|
|
87
88
|
if (!readonly &&
|
|
88
89
|
!selectedLabels.includes(label) &&
|
|
89
90
|
// for maxselect = 1 we always replace current option with new selection
|
|
90
|
-
(maxSelect
|
|
91
|
+
(maxSelect === null || maxSelect === 1 || selected.length < maxSelect)) {
|
|
91
92
|
searchText = ``; // reset search string on selection
|
|
92
93
|
const option = _options.find((op) => op.label === label);
|
|
93
94
|
if (!option) {
|
|
@@ -198,10 +199,10 @@ const handleEnterAndSpaceKeys = (handler) => (event) => {
|
|
|
198
199
|
<!-- z-index: 2 when showOptions is true ensures the ul.selected of one <MultiSelect />
|
|
199
200
|
display above those of another following shortly after it -->
|
|
200
201
|
<div
|
|
201
|
-
class="multiselect {outerDivClass}"
|
|
202
202
|
class:readonly
|
|
203
|
-
class:single={maxSelect
|
|
203
|
+
class:single={maxSelect === 1}
|
|
204
204
|
class:open={showOptions}
|
|
205
|
+
class="multiselect {outerDivClass}"
|
|
205
206
|
on:mouseup|stopPropagation={() => setOptionsVisible(true)}
|
|
206
207
|
use:onClickOutside={() => setOptionsVisible(false)}
|
|
207
208
|
use:onClickOutside={() => dispatch(`blur`)}
|
|
@@ -242,42 +243,48 @@ display above those of another following shortly after it -->
|
|
|
242
243
|
{#if readonly}
|
|
243
244
|
<ReadOnlyIcon height="14pt" />
|
|
244
245
|
{:else if selected.length > 0}
|
|
245
|
-
{#if maxSelect
|
|
246
|
+
{#if maxSelect && (maxSelect > 1 || maxSelectMsg)}
|
|
246
247
|
<Wiggle bind:wiggle angle={20}>
|
|
247
|
-
<span style="padding: 0 3pt;">
|
|
248
|
+
<span style="padding: 0 3pt;">
|
|
249
|
+
{maxSelectMsg?.(selected.length, maxSelect) ??
|
|
250
|
+
(maxSelect > 1 ? `${selected.length}/${maxSelect}` : ``)}
|
|
251
|
+
</span>
|
|
248
252
|
</Wiggle>
|
|
249
253
|
{/if}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
254
|
+
{#if maxSelect !== 1}
|
|
255
|
+
<button
|
|
256
|
+
type="button"
|
|
257
|
+
class="remove-all"
|
|
258
|
+
title={removeAllTitle}
|
|
259
|
+
on:mouseup|stopPropagation={removeAll}
|
|
260
|
+
on:keydown={handleEnterAndSpaceKeys(removeAll)}
|
|
261
|
+
>
|
|
262
|
+
<CrossIcon height="14pt" />
|
|
263
|
+
</button>
|
|
264
|
+
{/if}
|
|
259
265
|
{/if}
|
|
260
266
|
|
|
261
267
|
{#key showOptions}
|
|
262
268
|
<ul
|
|
263
|
-
class="options {ulOptionsClass}"
|
|
264
269
|
class:hidden={!showOptions}
|
|
270
|
+
class="options {ulOptionsClass}"
|
|
265
271
|
transition:fly|local={{ duration: 300, y: 40 }}
|
|
266
272
|
>
|
|
267
273
|
{#each matchingOptions as option, idx}
|
|
268
274
|
{@const { label, disabled, title = null, selectedTitle } = option}
|
|
269
275
|
{@const { disabledTitle = defaultDisabledTitle } = option}
|
|
276
|
+
{@const active = activeOption?.label === label}
|
|
270
277
|
<li
|
|
271
278
|
on:mouseup|preventDefault|stopPropagation
|
|
272
279
|
on:mousedown|preventDefault|stopPropagation={() => {
|
|
273
280
|
if (disabled) return
|
|
274
281
|
isSelected(label) ? remove(label) : add(label)
|
|
275
282
|
}}
|
|
283
|
+
title={disabled ? disabledTitle : (isSelected(label) && selectedTitle) || title}
|
|
276
284
|
class:selected={isSelected(label)}
|
|
277
|
-
class:active
|
|
285
|
+
class:active
|
|
278
286
|
class:disabled
|
|
279
|
-
|
|
280
|
-
class={liOptionClass}
|
|
287
|
+
class="{liOptionClass} {active ? liActiveOptionClass : ``}"
|
|
281
288
|
>
|
|
282
289
|
<slot name="renderOptions" {option} {idx}>
|
|
283
290
|
{option.label}
|
|
@@ -371,12 +378,14 @@ display above those of another following shortly after it -->
|
|
|
371
378
|
max-height: 50vh;
|
|
372
379
|
padding: 0;
|
|
373
380
|
top: 100%;
|
|
381
|
+
left: 0;
|
|
374
382
|
width: 100%;
|
|
375
383
|
position: absolute;
|
|
376
384
|
border-radius: 1ex;
|
|
377
385
|
overflow: auto;
|
|
378
386
|
background: var(--sms-options-bg, white);
|
|
379
387
|
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
388
|
+
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
380
389
|
}
|
|
381
390
|
:where(div.multiselect > ul.options.hidden) {
|
|
382
391
|
visibility: hidden;
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -5,8 +5,10 @@ declare const __propDef: {
|
|
|
5
5
|
selected?: Option[] | undefined;
|
|
6
6
|
selectedLabels?: Primitive[] | undefined;
|
|
7
7
|
selectedValues?: Primitive[] | undefined;
|
|
8
|
+
searchText?: string | undefined;
|
|
9
|
+
showOptions?: boolean | undefined;
|
|
8
10
|
maxSelect?: number | null | undefined;
|
|
9
|
-
maxSelectMsg?: ((current: number, max: number) => string) | undefined;
|
|
11
|
+
maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
|
|
10
12
|
readonly?: boolean | undefined;
|
|
11
13
|
options: ProtoOption[];
|
|
12
14
|
input?: HTMLInputElement | null | undefined;
|
|
@@ -21,6 +23,7 @@ declare const __propDef: {
|
|
|
21
23
|
liSelectedClass?: string | undefined;
|
|
22
24
|
ulOptionsClass?: string | undefined;
|
|
23
25
|
liOptionClass?: string | undefined;
|
|
26
|
+
liActiveOptionClass?: string | undefined;
|
|
24
27
|
removeBtnTitle?: string | undefined;
|
|
25
28
|
removeAllTitle?: string | undefined;
|
|
26
29
|
defaultDisabledTitle?: string | undefined;
|
package/package.json
CHANGED
|
@@ -5,13 +5,13 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "3.
|
|
8
|
+
"version": "3.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
|
"@sveltejs/adapter-static": "^1.0.0-next.28",
|
|
14
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
14
|
+
"@sveltejs/kit": "^1.0.0-next.278",
|
|
15
15
|
"@typescript-eslint/eslint-plugin": "^5.12.0",
|
|
16
16
|
"@typescript-eslint/parser": "^5.12.0",
|
|
17
17
|
"eslint": "^8.9.0",
|
|
@@ -23,14 +23,14 @@
|
|
|
23
23
|
"rehype-autolink-headings": "^6.1.1",
|
|
24
24
|
"rehype-slug": "^5.0.1",
|
|
25
25
|
"svelte": "^3.46.4",
|
|
26
|
-
"svelte-check": "^2.4.
|
|
26
|
+
"svelte-check": "^2.4.5",
|
|
27
27
|
"svelte-github-corner": "^0.1.0",
|
|
28
28
|
"svelte-preprocess": "^4.10.3",
|
|
29
|
-
"svelte-toc": "^0.2.
|
|
30
|
-
"svelte2tsx": "^0.5.
|
|
29
|
+
"svelte-toc": "^0.2.6",
|
|
30
|
+
"svelte2tsx": "^0.5.5",
|
|
31
31
|
"tslib": "^2.3.1",
|
|
32
32
|
"typescript": "^4.5.5",
|
|
33
|
-
"vite": "^2.8.
|
|
33
|
+
"vite": "^2.8.4"
|
|
34
34
|
},
|
|
35
35
|
"keywords": [
|
|
36
36
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -88,19 +88,22 @@ Full list of props/bindable variables for this component:
|
|
|
88
88
|
<div class="table">
|
|
89
89
|
|
|
90
90
|
<!-- prettier-ignore -->
|
|
91
|
-
| name | default
|
|
92
|
-
| :--------------- |
|
|
93
|
-
| `options` | required prop
|
|
94
|
-
| `
|
|
95
|
-
| `
|
|
96
|
-
| `
|
|
97
|
-
| `
|
|
98
|
-
| `
|
|
99
|
-
| `
|
|
100
|
-
| `
|
|
101
|
-
| `
|
|
102
|
-
| `
|
|
103
|
-
| `
|
|
91
|
+
| name | default | description |
|
|
92
|
+
| :--------------- | :---------------------- | :----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
93
|
+
| `options` | required prop | Array of strings/numbers or `Option` objects that will be listed in the dropdown. See `src/lib/index.ts` for admissible fields. The `label` is the only mandatory one. It must also be unique. |
|
|
94
|
+
| `showOptions` | `false` | Bindable boolean that controls whether the options dropdown is visible. |
|
|
95
|
+
| `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. |
|
|
96
|
+
| `activeOption` | `null` | Currently active option, i.e. the one the user currently hovers or navigated to with arrow keys. |
|
|
97
|
+
| `maxSelect` | `null` | Positive integer to limit the number of options users can pick. `null` means no limit. |
|
|
98
|
+
| `selected` | `[]` | Array of currently/pre-selected options when binding/passing as props respectively. |
|
|
99
|
+
| `selectedLabels` | `[]` | Labels of currently selected options. |
|
|
100
|
+
| `selectedValues` | `[]` | Values of currently selected options. |
|
|
101
|
+
| `noOptionsMsg` | `'No matching options'` | What message to show if no options match the user-entered search string. |
|
|
102
|
+
| `readonly` | `false` | Disable the component. It will still be rendered but users won't be able to interact with it. |
|
|
103
|
+
| `placeholder` | `undefined` | String shown in the text input when no option is selected. |
|
|
104
|
+
| `input` | `undefined` | Handle to the `<input>` DOM node. |
|
|
105
|
+
| `id` | `undefined` | Applied to the `<input>` element for associating HTML form `<label>`s with this component for accessibility. Also, clicking a `<label>` with same `for` attribute as `id` will focus this component. |
|
|
106
|
+
| `name` | `id` | Applied to the `<input>` element. If not provided, will be set to the value of `id`. Sets the key of this field in a submitted form data object. Not useful at the moment since the value is stored in Svelte state, not on the `<input>`. |
|
|
104
107
|
|
|
105
108
|
</div>
|
|
106
109
|
|
|
@@ -115,7 +118,7 @@ Full list of props/bindable variables for this component:
|
|
|
115
118
|
}
|
|
116
119
|
```
|
|
117
120
|
|
|
118
|
-
2. `maxSelectMsg = (current: number, max: number) => string`: Inform
|
|
121
|
+
2. `maxSelectMsg = (current: number, max: number) => string`: Inform users how many of the maximum allowed options they have already selected. Set `maxSelectMsg={null}` to not show a message. Defaults to `null` when `maxSelect={1}` or `maxSelect={null}`. Else if `maxSelect > 1`, defaults to:
|
|
119
122
|
|
|
120
123
|
```ts
|
|
121
124
|
maxSelectMsg = (current: number, max: number) => `${current}/${max}`
|
|
@@ -231,6 +234,7 @@ If you only want to make small adjustments, you can pass the following CSS varia
|
|
|
231
234
|
- `div.multiselect > ul.options`
|
|
232
235
|
- `background: var(--sms-options-bg, white)`: Background of dropdown list.
|
|
233
236
|
- `overscroll-behavior: var(--sms-options-overscroll, none)`: Whether scroll events bubble to parent elements when reaching the top/bottom of the options dropdown. See [MDN](https://developer.mozilla.org/en-US/docs/Web/CSS/overscroll-behavior).
|
|
237
|
+
- `box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);`: Box shadow of dropdown list.
|
|
234
238
|
- `div.multiselect > ul.options > li.selected`
|
|
235
239
|
- `border-left: var(--sms-li-selected-border-left, 3pt solid var(--sms-selected-color, green))`
|
|
236
240
|
- `background: var(--sms-li-selected-bg, inherit)`: Background of selected list items in options pane.
|
|
@@ -258,6 +262,7 @@ The second method allows you to pass in custom classes to the important DOM elem
|
|
|
258
262
|
- `liSelectedClass`
|
|
259
263
|
- `ulOptionsClass`
|
|
260
264
|
- `liOptionClass`
|
|
265
|
+
- `liActiveOptionClass`
|
|
261
266
|
|
|
262
267
|
This simplified version of the DOM structure of this component shows where these classes are inserted:
|
|
263
268
|
|
|
@@ -269,7 +274,9 @@ This simplified version of the DOM structure of this component shows where these
|
|
|
269
274
|
</ul>
|
|
270
275
|
<ul class="options {ulOptionsClass}">
|
|
271
276
|
<li class={liOptionClass}>Option 1</li>
|
|
272
|
-
<li class={liOptionClass}
|
|
277
|
+
<li class="{liOptionClass} {liActiveOptionClass}">
|
|
278
|
+
Option 2 (currently active)
|
|
279
|
+
</li>
|
|
273
280
|
</ul>
|
|
274
281
|
</div>
|
|
275
282
|
```
|