svelte-select-5 6.1.9 → 6.2.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.
package/Select.svelte CHANGED
@@ -151,6 +151,20 @@
151
151
  requiredSlot: requiredSlotSnippet = undefined,
152
152
  } = $props();
153
153
 
154
+ // Prop validation (runs once on init)
155
+ if (typeof itemId !== 'string') {
156
+ console.warn('[svelte-select-5] itemId must be a string, using "value"');
157
+ itemId = 'value';
158
+ }
159
+ if (typeof label !== 'string') {
160
+ console.warn('[svelte-select-5] label must be a string, using "label"');
161
+ label = 'label';
162
+ }
163
+ if (loadOptions !== undefined && typeof loadOptions !== 'function') {
164
+ console.warn('[svelte-select-5] loadOptions must be a function');
165
+ loadOptions = undefined;
166
+ }
167
+
154
168
  // Internal state
155
169
  let timeout;
156
170
  let activeValue = $state(undefined);
@@ -162,6 +176,8 @@
162
176
  let prefloat = $state(true);
163
177
  let _inputAttributes = $state({});
164
178
  let prevJustValue = $state(undefined);
179
+ let pendingJustValue = $state(undefined);
180
+ let loadRequestVersion = 0;
165
181
  let isScrollingTimer;
166
182
 
167
183
  // Floating UI config - using closure for listOffset to capture current value
@@ -319,7 +335,9 @@
319
335
 
320
336
  if (loadOptions) {
321
337
  debounce(async function () {
338
+ const currentVersion = ++loadRequestVersion;
322
339
  loading = true;
340
+
323
341
  let res = await getItems({
324
342
  dispatch: (event, data) => {
325
343
  if (event === 'error') onerror?.(data);
@@ -330,6 +348,11 @@
330
348
  filterText,
331
349
  });
332
350
 
351
+ // Ignore stale responses from earlier requests
352
+ if (currentVersion !== loadRequestVersion) {
353
+ return;
354
+ }
355
+
333
356
  if (res) {
334
357
  loading = res.loading;
335
358
  listOpen = listOpen ? res.listOpen : filterText.length > 0 ? true : false;
@@ -351,8 +374,12 @@
351
374
  }
352
375
 
353
376
  function computeJustValue() {
354
- if (multiple) return value ? value.map((item) => item[itemId]) : null;
355
- return value ? value[itemId] : value;
377
+ if (!value) return multiple ? null : undefined;
378
+ if (multiple) {
379
+ return value.map((item) => item?.[itemId]).filter(id => id !== undefined);
380
+ }
381
+ const id = value[itemId];
382
+ return id !== undefined ? id : undefined;
356
383
  }
357
384
 
358
385
  function checkValueForDuplicates() {
@@ -736,24 +763,21 @@
736
763
  if (inputAttributes || !searchable) assignInputAttributes();
737
764
  });
738
765
 
766
+ // Consolidated: Multiple-mode effects
739
767
  $effect(() => {
740
- if (multiple) setupMulti();
741
- });
742
-
743
- $effect(() => {
744
- if (prev_multiple && !multiple) setupSingle();
745
- });
746
-
747
- $effect(() => {
748
- if (multiple && value && value.length > 1) checkValueForDuplicates();
749
- });
750
-
751
- $effect(() => {
752
- if (value) dispatchSelectedItem();
768
+ if (multiple) {
769
+ setupMulti();
770
+ if (value && value.length > 1) checkValueForDuplicates();
771
+ } else if (prev_multiple) {
772
+ setupSingle();
773
+ }
753
774
  });
754
775
 
776
+ // Consolidated: Value change effects
755
777
  $effect(() => {
756
- if (!value && multiple && prev_value) {
778
+ if (value) {
779
+ dispatchSelectedItem();
780
+ } else if (prev_value) {
757
781
  oninput?.(value);
758
782
  }
759
783
  });
@@ -770,10 +794,6 @@
770
794
  if (!multiple && listOpen && value && filteredItems) setValueIndexAsHoverIndex();
771
795
  });
772
796
 
773
- $effect(() => {
774
- dispatchHover(hoverItemIndex);
775
- });
776
-
777
797
  $effect(() => {
778
798
  updateValueDisplay(items);
779
799
  });
@@ -782,6 +802,15 @@
782
802
  justValue = computeJustValue();
783
803
  });
