@parallelworks/ui 0.52.0 → 0.54.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.
Files changed (92) hide show
  1. package/README.md +97 -8
  2. package/dist/access/AccessDrawer.d.ts +55 -0
  3. package/dist/access/AccessDrawer.js +445 -0
  4. package/dist/access/AccessList.d.ts +58 -0
  5. package/dist/access/AccessList.js +382 -0
  6. package/dist/access/GrantView.d.ts +35 -0
  7. package/dist/access/GrantView.js +188 -0
  8. package/dist/access/GroupPicker.d.ts +35 -0
  9. package/dist/access/GroupPicker.js +336 -0
  10. package/dist/access/focus.d.ts +10 -0
  11. package/dist/access/focus.js +27 -0
  12. package/dist/access/index.d.ts +3 -0
  13. package/dist/access/index.js +3 -0
  14. package/dist/access/model.d.ts +49 -0
  15. package/dist/access/model.js +129 -0
  16. package/dist/access/strings.d.ts +122 -0
  17. package/dist/access/strings.js +151 -0
  18. package/dist/access/types.d.ts +56 -0
  19. package/dist/ai/ai.css +5 -3
  20. package/dist/ai/components/SlashMenu.js +1 -1
  21. package/dist/ai/components/agent/AgentMessageParts.js +1 -1
  22. package/dist/ai/strings.js +0 -1
  23. package/dist/ai/ui/sidebar.js +1 -1
  24. package/dist/components/Combobox.d.ts +19 -0
  25. package/dist/components/Combobox.js +101 -50
  26. package/dist/components/Drawer.d.ts +6 -1
  27. package/dist/components/Drawer.js +25 -16
  28. package/dist/components/Field.d.ts +5 -4
  29. package/dist/components/Field.js +27 -4
  30. package/dist/components/InfoTip.d.ts +12 -4
  31. package/dist/components/InfoTip.js +13 -3
  32. package/dist/components/MarkdownImpl.d.ts +1 -0
  33. package/dist/components/MarkdownImpl.js +1 -0
  34. package/dist/components/Menu.js +4 -4
  35. package/dist/components/MenuEntries.js +1 -1
  36. package/dist/components/OptionDescription.d.ts +7 -0
  37. package/dist/components/OptionDescription.js +19 -0
  38. package/dist/components/Popover.d.ts +4 -1
  39. package/dist/components/Popover.js +2 -1
  40. package/dist/components/Provider.d.ts +9 -0
  41. package/dist/components/Provider.js +4 -0
  42. package/dist/components/Select.js +13 -11
  43. package/dist/components/comboboxItems.d.ts +10 -0
  44. package/dist/components/comboboxItems.js +8 -1
  45. package/dist/components/floating.d.ts +5 -2
  46. package/dist/components/floating.js +2 -2
  47. package/dist/components/focus.d.ts +5 -0
  48. package/dist/components/focus.js +21 -1
  49. package/dist/editor/Editor.js +13 -3
  50. package/dist/editor/Monaco.d.ts +8 -1
  51. package/dist/editor/Monaco.js +16 -3
  52. package/dist/editor/editorModel.d.ts +1 -1
  53. package/dist/editor/editorModel.js +1 -1
  54. package/dist/editor/index.d.ts +1 -0
  55. package/dist/editor/index.js +2 -1
  56. package/dist/editor/languages.d.ts +24 -0
  57. package/dist/editor/languages.js +31 -0
  58. package/dist/editor/monacoFeatures.d.ts +7 -0
  59. package/dist/editor/monacoFeatures.js +19 -0
  60. package/dist/editor/themes.d.ts +5 -0
  61. package/dist/editor/themes.js +24 -4
  62. package/dist/editor/workers.d.ts +3 -3
  63. package/dist/editor/workers.js +8 -4
  64. package/dist/editor/yamlLanguage.js +55 -38
  65. package/dist/file-explorer/FileExplorer.d.ts +18 -4
  66. package/dist/file-explorer/FileExplorer.js +64 -20
  67. package/dist/file-explorer/FilePreview/Viewer.js +12 -8
  68. package/dist/file-explorer/FileRow.d.ts +6 -2
  69. package/dist/file-explorer/FileRow.js +35 -17
  70. package/dist/file-explorer/FileRowsWindow.d.ts +2 -1
  71. package/dist/file-explorer/FileRowsWindow.js +2 -1
  72. package/dist/file-explorer/FileTable.d.ts +3 -1
  73. package/dist/file-explorer/FileTable.js +3 -2
  74. package/dist/file-explorer/TreeView.d.ts +5 -1
  75. package/dist/file-explorer/TreeView.js +49 -25
  76. package/dist/file-explorer/helpers.js +3 -3
  77. package/dist/file-explorer/lib/paging.d.ts +1 -0
  78. package/dist/file-explorer/lib/paging.js +5 -1
  79. package/dist/file-explorer/lib/utils.d.ts +3 -0
  80. package/dist/file-explorer/lib/utils.js +6 -1
  81. package/dist/form/Wizard/utils.js +2 -1
  82. package/dist/list/ActionToolbar.js +3 -2
  83. package/dist/list/ListPage.js +1 -1
  84. package/dist/list/ListTable.d.ts +5 -0
  85. package/dist/list/ListTable.js +6 -2
  86. package/dist/styles/theme.css +82 -62
  87. package/dist/styles.css +1 -1
  88. package/dist/theme/derive.d.ts +5 -3
  89. package/dist/theme/derive.js +61 -3
  90. package/package.json +9 -2
  91. package/dist/file-explorer/FilePreview/CodePreviewEditor.d.ts +0 -6
  92. package/dist/file-explorer/FilePreview/CodePreviewEditor.js +0 -64
