@parallelworks/ui 0.83.3 → 0.84.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.
Files changed (56) hide show
  1. package/dist/ai/adapter/openai/createOpenAIStream.js +1 -1
  2. package/dist/ai/adapter/openai/sse.d.ts +3 -2
  3. package/dist/ai/adapter/openai/sse.js +4 -2
  4. package/dist/ai/adapter/types.d.ts +2 -1
  5. package/dist/ai/agent/index.d.ts +1 -1
  6. package/dist/ai/agent/toolfmt.d.ts +1 -0
  7. package/dist/ai/agent/toolfmt.js +6 -2
  8. package/dist/ai/ai.css +14 -0
  9. package/dist/ai/components/ChatActivity.js +6 -4
  10. package/dist/ai/components/ChatMessage.js +42 -30
  11. package/dist/ai/components/ChatMessageList.js +17 -9
  12. package/dist/ai/components/agent/AgentMessageParts.d.ts +1 -1
  13. package/dist/ai/components/agent/AgentMessageParts.js +77 -23
  14. package/dist/ai/core/ChatProvider.d.ts +3 -1
  15. package/dist/ai/core/ChatProvider.js +27 -6
  16. package/dist/ai/core/chatReducer.d.ts +6 -1
  17. package/dist/ai/core/chatReducer.js +18 -1
  18. package/dist/ai/core/config.d.ts +2 -1
  19. package/dist/ai/core/parts.d.ts +7 -4
  20. package/dist/ai/core/parts.js +23 -2
  21. package/dist/ai/core/useSSEStream.d.ts +8 -0
  22. package/dist/ai/core/useSSEStream.js +80 -22
  23. package/dist/ai/index.js +2 -2
  24. package/dist/ai/types.d.ts +14 -0
  25. package/dist/components/Avatar.d.ts +4 -2
  26. package/dist/components/Avatar.js +2 -1
  27. package/dist/components/Kbd.d.ts +5 -2
  28. package/dist/components/Kbd.js +4 -1
  29. package/dist/components/LetterBadge.d.ts +7 -3
  30. package/dist/components/LetterBadge.js +2 -3
  31. package/dist/components/Loader.d.ts +6 -4
  32. package/dist/components/Loader.js +53 -1
  33. package/dist/components/MarkdownImpl.d.ts +3 -0
  34. package/dist/components/MarkdownImpl.js +16 -2
  35. package/dist/components/RefreshButton.d.ts +3 -2
  36. package/dist/components/RefreshButton.js +25 -14
  37. package/dist/components/RunStatusIcon.d.ts +5 -2
  38. package/dist/components/RunStatusIcon.js +2 -1
  39. package/dist/components/StatusBadge.d.ts +17 -11
  40. package/dist/components/StatusBadge.js +10 -5
  41. package/dist/components/Table.d.ts +11 -1
  42. package/dist/components/Table.js +15 -4
  43. package/dist/components/Tabs.js +1 -1
  44. package/dist/components/Tag.d.ts +5 -3
  45. package/dist/components/Tag.js +7 -1
  46. package/dist/components/relativeTime.d.ts +12 -9
  47. package/dist/components/relativeTime.js +4 -2
  48. package/dist/components/stickyTop.d.ts +9 -0
  49. package/dist/components/stickyTop.js +10 -1
  50. package/dist/graph/DependencyGraph.js +1 -1
  51. package/dist/index.d.ts +3 -3
  52. package/dist/list/ActionToolbar.js +1 -1
  53. package/dist/list/ListPage.js +32 -14
  54. package/dist/list/ListTable.js +13 -4
  55. package/dist/styles.css +1 -1
  56. package/package.json +1 -1
@@ -1,7 +1,9 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { RunningIcon } from "../icons.js";
3
+ import { pinnedAbove, scrollContainer } from "./stickyTop.js";
3
4
  import { useStrings } from "./Provider.js";
4
- import { jsx, jsxs } from "react/jsx-runtime";
5
+ import { useLayoutEffect, useRef, useState } from "react";
6
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
5
7
  //#region src/components/Loader.tsx
6
8
  var RIPPLE_STAGGER_S = .22;