784
804
 
805
+ // Helper function to resolve justValue to value
806
+ function resolveJustValue(jv) {
807
+ if (multiple) {
808
+ value = jv ? items.filter(item => jv.includes(item[itemId])) : null;
809
+ } else {
810
+ value = jv != null ? items.find(item => item[itemId] === jv) ?? null : null;
811
+ }
812
+ }
813
+
785
814
  // Handle external changes to justValue (allows setting value via justValue)
786
815
  // Also handles case where justValue is set before items are loaded
787
816
  $effect(() => {
@@ -789,21 +818,23 @@
789
818
  const isExternalChange = justValue !== prevJustValue &&
790
819
  JSON.stringify(justValue) !== JSON.stringify(computed);
791
820
 
792
- // Update value if: external justValue change, OR items loaded while justValue is set but value is empty
793
- const needsValueUpdate = isExternalChange ||
794
- (items && justValue != null && !value && JSON.stringify(justValue) !== JSON.stringify(computed));
795
-
796
- if (needsValueUpdate && items) {
797
- if (multiple) {
798
- value = justValue
799
- ? items.filter(item => justValue.includes(item[itemId]))
800
- : null;
821
+ if (isExternalChange) {
822
+ if (!items) {
823
+ // Items not loaded yet - save for later
824
+ pendingJustValue = justValue;
801
825
  } else {
802
- value = justValue != null
803
- ? items.find(item => item[itemId] === justValue) ?? null
804
- : null;
826
+ // Items available - resolve immediately
827
+ resolveJustValue(justValue);
828
+ pendingJustValue = undefined;
805
829
  }
806
830
  }
831
+
832
+ // When items load and we have a pending justValue, resolve it
833
+ if (items && pendingJustValue !== undefined && !value) {
834
+ resolveJustValue(pendingJustValue);
835
+ pendingJustValue = undefined;
836
+ }
837
+
807
838
  prevJustValue = justValue;
808
839
  });
809
840
 
@@ -814,10 +845,6 @@
814
845
  readonlyId = computeJustValue();
815
846
  });
816
847
 
817
- $effect(() => {
818
- if (!multiple && prev_value && !value) oninput?.(value);
819
- });
820
-
821
848
  $effect(() => {
822
849
  if (listOpen && filteredItems && !multiple && !value) checkHoverSelectable();
823
850
  });
@@ -830,24 +857,19 @@
830
857
  if (container && floatingConfig) floatingUpdate(Object.assign(_floatingConfig, floatingConfig));
831
858
  });
832
859
 
860
+ // Consolidated: List open effects
833
861
  $effect(() => {
834
862
  listMounted(list, listOpen);
863
+ if (listOpen) {
864
+ if (container && list) setListWidth();
865
+ if (input && !focused) handleFocus();
866
+ }
835
867
  });
836
868
 
869
+ // Consolidated: hoverItemIndex effects
837
870
  $effect(() => {
838
- if (listOpen && container && list) setListWidth();
839
- });
840
-
841
- $effect(() => {
842
- if (listOpen && multiple) hoverItemIndex = 0;
843
- });
844
-
845
- $effect(() => {
846
- if (input && listOpen && !focused) handleFocus();
847
- });
848
-
849
- $effect(() => {
850
- if (filterText) hoverItemIndex = 0;
871
+ if (filterText || (listOpen && multiple)) hoverItemIndex = 0;
872
+ dispatchHover(hoverItemIndex);
851
873
  });
852
874
 
853
875
  // Lifecycle
