svelte-select-5 7.0.2 → 7.0.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
@@ -46,20 +46,25 @@
46
46
  import LoadingIcon from './LoadingIcon.svelte';
47
47
 
48
48
  // Performance: Polymorphic shallow equality comparison (faster than JSON.stringify)
49
- function shallowEqual(a, b) {
50
- if (a === b) return true;
51
- if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
52
-
53
- // Handle arrays
54
- if (Array.isArray(a) && Array.isArray(b)) {
55
- if (a.length !== b.length) return false;
56
- return a.every((item, i) => shallowEqual(item, b[i]));
49
+ // Uses $state.snapshot() at top level to avoid proxy equality mismatch warnings
50
+ function shallowEqual(a, b, _isNested = false) {
51
+ // Only snapshot at top level to avoid repeated snapshot overhead
52
+ const plainA = !_isNested && a && typeof a === 'object' ? $state.snapshot(a) : a;
53
+ const plainB = !_isNested && b && typeof b === 'object' ? $state.snapshot(b) : b;
54
+
55
+ if (plainA === plainB) return true;
56
+ if (!plainA || !plainB || typeof plainA !== 'object' || typeof plainB !== 'object') return false;
57
+
58
+ // Handle arrays - recursive comparison
59
+ if (Array.isArray(plainA) && Array.isArray(plainB)) {
60
+ if (plainA.length !== plainB.length) return false;
61
+ return plainA.every((item, i) => shallowEqual(item, plainB[i], true));
57
62
  }
58
63
 
59
64
  // Handle objects
60
- const keysA = Object.keys(a);
61
- if (keysA.length !== Object.keys(b).length) return false;
62
- return keysA.every(key => a[key] === b[key]);
65
+ const keysA = Object.keys(plainA);
66
+ if (keysA.length !== Object.keys(plainB).length) return false;
67
+ return keysA.every(key => plainA[key] === plainB[key]);
63
68
  }
64
69
 
65
70
  // Props with $props() rune
@@ -187,6 +192,7 @@
187
192
  let isScrollingTimer;
188
193
  let itemSelectedTimer;
189
194
  let startIdApplied = $state(false);
195
+ let startIdLoadTriggered = false; // Flag to prevent repeated load triggers
190
196
  let lastFilterText = ''; // Non-reactive for effect comparison
191
197
 
192
198
  // Validated props using $derived to avoid state_referenced_locally warning
@@ -856,19 +862,18 @@
856
862
  // Update value display when items change (untrack previousItemsRef to prevent loop)
857
863
  $effect(() => {
858
864
  const prevRef = untrack(() => previousItemsRef);
859
- if (items !== prevRef) {
860
- const itemsLen = items?.length ?? 0;
861
- const prevLen = prevRef?.length ?? 0;
862
- const hasChanged = itemsLen !== prevLen ||
863
- (itemsLen > 0 && (
864
- items[0]?.[validatedItemId] !== prevRef?.[0]?.[validatedItemId] ||
865
- items[itemsLen - 1]?.[validatedItemId] !== prevRef?.[prevLen - 1]?.[validatedItemId]
866
- ));
867
- if (hasChanged) {
868
- updateValueDisplay(items);
869
- }
870
- previousItemsRef = items;
865
+ const itemsLen = items?.length ?? 0;
866
+ const prevLen = prevRef?.length ?? 0;
867
+ // Compare by content, not reference, to avoid proxy issues
868
+ const hasChanged = itemsLen !== prevLen ||
869
+ (itemsLen > 0 && (
870
+ items[0]?.[validatedItemId] !== prevRef?.[0]?.[validatedItemId] ||
871
+ items[itemsLen - 1]?.[validatedItemId] !== prevRef?.[prevLen - 1]?.[validatedItemId]
872
+ ));
873
+ if (hasChanged) {
874
+ updateValueDisplay(items);
871
875
  }
876
+ previousItemsRef = items;
872
877
  });
873
878
 
874
879
  // Sync selectedValue → selectedId
@@ -902,7 +907,13 @@
902
907
  const valuesMatch = Array.isArray(selectedId) && Array.isArray(computed)
903
908
  ? selectedId.length === computed.length && selectedId.every((v, i) => v === computed[i])
904
909
  : selectedId === computed;
905
- const isExternalChange = selectedId !== untrack(() => previousSelectedId) && !valuesMatch;
910
+ // Use snapshot comparison to avoid proxy equality mismatch warnings
911
+ const currentIdSnapshot = selectedId !== undefined ? $state.snapshot(selectedId) : selectedId;
912
+ const prevIdSnapshot = untrack(() => previousSelectedId !== undefined ? $state.snapshot(previousSelectedId) : previousSelectedId);
913
+ const referenceDiffers = Array.isArray(currentIdSnapshot) && Array.isArray(prevIdSnapshot)
914
+ ? currentIdSnapshot.length !== prevIdSnapshot.length || currentIdSnapshot.some((v, i) => v !== prevIdSnapshot[i])
915
+ : currentIdSnapshot !== prevIdSnapshot;
916
+ const isExternalChange = referenceDiffers && !valuesMatch;
906
917
  if (isExternalChange) {
907
918
  if (!items) {
908
919
  pendingSelectedId = selectedId;
@@ -926,6 +937,14 @@
926
937
  }
927
938
  });
928
939
 
940
+ // Trigger initial load when startId is set with loadOptions but no items yet
941
+ $effect(() => {
942
+ if (startId !== undefined && validatedLoadOptions && !items && !startIdLoadTriggered) {
943
+ startIdLoadTriggered = true;
944
+ setupFilterText();
945
+ }
946
+ });
947
+
929
948
  $effect.pre(() => {
930
949
  readOnlySelectedValue = selectedValue;
931
950
  readOnlySelectedId = computeSelectedId();
@@ -46,20 +46,25 @@
46
46
  import LoadingIcon from './LoadingIcon.svelte';
47
47
 
48
48
  // Performance: Polymorphic shallow equality comparison (faster than JSON.stringify)
49
- function shallowEqual(a, b) {
50
- if (a === b) return true;
51
- if (!a || !b || typeof a !== 'object' || typeof b !== 'object') return false;
52
-
53
- // Handle arrays
54
- if (Array.isArray(a) && Array.isArray(b)) {
55
- if (a.length !== b.length) return false;
56
- return a.every((item, i) => shallowEqual(item, b[i]));
49
+ // Uses $state.snapshot() at top level to avoid proxy equality mismatch warnings
50
+ function shallowEqual(a, b, _isNested = false) {
51
+ // Only snapshot at top level to avoid repeated snapshot overhead
52
+ const plainA = !_isNested && a && typeof a === 'object' ? $state.snapshot(a) : a;
53
+ const plainB = !_isNested && b && typeof b === 'object' ? $state.snapshot(b) : b;
54
+
55
+ if (plainA === plainB) return true;
56
+ if (!plainA || !plainB || typeof plainA !== 'object' || typeof plainB !== 'object') return false;
57
+
58
+ // Handle arrays - recursive comparison
59
+ if (Array.isArray(plainA) && Array.isArray(plainB)) {
60
+ if (plainA.length !== plainB.length) return false;
61
+ return plainA.every((item, i) => shallowEqual(item, plainB[i], true));
57
62
  }
58
63
 
59
64
  // Handle objects
60
- const keysA = Object.keys(a);
61
- if (keysA.length !== Object.keys(b).length) return false;
62
- return keysA.every(key => a[key] === b[key]);
65
+ const keysA = Object.keys(plainA);
66
+ if (keysA.length !== Object.keys(plainB).length) return false;
67
+ return keysA.every(key => plainA[key] === plainB[key]);
63
68
  }
64
69
 
65
70
  // Props with $props() rune
@@ -187,6 +192,7 @@
187
192
  let isScrollingTimer;
188
193
  let itemSelectedTimer;
189
194
  let startIdApplied = $state(false);
195
+ let startIdLoadTriggered = false; // Flag to prevent repeated load triggers
190
196
  let lastFilterText = ''; // Non-reactive for effect comparison
191
197
 
192
198
  // Validated props using $derived to avoid state_referenced_locally warning
@@ -856,19 +862,18 @@
856
862
  // Update value display when items change (untrack previousItemsRef to prevent loop)
857
863
  $effect(() => {
858
864
  const prevRef = untrack(() => previousItemsRef);
859
- if (items !== prevRef) {
860
- const itemsLen = items?.length ?? 0;
861
- const prevLen = prevRef?.length ?? 0;
862
- const hasChanged = itemsLen !== prevLen ||
863
- (itemsLen > 0 && (
864
- items[0]?.[validatedItemId] !== prevRef?.[0]?.[validatedItemId] ||
865
- items[itemsLen - 1]?.[validatedItemId] !== prevRef?.[prevLen - 1]?.[validatedItemId]
866
- ));
867
- if (hasChanged) {
868
- updateValueDisplay(items);
869
- }
870
- previousItemsRef = items;
865
+ const itemsLen = items?.length ?? 0;
866
+ const prevLen = prevRef?.length ?? 0;
867
+ // Compare by content, not reference, to avoid proxy issues
868
+ const hasChanged = itemsLen !== prevLen ||
869
+ (itemsLen > 0 && (
870
+ items[0]?.[validatedItemId] !== prevRef?.[0]?.[validatedItemId] ||
871
+ items[itemsLen - 1]?.[validatedItemId] !== prevRef?.[prevLen - 1]?.[validatedItemId]
872
+ ));
873
+ if (hasChanged) {
874
+ updateValueDisplay(items);
871
875
  }
876
+ previousItemsRef = items;
872
877
  });
873
878
 
874
879
  // Sync selectedValue → selectedId
@@ -902,7 +907,13 @@
902
907
  const valuesMatch = Array.isArray(selectedId) && Array.isArray(computed)
903
908
  ? selectedId.length === computed.length && selectedId.every((v, i) => v === computed[i])
904
909
  : selectedId === computed;
905
- const isExternalChange = selectedId !== untrack(() => previousSelectedId) && !valuesMatch;
910
+ // Use snapshot comparison to avoid proxy equality mismatch warnings
911
+ const currentIdSnapshot = selectedId !== undefined ? $state.snapshot(selectedId) : selectedId;
912
+ const prevIdSnapshot = untrack(() => previousSelectedId !== undefined ? $state.snapshot(previousSelectedId) : previousSelectedId);
913
+ const referenceDiffers = Array.isArray(currentIdSnapshot) && Array.isArray(prevIdSnapshot)
914
+ ? currentIdSnapshot.length !== prevIdSnapshot.length || currentIdSnapshot.some((v, i) => v !== prevIdSnapshot[i])
915
+ : currentIdSnapshot !== prevIdSnapshot;
916
+ const isExternalChange = referenceDiffers && !valuesMatch;
906
917
  if (isExternalChange) {
907
918
  if (!items) {
908
919
  pendingSelectedId = selectedId;
@@ -926,6 +937,14 @@
926
937
  }
927
938
  });
928
939
 
940
+ // Trigger initial load when startId is set with loadOptions but no items yet
941
+ $effect(() => {
942
+ if (startId !== undefined && validatedLoadOptions && !items && !startIdLoadTriggered) {
943
+ startIdLoadTriggered = true;
944
+ setupFilterText();
945
+ }
946
+ });
947
+
929
948
  $effect.pre(() => {
930
949
  readOnlySelectedValue = selectedValue;
931
950
  readOnlySelectedId = computeSelectedId();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-select-5",
3
- "version": "7.0.2",
3
+ "version": "7.0.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)",