@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,1053 @@
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 { TREND_BANDS_DATASET, TREND_LATEST_DATASET, TREND_PERIODS_DATASET, TREND_REFERENCE_DATASET, TREND_TABLE_DATASET, buildTrendPlotSpec } from "./TrendCard.chart.js";
7
+ import { bandAt, buildLatestRows, buildPeriodRows, buildTrendChartDescription, buildTrendDescription, buildTrendInsight, buildTrendTooltip, createTrendFormatters, deriveTrendSpan, deriveTrendState, normalizeSeries, resolveBands, resolveSeriesColorMode, resolveTrendLabels, trendAxisGranularity } from "./TrendCard.model.js";
8
+ import { Fragment, forwardRef, useCallback, useEffect, useId, useMemo, useRef, useState } from "react";
9
+ import { AILoader, Badge, Box, Button, HorizontalScroll, Popover, PopoverPopup, PopoverTitle, PopoverTrigger, Skeleton, SkeletonGroup, SystemState, Table, TableBody, TableCaption, TableCell, TableContainer, TableHead, TableHeader, TableRow, Toggle, Tooltip, TooltipPopup, TooltipTrigger, cn } from "@arcfusionz/arc-primitive-ui";
10
+ import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
11
+ //#region src/components/TrendCard/TrendCard.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
+ plotHeight: 120,
26
+ legendLinePx: 24
27
+ },
28
+ md: {
29
+ padding: "md",
30
+ gap: "gap-3",
31
+ gapPx: 12,
32
+ label: "text-sm",
33
+ value: "text-3xl leading-9",
34
+ valueLinePx: 36,
35
+ unit: "text-sm",
36
+ body: "text-sm",
37
+ badge: "md",
38
+ deltaLinePx: 24,
39
+ comparisonLinePx: 20,
40
+ plotHeight: 160,
41
+ legendLinePx: 28
42
+ },
43
+ lg: {
44
+ padding: "lg",
45
+ gap: "gap-3",
46
+ gapPx: 12,
47
+ label: "text-sm",
48
+ value: "text-4xl leading-10",
49
+ valueLinePx: 40,
50
+ unit: "text-base",
51
+ body: "text-sm",
52
+ badge: "lg",
53
+ deltaLinePx: 28,
54
+ comparisonLinePx: 20,
55
+ plotHeight: 200,
56
+ legendLinePx: 28
57
+ }
58
+ };
59
+ const labelClasses = "min-w-0 font-sans font-medium text-muted-foreground";
60
+ const valueClasses = "font-heading font-semibold tracking-tight text-foreground";
61
+ const captionClasses = "font-sans text-xs text-muted-foreground";
62
+ const polarityBadgeVariant = {
63
+ positive: "success",
64
+ negative: "destructive",
65
+ neutral: "neutral"
66
+ };
67
+ const changeTextClasses = {
68
+ positive: "text-chart-positive",
69
+ negative: "text-chart-negative",
70
+ neutral: "text-foreground"
71
+ };
72
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
73
+ const panelStateClasses = "w-full max-w-none gap-3 px-3 py-4 [--artwork-box:4rem]";
74
+ const X_AXIS_HEIGHT_PX = 24;
75
+ const PX_PER_Y_TICK = 48;
76
+ const MISSING_NAME = "Trend";
77
+ function InfoIcon(props) {
78
+ return /* @__PURE__ */ jsxs("svg", {
79
+ viewBox: "0 0 24 24",
80
+ fill: "none",
81
+ stroke: "currentColor",
82
+ strokeWidth: 2,
83
+ strokeLinecap: "round",
84
+ strokeLinejoin: "round",
85
+ "aria-hidden": "true",
86
+ ...props,
87
+ children: [/* @__PURE__ */ jsx("circle", {
88
+ cx: "12",
89
+ cy: "12",
90
+ r: "9"
91
+ }), /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01" })]
92
+ });
93
+ }
94
+ function DeltaGlyph({ direction, ...props }) {
95
+ return /* @__PURE__ */ jsxs("svg", {
96
+ viewBox: "0 0 24 24",
97
+ fill: "none",
98
+ stroke: "currentColor",
99
+ strokeWidth: 2.5,
100
+ strokeLinecap: "round",
101
+ strokeLinejoin: "round",
102
+ "aria-hidden": "true",
103
+ ...props,
104
+ children: [
105
+ direction === "up" && /* @__PURE__ */ jsx("path", { d: "M7 17 17 7M7 7h10v10" }),
106
+ direction === "down" && /* @__PURE__ */ jsx("path", { d: "M7 7 17 17M7 17h10V7" }),
107
+ direction === "flat" && /* @__PURE__ */ jsx("path", { d: "M5 12h14M13 6l6 6-6 6" })
108
+ ]
109
+ });
110
+ }
111
+ /** Match the plot's mark shape so a series' meaning never depends on colour alone. */
112
+ function TrendSeriesKey({ label, mark, color, variant = "series" }) {
113
+ return /* @__PURE__ */ jsxs("span", {
114
+ className: "inline-flex min-w-0 items-center gap-1.5",
115
+ children: [/* @__PURE__ */ jsx("svg", {
116
+ width: "20",
117
+ height: "12",
118
+ viewBox: "0 0 20 12",
119
+ fill: "none",
120
+ "aria-hidden": "true",
121
+ className: "shrink-0",
122
+ style: { color },
123
+ children: variant === "band" ? /* @__PURE__ */ jsx("rect", {
124
+ x: "1",
125
+ y: "1",
126
+ width: "18",
127
+ height: "10",
128
+ rx: "2",
129
+ fill: "currentColor",
130
+ opacity: "0.5"
131
+ }) : mark === "bar" ? /* @__PURE__ */ jsx("rect", {
132
+ x: "5",
133
+ y: "1",
134
+ width: "10",
135
+ height: "10",
136
+ rx: "1",
137
+ fill: "currentColor"
138
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [mark === "area" && /* @__PURE__ */ jsx("rect", {
139
+ x: "1",
140
+ y: "6",
141
+ width: "18",
142
+ height: "5",
143
+ fill: "currentColor",
144
+ opacity: "0.16"
145
+ }), /* @__PURE__ */ jsx("line", {
146
+ x1: "2",
147
+ y1: "6",
148
+ x2: "18",
149
+ y2: "6",
150
+ stroke: "currentColor",
151
+ strokeWidth: variant === "target" ? 1.5 : 2,
152
+ strokeLinecap: "round",
153
+ strokeDasharray: variant === "target" ? "0 4" : void 0
154
+ })] })
155
+ }), /* @__PURE__ */ jsx("span", {
156
+ className: "min-w-0 truncate",
157
+ children: label
158
+ })]
159
+ });
160
+ }
161
+ /** A generated insight: words as text, values in bold. */
162
+ function InsightText({ segments }) {
163
+ return /* @__PURE__ */ jsx(Fragment$1, { children: segments.map((segment, index) => typeof segment === "string" ? /* @__PURE__ */ jsx(Fragment, { children: segment }, index) : /* @__PURE__ */ jsx("strong", { children: segment.value }, index)) });
164
+ }
165
+ function useMergedRef(forwarded) {
166
+ const inner = useRef(null);
167
+ return [inner, useCallback((node) => {
168
+ inner.current = node;
169
+ if (typeof forwarded === "function") forwarded(node);
170
+ else if (forwarded) forwarded.current = node;
171
+ }, [forwarded])];
172
+ }
173
+ function usePlotWidth(ref, enabled) {
174
+ const [width, setWidth] = useState(0);
175
+ useEffect(() => {
176
+ if (!enabled) return;
177
+ const el = ref.current;
178
+ if (!el) return;
179
+ let frame = 0;
180
+ const measure = () => {
181
+ const next = Math.round(el.getBoundingClientRect().width);
182
+ setWidth((prev) => prev === next ? prev : next);
183
+ };
184
+ measure();
185
+ if (typeof ResizeObserver === "undefined") return;
186
+ const observer = new ResizeObserver(() => {
187
+ cancelAnimationFrame(frame);
188
+ frame = requestAnimationFrame(measure);
189
+ });
190
+ observer.observe(el);
191
+ return () => {
192
+ cancelAnimationFrame(frame);
193
+ observer.disconnect();
194
+ };
195
+ }, [ref, enabled]);
196
+ return width;
197
+ }
198
+ /**
199
+ * A trend panel for dashboards: several series over the same periods on one
200
+ * shared scale, with labelled axes, a legend the reader operates, a shared-x
201
+ * crosshair that reads every series at the pointed period, named period bands,
202
+ * an optional target rule, the subject's change across the span as a headline,
203
+ * and every loading, empty, error and stale state — layout-stable, with a
204
+ * generated text equivalent and a data table in an info popover.
205
+ *
206
+ * Where `MetricCard` answers "what is this number and is it good", TrendCard
207
+ * answers "what shape is this metric in, and which series is driving it".
208
+ */
209
+ const TrendCard = forwardRef(({ label, name: nameProp, hint, series, focus, defaultFocus, onFocusChange, visibleSeries, defaultVisibleSeries, onVisibleSeriesChange, legend, legendMode = "auto", mark = "line", scale = "auto", stack = false, normalize = "none", indexFormat = "number:0", seriesColor = "auto", maxSeries, bands, target, expectedSeries, headline = "latest", deltaMode = "percent", goodDirection = "up", format = "compact", formatValue, unit, plotHeight: plotHeightProp, axes = true, labelSeries = false, granularity, 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) => {
210
+ const labels = useMemo(() => resolveTrendLabels(labelOverrides), [labelOverrides]);
211
+ const sizes = sizeStyles[size];
212
+ const plotHeight = plotHeightProp ?? sizes.plotHeight + (axes ? X_AXIS_HEIGHT_PX : 0);
213
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
214
+ const effectiveLabelSeries = labelSeries && stack === false && mark !== "bar";
215
+ const id = useId();
216
+ const headingId = `${id}-label`;
217
+ const descriptionId = `${id}-description`;
218
+ const hintId = `${id}-hint`;
219
+ const tableId = `${id}-table`;
220
+ const deltaExplanationId = `${id}-delta-explanation`;
221
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
222
+ const plotRef = useRef(null);
223
+ const [tableOpen, setTableOpen] = useState(defaultTableOpen);
224
+ const [popoverContainer, setPopoverContainer] = useState(null);
225
+ const tokens = useChartTokens(rootRef);
226
+ const isFocusControlled = focus !== void 0;
227
+ const [innerFocus, setInnerFocus] = useState(defaultFocus);
228
+ const focusKeyRequest = isFocusControlled ? focus : innerFocus;
229
+ const isVisibleControlled = visibleSeries !== void 0;
230
+ const [innerVisible, setInnerVisible] = useState(defaultVisibleSeries);
231
+ const visibleRequest = isVisibleControlled ? visibleSeries : innerVisible;
232
+ const pendingAnnounce = useRef(false);
233
+ const [announcement, setAnnouncement] = useState("");
234
+ const slots = tokens?.category.length ?? 3;
235
+ const seriesCount = series?.length ?? 0;
236
+ const colorMode = resolveSeriesColorMode(seriesColor, maxSeries != null ? Math.min(seriesCount, maxSeries) : seriesCount, slots);
237
+ const emphasize = colorMode === "emphasis" && seriesCount > 1;
238
+ const allKeys = useMemo(() => {
239
+ const seen = /* @__PURE__ */ new Map();
240
+ return (series ?? []).map((entry) => {
241
+ const base = entry.key ?? entry.label;
242
+ const repeats = seen.get(base) ?? 0;
243
+ seen.set(base, repeats + 1);
244
+ return repeats === 0 ? base : `${base}·${repeats + 1}`;
245
+ });
246
+ }, [series]);
247
+ const visibleKeys = useMemo(() => {
248
+ const known = new Set(allKeys);
249
+ const requested = visibleRequest?.filter((key) => known.has(key));
250
+ return requested && requested.length > 0 ? requested : allKeys;
251
+ }, [allKeys, visibleRequest]);
252
+ const summary = useMemo(() => tokens ? normalizeSeries(series, {
253
+ granularity,
254
+ normalize,
255
+ focus: focusKeyRequest,
256
+ visible: visibleKeys,
257
+ maxSeries,
258
+ otherLabel: labels.other,
259
+ colorMode,
260
+ tokens,
261
+ stack: mark === "line" ? false : stack
262
+ }) : null, [
263
+ tokens,
264
+ series,
265
+ granularity,
266
+ normalize,
267
+ focusKeyRequest,
268
+ visibleKeys,
269
+ maxSeries,
270
+ labels.other,
271
+ colorMode,
272
+ mark,
273
+ stack
274
+ ]);
275
+ const formatters = useMemo(() => createTrendFormatters({
276
+ format,
277
+ locale,
278
+ timeZone,
279
+ formatValue,
280
+ xType: summary?.xType ?? "temporal",
281
+ granularity: summary?.granularity ?? granularity ?? "day",
282
+ normalize,
283
+ stack: mark === "line" ? false : stack,
284
+ indexFormat
285
+ }), [
286
+ format,
287
+ locale,
288
+ timeZone,
289
+ formatValue,
290
+ summary,
291
+ granularity,
292
+ normalize,
293
+ mark,
294
+ stack,
295
+ indexFormat
296
+ ]);
297
+ const resolvedBands = useMemo(() => resolveBands(bands, summary), [bands, summary]);
298
+ const span = useMemo(() => deriveTrendSpan(summary, summary?.focusKey, {
299
+ deltaMode,
300
+ goodDirection,
301
+ format,
302
+ locale,
303
+ percentagePoints: labels.percentagePoints,
304
+ formatters
305
+ }), [
306
+ summary,
307
+ deltaMode,
308
+ goodDirection,
309
+ format,
310
+ locale,
311
+ labels.percentagePoints,
312
+ formatters
313
+ ]);
314
+ const hasReadings = (summary?.count ?? 0) > 0;
315
+ const state = deriveTrendState({
316
+ status,
317
+ hasValue: hasReadings,
318
+ hasData: seriesCount > 0,
319
+ filtered
320
+ });
321
+ const showContent = state === "ready" || state === "refreshing";
322
+ const hasChart = summary != null && summary.periods.length >= 2 && summary.count >= 2;
323
+ const insufficient = showContent && summary != null && !hasChart;
324
+ const hasTable = showContent && summary != null && summary.periods.length > 0;
325
+ const focusInfo = summary?.series.find((info) => info.key === summary.focusKey) ?? null;
326
+ const resolvedLegendMode = legendMode === "auto" ? emphasize ? "focus" : "visibility" : legendMode;
327
+ const showLegend = (legend ?? seriesCount > 1) && showContent && summary != null && summary.series.length > 1;
328
+ const visibleSet = useMemo(() => new Set(visibleKeys), [visibleKeys]);
329
+ const shownSeries = useMemo(() => (summary?.series ?? []).filter((info) => visibleSet.has(info.key)), [summary, visibleSet]);
330
+ const selectFocus = useCallback((key) => {
331
+ pendingAnnounce.current = true;
332
+ if (!isFocusControlled) setInnerFocus(key);
333
+ onFocusChange?.(key);
334
+ }, [isFocusControlled, onFocusChange]);
335
+ const toggleSeries = useCallback((key, shown) => {
336
+ const next = shown ? [...visibleKeys, key] : visibleKeys.filter((entry) => entry !== key);
337
+ if (next.length === 0) return;
338
+ pendingAnnounce.current = true;
339
+ if (!isVisibleControlled) setInnerVisible(next);
340
+ onVisibleSeriesChange?.(next);
341
+ }, [
342
+ visibleKeys,
343
+ isVisibleControlled,
344
+ onVisibleSeriesChange
345
+ ]);
346
+ const targetText = target != null ? formatters.formatValue(target) : null;
347
+ const asOfDate = updatedAt == null ? null : updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
348
+ const asOfValid = asOfDate != null && !Number.isNaN(asOfDate.getTime());
349
+ const asOfText = asOfValid ? typeof updatedAtFormat === "function" ? updatedAtFormat(asOfDate) : formatDate(asOfDate, {
350
+ granularity: updatedAtFormat,
351
+ locale,
352
+ timeZone
353
+ }) : void 0;
354
+ const cardDescription = description ?? buildTrendDescription({
355
+ name,
356
+ state,
357
+ headline,
358
+ summary,
359
+ visibleKeys,
360
+ span,
361
+ bands: resolvedBands,
362
+ formatters,
363
+ targetText,
364
+ asOfText,
365
+ labels
366
+ });
367
+ useEffect(() => {
368
+ if (!pendingAnnounce.current) return;
369
+ pendingAnnounce.current = false;
370
+ setAnnouncement(cardDescription);
371
+ }, [cardDescription]);
372
+ const deltaExplanationRows = span ? labels.deltaExplanation({
373
+ seriesLabel: span.seriesLabel,
374
+ fromLabel: formatters.formatX(span.from.x),
375
+ fromText: formatters.formatValue(span.from.y),
376
+ toLabel: formatters.formatX(span.to.x),
377
+ toText: formatters.formatValue(span.to.y),
378
+ changeText: span.delta.text,
379
+ secondaryText: span.delta.secondaryText,
380
+ renderedMode: span.delta.renderedMode,
381
+ direction: span.delta.direction
382
+ }) : [];
383
+ const deltaExplanation = deltaExplanationRows.map(([key, value]) => `${key}: ${value}`).join(". ");
384
+ const plotWidth = usePlotWidth(plotRef, showContent && hasChart);
385
+ const transformed = normalize === "index" || stack === "normalize" && mark !== "line";
386
+ const tooltipContent = useCallback(({ item }) => {
387
+ const raw = item.datum;
388
+ const datum = raw && "datum" in raw && raw.datum ? raw.datum : raw;
389
+ const x = datum && typeof datum === "object" && "x" in datum ? datum.x : void 0;
390
+ const model = buildTrendTooltip({
391
+ summary,
392
+ visibleKeys,
393
+ bands: resolvedBands,
394
+ x,
395
+ target,
396
+ formatters,
397
+ transformed
398
+ });
399
+ if (!model) return null;
400
+ const rows = [[labels.tablePeriod, model.periodText]];
401
+ if (model.bandText) rows.push([labels.band, model.bandText]);
402
+ for (const entry of model.entries) rows.push([/* @__PURE__ */ jsx(TrendSeriesKey, {
403
+ mark,
404
+ color: entry.color,
405
+ label: /* @__PURE__ */ jsxs(Fragment$1, { children: [entry.label, entry.isFocus && emphasize && /* @__PURE__ */ jsxs("span", {
406
+ className: "sr-only",
407
+ children: [
408
+ " (",
409
+ labels.focused,
410
+ ")"
411
+ ]
412
+ })] })
413
+ }, entry.key), entry.plottedText != null ? /* @__PURE__ */ jsxs(Fragment$1, { children: [entry.plottedText, /* @__PURE__ */ jsxs("span", {
414
+ className: "ms-1 text-background/70",
415
+ children: [
416
+ "(",
417
+ entry.valueText,
418
+ ")"
419
+ ]
420
+ })] }) : entry.valueText]);
421
+ if (model.targetText) rows.push([/* @__PURE__ */ jsx(TrendSeriesKey, {
422
+ mark: "target",
423
+ variant: "target",
424
+ label: labels.target,
425
+ color: tokens?.reference
426
+ }, "target"), model.targetText]);
427
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows });
428
+ }, [
429
+ summary,
430
+ visibleKeys,
431
+ resolvedBands,
432
+ target,
433
+ formatters,
434
+ transformed,
435
+ labels,
436
+ mark,
437
+ tokens,
438
+ emphasize
439
+ ]);
440
+ const { handler: tooltipHandler, tooltip } = useArcTooltipHandler({ content: tooltipContent });
441
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltipHandler }), [tooltipHandler]);
442
+ const xType = summary?.xType;
443
+ const summaryGranularity = summary?.granularity;
444
+ const hasReference = target != null;
445
+ const hasBands = resolvedBands.length > 0;
446
+ const periodTitle = labels.tablePeriod;
447
+ const yTicks = Math.max(2, Math.round(plotHeight / PX_PER_Y_TICK));
448
+ const plotFormat = normalize === "index" ? indexFormat : stack === "normalize" && mark !== "line" ? "percent:0" : format;
449
+ const spec = useMemo(() => {
450
+ if (!tokens || !xType || !summaryGranularity) return null;
451
+ const timeGrammar = (kind) => [
452
+ kind,
453
+ locale ?? "",
454
+ timeZone ?? ""
455
+ ].join("@").replace(/@+$/, "");
456
+ const built = buildTrendPlotSpec({
457
+ mark,
458
+ scale,
459
+ xType,
460
+ stack: mark === "line" ? false : stack,
461
+ height: plotHeight,
462
+ axes,
463
+ yTicks,
464
+ reference: hasReference,
465
+ bands: hasBands,
466
+ tooltip: true,
467
+ labelSeries: effectiveLabelSeries,
468
+ emphasize,
469
+ xTitle: periodTitle,
470
+ yTitle: name,
471
+ targetTitle: labels.target,
472
+ plotFormat: locale ? `${plotFormat}@${locale}` : plotFormat,
473
+ timeFormat: timeGrammar(summaryGranularity),
474
+ axisTimeFormat: timeGrammar(trendAxisGranularity(summaryGranularity)),
475
+ locale,
476
+ timeZone
477
+ }, tokens);
478
+ return patchSpec ? patchSpec(built) : built;
479
+ }, [
480
+ tokens,
481
+ xType,
482
+ summaryGranularity,
483
+ mark,
484
+ scale,
485
+ stack,
486
+ plotHeight,
487
+ axes,
488
+ yTicks,
489
+ hasReference,
490
+ hasBands,
491
+ effectiveLabelSeries,
492
+ emphasize,
493
+ periodTitle,
494
+ labels.target,
495
+ name,
496
+ plotFormat,
497
+ locale,
498
+ timeZone,
499
+ patchSpec
500
+ ]);
501
+ const tableRows = summary?.rows;
502
+ const referenceRows = useMemo(() => target != null ? [{ value: target }] : [], [target]);
503
+ const periodRows = useMemo(() => summary ? buildPeriodRows(summary) : [], [summary]);
504
+ const latestRows = useMemo(() => summary && effectiveLabelSeries ? buildLatestRows(summary, visibleKeys) : [], [
505
+ summary,
506
+ effectiveLabelSeries,
507
+ visibleKeys
508
+ ]);
509
+ const datasets = useMemo(() => ({
510
+ [TREND_TABLE_DATASET]: tableRows ?? [],
511
+ [TREND_PERIODS_DATASET]: periodRows,
512
+ [TREND_REFERENCE_DATASET]: referenceRows,
513
+ [TREND_BANDS_DATASET]: resolvedBands,
514
+ [TREND_LATEST_DATASET]: latestRows
515
+ }), [
516
+ tableRows,
517
+ periodRows,
518
+ referenceRows,
519
+ resolvedBands,
520
+ latestRows
521
+ ]);
522
+ const chartDescription = summary && hasChart ? buildTrendChartDescription({
523
+ name,
524
+ mark,
525
+ summary,
526
+ visibleKeys,
527
+ focusLabel: emphasize ? focusInfo?.label ?? null : null,
528
+ stack: mark === "line" ? false : stack,
529
+ normalize,
530
+ formatters,
531
+ targetText,
532
+ labels
533
+ }) : void 0;
534
+ const insightContent = insight === void 0 ? span ? /* @__PURE__ */ jsx(InsightText, { segments: buildTrendInsight({
535
+ span,
536
+ formatters,
537
+ labels
538
+ }) }) : null : insight;
539
+ const showInsight = insightContent != null && insightContent !== false;
540
+ const reserveHeadline = headline !== "none";
541
+ const countForLegend = seriesCount > 0 ? seriesCount : expectedSeries ?? 0;
542
+ const reserveLegend = legend ?? countForLegend > 1;
543
+ const reserveInsight = insight === void 0 ? true : insight != null && insight !== false;
544
+ const headlineMinHeight = reserveHeadline ? Math.max(sizes.valueLinePx, sizes.deltaLinePx + 4 + sizes.comparisonLinePx) : 0;
545
+ const captionText = !transformed ? null : normalize === "index" && summary && summary.periods.length > 0 ? labels.indexedAt(formatters.formatX(summary.periods[0])) : labels.normalized;
546
+ const bodyMinHeight = (reserveHeadline ? headlineMinHeight + sizes.gapPx : 0) + plotHeight + (reserveLegend ? sizes.gapPx + sizes.legendLinePx : 0) + (reserveInsight ? sizes.gapPx + sizes.comparisonLinePx : 0);
547
+ const HeadingTag = `h${titleLevel}`;
548
+ const busy = state === "loading" || state === "refreshing";
549
+ const directionWordFor = (direction) => direction === "up" ? labels.increase : direction === "down" ? labels.decrease : labels.unchanged;
550
+ const legendRef = useRef(null);
551
+ const onLegendKeyDown = useCallback((event) => {
552
+ const keys = shownSeries.map((info) => info.key);
553
+ if (keys.length === 0) return;
554
+ const current = keys.indexOf(summary?.focusKey ?? "");
555
+ let next = -1;
556
+ if (event.key === "ArrowRight" || event.key === "ArrowDown") next = (current + 1 + keys.length) % keys.length;
557
+ else if (event.key === "ArrowLeft" || event.key === "ArrowUp") next = (current - 1 + keys.length) % keys.length;
558
+ else if (event.key === "Home") next = 0;
559
+ else if (event.key === "End") next = keys.length - 1;
560
+ if (next < 0) return;
561
+ event.preventDefault();
562
+ selectFocus(keys[next]);
563
+ requestAnimationFrame(() => {
564
+ (legendRef.current?.querySelectorAll("[data-part=\"legend-chip\"]"))?.[next]?.focus();
565
+ });
566
+ }, [
567
+ shownSeries,
568
+ summary,
569
+ selectFocus
570
+ ]);
571
+ const legendNode = showLegend && summary && /* @__PURE__ */ jsx(HorizontalScroll, {
572
+ fade: true,
573
+ className: "min-w-0",
574
+ "data-part": "legend-scroll",
575
+ children: /* @__PURE__ */ jsxs("div", {
576
+ ref: legendRef,
577
+ role: resolvedLegendMode === "focus" ? "radiogroup" : "group",
578
+ "aria-label": labels.legendGroup(name),
579
+ onKeyDown: resolvedLegendMode === "focus" ? onLegendKeyDown : void 0,
580
+ className: cn("flex min-w-0 flex-wrap items-center gap-x-1 gap-y-1 font-sans", sizes.body),
581
+ "data-part": "legend",
582
+ children: [
583
+ summary.series.map((info) => {
584
+ const shown = visibleSet.has(info.key);
585
+ const isFocus = info.key === summary.focusKey;
586
+ const key = /* @__PURE__ */ jsx(TrendSeriesKey, {
587
+ mark,
588
+ color: shown ? info.color : tokens?.mutedForeground,
589
+ label: /* @__PURE__ */ jsxs("span", {
590
+ className: "inline-flex min-w-0 items-baseline gap-1.5",
591
+ children: [/* @__PURE__ */ jsx("span", {
592
+ className: "min-w-0 truncate",
593
+ children: info.label
594
+ }), /* @__PURE__ */ jsx("span", {
595
+ className: "shrink-0 text-muted-foreground tabular-nums",
596
+ children: formatters.formatValue(info.latest?.y)
597
+ })]
598
+ })
599
+ });
600
+ if (resolvedLegendMode === "focus") return /* @__PURE__ */ jsxs("button", {
601
+ type: "button",
602
+ role: "radio",
603
+ "aria-checked": isFocus,
604
+ tabIndex: isFocus ? 0 : -1,
605
+ onClick: () => selectFocus(info.key),
606
+ className: cn("inline-flex max-w-full cursor-pointer items-center rounded-md px-1.5 py-1 text-left transition-colors", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", isFocus ? "bg-primary-50 font-medium text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
607
+ "data-part": "legend-chip",
608
+ "data-focused": isFocus ? "" : void 0,
609
+ children: [key, isFocus && /* @__PURE__ */ jsxs("span", {
610
+ className: "sr-only",
611
+ children: [
612
+ " (",
613
+ labels.focused,
614
+ ")"
615
+ ]
616
+ })]
617
+ }, info.key);
618
+ return /* @__PURE__ */ jsx(Toggle, {
619
+ variant: "ghost",
620
+ size: "sm",
621
+ pressed: shown,
622
+ onPressedChange: (next) => toggleSeries(info.key, next),
623
+ disabled: shown && shownSeries.length <= 1,
624
+ "aria-label": shown ? labels.hideSeries(info.label) : labels.showSeries(info.label),
625
+ className: cn("h-auto max-w-full px-1.5 py-1", shown ? "" : "opacity-60"),
626
+ "data-part": "legend-chip",
627
+ "data-shown": shown ? "" : void 0,
628
+ children: key
629
+ }, info.key);
630
+ }),
631
+ resolvedBands.map((band, index) => /* @__PURE__ */ jsx("span", {
632
+ className: cn("inline-flex max-w-full items-center px-1.5 py-1 text-muted-foreground", sizes.body),
633
+ "data-part": "legend-band",
634
+ children: /* @__PURE__ */ jsx(TrendSeriesKey, {
635
+ mark: "band",
636
+ variant: "band",
637
+ color: tokens?.brush,
638
+ label: band.label ?? formatters.formatXRange(band.start, band.end)
639
+ })
640
+ }, index)),
641
+ target != null && /* @__PURE__ */ jsx("span", {
642
+ className: cn("inline-flex max-w-full items-center px-1.5 py-1 text-muted-foreground", sizes.body),
643
+ "data-part": "legend-target",
644
+ children: /* @__PURE__ */ jsx(TrendSeriesKey, {
645
+ mark: "target",
646
+ variant: "target",
647
+ color: tokens?.reference,
648
+ label: `${labels.target} ${targetText}`
649
+ })
650
+ })
651
+ ]
652
+ })
653
+ });
654
+ return /* @__PURE__ */ jsxs(Box, {
655
+ ref: setRootRef,
656
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
657
+ padding: sizes.padding,
658
+ "aria-labelledby": headingId,
659
+ "aria-describedby": descriptionId,
660
+ "aria-busy": busy || void 0,
661
+ "data-status": status,
662
+ "data-state": state,
663
+ "data-size": size,
664
+ "data-mark": mark,
665
+ "data-stack": mark === "line" ? "false" : String(stack),
666
+ "data-normalize": normalize,
667
+ "data-series-color": colorMode,
668
+ "data-legend": showLegend ? resolvedLegendMode : void 0,
669
+ "data-headline": headline,
670
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
671
+ "data-stale": stale ? "" : void 0,
672
+ "data-insufficient": insufficient ? "" : void 0,
673
+ "data-polarity": showContent && span ? span.delta.polarity : void 0,
674
+ "data-focus": showContent ? summary?.focusKey : void 0,
675
+ "data-folded": showContent && summary && summary.folded > 0 ? summary.folded : void 0,
676
+ "data-hidden": showContent && summary ? summary.series.length - shownSeries.length || void 0 : void 0,
677
+ ...rest,
678
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
679
+ children: [
680
+ /* @__PURE__ */ jsxs("div", {
681
+ className: "flex items-center justify-between gap-2",
682
+ children: [/* @__PURE__ */ jsxs("div", {
683
+ className: "flex min-w-0 items-center gap-1",
684
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
685
+ id: headingId,
686
+ className: cn(labelClasses, sizes.label, "line-clamp-2"),
687
+ children: label
688
+ }), hint && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
689
+ render: /* @__PURE__ */ jsx(Button, {
690
+ variant: "ghost",
691
+ size: "sm",
692
+ iconOnly: true,
693
+ circular: true,
694
+ "aria-label": labels.about(name),
695
+ "aria-describedby": hintId,
696
+ className: headerControlClasses
697
+ }),
698
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
699
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: hint })] }), /* @__PURE__ */ jsx("span", {
700
+ id: hintId,
701
+ className: "sr-only",
702
+ children: hint
703
+ })] })]
704
+ }), /* @__PURE__ */ jsxs("div", {
705
+ className: "flex shrink-0 items-center gap-1",
706
+ children: [
707
+ stale && /* @__PURE__ */ jsx(Badge, {
708
+ variant: "warning",
709
+ appearance: "soft",
710
+ size: "sm",
711
+ children: labels.stale
712
+ }),
713
+ hasTable && /* @__PURE__ */ jsxs(Popover, {
714
+ open: tableOpen,
715
+ onOpenChange: setTableOpen,
716
+ children: [!hideTableToggle && /* @__PURE__ */ jsx(PopoverTrigger, {
717
+ render: /* @__PURE__ */ jsx(Button, {
718
+ variant: "ghost",
719
+ size: "sm",
720
+ iconOnly: true,
721
+ circular: true,
722
+ "aria-label": tableOpen ? labels.hideTable(name) : labels.showTable(name),
723
+ className: headerControlClasses
724
+ }),
725
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
726
+ }), popoverContainer && /* @__PURE__ */ jsxs(PopoverPopup, {
727
+ id: tableId,
728
+ anchor: rootRef,
729
+ container: popoverContainer,
730
+ side: "right",
731
+ align: "start",
732
+ collisionPadding: 8,
733
+ className: "pointer-events-auto w-[min(28rem,var(--available-width))]",
734
+ "data-part": "data-popover",
735
+ children: [
736
+ /* @__PURE__ */ jsx(PopoverTitle, {
737
+ className: "mb-3",
738
+ children: name
739
+ }),
740
+ /* @__PURE__ */ jsx(TableContainer, {
741
+ variant: "plain",
742
+ tabIndex: 0,
743
+ role: "region",
744
+ "aria-label": labels.tableCaption(name),
745
+ className: "max-h-[min(20rem,calc(var(--available-height)-5rem))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
746
+ "data-part": "table",
747
+ children: /* @__PURE__ */ jsxs(Table, {
748
+ size: "sm",
749
+ stickyHeader: true,
750
+ hoverable: false,
751
+ children: [
752
+ /* @__PURE__ */ jsx(TableCaption, {
753
+ side: "top",
754
+ className: "sr-only",
755
+ children: labels.tableCaption(name)
756
+ }),
757
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [/* @__PURE__ */ jsx(TableHead, { children: labels.tablePeriod }), shownSeries.map((info) => /* @__PURE__ */ jsx(TableHead, {
758
+ className: "text-right",
759
+ children: info.label
760
+ }, info.key))] }) }),
761
+ /* @__PURE__ */ jsx(TableBody, { children: (summary?.periods ?? []).map((x, at) => {
762
+ const band = bandAt(resolvedBands, summary?.periods ?? [], x);
763
+ return /* @__PURE__ */ jsxs(TableRow, {
764
+ "data-band": band ? "" : void 0,
765
+ children: [/* @__PURE__ */ jsxs(TableCell, { children: [
766
+ band && /* @__PURE__ */ jsx("span", {
767
+ "aria-hidden": "true",
768
+ className: "me-1.5 inline-block size-1.5 rounded-full align-middle",
769
+ style: { background: tokens?.brush }
770
+ }),
771
+ formatters.formatX(x),
772
+ band && /* @__PURE__ */ jsxs("span", {
773
+ className: "sr-only",
774
+ children: [
775
+ " ",
776
+ "(",
777
+ band.label ?? labels.band,
778
+ ")"
779
+ ]
780
+ })
781
+ ] }), shownSeries.map((info) => /* @__PURE__ */ jsx(TableCell, {
782
+ className: "text-right tabular-nums",
783
+ children: info.reported[at] ? formatters.formatValue(info.values[at]) : "—"
784
+ }, info.key))]
785
+ }, at);
786
+ }) })
787
+ ]
788
+ })
789
+ }),
790
+ captionText && /* @__PURE__ */ jsx("p", {
791
+ className: cn(captionClasses, "mt-3"),
792
+ children: captionText
793
+ })
794
+ ]
795
+ })]
796
+ }),
797
+ action
798
+ ]
799
+ })]
800
+ }),
801
+ /* @__PURE__ */ jsx("p", {
802
+ id: descriptionId,
803
+ className: "sr-only",
804
+ children: cardDescription
805
+ }),
806
+ /* @__PURE__ */ jsx("p", {
807
+ role: "status",
808
+ "aria-live": "polite",
809
+ className: "sr-only",
810
+ "data-part": "announcement",
811
+ children: announcement
812
+ }),
813
+ /* @__PURE__ */ jsxs("div", {
814
+ className: "relative flex min-w-0 flex-col",
815
+ style: { minHeight: bodyMinHeight },
816
+ children: [
817
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
818
+ className: cn("flex flex-col", sizes.gap),
819
+ children: [
820
+ reserveHeadline && /* @__PURE__ */ jsxs("div", {
821
+ className: "flex min-w-0 items-center justify-between gap-2",
822
+ style: { minHeight: headlineMinHeight },
823
+ children: [/* @__PURE__ */ jsx(Skeleton, {
824
+ variant: "rounded",
825
+ className: "w-2/5",
826
+ style: { height: sizes.valueLinePx }
827
+ }), /* @__PURE__ */ jsxs("div", {
828
+ className: "flex w-1/3 flex-col items-end gap-1",
829
+ children: [/* @__PURE__ */ jsx(Skeleton, {
830
+ variant: "rounded",
831
+ className: "w-20 max-w-full",
832
+ style: { height: sizes.deltaLinePx }
833
+ }), /* @__PURE__ */ jsx(Skeleton, {
834
+ variant: "rounded",
835
+ className: "w-full",
836
+ style: { height: sizes.comparisonLinePx }
837
+ })]
838
+ })]
839
+ }),
840
+ /* @__PURE__ */ jsx(Skeleton, {
841
+ variant: "rounded",
842
+ className: "w-full",
843
+ style: { height: plotHeight }
844
+ }),
845
+ reserveLegend && /* @__PURE__ */ jsx("div", {
846
+ className: "flex flex-wrap gap-2",
847
+ children: Array.from({ length: Math.max(2, Math.min(4, countForLegend || 3)) }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
848
+ variant: "rounded",
849
+ className: "w-24",
850
+ style: { height: sizes.legendLinePx }
851
+ }, index))
852
+ }),
853
+ reserveInsight && /* @__PURE__ */ jsxs("div", {
854
+ className: "flex items-start gap-2",
855
+ children: [/* @__PURE__ */ jsx(Skeleton, {
856
+ variant: "rounded",
857
+ className: "size-4 shrink-0"
858
+ }), /* @__PURE__ */ jsx(Skeleton, {
859
+ variant: "rounded",
860
+ className: "w-full",
861
+ style: { height: sizes.comparisonLinePx }
862
+ })]
863
+ })
864
+ ]
865
+ }),
866
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
867
+ variant: "server-error",
868
+ scope: "inline",
869
+ title: labels.error,
870
+ description: false,
871
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
872
+ size: "sm",
873
+ onClick: onRetry,
874
+ children: labels.retry
875
+ }) : void 0,
876
+ className: cn(panelStateClasses, "my-auto")
877
+ }),
878
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
879
+ variant: "empty",
880
+ scope: "inline",
881
+ title: labels.noData,
882
+ description: false,
883
+ className: cn(panelStateClasses, "my-auto")
884
+ }),
885
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
886
+ variant: "no-results",
887
+ scope: "inline",
888
+ title: labels.noMatch,
889
+ description: false,
890
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
891
+ variant: "outline",
892
+ size: "sm",
893
+ onClick: onClearFilters,
894
+ children: labels.clearFilters
895
+ }) : void 0,
896
+ className: cn(panelStateClasses, "my-auto")
897
+ }),
898
+ showContent && /* @__PURE__ */ jsxs("div", {
899
+ className: cn("flex min-w-0 flex-col", sizes.gap),
900
+ children: [
901
+ headline !== "none" && /* @__PURE__ */ jsx("div", {
902
+ className: "flex min-w-0 items-center justify-between gap-2",
903
+ style: { minHeight: headlineMinHeight },
904
+ "data-part": "headline",
905
+ children: headline === "change" && span ? /* @__PURE__ */ jsxs("div", {
906
+ className: "flex min-w-0 flex-col",
907
+ "data-part": "value",
908
+ children: [/* @__PURE__ */ jsxs("span", {
909
+ className: cn(valueClasses, sizes.value, "min-w-0 wrap-break-word", changeTextClasses[span.delta.polarity]),
910
+ children: [/* @__PURE__ */ jsxs("span", {
911
+ className: "sr-only",
912
+ children: [directionWordFor(span.delta.direction), " "]
913
+ }), span.delta.text]
914
+ }), /* @__PURE__ */ jsxs("span", {
915
+ className: cn(captionClasses, "mt-1 min-w-0 wrap-break-word"),
916
+ children: [
917
+ formatters.formatValue(span.from.y),
918
+ " → ",
919
+ formatters.formatValue(span.to.y),
920
+ " · ",
921
+ span.rangeText
922
+ ]
923
+ })]
924
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
925
+ className: "flex min-w-0 flex-wrap items-baseline gap-x-1.5",
926
+ "data-part": "value",
927
+ children: [/* @__PURE__ */ jsx("span", {
928
+ className: cn(valueClasses, sizes.value, "min-w-0 wrap-break-word"),
929
+ children: formatters.formatValue(span?.to.y ?? focusInfo?.latest?.y)
930
+ }), unit != null && /* @__PURE__ */ jsx("span", {
931
+ className: cn("font-sans text-muted-foreground", sizes.unit),
932
+ children: unit
933
+ })]
934
+ }), span && /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsxs(TooltipTrigger, {
935
+ delay: 150,
936
+ render: /* @__PURE__ */ jsx("p", {
937
+ tabIndex: 0,
938
+ role: "group",
939
+ "aria-label": labels.changeGroup(name),
940
+ "aria-describedby": deltaExplanationId,
941
+ className: cn("ms-auto flex min-w-0 max-w-1/2 shrink-0 flex-col items-end gap-1 rounded-md text-right font-sans", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", sizes.body),
942
+ "data-part": "delta"
943
+ }),
944
+ children: [
945
+ /* @__PURE__ */ jsx("span", {
946
+ id: deltaExplanationId,
947
+ className: "sr-only",
948
+ children: deltaExplanation
949
+ }),
950
+ /* @__PURE__ */ jsxs(Badge, {
951
+ variant: polarityBadgeVariant[span.delta.polarity],
952
+ appearance: "soft",
953
+ size: sizes.badge,
954
+ "data-direction": span.delta.direction,
955
+ children: [
956
+ /* @__PURE__ */ jsx(DeltaGlyph, { direction: span.delta.direction }),
957
+ /* @__PURE__ */ jsxs("span", {
958
+ className: "sr-only",
959
+ children: [directionWordFor(span.delta.direction), " "]
960
+ }),
961
+ span.delta.text
962
+ ]
963
+ }),
964
+ /* @__PURE__ */ jsx("span", {
965
+ className: "max-w-full text-muted-foreground wrap-break-word text-xs",
966
+ children: labels.since(formatters.formatX(span.from.x))
967
+ })
968
+ ]
969
+ }), /* @__PURE__ */ jsx(TooltipPopup, {
970
+ arrow: false,
971
+ side: "bottom",
972
+ align: "end",
973
+ sideOffset: 8,
974
+ collisionPadding: 8,
975
+ container: popoverContainer,
976
+ className: "text-left",
977
+ "data-part": "delta-tooltip",
978
+ children: /* @__PURE__ */ jsx(ArcTooltipRows, { rows: deltaExplanationRows })
979
+ })] })] })
980
+ }),
981
+ /* @__PURE__ */ jsx("div", {
982
+ ref: plotRef,
983
+ className: "relative w-full",
984
+ style: { height: plotHeight },
985
+ "data-part": "plot",
986
+ children: hasChart ? plotWidth > 0 && spec ? /* @__PURE__ */ jsx(ArcVegaChart, {
987
+ spec,
988
+ options: embedOptions,
989
+ datasets,
990
+ description: chartDescription,
991
+ width: plotWidth,
992
+ height: plotHeight,
993
+ onEmbed,
994
+ className: "absolute inset-0 [&_svg]:block [&_.vega-bindings:empty]:hidden",
995
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
996
+ variant: "rounded",
997
+ className: "size-full"
998
+ })
999
+ }) : /* @__PURE__ */ jsx(Skeleton, {
1000
+ variant: "rounded",
1001
+ className: "size-full"
1002
+ }) : /* @__PURE__ */ jsx("p", {
1003
+ className: cn(captionClasses, "flex h-full items-center"),
1004
+ children: labels.insufficient
1005
+ })
1006
+ }),
1007
+ captionText && /* @__PURE__ */ jsx("p", {
1008
+ className: cn(captionClasses, "-mt-1"),
1009
+ "data-part": "caption",
1010
+ children: captionText
1011
+ }),
1012
+ legendNode,
1013
+ showInsight && /* @__PURE__ */ jsxs("div", {
1014
+ className: cn("flex items-start gap-2 font-sans text-foreground", sizes.body),
1015
+ "data-part": "insight",
1016
+ children: [/* @__PURE__ */ jsx(AILoader, {
1017
+ size: "sm",
1018
+ state: "default",
1019
+ label: labels.insightMark,
1020
+ className: cn("shrink-0", size === "sm" ? "" : "mt-0.5")
1021
+ }), /* @__PURE__ */ jsx("p", {
1022
+ className: "min-w-0 flex-1 text-xs leading-[1.8] [&_strong]:font-semibold [&_strong]:text-foreground",
1023
+ children: insightContent
1024
+ })]
1025
+ })
1026
+ ]
1027
+ }),
1028
+ state === "refreshing" && /* @__PURE__ */ jsx("div", {
1029
+ "aria-hidden": "true",
1030
+ className: "absolute -inset-1 rounded-md bg-veil"
1031
+ })
1032
+ ]
1033
+ }),
1034
+ asOfValid && asOfDate && /* @__PURE__ */ jsx("p", {
1035
+ className: cn(captionClasses, "text-right"),
1036
+ "data-part": "as-of",
1037
+ children: /* @__PURE__ */ jsx("time", {
1038
+ dateTime: asOfDate.toISOString(),
1039
+ children: asOfText
1040
+ })
1041
+ }),
1042
+ /* @__PURE__ */ jsx("div", {
1043
+ ref: setPopoverContainer,
1044
+ className: "pointer-events-none absolute inset-0",
1045
+ "data-part": "popover-host"
1046
+ }),
1047
+ tooltip
1048
+ ]
1049
+ });
1050
+ });
1051
+ TrendCard.displayName = "TrendCard";
1052
+ //#endregion
1053
+ export { TrendCard };