svelte-select-5 6.2.3 → 6.2.4

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/Select.svelte CHANGED
@@ -166,20 +166,6 @@
166
166
  requiredSlot: requiredSlotSnippet = undefined,
167
167
  } = $props();
168
168
 
169
- // Prop validation (runs once on init)
170
- if (typeof itemId !== 'string') {
171
- console.warn('[svelte-select-5] itemId must be a string, using "value"');
172
- itemId = 'value';
173
- }
174
- if (typeof label !== 'string') {
175
- console.warn('[svelte-select-5] label must be a string, using "label"');
176
- label = 'label';
177
- }
178
- if (loadOptions !== undefined && typeof loadOptions !== 'function') {
179
- console.warn('[svelte-select-5] loadOptions must be a function');
180
- loadOptions = undefined;
181
- }
182
-
183
169
  // Internal state
184
170
  let timeout;
185
171
  let activeValue = $state(undefined);
@@ -196,6 +182,31 @@
196
182
  let loadRequestVersion = 0;
197
183
  let isScrollingTimer;
198
184
 
185
+ // Validated props using $derived to avoid state_referenced_locally warning
186
+ const _itemId = $derived.by(() => {
187
+ if (typeof itemId !== 'string') {
188
+ console.warn('[svelte-select-5] itemId must be a string, using "value"');
189
+ return 'value';
190
+ }
191
+ return itemId;
192
+ });
193
+
194
+ const _label = $derived.by(() => {
195
+ if (typeof label !== 'string') {
196
+ console.warn('[svelte-select-5] label must be a string, using "label"');
197
+ return 'label';
198
+ }
199
+ return label;
200
+ });
201
+
202
+ const _loadOptions = $derived.by(() => {
203
+ if (loadOptions !== undefined && typeof loadOptions !== 'function') {
204
+ console.warn('[svelte-select-5] loadOptions must be a function');
205
+ return undefined;
206
+ }
207
+ return loadOptions;
208
+ });
209
+
199
210
  // Floating UI config - using closure for listOffset to capture current value
