svelte-multiselect 7.0.0 → 7.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/MultiSelect.svelte +181 -42
- package/index.d.ts +0 -2
- package/index.js +2 -5
- package/package.json +2 -1
- package/readme.md +1 -1
package/MultiSelect.svelte
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
|
-
<script>import { createEventDispatcher } from 'svelte';
|
|
2
|
-
import { get_label, get_value } from './';
|
|
1
|
+
<script>import { createEventDispatcher, tick } from 'svelte';
|
|
3
2
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
3
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
5
4
|
import Wiggle from './Wiggle.svelte';
|
|
@@ -52,6 +51,10 @@ export let selectedValues = [];
|
|
|
52
51
|
export let sortSelected = false;
|
|
53
52
|
export let ulOptionsClass = ``;
|
|
54
53
|
export let ulSelectedClass = ``;
|
|
54
|
+
// get the label key from an option object or the option itself if it's a string or number
|
|
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);
|
|
55
58
|
// selected and _selected are identical except if maxSelect=1, selected will be the single item (or null)
|
|
56
59
|
// in _selected which will always be an array for easier component internals. selected then solves
|
|
57
60
|
// https://github.com/janosh/svelte-multiselect/issues/86
|
|
@@ -85,8 +88,8 @@ let add_option_msg_is_active = false; // controls active state of <li>{addOption
|
|
|
85
88
|
let window_width;
|
|
86
89
|
// formValue binds to input.form-control to prevent form submission if required
|
|
87
90
|
// prop is true and no options are selected
|
|
88
|
-
$:
|
|
89
|
-
$: if (
|
|
91
|
+
$: form_value = _selectedValues.join(`,`);
|
|
92
|
+
$: if (form_value)
|
|
90
93
|
invalid = false; // reset error status whenever component state changes
|
|
91
94
|
// options matching the current search text
|
|
92
95
|
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !_selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
|
|
@@ -128,8 +131,8 @@ function add(label, event) {
|
|
|
128
131
|
options = [...options, option];
|
|
129
132
|
}
|
|
130
133
|
searchText = ``; // reset search string on selection
|
|
131
|
-
if (
|
|
132
|
-
console.error(`MultiSelect: option with label ${label}
|
|
134
|
+
if ([``, undefined, null].includes(option)) {
|
|
135
|
+
console.error(`MultiSelect: encountered missing option with label ${label} (or option is poorly labeled)`);
|
|
133
136
|
return;
|
|
134
137
|
}
|
|
135
138
|
if (maxSelect === 1) {
|
|
@@ -204,7 +207,7 @@ async function handle_keydown(event) {
|
|
|
204
207
|
event.preventDefault(); // prevent enter key from triggering form submission
|
|
205
208
|
if (activeOption) {
|
|
206
209
|
const label = get_label(activeOption);
|
|
207
|
-
selectedLabels
|
|
210
|
+
selectedLabels?.includes(label) ? remove(label) : add(label, event);
|
|
208
211
|
searchText = ``;
|
|
209
212
|
}
|
|
210
213
|
else if (allowUserOptions && searchText.length > 0) {
|
|
@@ -241,14 +244,13 @@ async function handle_keydown(event) {
|
|
|
241
244
|
activeIndex = matchingOptions.length - 1;
|
|
242
245
|
if (autoScroll) {
|
|
243
246
|
// TODO This ugly timeout hack is needed to properly scroll element into view when wrapping
|
|
244
|
-
// around start/end of option list. Find a better solution than waiting 10 ms
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
}, 10);
|
|
247
|
+
// around start/end of option list. Find a better solution than waiting 10 ms.
|
|
248
|
+
await tick();
|
|
249
|
+
const li = document.querySelector(`ul.options > li.active`);
|
|
250
|
+
if (li) {
|
|
251
|
+
li.parentNode?.scrollIntoView?.({ block: `center` });
|
|
252
|
+
li.scrollIntoViewIfNeeded?.();
|
|
253
|
+
}
|
|
252
254
|
}
|
|
253
255
|
}
|
|
254
256
|
// on backspace key: remove last selected option
|
|
@@ -295,9 +297,10 @@ function on_click_outside(event) {
|
|
|
295
297
|
title={disabled ? disabledInputTitle : null}
|
|
296
298
|
aria-disabled={disabled ? `true` : null}
|
|
297
299
|
>
|
|
300
|
+
<!-- formValue binds to input.form-control to prevent form submission if required prop is true and no options are selected -->
|
|
298
301
|
<input
|
|
299
302
|
{required}
|
|
300
|
-
bind:value={
|
|
303
|
+
bind:value={form_value}
|
|
301
304
|
tabindex="-1"
|
|
302
305
|
aria-hidden="true"
|
|
303
306
|
aria-label="ignore this, used only to prevent form submission if select is required but empty"
|
|
@@ -456,8 +459,146 @@ function on_click_outside(event) {
|
|
|
456
459
|
{/if}
|
|
457
460
|
</div>
|
|
458
461
|
|
|
459
|
-
<style
|
|
460
|
-
:
|
|
462
|
+
<style>:where(div.multiselect) {
|
|
463
|
+
position: relative;
|
|
464
|
+
align-items: center;
|
|
465
|
+
display: flex;
|
|
466
|
+
cursor: text;
|
|
467
|
+
border: var(--sms-border, 1pt solid lightgray);
|
|
468
|
+
border-radius: var(--sms-border-radius, 3pt);
|
|
469
|
+
background: var(--sms-bg);
|
|
470
|
+
max-width: var(--sms-max-width);
|
|
471
|
+
padding: var(--sms-padding, 0 3pt);
|
|
472
|
+
color: var(--sms-text-color);
|
|
473
|
+
font-size: var(--sms-font-size, inherit);
|
|
474
|
+
min-height: var(--sms-min-height, 19pt);
|
|
475
|
+
margin: var(--sms-margin);
|
|
476
|
+
}
|
|
477
|
+
:where(div.multiselect).open {
|
|
478
|
+
/* increase z-index when open to ensure the dropdown of one <MultiSelect />
|
|
479
|
+
displays above that of another slightly below it on the page */
|
|
480
|
+
z-index: var(--sms-open-z-index, 4);
|
|
481
|
+
}
|
|
482
|
+
:where(div.multiselect):focus-within {
|
|
483
|
+
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
484
|
+
}
|
|
485
|
+
:where(div.multiselect).disabled {
|
|
486
|
+
background: var(--sms-disabled-bg, lightgray);
|
|
487
|
+
cursor: not-allowed;
|
|
488
|
+
}
|
|
489
|
+
:where(div.multiselect) > ul.selected {
|
|
490
|
+
display: flex;
|
|
491
|
+
flex: 1;
|
|
492
|
+
padding: 0;
|
|
493
|
+
margin: 0;
|
|
494
|
+
flex-wrap: wrap;
|
|
495
|
+
}
|
|
496
|
+
:where(div.multiselect) > ul.selected > li {
|
|
497
|
+
align-items: center;
|
|
498
|
+
border-radius: 3pt;
|
|
499
|
+
display: flex;
|
|
500
|
+
margin: 2pt;
|
|
501
|
+
line-height: normal;
|
|
502
|
+
transition: 0.3s;
|
|
503
|
+
white-space: nowrap;
|
|
504
|
+
background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
|
|
505
|
+
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
506
|
+
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
507
|
+
}
|
|
508
|
+
:where(div.multiselect) button {
|
|
509
|
+
border-radius: 50%;
|
|
510
|
+
display: flex;
|
|
511
|
+
transition: 0.2s;
|
|
512
|
+
color: inherit;
|
|
513
|
+
background: transparent;
|
|
514
|
+
border: none;
|
|
515
|
+
cursor: pointer;
|
|
516
|
+
outline: none;
|
|
517
|
+
padding: 0;
|
|
518
|
+
margin: 0 0 0 3pt; /* CSS reset */
|
|
519
|
+
}
|
|
520
|
+
:where(div.multiselect) button.remove-all {
|
|
521
|
+
margin: 0 3pt;
|
|
522
|
+
}
|
|
523
|
+
:where(div.multiselect) ul.selected > li button:hover,
|
|
524
|
+
:where(div.multiselect) button.remove-all:hover,
|
|
525
|
+
:where(div.multiselect) button:focus {
|
|
526
|
+
color: var(--sms-button-hover-color, lightskyblue);
|
|
527
|
+
}
|
|
528
|
+
:where(div.multiselect) input {
|
|
529
|
+
margin: auto 0; /* CSS reset */
|
|
530
|
+
padding: 0; /* CSS reset */
|
|
531
|
+
}
|
|
532
|
+
:where(div.multiselect) > ul.selected > li > input {
|
|
533
|
+
border: none;
|
|
534
|
+
outline: none;
|
|
535
|
+
background: none;
|
|
536
|
+
flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
|
|
537
|
+
min-width: 2em;
|
|
538
|
+
/* ensure input uses text color and not --sms-selected-text-color */
|
|
539
|
+
color: var(--sms-text-color);
|
|
540
|
+
font-size: inherit;
|
|
541
|
+
cursor: inherit; /* needed for disabled state */
|
|
542
|
+
border-radius: 0; /* reset ul.selected > li */
|
|
543
|
+
}
|
|
544
|
+
:where(div.multiselect) > ul.selected > li > input::placeholder {
|
|
545
|
+
padding-left: 5pt;
|
|
546
|
+
color: var(--sms-placeholder-color);
|
|
547
|
+
opacity: var(--sms-placeholder-opacity);
|
|
548
|
+
}
|
|
549
|
+
:where(div.multiselect) > input.form-control {
|
|
550
|
+
width: 2em;
|
|
551
|
+
position: absolute;
|
|
552
|
+
background: transparent;
|
|
553
|
+
border: none;
|
|
554
|
+
outline: none;
|
|
555
|
+
z-index: -1;
|
|
556
|
+
opacity: 0;
|
|
557
|
+
pointer-events: none;
|
|
558
|
+
}
|
|
559
|
+
:where(div.multiselect) > ul.options {
|
|
560
|
+
list-style: none;
|
|
561
|
+
padding: 4pt 0;
|
|
562
|
+
top: 100%;
|
|
563
|
+
left: 0;
|
|
564
|
+
width: 100%;
|
|
565
|
+
position: absolute;
|
|
566
|
+
border-radius: 1ex;
|
|
567
|
+
overflow: auto;
|
|
568
|
+
background: var(--sms-options-bg, white);
|
|
569
|
+
max-height: var(--sms-options-max-height, 50vh);
|
|
570
|
+
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
571
|
+
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
572
|
+
transition: all 0.2s;
|
|
573
|
+
}
|
|
574
|
+
:where(div.multiselect) > ul.options.hidden {
|
|
575
|
+
visibility: hidden;
|
|
576
|
+
opacity: 0;
|
|
577
|
+
transform: translateY(50px);
|
|
578
|
+
}
|
|
579
|
+
:where(div.multiselect) > ul.options > li {
|
|
580
|
+
padding: 3pt 2ex;
|
|
581
|
+
cursor: pointer;
|
|
582
|
+
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
583
|
+
}
|
|
584
|
+
:where(div.multiselect) > ul.options span {
|
|
585
|
+
padding: 3pt 2ex;
|
|
586
|
+
}
|
|
587
|
+
:where(div.multiselect) > ul.options > li.selected {
|
|
588
|
+
background: var(--sms-li-selected-bg);
|
|
589
|
+
color: var(--sms-li-selected-color);
|
|
590
|
+
}
|
|
591
|
+
:where(div.multiselect) > ul.options > li.active {
|
|
592
|
+
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
593
|
+
}
|
|
594
|
+
:where(div.multiselect) > ul.options > li.disabled {
|
|
595
|
+
cursor: not-allowed;
|
|
596
|
+
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
597
|
+
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
@supports not selector(:where(div.multiselect)) {
|
|
601
|
+
div.multiselect {
|
|
461
602
|
position: relative;
|
|
462
603
|
align-items: center;
|
|
463
604
|
display: flex;
|
|
@@ -472,27 +613,26 @@ function on_click_outside(event) {
|
|
|
472
613
|
min-height: var(--sms-min-height, 19pt);
|
|
473
614
|
margin: var(--sms-margin);
|
|
474
615
|
}
|
|
475
|
-
|
|
616
|
+
div.multiselect.open {
|
|
476
617
|
/* increase z-index when open to ensure the dropdown of one <MultiSelect />
|
|
477
618
|
displays above that of another slightly below it on the page */
|
|
478
619
|
z-index: var(--sms-open-z-index, 4);
|
|
479
620
|
}
|
|
480
|
-
|
|
621
|
+
div.multiselect:focus-within {
|
|
481
622
|
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
482
623
|
}
|
|
483
|
-
|
|
624
|
+
div.multiselect.disabled {
|
|
484
625
|
background: var(--sms-disabled-bg, lightgray);
|
|
485
626
|
cursor: not-allowed;
|
|
486
627
|
}
|
|
487
|
-
|
|
488
|
-
:where(div.multiselect > ul.selected) {
|
|
628
|
+
div.multiselect > ul.selected {
|
|
489
629
|
display: flex;
|
|
490
630
|
flex: 1;
|
|
491
631
|
padding: 0;
|
|
492
632
|
margin: 0;
|
|
493
633
|
flex-wrap: wrap;
|
|
494
634
|
}
|
|
495
|
-
|
|
635
|
+
div.multiselect > ul.selected > li {
|
|
496
636
|
align-items: center;
|
|
497
637
|
border-radius: 3pt;
|
|
498
638
|
display: flex;
|
|
@@ -504,7 +644,7 @@ function on_click_outside(event) {
|
|
|
504
644
|
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
505
645
|
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
506
646
|
}
|
|
507
|
-
|
|
647
|
+
div.multiselect button {
|
|
508
648
|
border-radius: 50%;
|
|
509
649
|
display: flex;
|
|
510
650
|
transition: 0.2s;
|
|
@@ -516,18 +656,19 @@ function on_click_outside(event) {
|
|
|
516
656
|
padding: 0;
|
|
517
657
|
margin: 0 0 0 3pt; /* CSS reset */
|
|
518
658
|
}
|
|
519
|
-
|
|
659
|
+
div.multiselect button.remove-all {
|
|
520
660
|
margin: 0 3pt;
|
|
521
661
|
}
|
|
522
|
-
|
|
662
|
+
div.multiselect ul.selected > li button:hover,
|
|
663
|
+
div.multiselect button.remove-all:hover,
|
|
664
|
+
div.multiselect button:focus {
|
|
523
665
|
color: var(--sms-button-hover-color, lightskyblue);
|
|
524
666
|
}
|
|
525
|
-
|
|
526
|
-
:where(div.multiselect input) {
|
|
667
|
+
div.multiselect input {
|
|
527
668
|
margin: auto 0; /* CSS reset */
|
|
528
669
|
padding: 0; /* CSS reset */
|
|
529
670
|
}
|
|
530
|
-
|
|
671
|
+
div.multiselect > ul.selected > li > input {
|
|
531
672
|
border: none;
|
|
532
673
|
outline: none;
|
|
533
674
|
background: none;
|
|
@@ -539,12 +680,12 @@ function on_click_outside(event) {
|
|
|
539
680
|
cursor: inherit; /* needed for disabled state */
|
|
540
681
|
border-radius: 0; /* reset ul.selected > li */
|
|
541
682
|
}
|
|
542
|
-
|
|
683
|
+
div.multiselect > ul.selected > li > input::placeholder {
|
|
543
684
|
padding-left: 5pt;
|
|
544
685
|
color: var(--sms-placeholder-color);
|
|
545
686
|
opacity: var(--sms-placeholder-opacity);
|
|
546
687
|
}
|
|
547
|
-
|
|
688
|
+
div.multiselect > input.form-control {
|
|
548
689
|
width: 2em;
|
|
549
690
|
position: absolute;
|
|
550
691
|
background: transparent;
|
|
@@ -554,8 +695,7 @@ function on_click_outside(event) {
|
|
|
554
695
|
opacity: 0;
|
|
555
696
|
pointer-events: none;
|
|
556
697
|
}
|
|
557
|
-
|
|
558
|
-
:where(div.multiselect > ul.options) {
|
|
698
|
+
div.multiselect > ul.options {
|
|
559
699
|
list-style: none;
|
|
560
700
|
padding: 4pt 0;
|
|
561
701
|
top: 100%;
|
|
@@ -570,30 +710,29 @@ function on_click_outside(event) {
|
|
|
570
710
|
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
571
711
|
transition: all 0.2s;
|
|
572
712
|
}
|
|
573
|
-
|
|
713
|
+
div.multiselect > ul.options.hidden {
|
|
574
714
|
visibility: hidden;
|
|
575
715
|
opacity: 0;
|
|
576
716
|
transform: translateY(50px);
|
|
577
717
|
}
|
|
578
|
-
|
|
718
|
+
div.multiselect > ul.options > li {
|
|
579
719
|
padding: 3pt 2ex;
|
|
580
720
|
cursor: pointer;
|
|
581
721
|
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
582
722
|
}
|
|
583
|
-
|
|
584
|
-
:where(div.multiselect > ul.options span) {
|
|
723
|
+
div.multiselect > ul.options span {
|
|
585
724
|
padding: 3pt 2ex;
|
|
586
725
|
}
|
|
587
|
-
|
|
726
|
+
div.multiselect > ul.options > li.selected {
|
|
588
727
|
background: var(--sms-li-selected-bg);
|
|
589
728
|
color: var(--sms-li-selected-color);
|
|
590
729
|
}
|
|
591
|
-
|
|
730
|
+
div.multiselect > ul.options > li.active {
|
|
592
731
|
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
593
732
|
}
|
|
594
|
-
|
|
733
|
+
div.multiselect > ul.options > li.disabled {
|
|
595
734
|
cursor: not-allowed;
|
|
596
735
|
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
597
736
|
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
598
737
|
}
|
|
599
|
-
</style>
|
|
738
|
+
}</style>
|
package/index.d.ts
CHANGED
package/index.js
CHANGED
|
@@ -1,14 +1,11 @@
|
|
|
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
|
|
9
5
|
// https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
|
|
10
6
|
if (typeof Element !== `undefined` &&
|
|
11
|
-
!Element.prototype?.scrollIntoViewIfNeeded
|
|
7
|
+
!Element.prototype?.scrollIntoViewIfNeeded &&
|
|
8
|
+
typeof IntersectionObserver !== `undefined`) {
|
|
12
9
|
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded = true) {
|
|
13
10
|
const el = this;
|
|
14
11
|
new IntersectionObserver(function ([entry]) {
|
package/package.json
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
"homepage": "https://svelte-multiselect.netlify.app",
|
|
6
6
|
"repository": "https://github.com/janosh/svelte-multiselect",
|
|
7
7
|
"license": "MIT",
|
|
8
|
-
"version": "7.0.
|
|
8
|
+
"version": "7.0.2",
|
|
9
9
|
"type": "module",
|
|
10
10
|
"svelte": "index.js",
|
|
11
11
|
"main": "index.js",
|
|
@@ -27,6 +27,7 @@
|
|
|
27
27
|
"prettier-plugin-svelte": "^2.7.0",
|
|
28
28
|
"rehype-autolink-headings": "^6.1.1",
|
|
29
29
|
"rehype-slug": "^5.0.1",
|
|
30
|
+
"sass": "^1.55.0",
|
|
30
31
|
"svelte": "^3.50.1",
|
|
31
32
|
"svelte-check": "^2.9.0",
|
|
32
33
|
"svelte-github-corner": "^0.1.0",
|
package/readme.md
CHANGED
|
@@ -193,7 +193,7 @@ import type { Option } from 'svelte-multiselect'
|
|
|
193
193
|
maxSelect: number | null = null
|
|
194
194
|
```
|
|
195
195
|
|
|
196
|
-
Positive integer to limit the number of options users can pick. `null` means no limit.
|
|
196
|
+
Positive integer to limit the number of options users can pick. `null` means no limit. `maxSelect={1}` will change the type of `selected` to be a single `Option` (or `null`) (not a length-1 array). Likewise, the type of `selectedLabels` changes from `(string | number)[]` to `string | number | null` and `selectedValues` from `unknown[]` to `unknown | null`. `maxSelect={1}` will also give `div.multiselect` a class of `single`. I.e. you can target the selector `div.multiselect.single` to give single selects a different appearance from multi selects.
|
|
197
197
|
|
|
198
198
|
1. ```ts
|
|
199
199
|
maxSelectMsg: ((current: number, max: number) => string) | null = null
|