@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.
package/dist/RelativeTime.d.ts
CHANGED
|
@@ -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;
|
package/dist/RelativeTime.js
CHANGED
|
@@ -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
|
|
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(
|
|
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] =
|
|
57
|
-
$[7] =
|
|
58
|
-
$[8] =
|
|
59
|
-
$[9] =
|
|
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
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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
|
-
|
|
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
|
-
$[
|
|
89
|
-
$[
|
|
90
|
-
$[
|
|
91
|
-
$[
|
|
92
|
-
$[
|
|
93
|
-
$[
|
|
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
|
-
|
|
156
|
+
t8 = $[26];
|
|
96
157
|
}
|
|
97
|
-
let
|
|
98
|
-
if ($[
|
|
99
|
-
|
|
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:
|
|
165
|
+
children: t8
|
|
105
166
|
});
|
|
106
|
-
$[
|
|
107
|
-
$[
|
|
108
|
-
$[
|
|
109
|
-
$[
|
|
110
|
-
$[
|
|
111
|
-
$[
|
|
167
|
+
$[27] = defaultOpen;
|
|
168
|
+
$[28] = onOpenChange;
|
|
169
|
+
$[29] = onOpenChangeComplete;
|
|
170
|
+
$[30] = open;
|
|
171
|
+
$[31] = t8;
|
|
172
|
+
$[32] = t9;
|
|
112
173
|
} else {
|
|
113
|
-
|
|
174
|
+
t9 = $[32];
|
|
114
175
|
}
|
|
115
|
-
return
|
|
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
|
|
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 ??
|
|
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
|
|
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 =
|
|
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(
|
|
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
|
-
|
|
330
|
-
|
|
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] !==
|
|
339
|
-
t2 =
|
|
340
|
-
$[
|
|
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] !==
|
|
347
|
-
t3 =
|
|
348
|
-
|
|
349
|
-
|
|
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
|
-
$[
|
|
353
|
-
$[
|
|
354
|
-
$[
|
|
355
|
-
$[
|
|
429
|
+
$[11] = props;
|
|
430
|
+
$[12] = t2;
|
|
431
|
+
$[13] = t3;
|
|
432
|
+
$[14] = t4;
|
|
433
|
+
$[15] = t5;
|
|
356
434
|
} else {
|
|
357
|
-
|
|
435
|
+
t5 = $[15];
|
|
358
436
|
}
|
|
359
|
-
return
|
|
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
package/src/RelativeTime.tsx
CHANGED
|
@@ -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
|
-
|
|
15
|
-
|
|
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
|
-
<
|
|
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
|
-
|
|
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
|
-
</
|
|
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
|
|
119
|
+
const refs = useContext(PaletteRefsContext);
|
|
82
120
|
return (
|
|
83
121
|
<DialogPopup
|
|
84
122
|
aria-label={t('palette.label')}
|
|
85
|
-
initialFocus={initialFocus ??
|
|
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
|
|
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,
|
|
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}
|