200
211
  let _floatingConfig = {
201
212
  strategy: 'absolute',
@@ -221,9 +232,9 @@
221
232
  // Helper functions
222
233
  function setValue() {
223
234
  if (typeof value === 'string') {
224
- let item = (items || []).find((item) => item[itemId] === value);
235
+ let item = (items || []).find((item) => item[_itemId] === value);
225
236
  value = item || {
226
- [itemId]: value,
237
+ [_itemId]: value,
227
238
  label: value,
228
239
  };
229
240
  } else if (multiple && Array.isArray(value) && value.length > 0) {
@@ -314,7 +325,7 @@
314
325
  return;
315
326
  }
316
327
 
317
- if (!prev_value || value[itemId] !== prev_value[itemId]) {
328
+ if (!prev_value || value[_itemId] !== prev_value[_itemId]) {
318
329
  oninput?.(value);
319
330
  }
320
331
  }
@@ -331,7 +342,7 @@
331
342
 
332
343
  function setValueIndexAsHoverIndex() {
333
344
  const valueIndex = filteredItems.findIndex((i) => {
334
- return i[itemId] === value[itemId];
345
+ return i[_itemId] === value[_itemId];
335
346
  });
336
347
 
337
348
  checkHoverSelectable(valueIndex, true);
@@ -349,9 +360,9 @@
349
360
  }
350
361
 
351
362
  function setupFilterText() {
352
- if (!loadOptions && filterText.length === 0) return;
363
+ if (!_loadOptions && filterText.length === 0) return;
353
364
 
354
- if (loadOptions) {
365
+ if (_loadOptions) {
355
366
  debounce(async function () {
356
367
  const currentVersion = ++loadRequestVersion;
357
368
  loading = true;
@@ -361,7 +372,7 @@
361
372
  if (event === 'error') onerror?.(data);
362
373
  if (event === 'loaded') onloaded?.(data);
363
374
  },
364
- loadOptions,
375
+ loadOptions: _loadOptions,
365
376
  convertStringItemsToObjects,
366
377
  filterText,
367
378
  });
@@ -394,9 +405,9 @@
394
405
  function computeJustValue() {
395
406
  if (!value) return multiple ? null : undefined;
396
407
  if (multiple) {
397
- return value.map((item) => item?.[itemId]).filter(id => id !== undefined);
408
+ return value.map((item) => item?.[_itemId]).filter(id => id !== undefined);
398
409
  }
399
- const id = value[itemId];
410
+ const id = value[_itemId];
400
411
  return id !== undefined ? id : undefined;
401
412
  }
402
413
 
@@ -408,7 +419,7 @@
408
419
  let noDuplicates = true;
409
420
 
410
421
  for (const val of value) {
411
- const id = val[itemId];
422
+ const id = val[_itemId];
412
423
  if (seen.has(id)) {
413
424
  noDuplicates = false;
414
425
  } else {
@@ -422,13 +433,13 @@
422
433
  }
423
434
 
424
435
  function findItem(selection) {
425
- let matchTo = selection ? selection[itemId] : value[itemId];
426
- return items.find((item) => item[itemId] === matchTo);
436
+ let matchTo = selection ? selection[_itemId] : value[_itemId];
437
+ return items.find((item) => item[_itemId] === matchTo);
427
438
  }
428
439
 
429
440
  function updateValueDisplay(items) {
430
441
  if (!items || items.length === 0 || items.some((item) => typeof item !== 'object')) return;
431
- if (!value || (multiple ? value.some((selection) => !selection || !selection[itemId]) : !value[itemId])) return;
442
+ if (!value || (multiple ? value.some((selection) => !selection || !selection[_itemId]) : !value[_itemId])) return;
432
443
 
433
444
  if (Array.isArray(value)) {
434
445
  // Check if any value needs updating - compare properties, not references
@@ -437,7 +448,7 @@
437
448
  const newValue = value.map((selection) => {
438
449
  const found = findItem(selection);
439
450
  // Only update if found item has different properties (not just different reference)
440
- if (found && found[itemId] === selection[itemId]) {
451
+ if (found && found[_itemId] === selection[_itemId]) {
441
452
  // Same itemId - check if other properties differ using shallow comparison
442
453
  if (!shallowEqual(found, selection)) {
443
454
  needsUpdate = true;
@@ -452,7 +463,7 @@
452
463
  } else {
453
464
  const found = findItem();
454
465
  // Only update if found item has different properties
455
- if (found && found[itemId] === value[itemId]) {
466
+ if (found && found[_itemId] === value[_itemId]) {
456
467
  if (!shallowEqual(found, value)) {
457
468
  value = found;
458
469
  }
@@ -489,7 +500,7 @@
489
500
  if (filteredItems.length === 0) break;
490
501
  const hoverItem = filteredItems[hoverItemIndex];
491
502
 
492
- if (value && !multiple && value[itemId] === hoverItem[itemId]) {
503
+ if (value && !multiple && value[_itemId] === hoverItem[_itemId]) {
493
504
  closeList();
494
505
  break;
495
506
  } else {
@@ -524,7 +535,7 @@
524
535
  if (listOpen && focused) {
525
536
  if (
526
537
  filteredItems.length === 0 ||
527
- (value && value[itemId] === filteredItems[hoverItemIndex][itemId])
538
+ (value && value[_itemId] === filteredItems[hoverItemIndex][_itemId])
528
539
  )
529
540
  return closeList();
530
541
 
@@ -615,9 +626,9 @@
615
626
  let selected = undefined;
616
627
 
617
628
  if (_multiple && value.length > 0) {
618
- selected = value.map((v) => v[label]).join(', ');
629
+ selected = value.map((v) => v[_label]).join(', ');
619
630
  } else {
620
- selected = value[label];
631
+ selected = value[_label];
621
632
  }
622
633
 
623
634
  return ariaValues(selected);
@@ -628,7 +639,7 @@
628
639
  let _item = filteredItems[hoverItemIndex];
629
640
  if (listOpen && _item) {
630
641
  let count = filteredItems ? filteredItems.length : 0;
631
- return ariaListOpen(_item[label], count);
642
+ return ariaListOpen(_item[_label], count);
632
643
  } else {
633
644
  return ariaFocused();
634
645
  }
@@ -660,7 +671,7 @@
660
671
  function handleItemClick(args) {
661
672
  const { item, i } = args;
662
673
  if (item?.selectable === false) return;
663
- if (value && !multiple && value[itemId] === item[itemId]) return closeList();
674
+ if (value && !multiple && value[_itemId] === item[_itemId]) return closeList();
664
675
  if (isItemSelectable(item)) {
665
676
  hoverItemIndex = i;
666
677
  handleSelect(item);
@@ -750,14 +761,14 @@
750
761
 
751
762
  // Derived values - order matters! filteredItems must come before ariaContext
752
763
  let filteredItems = $derived(filter({
753
- loadOptions,
764
+ loadOptions: _loadOptions,
754
765
  filterText,
755
766
  items,
756
767
  multiple,
757
768
  value,
758
- itemId,
769
+ itemId: _itemId,
759
770
  groupBy,
760
- label,
771
+ label: _label,
761
772
  filterSelectedItems,
762
773
  itemFilter,
763
774
  convertStringItemsToObjects,
@@ -830,8 +841,10 @@
830
841
  // This prevents loops when parent components create new array references on each render
831
842
  $effect(() => {
832
843
  if (items !== prevItemsRef) {
833
- // Only update if content differs, not just reference
834
- if (!arrayShallowEqual(items, prevItemsRef)) {
844
+ // Use $state.snapshot() to compare plain objects, avoiding proxy identity issues
845
+ const itemsSnapshot = items ? $state.snapshot(items) : items;
846
+ const prevSnapshot = prevItemsRef ? $state.snapshot(prevItemsRef) : prevItemsRef;
847
+ if (!arrayShallowEqual(itemsSnapshot, prevSnapshot)) {
835
848
  updateValueDisplay(items);
836
849
  }
837
850
  prevItemsRef = items;
@@ -845,9 +858,9 @@
845
858
  // Helper function to resolve justValue to value
846
859
  function resolveJustValue(jv) {
847
860
  if (multiple) {
848
- value = jv ? items.filter(item => jv.includes(item[itemId])) : null;
861
+ value = jv ? items.filter(item => jv.includes(item[_itemId])) : null;
849
862
  } else {
850
- value = jv != null ? items.find(item => item[itemId] === jv) ?? null : null;
863
+ value = jv != null ? items.find(item => item[_itemId] === jv) ?? null : null;
851
864
  }
852
865
  }
853
866
 
@@ -967,11 +980,11 @@
967
980
  tabindex="-1"
968
981
  role="none">
969
982
  <div
970
- use:activeScroll={{ scroll: isItemActive(item, value, itemId), listDom }}
983
+ use:activeScroll={{ scroll: isItemActive(item, value, _itemId), listDom }}
971
984
  use:hoverScroll={{ scroll: scrollToHoverItem === i, listDom }}
972
985
  class="item"
973
986
  class:list-group-title={item.groupHeader}
974
- class:active={isItemActive(item, value, itemId)}
987
+ class:active={isItemActive(item, value, _itemId)}
975
988
  class:first={isItemFirst(i)}
976
989
  class:hover={hoverItemIndex === i}
977
990
  class:group-item={item.groupItem}
@@ -979,7 +992,7 @@
979
992
  {#if itemSnippet}
980
993
  {@render itemSnippet({ item, index: i })}
981
994
  {:else}
982
- {item?.[label]}
995
+ {item?.[_label]}
983
996
  {/if}
984
997
  </div>
985
998
  </div>
@@ -1025,7 +1038,7 @@
1025
1038
  {#if selectionSnippet}
1026
1039
  {@render selectionSnippet({ selection: item, index: i })}
1027
1040
  {:else}
1028
- {item[label]}
1041
+ {item[_label]}
1029
1042
  {/if}
1030
1043
  </span>
1031
1044
 
@@ -1047,7 +1060,7 @@
1047
1060
  {#if selectionSnippet}
1048
1061
  {@render selectionSnippet({ selection: value })}
1049
1062
  {:else}
1050
- {value[label]}
1063
+ {value[_label]}
1051
1064
  {/if}
1052
1065
  </div>
1053
1066
  {/if}
@@ -166,20 +166,6 @@
166
166
  requiredSlot: requiredSlotSnippet = undefined,
167
167
  } = $props();
168
168
 
169
- // Prop validation (runs once on init)
170
- if (typeof itemId !== 'string') {
171
- console.warn('[svelte-select-5] itemId must be a string, using "value"');
172
- itemId = 'value';
173
- }
174
- if (typeof label !== 'string') {
175
- console.warn('[svelte-select-5] label must be a string, using "label"');
176
- label = 'label';
177
- }
178
- if (loadOptions !== undefined && typeof loadOptions !== 'function') {
179
- console.warn('[svelte-select-5] loadOptions must be a function');
180
- loadOptions = undefined;
181
- }
182
-
183
169
  // Internal state
184
170
  let timeout;
185
171
  let activeValue = $state(undefined);
@@ -196,6 +182,31 @@
196
182
  let loadRequestVersion = 0;
197
183
  let isScrollingTimer;
198
184
 
185
+ // Validated props using $derived to avoid state_referenced_locally warning
186
+ const _itemId = $derived.by(() => {
187
+ if (typeof itemId !== 'string') {
188
+ console.warn('[svelte-select-5] itemId must be a string, using "value"');
189
+ return 'value';
190
+ }
191
+ return itemId;
192
+ });
193
+
194
+ const _label = $derived.by(() => {
195
+ if (typeof label !== 'string') {
196
+ console.warn('[svelte-select-5] label must be a string, using "label"');
197
+ return 'label';
198
+ }
199
+ return label;
200
+ });
201
+
202
+ const _loadOptions = $derived.by(() => {
203
+ if (loadOptions !== undefined && typeof loadOptions !== 'function') {
204
+ console.warn('[svelte-select-5] loadOptions must be a function');
205
+ return undefined;
206
+ }
207
+ return loadOptions;
208
+ });
209
+
199
210
  // Floating UI config - using closure for listOffset to capture current value
200
211
  let _floatingConfig = {
201
212
  strategy: 'absolute',
@@ -221,9 +232,9 @@
221
232
  // Helper functions
222
233
  function setValue() {
223
234
  if (typeof value === 'string') {
224
- let item = (items || []).find((item) => item[itemId] === value);
235
+ let item = (items || []).find((item) => item[_itemId] === value);
225
236
  value = item || {
226
- [itemId]: value,
237
+ [_itemId]: value,
227
238
  label: value,
228
239
  };
229
240
  } else if (multiple && Array.isArray(value) && value.length > 0) {
@@ -314,7 +325,7 @@
314
325
  return;
315
326
  }
316
327
 
317
- if (!prev_value || value[itemId] !== prev_value[itemId]) {
328
+ if (!prev_value || value[_itemId] !== prev_value[_itemId]) {
318
329
  oninput?.(value);
319
330
  }
320
331
  }
@@ -331,7 +342,7 @@
331
342
 
332
343
  function setValueIndexAsHoverIndex() {
333
344
  const valueIndex = filteredItems.findIndex((i) => {
334
- return i[itemId] === value[itemId];
345
+ return i[_itemId] === value[_itemId];
335
346
  });
336
347
 
337
348
  checkHoverSelectable(valueIndex, true);
@@ -349,9 +360,9 @@
349
360
  }
350
361
 
351
362
  function setupFilterText() {
352
- if (!loadOptions && filterText.length === 0) return;
363
+ if (!_loadOptions && filterText.length === 0) return;
353
364
 
354
- if (loadOptions) {
365
+ if (_loadOptions) {
355
366
  debounce(async function () {
356
367
  const currentVersion = ++loadRequestVersion;
357
368
  loading = true;
@@ -361,7 +372,7 @@
361
372
  if (event === 'error') onerror?.(data);
362
373
  if (event === 'loaded') onloaded?.(data);
363
374
  },
364
- loadOptions,
375
+ loadOptions: _loadOptions,
365
376
  convertStringItemsToObjects,
366
377
  filterText,
367
378
  });
@@ -394,9 +405,9 @@
394
405
  function computeJustValue() {
395
406
  if (!value) return multiple ? null : undefined;
396
407
  if (multiple) {
397
- return value.map((item) => item?.[itemId]).filter(id => id !== undefined);
408
+ return value.map((item) => item?.[_itemId]).filter(id => id !== undefined);
398
409
  }
399
- const id = value[itemId];
410
+ const id = value[_itemId];
400
411
  return id !== undefined ? id : undefined;
401
412
  }
402
413
 
@@ -408,7 +419,7 @@
408
419
  let noDuplicates = true;
409
420
 
410
421
  for (const val of value) {
411
- const id = val[itemId];
422
+ const id = val[_itemId];
412
423
  if (seen.has(id)) {
413
424
  noDuplicates = false;
414
425
  } else {
@@ -422,13 +433,13 @@
422
433
  }
423
434
 
424
435
  function findItem(selection) {
425
- let matchTo = selection ? selection[itemId] : value[itemId];
426
- return items.find((item) => item[itemId] === matchTo);
436
+ let matchTo = selection ? selection[_itemId] : value[_itemId];
437
+ return items.find((item) => item[_itemId] === matchTo);
427
438
  }
428
439
 
429
440
  function updateValueDisplay(items) {
430
441
  if (!items || items.length === 0 || items.some((item) => typeof item !== 'object')) return;
431
- if (!value || (multiple ? value.some((selection) => !selection || !selection[itemId]) : !value[itemId])) return;
442
+ if (!value || (multiple ? value.some((selection) => !selection || !selection[_itemId]) : !value[_itemId])) return;
432
443
 
433
444
  if (Array.isArray(value)) {
434
445
  // Check if any value needs updating - compare properties, not references
@@ -437,7 +448,7 @@
437
448
  const newValue = value.map((selection) => {
438
449
  const found = findItem(selection);
439
450
  // Only update if found item has different properties (not just different reference)
440
- if (found && found[itemId] === selection[itemId]) {
451
+ if (found && found[_itemId] === selection[_itemId]) {
441
452
  // Same itemId - check if other properties differ using shallow comparison
442
453
  if (!shallowEqual(found, selection)) {
443
454
  needsUpdate = true;
@@ -452,7 +463,7 @@
452
463
  } else {
453
464
  const found = findItem();
454
465
  // Only update if found item has different properties
455
- if (found && found[itemId] === value[itemId]) {
466
+ if (found && found[_itemId] === value[_itemId]) {
456
467
  if (!shallowEqual(found, value)) {
457
468
  value = found;
458
469
  }
@@ -489,7 +500,7 @@
489
500
  if (filteredItems.length === 0) break;
490
501
  const hoverItem = filteredItems[hoverItemIndex];
491
502
 
492
- if (value && !multiple && value[itemId] === hoverItem[itemId]) {
503
+ if (value && !multiple && value[_itemId] === hoverItem[_itemId]) {
493
504
  closeList();
494
505
  break;
495
506
  } else {
@@ -524,7 +535,7 @@
524
535
  if (listOpen && focused) {
525
536
  if (
526
537
  filteredItems.length === 0 ||
527
- (value && value[itemId] === filteredItems[hoverItemIndex][itemId])
538
+ (value && value[_itemId] === filteredItems[hoverItemIndex][_itemId])
528
539
  )
529
540
  return closeList();
530
541
 
@@ -615,9 +626,9 @@
615
626
  let selected = undefined;
616
627
 
617
628
  if (_multiple && value.length > 0) {
618
- selected = value.map((v) => v[label]).join(', ');
629
+ selected = value.map((v) => v[_label]).join(', ');
619
630
  } else {
620
- selected = value[label];
631
+ selected = value[_label];
621
632
  }
622
633
 
623
634
  return ariaValues(selected);
@@ -628,7 +639,7 @@
628
639
  let _item = filteredItems[hoverItemIndex];
629
640
  if (listOpen && _item) {
630
641
  let count = filteredItems ? filteredItems.length : 0;
631
- return ariaListOpen(_item[label], count);
642
+ return ariaListOpen(_item[_label], count);
632
643
  } else {
633
644
  return ariaFocused();
634
645
  }
@@ -660,7 +671,7 @@
660
671
  function handleItemClick(args) {
661
672
  const { item, i } = args;
662
673
  if (item?.selectable === false) return;
663
- if (value && !multiple && value[itemId] === item[itemId]) return closeList();
674
+ if (value && !multiple && value[_itemId] === item[_itemId]) return closeList();
664
675
  if (isItemSelectable(item)) {
665
676
  hoverItemIndex = i;
666
677
  handleSelect(item);
@@ -750,14 +761,14 @@
750
761
 
751
762
  // Derived values - order matters! filteredItems must come before ariaContext
752
763
  let filteredItems = $derived(filter({
753
- loadOptions,
764
+ loadOptions: _loadOptions,
754
765
  filterText,
755
766
  items,
756
767
  multiple,
757
768
  value,
758
- itemId,
769
+ itemId: _itemId,
759
770
  groupBy,
760
- label,
771
+ label: _label,
761
772
  filterSelectedItems,
762
773
  itemFilter,
763
774
  convertStringItemsToObjects,
@@ -830,8 +841,10 @@
830
841
  // This prevents loops when parent components create new array references on each render
831
842
  $effect(() => {
832
843
  if (items !== prevItemsRef) {
833
- // Only update if content differs, not just reference
834
- if (!arrayShallowEqual(items, prevItemsRef)) {
844
+ // Use $state.snapshot() to compare plain objects, avoiding proxy identity issues
845
+ const itemsSnapshot = items ? $state.snapshot(items) : items;
846
+ const prevSnapshot = prevItemsRef ? $state.snapshot(prevItemsRef) : prevItemsRef;
847
+ if (!arrayShallowEqual(itemsSnapshot, prevSnapshot)) {
835
848
  updateValueDisplay(items);
836
849
  }
837
850
  prevItemsRef = items;
@@ -845,9 +858,9 @@
845
858
  // Helper function to resolve justValue to value
846
859
  function resolveJustValue(jv) {
847
860
  if (multiple) {
848
- value = jv ? items.filter(item => jv.includes(item[itemId])) : null;
861
+ value = jv ? items.filter(item => jv.includes(item[_itemId])) : null;
849
862
  } else {
850
- value = jv != null ? items.find(item => item[itemId] === jv) ?? null : null;
863
+ value = jv != null ? items.find(item => item[_itemId] === jv) ?? null : null;
851
864
  }
852
865
  }
853
866
 
@@ -967,11 +980,11 @@
967
980
  tabindex="-1"
968
981
  role="none">
969
982
  <div
970
- use:activeScroll={{ scroll: isItemActive(item, value, itemId), listDom }}
983
+ use:activeScroll={{ scroll: isItemActive(item, value, _itemId), listDom }}
971
984
  use:hoverScroll={{ scroll: scrollToHoverItem === i, listDom }}
972
985
  class="item"
973
986
  class:list-group-title={item.groupHeader}
974
- class:active={isItemActive(item, value, itemId)}
987
+ class:active={isItemActive(item, value, _itemId)}
975
988
  class:first={isItemFirst(i)}
976
989
  class:hover={hoverItemIndex === i}
977
990
  class:group-item={item.groupItem}
@@ -979,7 +992,7 @@
979
992
  {#if itemSnippet}
980
993
  {@render itemSnippet({ item, index: i })}
981
994
  {:else}
982
- {item?.[label]}
995
+ {item?.[_label]}
983
996
  {/if}
984
997
  </div>
985
998
  </div>
@@ -1025,7 +1038,7 @@
1025
1038
  {#if selectionSnippet}
1026
1039
  {@render selectionSnippet({ selection: item, index: i })}
1027
1040
  {:else}
1028
- {item[label]}
1041
+ {item[_label]}
1029
1042
  {/if}
1030
1043
  </span>
1031
1044
 
@@ -1047,7 +1060,7 @@
1047
1060
  {#if selectionSnippet}
1048
1061
  {@render selectionSnippet({ selection: value })}
1049
1062
  {:else}
1050
- {value[label]}
1063
+ {value[_label]}
1051
1064
  {/if}
1052
1065
  </div>
1053
1066
  {/if}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-select-5",
3
- "version": "6.2.3",
3
+ "version": "6.2.4",
4
4
  "description": "A <Select> component for Svelte 5 apps (fork of svelte-select)",
5
5
  "repository": "https://github.com/Dbone29/svelte-select-5.git",
6
6
  "author": "Robert Balfré <rob.balfre@gmail.com> (https://github.com/rob-balfre)",