svelte-select-5 6.2.1 → 6.2.3
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 +76 -33
- package/no-styles/Select.svelte +76 -33
- package/package.json +1 -1
package/Select.svelte
CHANGED
|
@@ -45,6 +45,21 @@
|
|
|
45
45
|
import ClearIcon from './ClearIcon.svelte';
|
|
46
46
|
import LoadingIcon from './LoadingIcon.svelte';
|
|
47
47
|
|
|
48
|
+
// Performance: 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
|
+
const keysA = Object.keys(a);
|
|
53
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
54
|
+
return keysA.every(key => a[key] === b[key]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function arrayShallowEqual(a, b) {
|
|
58
|
+
if (a === b) return true;
|
|
59
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
60
|
+
return a.every((item, i) => shallowEqual(item, b[i]));
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
// Props with $props() rune
|
|
49
64
|
let {
|
|
50
65
|
// Bindable props (two-way binding)
|
|
@@ -177,6 +192,7 @@
|
|
|
177
192
|
let _inputAttributes = $state({});
|
|
178
193
|
let prevJustValue = $state(undefined);
|
|
179
194
|
let pendingJustValue = $state(undefined);
|
|
195
|
+
let prevItemsRef = $state(undefined);
|
|
180
196
|
let loadRequestVersion = 0;
|
|
181
197
|
let isScrollingTimer;
|
|
182
198
|
|
|
@@ -253,13 +269,15 @@
|
|
|
253
269
|
}
|
|
254
270
|
|
|
255
271
|
function filterGroupedItems(_items) {
|
|
272
|
+
const groupValuesSet = new Set();
|
|
256
273
|
const groupValues = [];
|
|
257
274
|
const groups = {};
|
|
258
275
|
|
|
259
276
|
_items.forEach((item) => {
|
|
260
277
|
const groupValue = groupBy(item);
|
|
261
278
|
|
|
262
|
-
if (!
|
|
279
|
+
if (!groupValuesSet.has(groupValue)) {
|
|
280
|
+
groupValuesSet.add(groupValue);
|
|
263
281
|
groupValues.push(groupValue);
|
|
264
282
|
groups[groupValue] = [];
|
|
265
283
|
|
|
@@ -288,7 +306,7 @@
|
|
|
288
306
|
|
|
289
307
|
function dispatchSelectedItem() {
|
|
290
308
|
if (multiple) {
|
|
291
|
-
if (
|
|
309
|
+
if (!arrayShallowEqual(value, prev_value)) {
|
|
292
310
|
if (checkValueForDuplicates()) {
|
|
293
311
|
oninput?.(value);
|
|
294
312
|
}
|
|
@@ -296,7 +314,7 @@
|
|
|
296
314
|
return;
|
|
297
315
|
}
|
|
298
316
|
|
|
299
|
-
if (!prev_value ||
|
|
317
|
+
if (!prev_value || value[itemId] !== prev_value[itemId]) {
|
|
300
318
|
oninput?.(value);
|
|
301
319
|
}
|
|
302
320
|
}
|
|
@@ -383,22 +401,23 @@
|
|
|
383
401
|
}
|
|
384
402
|
|
|
385
403
|
function checkValueForDuplicates() {
|
|
386
|
-
|
|
387
|
-
if (value) {
|
|
388
|
-
const ids = [];
|
|
389
|
-
const uniqueValues = [];
|
|
404
|
+
if (!value?.length) return true;
|
|
390
405
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
uniqueValues.push(val);
|
|
395
|
-
} else {
|
|
396
|
-
noDuplicates = false;
|
|
397
|
-
}
|
|
398
|
-
});
|
|
406
|
+
const seen = new Set();
|
|
407
|
+
const uniqueValues = [];
|
|
408
|
+
let noDuplicates = true;
|
|
399
409
|
|
|
400
|
-
|
|
410
|
+
for (const val of value) {
|
|
411
|
+
const id = val[itemId];
|
|
412
|
+
if (seen.has(id)) {
|
|
413
|
+
noDuplicates = false;
|
|
414
|
+
} else {
|
|
415
|
+
seen.add(id);
|
|
416
|
+
uniqueValues.push(val);
|
|
417
|
+
}
|
|
401
418
|
}
|
|
419
|
+
|
|
420
|
+
if (!noDuplicates) value = uniqueValues;
|
|
402
421
|
return noDuplicates;
|
|
403
422
|
}
|
|
404
423
|
|
|
@@ -419,8 +438,8 @@
|
|
|
419
438
|
const found = findItem(selection);
|
|
420
439
|
// Only update if found item has different properties (not just different reference)
|
|
421
440
|
if (found && found[itemId] === selection[itemId]) {
|
|
422
|
-
// Same itemId - check if other properties differ using
|
|
423
|
-
if (
|
|
441
|
+
// Same itemId - check if other properties differ using shallow comparison
|
|
442
|
+
if (!shallowEqual(found, selection)) {
|
|
424
443
|
needsUpdate = true;
|
|
425
444
|
return found;
|
|
426
445
|
}
|
|
@@ -434,7 +453,7 @@
|
|
|
434
453
|
const found = findItem();
|
|
435
454
|
// Only update if found item has different properties
|
|
436
455
|
if (found && found[itemId] === value[itemId]) {
|
|
437
|
-
if (
|
|
456
|
+
if (!shallowEqual(found, value)) {
|
|
438
457
|
value = found;
|
|
439
458
|
}
|
|
440
459
|
}
|
|
@@ -657,19 +676,32 @@
|
|
|
657
676
|
return (hoverItemIndex = 0);
|
|
658
677
|
}
|
|
659
678
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
679
|
+
// Use loop instead of recursion to prevent stack overflow with many non-selectable items
|
|
680
|
+
const maxIterations = filteredItems.length;
|
|
681
|
+
let iterations = 0;
|
|
682
|
+
|
|
683
|
+
while (iterations < maxIterations) {
|
|
684
|
+
if (increment > 0 && hoverItemIndex === filteredItems.length - 1) {
|
|
685
|
+
hoverItemIndex = 0;
|
|
686
|
+
} else if (increment < 0 && hoverItemIndex === 0) {
|
|
687
|
+
hoverItemIndex = filteredItems.length - 1;
|
|
688
|
+
} else {
|
|
689
|
+
hoverItemIndex = hoverItemIndex + increment;
|
|
690
|
+
}
|
|
667
691
|
|
|
668
|
-
|
|
692
|
+
const hover = filteredItems[hoverItemIndex];
|
|
669
693
|
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
694
|
+
// Found a selectable item - done
|
|
695
|
+
if (!hover || hover.selectable !== false) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Only continue for single-step increments
|
|
700
|
+
if (increment !== 1 && increment !== -1) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
iterations++;
|
|
673
705
|
}
|
|
674
706
|
}
|
|
675
707
|
|
|
@@ -794,8 +826,16 @@
|
|
|
794
826
|
if (!multiple && listOpen && value && filteredItems) setValueIndexAsHoverIndex();
|
|
795
827
|
});
|
|
796
828
|
|
|
829
|
+
// Only run updateValueDisplay when items content actually changes (not just reference)
|
|
830
|
+
// This prevents loops when parent components create new array references on each render
|
|
797
831
|
$effect(() => {
|
|
798
|
-
|
|
832
|
+
if (items !== prevItemsRef) {
|
|
833
|
+
// Only update if content differs, not just reference
|
|
834
|
+
if (!arrayShallowEqual(items, prevItemsRef)) {
|
|
835
|
+
updateValueDisplay(items);
|
|
836
|
+
}
|
|
837
|
+
prevItemsRef = items;
|
|
838
|
+
}
|
|
799
839
|
});
|
|
800
840
|
|
|
801
841
|
$effect(() => {
|
|
@@ -815,8 +855,11 @@
|
|
|
815
855
|
// Also handles case where justValue is set before items are loaded
|
|
816
856
|
$effect(() => {
|
|
817
857
|
const computed = computeJustValue();
|
|
818
|
-
|
|
819
|
-
|
|
858
|
+
// Compare justValue with computed - handles arrays (multiple) and primitives (single)
|
|
859
|
+
const valuesMatch = Array.isArray(justValue) && Array.isArray(computed)
|
|
860
|
+
? justValue.length === computed.length && justValue.every((v, i) => v === computed[i])
|
|
861
|
+
: justValue === computed;
|
|
862
|
+
const isExternalChange = justValue !== prevJustValue && !valuesMatch;
|
|
820
863
|
|
|
821
864
|
if (isExternalChange) {
|
|
822
865
|
if (!items) {
|
package/no-styles/Select.svelte
CHANGED
|
@@ -45,6 +45,21 @@
|
|
|
45
45
|
import ClearIcon from './ClearIcon.svelte';
|
|
46
46
|
import LoadingIcon from './LoadingIcon.svelte';
|
|
47
47
|
|
|
48
|
+
// Performance: 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
|
+
const keysA = Object.keys(a);
|
|
53
|
+
if (keysA.length !== Object.keys(b).length) return false;
|
|
54
|
+
return keysA.every(key => a[key] === b[key]);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function arrayShallowEqual(a, b) {
|
|
58
|
+
if (a === b) return true;
|
|
59
|
+
if (!a || !b || a.length !== b.length) return false;
|
|
60
|
+
return a.every((item, i) => shallowEqual(item, b[i]));
|
|
61
|
+
}
|
|
62
|
+
|
|
48
63
|
// Props with $props() rune
|
|
49
64
|
let {
|
|
50
65
|
// Bindable props (two-way binding)
|
|
@@ -177,6 +192,7 @@
|
|
|
177
192
|
let _inputAttributes = $state({});
|
|
178
193
|
let prevJustValue = $state(undefined);
|
|
179
194
|
let pendingJustValue = $state(undefined);
|
|
195
|
+
let prevItemsRef = $state(undefined);
|
|
180
196
|
let loadRequestVersion = 0;
|
|
181
197
|
let isScrollingTimer;
|
|
182
198
|
|
|
@@ -253,13 +269,15 @@
|
|
|
253
269
|
}
|
|
254
270
|
|
|
255
271
|
function filterGroupedItems(_items) {
|
|
272
|
+
const groupValuesSet = new Set();
|
|
256
273
|
const groupValues = [];
|
|
257
274
|
const groups = {};
|
|
258
275
|
|
|
259
276
|
_items.forEach((item) => {
|
|
260
277
|
const groupValue = groupBy(item);
|
|
261
278
|
|
|
262
|
-
if (!
|
|
279
|
+
if (!groupValuesSet.has(groupValue)) {
|
|
280
|
+
groupValuesSet.add(groupValue);
|
|
263
281
|
groupValues.push(groupValue);
|
|
264
282
|
groups[groupValue] = [];
|
|
265
283
|
|
|
@@ -288,7 +306,7 @@
|
|
|
288
306
|
|
|
289
307
|
function dispatchSelectedItem() {
|
|
290
308
|
if (multiple) {
|
|
291
|
-
if (
|
|
309
|
+
if (!arrayShallowEqual(value, prev_value)) {
|
|
292
310
|
if (checkValueForDuplicates()) {
|
|
293
311
|
oninput?.(value);
|
|
294
312
|
}
|
|
@@ -296,7 +314,7 @@
|
|
|
296
314
|
return;
|
|
297
315
|
}
|
|
298
316
|
|
|
299
|
-
if (!prev_value ||
|
|
317
|
+
if (!prev_value || value[itemId] !== prev_value[itemId]) {
|
|
300
318
|
oninput?.(value);
|
|
301
319
|
}
|
|
302
320
|
}
|
|
@@ -383,22 +401,23 @@
|
|
|
383
401
|
}
|
|
384
402
|
|
|
385
403
|
function checkValueForDuplicates() {
|
|
386
|
-
|
|
387
|
-
if (value) {
|
|
388
|
-
const ids = [];
|
|
389
|
-
const uniqueValues = [];
|
|
404
|
+
if (!value?.length) return true;
|
|
390
405
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
uniqueValues.push(val);
|
|
395
|
-
} else {
|
|
396
|
-
noDuplicates = false;
|
|
397
|
-
}
|
|
398
|
-
});
|
|
406
|
+
const seen = new Set();
|
|
407
|
+
const uniqueValues = [];
|
|
408
|
+
let noDuplicates = true;
|
|
399
409
|
|
|
400
|
-
|
|
410
|
+
for (const val of value) {
|
|
411
|
+
const id = val[itemId];
|
|
412
|
+
if (seen.has(id)) {
|
|
413
|
+
noDuplicates = false;
|
|
414
|
+
} else {
|
|
415
|
+
seen.add(id);
|
|
416
|
+
uniqueValues.push(val);
|
|
417
|
+
}
|
|
401
418
|
}
|
|
419
|
+
|
|
420
|
+
if (!noDuplicates) value = uniqueValues;
|
|
402
421
|
return noDuplicates;
|
|
403
422
|
}
|
|
404
423
|
|
|
@@ -419,8 +438,8 @@
|
|
|
419
438
|
const found = findItem(selection);
|
|
420
439
|
// Only update if found item has different properties (not just different reference)
|
|
421
440
|
if (found && found[itemId] === selection[itemId]) {
|
|
422
|
-
// Same itemId - check if other properties differ using
|
|
423
|
-
if (
|
|
441
|
+
// Same itemId - check if other properties differ using shallow comparison
|
|
442
|
+
if (!shallowEqual(found, selection)) {
|
|
424
443
|
needsUpdate = true;
|
|
425
444
|
return found;
|
|
426
445
|
}
|
|
@@ -434,7 +453,7 @@
|
|
|
434
453
|
const found = findItem();
|
|
435
454
|
// Only update if found item has different properties
|
|
436
455
|
if (found && found[itemId] === value[itemId]) {
|
|
437
|
-
if (
|
|
456
|
+
if (!shallowEqual(found, value)) {
|
|
438
457
|
value = found;
|
|
439
458
|
}
|
|
440
459
|
}
|
|
@@ -657,19 +676,32 @@
|
|
|
657
676
|
return (hoverItemIndex = 0);
|
|
658
677
|
}
|
|
659
678
|
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
679
|
+
// Use loop instead of recursion to prevent stack overflow with many non-selectable items
|
|
680
|
+
const maxIterations = filteredItems.length;
|
|
681
|
+
let iterations = 0;
|
|
682
|
+
|
|
683
|
+
while (iterations < maxIterations) {
|
|
684
|
+
if (increment > 0 && hoverItemIndex === filteredItems.length - 1) {
|
|
685
|
+
hoverItemIndex = 0;
|
|
686
|
+
} else if (increment < 0 && hoverItemIndex === 0) {
|
|
687
|
+
hoverItemIndex = filteredItems.length - 1;
|
|
688
|
+
} else {
|
|
689
|
+
hoverItemIndex = hoverItemIndex + increment;
|
|
690
|
+
}
|
|
667
691
|
|
|
668
|
-
|
|
692
|
+
const hover = filteredItems[hoverItemIndex];
|
|
669
693
|
|
|
670
|
-
|
|
671
|
-
if (
|
|
672
|
-
|
|
694
|
+
// Found a selectable item - done
|
|
695
|
+
if (!hover || hover.selectable !== false) {
|
|
696
|
+
return;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
// Only continue for single-step increments
|
|
700
|
+
if (increment !== 1 && increment !== -1) {
|
|
701
|
+
return;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
iterations++;
|
|
673
705
|
}
|
|
674
706
|
}
|
|
675
707
|
|
|
@@ -794,8 +826,16 @@
|
|
|
794
826
|
if (!multiple && listOpen && value && filteredItems) setValueIndexAsHoverIndex();
|
|
795
827
|
});
|
|
796
828
|
|
|
829
|
+
// Only run updateValueDisplay when items content actually changes (not just reference)
|
|
830
|
+
// This prevents loops when parent components create new array references on each render
|
|
797
831
|
$effect(() => {
|
|
798
|
-
|
|
832
|
+
if (items !== prevItemsRef) {
|
|
833
|
+
// Only update if content differs, not just reference
|
|
834
|
+
if (!arrayShallowEqual(items, prevItemsRef)) {
|
|
835
|
+
updateValueDisplay(items);
|
|
836
|
+
}
|
|
837
|
+
prevItemsRef = items;
|
|
838
|
+
}
|
|
799
839
|
});
|
|
800
840
|
|
|
801
841
|
$effect(() => {
|
|
@@ -815,8 +855,11 @@
|
|
|
815
855
|
// Also handles case where justValue is set before items are loaded
|
|
816
856
|
$effect(() => {
|
|
817
857
|
const computed = computeJustValue();
|
|
818
|
-
|
|
819
|
-
|
|
858
|
+
// Compare justValue with computed - handles arrays (multiple) and primitives (single)
|
|
859
|
+
const valuesMatch = Array.isArray(justValue) && Array.isArray(computed)
|
|
860
|
+
? justValue.length === computed.length && justValue.every((v, i) => v === computed[i])
|
|
861
|
+
: justValue === computed;
|
|
862
|
+
const isExternalChange = justValue !== prevJustValue && !valuesMatch;
|
|
820
863
|
|
|
821
864
|
if (isExternalChange) {
|
|
822
865
|
if (!items) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "svelte-select-5",
|
|
3
|
-
"version": "6.2.
|
|
3
|
+
"version": "6.2.3",
|
|
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)",
|