7
9
  var LOGO_RINGS = [
@@ -19,6 +21,10 @@ var fillClasses = {
19
21
  * known, such as rows of a list, sketch it with `Skeleton` instead. */
20
22
  function Loader({ fill, ...props }) {
21
23
  if (!fill) return /* @__PURE__ */ jsx(LoaderMark, { ...props });
24
+ if (fill === "content") return /* @__PURE__ */ jsx(ContentArea, { children: /* @__PURE__ */ jsx(LoaderMark, {
25
+ ...props,
26
+ size: props.size ?? 48
27
+ }) });
22
28
  return /* @__PURE__ */ jsx("div", {
23
29
  className: fillClasses[fill],
24
30
  children: /* @__PURE__ */ jsx(LoaderMark, {
@@ -68,5 +74,51 @@ function LoaderMark({ size = 48, label, variant = "spinner", inline = false, col
68
74
  })]
69
75
  });
70
76
  }
77
+ /** Centers its content in the box of the scrolling region it sits in, which in an app with a
78
+ * sidebar is its content area, below what that region pins at its top and held to what of it
79
+ * the window shows; the window, where the document itself scrolls. It lays nothing out around
80
+ * it and takes no pointer. */
81
+ function ContentArea({ children }) {
82
+ const anchor = useRef(null);
83
+ const [box, setBox] = useState(null);
84
+ useLayoutEffect(() => {
85
+ const el = anchor.current;
86
+ if (!el) return;
87
+ const area = scrollContainer(el);
88
+ if (area === el.ownerDocument.documentElement) return;
89
+ const view = el.ownerDocument.defaultView ?? window;
90
+ const pinned = pinnedAbove(el);
91
+ const place = () => {
92
+ const rect = area.getBoundingClientRect();
93
+ const chrome = Math.max(0, ...pinned.chrome.map((c) => c.getBoundingClientRect().bottom));
94
+ const top = Math.max(rect.top, chrome, 0);
95
+ const bottom = Math.min(rect.bottom, view.innerHeight);
96
+ setBox({
97
+ top,
98
+ left: rect.left,
99
+ width: rect.width,
100
+ height: Math.max(bottom - top, 0)
101
+ });
102
+ };
103
+ place();
104
+ view.addEventListener("resize", place);
105
+ const observer = typeof ResizeObserver === "undefined" ? null : new ResizeObserver(place);
106
+ observer?.observe(area);
107
+ for (const chrome of pinned.chrome) observer?.observe(chrome);
108
+ return () => {
109
+ view.removeEventListener("resize", place);
110
+ observer?.disconnect();
111
+ };
112
+ }, []);
113
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
114
+ ref: anchor,
115
+ hidden: true
116
+ }), /* @__PURE__ */ jsx("div", {
117
+ "data-loader-area": "",
118
+ className: "pointer-events-none fixed inset-0 flex items-center justify-center",
119
+ style: box ?? void 0,
120
+ children
121
+ })] });
122
+ }
71
123
  //#endregion
72
124
  export { Loader };
@@ -1,3 +1,6 @@
1
1
  import 'katex/dist/katex.min.css';
2
2
  import type { MarkdownProps } from './Markdown.js';
3
+ /** While a reply streams, a table's opening rows read as stray pipes until its
4
+ * delimiter row arrives; they are held back until then. */
5
+ export declare function withoutUnfinishedTable(text: string): string;
3
6
  export default function MarkdownImpl({ children, isStreaming, controls, components, httpsImagesOnly, compact, }: MarkdownProps): import("react").JSX.Element;
@@ -112,6 +112,20 @@ function makeCode(controls, diagrams) {
112
112
  });
113
113
  };
114
114
  }
115
+ var TABLE_ROW = /^\s*\|/;
116
+ var TABLE_DELIMITER = /^\s*\|?\s*:?-{3,}:?\s*(\|\s*:?-{3,}:?\s*)*\|?\s*$/;
117
+ /** While a reply streams, a table's opening rows read as stray pipes until its
118
+ * delimiter row arrives; they are held back until then. */
119
+ function withoutUnfinishedTable(text) {
120
+ if ((text.match(/^\s*(```|~~~)/gm)?.length ?? 0) % 2 === 1) return text;
121
+ const lines = text.split("\n");
122
+ let start = lines.length;
123
+ while (start > 0 && TABLE_ROW.test(lines[start - 1] ?? "")) start--;
124
+ if (start === lines.length) return text;
125
+ const rows = lines.slice(start);
126
+ if (rows.length >= 2 && TABLE_DELIMITER.test(rows[1] ?? "")) return text;
127
+ return lines.slice(0, start).join("\n");
128
+ }
115
129
  function MarkdownImpl({ children, isStreaming, controls, components, httpsImagesOnly, compact }) {
116
130
  const needsMermaid = !!children && children.includes("```mermaid");
117
131
  const [, setMermaidReady] = useState(() => !!pluginsWithMermaid);
@@ -158,9 +172,9 @@ function MarkdownImpl({ children, isStreaming, controls, components, httpsImages
158
172
  }),
159
173
  ...components
160
174
  },
161
- children: children || ""
175
+ children: (isStreaming ? withoutUnfinishedTable(children || "") : children) || ""
162
176
  })
163
177
  });
164
178
  }
165
179
  //#endregion
166
- export { MarkdownImpl as default };
180
+ export { MarkdownImpl as default, withoutUnfinishedTable };
@@ -14,8 +14,9 @@ export interface RefreshButtonProps {
14
14
  /** A button's look, `ghost` by default; `inline` is text-sized muted text with no box, for
15
15
  * a line of details such as a header's "Updated 2 minutes ago". */
16
16
  variant?: ButtonVariant | 'inline' | undefined;
17
- /** Only the icon shows, with "Refresh" as its name and tooltip. */
18
- iconOnly?: boolean | undefined;
17
+ /** Only the icon shows, with "Refresh" as its name and tooltip. `phone` shows the label on a
18
+ * wider screen and only the icon on a phone, the label still its name for a screen reader. */
19
+ iconOnly?: boolean | 'phone' | undefined;
19
20
  className?: string | undefined;
20
21
  }
