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.
@@ -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
- $: formValue = _selectedValues.join(`,`);
89
- $: if (formValue)
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.includes(label) ? remove(label) : add(label, event);
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 to.
245
- setTimeout(() => {
246
- const li = document.querySelector(`ul.options > li.active`);
247
- if (li) {
248
- li.parentNode?.scrollIntoView({ block: `center` });
249
- li.scrollIntoViewIfNeeded();
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={formValue}
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
- :where(div.multiselect) {
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
- :where(div.multiselect.open) {
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
- :where(div.multiselect:focus-within) {
618
+ div.multiselect:focus-within {
481
619
  border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
482
620
  }
483
- :where(div.multiselect.disabled) {
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
- :where(div.multiselect > ul.selected > li) {
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
- :where(div.multiselect button) {
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
- :where(div.multiselect button.remove-all) {
656
+ div.multiselect button.remove-all {
520
657
  margin: 0 3pt;
521
658
  }
522
- :where(ul.selected > li button:hover, button.remove-all:hover, button:focus) {
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
- :where(div.multiselect > ul.selected > li > input) {
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
- :where(div.multiselect > ul.selected > li > input)::placeholder {
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
- :where(div.multiselect > input.form-control) {
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
- :where(div.multiselect > ul.options.hidden) {
710
+ div.multiselect > ul.options.hidden {
574
711
  visibility: hidden;
575
712
  opacity: 0;
576
713
  transform: translateY(50px);
577
714
  }
578
- :where(div.multiselect > ul.options > li) {
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
- /* for noOptionsMsg */
584
- :where(div.multiselect > ul.options span) {
720
+ div.multiselect > ul.options span {
585
721
  padding: 3pt 2ex;
586
722
  }
587
- :where(div.multiselect > ul.options > li.selected) {
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
- :where(div.multiselect > ul.options > li.active) {
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
- :where(div.multiselect > ul.options > li.disabled) {
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.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