@@ -1,11 +1,13 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { AddIcon, CheckIcon, ChevronDownIcon, RemoveIcon } from "../icons.js";
3
3
  import { panelClassName, positionerClassName } from "./floating.js";
4
+ import { Loader } from "./Loader.js";
4
5
  import { Tooltip } from "./Tooltip.js";
5
6
  import { useStrings } from "./Provider.js";
6
7
  import { useQueryMatch } from "./search.js";
7
8
  import { fieldBox } from "./fieldBox.js";
8
- import { isComboboxGroup, pickerItemClassName } from "./comboboxItems.js";
9
+ import { isComboboxGroup, optionNaming, pickerItemClassName } from "./comboboxItems.js";
10
+ import { OptionDescription } from "./OptionDescription.js";
9
11
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
10
12
  import { useCallback, useId, useImperativeHandle, useMemo, useRef, useState } from "react";
11
13
  import { dequal } from "dequal";
@@ -79,13 +81,14 @@ var triggerClassName = "flex shrink-0 items-center self-stretch px-2 text-(--the
79
81
  var popupInputClassName = fieldBox({ size: "sm" });
80
82
  /** A pick-from-the-list field: one value, or several with `multiple`. Typing filters the list. */
81
83
  function Combobox$1(props) {
82
- const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip, size = "md" } = props;
84
+ const { items, isItemEqualToValue = dequal, itemToStringLabel = String, className, placeholder, disabled, required, id, "aria-label": ariaLabel, "aria-labelledby": ariaLabelledBy, "aria-describedby": ariaDescribedBy, "aria-invalid": ariaInvalid, "aria-required": ariaRequired, trigger, triggerTooltip, size = "md", details, detailsSummary, onItemHighlighted, onQueryChange, loading = false, collisionBoundary } = props;
83
85
  const entries = useEntries(items);
84
86
  const filter = useEntryFilter();
85
87
  const t = useStrings().dropdown;
86
88
  const list = useRef(null);
87
89
  const uid = useId();
88
90
  const [inputValue, setInputValue] = useState(null);
91
+ const [highlighted, setHighlighted] = useState(void 0);
89
92
  const entryOf = (value) => entries.flat.find((entry) => isItemEqualToValue(entry.value, value)) ?? {
90
93
  value,
91
94
  label: itemToStringLabel(value)
@@ -125,15 +128,21 @@ function Combobox$1(props) {
125
128
  autoComplete: "off",
126
129
  className: trigger ? popupInputClassName : inputClassName(size)
127
130
  });
131
+ const isHighlighted = (entry) => highlighted !== void 0 && !entry.created && !highlighted.created && isItemEqualToValue(entry.value, highlighted.value);
128
132
  const itemRow = (entry, rowProps) => /* @__PURE__ */ jsxs(Combobox.Item, {
129
133
  value: entry,
130
134
  disabled: entry.disabled || full && !isChosen(entry),
131
135
  ...rowProps.props,
132
- ...optionNaming(uid, rowProps.props.index, entry),
136
+ ...optionNaming(`${uid}${rowProps.props.index}`, entry),
133
137
  className: pickerItemClassName,
134
138
  children: [/* @__PURE__ */ jsx(ItemContent, {
135
139
  entry,
136
- id: `${uid}${rowProps.props.index}`
140
+ id: `${uid}${rowProps.props.index}`,
141
+ children: detailsSummary && isHighlighted(entry) && /* @__PURE__ */ jsx("span", {
142
+ "aria-hidden": "true",
143
+ className: "mt-0.5 text-xs leading-snug text-(--theme-muted-text-color) @min-[30rem]/picker:hidden",
144
+ children: detailsSummary(entry.value)
145
+ })
137
146
  }), /* @__PURE__ */ jsx(Combobox.ItemIndicator, {
138
147
  className: "ml-auto flex shrink-0 items-center",
139
148
  children: /* @__PURE__ */ jsx(CheckIcon, {
@@ -162,7 +171,10 @@ function Combobox$1(props) {
162
171
  className: "border-b border-(--theme-border) p-1.5",
163
172
  children: input
164
173
  }),
165
- className: "w-max min-w-(--anchor-width) max-w-[min(28rem,var(--available-width))]",
174
+ className: details ? "w-[min(36rem,var(--available-width))]" : "w-max min-w-(--anchor-width) max-w-[min(28rem,var(--available-width))]",
175
+ loading: loading ? t.searching : void 0,
176
+ details: details?.(highlighted?.created ? void 0 : highlighted?.value),
177
+ collisionBoundary,
166
178
  renderItem: itemRow
167
179
  })] }) : /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs(Combobox.InputGroup, {
168
180
  "data-testid": "combobox",
@@ -198,15 +210,25 @@ function Combobox$1(props) {
198
210
  empty: t.noOptionsFound,
199
211
  statusGroups: entries.statusGroups,
200
212
  query: inputValue ?? "",
213
+ loading: loading ? t.searching : void 0,
214
+ details: details?.(highlighted?.created ? void 0 : highlighted?.value),
215
+ collisionBoundary,
201
216
  renderItem: itemRow
202
217
  })] });