21
22
  /** Fetches again on demand, and says so while it does, out loud too. */
@@ -7,6 +7,12 @@ import { useRelativeTime } from "./relativeTime.js";
7
7
  import { useState } from "react";
8
8
  import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
9
9
  //#region src/components/RefreshButton.tsx
10
+ var phoneSquares = {
11
+ xs: "max-sm:w-6 max-sm:px-0",
12
+ sm: "max-sm:w-7 max-sm:px-0",
13
+ md: "max-sm:w-8 max-sm:px-0",
14
+ lg: "max-sm:w-10 max-sm:px-0"
15
+ };
10
16
  var inlineClasses = "inline-flex cursor-pointer items-center gap-1 rounded px-1 py-0.5 text-(--theme-muted-text-color) transition-colors hover:bg-(--theme-muted-panel-bg) hover:text-(--theme-app) focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-(--theme-focus) [&_svg]:size-3";
11
17
  /** Fetches again on demand, and says so while it does, out loud too. */
12
18
  function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md", variant = "ghost", iconOnly = false, className }) {
@@ -14,6 +20,8 @@ function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md",
14
20
  const [pending, setPending] = useState(false);
15
21
  const busy = refreshing || pending;
16
22
  const ago = useRelativeTime(updatedAt);
23
+ const phone = iconOnly === "phone";
24
+ const square = iconOnly === true;
17
25
  const refresh = () => {
18
26
  if (busy) return;
19
27
  const result = onRefresh();
@@ -27,27 +35,30 @@ function RefreshButton({ onRefresh, refreshing = false, updatedAt, size = "md",
27
35
  type: "button",
28
36
  onClick: refresh,
29
37
  "aria-busy": busy,
30
- "aria-label": iconOnly ? t.refresh : void 0,
38
+ "aria-label": square ? t.refresh : void 0,
31
39
  className: cx(variant === "inline" ? inlineClasses : buttonClasses({
32
40
  variant,
33
41
  size,
34
- iconOnly
35
- }), "group aria-busy:cursor-progress", className),
42
+ iconOnly: square
43
+ }), phone && variant !== "inline" && phoneSquares[size], "group aria-busy:cursor-progress", className),
36
44
  children: [/* @__PURE__ */ jsx(RefreshIcon, {
37
45
  "aria-hidden": "true",
38
46
  className: cx("shrink-0 motion-reduce:animate-none motion-reduce:transition-none", busy ? "animate-spin" : "transition-transform duration-300 group-hover:rotate-180")
39
- }), !iconOnly && (ago ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
40
- className: "sr-only",
41
- children: [t.refresh, ". "]
42
- }), t.updated(ago)] }) : busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
43
- className: "sr-only",
44
- children: t.refresh
45
- }), /* @__PURE__ */ jsx("span", {
46
- "aria-hidden": "true",
47
- children: t.refreshing
48
- })] }) : t.refresh)]
47
+ }), !square && /* @__PURE__ */ jsx("span", {
48
+ className: phone ? "max-sm:sr-only" : void 0,
49
+ children: ago ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
50
+ className: "sr-only",
51
+ children: [t.refresh, ". "]
52
+ }), t.updated(ago)] }) : busy ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
53
+ className: "sr-only",
54
+ children: t.refresh
55
+ }), /* @__PURE__ */ jsx("span", {
56
+ "aria-hidden": "true",
57
+ children: t.refreshing
58
+ })] }) : t.refresh
59
+ })]
49
60
  });
