@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,1118 @@
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 { REPORT_BANDS_DATASET, REPORT_BOUNDS_DATASET, REPORT_TABLE_DATASET, buildReportBulletSpec } from "./ReportCard.chart.js";
7
+ import { buildReportChartDescription, buildReportDescription, createReportFormatters, deriveReportDelta, deriveReportState, normalizeCriteria, resolveReportLabels } from "./ReportCard.model.js";
8
+ import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { AILoader, Badge, Box, Button, Popover, PopoverPopup, PopoverTitle, PopoverTrigger, Skeleton, SkeletonGroup, SystemState, Table, TableBody, TableCaption, TableCell, TableContainer, TableFooter, TableHead, TableHeader, TableRow, 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/ReportCard/ReportCard.tsx
12
+ const sizeStyles = {
13
+ sm: {
14
+ padding: "sm",
15
+ gap: "gap-2",
16
+ gapPx: 8,
17
+ label: "text-xs",
18
+ value: "text-2xl leading-8",
19
+ valueLinePx: 32,
20
+ unit: "text-xs",
21
+ body: "text-xs",
22
+ badge: "sm",
23
+ deltaLinePx: 20,
24
+ comparisonLinePx: 16,
25
+ chip: "sm",
26
+ chipLinePx: 20,
27
+ step: 24,
28
+ barHeight: 10,
29
+ rowText: "text-xs",
30
+ scaleLinePx: 14
31
+ },
32
+ md: {
33
+ padding: "md",
34
+ gap: "gap-2",
35
+ gapPx: 8,
36
+ label: "text-sm",
37
+ value: "text-3xl leading-9",
38
+ valueLinePx: 36,
39
+ unit: "text-sm",
40
+ body: "text-sm",
41
+ badge: "md",
42
+ deltaLinePx: 24,
43
+ comparisonLinePx: 20,
44
+ chip: "sm",
45
+ chipLinePx: 20,
46
+ step: 28,
47
+ barHeight: 12,
48
+ rowText: "text-sm",
49
+ scaleLinePx: 16
50
+ },
51
+ lg: {
52
+ padding: "lg",
53
+ gap: "gap-2",
54
+ gapPx: 8,
55
+ label: "text-sm",
56
+ value: "text-4xl leading-10",
57
+ valueLinePx: 40,
58
+ unit: "text-base",
59
+ body: "text-sm",
60
+ badge: "lg",
61
+ deltaLinePx: 28,
62
+ comparisonLinePx: 20,
63
+ chip: "md",
64
+ chipLinePx: 24,
65
+ step: 32,
66
+ barHeight: 14,
67
+ rowText: "text-sm",
68
+ scaleLinePx: 16
69
+ }
70
+ };
71
+ const CHIP_GAP_PX = 4;
72
+ const SCALE_LABEL_PX = 28;
73
+ const labelClasses = "min-w-0 font-sans font-medium text-muted-foreground";
74
+ const valueClasses = "font-heading font-semibold tracking-tight text-foreground";
75
+ const captionClasses = "font-sans text-xs text-muted-foreground mt-1";
76
+ const selectableRowClasses = "relative isolate cursor-pointer rounded-md before:pointer-events-none before:absolute before:-inset-x-1.5 before:inset-y-0 before:-z-10 before:rounded-md before:bg-muted before:opacity-0 hover:before:opacity-100 focus-visible:outline-2 focus-visible:outline-offset-1 focus-visible:outline-ring";
77
+ const selectedRowClasses = "before:opacity-100";
78
+ const toneBadgeVariant = {
79
+ positive: "success",
80
+ neutral: "neutral",
81
+ warning: "warning",
82
+ negative: "destructive"
83
+ };
84
+ const deltaBadgeVariant = {
85
+ positive: "success",
86
+ negative: "destructive",
87
+ neutral: "neutral"
88
+ };
89
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
90
+ const tileStateClasses = "w-full max-w-none gap-2 px-2 py-2 [--artwork-box:3rem]";
91
+ const rowGridClasses = "grid grid-cols-[fit-content(45%)_minmax(0,1fr)_auto_auto] gap-x-3";
92
+ const MISSING_NAME = "Report";
93
+ const EMPTY_ROWS = [];
94
+ /** The hue a tone wears on a mark — the same four values the spec paints with. */
95
+ function toneColorOf(tokens, tone) {
96
+ if (!tokens) return void 0;
97
+ if (tone === "positive") return tokens.divergingPositive;
98
+ if (tone === "warning") return tokens.status.degraded;
99
+ if (tone === "negative") return tokens.divergingNegative;
100
+ return tokens.emphasis;
101
+ }
102
+ /** The bar swatch beside a grade — the exact hue the row's bar wears. */
103
+ function GradeSwatch({ color }) {
104
+ return /* @__PURE__ */ jsx("svg", {
105
+ viewBox: "0 0 12 12",
106
+ fill: "none",
107
+ "aria-hidden": "true",
108
+ className: "size-2.5 shrink-0",
109
+ style: { color },
110
+ children: /* @__PURE__ */ jsx("rect", {
111
+ x: "1",
112
+ y: "3",
113
+ width: "10",
114
+ height: "6",
115
+ rx: "1.5",
116
+ fill: "currentColor"
117
+ })
118
+ });
119
+ }
120
+ /** Match the plot's mark shape so its meaning never depends on color alone. */
121
+ function ReportKey({ label, mark = "bar", color }) {
122
+ return /* @__PURE__ */ jsxs("span", {
123
+ className: "inline-flex items-center gap-2",
124
+ children: [/* @__PURE__ */ jsx("svg", {
125
+ width: "24",
126
+ height: "12",
127
+ viewBox: "0 0 24 12",
128
+ fill: "none",
129
+ "aria-hidden": "true",
130
+ className: "shrink-0",
131
+ style: { color },
132
+ children: mark === "bar" ? /* @__PURE__ */ jsx("rect", {
133
+ x: "1",
134
+ y: "2",
135
+ width: "22",
136
+ height: "8",
137
+ rx: "1",
138
+ fill: "currentColor"
139
+ }) : /* @__PURE__ */ jsx("rect", {
140
+ x: "11",
141
+ y: "0",
142
+ width: "2",
143
+ height: "12",
144
+ rx: "1",
145
+ fill: "currentColor"
146
+ })
147
+ }), label]
148
+ });
149
+ }
150
+ function InfoIcon(props) {
151
+ return /* @__PURE__ */ jsxs("svg", {
152
+ viewBox: "0 0 24 24",
153
+ fill: "none",
154
+ stroke: "currentColor",
155
+ strokeWidth: 2,
156
+ strokeLinecap: "round",
157
+ strokeLinejoin: "round",
158
+ "aria-hidden": "true",
159
+ ...props,
160
+ children: [/* @__PURE__ */ jsx("circle", {
161
+ cx: "12",
162
+ cy: "12",
163
+ r: "9"
164
+ }), /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01" })]
165
+ });
166
+ }
167
+ function DeltaGlyph({ direction, ...props }) {
168
+ return /* @__PURE__ */ jsxs("svg", {
169
+ viewBox: "0 0 24 24",
170
+ fill: "none",
171
+ stroke: "currentColor",
172
+ strokeWidth: 2.5,
173
+ strokeLinecap: "round",
174
+ strokeLinejoin: "round",
175
+ "aria-hidden": "true",
176
+ ...props,
177
+ children: [
178
+ direction === "up" && /* @__PURE__ */ jsx("path", { d: "M7 17 17 7M7 7h10v10" }),
179
+ direction === "down" && /* @__PURE__ */ jsx("path", { d: "M7 7 17 17M7 17h10V7" }),
180
+ direction === "flat" && /* @__PURE__ */ jsx("path", { d: "M5 12h14M13 6l6 6-6 6" })
181
+ ]
182
+ });
183
+ }
184
+ function useMergedRef(forwarded) {
185
+ const inner = useRef(null);
186
+ return [inner, useCallback((node) => {
187
+ inner.current = node;
188
+ if (typeof forwarded === "function") forwarded(node);
189
+ else if (forwarded) forwarded.current = node;
190
+ }, [forwarded])];
191
+ }
192
+ function usePlotBox(cellRef, wrapperRef, enabled, resetKey) {
193
+ const [box, setBox] = useState({
194
+ left: 0,
195
+ width: 0
196
+ });
197
+ useLayoutEffect(() => {
198
+ if (!enabled) return;
199
+ const cell = cellRef.current;
200
+ const wrapper = wrapperRef.current;
201
+ if (!cell || !wrapper) return;
202
+ let frame = 0;
203
+ const measure = () => {
204
+ const cellRect = cell.getBoundingClientRect();
205
+ const wrapperRect = wrapper.getBoundingClientRect();
206
+ const next = {
207
+ left: cellRect.left - wrapperRect.left,
208
+ width: Math.round(cellRect.width)
209
+ };
210
+ setBox((prev) => prev.left === next.left && prev.width === next.width ? prev : next);
211
+ };
212
+ measure();
213
+ if (typeof ResizeObserver === "undefined") return;
214
+ const observer = new ResizeObserver(() => {
215
+ cancelAnimationFrame(frame);
216
+ frame = requestAnimationFrame(measure);
217
+ });
218
+ observer.observe(cell);
219
+ observer.observe(wrapper);
220
+ return () => {
221
+ cancelAnimationFrame(frame);
222
+ observer.disconnect();
223
+ };
224
+ }, [
225
+ cellRef,
226
+ wrapperRef,
227
+ enabled,
228
+ resetKey
229
+ ]);
230
+ return box;
231
+ }
232
+ /**
233
+ * A scorecard panel — the roll-up subtype of the KPI card family: several
234
+ * criteria in their own units, each normalised to a score on one declared
235
+ * scale and graded against a ladder of bands, drawn as Few-style bullet rows
236
+ * over quiet qualitative ranges with a comparative tick, rolled up into one
237
+ * overall grade in the headline, with every name, score and grade as text
238
+ * beside the plot, a detail line for the selected criterion, and every
239
+ * loading, empty, insufficient, error and stale state — layout-stable inside
240
+ * one `Box` frame, with a generated text equivalent and a data table in an
241
+ * info popover.
242
+ */
243
+ const ReportCard = forwardRef(({ label, name: nameProp, hint, criteria, bands, bandPaint = "neutral", scoreDomain, scoreFormat = "number:0", formatScore, format = "compact", formatValue, goodDirection = "up", passing, overallMode = "weighted", overall, previousScore, previousLabel, headline = "score", showCeiling = true, showGrades = true, scaleTicks = "ends", sort = "none", maxCriteria, expectedCriteria = 4, selectable = true, selected, defaultSelected, onSelectedChange, 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) => {
244
+ const labels = useMemo(() => resolveReportLabels(labelOverrides), [labelOverrides]);
245
+ const sizes = sizeStyles[size];
246
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
247
+ const id = useId();
248
+ const headingId = `${id}-label`;
249
+ const descriptionId = `${id}-description`;
250
+ const hintId = `${id}-hint`;
251
+ const tableId = `${id}-table`;
252
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
253
+ const rowsRef = useRef(null);
254
+ const plotCellRef = useRef(null);
255
+ const rowRefs = useRef(/* @__PURE__ */ new Map());
256
+ const [tableOpen, setTableOpen] = useState(defaultTableOpen);
257
+ const [popoverContainer, setPopoverContainer] = useState(null);
258
+ const otherLabel = labels.other;
259
+ const summary = useMemo(() => normalizeCriteria(criteria, {
260
+ bands,
261
+ scoreDomain,
262
+ goodDirection,
263
+ passing,
264
+ overallMode,
265
+ overall,
266
+ sort,
267
+ maxCriteria,
268
+ otherLabel
269
+ }), [
270
+ criteria,
271
+ bands,
272
+ scoreDomain,
273
+ goodDirection,
274
+ passing,
275
+ overallMode,
276
+ overall,
277
+ sort,
278
+ maxCriteria,
279
+ otherLabel
280
+ ]);
281
+ const formatters = useMemo(() => createReportFormatters({
282
+ scoreFormat,
283
+ format,
284
+ locale,
285
+ formatScore,
286
+ formatValue
287
+ }), [
288
+ scoreFormat,
289
+ format,
290
+ locale,
291
+ formatScore,
292
+ formatValue
293
+ ]);
294
+ const rows = summary?.rows ?? EMPTY_ROWS;
295
+ const scored = summary?.overall.scored ?? 0;
296
+ const state = deriveReportState({
297
+ status,
298
+ hasScore: scored > 0,
299
+ hasData: rows.length > 0,
300
+ filtered
301
+ });
302
+ const showContent = state === "ready" || state === "refreshing" || state === "insufficient";
303
+ const hasChart = showContent && scored > 0 && rows.length > 0;
304
+ const hasTable = showContent && rows.length > 0;
305
+ const isSelectionControlled = selected !== void 0;
306
+ const [innerSelected, setInnerSelected] = useState(defaultSelected);
307
+ const selectedKey = isSelectionControlled ? selected : innerSelected;
308
+ const selectedRow = useMemo(() => {
309
+ if (selectedKey === null) return null;
310
+ if (selectedKey !== void 0) return rows.find((row) => row.key === selectedKey) ?? null;
311
+ return summary?.worst ?? rows[0] ?? null;
312
+ }, [
313
+ selectedKey,
314
+ rows,
315
+ summary
316
+ ]);
317
+ const pendingAnnounce = useRef(false);
318
+ const [announcement, setAnnouncement] = useState("");
319
+ const selectRow = useCallback((key) => {
320
+ pendingAnnounce.current = true;
321
+ if (!isSelectionControlled) setInnerSelected(key);
322
+ onSelectedChange?.(key);
323
+ }, [isSelectionControlled, onSelectedChange]);
324
+ const interactive = selectable && rows.length > 0 && selectedKey !== null && (!isSelectionControlled || onSelectedChange != null);
325
+ const handleRowKeyDown = (event, row) => {
326
+ if (!interactive) return;
327
+ const index = rows.indexOf(row);
328
+ if (index < 0) return;
329
+ let next;
330
+ switch (event.key) {
331
+ case "ArrowDown":
332
+ case "ArrowRight":
333
+ next = rows[(index + 1) % rows.length];
334
+ break;
335
+ case "ArrowUp":
336
+ case "ArrowLeft":
337
+ next = rows[(index - 1 + rows.length) % rows.length];
338
+ break;
339
+ case "Home":
340
+ next = rows[0];
341
+ break;
342
+ case "End":
343
+ next = rows[rows.length - 1];
344
+ break;
345
+ case " ":
346
+ case "Enter":
347
+ next = row;
348
+ break;
349
+ default: return;
350
+ }
351
+ event.preventDefault();
352
+ if (next.key !== selectedRow?.key) selectRow(next.key);
353
+ rowRefs.current.get(next.key)?.focus();
354
+ };
355
+ const handlePlotClick = (event) => {
356
+ if (!interactive) return;
357
+ const top = event.currentTarget.getBoundingClientRect().top;
358
+ const row = rows[Math.floor((event.clientY - top) / sizes.step)];
359
+ if (row && row.key !== selectedRow?.key) selectRow(row.key);
360
+ };
361
+ const overallScore = summary?.overall.score ?? null;
362
+ const overallBand = summary?.overall.band ?? null;
363
+ const delta = useMemo(() => deriveReportDelta(overallScore, previousScore, {
364
+ scoreFormat,
365
+ locale
366
+ }), [
367
+ overallScore,
368
+ previousScore,
369
+ scoreFormat,
370
+ locale
371
+ ]);
372
+ const ceilingText = summary ? formatters.formatScore(summary.ceiling) : "—";
373
+ const scoreText = formatters.formatScore(overallScore);
374
+ const valueText = headline === "none" || overallScore == null ? null : headline === "grade" ? overallBand?.label ?? "—" : scoreText;
375
+ const writeCeiling = showCeiling && summary?.floor === 0;
376
+ const unitText = headline === "score" && writeCeiling ? labels.outOf(ceilingText) : null;
377
+ const asOfDate = updatedAt == null ? null : updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
378
+ const asOfValid = asOfDate != null && !Number.isNaN(asOfDate.getTime());
379
+ const asOfText = asOfValid ? typeof updatedAtFormat === "function" ? updatedAtFormat(asOfDate) : formatDate(asOfDate, {
380
+ granularity: updatedAtFormat,
381
+ locale,
382
+ timeZone
383
+ }) : void 0;
384
+ const cardDescription = description ?? buildReportDescription({
385
+ name,
386
+ state,
387
+ summary,
388
+ delta,
389
+ previousScore,
390
+ previousLabel: typeof previousLabel === "string" ? previousLabel : void 0,
391
+ formatters,
392
+ asOfText,
393
+ labels
394
+ });
395
+ const detailText = selectedRow ? labels.rowDetail({
396
+ label: selectedRow.label,
397
+ scoreText: formatters.formatScore(selectedRow.score),
398
+ gradeLabel: selectedRow.band?.label ?? null,
399
+ valueText: selectedRow.value != null ? formatters.formatValue(selectedRow.value, selectedRow.format) : null,
400
+ targetText: selectedRow.target != null ? formatters.formatValue(selectedRow.target, selectedRow.format) : null,
401
+ targetScoreText: selectedRow.targetScore != null ? formatters.formatScore(selectedRow.targetScore) : null,
402
+ weightText: formatters.formatWeight(selectedRow.weight) ? `${labels.weight} ${formatters.formatWeight(selectedRow.weight)}` : null,
403
+ note: selectedRow.note
404
+ }) : null;
405
+ useEffect(() => {
406
+ if (!pendingAnnounce.current) return;
407
+ pendingAnnounce.current = false;
408
+ setAnnouncement(detailText ?? cardDescription);
409
+ }, [detailText, cardDescription]);
410
+ const tokens = useChartTokens(rootRef);
411
+ const plotHeight = rows.length * sizes.step;
412
+ const plotBox = usePlotBox(plotCellRef, rowsRef, hasChart, rows[0]?.key ?? "");
413
+ const tooltipContent = useCallback(({ item }) => {
414
+ const datum = item.datum;
415
+ if (!datum || !("key" in datum)) return null;
416
+ const row = rows.find((candidate) => candidate.key === datum.key) ?? datum;
417
+ const tooltipRows = [[/* @__PURE__ */ jsx(ReportKey, {
418
+ label: row.label,
419
+ color: toneColorOf(tokens, row.tone)
420
+ }, "criterion"), row.score == null ? labels.unscored : `${formatters.formatScore(row.score)}${row.band ? ` · ${row.band.label}` : ""}`]];
421
+ if (row.value != null) tooltipRows.push([labels.tableValue, row.target != null ? `${formatters.formatValue(row.value, row.format)} / ${formatters.formatValue(row.target, row.format)}` : formatters.formatValue(row.value, row.format)]);
422
+ if (row.targetScore != null) tooltipRows.push([/* @__PURE__ */ jsx(ReportKey, {
423
+ label: labels.expected,
424
+ mark: "tick",
425
+ color: tokens?.foreground
426
+ }, "expected"), formatters.formatScore(row.targetScore)]);
427
+ const weightText = formatters.formatWeight(row.weight);
428
+ if (weightText) tooltipRows.push([labels.weight, weightText]);
429
+ if (row.note) tooltipRows.push([" ", row.note]);
430
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows: tooltipRows });
431
+ }, [
432
+ rows,
433
+ tokens,
434
+ formatters,
435
+ labels
436
+ ]);
437
+ const { handler: tooltipHandler, tooltip } = useArcTooltipHandler({ content: tooltipContent });
438
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltipHandler }), [tooltipHandler]);
439
+ const criterionTitle = labels.tableCriterion;
440
+ const scoreTitle = labels.tableScore;
441
+ const barHeight = sizes.barHeight;
442
+ const hasTicks = rows.some((row) => row.targetScore != null);
443
+ const spec = useMemo(() => {
444
+ if (!tokens) return null;
445
+ const built = buildReportBulletSpec({
446
+ barHeight,
447
+ height: 112,
448
+ bandPaint,
449
+ ticks: hasTicks,
450
+ tooltip: true,
451
+ criterionTitle,
452
+ scoreTitle,
453
+ scoreFormat: locale ? `${scoreFormat}@${locale}` : scoreFormat,
454
+ locale
455
+ }, tokens);
456
+ return patchSpec ? patchSpec(built) : built;
457
+ }, [
458
+ tokens,
459
+ barHeight,
460
+ bandPaint,
461
+ hasTicks,
462
+ criterionTitle,
463
+ scoreTitle,
464
+ scoreFormat,
465
+ locale,
466
+ patchSpec
467
+ ]);
468
+ const plotRows = useMemo(() => rows.map((row) => ({
469
+ key: row.key,
470
+ label: row.label,
471
+ score: row.score,
472
+ plotted: row.plotted,
473
+ targetScore: row.targetScore,
474
+ tone: row.tone
475
+ })), [rows]);
476
+ const bandRows = useMemo(() => summary ? summary.bands.map(({ key, label, from, to, tone }) => ({
477
+ key,
478
+ label,
479
+ from,
480
+ to,
481
+ tone
482
+ })) : [], [summary]);
483
+ const boundsRows = useMemo(() => summary ? [{
484
+ min: summary.domain[0],
485
+ max: summary.domain[1]
486
+ }] : [], [summary]);
487
+ const datasets = useMemo(() => ({
488
+ [REPORT_TABLE_DATASET]: plotRows,
489
+ [REPORT_BANDS_DATASET]: bandRows,
490
+ [REPORT_BOUNDS_DATASET]: boundsRows
491
+ }), [
492
+ plotRows,
493
+ bandRows,
494
+ boundsRows
495
+ ]);
496
+ const chartDescription = summary && hasChart ? buildReportChartDescription({
497
+ name,
498
+ summary,
499
+ formatters,
500
+ labels
501
+ }) : void 0;
502
+ const scaleMarks = useMemo(() => {
503
+ if (!summary || scaleTicks === "none") return [];
504
+ const [min, max] = summary.domain;
505
+ const span = max - min;
506
+ if (!(span > 0)) return [];
507
+ const values = [
508
+ min,
509
+ ...scaleTicks === "bands" ? summary.bands.map((band) => band.from).filter((value) => value > min && value < max) : [],
510
+ max
511
+ ];
512
+ return values.map((value, index) => ({
513
+ value,
514
+ percent: (value - min) / span * 100,
515
+ text: formatters.formatScore(value),
516
+ anchor: index === 0 ? "start" : index === values.length - 1 ? "end" : "middle"
517
+ }));
518
+ }, [
519
+ summary,
520
+ scaleTicks,
521
+ formatters
522
+ ]);
523
+ const visibleScaleMarks = useMemo(() => {
524
+ if (scaleMarks.length <= 2 || plotBox.width <= 0) return scaleMarks;
525
+ const gap = SCALE_LABEL_PX / plotBox.width * 100;
526
+ const first = scaleMarks[0];
527
+ const last = scaleMarks[scaleMarks.length - 1];
528
+ const kept = [first];
529
+ for (const mark of scaleMarks.slice(1, -1)) {
530
+ const previous = kept[kept.length - 1];
531
+ if (mark.percent - previous.percent >= gap && last.percent - mark.percent >= gap) kept.push(mark);
532
+ }
533
+ kept.push(last);
534
+ return kept;
535
+ }, [scaleMarks, plotBox.width]);
536
+ const showScale = hasChart && visibleScaleMarks.length > 0;
537
+ const showInsight = insight != null && insight !== false;
538
+ const reserveHeadline = headline !== "none" || previousScore != null;
539
+ const reserveChip = headline !== "none";
540
+ const headlineMinHeight = Math.max(headline !== "none" ? sizes.valueLinePx + (reserveChip ? CHIP_GAP_PX + sizes.chipLinePx : 0) : 0, previousScore != null ? sizes.deltaLinePx + 4 + sizes.comparisonLinePx : 0);
541
+ const reserveDetail = selectable && selectedKey !== null;
542
+ const rowCount = rows.length > 0 ? rows.length : Math.max(0, expectedCriteria);
543
+ const unscoredCount = summary?.overall.unscored ?? 0;
544
+ const showUnscored = showContent && unscoredCount > 0 && state !== "insufficient";
545
+ const bodyMinHeight = (reserveHeadline ? headlineMinHeight : 0) + (reserveDetail ? sizes.gapPx + sizes.comparisonLinePx : 0) + (rowCount > 0 ? sizes.gapPx + rowCount * sizes.step : 0) + (scaleTicks !== "none" ? sizes.scaleLinePx : 0) + (showUnscored ? sizes.comparisonLinePx : 0) + (showInsight ? sizes.gapPx + sizes.comparisonLinePx : 0);
546
+ const HeadingTag = `h${titleLevel}`;
547
+ const busy = state === "loading" || state === "refreshing";
548
+ const gridRows = { gridAutoRows: `${sizes.step}px` };
549
+ const anyValue = rows.some((row) => row.value != null);
550
+ const anyTarget = rows.some((row) => row.target != null);
551
+ const anyWeight = summary?.overall.weighted ?? false;
552
+ const showShortfall = scored > 1;
553
+ return /* @__PURE__ */ jsxs(Box, {
554
+ ref: setRootRef,
555
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
556
+ padding: sizes.padding,
557
+ "aria-labelledby": headingId,
558
+ "aria-describedby": descriptionId,
559
+ "aria-busy": busy || void 0,
560
+ "data-status": status,
561
+ "data-state": state,
562
+ "data-size": size,
563
+ "data-headline": headline,
564
+ "data-sort": sort,
565
+ "data-bands": bandPaint,
566
+ "data-selectable": interactive ? "" : void 0,
567
+ "data-selected": showContent ? selectedRow?.key : void 0,
568
+ "data-grade": showContent ? overallBand?.key : void 0,
569
+ "data-overall-source": showContent && summary ? summary.overall.source : void 0,
570
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
571
+ "data-insufficient": state === "insufficient" ? "" : void 0,
572
+ "data-unmeasured": showContent && unscoredCount > 0 ? unscoredCount : void 0,
573
+ "data-folded": showContent && summary && summary.folded > 0 ? summary.folded : void 0,
574
+ "data-stale": stale ? "" : void 0,
575
+ "data-polarity": showContent && delta ? delta.polarity : void 0,
576
+ ...rest,
577
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
578
+ children: [
579
+ /* @__PURE__ */ jsxs("div", {
580
+ className: "flex items-center justify-between gap-2",
581
+ children: [/* @__PURE__ */ jsxs("div", {
582
+ className: "flex min-w-0 items-center gap-1",
583
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
584
+ id: headingId,
585
+ className: cn(labelClasses, sizes.label, "line-clamp-2"),
586
+ children: label
587
+ }), hint && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
588
+ render: /* @__PURE__ */ jsx(Button, {
589
+ variant: "ghost",
590
+ size: "sm",
591
+ iconOnly: true,
592
+ circular: true,
593
+ "aria-label": labels.about(name),
594
+ "aria-describedby": hintId,
595
+ className: headerControlClasses
596
+ }),
597
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
598
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: hint })] }), /* @__PURE__ */ jsx("span", {
599
+ id: hintId,
600
+ className: "sr-only",
601
+ children: hint
602
+ })] })]
603
+ }), /* @__PURE__ */ jsxs("div", {
604
+ className: "flex shrink-0 items-center gap-1",
605
+ children: [
606
+ stale && /* @__PURE__ */ jsx(Badge, {
607
+ variant: "warning",
608
+ appearance: "soft",
609
+ size: "sm",
610
+ children: labels.stale
611
+ }),
612
+ hasTable && summary && /* @__PURE__ */ jsxs(Popover, {
613
+ open: tableOpen,
614
+ onOpenChange: setTableOpen,
615
+ children: [!hideTableToggle && /* @__PURE__ */ jsx(PopoverTrigger, {
616
+ render: /* @__PURE__ */ jsx(Button, {
617
+ variant: "ghost",
618
+ size: "sm",
619
+ iconOnly: true,
620
+ circular: true,
621
+ "aria-label": tableOpen ? labels.hideTable(name) : labels.showTable(name),
622
+ className: headerControlClasses
623
+ }),
624
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
625
+ }), popoverContainer && /* @__PURE__ */ jsxs(PopoverPopup, {
626
+ id: tableId,
627
+ anchor: rootRef,
628
+ container: popoverContainer,
629
+ side: "right",
630
+ align: "start",
631
+ collisionPadding: 8,
632
+ className: "pointer-events-auto w-[28rem] max-w-[min(28rem,var(--available-width))]",
633
+ "data-part": "data-popover",
634
+ children: [
635
+ /* @__PURE__ */ jsx(PopoverTitle, {
636
+ className: "mb-3",
637
+ children: name
638
+ }),
639
+ /* @__PURE__ */ jsxs("div", {
640
+ className: "mb-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-xs text-muted-foreground",
641
+ "data-part": "chart-legend",
642
+ children: [
643
+ /* @__PURE__ */ jsx("span", {
644
+ className: "font-medium",
645
+ children: labels.bandsLegend
646
+ }),
647
+ summary.bands.slice().reverse().map((band) => /* @__PURE__ */ jsx(ReportKey, {
648
+ color: toneColorOf(tokens, band.tone),
649
+ label: `${band.label} ${labels.bandRange(formatters.formatScore(band.from), formatters.formatScore(band.to))}`
650
+ }, band.key)),
651
+ hasTicks && /* @__PURE__ */ jsx(ReportKey, {
652
+ label: labels.expected,
653
+ mark: "tick",
654
+ color: tokens?.foreground
655
+ })
656
+ ]
657
+ }),
658
+ /* @__PURE__ */ jsx(TableContainer, {
659
+ variant: "plain",
660
+ tabIndex: 0,
661
+ role: "region",
662
+ "aria-label": labels.tableCaption(name),
663
+ className: "max-h-[min(18rem,calc(var(--available-height)-4.5rem))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
664
+ "data-part": "table",
665
+ children: /* @__PURE__ */ jsxs(Table, {
666
+ size: "sm",
667
+ stickyHeader: true,
668
+ hoverable: false,
669
+ children: [
670
+ /* @__PURE__ */ jsx(TableCaption, {
671
+ side: "top",
672
+ className: "sr-only",
673
+ children: labels.tableCaption(name)
674
+ }),
675
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
676
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tableCriterion }),
677
+ anyValue && /* @__PURE__ */ jsx(TableHead, {
678
+ className: "text-right",
679
+ children: labels.tableValue
680
+ }),
681
+ anyTarget && /* @__PURE__ */ jsx(TableHead, {
682
+ className: "text-right",
683
+ children: labels.tableTarget
684
+ }),
685
+ /* @__PURE__ */ jsx(TableHead, {
686
+ className: "text-right",
687
+ children: labels.tableScore
688
+ }),
689
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tableGrade }),
690
+ anyWeight && /* @__PURE__ */ jsx(TableHead, {
691
+ className: "text-right",
692
+ children: labels.tableWeight
693
+ }),
694
+ showShortfall && /* @__PURE__ */ jsx(TableHead, {
695
+ className: "text-right",
696
+ children: labels.tableShortfall
697
+ })
698
+ ] }) }),
699
+ /* @__PURE__ */ jsx(TableBody, { children: rows.map((row) => /* @__PURE__ */ jsxs(TableRow, {
700
+ "data-role": row.role,
701
+ children: [
702
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("span", {
703
+ className: "inline-flex items-baseline gap-1.5",
704
+ children: [row.key === selectedRow?.key && interactive && /* @__PURE__ */ jsx("span", {
705
+ "aria-hidden": "true",
706
+ className: "inline-block size-1.5 shrink-0 self-center rounded-full",
707
+ style: { background: tokens?.selection }
708
+ }), /* @__PURE__ */ jsxs("span", { children: [row.label, row.key === selectedRow?.key && interactive && /* @__PURE__ */ jsxs("span", {
709
+ className: "sr-only",
710
+ children: [
711
+ " (",
712
+ labels.selected,
713
+ ")"
714
+ ]
715
+ })] })]
716
+ }) }),
717
+ anyValue && /* @__PURE__ */ jsx(TableCell, {
718
+ className: "text-right tabular-nums",
719
+ children: formatters.formatValue(row.value, row.format)
720
+ }),
721
+ anyTarget && /* @__PURE__ */ jsx(TableCell, {
722
+ className: "text-right tabular-nums",
723
+ children: formatters.formatValue(row.target, row.format)
724
+ }),
725
+ /* @__PURE__ */ jsx(TableCell, {
726
+ className: "text-right tabular-nums",
727
+ children: formatters.formatScore(row.score)
728
+ }),
729
+ /* @__PURE__ */ jsx(TableCell, { children: row.band ? /* @__PURE__ */ jsxs("span", {
730
+ className: "inline-flex items-center gap-1.5",
731
+ children: [/* @__PURE__ */ jsx(GradeSwatch, { color: toneColorOf(tokens, row.tone) }), row.band.label]
732
+ }) : labels.unscored }),
733
+ anyWeight && /* @__PURE__ */ jsx(TableCell, {
734
+ className: "text-right tabular-nums",
735
+ children: formatters.formatWeight(row.weight) ?? "×1"
736
+ }),
737
+ showShortfall && /* @__PURE__ */ jsx(TableCell, {
738
+ className: "text-right tabular-nums",
739
+ children: formatters.formatScore(row.shortfall)
740
+ })
741
+ ]
742
+ }, row.key)) }),
743
+ overallScore != null && /* @__PURE__ */ jsx(TableFooter, { children: /* @__PURE__ */ jsxs(TableRow, {
744
+ "data-role": "overall",
745
+ children: [
746
+ /* @__PURE__ */ jsx(TableCell, { children: labels.tableOverall }),
747
+ anyValue && /* @__PURE__ */ jsx(TableCell, {}),
748
+ anyTarget && /* @__PURE__ */ jsx(TableCell, {}),
749
+ /* @__PURE__ */ jsx(TableCell, {
750
+ className: "text-right tabular-nums",
751
+ children: scoreText
752
+ }),
753
+ /* @__PURE__ */ jsx(TableCell, { children: overallBand ? /* @__PURE__ */ jsxs("span", {
754
+ className: "inline-flex items-center gap-1.5",
755
+ children: [/* @__PURE__ */ jsx(GradeSwatch, { color: toneColorOf(tokens, overallBand.tone) }), overallBand.label]
756
+ }) : "—" }),
757
+ anyWeight && /* @__PURE__ */ jsx(TableCell, {}),
758
+ showShortfall && /* @__PURE__ */ jsx(TableCell, {})
759
+ ]
760
+ }) })
761
+ ]
762
+ })
763
+ })
764
+ ]
765
+ })]
766
+ }),
767
+ action
768
+ ]
769
+ })]
770
+ }),
771
+ /* @__PURE__ */ jsx("p", {
772
+ id: descriptionId,
773
+ className: "sr-only",
774
+ children: cardDescription
775
+ }),
776
+ /* @__PURE__ */ jsx("p", {
777
+ role: "status",
778
+ "aria-live": "polite",
779
+ className: "sr-only",
780
+ "data-part": "announcement",
781
+ children: announcement
782
+ }),
783
+ /* @__PURE__ */ jsxs("div", {
784
+ className: "relative flex min-w-0 flex-col",
785
+ style: { minHeight: bodyMinHeight },
786
+ children: [
787
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
788
+ className: cn("flex flex-col", sizes.gap),
789
+ children: [
790
+ reserveHeadline && /* @__PURE__ */ jsxs("div", {
791
+ className: "flex min-w-0 items-center justify-between gap-2",
792
+ style: { minHeight: headlineMinHeight },
793
+ children: [headline !== "none" && /* @__PURE__ */ jsxs("div", {
794
+ className: "flex w-2/5 flex-col items-start gap-1",
795
+ children: [/* @__PURE__ */ jsx(Skeleton, {
796
+ variant: "rounded",
797
+ className: "w-full",
798
+ style: { height: sizes.valueLinePx }
799
+ }), reserveChip && /* @__PURE__ */ jsx(Skeleton, {
800
+ variant: "rounded",
801
+ className: "w-16 max-w-full",
802
+ style: { height: sizes.chipLinePx }
803
+ })]
804
+ }), previousScore != null && /* @__PURE__ */ jsxs("div", {
805
+ className: cn("flex w-2/5 flex-col gap-1", headline !== "none" ? "items-end" : "items-start"),
806
+ children: [/* @__PURE__ */ jsx(Skeleton, {
807
+ variant: "rounded",
808
+ className: "w-20 max-w-full",
809
+ style: { height: sizes.deltaLinePx }
810
+ }), /* @__PURE__ */ jsx(Skeleton, {
811
+ variant: "rounded",
812
+ className: "w-full",
813
+ style: { height: sizes.comparisonLinePx }
814
+ })]
815
+ })]
816
+ }),
817
+ reserveDetail && /* @__PURE__ */ jsx(Skeleton, {
818
+ variant: "rounded",
819
+ className: "w-3/4",
820
+ style: { height: sizes.comparisonLinePx }
821
+ }),
822
+ rowCount > 0 && /* @__PURE__ */ jsx("div", {
823
+ className: rowGridClasses,
824
+ style: gridRows,
825
+ "data-part": "rows-skeleton",
826
+ children: Array.from({ length: rowCount }, (_, index) => /* @__PURE__ */ jsxs("div", {
827
+ className: "col-span-4 grid grid-cols-subgrid items-center",
828
+ children: [
829
+ /* @__PURE__ */ jsx(Skeleton, {
830
+ variant: "rounded",
831
+ className: "h-3 w-20"
832
+ }),
833
+ /* @__PURE__ */ jsx(Skeleton, {
834
+ variant: "rounded",
835
+ style: {
836
+ height: sizes.barHeight,
837
+ width: `${Math.max(35, 92 - index * 17)}%`
838
+ }
839
+ }),
840
+ /* @__PURE__ */ jsx(Skeleton, {
841
+ variant: "rounded",
842
+ className: "h-3 w-6"
843
+ }),
844
+ showGrades && /* @__PURE__ */ jsx(Skeleton, {
845
+ variant: "rounded",
846
+ className: "h-3 w-4"
847
+ })
848
+ ]
849
+ }, index))
850
+ }),
851
+ showInsight && /* @__PURE__ */ jsxs("div", {
852
+ className: "flex items-start gap-2",
853
+ children: [/* @__PURE__ */ jsx(Skeleton, {
854
+ variant: "rounded",
855
+ className: "size-4 shrink-0"
856
+ }), /* @__PURE__ */ jsx(Skeleton, {
857
+ variant: "rounded",
858
+ className: "w-full",
859
+ style: { height: sizes.comparisonLinePx }
860
+ })]
861
+ })
862
+ ]
863
+ }),
864
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
865
+ variant: "server-error",
866
+ scope: "inline",
867
+ title: labels.error,
868
+ description: false,
869
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
870
+ size: "sm",
871
+ onClick: onRetry,
872
+ children: labels.retry
873
+ }) : void 0,
874
+ className: cn(tileStateClasses, "my-auto")
875
+ }),
876
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
877
+ variant: "empty",
878
+ scope: "inline",
879
+ title: labels.noData,
880
+ description: false,
881
+ className: cn(tileStateClasses, "my-auto")
882
+ }),
883
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
884
+ variant: "no-results",
885
+ scope: "inline",
886
+ title: labels.noMatch,
887
+ description: false,
888
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
889
+ variant: "outline",
890
+ size: "sm",
891
+ onClick: onClearFilters,
892
+ children: labels.clearFilters
893
+ }) : void 0,
894
+ className: cn(tileStateClasses, "my-auto")
895
+ }),
896
+ showContent && /* @__PURE__ */ jsxs("div", {
897
+ className: cn("flex min-w-0 flex-col", sizes.gap),
898
+ children: [
899
+ (valueText != null || delta != null || state === "insufficient") && /* @__PURE__ */ jsxs("div", {
900
+ className: "flex min-w-0 items-center justify-between gap-2",
901
+ style: { minHeight: headlineMinHeight },
902
+ "data-part": "headline",
903
+ children: [valueText != null && /* @__PURE__ */ jsxs("div", {
904
+ className: "flex min-w-0 flex-col items-start gap-1",
905
+ "data-part": "value-stack",
906
+ children: [/* @__PURE__ */ jsxs("div", {
907
+ className: "flex min-w-0 flex-wrap items-baseline gap-x-1",
908
+ "data-part": "value",
909
+ children: [/* @__PURE__ */ jsx("span", {
910
+ className: cn(valueClasses, sizes.value, "min-w-0 wrap-break-word"),
911
+ children: valueText
912
+ }), unitText != null && /* @__PURE__ */ jsx("span", {
913
+ className: cn("font-sans text-muted-foreground", sizes.unit),
914
+ children: unitText
915
+ })]
916
+ }), overallBand && /* @__PURE__ */ jsxs(Badge, {
917
+ variant: toneBadgeVariant[overallBand.tone],
918
+ appearance: "soft",
919
+ size: sizes.chip,
920
+ className: "max-w-full",
921
+ "data-part": "grade-chip",
922
+ "data-tone": overallBand.tone,
923
+ children: [/* @__PURE__ */ jsx(GradeSwatch, { color: toneColorOf(tokens, overallBand.tone) }), /* @__PURE__ */ jsx("span", {
924
+ className: "truncate",
925
+ children: headline === "grade" ? `${scoreText}${writeCeiling ? labels.outOf(ceilingText) : ""}` : overallBand.label
926
+ })]
927
+ })]
928
+ }), state === "insufficient" ? /* @__PURE__ */ jsx("p", {
929
+ className: cn(captionClasses, "mt-0"),
930
+ "data-part": "insufficient",
931
+ children: labels.insufficient
932
+ }) : delta != null ? /* @__PURE__ */ jsxs("div", {
933
+ className: cn("flex min-w-0 shrink-0 flex-col gap-1 font-sans", valueText != null ? "ms-auto max-w-1/2 items-end text-right" : "items-start text-left", sizes.body),
934
+ "data-part": "delta",
935
+ "data-direction": delta.direction,
936
+ children: [/* @__PURE__ */ jsxs(Badge, {
937
+ variant: deltaBadgeVariant[delta.polarity],
938
+ appearance: "soft",
939
+ size: sizes.badge,
940
+ children: [
941
+ /* @__PURE__ */ jsx(DeltaGlyph, { direction: delta.direction }),
942
+ /* @__PURE__ */ jsxs("span", {
943
+ className: "sr-only",
944
+ children: [delta.direction === "up" ? labels.increase : delta.direction === "down" ? labels.decrease : labels.unchanged, " "]
945
+ }),
946
+ delta.text,
947
+ labels.points
948
+ ]
949
+ }), /* @__PURE__ */ jsx("span", {
950
+ className: "max-w-full text-xs text-muted-foreground wrap-break-word",
951
+ children: previousLabel ?? labels.versusPrevious
952
+ })]
953
+ }) : null]
954
+ }),
955
+ reserveDetail && /* @__PURE__ */ jsx("p", {
956
+ className: cn("min-w-0 truncate font-sans text-muted-foreground", sizes.body),
957
+ style: { minHeight: sizes.comparisonLinePx },
958
+ "data-part": "detail",
959
+ children: detailText
960
+ }),
961
+ rows.length > 0 && /* @__PURE__ */ jsxs("div", {
962
+ ref: rowsRef,
963
+ className: "relative w-full",
964
+ "data-part": "report",
965
+ children: [
966
+ /* @__PURE__ */ jsx("ul", {
967
+ role: interactive ? "radiogroup" : "list",
968
+ "aria-label": interactive ? labels.criteriaGroup(name) : void 0,
969
+ className: cn(rowGridClasses, "font-sans", sizes.rowText),
970
+ style: gridRows,
971
+ "data-part": "rows",
972
+ children: rows.map((row, index) => {
973
+ const isSelected = row.key === selectedRow?.key;
974
+ return /* @__PURE__ */ jsxs("li", {
975
+ ref: (node) => {
976
+ if (node) rowRefs.current.set(row.key, node);
977
+ else rowRefs.current.delete(row.key);
978
+ },
979
+ role: interactive ? "radio" : void 0,
980
+ "aria-checked": interactive ? isSelected : void 0,
981
+ tabIndex: interactive ? isSelected ? 0 : -1 : void 0,
982
+ onClick: interactive && !isSelected ? () => selectRow(row.key) : void 0,
983
+ onKeyDown: interactive ? (event) => handleRowKeyDown(event, row) : void 0,
984
+ className: cn("col-span-4 grid grid-cols-subgrid items-center", interactive && selectableRowClasses, interactive && isSelected && selectedRowClasses),
985
+ "data-part": "row",
986
+ "data-role": row.role,
987
+ "data-tone": row.score == null ? void 0 : row.tone,
988
+ children: [
989
+ /* @__PURE__ */ jsx("span", {
990
+ className: cn("min-w-0 truncate", isSelected && interactive ? "font-medium text-foreground" : "text-muted-foreground"),
991
+ "data-part": "row-label",
992
+ children: row.label
993
+ }),
994
+ /* @__PURE__ */ jsx("span", {
995
+ ref: index === 0 ? plotCellRef : void 0,
996
+ "aria-hidden": "true",
997
+ className: "min-w-0 self-stretch",
998
+ "data-part": "row-plot"
999
+ }),
1000
+ /* @__PURE__ */ jsx("span", {
1001
+ className: cn("text-right font-medium tabular-nums", row.score == null ? "text-muted-foreground" : "text-foreground"),
1002
+ "data-part": "row-score",
1003
+ children: formatters.formatScore(row.score)
1004
+ }),
1005
+ showGrades ? /* @__PURE__ */ jsx("span", {
1006
+ className: "inline-flex items-center gap-1 text-muted-foreground",
1007
+ "data-part": "row-grade",
1008
+ children: row.band ? /* @__PURE__ */ jsxs(Fragment$1, { children: [
1009
+ /* @__PURE__ */ jsx(GradeSwatch, { color: toneColorOf(tokens, row.tone) }),
1010
+ /* @__PURE__ */ jsx("span", {
1011
+ className: "truncate",
1012
+ children: row.band.short
1013
+ }),
1014
+ /* @__PURE__ */ jsx("span", {
1015
+ className: "sr-only",
1016
+ children: row.band.label
1017
+ })
1018
+ ] }) : /* @__PURE__ */ jsx("span", {
1019
+ className: "sr-only",
1020
+ children: labels.unscored
1021
+ })
1022
+ }) : /* @__PURE__ */ jsx("span", { "aria-hidden": "true" })
1023
+ ]
1024
+ }, row.key);
1025
+ })
1026
+ }),
1027
+ hasChart && plotBox.width > 0 && spec && /* @__PURE__ */ jsx(ArcVegaChart, {
1028
+ spec,
1029
+ options: embedOptions,
1030
+ datasets,
1031
+ description: chartDescription,
1032
+ width: plotBox.width,
1033
+ height: plotHeight,
1034
+ onEmbed,
1035
+ onClick: interactive ? handlePlotClick : void 0,
1036
+ className: cn("absolute top-0 [&_svg]:block [&_.vega-bindings:empty]:hidden", interactive && "cursor-pointer"),
1037
+ style: {
1038
+ left: plotBox.left,
1039
+ width: plotBox.width,
1040
+ height: plotHeight
1041
+ },
1042
+ "data-part": "plot",
1043
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
1044
+ variant: "rounded",
1045
+ className: "absolute top-0",
1046
+ style: {
1047
+ left: plotBox.left,
1048
+ width: plotBox.width,
1049
+ height: plotHeight
1050
+ }
1051
+ })
1052
+ }),
1053
+ showScale && /* @__PURE__ */ jsx("div", {
1054
+ "aria-hidden": "true",
1055
+ className: "relative font-sans text-[0.6875rem] text-muted-foreground tabular-nums",
1056
+ style: {
1057
+ marginLeft: plotBox.left,
1058
+ width: plotBox.width,
1059
+ height: sizes.scaleLinePx
1060
+ },
1061
+ "data-part": "scale",
1062
+ children: visibleScaleMarks.map((mark) => /* @__PURE__ */ jsx("span", {
1063
+ className: "absolute top-0 whitespace-nowrap",
1064
+ style: {
1065
+ left: `${mark.percent}%`,
1066
+ transform: mark.anchor === "start" ? void 0 : mark.anchor === "end" ? "translateX(-100%)" : "translateX(-50%)"
1067
+ },
1068
+ children: mark.text
1069
+ }, mark.value))
1070
+ })
1071
+ ]
1072
+ }),
1073
+ showUnscored && summary && /* @__PURE__ */ jsx("p", {
1074
+ className: cn(captionClasses, "mt-0"),
1075
+ "data-part": "unmeasured",
1076
+ children: labels.notMeasured(unscoredCount, summary.rows.length)
1077
+ }),
1078
+ showInsight && /* @__PURE__ */ jsxs("div", {
1079
+ className: cn("flex items-start gap-2 font-sans text-foreground", sizes.body),
1080
+ "data-part": "insight",
1081
+ children: [/* @__PURE__ */ jsx(AILoader, {
1082
+ size: "sm",
1083
+ state: "default",
1084
+ label: labels.insightMark,
1085
+ className: cn("shrink-0", size === "sm" ? "" : "mt-0.5")
1086
+ }), /* @__PURE__ */ jsx("p", {
1087
+ className: "min-w-0 flex-1 text-xs leading-[1.8] [&_strong]:font-semibold [&_strong]:text-foreground",
1088
+ children: insight
1089
+ })]
1090
+ })
1091
+ ]
1092
+ }),
1093
+ state === "refreshing" && /* @__PURE__ */ jsx("div", {
1094
+ "aria-hidden": "true",
1095
+ className: "absolute -inset-1 rounded-md bg-veil"
1096
+ })
1097
+ ]
1098
+ }),
1099
+ asOfValid && asOfDate && /* @__PURE__ */ jsx("p", {
1100
+ className: cn(captionClasses, "text-right"),
1101
+ "data-part": "as-of",
1102
+ children: /* @__PURE__ */ jsx("time", {
1103
+ dateTime: asOfDate.toISOString(),
1104
+ children: asOfText
1105
+ })
1106
+ }),
1107
+ /* @__PURE__ */ jsx("div", {
1108
+ ref: setPopoverContainer,
1109
+ className: "pointer-events-none absolute inset-0",
1110
+ "data-part": "popover-host"
1111
+ }),
1112
+ tooltip
1113
+ ]
1114
+ });
1115
+ });
1116
+ ReportCard.displayName = "ReportCard";
1117
+ //#endregion
1118
+ export { ReportCard };