@cosxai/ui 0.18.1 → 0.19.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,7 +1,7 @@
1
1
  {
2
2
  "name": "@cosxai/ui",
3
- "version": "0.18.1",
4
- "description": "COSX design system \u2014 React 19 component primitives shared across product-meta and other consumers",
3
+ "version": "0.19.0",
4
+ "description": "COSX design system — React 19 component primitives shared across product-meta and other consumers",
5
5
  "license": "UNLICENSED",
6
6
  "type": "module",
7
7
  "main": "./src/index.ts",
@@ -21,11 +21,19 @@ export interface CommandPaletteProps {
21
21
  // here come last in registration order.
22
22
  groupOrder?: string[];
23
23
  placeholder?: string;
24
+ // Fires whenever the query changes — on every keystroke and on the
25
+ // reset-to-empty when the palette opens. Lets a consumer drive an
26
+ // ASYNC command source (debounce → fetch → register the results as
27
+ // items via useCommandSource) that the built-in client-side filter
28
+ // can't provide on its own. Optional + backward compatible: omit it
29
+ // and the palette behaves exactly as before (static items only).
30
+ onQueryChange?: ((query: string) => void) | undefined;
24
31
  }
25
32
 
26
33
  export function CommandPalette({
27
34
  groupOrder = [],
28
35
  placeholder = "Type a command or search…",
36
+ onQueryChange,
29
37
  }: CommandPaletteProps) {
30
38
  const ctx = useContext(CommandContext);
31
39
  if (!ctx) throw new Error("<CommandPalette> must be inside <CommandProvider>");
@@ -34,12 +42,21 @@ export function CommandPalette({
34
42
  const [selectedIdx, setSelectedIdx] = useState(0);
35
43
  const inputRef = useRef<HTMLInputElement>(null);
36
44
  const listRef = useRef<HTMLDivElement>(null);
45
+ // Latest onQueryChange held in a ref so the open-reset effect can
46
+ // notify it WITHOUT taking it as a dep — an inline callback changes
47
+ // identity every render, which would otherwise re-run the effect and
48
+ // clear the query on every keystroke.
49
+ const onQueryChangeRef = useRef(onQueryChange);
50
+ onQueryChangeRef.current = onQueryChange;
37
51
 
38
52
  // Reset query + focus on every open.
39
53
  useEffect(() => {
40
54
  if (open) {
41
55
  setQ("");
42
56
  setSelectedIdx(0);
57
+ // Tell the consumer the query cleared, so an async source drops
58
+ // its stale results when the palette reopens.
59
+ onQueryChangeRef.current?.("");
43
60
  // RAF so the modal element exists before focus.
44
61
  requestAnimationFrame(() => inputRef.current?.focus());
45
62
  }
@@ -164,6 +181,7 @@ export function CommandPalette({
164
181
  onChange={(e) => {
165
182
  setQ(e.target.value);
166
183
  setSelectedIdx(0);
184
+ onQueryChange?.(e.target.value);
167
185
  }}
168
186
  placeholder={placeholder}
169
187
  style={{
@@ -123,6 +123,32 @@ a:visited { color: var(--ck-accent); }
123
123
  scrollbar-color: var(--ck-border-strong, rgba(0, 0, 0, 0.25)) transparent;
124
124
  }
125
125
 
126
+ /* Chromium < 121 doesn't know the standard properties above and fell
127
+ back to the classic arrow-button gutters (QA: Ben, 2026-07-24 —
128
+ the first pass "didn't cover everything"; it actually didn't cover
129
+ older ENGINES). The ::-webkit-scrollbar layer is the fallback for
130
+ those versions; engines that support scrollbar-width IGNORE these
131
+ pseudo-elements when it's set, so the two blocks never fight. */
132
+ *::-webkit-scrollbar {
133
+ width: 8px;
134
+ height: 8px;
135
+ }
136
+ *::-webkit-scrollbar-thumb {
137
+ background: var(--ck-border-strong, rgba(0, 0, 0, 0.25));
138
+ border-radius: 4px;
139
+ }
140
+ *::-webkit-scrollbar-track {
141
+ background: transparent;
142
+ }
143
+ *::-webkit-scrollbar-corner {
144
+ background: transparent;
145
+ }
146
+ *::-webkit-scrollbar-button {
147
+ display: none;
148
+ width: 0;
149
+ height: 0;
150
+ }
151
+
126
152
  /* ── ActionBar panel + toast slots (design#13) ─────────────────────
127
153
  Base chrome for the bar-intrinsic popover (useActionBarPanel) and
128
154
  transient bubble (useActionBarToast). Token-driven so every theme