203
218
  const root = {
204
219
  items: listItems,
205
- filter,
206
220
  isItemEqualToValue: (a, b) => isItemEqualToValue(a.value, b.value),
207
221
  itemToStringLabel: entryLabel,
208
- onInputValueChange: setInputValue,
209
- onItemHighlighted: (_, { index, reason }) => list.current?.reveal(index, reason),
222
+ filter: onQueryChange ? null : filter,
223
+ onInputValueChange: (next) => {
224
+ setInputValue(next);
225
+ onQueryChange?.(next);
226
+ },
227
+ onItemHighlighted: (entry, { index, reason }) => {
228
+ if (details || detailsSummary) setHighlighted(entry);
229
+ onItemHighlighted?.(entry && !entry.created ? entry.value : void 0);
230
+ list.current?.reveal(index, reason);
231
+ },
210
232
  disabled,
211
233
  required,
212
234
  virtualized: true,
@@ -218,7 +240,12 @@ function Combobox$1(props) {
218
240
  multiple: true,
219
241
  value: toEntries(props.value),
220
242
  defaultValue: toEntries(props.defaultValue),
221
- onValueChange: (next) => {
243
+ onValueChange: (next, details) => {
244
+ if (details.reason === "escape-key") {
245
+ details.cancel();
246
+ details.allowPropagation();
247
+ return;
248
+ }
222
249
  setUncontrolledCount(next.length);
223
250
  props.onValueChange?.(next.map((entry) => entry.value));
224
251
  }
@@ -275,7 +302,7 @@ function Autocomplete$1({ items, value, defaultValue, onValueChange, className,
275
302
  value: entry,
276
303
  disabled: entry.disabled,
277
304
  ...rowProps.props,
278
- ...optionNaming(uid, rowProps.props.index, entry),
305
+ ...optionNaming(`${uid}${rowProps.props.index}`, entry),
279
306
  className: pickerItemClassName,
280
307
  children: /* @__PURE__ */ jsx(ItemContent, {
281
308
  entry,
@@ -285,33 +312,31 @@ function Autocomplete$1({ items, value, defaultValue, onValueChange, className,
285
312
  })]
286
313
  });
287
314
  }
288
- /** An option is named by its label, with its description read after it rather than as part of
289
- * its name. */
290
- function optionNaming(uid, index, entry) {
291
- return {
292
- "aria-labelledby": `${uid}${index}label`,
293
- "aria-describedby": entry.description ? `${uid}${index}description` : void 0
294
- };
295
- }
296
- function ItemContent({ entry, id }) {
315
+ function ItemContent({ entry, id, children }) {
297
316
  return /* @__PURE__ */ jsxs(Fragment, { children: [entry.icon && /* @__PURE__ */ jsx("span", {
298
317
  className: "flex shrink-0 items-center",
299
318
  children: entry.icon
300
319
  }), /* @__PURE__ */ jsxs("span", {
301
320
  className: "flex min-w-0 flex-col",
302
- children: [/* @__PURE__ */ jsx(Tooltip, {
303
- content: entry.label,
304
- overflowOnly: true,
305
- children: /* @__PURE__ */ jsx("span", {
306
- id: `${id}label`,
307
- className: "truncate",
308
- children: entry.label
309
- })
310
- }), entry.description && /* @__PURE__ */ jsx("span", {
311
- id: `${id}description`,
312
- className: "text-xs leading-snug text-(--theme-muted-text-color)",
313
- children: entry.description
314
- })]
321
+ children: [
322
+ /* @__PURE__ */ jsxs("span", {
323
+ className: "flex min-w-0 items-center gap-2",
324
+ children: [/* @__PURE__ */ jsx(Tooltip, {
325
+ content: entry.label,
326
+ overflowOnly: true,
327
+ children: /* @__PURE__ */ jsx("span", {
328
+ id: `${id}label`,
329
+ className: "truncate",
330
+ children: entry.label
331
+ })
332
+ }), entry.badge]
333
+ }),
334
+ /* @__PURE__ */ jsx(OptionDescription, {
335
+ id: `${id}description`,
336
+ item: entry
337
+ }),
338
+ children
339
+ ]
315
340
  })] });
316
341
  }
317
342
  function GroupHeading({ group }) {
@@ -335,26 +360,52 @@ function GroupHeading({ group }) {
335
360
  ] });
336
361
  }
337
362
  /** Portal, positioner and popup shared by Combobox and Autocomplete; both use the same parts. */
338
- function ListPopup({ listRef, empty, statusGroups = [], query = "", header, className = "w-(--anchor-width) max-w-(--available-width)", renderItem }) {
363
+ function ListPopup({ listRef, empty, statusGroups = [], query = "", header, className = "w-(--anchor-width) max-w-(--available-width)", loading, details, collisionBoundary, renderItem }) {
364
+ const withDetails = details !== void 0;
365
+ const list = /* @__PURE__ */ jsxs(Fragment, { children: [
366
+ loading && /* @__PURE__ */ jsxs("div", {
367
+ role: "status",
368
+ className: "flex items-center gap-2 px-3 py-2 text-(--theme-muted-text-color)",
369
+ children: [/* @__PURE__ */ jsx(Loader, {
370
+ inline: true,
371
+ decorative: true,
372
+ size: "1em"
373
+ }), loading]
374
+ }),
375
+ empty && !loading && statusGroups.length === 0 && /* @__PURE__ */ jsx(Combobox.Empty, {
376
+ className: "px-3 py-2 text-(--theme-muted-text-color) empty:hidden",
377
+ children: empty
378
+ }),
379
+ /* @__PURE__ */ jsx(Combobox.List, {
380
+ className: cx(withDetails && "min-h-0 flex-1"),
381
+ children: /* @__PURE__ */ jsx(VirtualList, {
382
+ ref: listRef,
383
+ statusGroups,
384
+ query,
385
+ tall: withDetails,
386
+ renderItem
387
+ })
388
+ })
389
+ ] });
339
390
  return /* @__PURE__ */ jsx(Combobox.Portal, { children: /* @__PURE__ */ jsx(Combobox.Positioner, {
340
391
  align: "start",
341
392
  sideOffset: 4,
342
393
  className: positionerClassName,
394
+ ...collisionBoundary && { collisionBoundary },
343
395
  children: /* @__PURE__ */ jsxs(Combobox.Popup, {
344
- className: cx("overflow-hidden", className, panelClassName),
345
- children: [
346
- header,
347
- empty && statusGroups.length === 0 && /* @__PURE__ */ jsx(Combobox.Empty, {
348
- className: "px-3 py-2 text-(--theme-muted-text-color) empty:hidden",
349
- children: empty
350
- }),
351
- /* @__PURE__ */ jsx(Combobox.List, { children: /* @__PURE__ */ jsx(VirtualList, {
352
- ref: listRef,
353
- statusGroups,
354
- query,
355
- renderItem
356
- }) })
357
- ]
396
+ className: cx("overflow-hidden", withDetails && "@container/picker flex h-[min(40rem,var(--available-height))] flex-col", className, panelClassName),
397
+ children: [header, withDetails ? /* @__PURE__ */ jsxs("div", {
398
+ className: "flex min-h-0 flex-1",
399
+ children: [/* @__PURE__ */ jsx("div", {
400
+ className: "flex min-w-0 flex-1 flex-col",
401
+ children: list
402
+ }), /* @__PURE__ */ jsxs("div", {
403
+ "aria-hidden": "true",
404
+ "data-testid": "combobox-details",
405
+ className: "hidden w-1/2 shrink-0 overflow-y-auto overscroll-contain border-l border-(--theme-border) bg-(--theme-panel-bg) @min-[30rem]/picker:block",
406
+ children: [details, /* @__PURE__ */ jsx("div", { className: "pointer-events-none sticky bottom-0 -mt-6 h-6 bg-linear-to-t from-(--theme-panel-bg) to-transparent" })]
407
+ })]
408
+ }) : list]
358
409
  })
359
410
  }) });
360
411
  }
@@ -382,7 +433,7 @@ function toRows(filtered, statusGroups) {
382
433
  rowOfItem
383
434
  };
384
435
  }
385
- function VirtualList({ ref, statusGroups, query, renderItem }) {
436
+ function VirtualList({ ref, statusGroups, query, tall, renderItem }) {
386
437
  const filtered = Combobox.useFilteredItems();
387
438
  const match = useQueryMatch();
388
439
  const { rows, rowOfItem } = useMemo(() => {
@@ -420,7 +471,7 @@ function VirtualList({ ref, statusGroups, query, renderItem }) {
420
471
  return /* @__PURE__ */ jsx("div", {
421
472
  role: "presentation",
422
473
  ref: scrollerRef,
423
- className: "max-h-[min(15rem,var(--available-height))] overflow-auto overscroll-contain",
474
+ className: cx("overflow-auto overscroll-contain px-1", tall ? "max-h-full" : "max-h-[min(15rem,var(--available-height))]"),
424
475
  style: { height: total },
425
476
  children: /* @__PURE__ */ jsx("div", {
426
477
  role: "presentation",
@@ -441,7 +492,7 @@ function VirtualList({ ref, statusGroups, query, renderItem }) {
441
492
  "data-index": virtual.index,
442
493
  ref: virtualizer.measureElement,
443
494
  style,
444
- className: "flex items-center gap-1.5 px-3 pt-2 pb-1 text-xs text-(--theme-muted-text-color)",
495
+ className: "flex items-center gap-1.5 px-2 pt-2 pb-1 text-xs text-(--theme-muted-text-color)",
445
496
  children: /* @__PURE__ */ jsx(GroupHeading, { group: row.group })
446
497
  }, virtual.key);
447
498
  return renderItem(row.entry, {
@@ -7,6 +7,11 @@ export interface DrawerProps extends Omit<BaseDrawer.Popup.Props, 'className' |
7
7
  title?: ReactNode;
8
8
  /** A line under the title. */
9
9
  description?: ReactNode;
10
+ /** Adds a back arrow before the title, for a view the drawer opened over its first, such as
11
+ * a form; the drawer stays one drawer rather than another stacking on it. */
12
+ onBack?: (() => void) | undefined;
13
+ /** Names the back arrow; defaults to the provider's `back`. */
14
+ backLabel?: string | undefined;
10
15
  /** Actions along the bottom edge, such as Cancel and Save; they stay in view as the body scrolls. */
11
16
  footer?: ReactNode;
12
17
  /** The edge it slides in from, and the way a swipe dismisses it. */
@@ -44,4 +49,4 @@ export interface DrawerProps extends Omit<BaseDrawer.Popup.Props, 'className' |
44
49
  * `--app-inset-top` (a top bar's height), and at its widest leaves the strip
45
50
  * `--app-inset-start` (a start-side sidebar's width, at least 2.5rem). Set both
46
51
  * on the document root. */
47
- export declare function Drawer({ open, onClose, title, description, footer, side, width, resizable, minWidth, dismissible, modal, closeButton, contained, keepMounted, padded, className, children, ...popupProps }: DrawerProps): import("react").JSX.Element;
52
+ export declare function Drawer({ open, onClose, title, description, onBack, backLabel, footer, side, width, resizable, minWidth, dismissible, modal, closeButton, contained, keepMounted, padded, className, children, ...popupProps }: DrawerProps): import("react").JSX.Element;
@@ -1,5 +1,5 @@
1
1
  import { cx } from "../cx.js";
2
- import { CloseIcon } from "../icons.js";
2
+ import { ArrowLeftIcon, CloseIcon } from "../icons.js";
3
3
  import { useStrings } from "./Provider.js";
4
4
  import { IconButton } from "./IconButton.js";
5
5
  import { closeHandler } from "./dismiss.js";
@@ -15,7 +15,7 @@ import { Drawer } from "@base-ui/react/drawer";
15
15
  * `--app-inset-top` (a top bar's height), and at its widest leaves the strip
16
16
  * `--app-inset-start` (a start-side sidebar's width, at least 2.5rem). Set both
17
17
  * on the document root. */
18
- function Drawer$1({ open, onClose, title, description, footer, side = "right", width = 480, resizable = false, minWidth = 320, dismissible = true, modal = true, closeButton = true, contained = false, keepMounted = false, padded = true, className, children, ...popupProps }) {
18
+ function Drawer$1({ open, onClose, title, description, onBack, backLabel, footer, side = "right", width = 480, resizable = false, minWidth = 320, dismissible = true, modal = true, closeButton = true, contained = false, keepMounted = false, padded = true, className, children, ...popupProps }) {
19
19
  const { common } = useStrings();
20
20
  const container = useRef(null);
21
21
  const viewport = useRef(null);
@@ -61,20 +61,29 @@ function Drawer$1({ open, onClose, title, description, footer, side = "right", w
61
61
  children: [
62
62
  header && /* @__PURE__ */ jsxs("div", {
63
63
  className: "flex shrink-0 items-start gap-3 border-b border-(--theme-border) px-4 py-3",
64
- children: [/* @__PURE__ */ jsxs("div", {
65
- className: "min-w-0 flex-1",
66
- children: [title !== void 0 && /* @__PURE__ */ jsx(Drawer.Title, {
67
- className: "m-0 text-sm font-semibold wrap-break-word",
68
- children: title
69
- }), description !== void 0 && /* @__PURE__ */ jsx(Drawer.Description, {
70
- className: "mt-0.5 text-xs text-(--theme-muted-text-color)",
71
- children: description
72
- })]
73
- }), closeButton && /* @__PURE__ */ jsx(Drawer.Close, { render: /* @__PURE__ */ jsx(IconButton, {
74
- icon: /* @__PURE__ */ jsx(CloseIcon, {}),
75
- label: common.close,
76
- className: "-my-1 -me-1.5"
77
- }) })]
64
+ children: [
65
+ onBack && /* @__PURE__ */ jsx(IconButton, {
66
+ icon: /* @__PURE__ */ jsx(ArrowLeftIcon, {}),
67
+ label: backLabel ?? common.back,
68
+ onClick: onBack,
69
+ className: "-my-1 -ms-1.5"
70
+ }),
71
+ /* @__PURE__ */ jsxs("div", {
72
+ className: "min-w-0 flex-1",
73
+ children: [title !== void 0 && /* @__PURE__ */ jsx(Drawer.Title, {
74
+ className: "m-0 text-sm font-semibold wrap-break-word",
75
+ children: title
76
+ }), description !== void 0 && /* @__PURE__ */ jsx(Drawer.Description, {
77
+ className: "mt-0.5 text-xs text-(--theme-muted-text-color)",
78
+ children: description
79
+ })]
80
+ }),
81
+ closeButton && /* @__PURE__ */ jsx(Drawer.Close, { render: /* @__PURE__ */ jsx(IconButton, {
82
+ icon: /* @__PURE__ */ jsx(CloseIcon, {}),
83
+ label: common.close,
84
+ className: "-my-1 -me-1.5"
85
+ }) })
86
+ ]
78
87
  }),
79
88
  /* @__PURE__ */ jsx("div", {
80
89
  className: cx("relative flex min-h-0 flex-1 flex-col overflow-y-auto", padded && "p-4", className),
@@ -26,10 +26,11 @@ export interface FieldProps {
26
26
  /** Beside the label and outside it, so it stays out of the control's name, such as a count. For
27
27
  * help text, use `help`. */
28
28
  hint?: ReactNode;
29
- /** More about the field, behind a ? beside the label that opens on hover or press. It is read
30
- * with the control as part of its description, so the ? stays out of the Tab order, and it shows
31
- * under the control while the control has keyboard focus. `[text](url)` links and line breaks
32
- * show as such. */
29
+ /** More about the field, behind a ? beside the label. It is read with the control as part of
30
+ * its description, so the ? is hidden from assistive technology and Tab passes over it. It
31
+ * opens on hover or press of the ?, and while the control has keyboard focus, until Escape.
32
+ * `[text](url)` links and line breaks show as such; a link keeps the ? in the Tab order, as
33
+ * the keyboard's way to it. */
33
34
  help?: string | undefined;
34
35
  /** Buttons at the far end of the label's row that act on the field, such as one that
35
36
  * switches its control to a typed expression. */
@@ -1,10 +1,11 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { EyeIcon, EyeOffIcon, WarningIcon } from "../icons.js";
3
3
  import { useStrings } from "./Provider.js";
4
+ import { focusCameFromKeyboard, trackFocusModality } from "./focus.js";
4
5
  import { InfoTip, formatInfoTipContent } from "./InfoTip.js";
5
6
  import { RequiredMark } from "./RequiredMark.js";
6
7
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
7
- import { createContext, use, useId } from "react";
8
+ import { createContext, use, useEffect, useId, useState } from "react";
8
9
  import { Field } from "@base-ui/react/field";
9
10
  import { Fieldset } from "@base-ui/react/fieldset";
10
11
  //#region src/components/Field.tsx
@@ -81,10 +82,31 @@ function Field$1({ label, hint, help, actions, description, error, invalid, requ
81
82
  const inline = layout === "inline";
82
83
  const inRow = use(FieldRowContext) && !inline && !group;
83
84
  const labelSize = labelSizes[size];
85
+ const [helpOpen, setHelpOpen] = useState(false);
86
+ useEffect(() => {
87
+ if (help) trackFocusModality();
88
+ }, [help]);
84
89
  const helpTip = help && /* @__PURE__ */ jsx(InfoTip, {
85
90
  text: help,
86
- tabIndex: -1
91
+ describesControl: true,
92
+ open: Boolean(helpOpen),
93
+ onOpenChange: (open) => setHelpOpen(open && "pointer"),
94
+ keepSide: helpOpen === "focus"
87
95
  });
96
+ const helpFocus = help ? {
97
+ onFocus: () => {
98
+ if (focusCameFromKeyboard()) setHelpOpen("focus");
99
+ },
100
+ onBlur: (event) => {
101
+ if (!event.currentTarget.contains(event.relatedTarget)) setHelpOpen(false);
102
+ },
103
+ onKeyDown: (event) => {
104
+ if (event.key === "Escape" && helpOpen) {
105
+ event.stopPropagation();
106
+ setHelpOpen(false);
107
+ }
108
+ }
109
+ } : {};
88
110
  const labelBox = cx(labelClassName, labelSize.text, inline && cx(labelSize.inlineHeight, "shrink-0", compact ? "mr-2 w-fit" : "w-52 pr-3"), inRow && "row-start-1 self-end");
89
111
  const labelContent = /* @__PURE__ */ jsxs(Fragment, { children: [
90
112
  /* @__PURE__ */ jsxs("span", { children: [label, required && /* @__PURE__ */ jsx(RequiredMark, {})] }),
@@ -98,6 +120,7 @@ function Field$1({ label, hint, help, actions, description, error, invalid, requ
98
120
  const placed = typeof children === "function" ? children(control) : children;
99
121
  const body = /* @__PURE__ */ jsxs("div", {
100
122
  className: cx("flex w-full min-w-0 flex-col gap-1", inRow && "row-start-2"),
123
+ ...helpFocus,
101
124
  children: [
102
125
  /* @__PURE__ */ jsx(FieldRowContext, {
103
126
  value: false,
@@ -116,7 +139,7 @@ function Field$1({ label, hint, help, actions, description, error, invalid, requ
116
139
  }),
117
140
  help && /* @__PURE__ */ jsx(Field.Description, {
118
141
  id: helpId,
119
- className: "hidden text-xs leading-relaxed text-(--theme-muted-text-color) group-has-[:focus-visible]/field:block [&_a]:underline",
142
+ hidden: true,
120
143
  children: formatInfoTipContent(help)
121
144
  }),
122
145
  error && /* @__PURE__ */ jsxs(Field.Error, {
@@ -131,7 +154,7 @@ function Field$1({ label, hint, help, actions, description, error, invalid, requ
131
154
  })
132
155
  ]
133
156
  });
134
- const frame = inRow ? cx("group/field contents", className) : cx("group/field flex w-full", inline ? "items-start" : cx("flex-col", labelSize.stackGap), className);
157
+ const frame = inRow ? cx("contents", className) : cx("flex w-full", inline ? "items-start" : cx("flex-col", labelSize.stackGap), className);
135
158
  if (group) return /* @__PURE__ */ jsx(Field.Root, {
136
159
  invalid: isInvalid,
137
160
  disabled,
@@ -8,11 +8,19 @@ interface InfoTipProps {
8
8
  className?: string | undefined;
9
9
  /** Replaces the info icon as the trigger, such as a status label the text explains. */
10
10
  children?: ReactNode;
11
- /** -1 takes the trigger out of the Tab order, for help that is also read another way, such as
12
- * a Field's, which its control is described by. */
13
- tabIndex?: number | undefined;
11
+ /** The text is also a control's description, which assistive technology reads with the
12
+ * control, so the ? is hidden from it and skipped by Tab rather than offering the text twice.
13
+ * Text with a link keeps the ? reachable, as the only way to the link from the keyboard. */
14
+ describesControl?: boolean | undefined;
15
+ /** Opens it from outside as well as on hover and press, such as while the described control
16
+ * has keyboard focus. Pair it with `onOpenChange`. */
17
+ open?: boolean | undefined;
18
+ onOpenChange?: ((open: boolean) => void) | undefined;
19
+ /** Stays on `side` rather than flipping where it doesn't fit, as while the control it
20
+ * describes is typed in, which a flipped tip would cover. */
21
+ keepSide?: boolean | undefined;
14
22
  }
15
23
  /** Extra detail behind an info trigger. A popover that also opens on hover, so
16
24
  * touch and screen reader users can reach it and its links. */
17
- export declare function InfoTip({ text, side, className, children, tabIndex }: InfoTipProps): import("react").JSX.Element | null;
25
+ export declare function InfoTip({ text, side, className, children, describesControl, open, onOpenChange, keepSide, }: InfoTipProps): import("react").JSX.Element | null;
18
26
  export {};
@@ -28,22 +28,32 @@ function formatInfoTipContent(content) {
28
28
  if (!content) return null;
29
29
  return keyedByContent(content.split(/<br\s*\/?>|\n/i), (l) => l).map(({ key, item: line }, idx) => /* @__PURE__ */ jsxs(Fragment$1, { children: [idx > 0 && /* @__PURE__ */ jsx("br", {}), formatLine(line)] }, key));
30
30
  }
31
+ var hasLink = (content) => new RegExp(linkRegex.source).test(content);
31
32
  /** Extra detail behind an info trigger. A popover that also opens on hover, so
32
33
  * touch and screen reader users can reach it and its links. */
33
- function InfoTip({ text, side = "top", className, children, tabIndex }) {
34
+ function InfoTip({ text, side = "top", className, children, describesControl = false, open, onOpenChange, keepSide = false }) {
34
35
  const t = useStrings().common;
35
36
  if (!text) return children ? /* @__PURE__ */ jsx("span", {
36
37
  className,
37
38
  children
38
39
  }) : null;
40
+ const hidden = describesControl && !hasLink(text);
39
41
  return /* @__PURE__ */ jsx(Popover, {
40
42
  variant: "tip",
41
43
  openOnHover: true,
42
44
  side,
45
+ keepSide,
46
+ ...open === void 0 ? {} : { open },
47
+ ...onOpenChange ? { onOpenChange } : {},
48
+ ...describesControl && { initialFocus: "none" },
49
+ ...hidden && { finalFocus: false },
43
50
  trigger: /* @__PURE__ */ jsx("button", {
44
51
  type: "button",
45
- tabIndex,
46
- "aria-label": children ? void 0 : t.moreInfo,
52
+ ...hidden && {
53
+ tabIndex: -1,
54
+ "aria-hidden": true
55
+ },
56
+ "aria-label": children || hidden ? void 0 : t.moreInfo,
47
57
  className: cx("inline-flex items-center cursor-help", !children && "tooltip", className),
48
58
  children: children ?? /* @__PURE__ */ jsx(HelpIcon, { "aria-hidden": "true" })
49
59
  }),
@@ -1,2 +1,3 @@
1
+ import 'katex/dist/katex.min.css';
1
2
  import type { MarkdownProps } from './Markdown.js';
2
3
  export default function MarkdownImpl({ children, isStreaming, controls, components, }: MarkdownProps): import("react").JSX.Element;
@@ -7,6 +7,7 @@ import { jsx } from "react/jsx-runtime";
7
7
  import { useEffect, useState } from "react";
8
8
  import { code } from "@streamdown/code";
9
9
  import { math } from "@streamdown/math";
10
+ import "katex/dist/katex.min.css";
10
11
  import { Streamdown, defaultRehypePlugins } from "streamdown";
11
12
  //#region src/components/MarkdownImpl.tsx
12
13
  var basePlugins = {
@@ -16,7 +16,7 @@ function MenuPopup({ children, className, side = "bottom", align = "start", anch
16
16
  sideOffset: 4,
17
17
  className: positionerClassName,
18
18
  children: /* @__PURE__ */ jsx(Menu.Popup, {
19
- className: cx("min-w-44 max-h-(--available-height) overflow-y-auto py-1", panelClassName, className),
19
+ className: cx("min-w-44 max-h-(--available-height) overflow-y-auto", "p-1", panelClassName, className),
20
20
  children
21
21
  })
22
22
  })
@@ -41,7 +41,7 @@ function MenuRadioItem({ className, children, ...props }) {
41
41
  return /* @__PURE__ */ jsxs(Menu.RadioItem, {
42
42
  closeOnClick: true,
43
43
  ...props,
44
- className: cx(itemClassName, "pl-2", className),
44
+ className: cx(itemClassName, "pl-1", className),
45
45
  children: [/* @__PURE__ */ jsx("span", {
46
46
  className: "flex w-3.5 shrink-0 justify-center",
47
47
  children: /* @__PURE__ */ jsx(Menu.RadioItemIndicator, { children: /* @__PURE__ */ jsx(CheckIcon, { className: "size-3" }) })
@@ -72,11 +72,11 @@ function MenuSubmenuTrigger({ className, children, ...props }) {
72
72
  });
73
73
  }
74
74
  function MenuSeparator({ className }) {
75
- return /* @__PURE__ */ jsx(Menu.Separator, { className: cx("my-1 h-px bg-(--theme-border)", className) });
75
+ return /* @__PURE__ */ jsx(Menu.Separator, { className: cx("-mx-1 my-1 h-px bg-(--theme-border)", className) });
76
76
  }
77
77
  function MenuGroupLabel({ children }) {
78
78
  return /* @__PURE__ */ jsx(Menu.GroupLabel, {
79
- className: "px-3 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wide text-(--theme-muted-text-color)",
79
+ className: "px-2 pt-2 pb-1 text-[10px] font-semibold uppercase tracking-wide text-(--theme-muted-text-color)",
80
80
  children
81
81
  });
82
82
  }
@@ -34,7 +34,7 @@ function MenuEntries({ entries, search, query = "" }) {
34
34
  const shown = needle ? searchMatches(entries, (a) => matches(needle, a.label, a.keywords)) : entries;
35
35
  return /* @__PURE__ */ jsxs(Fragment, { children: [
36
36
  /* @__PURE__ */ jsx("div", {
37
- className: "sticky top-0 z-[1] bg-(--theme-app-bg) px-2 pt-0.5 pb-1",
37
+ className: "sticky top-0 z-[1] bg-(--theme-app-bg) px-1 pt-0.5 pb-1",
38
38
  children: /* @__PURE__ */ jsx(Menu.Input, {
39
39
  "aria-label": search.placeholder,
40
40
  placeholder: search.placeholder,
@@ -0,0 +1,7 @@
1
+ import type { ComboboxItem } from './comboboxItems.js';
2
+ /** An option's description under its label. An `accessibleDescription` is read in its place,
3
+ * so the shown one is then hidden from assistive technology rather than read twice. */
4
+ export declare function OptionDescription({ id, item, }: {
5
+ id: string;
6
+ item: Pick<ComboboxItem<unknown>, 'description' | 'accessibleDescription'>;
7
+ }): import("react").JSX.Element;
@@ -0,0 +1,19 @@
1
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
2
+ //#region src/components/OptionDescription.tsx
3
+ /** An option's description under its label. An `accessibleDescription` is read in its place,
4
+ * so the shown one is then hidden from assistive technology rather than read twice. */
5
+ function OptionDescription({ id, item }) {
6
+ const spoken = item.accessibleDescription;
7
+ return /* @__PURE__ */ jsxs(Fragment, { children: [item.description && /* @__PURE__ */ jsx("span", {
8
+ id: spoken ? void 0 : id,
9
+ "aria-hidden": spoken ? true : void 0,
10
+ className: "text-xs leading-snug text-(--theme-muted-text-color)",
11
+ children: item.description
12
+ }), spoken && /* @__PURE__ */ jsx("span", {
13
+ id,
14
+ hidden: true,
15
+ children: spoken
16
+ })] });
17
+ }
18
+ //#endregion
19
+ export { OptionDescription };
@@ -33,6 +33,9 @@ export interface PopoverOptions extends Omit<BasePopover.Popup.Props, 'className
33
33
  * a list read before one item in it is picked, or nowhere, for a popover that
34
34
  * offers an action on what the reader is doing, such as copying a selection. */
35
35
  initialFocus?: 'first-control' | 'popup' | 'none';
36
+ /** Stays on `side` where it doesn't fit rather than flipping to the other, for a popup that
37
+ * must not cover what is there, such as the control a tip describes while it is typed in. */
38
+ keepSide?: boolean | undefined;
36
39
  /** `panel` holds controls or a list; `tip` is a tooltip-sized note with an arrow. */
37
40
  variant?: 'panel' | 'tip';
38
41
  /** On the popup: its width, padding or text size. */
@@ -43,5 +46,5 @@ export interface PopoverOptions extends Omit<BasePopover.Popup.Props, 'className
43
46
  * flips and shifts to stay on screen, closes on Escape or a press outside, and
44
47
  * returns focus to the trigger. Use a Menu for a list of actions, and a
45
48
  * HoverCard for a preview a link already leads to. */
46
- export declare function Popover({ trigger, at, title, open, defaultOpen, onOpenChange, side, align, openOnHover, initialFocus, variant, className, children, ...popupProps }: PopoverProps): import("react").JSX.Element;
49
+ export declare function Popover({ trigger, at, title, open, defaultOpen, onOpenChange, side, align, openOnHover, initialFocus, keepSide, variant, className, children, ...popupProps }: PopoverProps): import("react").JSX.Element;
47
50
  export {};
@@ -16,7 +16,7 @@ function useClosePopover() {
16
16
  * flips and shifts to stay on screen, closes on Escape or a press outside, and
17
17
  * returns focus to the trigger. Use a Menu for a list of actions, and a
18
18
  * HoverCard for a preview a link already leads to. */
19
- function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side = "bottom", align = "center", openOnHover = false, initialFocus = "first-control", variant = "panel", className, children, ...popupProps }) {
19
+ function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side = "bottom", align = "center", openOnHover = false, initialFocus = "first-control", keepSide = false, variant = "panel", className, children, ...popupProps }) {
20
20
  const tip = variant === "tip";
21
21
  const popup = useRef(null);
22
22
  const actions = useRef(null);
@@ -38,6 +38,7 @@ function Popover$1({ trigger, at, title, open, defaultOpen, onOpenChange, side =
38
38
  align,
39
39
  sideOffset: tip ? 8 : 6,
40
40
  collisionPadding: 8,
41
+ ...keepSide && { collisionAvoidance: { side: "none" } },
41
42
  ...at ? { anchor: toPositionerAnchor(at) } : {},
42
43
  className: positionerClassName,
43
44
  children: /* @__PURE__ */ jsxs(Popover.Popup, {