@arcfusionz/arc-analytic-ui 0.0.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 (156) hide show
  1. package/dist/_virtual/_rolldown/runtime.js +13 -0
  2. package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
  3. package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
  4. package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
  5. package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
  6. package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
  7. package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
  8. package/dist/components/AnomalyCard/index.d.ts +4 -0
  9. package/dist/components/AnomalyCard/index.js +4 -0
  10. package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
  11. package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
  12. package/dist/components/ChartCard/ChartCard.d.ts +206 -0
  13. package/dist/components/ChartCard/ChartCard.js +718 -0
  14. package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
  15. package/dist/components/ChartCard/ChartCard.model.js +1223 -0
  16. package/dist/components/ChartCard/index.d.ts +4 -0
  17. package/dist/components/ChartCard/index.js +4 -0
  18. package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
  19. package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
  20. package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
  21. package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
  22. package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
  23. package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
  24. package/dist/components/ComparisonCard/index.d.ts +4 -0
  25. package/dist/components/ComparisonCard/index.js +4 -0
  26. package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
  27. package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
  28. package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
  29. package/dist/components/ForecastCard/ForecastCard.js +1029 -0
  30. package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
  31. package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
  32. package/dist/components/ForecastCard/index.d.ts +4 -0
  33. package/dist/components/ForecastCard/index.js +4 -0
  34. package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
  35. package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
  36. package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
  37. package/dist/components/HighlightCard/HighlightCard.js +1286 -0
  38. package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
  39. package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
  40. package/dist/components/HighlightCard/index.d.ts +4 -0
  41. package/dist/components/HighlightCard/index.js +4 -0
  42. package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
  43. package/dist/components/InsightCard/InsightCard.chart.js +471 -0
  44. package/dist/components/InsightCard/InsightCard.d.ts +161 -0
  45. package/dist/components/InsightCard/InsightCard.js +961 -0
  46. package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
  47. package/dist/components/InsightCard/InsightCard.model.js +421 -0
  48. package/dist/components/InsightCard/index.d.ts +4 -0
  49. package/dist/components/InsightCard/index.js +4 -0
  50. package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
  51. package/dist/components/MetricCard/MetricCard.chart.js +435 -0
  52. package/dist/components/MetricCard/MetricCard.d.ts +227 -0
  53. package/dist/components/MetricCard/MetricCard.js +1293 -0
  54. package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
  55. package/dist/components/MetricCard/MetricCard.model.js +627 -0
  56. package/dist/components/MetricCard/index.d.ts +4 -0
  57. package/dist/components/MetricCard/index.js +4 -0
  58. package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
  59. package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
  60. package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
  61. package/dist/components/ProfileCard/ProfileCard.js +735 -0
  62. package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
  63. package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
  64. package/dist/components/ProfileCard/index.d.ts +4 -0
  65. package/dist/components/ProfileCard/index.js +4 -0
  66. package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
  67. package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
  68. package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
  69. package/dist/components/ProgressCard/ProgressCard.js +908 -0
  70. package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
  71. package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
  72. package/dist/components/ProgressCard/index.d.ts +4 -0
  73. package/dist/components/ProgressCard/index.js +4 -0
  74. package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
  75. package/dist/components/ReportCard/ReportCard.chart.js +231 -0
  76. package/dist/components/ReportCard/ReportCard.d.ts +153 -0
  77. package/dist/components/ReportCard/ReportCard.js +1118 -0
  78. package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
  79. package/dist/components/ReportCard/ReportCard.model.js +399 -0
  80. package/dist/components/ReportCard/index.d.ts +4 -0
  81. package/dist/components/ReportCard/index.js +4 -0
  82. package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
  83. package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
  84. package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
  85. package/dist/components/SankeyCard/SankeyCard.js +788 -0
  86. package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
  87. package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
  88. package/dist/components/SankeyCard/index.d.ts +4 -0
  89. package/dist/components/SankeyCard/index.js +4 -0
  90. package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
  91. package/dist/components/StatusCard/StatusCard.chart.js +188 -0
  92. package/dist/components/StatusCard/StatusCard.d.ts +176 -0
  93. package/dist/components/StatusCard/StatusCard.js +989 -0
  94. package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
  95. package/dist/components/StatusCard/StatusCard.model.js +776 -0
  96. package/dist/components/StatusCard/index.d.ts +4 -0
  97. package/dist/components/StatusCard/index.js +4 -0
  98. package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
  99. package/dist/components/TableCard/TableCard.chart.js +192 -0
  100. package/dist/components/TableCard/TableCard.d.ts +140 -0
  101. package/dist/components/TableCard/TableCard.js +811 -0
  102. package/dist/components/TableCard/TableCard.model.d.ts +381 -0
  103. package/dist/components/TableCard/TableCard.model.js +475 -0
  104. package/dist/components/TableCard/index.d.ts +4 -0
  105. package/dist/components/TableCard/index.js +4 -0
  106. package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
  107. package/dist/components/TargetCard/TargetCard.chart.js +273 -0
  108. package/dist/components/TargetCard/TargetCard.d.ts +143 -0
  109. package/dist/components/TargetCard/TargetCard.js +910 -0
  110. package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
  111. package/dist/components/TargetCard/TargetCard.model.js +581 -0
  112. package/dist/components/TargetCard/index.d.ts +4 -0
  113. package/dist/components/TargetCard/index.js +4 -0
  114. package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
  115. package/dist/components/TreeCard/TreeCard.chart.js +178 -0
  116. package/dist/components/TreeCard/TreeCard.d.ts +182 -0
  117. package/dist/components/TreeCard/TreeCard.js +1187 -0
  118. package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
  119. package/dist/components/TreeCard/TreeCard.model.js +401 -0
  120. package/dist/components/TreeCard/index.d.ts +4 -0
  121. package/dist/components/TreeCard/index.js +4 -0
  122. package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
  123. package/dist/components/TrendCard/TrendCard.chart.js +393 -0
  124. package/dist/components/TrendCard/TrendCard.d.ts +224 -0
  125. package/dist/components/TrendCard/TrendCard.js +1053 -0
  126. package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
  127. package/dist/components/TrendCard/TrendCard.model.js +623 -0
  128. package/dist/components/TrendCard/index.d.ts +4 -0
  129. package/dist/components/TrendCard/index.js +4 -0
  130. package/dist/index.d.ts +76 -0
  131. package/dist/index.js +57 -0
  132. package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
  133. package/dist/lib/vega/ArcVegaChart.js +42 -0
  134. package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
  135. package/dist/lib/vega/ArcVegaSurface.js +38 -0
  136. package/dist/lib/vega/axisTicks.js +156 -0
  137. package/dist/lib/vega/config.d.ts +29 -0
  138. package/dist/lib/vega/config.js +113 -0
  139. package/dist/lib/vega/embed.d.ts +29 -0
  140. package/dist/lib/vega/embed.js +29 -0
  141. package/dist/lib/vega/engine.d.ts +10 -0
  142. package/dist/lib/vega/engine.js +6 -0
  143. package/dist/lib/vega/formatters.d.ts +125 -0
  144. package/dist/lib/vega/formatters.js +277 -0
  145. package/dist/lib/vega/index.d.ts +11 -0
  146. package/dist/lib/vega/load.d.ts +13 -0
  147. package/dist/lib/vega/load.js +21 -0
  148. package/dist/lib/vega/time.d.ts +45 -0
  149. package/dist/lib/vega/time.js +105 -0
  150. package/dist/lib/vega/tokens.d.ts +73 -0
  151. package/dist/lib/vega/tokens.js +137 -0
  152. package/dist/lib/vega/tooltip.d.ts +45 -0
  153. package/dist/lib/vega/tooltip.js +124 -0
  154. package/dist/lib/vega/useArcVega.d.ts +43 -0
  155. package/dist/lib/vega/useArcVega.js +121 -0
  156. package/package.json +60 -0
