svelte-multiselect 6.0.3 → 6.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 +37 -16
- package/MultiSelect.svelte.d.ts +2 -0
- package/index.d.ts +18 -2
- package/package.json +11 -11
- package/readme.md +31 -9
package/MultiSelect.svelte
CHANGED
|
@@ -48,6 +48,8 @@ export let selectedValues = [];
|
|
|
48
48
|
export let sortSelected = false;
|
|
49
49
|
export let ulOptionsClass = ``;
|
|
50
50
|
export let ulSelectedClass = ``;
|
|
51
|
+
export let inputmode = ``;
|
|
52
|
+
export let pattern = ``;
|
|
51
53
|
if (!(options?.length > 0)) {
|
|
52
54
|
if (allowUserOptions) {
|
|
53
55
|
options = []; // initializing as array avoids errors when component mounts
|
|
@@ -85,9 +87,9 @@ if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
|
85
87
|
throw `Run time error, activeIndex=${activeIndex} is out of bounds, matchingOptions.length=${matchingOptions.length}`;
|
|
86
88
|
}
|
|
87
89
|
// update activeOption when activeIndex changes
|
|
88
|
-
$: activeOption = activeIndex ? matchingOptions[activeIndex] : null;
|
|
90
|
+
$: activeOption = activeIndex !== null ? matchingOptions[activeIndex] : null;
|
|
89
91
|
// add an option to selected list
|
|
90
|
-
function add(label) {
|
|
92
|
+
function add(label, event) {
|
|
91
93
|
if (maxSelect && maxSelect > 1 && selected.length >= maxSelect)
|
|
92
94
|
wiggle = true;
|
|
93
95
|
// to prevent duplicate selection, we could add `&& !selectedLabels.includes(label)`
|
|
@@ -139,7 +141,7 @@ function add(label) {
|
|
|
139
141
|
}
|
|
140
142
|
}
|
|
141
143
|
if (selected.length === maxSelect)
|
|
142
|
-
close_dropdown();
|
|
144
|
+
close_dropdown(event);
|
|
143
145
|
else if (focusInputOnSelect === true ||
|
|
144
146
|
(focusInputOnSelect === `desktop` && window_width > breakpoint)) {
|
|
145
147
|
input?.focus();
|
|
@@ -165,24 +167,27 @@ function remove(label) {
|
|
|
165
167
|
dispatch(`remove`, { option });
|
|
166
168
|
dispatch(`change`, { option, type: `remove` });
|
|
167
169
|
}
|
|
168
|
-
function open_dropdown() {
|
|
170
|
+
function open_dropdown(event) {
|
|
169
171
|
if (disabled)
|
|
170
172
|
return;
|
|
171
173
|
open = true;
|
|
172
|
-
|
|
173
|
-
|
|
174
|
+
if (!(event instanceof FocusEvent)) {
|
|
175
|
+
// avoid double-focussing input when event that opened dropdown was already input FocusEvent
|
|
176
|
+
input?.focus();
|
|
177
|
+
}
|
|
178
|
+
dispatch(`open`, { event });
|
|
174
179
|
}
|
|
175
|
-
function close_dropdown() {
|
|
180
|
+
function close_dropdown(event) {
|
|
176
181
|
open = false;
|
|
177
182
|
input?.blur();
|
|
178
183
|
activeOption = null;
|
|
179
|
-
dispatch(`
|
|
184
|
+
dispatch(`close`, { event });
|
|
180
185
|
}
|
|
181
186
|
// handle all keyboard events this component receives
|
|
182
187
|
async function handle_keydown(event) {
|
|
183
188
|
// on escape or tab out of input: dismiss options dropdown and reset search text
|
|
184
189
|
if (event.key === `Escape` || event.key === `Tab`) {
|
|
185
|
-
close_dropdown();
|
|
190
|
+
close_dropdown(event);
|
|
186
191
|
searchText = ``;
|
|
187
192
|
}
|
|
188
193
|
// on enter key: toggle active option and reset search text
|
|
@@ -190,17 +195,17 @@ async function handle_keydown(event) {
|
|
|
190
195
|
event.preventDefault(); // prevent enter key from triggering form submission
|
|
191
196
|
if (activeOption) {
|
|
192
197
|
const label = get_label(activeOption);
|
|
193
|
-
selectedLabels.includes(label) ? remove(label) : add(label);
|
|
198
|
+
selectedLabels.includes(label) ? remove(label) : add(label, event);
|
|
194
199
|
searchText = ``;
|
|
195
200
|
}
|
|
196
201
|
else if (allowUserOptions && searchText.length > 0) {
|
|
197
202
|
// user entered text but no options match, so if allowUserOptions is truthy, we create new option
|
|
198
|
-
add(searchText);
|
|
203
|
+
add(searchText, event);
|
|
199
204
|
}
|
|
200
205
|
// no active option and no search text means the options dropdown is closed
|
|
201
206
|
// in which case enter means open it
|
|
202
207
|
else
|
|
203
|
-
open_dropdown();
|
|
208
|
+
open_dropdown(event);
|
|
204
209
|
}
|
|
205
210
|
// on up/down arrow keys: update active option
|
|
206
211
|
else if ([`ArrowDown`, `ArrowUp`].includes(event.key)) {
|
|
@@ -257,7 +262,7 @@ const if_enter_or_space = (handler) => (event) => {
|
|
|
257
262
|
};
|
|
258
263
|
function on_click_outside(event) {
|
|
259
264
|
if (outerDiv && !outerDiv.contains(event.target)) {
|
|
260
|
-
close_dropdown();
|
|
265
|
+
close_dropdown(event);
|
|
261
266
|
}
|
|
262
267
|
}
|
|
263
268
|
</script>
|
|
@@ -321,13 +326,29 @@ function on_click_outside(event) {
|
|
|
321
326
|
bind:value={searchText}
|
|
322
327
|
on:mouseup|self|stopPropagation={open_dropdown}
|
|
323
328
|
on:keydown={handle_keydown}
|
|
329
|
+
on:focus
|
|
324
330
|
on:focus={open_dropdown}
|
|
325
331
|
{id}
|
|
326
332
|
{name}
|
|
327
333
|
{disabled}
|
|
334
|
+
{inputmode}
|
|
335
|
+
{pattern}
|
|
328
336
|
placeholder={selectedLabels.length ? `` : placeholder}
|
|
329
337
|
aria-invalid={invalid ? `true` : null}
|
|
338
|
+
on:blur
|
|
339
|
+
on:change
|
|
340
|
+
on:click
|
|
341
|
+
on:keydown
|
|
342
|
+
on:keyup
|
|
343
|
+
on:mousedown
|
|
344
|
+
on:mouseenter
|
|
345
|
+
on:mouseleave
|
|
346
|
+
on:touchcancel
|
|
347
|
+
on:touchend
|
|
348
|
+
on:touchmove
|
|
349
|
+
on:touchstart
|
|
330
350
|
/>
|
|
351
|
+
<!-- the above on:* lines forward potentially useful DOM events -->
|
|
331
352
|
</li>
|
|
332
353
|
</ul>
|
|
333
354
|
{#if loading}
|
|
@@ -375,8 +396,8 @@ function on_click_outside(event) {
|
|
|
375
396
|
{@const active = activeIndex === idx}
|
|
376
397
|
<li
|
|
377
398
|
on:mousedown|stopPropagation
|
|
378
|
-
on:mouseup|stopPropagation={() => {
|
|
379
|
-
if (!disabled) is_selected(label) ? remove(label) : add(label)
|
|
399
|
+
on:mouseup|stopPropagation={(event) => {
|
|
400
|
+
if (!disabled) is_selected(label) ? remove(label) : add(label, event)
|
|
380
401
|
}}
|
|
381
402
|
title={disabled
|
|
382
403
|
? disabledTitle
|
|
@@ -407,7 +428,7 @@ function on_click_outside(event) {
|
|
|
407
428
|
{#if allowUserOptions && searchText}
|
|
408
429
|
<li
|
|
409
430
|
on:mousedown|stopPropagation
|
|
410
|
-
on:mouseup|stopPropagation={() => add(searchText)}
|
|
431
|
+
on:mouseup|stopPropagation={(event) => add(searchText, event)}
|
|
411
432
|
title={addOptionMsg}
|
|
412
433
|
class:active={add_option_msg_is_active}
|
|
413
434
|
on:mouseover={() => (add_option_msg_is_active = true)}
|
package/MultiSelect.svelte.d.ts
CHANGED
|
@@ -43,6 +43,8 @@ declare const __propDef: {
|
|
|
43
43
|
sortSelected?: boolean | ((op1: Option, op2: Option) => number) | undefined;
|
|
44
44
|
ulOptionsClass?: string | undefined;
|
|
45
45
|
ulSelectedClass?: string | undefined;
|
|
46
|
+
inputmode?: string | undefined;
|
|
47
|
+
pattern?: string | undefined;
|
|
46
48
|
};
|
|
47
49
|
slots: {
|
|
48
50
|
selected: {
|
package/index.d.ts
CHANGED
|
@@ -25,11 +25,27 @@ export declare type DispatchEvents = {
|
|
|
25
25
|
options?: Option[];
|
|
26
26
|
type: 'add' | 'remove' | 'removeAll';
|
|
27
27
|
};
|
|
28
|
-
|
|
29
|
-
|
|
28
|
+
open: {
|
|
29
|
+
event: Event;
|
|
30
|
+
};
|
|
31
|
+
close: {
|
|
32
|
+
event: Event;
|
|
33
|
+
};
|
|
30
34
|
};
|
|
31
35
|
export declare type MultiSelectEvents = {
|
|
32
36
|
[key in keyof DispatchEvents]: CustomEvent<DispatchEvents[key]>;
|
|
37
|
+
} & {
|
|
38
|
+
blur: FocusEvent;
|
|
39
|
+
click: MouseEvent;
|
|
40
|
+
focus: FocusEvent;
|
|
41
|
+
keydown: KeyboardEvent;
|
|
42
|
+
keyup: KeyboardEvent;
|
|
43
|
+
mouseenter: MouseEvent;
|
|
44
|
+
mouseleave: MouseEvent;
|
|
45
|
+
touchcancel: TouchEvent;
|
|
46
|
+
touchend: TouchEvent;
|
|
47
|
+
touchmove: TouchEvent;
|
|
48
|
+
touchstart: TouchEvent;
|
|
33
49
|
};
|
|
34
50
|
export declare const get_label: (op: Option) => string | number;
|
|
35
51
|
export declare const get_value: (op: Option) => {};
|
package/package.json
CHANGED
|
@@ -5,20 +5,20 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "6.0
|
|
8
|
+
"version": "6.1.0",
|
|
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
|
-
"@playwright/test": "^1.
|
|
14
|
+
"@playwright/test": "^1.26.0",
|
|
15
15
|
"@sveltejs/adapter-static": "^1.0.0-next.43",
|
|
16
|
-
"@sveltejs/kit": "^1.0.0-next.
|
|
17
|
-
"@sveltejs/package": "^1.0.0-next.
|
|
18
|
-
"@sveltejs/vite-plugin-svelte": "^1.0.
|
|
19
|
-
"@typescript-eslint/eslint-plugin": "^5.
|
|
20
|
-
"@typescript-eslint/parser": "^5.
|
|
21
|
-
"eslint": "^8.
|
|
16
|
+
"@sveltejs/kit": "^1.0.0-next.502",
|
|
17
|
+
"@sveltejs/package": "^1.0.0-next.5",
|
|
18
|
+
"@sveltejs/vite-plugin-svelte": "^1.0.8",
|
|
19
|
+
"@typescript-eslint/eslint-plugin": "^5.38.0",
|
|
20
|
+
"@typescript-eslint/parser": "^5.38.0",
|
|
21
|
+
"eslint": "^8.24.0",
|
|
22
22
|
"eslint-plugin-svelte3": "^4.0.0",
|
|
23
23
|
"hastscript": "^7.0.2",
|
|
24
24
|
"jsdom": "^20.0.0",
|
|
@@ -32,11 +32,11 @@
|
|
|
32
32
|
"svelte-github-corner": "^0.1.0",
|
|
33
33
|
"svelte-preprocess": "^4.10.6",
|
|
34
34
|
"svelte-toc": "^0.4.0",
|
|
35
|
-
"svelte2tsx": "^0.5.
|
|
35
|
+
"svelte2tsx": "^0.5.18",
|
|
36
36
|
"tslib": "^2.4.0",
|
|
37
37
|
"typescript": "^4.8.3",
|
|
38
|
-
"vite": "^3.1.
|
|
39
|
-
"vitest": "^0.23.
|
|
38
|
+
"vite": "^3.1.3",
|
|
39
|
+
"vitest": "^0.23.4"
|
|
40
40
|
},
|
|
41
41
|
"keywords": [
|
|
42
42
|
"svelte",
|
package/readme.md
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
[](https://app.netlify.com/sites/svelte-multiselect/deploys)
|
|
10
10
|
[](https://npmjs.com/package/svelte-multiselect)
|
|
11
11
|
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
12
|
-
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
12
|
+
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
13
13
|
[](https://stackblitz.com/github/janosh/svelte-multiselect)
|
|
14
14
|
|
|
15
15
|
</h4>
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
- **v6.0.0** The prop `showOptions` which controls whether the list of dropdown options is currently open or closed was renamed to `open`. See [PR 103](https://github.com/janosh/svelte-multiselect/pull/103).
|
|
41
41
|
- **v6.0.1** The prop `disabledTitle` which sets the title of the `<MultiSelect>` `<input>` node if in `disabled` mode was renamed to `disabledInputTitle`. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
|
|
42
42
|
- **v6.0.1** The default margin of `1em 0` on the wrapper `div.multiselect` was removed. Instead, there is now a new CSS variable `--sms-margin`. Set it to `--sms-margin: 1em 0;` to restore the old appearance. See [PR 105](https://github.com/janosh/svelte-multiselect/pull/105).
|
|
43
|
+
- **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. See [PR 120](https://github.com/janosh/svelte-multiselect/pull/120).
|
|
43
44
|
|
|
44
45
|
## Installation
|
|
45
46
|
|
|
@@ -97,7 +98,8 @@ import type { Option } from 'svelte-multiselect'
|
|
|
97
98
|
allowUserOptions: boolean | 'append' = false
|
|
98
99
|
```
|
|
99
100
|
|
|
100
|
-
Whether users
|
|
101
|
+
Whether users can enter values that are 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
|
+
If `allowUserOptions` is `true` or `'append'` then the type `object | number | string` of entered value is determined from the first option of the list to keep type homogeneity.
|
|
101
103
|
|
|
102
104
|
1. ```ts
|
|
103
105
|
autocomplete: string = `off`
|
|
@@ -287,11 +289,16 @@ import type { Option } from 'svelte-multiselect'
|
|
|
287
289
|
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.
|
|
288
290
|
|
|
289
291
|
1. ```ts
|
|
290
|
-
|
|
291
|
-
options.length > 0 ? (typeof options[0] as 'object' | 'string' | 'number') : 'string'
|
|
292
|
+
inputmode: string = ``
|
|
292
293
|
```
|
|
293
294
|
|
|
294
|
-
|
|
295
|
+
The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow browsers to display an appropriate virtual keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details.
|
|
296
|
+
|
|
297
|
+
1. ```ts
|
|
298
|
+
pattern: string = ``
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
The pattern attribute specifies a regular expression which the input's value must match. If a non-null value doesn't match the `pattern` regex, the read-only `patternMismatch` property will be `true`. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Attributes/pattern) for details.
|
|
295
302
|
|
|
296
303
|
## Slots
|
|
297
304
|
|
|
@@ -353,10 +360,16 @@ Example:
|
|
|
353
360
|
Triggers when an option is either added or removed, or all options are removed at once. `type` is one of `'add' | 'remove' | 'removeAll'` and payload will be `option: Option` or `options: Option[]`, respectively.
|
|
354
361
|
|
|
355
362
|
1. ```ts
|
|
356
|
-
on:
|
|
363
|
+
on:open={(event) => console.log(`Multiselect dropdown was opened by ${event}`)}
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
Triggers when the dropdown list of options appears. Event is the DOM's `FocusEvent`,`KeyboardEvent` or `ClickEvent` that initiated this Svelte `dispatch` event.
|
|
367
|
+
|
|
368
|
+
1. ```ts
|
|
369
|
+
on:close={(event) => console.log(`Multiselect dropdown was closed by ${event}`)}
|
|
357
370
|
```
|
|
358
371
|
|
|
359
|
-
Triggers when the
|
|
372
|
+
Triggers when the dropdown list of options disappears. Event is the DOM's `FocusEvent`, `KeyboardEvent` or `ClickEvent` that initiated this Svelte `dispatch` event.
|
|
360
373
|
|
|
361
374
|
For example, here's how you might annoy your users with an alert every time one or more options are added or removed:
|
|
362
375
|
|
|
@@ -372,6 +385,15 @@ For example, here's how you might annoy your users with an alert every time one
|
|
|
372
385
|
|
|
373
386
|
> Note: Depending on the data passed to the component the `options(s)` payload will either be objects or simple strings/numbers.
|
|
374
387
|
|
|
388
|
+
This component also forwards many DOM events from the `<input>` node: `blur`, `change`, `click`, `keydown`, `keyup`, `mousedown`, `mouseenter`, `mouseleave`, `touchcancel`, `touchend`, `touchmove`, `touchstart`. You can register listeners for these just like for the above [Svelte `dispatch` events](https://svelte.dev/tutorial/component-events):
|
|
389
|
+
|
|
390
|
+
```svelte
|
|
391
|
+
<MultiSelect
|
|
392
|
+
options={[1, 2, 3]}
|
|
393
|
+
on:keyup={(event) => console.log('key', event.target.value)}
|
|
394
|
+
/>
|
|
395
|
+
```
|
|
396
|
+
|
|
375
397
|
## TypeScript
|
|
376
398
|
|
|
377
399
|
TypeScript users can import the types used for internal type safety:
|
|
@@ -486,9 +508,9 @@ This simplified version of the DOM structure of the component shows where these
|
|
|
486
508
|
</div>
|
|
487
509
|
```
|
|
488
510
|
|
|
489
|
-
###
|
|
511
|
+
### With global CSS
|
|
490
512
|
|
|
491
|
-
|
|
513
|
+
Odd as it may seem, you get the most fine-grained control over the styling of every part of this component by using the following `:global()` CSS selectors. `ul.selected` is the list of currently selected options rendered inside the component's input whereas `ul.options` is the list of available options that slides out when the component is in its `open` state. See also [simplified DOM structure](#styling).
|
|
492
514
|
|
|
493
515
|
```css
|
|
494
516
|
:global(div.multiselect) {
|