@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,45 @@
1
+ //#region src/lib/vega/time.d.ts
2
+ /** An instant read as a wall-clock date and time in a time zone. */
3
+ interface ZonedParts {
4
+ year: number;
5
+ /** 1–12, the way a calendar names a month. */
6
+ month: number;
7
+ day: number;
8
+ hour: number;
9
+ minute: number;
10
+ second: number;
11
+ }
12
+ /**
13
+ * An instant's wall-clock parts in the zone. Without a zone — or with one the
14
+ * runtime does not know — the runtime's own zone applies, the way `Intl`
15
+ * itself would default.
16
+ */
17
+ declare function zonedParts(utcMs: number, timeZone?: string): ZonedParts;
18
+ /**
19
+ * The zone's offset from UTC at an instant, in milliseconds — positive east of
20
+ * Greenwich. Without a zone (or with an unknown one) the runtime's own offset
21
+ * applies, the way `Intl` itself would default.
22
+ */
23
+ declare function timeZoneOffsetMs(utcMs: number, timeZone?: string): number;
24
+ /**
25
+ * Midnight at the start of a calendar date in the zone, as an instant — the
26
+ * date named by its parts. Two passes absorb a daylight-saving shift on the
27
+ * day itself.
28
+ */
29
+ declare function zonedMidnight(year: number, monthIndex: number, day: number, timeZone?: string): number;
30
+ /**
31
+ * Midnight at the start of the calendar day an instant falls in, in the zone —
32
+ * the boundary `zonedMidnight` names, reached from an instant rather than from
33
+ * calendar parts. Two passes absorb a daylight-saving shift on the day itself.
34
+ */
35
+ declare function zonedDayStart(utcMs: number, timeZone?: string): number;
36
+ /**
37
+ * An instant from a `Date`, a timestamp, or an ISO string. `Date`s and
38
+ * timestamps pass through; a date-only string is that calendar day's midnight
39
+ * in the zone (`"2026-09-01"` in `Asia/Bangkok` is 31 August 17:00 UTC), so a
40
+ * card's readings land on the calendar dates the product means rather than on
41
+ * UTC days.
42
+ */
43
+ declare function resolveInstant(value: Date | string | number, timeZone?: string): number;
44
+ //#endregion
45
+ export { ZonedParts, resolveInstant, timeZoneOffsetMs, zonedDayStart, zonedMidnight, zonedParts };
@@ -0,0 +1,105 @@
1
+ //#region src/lib/vega/time.ts
2
+ const SECOND_MS = 1e3;
3
+ const MINUTE_MS = 6e4;
4
+ const DAY_MS = 864e5;
5
+ const DATE_ONLY = /^(\d{4})-(\d{2})-(\d{2})$/;
6
+ const offsetFormatCache = /* @__PURE__ */ new Map();
7
+ function offsetFormat(timeZone) {
8
+ const cached = offsetFormatCache.get(timeZone);
9
+ if (cached !== void 0) return cached;
10
+ let formatter = null;
11
+ try {
12
+ formatter = new Intl.DateTimeFormat("en-US-u-ca-gregory", {
13
+ timeZone,
14
+ hourCycle: "h23",
15
+ year: "numeric",
16
+ month: "numeric",
17
+ day: "numeric",
18
+ hour: "numeric",
19
+ minute: "numeric",
20
+ second: "numeric"
21
+ });
22
+ } catch {
23
+ formatter = null;
24
+ }
25
+ offsetFormatCache.set(timeZone, formatter);
26
+ return formatter;
27
+ }
28
+ /**
29
+ * An instant's wall-clock parts in the zone. Without a zone — or with one the
30
+ * runtime does not know — the runtime's own zone applies, the way `Intl`
31
+ * itself would default.
32
+ */
33
+ function zonedParts(utcMs, timeZone) {
34
+ const formatter = timeZone ? offsetFormat(timeZone) : null;
35
+ if (!formatter) {
36
+ const local = new Date(utcMs);
37
+ return {
38
+ year: local.getFullYear(),
39
+ month: local.getMonth() + 1,
40
+ day: local.getDate(),
41
+ hour: local.getHours(),
42
+ minute: local.getMinutes(),
43
+ second: local.getSeconds()
44
+ };
45
+ }
46
+ const parts = {};
47
+ for (const part of formatter.formatToParts(new Date(utcMs))) if (part.type !== "literal") parts[part.type] = Number(part.value);
48
+ return {
49
+ year: parts.year ?? 1970,
50
+ month: parts.month ?? 1,
51
+ day: parts.day ?? 1,
52
+ hour: (parts.hour ?? 0) % 24,
53
+ minute: parts.minute ?? 0,
54
+ second: parts.second ?? 0
55
+ };
56
+ }
57
+ /**
58
+ * The zone's offset from UTC at an instant, in milliseconds — positive east of
59
+ * Greenwich. Without a zone (or with an unknown one) the runtime's own offset
60
+ * applies, the way `Intl` itself would default.
61
+ */
62
+ function timeZoneOffsetMs(utcMs, timeZone) {
63
+ if (!timeZone || !offsetFormat(timeZone)) return -new Date(utcMs).getTimezoneOffset() * MINUTE_MS;
64
+ const { year, month, day, hour, minute, second } = zonedParts(utcMs, timeZone);
65
+ return Date.UTC(year, month - 1, day, hour, minute, second) - Math.floor(utcMs / SECOND_MS) * SECOND_MS;
66
+ }
67
+ /**
68
+ * Midnight at the start of a calendar date in the zone, as an instant — the
69
+ * date named by its parts. Two passes absorb a daylight-saving shift on the
70
+ * day itself.
71
+ */
72
+ function zonedMidnight(year, monthIndex, day, timeZone) {
73
+ const guess = Date.UTC(year, monthIndex, day);
74
+ return guess - timeZoneOffsetMs(guess - timeZoneOffsetMs(guess, timeZone), timeZone);
75
+ }
76
+ /**
77
+ * Midnight at the start of the calendar day an instant falls in, in the zone —
78
+ * the boundary `zonedMidnight` names, reached from an instant rather than from
79
+ * calendar parts. Two passes absorb a daylight-saving shift on the day itself.
80
+ */
81
+ function zonedDayStart(utcMs, timeZone) {
82
+ const offset = timeZoneOffsetMs(utcMs, timeZone);
83
+ const local = utcMs + offset;
84
+ const midnightLocal = Math.floor(local / DAY_MS) * DAY_MS;
85
+ return midnightLocal - timeZoneOffsetMs(midnightLocal - offset, timeZone);
86
+ }
87
+ /**
88
+ * An instant from a `Date`, a timestamp, or an ISO string. `Date`s and
89
+ * timestamps pass through; a date-only string is that calendar day's midnight
90
+ * in the zone (`"2026-09-01"` in `Asia/Bangkok` is 31 August 17:00 UTC), so a
91
+ * card's readings land on the calendar dates the product means rather than on
92
+ * UTC days.
93
+ */
94
+ function resolveInstant(value, timeZone) {
95
+ if (value instanceof Date) return value.getTime();
96
+ if (typeof value === "number") return value;
97
+ const match = DATE_ONLY.exec(value);
98
+ if (!match) return new Date(value).getTime();
99
+ if (!timeZone) return (/* @__PURE__ */ new Date(`${value}T00:00:00`)).getTime();
100
+ const [, year, month, day] = match.map(Number);
101
+ const guess = Date.UTC(year ?? 1970, (month ?? 1) - 1, day ?? 1);
102
+ return guess - timeZoneOffsetMs(guess - timeZoneOffsetMs(guess, timeZone), timeZone);
103
+ }
104
+ //#endregion
105
+ export { resolveInstant, timeZoneOffsetMs, zonedDayStart, zonedMidnight, zonedParts };
@@ -0,0 +1,73 @@
1
+ import { RefObject } from "react";
2
+ //#region src/lib/vega/tokens.d.ts
3
+ /**
4
+ * The chart token group from `colors.css`, resolved to paintable values.
5
+ *
6
+ * Vega draws computed colors into SVG/canvas and cannot read `var()`, so the
7
+ * bridge reads every `--color-chart-*` token (plus the text and surface roles
8
+ * Vega text and halos borrow, and the two font stacks) from the component's
9
+ * own node at runtime — subtree-scoped themes therefore resolve correctly —
10
+ * and rebuilds the Vega config when the theme changes.
11
+ */
12
+ interface ChartTokens {
13
+ /** Categorical identity slots in fixed order (`chart-1…N`). Only the slots the theme defines are present. */
14
+ category: string[];
15
+ /** The one hue a single-series chart draws with (`chart-emphasis`). */
16
+ emphasis: string;
17
+ /** The gray that recedes behind the emphasis series (`chart-context`). */
18
+ context: string;
19
+ /** Magnitude ramp, low → high (`chart-sequential-1…7`). */
20
+ sequential: string[];
21
+ /** Polarity scale ends and midpoint (`chart-diverging-*`). */
22
+ divergingNegative: string;
23
+ divergingMid: string;
24
+ divergingPositive: string;
25
+ /** Delta text steps (`chart-positive` / `chart-negative`) — the same values `text-success-700` / `text-error-700` resolve to. */
26
+ positive: string;
27
+ negative: string;
28
+ /**
29
+ * The severity ladder for state marks (`chart-status-*`) — the one place the
30
+ * reserved status hues belong on a mark, because there the encoded value IS
31
+ * good or bad. `maintenance` (planned) and `unknown` (nobody was watching)
32
+ * sit outside the ladder. Keyed by `StatusLevel`.
33
+ */
34
+ status: {
35
+ operational: string;
36
+ degraded: string;
37
+ down: string;
38
+ maintenance: string;
39
+ unknown: string;
40
+ };
41
+ /** Guides. */
42
+ grid: string;
43
+ axis: string;
44
+ axisLabel: string;
45
+ reference: string;
46
+ selection: string;
47
+ brush: string;
48
+ /** Page roles: text Vega draws, the surface used for halos and gaps between touching fills, and the muted fill a guide track borrows (the Meter's own track colour). */
49
+ foreground: string;
50
+ mutedForeground: string;
51
+ background: string;
52
+ surface: string;
53
+ muted: string;
54
+ /** Font stacks (`--font-sans`, `--font-heading`); the Thai fallback rides the stack string. */
55
+ fontSans: string;
56
+ fontHeading: string;
57
+ }
58
+ /**
59
+ * Reads the chart token group as computed on `el`. Call from an effect (the
60
+ * node must be attached and styled); prefer `useChartTokens`, which re-reads
61
+ * when the theme flips.
62
+ */
63
+ declare function readChartTokens(el: Element): ChartTokens;
64
+ /**
65
+ * Chart tokens for the element behind `ref`, re-read when `class`, `data-theme`
66
+ * or `style` change on `<html>` (the theme toggle) or when `theme` changes (an
67
+ * explicit theme prop on a subtree). `null` until the first read completes
68
+ * after mount; the returned object keeps its identity while the values are
69
+ * unchanged, so a spec memoized on it does not rebuild for nothing.
70
+ */
71
+ declare function useChartTokens(ref: RefObject<Element | null>, theme?: string): ChartTokens | null;
72
+ //#endregion
73
+ export { ChartTokens, readChartTokens, useChartTokens };
@@ -0,0 +1,137 @@
1
+ import { useEffect, useState } from "react";
2
+ //#region src/lib/vega/tokens.ts
3
+ const MAX_CATEGORY_SLOTS = 12;
4
+ const SEQUENTIAL_STEPS = 7;
5
+ const FALLBACK_TOKENS = {
6
+ category: [
7
+ "#1848f3",
8
+ "#4b3dbf",
9
+ "#4a5878"
10
+ ],
11
+ emphasis: "#1848f3",
12
+ context: "#647195",
13
+ sequential: [
14
+ "#dfeafd",
15
+ "#c2d4f9",
16
+ "#9bbaf8",
17
+ "#699ef7",
18
+ "#3b82f6",
19
+ "#1848f3",
20
+ "#143ad4"
21
+ ],
22
+ divergingNegative: "#ee4540",
23
+ divergingMid: "#dee4ef",
24
+ divergingPositive: "#059b4e",
25
+ positive: "#077c41",
26
+ negative: "#b81d19",
27
+ status: {
28
+ operational: "#059b4e",
29
+ degraded: "#b36209",
30
+ down: "#db2723",
31
+ maintenance: "#3b82f6",
32
+ unknown: "#647195"
33
+ },
34
+ grid: "#dee4ef",
35
+ axis: "#93a2bd",
36
+ axisLabel: "#647195",
37
+ reference: "#647195",
38
+ selection: "#1848f3",
39
+ brush: "#c2d4f9",
40
+ foreground: "#111a30",
41
+ mutedForeground: "#647195",
42
+ background: "#ffffff",
43
+ surface: "#f7f9fc",
44
+ muted: "#eff2f8",
45
+ fontSans: "Inter, Sarabun, ui-sans-serif, system-ui, sans-serif",
46
+ fontHeading: "Inter, Sarabun, ui-sans-serif, system-ui, sans-serif"
47
+ };
48
+ /**
49
+ * Reads the chart token group as computed on `el`. Call from an effect (the
50
+ * node must be attached and styled); prefer `useChartTokens`, which re-reads
51
+ * when the theme flips.
52
+ */
53
+ function readChartTokens(el) {
54
+ const style = getComputedStyle(el);
55
+ const read = (name, fallback) => {
56
+ return style.getPropertyValue(name).trim() || fallback;
57
+ };
58
+ const category = [];
59
+ for (let slot = 1; slot <= MAX_CATEGORY_SLOTS; slot += 1) {
60
+ const value = style.getPropertyValue(`--color-chart-${slot}`).trim();
61
+ if (!value) break;
62
+ category.push(value);
63
+ }
64
+ const sequential = [];
65
+ for (let step = 1; step <= SEQUENTIAL_STEPS; step += 1) {
66
+ const value = style.getPropertyValue(`--color-chart-sequential-${step}`).trim();
67
+ if (!value) break;
68
+ sequential.push(value);
69
+ }
70
+ return {
71
+ category: category.length > 0 ? category : FALLBACK_TOKENS.category,
72
+ emphasis: read("--color-chart-emphasis", FALLBACK_TOKENS.emphasis),
73
+ context: read("--color-chart-context", FALLBACK_TOKENS.context),
74
+ sequential: sequential.length > 0 ? sequential : FALLBACK_TOKENS.sequential,
75
+ divergingNegative: read("--color-chart-diverging-negative", FALLBACK_TOKENS.divergingNegative),
76
+ divergingMid: read("--color-chart-diverging-mid", FALLBACK_TOKENS.divergingMid),
77
+ divergingPositive: read("--color-chart-diverging-positive", FALLBACK_TOKENS.divergingPositive),
78
+ positive: read("--color-chart-positive", FALLBACK_TOKENS.positive),
79
+ negative: read("--color-chart-negative", FALLBACK_TOKENS.negative),
80
+ status: {
81
+ operational: read("--color-chart-status-operational", FALLBACK_TOKENS.status.operational),
82
+ degraded: read("--color-chart-status-degraded", FALLBACK_TOKENS.status.degraded),
83
+ down: read("--color-chart-status-down", FALLBACK_TOKENS.status.down),
84
+ maintenance: read("--color-chart-status-maintenance", FALLBACK_TOKENS.status.maintenance),
85
+ unknown: read("--color-chart-status-unknown", FALLBACK_TOKENS.status.unknown)
86
+ },
87
+ grid: read("--color-chart-grid", FALLBACK_TOKENS.grid),
88
+ axis: read("--color-chart-axis", FALLBACK_TOKENS.axis),
89
+ axisLabel: read("--color-chart-axis-label", FALLBACK_TOKENS.axisLabel),
90
+ reference: read("--color-chart-reference", FALLBACK_TOKENS.reference),
91
+ selection: read("--color-chart-selection", FALLBACK_TOKENS.selection),
92
+ brush: read("--color-chart-brush", FALLBACK_TOKENS.brush),
93
+ foreground: read("--color-foreground", FALLBACK_TOKENS.foreground),
94
+ mutedForeground: read("--color-muted-foreground", FALLBACK_TOKENS.mutedForeground),
95
+ background: read("--color-background", FALLBACK_TOKENS.background),
96
+ surface: read("--color-surface", FALLBACK_TOKENS.surface),
97
+ muted: read("--color-muted", FALLBACK_TOKENS.muted),
98
+ fontSans: read("--font-sans", FALLBACK_TOKENS.fontSans),
99
+ fontHeading: read("--font-heading", FALLBACK_TOKENS.fontHeading)
100
+ };
101
+ }
102
+ function sameTokens(a, b) {
103
+ return JSON.stringify(a) === JSON.stringify(b);
104
+ }
105
+ /**
106
+ * Chart tokens for the element behind `ref`, re-read when `class`, `data-theme`
107
+ * or `style` change on `<html>` (the theme toggle) or when `theme` changes (an
108
+ * explicit theme prop on a subtree). `null` until the first read completes
109
+ * after mount; the returned object keeps its identity while the values are
110
+ * unchanged, so a spec memoized on it does not rebuild for nothing.
111
+ */
112
+ function useChartTokens(ref, theme) {
113
+ const [tokens, setTokens] = useState(null);
114
+ useEffect(() => {
115
+ const el = ref.current;
116
+ if (!el) return;
117
+ const read = () => {
118
+ const next = readChartTokens(el);
119
+ setTokens((prev) => prev && sameTokens(prev, next) ? prev : next);
120
+ };
121
+ read();
122
+ if (typeof MutationObserver === "undefined") return;
123
+ const observer = new MutationObserver(read);
124
+ observer.observe(document.documentElement, {
125
+ attributes: true,
126
+ attributeFilter: [
127
+ "class",
128
+ "data-theme",
129
+ "style"
130
+ ]
131
+ });
132
+ return () => observer.disconnect();
133
+ }, [ref, theme]);
134
+ return tokens;
135
+ }
136
+ //#endregion
137
+ export { readChartTokens, useChartTokens };
@@ -0,0 +1,45 @@
1
+ import { ReactNode } from "react";
2
+ import { Item, TooltipHandler } from "vega";
3
+ //#region src/lib/vega/tooltip.d.ts
4
+ /** What the handler saw: the evaluated `tooltip` signal, the scenegraph item, and the pointer event. */
5
+ interface ArcTooltipContext {
6
+ /** The evaluated tooltip value — a string, or an object of title → formatted value for the `tooltip` channel. */
7
+ value: unknown;
8
+ /** The hovered scenegraph item. For Vega-Lite's `nearest` voronoi cells the point's datum is `item.datum.datum`. */
9
+ item: Item;
10
+ event: MouseEvent;
11
+ }
12
+ interface UseArcTooltipHandlerOptions {
13
+ /**
14
+ * Renders the chip content from the hovered item. Return `null` to show
15
+ * nothing. Default: the tooltip value's title → value pairs, formatted by
16
+ * Vega. Format in React here when the chart's DOM text has its own
17
+ * formatter, so the two can never round differently.
18
+ */
19
+ content?: (context: ArcTooltipContext) => ReactNode;
20
+ }
21
+ interface ArcTooltipHandle {
22
+ /** Pass to `arcEmbedOptions({ tooltip })`. Stable for the life of the component. */
23
+ handler: TooltipHandler;
24
+ /** Render this once anywhere in the component tree; it portals the chip to `<body>`. */
25
+ tooltip: ReactNode;
26
+ open: boolean;
27
+ }
28
+ /** Title → value rows in the chip's own type. */
29
+ declare function ArcTooltipRows({ rows }: {
30
+ rows: readonly (readonly [label: ReactNode, value: ReactNode])[];
31
+ }): import("react").JSX.Element;
32
+ /**
33
+ * A Vega tooltip handler that lifts the hovered value into React state and
34
+ * renders the Arc tooltip chip (`tooltipPopupVariants()`) at the pointer,
35
+ * flipped inside the viewport, text only (React children — never
36
+ * `innerHTML`), dismissed on Escape. It replaces vega-tooltip's injected
37
+ * `#vg-tooltip-element` and stylesheet.
38
+ *
39
+ * The chip is `aria-hidden`: like the `Tooltip` primitive's popup it is a
40
+ * redundant visual hint — every value it shows must also be reachable through
41
+ * labels, the summary sentence, or the table twin.
42
+ */
43
+ declare function useArcTooltipHandler({ content }?: UseArcTooltipHandlerOptions): ArcTooltipHandle;
44
+ //#endregion
45
+ export { ArcTooltipContext, ArcTooltipHandle, ArcTooltipRows, UseArcTooltipHandlerOptions, useArcTooltipHandler };
@@ -0,0 +1,124 @@
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
2
+ import { tooltipPopupVariants } from "@arcfusionz/arc-primitive-ui";
3
+ import { jsx, jsxs } from "react/jsx-runtime";
4
+ import { createPortal } from "react-dom";
5
+ //#region src/lib/vega/tooltip.tsx
6
+ const OFFSET_PX = 12;
7
+ const VIEWPORT_MARGIN_PX = 8;
8
+ /** Title → value rows in the chip's own type. */
9
+ function ArcTooltipRows({ rows }) {
10
+ return /* @__PURE__ */ jsx("div", {
11
+ className: "flex flex-col gap-0.5",
12
+ children: rows.map(([label, value], index) => /* @__PURE__ */ jsxs("div", {
13
+ className: "flex items-baseline justify-between gap-3",
14
+ children: [/* @__PURE__ */ jsx("span", {
15
+ className: "text-background/70",
16
+ children: label
17
+ }), /* @__PURE__ */ jsx("span", {
18
+ className: "font-medium tabular-nums",
19
+ children: value
20
+ })]
21
+ }, index))
22
+ });
23
+ }
24
+ function defaultContent(value) {
25
+ if (value !== null && typeof value === "object") {
26
+ const rows = Object.entries(value).map(([label, entry]) => [label, String(entry)]);
27
+ return /* @__PURE__ */ jsx(ArcTooltipRows, { rows });
28
+ }
29
+ return String(value);
30
+ }
31
+ function ArcTooltipChip({ x, y, onDismiss, children }) {
32
+ const ref = useRef(null);
33
+ const [position, setPosition] = useState({
34
+ left: x + OFFSET_PX,
35
+ top: y + OFFSET_PX
36
+ });
37
+ useLayoutEffect(() => {
38
+ const el = ref.current;
39
+ if (!el) return;
40
+ const rect = el.getBoundingClientRect();
41
+ let left = x + OFFSET_PX;
42
+ let top = y + OFFSET_PX;
43
+ if (left + rect.width > window.innerWidth - VIEWPORT_MARGIN_PX) left = x - OFFSET_PX - rect.width;
44
+ if (top + rect.height > window.innerHeight - VIEWPORT_MARGIN_PX) top = y - OFFSET_PX - rect.height;
45
+ setPosition({
46
+ left: Math.max(VIEWPORT_MARGIN_PX, left),
47
+ top: Math.max(VIEWPORT_MARGIN_PX, top)
48
+ });
49
+ }, [
50
+ x,
51
+ y,
52
+ children
53
+ ]);
54
+ useEffect(() => {
55
+ const onKeyDown = (event) => {
56
+ if (event.key === "Escape") onDismiss();
57
+ };
58
+ window.addEventListener("keydown", onKeyDown);
59
+ return () => window.removeEventListener("keydown", onKeyDown);
60
+ }, [onDismiss]);
61
+ if (typeof document === "undefined") return null;
62
+ return createPortal(/* @__PURE__ */ jsx("div", {
63
+ ref,
64
+ role: "presentation",
65
+ "aria-hidden": "true",
66
+ "data-part": "chart-tooltip",
67
+ className: tooltipPopupVariants({ className: "pointer-events-none fixed z-50 whitespace-nowrap" }),
68
+ style: {
69
+ left: position.left,
70
+ top: position.top
71
+ },
72
+ children
73
+ }), document.body);
74
+ }
75
+ /**
76
+ * A Vega tooltip handler that lifts the hovered value into React state and
77
+ * renders the Arc tooltip chip (`tooltipPopupVariants()`) at the pointer,
78
+ * flipped inside the viewport, text only (React children — never
79
+ * `innerHTML`), dismissed on Escape. It replaces vega-tooltip's injected
80
+ * `#vg-tooltip-element` and stylesheet.
81
+ *
82
+ * The chip is `aria-hidden`: like the `Tooltip` primitive's popup it is a
83
+ * redundant visual hint — every value it shows must also be reachable through
84
+ * labels, the summary sentence, or the table twin.
85
+ */
86
+ function useArcTooltipHandler({ content } = {}) {
87
+ const [state, setState] = useState(null);
88
+ const contentRef = useRef(content);
89
+ contentRef.current = content;
90
+ const handler = useCallback((_handler, event, item, value) => {
91
+ if (value == null || value === "") {
92
+ setState(null);
93
+ return;
94
+ }
95
+ const render = contentRef.current;
96
+ const node = render ? render({
97
+ value,
98
+ item,
99
+ event
100
+ }) : defaultContent(value);
101
+ if (node == null) {
102
+ setState(null);
103
+ return;
104
+ }
105
+ setState({
106
+ x: event.clientX,
107
+ y: event.clientY,
108
+ node
109
+ });
110
+ }, []);
111
+ const dismiss = useCallback(() => setState(null), []);
112
+ return {
113
+ handler,
114
+ tooltip: state ? /* @__PURE__ */ jsx(ArcTooltipChip, {
115
+ x: state.x,
116
+ y: state.y,
117
+ onDismiss: dismiss,
118
+ children: state.node
119
+ }) : null,
120
+ open: state != null
121
+ };
122
+ }
123
+ //#endregion
124
+ export { ArcTooltipRows, useArcTooltipHandler };
@@ -0,0 +1,43 @@
1
+ import { RefObject } from "react";
2
+ import { TopLevelSpec } from "vega-lite";
3
+ import { EmbedOptions, Result } from "vega-embed";
4
+ //#region src/lib/vega/useArcVega.d.ts
5
+ /** Named datasets pushed through the View API — `{ table: rows }` for a spec with `data: { name: "table" }`. */
6
+ type ArcVegaDatasets = Readonly<Record<string, readonly object[]>>;
7
+ interface UseArcVegaParams {
8
+ /** The element vega-embed renders into. */
9
+ ref: RefObject<HTMLDivElement | null>;
10
+ /**
11
+ * The structural spec: named datasets, no inline values, memoized. Any deep
12
+ * change re-embeds (react-vega compares with fast-deep-equal), so data,
13
+ * size and description must travel through the other params, not the spec.
14
+ */
15
+ spec: TopLevelSpec;
16
+ /** `arcEmbedOptions(...)`, memoized for the same reason. */
17
+ options: EmbedOptions;
18
+ /** Rows per named dataset. A new array for a dataset pushes it with `view.data` and runs the dataflow once; a name no layer of the spec reads is ignored. */
19
+ datasets?: ArcVegaDatasets;
20
+ /** The accessible description (`aria-label` of the `graphics-document`), set live with `view.description`. */
21
+ description?: string;
22
+ /** Plot width in pixels from the frame's ResizeObserver, applied with `view.width` — never through the spec. */
23
+ width?: number;
24
+ /** Plot height in pixels, applied with `view.height`. */
25
+ height?: number;
26
+ /** Variable params driven from Arc controls, applied with `view.signal`. */
27
+ signals?: Readonly<Record<string, unknown>>;
28
+ /** Selection params read back: listeners are attached once per view and always call the latest callback. */
29
+ onSignal?: Readonly<Record<string, (value: unknown) => void>>;
30
+ onEmbed?: (result: Result) => void;
31
+ onError?: (error: unknown) => void;
32
+ }
33
+ /**
34
+ * react-vega 8's `useVegaEmbed` plus the View API discipline the analytics
35
+ * package needs: one embed per mount (a re-embed only when the structural spec
36
+ * or the options change), and one `runAsync` per batch of data / size /
37
+ * description / signal changes. Listeners and callbacks go through
38
+ * latest-value refs, so they never go stale while the view lives; react-vega
39
+ * finalizes the view on unmount and before every re-embed.
40
+ */
41
+ declare function useArcVega(params: UseArcVegaParams): Result | null;
42
+ //#endregion
43
+ export { ArcVegaDatasets, UseArcVegaParams, useArcVega };