@@ -0,0 +1,735 @@
1
+ import { ArcVegaChart } from "../../lib/vega/ArcVegaChart.js";
2
+ import { arcEmbedOptions } from "../../lib/vega/embed.js";
3
+ import { formatDate } from "../../lib/vega/formatters.js";
4
+ import { useChartTokens } from "../../lib/vega/tokens.js";
5
+ import { ArcTooltipRows, useArcTooltipHandler } from "../../lib/vega/tooltip.js";
6
+ import { PROFILE_AXES_DATASET, PROFILE_CELLS_DATASET, PROFILE_RINGS_DATASET, buildProfilePlotSpec } from "./ProfileCard.chart.js";
7
+ import { buildProfileChartDescription, buildProfileDescription, createProfileFormatters, deriveProfileHighlights, deriveProfileState, normalizeProfile, resolveProfileLabels } from "./ProfileCard.model.js";
8
+ import { forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
9
+ import { AILoader, Badge, Box, Button, HorizontalScroll, Popover, PopoverPopup, PopoverTitle, PopoverTrigger, Skeleton, SkeletonGroup, SystemState, Table, TableBody, TableCaption, TableCell, TableContainer, TableHead, TableHeader, TableRow, Toggle, Tooltip, TooltipPopup, TooltipTrigger, cn } from "@arcfusionz/arc-primitive-ui";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/components/ProfileCard/ProfileCard.tsx
12
+ const sizeStyles = {
13
+ sm: {
14
+ padding: "sm",
15
+ gap: "gap-2",
16
+ gapPx: 8,
17
+ label: "text-xs",
18
+ body: "text-xs",
19
+ plot: 180,
20
+ groupedRow: 34,
21
+ legendPx: 24
22
+ },
23
+ md: {
24
+ padding: "md",
25
+ gap: "gap-3",
26
+ gapPx: 12,
27
+ label: "text-sm",
28
+ body: "text-sm",
29
+ plot: 240,
30
+ groupedRow: 40,
31
+ legendPx: 28
32
+ },
33
+ lg: {
34
+ padding: "lg",
35
+ gap: "gap-3",
36
+ gapPx: 12,
37
+ label: "text-sm",
38
+ body: "text-sm",
39
+ plot: 300,
40
+ groupedRow: 46,
41
+ legendPx: 32
42
+ }
43
+ };
44
+ const RING_LEVELS = [
45
+ .25,
46
+ .5,
47
+ .75,
48
+ 1
49
+ ];
50
+ const labelClasses = "min-w-0 font-sans font-medium text-muted-foreground";
51
+ const captionClasses = "font-sans text-xs text-muted-foreground";
52
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
53
+ const tileStateClasses = "w-full max-w-none gap-2 px-2 py-2 [--artwork-box:3rem]";
54
+ const MISSING_NAME = "Profile";
55
+ const EMPTY_KEYS = [];
56
+ const EMPTY_PALETTE = [];
57
+ const EMPTY_SERIES = [];
58
+ /** The series key beside its name — the mark's own shape, so identity never rests on colour alone. */
59
+ function ProfileSeriesKey({ label, color, mark }) {
60
+ return /* @__PURE__ */ jsxs("span", {
61
+ className: "inline-flex min-w-0 items-center gap-2",
62
+ children: [/* @__PURE__ */ jsx("svg", {
63
+ width: "18",
64
+ height: "12",
65
+ viewBox: "0 0 18 12",
66
+ fill: "none",
67
+ "aria-hidden": "true",
68
+ className: "shrink-0",
69
+ style: { color },
70
+ children: mark === "grouped" ? /* @__PURE__ */ jsx("rect", {
71
+ x: "1",
72
+ y: "2",
73
+ width: "16",
74
+ height: "8",
75
+ rx: "1",
76
+ fill: "currentColor"
77
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("line", {
78
+ x1: "1",
79
+ y1: "6",
80
+ x2: "17",
81
+ y2: "6",
82
+ stroke: "currentColor",
83
+ strokeWidth: 2,
84
+ strokeLinecap: "round"
85
+ }), /* @__PURE__ */ jsx("circle", {
86
+ cx: "9",
87
+ cy: "6",
88
+ r: "3",
89
+ fill: "currentColor"
90
+ })] })
91
+ }), label]
92
+ });
93
+ }
94
+ function InfoIcon(props) {
95
+ return /* @__PURE__ */ jsxs("svg", {
96
+ viewBox: "0 0 24 24",
97
+ fill: "none",
98
+ stroke: "currentColor",
99
+ strokeWidth: 2,
100
+ strokeLinecap: "round",
101
+ strokeLinejoin: "round",
102
+ "aria-hidden": "true",
103
+ ...props,
104
+ children: [/* @__PURE__ */ jsx("circle", {
105
+ cx: "12",
106
+ cy: "12",
107
+ r: "9"
108
+ }), /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01" })]
109
+ });
110
+ }
111
+ function useMergedRef(forwarded) {
112
+ const inner = useRef(null);
113
+ return [inner, useCallback((node) => {
114
+ inner.current = node;
115
+ if (typeof forwarded === "function") forwarded(node);
116
+ else if (forwarded) forwarded.current = node;
117
+ }, [forwarded])];
118
+ }
119
+ function usePlotWidth(ref, enabled) {
120
+ const [width, setWidth] = useState(0);
121
+ useEffect(() => {
122
+ if (!enabled) return;
123
+ const node = ref.current;
124
+ if (!node) return;
125
+ let frame = 0;
126
+ const measure = () => setWidth((previous) => {
127
+ const next = Math.round(node.getBoundingClientRect().width);
128
+ return previous === next ? previous : next;
129
+ });
130
+ measure();
131
+ if (typeof ResizeObserver === "undefined") return;
132
+ const observer = new ResizeObserver(() => {
133
+ cancelAnimationFrame(frame);
134
+ frame = requestAnimationFrame(measure);
135
+ });
136
+ observer.observe(node);
137
+ return () => {
138
+ cancelAnimationFrame(frame);
139
+ observer.disconnect();
140
+ };
141
+ }, [ref, enabled]);
142
+ return width;
143
+ }
144
+ /**
145
+ * A profile panel — the ninth member of the analytics card family, and the
146
+ * only one that compares **several items across several measures at once**.
147
+ * MetricCard compares one metric with its own past; ComparisonCard compares
148
+ * one metric across items; TrendCard asks what shape a metric is in over
149
+ * time. ProfileCard answers the question none of them can: *what shape is
150
+ * each of these things across the same set of measures, and where does one
151
+ * lead or lag?*
152
+ *
153
+ * `mark="radar"` (default) draws the profile as a polygon per item — with the
154
+ * axis order fixed, the fill off and the scaling announced, which is what
155
+ * makes a radar honest. `mark="grouped"` is the accurate alternative for
156
+ * comparing values, and `mark="parallel"` carries more items than a radar
157
+ * can. Every reading is also in the popover table, the legend is real
158
+ * toggles, and every loading, empty, error and stale state is layout-stable
159
+ * inside one `Box` frame.
160
+ */
161
+ const ProfileCard = forwardRef(({ label, name: nameProp, hint, metrics, items, mark = "radar", normalize = "shared", fill = false, vertices = true, axisLabels = true, focus, defaultFocus, onFocusChange, visibleSeries, defaultVisibleSeries, onVisibleSeriesChange, showLegend = true, format = "compact", formatValue, expectedMetrics = 5, expectedItems = 2, insight, status = "ready", filtered = false, stale = false, updatedAt, updatedAtFormat = "day", onRetry, onClearFilters, size = "md", titleLevel = 3, locale, timeZone, labels: labelOverrides, description, action, defaultTableOpen = false, hideTableToggle = false, onEmbed, patchSpec, render, className, ...rest }, forwardedRef) => {
162
+ const labels = useMemo(() => resolveProfileLabels(labelOverrides), [labelOverrides]);
163
+ const sizes = sizeStyles[size];
164
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
165
+ const id = useId();
166
+ const headingId = `${id}-label`;
167
+ const descriptionId = `${id}-description`;
168
+ const hintId = `${id}-hint`;
169
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
170
+ const plotRef = useRef(null);
171
+ const legendRef = useRef(null);
172
+ const [popoverContainer, setPopoverContainer] = useState(null);
173
+ const tokens = useChartTokens(rootRef);
174
+ const [focusState, setFocusState] = useState(defaultFocus);
175
+ const focusKey = focus !== void 0 ? focus : focusState;
176
+ const selectFocus = useCallback((key) => {
177
+ if (focus === void 0) setFocusState(key);
178
+ onFocusChange?.(key);
179
+ }, [focus, onFocusChange]);
180
+ const [visibleState, setVisibleState] = useState(defaultVisibleSeries);
181
+ const resolvedVisible = visibleSeries !== void 0 ? visibleSeries : visibleState;
182
+ const hidden = useMemo(() => {
183
+ if (!resolvedVisible) return EMPTY_KEYS;
184
+ const shown = new Set(resolvedVisible);
185
+ return (items ?? []).map((item, index) => item.key ?? item.label ?? String(index)).filter((key) => !shown.has(key));
186
+ }, [resolvedVisible, items]);
187
+ const palette = tokens?.category ?? EMPTY_PALETTE;
188
+ const emphasis = tokens?.emphasis ?? "";
189
+ const summary = useMemo(() => normalizeProfile(metrics, items, {
190
+ focus: focusKey,
191
+ normalize,
192
+ hidden,
193
+ palette,
194
+ emphasis
195
+ }), [
196
+ metrics,
197
+ items,
198
+ focusKey,
199
+ normalize,
200
+ hidden,
201
+ palette,
202
+ emphasis
203
+ ]);
204
+ const formatters = useMemo(() => createProfileFormatters({
205
+ format,
206
+ locale,
207
+ formatValue
208
+ }), [
209
+ format,
210
+ locale,
211
+ formatValue
212
+ ]);
213
+ const hasData = (metrics?.length ?? 0) > 0 && (items?.length ?? 0) > 0;
214
+ const hasValue = (summary?.count ?? 0) > 0;
215
+ const state = deriveProfileState({
216
+ status,
217
+ hasValue,
218
+ hasData,
219
+ filtered
220
+ });
221
+ const showContent = state === "ready" || state === "refreshing";
222
+ const hasPlot = showContent && summary != null && summary.count > 0;
223
+ const asOfText = useMemo(() => {
224
+ if (updatedAt == null) return null;
225
+ return typeof updatedAtFormat === "function" ? updatedAtFormat(new Date(updatedAt)) : formatDate(updatedAt, {
226
+ granularity: updatedAtFormat,
227
+ locale,
228
+ timeZone
229
+ });
230
+ }, [
231
+ updatedAt,
232
+ updatedAtFormat,
233
+ locale,
234
+ timeZone
235
+ ]);
236
+ const cardDescription = description ?? buildProfileDescription({
237
+ name,
238
+ state,
239
+ summary,
240
+ formatters,
241
+ normalize,
242
+ asOfText,
243
+ labels
244
+ });
245
+ const axisCount = summary?.metrics.length ?? 0;
246
+ const angleOf = useCallback((index) => axisCount > 0 ? index / axisCount * 2 * Math.PI : 0, [axisCount]);
247
+ const cellRows = useMemo(() => summary ? summary.cells.map((cell) => ({
248
+ ...cell,
249
+ a: angleOf(cell.i)
250
+ })) : [], [summary, angleOf]);
251
+ const axisRows = useMemo(() => summary ? summary.metrics.map((metric) => ({
252
+ i: metric.index,
253
+ a: angleOf(metric.index),
254
+ m: metric.key,
255
+ ml: metric.label
256
+ })) : [], [summary, angleOf]);
257
+ const ringRows = useMemo(() => mark === "radar" && summary ? RING_LEVELS.flatMap((r) => summary.metrics.map((metric) => ({
258
+ i: metric.index,
259
+ a: angleOf(metric.index),
260
+ r
261
+ }))) : [], [
262
+ mark,
263
+ summary,
264
+ angleOf
265
+ ]);
266
+ const datasets = useMemo(() => ({
267
+ [PROFILE_CELLS_DATASET]: cellRows,
268
+ [PROFILE_AXES_DATASET]: axisRows,
269
+ [PROFILE_RINGS_DATASET]: ringRows
270
+ }), [
271
+ cellRows,
272
+ axisRows,
273
+ ringRows
274
+ ]);
275
+ const plotHeight = mark === "grouped" ? Math.max(sizes.plot, Math.max(axisCount, expectedMetrics) * sizes.groupedRow) : sizes.plot;
276
+ const plotWidth = usePlotWidth(plotRef, hasPlot);
277
+ const chartWidth = mark === "radar" ? Math.min(plotWidth, plotHeight) : plotWidth;
278
+ const chartHeight = mark === "radar" ? Math.min(plotWidth, plotHeight) : plotHeight;
279
+ const emphasize = summary?.focus != null && (summary?.series.length ?? 0) > 1;
280
+ const itemTitle = labels.tableMetric;
281
+ const spec = useMemo(() => {
282
+ if (!tokens) return null;
283
+ const built = buildProfilePlotSpec({
284
+ mark,
285
+ height: 240,
286
+ valueField: mark === "grouped" && normalize === "shared" ? "raw" : "n",
287
+ rings: mark === "radar",
288
+ fill,
289
+ vertices,
290
+ tooltip: true,
291
+ emphasize,
292
+ axisLabels,
293
+ itemTitle: name,
294
+ metricTitle: itemTitle,
295
+ valueTitle: name,
296
+ valueFormat: locale ? `${format}@${locale}` : format,
297
+ locale
298
+ }, tokens);
299
+ return patchSpec ? patchSpec(built) : built;
300
+ }, [
301
+ tokens,
302
+ mark,
303
+ normalize,
304
+ fill,
305
+ vertices,
306
+ emphasize,
307
+ axisLabels,
308
+ name,
309
+ itemTitle,
310
+ format,
311
+ locale,
312
+ patchSpec
313
+ ]);
314
+ const chartDescription = summary && hasPlot ? buildProfileChartDescription({
315
+ mark,
316
+ name,
317
+ summary,
318
+ normalize,
319
+ labels
320
+ }) : void 0;
321
+ const metricByKey = useMemo(() => new Map((summary?.metrics ?? []).map((metric) => [metric.key, metric])), [summary]);
322
+ const tooltipContent = useCallback(({ item }) => {
323
+ const datum = item.datum;
324
+ if (!datum || datum.sl == null) return null;
325
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows: [[/* @__PURE__ */ jsx(ProfileSeriesKey, {
326
+ mark,
327
+ label: datum.sl,
328
+ color: datum.c
329
+ }, "series"), ""], [datum.ml ?? "—", formatters.formatValue(datum.raw, datum.m ? metricByKey.get(datum.m) : void 0)]] });
330
+ }, [
331
+ mark,
332
+ formatters,
333
+ metricByKey
334
+ ]);
335
+ const { handler: tooltipHandler, tooltip } = useArcTooltipHandler({ content: tooltipContent });
336
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltipHandler }), [tooltipHandler]);
337
+ const shownSeries = summary?.series ?? EMPTY_SERIES;
338
+ const allKeys = useMemo(() => (items ?? []).map((item, index) => item.key ?? item.label ?? String(index)), [items]);
339
+ const toggleSeries = useCallback((key, next) => {
340
+ const current = resolvedVisible ?? allKeys;
341
+ const nextKeys = next ? allKeys.filter((k) => current.includes(k) || k === key) : current.filter((k) => k !== key);
342
+ if (visibleSeries === void 0) setVisibleState(nextKeys);
343
+ onVisibleSeriesChange?.(nextKeys);
344
+ }, [
345
+ resolvedVisible,
346
+ allKeys,
347
+ visibleSeries,
348
+ onVisibleSeriesChange
349
+ ]);
350
+ const onLegendKeyDown = useCallback((event) => {
351
+ if (![
352
+ "ArrowRight",
353
+ "ArrowDown",
354
+ "ArrowLeft",
355
+ "ArrowUp",
356
+ "Home",
357
+ "End"
358
+ ].includes(event.key) || shownSeries.length === 0) return;
359
+ event.preventDefault();
360
+ const current = shownSeries.findIndex((series) => series.role === "focus");
361
+ const last = shownSeries.length - 1;
362
+ const next = event.key === "Home" ? 0 : event.key === "End" ? last : event.key === "ArrowRight" || event.key === "ArrowDown" ? Math.min(last, current + 1) : Math.max(0, current - 1);
363
+ const target = shownSeries[next];
364
+ if (!target || target.role === "focus") return;
365
+ selectFocus(target.key);
366
+ requestAnimationFrame(() => {
367
+ legendRef.current?.querySelectorAll("[data-part=\"legend-chip\"]")[next]?.focus();
368
+ });
369
+ }, [shownSeries, selectFocus]);
370
+ const legendMode = onFocusChange || focus === void 0 ? "focus" : onVisibleSeriesChange || visibleSeries === void 0 ? "toggle" : "static";
371
+ const showInsight = insight != null && insight !== false;
372
+ const legendRows = Math.max(1, Math.ceil(Math.max(shownSeries.length, expectedItems) / 3));
373
+ const bodyMinHeight = plotHeight + (showLegend ? sizes.gapPx + legendRows * sizes.legendPx : 0) + (showInsight ? sizes.gapPx + 20 : 0);
374
+ const HeadingTag = `h${titleLevel}`;
375
+ const busy = state === "loading" || state === "refreshing";
376
+ const highlights = deriveProfileHighlights(summary, formatters);
377
+ const overCap = mark === "radar" && (summary?.overCap ?? false);
378
+ const underCap = mark === "radar" && (summary?.underCap ?? false) && hasPlot;
379
+ return /* @__PURE__ */ jsxs(Box, {
380
+ ref: setRootRef,
381
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
382
+ padding: sizes.padding,
383
+ "aria-labelledby": headingId,
384
+ "aria-describedby": descriptionId,
385
+ "aria-busy": busy || void 0,
386
+ "data-status": status,
387
+ "data-state": state,
388
+ "data-size": size,
389
+ "data-mark": mark,
390
+ "data-normalize": normalize,
391
+ "data-stale": stale ? "" : void 0,
392
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
393
+ "data-focus": summary?.focus?.key,
394
+ "data-over-cap": overCap ? "" : void 0,
395
+ "data-under-cap": underCap ? "" : void 0,
396
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
397
+ ...rest,
398
+ children: [
399
+ /* @__PURE__ */ jsx("div", {
400
+ ref: setPopoverContainer,
401
+ className: "contents"
402
+ }),
403
+ /* @__PURE__ */ jsxs("div", {
404
+ className: "flex min-w-0 items-start justify-between gap-2",
405
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
406
+ id: headingId,
407
+ className: cn(labelClasses, sizes.label, "truncate"),
408
+ "data-part": "label",
409
+ children: label
410
+ }), /* @__PURE__ */ jsxs("div", {
411
+ className: "flex shrink-0 items-center gap-1",
412
+ children: [
413
+ stale && /* @__PURE__ */ jsx(Badge, {
414
+ variant: "warning",
415
+ appearance: "soft",
416
+ size: "sm",
417
+ "data-part": "stale",
418
+ children: labels.stale
419
+ }),
420
+ hint && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
421
+ delay: 150,
422
+ render: /* @__PURE__ */ jsx(Button, {
423
+ variant: "ghost",
424
+ size: "sm",
425
+ iconOnly: true,
426
+ "aria-label": labels.about(name),
427
+ "aria-describedby": hintId,
428
+ className: headerControlClasses
429
+ }),
430
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" })
431
+ }), /* @__PURE__ */ jsx(TooltipPopup, {
432
+ side: "top",
433
+ sideOffset: 6,
434
+ container: popoverContainer,
435
+ children: /* @__PURE__ */ jsx("span", {
436
+ id: hintId,
437
+ children: hint
438
+ })
439
+ })] }),
440
+ !hideTableToggle && hasPlot && summary && /* @__PURE__ */ jsxs(Popover, {
441
+ defaultOpen: defaultTableOpen,
442
+ children: [/* @__PURE__ */ jsx(PopoverTrigger, {
443
+ render: /* @__PURE__ */ jsx(Button, {
444
+ variant: "ghost",
445
+ size: "sm",
446
+ iconOnly: true,
447
+ "aria-label": labels.showTable(name),
448
+ className: headerControlClasses
449
+ }),
450
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-4" })
451
+ }), /* @__PURE__ */ jsxs(PopoverPopup, {
452
+ side: "bottom",
453
+ align: "end",
454
+ sideOffset: 8,
455
+ collisionPadding: 8,
456
+ container: popoverContainer,
457
+ className: "w-[min(32rem,var(--available-width))]",
458
+ "data-part": "popover",
459
+ children: [
460
+ /* @__PURE__ */ jsx(PopoverTitle, {
461
+ className: "sr-only",
462
+ children: labels.tableCaption(name)
463
+ }),
464
+ /* @__PURE__ */ jsx("p", {
465
+ className: cn(captionClasses, "mb-2"),
466
+ "data-part": "normalize-note",
467
+ children: labels.normalizeNote[normalize]
468
+ }),
469
+ /* @__PURE__ */ jsx(TableContainer, {
470
+ variant: "plain",
471
+ tabIndex: 0,
472
+ role: "region",
473
+ "aria-label": labels.tableCaption(name),
474
+ className: "max-h-[min(18rem,calc(var(--available-height)-5rem))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
475
+ "data-part": "table",
476
+ children: /* @__PURE__ */ jsxs(Table, {
477
+ size: "sm",
478
+ stickyHeader: true,
479
+ hoverable: false,
480
+ children: [
481
+ /* @__PURE__ */ jsx(TableCaption, {
482
+ side: "top",
483
+ className: "sr-only",
484
+ children: labels.tableCaption(name)
485
+ }),
486
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
487
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tableMetric }),
488
+ summary.series.map((series) => /* @__PURE__ */ jsxs(TableHead, {
489
+ className: "text-right",
490
+ "data-role": series.role,
491
+ children: [series.label, series.role === "focus" && /* @__PURE__ */ jsxs("span", {
492
+ className: "sr-only",
493
+ children: [
494
+ " (",
495
+ labels.highlighted,
496
+ ")"
497
+ ]
498
+ })]
499
+ }, series.key)),
500
+ /* @__PURE__ */ jsx(TableHead, {
501
+ className: "text-right",
502
+ children: labels.tableLeader
503
+ })
504
+ ] }) }),
505
+ /* @__PURE__ */ jsx(TableBody, { children: summary.metrics.map((metric) => /* @__PURE__ */ jsxs(TableRow, { children: [
506
+ /* @__PURE__ */ jsx(TableCell, { children: metric.label }),
507
+ summary.series.map((series) => /* @__PURE__ */ jsx(TableCell, {
508
+ className: cn("text-right tabular-nums", metric.leader === series.key && "font-semibold text-foreground"),
509
+ children: formatters.formatValue(series.values[metric.index], metric)
510
+ }, series.key)),
511
+ /* @__PURE__ */ jsx(TableCell, {
512
+ className: "text-right text-muted-foreground",
513
+ children: metric.leader ? summary.series.find((series) => series.key === metric.leader)?.label ?? "—" : "—"
514
+ })
515
+ ] }, metric.key)) })
516
+ ]
517
+ })
518
+ })
519
+ ]
520
+ })]
521
+ }),
522
+ action
523
+ ]
524
+ })]
525
+ }),
526
+ /* @__PURE__ */ jsx("p", {
527
+ id: descriptionId,
528
+ className: "sr-only",
529
+ children: cardDescription
530
+ }),
531
+ /* @__PURE__ */ jsxs("div", {
532
+ className: "relative flex min-w-0 flex-col",
533
+ style: { minHeight: bodyMinHeight },
534
+ children: [
535
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
536
+ className: cn("flex flex-col", sizes.gap),
537
+ children: [/* @__PURE__ */ jsx(Skeleton, {
538
+ variant: "rounded",
539
+ className: "w-full",
540
+ style: { height: plotHeight }
541
+ }), showLegend && /* @__PURE__ */ jsx("div", {
542
+ className: "flex flex-wrap gap-2",
543
+ children: Array.from({ length: Math.max(1, expectedItems) }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
544
+ variant: "rounded",
545
+ className: "w-20",
546
+ style: { height: sizes.legendPx - 6 }
547
+ }, index))
548
+ })]
549
+ }),
550
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
551
+ variant: "server-error",
552
+ scope: "inline",
553
+ title: labels.error,
554
+ description: false,
555
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
556
+ size: "sm",
557
+ onClick: onRetry,
558
+ children: labels.retry
559
+ }) : void 0,
560
+ className: cn(tileStateClasses, "my-auto")
561
+ }),
562
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
563
+ variant: "empty",
564
+ scope: "inline",
565
+ title: labels.noData,
566
+ description: false,
567
+ className: cn(tileStateClasses, "my-auto")
568
+ }),
569
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
570
+ variant: "no-results",
571
+ scope: "inline",
572
+ title: labels.noMatch,
573
+ description: false,
574
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
575
+ size: "sm",
576
+ variant: "secondary",
577
+ onClick: onClearFilters,
578
+ children: labels.clearFilters
579
+ }) : void 0,
580
+ className: cn(tileStateClasses, "my-auto")
581
+ }),
582
+ showContent && /* @__PURE__ */ jsxs("div", {
583
+ className: cn("flex min-w-0 flex-col", sizes.gap, state === "refreshing" && "bg-veil"),
584
+ children: [
585
+ /* @__PURE__ */ jsx("div", {
586
+ ref: plotRef,
587
+ className: "relative w-full",
588
+ style: { height: plotHeight },
589
+ "data-part": "plot-block",
590
+ children: hasPlot && chartWidth > 0 && spec && /* @__PURE__ */ jsx(ArcVegaChart, {
591
+ spec,
592
+ options: embedOptions,
593
+ datasets,
594
+ description: chartDescription,
595
+ width: chartWidth,
596
+ height: chartHeight,
597
+ onEmbed,
598
+ className: "absolute top-0 left-1/2 -translate-x-1/2 [&_svg]:block [&_.vega-bindings:empty]:hidden",
599
+ style: {
600
+ width: chartWidth,
601
+ height: chartHeight
602
+ },
603
+ "data-part": "plot",
604
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
605
+ variant: "rounded",
606
+ className: "absolute inset-0"
607
+ })
608
+ })
609
+ }),
610
+ showLegend && summary && summary.series.length > 0 && /* @__PURE__ */ jsx(HorizontalScroll, {
611
+ fade: true,
612
+ className: "min-w-0",
613
+ "data-part": "legend-scroll",
614
+ children: /* @__PURE__ */ jsxs("div", {
615
+ ref: legendRef,
616
+ role: legendMode === "focus" ? "radiogroup" : "group",
617
+ "aria-label": labels.seriesGroup(name),
618
+ onKeyDown: legendMode === "focus" ? onLegendKeyDown : void 0,
619
+ className: cn("flex min-w-0 flex-wrap items-center gap-x-1 gap-y-1 font-sans", sizes.body),
620
+ "data-part": "legend",
621
+ children: [summary.series.map((series, index) => {
622
+ const chipLabel = /* @__PURE__ */ jsx("span", {
623
+ className: "min-w-0 truncate",
624
+ children: series.label
625
+ });
626
+ const key = /* @__PURE__ */ jsx(ProfileSeriesKey, {
627
+ mark,
628
+ color: series.color,
629
+ label: chipLabel
630
+ });
631
+ if (legendMode === "focus") {
632
+ const isFocus = series.role === "focus";
633
+ return /* @__PURE__ */ jsxs("button", {
634
+ type: "button",
635
+ role: "radio",
636
+ "aria-checked": isFocus,
637
+ tabIndex: isFocus || index === 0 && !summary.focus ? 0 : -1,
638
+ onClick: () => selectFocus(series.key),
639
+ className: cn("inline-flex max-w-full cursor-pointer items-center rounded-md px-1.5 py-1 text-left transition-colors", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", isFocus ? "bg-primary-50 font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
640
+ "data-part": "legend-chip",
641
+ "data-focused": isFocus ? "" : void 0,
642
+ children: [key, isFocus && /* @__PURE__ */ jsxs("span", {
643
+ className: "sr-only",
644
+ children: [
645
+ " (",
646
+ labels.highlighted,
647
+ ")"
648
+ ]
649
+ })]
650
+ }, series.key);
651
+ }
652
+ if (legendMode === "static") return /* @__PURE__ */ jsx("span", {
653
+ className: "inline-flex max-w-full items-center px-1.5 py-1 text-muted-foreground",
654
+ "data-part": "legend-chip",
655
+ children: key
656
+ }, series.key);
657
+ return /* @__PURE__ */ jsx(Toggle, {
658
+ variant: "ghost",
659
+ size: "sm",
660
+ pressed: true,
661
+ onPressedChange: (next) => toggleSeries(series.key, next),
662
+ disabled: summary.series.length <= 1,
663
+ "aria-label": labels.hideSeries(series.label),
664
+ className: "h-auto max-w-full px-1.5 py-1",
665
+ "data-part": "legend-chip",
666
+ children: key
667
+ }, series.key);
668
+ }), (items ?? []).map((item, index) => {
669
+ const key = allKeys[index];
670
+ if (summary.series.some((series) => series.key === key)) return null;
671
+ return /* @__PURE__ */ jsx(Toggle, {
672
+ variant: "ghost",
673
+ size: "sm",
674
+ pressed: false,
675
+ onPressedChange: () => toggleSeries(key, true),
676
+ "aria-label": labels.showSeries(item.label),
677
+ className: "h-auto max-w-full px-1.5 py-1 opacity-60",
678
+ "data-part": "legend-chip",
679
+ "data-hidden": "",
680
+ children: /* @__PURE__ */ jsx(ProfileSeriesKey, {
681
+ mark,
682
+ color: tokens?.mutedForeground,
683
+ label: /* @__PURE__ */ jsx("span", {
684
+ className: "min-w-0 truncate",
685
+ children: item.label
686
+ })
687
+ })
688
+ }, key);
689
+ })]
690
+ })
691
+ }),
692
+ showInsight && /* @__PURE__ */ jsxs("div", {
693
+ className: cn("flex items-start gap-2 font-sans text-foreground", sizes.body),
694
+ "data-part": "insight",
695
+ children: [/* @__PURE__ */ jsx(AILoader, {
696
+ size: "sm",
697
+ state: "default",
698
+ label: labels.insightMark,
699
+ className: cn("shrink-0", size === "sm" ? "" : "mt-0.5")
700
+ }), /* @__PURE__ */ jsx("p", {
701
+ className: "min-w-0 flex-1 text-xs leading-[1.8] [&_strong]:font-semibold [&_strong]:text-foreground",
702
+ children: insight
703
+ })]
704
+ }),
705
+ !showInsight && highlights && summary?.focus && /* @__PURE__ */ jsx("p", {
706
+ className: cn(captionClasses, "min-w-0"),
707
+ "data-part": "highlights",
708
+ children: labels.profileSentence({
709
+ name: summary.focus.label,
710
+ strongest: highlights.strongest.label,
711
+ strongestValue: highlights.strongestValue,
712
+ weakest: highlights.weakest.label,
713
+ weakestValue: highlights.weakestValue
714
+ })
715
+ })
716
+ ]
717
+ })
718
+ ]
719
+ }),
720
+ asOfText && /* @__PURE__ */ jsxs("figcaption", {
721
+ className: cn(captionClasses, "text-right"),
722
+ "data-part": "as-of",
723
+ children: [
724
+ labels.asOf,
725
+ " ",
726
+ asOfText
727
+ ]
728
+ }),
729
+ tooltip
730
+ ]
731
+ });
732
+ });
733
+ ProfileCard.displayName = "ProfileCard";
734
+ //#endregion
735
+ export { ProfileCard };