@cosxai/ui 0.22.0 → 0.23.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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "0.22.0",
3
+ "version": "0.23.1",
4
4
  "description": "COSX design system \u2014 React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
@@ -67,6 +67,12 @@ export interface ComboboxProps {
67
67
  // Async search. Called after `debounceMs` of quiet with a non-empty
68
68
  // trimmed query; the AbortSignal cancels superseded requests.
69
69
  search: (query: string, signal: AbortSignal) => Promise<ComboboxOption[]>;
70
+ // Select-with-search mode: run `search("")` the moment the input
71
+ // focuses, so clicking the field presents the option list before
72
+ // any typing — the picker pattern for small, known sets. Off by
73
+ // default (async person/document searches would fire on every
74
+ // focus for no gain).
75
+ searchOnFocus?: boolean | undefined;
70
76
  debounceMs?: number | undefined;
71
77
  // Free-entry gate: return true when the raw text is committable on
72
78
  // its own (e.g. a syntactically valid email). Omit to disable free
@@ -109,6 +115,7 @@ export function Combobox({
109
115
  autoFocus,
110
116
  disabled,
111
117
  search,
118
+ searchOnFocus,
112
119
  debounceMs = DEFAULT_DEBOUNCE_MS,
113
120
  allowFreeEntry,
114
121
  freeEntryLabel,
@@ -138,7 +145,9 @@ export function Combobox({
138
145
  if (debounceRef.current) clearTimeout(debounceRef.current);
139
146
  // Search is suspended while a commit is displayed — the input
140
147
  // still holds the committed text but shouldn't re-open the list.
141
- if (!query.trim() || isCommitted) {
148
+ // searchOnFocus lifts the non-empty-query requirement while the
149
+ // field is focused: clicking presents the list, typing narrows.
150
+ if (isCommitted || (!query.trim() && !(searchOnFocus && focused))) {
142
151
  setResults([]);
143
152
  setLoading(false);
144
153
  return;
@@ -168,7 +177,7 @@ export function Combobox({
168
177
  if (debounceRef.current) clearTimeout(debounceRef.current);
169
178
  abortRef.current?.abort();
170
179
  };
171
- }, [query, isCommitted, search, debounceMs]);
180
+ }, [query, isCommitted, search, debounceMs, searchOnFocus, focused]);
172
181
 
173
182
  const trimmed = query.trim();
174
183
  const freeEntryOk = !!allowFreeEntry && trimmed.length > 0 && allowFreeEntry(trimmed);
@@ -696,14 +696,36 @@ export const Select = forwardRef<HTMLButtonElement, SelectProps>(function Select
696
696
  opacity: disabled ? 0.55 : 1,
697
697
  }}
698
698
  >
699
- <span
700
- style={{
701
- overflow: "hidden",
702
- textOverflow: "ellipsis",
703
- whiteSpace: "nowrap",
704
- }}
705
- >
706
- {triggerLabel}
699
+ <span style={{ display: "inline-grid", overflow: "hidden" }}>
700
+ {/* fit="auto" width stabiliser: every option label renders
701
+ invisibly in the same grid cell, so the trigger takes
702
+ the WIDEST option's width up front. Without it the
703
+ trigger resized to each selection and the popover
704
+ (which matches trigger width) truncated longer options
705
+ to "E-sig…". Skipped for fit="full" (width is the
706
+ container's) and for searchable lists (potentially
707
+ hundreds of rows — and those popovers are wide anyway). */}
708
+ {fit === "auto" &&
709
+ !searchable &&
710
+ options.map((o) => (
711
+ <span
712
+ key={o.value}
713
+ aria-hidden
714
+ style={{ gridArea: "1 / 1", visibility: "hidden", whiteSpace: "nowrap" }}
715
+ >
716
+ {o.label}
717
+ </span>
718
+ ))}
719
+ <span
720
+ style={{
721
+ gridArea: "1 / 1",
722
+ overflow: "hidden",
723
+ textOverflow: "ellipsis",
724
+ whiteSpace: "nowrap",
725
+ }}
726
+ >
727
+ {triggerLabel}
728
+ </span>
707
729
  </span>
708
730
  <svg
709
731
  width="10"