svelte-multiselect 11.5.2 → 11.6.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.
@@ -21,7 +21,7 @@ declare function $$render<Action extends {
21
21
  dialog_props?: HTMLAttributes<HTMLDialogElement>;
22
22
  };
23
23
  exports: {};
24
- bindings: "dialog" | "input" | "open";
24
+ bindings: "open" | "input" | "dialog";
25
25
  slots: {};
26
26
  events: {};
27
27
  };
@@ -37,7 +37,7 @@ declare class __sveltets_Render<Action extends {
37
37
  props(): ReturnType<typeof $$render<Action>>['props'];
38
38
  events(): ReturnType<typeof $$render<Action>>['events'];
39
39
  slots(): ReturnType<typeof $$render<Action>>['slots'];
40
- bindings(): "dialog" | "input" | "open";
40
+ bindings(): "open" | "input" | "dialog";
41
41
  exports(): {};
42
42
  }
43
43
  interface $$IsomorphicComponent {
@@ -5,17 +5,17 @@ import { SvelteMap, SvelteSet } from 'svelte/reactivity';
5
5
  import { highlight_matches } from './attachments';
6
6
  import CircleSpinner from './CircleSpinner.svelte';
7
7
  import Icon from './Icon.svelte';
8
- import { fuzzy_match, get_label, get_style, has_group, is_object } from './utils';
8
+ import * as utils from './utils';
9
9
  import Wiggle from './Wiggle.svelte';
10
- let { activeIndex = $bindable(null), activeOption = $bindable(null), createOptionMsg = `Create this option...`, allowUserOptions = false, allowEmpty = false, autocomplete = `off`, autoScroll = true, breakpoint = 800, defaultDisabledTitle = `This option is disabled`, disabled = false, disabledInputTitle = `This input is disabled`, duplicateOptionMsg = `This option is already selected`, duplicates = false, keepSelectedInDropdown = false, key = (opt) => `${get_label(opt)}`.toLowerCase(), filterFunc = (opt, searchText) => {
10
+ let { activeIndex = $bindable(null), activeOption = $bindable(null), createOptionMsg = `Create this option...`, allowUserOptions = false, allowEmpty = false, autocomplete = `off`, autoScroll = true, breakpoint = 800, defaultDisabledTitle = `This option is disabled`, disabled = false, disabledInputTitle = `This input is disabled`, duplicateOptionMsg = `This option is already selected`, duplicates = false, keepSelectedInDropdown = false, key = (opt) => `${utils.get_label(opt)}`.toLowerCase(), filterFunc = (opt, searchText) => {
11
11
  if (!searchText)
12
12
  return true;
13
- const label = `${get_label(opt)}`;
13
+ const label = `${utils.get_label(opt)}`;
14
14
  return fuzzy
15
- ? fuzzy_match(searchText, label)
15
+ ? utils.fuzzy_match(searchText, label)
16
16
  : label.toLowerCase().includes(searchText.toLowerCase());
17
17
  }, fuzzy = true, closeDropdownOnSelect = false, form_input = $bindable(null), highlightMatches = true, id = null, input = $bindable(null), inputClass = ``, inputStyle = null, inputmode = null, invalid = $bindable(false), liActiveOptionClass = ``, liActiveUserMsgClass = ``, liOptionClass = ``, liOptionStyle = null, liSelectedClass = ``, liSelectedStyle = null, liUserMsgClass = ``, loading = false, matchingOptions = $bindable([]), maxOptions = undefined, maxSelect = $bindable(null), maxSelectMsg = (current, max) => (max > 1 ? `${current}/${max}` : ``), maxSelectMsgClass = ``, name = null, noMatchingOptionsMsg = `No matching options`, open = $bindable(false), options = $bindable(), outerDiv = $bindable(null), outerDivClass = ``, parseLabelsAsHtml = false, pattern = null, placeholder = null, removeAllTitle = `Remove all`, removeBtnTitle = `Remove`, minSelect = null, required = false, resetFilterOnAdd = true, searchText = $bindable(``), value = $bindable(null), selected = $bindable(value !== null && value !== undefined
18
- ? (Array.isArray(value) ? value : [value])
18
+ ? Array.isArray(value) ? value : [value]
19
19
  : (options
20
20
  ?.filter((opt) => typeof opt === `object` && opt !== null && opt?.preselected)
21
21
  .slice(0, maxSelect ?? undefined) ?? [])), sortSelected = false, selectedOptionsDraggable = !sortSelected, style = null, ulOptionsClass = ``, ulSelectedClass = ``, ulSelectedStyle = null, ulOptionsStyle = null, expandIcon, selectedItem, children, removeIcon, afterInput, spinner, disabledIcon, option, userMsg, onblur, onclick, onfocus, onkeydown, onkeyup, onmousedown, onmouseenter, onmouseleave, ontouchcancel, ontouchend, ontouchmove, ontouchstart, onadd, oncreate, onremove, onremoveAll, onchange, onopen, onclose, onselectAll, onreorder, portal: portal_params = {},
@@ -28,22 +28,22 @@ selectedFlipParams = { duration: 100 },
28
28
  // Option grouping feature
29
29
  collapsibleGroups = false, collapsedGroups = $bindable(new Set()), groupSelectAll = false, ungroupedPosition = `first`, groupSortOrder = `none`, searchExpandsCollapsedGroups = false, searchMatchesGroups = false, keyboardExpandsCollapsedGroups = false, stickyGroupHeaders = false, liGroupHeaderClass = ``, liGroupHeaderStyle = null, groupHeader, ongroupToggle, oncollapseAll, onexpandAll, onsearch, onmaxreached, onduplicate, onactivate, collapseAllGroups = $bindable(), expandAllGroups = $bindable(),
30
30
  // Keyboard shortcuts for common actions
31
- shortcuts = {}, ...rest } = $props();
31
+ shortcuts = {},
32
+ // Selection history for undo/redo (enabled by default, set to false or 0 to disable)
33
+ history = true, undo = $bindable(), redo = $bindable(), canUndo = $bindable(false), canRedo = $bindable(false), onundo, onredo, ...rest } = $props();
32
34
  // Generate unique IDs for ARIA associations (combobox pattern)
33
35
  // Uses provided id prop or generates a random one using crypto API
34
- const internal_id = $derived(id ?? `sms-${crypto.randomUUID().slice(0, 8)}`);
36
+ const internal_id = $derived(id ?? `sms-${utils.get_uuid().slice(0, 8)}`);
35
37
  const listbox_id = $derived(`${internal_id}-listbox`);
36
38
  // Parse shortcut string into modifier+key parts
37
39
  function parse_shortcut(shortcut) {
38
40
  const parts = shortcut.toLowerCase().split(`+`).map((part) => part.trim());
39
41
  const key = parts.pop() ?? ``;
40
- return {
41
- key,
42
- ctrl: parts.includes(`ctrl`),
43
- shift: parts.includes(`shift`),
44
- alt: parts.includes(`alt`),
45
- meta: parts.includes(`meta`) || parts.includes(`cmd`),
46
- };
42
+ const ctrl = parts.includes(`ctrl`);
43
+ const shift = parts.includes(`shift`);
44
+ const alt = parts.includes(`alt`);
45
+ const meta = parts.includes(`meta`) || parts.includes(`cmd`);
46
+ return { key, ctrl, shift, alt, meta };
47
47
  }
48
48
  function matches_shortcut(event, shortcut) {
49
49
  if (!shortcut)
@@ -57,19 +57,22 @@ function matches_shortcut(event, shortcut) {
57
57
  const shift_matches = event.shiftKey === parsed.shift;
58
58
  const alt_matches = event.altKey === parsed.alt;
59
59
  const meta_matches = event.metaKey === parsed.meta;
60
- return (key_matches && ctrl_matches && shift_matches && alt_matches && meta_matches);
60
+ return key_matches && ctrl_matches && shift_matches && alt_matches && meta_matches;
61
61
  }
62
+ // Platform detection for keyboard shortcuts (Mac uses Cmd, others use Ctrl)
63
+ const is_mac = typeof navigator !== `undefined` &&
64
+ /Mac|iPhone|iPad|iPod/.test(navigator.userAgent);
65
+ const mod_key = is_mac ? `meta` : `ctrl`;
62
66
  // Default shortcuts
63
67
  const default_shortcuts = {
64
- select_all: `ctrl+a`,
65
- clear_all: `ctrl+shift+a`,
68
+ select_all: `${mod_key}+a`,
69
+ clear_all: `${mod_key}+shift+a`,
66
70
  open: null,
67
71
  close: null,
72
+ undo: `${mod_key}+z`,
73
+ redo: `${mod_key}+shift+z`,
68
74
  };
69
- const effective_shortcuts = $derived({
70
- ...default_shortcuts,
71
- ...shortcuts,
72
- });
75
+ const effective_shortcuts = $derived({ ...default_shortcuts, ...shortcuts });
73
76
  // Extract loadOptions config into single derived object (supports both simple function and config object)
74
77
  const load_options_config = $derived.by(() => {
75
78
  if (!loadOptions)
@@ -94,8 +97,8 @@ function values_equal(val1, val2) {
94
97
  if (empty1 && empty2)
95
98
  return true;
96
99
  if (Array.isArray(val1) && Array.isArray(val2)) {
97
- return val1.length === val2.length &&
98
- val1.every((item, idx) => item === val2[idx]);
100
+ return (val1.length === val2.length &&
101
+ val1.every((item, idx) => item === val2[idx]));
99
102
  }
100
103
  return false;
101
104
  }
@@ -104,15 +107,17 @@ function values_equal(val1, val2) {
104
107
  // infinite loops with reactive wrappers that clone arrays. See issue #309.
105
108
  $effect.pre(() => {
106
109
  const new_value = maxSelect === 1 ? (selected[0] ?? null) : selected;
107
- if (!values_equal(untrack(() => value), new_value))
110
+ if (!values_equal(untrack(() => value), new_value)) {
108
111
  value = new_value;
112
+ }
109
113
  });
110
114
  $effect.pre(() => {
111
115
  const new_selected = maxSelect === 1
112
116
  ? (value ? [value] : [])
113
117
  : (Array.isArray(value) ? value : []);
114
- if (!values_equal(untrack(() => selected), new_selected))
118
+ if (!values_equal(untrack(() => selected), new_selected)) {
115
119
  selected = new_selected;
120
+ }
116
121
  });
117
122
  let wiggle = $state(false); // controls wiggle animation when user tries to exceed maxSelect
118
123
  let ignore_hover = $state(false); // ignore mouseover during keyboard navigation to prevent scroll-triggered hover
@@ -125,6 +130,83 @@ $effect(() => {
125
130
  return () => clearTimeout(timer);
126
131
  }
127
132
  });
133
+ // History tracking for undo/redo
134
+ const max_history = $derived(history === true
135
+ ? 50
136
+ : typeof history === `number` && Number.isFinite(history)
137
+ ? Math.max(0, Math.floor(history))
138
+ : 0);
139
+ let history_stack = $state([]);
140
+ let history_index = $state(-1); // -1 = no history yet
141
+ let prev_selected = null; // null = uninitialized, sync on first run
142
+ // Track changes to selected via $effect (catches internal + external changes)
143
+ $effect(() => {
144
+ // Disabled when max_history is 0 (handles false, 0, negative, non-finite inputs)
145
+ const history_disabled = !(max_history > 0);
146
+ if (history_disabled) {
147
+ // Clear history when disabled so re-enabling starts fresh
148
+ history_stack = [];
149
+ history_index = -1;
150
+ // Don't read `selected` here to avoid creating unnecessary reactive dependency
151
+ prev_selected = null;
152
+ return;
153
+ }
154
+ // Initialize prev_selected on first run to avoid phantom undo from [] → initial selection
155
+ if (prev_selected === null) {
156
+ prev_selected = [...selected];
157
+ return;
158
+ }
159
+ // Check if actually changed (avoid duplicates from reactive updates)
160
+ if (values_equal(selected, prev_selected))
161
+ return;
162
+ // On first change, push initial state first
163
+ if (history_stack.length === 0) {
164
+ history_stack = [[...prev_selected]];
165
+ history_index = 0;
166
+ }
167
+ // Truncate any redo states
168
+ if (history_index < history_stack.length - 1) {
169
+ history_stack = history_stack.slice(0, history_index + 1);
170
+ }
171
+ // Push new state
172
+ history_stack = [...history_stack, [...selected]];
173
+ history_index = history_stack.length - 1;
174
+ // Trim to max size (remove oldest)
175
+ if (history_stack.length > max_history) {
176
+ const excess = history_stack.length - max_history;
177
+ history_stack = history_stack.slice(excess);
178
+ history_index = Math.max(0, history_index - excess);
179
+ }
180
+ prev_selected = [...selected];
181
+ });
182
+ // Derived canUndo/canRedo (update bindable props reactively)
183
+ $effect(() => {
184
+ canUndo = max_history > 0 && !disabled && history_index > 0;
185
+ canRedo = max_history > 0 && !disabled && history_index < history_stack.length - 1;
186
+ });
187
+ // Undo: restore previous state
188
+ undo = () => {
189
+ if (max_history <= 0 || disabled || history_index <= 0)
190
+ return false;
191
+ const previous = [...selected];
192
+ history_index--;
193
+ selected = [...history_stack[history_index]];
194
+ prev_selected = [...selected]; // sync tracker to prevent $effect re-recording
195
+ onundo?.({ previous, current: selected });
196
+ return true;
197
+ };
198
+ // Redo: restore next state
199
+ redo = () => {
200
+ if (max_history <= 0 || disabled || history_index >= history_stack.length - 1) {
201
+ return false;
202
+ }
203
+ const previous = [...selected];
204
+ history_index++;
205
+ selected = [...history_stack[history_index]];
206
+ prev_selected = [...selected]; // sync tracker to prevent $effect re-recording
207
+ onredo?.({ previous, current: selected });
208
+ return true;
209
+ };
128
210
  // Debounced onsearch event - fires 150ms after search text stops changing
129
211
  let search_debounce_timer = null;
130
212
  let search_initialized = false;
@@ -158,13 +240,13 @@ let debounce_timer = null;
158
240
  let effective_options = $derived(loadOptions ? loaded_options : (options ?? []));
159
241
  // Cache selected keys and labels to avoid repeated .map() calls
160
242
  let selected_keys = $derived(selected.map(key));
161
- let selected_labels = $derived(selected.map(get_label));
243
+ let selected_labels = $derived(selected.map(utils.get_label));
162
244
  // Sets for O(1) lookups (used in template, has_user_msg, group_header_state, batch operations)
163
245
  let selected_keys_set = $derived(new Set(selected_keys));
164
246
  let selected_labels_set = $derived(new Set(selected_labels));
165
247
  // Memoized Set of disabled option keys for O(1) lookups in large option sets
166
248
  let disabled_option_keys = $derived(new Set(effective_options
167
- .filter((opt) => is_object(opt) && opt.disabled)
249
+ .filter((opt) => utils.is_object(opt) && opt.disabled)
168
250
  .map(key)));
169
251
  // Check if an option is disabled (uses memoized Set for O(1) lookup)
170
252
  const is_disabled = (opt) => disabled_option_keys.has(key(opt));
@@ -175,7 +257,7 @@ let grouped_options = $derived.by(() => {
175
257
  const groups_map = new SvelteMap();
176
258
  const ungrouped = [];
177
259
  for (const opt of matchingOptions) {
178
- if (has_group(opt)) {
260
+ if (utils.has_group(opt)) {
179
261
  const existing = groups_map.get(opt.group);
180
262
  if (existing)
181
263
  existing.push(opt);
@@ -203,7 +285,11 @@ let grouped_options = $derived.by(() => {
203
285
  }
204
286
  if (ungrouped.length === 0)
205
287
  return grouped;
206
- const ungrouped_entry = { group: null, options: ungrouped, collapsed: false };
288
+ const ungrouped_entry = {
289
+ group: null,
290
+ options: ungrouped,
291
+ collapsed: false,
292
+ };
207
293
  return ungroupedPosition === `first`
208
294
  ? [ungrouped_entry, ...grouped]
209
295
  : [...grouped, ungrouped_entry];
@@ -272,8 +358,9 @@ function expand_groups(groups_to_expand) {
272
358
  if (groups_to_expand.length === 0)
273
359
  return;
274
360
  update_collapsed_groups(`delete`, groups_to_expand);
275
- for (const group of groups_to_expand)
361
+ for (const group of groups_to_expand) {
276
362
  ongroupToggle?.({ group, collapsed: false });
363
+ }
277
364
  }
278
365
  // Get names of collapsed groups that have matching options
279
366
  const get_collapsed_with_matches = () => grouped_options.flatMap(({ group, collapsed, options: opts }) => group && collapsed && opts.length > 0 ? [group] : []);
@@ -284,12 +371,12 @@ $effect(() => {
284
371
  }
285
372
  });
286
373
  // Normalize placeholder prop (supports string or { text, persistent } object)
287
- const placeholder_text = $derived(typeof placeholder === `string` ? placeholder : placeholder?.text ?? null);
374
+ const placeholder_text = $derived(typeof placeholder === `string` ? placeholder : (placeholder?.text ?? null));
288
375
  const placeholder_persistent = $derived(typeof placeholder === `object` && placeholder?.persistent === true);
289
376
  // Helper to sort selected options (used by add() and select_all())
290
377
  function sort_selected(items) {
291
378
  if (sortSelected === true) {
292
- return items.toSorted((op1, op2) => `${get_label(op1)}`.localeCompare(`${get_label(op2)}`));
379
+ return items.toSorted((op1, op2) => `${utils.get_label(op1)}`.localeCompare(`${utils.get_label(op2)}`));
293
380
  }
294
381
  else if (typeof sortSelected === `function`) {
295
382
  return items.toSorted(sortSelected);
@@ -340,9 +427,9 @@ let window_width = $state(0);
340
427
  const matches_search = (opt, search) => {
341
428
  if (filterFunc(opt, search))
342
429
  return true;
343
- if (searchMatchesGroups && search && has_group(opt)) {
430
+ if (searchMatchesGroups && search && utils.has_group(opt)) {
344
431
  return fuzzy
345
- ? fuzzy_match(search, opt.group)
432
+ ? utils.fuzzy_match(search, opt.group)
346
433
  : opt.group.toLowerCase().includes(search.toLowerCase());
347
434
  }
348
435
  return false;
@@ -391,7 +478,8 @@ function add(option_to_add, event) {
391
478
  event.stopPropagation();
392
479
  if (maxSelect !== null && selected.length >= maxSelect)
393
480
  wiggle = true;
394
- if (!isNaN(Number(option_to_add)) && typeof selected_labels[0] === `number`) {
481
+ if (!isNaN(Number(option_to_add)) &&
482
+ typeof selected_labels[0] === `number`) {
395
483
  option_to_add = Number(option_to_add); // convert to number if possible
396
484
  }
397
485
  const is_duplicate = selected_keys_set.has(key(option_to_add));
@@ -451,7 +539,7 @@ function add(option_to_add, event) {
451
539
  }
452
540
  clear_validity();
453
541
  handle_dropdown_after_select(event);
454
- last_action = { type: `add`, label: `${get_label(option_to_add)}` };
542
+ last_action = { type: `add`, label: `${utils.get_label(option_to_add)}` };
455
543
  onadd?.({ option: option_to_add });
456
544
  onchange?.({ option: option_to_add, type: `add` });
457
545
  }
@@ -476,7 +564,7 @@ function remove(option_to_drop, event) {
476
564
  }
477
565
  selected = selected.filter((_, remove_idx) => remove_idx !== idx);
478
566
  clear_validity();
479
- last_action = { type: `remove`, label: `${get_label(option_removed)}` };
567
+ last_action = { type: `remove`, label: `${utils.get_label(option_removed)}` };
480
568
  onremove?.({ option: option_removed });
481
569
  onchange?.({ option: option_removed, type: `remove` });
482
570
  }
@@ -506,7 +594,8 @@ function handle_dropdown_after_select(event) {
506
594
  const reached_max = selected.length >= (maxSelect ?? Infinity);
507
595
  const should_close = closeDropdownOnSelect === true ||
508
596
  closeDropdownOnSelect === `retain-focus` ||
509
- (closeDropdownOnSelect === `if-mobile` && window_width &&
597
+ (closeDropdownOnSelect === `if-mobile` &&
598
+ window_width &&
510
599
  window_width < breakpoint);
511
600
  if (reached_max || should_close) {
512
601
  close_dropdown(event, closeDropdownOnSelect === `retain-focus`);
@@ -515,19 +604,24 @@ function handle_dropdown_after_select(event) {
515
604
  input?.focus();
516
605
  }
517
606
  // Check if a user message (create option, duplicate warning, no match) is visible
518
- const has_user_msg = $derived(searchText.length > 0 && Boolean((allowUserOptions && createOptionMsg) ||
519
- (!duplicates && selected_labels_set.has(searchText)) ||
520
- (navigable_options.length === 0 && noMatchingOptionsMsg)));
607
+ const has_user_msg = $derived(searchText.length > 0 &&
608
+ Boolean((allowUserOptions && createOptionMsg) ||
609
+ (!duplicates && selected_labels_set.has(searchText)) ||
610
+ (navigable_options.length === 0 && noMatchingOptionsMsg)));
521
611
  // Handle arrow key navigation through options (uses navigable_options to skip collapsed groups)
522
612
  async function handle_arrow_navigation(direction) {
523
613
  ignore_hover = true;
524
614
  // Auto-expand collapsed groups when keyboard navigating
525
- if (keyboardExpandsCollapsedGroups && collapsibleGroups && collapsedGroups.size > 0) {
615
+ if (keyboardExpandsCollapsedGroups &&
616
+ collapsibleGroups &&
617
+ collapsedGroups.size > 0) {
526
618
  expand_groups(get_collapsed_with_matches());
527
619
  await tick();
528
620
  }
529
621
  // toggle user message when no options match but user can create
530
- if (allowUserOptions && !navigable_options.length && searchText.length > 0) {
622
+ if (allowUserOptions &&
623
+ !navigable_options.length &&
624
+ searchText.length > 0) {
531
625
  option_msg_is_active = !option_msg_is_active;
532
626
  return;
533
627
  }
@@ -550,10 +644,12 @@ async function handle_arrow_navigation(direction) {
550
644
  option_msg_is_active = has_user_msg && activeIndex === navigable_options.length;
551
645
  activeOption = option_msg_is_active
552
646
  ? null
553
- : navigable_options[activeIndex] ?? null;
647
+ : (navigable_options[activeIndex] ?? null);
554
648
  if (autoScroll) {
555
649
  await tick();
556
- document.querySelector(`ul.options > li.active`)?.scrollIntoViewIfNeeded?.();
650
+ document
651
+ .querySelector(`ul.options > li.active`)
652
+ ?.scrollIntoViewIfNeeded?.();
557
653
  }
558
654
  // Fire onactivate for keyboard navigation only (not mouse hover)
559
655
  onactivate?.({ option: activeOption, index: activeIndex });
@@ -587,6 +683,16 @@ async function handle_keydown(event) {
587
683
  searchText = ``;
588
684
  },
589
685
  },
686
+ {
687
+ key: `undo`,
688
+ condition: () => !!canUndo,
689
+ action: () => undo?.(),
690
+ },
691
+ {
692
+ key: `redo`,
693
+ condition: () => !!canRedo,
694
+ action: () => redo?.(),
695
+ },
590
696
  ];
591
697
  for (const { key, condition, action } of shortcut_actions) {
592
698
  if (matches_shortcut(event, effective_shortcuts[key]) && condition()) {
@@ -659,7 +765,10 @@ function remove_all(event) {
659
765
  // Only fire events if something was actually removed
660
766
  if (removed_options.length > 0) {
661
767
  searchText = ``; // always clear on remove all (resetFilterOnAdd only applies to add operations)
662
- last_action = { type: `removeAll`, label: `${removed_options.length} options` };
768
+ last_action = {
769
+ type: `removeAll`,
770
+ label: `${removed_options.length} options`,
771
+ };
663
772
  onremoveAll?.({ options: removed_options });
664
773
  onchange?.({ options: selected, type: `removeAll` });
665
774
  }
@@ -742,8 +851,9 @@ function on_click_outside(event) {
742
851
  if (outerDiv.contains(target))
743
852
  return;
744
853
  // If portal is active, also check if click is inside the portalled options dropdown
745
- if (portal_params?.active && ul_options && ul_options.contains(target))
854
+ if (portal_params?.active && ul_options && ul_options.contains(target)) {
746
855
  return;
856
+ }
747
857
  // Click is outside both the main component and any portalled dropdown
748
858
  close_dropdown(event);
749
859
  }
@@ -855,8 +965,9 @@ function portal(node, params) {
855
965
  target_node = params.target_node;
856
966
  render_in_place = typeof window === `undefined` ||
857
967
  !document.body.contains(node);
858
- if (open && !render_in_place && target_node)
968
+ if (open && !render_in_place && target_node) {
859
969
  tick().then(update_position);
970
+ }
860
971
  else if (!open || !target_node)
861
972
  node.hidden = true;
862
973
  },
@@ -871,7 +982,8 @@ function portal(node, params) {
871
982
  }
872
983
  // Dynamic options loading - captures search at call time to avoid race conditions
873
984
  async function load_dynamic_options(reset) {
874
- if (!load_options_config || load_options_loading ||
985
+ if (!load_options_config ||
986
+ load_options_loading ||
875
987
  (!reset && !load_options_has_more)) {
876
988
  return;
877
989
  }
@@ -932,11 +1044,13 @@ $effect(() => {
932
1044
  };
933
1045
  });
934
1046
  function handle_options_scroll(event) {
935
- if (!load_options_config || load_options_loading || !load_options_has_more)
1047
+ if (!load_options_config || load_options_loading || !load_options_has_more) {
936
1048
  return;
1049
+ }
937
1050
  const { scrollTop, scrollHeight, clientHeight } = event.target;
938
- if (scrollHeight - scrollTop - clientHeight <= 100)
1051
+ if (scrollHeight - scrollTop - clientHeight <= 100) {
939
1052
  load_dynamic_options(false);
1053
+ }
940
1054
  }
941
1055
  </script>
942
1056
 
@@ -1000,11 +1114,9 @@ function handle_options_scroll(event) {
1000
1114
  style={ulSelectedStyle}
1001
1115
  >
1002
1116
  {#each selected as option, idx (duplicates ? `${key(option)}-${idx}` : key(option))}
1003
- {@const selectedOptionStyle =
1004
- [get_style(option, `selected`), liSelectedStyle].filter(Boolean).join(
1005
- ` `,
1006
- ) ||
1007
- null}
1117
+ {@const selectedOptionStyle = [utils.get_style(option, `selected`), liSelectedStyle]
1118
+ .filter(Boolean)
1119
+ .join(` `) || null}
1008
1120
  <li
1009
1121
  class={liSelectedClass}
1010
1122
  role="option"
@@ -1022,26 +1134,20 @@ function handle_options_scroll(event) {
1022
1134
  onmouseup={(event) => event.stopPropagation()}
1023
1135
  >
1024
1136
  {#if selectedItem}
1025
- {@render selectedItem({
1026
- option,
1027
- idx,
1028
- })}
1137
+ {@render selectedItem({ option, idx })}
1029
1138
  {:else if children}
1030
- {@render children({
1031
- option,
1032
- idx,
1033
- })}
1139
+ {@render children({ option, idx })}
1034
1140
  {:else if parseLabelsAsHtml}
1035
- {@html get_label(option)}
1141
+ {@html utils.get_label(option)}
1036
1142
  {:else}
1037
- {get_label(option)}
1143
+ {utils.get_label(option)}
1038
1144
  {/if}
1039
1145
  {#if !disabled && can_remove}
1040
1146
  <button
1041
1147
  onclick={(event) => remove(option, event)}
1042
1148
  onkeydown={if_enter_or_space((event) => remove(option, event))}
1043
1149
  type="button"
1044
- title="{removeBtnTitle} {get_label(option)}"
1150
+ title="{removeBtnTitle} {utils.get_label(option)}"
1045
1151
  class="remove"
1046
1152
  >
1047
1153
  {#if removeIcon}
@@ -1188,8 +1294,9 @@ function handle_options_scroll(event) {
1188
1294
  (group_name ?? `ungrouped-${group_idx}`)
1189
1295
  }
1190
1296
  {#if group_name !== null}
1191
- {@const { all_selected, selected_count } = group_header_state.get(group_name) ??
1192
- { all_selected: false, selected_count: 0 }}
1297
+ {@const { all_selected, selected_count } = group_header_state.get(
1298
+ group_name,
1299
+ ) ?? { all_selected: false, selected_count: 0 }}
1193
1300
  {@const handle_toggle = () =>
1194
1301
  collapsibleGroups && toggle_group_collapsed(group_name)}
1195
1302
  {@const handle_group_select = (event: Event) =>
@@ -1253,14 +1360,12 @@ function handle_options_scroll(event) {
1253
1360
  title = null,
1254
1361
  selectedTitle = null,
1255
1362
  disabledTitle = defaultDisabledTitle,
1256
- } = is_object(option_item) ? option_item : { label: option_item }}
1363
+ } = utils.is_object(option_item) ? option_item : { label: option_item }}
1257
1364
  {@const active = activeIndex === flat_idx && flat_idx >= 0}
1258
1365
  {@const selected = is_selected(label)}
1259
- {@const optionStyle =
1260
- [get_style(option_item, `option`), liOptionStyle].filter(Boolean).join(
1261
- ` `,
1262
- ) ||
1263
- null}
1366
+ {@const optionStyle = [utils.get_style(option_item, `option`), liOptionStyle]
1367
+ .filter(Boolean)
1368
+ .join(` `) || null}
1264
1369
  {#if is_option_visible(flat_idx)}
1265
1370
  <li
1266
1371
  id="{internal_id}-opt-{flat_idx}"
@@ -1290,24 +1395,18 @@ function handle_options_scroll(event) {
1290
1395
  type="checkbox"
1291
1396
  class="option-checkbox"
1292
1397
  checked={selected}
1293
- aria-label="Toggle {get_label(option_item)}"
1398
+ aria-label="Toggle {utils.get_label(option_item)}"
1294
1399
  tabindex="-1"
1295
1400
  />
1296
1401
  {/if}
1297
1402
  {#if option}
1298
- {@render option({
1299
- option: option_item,
1300
- idx: flat_idx,
1301
- })}
1403
+ {@render option({ option: option_item, idx: flat_idx })}
1302
1404
  {:else if children}
1303
- {@render children({
1304
- option: option_item,
1305
- idx: flat_idx,
1306
- })}
1405
+ {@render children({ option: option_item, idx: flat_idx })}
1307
1406
  {:else if parseLabelsAsHtml}
1308
- {@html get_label(option_item)}
1407
+ {@html utils.get_label(option_item)}
1309
1408
  {:else}
1310
- {get_label(option_item)}
1409
+ {utils.get_label(option_item)}
1311
1410
  {/if}
1312
1411
  </li>
1313
1412
  {/if}
@@ -1366,7 +1465,11 @@ function handle_options_scroll(event) {
1366
1465
  {/if}
1367
1466
  {/if}
1368
1467
  {#if loadOptions && load_options_loading}
1369
- <li class="loading-more" role="status" aria-label="Loading more options">
1468
+ <li
1469
+ class="loading-more"
1470
+ role="status"
1471
+ aria-label="Loading more options"
1472
+ >
1370
1473
  <CircleSpinner />
1371
1474
  </li>
1372
1475
  {/if}
@@ -1427,7 +1530,10 @@ function handle_options_scroll(event) {
1427
1530
  z-index: var(--sms-open-z-index, 4);
1428
1531
  }
1429
1532
  :where(div.multiselect:focus-within) {
1430
- border: var(--sms-focus-border, 1pt solid var(--sms-active-color, cornflowerblue));
1533
+ border: var(
1534
+ --sms-focus-border,
1535
+ 1pt solid var(--sms-active-color, cornflowerblue)
1536
+ );
1431
1537
  }
1432
1538
  :where(div.multiselect.disabled) {
1433
1539
  background: var(--sms-disabled-bg, light-dark(lightgray, #444));
@@ -1462,7 +1568,10 @@ function handle_options_scroll(event) {
1462
1568
  :where(div.multiselect > ul.selected > li.active) {
1463
1569
  background: var(
1464
1570
  --sms-li-active-bg,
1465
- var(--sms-active-color, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15)))
1571
+ var(
1572
+ --sms-active-color,
1573
+ light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15))
1574
+ )
1466
1575
  );
1467
1576
  }
1468
1577
  :is(div.multiselect button) {
@@ -1539,10 +1648,8 @@ function handle_options_scroll(event) {
1539
1648
  width: 100%;
1540
1649
  /* Default z-index if not portaled/overridden by portal */
1541
1650
  z-index: var(--sms-options-z-index, 3);
1542
-
1543
1651
  overflow: auto;
1544
- transition: all
1545
- 0.2s; /* Consider if this transition is desirable with portal positioning */
1652
+ transition: all 0.2s; /* is this transition is desirable with portal positioning? */
1546
1653
  box-sizing: border-box;
1547
1654
  background: var(--sms-options-bg, light-dark(#fafafa, #1a1a1a));
1548
1655
  max-height: var(--sms-options-max-height, 50vh);
@@ -1587,7 +1694,10 @@ function handle_options_scroll(event) {
1587
1694
  :where(ul.options > li.active) {
1588
1695
  background: var(
1589
1696
  --sms-li-active-bg,
1590
- var(--sms-active-color, light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15)))
1697
+ var(
1698
+ --sms-active-color,
1699
+ light-dark(rgba(0, 0, 0, 0.15), rgba(255, 255, 255, 0.15))
1700
+ )
1591
1701
  );
1592
1702
  }
1593
1703
  :where(ul.options > li.disabled) {
@@ -1641,7 +1751,10 @@ function handle_options_scroll(event) {
1641
1751
  }
1642
1752
  :where(ul.options > li.group-header:not(:first-child)) {
1643
1753
  margin-top: var(--sms-group-header-margin-top, 4pt);
1644
- border-top: var(--sms-group-header-border-top, 1px solid light-dark(#eee, #333));
1754
+ border-top: var(
1755
+ --sms-group-header-border-top,
1756
+ 1px solid light-dark(#eee, #333)
1757
+ );
1645
1758
  }
1646
1759
  :where(ul.options > li.group-header.collapsible) {
1647
1760
  cursor: pointer;
@@ -2,7 +2,7 @@ import type { MultiSelectProps } from './types';
2
2
  declare function $$render<Option extends import('./types').Option>(): {
3
3
  props: MultiSelectProps<Option>;
4
4
  exports: {};
5
- bindings: "input" | "invalid" | "value" | "selected" | "open" | "activeIndex" | "activeOption" | "form_input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups";
5
+ bindings: "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups" | "undo" | "redo" | "canUndo" | "canRedo";
6
6
  slots: {};
7
7
  events: {};
8
8
  };
@@ -10,7 +10,7 @@ declare class __sveltets_Render<Option extends import('./types').Option> {
10
10
  props(): ReturnType<typeof $$render<Option>>['props'];
11
11
  events(): ReturnType<typeof $$render<Option>>['events'];
12
12
  slots(): ReturnType<typeof $$render<Option>>['slots'];
13
- bindings(): "input" | "invalid" | "value" | "selected" | "open" | "activeIndex" | "activeOption" | "form_input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups";
13
+ bindings(): "value" | "selected" | "invalid" | "open" | "activeIndex" | "activeOption" | "form_input" | "input" | "matchingOptions" | "maxSelect" | "options" | "outerDiv" | "searchText" | "collapsedGroups" | "collapseAllGroups" | "expandAllGroups" | "undo" | "redo" | "canUndo" | "canRedo";
14
14
  exports(): {};
15
15
  }
16
16
  interface $$IsomorphicComponent {