@@ -151,6 +151,20 @@
151
151
  requiredSlot: requiredSlotSnippet = undefined,
152
152
  } = $props();
153
153
 
154
+ // Prop validation (runs once on init)
155
+ if (typeof itemId !== 'string') {
156
+ console.warn('[svelte-select-5] itemId must be a string, using "value"');
157
+ itemId = 'value';
158
+ }
159
+ if (typeof label !== 'string') {
160
+ console.warn('[svelte-select-5] label must be a string, using "label"');
161
+ label = 'label';
162
+ }
163
+ if (loadOptions !== undefined && typeof loadOptions !== 'function') {
164
+ console.warn('[svelte-select-5] loadOptions must be a function');
165
+ loadOptions = undefined;
166
+ }
167
+
154
168
  // Internal state
155
169
  let timeout;
156
170
  let activeValue = $state(undefined);
@@ -162,6 +176,8 @@
162
176
  let prefloat = $state(true);
163
177
  let _inputAttributes = $state({});
164
178
  let prevJustValue = $state(undefined);
179
+ let pendingJustValue = $state(undefined);
180
+ let loadRequestVersion = 0;
165
181
  let isScrollingTimer;
166
182
 
167
183
  // Floating UI config - using closure for listOffset to capture current value
@@ -319,7 +335,9 @@
319
335
 
320
336
  if (loadOptions) {
321
337
  debounce(async function () {
338
+ const currentVersion = ++loadRequestVersion;
322
339
  loading = true;
340
+
323
341
  let res = await getItems({
324
342
  dispatch: (event, data) => {
325
343
  if (event === 'error') onerror?.(data);
@@ -330,6 +348,11 @@
330
348
  filterText,
331
349
  });
332
350
 
351
+ // Ignore stale responses from earlier requests
352
+ if (currentVersion !== loadRequestVersion) {
353
+ return;
354
+ }
355
+
333
356
  if (res) {
334
357
  loading = res.loading;
335
358
  listOpen = listOpen ? res.listOpen : filterText.length > 0 ? true : false;
@@ -351,8 +374,12 @@
351
374
  }
352
375
 
353
376
  function computeJustValue() {
354
- if (multiple) return value ? value.map((item) => item[itemId]) : null;
355
- return value ? value[itemId] : value;
377
+ if (!value) return multiple ? null : undefined;
378
+ if (multiple) {
379
+ return value.map((item) => item?.[itemId]).filter(id => id !== undefined);
380
+ }
381
+ const id = value[itemId];
382
+ return id !== undefined ? id : undefined;
356
383
  }
357
384
 
358
385
  function checkValueForDuplicates() {
@@ -736,24 +763,21 @@
736
763
  if (inputAttributes || !searchable) assignInputAttributes();
737
764
  });
738
765
 
766
+ // Consolidated: Multiple-mode effects
739
767
  $effect(() => {
740
- if (multiple) setupMulti();
741
- });
742
-
743
- $effect(() => {
744
- if (prev_multiple && !multiple) setupSingle();
745
- });
746
-
747
- $effect(() => {
748
- if (multiple && value && value.length > 1) checkValueForDuplicates();
749
- });
750
-
751
- $effect(() => {
752
- if (value) dispatchSelectedItem();
768
+ if (multiple) {
769
+ setupMulti();
770
+ if (value && value.length > 1) checkValueForDuplicates();
771
+ } else if (prev_multiple) {
772
+ setupSingle();
773
+ }
753
774
  });
754
775
 
776
+ // Consolidated: Value change effects
755
777
  $effect(() => {
756
- if (!value && multiple && prev_value) {
778
+ if (value) {
779
+ dispatchSelectedItem();
780
+ } else if (prev_value) {
757
781
  oninput?.(value);
758
782
  }
759
783
  });
@@ -770,10 +794,6 @@
770
794
  if (!multiple && listOpen && value && filteredItems) setValueIndexAsHoverIndex();
771
795
  });
772
796
 
773
- $effect(() => {
774
- dispatchHover(hoverItemIndex);
775
- });
776
-
777
797
  $effect(() => {
778
798
  updateValueDisplay(items);
779
799
  });
@@ -782,6 +802,15 @@
782
802
  justValue = computeJustValue();
783
803
  });
