@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,627 @@
1
+ import { formatDate, formatDateRange, formatDelta, formatNumber, parseArcNumberFormat } from "../../lib/vega/formatters.js";
2
+ //#region src/components/MetricCard/MetricCard.model.ts
3
+ const DEFAULT_METRIC_LABELS = {
4
+ previousPeriod: "vs previous period",
5
+ increase: "Up",
6
+ decrease: "Down",
7
+ unchanged: "No change",
8
+ ofTarget: "of target",
9
+ vsTarget: "vs target",
10
+ percentagePoints: " pp",
11
+ noData: "No data",
12
+ noMatch: "No matching data",
13
+ clearFilters: "Clear filters",
14
+ error: "Couldn't load this metric",
15
+ retry: "Try again",
16
+ insufficientTrend: "Not enough data for a trend",
17
+ stale: "Stale",
18
+ asOf: "As of",
19
+ tablePeriod: "Period",
20
+ tableValue: "Value",
21
+ target: "Target",
22
+ highlightStart: "Start",
23
+ highlightEnd: "End",
24
+ highlightChange: "Change",
25
+ highlightChangePercent: "Change %",
26
+ highlightRange: "Period",
27
+ highlighted: "highlighted",
28
+ versus: (baseline) => `vs ${baseline}`,
29
+ chooseComparison: (name) => `Choose what ${name} is compared with`,
30
+ compareWith: "Compare with",
31
+ previous: "Previous",
32
+ changePercent: "Change %",
33
+ changeAmount: "Change",
34
+ highlightDetails: (name) => `Details of the ${name} highlight`,
35
+ highlightReadings: (name) => `Readings of ${name} inside the highlighted period`,
36
+ highlightBefore: "Before",
37
+ highlightSince: "Since",
38
+ highlightLatest: "Latest",
39
+ markNames: {
40
+ line: "Line",
41
+ area: "Area",
42
+ bar: "Bar"
43
+ },
44
+ showTable: (name) => `Show the data behind ${name}`,
45
+ hideTable: (name) => `Hide the data behind ${name}`,
46
+ about: (name) => `About ${name}`,
47
+ loading: (name) => `Loading ${name}`,
48
+ tableCaption: (name) => `Data behind the ${name} trend`,
49
+ targetProgress: (name) => `${name} progress to target`,
50
+ targetExplanation: ({ percentText, progressAmountText, targetText, varianceText, direction }) => [
51
+ ["Progress toward target", percentText],
52
+ [`Amount at ${percentText}`, progressAmountText],
53
+ ["Target", targetText],
54
+ [direction === "down" ? "Below target" : direction === "up" ? "Above target" : "On target", varianceText]
55
+ ],
56
+ deltaComparison: (name) => `${name} comparison`,
57
+ deltaExplanation: ({ kind, previousText, previousLabel, currentText, changeText, secondaryText, renderedMode, direction, comparisonText, periodText }) => {
58
+ const rows = [];
59
+ if (kind === "window" && periodText && periodText !== comparisonText) rows.push(["Period", periodText]);
60
+ rows.push([kind === "window" ? "Start" : previousLabel ?? "Previous", previousText], [kind === "window" ? "End" : "Current", currentText], [direction === "up" ? "Increase" : direction === "down" ? "Decrease" : "No change", changeText]);
61
+ if (secondaryText) rows.push([renderedMode === "percent" ? "Change" : "Change %", secondaryText]);
62
+ return rows;
63
+ },
64
+ trendSummary: (count, first, last, min, max) => `Trend of ${count} points from ${first} to ${last}, ranging ${min} to ${max}.`,
65
+ chartDescription: ({ mark, name, count, first, last, min, max, latest, target }) => `${mark} sparkline of ${name}: ${count} points from ${first} to ${last}, ranging ${min} to ${max}, latest ${latest}.` + (target ? ` Reference line at the ${target} target.` : ""),
66
+ highlightSentence: ({ word, primary, secondary, range, label, from, to }) => {
67
+ const period = label ? `${label} (${range})` : range;
68
+ return primary ? `${word} ${primary}${secondary ? ` (${secondary})` : ""} over ${period}, from ${from} to ${to}.` : `${word} over ${period} (${from}).`;
69
+ },
70
+ highlightDescription: ({ range, label, from, to, direction, primary }) => {
71
+ return `Highlighted ${label ? `${label}, ${range}` : range}: ${from} to ${to}, ${direction === "flat" ? "unchanged" : `${direction === "up" ? "up" : "down"} ${primary}`}.`;
72
+ },
73
+ insightMark: "Insight",
74
+ highlightInsight: ({ name, direction, from, to, change, secondary, range }) => {
75
+ if (direction === "flat") return [
76
+ `${name} held at `,
77
+ { value: from },
78
+ ` over ${range}.`
79
+ ];
80
+ const segments = [
81
+ `${name} ${direction === "up" ? "rose" : "fell"} from `,
82
+ { value: from },
83
+ " to ",
84
+ { value: to },
85
+ ` over ${range}, a ${direction === "up" ? "rise" : "drop"} of `,
86
+ { value: change }
87
+ ];
88
+ if (secondary) segments.push(" (", { value: secondary }, ")");
89
+ segments.push(".");
90
+ return segments;
91
+ }
92
+ };
93
+ function resolveMetricLabels(overrides) {
94
+ return overrides ? {
95
+ ...DEFAULT_METRIC_LABELS,
96
+ ...overrides
97
+ } : DEFAULT_METRIC_LABELS;
98
+ }
99
+ /**
100
+ * The state machine. Error wins; loading over existing data is a refresh (the
101
+ * previous render stays under the veil); a missing value is `no-match` when
102
+ * the consumer's filter caused it and `empty` otherwise.
103
+ */
104
+ function deriveMetricState({ status, hasValue, hasData, filtered }) {
105
+ if (status === "error") return "error";
106
+ if (status === "loading") return hasData ? "refreshing" : "loading";
107
+ if (hasValue) return "ready";
108
+ return filtered ? "no-match" : "empty";
109
+ }
110
+ const isFinite = (value) => typeof value === "number" && Number.isFinite(value);
111
+ const DAY_MS = 864e5;
112
+ const ISO_DATE = /^\d{4}-\d{2}(-\d{2})?([T ].*)?$/;
113
+ function inferXType(points) {
114
+ const sample = points.find((point) => point.x != null);
115
+ if (!sample) return "ordinal";
116
+ const { x } = sample;
117
+ if (x instanceof Date) return "temporal";
118
+ if (typeof x === "number") return "quantitative";
119
+ return ISO_DATE.test(x) ? "temporal" : "ordinal";
120
+ }
121
+ function inferGranularity(rows) {
122
+ const xs = rows.map((row) => row.x).filter((x) => typeof x === "number");
123
+ if (xs.length < 2) return "day";
124
+ const gaps = [];
125
+ for (let index = 1; index < xs.length; index += 1) gaps.push(Math.abs(xs[index] - xs[index - 1]));
126
+ gaps.sort((a, b) => a - b);
127
+ const median = gaps[Math.floor(gaps.length / 2)];
128
+ if (median < DAY_MS) return "time";
129
+ if (median >= 360 * DAY_MS) return "year";
130
+ if (median >= 27 * DAY_MS) return "month";
131
+ return "day";
132
+ }
133
+ /**
134
+ * Normalises `trend` for Vega and the table: temporal `x` becomes epoch
135
+ * milliseconds (numbers are treated as quantitative; pass a `Date` or an ISO
136
+ * string for time), invalid dates are dropped, `null` readings are kept as
137
+ * gaps, and the range and the latest reading are summarised for the text
138
+ * equivalents. Returns `null` when there is no trend at all.
139
+ */
140
+ function normalizeTrend(points, granularity) {
141
+ if (!points) return null;
142
+ const xType = inferXType(points);
143
+ const rows = [];
144
+ for (const point of points) {
145
+ const y = isFinite(point.y) ? point.y : null;
146
+ if (xType === "temporal") {
147
+ const time = point.x instanceof Date ? point.x.getTime() : new Date(point.x).getTime();
148
+ if (Number.isNaN(time)) continue;
149
+ rows.push({
150
+ x: time,
151
+ y
152
+ });
153
+ } else if (xType === "quantitative") {
154
+ if (!isFinite(point.x)) continue;
155
+ rows.push({
156
+ x: point.x,
157
+ y
158
+ });
159
+ } else rows.push({
160
+ x: String(point.x),
161
+ y
162
+ });
163
+ }
164
+ const valid = rows.filter((row) => row.y != null);
165
+ let latest = null;
166
+ if (valid.length > 0) latest = xType === "ordinal" ? valid[valid.length - 1] : valid.reduce((best, row) => row.x >= best.x ? row : best);
167
+ const values = valid.map((row) => row.y);
168
+ return {
169
+ rows,
170
+ xType,
171
+ count: valid.length,
172
+ first: rows[0] ?? null,
173
+ last: rows[rows.length - 1] ?? null,
174
+ latest,
175
+ min: values.length > 0 ? Math.min(...values) : null,
176
+ max: values.length > 0 ? Math.max(...values) : null,
177
+ granularity: granularity ?? (xType === "temporal" ? inferGranularity(rows) : "day")
178
+ };
179
+ }
180
+ /** The comparison against the previous reading, with polarity resolved through `goodDirection`. */
181
+ function deriveDelta(value, previousValue, { deltaMode, goodDirection, format, locale, percentagePoints }) {
182
+ if (!isFinite(value) || !isFinite(previousValue)) return null;
183
+ const delta = formatDelta(value, previousValue, {
184
+ mode: deltaMode,
185
+ format,
186
+ locale,
187
+ pointsSuffix: percentagePoints
188
+ });
189
+ const other = formatDelta(value, previousValue, {
190
+ mode: delta.renderedMode === "percent" ? "absolute" : "percent",
191
+ format,
192
+ locale,
193
+ pointsSuffix: percentagePoints
194
+ });
195
+ let polarity = "neutral";
196
+ if (delta.direction !== "flat" && goodDirection !== "none") polarity = delta.direction === "up" === (goodDirection === "up") ? "positive" : "negative";
197
+ return {
198
+ direction: delta.direction,
199
+ polarity,
200
+ text: delta.text,
201
+ renderedMode: delta.renderedMode,
202
+ secondaryText: other.renderedMode === delta.renderedMode ? null : other.text
203
+ };
204
+ }
205
+ /** Progress against the target. `null` without a usable target (a zero target has no ratio). */
206
+ function deriveTarget(value, target, { goodDirection, format, locale, formatValue }) {
207
+ if (!isFinite(value) || !isFinite(target) || target === 0) return null;
208
+ const ratio = value / target;
209
+ const displayedRatio = Math.sign(ratio) * Math.round(Math.abs(ratio) * 100) / 100;
210
+ const progressAmount = displayedRatio * target;
211
+ const amountFormat = parseArcNumberFormat(format);
212
+ const amountPrecision = amountFormat.kind === "compact" || amountFormat.compact ? { maximumFractionDigits: Math.max(2, amountFormat.digits ?? 1) } : void 0;
213
+ const met = goodDirection === "down" ? value <= target : value >= target;
214
+ const variant = goodDirection === "none" ? "primary" : met ? "success" : goodDirection === "down" ? "destructive" : "primary";
215
+ const variance = formatDelta(value, target, {
216
+ mode: "percent",
217
+ format,
218
+ locale
219
+ });
220
+ return {
221
+ ratio,
222
+ met,
223
+ variant,
224
+ percentText: formatNumber(displayedRatio, "percent:0", locale),
225
+ progressAmountText: formatValue ? formatValue(progressAmount) : formatNumber(progressAmount, format, locale, amountPrecision),
226
+ targetText: formatValue ? formatValue(target) : formatNumber(target, format, locale),
227
+ varianceText: variance.text,
228
+ direction: variance.direction
229
+ };
230
+ }
231
+ /**
232
+ * The granularity an x axis labels its ticks at: the tooltip and table keep
233
+ * the full form (`Sep 8, 2026`); a 250px axis has room for `Sep 8`.
234
+ */
235
+ function axisGranularity(granularity) {
236
+ switch (granularity) {
237
+ case "day": return "day-short";
238
+ case "month": return "month-short";
239
+ case "datetime": return "time";
240
+ default: return granularity;
241
+ }
242
+ }
243
+ /** The two formatters every text surface shares — headline, delta, table, tooltip, descriptions. */
244
+ function createMetricFormatters({ format, locale, timeZone, formatValue, xType, granularity }) {
245
+ const formatX = (x) => {
246
+ if (xType === "temporal" && typeof x === "number") return formatDate(x, {
247
+ granularity,
248
+ locale,
249
+ timeZone
250
+ });
251
+ if (xType === "quantitative" && typeof x === "number") return formatNumber(x, "number", locale);
252
+ return String(x);
253
+ };
254
+ return {
255
+ formatY: (value) => isFinite(value) ? formatValue ? formatValue(value) : formatNumber(value, format, locale) : "—",
256
+ formatX,
257
+ formatXRange: (start, end) => xType === "temporal" && typeof start === "number" && typeof end === "number" ? formatDateRange(start, end, {
258
+ granularity,
259
+ locale,
260
+ timeZone
261
+ }) : `${formatX(start)} – ${formatX(end)}`
262
+ };
263
+ }
264
+ function normalizeHighlightBound(value, xType) {
265
+ if (xType === "temporal") {
266
+ const time = value instanceof Date ? value.getTime() : new Date(value).getTime();
267
+ return Number.isNaN(time) ? null : time;
268
+ }
269
+ if (xType === "quantitative") {
270
+ const numeric = typeof value === "number" ? value : Number(value);
271
+ return Number.isFinite(numeric) ? numeric : null;
272
+ }
273
+ return String(value);
274
+ }
275
+ const stripSign = (text) => text.replace(/^[+\-−]/, "");
276
+ /**
277
+ * Snaps a highlight window onto the trend and measures it. Each bound moves
278
+ * inward to the nearest finite reading (a temporal or quantitative bound need
279
+ * not hit a point exactly; an ordinal bound is a category label), the bounds
280
+ * may come in either order, and the result is `null` when the window does not
281
+ * hold two distinct finite readings — the card then draws the plain sparkline
282
+ * and stamps `data-highlight="unresolved"` rather than inventing a change.
283
+ */
284
+ function resolveHighlight(summary, range, { deltaMode, goodDirection, format, locale, percentagePoints, formatters }) {
285
+ if (!summary || !range) return null;
286
+ const { rows, xType } = summary;
287
+ const lo = normalizeHighlightBound(range.start, xType);
288
+ const hi = normalizeHighlightBound(range.end, xType);
289
+ if (lo == null || hi == null) return null;
290
+ let startIndex = -1;
291
+ let endIndex = -1;
292
+ if (xType === "ordinal") {
293
+ let first = rows.findIndex((row) => row.x === lo);
294
+ let last = -1;
295
+ for (let index = rows.length - 1; index >= 0; index -= 1) if (rows[index].x === hi) {
296
+ last = index;
297
+ break;
298
+ }
299
+ if (first < 0 || last < 0) return null;
300
+ if (first > last) [first, last] = [last, first];
301
+ while (first <= last && rows[first].y == null) first += 1;
302
+ while (last >= first && rows[last].y == null) last -= 1;
303
+ if (first >= last) return null;
304
+ startIndex = first;
305
+ endIndex = last;
306
+ } else {
307
+ const [min, max] = lo <= hi ? [lo, hi] : [hi, lo];
308
+ rows.forEach((row, index) => {
309
+ if (row.y == null || typeof row.x !== "number" || row.x < min || row.x > max) return;
310
+ if (startIndex < 0 || row.x < rows[startIndex].x) startIndex = index;
311
+ if (endIndex < 0 || row.x > rows[endIndex].x) endIndex = index;
312
+ });
313
+ if (startIndex < 0 || endIndex < 0 || rows[startIndex].x === rows[endIndex].x) return null;
314
+ }
315
+ const start = {
316
+ x: rows[startIndex].x,
317
+ y: rows[startIndex].y,
318
+ index: startIndex
319
+ };
320
+ const end = {
321
+ x: rows[endIndex].x,
322
+ y: rows[endIndex].y,
323
+ index: endIndex
324
+ };
325
+ const delta = deriveDelta(end.y, start.y, {
326
+ deltaMode,
327
+ goodDirection,
328
+ format,
329
+ locale,
330
+ percentagePoints
331
+ });
332
+ if (!delta) return null;
333
+ const absolute = formatDelta(end.y, start.y, {
334
+ mode: "absolute",
335
+ format,
336
+ locale,
337
+ pointsSuffix: percentagePoints
338
+ });
339
+ const percent = formatDelta(end.y, start.y, {
340
+ mode: "percent",
341
+ format,
342
+ locale
343
+ });
344
+ return {
345
+ start,
346
+ end,
347
+ label: range.label,
348
+ rangeText: formatters.formatXRange(start.x, end.x),
349
+ delta,
350
+ absoluteText: absolute.text,
351
+ percentText: percent.percent == null ? "—" : percent.text
352
+ };
353
+ }
354
+ /**
355
+ * The highlight variant's default insight sentence — what the card shows
356
+ * beside the AI mark when the consumer passes no `insight`. The window's start
357
+ * and end readings and its change (in the badge's form, with the other form
358
+ * in brackets) come back as `{ value }` segments so the card can set them in
359
+ * bold; the words come from `labels.highlightInsight`.
360
+ */
361
+ function buildHighlightInsight({ name, highlight, formatters, labels }) {
362
+ const flat = highlight.delta.direction === "flat";
363
+ const secondary = highlight.delta.renderedMode === "percent" ? highlight.absoluteText : highlight.percentText;
364
+ return labels.highlightInsight({
365
+ name,
366
+ direction: highlight.delta.direction,
367
+ from: formatters.formatY(highlight.start.y),
368
+ to: formatters.formatY(highlight.end.y),
369
+ change: flat ? "" : stripSign(highlight.delta.text),
370
+ secondary: flat || secondary === "—" ? null : stripSign(secondary),
371
+ range: highlight.rangeText,
372
+ label: highlight.label
373
+ });
374
+ }
375
+ /** The plain-text reading of insight segments — for descriptions and tests. */
376
+ function insightSegmentsToText(segments) {
377
+ return segments.map((segment) => typeof segment === "string" ? segment : segment.value).join("");
378
+ }
379
+ /**
380
+ * The datasets the highlight sparkline reads, derived once per trend or window
381
+ * change: every row flagged with `hl` (bars colour themselves from it, the
382
+ * hover dot too), the context readings split into a `before` and an `after`
383
+ * run so the gray line stops at the window's edges, the window's own readings
384
+ * for the coloured segment, its two endpoint dots, and the one-row band. A
385
+ * continuous axis decides membership by `x`, an ordinal one by position.
386
+ */
387
+ function buildHighlightDatasets(summary, info) {
388
+ const { rows, xType } = summary;
389
+ const tone = info.delta.polarity;
390
+ const at = (row, index) => xType === "ordinal" ? index : row.x;
391
+ const startAt = at(rows[info.start.index], info.start.index);
392
+ const endAt = at(rows[info.end.index], info.end.index);
393
+ const table = [];
394
+ const context = [];
395
+ const highlightRows = [];
396
+ rows.forEach((row, index) => {
397
+ const position = at(row, index);
398
+ const inside = position >= startAt && position <= endAt;
399
+ table.push({
400
+ ...row,
401
+ hl: inside,
402
+ tone: inside ? tone : null
403
+ });
404
+ if (inside) highlightRows.push({
405
+ ...row,
406
+ hl: true,
407
+ tone
408
+ });
409
+ if (position <= startAt) context.push({
410
+ ...row,
411
+ seg: "before"
412
+ });
413
+ if (position >= endAt) context.push({
414
+ ...row,
415
+ seg: "after"
416
+ });
417
+ });
418
+ return {
419
+ table,
420
+ context,
421
+ highlightRows,
422
+ highlightPoints: [{
423
+ x: info.start.x,
424
+ y: info.start.y,
425
+ role: "start",
426
+ tone
427
+ }, {
428
+ x: info.end.x,
429
+ y: info.end.y,
430
+ role: "end",
431
+ tone
432
+ }],
433
+ highlight: [{
434
+ start: info.start.x,
435
+ end: info.end.x,
436
+ startY: info.start.y,
437
+ endY: info.end.y,
438
+ tone
439
+ }]
440
+ };
441
+ }
442
+ /** The key of the one baseline a tile builds from `previousValue` for its compare-with menu. Never collides with a consumer's key. */
443
+ const METRIC_PREVIOUS_KEY = "__previous__";
444
+ /**
445
+ * Normalises the baselines a metric tile offers: keys default to labels (a
446
+ * repeated key is suffixed so two options never collide), non-finite values
447
+ * become `null`. The consumer's order is kept — it is the menu's order.
448
+ */
449
+ function normalizeComparisons(items) {
450
+ if (!items) return [];
451
+ const seen = /* @__PURE__ */ new Map();
452
+ return items.map((item) => {
453
+ const base = item.key ?? item.label;
454
+ const repeats = seen.get(base) ?? 0;
455
+ seen.set(base, repeats + 1);
456
+ return {
457
+ key: repeats === 0 ? base : `${base}·${repeats + 1}`,
458
+ label: item.label,
459
+ value: isFinite(item.value) ? item.value : null
460
+ };
461
+ });
462
+ }
463
+ /**
464
+ * The selected baseline. `null` designates none; a key names one — an absent
465
+ * key selects none rather than a substitute, so the card never compares
466
+ * against something the app did not ask for; `undefined` takes the first
467
+ * baseline with a value, else the first baseline.
468
+ */
469
+ function resolveComparison(rows, key) {
470
+ if (key === null) return null;
471
+ if (key !== void 0) return rows.find((row) => row.key === key) ?? null;
472
+ return rows.find((row) => row.value != null) ?? rows[0] ?? null;
473
+ }
474
+ /** The compare-with menu's table: every baseline with the change the headline would show against it, in percent and in the value's units. */
475
+ function describeComparisons(rows, value, { formatters, ...options }) {
476
+ return rows.map((row) => {
477
+ const delta = deriveDelta(value, row.value, options);
478
+ const percentText = delta ? delta.renderedMode === "percent" ? delta.text : delta.secondaryText : null;
479
+ const amountText = delta ? delta.renderedMode === "absolute" ? delta.text : delta.secondaryText : null;
480
+ return {
481
+ row,
482
+ delta,
483
+ polarity: delta?.polarity ?? "neutral",
484
+ valueText: formatters.formatY(row.value),
485
+ percentText: percentText ?? "—",
486
+ amountText: amountText ?? "—"
487
+ };
488
+ });
489
+ }
490
+ /**
491
+ * The detail behind a resolved window — what the sparkline cannot show at
492
+ * tile scale: the readings inside the window one by one, each with its step
493
+ * from the previous finite reading, and the trend's movement before the
494
+ * window and since it, so the reader can tell a step change from a slide and
495
+ * a recovery from a plateau. A continuous axis decides membership by `x`, an
496
+ * ordinal one by position.
497
+ */
498
+ function buildHighlightDetails(summary, info, { deltaMode, goodDirection, format, locale, percentagePoints, formatters }) {
499
+ const { rows, xType } = summary;
500
+ const deltaOptions = {
501
+ deltaMode,
502
+ goodDirection,
503
+ format,
504
+ locale,
505
+ percentagePoints
506
+ };
507
+ const at = (row, index) => xType === "ordinal" ? index : row.x;
508
+ const startAt = at(rows[info.start.index], info.start.index);
509
+ const endAt = at(rows[info.end.index], info.end.index);
510
+ const reading = (row) => ({
511
+ x: row.x,
512
+ xText: formatters.formatX(row.x),
513
+ yText: formatters.formatY(row.y)
514
+ });
515
+ const inside = [];
516
+ let first = null;
517
+ let firstAt = Number.POSITIVE_INFINITY;
518
+ let latest = null;
519
+ let latestAt = Number.NEGATIVE_INFINITY;
520
+ for (let index = 0; index < rows.length; index += 1) {
521
+ const row = rows[index];
522
+ const position = at(row, index);
523
+ if (position >= startAt && position <= endAt) inside.push({
524
+ row,
525
+ index,
526
+ position
527
+ });
528
+ if (row.y == null) continue;
529
+ if (position < startAt && position < firstAt) {
530
+ first = row;
531
+ firstAt = position;
532
+ }
533
+ if (position > endAt && position > latestAt) {
534
+ latest = row;
535
+ latestAt = position;
536
+ }
537
+ }
538
+ inside.sort((a, b) => a.position - b.position);
539
+ let previous = null;
540
+ const steps = inside.map(({ row, index }) => {
541
+ const delta = row.y != null && previous != null ? deriveDelta(row.y, previous, deltaOptions) : null;
542
+ if (row.y != null) previous = row.y;
543
+ return {
544
+ ...reading(row),
545
+ changeText: delta?.text ?? null,
546
+ polarity: delta?.polarity ?? "neutral",
547
+ role: index === info.start.index ? "start" : index === info.end.index ? "end" : "inside"
548
+ };
549
+ });
550
+ const stretch = (from, to) => {
551
+ const delta = deriveDelta(to.y, from.y, deltaOptions);
552
+ if (!delta) return null;
553
+ return {
554
+ from: reading(from),
555
+ to: reading(to),
556
+ rangeText: formatters.formatXRange(from.x, to.x),
557
+ changeText: delta.text,
558
+ secondaryText: delta.secondaryText,
559
+ direction: delta.direction,
560
+ polarity: delta.polarity
561
+ };
562
+ };
563
+ return {
564
+ steps,
565
+ before: first ? stretch(first, rows[info.start.index]) : null,
566
+ since: latest ? stretch(rows[info.end.index], latest) : null
567
+ };
568
+ }
569
+ /**
570
+ * The card's text equivalent (Amy Cesal's alt-text formula: what it is, what
571
+ * it shows, why it is here, when it was true) — the figure's accessible
572
+ * description, and the summary a screen reader gets before the parts.
573
+ */
574
+ function buildMetricDescription({ name, state, valueText, unit, delta, comparisonText, target, trend, formatters, asOfText, labels, highlight }) {
575
+ if (state === "loading") return `${labels.loading(name)}.`;
576
+ if (state === "error") return `${name}: ${labels.error}.`;
577
+ if (state === "empty") return `${name}: ${labels.noData}.`;
578
+ if (state === "no-match") return `${name}: ${labels.noMatch}.`;
579
+ const directionWord = (direction) => direction === "up" ? labels.increase : direction === "down" ? labels.decrease : labels.unchanged;
580
+ const sentences = [`${name}: ${valueText}${unit ? ` ${unit}` : ""}.`];
581
+ if (highlight) {
582
+ const flat = highlight.delta.direction === "flat";
583
+ const secondary = highlight.delta.renderedMode === "percent" ? highlight.absoluteText : highlight.percentText;
584
+ sentences.push(labels.highlightSentence({
585
+ word: directionWord(highlight.delta.direction),
586
+ primary: flat ? "" : stripSign(highlight.delta.text),
587
+ secondary: flat || secondary === "—" ? null : stripSign(secondary),
588
+ range: highlight.rangeText,
589
+ label: highlight.label,
590
+ from: formatters.formatY(highlight.start.y),
591
+ to: formatters.formatY(highlight.end.y)
592
+ }));
593
+ } else if (delta) {
594
+ const word = directionWord(delta.direction);
595
+ const magnitude = stripSign(delta.text);
596
+ sentences.push(delta.direction === "flat" ? `${word} ${comparisonText}.` : `${word} ${magnitude} ${comparisonText}.`);
597
+ }
598
+ if (target) sentences.push(`${target.percentText} ${labels.ofTarget} (${target.targetText}).`);
599
+ if (trend && trend.count >= 2 && trend.first && trend.last) sentences.push(labels.trendSummary(trend.count, formatters.formatX(trend.first.x), formatters.formatX(trend.last.x), formatters.formatY(trend.min), formatters.formatY(trend.max)));
600
+ if (asOfText) sentences.push(`${labels.asOf} ${asOfText}.`);
601
+ return sentences.join(" ");
602
+ }
603
+ /** The sparkline's own `description` — the `aria-label` Vega stamps on the `graphics-document`. */
604
+ function buildMetricChartDescription({ name, mark, trend, target, formatters, labels, highlight }) {
605
+ const base = labels.chartDescription({
606
+ mark: labels.markNames[mark],
607
+ name,
608
+ count: trend.count,
609
+ first: trend.first ? formatters.formatX(trend.first.x) : "—",
610
+ last: trend.last ? formatters.formatX(trend.last.x) : "—",
611
+ min: formatters.formatY(trend.min),
612
+ max: formatters.formatY(trend.max),
613
+ latest: trend.latest ? formatters.formatY(trend.latest.y) : "—",
614
+ target: target ? target.targetText : null
615
+ });
616
+ if (!highlight) return base;
617
+ return `${base} ${labels.highlightDescription({
618
+ range: highlight.rangeText,
619
+ label: highlight.label,
620
+ from: formatters.formatY(highlight.start.y),
621
+ to: formatters.formatY(highlight.end.y),
622
+ direction: highlight.delta.direction,
623
+ primary: stripSign(highlight.delta.text)
624
+ })}`;
625
+ }
626
+ //#endregion
627
+ export { DEFAULT_METRIC_LABELS, METRIC_PREVIOUS_KEY, axisGranularity, buildHighlightDatasets, buildHighlightDetails, buildHighlightInsight, buildMetricChartDescription, buildMetricDescription, createMetricFormatters, deriveDelta, deriveMetricState, deriveTarget, describeComparisons, insightSegmentsToText, normalizeComparisons, normalizeTrend, resolveComparison, resolveHighlight, resolveMetricLabels };
@@ -0,0 +1,4 @@
1
+ import { DEFAULT_METRIC_LABELS, DescribeComparisonsOptions, METRIC_PREVIOUS_KEY, MetricCardLabels, MetricCardSize, MetricCardState, MetricCardStatus, MetricCardVariant, MetricComparison, MetricComparisonCandidate, MetricComparisonRow, MetricDelta, MetricDeltaExplanation, MetricDeltaMode, MetricGoodDirection, MetricHighlightBand, MetricHighlightContextRow, MetricHighlightDatasets, MetricHighlightDetails, MetricHighlightInfo, MetricHighlightPoint, MetricHighlightRange, MetricHighlightReading, MetricHighlightReadingText, MetricHighlightStep, MetricHighlightStretch, MetricHighlightTrendRow, MetricInsightSegment, MetricPolarity, MetricTargetInfo, MetricTextFormatters, MetricTrendAxes, MetricTrendMark, MetricTrendPoint, MetricTrendRow, MetricTrendScale, MetricTrendSummary, MetricTrendXType, ResolveHighlightOptions, axisGranularity, buildHighlightDatasets, buildHighlightDetails, buildHighlightInsight, buildMetricChartDescription, buildMetricDescription, createMetricFormatters, deriveDelta, deriveMetricState, deriveTarget, describeComparisons, insightSegmentsToText, normalizeComparisons, normalizeTrend, resolveComparison, resolveHighlight, resolveMetricLabels } from "./MetricCard.model.js";
2
+ import { MetricCard, MetricCardBaseProps, MetricCardComparisonProps, MetricCardProps, MetricCardVariantProps } from "./MetricCard.js";
3
+ import { METRIC_CONTEXT_DATASET, METRIC_HIGHLIGHT_DATASET, METRIC_HIGHLIGHT_END_PARAM, METRIC_HIGHLIGHT_POINTS_DATASET, METRIC_HIGHLIGHT_ROWS_DATASET, METRIC_HIGHLIGHT_START_PARAM, METRIC_HOVER_PARAM, METRIC_LATEST_DATASET, METRIC_REFERENCE_DATASET, METRIC_TREND_DATASET, METRIC_X_TICKS_PARAM, MetricTrendSpecOptions, buildMetricTrendSpec } from "./MetricCard.chart.js";
4
+ export { DEFAULT_METRIC_LABELS, type DescribeComparisonsOptions, METRIC_CONTEXT_DATASET, METRIC_HIGHLIGHT_DATASET, METRIC_HIGHLIGHT_END_PARAM, METRIC_HIGHLIGHT_POINTS_DATASET, METRIC_HIGHLIGHT_ROWS_DATASET, METRIC_HIGHLIGHT_START_PARAM, METRIC_HOVER_PARAM, METRIC_LATEST_DATASET, METRIC_PREVIOUS_KEY, METRIC_REFERENCE_DATASET, METRIC_TREND_DATASET, METRIC_X_TICKS_PARAM, MetricCard, type MetricCardBaseProps, type MetricCardComparisonProps, type MetricCardLabels, type MetricCardProps, type MetricCardSize, type MetricCardState, type MetricCardStatus, type MetricCardVariant, type MetricCardVariantProps, type MetricComparison, type MetricComparisonCandidate, type MetricComparisonRow, type MetricDelta, type MetricDeltaExplanation, type MetricDeltaMode, type MetricGoodDirection, type MetricHighlightBand, type MetricHighlightContextRow, type MetricHighlightDatasets, type MetricHighlightDetails, type MetricHighlightInfo, type MetricHighlightPoint, type MetricHighlightRange, type MetricHighlightReading, type MetricHighlightReadingText, type MetricHighlightStep, type MetricHighlightStretch, type MetricHighlightTrendRow, type MetricInsightSegment, type MetricPolarity, type MetricTargetInfo, type MetricTextFormatters, type MetricTrendAxes, type MetricTrendMark, type MetricTrendPoint, type MetricTrendRow, type MetricTrendScale, type MetricTrendSpecOptions, type MetricTrendSummary, type MetricTrendXType, type ResolveHighlightOptions, axisGranularity, buildHighlightDatasets, buildHighlightDetails, buildHighlightInsight, buildMetricChartDescription, buildMetricDescription, buildMetricTrendSpec, createMetricFormatters, deriveDelta, deriveMetricState, deriveTarget, describeComparisons, insightSegmentsToText, normalizeComparisons, normalizeTrend, resolveComparison, resolveHighlight, resolveMetricLabels };
@@ -0,0 +1,4 @@
1
+ import { METRIC_CONTEXT_DATASET, METRIC_HIGHLIGHT_DATASET, METRIC_HIGHLIGHT_END_PARAM, METRIC_HIGHLIGHT_POINTS_DATASET, METRIC_HIGHLIGHT_ROWS_DATASET, METRIC_HIGHLIGHT_START_PARAM, METRIC_HOVER_PARAM, METRIC_LATEST_DATASET, METRIC_REFERENCE_DATASET, METRIC_TREND_DATASET, METRIC_X_TICKS_PARAM, buildMetricTrendSpec } from "./MetricCard.chart.js";
2
+ import { DEFAULT_METRIC_LABELS, METRIC_PREVIOUS_KEY, axisGranularity, buildHighlightDatasets, buildHighlightDetails, buildHighlightInsight, buildMetricChartDescription, buildMetricDescription, createMetricFormatters, deriveDelta, deriveMetricState, deriveTarget, describeComparisons, insightSegmentsToText, normalizeComparisons, normalizeTrend, resolveComparison, resolveHighlight, resolveMetricLabels } from "./MetricCard.model.js";
3
+ import { MetricCard } from "./MetricCard.js";
4
+ export { DEFAULT_METRIC_LABELS, METRIC_CONTEXT_DATASET, METRIC_HIGHLIGHT_DATASET, METRIC_HIGHLIGHT_END_PARAM, METRIC_HIGHLIGHT_POINTS_DATASET, METRIC_HIGHLIGHT_ROWS_DATASET, METRIC_HIGHLIGHT_START_PARAM, METRIC_HOVER_PARAM, METRIC_LATEST_DATASET, METRIC_PREVIOUS_KEY, METRIC_REFERENCE_DATASET, METRIC_TREND_DATASET, METRIC_X_TICKS_PARAM, MetricCard, axisGranularity, buildHighlightDatasets, buildHighlightDetails, buildHighlightInsight, buildMetricChartDescription, buildMetricDescription, buildMetricTrendSpec, createMetricFormatters, deriveDelta, deriveMetricState, deriveTarget, describeComparisons, insightSegmentsToText, normalizeComparisons, normalizeTrend, resolveComparison, resolveHighlight, resolveMetricLabels };