@adecore/ui 0.34.1 → 0.35.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.
@@ -2,8 +2,9 @@ import type { Ref } from 'react';
2
2
  import { type TooltipSide } from './Tooltip.tsx';
3
3
  export interface RelativeTimeProps {
4
4
  at: Date | number | null | undefined;
5
+ tooltip?: boolean;
5
6
  tooltipSide?: TooltipSide;
6
7
  className?: string;
7
8
  ref?: Ref<HTMLElement>;
8
9
  }
9
- export declare function RelativeTime({ at, tooltipSide, className, ref }: RelativeTimeProps): import("react").JSX.Element;
10
+ export declare function RelativeTime({ at, tooltip, tooltipSide, className, ref }: RelativeTimeProps): import("react").JSX.Element;
@@ -8,6 +8,7 @@ const TICK_MS = 30_000;
8
8
  /* A moment as its age, `3m ago`, counting on while it is on screen, with the exact time on hover and on keyboard focus. */
9
9
  export function RelativeTime({
10
10
  at,
11
+ tooltip = true,
11
12
  tooltipSide,
12
13
  className,
13
14
  ref
@@ -24,6 +25,14 @@ export function RelativeTime({
24
25
  children: "-"
25
26
  });
26
27
  }
28
+ if (!tooltip) {
29
+ return /*#__PURE__*/_jsx("time", {
30
+ ref: ref,
31
+ dateTime: new Date(at).toISOString(),
32
+ className: clsx('whitespace-nowrap', className),
33
+ children: formatAge(at, now)
34
+ });
35
+ }
27
36
  return /*#__PURE__*/_jsx(Tooltip, {
28
37
  label: formatDayClockWithYear(at),
29
38
  side: tooltipSide,
@@ -13,7 +13,7 @@ type AutocompleteProps<ItemValue> = Omit<Autocomplete.Root.Props<ItemValue>, 'op
13
13
  };
14
14
  type DialogProps = Pick<ComponentProps<typeof DialogRoot>, 'open' | 'defaultOpen' | 'onOpenChange' | 'onOpenChangeComplete'>;
15
15
  export type CommandPaletteRootProps<ItemValue> = AutocompleteProps<ItemValue> & DialogProps;
16
- export declare function CommandPaletteRoot<ItemValue>({ open, defaultOpen, onOpenChange, onOpenChangeComplete, autoHighlight, keepHighlight, items, children, ...props }: CommandPaletteRootProps<ItemValue>): import("react").JSX.Element;
16
+ export declare function CommandPaletteRoot<ItemValue>({ open, defaultOpen, onOpenChange, onOpenChangeComplete, autoHighlight, keepHighlight, value, onValueChange, items, children, ...props }: CommandPaletteRootProps<ItemValue>): import("react").JSX.Element;
17
17
  export type CommandPalettePopupProps = Omit<DialogPopupProps, 'size'> & {
18
18
  size?: keyof typeof SIZE;
19
19
  };
@@ -26,7 +26,7 @@ export type CommandPaletteInputProps = Omit<ComponentProps<typeof Autocomplete.I
26
26
  className?: string;
27
27
  };
28
28
  export declare function CommandPaletteInput({ leading, trailing, escapeHint, mono, className, ref, ...props }: CommandPaletteInputProps): import("react").JSX.Element;
29
- export declare function CommandPaletteList({ className, ...props }: ComponentProps<typeof Autocomplete.List>): import("react").JSX.Element;
29
+ export declare function CommandPaletteList({ className, ref, ...props }: ComponentProps<typeof Autocomplete.List>): import("react").JSX.Element;
30
30
  export type CommandPaletteFooterProps = useRender.ComponentProps<'div'>;
31
31
  export declare function CommandPaletteFooter({ render, className, ref, ...props }: CommandPaletteFooterProps): import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
32
32
  export {};
@@ -1,5 +1,5 @@
1
1
  import { c as _c } from "react/compiler-runtime";
2
- import { createContext, useContext, useRef } from 'react';
2
+ import { createContext, useContext, useLayoutEffect, useRef, useState } from 'react';
3
3
  import clsx from 'clsx';
4
4
  import { Search } from 'lucide-react';
5
5
  import { useTranslation } from 'react-i18next';
@@ -11,9 +11,9 @@ import { Icon } from "../Icon.js";
11
11
  import { Kbd } from "../Kbd.js";
12
12
  import { mergeRefs } from '../merge-refs.js';
13
13
  import { KEY_SHORTCUTS } from '../shortcut.js';
14
- /* The field, for the popup to put the focus in when it opens: a control before it would take it otherwise. */
15
14
  import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
16
- const InputRefContext = createContext(null);
15
+ const PaletteRefsContext = createContext(null);
16
+ const NO_ITEMS = [];
17
17
  /* A palette asks for a few words and shows rows of one line; a search whose rows are lines of source needs the wider one. */
18
18
  const SIZE = {
19
19
  md: 'w-[576px]',
@@ -25,16 +25,18 @@ const SIZE = {
25
25
  * first row is highlighted and stays so while the pointer leaves the list, which is what Enter runs.
26
26
  */
27
27
  export function CommandPaletteRoot(t0) {
28
- const $ = _c(22);
28
+ const $ = _c(33);
29
29
  let children;
30
30
  let defaultOpen;
31
31
  let items;
32
32
  let onOpenChange;
33
33
  let onOpenChangeComplete;
34
+ let onValueChange;
34
35
  let open;
35
36
  let props;
36
37
  let t1;
37
38
  let t2;
39
+ let value;
38
40
  if ($[0] !== t0) {
39
41
  ({
40
42
  open,
@@ -43,6 +45,8 @@ export function CommandPaletteRoot(t0) {
43
45
  onOpenChangeComplete,
44
46
  autoHighlight: t1,
45
47
  keepHighlight: t2,
48
+ value,
49
+ onValueChange,
46
50
  items,
47
51
  children,
48
52
  ...props
@@ -53,31 +57,86 @@ export function CommandPaletteRoot(t0) {
53
57
  $[3] = items;
54
58
  $[4] = onOpenChange;
55
59
  $[5] = onOpenChangeComplete;
56
- $[6] = open;
57
- $[7] = props;
58
- $[8] = t1;
59
- $[9] = t2;
60
+ $[6] = onValueChange;
61
+ $[7] = open;
62
+ $[8] = props;
63
+ $[9] = t1;
64
+ $[10] = t2;
65
+ $[11] = value;
60
66
  } else {
61
67
  children = $[1];
62
68
  defaultOpen = $[2];
63
69
  items = $[3];
64
70
  onOpenChange = $[4];
65
71
  onOpenChangeComplete = $[5];
66
- open = $[6];
67
- props = $[7];
68
- t1 = $[8];
69
- t2 = $[9];
72
+ onValueChange = $[6];
73
+ open = $[7];
74
+ props = $[8];
75
+ t1 = $[9];
76
+ t2 = $[10];
77
+ value = $[11];
70
78
  }
71
79
  const autoHighlight = t1 === undefined ? "always" : t1;
72
80
  const keepHighlight = t2 === undefined ? true : t2;
73
81
  const inputRef = useRef(null);
82
+ const listRef = useRef(null);
83
+ const [settledValue, setSettledValue] = useState(value);
84
+ const resetting = value !== undefined && value !== settledValue;
74
85
  let t3;
75
- if ($[10] !== autoHighlight || $[11] !== children || $[12] !== items || $[13] !== keepHighlight || $[14] !== props) {
76
- t3 = /*#__PURE__*/_jsx(InputRefContext, {
77
- value: inputRef,
86
+ let t4;
87
+ if ($[12] !== resetting || $[13] !== value) {
88
+ t3 = () => {
89
+ if (!resetting) {
90
+ return;
91
+ }
92
+ if (listRef.current !== null) {
93
+ listRef.current.scrollTop = 0;
94
+ }
95
+ setSettledValue(value);
96
+ };
97
+ t4 = [resetting, value];
98
+ $[12] = resetting;
99
+ $[13] = value;
100
+ $[14] = t3;
101
+ $[15] = t4;
102
+ } else {
103
+ t3 = $[14];
104
+ t4 = $[15];
105
+ }
106
+ useLayoutEffect(t3, t4);
107
+ let t5;
108
+ if ($[16] === Symbol.for("react.memo_cache_sentinel")) {
109
+ t5 = {
110
+ input: inputRef,
111
+ list: listRef
112
+ };
113
+ $[16] = t5;
114
+ } else {
115
+ t5 = $[16];
116
+ }
117
+ let t6;
118
+ if ($[17] !== onValueChange) {
119
+ t6 = (next, details) => {
120
+ if (details.reason === "input-change") {
121
+ setSettledValue(next);
122
+ }
123
+ onValueChange?.(next, details);
124
+ };
125
+ $[17] = onValueChange;
126
+ $[18] = t6;
127
+ } else {
128
+ t6 = $[18];
129
+ }
130
+ const t7 = resetting ? NO_ITEMS : items;
131
+ let t8;
132
+ if ($[19] !== autoHighlight || $[20] !== children || $[21] !== keepHighlight || $[22] !== props || $[23] !== t6 || $[24] !== t7 || $[25] !== value) {
133
+ t8 = /*#__PURE__*/_jsx(PaletteRefsContext, {
134
+ value: t5,
78
135
  children: /*#__PURE__*/_jsx(Autocomplete.Root, {
79
136
  ...props,
80
- items: items,
137
+ value: value,
138
+ onValueChange: t6,
139
+ items: t7,
81
140
  inline: true,
82
141
  open: true,
83
142
  autoHighlight: autoHighlight,
@@ -85,34 +144,36 @@ export function CommandPaletteRoot(t0) {
85
144
  children: children
86
145
  })
87
146
  });
88
- $[10] = autoHighlight;
89
- $[11] = children;
90
- $[12] = items;
91
- $[13] = keepHighlight;
92
- $[14] = props;
93
- $[15] = t3;
147
+ $[19] = autoHighlight;
148
+ $[20] = children;
149
+ $[21] = keepHighlight;
150
+ $[22] = props;
151
+ $[23] = t6;
152
+ $[24] = t7;
153
+ $[25] = value;
154
+ $[26] = t8;
94
155
  } else {
95
- t3 = $[15];
156
+ t8 = $[26];
96
157
  }
97
- let t4;
98
- if ($[16] !== defaultOpen || $[17] !== onOpenChange || $[18] !== onOpenChangeComplete || $[19] !== open || $[20] !== t3) {
99
- t4 = /*#__PURE__*/_jsx(DialogRoot, {
158
+ let t9;
159
+ if ($[27] !== defaultOpen || $[28] !== onOpenChange || $[29] !== onOpenChangeComplete || $[30] !== open || $[31] !== t8) {
160
+ t9 = /*#__PURE__*/_jsx(DialogRoot, {
100
161
  open: open,
101
162
  defaultOpen: defaultOpen,
102
163
  onOpenChange: onOpenChange,
103
164
  onOpenChangeComplete: onOpenChangeComplete,
104
- children: t3
165
+ children: t8
105
166
  });
106
- $[16] = defaultOpen;
107
- $[17] = onOpenChange;
108
- $[18] = onOpenChangeComplete;
109
- $[19] = open;
110
- $[20] = t3;
111
- $[21] = t4;
167
+ $[27] = defaultOpen;
168
+ $[28] = onOpenChange;
169
+ $[29] = onOpenChangeComplete;
170
+ $[30] = open;
171
+ $[31] = t8;
172
+ $[32] = t9;
112
173
  } else {
113
- t4 = $[21];
174
+ t9 = $[32];
114
175
  }
115
- return t4;
176
+ return t9;
116
177
  }
117
178
  /*
118
179
  * The dialog's surface. It hangs from a fixed line near the top instead of the middle: the list
@@ -146,7 +207,7 @@ export function CommandPalettePopup(t0) {
146
207
  const {
147
208
  t
148
209
  } = useTranslation("ui");
149
- const inputRef = useContext(InputRefContext);
210
+ const refs = useContext(PaletteRefsContext);
150
211
  let t2;
151
212
  if ($[5] !== t) {
152
213
  t2 = t("palette.label");
@@ -155,7 +216,7 @@ export function CommandPalettePopup(t0) {
155
216
  } else {
156
217
  t2 = $[6];
157
218
  }
158
- const t3 = initialFocus ?? inputRef ?? undefined;
219
+ const t3 = initialFocus ?? refs?.input;
159
220
  let t4;
160
221
  if ($[7] !== className || $[8] !== size) {
161
222
  t4 = withClass(clsx("top-[18vh] flex flex-col [translate:-50%_0]", SIZE[size]), className);
@@ -222,7 +283,7 @@ export function CommandPaletteInput(t0) {
222
283
  }
223
284
  const escapeHint = t1 === undefined ? true : t1;
224
285
  const mono = t2 === undefined ? false : t2;
225
- const inputRef = useContext(InputRefContext);
286
+ const refs = useContext(PaletteRefsContext);
226
287
  let t3;
227
288
  if ($[8] !== className) {
228
289
  t3 = clsx("relative flex shrink-0 items-center gap-2 border-b border-border px-3", className);
@@ -243,7 +304,7 @@ export function CommandPaletteInput(t0) {
243
304
  } else {
244
305
  t4 = $[11];
245
306
  }
246
- const t5 = inputRef ?? undefined;
307
+ const t5 = refs?.input;
247
308
  let t6;
248
309
  if ($[12] !== ref || $[13] !== t5) {
249
310
  t6 = mergeRefs(ref, t5);
@@ -308,55 +369,72 @@ export function CommandPaletteInput(t0) {
308
369
  }
309
370
  /* The rows. It scrolls on its own, so the field and the footer stay put. */
310
371
  export function CommandPaletteList(t0) {
311
- const $ = _c(11);
372
+ const $ = _c(16);
312
373
  let className;
313
374
  let props;
375
+ let ref;
314
376
  if ($[0] !== t0) {
315
377
  ({
316
378
  className,
379
+ ref,
317
380
  ...props
318
381
  } = t0);
319
382
  $[0] = t0;
320
383
  $[1] = className;
321
384
  $[2] = props;
385
+ $[3] = ref;
322
386
  } else {
323
387
  className = $[1];
324
388
  props = $[2];
389
+ ref = $[3];
325
390
  }
326
391
  const {
327
392
  t
328
393
  } = useTranslation("ui");
329
- let t1;
330
- if ($[3] !== t) {
331
- t1 = t("palette.results");
332
- $[3] = t;
333
- $[4] = t1;
334
- } else {
335
- t1 = $[4];
336
- }
394
+ const refs = useContext(PaletteRefsContext);
395
+ const t1 = refs?.list;
337
396
  let t2;
338
- if ($[5] !== className) {
339
- t2 = withClass("max-h-[50vh] min-h-0 overflow-y-auto overscroll-contain scroll-py-1.5 not-empty:p-1.5", className);
340
- $[5] = className;
397
+ if ($[4] !== ref || $[5] !== t1) {
398
+ t2 = mergeRefs(ref, t1);
399
+ $[4] = ref;
400
+ $[5] = t1;
341
401
  $[6] = t2;
342
402
  } else {
343
403
  t2 = $[6];
344
404
  }
345
405
  let t3;
346
- if ($[7] !== props || $[8] !== t1 || $[9] !== t2) {
347
- t3 = /*#__PURE__*/_jsx(Autocomplete.List, {
348
- "aria-label": t1,
349
- className: t2,
406
+ if ($[7] !== t) {
407
+ t3 = t("palette.results");
408
+ $[7] = t;
409
+ $[8] = t3;
410
+ } else {
411
+ t3 = $[8];
412
+ }
413
+ let t4;
414
+ if ($[9] !== className) {
415
+ t4 = withClass("max-h-[50vh] min-h-0 overflow-y-auto overscroll-contain scroll-py-1.5 not-empty:p-1.5", className);
416
+ $[9] = className;
417
+ $[10] = t4;
418
+ } else {
419
+ t4 = $[10];
420
+ }
421
+ let t5;
422
+ if ($[11] !== props || $[12] !== t2 || $[13] !== t3 || $[14] !== t4) {
423
+ t5 = /*#__PURE__*/_jsx(Autocomplete.List, {
424
+ ref: t2,
425
+ "aria-label": t3,
426
+ className: t4,
350
427
  ...props
351
428
  });
352
- $[7] = props;
353
- $[8] = t1;
354
- $[9] = t2;
355
- $[10] = t3;
429
+ $[11] = props;
430
+ $[12] = t2;
431
+ $[13] = t3;
432
+ $[14] = t4;
433
+ $[15] = t5;
356
434
  } else {
357
- t3 = $[10];
435
+ t5 = $[15];
358
436
  }
359
- return t3;
437
+ return t5;
360
438
  }
361
439
  /* Under the list: the keys that work here, a count, an action that leaves the palette. */
362
440
  export function CommandPaletteFooter(t0) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adecore/ui",
3
- "version": "0.34.1",
3
+ "version": "0.35.1",
4
4
  "description": "React components, a theme, formatters and a settings dialog for desktop-grade apps on React 19, Base UI and Tailwind 4.",
5
5
  "license": "FSL-1.1-MIT",
6
6
  "author": "Bas Milius",
@@ -10,13 +10,15 @@ const TICK_MS = 30_000;
10
10
  export interface RelativeTimeProps {
11
11
  /* `null`, `undefined` or an invalid date for a moment that is not known, drawn as a dash. */
12
12
  at: Date | number | null | undefined;
13
+ /* Off inside a row that is the focus target itself: no tab stop of its own, and the row shows the exact time. */
14
+ tooltip?: boolean;
13
15
  tooltipSide?: TooltipSide;
14
16
  className?: string;
15
17
  ref?: Ref<HTMLElement>;
16
18
  }
17
19
 
18
20
  /* A moment as its age, `3m ago`, counting on while it is on screen, with the exact time on hover and on keyboard focus. */
19
- export function RelativeTime({ at, tooltipSide, className, ref }: RelativeTimeProps) {
21
+ export function RelativeTime({ at, tooltip = true, tooltipSide, className, ref }: RelativeTimeProps) {
20
22
  // The formatters read the locale from a module, not from an input, so a compiled render would keep its first words.
21
23
  'use no memo';
22
24
  useFormatLocale();
@@ -28,6 +30,13 @@ export function RelativeTime({ at, tooltipSide, className, ref }: RelativeTimePr
28
30
  </span>
29
31
  );
30
32
  }
33
+ if (!tooltip) {
34
+ return (
35
+ <time ref={ref as Ref<HTMLTimeElement>} dateTime={new Date(at).toISOString()} className={clsx('whitespace-nowrap', className)}>
36
+ {formatAge(at, now)}
37
+ </time>
38
+ );
39
+ }
31
40
  return (
32
41
  <Tooltip label={formatDayClockWithYear(at)} side={tooltipSide}>
33
42
  {/* Text is no tab stop of its own, and a hint that only the pointer can open is lost to the keyboard. */}
@@ -1,4 +1,4 @@
1
- import { createContext, useContext, useRef, type ComponentProps, type ReactNode, type RefObject } from 'react';
1
+ import { createContext, useContext, useLayoutEffect, useRef, useState, type ComponentProps, type ReactNode, type RefObject } from 'react';
2
2
  import clsx from 'clsx';
3
3
  import { Search } from 'lucide-react';
4
4
  import { useTranslation } from 'react-i18next';
@@ -11,8 +11,16 @@ import { Kbd } from '../Kbd.tsx';
11
11
  import { mergeRefs } from '../merge-refs.ts';
12
12
  import { KEY_SHORTCUTS } from '../shortcut.ts';
13
13
 
14
- /* The field, for the popup to put the focus in when it opens: a control before it would take it otherwise. */
15
- const InputRefContext = createContext<RefObject<HTMLInputElement | null> | null>(null);
14
+ interface PaletteRefs {
15
+ /* For the popup to put the focus in when it opens: a control before it would take it otherwise. */
16
+ input: RefObject<HTMLInputElement | null>;
17
+ /* For the root to scroll back to the top when the app sets the query. */
18
+ list: RefObject<HTMLDivElement | null>;
19
+ }
20
+
21
+ const PaletteRefsContext = createContext<PaletteRefs | null>(null);
22
+
23
+ const NO_ITEMS: readonly never[] = [];
16
24
 
17
25
  /* A palette asks for a few words and shows rows of one line; a search whose rows are lines of source needs the wider one. */
18
26
  const SIZE = {
@@ -44,18 +52,48 @@ export function CommandPaletteRoot<ItemValue>({
44
52
  onOpenChangeComplete,
45
53
  autoHighlight = 'always',
46
54
  keepHighlight = true,
55
+ value,
56
+ onValueChange,
47
57
  items,
48
58
  children,
49
59
  ...props
50
60
  }: CommandPaletteRootProps<ItemValue>) {
51
61
  const inputRef = useRef<HTMLInputElement>(null);
62
+ const listRef = useRef<HTMLDivElement>(null);
63
+ /*
64
+ * Base UI moves the highlight to the first row for a typed query only. For a query the app sets,
65
+ * the list is empty for one commit that is never painted: Base UI drops the highlight then and
66
+ * puts it on the first row of what comes back.
67
+ */
68
+ const [settledValue, setSettledValue] = useState(value);
69
+ const resetting = value !== undefined && value !== settledValue;
70
+
71
+ useLayoutEffect(() => {
72
+ if (!resetting) {
73
+ return;
74
+ }
75
+ if (listRef.current !== null) {
76
+ listRef.current.scrollTop = 0;
77
+ }
78
+ // The rows may come back only after Base UI has seen the empty list in a commit of its own.
79
+ // oxlint-disable-next-line react/set-state-in-effect
80
+ setSettledValue(value);
81
+ }, [resetting, value]);
82
+
52
83
  return (
53
84
  <DialogRoot open={open} defaultOpen={defaultOpen} onOpenChange={onOpenChange} onOpenChangeComplete={onOpenChangeComplete}>
54
- <InputRefContext value={inputRef}>
85
+ <PaletteRefsContext value={{ input: inputRef, list: listRef }}>
55
86
  {/* Its props take a flat list or groups; only its overloads split the two, which a generic caller cannot pick between. */}
56
87
  <Autocomplete.Root<ItemValue>
57
88
  {...props}
58
- items={items as readonly ItemValue[] | undefined}
89
+ value={value}
90
+ onValueChange={(next, details) => {
91
+ if (details.reason === 'input-change') {
92
+ setSettledValue(next);
93
+ }
94
+ onValueChange?.(next, details);
95
+ }}
96
+ items={(resetting ? NO_ITEMS : items) as readonly ItemValue[] | undefined}
59
97
  inline
60
98
  open
61
99
  autoHighlight={autoHighlight}
@@ -63,7 +101,7 @@ export function CommandPaletteRoot<ItemValue>({
63
101
  >
64
102
  {children}
65
103
  </Autocomplete.Root>
66
- </InputRefContext>
104
+ </PaletteRefsContext>
67
105
  </DialogRoot>
68
106
  );
69
107
  }
@@ -78,11 +116,11 @@ export type CommandPalettePopupProps = Omit<DialogPopupProps, 'size'> & {
78
116
  */
79
117
  export function CommandPalettePopup({ size = 'md', initialFocus, className, ...props }: CommandPalettePopupProps) {
80
118
  const { t } = useTranslation('ui');
81
- const inputRef = useContext(InputRefContext);
119
+ const refs = useContext(PaletteRefsContext);
82
120
  return (
83
121
  <DialogPopup
84
122
  aria-label={t('palette.label')}
85
- initialFocus={initialFocus ?? inputRef ?? undefined}
123
+ initialFocus={initialFocus ?? refs?.input}
86
124
  className={withClass(clsx('top-[18vh] flex flex-col [translate:-50%_0]', SIZE[size]), className)}
87
125
  {...props}
88
126
  />
@@ -104,12 +142,12 @@ export type CommandPaletteInputProps = Omit<ComponentProps<typeof Autocomplete.I
104
142
 
105
143
  /* The field across the top. It keeps the keys while the arrows move the highlight; a control beside it should not take the focus on a click. */
106
144
  export function CommandPaletteInput({ leading, trailing, escapeHint = true, mono = false, className, ref, ...props }: CommandPaletteInputProps) {
107
- const inputRef = useContext(InputRefContext);
145
+ const refs = useContext(PaletteRefsContext);
108
146
  return (
109
147
  <div className={clsx('relative flex shrink-0 items-center gap-2 border-b border-border px-3', className)}>
110
148
  {leading ?? <Icon icon={Search} size={14} className="shrink-0 text-text-faint" />}
111
149
  <Autocomplete.Input
112
- ref={mergeRefs(ref, inputRef ?? undefined)}
150
+ ref={mergeRefs(ref, refs?.input)}
113
151
  spellCheck={false}
114
152
  className={clsx('h-11 min-w-0 flex-1 bg-transparent text-sm text-text outline-none placeholder:text-text-faint', mono && 'font-mono text-code')}
115
153
  {...props}
@@ -121,10 +159,12 @@ export function CommandPaletteInput({ leading, trailing, escapeHint = true, mono
121
159
  }
122
160
 
123
161
  /* The rows. It scrolls on its own, so the field and the footer stay put. */
124
- export function CommandPaletteList({ className, ...props }: ComponentProps<typeof Autocomplete.List>) {
162
+ export function CommandPaletteList({ className, ref, ...props }: ComponentProps<typeof Autocomplete.List>) {
125
163
  const { t } = useTranslation('ui');
164
+ const refs = useContext(PaletteRefsContext);
126
165
  return (
127
166
  <Autocomplete.List
167
+ ref={mergeRefs(ref, refs?.list)}
128
168
  aria-label={t('palette.results')}
129
169
  className={withClass('max-h-[50vh] min-h-0 overflow-y-auto overscroll-contain scroll-py-1.5 not-empty:p-1.5', className)}
130
170
  {...props}