@devicechain/widget-sdk 0.21.1 → 0.22.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/README.md +37 -1
- package/dist/chart/chart-options.d.ts +26 -0
- package/dist/chart/chart-theme.d.ts +2 -0
- package/dist/chart/echart-markers.d.ts +2 -0
- package/dist/chart/echart.d.ts +2 -0
- package/dist/chunks/chunk-A5BJ2ALW.js +318 -0
- package/dist/chunks/chunk-A5BJ2ALW.js.map +7 -0
- package/dist/chunks/echart-markers-JDFD265P.js +7 -0
- package/dist/chunks/echart-markers-JDFD265P.js.map +7 -0
- package/dist/connection-badge.d.ts +6 -0
- package/dist/context.d.ts +34 -1
- package/dist/echarts.d.ts +1 -1
- package/dist/echarts.js +222 -25
- package/dist/echarts.js.map +2 -2
- package/dist/fit-text.d.ts +17 -0
- package/dist/format.d.ts +2 -0
- package/dist/frame.d.ts +7 -2
- package/dist/freshness-readout.d.ts +22 -0
- package/dist/freshness.d.ts +13 -0
- package/dist/hooks.d.ts +14 -4
- package/dist/index.d.ts +11 -3
- package/dist/index.js +694 -168
- package/dist/index.js.map +4 -4
- package/dist/scale.d.ts +19 -0
- package/dist/severity.d.ts +18 -0
- package/dist/theme.d.ts +2 -1
- package/dist/thresholds.d.ts +16 -0
- package/dist/tokens.d.ts +25 -0
- package/package.json +3 -3
- package/dist/chunks/chunk-CC2V7LVI.js +0 -67
- package/dist/chunks/chunk-CC2V7LVI.js.map +0 -7
package/dist/index.js
CHANGED
|
@@ -1,15 +1,45 @@
|
|
|
1
1
|
import {
|
|
2
|
+
ALARM_LEVELS,
|
|
3
|
+
FIT_MIN_FONT_PX,
|
|
4
|
+
SEVERITY_LEVELS,
|
|
5
|
+
SEVERITY_TOKENS,
|
|
6
|
+
WidgetBodySizeProvider,
|
|
2
7
|
WidgetCandidatesProvider,
|
|
8
|
+
WidgetDensityProvider,
|
|
9
|
+
WidgetFrameStyleProvider,
|
|
10
|
+
WidgetFreshnessProvider,
|
|
3
11
|
WidgetSelectProvider,
|
|
4
12
|
WidgetSubjectProvider,
|
|
5
13
|
WidgetThemeRootProvider,
|
|
14
|
+
WidgetTimeRangeProvider,
|
|
6
15
|
WidgetTimeZoneProvider,
|
|
16
|
+
bandAt,
|
|
17
|
+
fitValueFont,
|
|
18
|
+
fitValueFontCss,
|
|
19
|
+
formatDateTime,
|
|
20
|
+
formatTimestamp,
|
|
21
|
+
formatValue,
|
|
22
|
+
isAlarmLevel,
|
|
23
|
+
isSeverityLevel,
|
|
24
|
+
levelColor,
|
|
25
|
+
optThresholds,
|
|
26
|
+
parseThresholds,
|
|
27
|
+
severityTokenCss,
|
|
28
|
+
severityVar,
|
|
29
|
+
stickyChars,
|
|
30
|
+
thresholdBands,
|
|
31
|
+
useSeverityColors,
|
|
32
|
+
useWidgetBodySize,
|
|
7
33
|
useWidgetCandidates,
|
|
34
|
+
useWidgetDensityScale,
|
|
35
|
+
useWidgetFrameStyle,
|
|
36
|
+
useWidgetFreshness,
|
|
8
37
|
useWidgetSelect,
|
|
9
38
|
useWidgetSubject,
|
|
10
39
|
useWidgetThemeRoot,
|
|
40
|
+
useWidgetTimeRange,
|
|
11
41
|
useWidgetTimeZone
|
|
12
|
-
} from "./chunks/chunk-
|
|
42
|
+
} from "./chunks/chunk-A5BJ2ALW.js";
|
|
13
43
|
|
|
14
44
|
// src/define-widget.ts
|
|
15
45
|
function defineWidget(definition) {
|
|
@@ -77,140 +107,12 @@ function createWidgetRegistry(definitions) {
|
|
|
77
107
|
});
|
|
78
108
|
}
|
|
79
109
|
|
|
80
|
-
// src/theme.ts
|
|
81
|
-
function css(name) {
|
|
82
|
-
return `hsl(var(--${name}))`;
|
|
83
|
-
}
|
|
84
|
-
|
|
85
110
|
// src/frame.tsx
|
|
86
|
-
import {
|
|
87
|
-
|
|
88
|
-
const subject = useWidgetSubject();
|
|
89
|
-
const sub = subtitle ?? subject;
|
|
90
|
-
return /* @__PURE__ */ jsxs(
|
|
91
|
-
"div",
|
|
92
|
-
{
|
|
93
|
-
style: {
|
|
94
|
-
display: "flex",
|
|
95
|
-
flexDirection: "column",
|
|
96
|
-
width: "100%",
|
|
97
|
-
height: "100%",
|
|
98
|
-
boxSizing: "border-box",
|
|
99
|
-
background: css("card"),
|
|
100
|
-
color: css("card-foreground"),
|
|
101
|
-
border: `1px solid ${css("border")}`,
|
|
102
|
-
borderRadius: "var(--radius, 8px)",
|
|
103
|
-
overflow: "hidden"
|
|
104
|
-
},
|
|
105
|
-
children: [
|
|
106
|
-
title || sub ? /* @__PURE__ */ jsxs(
|
|
107
|
-
"div",
|
|
108
|
-
{
|
|
109
|
-
style: {
|
|
110
|
-
padding: "8px 12px",
|
|
111
|
-
borderBottom: `1px solid ${css("border")}`
|
|
112
|
-
},
|
|
113
|
-
children: [
|
|
114
|
-
title ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, color: css("muted-foreground") }, children: title }) : null,
|
|
115
|
-
sub ? /* @__PURE__ */ jsx(
|
|
116
|
-
"div",
|
|
117
|
-
{
|
|
118
|
-
title: sub,
|
|
119
|
-
style: {
|
|
120
|
-
fontSize: 11,
|
|
121
|
-
fontWeight: 400,
|
|
122
|
-
color: css("muted-foreground"),
|
|
123
|
-
opacity: 0.75,
|
|
124
|
-
marginTop: title ? 2 : 0,
|
|
125
|
-
whiteSpace: "nowrap",
|
|
126
|
-
overflow: "hidden",
|
|
127
|
-
textOverflow: "ellipsis"
|
|
128
|
-
},
|
|
129
|
-
children: sub
|
|
130
|
-
}
|
|
131
|
-
) : null
|
|
132
|
-
]
|
|
133
|
-
}
|
|
134
|
-
) : null,
|
|
135
|
-
/* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, ...bodyStyle }, children })
|
|
136
|
-
]
|
|
137
|
-
}
|
|
138
|
-
);
|
|
139
|
-
}
|
|
111
|
+
import { resolveFrame, sameLabel } from "@devicechain/dashboards";
|
|
112
|
+
import { useMemo as useMemo3 } from "react";
|
|
140
113
|
|
|
141
|
-
// src/
|
|
142
|
-
|
|
143
|
-
if (!iso) return "";
|
|
144
|
-
const date = new Date(iso);
|
|
145
|
-
if (Number.isNaN(date.getTime())) return iso;
|
|
146
|
-
return timeZone ? date.toLocaleTimeString(void 0, { timeZone, timeZoneName: "short" }) : date.toLocaleTimeString();
|
|
147
|
-
}
|
|
148
|
-
function formatDateTime(iso, timeZone) {
|
|
149
|
-
if (!iso) return "";
|
|
150
|
-
const date = new Date(iso);
|
|
151
|
-
if (Number.isNaN(date.getTime())) return iso;
|
|
152
|
-
return timeZone ? date.toLocaleString(void 0, { timeZone, timeZoneName: "short" }) : date.toLocaleString();
|
|
153
|
-
}
|
|
154
|
-
function formatValue(value, precision) {
|
|
155
|
-
if (value == null) return "\u2014";
|
|
156
|
-
if (precision == null) return String(value);
|
|
157
|
-
const places = Math.min(100, Math.max(0, Math.trunc(precision)));
|
|
158
|
-
return value.toFixed(places);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
// src/flash.tsx
|
|
162
|
-
import { useEffect, useRef, useState } from "react";
|
|
163
|
-
import { jsx as jsx2 } from "react/jsx-runtime";
|
|
164
|
-
var UP = {
|
|
165
|
-
fg: "var(--flash-up, hsl(142 72% 40%))",
|
|
166
|
-
bg: "var(--flash-up-bg, hsl(142 72% 45% / 0.16))"
|
|
167
|
-
};
|
|
168
|
-
var DOWN = {
|
|
169
|
-
fg: "var(--flash-down, hsl(0 72% 51%))",
|
|
170
|
-
bg: "var(--flash-down-bg, hsl(0 72% 55% / 0.16))"
|
|
171
|
-
};
|
|
172
|
-
var HOLD_MS = 400;
|
|
173
|
-
var FADE_MS = 600;
|
|
174
|
-
function useFlashOnChange(value, enabled, identity) {
|
|
175
|
-
const prev = useRef(value);
|
|
176
|
-
const prevIdentity = useRef(identity);
|
|
177
|
-
const [direction, setDirection] = useState(null);
|
|
178
|
-
useEffect(() => {
|
|
179
|
-
const before = prev.current;
|
|
180
|
-
const sameEntity = prevIdentity.current === identity;
|
|
181
|
-
prev.current = value;
|
|
182
|
-
prevIdentity.current = identity;
|
|
183
|
-
if (!enabled || !sameEntity || typeof value !== "number" || typeof before !== "number" || !Number.isFinite(value) || !Number.isFinite(before) || value === before) {
|
|
184
|
-
setDirection(null);
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
setDirection(value > before ? "up" : "down");
|
|
188
|
-
const timer = setTimeout(() => setDirection(null), HOLD_MS);
|
|
189
|
-
return () => clearTimeout(timer);
|
|
190
|
-
}, [value, enabled, identity]);
|
|
191
|
-
return enabled ? direction : null;
|
|
192
|
-
}
|
|
193
|
-
function flashTextStyle(direction, fadeMs = FADE_MS) {
|
|
194
|
-
if (direction) return { color: direction === "up" ? UP.fg : DOWN.fg, transition: "none" };
|
|
195
|
-
return { transition: `color ${fadeMs}ms ease-out` };
|
|
196
|
-
}
|
|
197
|
-
function flashBackgroundStyle(direction, fadeMs = FADE_MS) {
|
|
198
|
-
if (direction) {
|
|
199
|
-
return { backgroundColor: direction === "up" ? UP.bg : DOWN.bg, transition: "none" };
|
|
200
|
-
}
|
|
201
|
-
return { transition: `background-color ${fadeMs}ms ease-out` };
|
|
202
|
-
}
|
|
203
|
-
function FlashValue({
|
|
204
|
-
value,
|
|
205
|
-
precision,
|
|
206
|
-
enabled,
|
|
207
|
-
identity,
|
|
208
|
-
baseColor = "inherit",
|
|
209
|
-
style
|
|
210
|
-
}) {
|
|
211
|
-
const direction = useFlashOnChange(value, enabled, identity);
|
|
212
|
-
return /* @__PURE__ */ jsx2("span", { style: { color: baseColor, ...style, ...flashTextStyle(direction) }, children: formatValue(value, precision) });
|
|
213
|
-
}
|
|
114
|
+
// src/freshness-readout.tsx
|
|
115
|
+
import { useEffect, useState } from "react";
|
|
214
116
|
|
|
215
117
|
// src/widget-options.ts
|
|
216
118
|
function pickSample(latest, name) {
|
|
@@ -235,6 +137,208 @@ function primaryMeasurementName(widget) {
|
|
|
235
137
|
return void 0;
|
|
236
138
|
}
|
|
237
139
|
|
|
140
|
+
// src/freshness.ts
|
|
141
|
+
function resolveStaleAfterSeconds(options) {
|
|
142
|
+
const configured = optNumber(options, "staleAfterSeconds");
|
|
143
|
+
return configured !== void 0 && configured > 0 ? configured : void 0;
|
|
144
|
+
}
|
|
145
|
+
function newestSampleTime(latest, names) {
|
|
146
|
+
let newest;
|
|
147
|
+
for (const [name, sample] of Object.entries(latest)) {
|
|
148
|
+
if (names && names.length > 0 && !names.includes(name)) continue;
|
|
149
|
+
if (!sample.occurredTime) continue;
|
|
150
|
+
const at = Date.parse(sample.occurredTime);
|
|
151
|
+
if (Number.isNaN(at)) continue;
|
|
152
|
+
if (newest === void 0 || at > newest) newest = at;
|
|
153
|
+
}
|
|
154
|
+
return newest;
|
|
155
|
+
}
|
|
156
|
+
function ageSeconds(at, now) {
|
|
157
|
+
return Math.max(0, Math.floor((now - at) / 1e3));
|
|
158
|
+
}
|
|
159
|
+
function isStale(age, staleAfter) {
|
|
160
|
+
return staleAfter !== void 0 && age > staleAfter;
|
|
161
|
+
}
|
|
162
|
+
function viewerLocale() {
|
|
163
|
+
if (typeof document !== "undefined" && document.documentElement.lang) return document.documentElement.lang;
|
|
164
|
+
if (typeof navigator !== "undefined" && navigator.language) return navigator.language;
|
|
165
|
+
return void 0;
|
|
166
|
+
}
|
|
167
|
+
var formatters = /* @__PURE__ */ new Map();
|
|
168
|
+
function relativeTimeFormat(locale) {
|
|
169
|
+
const key = locale ?? "";
|
|
170
|
+
let formatter = formatters.get(key);
|
|
171
|
+
if (!formatter) {
|
|
172
|
+
try {
|
|
173
|
+
formatter = new Intl.RelativeTimeFormat(locale, { numeric: "auto", style: "short" });
|
|
174
|
+
} catch {
|
|
175
|
+
formatter = new Intl.RelativeTimeFormat(void 0, { numeric: "auto", style: "short" });
|
|
176
|
+
}
|
|
177
|
+
formatters.set(key, formatter);
|
|
178
|
+
}
|
|
179
|
+
return formatter;
|
|
180
|
+
}
|
|
181
|
+
function formatAge(age, locale = viewerLocale()) {
|
|
182
|
+
const formatter = relativeTimeFormat(locale);
|
|
183
|
+
if (age < 60) return formatter.format(-age, "second");
|
|
184
|
+
if (age < 3600) return formatter.format(-Math.floor(age / 60), "minute");
|
|
185
|
+
if (age < 86400) return formatter.format(-Math.floor(age / 3600), "hour");
|
|
186
|
+
return formatter.format(-Math.floor(age / 86400), "day");
|
|
187
|
+
}
|
|
188
|
+
var FRESHNESS_STRINGS = {
|
|
189
|
+
en: { stale: "stale", ageLabel: "Last sample" },
|
|
190
|
+
es: { stale: "obsoleto", ageLabel: "\xDAltima muestra" }
|
|
191
|
+
};
|
|
192
|
+
function freshnessStrings(locale = viewerLocale()) {
|
|
193
|
+
const base = locale?.toLowerCase().split("-")[0];
|
|
194
|
+
return base === "es" ? FRESHNESS_STRINGS.es : FRESHNESS_STRINGS.en;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
// src/theme.ts
|
|
198
|
+
function css(name) {
|
|
199
|
+
return `hsl(var(--${name}))`;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// src/tokens.tsx
|
|
203
|
+
import { DENSITY_SCALE, SPACE_TOKEN_PX } from "@devicechain/dashboards";
|
|
204
|
+
import { jsx } from "react/jsx-runtime";
|
|
205
|
+
var FONT_TOKENS = {
|
|
206
|
+
xs: 11,
|
|
207
|
+
sm: 12,
|
|
208
|
+
md: 13,
|
|
209
|
+
lg: 16,
|
|
210
|
+
xl: 20,
|
|
211
|
+
display: 28
|
|
212
|
+
};
|
|
213
|
+
var SPACE_TOKENS = {
|
|
214
|
+
1: SPACE_TOKEN_PX["space-1"],
|
|
215
|
+
2: SPACE_TOKEN_PX["space-2"],
|
|
216
|
+
3: SPACE_TOKEN_PX["space-3"],
|
|
217
|
+
4: SPACE_TOKEN_PX["space-4"]
|
|
218
|
+
};
|
|
219
|
+
function densityScale(density) {
|
|
220
|
+
return DENSITY_SCALE[density ?? "comfortable"];
|
|
221
|
+
}
|
|
222
|
+
function fontVar(token) {
|
|
223
|
+
return `var(--dc-font-${token}, ${FONT_TOKENS[token]}px)`;
|
|
224
|
+
}
|
|
225
|
+
function spaceVar(token) {
|
|
226
|
+
return `var(--dc-space-${token}, ${SPACE_TOKENS[token]}px)`;
|
|
227
|
+
}
|
|
228
|
+
function tokenPx(token, scale = 1) {
|
|
229
|
+
const [kind, name] = token.split("-");
|
|
230
|
+
const base = kind === "font" ? FONT_TOKENS[name] : SPACE_TOKENS[Number(name)];
|
|
231
|
+
return Math.round(base * scale * 100) / 100;
|
|
232
|
+
}
|
|
233
|
+
function tokenStyle(scale) {
|
|
234
|
+
const vars = { "--dc-scale": String(scale) };
|
|
235
|
+
for (const [name, px] of Object.entries(FONT_TOKENS)) vars[`--dc-font-${name}`] = `calc(${px}px * var(--dc-scale))`;
|
|
236
|
+
for (const [name, px] of Object.entries(SPACE_TOKENS)) vars[`--dc-space-${name}`] = `calc(${px}px * var(--dc-scale))`;
|
|
237
|
+
return vars;
|
|
238
|
+
}
|
|
239
|
+
function WidgetTokenStyles() {
|
|
240
|
+
return /* @__PURE__ */ jsx("style", { "data-dc-tokens": "", children: severityTokenCss({ hostWins: true }) });
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// src/freshness-readout.tsx
|
|
244
|
+
import { jsx as jsx2, jsxs } from "react/jsx-runtime";
|
|
245
|
+
var tickers = /* @__PURE__ */ new Map();
|
|
246
|
+
function subscribeTick(everyMs, listener) {
|
|
247
|
+
let ticker = tickers.get(everyMs);
|
|
248
|
+
if (!ticker) {
|
|
249
|
+
const listeners = /* @__PURE__ */ new Set();
|
|
250
|
+
const timer = setInterval(() => {
|
|
251
|
+
const now = Date.now();
|
|
252
|
+
for (const l of [...listeners]) l(now);
|
|
253
|
+
}, everyMs);
|
|
254
|
+
ticker = { timer, listeners };
|
|
255
|
+
tickers.set(everyMs, ticker);
|
|
256
|
+
}
|
|
257
|
+
ticker.listeners.add(listener);
|
|
258
|
+
const owned = ticker;
|
|
259
|
+
return () => {
|
|
260
|
+
owned.listeners.delete(listener);
|
|
261
|
+
if (owned.listeners.size === 0 && tickers.get(everyMs) === owned) {
|
|
262
|
+
clearInterval(owned.timer);
|
|
263
|
+
tickers.delete(everyMs);
|
|
264
|
+
}
|
|
265
|
+
};
|
|
266
|
+
}
|
|
267
|
+
function useNow(everyMs = 1e3, enabled = true) {
|
|
268
|
+
const [now, setNow] = useState(() => Date.now());
|
|
269
|
+
useEffect(() => {
|
|
270
|
+
if (!enabled) return;
|
|
271
|
+
setNow(Date.now());
|
|
272
|
+
return subscribeTick(everyMs, setNow);
|
|
273
|
+
}, [everyMs, enabled]);
|
|
274
|
+
return now;
|
|
275
|
+
}
|
|
276
|
+
function useFreshness(at, staleAfterSeconds) {
|
|
277
|
+
const now = useNow(1e3, at !== void 0);
|
|
278
|
+
const [seen, setSeen] = useState(void 0);
|
|
279
|
+
let current = seen;
|
|
280
|
+
if (at !== void 0 && seen?.at !== at) {
|
|
281
|
+
current = { at, seenAt: Math.max(now, Date.now()) };
|
|
282
|
+
setSeen(current);
|
|
283
|
+
}
|
|
284
|
+
if (at === void 0 || current === void 0) return void 0;
|
|
285
|
+
const age = ageSeconds(Math.min(at, current.seenAt), now);
|
|
286
|
+
return { age, stale: isStale(age, staleAfterSeconds) };
|
|
287
|
+
}
|
|
288
|
+
function StaleBadge({ label }) {
|
|
289
|
+
return /* @__PURE__ */ jsx2(
|
|
290
|
+
"span",
|
|
291
|
+
{
|
|
292
|
+
"data-stale-badge": "",
|
|
293
|
+
style: {
|
|
294
|
+
fontSize: fontVar("xs"),
|
|
295
|
+
fontWeight: 600,
|
|
296
|
+
textTransform: "uppercase",
|
|
297
|
+
letterSpacing: 0.4,
|
|
298
|
+
padding: "0 5px",
|
|
299
|
+
borderRadius: 4,
|
|
300
|
+
border: `1px dashed ${css("muted-foreground")}`,
|
|
301
|
+
color: css("foreground"),
|
|
302
|
+
background: css("muted"),
|
|
303
|
+
whiteSpace: "nowrap"
|
|
304
|
+
},
|
|
305
|
+
children: label ?? freshnessStrings().stale
|
|
306
|
+
}
|
|
307
|
+
);
|
|
308
|
+
}
|
|
309
|
+
function Freshness({ at, staleAfterSeconds, staleLabel, style }) {
|
|
310
|
+
const reading = useFreshness(at, staleAfterSeconds);
|
|
311
|
+
if (!reading) return null;
|
|
312
|
+
return /* @__PURE__ */ jsx2(FreshnessReadout, { reading, staleLabel, style });
|
|
313
|
+
}
|
|
314
|
+
function FreshnessReadout({
|
|
315
|
+
reading,
|
|
316
|
+
staleLabel,
|
|
317
|
+
style
|
|
318
|
+
}) {
|
|
319
|
+
return /* @__PURE__ */ jsxs(
|
|
320
|
+
"span",
|
|
321
|
+
{
|
|
322
|
+
"data-freshness": "",
|
|
323
|
+
"data-stale": reading.stale ? "true" : void 0,
|
|
324
|
+
title: freshnessStrings().ageLabel,
|
|
325
|
+
style: {
|
|
326
|
+
display: "inline-flex",
|
|
327
|
+
alignItems: "center",
|
|
328
|
+
gap: 6,
|
|
329
|
+
fontSize: fontVar("xs"),
|
|
330
|
+
color: css("muted-foreground"),
|
|
331
|
+
fontVariantNumeric: "tabular-nums",
|
|
332
|
+
...style
|
|
333
|
+
},
|
|
334
|
+
children: [
|
|
335
|
+
/* @__PURE__ */ jsx2("time", { children: formatAge(reading.age) }),
|
|
336
|
+
reading.stale ? /* @__PURE__ */ jsx2(StaleBadge, { label: staleLabel }) : null
|
|
337
|
+
]
|
|
338
|
+
}
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
238
342
|
// src/hooks.ts
|
|
239
343
|
import {
|
|
240
344
|
bindingsWithoutScopedSlots,
|
|
@@ -243,7 +347,7 @@ import {
|
|
|
243
347
|
resolveContextBindings,
|
|
244
348
|
resolveSlotCandidates
|
|
245
349
|
} from "@devicechain/dashboards";
|
|
246
|
-
import { useEffect as useEffect2, useMemo, useRef
|
|
350
|
+
import { useEffect as useEffect2, useMemo, useRef, useState as useState2, useSyncExternalStore } from "react";
|
|
247
351
|
function useDashboardHub(resolver, authorities) {
|
|
248
352
|
const authoritiesKey = JSON.stringify(authorities ?? []);
|
|
249
353
|
const hub = useMemo(
|
|
@@ -259,7 +363,7 @@ function useResolvedBindings(definition, base, selection, resolver) {
|
|
|
259
363
|
const scoped = useMemo(() => hasScopedSlots(definition), [definition]);
|
|
260
364
|
const overlaid = useMemo(() => ({ ...base, ...selection }), [base, selection]);
|
|
261
365
|
const [resolved, setResolved] = useState2(() => bindingsWithoutScopedSlots(definition, overlaid));
|
|
262
|
-
const genRef =
|
|
366
|
+
const genRef = useRef(0);
|
|
263
367
|
useEffect2(() => {
|
|
264
368
|
if (!scoped) {
|
|
265
369
|
setResolved((prev) => sameBindings(prev, overlaid) ? prev : overlaid);
|
|
@@ -308,7 +412,7 @@ function useCandidates(slot) {
|
|
|
308
412
|
loading: Boolean(provider && slot),
|
|
309
413
|
error: null
|
|
310
414
|
}));
|
|
311
|
-
const genRef =
|
|
415
|
+
const genRef = useRef(0);
|
|
312
416
|
useEffect2(() => {
|
|
313
417
|
if (!provider || !slot) {
|
|
314
418
|
setState({ candidates: [], loading: false, error: null });
|
|
@@ -328,16 +432,49 @@ function useCandidates(slot) {
|
|
|
328
432
|
}, [provider, slot]);
|
|
329
433
|
return { ...state, wired };
|
|
330
434
|
}
|
|
435
|
+
var MAX_SPAN_SAMPLES = 2e3;
|
|
331
436
|
var EMPTY = { latest: {}, samples: [], error: null };
|
|
332
437
|
function latestOf(samples) {
|
|
333
438
|
const latest = {};
|
|
334
439
|
for (const s of samples) latest[s.name] = s;
|
|
335
440
|
return latest;
|
|
336
441
|
}
|
|
442
|
+
function evictOldest(samples, max, evicted) {
|
|
443
|
+
if (samples.length <= max) return samples;
|
|
444
|
+
const cut = samples.length - max;
|
|
445
|
+
for (let i = 0; i < cut; i++) {
|
|
446
|
+
const at = Date.parse(samples[i].occurredTime ?? "");
|
|
447
|
+
if (at > evicted.current) evicted.current = at;
|
|
448
|
+
}
|
|
449
|
+
return samples.slice(cut);
|
|
450
|
+
}
|
|
451
|
+
function withinSpan(samples, spanSeconds, end) {
|
|
452
|
+
const from = end - spanSeconds * 1e3;
|
|
453
|
+
return samples.filter((s) => {
|
|
454
|
+
if (!s.occurredTime) return true;
|
|
455
|
+
const at = Date.parse(s.occurredTime);
|
|
456
|
+
return Number.isNaN(at) || at >= from;
|
|
457
|
+
});
|
|
458
|
+
}
|
|
337
459
|
function useMeasurementStream(hub, datasource, options = {}) {
|
|
338
|
-
const
|
|
460
|
+
const { windowSeconds, pausedAt } = options;
|
|
461
|
+
const windowSize = options.window ?? (windowSeconds !== void 0 ? MAX_SPAN_SAMPLES : 300);
|
|
339
462
|
const initialSamples = options.initialSamples;
|
|
463
|
+
const paused = pausedAt !== void 0;
|
|
340
464
|
const key = datasource ? JSON.stringify(datasource) : null;
|
|
465
|
+
const spanRef = useRef(windowSeconds);
|
|
466
|
+
const clockRef = useRef(options.now ?? Date.now);
|
|
467
|
+
const pausedRef = useRef(paused);
|
|
468
|
+
const queueRef = useRef([]);
|
|
469
|
+
const queueLatestRef = useRef({});
|
|
470
|
+
const evictedRef = useRef(-Infinity);
|
|
471
|
+
const flushRef = useRef(() => {
|
|
472
|
+
});
|
|
473
|
+
const clock = options.now ?? Date.now;
|
|
474
|
+
useEffect2(() => {
|
|
475
|
+
spanRef.current = windowSeconds;
|
|
476
|
+
clockRef.current = clock;
|
|
477
|
+
}, [windowSeconds, clock]);
|
|
341
478
|
const [tagged, setTagged] = useState2({
|
|
342
479
|
key,
|
|
343
480
|
state: EMPTY
|
|
@@ -345,40 +482,79 @@ function useMeasurementStream(hub, datasource, options = {}) {
|
|
|
345
482
|
const live = tagged.key === key ? tagged.state : EMPTY;
|
|
346
483
|
useEffect2(() => {
|
|
347
484
|
setTagged({ key, state: EMPTY });
|
|
485
|
+
queueRef.current = [];
|
|
486
|
+
queueLatestRef.current = {};
|
|
487
|
+
evictedRef.current = -Infinity;
|
|
488
|
+
flushRef.current = () => {
|
|
489
|
+
};
|
|
348
490
|
if (!datasource) return;
|
|
349
491
|
const update = (fn) => setTagged((prev) => prev.key === key ? { key, state: fn(prev.state) } : prev);
|
|
492
|
+
const fold = (prev, incoming, newest = latestOf(incoming)) => {
|
|
493
|
+
let samples = prev.samples.concat(incoming);
|
|
494
|
+
const span = spanRef.current;
|
|
495
|
+
if (span !== void 0) samples = withinSpan(samples, span, clockRef.current());
|
|
496
|
+
samples = evictOldest(samples, windowSize, evictedRef);
|
|
497
|
+
return { latest: { ...prev.latest, ...newest }, samples, error: null };
|
|
498
|
+
};
|
|
499
|
+
flushRef.current = () => {
|
|
500
|
+
const queued = queueRef.current;
|
|
501
|
+
if (queued.length === 0) return;
|
|
502
|
+
const newest = queueLatestRef.current;
|
|
503
|
+
queueRef.current = [];
|
|
504
|
+
queueLatestRef.current = {};
|
|
505
|
+
update((prev) => fold(prev, queued, newest));
|
|
506
|
+
};
|
|
350
507
|
return hub.subscribeWidget(datasource, {
|
|
351
|
-
next: (sample) =>
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
508
|
+
next: (sample) => {
|
|
509
|
+
if (pausedRef.current) {
|
|
510
|
+
queueRef.current = evictOldest([...queueRef.current, sample], windowSize, evictedRef);
|
|
511
|
+
queueLatestRef.current[sample.name] = sample;
|
|
512
|
+
return;
|
|
513
|
+
}
|
|
514
|
+
update((prev) => fold(prev, [sample]));
|
|
515
|
+
},
|
|
356
516
|
error: (err) => update((prev) => ({ ...prev, error: err }))
|
|
357
517
|
});
|
|
358
518
|
}, [hub, key, windowSize]);
|
|
519
|
+
useEffect2(() => {
|
|
520
|
+
pausedRef.current = paused;
|
|
521
|
+
if (!paused) flushRef.current();
|
|
522
|
+
}, [paused]);
|
|
359
523
|
return useMemo(() => {
|
|
360
|
-
|
|
361
|
-
|
|
524
|
+
const end = pausedAt ?? clock();
|
|
525
|
+
if (windowSeconds === void 0) {
|
|
526
|
+
if (!initialSamples || initialSamples.length === 0) return live;
|
|
527
|
+
const merged2 = initialSamples.concat(live.samples);
|
|
528
|
+
return {
|
|
529
|
+
latest: { ...latestOf(initialSamples), ...live.latest },
|
|
530
|
+
samples: merged2.length > windowSize ? merged2.slice(merged2.length - windowSize) : merged2,
|
|
531
|
+
error: live.error
|
|
532
|
+
};
|
|
533
|
+
}
|
|
534
|
+
const from = end - windowSeconds * 1e3;
|
|
535
|
+
const merged = withinSpan(initialSamples ? initialSamples.concat(live.samples) : live.samples, windowSeconds, end);
|
|
362
536
|
const samples = merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged;
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
};
|
|
368
|
-
|
|
537
|
+
const cut = samples.length < merged.length || evictedRef.current >= from;
|
|
538
|
+
const truncatedBefore = cut ? samples[0]?.occurredTime ?? void 0 : void 0;
|
|
539
|
+
const latest = initialSamples ? { ...latestOf(initialSamples), ...live.latest } : live.latest;
|
|
540
|
+
if (!truncatedBefore && latest === live.latest && samples.length === live.samples.length) return live;
|
|
541
|
+
const state = { latest, samples, error: live.error };
|
|
542
|
+
if (truncatedBefore) state.truncatedBefore = truncatedBefore;
|
|
543
|
+
return state;
|
|
544
|
+
}, [initialSamples, live, windowSize, windowSeconds, pausedAt, clock]);
|
|
369
545
|
}
|
|
370
546
|
var EMPTY_ALARMS = { alarms: [], total: 0, loading: true, error: null };
|
|
371
547
|
function useAlarmStream(hub, subscription) {
|
|
372
|
-
const [state, setState] = useState2(EMPTY_ALARMS);
|
|
373
548
|
const key = JSON.stringify(subscription);
|
|
549
|
+
const [[at, state], setState] = useState2([key, EMPTY_ALARMS]);
|
|
374
550
|
useEffect2(() => {
|
|
375
|
-
setState(EMPTY_ALARMS);
|
|
551
|
+
setState([key, EMPTY_ALARMS]);
|
|
376
552
|
return hub.subscribeAlarms(subscription, {
|
|
377
|
-
next: (snapshot) => setState({ alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }),
|
|
378
|
-
error: (err) => setState((prev) =>
|
|
553
|
+
next: (snapshot) => setState([key, { alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }]),
|
|
554
|
+
error: (err) => setState(([, prev]) => [key, { ...prev, loading: false, error: err }])
|
|
379
555
|
});
|
|
380
556
|
}, [hub, key]);
|
|
381
|
-
return state;
|
|
557
|
+
return at === key ? state : EMPTY_ALARMS;
|
|
382
558
|
}
|
|
383
559
|
var EMPTY_COMMANDS = {
|
|
384
560
|
deviceToken: null,
|
|
@@ -413,12 +589,13 @@ var EMPTY_LOCATIONS = {
|
|
|
413
589
|
error: null
|
|
414
590
|
};
|
|
415
591
|
function useLocationStream(hub, subscription) {
|
|
416
|
-
const [state, setState] = useState2(EMPTY_LOCATIONS);
|
|
417
592
|
const key = JSON.stringify(subscription);
|
|
593
|
+
const [[at, state], setState] = useState2([key, EMPTY_LOCATIONS]);
|
|
418
594
|
useEffect2(() => {
|
|
419
|
-
setState(EMPTY_LOCATIONS);
|
|
595
|
+
setState([key, EMPTY_LOCATIONS]);
|
|
420
596
|
return hub.subscribeLocations(subscription, {
|
|
421
|
-
next: (snapshot) => setState(
|
|
597
|
+
next: (snapshot) => setState([
|
|
598
|
+
key,
|
|
422
599
|
snapshot.kind === "forbidden" ? { locations: [], deviceTokens: [], forbidden: true, loading: false, error: null } : {
|
|
423
600
|
locations: snapshot.locations,
|
|
424
601
|
deviceTokens: snapshot.deviceTokens,
|
|
@@ -426,11 +603,11 @@ function useLocationStream(hub, subscription) {
|
|
|
426
603
|
loading: false,
|
|
427
604
|
error: null
|
|
428
605
|
}
|
|
429
|
-
),
|
|
430
|
-
error: (err) => setState((prev) =>
|
|
606
|
+
]),
|
|
607
|
+
error: (err) => setState(([, prev]) => [key, { ...prev, loading: false, error: err }])
|
|
431
608
|
});
|
|
432
609
|
}, [hub, key]);
|
|
433
|
-
return state;
|
|
610
|
+
return at === key ? state : EMPTY_LOCATIONS;
|
|
434
611
|
}
|
|
435
612
|
var AVAILABILITY_REVALIDATE_MS = 6e4;
|
|
436
613
|
function useDatasourceAvailability(hub, datasource) {
|
|
@@ -470,8 +647,8 @@ function useDatasourceAvailability(hub, datasource) {
|
|
|
470
647
|
}, [hub, key]);
|
|
471
648
|
return state;
|
|
472
649
|
}
|
|
473
|
-
function useElementSize() {
|
|
474
|
-
const ref =
|
|
650
|
+
function useElementSize(step = 1) {
|
|
651
|
+
const ref = useRef(null);
|
|
475
652
|
const [size, setSize] = useState2({ width: 0, height: 0 });
|
|
476
653
|
useEffect2(() => {
|
|
477
654
|
const el = ref.current;
|
|
@@ -479,15 +656,314 @@ function useElementSize() {
|
|
|
479
656
|
const observer = new ResizeObserver((entries) => {
|
|
480
657
|
const rect = entries[0]?.contentRect;
|
|
481
658
|
if (!rect) return;
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
);
|
|
659
|
+
const width = step > 1 ? Math.floor(rect.width / step) * step : rect.width;
|
|
660
|
+
const height = step > 1 ? Math.floor(rect.height / step) * step : rect.height;
|
|
661
|
+
setSize((prev) => prev.width === width && prev.height === height ? prev : { width, height });
|
|
485
662
|
});
|
|
486
663
|
observer.observe(el);
|
|
487
664
|
return () => observer.disconnect();
|
|
488
|
-
}, []);
|
|
665
|
+
}, [step]);
|
|
489
666
|
return [ref, size];
|
|
490
667
|
}
|
|
668
|
+
var NO_CONNECTION_SOURCE = {
|
|
669
|
+
connectionState: "live",
|
|
670
|
+
onConnectionState: () => () => {
|
|
671
|
+
}
|
|
672
|
+
};
|
|
673
|
+
function useHubConnectionState(hub) {
|
|
674
|
+
const source = hub && typeof hub.onConnectionState === "function" ? hub : NO_CONNECTION_SOURCE;
|
|
675
|
+
return useSyncExternalStore(
|
|
676
|
+
(listener) => source.onConnectionState(listener),
|
|
677
|
+
() => source.connectionState
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
// src/scale.ts
|
|
682
|
+
import { useEffect as useEffect3, useMemo as useMemo2, useReducer, useRef as useRef2 } from "react";
|
|
683
|
+
var SIZE_CLASS_BREAKS = {
|
|
684
|
+
compactWidth: 120,
|
|
685
|
+
compactHeight: 72,
|
|
686
|
+
largeWidth: 420,
|
|
687
|
+
largeHeight: 220
|
|
688
|
+
};
|
|
689
|
+
function sizeClassOf(size, scale = 1) {
|
|
690
|
+
if (!(size.width > 0) || !(size.height > 0)) return "regular";
|
|
691
|
+
const b = SIZE_CLASS_BREAKS;
|
|
692
|
+
if (size.width < b.compactWidth * scale || size.height < b.compactHeight * scale) return "compact";
|
|
693
|
+
if (size.width >= b.largeWidth * scale && size.height >= b.largeHeight * scale) return "large";
|
|
694
|
+
return "regular";
|
|
695
|
+
}
|
|
696
|
+
function makeWidgetScale(size, scale) {
|
|
697
|
+
return { sizeClass: sizeClassOf(size, scale), scale, size, px: (token) => tokenPx(token, scale) };
|
|
698
|
+
}
|
|
699
|
+
function useWidgetScale() {
|
|
700
|
+
const size = useWidgetBodySize();
|
|
701
|
+
const scale = useWidgetDensityScale();
|
|
702
|
+
return useMemo2(() => makeWidgetScale(size, scale), [size, scale]);
|
|
703
|
+
}
|
|
704
|
+
var STICKY_HOLD_MS = 6e4;
|
|
705
|
+
function useStickyChars(chars, holdMs = STICKY_HOLD_MS) {
|
|
706
|
+
const held = useRef2(void 0);
|
|
707
|
+
const [tick, wake] = useReducer((n) => n + 1, 0);
|
|
708
|
+
held.current = stickyChars(held.current, chars, Date.now(), holdMs);
|
|
709
|
+
const { value, since } = held.current;
|
|
710
|
+
useEffect3(() => {
|
|
711
|
+
if (value <= chars) return;
|
|
712
|
+
const timer = setTimeout(wake, Math.max(0, since + holdMs - Date.now()));
|
|
713
|
+
return () => clearTimeout(timer);
|
|
714
|
+
}, [value, since, chars, holdMs, tick]);
|
|
715
|
+
return value;
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// src/frame.tsx
|
|
719
|
+
import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
720
|
+
var FRESHNESS_MIN_WIDTH = 150;
|
|
721
|
+
var FRESHNESS_MIN_HEIGHT = 110;
|
|
722
|
+
var FRAME_SUBTITLE_MIN_HEIGHT = 120;
|
|
723
|
+
var FRAME_TITLE_MIN_HEIGHT = 76;
|
|
724
|
+
var BODY_SIZE_STEP = 4;
|
|
725
|
+
var DEFAULT_FRAME = resolveFrame(void 0, void 0);
|
|
726
|
+
var RADIUS = {
|
|
727
|
+
none: "0px",
|
|
728
|
+
sm: "calc(var(--radius, 8px) / 2)",
|
|
729
|
+
md: "var(--radius, 8px)",
|
|
730
|
+
lg: "calc(var(--radius, 8px) * 1.5)"
|
|
731
|
+
};
|
|
732
|
+
function spaceTokenVar(token) {
|
|
733
|
+
return token === "space-0" ? "0px" : spaceVar(Number(token.slice("space-".length)));
|
|
734
|
+
}
|
|
735
|
+
function parseSampleTime(time) {
|
|
736
|
+
if (!time) return void 0;
|
|
737
|
+
const at = Date.parse(time);
|
|
738
|
+
return Number.isNaN(at) ? void 0 : at;
|
|
739
|
+
}
|
|
740
|
+
function WidgetFrame({ title, subtitle, children, bodyStyle, flush, sampleTime }) {
|
|
741
|
+
const subject = useWidgetSubject();
|
|
742
|
+
const rawSub = subtitle ?? subject;
|
|
743
|
+
const dupSub = rawSub && sameLabel(rawSub, title) ? void 0 : rawSub;
|
|
744
|
+
const freshness = useWidgetFreshness();
|
|
745
|
+
const at = freshness === void 0 ? void 0 : sampleTime === void 0 ? freshness.at : parseSampleTime(sampleTime);
|
|
746
|
+
const reading = useFreshness(at, freshness?.staleAfterSeconds);
|
|
747
|
+
const [rootRef, size] = useElementSize();
|
|
748
|
+
const [columnRef, columnSize] = useElementSize(BODY_SIZE_STEP);
|
|
749
|
+
const scale = useWidgetDensityScale();
|
|
750
|
+
const frame = useWidgetFrameStyle() ?? DEFAULT_FRAME;
|
|
751
|
+
const measured = size.height > 0;
|
|
752
|
+
const header = frame.header;
|
|
753
|
+
const showSub = header === "show" || header === "auto" && (!measured || size.height >= FRAME_SUBTITLE_MIN_HEIGHT * scale);
|
|
754
|
+
const showTitle = header === "show" || header === "auto" && (!measured || size.height >= FRAME_TITLE_MIN_HEIGHT * scale);
|
|
755
|
+
const rule = frame.border.width > 0 ? `${frame.border.width}px solid ${css(frame.border.color)}` : "none";
|
|
756
|
+
const sub = showSub ? dupSub : void 0;
|
|
757
|
+
const shownTitle = showTitle ? title : void 0;
|
|
758
|
+
const widgetScale = useMemo3(() => makeWidgetScale(columnSize, scale), [columnSize, scale]);
|
|
759
|
+
const compact = size.width > 0 && (size.width < FRESHNESS_MIN_WIDTH || size.height < FRESHNESS_MIN_HEIGHT);
|
|
760
|
+
const stale = reading?.stale === true;
|
|
761
|
+
return /* @__PURE__ */ jsxs2(
|
|
762
|
+
"div",
|
|
763
|
+
{
|
|
764
|
+
ref: rootRef,
|
|
765
|
+
"data-stale": stale ? "true" : void 0,
|
|
766
|
+
role: title && !showTitle ? "group" : void 0,
|
|
767
|
+
"aria-label": title && !showTitle ? title : void 0,
|
|
768
|
+
style: {
|
|
769
|
+
position: "relative",
|
|
770
|
+
display: "flex",
|
|
771
|
+
flexDirection: "column",
|
|
772
|
+
width: "100%",
|
|
773
|
+
height: "100%",
|
|
774
|
+
boxSizing: "border-box",
|
|
775
|
+
background: frame.background === "transparent" ? "transparent" : css(frame.background),
|
|
776
|
+
color: css("card-foreground"),
|
|
777
|
+
border: rule,
|
|
778
|
+
borderRadius: RADIUS[frame.border.radius],
|
|
779
|
+
overflow: "hidden"
|
|
780
|
+
},
|
|
781
|
+
children: [
|
|
782
|
+
shownTitle || sub ? /* @__PURE__ */ jsxs2(
|
|
783
|
+
"div",
|
|
784
|
+
{
|
|
785
|
+
style: {
|
|
786
|
+
padding: `${spaceVar(2)} ${spaceVar(3)}`,
|
|
787
|
+
borderBottom: rule
|
|
788
|
+
},
|
|
789
|
+
children: [
|
|
790
|
+
shownTitle ? /* @__PURE__ */ jsx3(
|
|
791
|
+
"div",
|
|
792
|
+
{
|
|
793
|
+
title: shownTitle,
|
|
794
|
+
style: {
|
|
795
|
+
fontSize: fontVar("sm"),
|
|
796
|
+
fontWeight: 600,
|
|
797
|
+
color: css("muted-foreground"),
|
|
798
|
+
whiteSpace: "nowrap",
|
|
799
|
+
overflow: "hidden",
|
|
800
|
+
textOverflow: "ellipsis"
|
|
801
|
+
},
|
|
802
|
+
children: shownTitle
|
|
803
|
+
}
|
|
804
|
+
) : null,
|
|
805
|
+
sub ? /* @__PURE__ */ jsx3(
|
|
806
|
+
"div",
|
|
807
|
+
{
|
|
808
|
+
title: sub,
|
|
809
|
+
style: {
|
|
810
|
+
fontSize: fontVar("xs"),
|
|
811
|
+
fontWeight: 400,
|
|
812
|
+
color: css("muted-foreground"),
|
|
813
|
+
opacity: 0.75,
|
|
814
|
+
marginTop: shownTitle ? 2 : 0,
|
|
815
|
+
whiteSpace: "nowrap",
|
|
816
|
+
overflow: "hidden",
|
|
817
|
+
textOverflow: "ellipsis"
|
|
818
|
+
},
|
|
819
|
+
children: sub
|
|
820
|
+
}
|
|
821
|
+
) : null
|
|
822
|
+
]
|
|
823
|
+
}
|
|
824
|
+
) : null,
|
|
825
|
+
/* @__PURE__ */ jsx3(
|
|
826
|
+
"div",
|
|
827
|
+
{
|
|
828
|
+
"data-widget-body": "",
|
|
829
|
+
style: {
|
|
830
|
+
position: "relative",
|
|
831
|
+
flex: 1,
|
|
832
|
+
minHeight: 0,
|
|
833
|
+
minWidth: 0,
|
|
834
|
+
overflow: "hidden",
|
|
835
|
+
...bodyStyle,
|
|
836
|
+
...stale ? { opacity: 0.45, filter: "grayscale(1)" } : null
|
|
837
|
+
},
|
|
838
|
+
children: /* @__PURE__ */ jsx3(
|
|
839
|
+
"div",
|
|
840
|
+
{
|
|
841
|
+
ref: columnRef,
|
|
842
|
+
"data-widget-content": "",
|
|
843
|
+
style: {
|
|
844
|
+
position: "absolute",
|
|
845
|
+
inset: 0,
|
|
846
|
+
boxSizing: "border-box",
|
|
847
|
+
containerType: "size",
|
|
848
|
+
overflow: "hidden",
|
|
849
|
+
padding: flush ? 0 : spaceTokenVar(frame.padding)
|
|
850
|
+
},
|
|
851
|
+
children: /* @__PURE__ */ jsx3(WidgetBodySizeProvider, { size: columnSize, children: typeof children === "function" ? children(widgetScale) : children })
|
|
852
|
+
}
|
|
853
|
+
)
|
|
854
|
+
}
|
|
855
|
+
),
|
|
856
|
+
reading && !compact ? /* @__PURE__ */ jsx3(
|
|
857
|
+
"div",
|
|
858
|
+
{
|
|
859
|
+
"data-widget-freshness": "",
|
|
860
|
+
style: { padding: `${spaceVar(1)} ${spaceVar(3)}`, borderTop: rule, flex: "0 0 auto" },
|
|
861
|
+
children: /* @__PURE__ */ jsx3(FreshnessReadout, { reading })
|
|
862
|
+
}
|
|
863
|
+
) : null,
|
|
864
|
+
reading && compact && stale ? /* @__PURE__ */ jsx3("div", { style: { position: "absolute", top: spaceVar(1), right: spaceVar(1) }, children: /* @__PURE__ */ jsx3(StaleBadge, {}) }) : null
|
|
865
|
+
]
|
|
866
|
+
}
|
|
867
|
+
);
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/connection-badge.tsx
|
|
871
|
+
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
872
|
+
var DOT = {
|
|
873
|
+
live: "primary",
|
|
874
|
+
reconnecting: "muted-foreground",
|
|
875
|
+
offline: "destructive"
|
|
876
|
+
};
|
|
877
|
+
function HubConnectionBadge({ state, labels }) {
|
|
878
|
+
return /* @__PURE__ */ jsxs3(
|
|
879
|
+
"span",
|
|
880
|
+
{
|
|
881
|
+
role: "status",
|
|
882
|
+
"data-connection-state": state,
|
|
883
|
+
style: {
|
|
884
|
+
display: "inline-flex",
|
|
885
|
+
alignItems: "center",
|
|
886
|
+
gap: 6,
|
|
887
|
+
fontSize: fontVar("sm"),
|
|
888
|
+
padding: "2px 8px",
|
|
889
|
+
borderRadius: 999,
|
|
890
|
+
border: `1px solid ${css("border")}`,
|
|
891
|
+
color: state === "offline" ? css("destructive") : css("muted-foreground"),
|
|
892
|
+
whiteSpace: "nowrap"
|
|
893
|
+
},
|
|
894
|
+
children: [
|
|
895
|
+
/* @__PURE__ */ jsx4(
|
|
896
|
+
"span",
|
|
897
|
+
{
|
|
898
|
+
"aria-hidden": true,
|
|
899
|
+
style: {
|
|
900
|
+
width: 8,
|
|
901
|
+
height: 8,
|
|
902
|
+
borderRadius: "50%",
|
|
903
|
+
background: css(DOT[state]),
|
|
904
|
+
opacity: state === "reconnecting" ? 0.6 : 1
|
|
905
|
+
}
|
|
906
|
+
}
|
|
907
|
+
),
|
|
908
|
+
labels[state]
|
|
909
|
+
]
|
|
910
|
+
}
|
|
911
|
+
);
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
// src/flash.tsx
|
|
915
|
+
import { useEffect as useEffect4, useRef as useRef3, useState as useState3 } from "react";
|
|
916
|
+
import { jsx as jsx5 } from "react/jsx-runtime";
|
|
917
|
+
var UP = {
|
|
918
|
+
fg: "var(--flash-up, hsl(142 72% 40%))",
|
|
919
|
+
bg: "var(--flash-up-bg, hsl(142 72% 45% / 0.16))"
|
|
920
|
+
};
|
|
921
|
+
var DOWN = {
|
|
922
|
+
fg: "var(--flash-down, hsl(0 72% 51%))",
|
|
923
|
+
bg: "var(--flash-down-bg, hsl(0 72% 55% / 0.16))"
|
|
924
|
+
};
|
|
925
|
+
var HOLD_MS = 400;
|
|
926
|
+
var FADE_MS = 600;
|
|
927
|
+
function useFlashOnChange(value, enabled, identity) {
|
|
928
|
+
const prev = useRef3(value);
|
|
929
|
+
const prevIdentity = useRef3(identity);
|
|
930
|
+
const [direction, setDirection] = useState3(null);
|
|
931
|
+
useEffect4(() => {
|
|
932
|
+
const before = prev.current;
|
|
933
|
+
const sameEntity = prevIdentity.current === identity;
|
|
934
|
+
prev.current = value;
|
|
935
|
+
prevIdentity.current = identity;
|
|
936
|
+
if (!enabled || !sameEntity || typeof value !== "number" || typeof before !== "number" || !Number.isFinite(value) || !Number.isFinite(before) || value === before) {
|
|
937
|
+
setDirection(null);
|
|
938
|
+
return;
|
|
939
|
+
}
|
|
940
|
+
setDirection(value > before ? "up" : "down");
|
|
941
|
+
const timer = setTimeout(() => setDirection(null), HOLD_MS);
|
|
942
|
+
return () => clearTimeout(timer);
|
|
943
|
+
}, [value, enabled, identity]);
|
|
944
|
+
return enabled ? direction : null;
|
|
945
|
+
}
|
|
946
|
+
function flashTextStyle(direction, fadeMs = FADE_MS) {
|
|
947
|
+
if (direction) return { color: direction === "up" ? UP.fg : DOWN.fg, transition: "none" };
|
|
948
|
+
return { transition: `color ${fadeMs}ms ease-out` };
|
|
949
|
+
}
|
|
950
|
+
function flashBackgroundStyle(direction, fadeMs = FADE_MS) {
|
|
951
|
+
if (direction) {
|
|
952
|
+
return { backgroundColor: direction === "up" ? UP.bg : DOWN.bg, transition: "none" };
|
|
953
|
+
}
|
|
954
|
+
return { transition: `background-color ${fadeMs}ms ease-out` };
|
|
955
|
+
}
|
|
956
|
+
function FlashValue({
|
|
957
|
+
value,
|
|
958
|
+
precision,
|
|
959
|
+
enabled,
|
|
960
|
+
identity,
|
|
961
|
+
baseColor = "inherit",
|
|
962
|
+
style
|
|
963
|
+
}) {
|
|
964
|
+
const direction = useFlashOnChange(value, enabled, identity);
|
|
965
|
+
return /* @__PURE__ */ jsx5("span", { style: { color: baseColor, ...style, ...flashTextStyle(direction) }, children: formatValue(value, precision) });
|
|
966
|
+
}
|
|
491
967
|
|
|
492
968
|
// src/index.ts
|
|
493
969
|
import {
|
|
@@ -499,32 +975,71 @@ import {
|
|
|
499
975
|
validateWidgetDescriptor
|
|
500
976
|
} from "@devicechain/dashboards";
|
|
501
977
|
export {
|
|
978
|
+
ALARM_LEVELS,
|
|
979
|
+
FIT_MIN_FONT_PX,
|
|
980
|
+
FONT_TOKENS,
|
|
502
981
|
FlashValue,
|
|
982
|
+
Freshness,
|
|
983
|
+
FreshnessReadout,
|
|
984
|
+
HubConnectionBadge,
|
|
985
|
+
MAX_SPAN_SAMPLES,
|
|
503
986
|
SDK_API_VERSION,
|
|
987
|
+
SEVERITY_LEVELS,
|
|
988
|
+
SEVERITY_TOKENS,
|
|
989
|
+
SIZE_CLASS_BREAKS,
|
|
990
|
+
SPACE_TOKENS,
|
|
991
|
+
StaleBadge,
|
|
504
992
|
WidgetCandidatesProvider,
|
|
505
993
|
WidgetCatalogError2 as WidgetCatalogError,
|
|
994
|
+
WidgetDensityProvider,
|
|
506
995
|
WidgetFrame,
|
|
996
|
+
WidgetFrameStyleProvider,
|
|
997
|
+
WidgetFreshnessProvider,
|
|
507
998
|
WidgetRegistryError,
|
|
508
999
|
WidgetSelectProvider,
|
|
509
1000
|
WidgetSubjectProvider,
|
|
510
1001
|
WidgetThemeRootProvider,
|
|
1002
|
+
WidgetTimeRangeProvider,
|
|
511
1003
|
WidgetTimeZoneProvider,
|
|
1004
|
+
WidgetTokenStyles,
|
|
1005
|
+
ageSeconds,
|
|
1006
|
+
bandAt,
|
|
512
1007
|
createWidgetRegistry,
|
|
513
1008
|
css,
|
|
514
1009
|
defineWidget,
|
|
515
1010
|
defineWidgetDescriptor,
|
|
1011
|
+
densityScale,
|
|
1012
|
+
fitValueFont,
|
|
1013
|
+
fitValueFontCss,
|
|
516
1014
|
flashBackgroundStyle,
|
|
517
1015
|
flashTextStyle,
|
|
1016
|
+
fontVar,
|
|
1017
|
+
formatAge,
|
|
518
1018
|
formatDateTime,
|
|
519
1019
|
formatTimestamp,
|
|
520
1020
|
formatValue,
|
|
1021
|
+
freshnessStrings,
|
|
1022
|
+
isAlarmLevel,
|
|
521
1023
|
isBuiltinWidgetId,
|
|
522
1024
|
isNamespacedWidgetId,
|
|
1025
|
+
isSeverityLevel,
|
|
1026
|
+
isStale,
|
|
1027
|
+
levelColor,
|
|
1028
|
+
newestSampleTime,
|
|
523
1029
|
optBoolean,
|
|
524
1030
|
optNumber,
|
|
525
1031
|
optString,
|
|
1032
|
+
optThresholds,
|
|
1033
|
+
parseThresholds,
|
|
526
1034
|
pickSample,
|
|
527
1035
|
primaryMeasurementName,
|
|
1036
|
+
resolveStaleAfterSeconds,
|
|
1037
|
+
severityTokenCss,
|
|
1038
|
+
severityVar,
|
|
1039
|
+
spaceVar,
|
|
1040
|
+
thresholdBands,
|
|
1041
|
+
tokenPx,
|
|
1042
|
+
tokenStyle,
|
|
528
1043
|
useAlarmStream,
|
|
529
1044
|
useCandidates,
|
|
530
1045
|
useCommandStream,
|
|
@@ -532,15 +1047,26 @@ export {
|
|
|
532
1047
|
useDatasourceAvailability,
|
|
533
1048
|
useElementSize,
|
|
534
1049
|
useFlashOnChange,
|
|
1050
|
+
useFreshness,
|
|
1051
|
+
useHubConnectionState,
|
|
535
1052
|
useLocationStream,
|
|
536
1053
|
useMeasurementStream,
|
|
1054
|
+
useNow,
|
|
537
1055
|
useResolvedBindings,
|
|
1056
|
+
useSeverityColors,
|
|
538
1057
|
useSlotCandidates,
|
|
1058
|
+
useStickyChars,
|
|
539
1059
|
useWidgetCandidates,
|
|
1060
|
+
useWidgetFrameStyle,
|
|
1061
|
+
useWidgetFreshness,
|
|
1062
|
+
useWidgetScale,
|
|
540
1063
|
useWidgetSelect,
|
|
541
1064
|
useWidgetSubject,
|
|
542
1065
|
useWidgetThemeRoot,
|
|
1066
|
+
useWidgetTimeRange,
|
|
543
1067
|
useWidgetTimeZone,
|
|
544
|
-
validateWidgetDescriptor
|
|
1068
|
+
validateWidgetDescriptor,
|
|
1069
|
+
viewerLocale,
|
|
1070
|
+
withinSpan
|
|
545
1071
|
};
|
|
546
1072
|
//# sourceMappingURL=index.js.map
|