@proyecto-viviana/solid-stately 0.4.1 → 0.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@proyecto-viviana/solid-stately",
3
- "version": "0.4.1",
3
+ "version": "0.5.0",
4
4
  "description": "A 1-1 SolidJS port of React Stately - headless state management for UI components",
5
5
  "keywords": [
6
6
  "headless",
@@ -160,7 +160,15 @@ export function createListCollection<T>(
160
160
  },
161
161
  getTextValue = (item: T) => {
162
162
  const o = item as CollectionItemLike;
163
- return o.textValue ?? o.label ?? String(item);
163
+ // See createListState: `String(item)` yields "[object Object]" for a plain
164
+ // object, so widen to name/title and fall back to the item's key.
165
+ return (
166
+ o.textValue ??
167
+ o.label ??
168
+ o.name ??
169
+ o.title ??
170
+ (item != null && typeof item === "object" ? String(o.key ?? o.id ?? "") : String(item))
171
+ );
164
172
  },
165
173
  getDisabled = (item: T) => (item as CollectionItemLike).isDisabled ?? false,
166
174
  } = options;
@@ -83,7 +83,19 @@ export function createListState<T = unknown>(
83
83
  const nodes: CollectionNode<T>[] = items.map((item, index) => {
84
84
  const o = item as CollectionItemLike;
85
85
  const key = p.getKey?.(item) ?? o.key ?? o.id ?? index;
86
- const textValue = p.getTextValue?.(item) ?? o.textValue ?? o.label ?? String(item);
86
+ // Derive the item's display text. Prefer an explicit getter/field, then the
87
+ // conventional display fields (label/name/title). `String(item)` is the last
88
+ // resort — right for a primitive item (a bare string/number) but useless for
89
+ // a plain object, where it yields "[object Object]". That default surfaced
90
+ // verbatim in a ComboBox/Picker input for `{ id, name }`-shaped items, so fall
91
+ // back to the item's key rather than "[object Object]".
92
+ const textValue =
93
+ p.getTextValue?.(item) ??
94
+ o.textValue ??
95
+ o.label ??
96
+ o.name ??
97
+ o.title ??
98
+ (item != null && typeof item === "object" ? String(key) : String(item));
87
99
  const isDisabled = p.getDisabled?.(item) ?? o.isDisabled ?? false;
88
100
 
89
101
  return {
@@ -109,6 +109,9 @@ export interface CollectionItemLike {
109
109
  id?: Key;
110
110
  textValue?: string;
111
111
  label?: string;
112
+ /** Conventional display fields, read when no textValue/label/getTextValue is given. */
113
+ name?: string;
114
+ title?: string;
112
115
  isDisabled?: boolean;
113
116
  }
114
117
 
@@ -6,7 +6,7 @@
6
6
  * either type to filter options or select from a list.
7
7
  */
8
8
 
9
- import { createSignal, createMemo, createEffect, type Accessor } from "solid-js";
9
+ import { createSignal, createMemo, createEffect, untrack, type Accessor } from "solid-js";
10
10
  import { access, type MaybeAccessor } from "../utils";
11
11
  import { createListState } from "../collections/createListState";
12
12
  import { createOverlayTriggerState } from "../overlays";
@@ -569,7 +569,22 @@ export function createComboBoxState<T = unknown>(
569
569
  }
570
570
  });
571
571
 
572
- // Update input when selection changes (only in single mode)
572
+ // Keep the input text in sync with the selected item (single mode only).
573
+ //
574
+ // This effect must react to the SELECTION and the COLLECTION — when the
575
+ // selected key changes, or when its item (and thus textValue) resolves as
576
+ // items load — but it must NOT react to the input value itself. The previous
577
+ // port compared `textValue !== inputValue()`, reading `inputValue()` inside
578
+ // the effect; in Solid that subscribes the effect to the input signal, so it
579
+ // re-ran on every keystroke and — while any selection was active — immediately
580
+ // reset the input back to the selected item's text. The result: the user
581
+ // could neither type nor delete in the field.
582
+ //
583
+ // The comparison is unchanged, but `inputValue()` is now read via `untrack`,
584
+ // so it is used only to decide whether a reset is needed and never becomes a
585
+ // dependency. `originalCollection()` stays tracked (the unfiltered collection
586
+ // only changes when `items` change, never on filtering) so a preset selection
587
+ // still gets its text once async items arrive.
573
588
  createEffect(() => {
574
589
  if (isMultiple()) return;
575
590
  const key = selectedKey();
@@ -578,7 +593,7 @@ export function createComboBoxState<T = unknown>(
578
593
 
579
594
  // Only update if selection changed and not fully controlled
580
595
  if (!isInputControlled() || !isSelectionControlled()) {
581
- if (key != null && textValue !== inputValue()) {
596
+ if (key != null && textValue !== untrack(inputValue)) {
582
597
  setInputValue(textValue);
583
598
  setLastValue(textValue);
584
599
  }
@@ -88,7 +88,15 @@ export function createTabListState<T = unknown>(
88
88
  const nodes: CollectionNode<T>[] = items.map((item, index) => {
89
89
  const o = item as CollectionItemLike;
90
90
  const key = p.getKey?.(item) ?? o.key ?? o.id ?? index;
91
- const textValue = p.getTextValue?.(item) ?? o.textValue ?? o.label ?? String(item);
91
+ // See createListState: `String(item)` yields "[object Object]" for a plain
92
+ // object, so widen to name/title and fall back to the key for object items.
93
+ const textValue =
94
+ p.getTextValue?.(item) ??
95
+ o.textValue ??
96
+ o.label ??
97
+ o.name ??
98
+ o.title ??
99
+ (item != null && typeof item === "object" ? String(key) : String(item));
92
100
  const isDisabled = p.getDisabled?.(item) ?? o.isDisabled ?? false;
93
101
 
94
102
  return {