@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,1019 @@
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 { ANOMALY_PERIODS_DATASET, ANOMALY_SPAN_DATASET, ANOMALY_TABLE_DATASET, buildAnomalyPlotSpec } from "./AnomalyCard.chart.js";
7
+ import { anomalyAxisGranularity, buildAnomalyChartDescription, buildAnomalyDescription, buildAnomalyExplanation, buildAnomalyInsight, buildAnomalyPeriodRows, buildAnomalySpanRows, buildAnomalyTooltip, createAnomalyFormatters, deriveAnomalyStanding, deriveAnomalyState, normalizeAnomalies, resolveAnomalyLabels } from "./AnomalyCard.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/AnomalyCard/AnomalyCard.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
+ verdictLinePx: 20,
24
+ captionLinePx: 16,
25
+ plotHeight: 120,
26
+ keyLinePx: 20,
27
+ episodeRowPx: 26
28
+ },
29
+ md: {
30
+ padding: "md",
31
+ gap: "gap-3",
32
+ gapPx: 12,
33
+ label: "text-sm",
34
+ value: "text-3xl leading-9",
35
+ valueLinePx: 36,
36
+ unit: "text-sm",
37
+ body: "text-sm",
38
+ badge: "md",
39
+ verdictLinePx: 24,
40
+ captionLinePx: 20,
41
+ plotHeight: 160,
42
+ keyLinePx: 22,
43
+ episodeRowPx: 30
44
+ },
45
+ lg: {
46
+ padding: "lg",
47
+ gap: "gap-3",
48
+ gapPx: 12,
49
+ label: "text-sm",
50
+ value: "text-4xl leading-10",
51
+ valueLinePx: 40,
52
+ unit: "text-base",
53
+ body: "text-sm",
54
+ badge: "lg",
55
+ verdictLinePx: 28,
56
+ captionLinePx: 20,
57
+ plotHeight: 200,
58
+ keyLinePx: 22,
59
+ episodeRowPx: 32
60
+ }
61
+ };
62
+ const labelClasses = "min-w-0 font-sans font-medium text-muted-foreground";
63
+ const valueClasses = "font-heading font-semibold tracking-tight text-foreground";
64
+ const captionClasses = "font-sans text-xs text-muted-foreground";
65
+ const verdictBadgeVariant = {
66
+ clear: "success",
67
+ watch: "warning",
68
+ breach: "warning"
69
+ };
70
+ const polarityTextClasses = {
71
+ positive: "text-chart-positive",
72
+ negative: "text-chart-negative",
73
+ neutral: "text-foreground"
74
+ };
75
+ const headerControlClasses = "size-6 min-w-0 text-muted-foreground hover:text-foreground";
76
+ const panelStateClasses = "w-full max-w-none gap-3 px-3 py-4 [--artwork-box:4rem]";
77
+ const X_AXIS_HEIGHT_PX = 24;
78
+ const PX_PER_Y_TICK = 48;
79
+ const INSIGHT_LINES = 4;
80
+ const MISSING_NAME = "Metric";
81
+ const NO_EPISODES = [];
82
+ function InfoIcon(props) {
83
+ return /* @__PURE__ */ jsxs("svg", {
84
+ viewBox: "0 0 24 24",
85
+ fill: "none",
86
+ stroke: "currentColor",
87
+ strokeWidth: 2,
88
+ strokeLinecap: "round",
89
+ strokeLinejoin: "round",
90
+ "aria-hidden": "true",
91
+ ...props,
92
+ children: [/* @__PURE__ */ jsx("circle", {
93
+ cx: "12",
94
+ cy: "12",
95
+ r: "9"
96
+ }), /* @__PURE__ */ jsx("path", { d: "M12 11v5M12 8h.01" })]
97
+ });
98
+ }
99
+ /** The verdict's own glyph, so the chip never depends on its hue (WCAG 1.4.1). */
100
+ function VerdictGlyph({ verdict, ...props }) {
101
+ return /* @__PURE__ */ jsxs("svg", {
102
+ viewBox: "0 0 24 24",
103
+ fill: "none",
104
+ stroke: "currentColor",
105
+ strokeWidth: 2.5,
106
+ strokeLinecap: "round",
107
+ strokeLinejoin: "round",
108
+ "aria-hidden": "true",
109
+ ...props,
110
+ children: [
111
+ verdict === "clear" && /* @__PURE__ */ jsx("path", { d: "M20 6 9 17l-5-5" }),
112
+ verdict === "watch" && /* @__PURE__ */ jsx("path", { d: "M12 7v5l3 2M21 12a9 9 0 1 1-9-9" }),
113
+ verdict === "breach" && /* @__PURE__ */ jsx("path", { d: "M12 9v4M12 17h.01M10.3 3.9 1.8 18a2 2 0 0 0 1.7 3h17a2 2 0 0 0 1.7-3L13.7 3.9a2 2 0 0 0-3.4 0Z" })
114
+ ]
115
+ });
116
+ }
117
+ /**
118
+ * The flag's own shape, repeated in DOM text wherever an excursion is named —
119
+ * the same triangle the plot draws, so a reader who cannot separate the hues
120
+ * still reads the direction off the mark. A flag with no side to place it on
121
+ * takes the plot's neutral circle.
122
+ */
123
+ function FlagGlyph({ direction, ...props }) {
124
+ return /* @__PURE__ */ jsxs("svg", {
125
+ viewBox: "0 0 24 24",
126
+ fill: "currentColor",
127
+ "aria-hidden": "true",
128
+ ...props,
129
+ children: [
130
+ direction === "high" && /* @__PURE__ */ jsx("path", { d: "M12 4 22 20H2Z" }),
131
+ direction === "low" && /* @__PURE__ */ jsx("path", { d: "M12 20 2 4h20Z" }),
132
+ direction == null && /* @__PURE__ */ jsx("circle", {
133
+ cx: "12",
134
+ cy: "12",
135
+ r: "8"
136
+ })
137
+ ]
138
+ });
139
+ }
140
+ /** A key-row swatch in the plot's own mark shape, so a name never depends on colour alone. */
141
+ function AnomalyKey({ label, variant, color }) {
142
+ return /* @__PURE__ */ jsxs("span", {
143
+ className: "inline-flex min-w-0 items-center gap-1.5",
144
+ children: [/* @__PURE__ */ jsxs("svg", {
145
+ width: "20",
146
+ height: "12",
147
+ viewBox: "0 0 20 12",
148
+ fill: "none",
149
+ "aria-hidden": "true",
150
+ className: "shrink-0",
151
+ style: { color },
152
+ children: [
153
+ variant === "band" && /* @__PURE__ */ jsx("rect", {
154
+ x: "1",
155
+ y: "2",
156
+ width: "18",
157
+ height: "8",
158
+ rx: "2",
159
+ fill: "currentColor",
160
+ opacity: "0.28"
161
+ }),
162
+ variant === "expected" && /* @__PURE__ */ jsx("line", {
163
+ x1: "2",
164
+ y1: "6",
165
+ x2: "18",
166
+ y2: "6",
167
+ stroke: "currentColor",
168
+ strokeWidth: 1,
169
+ strokeDasharray: "4 3",
170
+ strokeLinecap: "round",
171
+ opacity: "0.9"
172
+ }),
173
+ variant === "series" && /* @__PURE__ */ jsx("line", {
174
+ x1: "2",
175
+ y1: "6",
176
+ x2: "18",
177
+ y2: "6",
178
+ stroke: "currentColor",
179
+ strokeWidth: 2,
180
+ strokeLinecap: "round"
181
+ }),
182
+ variant === "flag" && /* @__PURE__ */ jsx("path", {
183
+ d: "M10 1.5 15 10.5H5Z",
184
+ fill: "currentColor"
185
+ })
186
+ ]
187
+ }), /* @__PURE__ */ jsx("span", {
188
+ className: "min-w-0 truncate",
189
+ children: label
190
+ })]
191
+ });
192
+ }
193
+ /** A generated insight: words as text, values in bold. */
194
+ function InsightText({ segments }) {
195
+ 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)) });
196
+ }
197
+ function useMergedRef(forwarded) {
198
+ const inner = useRef(null);
199
+ return [inner, useCallback((node) => {
200
+ inner.current = node;
201
+ if (typeof forwarded === "function") forwarded(node);
202
+ else if (forwarded) forwarded.current = node;
203
+ }, [forwarded])];
204
+ }
205
+ function usePlotWidth(ref, enabled) {
206
+ const [width, setWidth] = useState(0);
207
+ useEffect(() => {
208
+ if (!enabled) return;
209
+ const el = ref.current;
210
+ if (!el) return;
211
+ let frame = 0;
212
+ const measure = () => {
213
+ const next = Math.round(el.getBoundingClientRect().width);
214
+ setWidth((prev) => prev === next ? prev : next);
215
+ };
216
+ measure();
217
+ if (typeof ResizeObserver === "undefined") return;
218
+ const observer = new ResizeObserver(() => {
219
+ cancelAnimationFrame(frame);
220
+ frame = requestAnimationFrame(measure);
221
+ });
222
+ observer.observe(el);
223
+ return () => {
224
+ cancelAnimationFrame(frame);
225
+ observer.disconnect();
226
+ };
227
+ }, [ref, enabled]);
228
+ return width;
229
+ }
230
+ /**
231
+ * An anomaly panel for dashboards: one measure plotted against the range it
232
+ * was expected to stay inside, every reading that broke out marked with a
233
+ * direction-shaped flag, the excursions grouped into episodes and ranked as
234
+ * rows the reader can select, a verdict that says whether it is happening
235
+ * now, and every loading, empty, error and stale state — layout-stable, with
236
+ * a generated text equivalent and a data table in an info popover.
237
+ *
238
+ * Where `HighlightCard` explains ONE window it was handed, AnomalyCard is the
239
+ * surface that finds them: select an excursion here and hand its window on.
240
+ */
241
+ const AnomalyCard = forwardRef(({ label, name: nameProp, hint, points, bounds, selectedEpisode, defaultSelectedEpisode = null, onSelectedEpisodeChange, mark = "line", scale = "auto", headline = "latest", deltaMode = "absolute", goodDirection = "none", format = "compact", formatValue, scoreFormat = "number:1", unit, plotHeight: plotHeightProp, axes = true, band = true, expected = true, keys = true, episodes = true, maxEpisodes = 3, 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) => {
242
+ const labels = useMemo(() => resolveAnomalyLabels(labelOverrides), [labelOverrides]);
243
+ const sizes = sizeStyles[size];
244
+ const plotHeight = plotHeightProp ?? sizes.plotHeight + (axes ? X_AXIS_HEIGHT_PX : 0);
245
+ const name = nameProp ?? (typeof label === "string" ? label : MISSING_NAME);
246
+ const episodeSlots = Math.max(1, Math.round(maxEpisodes));
247
+ const id = useId();
248
+ const headingId = `${id}-label`;
249
+ const descriptionId = `${id}-description`;
250
+ const hintId = `${id}-hint`;
251
+ const tableId = `${id}-table`;
252
+ const episodesId = `${id}-episodes`;
253
+ const standingId = `${id}-standing`;
254
+ const [rootRef, setRootRef] = useMergedRef(forwardedRef);
255
+ const plotRef = useRef(null);
256
+ const [tableOpen, setTableOpen] = useState(defaultTableOpen);
257
+ const [popoverContainer, setPopoverContainer] = useState(null);
258
+ const tokens = useChartTokens(rootRef);
259
+ const isSelectionControlled = selectedEpisode !== void 0;
260
+ const [innerSelected, setInnerSelected] = useState(defaultSelectedEpisode);
261
+ const selectionRequest = isSelectionControlled ? selectedEpisode : innerSelected;
262
+ const pendingAnnounce = useRef(false);
263
+ const [announcement, setAnnouncement] = useState("");
264
+ const summary = useMemo(() => tokens ? normalizeAnomalies(points, {
265
+ granularity,
266
+ bounds,
267
+ goodDirection,
268
+ tokens
269
+ }) : null, [
270
+ tokens,
271
+ points,
272
+ granularity,
273
+ bounds,
274
+ goodDirection
275
+ ]);
276
+ const formatters = useMemo(() => createAnomalyFormatters({
277
+ format,
278
+ locale,
279
+ timeZone,
280
+ formatValue,
281
+ xType: summary?.xType ?? "temporal",
282
+ granularity: summary?.granularity ?? granularity ?? "day",
283
+ deltaMode,
284
+ percentagePoints: labels.percentagePoints,
285
+ scoreFormat
286
+ }), [
287
+ format,
288
+ locale,
289
+ timeZone,
290
+ formatValue,
291
+ summary,
292
+ granularity,
293
+ deltaMode,
294
+ labels.percentagePoints,
295
+ scoreFormat
296
+ ]);
297
+ const standing = useMemo(() => deriveAnomalyStanding(summary), [summary]);
298
+ const hasReadings = (summary?.count ?? 0) > 0;
299
+ const hasPoints = (points?.length ?? 0) > 0;
300
+ const state = deriveAnomalyState({
301
+ status,
302
+ hasValue: hasReadings,
303
+ hasData: hasPoints,
304
+ filtered
305
+ });
306
+ const showContent = state === "ready" || state === "refreshing";
307
+ const hasChart = summary != null && summary.periods.length >= 2 && summary.count >= 2;
308
+ const insufficient = showContent && summary != null && !hasChart;
309
+ const hasTable = showContent && summary != null && summary.periods.length > 0;
310
+ const showBand = band && (summary?.hasBand ?? false);
311
+ const showExpected = expected && (summary?.hasExpected ?? false);
312
+ const showScore = summary?.hasScore ?? false;
313
+ const allEpisodes = summary?.episodes ?? NO_EPISODES;
314
+ const rankedEpisodes = useMemo(() => {
315
+ const magnitude = (episode) => {
316
+ if (!episode.peak) return -1;
317
+ return episode.peak.bound === 0 ? Math.abs(episode.peak.deviation) : Math.abs(episode.peak.deviation / episode.peak.bound);
318
+ };
319
+ return [...allEpisodes].sort((a, b) => magnitude(b) - magnitude(a));
320
+ }, [allEpisodes]);
321
+ const selectedKey = useMemo(() => selectionRequest != null && allEpisodes.some((episode) => episode.key === selectionRequest) ? selectionRequest : null, [selectionRequest, allEpisodes]);
322
+ const selectEpisode = useCallback((key) => {
323
+ pendingAnnounce.current = true;
324
+ if (!isSelectionControlled) setInnerSelected(key);
325
+ onSelectedEpisodeChange?.(key);
326
+ }, [isSelectionControlled, onSelectedEpisodeChange]);
327
+ const asOfDate = updatedAt == null ? null : updatedAt instanceof Date ? updatedAt : new Date(updatedAt);
328
+ const asOfValid = asOfDate != null && !Number.isNaN(asOfDate.getTime());
329
+ const asOfText = asOfValid ? typeof updatedAtFormat === "function" ? updatedAtFormat(asOfDate) : formatDate(asOfDate, {
330
+ granularity: updatedAtFormat,
331
+ locale,
332
+ timeZone
333
+ }) : void 0;
334
+ const cardDescription = description ?? buildAnomalyDescription({
335
+ name,
336
+ state,
337
+ summary,
338
+ standing,
339
+ formatters,
340
+ asOfText,
341
+ labels
342
+ });
343
+ useEffect(() => {
344
+ if (!pendingAnnounce.current) return;
345
+ pendingAnnounce.current = false;
346
+ setAnnouncement(cardDescription);
347
+ }, [cardDescription]);
348
+ const standingExplanation = useMemo(() => {
349
+ if (!summary?.latest) return null;
350
+ return buildAnomalyExplanation({
351
+ row: summary.latest,
352
+ formatters,
353
+ labels
354
+ });
355
+ }, [
356
+ summary,
357
+ formatters,
358
+ labels
359
+ ]);
360
+ const standingRows = standingExplanation ? labels.explanation(standingExplanation) : [];
361
+ const standingText = standingRows.map(([key, value]) => `${key}: ${value}`).join(". ");
362
+ const plotWidth = usePlotWidth(plotRef, showContent && hasChart);
363
+ const tooltipContent = useCallback(({ item }) => {
364
+ const raw = item.datum;
365
+ const datum = raw && "datum" in raw && raw.datum ? raw.datum : raw;
366
+ const x = datum && typeof datum === "object" && "x" in datum ? datum.x : void 0;
367
+ const model = buildAnomalyTooltip({
368
+ summary,
369
+ x,
370
+ formatters,
371
+ labels
372
+ });
373
+ if (!model) return null;
374
+ const rows = labels.explanation(model.explanation).map(([key, value]) => [key, value]);
375
+ if (model.flagged && model.directionText) rows.unshift([/* @__PURE__ */ jsxs("span", {
376
+ className: "inline-flex min-w-0 items-center gap-1.5",
377
+ children: [/* @__PURE__ */ jsx(FlagGlyph, {
378
+ direction: model.explanation.direction,
379
+ className: "size-2.5 shrink-0",
380
+ style: { color: model.color }
381
+ }), /* @__PURE__ */ jsx("span", {
382
+ className: "min-w-0 truncate",
383
+ children: model.directionText
384
+ })]
385
+ }, "flag"), ""]);
386
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows });
387
+ }, [
388
+ summary,
389
+ formatters,
390
+ labels
391
+ ]);
392
+ const { handler: tooltipHandler, tooltip } = useArcTooltipHandler({ content: tooltipContent });
393
+ const embedOptions = useMemo(() => arcEmbedOptions({ tooltip: tooltipHandler }), [tooltipHandler]);
394
+ const xType = summary?.xType;
395
+ const summaryGranularity = summary?.granularity;
396
+ const periodTitle = labels.tablePeriod;
397
+ const yTicks = Math.max(2, Math.round(plotHeight / PX_PER_Y_TICK));
398
+ const spec = useMemo(() => {
399
+ if (!tokens || !xType || !summaryGranularity) return null;
400
+ const timeGrammar = (kind) => [
401
+ kind,
402
+ locale ?? "",
403
+ timeZone ?? ""
404
+ ].join("@").replace(/@+$/, "");
405
+ const built = buildAnomalyPlotSpec({
406
+ mark,
407
+ scale,
408
+ xType,
409
+ height: plotHeight,
410
+ axes,
411
+ yTicks,
412
+ band: showBand,
413
+ expected: showExpected,
414
+ span: true,
415
+ tooltip: true,
416
+ xTitle: periodTitle,
417
+ yTitle: name,
418
+ plotFormat: locale ? `${format}@${locale}` : format,
419
+ timeFormat: timeGrammar(summaryGranularity),
420
+ axisTimeFormat: timeGrammar(anomalyAxisGranularity(summaryGranularity)),
421
+ locale,
422
+ timeZone
423
+ }, tokens);
424
+ return patchSpec ? patchSpec(built) : built;
425
+ }, [
426
+ tokens,
427
+ xType,
428
+ summaryGranularity,
429
+ mark,
430
+ scale,
431
+ plotHeight,
432
+ axes,
433
+ yTicks,
434
+ showBand,
435
+ showExpected,
436
+ periodTitle,
437
+ name,
438
+ format,
439
+ locale,
440
+ timeZone,
441
+ patchSpec
442
+ ]);
443
+ const tableRows = summary?.rows;
444
+ const periodRows = useMemo(() => summary ? buildAnomalyPeriodRows(summary) : [], [summary]);
445
+ const spanRows = useMemo(() => buildAnomalySpanRows(summary, selectedKey), [summary, selectedKey]);
446
+ const datasets = useMemo(() => ({
447
+ [ANOMALY_TABLE_DATASET]: tableRows ?? [],
448
+ [ANOMALY_PERIODS_DATASET]: periodRows,
449
+ [ANOMALY_SPAN_DATASET]: spanRows
450
+ }), [
451
+ tableRows,
452
+ periodRows,
453
+ spanRows
454
+ ]);
455
+ const chartDescription = summary && hasChart ? buildAnomalyChartDescription({
456
+ name,
457
+ mark,
458
+ summary,
459
+ formatters,
460
+ labels
461
+ }) : void 0;
462
+ const insightContent = insight === void 0 ? summary && hasChart ? /* @__PURE__ */ jsx(InsightText, { segments: buildAnomalyInsight({
463
+ name,
464
+ summary,
465
+ standing,
466
+ formatters,
467
+ labels
468
+ }) }) : null : insight;
469
+ const showInsight = insightContent != null && insightContent !== false;
470
+ const reserveHeadline = headline !== "none";
471
+ const reserveInsight = insight === void 0 ? true : insight != null && insight !== false;
472
+ const headlineMinHeight = reserveHeadline ? Math.max(sizes.valueLinePx + sizes.captionLinePx, sizes.verdictLinePx + 4 + sizes.captionLinePx) : 0;
473
+ const episodesMinHeight = episodes ? sizes.captionLinePx + 4 + episodeSlots * sizes.episodeRowPx : 0;
474
+ const bodyMinHeight = (reserveHeadline ? headlineMinHeight + sizes.gapPx : 0) + plotHeight + (keys ? sizes.gapPx + sizes.keyLinePx : 0) + (episodes ? sizes.gapPx + episodesMinHeight : 0) + (reserveInsight ? sizes.gapPx + 88 : 0);
475
+ const HeadingTag = `h${titleLevel}`;
476
+ const busy = state === "loading" || state === "refreshing";
477
+ const captionText = summary && hasChart && !summary.hasBand ? labels.unjudged : null;
478
+ const worstPeak = summary?.worst?.peak ?? null;
479
+ const headlineValue = headline === "count" ? String(allEpisodes.length) : headline === "worst" ? worstPeak ? formatters.formatDeviation(worstPeak.deviation, worstPeak.bound) ?? "—" : "—" : formatters.formatValue(summary?.latest?.y);
480
+ const headlineCaption = headline === "count" ? summary ? labels.ofJudged(summary.judged) : null : headline === "worst" ? worstPeak ? `${formatters.formatX(worstPeak.x)} · ${worstPeak.direction === "high" ? labels.high : labels.low}` : null : summary?.latest && (summary.latest.lo != null || summary.latest.hi != null) ? labels.expectedRange(formatters.formatRange(summary.latest.lo, summary.latest.hi)) : null;
481
+ const headlineClasses = headline === "worst" && summary?.worst ? polarityTextClasses[summary.worst.polarity] : "text-foreground";
482
+ const shownEpisodes = rankedEpisodes.slice(0, allEpisodes.length > episodeSlots ? episodeSlots - 1 : episodeSlots);
483
+ const overflow = allEpisodes.length - shownEpisodes.length;
484
+ const episodeRow = (episode) => {
485
+ const range = formatters.formatXRange(episode.start, episode.end);
486
+ const isSelected = episode.key === selectedKey;
487
+ const directionText = episode.mixed ? labels.mixed : episode.direction === "high" ? labels.high : episode.direction === "low" ? labels.low : labels.flagKey;
488
+ const deviation = episode.peak ? formatters.formatDeviation(episode.peak.deviation, episode.peak.bound) : null;
489
+ const score = episode.peak ? formatters.formatScore(episode.peak.score) : null;
490
+ return /* @__PURE__ */ jsxs("button", {
491
+ type: "button",
492
+ "aria-pressed": isSelected,
493
+ "aria-label": labels.openEpisode({
494
+ range,
495
+ direction: directionText,
496
+ periods: labels.periodCount(episode.count),
497
+ deviation,
498
+ score,
499
+ ongoing: episode.ongoing
500
+ }),
501
+ onClick: () => selectEpisode(isSelected ? null : episode.key),
502
+ className: cn("flex w-full min-w-0 cursor-pointer items-center gap-2 rounded-md px-1.5 text-left font-sans transition-colors", "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring", sizes.body, isSelected ? "bg-primary-50 text-foreground" : "text-muted-foreground hover:bg-muted hover:text-foreground"),
503
+ style: { height: sizes.episodeRowPx },
504
+ "data-part": "episode",
505
+ "data-direction": episode.direction ?? void 0,
506
+ "data-selected": isSelected ? "" : void 0,
507
+ "data-ongoing": episode.ongoing ? "" : void 0,
508
+ children: [
509
+ /* @__PURE__ */ jsx(FlagGlyph, {
510
+ direction: episode.mixed ? null : episode.direction,
511
+ className: "size-2.5 shrink-0",
512
+ style: { color: episode.color }
513
+ }),
514
+ /* @__PURE__ */ jsxs("span", {
515
+ className: "min-w-0 flex-1 truncate text-foreground",
516
+ children: [
517
+ range,
518
+ /* @__PURE__ */ jsxs("span", {
519
+ className: "sr-only",
520
+ children: [", ", directionText]
521
+ }),
522
+ episode.ongoing && /* @__PURE__ */ jsxs("span", {
523
+ className: "ms-1.5 text-muted-foreground",
524
+ children: [
525
+ "(",
526
+ labels.ongoing,
527
+ ")"
528
+ ]
529
+ })
530
+ ]
531
+ }),
532
+ /* @__PURE__ */ jsx("span", {
533
+ className: "shrink-0 text-muted-foreground tabular-nums",
534
+ children: labels.periodCount(episode.count)
535
+ }),
536
+ deviation && /* @__PURE__ */ jsx("span", {
537
+ className: cn("shrink-0 font-medium tabular-nums", polarityTextClasses[episode.polarity]),
538
+ children: deviation
539
+ }),
540
+ score && /* @__PURE__ */ jsxs("span", {
541
+ className: "shrink-0 text-muted-foreground tabular-nums",
542
+ children: [/* @__PURE__ */ jsxs("span", {
543
+ className: "sr-only",
544
+ children: [labels.score, " "]
545
+ }), score]
546
+ })
547
+ ]
548
+ }, episode.key);
549
+ };
550
+ return /* @__PURE__ */ jsxs(Box, {
551
+ ref: setRootRef,
552
+ render: render ?? /* @__PURE__ */ jsx("figure", {}),
553
+ padding: sizes.padding,
554
+ "aria-labelledby": headingId,
555
+ "aria-describedby": descriptionId,
556
+ "aria-busy": busy || void 0,
557
+ "data-status": status,
558
+ "data-state": state,
559
+ "data-size": size,
560
+ "data-mark": mark,
561
+ "data-headline": headline,
562
+ "data-verdict": showContent ? standing.verdict : void 0,
563
+ "data-polarity": showContent ? standing.polarity : void 0,
564
+ "data-flagged": showContent && summary ? summary.flagged || void 0 : void 0,
565
+ "data-episodes": showContent ? allEpisodes.length || void 0 : void 0,
566
+ "data-selected": showContent ? selectedKey ?? void 0 : void 0,
567
+ "data-unjudged": showContent && summary && hasChart && !summary.hasBand ? "" : void 0,
568
+ "data-empty": state === "empty" || state === "no-match" ? "" : void 0,
569
+ "data-stale": stale ? "" : void 0,
570
+ "data-insufficient": insufficient ? "" : void 0,
571
+ ...rest,
572
+ className: cn("relative flex min-w-0 flex-col", sizes.gap, className),
573
+ children: [
574
+ /* @__PURE__ */ jsxs("div", {
575
+ className: "flex min-h-6 items-center justify-between gap-2",
576
+ children: [/* @__PURE__ */ jsxs("div", {
577
+ className: "flex min-w-0 items-center gap-1",
578
+ children: [/* @__PURE__ */ jsx(HeadingTag, {
579
+ id: headingId,
580
+ className: cn(labelClasses, sizes.label, "line-clamp-2"),
581
+ children: label
582
+ }), hint && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
583
+ render: /* @__PURE__ */ jsx(Button, {
584
+ variant: "ghost",
585
+ size: "sm",
586
+ iconOnly: true,
587
+ circular: true,
588
+ "aria-label": labels.about(name),
589
+ "aria-describedby": hintId,
590
+ className: headerControlClasses
591
+ }),
592
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
593
+ }), /* @__PURE__ */ jsx(TooltipPopup, { children: hint })] }), /* @__PURE__ */ jsx("span", {
594
+ id: hintId,
595
+ className: "sr-only",
596
+ children: hint
597
+ })] })]
598
+ }), /* @__PURE__ */ jsxs("div", {
599
+ className: "flex shrink-0 items-center gap-1",
600
+ children: [
601
+ stale && /* @__PURE__ */ jsx(Badge, {
602
+ variant: "warning",
603
+ appearance: "soft",
604
+ size: "sm",
605
+ children: labels.stale
606
+ }),
607
+ hasTable && /* @__PURE__ */ jsxs(Popover, {
608
+ open: tableOpen,
609
+ onOpenChange: setTableOpen,
610
+ children: [!hideTableToggle && /* @__PURE__ */ jsx(PopoverTrigger, {
611
+ render: /* @__PURE__ */ jsx(Button, {
612
+ variant: "ghost",
613
+ size: "sm",
614
+ iconOnly: true,
615
+ circular: true,
616
+ "aria-label": tableOpen ? labels.hideTable(name) : labels.showTable(name),
617
+ className: headerControlClasses
618
+ }),
619
+ children: /* @__PURE__ */ jsx(InfoIcon, { className: "size-3.5" })
620
+ }), popoverContainer && /* @__PURE__ */ jsxs(PopoverPopup, {
621
+ id: tableId,
622
+ anchor: rootRef,
623
+ container: popoverContainer,
624
+ side: "right",
625
+ align: "start",
626
+ collisionPadding: 8,
627
+ className: "pointer-events-auto w-[min(34rem,var(--available-width))]",
628
+ "data-part": "data-popover",
629
+ children: [/* @__PURE__ */ jsx(PopoverTitle, {
630
+ className: "mb-3",
631
+ children: name
632
+ }), /* @__PURE__ */ jsx(TableContainer, {
633
+ variant: "plain",
634
+ tabIndex: 0,
635
+ role: "region",
636
+ "aria-label": labels.tableCaption(name),
637
+ className: "max-h-[min(20rem,calc(var(--available-height)-5rem))] focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring",
638
+ "data-part": "table",
639
+ children: /* @__PURE__ */ jsxs(Table, {
640
+ size: "sm",
641
+ stickyHeader: true,
642
+ hoverable: false,
643
+ children: [
644
+ /* @__PURE__ */ jsx(TableCaption, {
645
+ side: "top",
646
+ className: "sr-only",
647
+ children: labels.tableCaption(name)
648
+ }),
649
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
650
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tablePeriod }),
651
+ /* @__PURE__ */ jsx(TableHead, {
652
+ className: "text-right",
653
+ children: labels.tableValue
654
+ }),
655
+ (summary?.hasExpected ?? false) && /* @__PURE__ */ jsx(TableHead, {
656
+ className: "text-right",
657
+ children: labels.tableExpected
658
+ }),
659
+ /* @__PURE__ */ jsx(TableHead, {
660
+ className: "text-right",
661
+ children: labels.tableRange
662
+ }),
663
+ /* @__PURE__ */ jsx(TableHead, {
664
+ className: "text-right",
665
+ children: labels.tableDeviation
666
+ }),
667
+ showScore && /* @__PURE__ */ jsx(TableHead, {
668
+ className: "text-right",
669
+ children: labels.score
670
+ }),
671
+ /* @__PURE__ */ jsx(TableHead, { children: labels.tableFlag })
672
+ ] }) }),
673
+ /* @__PURE__ */ jsx(TableBody, { children: (summary?.rows ?? []).map((row, at) => {
674
+ const bound = row.dir === "high" ? row.hi : row.dir === "low" ? row.lo : null;
675
+ const deviation = formatters.formatDeviation(row.d, bound);
676
+ const directionText = row.dir === "high" ? labels.high : row.dir === "low" ? labels.low : null;
677
+ return /* @__PURE__ */ jsxs(TableRow, {
678
+ "data-flagged": row.a ? "" : void 0,
679
+ children: [
680
+ /* @__PURE__ */ jsx(TableCell, { children: formatters.formatX(row.x) }),
681
+ /* @__PURE__ */ jsx(TableCell, {
682
+ className: "text-right tabular-nums",
683
+ children: formatters.formatValue(row.y)
684
+ }),
685
+ (summary?.hasExpected ?? false) && /* @__PURE__ */ jsx(TableCell, {
686
+ className: "text-right tabular-nums",
687
+ children: formatters.formatValue(row.ex)
688
+ }),
689
+ /* @__PURE__ */ jsx(TableCell, {
690
+ className: "text-right tabular-nums",
691
+ children: formatters.formatRange(row.lo, row.hi)
692
+ }),
693
+ /* @__PURE__ */ jsx(TableCell, {
694
+ className: "text-right tabular-nums",
695
+ children: deviation ?? "—"
696
+ }),
697
+ showScore && /* @__PURE__ */ jsx(TableCell, {
698
+ className: "text-right tabular-nums",
699
+ children: formatters.formatScore(row.sc) ?? "—"
700
+ }),
701
+ /* @__PURE__ */ jsx(TableCell, { children: row.a ? /* @__PURE__ */ jsxs("span", {
702
+ className: "inline-flex items-center gap-1.5",
703
+ children: [/* @__PURE__ */ jsx(FlagGlyph, {
704
+ direction: row.dir,
705
+ className: "size-2.5 shrink-0",
706
+ style: { color: row.c }
707
+ }), directionText ?? labels.flagKey]
708
+ }) : /* @__PURE__ */ jsx("span", {
709
+ className: "text-muted-foreground",
710
+ children: labels.inRange
711
+ }) })
712
+ ]
713
+ }, at);
714
+ }) })
715
+ ]
716
+ })
717
+ })]
718
+ })]
719
+ }),
720
+ action
721
+ ]
722
+ })]
723
+ }),
724
+ /* @__PURE__ */ jsx("p", {
725
+ id: descriptionId,
726
+ className: "sr-only",
727
+ children: cardDescription
728
+ }),
729
+ /* @__PURE__ */ jsx("p", {
730
+ role: "status",
731
+ "aria-live": "polite",
732
+ className: "sr-only",
733
+ "data-part": "announcement",
734
+ children: announcement
735
+ }),
736
+ /* @__PURE__ */ jsxs("div", {
737
+ className: "relative flex min-w-0 flex-col",
738
+ style: { minHeight: bodyMinHeight },
739
+ children: [
740
+ state === "loading" && /* @__PURE__ */ jsxs(SkeletonGroup, {
741
+ className: cn("flex flex-col", sizes.gap),
742
+ children: [
743
+ reserveHeadline && /* @__PURE__ */ jsxs("div", {
744
+ className: "flex min-w-0 items-start justify-between gap-2",
745
+ style: { minHeight: headlineMinHeight },
746
+ children: [/* @__PURE__ */ jsxs("div", {
747
+ className: "flex w-2/5 flex-col gap-1",
748
+ children: [/* @__PURE__ */ jsx(Skeleton, {
749
+ variant: "rounded",
750
+ className: "w-full",
751
+ style: { height: sizes.valueLinePx }
752
+ }), /* @__PURE__ */ jsx(Skeleton, {
753
+ variant: "rounded",
754
+ className: "w-3/4",
755
+ style: { height: sizes.captionLinePx }
756
+ })]
757
+ }), /* @__PURE__ */ jsx(Skeleton, {
758
+ variant: "rounded",
759
+ className: "w-24 max-w-1/3",
760
+ style: { height: sizes.verdictLinePx }
761
+ })]
762
+ }),
763
+ /* @__PURE__ */ jsx(Skeleton, {
764
+ variant: "rounded",
765
+ className: "w-full",
766
+ style: { height: plotHeight }
767
+ }),
768
+ keys && /* @__PURE__ */ jsx("div", {
769
+ className: "flex flex-wrap gap-2",
770
+ children: Array.from({ length: 3 }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
771
+ variant: "rounded",
772
+ className: "w-20",
773
+ style: { height: sizes.keyLinePx }
774
+ }, index))
775
+ }),
776
+ episodes && /* @__PURE__ */ jsxs("div", {
777
+ className: "flex flex-col gap-1",
778
+ style: { minHeight: episodesMinHeight },
779
+ children: [/* @__PURE__ */ jsx(Skeleton, {
780
+ variant: "rounded",
781
+ className: "w-24",
782
+ style: { height: sizes.captionLinePx }
783
+ }), Array.from({ length: episodeSlots }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
784
+ variant: "rounded",
785
+ className: "w-full",
786
+ style: { height: sizes.episodeRowPx - 4 }
787
+ }, index))]
788
+ }),
789
+ reserveInsight && /* @__PURE__ */ jsxs("div", {
790
+ className: "flex items-start gap-2",
791
+ children: [/* @__PURE__ */ jsx(Skeleton, {
792
+ variant: "rounded",
793
+ className: "size-4 shrink-0"
794
+ }), /* @__PURE__ */ jsx("div", {
795
+ className: "flex min-w-0 flex-1 flex-col justify-center gap-1",
796
+ children: Array.from({ length: INSIGHT_LINES }, (_, index) => /* @__PURE__ */ jsx(Skeleton, {
797
+ variant: "rounded",
798
+ className: index === 3 ? "w-2/3" : "w-full",
799
+ style: { height: 16 }
800
+ }, index))
801
+ })]
802
+ })
803
+ ]
804
+ }),
805
+ state === "error" && /* @__PURE__ */ jsx(SystemState, {
806
+ variant: "server-error",
807
+ scope: "inline",
808
+ title: labels.error,
809
+ description: false,
810
+ action: onRetry ? /* @__PURE__ */ jsx(Button, {
811
+ size: "sm",
812
+ onClick: onRetry,
813
+ children: labels.retry
814
+ }) : void 0,
815
+ className: cn(panelStateClasses, "my-auto")
816
+ }),
817
+ state === "empty" && /* @__PURE__ */ jsx(SystemState, {
818
+ variant: "empty",
819
+ scope: "inline",
820
+ title: labels.noData,
821
+ description: false,
822
+ className: cn(panelStateClasses, "my-auto")
823
+ }),
824
+ state === "no-match" && /* @__PURE__ */ jsx(SystemState, {
825
+ variant: "no-results",
826
+ scope: "inline",
827
+ title: labels.noMatch,
828
+ description: false,
829
+ action: onClearFilters ? /* @__PURE__ */ jsx(Button, {
830
+ variant: "outline",
831
+ size: "sm",
832
+ onClick: onClearFilters,
833
+ children: labels.clearFilters
834
+ }) : void 0,
835
+ className: cn(panelStateClasses, "my-auto")
836
+ }),
837
+ showContent && /* @__PURE__ */ jsxs("div", {
838
+ className: cn("flex min-w-0 flex-col", sizes.gap),
839
+ children: [
840
+ headline !== "none" && /* @__PURE__ */ jsxs("div", {
841
+ className: "flex min-w-0 items-start justify-between gap-2",
842
+ style: { minHeight: headlineMinHeight },
843
+ "data-part": "headline",
844
+ children: [/* @__PURE__ */ jsxs("div", {
845
+ className: "flex min-w-0 flex-col",
846
+ "data-part": "value",
847
+ children: [/* @__PURE__ */ jsxs("span", {
848
+ className: "flex min-w-0 flex-wrap items-baseline gap-x-1.5",
849
+ children: [/* @__PURE__ */ jsx("span", {
850
+ className: cn(valueClasses, sizes.value, "min-w-0 wrap-break-word", headlineClasses),
851
+ children: headlineValue
852
+ }), headline === "latest" && unit != null && /* @__PURE__ */ jsx("span", {
853
+ className: cn("font-sans text-muted-foreground", sizes.unit),
854
+ children: unit
855
+ })]
856
+ }), headlineCaption && /* @__PURE__ */ jsx("span", {
857
+ className: cn(captionClasses, "mt-1 min-w-0 wrap-break-word"),
858
+ children: headlineCaption
859
+ })]
860
+ }), /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsxs(TooltipTrigger, {
861
+ delay: 150,
862
+ render: /* @__PURE__ */ jsx("p", {
863
+ tabIndex: 0,
864
+ role: "group",
865
+ "aria-label": labels.standingGroup(name),
866
+ "aria-describedby": standingExplanation ? standingId : void 0,
867
+ 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),
868
+ "data-part": "verdict"
869
+ }),
870
+ children: [standingExplanation && /* @__PURE__ */ jsx("span", {
871
+ id: standingId,
872
+ className: "sr-only",
873
+ children: standingText
874
+ }), /* @__PURE__ */ jsxs(Badge, {
875
+ variant: verdictBadgeVariant[standing.verdict],
876
+ appearance: "soft",
877
+ size: sizes.badge,
878
+ "data-verdict": standing.verdict,
879
+ children: [/* @__PURE__ */ jsx(VerdictGlyph, { verdict: standing.verdict }), labels.verdicts[standing.verdict]]
880
+ })]
881
+ }), standingExplanation && /* @__PURE__ */ jsx(TooltipPopup, {
882
+ arrow: false,
883
+ side: "bottom",
884
+ align: "end",
885
+ sideOffset: 8,
886
+ collisionPadding: 8,
887
+ container: popoverContainer,
888
+ className: "text-left",
889
+ "data-part": "verdict-tooltip",
890
+ children: /* @__PURE__ */ jsx(ArcTooltipRows, { rows: standingRows })
891
+ })] })]
892
+ }),
893
+ /* @__PURE__ */ jsx("div", {
894
+ ref: plotRef,
895
+ className: "relative w-full",
896
+ style: { height: plotHeight },
897
+ "data-part": "plot",
898
+ children: hasChart ? plotWidth > 0 && spec ? /* @__PURE__ */ jsx(ArcVegaChart, {
899
+ spec,
900
+ options: embedOptions,
901
+ datasets,
902
+ description: chartDescription,
903
+ width: plotWidth,
904
+ height: plotHeight,
905
+ onEmbed,
906
+ className: "absolute inset-0 [&_svg]:block [&_.vega-bindings:empty]:hidden",
907
+ fallback: /* @__PURE__ */ jsx(Skeleton, {
908
+ variant: "rounded",
909
+ className: "size-full"
910
+ })
911
+ }) : /* @__PURE__ */ jsx(Skeleton, {
912
+ variant: "rounded",
913
+ className: "size-full"
914
+ }) : /* @__PURE__ */ jsx("p", {
915
+ className: cn(captionClasses, "flex h-full items-center"),
916
+ children: labels.insufficient
917
+ })
918
+ }),
919
+ captionText && /* @__PURE__ */ jsx("p", {
920
+ className: cn(captionClasses, "-mt-1"),
921
+ "data-part": "caption",
922
+ children: captionText
923
+ }),
924
+ keys && /* @__PURE__ */ jsx(HorizontalScroll, {
925
+ fade: true,
926
+ className: cn("min-w-0 font-sans text-muted-foreground", sizes.body),
927
+ style: { height: sizes.keyLinePx },
928
+ "data-part": "keys",
929
+ children: /* @__PURE__ */ jsxs("div", {
930
+ className: "flex items-center gap-x-3 whitespace-nowrap",
931
+ children: [
932
+ /* @__PURE__ */ jsx(AnomalyKey, {
933
+ variant: "series",
934
+ label: labels.seriesKey,
935
+ color: tokens?.emphasis
936
+ }),
937
+ showBand && /* @__PURE__ */ jsx(AnomalyKey, {
938
+ variant: "band",
939
+ label: labels.bandKey,
940
+ color: tokens?.context
941
+ }),
942
+ showExpected && /* @__PURE__ */ jsx(AnomalyKey, {
943
+ variant: "expected",
944
+ label: labels.expectedKey,
945
+ color: tokens?.context
946
+ }),
947
+ (summary?.flagged ?? 0) > 0 && /* @__PURE__ */ jsx(AnomalyKey, {
948
+ variant: "flag",
949
+ label: labels.flagKey,
950
+ color: (summary?.current ?? summary?.worst)?.color
951
+ })
952
+ ]
953
+ })
954
+ }),
955
+ episodes && /* @__PURE__ */ jsxs("div", {
956
+ className: "flex min-w-0 flex-col gap-1",
957
+ style: { minHeight: episodesMinHeight },
958
+ "data-part": "episodes",
959
+ children: [/* @__PURE__ */ jsx("p", {
960
+ id: episodesId,
961
+ className: cn(captionClasses, "font-medium"),
962
+ children: labels.episodes
963
+ }), shownEpisodes.length > 0 ? /* @__PURE__ */ jsxs("div", {
964
+ role: "group",
965
+ "aria-labelledby": episodesId,
966
+ className: "flex min-w-0 flex-col",
967
+ children: [shownEpisodes.map(episodeRow), overflow > 0 && /* @__PURE__ */ jsx("p", {
968
+ className: cn("flex items-center px-1.5 font-sans text-muted-foreground", sizes.body),
969
+ style: { height: sizes.episodeRowPx },
970
+ "data-part": "episode-overflow",
971
+ children: labels.moreEpisodes(overflow)
972
+ })]
973
+ }) : /* @__PURE__ */ jsx("p", {
974
+ className: cn(captionClasses, "flex items-center px-1.5"),
975
+ style: { height: sizes.episodeRowPx },
976
+ children: labels.noEpisodes
977
+ })]
978
+ }),
979
+ showInsight && /* @__PURE__ */ jsxs("div", {
980
+ className: cn("flex items-start gap-2 font-sans text-foreground", sizes.body),
981
+ "data-part": "insight",
982
+ children: [/* @__PURE__ */ jsx(AILoader, {
983
+ size: "sm",
984
+ state: "default",
985
+ label: labels.insightMark,
986
+ className: cn("shrink-0", size === "sm" ? "" : "mt-0.5")
987
+ }), /* @__PURE__ */ jsx("p", {
988
+ className: "min-w-0 flex-1 text-xs leading-[1.8] [&_strong]:font-semibold [&_strong]:text-foreground",
989
+ children: insightContent
990
+ })]
991
+ })
992
+ ]
993
+ }),
994
+ state === "refreshing" && /* @__PURE__ */ jsx("div", {
995
+ "aria-hidden": "true",
996
+ className: "absolute -inset-1 rounded-md bg-veil"
997
+ })
998
+ ]
999
+ }),
1000
+ asOfValid && asOfDate && /* @__PURE__ */ jsx("p", {
1001
+ className: cn(captionClasses, "text-right"),
1002
+ "data-part": "as-of",
1003
+ children: /* @__PURE__ */ jsx("time", {
1004
+ dateTime: asOfDate.toISOString(),
1005
+ children: asOfText
1006
+ })
1007
+ }),
1008
+ /* @__PURE__ */ jsx("div", {
1009
+ ref: setPopoverContainer,
1010
+ className: "pointer-events-none absolute inset-0",
1011
+ "data-part": "popover-host"
1012
+ }),
1013
+ tooltip
1014
+ ]
1015
+ });
1016
+ });
1017
+ AnomalyCard.displayName = "AnomalyCard";
1018
+ //#endregion
1019
+ export { AnomalyCard };