@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,1325 @@
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 { COMPARISON_REFERENCE_DATASET, COMPARISON_TABLE_DATASET, COMPARISON_ZERO_DATASET, buildComparisonPlotSpec, comparisonOrientation, comparisonUsesFrom, resolveComparisonScale } from "./ComparisonCard.chart.js";
7
+ import { buildComparisonChartDescription, buildComparisonDescription, createComparisonFormatters, deriveComparisonDelta, deriveComparisonState, normalizeItems, resolveComparisonLabels } from "./ComparisonCard.model.js";
8
+ import { forwardRef, useCallback, useEffect, useId, useLayoutEffect, useMemo, useRef, useState } from "react";
9
+ import { AILoader, Badge, Box, Button, Menu, MenuGroupLabel, MenuPopup, MenuRadioGroup, MenuRadioItem, MenuSeparator, MenuTrigger, Popover, PopoverPopup, PopoverTitle, PopoverTrigger, Skeleton, SkeletonGroup, SystemState, Table, TableBody, TableCaption, TableCell, TableContainer, 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/ComparisonCard/ComparisonCard.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
+ pointSize: 64,
30
+ plotBlock: 104,
31
+ rowText: "text-xs"
32
+ },
33
+ md: {
34
+ padding: "md",
35
+ gap: "gap-2",
36
+ gapPx: 8,
37
+ label: "text-sm",
38
+ value: "text-3xl leading-9",
39
+ valueLinePx: 36,
40
+ unit: "text-sm",
41
+ body: "text-sm",
42
+ badge: "md",
43
+ deltaLinePx: 24,
44
+ comparisonLinePx: 20,
45
+ chip: "sm",
46
+ chipLinePx: 20,
47
+ step: 28,
48
+ barHeight: 12,
49
+ pointSize: 90,
50
+ plotBlock: 128,
51
+ rowText: "text-sm"
52
+ },
53
+ lg: {
54
+ padding: "lg",
55
+ gap: "gap-2",
56
+ gapPx: 8,
57
+ label: "text-sm",
58
+ value: "text-4xl leading-10",
59
+ valueLinePx: 40,
60
+ unit: "text-base",
61
+ body: "text-sm",
62
+ badge: "lg",
63
+ deltaLinePx: 28,
64
+ comparisonLinePx: 20,
65
+ chip: "md",
66
+ chipLinePx: 24,
67
+ step: 32,
68
+ barHeight: 14,
69
+ pointSize: 120,
70
+ plotBlock: 152,
71
+ rowText: "text-sm"
72
+ }
73
+ };
74
+ const CHIP_GAP_PX = 4;
75
+ const deltaStackClasses = "flex min-w-0 shrink-0 flex-col gap-1 font-sans focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring";
76
+ const labelClasses = "min-w-0 font-sans font-medium text-muted-foreground";
77
+ const valueClasses = "font-heading font-semibold tracking-tight text-foreground";
78
+ const captionClasses = "font-sans text-xs text-muted-foreground mt-1";
79
+ const rowLabelClasses = {
80
+ focus: "font-medium text-foreground",
81
+ baseline: "text-foreground",
82
+ item: "text-muted-foreground",
83
+ other: "text-muted-foreground"
84
+ };
85
+ const rowValueClasses = {
86
+ focus: "font-semibold text-foreground",
87
+ baseline: "font-medium text-foreground",
88
+ item: "font-medium text-foreground",
89
+ other: "font-medium text-muted-foreground"
90
+ };
91
+ 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";
92
+ const disabledRowClasses = "cursor-default hover:before:opacity-0";
93
+ const polarityBadgeVariant = {
94
+ positive: "success",
95
+ negative: "destructive",
96
+ neutral: "neutral"
97
+ };
98
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
99
+ const tileStateClasses = "w-full max-w-none gap-2 px-2 py-2 [--artwork-box:3rem]";
100
+ const rowGridClasses = "grid grid-cols-[fit-content(40%)_minmax(0,1fr)_auto] gap-x-3";
101
+ const blockRowGridClasses = "grid grid-cols-[minmax(0,1fr)_auto] gap-x-3";
102
+ const MISSING_NAME = "Comparison";
103
+ const EMPTY_ROWS = [];
104
+ /** The swatch inside the subject chip — the exact hue AND the exact shape the subject's mark wears. */
105
+ function FocusSwatch({ color, mark = "bar" }) {
106
+ return /* @__PURE__ */ jsx("svg", {
107
+ viewBox: "0 0 12 12",
108
+ fill: "none",
109
+ "aria-hidden": "true",
110
+ className: "size-3 shrink-0",
111
+ style: { color },
112
+ children: mark === "bar" || mark === "column" ? /* @__PURE__ */ jsx("rect", {
113
+ x: "1",
114
+ y: "3",
115
+ width: "10",
116
+ height: "6",
117
+ rx: "1.5",
118
+ fill: "currentColor"
119
+ }) : /* @__PURE__ */ jsx("circle", {
120
+ cx: "6",
121
+ cy: "6",
122
+ r: "3.5",
123
+ fill: "currentColor"
124
+ })
125
+ });
126
+ }
127
+ /**
128
+ * Match the plot's mark shape so its meaning never depends on color alone —
129
+ * the key beside a lollipop is a stemmed dot, beside a dumbbell a hollow dot
130
+ * joined to a solid one, and the target is always the dotted rule the plot
131
+ * draws.
132
+ */
133
+ function ComparisonKey({ label, mark = "bar", target = false, color }) {
134
+ return /* @__PURE__ */ jsxs("span", {
135
+ className: "inline-flex items-center gap-2",
136
+ children: [/* @__PURE__ */ jsx("svg", {
137
+ width: "24",
138
+ height: "12",
139
+ viewBox: "0 0 24 12",
140
+ fill: "none",
141
+ "aria-hidden": "true",
142
+ className: "shrink-0",
143
+ style: { color },
144
+ children: target ? /* @__PURE__ */ jsx("line", {
145
+ x1: "2",
146
+ y1: "6",
147
+ x2: "22",
148
+ y2: "6",
149
+ stroke: "currentColor",
150
+ strokeWidth: 1.5,
151
+ strokeLinecap: "round",
152
+ strokeDasharray: "0 4"
153
+ }) : mark === "bar" || mark === "column" ? /* @__PURE__ */ jsx("rect", {
154
+ x: "1",
155
+ y: "2",
156
+ width: "22",
157
+ height: "8",
158
+ rx: "1",
159
+ fill: "currentColor"
160
+ }) : mark === "dot" ? /* @__PURE__ */ jsx("circle", {
161
+ cx: "12",
162
+ cy: "6",
163
+ r: "4",
164
+ fill: "currentColor"
165
+ }) : mark === "lollipop" ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("line", {
166
+ x1: "2",
167
+ y1: "6",
168
+ x2: "18",
169
+ y2: "6",
170
+ stroke: "currentColor",
171
+ strokeWidth: 2,
172
+ strokeLinecap: "round"
173
+ }), /* @__PURE__ */ jsx("circle", {
174
+ cx: "19",
175
+ cy: "6",
176
+ r: "3.5",
177
+ fill: "currentColor"
178
+ })] }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [
179
+ /* @__PURE__ */ jsx("line", {
180
+ x1: "5",
181
+ y1: "6",
182
+ x2: "19",
183
+ y2: "6",
184
+ stroke: "currentColor",
185
+ strokeWidth: 2,
186
+ strokeLinecap: "round"
187
+ }),
188
+ /* @__PURE__ */ jsx("circle", {
189
+ cx: "4",
190
+ cy: "6",
191
+ r: "3",
192
+ fill: "var(--color-background)",
193
+ stroke: "currentColor",
194
+ strokeWidth: 1.5
195
+ }),
196
+ /* @__PURE__ */ jsx("circle", {
197
+ cx: "20",
198
+ cy: "6",
199
+ r: "3.5",
200
+ fill: "currentColor"
201
+ })
202
+ ] })
203
+ }), label]
204
+ });
205
+ }
206
+ function InfoIcon(props) {
207
+ return /* @__PURE__ */ jsxs("svg", {
208
+ viewBox: "0 0 24 24",
209
+ fill: "none",
210
+ stroke: "currentColor",
211
+ strokeWidth: 2,
212
+ strokeLinecap: "round",
213
+ strokeLinejoin: "round",
214
+ "aria-hidden": "true",
215
+ ...props,
216
+ children: [/* @__PURE__ */ jsx("circle", {
217
+ cx: "12",
218
+ cy: "12",
219
+ r: "9"
220
+ }), /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01" })]
221
+ });
222
+ }
223
+ function ChevronDownIcon(props) {
224
+ return /* @__PURE__ */ jsx("svg", {
225
+ viewBox: "0 0 24 24",
226
+ fill: "none",
227
+ stroke: "currentColor",
228
+ strokeWidth: 2,
229
+ strokeLinecap: "round",
230
+ strokeLinejoin: "round",
231
+ "aria-hidden": "true",
232
+ ...props,
233
+ children: /* @__PURE__ */ jsx("path", { d: "m6 9 6 6 6-6" })
234
+ });
235
+ }
236
+ const menuValueColumnClasses = "w-16 shrink-0 text-right tabular-nums";
237
+ const menuChangeColumnClasses = "w-16 shrink-0 text-right tabular-nums";
238
+ const polarityTextClasses = {
239
+ positive: "text-chart-positive",
240
+ negative: "text-chart-negative",
241
+ neutral: "text-foreground"
242
+ };
243
+ function DeltaGlyph({ direction, ...props }) {
244
+ return /* @__PURE__ */ jsxs("svg", {
245
+ viewBox: "0 0 24 24",
246
+ fill: "none",
247
+ stroke: "currentColor",
248
+ strokeWidth: 2.5,
249
+ strokeLinecap: "round",
250
+ strokeLinejoin: "round",
251
+ "aria-hidden": "true",
252
+ ...props,
253
+ children: [
254
+ direction === "up" && /* @__PURE__ */ jsx("path", { d: "M7 17 17 7M7 7h10v10" }),
255
+ direction === "down" && /* @__PURE__ */ jsx("path", { d: "M7 7 17 17M7 17h10V7" }),
256
+ direction === "flat" && /* @__PURE__ */ jsx("path", { d: "M5 12h14M13 6l6 6-6 6" })
257
+ ]
258
+ });
259
+ }
260
+ function useMergedRef(forwarded) {
261
+ const inner = useRef(null);
262
+ return [inner, useCallback((node) => {
263
+ inner.current = node;
264
+ if (typeof forwarded === "function") forwarded(node);
265
+ else if (forwarded) forwarded.current = node;
266
+ }, [forwarded])];
267
+ }
268
+ function usePlotBox(cellRef, wrapperRef, enabled, resetKey) {
269
+ const [box, setBox] = useState({
270
+ left: 0,
271
+ width: 0
272
+ });
273
+ useLayoutEffect(() => {
274
+ if (!enabled) return;
275
+ const cell = cellRef.current;
276
+ const wrapper = wrapperRef.current;
277
+ if (!cell || !wrapper) return;
278
+ let frame = 0;
279
+ const measure = () => {
280
+ const cellRect = cell.getBoundingClientRect();
281
+ const wrapperRect = wrapper.getBoundingClientRect();
282
+ const next = {
283
+ left: cellRect.left - wrapperRect.left,
284
+ width: Math.round(cellRect.width)
285
+ };
286
+ setBox((prev) => prev.left === next.left && prev.width === next.width ? prev : next);
287
+ };
288
+ measure();
289
+ if (typeof ResizeObserver === "undefined") return;
290
+ const observer = new ResizeObserver(() => {
291
+ cancelAnimationFrame(frame);
292
+ frame = requestAnimationFrame(measure);
293
+ });
294
+ observer.observe(cell);
295
+ observer.observe(wrapper);
296
+ return () => {
297
+ cancelAnimationFrame(frame);
298
+ observer.disconnect();
299
+ };
300
+ }, [
301
+ cellRef,
302
+ wrapperRef,
303
+ enabled,
304
+ resetKey
305
+ ]);
306
+ return box;
307
+ }
308
+ /**
309
+ * A comparison tile for dashboard grids — the second subtype of the KPI card
310
+ * family beside `MetricCard`: one metric across a few items (periods,
311
+ * segments, sources, plans) on one shared scale, with every name and value as
312
+ * text beside the plot, the focus item in the emphasis hue, its delta against
313
+ * a baseline in a polarity badge, an optional target rule, and every loading,
314
+ * empty, error and stale state — layout-stable inside one `Box` frame, with a
315
+ * generated text equivalent and a data table in an info popover.
316
+ *
317
+ * `mark` picks the form the data calls for: `bar` (default), `column` and
318
+ * `lollipop` put length on a zero baseline; `dot` drops the baseline so
319
+ * values clustered far from zero separate; `dumbbell` and `slope` read a
320
+ * second reading per item (`ComparisonItem.from`) and draw the gap and the
321
+ * move between the two.
322
+ */
323
+ const ComparisonCard = forwardRef(({ label, name: nameProp, hint, items, focus, defaultFocus, onFocusChange, selectable = true, baseline, defaultBaseline, onBaselineChange, headline = "focus", focusChip = true, comparisonLabel, deltaMode = "percent", goodDirection = "up", format = "compact", formatValue, unit, target, mark = "bar", scale, fromLabel, toLabel, sort = "none", maxItems, 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) => {
324
+ const labels = useMemo(() => resolveComparisonLabels(labelOverrides), [labelOverrides]);
325
+ const sizes = sizeStyles[size];
326
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
327
+ const id = useId();
328
+ const headingId = `${id}-label`;
329
+ const descriptionId = `${id}-description`;
330
+ const hintId = `${id}-hint`;
331
+ const tableId = `${id}-table`;
332
+ const deltaExplanationId = `${id}-delta-explanation`;
333
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
334
+ const rowsRef = useRef(null);
335
+ const plotCellRef = useRef(null);
336
+ const blockCellRef = useRef(null);
337
+ const [tableOpen, setTableOpen] = useState(defaultTableOpen);
338
+ const [popoverContainer, setPopoverContainer] = useState(null);
339
+ const isFocusControlled = focus !== void 0;
340
+ const [innerFocus, setInnerFocus] = useState(defaultFocus);
341
+ const focusKey = isFocusControlled ? focus : innerFocus;
342
+ const rowRefs = useRef(/* @__PURE__ */ new Map());
343
+ const pendingAnnounce = useRef(false);
344
+ const [announcement, setAnnouncement] = useState("");
345
+ const selectFocus = useCallback((key) => {
346
+ pendingAnnounce.current = true;
347
+ if (!isFocusControlled) setInnerFocus(key);
348
+ onFocusChange?.(key);
349
+ }, [isFocusControlled, onFocusChange]);
350
+ const isBaselineControlled = baseline !== void 0;
351
+ const [innerBaseline, setInnerBaseline] = useState(defaultBaseline);
352
+ const baselineKey = isBaselineControlled ? baseline : innerBaseline;
353
+ const selectBaseline = useCallback((key) => {
354
+ pendingAnnounce.current = true;
355
+ if (!isBaselineControlled) setInnerBaseline(key);
356
+ onBaselineChange?.(key);
357
+ }, [isBaselineControlled, onBaselineChange]);
358
+ const otherLabel = labels.other;
359
+ const summary = useMemo(() => normalizeItems(items, {
360
+ focus: focusKey,
361
+ baseline: baselineKey,
362
+ sort,
363
+ maxItems,
364
+ otherLabel
365
+ }), [
366
+ items,
367
+ focusKey,
368
+ baselineKey,
369
+ sort,
370
+ maxItems,
371
+ otherLabel
372
+ ]);
373
+ const formatters = useMemo(() => createComparisonFormatters({
374
+ format,
375
+ locale,
376
+ formatValue,
377
+ deltaMode,
378
+ percentagePoints: labels.percentagePoints
379
+ }), [
380
+ format,
381
+ locale,
382
+ formatValue,
383
+ deltaMode,
384
+ labels.percentagePoints
385
+ ]);
386
+ const rows = summary?.rows ?? EMPTY_ROWS;
387
+ const focusRow = summary?.focus ?? null;
388
+ const baselineRow = summary?.baseline ?? null;
389
+ const hasValue = (summary?.count ?? 0) > 0;
390
+ const hasData = rows.length > 0;
391
+ const state = deriveComparisonState({
392
+ status,
393
+ hasValue,
394
+ hasData,
395
+ filtered
396
+ });
397
+ const showContent = state === "ready" || state === "refreshing";
398
+ const hasChart = showContent && rows.length > 0;
399
+ const hasTable = hasChart;
400
+ const delta = useMemo(() => deriveComparisonDelta(focusRow, baselineRow, {
401
+ deltaMode,
402
+ goodDirection,
403
+ format,
404
+ locale,
405
+ percentagePoints: labels.percentagePoints
406
+ }), [
407
+ focusRow,
408
+ baselineRow,
409
+ deltaMode,
410
+ goodDirection,
411
+ format,
412
+ locale,
413
+ labels.percentagePoints
414
+ ]);
415
+ const comparisonWanted = focus !== null && baseline !== null;
416
+ const insufficient = showContent && comparisonWanted && delta == null;
417
+ const targetText = target != null && Number.isFinite(target) ? formatters.formatValue(target) : null;
418
+ const hasReference = targetText != null;
419
+ const emphasize = focusRow != null;
420
+ const valueText = headline === "focus" ? formatters.formatValue(focusRow?.value) : headline === "gap" ? delta?.text ?? "—" : null;
421
+ const badgeText = delta ? headline === "gap" ? delta.secondaryText : delta.text : null;
422
+ const comparisonNode = comparisonLabel ?? (baselineRow ? headline === "gap" && focusRow ? labels.gapComparison(focusRow.label, baselineRow.label) : labels.versus(baselineRow.label) : null);
423
+ const deltaExplanationRows = delta && focusRow && baselineRow ? labels.deltaExplanation({
424
+ focusLabel: focusRow.label,
425
+ focusText: formatters.formatValue(focusRow.value),
426
+ baselineLabel: baselineRow.label,
427
+ baselineText: formatters.formatValue(baselineRow.value),
428
+ changeText: delta.text,
429
+ secondaryText: delta.secondaryText,
430
+ renderedMode: delta.renderedMode,
431
+ direction: delta.direction
432
+ }) : [];
433
+ const deltaExplanation = deltaExplanationRows.map(([key, value]) => `${key}: ${value}`).join(". ");
434
+ const asOfDate = updatedAt == null ? null : updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
435
+ const asOfValid = asOfDate != null && !Number.isNaN(asOfDate.getTime());
436
+ const asOfText = asOfValid ? typeof updatedAtFormat === "function" ? updatedAtFormat(asOfDate) : formatDate(asOfDate, {
437
+ granularity: updatedAtFormat,
438
+ locale,
439
+ timeZone
440
+ }) : void 0;
441
+ const cardDescription = description ?? buildComparisonDescription({
442
+ name,
443
+ state,
444
+ headline,
445
+ summary,
446
+ delta,
447
+ formatters,
448
+ targetText,
449
+ asOfText,
450
+ labels
451
+ });
452
+ useEffect(() => {
453
+ if (!pendingAnnounce.current) return;
454
+ pendingAnnounce.current = false;
455
+ setAnnouncement(cardDescription);
456
+ }, [cardDescription]);
457
+ const rowsSelectable = selectable && focusRow != null && (!isFocusControlled || onFocusChange != null);
458
+ const baselineSelectable = selectable && focusRow != null && (!isBaselineControlled || onBaselineChange != null);
459
+ const interactive = rowsSelectable;
460
+ const selectableRows = useMemo(() => rows.filter((row) => row.role !== "other"), [rows]);
461
+ const baselineCandidates = useMemo(() => rows.filter((row) => row.key !== focusRow?.key && row.value != null), [rows, focusRow]);
462
+ const percentagePoints = labels.percentagePoints;
463
+ const candidateChanges = useMemo(() => baselineCandidates.map((row) => {
464
+ const change = deriveComparisonDelta(focusRow, row, {
465
+ deltaMode,
466
+ goodDirection,
467
+ format,
468
+ locale,
469
+ percentagePoints
470
+ });
471
+ const percentText = change ? change.renderedMode === "percent" ? change.text : change.secondaryText : null;
472
+ const amountText = change ? change.renderedMode === "absolute" ? change.text : change.secondaryText : null;
473
+ return {
474
+ row,
475
+ polarity: change?.polarity ?? "neutral",
476
+ percentText: percentText ?? "—",
477
+ amountText: amountText ?? "—"
478
+ };
479
+ }), [
480
+ baselineCandidates,
481
+ focusRow,
482
+ deltaMode,
483
+ goodDirection,
484
+ format,
485
+ locale,
486
+ percentagePoints
487
+ ]);
488
+ const handleRowKeyDown = (event, row) => {
489
+ if (!interactive) return;
490
+ const index = selectableRows.indexOf(row);
491
+ if (index < 0) return;
492
+ let next;
493
+ switch (event.key) {
494
+ case "ArrowDown":
495
+ case "ArrowRight":
496
+ next = selectableRows[(index + 1) % selectableRows.length];
497
+ break;
498
+ case "ArrowUp":
499
+ case "ArrowLeft":
500
+ next = selectableRows[(index - 1 + selectableRows.length) % selectableRows.length];
501
+ break;
502
+ case "Home":
503
+ next = selectableRows[0];
504
+ break;
505
+ case "End":
506
+ next = selectableRows[selectableRows.length - 1];
507
+ break;
508
+ case " ":
509
+ case "Enter":
510
+ next = row;
511
+ break;
512
+ default: return;
513
+ }
514
+ event.preventDefault();
515
+ if (next.key !== focusRow?.key) selectFocus(next.key);
516
+ rowRefs.current.get(next.key)?.focus();
517
+ };
518
+ const handlePlotClick = (event) => {
519
+ if (!interactive) return;
520
+ const top = event.currentTarget.getBoundingClientRect().top;
521
+ const row = rows[Math.floor((event.clientY - top) / sizes.step)];
522
+ if (row && row.role !== "other" && row.key !== focusRow?.key) selectFocus(row.key);
523
+ };
524
+ const tokens = useChartTokens(rootRef);
525
+ const vertical = comparisonOrientation(mark) === "vertical";
526
+ const usesFrom = comparisonUsesFrom(mark);
527
+ const plotScale = resolveComparisonScale(mark, scale);
528
+ const fromTitle = fromLabel ?? labels.from;
529
+ const toTitle = toLabel ?? labels.to;
530
+ const showFromColumns = usesFrom && (summary?.hasFrom ?? false);
531
+ const plotHeight = vertical ? sizes.plotBlock : rows.length * sizes.step;
532
+ const overlayBox = usePlotBox(plotCellRef, rowsRef, hasChart && !vertical, rows[0]?.key ?? "");
533
+ const blockBox = usePlotBox(blockCellRef, blockCellRef, hasChart && vertical, mark);
534
+ const plotBox = vertical ? blockBox : overlayBox;
535
+ const barColor = useCallback((role) => tokens ? !emphasize || role === "focus" ? tokens.emphasis : tokens.context : void 0, [tokens, emphasize]);
536
+ const tooltipContent = useCallback(({ item }) => {
537
+ const datum = item.datum;
538
+ if (!datum) return null;
539
+ if ("key" in datum) {
540
+ const tooltipRows = [[/* @__PURE__ */ jsx(ComparisonKey, {
541
+ mark,
542
+ label: datum.label,
543
+ color: barColor(datum.role)
544
+ }, "item"), formatters.formatValue(datum.value)]];
545
+ if (showFromColumns) {
546
+ tooltipRows.unshift([fromTitle, formatters.formatValue(datum.from)]);
547
+ tooltipRows[1] = [/* @__PURE__ */ jsx(ComparisonKey, {
548
+ mark,
549
+ label: toTitle,
550
+ color: barColor(datum.role)
551
+ }, "item"), formatters.formatValue(datum.value)];
552
+ tooltipRows.push([labels.tableChange, formatters.formatVariance(datum.value, datum.from)]);
553
+ }
554
+ if (baselineRow && datum.key !== baselineRow.key) tooltipRows.push([labels.tableVersus(baselineRow.label), formatters.formatVariance(datum.value, baselineRow.value)]);
555
+ if (target != null) tooltipRows.push([/* @__PURE__ */ jsx(ComparisonKey, {
556
+ label: labels.target,
557
+ target: true,
558
+ color: tokens?.reference
559
+ }, "target"), formatters.formatValue(target)]);
560
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows: tooltipRows });
561
+ }
562
+ if ("value" in datum) return /* @__PURE__ */ jsx(ArcTooltipRows, { rows: [[/* @__PURE__ */ jsx(ComparisonKey, {
563
+ label: labels.target,
564
+ target: true,
565
+ color: tokens?.reference
566
+ }, "target"), formatters.formatValue(datum.value)]] });
567
+ return null;
568
+ }, [
569
+ barColor,
570
+ formatters,
571
+ baselineRow,
572
+ target,
573
+ labels,
574
+ tokens,
575
+ mark,
576
+ showFromColumns,
577
+ fromTitle,
578
+ toTitle
579
+ ]);
580
+ const { handler: tooltipHandler, tooltip } = useArcTooltipHandler({ content: tooltipContent });
581
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltipHandler }), [tooltipHandler]);
582
+ const itemTitle = labels.tableItem;
583
+ const targetTitle = labels.target;
584
+ const barHeight = sizes.barHeight;
585
+ const pointSize = sizes.pointSize;
586
+ const hasNegative = summary?.hasNegative ?? false;
587
+ const spec = useMemo(() => {
588
+ if (!tokens) return null;
589
+ const built = buildComparisonPlotSpec({
590
+ mark,
591
+ barHeight,
592
+ pointSize,
593
+ height: 56,
594
+ scale: plotScale,
595
+ reference: hasReference,
596
+ zeroRule: hasNegative,
597
+ tooltip: true,
598
+ emphasize,
599
+ itemTitle,
600
+ valueTitle: name,
601
+ fromTitle,
602
+ toTitle,
603
+ targetTitle,
604
+ valueFormat: locale ? `${format}@${locale}` : format,
605
+ locale
606
+ }, tokens);
607
+ return patchSpec ? patchSpec(built) : built;
608
+ }, [
609
+ tokens,
610
+ mark,
611
+ barHeight,
612
+ pointSize,
613
+ plotScale,
614
+ hasReference,
615
+ hasNegative,
616
+ emphasize,
617
+ itemTitle,
618
+ name,
619
+ fromTitle,
620
+ toTitle,
621
+ targetTitle,
622
+ format,
623
+ locale,
624
+ patchSpec
625
+ ]);
626
+ const referenceRows = useMemo(() => hasReference ? [{ value: target }] : [], [hasReference, target]);
627
+ const zeroRows = useMemo(() => hasNegative ? [{ value: 0 }] : [], [hasNegative]);
628
+ const datasets = useMemo(() => ({
629
+ [COMPARISON_TABLE_DATASET]: rows,
630
+ [COMPARISON_REFERENCE_DATASET]: referenceRows,
631
+ [COMPARISON_ZERO_DATASET]: zeroRows
632
+ }), [
633
+ rows,
634
+ referenceRows,
635
+ zeroRows
636
+ ]);
637
+ const chartDescription = summary && hasChart ? buildComparisonChartDescription({
638
+ mark,
639
+ name,
640
+ summary,
641
+ formatters,
642
+ targetText,
643
+ span: usesFrom && summary.hasFrom ? {
644
+ from: fromTitle,
645
+ to: toTitle
646
+ } : null,
647
+ labels
648
+ }) : void 0;
649
+ const showInsight = insight != null && insight !== false;
650
+ const reserveHeadline = headline !== "none" || comparisonWanted;
651
+ const reserveChip = focusChip && headline !== "none" && focus !== null;
652
+ const showChip = reserveChip && focusRow != null;
653
+ const headlineMinHeight = Math.max(headline !== "none" ? sizes.valueLinePx + (reserveChip ? CHIP_GAP_PX + sizes.chipLinePx : 0) : 0, comparisonWanted ? sizes.deltaLinePx + 4 + sizes.comparisonLinePx : 0);
654
+ const rowCount = hasChart ? rows.length : Math.max(0, expectedItems);
655
+ const bodyMinHeight = (reserveHeadline ? headlineMinHeight : 0) + (vertical ? sizes.gapPx + sizes.plotBlock : 0) + (rowCount > 0 ? (reserveHeadline ? sizes.gapPx : 0) + rowCount * sizes.step : 0) + (showInsight ? sizes.gapPx + sizes.comparisonLinePx : 0);
656
+ const HeadingTag = `h${titleLevel}`;
657
+ const busy = state === "loading" || state === "refreshing";
658
+ const directionWordFor = (direction) => direction === "up" ? labels.increase : direction === "down" ? labels.decrease : labels.unchanged;
659
+ const gridRows = { gridAutoRows: `${sizes.step}px` };
660
+ return /* @__PURE__ */ jsxs(Box, {
661
+ ref: setRootRef,
662
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
663
+ padding: sizes.padding,
664
+ "aria-labelledby": headingId,
665
+ "aria-describedby": descriptionId,
666
+ "aria-busy": busy || void 0,
667
+ "data-status": status,
668
+ "data-state": state,
669
+ "data-size": size,
670
+ "data-mark": mark,
671
+ "data-scale": plotScale,
672
+ "data-headline": headline,
673
+ "data-sort": sort,
674
+ "data-selectable": rowsSelectable || baselineSelectable ? "" : void 0,
675
+ "data-focus": showContent ? focusRow?.key : void 0,
676
+ "data-baseline": showContent ? baselineRow?.key : void 0,
677
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
678
+ "data-stale": stale ? "" : void 0,
679
+ "data-insufficient": insufficient ? "" : void 0,
680
+ "data-folded": showContent && summary && summary.folded > 0 ? summary.folded : void 0,
681
+ "data-polarity": showContent && delta ? delta.polarity : void 0,
682
+ ...rest,
683
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
684
+ children: [
685
+ /* @__PURE__ */ jsxs("div", {
686
+ className: "flex items-center justify-between gap-2",
687
+ children: [/* @__PURE__ */ jsxs("div", {
688
+ className: "flex min-w-0 items-center gap-1",
689
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
690
+ id: headingId,
691
+ className: cn(labelClasses, sizes.label, "line-clamp-2"),
692
+ children: label
693
+ }), hint && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
694
+ render: /* @__PURE__ */ jsx(Button, {
695
+ variant: "ghost",
696
+ size: "sm",
697
+ iconOnly: true,
698
+ circular: true,
699
+ "aria-label": labels.about(name),
700
+ "aria-describedby": hintId,
701
+ className: headerControlClasses
702
+ }),
703
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
704
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: hint })] }), /* @__PURE__ */ jsx("span", {
705
+ id: hintId,
706
+ className: "sr-only",
707
+ children: hint
708
+ })] })]
709
+ }), /* @__PURE__ */ jsxs("div", {
710
+ className: "flex shrink-0 items-center gap-1",
711
+ children: [
712
+ stale && /* @__PURE__ */ jsx(Badge, {
713
+ variant: "warning",
714
+ appearance: "soft",
715
+ size: "sm",
716
+ children: labels.stale
717
+ }),
718
+ hasTable && summary && /* @__PURE__ */ jsxs(Popover, {
719
+ open: tableOpen,
720
+ onOpenChange: setTableOpen,
721
+ children: [!hideTableToggle && /* @__PURE__ */ jsx(PopoverTrigger, {
722
+ render: /* @__PURE__ */ jsx(Button, {
723
+ variant: "ghost",
724
+ size: "sm",
725
+ iconOnly: true,
726
+ circular: true,
727
+ "aria-label": tableOpen ? labels.hideTable(name) : labels.showTable(name),
728
+ className: headerControlClasses
729
+ }),
730
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
731
+ }), popoverContainer && /* @__PURE__ */ jsxs(PopoverPopup, {
732
+ id: tableId,
733
+ anchor: rootRef,
734
+ container: popoverContainer,
735
+ side: "right",
736
+ align: "start",
737
+ collisionPadding: 8,
738
+ className: cn("pointer-events-auto", baselineRow && hasReference ? "w-96 max-w-[min(24rem,var(--available-width))]" : "w-80"),
739
+ "data-part": "data-popover",
740
+ children: [
741
+ /* @__PURE__ */ jsx(PopoverTitle, {
742
+ className: "mb-3",
743
+ children: name
744
+ }),
745
+ /* @__PURE__ */ jsxs("div", {
746
+ className: "mb-3 flex flex-wrap gap-x-3 gap-y-1 text-xs text-muted-foreground",
747
+ "data-part": "chart-legend",
748
+ children: [focusRow && emphasize ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(ComparisonKey, {
749
+ mark,
750
+ label: focusRow.label,
751
+ color: tokens?.emphasis
752
+ }), rows.length > 1 && /* @__PURE__ */ jsx(ComparisonKey, {
753
+ mark,
754
+ label: labels.otherItems,
755
+ color: tokens?.context
756
+ })] }) : /* @__PURE__ */ jsx(ComparisonKey, {
757
+ mark,
758
+ label: name,
759
+ color: tokens?.emphasis
760
+ }), hasReference && /* @__PURE__ */ jsx(ComparisonKey, {
761
+ label: `${labels.target} ${targetText}`,
762
+ target: true,
763
+ color: tokens?.reference
764
+ })]
765
+ }),
766
+ /* @__PURE__ */ jsx(TableContainer, {
767
+ variant: "plain",
768
+ tabIndex: 0,
769
+ role: "region",
770
+ "aria-label": labels.tableCaption(name),
771
+ className: "max-h-[min(16rem,calc(var(--available-height)-4.5rem))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
772
+ "data-part": "table",
773
+ children: /* @__PURE__ */ jsxs(Table, {
774
+ size: "sm",
775
+ stickyHeader: true,
776
+ hoverable: false,
777
+ children: [
778
+ /* @__PURE__ */ jsx(TableCaption, {
779
+ side: "top",
780
+ className: "sr-only",
781
+ children: labels.tableCaption(name)
782
+ }),
783
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
784
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tableItem }),
785
+ showFromColumns && /* @__PURE__ */ jsx(TableHead, {
786
+ className: "text-right",
787
+ children: fromTitle
788
+ }),
789
+ /* @__PURE__ */ jsx(TableHead, {
790
+ className: "text-right",
791
+ children: showFromColumns ? toTitle : labels.tableValue
792
+ }),
793
+ showFromColumns && /* @__PURE__ */ jsx(TableHead, {
794
+ className: "text-right",
795
+ children: labels.tableChange
796
+ }),
797
+ baselineRow && /* @__PURE__ */ jsx(TableHead, {
798
+ className: "text-right",
799
+ children: labels.tableVersus(baselineRow.label)
800
+ }),
801
+ hasReference && /* @__PURE__ */ jsx(TableHead, {
802
+ className: "text-right",
803
+ children: labels.tableVsTarget
804
+ })
805
+ ] }) }),
806
+ /* @__PURE__ */ jsx(TableBody, { children: rows.map((row) => /* @__PURE__ */ jsxs(TableRow, {
807
+ "data-role": row.role,
808
+ children: [
809
+ /* @__PURE__ */ jsx(TableCell, { children: /* @__PURE__ */ jsxs("span", {
810
+ className: "inline-flex items-baseline gap-1.5",
811
+ children: [row.role === "focus" && /* @__PURE__ */ jsx("span", {
812
+ "aria-hidden": "true",
813
+ className: "inline-block size-1.5 shrink-0 self-center rounded-full",
814
+ style: { background: tokens?.emphasis }
815
+ }), /* @__PURE__ */ jsxs("span", { children: [row.label, row.role === "focus" && /* @__PURE__ */ jsxs("span", {
816
+ className: "sr-only",
817
+ children: [
818
+ " (",
819
+ labels.highlighted,
820
+ ")"
821
+ ]
822
+ })] })]
823
+ }) }),
824
+ showFromColumns && /* @__PURE__ */ jsx(TableCell, {
825
+ className: "text-right tabular-nums",
826
+ children: formatters.formatValue(row.from)
827
+ }),
828
+ /* @__PURE__ */ jsx(TableCell, {
829
+ className: "text-right tabular-nums",
830
+ children: formatters.formatValue(row.value)
831
+ }),
832
+ showFromColumns && /* @__PURE__ */ jsx(TableCell, {
833
+ className: "text-right tabular-nums",
834
+ children: formatters.formatVariance(row.value, row.from)
835
+ }),
836
+ baselineRow && /* @__PURE__ */ jsx(TableCell, {
837
+ className: "text-right tabular-nums",
838
+ children: row.key === baselineRow.key ? "—" : formatters.formatVariance(row.value, baselineRow.value)
839
+ }),
840
+ hasReference && /* @__PURE__ */ jsx(TableCell, {
841
+ className: "text-right tabular-nums",
842
+ children: formatters.formatVariance(row.value, target)
843
+ })
844
+ ]
845
+ }, row.key)) })
846
+ ]
847
+ })
848
+ })
849
+ ]
850
+ })]
851
+ }),
852
+ action
853
+ ]
854
+ })]
855
+ }),
856
+ /* @__PURE__ */ jsx("p", {
857
+ id: descriptionId,
858
+ className: "sr-only",
859
+ children: cardDescription
860
+ }),
861
+ /* @__PURE__ */ jsx("p", {
862
+ role: "status",
863
+ "aria-live": "polite",
864
+ className: "sr-only",
865
+ "data-part": "announcement",
866
+ children: announcement
867
+ }),
868
+ /* @__PURE__ */ jsxs("div", {
869
+ className: "relative flex min-w-0 flex-col",
870
+ style: { minHeight: bodyMinHeight },
871
+ children: [
872
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
873
+ className: cn("flex flex-col", sizes.gap),
874
+ children: [
875
+ reserveHeadline && /* @__PURE__ */ jsxs("div", {
876
+ className: "flex min-w-0 items-center justify-between gap-2",
877
+ style: { minHeight: headlineMinHeight },
878
+ children: [headline !== "none" && /* @__PURE__ */ jsxs("div", {
879
+ className: "flex w-2/5 flex-col items-start gap-1",
880
+ children: [/* @__PURE__ */ jsx(Skeleton, {
881
+ variant: "rounded",
882
+ className: "w-full",
883
+ style: { height: sizes.valueLinePx }
884
+ }), reserveChip && /* @__PURE__ */ jsx(Skeleton, {
885
+ variant: "rounded",
886
+ className: "w-16 max-w-full",
887
+ style: { height: sizes.chipLinePx }
888
+ })]
889
+ }), comparisonWanted && /* @__PURE__ */ jsxs("div", {
890
+ className: cn("flex w-2/5 flex-col gap-1", headline !== "none" ? "items-end" : "items-start"),
891
+ children: [/* @__PURE__ */ jsx(Skeleton, {
892
+ variant: "rounded",
893
+ className: "w-20 max-w-full",
894
+ style: { height: sizes.deltaLinePx }
895
+ }), /* @__PURE__ */ jsx(Skeleton, {
896
+ variant: "rounded",
897
+ className: "w-full",
898
+ style: { height: sizes.comparisonLinePx }
899
+ })]
900
+ })]
901
+ }),
902
+ vertical && /* @__PURE__ */ jsx(Skeleton, {
903
+ variant: "rounded",
904
+ className: "w-full",
905
+ style: { height: sizes.plotBlock }
906
+ }),
907
+ rowCount > 0 && /* @__PURE__ */ jsx("div", {
908
+ className: vertical ? blockRowGridClasses : rowGridClasses,
909
+ style: gridRows,
910
+ "data-part": "rows-skeleton",
911
+ children: Array.from({ length: rowCount }, (_, index) => /* @__PURE__ */ jsxs("div", {
912
+ className: cn(vertical ? "col-span-2" : "col-span-3", "grid grid-cols-subgrid items-center"),
913
+ children: [
914
+ /* @__PURE__ */ jsx(Skeleton, {
915
+ variant: "rounded",
916
+ className: "h-3 w-16"
917
+ }),
918
+ !vertical && /* @__PURE__ */ jsx(Skeleton, {
919
+ variant: "rounded",
920
+ style: {
921
+ height: sizes.barHeight,
922
+ width: `${Math.max(30, 90 - index * 22)}%`
923
+ }
924
+ }),
925
+ /* @__PURE__ */ jsx(Skeleton, {
926
+ variant: "rounded",
927
+ className: "h-3 w-10"
928
+ })
929
+ ]
930
+ }, index))
931
+ }),
932
+ showInsight && /* @__PURE__ */ jsxs("div", {
933
+ className: "flex items-start gap-2",
934
+ children: [/* @__PURE__ */ jsx(Skeleton, {
935
+ variant: "rounded",
936
+ className: "size-4 shrink-0"
937
+ }), /* @__PURE__ */ jsx(Skeleton, {
938
+ variant: "rounded",
939
+ className: "w-full",
940
+ style: { height: sizes.comparisonLinePx }
941
+ })]
942
+ })
943
+ ]
944
+ }),
945
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
946
+ variant: "server-error",
947
+ scope: "inline",
948
+ title: labels.error,
949
+ description: false,
950
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
951
+ size: "sm",
952
+ onClick: onRetry,
953
+ children: labels.retry
954
+ }) : void 0,
955
+ className: cn(tileStateClasses, "my-auto")
956
+ }),
957
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
958
+ variant: "empty",
959
+ scope: "inline",
960
+ title: labels.noData,
961
+ description: false,
962
+ className: cn(tileStateClasses, "my-auto")
963
+ }),
964
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
965
+ variant: "no-results",
966
+ scope: "inline",
967
+ title: labels.noMatch,
968
+ description: false,
969
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
970
+ variant: "outline",
971
+ size: "sm",
972
+ onClick: onClearFilters,
973
+ children: labels.clearFilters
974
+ }) : void 0,
975
+ className: cn(tileStateClasses, "my-auto")
976
+ }),
977
+ showContent && /* @__PURE__ */ jsxs("div", {
978
+ className: cn("flex min-w-0 flex-col", sizes.gap),
979
+ children: [
980
+ (valueText != null || delta || insufficient) && /* @__PURE__ */ jsxs("div", {
981
+ className: "flex min-w-0 items-center justify-between gap-2",
982
+ style: { minHeight: headlineMinHeight },
983
+ "data-part": "headline",
984
+ children: [valueText != null && /* @__PURE__ */ jsxs("div", {
985
+ className: "flex min-w-0 flex-col items-start gap-1",
986
+ "data-part": "value-stack",
987
+ children: [/* @__PURE__ */ jsxs("div", {
988
+ className: "flex min-w-0 flex-wrap items-baseline gap-x-1.5",
989
+ "data-part": "value",
990
+ children: [/* @__PURE__ */ jsx("span", {
991
+ className: cn(valueClasses, sizes.value, "min-w-0 wrap-break-word"),
992
+ children: valueText
993
+ }), unit != null && headline === "focus" && /* @__PURE__ */ jsx("span", {
994
+ className: cn("font-sans text-muted-foreground", sizes.unit),
995
+ children: unit
996
+ })]
997
+ }), showChip && focusRow && /* @__PURE__ */ jsxs(Badge, {
998
+ variant: "primary",
999
+ appearance: "soft",
1000
+ size: sizes.chip,
1001
+ className: "max-w-full",
1002
+ "data-part": "focus-chip",
1003
+ children: [/* @__PURE__ */ jsx(FocusSwatch, {
1004
+ mark,
1005
+ color: tokens?.emphasis
1006
+ }), /* @__PURE__ */ jsx("span", {
1007
+ className: "truncate",
1008
+ children: focusRow.label
1009
+ })]
1010
+ })]
1011
+ }), delta && focusRow && baselineRow ? baselineSelectable ? /* @__PURE__ */ jsxs(Menu, { children: [/* @__PURE__ */ jsxs(MenuTrigger, {
1012
+ render: /* @__PURE__ */ jsx("button", {
1013
+ type: "button",
1014
+ "aria-label": labels.chooseBaseline(name),
1015
+ "aria-describedby": deltaExplanationId,
1016
+ className: cn(deltaStackClasses, "group cursor-pointer rounded-md", valueText != null ? "ms-auto max-w-1/2 items-end text-right" : "items-start text-left", sizes.body),
1017
+ "data-part": "delta"
1018
+ }),
1019
+ children: [
1020
+ /* @__PURE__ */ jsx("span", {
1021
+ id: deltaExplanationId,
1022
+ className: "sr-only",
1023
+ children: deltaExplanation
1024
+ }),
1025
+ badgeText && /* @__PURE__ */ jsxs(Badge, {
1026
+ variant: polarityBadgeVariant[delta.polarity],
1027
+ appearance: "soft",
1028
+ size: sizes.badge,
1029
+ "data-direction": delta.direction,
1030
+ children: [
1031
+ /* @__PURE__ */ jsx(DeltaGlyph, { direction: delta.direction }),
1032
+ /* @__PURE__ */ jsxs("span", {
1033
+ className: "sr-only",
1034
+ children: [directionWordFor(delta.direction), " "]
1035
+ }),
1036
+ badgeText
1037
+ ]
1038
+ }),
1039
+ /* @__PURE__ */ jsxs("span", {
1040
+ className: "inline-flex max-w-full items-center gap-0.5 text-muted-foreground text-xs transition-colors group-hover:text-foreground group-data-popup-open:text-foreground",
1041
+ children: [/* @__PURE__ */ jsx("span", {
1042
+ className: "min-w-0 wrap-break-word",
1043
+ children: comparisonNode
1044
+ }), /* @__PURE__ */ jsx(ChevronDownIcon, { className: "size-3 shrink-0" })]
1045
+ })
1046
+ ]
1047
+ }), /* @__PURE__ */ jsxs(MenuPopup, {
1048
+ side: "right",
1049
+ align: "start",
1050
+ sideOffset: 8,
1051
+ collisionPadding: 8,
1052
+ container: popoverContainer,
1053
+ className: "pointer-events-auto w-[26rem] max-w-[min(26rem,var(--available-width))]",
1054
+ "data-part": "baseline-menu",
1055
+ children: [
1056
+ /* @__PURE__ */ jsxs("div", {
1057
+ className: "flex items-center justify-between gap-3 px-2 py-1.5 font-sans text-xs",
1058
+ "data-part": "baseline-menu-subject",
1059
+ children: [/* @__PURE__ */ jsxs("span", {
1060
+ className: "inline-flex min-w-0 items-center gap-1.5 font-medium text-foreground",
1061
+ children: [/* @__PURE__ */ jsx(FocusSwatch, {
1062
+ mark,
1063
+ color: tokens?.emphasis
1064
+ }), /* @__PURE__ */ jsx("span", {
1065
+ className: "truncate",
1066
+ children: focusRow.label
1067
+ })]
1068
+ }), /* @__PURE__ */ jsx("span", {
1069
+ className: "shrink-0 font-medium text-foreground tabular-nums",
1070
+ children: formatters.formatValue(focusRow.value)
1071
+ })]
1072
+ }),
1073
+ /* @__PURE__ */ jsx(MenuSeparator, {}),
1074
+ /* @__PURE__ */ jsxs(MenuRadioGroup, {
1075
+ value: baselineRow.key,
1076
+ onValueChange: (value) => selectBaseline(String(value)),
1077
+ children: [/* @__PURE__ */ jsxs(MenuGroupLabel, {
1078
+ inset: true,
1079
+ className: "flex items-center gap-2",
1080
+ "data-part": "baseline-menu-header",
1081
+ children: [
1082
+ /* @__PURE__ */ jsx("span", {
1083
+ className: "min-w-0 flex-1 truncate",
1084
+ children: labels.compareWith
1085
+ }),
1086
+ /* @__PURE__ */ jsx("span", {
1087
+ className: menuValueColumnClasses,
1088
+ children: labels.tableValue
1089
+ }),
1090
+ /* @__PURE__ */ jsx("span", {
1091
+ className: menuChangeColumnClasses,
1092
+ children: labels.changePercent
1093
+ }),
1094
+ /* @__PURE__ */ jsx("span", {
1095
+ className: menuChangeColumnClasses,
1096
+ children: labels.changeAmount
1097
+ })
1098
+ ]
1099
+ }), candidateChanges.map(({ row, polarity, percentText, amountText }) => /* @__PURE__ */ jsxs(MenuRadioItem, {
1100
+ value: row.key,
1101
+ closeOnClick: true,
1102
+ "data-role": row.role,
1103
+ "data-polarity": polarity,
1104
+ children: [
1105
+ /* @__PURE__ */ jsx("span", {
1106
+ className: "min-w-0 flex-1 truncate",
1107
+ children: row.label
1108
+ }),
1109
+ /* @__PURE__ */ jsx("span", {
1110
+ className: cn(menuValueColumnClasses, "text-muted-foreground"),
1111
+ children: formatters.formatValue(row.value)
1112
+ }),
1113
+ /* @__PURE__ */ jsx("span", {
1114
+ className: cn(menuChangeColumnClasses, "font-medium", polarityTextClasses[polarity]),
1115
+ children: percentText
1116
+ }),
1117
+ /* @__PURE__ */ jsx("span", {
1118
+ className: cn(menuChangeColumnClasses, "font-medium", polarityTextClasses[polarity]),
1119
+ children: amountText
1120
+ })
1121
+ ]
1122
+ }, row.key))]
1123
+ })
1124
+ ]
1125
+ })] }) : /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsxs(TooltipTrigger, {
1126
+ delay: 150,
1127
+ render: /* @__PURE__ */ jsx("p", {
1128
+ tabIndex: 0,
1129
+ role: "group",
1130
+ "aria-label": labels.deltaComparison(name),
1131
+ "aria-describedby": deltaExplanationId,
1132
+ className: cn(deltaStackClasses, "rounded-md", valueText != null ? "ms-auto max-w-1/2 items-end text-right" : "items-start text-left", sizes.body),
1133
+ "data-part": "delta"
1134
+ }),
1135
+ children: [
1136
+ /* @__PURE__ */ jsx("span", {
1137
+ id: deltaExplanationId,
1138
+ className: "sr-only",
1139
+ children: deltaExplanation
1140
+ }),
1141
+ badgeText && /* @__PURE__ */ jsxs(Badge, {
1142
+ variant: polarityBadgeVariant[delta.polarity],
1143
+ appearance: "soft",
1144
+ size: sizes.badge,
1145
+ "data-direction": delta.direction,
1146
+ children: [
1147
+ /* @__PURE__ */ jsx(DeltaGlyph, { direction: delta.direction }),
1148
+ /* @__PURE__ */ jsxs("span", {
1149
+ className: "sr-only",
1150
+ children: [directionWordFor(delta.direction), " "]
1151
+ }),
1152
+ badgeText
1153
+ ]
1154
+ }),
1155
+ /* @__PURE__ */ jsx("span", {
1156
+ className: "max-w-full text-muted-foreground wrap-break-word text-xs",
1157
+ children: comparisonNode
1158
+ })
1159
+ ]
1160
+ }), /* @__PURE__ */ jsx(TooltipPopup, {
1161
+ arrow: false,
1162
+ side: "bottom",
1163
+ align: valueText != null ? "end" : "start",
1164
+ sideOffset: 8,
1165
+ collisionPadding: 8,
1166
+ container: popoverContainer,
1167
+ className: "text-left",
1168
+ "data-part": "delta-tooltip",
1169
+ children: /* @__PURE__ */ jsx(ArcTooltipRows, { rows: deltaExplanationRows })
1170
+ })] }) : insufficient ? /* @__PURE__ */ jsx("p", {
1171
+ className: cn(captionClasses, "mt-0", valueText != null && "ms-auto max-w-1/2 text-right"),
1172
+ "data-part": "delta",
1173
+ children: labels.insufficient
1174
+ }) : null]
1175
+ }),
1176
+ hasChart && /* @__PURE__ */ jsxs("div", {
1177
+ ref: rowsRef,
1178
+ className: "relative w-full",
1179
+ "data-part": "comparison",
1180
+ children: [
1181
+ vertical && /* @__PURE__ */ jsx("div", {
1182
+ ref: blockCellRef,
1183
+ className: "relative mb-2 w-full",
1184
+ style: { height: plotHeight },
1185
+ "data-part": "plot-block",
1186
+ children: plotBox.width > 0 && spec && /* @__PURE__ */ jsx(ArcVegaChart, {
1187
+ spec,
1188
+ options: embedOptions,
1189
+ datasets,
1190
+ description: chartDescription,
1191
+ width: plotBox.width,
1192
+ height: plotHeight,
1193
+ onEmbed,
1194
+ className: "absolute inset-0 [&_svg]:block [&_.vega-bindings:empty]:hidden",
1195
+ style: {
1196
+ width: plotBox.width,
1197
+ height: plotHeight
1198
+ },
1199
+ "data-part": "plot",
1200
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
1201
+ variant: "rounded",
1202
+ className: "absolute inset-0"
1203
+ })
1204
+ })
1205
+ }),
1206
+ /* @__PURE__ */ jsx("ul", {
1207
+ role: interactive ? "radiogroup" : "list",
1208
+ "aria-label": interactive ? labels.focusGroup(name) : void 0,
1209
+ className: cn(vertical ? blockRowGridClasses : rowGridClasses, "font-sans", sizes.rowText),
1210
+ style: gridRows,
1211
+ "data-part": "rows",
1212
+ children: rows.map((row, index) => {
1213
+ const choosable = interactive && row.role !== "other";
1214
+ const selected = row.role === "focus";
1215
+ return /* @__PURE__ */ jsxs("li", {
1216
+ ref: (node) => {
1217
+ if (node) rowRefs.current.set(row.key, node);
1218
+ else rowRefs.current.delete(row.key);
1219
+ },
1220
+ role: interactive ? "radio" : void 0,
1221
+ "aria-checked": interactive ? selected : void 0,
1222
+ "aria-disabled": interactive && !choosable ? true : void 0,
1223
+ tabIndex: choosable ? selected ? 0 : -1 : void 0,
1224
+ onClick: choosable && !selected ? () => selectFocus(row.key) : void 0,
1225
+ onKeyDown: choosable ? (event) => handleRowKeyDown(event, row) : void 0,
1226
+ className: cn(vertical ? "col-span-2" : "col-span-3", "grid grid-cols-subgrid items-center", interactive && selectableRowClasses, interactive && !choosable && disabledRowClasses),
1227
+ "data-part": "row",
1228
+ "data-role": row.role,
1229
+ children: [
1230
+ /* @__PURE__ */ jsxs("span", {
1231
+ className: cn("min-w-0 truncate", rowLabelClasses[row.role]),
1232
+ "data-part": "row-label",
1233
+ children: [row.label, selected && !interactive && /* @__PURE__ */ jsxs("span", {
1234
+ className: "sr-only",
1235
+ children: [
1236
+ " (",
1237
+ labels.highlighted,
1238
+ ")"
1239
+ ]
1240
+ })]
1241
+ }),
1242
+ !vertical && /* @__PURE__ */ jsx("span", {
1243
+ ref: index === 0 ? plotCellRef : void 0,
1244
+ "aria-hidden": "true",
1245
+ className: "min-w-0 self-stretch",
1246
+ "data-part": "row-plot"
1247
+ }),
1248
+ /* @__PURE__ */ jsx("span", {
1249
+ className: cn("text-right tabular-nums", rowValueClasses[row.role]),
1250
+ "data-part": "row-value",
1251
+ children: formatters.formatValue(row.value)
1252
+ })
1253
+ ]
1254
+ }, row.key);
1255
+ })
1256
+ }),
1257
+ !vertical && plotBox.width > 0 && spec && /* @__PURE__ */ jsx(ArcVegaChart, {
1258
+ spec,
1259
+ options: embedOptions,
1260
+ datasets,
1261
+ description: chartDescription,
1262
+ width: plotBox.width,
1263
+ height: plotHeight,
1264
+ onEmbed,
1265
+ onClick: interactive ? handlePlotClick : void 0,
1266
+ className: cn("absolute top-0 [&_svg]:block [&_.vega-bindings:empty]:hidden", interactive && "cursor-pointer"),
1267
+ style: {
1268
+ left: plotBox.left,
1269
+ width: plotBox.width,
1270
+ height: plotHeight
1271
+ },
1272
+ "data-part": "plot",
1273
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
1274
+ variant: "rounded",
1275
+ className: "absolute top-0",
1276
+ style: {
1277
+ left: plotBox.left,
1278
+ width: plotBox.width,
1279
+ height: plotHeight
1280
+ }
1281
+ })
1282
+ })
1283
+ ]
1284
+ }),
1285
+ showInsight && /* @__PURE__ */ jsxs("div", {
1286
+ className: cn("flex items-start gap-2 font-sans text-foreground", sizes.body),
1287
+ "data-part": "insight",
1288
+ children: [/* @__PURE__ */ jsx(AILoader, {
1289
+ size: "sm",
1290
+ state: "default",
1291
+ label: labels.insightMark,
1292
+ className: cn("shrink-0", size === "sm" ? "" : "mt-0.5")
1293
+ }), /* @__PURE__ */ jsx("p", {
1294
+ className: "min-w-0 flex-1 [&_strong]:font-semibold [&_strong]:text-foreground text-xs leading-[1.8]",
1295
+ children: insight
1296
+ })]
1297
+ })
1298
+ ]
1299
+ }),
1300
+ state === "refreshing" && /* @__PURE__ */ jsx("div", {
1301
+ "aria-hidden": "true",
1302
+ className: "absolute -inset-1 rounded-md bg-veil"
1303
+ })
1304
+ ]
1305
+ }),
1306
+ asOfValid && asOfDate && /* @__PURE__ */ jsx("p", {
1307
+ className: cn(captionClasses, "text-right"),
1308
+ "data-part": "as-of",
1309
+ children: /* @__PURE__ */ jsx("time", {
1310
+ dateTime: asOfDate.toISOString(),
1311
+ children: asOfText
1312
+ })
1313
+ }),
1314
+ /* @__PURE__ */ jsx("div", {
1315
+ ref: setPopoverContainer,
1316
+ className: "pointer-events-none absolute inset-0",
1317
+ "data-part": "popover-host"
1318
+ }),
1319
+ tooltip
1320
+ ]
1321
+ });
1322
+ });
1323
+ ComparisonCard.displayName = "ComparisonCard";
1324
+ //#endregion
1325
+ export { ComparisonCard };