50
- return /* @__PURE__ */ jsxs(Fragment$1, { children: [iconOnly ? /* @__PURE__ */ jsx(Tooltip, {
61
+ return /* @__PURE__ */ jsxs(Fragment$1, { children: [square ? /* @__PURE__ */ jsx(Tooltip, {
51
62
  content: t.refresh,
52
63
  children: button
53
64
  }) : button, /* @__PURE__ */ jsx("span", {
@@ -1,3 +1,4 @@
1
+ import type { ComponentPropsWithRef } from 'react';
1
2
  import type { RunStatus } from '../engine.js';
2
3
  import { type Tone } from './tone.js';
3
4
  /** A status the icon shows: a run's, or one a host maps onto it, such as
@@ -6,7 +7,9 @@ export type RunStatusIconStatus = RunStatus | 'pending' | 'warning';
6
7
  /** The tone a run status reads in, so a badge that says the status in words matches the
7
8
  * icon: `<StatusBadge variant={runStatusTone(status)}>`. */
8
9
  export declare function runStatusTone(status: RunStatusIconStatus): Tone;
9
- export interface RunStatusIconProps {
10
+ /** Any other span attribute and the ref reach the icon's element, so it can be a Tooltip's or a
11
+ * popover's direct child. */
12
+ export interface RunStatusIconProps extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'className'> {
10
13
  status: RunStatusIconStatus;
11
14
  /** Names the icon in place of `strings.dag.statusLabel`, for a host's own status
12
15
  * shown in a run's look, such as "Provisioning" as running. */
@@ -18,4 +21,4 @@ export interface RunStatusIconProps {
18
21
  /** A run's, job's or step's status as an icon in its tone, beside the name of what ran. It
19
22
  * is named by `label`, or else `strings.dag.statusLabel`. To say the status in words, use
20
23
  * `StatusBadge` with `runStatusTone`. */
21
- export declare function RunStatusIcon({ status, label: ownLabel, tooltip, className, }: RunStatusIconProps): import("react").JSX.Element;
24
+ export declare function RunStatusIcon({ status, label: ownLabel, tooltip, className, ...rest }: RunStatusIconProps): import("react").JSX.Element;
@@ -71,12 +71,13 @@ function runStatusTone(status) {
71
71
  /** A run's, job's or step's status as an icon in its tone, beside the name of what ran. It
72
72
  * is named by `label`, or else `strings.dag.statusLabel`. To say the status in words, use
73
73
  * `StatusBadge` with `runStatusTone`. */
74
- function RunStatusIcon({ status, label: ownLabel, tooltip = false, className }) {
74
+ function RunStatusIcon({ status, label: ownLabel, tooltip = false, className, ...rest }) {
75
75
  const { dag } = useStrings();
76
76
  const look = looks[status ?? ""];
77
77
  const Icon = look.icon;
78
78
  const label = ownLabel ?? dag.statusLabel(status ?? "");
79
79
  const icon = /* @__PURE__ */ jsx("span", {
80
+ ...rest,
80
81
  ...label ? {
81
82
  role: "img",
82
83
  "aria-label": label
@@ -1,4 +1,4 @@
1
- import type { ReactElement, ReactNode } from 'react';
1
+ import type { ComponentPropsWithRef, ReactElement, ReactNode } from 'react';
2
2
  import { type Tone } from './tone.js';
3
3
  /** A badge's tone: a status, or the theme's accent for something set apart that
4
4
  * isn't a status, such as "New". */
@@ -13,7 +13,9 @@ declare const sizeClasses: {
13
13
  readonly md: 'gap-1.5 py-0.5 text-[11px] leading-4';
14
14
  };
15
15
  export type BadgeSize = keyof typeof sizeClasses;
16
- export interface StatusBadgeProps {
16
+ /** Any other span attribute and the ref reach its element, so it can be a Tooltip's or a popover's
17
+ * direct child. */
18
+ export interface StatusBadgeProps extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'className'> {
17
19
  /** Badge content */
18
20
  children: ReactNode;
19
21
  /** Visual variant */
@@ -41,8 +43,10 @@ export interface StatusBadgeProps {
41
43
  * dot rather than inside a badge. */
42
44
  export declare function statusTextClass(variant: BadgeVariant): string;
43
45
  /** A short status or label in a tone, such as "Running" or "Preview". */
44
- export declare function StatusBadge({ children, variant, emphasis, shape, caps, truncate, pulse, dot, size, render, className, }: StatusBadgeProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
45
- export interface CountBadgeProps {
46
+ export declare function StatusBadge({ children, variant, emphasis, shape, caps, truncate, pulse, dot, size, render, className, ref, ...rest }: StatusBadgeProps): ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
47
+ /** Any other span attribute and the ref reach its element, so it can be a Tooltip's or a popover's
48
+ * direct child. */
49
+ export interface CountBadgeProps extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'className'> {
46
50
  count: number;
47
51
  /** Above it, the count reads as `max` and a plus, such as "99+". */
48
52
  max?: number | undefined;
@@ -64,12 +68,14 @@ export interface CountBadgeProps {
64
68
  }
65
69
  /** A number beside a link or tab, such as unread messages or items waiting,
66
70
  * capped at `max` so a big count doesn't widen what it sits in. */
67
- export declare function CountBadge({ count, max, variant, emphasis, size, label, showZero, locale, className, }: CountBadgeProps): import("react").JSX.Element | null;
68
- interface StatusDotProps {
69
- variant?: BadgeVariant;
70
- pulse?: boolean;
71
- size?: number;
72
- className?: string;
71
+ export declare function CountBadge({ count, max, variant, emphasis, size, label, showZero, locale, className, ...rest }: CountBadgeProps): import("react").JSX.Element | null;
72
+ /** Any other span attribute and the ref reach its element, so it can be a Tooltip's or a popover's
73
+ * direct child. */
74
+ export interface StatusDotProps extends Omit<ComponentPropsWithRef<'span'>, 'children' | 'className'> {
75
+ variant?: BadgeVariant | undefined;
76
+ pulse?: boolean | undefined;
77
+ size?: number | undefined;
78
+ className?: string | undefined;
73
79
  }
74
- export declare function StatusDot({ variant, pulse, size, className, }: StatusDotProps): import("react").JSX.Element;
80
+ export declare function StatusDot({ variant, pulse, size, className, style, ...rest }: StatusDotProps): import("react").JSX.Element;
75
81
  export {};
@@ -3,6 +3,7 @@ import { toneFill, toneSurface, toneText } from "./tone.js";
3
3
  import { useStrings } from "./Provider.js";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
5
  import { useRender } from "@base-ui/react/use-render";
6
+ import { mergeProps } from "@base-ui/react/merge-props";
6
7
  //#region src/components/StatusBadge.tsx
7
8
  var emphasisClasses = {
8
9
  soft: {
@@ -56,11 +57,12 @@ function statusTextClass(variant) {
56
57
  return variant === "accent" ? "text-(--theme-link)" : toneText[variant];
57
58
  }
58
59
  /** A short status or label in a tone, such as "Running" or "Preview". */
59
- function StatusBadge({ children, variant = "default", emphasis = "soft", shape = "pill", caps = false, truncate = false, pulse = false, dot = false, size = "sm", render, className }) {
60
+ function StatusBadge({ children, variant = "default", emphasis = "soft", shape = "pill", caps = false, truncate = false, pulse = false, dot = false, size = "sm", render, className, ref, ...rest }) {
60
61
  const indicator = emphasis === "solid" ? "bg-current" : dotFill[variant];
61
62
  return useRender({
62
63
  render: render ?? /* @__PURE__ */ jsx("span", {}),
63
- props: {
64
+ ref,
65
+ props: mergeProps(rest, {
64
66
  className: cx("inline-flex items-center border font-medium no-underline", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-(--theme-focus)", sizeClasses[size], padClasses[shape][size], shapeClasses[shape], caps && "uppercase tracking-wider", truncate && "min-w-0 max-w-full", emphasisClasses[emphasis][variant], className),
65
67
  children: /* @__PURE__ */ jsxs(Fragment, { children: [
66
68
  pulse && /* @__PURE__ */ jsxs("span", {
@@ -73,7 +75,7 @@ function StatusBadge({ children, variant = "default", emphasis = "soft", shape =
73
75
  children
74
76
  }) : children
75
77
  ] })
76
- }
78
+ })
77
79
  });
78
80
  }
79
81
  var countSizeClasses = {
@@ -82,12 +84,13 @@ var countSizeClasses = {
82
84
  };
83
85
  /** A number beside a link or tab, such as unread messages or items waiting,
84
86
  * capped at `max` so a big count doesn't widen what it sits in. */
85
- function CountBadge({ count, max = 99, variant = "accent", emphasis = "solid", size = "md", label, showZero = false, locale, className }) {
87
+ function CountBadge({ count, max = 99, variant = "accent", emphasis = "solid", size = "md", label, showZero = false, locale, className, ...rest }) {
86
88
  const { common } = useStrings();
87
89
  if (count <= 0 && !showZero) return null;
88
90
  const number = new Intl.NumberFormat(locale);
89
91
  const text = count > max ? common.countOverflow(number.format(max)) : number.format(count);
90
92
  return /* @__PURE__ */ jsxs("span", {
93
+ ...rest,
91
94
  "data-count": count,
92
95
  className: cx("inline-flex shrink-0 items-center justify-center rounded-full font-semibold tabular-nums", countSizeClasses[size], emphasisClasses[emphasis][variant], className),
93
96
  children: [text, label && /* @__PURE__ */ jsxs("span", {
@@ -96,10 +99,12 @@ function CountBadge({ count, max = 99, variant = "accent", emphasis = "solid", s
96
99
  })]
97
100
  });
98
101
  }
99
- function StatusDot({ variant = "default", pulse = false, size = 8, className }) {
102
+ function StatusDot({ variant = "default", pulse = false, size = 8, className, style, ...rest }) {
100
103
  return /* @__PURE__ */ jsxs("span", {
104
+ ...rest,
101
105
  className: cx("relative inline-flex", className),
102
106
  style: {
107
+ ...style,
103
108
  width: size,
104
109
  height: size
105
110
  },
@@ -5,11 +5,21 @@ export type SortDirection = 'ascending' | 'descending' | 'none';
5
5
  * height, such as a key and value list or a dashboard card's runs; `dense`
6
6
  * sets small type in tight rows, for a panel of figures. */
7
7
  export type TableDensity = 'comfortable' | 'compact' | 'dense';
8
+ /** What a list row asks of the cell in a place, through whatever component of the host's renders
9
+ * it: its column's classes, content to show after its own, or that it shows stacked, under the
10
+ * first cell's, as a line in place of a cell. */
11
+ export interface CellPlacement {
12
+ className?: string | undefined;
13
+ after?: React.ReactNode;
14
+ stacked?: boolean | undefined;
15
+ }
16
+ /** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
17
+ export declare const CellPlacementContext: React.Context<CellPlacement | null>;
8
18
  interface ItemProps extends React.TdHTMLAttributes<HTMLTableCellElement> {
9
19
  /** Lets the text wrap; by default a cell keeps to one line. */
10
20
  wrap?: boolean;
11
21
  }
12
- declare function Item({ wrap, children, className, ...tdProps }: ItemProps): React.JSX.Element;
22
+ declare function Item({ wrap, children, className, ...tdProps }: ItemProps): React.JSX.Element | null;
13
23
  interface HeaderProps {
14
24
  children?: React.ReactNode;
15
25
  className?: string | undefined;
@@ -1,7 +1,7 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { SortDownIcon, SortUnsortedIcon, SortUpIcon } from "../icons.js";
3
- import { Loader } from "./Loader.js";
4
3
  import { useStickyTop } from "./stickyTop.js";
4
+ import { Loader } from "./Loader.js";
5
5
  import React, { createContext, useContext, useState } from "react";
6
6
  import { jsx, jsxs } from "react/jsx-runtime";
7
7
  //#region src/components/Table.tsx
@@ -23,12 +23,23 @@ var headerY = {
23
23
  dense: "py-1"
24
24
  };
25
25
  var cellX = "px-3";
26
+ /** Set by a list row around each of its cells; a `Table.Item` takes what it asks. */
27
+ var CellPlacementContext = createContext(null);
26
28
  function Item({ wrap = false, children, className, ...tdProps }) {
27
29
  const density = useContext(DensityContext);
30
+ const placed = useContext(CellPlacementContext);
31
+ if (placed?.stacked) return React.Children.toArray(children).length > 0 ? /* @__PURE__ */ jsx("div", {
32
+ className: "mt-1 flex min-w-0 items-center @[36rem]:hidden",
33
+ "data-stacked": true,
34
+ children
35
+ }) : null;
28
36
  return /* @__PURE__ */ jsx("td", {
29
37
  ...tdProps,
30
- className: cx(cellX, cellY[density], wrap ? wrapY[density] : "whitespace-nowrap", className),
31
- children
38
+ className: cx(cellX, cellY[density], wrap ? wrapY[density] : "whitespace-nowrap", className, placed?.className),
39
+ children: /* @__PURE__ */ jsxs(CellPlacementContext, {
40
+ value: null,
41
+ children: [children, placed?.after]
42
+ })
32
43
  });
33
44
  }
34
45
  var sortIcons = {
@@ -107,4 +118,4 @@ function Table({ density = "comfortable", columns, caps = true, flush = false, c
107
118
  Table.Header = Header;
108
119
  Table.Item = Item;
109
120
  //#endregion
110
- export { Table };
121
+ export { CellPlacementContext, Table };
@@ -1,8 +1,8 @@
1
1
  import { cx } from "../cx.js";
2
+ import { useStickyTop } from "./stickyTop.js";
2
3
  import { Tooltip } from "./Tooltip.js";
3
4
  import { AppLink } from "./AppLink.js";
4
5
  import { SegmentContent, segmentClassName, segmentGroupClassName } from "./segmented.js";
5
- import { useStickyTop } from "./stickyTop.js";
6
6
  import { useState } from "react";
7
7
  import { jsx, jsxs } from "react/jsx-runtime";
8
8
  import { Tabs } from "@base-ui/react/tabs";
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from 'react';
1
+ import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
2
  export interface TagStyle {
3
3
  /** Monospace text, for a value read as code, such as a key=value label. */
4
4
  mono?: boolean | undefined;
@@ -8,7 +8,9 @@ export interface TagStyle {
8
8
  /** The one tag look: a pill with a hairline border and 11px text. Another component's chip,
9
9
  * such as a picker's chosen value, uses it to look the same. */
10
10
  export declare function tagClasses({ mono, caps }?: TagStyle): string;
11
- export interface TagProps extends TagStyle {
11
+ /** Any other attribute and the ref reach the tag's element, its link with `href` and no `onRemove`,
12
+ * so it can be a Tooltip's or a popover's direct child. */
13
+ export interface TagProps extends TagStyle, Omit<ComponentPropsWithRef<'span'>, 'children' | 'className' | 'color' | 'title'> {
12
14
  children: ReactNode;
13
15
  /** A dot before the text in this color, any CSS color, such as a label's own. */
14
16
  color?: string | undefined;
@@ -23,7 +25,7 @@ export interface TagProps extends TagStyle {
23
25
  }
24
26
  /** A tag or label on a thing: a pill with an optional color dot, a link with `href`, and an ×
25
27
  * with `onRemove`. */
26
- export declare function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className, }: TagProps): import("react").JSX.Element;
28
+ export declare function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className, ref, ...rest }: TagProps): import("react").JSX.Element;
27
29
  /** One tag of a `TagList`: a string is its own key and label. */
28
30
  export type TagItem = string | {
29
31
  key: string;
@@ -13,7 +13,7 @@ function tagClasses({ mono = false, caps = false } = {}) {
13
13
  }
14
14
  /** A tag or label on a thing: a pill with an optional color dot, a link with `href`, and an ×
15
15
  * with `onRemove`. */
16
- function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className }) {
16
+ function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps, className, ref, ...rest }) {
17
17
  const { dropdown } = useStrings();
18
18
  const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [color && /* @__PURE__ */ jsx("span", {
19
19
  "aria-hidden": "true",
@@ -29,17 +29,23 @@ function Tag({ children, color, href, onRemove, removeLabel, title, mono, caps,
29
29
  }), className);
30
30
  const hover = "transition-colors hover:bg-(--theme-hover)";
31
31
  if (!onRemove) return href ? /* @__PURE__ */ jsx(AppLink, {
32
+ ...rest,
33
+ ref,
32
34
  href,
33
35
  title,
34
36
  className: cx(classes, hover, "no-underline"),
35
37
  children: content
36
38
  }) : /* @__PURE__ */ jsx("span", {
39
+ ...rest,
40
+ ref,
37
41
  title,
38
42
  className: classes,
39
43
  children: content
40
44
  });
41
45
  const name = removeLabel ?? dropdown.removeValue(typeof children === "string" ? children : "");
42
46
  return /* @__PURE__ */ jsxs("span", {
47
+ ...rest,
48
+ ref,
43
49
  title,
44
50
  className: cx(classes, "pr-0.5"),
45
51
  children: [href ? /* @__PURE__ */ jsx(AppLink, {
@@ -1,4 +1,4 @@
1
- import { type ReactNode } from 'react';
1
+ import { type ComponentPropsWithRef, type ReactNode } from 'react';
2
2
  import { type DateTimeStyle, type TimeValue } from '../format.js';
3
3
  export type { TimeValue };
4
4
  /** Something worked out from the time, kept current by one clock that ticks
@@ -15,7 +15,8 @@ export type RelativeTimeFormat = 'long' | 'compact';
15
15
  /** How long ago, or until, `value` is, kept current as time passes. Empty for
16
16
  * a missing or invalid time. */
17
17
  export declare function useRelativeTime(value: TimeValue, format?: RelativeTimeFormat): string;
18
- export interface RelativeTimeProps {
18
+ /** Any other attribute and the ref reach the `<time>`, so it can be a popover's direct child. */
19
+ export interface RelativeTimeProps extends Omit<ComponentPropsWithRef<'time'>, 'children' | 'className' | 'dateTime'> {
19
20
  value: TimeValue;
20
21
  format?: RelativeTimeFormat | undefined;
21
22
  /** Shown in place of a missing or invalid time, such as "Never" or "—". Default nothing. */
@@ -31,17 +32,19 @@ export interface RelativeTimeProps {
31
32
  /** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
32
33
  * on while it stays on screen, with the exact time on hover. Renders the
33
34
  * `fallback`, or nothing, for a missing time. */
34
- export declare function RelativeTime({ value, format, fallback, exact, show, className, }: RelativeTimeProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
35
+ export declare function RelativeTime({ value, format, fallback, exact, show, className, ...rest }: RelativeTimeProps): string | number | bigint | boolean | import("react").JSX.Element | Iterable<ReactNode> | Promise<string | number | bigint | boolean | Iterable<ReactNode> | import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | import("react").ReactPortal | null | undefined> | null;
35
36
  /** Milliseconds from `start` to `end`, or to now while `running`, in whole
36
37
  * seconds so it changes at most once a second. Running defaults to having no
37
38
  * end; pass false for something that stopped without recording when. Null
38
39
  * without a start. */
39
40
  export declare function useElapsed(start: TimeValue, end?: TimeValue, running?: boolean): number | null;
40
- /** How long something took, or has taken so far while it runs: 45s, 2m 13s.
41
- * Renders nothing without a start. */
42
- export declare function Elapsed({ start, end, running, className, }: {
41
+ /** Any other attribute and the ref reach the `<time>`, so it can be a Tooltip's direct child. */
42
+ export interface ElapsedProps extends Omit<ComponentPropsWithRef<'time'>, 'children' | 'className' | 'dateTime'> {
43
43
  start: TimeValue;
44
44
  end?: TimeValue;
45
- running?: boolean;
46
- className?: string;
47
- }): import("react").JSX.Element | null;
45
+ running?: boolean | undefined;
46
+ className?: string | undefined;
47
+ }
48
+ /** How long something took, or has taken so far while it runs: 45s, 2m 13s.
49
+ * Renders nothing without a start. */
50
+ export declare function Elapsed({ start, end, running, className, ...rest }: ElapsedProps): import("react").JSX.Element | null;
@@ -100,7 +100,7 @@ function useRelativeTime(value, format = "long") {
100
100
  /** A time shown relative to now ("5 minutes ago", or "5m" compact), counting
101
101
  * on while it stays on screen, with the exact time on hover. Renders the
102
102
  * `fallback`, or nothing, for a missing time. */
103
- function RelativeTime({ value, format = "long", fallback = null, exact = "seconds", show = "relative", className }) {
103
+ function RelativeTime({ value, format = "long", fallback = null, exact = "seconds", show = "relative", className, ...rest }) {
104
104
  const label = useRelativeTime(value, format);
105
105
  const locale = useLocale();
106
106
  const time = toDateTime(value);
@@ -108,6 +108,7 @@ function RelativeTime({ value, format = "long", fallback = null, exact = "second
108
108
  const absolute = formatDateTime(time, exact || "seconds", locale);
109
109
  const [text, tip] = show === "absolute" ? [absolute, label] : [label, exact ? absolute : null];
110
110
  const element = /* @__PURE__ */ jsx("time", {
111
+ ...rest,
111
112
  dateTime: time.toISO() ?? void 0,
112
113
  className,
113
114
  children: text
@@ -129,11 +130,12 @@ function useElapsed(start, end, running = toDateTime(end) === null) {
129
130
  }
130
131
  /** How long something took, or has taken so far while it runs: 45s, 2m 13s.
131
132
  * Renders nothing without a start. */
132
- function Elapsed({ start, end, running, className }) {
133
+ function Elapsed({ start, end, running, className, ...rest }) {
133
134
  const elapsed = useElapsed(start, end, running);
134
135
  const locale = useLocale();
135
136
  if (elapsed === null) return null;
136
137
  return /* @__PURE__ */ jsx("time", {
138
+ ...rest,
137
139
  dateTime: Duration.fromMillis(elapsed).toISO() ?? void 0,
138
140
  className,
139
141
  children: formatDuration(elapsed, locale)
@@ -1,6 +1,15 @@
1
1
  /** Pins a bar to the top of its scroller, over what scrolls under it.
2
2
  * `useStickyTop` moves it below whatever else is pinned there. */
3
3
  export declare const stickyClasses = "sticky top-0 z-(--layer-sticky) bg-(--theme-app-bg)";
4
+ /** The box a sticky element sticks to: its nearest ancestor that scrolls, or
5
+ * the document's when none does. */
6
+ export declare function scrollContainer(el: Element): Element;
7
+ /** What a page pins at the top of `el`'s scroller over it, such as a page header and a toolbar:
8
+ * those elements, and how far below the scroller's top their bottom edge sits. */
9
+ export declare function pinnedAbove(el: Element): {
10
+ chrome: HTMLElement[];
11
+ bottom: number;
12
+ };
4
13
  /**
5
14
  * Pins a `sticky` element just below whatever its scroller already pins at
6
15
  * the top, such as a page header, a toolbar and a tab row, rather than at
@@ -47,6 +47,15 @@ function pinnedBottom(chrome) {
47
47
  const height = chrome.getBoundingClientRect().height;
48
48
  return height > 0 ? (Number.parseFloat(getComputedStyle(chrome).top) || 0) + height : 0;
49
49
  }
50
+ /** What a page pins at the top of `el`'s scroller over it, such as a page header and a toolbar:
51
+ * those elements, and how far below the scroller's top their bottom edge sits. */
52
+ function pinnedAbove(el) {
53
+ const { chrome } = chromeAbove(el, scrollContainer(el));
54
+ return {
55
+ chrome,
56
+ bottom: Math.max(0, ...chrome.map(pinnedBottom))
57
+ };
58
+ }
50
59
  /**
51
60
  * Pins a `sticky` element just below whatever its scroller already pins at
52
61
  * the top, such as a page header, a toolbar and a tab row, rather than at
@@ -95,4 +104,4 @@ function useStickyTop(el, enabled) {
95
104
  }, [el, enabled]);
96
105
  }
97
106
  //#endregion
98
- export { stickyClasses, useStickyTop };
107
+ export { pinnedAbove, scrollContainer, stickyClasses, useStickyTop };
@@ -1,5 +1,6 @@
1
1
  import { cx } from "../cx.js";
2
2
  import { ArrowLeftIcon, ArrowRightIcon, ChevronLeftIcon, ChevronRightIcon, CloseIcon, FileTextIcon, ZoomInIcon, ZoomOutIcon } from "../icons.js";
3
+ import { useStickyTop } from "../components/stickyTop.js";
3
4
  import { Loader } from "../components/Loader.js";
4
5
  import { useLocale, useRunFile, useSlots, useStrings, useWorkflowEngine } from "../components/Provider.js";
5
6
  import { Button } from "../components/Button.js";
@@ -10,7 +11,6 @@ import { DisclosurePanel } from "../components/Disclosure.js";
10
11
  import { formatDateTime } from "../format.js";
11
12
  import { Elapsed } from "../components/relativeTime.js";
12
13
  import { RunStatusIcon } from "../components/RunStatusIcon.js";
13
- import { useStickyTop } from "../components/stickyTop.js";
14
14
  import { Tabs } from "../components/Tabs.js";
15
15
  import { LogViewer } from "../logviewer/LogViewer.js";
16
16
  import { initializeValues } from "../form/lib.js";