784
804
 
805
+ // Helper function to resolve justValue to value
806
+ function resolveJustValue(jv) {
807
+ if (multiple) {
808
+ value = jv ? items.filter(item => jv.includes(item[itemId])) : null;
809
+ } else {
810
+ value = jv != null ? items.find(item => item[itemId] === jv) ?? null : null;
811
+ }
812
+ }
813
+
785
814
  // Handle external changes to justValue (allows setting value via justValue)
786
815
  // Also handles case where justValue is set before items are loaded
787
816
  $effect(() => {
@@ -789,21 +818,23 @@
789
818
  const isExternalChange = justValue !== prevJustValue &&
790
819
  JSON.stringify(justValue) !== JSON.stringify(computed);
791
820
 
792
- // Update value if: external justValue change, OR items loaded while justValue is set but value is empty
793
- const needsValueUpdate = isExternalChange ||
794
- (items && justValue != null && !value && JSON.stringify(justValue) !== JSON.stringify(computed));
795
-
796
- if (needsValueUpdate && items) {
797
- if (multiple) {
798
- value = justValue
799
- ? items.filter(item => justValue.includes(item[itemId]))
800
- : null;
821
+ if (isExternalChange) {
822
+ if (!items) {
823
+ // Items not loaded yet - save for later
824
+ pendingJustValue = justValue;
801
825
  } else {
802
- value = justValue != null
803
- ? items.find(item => item[itemId] === justValue) ?? null
804
- : null;
826
+ // Items available - resolve immediately
827
+ resolveJustValue(justValue);
828
+ pendingJustValue = undefined;
805
829
  }
806
830
  }
831
+
832
+ // When items load and we have a pending justValue, resolve it
833
+ if (items && pendingJustValue !== undefined && !value) {
834
+ resolveJustValue(pendingJustValue);
835
+ pendingJustValue = undefined;
836
+ }
837
+
807
838
  prevJustValue = justValue;
808
839
  });
809
840
 
@@ -814,10 +845,6 @@
814
845
  readonlyId = computeJustValue();
815
846
  });
816
847
 
817
- $effect(() => {
818
- if (!multiple && prev_value && !value) oninput?.(value);
819
- });
820
-
821
848
  $effect(() => {
822
849
  if (listOpen && filteredItems && !multiple && !value) checkHoverSelectable();
823
850
  });
@@ -830,24 +857,19 @@
830
857
  if (container && floatingConfig) floatingUpdate(Object.assign(_floatingConfig, floatingConfig));
831
858
  });
832
859
 
860
+ // Consolidated: List open effects
833
861
  $effect(() => {
834
862
  listMounted(list, listOpen);
863
+ if (listOpen) {
864
+ if (container && list) setListWidth();
865
+ if (input && !focused) handleFocus();
866
+ }
835
867
  });
836
868
 
869
+ // Consolidated: hoverItemIndex effects
837
870
  $effect(() => {
838
- if (listOpen && container && list) setListWidth();
839
- });
840
-
841
- $effect(() => {
842
- if (listOpen && multiple) hoverItemIndex = 0;
843
- });
844
-
845
- $effect(() => {
846
- if (input && listOpen && !focused) handleFocus();
847
- });
848
-
849
- $effect(() => {
850
- if (filterText) hoverItemIndex = 0;
871
+ if (filterText || (listOpen && multiple)) hoverItemIndex = 0;
872
+ dispatchHover(hoverItemIndex);
851
873
  });
852
874
 
853
875
  // Lifecycle
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "svelte-select-5",
3
- "version": "6.1.9",
3
+ "version": "6.2.1",
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)",