@devicechain/widgets 0.21.1-rc.1 → 0.22.0-rc.1

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 (78) hide show
  1. package/README.md +28 -7
  2. package/dist/aggregate-feed.d.ts +14 -0
  3. package/dist/aggregate-fold.d.ts +11 -0
  4. package/dist/aggregate-reading.d.ts +19 -0
  5. package/dist/aggregate-strings.d.ts +16 -0
  6. package/dist/binding-policy.d.ts +3 -1
  7. package/dist/builtin-descriptors.d.ts +1210 -8
  8. package/dist/builtin-registry.d.ts +1197 -8
  9. package/dist/category-geometry.d.ts +19 -0
  10. package/dist/category-items.d.ts +10 -0
  11. package/dist/chunks/aggregate-feed-6ZYKOQOB.js +130 -0
  12. package/dist/chunks/aggregate-feed-6ZYKOQOB.js.map +7 -0
  13. package/dist/chunks/aggregate-widgets-WVXAW3UL.js +895 -0
  14. package/dist/chunks/aggregate-widgets-WVXAW3UL.js.map +7 -0
  15. package/dist/chunks/chunk-2D7JNKFE.js +65 -0
  16. package/dist/chunks/chunk-2D7JNKFE.js.map +7 -0
  17. package/dist/chunks/chunk-S2OMBJLI.js +200 -0
  18. package/dist/chunks/chunk-S2OMBJLI.js.map +7 -0
  19. package/dist/chunks/chunk-XQQQNTBX.js +76 -0
  20. package/dist/chunks/chunk-XQQQNTBX.js.map +7 -0
  21. package/dist/chunks/chunk-YPFB6DAL.js +157 -0
  22. package/dist/chunks/chunk-YPFB6DAL.js.map +7 -0
  23. package/dist/chunks/chunk-ZR74ORIT.js +35 -0
  24. package/dist/chunks/chunk-ZR74ORIT.js.map +7 -0
  25. package/dist/chunks/control-widgets-UDS4ET3T.js +775 -0
  26. package/dist/chunks/control-widgets-UDS4ET3T.js.map +7 -0
  27. package/dist/chunks/fleet-feed-FK4ZMKIK.js +252 -0
  28. package/dist/chunks/fleet-feed-FK4ZMKIK.js.map +7 -0
  29. package/dist/chunks/level-widgets-MJB7TOLU.js +833 -0
  30. package/dist/chunks/level-widgets-MJB7TOLU.js.map +7 -0
  31. package/dist/command-control-core.d.ts +68 -0
  32. package/dist/command-control-strings.d.ts +32 -0
  33. package/dist/command-control.d.ts +31 -0
  34. package/dist/dashboard-renderer.d.ts +15 -4
  35. package/dist/fleet-feed.d.ts +21 -0
  36. package/dist/fleet-model.d.ts +43 -0
  37. package/dist/fleet-seed.d.ts +7 -0
  38. package/dist/fleet-strings.d.ts +25 -0
  39. package/dist/heading.d.ts +3 -0
  40. package/dist/index.d.ts +3 -2
  41. package/dist/index.js +1774 -676
  42. package/dist/index.js.map +4 -4
  43. package/dist/level-reading.d.ts +29 -0
  44. package/dist/level-readout.d.ts +25 -0
  45. package/dist/level-strings.d.ts +13 -0
  46. package/dist/options.d.ts +1 -1
  47. package/dist/time-range-bar.d.ts +28 -0
  48. package/dist/use-held.d.ts +1 -0
  49. package/dist/value-scale.d.ts +30 -0
  50. package/dist/widgets/aggregate-lazy.d.ts +305 -0
  51. package/dist/widgets/aggregate-widgets.d.ts +4 -0
  52. package/dist/widgets/alarm-count.d.ts +1 -0
  53. package/dist/widgets/alarm-table.d.ts +4 -1
  54. package/dist/widgets/bar-chart.d.ts +5 -0
  55. package/dist/widgets/battery.d.ts +7 -0
  56. package/dist/widgets/command-slider.d.ts +2 -0
  57. package/dist/widgets/command-toggle.d.ts +4 -0
  58. package/dist/widgets/compass.d.ts +2 -0
  59. package/dist/widgets/control-lazy.d.ts +92 -0
  60. package/dist/widgets/control-widgets.d.ts +2 -0
  61. package/dist/widgets/fleet-table.d.ts +13 -0
  62. package/dist/widgets/gauge.d.ts +52 -0
  63. package/dist/widgets/label.d.ts +1 -1
  64. package/dist/widgets/latest-card.d.ts +44 -1
  65. package/dist/widgets/lazy-chunk.d.ts +8 -0
  66. package/dist/widgets/level-gauge.d.ts +2 -0
  67. package/dist/widgets/level-lazy.d.ts +647 -0
  68. package/dist/widgets/level-widgets.d.ts +7 -0
  69. package/dist/widgets/linear-gauge.d.ts +2 -0
  70. package/dist/widgets/pie-chart.d.ts +2 -0
  71. package/dist/widgets/progress-ring.d.ts +3 -0
  72. package/dist/widgets/severity.d.ts +2 -1
  73. package/dist/widgets/signal-bars.d.ts +3 -0
  74. package/dist/widgets/stat-card.d.ts +3 -0
  75. package/dist/widgets/status-light.d.ts +7 -0
  76. package/dist/widgets/table.d.ts +9 -1
  77. package/dist/widgets/time-series-chart.d.ts +62 -4
  78. package/package.json +8 -8
