svelte-multiselect 9.0.0 → 10.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/dist/CircleSpinner.svelte.d.ts +2 -2
- package/dist/CmdPalette.svelte +1 -3
- package/dist/CmdPalette.svelte.d.ts +2 -2
- package/dist/MultiSelect.svelte +70 -37
- package/dist/MultiSelect.svelte.d.ts +19 -6
- package/dist/Wiggle.svelte.d.ts +2 -2
- package/dist/icons/ChevronExpand.svelte.d.ts +2 -2
- package/dist/icons/Cross.svelte.d.ts +2 -2
- package/dist/icons/Disabled.svelte.d.ts +2 -2
- package/dist/icons/Octocat.svelte.d.ts +2 -2
- package/dist/types.d.ts +5 -0
- package/dist/utils.d.ts +6 -0
- package/dist/utils.js +30 -0
- package/package.json +23 -22
- package/readme.md +36 -15
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
color?: string | undefined;
|
|
@@ -13,6 +13,6 @@ declare const __propDef: {
|
|
|
13
13
|
export type CircleSpinnerProps = typeof __propDef.props;
|
|
14
14
|
export type CircleSpinnerEvents = typeof __propDef.events;
|
|
15
15
|
export type CircleSpinnerSlots = typeof __propDef.slots;
|
|
16
|
-
export default class CircleSpinner extends
|
|
16
|
+
export default class CircleSpinner extends SvelteComponent<CircleSpinnerProps, CircleSpinnerEvents, CircleSpinnerSlots> {
|
|
17
17
|
}
|
|
18
18
|
export {};
|
package/dist/CmdPalette.svelte
CHANGED
|
@@ -1,6 +1,4 @@
|
|
|
1
|
-
<script
|
|
2
|
-
// https://github.com/sveltejs/eslint-plugin-svelte3/issues/201
|
|
3
|
-
import { tick } from 'svelte';
|
|
1
|
+
<script>import { tick } from 'svelte';
|
|
4
2
|
import { fade } from 'svelte/transition';
|
|
5
3
|
import Select from './MultiSelect.svelte';
|
|
6
4
|
export let actions;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
[x: string]: any;
|
|
@@ -26,6 +26,6 @@ declare const __propDef: {
|
|
|
26
26
|
export type CmdPaletteProps = typeof __propDef.props;
|
|
27
27
|
export type CmdPaletteEvents = typeof __propDef.events;
|
|
28
28
|
export type CmdPaletteSlots = typeof __propDef.slots;
|
|
29
|
-
export default class CmdPalette extends
|
|
29
|
+
export default class CmdPalette extends SvelteComponent<CmdPaletteProps, CmdPaletteEvents, CmdPaletteSlots> {
|
|
30
30
|
}
|
|
31
31
|
export {};
|
package/dist/MultiSelect.svelte
CHANGED
|
@@ -3,6 +3,7 @@ import { flip } from 'svelte/animate';
|
|
|
3
3
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
4
|
import Wiggle from './Wiggle.svelte';
|
|
5
5
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
6
|
+
import { get_label, get_style } from './utils';
|
|
6
7
|
export let activeIndex = null;
|
|
7
8
|
export let activeOption = null;
|
|
8
9
|
export let createOptionMsg = `Create this option...`;
|
|
@@ -14,15 +15,16 @@ export let breakpoint = 800; // any screen with more horizontal pixels is consid
|
|
|
14
15
|
export let defaultDisabledTitle = `This option is disabled`;
|
|
15
16
|
export let disabled = false;
|
|
16
17
|
export let disabledInputTitle = `This input is disabled`;
|
|
17
|
-
// case-insensitive equality comparison after string coercion (looking only at the `label` key of object options)
|
|
18
18
|
// prettier-ignore
|
|
19
|
-
export let duplicateFunc = (op1, op2) => `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase();
|
|
20
19
|
export let duplicateOptionMsg = `This option is already selected`;
|
|
21
20
|
export let duplicates = false; // whether to allow duplicate options
|
|
22
|
-
|
|
21
|
+
// takes two options and returns true if they are equal
|
|
22
|
+
// case-insensitive equality comparison after string coercion and looks only at the `label` key of object options by default
|
|
23
|
+
export let key = (opt) => `${get_label(opt)}`.toLowerCase();
|
|
24
|
+
export let filterFunc = (opt, searchText) => {
|
|
23
25
|
if (!searchText)
|
|
24
26
|
return true;
|
|
25
|
-
return `${get_label(
|
|
27
|
+
return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase());
|
|
26
28
|
};
|
|
27
29
|
export let focusInputOnSelect = `desktop`;
|
|
28
30
|
export let form_input = null;
|
|
@@ -37,6 +39,7 @@ export let liOptionClass = ``;
|
|
|
37
39
|
export let liSelectedClass = ``;
|
|
38
40
|
export let loading = false;
|
|
39
41
|
export let matchingOptions = [];
|
|
42
|
+
export let maxOptions = undefined;
|
|
40
43
|
export let maxSelect = null; // null means there is no upper limit for selected.length
|
|
41
44
|
export let maxSelectMsg = (current, max) => (max > 1 ? `${current}/${max}` : ``);
|
|
42
45
|
export let maxSelectMsgClass = ``;
|
|
@@ -56,27 +59,27 @@ export let required = false;
|
|
|
56
59
|
export let resetFilterOnAdd = true;
|
|
57
60
|
export let searchText = ``;
|
|
58
61
|
export let selected = options
|
|
59
|
-
?.filter((
|
|
60
|
-
.slice(0, maxSelect ?? undefined) ?? [];
|
|
62
|
+
?.filter((opt) => opt instanceof Object && opt?.preselected)
|
|
63
|
+
.slice(0, maxSelect ?? undefined) ?? []; // don't allow more than maxSelect preselected options
|
|
61
64
|
export let sortSelected = false;
|
|
62
65
|
export let selectedOptionsDraggable = !sortSelected;
|
|
63
66
|
export let ulOptionsClass = ``;
|
|
64
67
|
export let ulSelectedClass = ``;
|
|
65
68
|
export let value = null;
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
return op;
|
|
69
|
+
const selected_to_value = (selected) => {
|
|
70
|
+
value = maxSelect === 1 ? selected[0] ?? null : selected;
|
|
71
|
+
};
|
|
72
|
+
const value_to_selected = (value) => {
|
|
73
|
+
if (maxSelect === 1)
|
|
74
|
+
selected = value ? [value] : [];
|
|
75
|
+
else
|
|
76
|
+
selected = value ?? [];
|
|
75
77
|
};
|
|
76
78
|
// if maxSelect=1, value is the single item in selected (or null if selected is empty)
|
|
77
79
|
// this solves both https://github.com/janosh/svelte-multiselect/issues/86 and
|
|
78
80
|
// https://github.com/janosh/svelte-multiselect/issues/136
|
|
79
|
-
$:
|
|
81
|
+
$: selected_to_value(selected);
|
|
82
|
+
$: value_to_selected(value);
|
|
80
83
|
let wiggle = false; // controls wiggle animation when user tries to exceed maxSelect
|
|
81
84
|
if (!(options?.length > 0)) {
|
|
82
85
|
if (allowUserOptions || loading || disabled || allowEmpty) {
|
|
@@ -108,12 +111,17 @@ if (allowUserOptions && !createOptionMsg && createOptionMsg !== null) {
|
|
|
108
111
|
console.error(`MultiSelect has allowUserOptions=${allowUserOptions} but createOptionMsg=${createOptionMsg} is falsy. ` +
|
|
109
112
|
`This prevents the "Add option" <span> from showing up, resulting in a confusing user experience.`);
|
|
110
113
|
}
|
|
114
|
+
if (maxOptions &&
|
|
115
|
+
(typeof maxOptions != `number` || maxOptions < 0 || maxOptions % 1 != 0)) {
|
|
116
|
+
console.error(`MultiSelect's maxOptions must be undefined or a positive integer, got ${maxOptions}`);
|
|
117
|
+
}
|
|
111
118
|
const dispatch = createEventDispatcher();
|
|
112
119
|
let option_msg_is_active = false; // controls active state of <li>{createOptionMsg}</li>
|
|
113
120
|
let window_width;
|
|
114
121
|
// options matching the current search text
|
|
115
|
-
$: matchingOptions = options.filter((
|
|
116
|
-
|
|
122
|
+
$: matchingOptions = options.filter((opt) => filterFunc(opt, searchText) &&
|
|
123
|
+
// remove already selected options from dropdown list unless duplicate selections are allowed
|
|
124
|
+
(!selected.map(key).includes(key(opt)) || duplicates));
|
|
117
125
|
// raise if matchingOptions[activeIndex] does not yield a value
|
|
118
126
|
if (activeIndex !== null && !matchingOptions[activeIndex]) {
|
|
119
127
|
throw `Run time error, activeIndex=${activeIndex} is out of bounds, matchingOptions.length=${matchingOptions.length}`;
|
|
@@ -127,7 +135,7 @@ function add(option, event) {
|
|
|
127
135
|
if (!isNaN(Number(option)) && typeof selected.map(get_label)[0] === `number`) {
|
|
128
136
|
option = Number(option); // convert to number if possible
|
|
129
137
|
}
|
|
130
|
-
const is_duplicate = selected.
|
|
138
|
+
const is_duplicate = selected.map(key).includes(key(option));
|
|
131
139
|
if ((maxSelect === null || maxSelect === 1 || selected.length < maxSelect) &&
|
|
132
140
|
(duplicates || !is_duplicate)) {
|
|
133
141
|
if (!options.includes(option) && // first check if we find option in the options list
|
|
@@ -194,7 +202,7 @@ function add(option, event) {
|
|
|
194
202
|
function remove(to_remove) {
|
|
195
203
|
if (selected.length === 0)
|
|
196
204
|
return;
|
|
197
|
-
const idx = selected.findIndex((
|
|
205
|
+
const idx = selected.findIndex((opt) => key(opt) === key(to_remove));
|
|
198
206
|
let [option] = selected.splice(idx, 1); // remove option from selected list
|
|
199
207
|
if (option === undefined && allowUserOptions) {
|
|
200
208
|
// if option with label could not be found but allowUserOptions is truthy,
|
|
@@ -408,6 +416,7 @@ function highlight_matching_options(event) {
|
|
|
408
416
|
role="searchbox"
|
|
409
417
|
tabindex="-1"
|
|
410
418
|
>
|
|
419
|
+
<!-- form control input invisible to the user, only purpose is to abort form submission if this component fails data validation -->
|
|
411
420
|
<!-- bind:value={selected} prevents form submission if required prop is true and no options are selected -->
|
|
412
421
|
<input
|
|
413
422
|
{name}
|
|
@@ -435,7 +444,7 @@ function highlight_matching_options(event) {
|
|
|
435
444
|
<ExpandIcon width="15px" style="min-width: 1em; padding: 0 1pt; cursor: pointer;" />
|
|
436
445
|
</slot>
|
|
437
446
|
<ul class="selected {ulSelectedClass}" aria-label="selected options">
|
|
438
|
-
{#each selected as option, idx (option)}
|
|
447
|
+
{#each selected as option, idx (duplicates ? [key(option), idx] : key(option))}
|
|
439
448
|
<li
|
|
440
449
|
class={liSelectedClass}
|
|
441
450
|
role="option"
|
|
@@ -447,6 +456,7 @@ function highlight_matching_options(event) {
|
|
|
447
456
|
on:dragenter={() => (drag_idx = idx)}
|
|
448
457
|
on:dragover|preventDefault
|
|
449
458
|
class:active={drag_idx === idx}
|
|
459
|
+
style={get_style(option, `selected`)}
|
|
450
460
|
>
|
|
451
461
|
<!-- on:dragover|preventDefault needed for the drop to succeed https://stackoverflow.com/a/31085796 -->
|
|
452
462
|
<slot name="selected" {option} {idx}>
|
|
@@ -504,6 +514,16 @@ function highlight_matching_options(event) {
|
|
|
504
514
|
on:touchstart
|
|
505
515
|
/>
|
|
506
516
|
<!-- the above on:* lines forward potentially useful DOM events -->
|
|
517
|
+
<slot
|
|
518
|
+
name="after-input"
|
|
519
|
+
{selected}
|
|
520
|
+
{disabled}
|
|
521
|
+
{invalid}
|
|
522
|
+
{id}
|
|
523
|
+
{placeholder}
|
|
524
|
+
{open}
|
|
525
|
+
{required}
|
|
526
|
+
/>
|
|
507
527
|
</ul>
|
|
508
528
|
{#if loading}
|
|
509
529
|
<slot name="spinner">
|
|
@@ -537,7 +557,7 @@ function highlight_matching_options(event) {
|
|
|
537
557
|
{/if}
|
|
538
558
|
{/if}
|
|
539
559
|
|
|
540
|
-
<!-- only render options dropdown if options or searchText is not empty needed to avoid briefly flashing empty dropdown -->
|
|
560
|
+
<!-- only render options dropdown if options or searchText is not empty (needed to avoid briefly flashing empty dropdown) -->
|
|
541
561
|
{#if (searchText && noMatchingOptionsMsg) || options?.length > 0}
|
|
542
562
|
<ul
|
|
543
563
|
class:hidden={!open}
|
|
@@ -548,7 +568,7 @@ function highlight_matching_options(event) {
|
|
|
548
568
|
aria-disabled={disabled ? `true` : null}
|
|
549
569
|
bind:this={ul_options}
|
|
550
570
|
>
|
|
551
|
-
{#each matchingOptions as option, idx}
|
|
571
|
+
{#each matchingOptions.slice(0, Math.max(0, maxOptions ?? 0) || Infinity) as option, idx}
|
|
552
572
|
{@const {
|
|
553
573
|
label,
|
|
554
574
|
disabled = null,
|
|
@@ -579,6 +599,7 @@ function highlight_matching_options(event) {
|
|
|
579
599
|
on:blur={() => (activeIndex = null)}
|
|
580
600
|
role="option"
|
|
581
601
|
aria-selected="false"
|
|
602
|
+
style={get_style(option, `option`)}
|
|
582
603
|
>
|
|
583
604
|
<slot name="option" {option} {idx}>
|
|
584
605
|
<slot {option} {idx}>
|
|
@@ -590,16 +611,25 @@ function highlight_matching_options(event) {
|
|
|
590
611
|
</slot>
|
|
591
612
|
</slot>
|
|
592
613
|
</li>
|
|
593
|
-
{
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
{@const
|
|
598
|
-
|
|
599
|
-
|
|
614
|
+
{/each}
|
|
615
|
+
{#if searchText}
|
|
616
|
+
{@const text_input_is_duplicate = selected.map(get_label).includes(searchText)}
|
|
617
|
+
{@const is_dupe = !duplicates && text_input_is_duplicate && `dupe`}
|
|
618
|
+
{@const can_create = allowUserOptions && createOptionMsg && `create`}
|
|
619
|
+
{@const no_match =
|
|
620
|
+
matchingOptions?.length == 0 && noMatchingOptionsMsg && `no-match`}
|
|
621
|
+
{@const msgType = is_dupe || can_create || no_match}
|
|
622
|
+
{#if msgType}
|
|
623
|
+
{@const msg = {
|
|
624
|
+
dupe: duplicateOptionMsg,
|
|
625
|
+
create: createOptionMsg,
|
|
626
|
+
'no-match': noMatchingOptionsMsg,
|
|
627
|
+
}[msgType]}
|
|
600
628
|
<li
|
|
601
629
|
on:mousedown|stopPropagation
|
|
602
|
-
on:mouseup|stopPropagation={(event) =>
|
|
630
|
+
on:mouseup|stopPropagation={(event) => {
|
|
631
|
+
if (allowUserOptions) add(searchText, event)
|
|
632
|
+
}}
|
|
603
633
|
title={createOptionMsg}
|
|
604
634
|
class:active={option_msg_is_active}
|
|
605
635
|
on:mouseover={() => (option_msg_is_active = true)}
|
|
@@ -609,15 +639,18 @@ function highlight_matching_options(event) {
|
|
|
609
639
|
role="option"
|
|
610
640
|
aria-selected="false"
|
|
611
641
|
class="user-msg"
|
|
642
|
+
style:cursor={{
|
|
643
|
+
dupe: `not-allowed`,
|
|
644
|
+
create: `pointer`,
|
|
645
|
+
'no-match': `default`,
|
|
646
|
+
}[msgType]}
|
|
612
647
|
>
|
|
613
|
-
{msg}
|
|
648
|
+
<slot name="user-msg" {searchText} {msgType} {msg}>
|
|
649
|
+
{msg}
|
|
650
|
+
</slot>
|
|
614
651
|
</li>
|
|
615
|
-
{:else if noMatchingOptionsMsg}
|
|
616
|
-
<!-- use span to not have cursor: pointer -->
|
|
617
|
-
<span class="user-msg">{noMatchingOptionsMsg}</span>
|
|
618
652
|
{/if}
|
|
619
|
-
|
|
620
|
-
{/each}
|
|
653
|
+
{/if}
|
|
621
654
|
</ul>
|
|
622
655
|
{/if}
|
|
623
656
|
</div>
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
2
|
import type { MultiSelectEvents, Option as T } from './types';
|
|
3
3
|
declare class __sveltets_Render<Option extends T> {
|
|
4
4
|
props(): {
|
|
@@ -13,10 +13,10 @@ declare class __sveltets_Render<Option extends T> {
|
|
|
13
13
|
defaultDisabledTitle?: string | undefined;
|
|
14
14
|
disabled?: boolean | undefined;
|
|
15
15
|
disabledInputTitle?: string | undefined;
|
|
16
|
-
duplicateFunc?: ((op1: T, op2: T) => boolean) | undefined;
|
|
17
16
|
duplicateOptionMsg?: string | undefined;
|
|
18
17
|
duplicates?: boolean | undefined;
|
|
19
|
-
|
|
18
|
+
key?: ((opt: T) => unknown) | undefined;
|
|
19
|
+
filterFunc?: ((opt: Option, searchText: string) => boolean) | undefined;
|
|
20
20
|
focusInputOnSelect?: boolean | "desktop" | undefined;
|
|
21
21
|
form_input?: HTMLInputElement | null | undefined;
|
|
22
22
|
highlightMatches?: boolean | undefined;
|
|
@@ -30,6 +30,7 @@ declare class __sveltets_Render<Option extends T> {
|
|
|
30
30
|
liSelectedClass?: string | undefined;
|
|
31
31
|
loading?: boolean | undefined;
|
|
32
32
|
matchingOptions?: Option[] | undefined;
|
|
33
|
+
maxOptions?: number | undefined;
|
|
33
34
|
maxSelect?: number | null | undefined;
|
|
34
35
|
maxSelectMsg?: ((current: number, max: number) => string) | null | undefined;
|
|
35
36
|
maxSelectMsgClass?: string | undefined;
|
|
@@ -54,7 +55,6 @@ declare class __sveltets_Render<Option extends T> {
|
|
|
54
55
|
ulOptionsClass?: string | undefined;
|
|
55
56
|
ulSelectedClass?: string | undefined;
|
|
56
57
|
value?: Option | Option[] | null | undefined;
|
|
57
|
-
get_label?: ((op: T) => string | number) | undefined;
|
|
58
58
|
};
|
|
59
59
|
events(): MultiSelectEvents;
|
|
60
60
|
slots(): {
|
|
@@ -70,18 +70,31 @@ declare class __sveltets_Render<Option extends T> {
|
|
|
70
70
|
idx: any;
|
|
71
71
|
};
|
|
72
72
|
'remove-icon': {};
|
|
73
|
+
'after-input': {
|
|
74
|
+
selected: Option[];
|
|
75
|
+
disabled: boolean;
|
|
76
|
+
invalid: boolean;
|
|
77
|
+
id: string | null;
|
|
78
|
+
placeholder: string | null;
|
|
79
|
+
open: boolean;
|
|
80
|
+
required: number | boolean;
|
|
81
|
+
};
|
|
73
82
|
spinner: {};
|
|
74
83
|
'disabled-icon': {};
|
|
75
84
|
option: {
|
|
76
85
|
option: Option;
|
|
77
86
|
idx: any;
|
|
78
87
|
};
|
|
88
|
+
'user-msg': {
|
|
89
|
+
searchText: string;
|
|
90
|
+
msgType: any;
|
|
91
|
+
msg: any;
|
|
92
|
+
};
|
|
79
93
|
};
|
|
80
94
|
}
|
|
81
95
|
export type MultiSelectProps<Option extends T> = ReturnType<__sveltets_Render<Option>['props']>;
|
|
82
96
|
export type MultiSelectEvents<Option extends T> = ReturnType<__sveltets_Render<Option>['events']>;
|
|
83
97
|
export type MultiSelectSlots<Option extends T> = ReturnType<__sveltets_Render<Option>['slots']>;
|
|
84
|
-
export default class MultiSelect<Option extends T> extends
|
|
85
|
-
get get_label(): (op: T) => string | number;
|
|
98
|
+
export default class MultiSelect<Option extends T> extends SvelteComponent<MultiSelectProps<Option>, MultiSelectEvents<Option>, MultiSelectSlots<Option>> {
|
|
86
99
|
}
|
|
87
100
|
export {};
|
package/dist/Wiggle.svelte.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { SvelteComponent } from "svelte";
|
|
2
2
|
declare const __propDef: {
|
|
3
3
|
props: {
|
|
4
4
|
wiggle?: boolean | undefined;
|
|
@@ -20,6 +20,6 @@ declare const __propDef: {
|
|
|
20
20
|
export type WiggleProps = typeof __propDef.props;
|
|
21
21
|
export type WiggleEvents = typeof __propDef.events;
|
|
22
22
|
export type WiggleSlots = typeof __propDef.slots;
|
|
23
|
-
export default class Wiggle extends
|
|
23
|
+
export default class Wiggle extends SvelteComponent<WiggleProps, WiggleEvents, WiggleSlots> {
|
|
24
24
|
}
|
|
25
25
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @typedef {typeof __propDef.props} ChevronExpandProps */
|
|
2
2
|
/** @typedef {typeof __propDef.events} ChevronExpandEvents */
|
|
3
3
|
/** @typedef {typeof __propDef.slots} ChevronExpandSlots */
|
|
4
|
-
export default class ChevronExpand extends
|
|
4
|
+
export default class ChevronExpand extends SvelteComponent<{
|
|
5
5
|
[x: string]: any;
|
|
6
6
|
}, {
|
|
7
7
|
[evt: string]: CustomEvent<any>;
|
|
@@ -10,7 +10,7 @@ export default class ChevronExpand extends SvelteComponentTyped<{
|
|
|
10
10
|
export type ChevronExpandProps = typeof __propDef.props;
|
|
11
11
|
export type ChevronExpandEvents = typeof __propDef.events;
|
|
12
12
|
export type ChevronExpandSlots = typeof __propDef.slots;
|
|
13
|
-
import {
|
|
13
|
+
import { SvelteComponent } from "svelte";
|
|
14
14
|
declare const __propDef: {
|
|
15
15
|
props: {
|
|
16
16
|
[x: string]: any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @typedef {typeof __propDef.props} CrossProps */
|
|
2
2
|
/** @typedef {typeof __propDef.events} CrossEvents */
|
|
3
3
|
/** @typedef {typeof __propDef.slots} CrossSlots */
|
|
4
|
-
export default class Cross extends
|
|
4
|
+
export default class Cross extends SvelteComponent<{
|
|
5
5
|
[x: string]: any;
|
|
6
6
|
}, {
|
|
7
7
|
[evt: string]: CustomEvent<any>;
|
|
@@ -10,7 +10,7 @@ export default class Cross extends SvelteComponentTyped<{
|
|
|
10
10
|
export type CrossProps = typeof __propDef.props;
|
|
11
11
|
export type CrossEvents = typeof __propDef.events;
|
|
12
12
|
export type CrossSlots = typeof __propDef.slots;
|
|
13
|
-
import {
|
|
13
|
+
import { SvelteComponent } from "svelte";
|
|
14
14
|
declare const __propDef: {
|
|
15
15
|
props: {
|
|
16
16
|
[x: string]: any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @typedef {typeof __propDef.props} DisabledProps */
|
|
2
2
|
/** @typedef {typeof __propDef.events} DisabledEvents */
|
|
3
3
|
/** @typedef {typeof __propDef.slots} DisabledSlots */
|
|
4
|
-
export default class Disabled extends
|
|
4
|
+
export default class Disabled extends SvelteComponent<{
|
|
5
5
|
[x: string]: any;
|
|
6
6
|
}, {
|
|
7
7
|
[evt: string]: CustomEvent<any>;
|
|
@@ -10,7 +10,7 @@ export default class Disabled extends SvelteComponentTyped<{
|
|
|
10
10
|
export type DisabledProps = typeof __propDef.props;
|
|
11
11
|
export type DisabledEvents = typeof __propDef.events;
|
|
12
12
|
export type DisabledSlots = typeof __propDef.slots;
|
|
13
|
-
import {
|
|
13
|
+
import { SvelteComponent } from "svelte";
|
|
14
14
|
declare const __propDef: {
|
|
15
15
|
props: {
|
|
16
16
|
[x: string]: any;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
/** @typedef {typeof __propDef.props} OctocatProps */
|
|
2
2
|
/** @typedef {typeof __propDef.events} OctocatEvents */
|
|
3
3
|
/** @typedef {typeof __propDef.slots} OctocatSlots */
|
|
4
|
-
export default class Octocat extends
|
|
4
|
+
export default class Octocat extends SvelteComponent<{
|
|
5
5
|
[x: string]: any;
|
|
6
6
|
}, {
|
|
7
7
|
[evt: string]: CustomEvent<any>;
|
|
@@ -10,7 +10,7 @@ export default class Octocat extends SvelteComponentTyped<{
|
|
|
10
10
|
export type OctocatProps = typeof __propDef.props;
|
|
11
11
|
export type OctocatEvents = typeof __propDef.events;
|
|
12
12
|
export type OctocatSlots = typeof __propDef.slots;
|
|
13
|
-
import {
|
|
13
|
+
import { SvelteComponent } from "svelte";
|
|
14
14
|
declare const __propDef: {
|
|
15
15
|
props: {
|
|
16
16
|
[x: string]: any;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
export type Option = string | number | ObjectOption;
|
|
2
|
+
export type OptionStyle = string | {
|
|
3
|
+
option: string;
|
|
4
|
+
selected: string;
|
|
5
|
+
};
|
|
2
6
|
export type ObjectOption = {
|
|
3
7
|
label: string | number;
|
|
4
8
|
value?: unknown;
|
|
@@ -7,6 +11,7 @@ export type ObjectOption = {
|
|
|
7
11
|
preselected?: boolean;
|
|
8
12
|
disabledTitle?: string;
|
|
9
13
|
selectedTitle?: string;
|
|
14
|
+
style?: OptionStyle;
|
|
10
15
|
[key: string]: unknown;
|
|
11
16
|
};
|
|
12
17
|
export type DispatchEvents<T = Option> = {
|
package/dist/utils.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import type { Option, OptionStyle } from './types';
|
|
2
|
+
export declare const get_label: (opt: Option) => string | number;
|
|
3
|
+
export declare function get_style(option: {
|
|
4
|
+
style?: OptionStyle;
|
|
5
|
+
[key: string]: unknown;
|
|
6
|
+
} | string | number, key?: 'selected' | 'option' | null): string | null | undefined;
|
package/dist/utils.js
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// get the label key from an option object or the option itself if it's a string or number
|
|
2
|
+
export const get_label = (opt) => {
|
|
3
|
+
if (opt instanceof Object) {
|
|
4
|
+
if (opt.label === undefined) {
|
|
5
|
+
console.error(`MultiSelect option ${JSON.stringify(opt)} is an object but has no label key`);
|
|
6
|
+
}
|
|
7
|
+
return opt.label;
|
|
8
|
+
}
|
|
9
|
+
return `${opt}`;
|
|
10
|
+
};
|
|
11
|
+
export function get_style(option, key = null) {
|
|
12
|
+
if (!option?.style)
|
|
13
|
+
return null;
|
|
14
|
+
if (![`selected`, `option`, null].includes(key)) {
|
|
15
|
+
console.error(`MultiSelect: Invalid key=${key} for get_style`);
|
|
16
|
+
return;
|
|
17
|
+
}
|
|
18
|
+
if (typeof option == `object` && option.style) {
|
|
19
|
+
if (typeof option.style == `string`) {
|
|
20
|
+
return option.style;
|
|
21
|
+
}
|
|
22
|
+
if (typeof option.style == `object`) {
|
|
23
|
+
if (key && key in option.style)
|
|
24
|
+
return option.style[key];
|
|
25
|
+
else {
|
|
26
|
+
console.error(`Invalid style object for option=${JSON.stringify(option)}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
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
|
+
"version": "10.1.0",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "./dist/index.js",
|
|
11
11
|
"bugs": "https://github.com/janosh/svelte-multiselect/issues",
|
|
@@ -23,37 +23,37 @@
|
|
|
23
23
|
"update-coverage": "vitest tests/unit --run --coverage && npx istanbul-badges-readme"
|
|
24
24
|
},
|
|
25
25
|
"dependencies": {
|
|
26
|
-
"svelte": "^4.0.
|
|
26
|
+
"svelte": "^4.0.5"
|
|
27
27
|
},
|
|
28
28
|
"devDependencies": {
|
|
29
|
-
"@iconify/svelte": "^3.1.
|
|
30
|
-
"@playwright/test": "^1.
|
|
29
|
+
"@iconify/svelte": "^3.1.4",
|
|
30
|
+
"@playwright/test": "^1.36.1",
|
|
31
31
|
"@sveltejs/adapter-static": "^2.0.2",
|
|
32
|
-
"@sveltejs/kit": "^1.
|
|
33
|
-
"@sveltejs/package": "2.0
|
|
34
|
-
"@sveltejs/vite-plugin-svelte": "
|
|
35
|
-
"@typescript-eslint/eslint-plugin": "^
|
|
36
|
-
"@typescript-eslint/parser": "^
|
|
37
|
-
"@vitest/coverage-
|
|
38
|
-
"eslint": "^8.
|
|
39
|
-
"eslint-plugin-
|
|
32
|
+
"@sveltejs/kit": "^1.22.3",
|
|
33
|
+
"@sveltejs/package": "2.2.0",
|
|
34
|
+
"@sveltejs/vite-plugin-svelte": "2.4.2",
|
|
35
|
+
"@typescript-eslint/eslint-plugin": "^6.1.0",
|
|
36
|
+
"@typescript-eslint/parser": "^6.1.0",
|
|
37
|
+
"@vitest/coverage-v8": "^0.33.0",
|
|
38
|
+
"eslint": "^8.45.0",
|
|
39
|
+
"eslint-plugin-svelte": "^2.32.2",
|
|
40
40
|
"hastscript": "^7.2.0",
|
|
41
41
|
"highlight.js": "^11.8.0",
|
|
42
|
-
"jsdom": "^22.
|
|
43
|
-
"mdsvex": "^0.
|
|
42
|
+
"jsdom": "^22.1.0",
|
|
43
|
+
"mdsvex": "^0.11.0",
|
|
44
44
|
"mdsvexamples": "^0.3.3",
|
|
45
45
|
"prettier": "^2.8.8",
|
|
46
|
-
"prettier-plugin-svelte": "^2.10.
|
|
46
|
+
"prettier-plugin-svelte": "^2.10.1",
|
|
47
47
|
"rehype-autolink-headings": "^6.1.1",
|
|
48
48
|
"rehype-slug": "^5.1.0",
|
|
49
|
-
"svelte-check": "^3.4.
|
|
50
|
-
"svelte-preprocess": "^5.0.
|
|
49
|
+
"svelte-check": "^3.4.6",
|
|
50
|
+
"svelte-preprocess": "^5.0.4",
|
|
51
51
|
"svelte-toc": "^0.5.5",
|
|
52
|
-
"svelte-zoo": "^0.4.
|
|
53
|
-
"svelte2tsx": "^0.6.
|
|
54
|
-
"typescript": "5.
|
|
55
|
-
"vite": "^4.
|
|
56
|
-
"vitest": "^0.
|
|
52
|
+
"svelte-zoo": "^0.4.9",
|
|
53
|
+
"svelte2tsx": "^0.6.19",
|
|
54
|
+
"typescript": "5.1.6",
|
|
55
|
+
"vite": "^4.4.4",
|
|
56
|
+
"vitest": "^0.33.0"
|
|
57
57
|
},
|
|
58
58
|
"keywords": [
|
|
59
59
|
"svelte",
|
|
@@ -77,6 +77,7 @@
|
|
|
77
77
|
"default": "./dist/index.js"
|
|
78
78
|
}
|
|
79
79
|
},
|
|
80
|
+
"types": "./dist/index.d.ts",
|
|
80
81
|
"files": [
|
|
81
82
|
"dist"
|
|
82
83
|
]
|
package/readme.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
[](https://github.com/janosh/svelte-multiselect/actions/workflows/test.yml)
|
|
9
9
|
[](https://github.com/janosh/svelte-multiselect/actions/workflows/gh-pages.yml)
|
|
10
10
|
[](https://npmjs.com/package/svelte-multiselect)
|
|
11
|
-
[](https://github.com/sveltejs/svelte/blob/master/CHANGELOG.md)
|
|
11
|
+
[](https://github.com/sveltejs/svelte/blob/master/packages/svelte/CHANGELOG.md)
|
|
12
12
|
[](https://svelte.dev/repl/a5a14b8f15d64cb083b567292480db05)
|
|
13
13
|
[](https://stackblitz.com/github/janosh/svelte-multiselect)
|
|
14
14
|
|
|
@@ -59,6 +59,15 @@
|
|
|
59
59
|
</MultiSelect>
|
|
60
60
|
```
|
|
61
61
|
|
|
62
|
+
- **v10.0.0** (2023-06-23) `duplicateFunc()` renamed to `key` in [#238](https://github.com/janosh/svelte-multiselect/pull/238). Signature changed:
|
|
63
|
+
|
|
64
|
+
```diff
|
|
65
|
+
- duplicateFunc: (op1: T, op2: T) => boolean = (op1, op2) => `${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
|
|
66
|
+
+ key: (opt: T) => unknown = (opt) => `${get_label(opt)}`.toLowerCase()
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Rather than implementing custom equality in `duplicateFunc`, the `key` function is now expected to map options to a unique identifier. `key(op1) === key(op2)` should mean `op1` and `op2` are the same option. `key` can return any type but usually best to return primitives (`string`, `number`, ...) for Svelte keyed each blocks (see [#217](https://github.com/janosh/svelte-multiselect/pull/217)).
|
|
70
|
+
|
|
62
71
|
## 🔨   Installation
|
|
63
72
|
|
|
64
73
|
```sh
|
|
@@ -156,30 +165,29 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
156
165
|
|
|
157
166
|
Tooltip text to display on hover when the component is in `disabled` state.
|
|
158
167
|
|
|
159
|
-
<!-- prettier-ignore -->
|
|
160
168
|
1. ```ts
|
|
161
|
-
|
|
162
|
-
`${get_label(op1)}`.toLowerCase() === `${get_label(op2)}`.toLowerCase()
|
|
169
|
+
duplicates: boolean = false
|
|
163
170
|
```
|
|
164
171
|
|
|
165
|
-
|
|
172
|
+
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 `key` to customize when a pair of options is considered equal.
|
|
166
173
|
|
|
167
174
|
1. ```ts
|
|
168
|
-
|
|
175
|
+
duplicateOptionMsg: string = `This option is already selected`
|
|
169
176
|
```
|
|
170
177
|
|
|
171
|
-
|
|
178
|
+
Text to display to users when `allowUserOptions` is truthy and they try to create a new option that's already selected.
|
|
172
179
|
|
|
180
|
+
<!-- prettier-ignore -->
|
|
173
181
|
1. ```ts
|
|
174
|
-
|
|
182
|
+
key: (opt: T) => unknown = (opt) => `${get_label(opt)}`.toLowerCase()
|
|
175
183
|
```
|
|
176
184
|
|
|
177
|
-
|
|
185
|
+
A function that maps options to a value by which equality of options is determined. Defaults to mapping options to their lower-cased label. E.g. by default ``const opt1 = { label: `foo`, id: 1 }`` and ``const opt2 = { label: `foo`, id: 2 }`` are considered equal. If you want to consider them different, you can set `key` to e.g. `key={(opt) => opt.id}` or ``key={(opt) => `${opt.label}-${opt.id}}`` or even `key={JSON.stringify}`.
|
|
178
186
|
|
|
179
187
|
1. ```ts
|
|
180
|
-
filterFunc = (
|
|
188
|
+
filterFunc = (opt: Option, searchText: string): boolean => {
|
|
181
189
|
if (!searchText) return true
|
|
182
|
-
return `${get_label(
|
|
190
|
+
return `${get_label(opt)}`.toLowerCase().includes(searchText.toLowerCase())
|
|
183
191
|
}
|
|
184
192
|
```
|
|
185
193
|
|
|
@@ -219,7 +227,7 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
219
227
|
inputmode: string | null = null
|
|
220
228
|
```
|
|
221
229
|
|
|
222
|
-
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.
|
|
230
|
+
The `inputmode` attribute hints at the type of data the user may enter. Values like `'numeric' | 'tel' | 'email'` allow mobile browsers to display an appropriate virtual on-screen keyboard. See [MDN](https://developer.mozilla.org/docs/Web/HTML/Global_attributes/inputmode) for details. If you want to suppress the on-screen keyboard to leave full-screen real estate for the dropdown list of options, set `inputmode="none"`.
|
|
223
231
|
|
|
224
232
|
1. ```ts
|
|
225
233
|
invalid: boolean = false
|
|
@@ -239,6 +247,12 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
239
247
|
|
|
240
248
|
List of options currently displayed to the user. Same as `options` unless the user entered `searchText` in which case this array contains only those options for which `filterFunc = (op: Option, searchText: string) => boolean` returned `true`.
|
|
241
249
|
|
|
250
|
+
1. ```ts
|
|
251
|
+
maxOptions: number | undefined = undefined
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Positive integer to limit the number of options displayed in the dropdown. `undefined` and 0 mean no limit.
|
|
255
|
+
|
|
242
256
|
1. ```ts
|
|
243
257
|
maxSelect: number | null = null
|
|
244
258
|
```
|
|
@@ -371,7 +385,7 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
371
385
|
value: Option | Option[] | null = null
|
|
372
386
|
```
|
|
373
387
|
|
|
374
|
-
If `maxSelect={1}`, `value` will be the single item in `selected` (or `null` if `selected` is empty). If `maxSelect != 1`, `maxSelect` and `selected` are equal. Warning: `value`
|
|
388
|
+
If `maxSelect={1}`, `value` will be the single item in `selected` (or `null` if `selected` is empty). If `maxSelect != 1`, `maxSelect` and `selected` are equal. Warning: Setting `value` does not rendered state on initial mount, meaning `bind:value` will update local variable `value` whenever internal component state changes but passing a `value` when component first mounts won't be reflected in UI. This is because the source of truth for rendering is `bind:selected`. `selected` is reactive to `value` internally but only on reassignment from initial value. Suggestions for better solutions than [#249](https://github.com/janosh/svelte-multiselect/issues/249) welcome!
|
|
375
389
|
|
|
376
390
|
## 🎰   Slots
|
|
377
391
|
|
|
@@ -383,8 +397,15 @@ Full list of props/bindable variables for this component. The `Option` type you
|
|
|
383
397
|
1. `slot="disabled-icon"`: Custom icon to display inside the input when in `disabled` state. Receives no props. Use an empty `<span slot="disabled-icon" />` or `div` to remove the default disabled icon.
|
|
384
398
|
1. `slot="expand-icon"`: Allows setting a custom icon to indicate to users that the Multiselect text input field is expandable into a dropdown list. Receives prop `open: boolean` which is true if the Multiselect dropdown is visible and false if it's hidden.
|
|
385
399
|
1. `slot="remove-icon"`: Custom icon to display as remove button. Will be used both by buttons to remove individual selected options and the 'remove all' button that clears all options at once. Receives no props.
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
1. `slot="user-msg"`: Displayed like a dropdown item when the list is empty and user is allowed to create custom options based on text input (or if the user's text input clashes with an existing option). `let:props`:
|
|
401
|
+
- `duplicateOptionMsg: string`: See [props](#🔣-props).
|
|
402
|
+
- `createOptionMsg: string`: See [props](#🔣-props).
|
|
403
|
+
- `textInputIsDuplicate: boolean`: Whether user has typed text that matches an already existing option.
|
|
404
|
+
- `searchText: string`: The text user typed into search input.
|
|
405
|
+
- `msg: string`: `duplicateOptionMsg` if user input is a duplicate else `createOptionMsg`.
|
|
406
|
+
1. `slot='after-input'`: ForPlaced after the search input. For arbitrary content like icons or temporary messages. Receives props `selected`, `disabled`, `invalid`, `id`, `placeholder`, `open`, `required`.
|
|
407
|
+
|
|
408
|
+
Example using several slots:
|
|
388
409
|
|
|
389
410
|
```svelte
|
|
390
411
|
<MultiSelect options={[`Red`, `Green`, `Blue`, `Yellow`, `Purple`]} let:idx let:option>
|