svelte-multiselect 7.0.0 → 7.0.1
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 +175 -39
- package/index.js +2 -1
- package/package.json +2 -1
- package/readme.md +1 -1
package/MultiSelect.svelte
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
<script>import { createEventDispatcher } from 'svelte';
|
|
1
|
+
<script>import { createEventDispatcher, tick } from 'svelte';
|
|
2
2
|
import { get_label, get_value } from './';
|
|
3
3
|
import CircleSpinner from './CircleSpinner.svelte';
|
|
4
4
|
import { CrossIcon, DisabledIcon, ExpandIcon } from './icons';
|
|
@@ -85,8 +85,8 @@ let add_option_msg_is_active = false; // controls active state of <li>{addOption
|
|
|
85
85
|
let window_width;
|
|
86
86
|
// formValue binds to input.form-control to prevent form submission if required
|
|
87
87
|
// prop is true and no options are selected
|
|
88
|
-
$:
|
|
89
|
-
$: if (
|
|
88
|
+
$: form_value = _selectedValues.join(`,`);
|
|
89
|
+
$: if (form_value)
|
|
90
90
|
invalid = false; // reset error status whenever component state changes
|
|
91
91
|
// options matching the current search text
|
|
92
92
|
$: matchingOptions = options.filter((op) => filterFunc(op, searchText) && !_selectedLabels.includes(get_label(op)) // remove already selected options from dropdown list
|
|
@@ -204,7 +204,7 @@ async function handle_keydown(event) {
|
|
|
204
204
|
event.preventDefault(); // prevent enter key from triggering form submission
|
|
205
205
|
if (activeOption) {
|
|
206
206
|
const label = get_label(activeOption);
|
|
207
|
-
selectedLabels
|
|
207
|
+
selectedLabels?.includes(label) ? remove(label) : add(label, event);
|
|
208
208
|
searchText = ``;
|
|
209
209
|
}
|
|
210
210
|
else if (allowUserOptions && searchText.length > 0) {
|
|
@@ -241,14 +241,13 @@ async function handle_keydown(event) {
|
|
|
241
241
|
activeIndex = matchingOptions.length - 1;
|
|
242
242
|
if (autoScroll) {
|
|
243
243
|
// 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);
|
|
244
|
+
// around start/end of option list. Find a better solution than waiting 10 ms.
|
|
245
|
+
await tick();
|
|
246
|
+
const li = document.querySelector(`ul.options > li.active`);
|
|
247
|
+
if (li) {
|
|
248
|
+
li.parentNode?.scrollIntoView?.({ block: `center` });
|
|
249
|
+
li.scrollIntoViewIfNeeded?.();
|
|
250
|
+
}
|
|
252
251
|
}
|
|
253
252
|
}
|
|
254
253
|
// on backspace key: remove last selected option
|
|
@@ -295,9 +294,10 @@ function on_click_outside(event) {
|
|
|
295
294
|
title={disabled ? disabledInputTitle : null}
|
|
296
295
|
aria-disabled={disabled ? `true` : null}
|
|
297
296
|
>
|
|
297
|
+
<!-- formValue binds to input.form-control to prevent form submission if required prop is true and no options are selected -->
|
|
298
298
|
<input
|
|
299
299
|
{required}
|
|
300
|
-
bind:value={
|
|
300
|
+
bind:value={form_value}
|
|
301
301
|
tabindex="-1"
|
|
302
302
|
aria-hidden="true"
|
|
303
303
|
aria-label="ignore this, used only to prevent form submission if select is required but empty"
|
|
@@ -456,8 +456,146 @@ function on_click_outside(event) {
|
|
|
456
456
|
{/if}
|
|
457
457
|
</div>
|
|
458
458
|
|
|
459
|
-
<style
|
|
460
|
-
:
|
|
459
|
+
<style>:where(div.multiselect) {
|
|
460
|
+
position: relative;
|
|
461
|
+
align-items: center;
|
|
462
|
+
display: flex;
|
|
463
|
+
cursor: text;
|
|
464
|
+
border: var(--sms-border, 1pt solid lightgray);
|
|
465
|
+
border-radius: var(--sms-border-radius, 3pt);
|
|
466
|
+
background: var(--sms-bg);
|
|
467
|
+
max-width: var(--sms-max-width);
|
|
468
|
+
padding: var(--sms-padding, 0 3pt);
|
|
469
|
+
color: var(--sms-text-color);
|
|
470
|
+
font-size: var(--sms-font-size, inherit);
|
|
471
|
+
min-height: var(--sms-min-height, 19pt);
|
|
472
|
+
margin: var(--sms-margin);
|
|
473
|
+
}
|
|
474
|
+
:where(div.multiselect).open {
|
|
475
|
+
/* increase z-index when open to ensure the dropdown of one <MultiSelect />
|
|
476
|
+
displays above that of another slightly below it on the page */
|
|
477
|
+
z-index: var(--sms-open-z-index, 4);
|
|
478
|
+
}
|
|
479
|
+
:where(div.multiselect):focus-within {
|
|
480
|
+
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
481
|
+
}
|
|
482
|
+
:where(div.multiselect).disabled {
|
|
483
|
+
background: var(--sms-disabled-bg, lightgray);
|
|
484
|
+
cursor: not-allowed;
|
|
485
|
+
}
|
|
486
|
+
:where(div.multiselect) > ul.selected {
|
|
487
|
+
display: flex;
|
|
488
|
+
flex: 1;
|
|
489
|
+
padding: 0;
|
|
490
|
+
margin: 0;
|
|
491
|
+
flex-wrap: wrap;
|
|
492
|
+
}
|
|
493
|
+
:where(div.multiselect) > ul.selected > li {
|
|
494
|
+
align-items: center;
|
|
495
|
+
border-radius: 3pt;
|
|
496
|
+
display: flex;
|
|
497
|
+
margin: 2pt;
|
|
498
|
+
line-height: normal;
|
|
499
|
+
transition: 0.3s;
|
|
500
|
+
white-space: nowrap;
|
|
501
|
+
background: var(--sms-selected-bg, rgba(0, 0, 0, 0.15));
|
|
502
|
+
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
503
|
+
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
504
|
+
}
|
|
505
|
+
:where(div.multiselect) button {
|
|
506
|
+
border-radius: 50%;
|
|
507
|
+
display: flex;
|
|
508
|
+
transition: 0.2s;
|
|
509
|
+
color: inherit;
|
|
510
|
+
background: transparent;
|
|
511
|
+
border: none;
|
|
512
|
+
cursor: pointer;
|
|
513
|
+
outline: none;
|
|
514
|
+
padding: 0;
|
|
515
|
+
margin: 0 0 0 3pt; /* CSS reset */
|
|
516
|
+
}
|
|
517
|
+
:where(div.multiselect) button.remove-all {
|
|
518
|
+
margin: 0 3pt;
|
|
519
|
+
}
|
|
520
|
+
:where(div.multiselect) ul.selected > li button:hover,
|
|
521
|
+
:where(div.multiselect) button.remove-all:hover,
|
|
522
|
+
:where(div.multiselect) button:focus {
|
|
523
|
+
color: var(--sms-button-hover-color, lightskyblue);
|
|
524
|
+
}
|
|
525
|
+
:where(div.multiselect) input {
|
|
526
|
+
margin: auto 0; /* CSS reset */
|
|
527
|
+
padding: 0; /* CSS reset */
|
|
528
|
+
}
|
|
529
|
+
:where(div.multiselect) > ul.selected > li > input {
|
|
530
|
+
border: none;
|
|
531
|
+
outline: none;
|
|
532
|
+
background: none;
|
|
533
|
+
flex: 1; /* this + next line fix issue #12 https://git.io/JiDe3 */
|
|
534
|
+
min-width: 2em;
|
|
535
|
+
/* ensure input uses text color and not --sms-selected-text-color */
|
|
536
|
+
color: var(--sms-text-color);
|
|
537
|
+
font-size: inherit;
|
|
538
|
+
cursor: inherit; /* needed for disabled state */
|
|
539
|
+
border-radius: 0; /* reset ul.selected > li */
|
|
540
|
+
}
|
|
541
|
+
:where(div.multiselect) > ul.selected > li > input::placeholder {
|
|
542
|
+
padding-left: 5pt;
|
|
543
|
+
color: var(--sms-placeholder-color);
|
|
544
|
+
opacity: var(--sms-placeholder-opacity);
|
|
545
|
+
}
|
|
546
|
+
:where(div.multiselect) > input.form-control {
|
|
547
|
+
width: 2em;
|
|
548
|
+
position: absolute;
|
|
549
|
+
background: transparent;
|
|
550
|
+
border: none;
|
|
551
|
+
outline: none;
|
|
552
|
+
z-index: -1;
|
|
553
|
+
opacity: 0;
|
|
554
|
+
pointer-events: none;
|
|
555
|
+
}
|
|
556
|
+
:where(div.multiselect) > ul.options {
|
|
557
|
+
list-style: none;
|
|
558
|
+
padding: 4pt 0;
|
|
559
|
+
top: 100%;
|
|
560
|
+
left: 0;
|
|
561
|
+
width: 100%;
|
|
562
|
+
position: absolute;
|
|
563
|
+
border-radius: 1ex;
|
|
564
|
+
overflow: auto;
|
|
565
|
+
background: var(--sms-options-bg, white);
|
|
566
|
+
max-height: var(--sms-options-max-height, 50vh);
|
|
567
|
+
overscroll-behavior: var(--sms-options-overscroll, none);
|
|
568
|
+
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
569
|
+
transition: all 0.2s;
|
|
570
|
+
}
|
|
571
|
+
:where(div.multiselect) > ul.options.hidden {
|
|
572
|
+
visibility: hidden;
|
|
573
|
+
opacity: 0;
|
|
574
|
+
transform: translateY(50px);
|
|
575
|
+
}
|
|
576
|
+
:where(div.multiselect) > ul.options > li {
|
|
577
|
+
padding: 3pt 2ex;
|
|
578
|
+
cursor: pointer;
|
|
579
|
+
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
580
|
+
}
|
|
581
|
+
:where(div.multiselect) > ul.options span {
|
|
582
|
+
padding: 3pt 2ex;
|
|
583
|
+
}
|
|
584
|
+
:where(div.multiselect) > ul.options > li.selected {
|
|
585
|
+
background: var(--sms-li-selected-bg);
|
|
586
|
+
color: var(--sms-li-selected-color);
|
|
587
|
+
}
|
|
588
|
+
:where(div.multiselect) > ul.options > li.active {
|
|
589
|
+
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
590
|
+
}
|
|
591
|
+
:where(div.multiselect) > ul.options > li.disabled {
|
|
592
|
+
cursor: not-allowed;
|
|
593
|
+
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
594
|
+
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
595
|
+
}
|
|
596
|
+
|
|
597
|
+
@supports not selector(:where(div.multiselect)) {
|
|
598
|
+
div.multiselect {
|
|
461
599
|
position: relative;
|
|
462
600
|
align-items: center;
|
|
463
601
|
display: flex;
|
|
@@ -472,27 +610,26 @@ function on_click_outside(event) {
|
|
|
472
610
|
min-height: var(--sms-min-height, 19pt);
|
|
473
611
|
margin: var(--sms-margin);
|
|
474
612
|
}
|
|
475
|
-
|
|
613
|
+
div.multiselect.open {
|
|
476
614
|
/* increase z-index when open to ensure the dropdown of one <MultiSelect />
|
|
477
615
|
displays above that of another slightly below it on the page */
|
|
478
616
|
z-index: var(--sms-open-z-index, 4);
|
|
479
617
|
}
|
|
480
|
-
|
|
618
|
+
div.multiselect:focus-within {
|
|
481
619
|
border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
|
|
482
620
|
}
|
|
483
|
-
|
|
621
|
+
div.multiselect.disabled {
|
|
484
622
|
background: var(--sms-disabled-bg, lightgray);
|
|
485
623
|
cursor: not-allowed;
|
|
486
624
|
}
|
|
487
|
-
|
|
488
|
-
:where(div.multiselect > ul.selected) {
|
|
625
|
+
div.multiselect > ul.selected {
|
|
489
626
|
display: flex;
|
|
490
627
|
flex: 1;
|
|
491
628
|
padding: 0;
|
|
492
629
|
margin: 0;
|
|
493
630
|
flex-wrap: wrap;
|
|
494
631
|
}
|
|
495
|
-
|
|
632
|
+
div.multiselect > ul.selected > li {
|
|
496
633
|
align-items: center;
|
|
497
634
|
border-radius: 3pt;
|
|
498
635
|
display: flex;
|
|
@@ -504,7 +641,7 @@ function on_click_outside(event) {
|
|
|
504
641
|
padding: var(--sms-selected-li-padding, 1pt 5pt);
|
|
505
642
|
color: var(--sms-selected-text-color, var(--sms-text-color));
|
|
506
643
|
}
|
|
507
|
-
|
|
644
|
+
div.multiselect button {
|
|
508
645
|
border-radius: 50%;
|
|
509
646
|
display: flex;
|
|
510
647
|
transition: 0.2s;
|
|
@@ -516,18 +653,19 @@ function on_click_outside(event) {
|
|
|
516
653
|
padding: 0;
|
|
517
654
|
margin: 0 0 0 3pt; /* CSS reset */
|
|
518
655
|
}
|
|
519
|
-
|
|
656
|
+
div.multiselect button.remove-all {
|
|
520
657
|
margin: 0 3pt;
|
|
521
658
|
}
|
|
522
|
-
|
|
659
|
+
div.multiselect ul.selected > li button:hover,
|
|
660
|
+
div.multiselect button.remove-all:hover,
|
|
661
|
+
div.multiselect button:focus {
|
|
523
662
|
color: var(--sms-button-hover-color, lightskyblue);
|
|
524
663
|
}
|
|
525
|
-
|
|
526
|
-
:where(div.multiselect input) {
|
|
664
|
+
div.multiselect input {
|
|
527
665
|
margin: auto 0; /* CSS reset */
|
|
528
666
|
padding: 0; /* CSS reset */
|
|
529
667
|
}
|
|
530
|
-
|
|
668
|
+
div.multiselect > ul.selected > li > input {
|
|
531
669
|
border: none;
|
|
532
670
|
outline: none;
|
|
533
671
|
background: none;
|
|
@@ -539,12 +677,12 @@ function on_click_outside(event) {
|
|
|
539
677
|
cursor: inherit; /* needed for disabled state */
|
|
540
678
|
border-radius: 0; /* reset ul.selected > li */
|
|
541
679
|
}
|
|
542
|
-
|
|
680
|
+
div.multiselect > ul.selected > li > input::placeholder {
|
|
543
681
|
padding-left: 5pt;
|
|
544
682
|
color: var(--sms-placeholder-color);
|
|
545
683
|
opacity: var(--sms-placeholder-opacity);
|
|
546
684
|
}
|
|
547
|
-
|
|
685
|
+
div.multiselect > input.form-control {
|
|
548
686
|
width: 2em;
|
|
549
687
|
position: absolute;
|
|
550
688
|
background: transparent;
|
|
@@ -554,8 +692,7 @@ function on_click_outside(event) {
|
|
|
554
692
|
opacity: 0;
|
|
555
693
|
pointer-events: none;
|
|
556
694
|
}
|
|
557
|
-
|
|
558
|
-
:where(div.multiselect > ul.options) {
|
|
695
|
+
div.multiselect > ul.options {
|
|
559
696
|
list-style: none;
|
|
560
697
|
padding: 4pt 0;
|
|
561
698
|
top: 100%;
|
|
@@ -570,30 +707,29 @@ function on_click_outside(event) {
|
|
|
570
707
|
box-shadow: var(--sms-options-shadow, 0 0 14pt -8pt black);
|
|
571
708
|
transition: all 0.2s;
|
|
572
709
|
}
|
|
573
|
-
|
|
710
|
+
div.multiselect > ul.options.hidden {
|
|
574
711
|
visibility: hidden;
|
|
575
712
|
opacity: 0;
|
|
576
713
|
transform: translateY(50px);
|
|
577
714
|
}
|
|
578
|
-
|
|
715
|
+
div.multiselect > ul.options > li {
|
|
579
716
|
padding: 3pt 2ex;
|
|
580
717
|
cursor: pointer;
|
|
581
718
|
scroll-margin: var(--sms-options-scroll-margin, 100px);
|
|
582
719
|
}
|
|
583
|
-
|
|
584
|
-
:where(div.multiselect > ul.options span) {
|
|
720
|
+
div.multiselect > ul.options span {
|
|
585
721
|
padding: 3pt 2ex;
|
|
586
722
|
}
|
|
587
|
-
|
|
723
|
+
div.multiselect > ul.options > li.selected {
|
|
588
724
|
background: var(--sms-li-selected-bg);
|
|
589
725
|
color: var(--sms-li-selected-color);
|
|
590
726
|
}
|
|
591
|
-
|
|
727
|
+
div.multiselect > ul.options > li.active {
|
|
592
728
|
background: var(--sms-li-active-bg, var(--sms-active-color, rgba(0, 0, 0, 0.15)));
|
|
593
729
|
}
|
|
594
|
-
|
|
730
|
+
div.multiselect > ul.options > li.disabled {
|
|
595
731
|
cursor: not-allowed;
|
|
596
732
|
background: var(--sms-li-disabled-bg, #f5f5f6);
|
|
597
733
|
color: var(--sms-li-disabled-text, #b8b8b8);
|
|
598
734
|
}
|
|
599
|
-
</style>
|
|
735
|
+
}</style>
|
package/index.js
CHANGED
|
@@ -8,7 +8,8 @@ export const get_value = (op) => op instanceof Object ? op.value ?? op.label : o
|
|
|
8
8
|
// this polyfill was copied from
|
|
9
9
|
// https://github.com/nuxodin/lazyfill/blob/a8e63/polyfills/Element/prototype/scrollIntoViewIfNeeded.js
|
|
10
10
|
if (typeof Element !== `undefined` &&
|
|
11
|
-
!Element.prototype?.scrollIntoViewIfNeeded
|
|
11
|
+
!Element.prototype?.scrollIntoViewIfNeeded &&
|
|
12
|
+
typeof IntersectionObserver !== `undefined`) {
|
|
12
13
|
Element.prototype.scrollIntoViewIfNeeded = function (centerIfNeeded = true) {
|
|
13
14
|
const el = this;
|
|
14
15
|
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.1",
|
|
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
|