@@ -0,0 +1,895 @@
1
+ import {
2
+ DEFAULT_PAGE_SIZE,
3
+ cellFor,
4
+ clampPage,
5
+ columnNames,
6
+ deviceBars,
7
+ deviceLabel,
8
+ fleetStrings,
9
+ pageCount,
10
+ sortMembers
11
+ } from "./chunk-S2OMBJLI.js";
12
+ import {
13
+ LevelEmpty,
14
+ LevelReadout
15
+ } from "./chunk-XQQQNTBX.js";
16
+
17
+ // src/widgets/bar-chart.tsx
18
+ import { useMemo } from "react";
19
+ import {
20
+ css,
21
+ formatValue,
22
+ optNumber,
23
+ optString,
24
+ WidgetFrame
25
+ } from "@devicechain/widget-sdk";
26
+
27
+ // src/aggregate-strings.ts
28
+ import { viewerLocale } from "@devicechain/widget-sdk";
29
+ var AGGREGATE_STATS = ["avg", "min", "max", "sum", "count", "last"];
30
+ var AGGREGATE_STRINGS = {
31
+ en: {
32
+ stats: { avg: "Average", min: "Minimum", max: "Maximum", sum: "Sum", count: "Readings", last: "Latest" },
33
+ loading: "Loading",
34
+ unsupported: "This data source cannot compute aggregates",
35
+ failed: (m) => m ? `Aggregates unavailable: ${m}` : "Aggregates unavailable",
36
+ noReadings: (span) => `No readings in the last ${span}`,
37
+ noData: "No data",
38
+ unbound: "Choose a device for this widget",
39
+ noCategories: "Select the measurements to show",
40
+ pieNegative: "A pie needs values of zero or more",
41
+ pieNoShare: "Every value is zero, so there is no share to show"
42
+ },
43
+ es: {
44
+ stats: { avg: "Promedio", min: "M\xEDnimo", max: "M\xE1ximo", sum: "Suma", count: "Lecturas", last: "\xDAltimo" },
45
+ loading: "Cargando",
46
+ unsupported: "Esta fuente de datos no puede calcular agregados",
47
+ failed: (m) => m ? `Agregados no disponibles: ${m}` : "Agregados no disponibles",
48
+ noReadings: (span) => `Sin lecturas en los \xFAltimos ${span}`,
49
+ noData: "Sin datos",
50
+ unbound: "Elija un dispositivo para este widget",
51
+ noCategories: "Seleccione las mediciones que mostrar",
52
+ pieNegative: "Un gr\xE1fico circular necesita valores de cero o m\xE1s",
53
+ pieNoShare: "Todos los valores son cero, no hay proporci\xF3n que mostrar"
54
+ }
55
+ };
56
+ function aggregateStrings(locale = viewerLocale()) {
57
+ return locale?.toLowerCase().split("-")[0] === "es" ? AGGREGATE_STRINGS.es : AGGREGATE_STRINGS.en;
58
+ }
59
+
60
+ // src/category-items.ts
61
+ var CATEGORY_SORTS = ["none", "ascending", "descending"];
62
+ function parseSort(v) {
63
+ return CATEGORY_SORTS.find((s) => s === v) ?? "none";
64
+ }
65
+ function categoryItems(widget, data, sort) {
66
+ const named = widget.datasource?.measurements ?? [];
67
+ const names = named.length > 0 ? [...new Set(named)] : Object.keys(data.latest);
68
+ const items = names.map((label) => {
69
+ const v = data.latest[label]?.value;
70
+ return { label, value: typeof v === "number" && Number.isFinite(v) ? v : null };
71
+ });
72
+ if (sort === "none") return items;
73
+ const sign = sort === "ascending" ? 1 : -1;
74
+ return items.map((item, i) => ({ item, i })).sort((a, b) => {
75
+ if (a.item.value === null || b.item.value === null) {
76
+ return a.item.value === b.item.value ? a.i - b.i : a.item.value === null ? 1 : -1;
77
+ }
78
+ return sign * (a.item.value - b.item.value) || a.i - b.i;
79
+ }).map((x) => x.item);
80
+ }
81
+ function pieProblem(items) {
82
+ const values = items.map((i) => i.value).filter((v) => v !== null);
83
+ if (values.length === 0) return "noData";
84
+ if (values.some((v) => v < 0)) return "negative";
85
+ if (values.every((v) => v === 0)) return "noShare";
86
+ return void 0;
87
+ }
88
+
89
+ // src/category-geometry.ts
90
+ function niceDomain(values, intervals = 4) {
91
+ const finite = values.filter(Number.isFinite);
92
+ let lo = Math.min(0, ...finite);
93
+ let hi = Math.max(0, ...finite);
94
+ if (lo === hi) hi = lo + 1;
95
+ const raw = (hi - lo) / Math.max(1, intervals);
96
+ const magnitude = 10 ** Math.floor(Math.log10(raw));
97
+ const norm = raw / magnitude;
98
+ const step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * magnitude;
99
+ lo = Math.floor(lo / step + 1e-9) * step;
100
+ hi = Math.ceil(hi / step - 1e-9) * step;
101
+ const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
102
+ const round = (v) => Number(v.toFixed(decimals));
103
+ const ticks = [];
104
+ for (let i = 0; lo + i * step <= hi + step * 1e-9; i++) ticks.push(round(lo + i * step));
105
+ return { min: round(lo), max: round(hi), step, ticks };
106
+ }
107
+ function pieSlices(values) {
108
+ const positive = values.map((v) => v !== null && v > 0 ? v : 0);
109
+ const total = positive.reduce((a, v) => a + v, 0);
110
+ let at = 0;
111
+ return positive.map((v, index) => {
112
+ const fraction = total > 0 ? v / total : 0;
113
+ const slice = { index, fraction, from: at, to: at + fraction };
114
+ at += fraction;
115
+ return slice;
116
+ });
117
+ }
118
+ function polar(cx, cy, r, turns) {
119
+ const angle = turns * 2 * Math.PI;
120
+ return { x: cx + r * Math.sin(angle), y: cy - r * Math.cos(angle) };
121
+ }
122
+ function wedgePath(cx, cy, r, from, to) {
123
+ const span = to - from;
124
+ if (span <= 0) return void 0;
125
+ if (span >= 1 - 1e-9) {
126
+ return `M${cx},${cy - r}A${r},${r} 0 1 1 ${cx},${cy + r}A${r},${r} 0 1 1 ${cx},${cy - r}Z`;
127
+ }
128
+ const a = polar(cx, cy, r, from);
129
+ const b = polar(cx, cy, r, to);
130
+ return `M${cx},${cy}L${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}Z`;
131
+ }
132
+
133
+ // src/widgets/bar-chart.tsx
134
+ import { jsx, jsxs } from "react/jsx-runtime";
135
+ var CHAR_EM = 0.6;
136
+ var MAX_LABELLED_BARS = 12;
137
+ var MAX_BAR_PX = 56;
138
+ var MAX_DEVICE_BARS = 100;
139
+ function BarChart({ widget, data }) {
140
+ const sort = parseSort(optString(widget.options, "sort"));
141
+ const perDevice = optString(widget.options, "groupBy") === "device";
142
+ const measurement = optString(widget.options, "measurement") ?? widget.datasource?.measurements[0];
143
+ const items = useMemo(
144
+ () => perDevice ? [] : categoryItems(widget, data, sort),
145
+ [widget, data, sort, perDevice]
146
+ );
147
+ const unit = optString(widget.options, "unit");
148
+ const precision = optNumber(widget.options, "precision");
149
+ return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title"), children: (ws) => {
150
+ if (perDevice) {
151
+ return /* @__PURE__ */ jsx(DeviceBars, { data, measurement, sort, unit, precision, ws });
152
+ }
153
+ return items.length === 0 ? /* @__PURE__ */ jsx(LevelEmpty, { message: aggregateStrings().noCategories }) : /* @__PURE__ */ jsx(Bars, { items, unit, precision, ws });
154
+ } });
155
+ }
156
+ function DeviceBars({
157
+ data,
158
+ measurement,
159
+ sort,
160
+ unit,
161
+ precision,
162
+ ws
163
+ }) {
164
+ const strings = fleetStrings();
165
+ const fleet = data.members;
166
+ const bars = useMemo(
167
+ () => fleet && measurement ? deviceBars(fleet.members, measurement, sort) : [],
168
+ [fleet, measurement, sort]
169
+ );
170
+ if (!measurement) return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.noMeasurement });
171
+ if (!fleet || fleet.status === "loading") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.loading });
172
+ if (fleet.status === "unbound") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.unbound });
173
+ if (fleet.status === "forbidden") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.forbidden });
174
+ if (fleet.status === "unsupported") return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.unsupported });
175
+ if (bars.length === 0) return /* @__PURE__ */ jsx(LevelEmpty, { message: strings.noMembers });
176
+ const drawn = bars.slice(0, MAX_DEVICE_BARS);
177
+ const note = fleet.truncated || drawn.length < bars.length ? strings.truncated(drawn.length, Math.max(fleet.total, bars.length)) : void 0;
178
+ return /* @__PURE__ */ jsx(
179
+ Bars,
180
+ {
181
+ items: drawn.map((b) => ({ label: b.label, key: b.token, value: b.value })),
182
+ unit,
183
+ precision,
184
+ ws,
185
+ note
186
+ }
187
+ );
188
+ }
189
+ function fitLabel(label, chars) {
190
+ if (chars < 2) return "";
191
+ return label.length <= chars ? label : `${label.slice(0, Math.max(1, chars - 1))}\u2026`;
192
+ }
193
+ function Bars({
194
+ items,
195
+ unit,
196
+ precision,
197
+ ws,
198
+ note
199
+ }) {
200
+ const W = ws.size.width > 0 ? ws.size.width : 240;
201
+ const fullH = ws.size.height > 0 ? ws.size.height : 160;
202
+ const noteH = note ? ws.px("font-xs") * 1.8 : 0;
203
+ const H = Math.max(1, fullH - noteH);
204
+ const horizontal = H > W;
205
+ const font = ws.px("font-xs");
206
+ const pad = ws.px("space-1");
207
+ const charPx = font * CHAR_EM;
208
+ const fmt = (v) => formatValue(v, precision);
209
+ const withUnit = (v) => unit ? `${fmt(v)} ${unit}` : fmt(v);
210
+ const domain = niceDomain(items.flatMap((i) => i.value === null ? [] : [i.value]));
211
+ const span = domain.max - domain.min;
212
+ const few = items.length <= MAX_LABELLED_BARS;
213
+ const widest = (texts) => texts.reduce((n, t) => Math.max(n, t.length), 0) * charPx;
214
+ const tickText = domain.ticks.map(fmt);
215
+ const catLabelH = font * 1.7;
216
+ const topH = font * 1.7;
217
+ let x0, x1, y0, y1;
218
+ if (horizontal) {
219
+ x0 = Math.min(W * 0.4, widest(items.map((i) => i.label)) + pad * 2);
220
+ x1 = W - pad - (few ? widest(items.map((i) => i.value === null ? "" : withUnit(i.value))) + pad : pad);
221
+ y0 = pad;
222
+ y1 = H - catLabelH;
223
+ } else {
224
+ x0 = widest(tickText) + pad * 2;
225
+ x1 = W - pad;
226
+ y0 = topH;
227
+ y1 = H - catLabelH;
228
+ }
229
+ const length = horizontal ? Math.max(0, x1 - x0) : Math.max(0, y1 - y0);
230
+ const along = (v) => (v - domain.min) / span * length;
231
+ const bandPx = (horizontal ? y1 - y0 : x1 - x0) / items.length;
232
+ const labelled = few && (horizontal || widest(items.map((i) => i.value === null ? "" : fmt(i.value))) <= bandPx);
233
+ const thick = Math.min(bandPx * 0.62, MAX_BAR_PX * ws.scale);
234
+ const muted = { fill: css("muted-foreground") };
235
+ const text = { fontSize: font, fontVariantNumeric: "tabular-nums" };
236
+ const zero = along(0);
237
+ return /* @__PURE__ */ jsxs(
238
+ "svg",
239
+ {
240
+ "data-bar-chart": horizontal ? "horizontal" : "vertical",
241
+ width: W,
242
+ height: H,
243
+ viewBox: `0 0 ${W} ${H}`,
244
+ role: "img",
245
+ "aria-label": items.map((i) => `${i.label}: ${i.value === null ? "\u2014" : withUnit(i.value)}`).join(", "),
246
+ style: { display: "block" },
247
+ children: [
248
+ note ? /* @__PURE__ */ jsx("text", { "data-bar-note": "", x: pad, y: fullH - font * 0.4, style: { ...text, ...muted }, children: note }) : null,
249
+ unit ? /* @__PURE__ */ jsx(
250
+ "text",
251
+ {
252
+ x: horizontal ? W - pad : pad,
253
+ y: horizontal ? H - pad : font,
254
+ textAnchor: horizontal ? "end" : "start",
255
+ style: { ...text, ...muted },
256
+ children: unit
257
+ }
258
+ ) : null,
259
+ domain.ticks.map((t, i) => {
260
+ const d = along(t);
261
+ return horizontal ? /* @__PURE__ */ jsxs("g", { children: [
262
+ /* @__PURE__ */ jsx("line", { x1: x0 + d, x2: x0 + d, y1: y0, y2: y1, style: { stroke: css("border") } }),
263
+ /* @__PURE__ */ jsx("text", { x: x0 + d, y: y1 + font * 1.2, textAnchor: "middle", style: { ...text, ...muted }, children: tickText[i] })
264
+ ] }, t) : /* @__PURE__ */ jsxs("g", { children: [
265
+ /* @__PURE__ */ jsx("line", { x1: x0, x2: x1, y1: y1 - d, y2: y1 - d, style: { stroke: css("border") } }),
266
+ /* @__PURE__ */ jsx("text", { x: x0 - pad, y: y1 - d + font * 0.35, textAnchor: "end", style: { ...text, ...muted }, children: tickText[i] })
267
+ ] }, t);
268
+ }),
269
+ items.map((item, i) => {
270
+ const centre = (horizontal ? y0 : x0) + bandPx * (i + 0.5);
271
+ const label = fitLabel(item.label, Math.floor((horizontal ? x0 - pad : bandPx) / charPx));
272
+ const v = item.value;
273
+ const end = v === null ? zero : along(v);
274
+ const lo = Math.min(zero, end);
275
+ const hi = Math.max(zero, end);
276
+ return /* @__PURE__ */ jsxs("g", { "data-bar": item.label, "data-value": v ?? void 0, children: [
277
+ /* @__PURE__ */ jsx("title", { children: `${item.label}: ${v === null ? "\u2014" : withUnit(v)}` }),
278
+ v === null ? null : horizontal ? /* @__PURE__ */ jsx("rect", { x: x0 + lo, y: centre - thick / 2, width: Math.max(1, hi - lo), height: thick, style: { fill: css("chart-1") } }) : /* @__PURE__ */ jsx("rect", { x: centre - thick / 2, y: y1 - hi, width: thick, height: Math.max(1, hi - lo), style: { fill: css("chart-1") } }),
279
+ labelled && v !== null ? horizontal ? /* @__PURE__ */ jsx("text", { x: x0 + hi + pad, y: centre + font * 0.35, style: { ...text, fill: css("foreground") }, children: withUnit(v) }) : /* @__PURE__ */ jsx("text", { x: centre, y: y1 - hi - pad, textAnchor: "middle", style: { ...text, fill: css("foreground") }, children: fmt(v) }) : null,
280
+ /* @__PURE__ */ jsx(
281
+ "text",
282
+ {
283
+ x: horizontal ? x0 - pad : centre,
284
+ y: horizontal ? centre + font * 0.35 : y1 + font * 1.2,
285
+ textAnchor: horizontal ? "end" : "middle",
286
+ style: { ...text, ...muted },
287
+ children: label
288
+ }
289
+ )
290
+ ] }, item.key ?? item.label);
291
+ })
292
+ ]
293
+ }
294
+ );
295
+ }
296
+
297
+ // src/widgets/fleet-table.tsx
298
+ import { useEffect, useMemo as useMemo2, useState } from "react";
299
+ import {
300
+ ageSeconds,
301
+ bandAt,
302
+ css as css2,
303
+ fontVar,
304
+ formatAge,
305
+ formatValue as formatValue2,
306
+ isStale,
307
+ levelColor,
308
+ optNumber as optNumber2,
309
+ optBoolean,
310
+ optString as optString2,
311
+ parseThresholds,
312
+ resolveStaleAfterSeconds,
313
+ spaceVar,
314
+ useSeverityColors,
315
+ useWidgetScale,
316
+ useWidgetSelect,
317
+ useWidgetTimeRange,
318
+ WidgetFrame as WidgetFrame2
319
+ } from "@devicechain/widget-sdk";
320
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
321
+ var AGE_TICK_MS = 5e3;
322
+ function isRecord(v) {
323
+ return typeof v === "object" && v !== null && !Array.isArray(v);
324
+ }
325
+ function columnFormats(raw) {
326
+ const out = /* @__PURE__ */ new Map();
327
+ if (!Array.isArray(raw)) return out;
328
+ for (const item of raw) {
329
+ if (!isRecord(item) || typeof item.measurement !== "string" || item.measurement === "") continue;
330
+ const f = {};
331
+ if (typeof item.label === "string" && item.label !== "") f.label = item.label;
332
+ if (typeof item.unit === "string" && item.unit !== "") f.unit = item.unit;
333
+ if (typeof item.precision === "number" && Number.isInteger(item.precision) && item.precision >= 0 && item.precision <= 100) {
334
+ f.precision = item.precision;
335
+ }
336
+ out.set(item.measurement, f);
337
+ }
338
+ return out;
339
+ }
340
+ function columnBands(raw) {
341
+ const grouped = /* @__PURE__ */ new Map();
342
+ if (Array.isArray(raw)) {
343
+ for (const item of raw) {
344
+ if (!isRecord(item) || typeof item.measurement !== "string") continue;
345
+ const list = grouped.get(item.measurement) ?? [];
346
+ list.push(item);
347
+ grouped.set(item.measurement, list);
348
+ }
349
+ }
350
+ const out = /* @__PURE__ */ new Map();
351
+ for (const [name, items] of grouped) out.set(name, parseThresholds(items));
352
+ return out;
353
+ }
354
+ var cellBox = { padding: `${spaceVar(1)} ${spaceVar(3)}`, textAlign: "left", whiteSpace: "nowrap", verticalAlign: "top" };
355
+ var headBox = {
356
+ ...cellBox,
357
+ fontSize: fontVar("xs"),
358
+ fontWeight: 600,
359
+ letterSpacing: "0.04em",
360
+ color: css2("muted-foreground"),
361
+ borderBottom: `1px solid ${css2("border")}`,
362
+ background: css2("card"),
363
+ position: "sticky",
364
+ top: 0,
365
+ zIndex: 1
366
+ };
367
+ var stickyFirst = { position: "sticky", left: 0, background: css2("card") };
368
+ var plainButton = {
369
+ padding: 0,
370
+ border: "none",
371
+ background: "none",
372
+ font: "inherit",
373
+ color: "inherit",
374
+ cursor: "pointer",
375
+ textAlign: "left"
376
+ };
377
+ function FleetTable({ widget, data }) {
378
+ const strings = fleetStrings();
379
+ const fleet = data.members;
380
+ const title = optString2(widget.options, "title");
381
+ if (!fleet || fleet.status === "loading") {
382
+ return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.loading }) });
383
+ }
384
+ if (fleet.status === "unbound") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.unbound }) });
385
+ if (fleet.status === "forbidden") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.forbidden }) });
386
+ if (fleet.status === "unsupported") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.unsupported }) });
387
+ if (fleet.members.length === 0) return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.noMembers }) });
388
+ return /* @__PURE__ */ jsx2(Table, { widget, fleet, strings });
389
+ }
390
+ var STACK_BELOW_PX = 480;
391
+ function Responsive({ wide, narrow }) {
392
+ const { width } = useWidgetScale().size;
393
+ return width > 0 && width < STACK_BELOW_PX ? narrow : wide;
394
+ }
395
+ function Frame({ title, children }) {
396
+ return /* @__PURE__ */ jsx2(WidgetFrame2, { title, flush: true, children });
397
+ }
398
+ function Table({
399
+ widget,
400
+ fleet,
401
+ strings
402
+ }) {
403
+ const colors = useSeverityColors();
404
+ const range = useWidgetTimeRange();
405
+ const select = useWidgetSelect();
406
+ const selectionTarget = optString2(widget.options, "selectionTarget");
407
+ const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : void 0;
408
+ const columns = useMemo2(() => columnNames(widget.datasource?.measurements ?? [], fleet.members), [widget.datasource, fleet.members]);
409
+ const formats = useMemo2(() => columnFormats(widget.options?.columns), [widget.options?.columns]);
410
+ const bands = useMemo2(() => columnBands(widget.options?.thresholds), [widget.options?.thresholds]);
411
+ const staleAfter = resolveStaleAfterSeconds(widget.options);
412
+ const ageLag = positiveSeconds(optNumber2(widget.options, "ageLagSeconds"));
413
+ const newest = useMemo2(() => ageLag === void 0 ? void 0 : newestShown(fleet.members, columns), [ageLag, fleet.members, columns]);
414
+ const pageSize = optNumber2(widget.options, "pageSize") ?? DEFAULT_PAGE_SIZE;
415
+ const authored = { by: optString2(widget.options, "sortBy") || null, descending: optBoolean(widget.options, "sortDescending") === true };
416
+ const [picked, setPicked] = useState(void 0);
417
+ const sort = picked ?? authored;
418
+ const [page, setPage] = useState(0);
419
+ const [tick, setTick] = useState(() => Date.now());
420
+ useEffect(() => {
421
+ const id = setInterval(() => setTick(Date.now()), AGE_TICK_MS);
422
+ return () => clearInterval(id);
423
+ }, []);
424
+ const now = range?.paused ? range.pausedAt ?? tick : tick;
425
+ const sorted = useMemo2(() => sortMembers(fleet.members, sort), [fleet.members, sort.by, sort.descending]);
426
+ const shownPage = clampPage(page, sorted.length, pageSize);
427
+ const rows = sorted.slice(shownPage * pageSize, (shownPage + 1) * pageSize);
428
+ const pages = pageCount(sorted.length, pageSize);
429
+ const sortOn = (by) => {
430
+ setPicked({ by, descending: sort.by === by ? !sort.descending : false });
431
+ setPage(0);
432
+ };
433
+ const ariaSort = (by) => sort.by === by ? sort.descending ? "descending" : "ascending" : "none";
434
+ const arrow = (by) => sort.by === by ? sort.descending ? " \u25BC" : " \u25B2" : "";
435
+ const wide = /* @__PURE__ */ jsxs2("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: fontVar("md"), color: css2("foreground") }, children: [
436
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
437
+ /* @__PURE__ */ jsx2("th", { scope: "col", "aria-sort": ariaSort(null), style: { ...headBox, ...stickyFirst, zIndex: 2 }, children: /* @__PURE__ */ jsxs2("button", { type: "button", style: plainButton, onClick: () => sortOn(null), title: strings.sortBy(strings.device), children: [
438
+ strings.device,
439
+ arrow(null)
440
+ ] }) }),
441
+ columns.map((name) => {
442
+ const f = formats.get(name);
443
+ const heading = (f?.label ?? name) + (f?.unit ? ` (${f.unit})` : "");
444
+ return /* @__PURE__ */ jsx2("th", { scope: "col", "aria-sort": ariaSort(name), style: headBox, children: /* @__PURE__ */ jsxs2("button", { type: "button", style: plainButton, onClick: () => sortOn(name), title: strings.sortBy(heading), children: [
445
+ heading,
446
+ arrow(name)
447
+ ] }) }, name);
448
+ })
449
+ ] }) }),
450
+ /* @__PURE__ */ jsx2("tbody", { children: rows.map((member) => {
451
+ const label = deviceLabel(member);
452
+ return /* @__PURE__ */ jsxs2("tr", { "data-device": member.token, style: { borderBottom: `1px solid ${css2("border")}` }, children: [
453
+ /* @__PURE__ */ jsx2("th", { scope: "row", style: { ...cellBox, ...stickyFirst, fontWeight: 500 }, children: drillTo ? /* @__PURE__ */ jsx2(
454
+ "button",
455
+ {
456
+ type: "button",
457
+ style: { ...plainButton, color: css2("primary"), textDecoration: "underline", textUnderlineOffset: 2 },
458
+ title: strings.drill(label),
459
+ onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: member.token } }),
460
+ children: label
461
+ }
462
+ ) : label }),
463
+ columns.map((name) => /* @__PURE__ */ jsx2("td", { "data-measurement": name, style: cellBox, children: /* @__PURE__ */ jsx2(
464
+ CellView,
465
+ {
466
+ cell: cellFor(member, name),
467
+ format: formats.get(name),
468
+ bands: bands.get(name),
469
+ colors,
470
+ staleAfter,
471
+ ageLag,
472
+ newest,
473
+ now,
474
+ strings
475
+ }
476
+ ) }, name))
477
+ ] }, member.token);
478
+ }) })
479
+ ] });
480
+ const narrow = /* @__PURE__ */ jsxs2("div", { "data-fleet-stacked": "", style: { color: css2("foreground"), fontSize: fontVar("md") }, children: [
481
+ /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: spaceVar(2), padding: `${spaceVar(2)} ${spaceVar(3)}` }, children: [null, ...columns].map((by) => {
482
+ const f = by === null ? void 0 : formats.get(by);
483
+ const heading = by === null ? strings.device : f?.label ?? by;
484
+ return /* @__PURE__ */ jsxs2(
485
+ "button",
486
+ {
487
+ type: "button",
488
+ "aria-pressed": sort.by === by,
489
+ style: { ...plainButton, fontSize: fontVar("xs"), fontWeight: 600, color: css2("muted-foreground"), overflowWrap: "anywhere" },
490
+ onClick: () => sortOn(by),
491
+ title: strings.sortBy(heading),
492
+ children: [
493
+ heading,
494
+ arrow(by)
495
+ ]
496
+ },
497
+ by ?? ""
498
+ );
499
+ }) }),
500
+ rows.map((member) => {
501
+ const label = deviceLabel(member);
502
+ return /* @__PURE__ */ jsxs2("div", { "data-device": member.token, style: { padding: `${spaceVar(2)} ${spaceVar(3)}`, borderTop: `1px solid ${css2("border")}` }, children: [
503
+ /* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflowWrap: "anywhere" }, children: drillTo ? /* @__PURE__ */ jsx2(
504
+ "button",
505
+ {
506
+ type: "button",
507
+ style: { ...plainButton, color: css2("primary"), textDecoration: "underline", textUnderlineOffset: 2, overflowWrap: "anywhere" },
508
+ title: strings.drill(label),
509
+ onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: member.token } }),
510
+ children: label
511
+ }
512
+ ) : label }),
513
+ columns.map((name) => {
514
+ const f = formats.get(name);
515
+ return /* @__PURE__ */ jsxs2("div", { "data-measurement": name, style: { display: "flex", justifyContent: "space-between", gap: spaceVar(3), alignItems: "baseline" }, children: [
516
+ /* @__PURE__ */ jsx2("span", { style: { color: css2("muted-foreground"), fontSize: fontVar("sm"), overflowWrap: "anywhere" }, children: f?.label ?? name }),
517
+ /* @__PURE__ */ jsx2("span", { style: { textAlign: "right", overflowWrap: "anywhere" }, children: /* @__PURE__ */ jsx2(
518
+ CellView,
519
+ {
520
+ cell: cellFor(member, name),
521
+ format: f,
522
+ bands: bands.get(name),
523
+ colors,
524
+ staleAfter,
525
+ ageLag,
526
+ newest,
527
+ now,
528
+ strings,
529
+ wrap: true
530
+ }
531
+ ) })
532
+ ] }, name);
533
+ })
534
+ ] }, member.token);
535
+ })
536
+ ] });
537
+ return /* @__PURE__ */ jsx2(Frame, { title: optString2(widget.options, "title"), children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
538
+ /* @__PURE__ */ jsx2(
539
+ "div",
540
+ {
541
+ role: "region",
542
+ "aria-label": optString2(widget.options, "title") ?? strings.device,
543
+ tabIndex: 0,
544
+ "data-fleet-scroll": "",
545
+ style: { flex: 1, minHeight: 0, overflow: "auto" },
546
+ children: /* @__PURE__ */ jsx2(Responsive, { wide, narrow })
547
+ }
548
+ ),
549
+ (fleet.truncated || pages > 1) && /* @__PURE__ */ jsxs2(
550
+ "div",
551
+ {
552
+ "data-fleet-footer": "",
553
+ style: {
554
+ display: "flex",
555
+ flexWrap: "wrap",
556
+ alignItems: "center",
557
+ justifyContent: "space-between",
558
+ gap: spaceVar(2),
559
+ padding: `${spaceVar(1)} ${spaceVar(3)}`,
560
+ fontSize: fontVar("sm"),
561
+ color: css2("muted-foreground"),
562
+ borderTop: `1px solid ${css2("border")}`
563
+ },
564
+ children: [
565
+ /* @__PURE__ */ jsx2("span", { "data-fleet-truncated": fleet.truncated ? "" : void 0, children: fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : "" }),
566
+ pages > 1 && /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", alignItems: "center", gap: spaceVar(2) }, children: [
567
+ /* @__PURE__ */ jsx2("button", { type: "button", style: pagerButton, disabled: shownPage === 0, onClick: () => setPage(shownPage - 1), "aria-label": strings.previous, children: "\u2039" }),
568
+ /* @__PURE__ */ jsx2("span", { children: strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length) }),
569
+ /* @__PURE__ */ jsx2("button", { type: "button", style: pagerButton, disabled: shownPage >= pages - 1, onClick: () => setPage(shownPage + 1), "aria-label": strings.next, children: "\u203A" })
570
+ ] })
571
+ ]
572
+ }
573
+ )
574
+ ] }) });
575
+ }
576
+ function positiveSeconds(v) {
577
+ return v !== void 0 && Number.isFinite(v) && v > 0 ? v : void 0;
578
+ }
579
+ function newestShown(members, columns) {
580
+ let newest;
581
+ for (const m of members) {
582
+ for (const name of columns) {
583
+ const t = m.latest[name]?.occurredTime;
584
+ const at = t ? Date.parse(t) : Number.NaN;
585
+ if (!Number.isNaN(at) && (newest === void 0 || at > newest)) newest = at;
586
+ }
587
+ }
588
+ return newest;
589
+ }
590
+ var pagerButton = {
591
+ minWidth: "2em",
592
+ padding: `0 ${spaceVar(2)}`,
593
+ border: `1px solid ${css2("border")}`,
594
+ borderRadius: 4,
595
+ background: "none",
596
+ color: "inherit",
597
+ font: "inherit",
598
+ cursor: "pointer"
599
+ };
600
+ function CellView({
601
+ cell,
602
+ format,
603
+ bands,
604
+ colors,
605
+ staleAfter,
606
+ ageLag,
607
+ newest,
608
+ now,
609
+ strings,
610
+ wrap
611
+ }) {
612
+ const muted = { color: css2("muted-foreground"), fontSize: fontVar("sm"), ...wrap ? { overflowWrap: "anywhere" } : {} };
613
+ switch (cell.kind) {
614
+ case "loading":
615
+ return /* @__PURE__ */ jsx2("span", { style: muted, title: strings.loading, "aria-label": strings.loading, children: "\u2026" });
616
+ case "failed":
617
+ return /* @__PURE__ */ jsx2("span", { style: muted, children: strings.failed });
618
+ case "not-reported":
619
+ return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "not-reported", children: strings.notReported });
620
+ case "unread":
621
+ return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "unread", children: strings.unread });
622
+ case "no-data":
623
+ return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "no-data", children: strings.noData });
624
+ case "value": {
625
+ const band = bands ? bandAt(bands, cell.value) : void 0;
626
+ const color = levelColor(colors, band?.level);
627
+ const at = cell.sample.occurredTime ? Date.parse(cell.sample.occurredTime) : Number.NaN;
628
+ const age = Number.isNaN(at) ? void 0 : ageSeconds(at, now);
629
+ const stale = age !== void 0 && isStale(age, staleAfter);
630
+ const showAge = age !== void 0 && (ageLag === void 0 || stale || newest === void 0 || newest - at >= ageLag * 1e3);
631
+ return /* @__PURE__ */ jsxs2("span", { "data-stale": stale ? "" : void 0, style: { display: "inline-flex", flexDirection: "column", opacity: stale ? 0.6 : 1 }, children: [
632
+ /* @__PURE__ */ jsxs2("span", { "data-threshold-level": band?.level, style: { fontVariantNumeric: "tabular-nums", color }, children: [
633
+ formatValue2(cell.value, format?.precision),
634
+ format?.unit ? ` ${format.unit}` : ""
635
+ ] }),
636
+ age !== void 0 && showAge && /* @__PURE__ */ jsxs2("span", { style: muted, "data-cell-age": "", children: [
637
+ formatAge(age),
638
+ stale ? ` \xB7 ${strings.stale}` : ""
639
+ ] })
640
+ ] });
641
+ }
642
+ }
643
+ }
644
+
645
+ // src/widgets/pie-chart.tsx
646
+ import { useMemo as useMemo3 } from "react";
647
+ import { css as css3, formatValue as formatValue3, optNumber as optNumber3, optString as optString3, fontVar as fontVar2, spaceVar as spaceVar2, WidgetFrame as WidgetFrame3 } from "@devicechain/widget-sdk";
648
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
649
+ var RAMP = ["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"];
650
+ function PieChart({ widget, data }) {
651
+ const sort = parseSort(optString3(widget.options, "sort"));
652
+ const items = useMemo3(() => categoryItems(widget, data, sort), [widget, data, sort]);
653
+ const unit = optString3(widget.options, "unit");
654
+ const precision = optNumber3(widget.options, "precision");
655
+ const strings = aggregateStrings();
656
+ const problem = items.length === 0 ? void 0 : pieProblem(items);
657
+ return /* @__PURE__ */ jsx3(WidgetFrame3, { title: optString3(widget.options, "title"), children: items.length === 0 ? /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.noCategories }) : problem ? /* @__PURE__ */ jsx3(LevelEmpty, { message: problem === "negative" ? strings.pieNegative : problem === "noShare" ? strings.pieNoShare : strings.noData }) : /* @__PURE__ */ jsx3(Pie, { items, unit, precision }) });
658
+ }
659
+ function Pie({
660
+ items,
661
+ unit,
662
+ precision
663
+ }) {
664
+ const slices = pieSlices(items.map((i) => i.value));
665
+ const fmt = (v) => `${formatValue3(v, precision)}${unit ? ` ${unit}` : ""}`;
666
+ const percent = (fraction) => `${Math.round(fraction * 100)}%`;
667
+ return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: spaceVar2(1) }, children: [
668
+ /* @__PURE__ */ jsx3(
669
+ "svg",
670
+ {
671
+ "data-pie": "",
672
+ viewBox: "0 0 100 100",
673
+ preserveAspectRatio: "xMidYMid meet",
674
+ role: "img",
675
+ "aria-label": items.map((i, n) => `${i.label}: ${i.value === null ? "\u2014" : fmt(i.value)} (${percent(slices[n].fraction)})`).join(", "),
676
+ style: { flex: "1 1 0", minHeight: 0, width: "100%", display: "block" },
677
+ children: slices.map((s) => {
678
+ const d = wedgePath(50, 50, 48, s.from, s.to);
679
+ const item = items[s.index];
680
+ return d ? /* @__PURE__ */ jsx3(
681
+ "path",
682
+ {
683
+ "data-slice": item.label,
684
+ "data-fraction": s.fraction,
685
+ d,
686
+ style: { fill: css3(RAMP[s.index % RAMP.length]), stroke: css3("card"), strokeWidth: 1 },
687
+ children: /* @__PURE__ */ jsx3("title", { children: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)} (${percent(s.fraction)})` })
688
+ },
689
+ item.label
690
+ ) : null;
691
+ })
692
+ }
693
+ ),
694
+ /* @__PURE__ */ jsx3(
695
+ "ul",
696
+ {
697
+ "data-pie-legend": "",
698
+ style: {
699
+ flex: "0 1 auto",
700
+ maxHeight: "45%",
701
+ overflow: "hidden",
702
+ margin: 0,
703
+ padding: 0,
704
+ listStyle: "none",
705
+ display: "flex",
706
+ flexWrap: "wrap",
707
+ justifyContent: "center",
708
+ gap: `0 ${spaceVar2(3)}`,
709
+ fontSize: fontVar2("xs"),
710
+ color: css3("muted-foreground")
711
+ },
712
+ children: items.map((item, i) => /* @__PURE__ */ jsxs3("li", { title: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)}`, style: { whiteSpace: "nowrap" }, children: [
713
+ /* @__PURE__ */ jsx3(
714
+ "span",
715
+ {
716
+ "aria-hidden": "true",
717
+ style: {
718
+ display: "inline-block",
719
+ width: "0.7em",
720
+ height: "0.7em",
721
+ marginRight: "0.4em",
722
+ borderRadius: 2,
723
+ background: css3(RAMP[i % RAMP.length])
724
+ }
725
+ }
726
+ ),
727
+ item.label,
728
+ " ",
729
+ item.value === null ? "\u2014" : `${fmt(item.value)} \xB7 ${percent(slices[i].fraction)}`
730
+ ] }, item.label))
731
+ }
732
+ )
733
+ ] });
734
+ }
735
+
736
+ // src/widgets/stat-card.tsx
737
+ import { useMemo as useMemo4 } from "react";
738
+ import { spanLabel } from "@devicechain/dashboards";
739
+ import {
740
+ bandAt as bandAt2,
741
+ css as css4,
742
+ FIT_MIN_FONT_PX,
743
+ fontVar as fontVar3,
744
+ formatValue as formatValue4,
745
+ levelColor as levelColor2,
746
+ optNumber as optNumber4,
747
+ optString as optString4,
748
+ optThresholds,
749
+ spaceVar as spaceVar3,
750
+ useSeverityColors as useSeverityColors2,
751
+ WidgetFrame as WidgetFrame4,
752
+ WidgetFreshnessProvider
753
+ } from "@devicechain/widget-sdk";
754
+
755
+ // src/aggregate-reading.ts
756
+ import { pickSample, primaryMeasurementName } from "@devicechain/widget-sdk";
757
+ function parseStat(v) {
758
+ return AGGREGATE_STATS.find((s) => s === v) ?? "avg";
759
+ }
760
+ function statReading(widget, data, stat) {
761
+ const { aggregates } = data;
762
+ const pinned = primaryMeasurementName(widget);
763
+ if (widget.datasource?.kind === "unbound") return { kind: "empty", reason: "unbound", name: pinned };
764
+ if (stat === "last") {
765
+ const sample = pickSample(data.latest, pinned);
766
+ const name2 = pinned ?? sample?.name;
767
+ if (sample?.value == null) return { kind: "empty", reason: "noData", name: name2 };
768
+ if (aggregates.status === "ready" && sample.occurredTime && Date.parse(sample.occurredTime) < Date.parse(aggregates.startTime)) {
769
+ return { kind: "empty", reason: "noReadings", name: name2, spanSeconds: aggregates.spanSeconds };
770
+ }
771
+ return { kind: "value", stat, name: name2, value: sample.value, spanSeconds: void 0, time: sample.occurredTime };
772
+ }
773
+ if (aggregates.status === "loading") return { kind: "empty", reason: "loading", name: pinned };
774
+ if (aggregates.status === "unavailable") {
775
+ return { kind: "empty", reason: aggregates.reason, name: pinned, message: aggregates.message };
776
+ }
777
+ const name = pinned ?? Object.keys(aggregates.byName)[0];
778
+ const a = name === void 0 ? void 0 : aggregates.byName[name];
779
+ if (!a) {
780
+ if (stat === "count" && pinned !== void 0 && widget.datasource) {
781
+ return { kind: "value", stat, name: pinned, value: 0, spanSeconds: aggregates.spanSeconds };
782
+ }
783
+ return { kind: "empty", reason: "noReadings", name, spanSeconds: aggregates.spanSeconds };
784
+ }
785
+ return { kind: "value", stat, name, value: a[stat], spanSeconds: aggregates.spanSeconds };
786
+ }
787
+
788
+ // src/widgets/stat-card.tsx
789
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
790
+ var CAPTION_ROOM_PX = 22;
791
+ var BESIDE_FIGURE_SHARE = 0.6;
792
+ var AGE_ONLY = { at: void 0, staleAfterSeconds: void 0 };
793
+ function StatCard({ widget, data }) {
794
+ const severity = useSeverityColors2();
795
+ const strings = aggregateStrings();
796
+ const stat = parseStat(optString4(widget.options, "stat"));
797
+ const reading = statReading(widget, data, stat);
798
+ const thresholds = useMemo4(() => optThresholds(widget.options, "thresholds"), [widget.options]);
799
+ const unit = optString4(widget.options, "unit");
800
+ const precision = optNumber4(widget.options, "precision");
801
+ const title = optString4(widget.options, "title") ?? reading.name;
802
+ const card = /* @__PURE__ */ jsx4(WidgetFrame4, { title, sampleTime: stat === "last" ? reading.kind === "value" ? reading.time ?? null : null : void 0, children: ({ size, scale }) => {
803
+ if (reading.kind === "empty") {
804
+ return /* @__PURE__ */ jsx4(LevelEmpty, { message: emptyMessage(reading, strings) });
805
+ }
806
+ const isCount = reading.stat === "count";
807
+ const band = bandAt2(thresholds, reading.value);
808
+ const room = Math.round(CAPTION_ROOM_PX * scale);
809
+ const beside = size.height > 0 && size.height - room < 2 * Math.round(FIT_MIN_FONT_PX * scale);
810
+ const figure = beside ? { width: Math.round(size.width * BESIDE_FIGURE_SHARE), height: size.height } : { width: size.width, height: Math.max(0, size.height - room) };
811
+ return /* @__PURE__ */ jsxs4(
812
+ "div",
813
+ {
814
+ "data-stat": reading.stat,
815
+ "data-stat-span": reading.spanSeconds,
816
+ "data-stat-layout": beside ? "beside" : "under",
817
+ "data-threshold-level": band?.level,
818
+ title: band?.label,
819
+ style: {
820
+ display: "flex",
821
+ flexDirection: beside ? "row" : "column",
822
+ alignItems: beside ? "center" : void 0,
823
+ justifyContent: "safe center",
824
+ gap: beside ? spaceVar3(2) : void 0,
825
+ height: "100%",
826
+ overflow: "hidden"
827
+ },
828
+ children: [
829
+ /* @__PURE__ */ jsx4(
830
+ "div",
831
+ {
832
+ style: beside ? { flex: "0 1 auto", minWidth: 0, display: "flex", alignItems: "center" } : { flex: "1 1 0", minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" },
833
+ children: /* @__PURE__ */ jsx4(
834
+ LevelReadout,
835
+ {
836
+ text: formatValue4(reading.value, isCount ? 0 : precision),
837
+ unit: isCount ? void 0 : unit,
838
+ color: levelColor2(severity, band?.level),
839
+ size: figure,
840
+ scale
841
+ }
842
+ )
843
+ }
844
+ ),
845
+ /* @__PURE__ */ jsx4(
846
+ "div",
847
+ {
848
+ "data-stat-caption": "",
849
+ style: {
850
+ flex: beside ? "0 1 auto" : "0 0 auto",
851
+ minWidth: 0,
852
+ marginTop: beside ? void 0 : spaceVar3(1),
853
+ textAlign: "center",
854
+ fontSize: fontVar3("sm"),
855
+ color: css4("muted-foreground"),
856
+ whiteSpace: "nowrap",
857
+ overflow: "hidden",
858
+ textOverflow: "ellipsis"
859
+ },
860
+ children: caption(reading, strings.stats)
861
+ }
862
+ )
863
+ ]
864
+ }
865
+ );
866
+ } });
867
+ return stat === "last" ? /* @__PURE__ */ jsx4(WidgetFreshnessProvider, { freshness: AGE_ONLY, children: card }) : card;
868
+ }
869
+ function caption(r, stats) {
870
+ const name = stats[r.stat];
871
+ return r.spanSeconds === void 0 ? name : `${name} \xB7 ${spanLabel(r.spanSeconds)}`;
872
+ }
873
+ function emptyMessage(r, strings) {
874
+ switch (r.reason) {
875
+ case "loading":
876
+ return strings.loading;
877
+ case "unsupported":
878
+ return strings.unsupported;
879
+ case "failed":
880
+ return strings.failed(r.message);
881
+ case "noReadings":
882
+ return strings.noReadings(r.spanSeconds === void 0 ? "" : spanLabel(r.spanSeconds));
883
+ case "noData":
884
+ return strings.noData;
885
+ case "unbound":
886
+ return strings.unbound;
887
+ }
888
+ }
889
+ export {
890
+ BarChart,
891
+ FleetTable,
892
+ PieChart,
893
+ StatCard
894
+ };
895
+ //# sourceMappingURL=aggregate-widgets-WVXAW3UL.js.map