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