@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.
- package/dist/_virtual/_rolldown/runtime.js +13 -0
- package/dist/components/AnomalyCard/AnomalyCard.chart.d.ts +64 -0
- package/dist/components/AnomalyCard/AnomalyCard.chart.js +341 -0
- package/dist/components/AnomalyCard/AnomalyCard.d.ts +169 -0
- package/dist/components/AnomalyCard/AnomalyCard.js +1019 -0
- package/dist/components/AnomalyCard/AnomalyCard.model.d.ts +508 -0
- package/dist/components/AnomalyCard/AnomalyCard.model.js +553 -0
- package/dist/components/AnomalyCard/index.d.ts +4 -0
- package/dist/components/AnomalyCard/index.js +4 -0
- package/dist/components/ChartCard/ChartCard.chart.d.ts +72 -0
- package/dist/components/ChartCard/ChartCard.chart.js +1333 -0
- package/dist/components/ChartCard/ChartCard.d.ts +206 -0
- package/dist/components/ChartCard/ChartCard.js +718 -0
- package/dist/components/ChartCard/ChartCard.model.d.ts +452 -0
- package/dist/components/ChartCard/ChartCard.model.js +1223 -0
- package/dist/components/ChartCard/index.d.ts +4 -0
- package/dist/components/ChartCard/index.js +4 -0
- package/dist/components/ComparisonCard/ComparisonCard.chart.d.ts +80 -0
- package/dist/components/ComparisonCard/ComparisonCard.chart.js +337 -0
- package/dist/components/ComparisonCard/ComparisonCard.d.ts +200 -0
- package/dist/components/ComparisonCard/ComparisonCard.js +1325 -0
- package/dist/components/ComparisonCard/ComparisonCard.model.d.ts +309 -0
- package/dist/components/ComparisonCard/ComparisonCard.model.js +263 -0
- package/dist/components/ComparisonCard/index.d.ts +4 -0
- package/dist/components/ComparisonCard/index.js +4 -0
- package/dist/components/ForecastCard/ForecastCard.chart.d.ts +82 -0
- package/dist/components/ForecastCard/ForecastCard.chart.js +416 -0
- package/dist/components/ForecastCard/ForecastCard.d.ts +148 -0
- package/dist/components/ForecastCard/ForecastCard.js +1029 -0
- package/dist/components/ForecastCard/ForecastCard.model.d.ts +497 -0
- package/dist/components/ForecastCard/ForecastCard.model.js +554 -0
- package/dist/components/ForecastCard/index.d.ts +4 -0
- package/dist/components/ForecastCard/index.js +4 -0
- package/dist/components/HighlightCard/HighlightCard.chart.d.ts +122 -0
- package/dist/components/HighlightCard/HighlightCard.chart.js +660 -0
- package/dist/components/HighlightCard/HighlightCard.d.ts +202 -0
- package/dist/components/HighlightCard/HighlightCard.js +1286 -0
- package/dist/components/HighlightCard/HighlightCard.model.d.ts +692 -0
- package/dist/components/HighlightCard/HighlightCard.model.js +875 -0
- package/dist/components/HighlightCard/index.d.ts +4 -0
- package/dist/components/HighlightCard/index.js +4 -0
- package/dist/components/InsightCard/InsightCard.chart.d.ts +91 -0
- package/dist/components/InsightCard/InsightCard.chart.js +471 -0
- package/dist/components/InsightCard/InsightCard.d.ts +161 -0
- package/dist/components/InsightCard/InsightCard.js +961 -0
- package/dist/components/InsightCard/InsightCard.model.d.ts +405 -0
- package/dist/components/InsightCard/InsightCard.model.js +421 -0
- package/dist/components/InsightCard/index.d.ts +4 -0
- package/dist/components/InsightCard/index.js +4 -0
- package/dist/components/MetricCard/MetricCard.chart.d.ts +79 -0
- package/dist/components/MetricCard/MetricCard.chart.js +435 -0
- package/dist/components/MetricCard/MetricCard.d.ts +227 -0
- package/dist/components/MetricCard/MetricCard.js +1293 -0
- package/dist/components/MetricCard/MetricCard.model.d.ts +521 -0
- package/dist/components/MetricCard/MetricCard.model.js +627 -0
- package/dist/components/MetricCard/index.d.ts +4 -0
- package/dist/components/MetricCard/index.js +4 -0
- package/dist/components/ProfileCard/ProfileCard.chart.d.ts +64 -0
- package/dist/components/ProfileCard/ProfileCard.chart.js +427 -0
- package/dist/components/ProfileCard/ProfileCard.d.ts +140 -0
- package/dist/components/ProfileCard/ProfileCard.js +735 -0
- package/dist/components/ProfileCard/ProfileCard.model.d.ts +279 -0
- package/dist/components/ProfileCard/ProfileCard.model.js +244 -0
- package/dist/components/ProfileCard/index.d.ts +4 -0
- package/dist/components/ProfileCard/index.js +4 -0
- package/dist/components/ProgressCard/ProgressCard.chart.d.ts +73 -0
- package/dist/components/ProgressCard/ProgressCard.chart.js +373 -0
- package/dist/components/ProgressCard/ProgressCard.d.ts +137 -0
- package/dist/components/ProgressCard/ProgressCard.js +908 -0
- package/dist/components/ProgressCard/ProgressCard.model.d.ts +385 -0
- package/dist/components/ProgressCard/ProgressCard.model.js +389 -0
- package/dist/components/ProgressCard/index.d.ts +4 -0
- package/dist/components/ProgressCard/index.js +4 -0
- package/dist/components/ReportCard/ReportCard.chart.d.ts +45 -0
- package/dist/components/ReportCard/ReportCard.chart.js +231 -0
- package/dist/components/ReportCard/ReportCard.d.ts +153 -0
- package/dist/components/ReportCard/ReportCard.js +1118 -0
- package/dist/components/ReportCard/ReportCard.model.d.ts +392 -0
- package/dist/components/ReportCard/ReportCard.model.js +399 -0
- package/dist/components/ReportCard/index.d.ts +4 -0
- package/dist/components/ReportCard/index.js +4 -0
- package/dist/components/SankeyCard/SankeyCard.chart.d.ts +75 -0
- package/dist/components/SankeyCard/SankeyCard.chart.js +249 -0
- package/dist/components/SankeyCard/SankeyCard.d.ts +185 -0
- package/dist/components/SankeyCard/SankeyCard.js +788 -0
- package/dist/components/SankeyCard/SankeyCard.model.d.ts +501 -0
- package/dist/components/SankeyCard/SankeyCard.model.js +789 -0
- package/dist/components/SankeyCard/index.d.ts +4 -0
- package/dist/components/SankeyCard/index.js +4 -0
- package/dist/components/StatusCard/StatusCard.chart.d.ts +60 -0
- package/dist/components/StatusCard/StatusCard.chart.js +188 -0
- package/dist/components/StatusCard/StatusCard.d.ts +176 -0
- package/dist/components/StatusCard/StatusCard.js +989 -0
- package/dist/components/StatusCard/StatusCard.model.d.ts +555 -0
- package/dist/components/StatusCard/StatusCard.model.js +776 -0
- package/dist/components/StatusCard/index.d.ts +4 -0
- package/dist/components/StatusCard/index.js +4 -0
- package/dist/components/TableCard/TableCard.chart.d.ts +53 -0
- package/dist/components/TableCard/TableCard.chart.js +192 -0
- package/dist/components/TableCard/TableCard.d.ts +140 -0
- package/dist/components/TableCard/TableCard.js +811 -0
- package/dist/components/TableCard/TableCard.model.d.ts +381 -0
- package/dist/components/TableCard/TableCard.model.js +475 -0
- package/dist/components/TableCard/index.d.ts +4 -0
- package/dist/components/TableCard/index.js +4 -0
- package/dist/components/TargetCard/TargetCard.chart.d.ts +52 -0
- package/dist/components/TargetCard/TargetCard.chart.js +273 -0
- package/dist/components/TargetCard/TargetCard.d.ts +143 -0
- package/dist/components/TargetCard/TargetCard.js +910 -0
- package/dist/components/TargetCard/TargetCard.model.d.ts +468 -0
- package/dist/components/TargetCard/TargetCard.model.js +581 -0
- package/dist/components/TargetCard/index.d.ts +4 -0
- package/dist/components/TargetCard/index.js +4 -0
- package/dist/components/TreeCard/TreeCard.chart.d.ts +80 -0
- package/dist/components/TreeCard/TreeCard.chart.js +178 -0
- package/dist/components/TreeCard/TreeCard.d.ts +182 -0
- package/dist/components/TreeCard/TreeCard.js +1187 -0
- package/dist/components/TreeCard/TreeCard.model.d.ts +330 -0
- package/dist/components/TreeCard/TreeCard.model.js +401 -0
- package/dist/components/TreeCard/index.d.ts +4 -0
- package/dist/components/TreeCard/index.js +4 -0
- package/dist/components/TrendCard/TrendCard.chart.d.ts +75 -0
- package/dist/components/TrendCard/TrendCard.chart.js +393 -0
- package/dist/components/TrendCard/TrendCard.d.ts +224 -0
- package/dist/components/TrendCard/TrendCard.js +1053 -0
- package/dist/components/TrendCard/TrendCard.model.d.ts +545 -0
- package/dist/components/TrendCard/TrendCard.model.js +623 -0
- package/dist/components/TrendCard/index.d.ts +4 -0
- package/dist/components/TrendCard/index.js +4 -0
- package/dist/index.d.ts +76 -0
- package/dist/index.js +57 -0
- package/dist/lib/vega/ArcVegaChart.d.ts +24 -0
- package/dist/lib/vega/ArcVegaChart.js +42 -0
- package/dist/lib/vega/ArcVegaSurface.d.ts +13 -0
- package/dist/lib/vega/ArcVegaSurface.js +38 -0
- package/dist/lib/vega/axisTicks.js +156 -0
- package/dist/lib/vega/config.d.ts +29 -0
- package/dist/lib/vega/config.js +113 -0
- package/dist/lib/vega/embed.d.ts +29 -0
- package/dist/lib/vega/embed.js +29 -0
- package/dist/lib/vega/engine.d.ts +10 -0
- package/dist/lib/vega/engine.js +6 -0
- package/dist/lib/vega/formatters.d.ts +125 -0
- package/dist/lib/vega/formatters.js +277 -0
- package/dist/lib/vega/index.d.ts +11 -0
- package/dist/lib/vega/load.d.ts +13 -0
- package/dist/lib/vega/load.js +21 -0
- package/dist/lib/vega/time.d.ts +45 -0
- package/dist/lib/vega/time.js +105 -0
- package/dist/lib/vega/tokens.d.ts +73 -0
- package/dist/lib/vega/tokens.js +137 -0
- package/dist/lib/vega/tooltip.d.ts +45 -0
- package/dist/lib/vega/tooltip.js +124 -0
- package/dist/lib/vega/useArcVega.d.ts +43 -0
- package/dist/lib/vega/useArcVega.js +121 -0
- 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 };
|