@devicechain/widgets 0.23.0 → 0.24.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -3
  2. package/dist/aggregate-fold.d.ts +3 -2
  3. package/dist/aggregate-strings.d.ts +1 -0
  4. package/dist/alarm-strings.d.ts +18 -0
  5. package/dist/binding-policy.d.ts +4 -0
  6. package/dist/bound-devices-notice.d.ts +30 -0
  7. package/dist/builtin-descriptors.d.ts +290 -249
  8. package/dist/builtin-option-docs.d.ts +2 -0
  9. package/dist/builtin-registry.d.ts +249 -226
  10. package/dist/category-geometry.d.ts +1 -0
  11. package/dist/chunks/{aggregate-feed-BD3CHNFU.js → aggregate-feed-4FSBQYJL.js} +39 -11
  12. package/dist/chunks/aggregate-feed-4FSBQYJL.js.map +7 -0
  13. package/dist/chunks/{aggregate-widgets-HZQI5HTX.js → aggregate-widgets-2ZQBW3YF.js} +376 -182
  14. package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +7 -0
  15. package/dist/chunks/alarm-widgets-X4PX5MYJ.js +72 -0
  16. package/dist/chunks/alarm-widgets-X4PX5MYJ.js.map +7 -0
  17. package/dist/chunks/chunk-546TK2QG.js +722 -0
  18. package/dist/chunks/chunk-546TK2QG.js.map +7 -0
  19. package/dist/chunks/{chunk-WBKHYZU2.js → chunk-62CHYCHI.js} +6 -62
  20. package/dist/chunks/chunk-62CHYCHI.js.map +7 -0
  21. package/dist/chunks/chunk-BMFQOTYG.js +41 -0
  22. package/dist/chunks/chunk-BMFQOTYG.js.map +7 -0
  23. package/dist/chunks/{chunk-2D7JNKFE.js → chunk-JFH4QMLA.js} +28 -4
  24. package/dist/chunks/chunk-JFH4QMLA.js.map +7 -0
  25. package/dist/chunks/chunk-QMGKB6J3.js +84 -0
  26. package/dist/chunks/chunk-QMGKB6J3.js.map +7 -0
  27. package/dist/chunks/chunk-XNEBAY4J.js +19 -0
  28. package/dist/chunks/chunk-XNEBAY4J.js.map +7 -0
  29. package/dist/chunks/chunk-YYJV54BV.js +58 -0
  30. package/dist/chunks/chunk-YYJV54BV.js.map +7 -0
  31. package/dist/chunks/compare-feed-AY3CVKUZ.js +346 -0
  32. package/dist/chunks/compare-feed-AY3CVKUZ.js.map +7 -0
  33. package/dist/chunks/{control-widgets-3V2LSSRF.js → control-widgets-UCTJJJ2D.js} +267 -78
  34. package/dist/chunks/control-widgets-UCTJJJ2D.js.map +7 -0
  35. package/dist/chunks/event-markers-feed-GEGTIHXT.js +373 -0
  36. package/dist/chunks/event-markers-feed-GEGTIHXT.js.map +7 -0
  37. package/dist/chunks/{fleet-feed-5QGDLOV5.js → fleet-feed-2SKDR6ZQ.js} +47 -8
  38. package/dist/chunks/fleet-feed-2SKDR6ZQ.js.map +7 -0
  39. package/dist/chunks/presence-widgets-2LIUKN4Y.js +350 -0
  40. package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +7 -0
  41. package/dist/command-control-strings.d.ts +5 -0
  42. package/dist/compare-feed.d.ts +13 -0
  43. package/dist/compare-model.d.ts +38 -0
  44. package/dist/compare-strings.d.ts +13 -0
  45. package/dist/documented-descriptors.d.ts +4826 -0
  46. package/dist/event-markers-feed.d.ts +11 -0
  47. package/dist/event-markers-model.d.ts +38 -0
  48. package/dist/event-markers-strings.d.ts +20 -0
  49. package/dist/event-markers-types.d.ts +7 -0
  50. package/dist/fleet-count-model.d.ts +13 -0
  51. package/dist/fleet-feed.d.ts +1 -0
  52. package/dist/fleet-model.d.ts +1 -0
  53. package/dist/fleet-strings.d.ts +11 -0
  54. package/dist/index.d.ts +6 -3
  55. package/dist/index.js +946 -398
  56. package/dist/index.js.map +4 -4
  57. package/dist/presence-feed.d.ts +24 -0
  58. package/dist/presence-model.d.ts +13 -0
  59. package/dist/presence-strings.d.ts +19 -0
  60. package/dist/sparkline.d.ts +23 -0
  61. package/dist/use-bound-devices.d.ts +26 -0
  62. package/dist/widgets/aggregate-lazy.d.ts +81 -46
  63. package/dist/widgets/aggregate-widgets.d.ts +1 -0
  64. package/dist/widgets/alarm-grouped.d.ts +17 -0
  65. package/dist/widgets/alarm-lazy.d.ts +63 -16
  66. package/dist/widgets/alarm-severity.d.ts +8 -0
  67. package/dist/widgets/alarm-table.d.ts +2 -0
  68. package/dist/widgets/alarm-type-tabs.d.ts +10 -0
  69. package/dist/widgets/alarm-widgets.d.ts +2 -0
  70. package/dist/widgets/command-stepper.d.ts +22 -0
  71. package/dist/widgets/control-lazy.d.ts +16 -24
  72. package/dist/widgets/device-status.d.ts +4 -0
  73. package/dist/widgets/fleet-count.d.ts +2 -0
  74. package/dist/widgets/gauge.d.ts +2 -15
  75. package/dist/widgets/image.d.ts +2 -6
  76. package/dist/widgets/label.d.ts +2 -6
  77. package/dist/widgets/latest-card.d.ts +2 -13
  78. package/dist/widgets/lazy-chunk.d.ts +7 -2
  79. package/dist/widgets/level-lazy.d.ts +14 -96
  80. package/dist/widgets/map.d.ts +8 -5
  81. package/dist/widgets/online-doughnut.d.ts +2 -0
  82. package/dist/widgets/presence-lazy.d.ts +27 -0
  83. package/dist/widgets/presence-widgets.d.ts +3 -0
  84. package/dist/widgets/selection-lazy.d.ts +2 -4
  85. package/dist/widgets/table-lazy.d.ts +2 -7
  86. package/dist/widgets/time-series-chart.d.ts +62 -12
  87. package/package.json +13 -13
  88. package/dist/chunks/aggregate-feed-BD3CHNFU.js.map +0 -7
  89. package/dist/chunks/aggregate-widgets-HZQI5HTX.js.map +0 -7
  90. package/dist/chunks/alarm-widgets-7UY6MIYZ.js +0 -9
  91. package/dist/chunks/alarm-widgets-7UY6MIYZ.js.map +0 -7
  92. package/dist/chunks/chunk-2D7JNKFE.js.map +0 -7
  93. package/dist/chunks/chunk-6OGI4UFJ.js +0 -367
  94. package/dist/chunks/chunk-6OGI4UFJ.js.map +0 -7
  95. package/dist/chunks/chunk-WBKHYZU2.js.map +0 -7
  96. package/dist/chunks/control-widgets-3V2LSSRF.js.map +0 -7
  97. package/dist/chunks/fleet-feed-5QGDLOV5.js.map +0 -7
@@ -5,10 +5,17 @@ import {
5
5
  columnNames,
6
6
  deviceBars,
7
7
  deviceLabel,
8
- fleetStrings,
9
8
  pageCount,
10
9
  sortMembers
11
- } from "./chunk-WBKHYZU2.js";
10
+ } from "./chunk-62CHYCHI.js";
11
+ import {
12
+ niceDomain,
13
+ pieSlices,
14
+ wedgePath
15
+ } from "./chunk-YYJV54BV.js";
16
+ import {
17
+ fleetStrings
18
+ } from "./chunk-QMGKB6J3.js";
12
19
  import "./chunk-BRH66T5F.js";
13
20
  import {
14
21
  LevelEmpty,
@@ -37,6 +44,7 @@ var AGGREGATE_STRINGS = {
37
44
  noReadings: (span) => `No readings in the last ${span}`,
38
45
  noData: "No data",
39
46
  unbound: "Choose a device for this widget",
47
+ trend: (stat, span) => `${stat} trend over the last ${span}`,
40
48
  noCategories: "Select the measurements to show",
41
49
  pieNegative: "A pie needs values of zero or more",
42
50
  pieNoShare: "Every value is zero, so there is no share to show"
@@ -49,6 +57,7 @@ var AGGREGATE_STRINGS = {
49
57
  noReadings: (span) => `Sin lecturas en los \xFAltimos ${span}`,
50
58
  noData: "Sin datos",
51
59
  unbound: "Elija un dispositivo para este widget",
60
+ trend: (stat, span) => `Tendencia de ${stat.toLowerCase()} en los \xFAltimos ${span}`,
52
61
  noCategories: "Seleccione las mediciones que mostrar",
53
62
  pieNegative: "Un gr\xE1fico circular necesita valores de cero o m\xE1s",
54
63
  pieNoShare: "Todos los valores son cero, no hay proporci\xF3n que mostrar"
@@ -87,50 +96,6 @@ function pieProblem(items) {
87
96
  return void 0;
88
97
  }
89
98
 
90
- // src/category-geometry.ts
91
- function niceDomain(values, intervals = 4) {
92
- const finite = values.filter(Number.isFinite);
93
- let lo = Math.min(0, ...finite);
94
- let hi = Math.max(0, ...finite);
95
- if (lo === hi) hi = lo + 1;
96
- const raw = (hi - lo) / Math.max(1, intervals);
97
- const magnitude = 10 ** Math.floor(Math.log10(raw));
98
- const norm = raw / magnitude;
99
- const step = (norm <= 1 ? 1 : norm <= 2 ? 2 : norm <= 2.5 ? 2.5 : norm <= 5 ? 5 : 10) * magnitude;
100
- lo = Math.floor(lo / step + 1e-9) * step;
101
- hi = Math.ceil(hi / step - 1e-9) * step;
102
- const decimals = Math.max(0, -Math.floor(Math.log10(step)) + 1);
103
- const round = (v) => Number(v.toFixed(decimals));
104
- const ticks = [];
105
- for (let i = 0; lo + i * step <= hi + step * 1e-9; i++) ticks.push(round(lo + i * step));
106
- return { min: round(lo), max: round(hi), step, ticks };
107
- }
108
- function pieSlices(values) {
109
- const positive = values.map((v) => v !== null && v > 0 ? v : 0);
110
- const total = positive.reduce((a, v) => a + v, 0);
111
- let at = 0;
112
- return positive.map((v, index) => {
113
- const fraction = total > 0 ? v / total : 0;
114
- const slice = { index, fraction, from: at, to: at + fraction };
115
- at += fraction;
116
- return slice;
117
- });
118
- }
119
- function polar(cx, cy, r, turns) {
120
- const angle = turns * 2 * Math.PI;
121
- return { x: cx + r * Math.sin(angle), y: cy - r * Math.cos(angle) };
122
- }
123
- function wedgePath(cx, cy, r, from, to) {
124
- const span = to - from;
125
- if (span <= 0) return void 0;
126
- if (span >= 1 - 1e-9) {
127
- return `M${cx},${cy - r}A${r},${r} 0 1 1 ${cx},${cy + r}A${r},${r} 0 1 1 ${cx},${cy - r}Z`;
128
- }
129
- const a = polar(cx, cy, r, from);
130
- const b = polar(cx, cy, r, to);
131
- return `M${cx},${cy}L${a.x},${a.y}A${r},${r} 0 ${span > 0.5 ? 1 : 0} 1 ${b.x},${b.y}Z`;
132
- }
133
-
134
99
  // src/widgets/bar-chart.tsx
135
100
  import { jsx, jsxs } from "react/jsx-runtime";
136
101
  var CHAR_EM = 0.6;
@@ -295,30 +260,156 @@ function Bars({
295
260
  );
296
261
  }
297
262
 
298
- // src/widgets/fleet-table.tsx
299
- import { useEffect, useMemo as useMemo2, useState } from "react";
263
+ // src/widgets/fleet-count.tsx
264
+ import { useMemo as useMemo2 } from "react";
300
265
  import {
301
- ageSeconds,
302
- bandAt,
303
266
  css as css2,
267
+ FIT_MIN_FONT_PX,
304
268
  fontVar,
269
+ levelColor,
270
+ optString as optString2,
271
+ optThresholds,
272
+ primaryMeasurementName,
273
+ spaceVar,
274
+ useSeverityColors,
275
+ WidgetFrame as WidgetFrame2
276
+ } from "@devicechain/widget-sdk";
277
+
278
+ // src/fleet-count-model.ts
279
+ import { bandAt, SEVERITY_LEVELS, isAlarmLevel } from "@devicechain/widget-sdk";
280
+ function attentionCount(members, measurement, thresholds) {
281
+ let flagged = 0;
282
+ let reporting = 0;
283
+ let pending = 0;
284
+ let unread = 0;
285
+ let notMeasured = 0;
286
+ let worst;
287
+ let band;
288
+ let mixed = false;
289
+ for (const m of members) {
290
+ const cell = cellFor(m, measurement);
291
+ if (cell.kind === "loading") pending++;
292
+ else if (cell.kind === "failed" || cell.kind === "unread") unread++;
293
+ else if (cell.kind === "not-reported") notMeasured++;
294
+ if (cell.kind !== "value") continue;
295
+ reporting++;
296
+ const hit = bandAt(thresholds, cell.value);
297
+ if (!hit || !isAlarmLevel(hit.level)) continue;
298
+ flagged++;
299
+ if (worst === void 0 || SEVERITY_LEVELS.indexOf(hit.level) > SEVERITY_LEVELS.indexOf(worst)) worst = hit.level;
300
+ if (band === void 0) band = hit;
301
+ else if (band !== hit) mixed = true;
302
+ }
303
+ return { members: members.length - notMeasured, notMeasured, flagged, reporting, pending, unread, worst, label: !mixed ? band?.label : void 0 };
304
+ }
305
+
306
+ // src/widgets/fleet-count.tsx
307
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
308
+ var CAPTION_ROOM_PX = 22;
309
+ function FleetCount({ widget, data }) {
310
+ const strings = fleetStrings();
311
+ const severity = useSeverityColors();
312
+ const fleet = data.members;
313
+ const showDevices = optString2(widget.options, "show") === "devices";
314
+ const measurement = optString2(widget.options, "measurement") ?? primaryMeasurementName(widget);
315
+ const thresholds = useMemo2(() => optThresholds(widget.options, "thresholds"), [widget.options]);
316
+ const title = optString2(widget.options, "title");
317
+ const empty = (message) => /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message }) });
318
+ if (!fleet || fleet.status === "loading") return empty(strings.loading);
319
+ if (fleet.status === "unbound") return empty(strings.unbound);
320
+ if (fleet.status === "forbidden") return empty(strings.forbidden);
321
+ if (fleet.status === "unsupported") return empty(strings.unsupported);
322
+ if (!showDevices) {
323
+ if (measurement === void 0) return empty(strings.noMeasurementCount);
324
+ if (thresholds.length === 0) return empty(strings.noBands);
325
+ }
326
+ const counted = showDevices ? void 0 : attentionCount(fleet.members, measurement ?? "", thresholds);
327
+ if (!showDevices && fleet.members.length === 0) return empty(strings.noMembers);
328
+ if (counted && counted.pending > 0) return empty(strings.loading);
329
+ if (counted && counted.members === 0) {
330
+ return empty(fleet.truncated ? strings.noneMeasuredShown(fleet.members.length, fleet.total) : strings.noneMeasured);
331
+ }
332
+ const figure = showDevices ? String(fleet.total) : String(counted.flagged);
333
+ const unit = showDevices ? void 0 : strings.countOf(counted.members);
334
+ const color = counted ? levelColor(severity, counted.worst) : void 0;
335
+ const caption2 = showDevices ? strings.devices : counted.flagged > 0 ? counted.label ?? strings.needAttention : strings.needAttention;
336
+ const notes = [];
337
+ if (counted) {
338
+ const silent = counted.members - counted.reporting - counted.unread;
339
+ if (silent > 0) notes.push(strings.notReporting(silent));
340
+ if (counted.unread > 0) notes.push(strings.notRead(counted.unread));
341
+ if (counted.notMeasured > 0) notes.push(strings.notMeasured(counted.notMeasured));
342
+ if (fleet.truncated) notes.push(strings.truncated(fleet.members.length, fleet.total));
343
+ }
344
+ return /* @__PURE__ */ jsx2(WidgetFrame2, { title, children: ({ size, scale }) => {
345
+ const room = Math.round(CAPTION_ROOM_PX * scale) * (1 + notes.length);
346
+ const fit = { width: size.width, height: Math.max(Math.round(FIT_MIN_FONT_PX * scale), size.height - room) };
347
+ return /* @__PURE__ */ jsxs2(
348
+ "div",
349
+ {
350
+ "data-fleet-count": showDevices ? "devices" : "attention",
351
+ "data-flagged": counted?.flagged,
352
+ "data-of": counted?.members,
353
+ "data-threshold-level": counted?.worst,
354
+ style: { display: "flex", flexDirection: "column", justifyContent: "safe center", height: "100%", overflow: "hidden" },
355
+ children: [
356
+ /* @__PURE__ */ jsx2("div", { style: { flex: "1 1 0", minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx2(LevelReadout, { text: figure, unit, color, size: fit, scale }) }),
357
+ /* @__PURE__ */ jsx2(
358
+ "div",
359
+ {
360
+ "data-fleet-count-caption": "",
361
+ style: {
362
+ flex: "0 0 auto",
363
+ marginTop: spaceVar(1),
364
+ textAlign: "center",
365
+ fontSize: fontVar("sm"),
366
+ color: css2("muted-foreground"),
367
+ overflow: "hidden",
368
+ textOverflow: "ellipsis",
369
+ whiteSpace: "nowrap"
370
+ },
371
+ children: caption2
372
+ }
373
+ ),
374
+ notes.map((note) => /* @__PURE__ */ jsx2(
375
+ "div",
376
+ {
377
+ "data-fleet-count-note": "",
378
+ style: { textAlign: "center", fontSize: fontVar("xs"), color: css2("muted-foreground"), overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
379
+ children: note
380
+ },
381
+ note
382
+ ))
383
+ ]
384
+ }
385
+ );
386
+ } });
387
+ }
388
+
389
+ // src/widgets/fleet-table.tsx
390
+ import { useEffect, useMemo as useMemo3, useState } from "react";
391
+ import {
392
+ ageSeconds,
393
+ bandAt as bandAt2,
394
+ css as css3,
395
+ fontVar as fontVar2,
305
396
  formatAge,
306
397
  formatValue as formatValue2,
307
398
  isStale,
308
- levelColor,
399
+ levelColor as levelColor2,
309
400
  optNumber as optNumber2,
310
401
  optBoolean,
311
- optString as optString2,
402
+ optString as optString3,
312
403
  parseThresholds,
313
404
  resolveStaleAfterSeconds,
314
- spaceVar,
315
- useSeverityColors,
405
+ spaceVar as spaceVar2,
406
+ useSeverityColors as useSeverityColors2,
316
407
  useWidgetScale,
317
408
  useWidgetSelect,
318
409
  useWidgetTimeRange,
319
- WidgetFrame as WidgetFrame2
410
+ WidgetFrame as WidgetFrame3
320
411
  } from "@devicechain/widget-sdk";
321
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
412
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
322
413
  var AGE_TICK_MS = 5e3;
323
414
  function isRecord(v) {
324
415
  return typeof v === "object" && v !== null && !Array.isArray(v);
@@ -352,20 +443,20 @@ function columnBands(raw) {
352
443
  for (const [name, items] of grouped) out.set(name, parseThresholds(items));
353
444
  return out;
354
445
  }
355
- var cellBox = { padding: `${spaceVar(1)} ${spaceVar(3)}`, textAlign: "left", whiteSpace: "nowrap", verticalAlign: "top" };
446
+ var cellBox = { padding: `${spaceVar2(1)} ${spaceVar2(3)}`, textAlign: "left", whiteSpace: "nowrap", verticalAlign: "top" };
356
447
  var headBox = {
357
448
  ...cellBox,
358
- fontSize: fontVar("xs"),
449
+ fontSize: fontVar2("xs"),
359
450
  fontWeight: 600,
360
451
  letterSpacing: "0.04em",
361
- color: css2("muted-foreground"),
362
- borderBottom: `1px solid ${css2("border")}`,
363
- background: css2("card"),
452
+ color: css3("muted-foreground"),
453
+ borderBottom: `1px solid ${css3("border")}`,
454
+ background: css3("card"),
364
455
  position: "sticky",
365
456
  top: 0,
366
457
  zIndex: 1
367
458
  };
368
- var stickyFirst = { position: "sticky", left: 0, background: css2("card") };
459
+ var stickyFirst = { position: "sticky", left: 0, background: css3("card") };
369
460
  var plainButton = {
370
461
  padding: 0,
371
462
  border: "none",
@@ -378,15 +469,15 @@ var plainButton = {
378
469
  function FleetTable({ widget, data }) {
379
470
  const strings = fleetStrings();
380
471
  const fleet = data.members;
381
- const title = optString2(widget.options, "title");
472
+ const title = optString3(widget.options, "title");
382
473
  if (!fleet || fleet.status === "loading") {
383
- return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.loading }) });
474
+ return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.loading }) });
384
475
  }
385
- if (fleet.status === "unbound") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.unbound }) });
386
- if (fleet.status === "forbidden") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.forbidden }) });
387
- if (fleet.status === "unsupported") return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.unsupported }) });
388
- if (fleet.members.length === 0) return /* @__PURE__ */ jsx2(Frame, { title, children: /* @__PURE__ */ jsx2(LevelEmpty, { message: strings.noMembers }) });
389
- return /* @__PURE__ */ jsx2(Table, { widget, fleet, strings });
476
+ if (fleet.status === "unbound") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.unbound }) });
477
+ if (fleet.status === "forbidden") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.forbidden }) });
478
+ if (fleet.status === "unsupported") return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.unsupported }) });
479
+ if (fleet.members.length === 0) return /* @__PURE__ */ jsx3(Frame, { title, children: /* @__PURE__ */ jsx3(LevelEmpty, { message: strings.noMembers }) });
480
+ return /* @__PURE__ */ jsx3(Table, { widget, fleet, strings });
390
481
  }
391
482
  var STACK_BELOW_PX = 480;
392
483
  function Responsive({ wide, narrow }) {
@@ -394,26 +485,26 @@ function Responsive({ wide, narrow }) {
394
485
  return width > 0 && width < STACK_BELOW_PX ? narrow : wide;
395
486
  }
396
487
  function Frame({ title, children }) {
397
- return /* @__PURE__ */ jsx2(WidgetFrame2, { title, flush: true, children });
488
+ return /* @__PURE__ */ jsx3(WidgetFrame3, { title, flush: true, children });
398
489
  }
399
490
  function Table({
400
491
  widget,
401
492
  fleet,
402
493
  strings
403
494
  }) {
404
- const colors = useSeverityColors();
495
+ const colors = useSeverityColors2();
405
496
  const range = useWidgetTimeRange();
406
497
  const select = useWidgetSelect();
407
- const selectionTarget = optString2(widget.options, "selectionTarget");
498
+ const selectionTarget = optString3(widget.options, "selectionTarget");
408
499
  const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : void 0;
409
- const columns = useMemo2(() => columnNames(widget.datasource?.measurements ?? [], fleet.members), [widget.datasource, fleet.members]);
410
- const formats = useMemo2(() => columnFormats(widget.options?.columns), [widget.options?.columns]);
411
- const bands = useMemo2(() => columnBands(widget.options?.thresholds), [widget.options?.thresholds]);
500
+ const columns = useMemo3(() => columnNames(widget.datasource?.measurements ?? [], fleet.members), [widget.datasource, fleet.members]);
501
+ const formats = useMemo3(() => columnFormats(widget.options?.columns), [widget.options?.columns]);
502
+ const bands = useMemo3(() => columnBands(widget.options?.thresholds), [widget.options?.thresholds]);
412
503
  const staleAfter = resolveStaleAfterSeconds(widget.options);
413
504
  const ageLag = positiveSeconds(optNumber2(widget.options, "ageLagSeconds"));
414
- const newest = useMemo2(() => ageLag === void 0 ? void 0 : newestShown(fleet.members, columns), [ageLag, fleet.members, columns]);
505
+ const newest = useMemo3(() => ageLag === void 0 ? void 0 : newestShown(fleet.members, columns), [ageLag, fleet.members, columns]);
415
506
  const pageSize = optNumber2(widget.options, "pageSize") ?? DEFAULT_PAGE_SIZE;
416
- const authored = { by: optString2(widget.options, "sortBy") || null, descending: optBoolean(widget.options, "sortDescending") === true };
507
+ const authored = { by: optString3(widget.options, "sortBy") || null, descending: optBoolean(widget.options, "sortDescending") === true };
417
508
  const [picked, setPicked] = useState(void 0);
418
509
  const sort = picked ?? authored;
419
510
  const [page, setPage] = useState(0);
@@ -423,7 +514,7 @@ function Table({
423
514
  return () => clearInterval(id);
424
515
  }, []);
425
516
  const now = range?.paused ? range.pausedAt ?? tick : tick;
426
- const sorted = useMemo2(() => sortMembers(fleet.members, sort), [fleet.members, sort.by, sort.descending]);
517
+ const sorted = useMemo3(() => sortMembers(fleet.members, sort), [fleet.members, sort.by, sort.descending]);
427
518
  const shownPage = clampPage(page, sorted.length, pageSize);
428
519
  const rows = sorted.slice(shownPage * pageSize, (shownPage + 1) * pageSize);
429
520
  const pages = pageCount(sorted.length, pageSize);
@@ -433,35 +524,35 @@ function Table({
433
524
  };
434
525
  const ariaSort = (by) => sort.by === by ? sort.descending ? "descending" : "ascending" : "none";
435
526
  const arrow = (by) => sort.by === by ? sort.descending ? " \u25BC" : " \u25B2" : "";
436
- const wide = /* @__PURE__ */ jsxs2("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: fontVar("md"), color: css2("foreground") }, children: [
437
- /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
438
- /* @__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: [
527
+ const wide = /* @__PURE__ */ jsxs3("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: fontVar2("md"), color: css3("foreground") }, children: [
528
+ /* @__PURE__ */ jsx3("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
529
+ /* @__PURE__ */ jsx3("th", { scope: "col", "aria-sort": ariaSort(null), style: { ...headBox, ...stickyFirst, zIndex: 2 }, children: /* @__PURE__ */ jsxs3("button", { type: "button", style: plainButton, onClick: () => sortOn(null), title: strings.sortBy(strings.device), children: [
439
530
  strings.device,
440
531
  arrow(null)
441
532
  ] }) }),
442
533
  columns.map((name) => {
443
534
  const f = formats.get(name);
444
535
  const heading = (f?.label ?? name) + (f?.unit ? ` (${f.unit})` : "");
445
- 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: [
536
+ return /* @__PURE__ */ jsx3("th", { scope: "col", "aria-sort": ariaSort(name), style: headBox, children: /* @__PURE__ */ jsxs3("button", { type: "button", style: plainButton, onClick: () => sortOn(name), title: strings.sortBy(heading), children: [
446
537
  heading,
447
538
  arrow(name)
448
539
  ] }) }, name);
449
540
  })
450
541
  ] }) }),
451
- /* @__PURE__ */ jsx2("tbody", { children: rows.map((member) => {
542
+ /* @__PURE__ */ jsx3("tbody", { children: rows.map((member) => {
452
543
  const label = deviceLabel(member);
453
- return /* @__PURE__ */ jsxs2("tr", { "data-device": member.token, style: { borderBottom: `1px solid ${css2("border")}` }, children: [
454
- /* @__PURE__ */ jsx2("th", { scope: "row", style: { ...cellBox, ...stickyFirst, fontWeight: 500 }, children: drillTo ? /* @__PURE__ */ jsx2(
544
+ return /* @__PURE__ */ jsxs3("tr", { "data-device": member.token, style: { borderBottom: `1px solid ${css3("border")}` }, children: [
545
+ /* @__PURE__ */ jsx3("th", { scope: "row", style: { ...cellBox, ...stickyFirst, fontWeight: 500 }, children: drillTo ? /* @__PURE__ */ jsx3(
455
546
  "button",
456
547
  {
457
548
  type: "button",
458
- style: { ...plainButton, color: css2("primary"), textDecoration: "underline", textUnderlineOffset: 2 },
549
+ style: { ...plainButton, color: css3("primary"), textDecoration: "underline", textUnderlineOffset: 2 },
459
550
  title: strings.drill(label),
460
551
  onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: member.token } }),
461
552
  children: label
462
553
  }
463
554
  ) : label }),
464
- columns.map((name) => /* @__PURE__ */ jsx2("td", { "data-measurement": name, style: cellBox, children: /* @__PURE__ */ jsx2(
555
+ columns.map((name) => /* @__PURE__ */ jsx3("td", { "data-measurement": name, style: cellBox, children: /* @__PURE__ */ jsx3(
465
556
  CellView,
466
557
  {
467
558
  cell: cellFor(member, name),
@@ -478,16 +569,16 @@ function Table({
478
569
  ] }, member.token);
479
570
  }) })
480
571
  ] });
481
- const narrow = /* @__PURE__ */ jsxs2("div", { "data-fleet-stacked": "", style: { color: css2("foreground"), fontSize: fontVar("md") }, children: [
482
- /* @__PURE__ */ jsx2("div", { style: { display: "flex", flexWrap: "wrap", gap: spaceVar(2), padding: `${spaceVar(2)} ${spaceVar(3)}` }, children: [null, ...columns].map((by) => {
572
+ const narrow = /* @__PURE__ */ jsxs3("div", { "data-fleet-stacked": "", style: { color: css3("foreground"), fontSize: fontVar2("md") }, children: [
573
+ /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexWrap: "wrap", gap: spaceVar2(2), padding: `${spaceVar2(2)} ${spaceVar2(3)}` }, children: [null, ...columns].map((by) => {
483
574
  const f = by === null ? void 0 : formats.get(by);
484
575
  const heading = by === null ? strings.device : f?.label ?? by;
485
- return /* @__PURE__ */ jsxs2(
576
+ return /* @__PURE__ */ jsxs3(
486
577
  "button",
487
578
  {
488
579
  type: "button",
489
580
  "aria-pressed": sort.by === by,
490
- style: { ...plainButton, fontSize: fontVar("xs"), fontWeight: 600, color: css2("muted-foreground"), overflowWrap: "anywhere" },
581
+ style: { ...plainButton, fontSize: fontVar2("xs"), fontWeight: 600, color: css3("muted-foreground"), overflowWrap: "anywhere" },
491
582
  onClick: () => sortOn(by),
492
583
  title: strings.sortBy(heading),
493
584
  children: [
@@ -500,12 +591,12 @@ function Table({
500
591
  }) }),
501
592
  rows.map((member) => {
502
593
  const label = deviceLabel(member);
503
- return /* @__PURE__ */ jsxs2("div", { "data-device": member.token, style: { padding: `${spaceVar(2)} ${spaceVar(3)}`, borderTop: `1px solid ${css2("border")}` }, children: [
504
- /* @__PURE__ */ jsx2("div", { style: { fontWeight: 500, overflowWrap: "anywhere" }, children: drillTo ? /* @__PURE__ */ jsx2(
594
+ return /* @__PURE__ */ jsxs3("div", { "data-device": member.token, style: { padding: `${spaceVar2(2)} ${spaceVar2(3)}`, borderTop: `1px solid ${css3("border")}` }, children: [
595
+ /* @__PURE__ */ jsx3("div", { style: { fontWeight: 500, overflowWrap: "anywhere" }, children: drillTo ? /* @__PURE__ */ jsx3(
505
596
  "button",
506
597
  {
507
598
  type: "button",
508
- style: { ...plainButton, color: css2("primary"), textDecoration: "underline", textUnderlineOffset: 2, overflowWrap: "anywhere" },
599
+ style: { ...plainButton, color: css3("primary"), textDecoration: "underline", textUnderlineOffset: 2, overflowWrap: "anywhere" },
509
600
  title: strings.drill(label),
510
601
  onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: member.token } }),
511
602
  children: label
@@ -513,9 +604,9 @@ function Table({
513
604
  ) : label }),
514
605
  columns.map((name) => {
515
606
  const f = formats.get(name);
516
- return /* @__PURE__ */ jsxs2("div", { "data-measurement": name, style: { display: "flex", justifyContent: "space-between", gap: spaceVar(3), alignItems: "baseline" }, children: [
517
- /* @__PURE__ */ jsx2("span", { style: { color: css2("muted-foreground"), fontSize: fontVar("sm"), overflowWrap: "anywhere" }, children: f?.label ?? name }),
518
- /* @__PURE__ */ jsx2("span", { style: { textAlign: "right", overflowWrap: "anywhere" }, children: /* @__PURE__ */ jsx2(
607
+ return /* @__PURE__ */ jsxs3("div", { "data-measurement": name, style: { display: "flex", justifyContent: "space-between", gap: spaceVar2(3), alignItems: "baseline" }, children: [
608
+ /* @__PURE__ */ jsx3("span", { style: { color: css3("muted-foreground"), fontSize: fontVar2("sm"), overflowWrap: "anywhere" }, children: f?.label ?? name }),
609
+ /* @__PURE__ */ jsx3("span", { style: { textAlign: "right", overflowWrap: "anywhere" }, children: /* @__PURE__ */ jsx3(
519
610
  CellView,
520
611
  {
521
612
  cell: cellFor(member, name),
@@ -535,19 +626,19 @@ function Table({
535
626
  ] }, member.token);
536
627
  })
537
628
  ] });
538
- return /* @__PURE__ */ jsx2(Frame, { title: optString2(widget.options, "title"), children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
539
- /* @__PURE__ */ jsx2(
629
+ return /* @__PURE__ */ jsx3(Frame, { title: optString3(widget.options, "title"), children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", minHeight: 0 }, children: [
630
+ /* @__PURE__ */ jsx3(
540
631
  "div",
541
632
  {
542
633
  role: "region",
543
- "aria-label": optString2(widget.options, "title") ?? strings.device,
634
+ "aria-label": optString3(widget.options, "title") ?? strings.device,
544
635
  tabIndex: 0,
545
636
  "data-fleet-scroll": "",
546
637
  style: { flex: 1, minHeight: 0, overflow: "auto" },
547
- children: /* @__PURE__ */ jsx2(Responsive, { wide, narrow })
638
+ children: /* @__PURE__ */ jsx3(Responsive, { wide, narrow })
548
639
  }
549
640
  ),
550
- (fleet.truncated || pages > 1) && /* @__PURE__ */ jsxs2(
641
+ (fleet.truncated || pages > 1) && /* @__PURE__ */ jsxs3(
551
642
  "div",
552
643
  {
553
644
  "data-fleet-footer": "",
@@ -556,18 +647,18 @@ function Table({
556
647
  flexWrap: "wrap",
557
648
  alignItems: "center",
558
649
  justifyContent: "space-between",
559
- gap: spaceVar(2),
560
- padding: `${spaceVar(1)} ${spaceVar(3)}`,
561
- fontSize: fontVar("sm"),
562
- color: css2("muted-foreground"),
563
- borderTop: `1px solid ${css2("border")}`
650
+ gap: spaceVar2(2),
651
+ padding: `${spaceVar2(1)} ${spaceVar2(3)}`,
652
+ fontSize: fontVar2("sm"),
653
+ color: css3("muted-foreground"),
654
+ borderTop: `1px solid ${css3("border")}`
564
655
  },
565
656
  children: [
566
- /* @__PURE__ */ jsx2("span", { "data-fleet-truncated": fleet.truncated ? "" : void 0, children: fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : "" }),
567
- pages > 1 && /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", alignItems: "center", gap: spaceVar(2) }, children: [
568
- /* @__PURE__ */ jsx2("button", { type: "button", style: pagerButton, disabled: shownPage === 0, onClick: () => setPage(shownPage - 1), "aria-label": strings.previous, children: "\u2039" }),
569
- /* @__PURE__ */ jsx2("span", { children: strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length) }),
570
- /* @__PURE__ */ jsx2("button", { type: "button", style: pagerButton, disabled: shownPage >= pages - 1, onClick: () => setPage(shownPage + 1), "aria-label": strings.next, children: "\u203A" })
657
+ /* @__PURE__ */ jsx3("span", { "data-fleet-truncated": fleet.truncated ? "" : void 0, children: fleet.truncated ? strings.truncated(fleet.members.length, fleet.total) : "" }),
658
+ pages > 1 && /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: spaceVar2(2) }, children: [
659
+ /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage === 0, onClick: () => setPage(shownPage - 1), "aria-label": strings.previous, children: "\u2039" }),
660
+ /* @__PURE__ */ jsx3("span", { children: strings.range(shownPage * pageSize + 1, shownPage * pageSize + rows.length, sorted.length) }),
661
+ /* @__PURE__ */ jsx3("button", { type: "button", style: pagerButton, disabled: shownPage >= pages - 1, onClick: () => setPage(shownPage + 1), "aria-label": strings.next, children: "\u203A" })
571
662
  ] })
572
663
  ]
573
664
  }
@@ -590,8 +681,8 @@ function newestShown(members, columns) {
590
681
  }
591
682
  var pagerButton = {
592
683
  minWidth: "2em",
593
- padding: `0 ${spaceVar(2)}`,
594
- border: `1px solid ${css2("border")}`,
684
+ padding: `0 ${spaceVar2(2)}`,
685
+ border: `1px solid ${css3("border")}`,
595
686
  borderRadius: 4,
596
687
  background: "none",
597
688
  color: "inherit",
@@ -610,31 +701,31 @@ function CellView({
610
701
  strings,
611
702
  wrap
612
703
  }) {
613
- const muted = { color: css2("muted-foreground"), fontSize: fontVar("sm"), ...wrap ? { overflowWrap: "anywhere" } : {} };
704
+ const muted = { color: css3("muted-foreground"), fontSize: fontVar2("sm"), ...wrap ? { overflowWrap: "anywhere" } : {} };
614
705
  switch (cell.kind) {
615
706
  case "loading":
616
- return /* @__PURE__ */ jsx2("span", { style: muted, title: strings.loading, "aria-label": strings.loading, children: "\u2026" });
707
+ return /* @__PURE__ */ jsx3("span", { style: muted, title: strings.loading, "aria-label": strings.loading, children: "\u2026" });
617
708
  case "failed":
618
- return /* @__PURE__ */ jsx2("span", { style: muted, children: strings.failed });
709
+ return /* @__PURE__ */ jsx3("span", { style: muted, children: strings.failed });
619
710
  case "not-reported":
620
- return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "not-reported", children: strings.notReported });
711
+ return /* @__PURE__ */ jsx3("span", { style: muted, "data-cell-empty": "not-reported", children: strings.notReported });
621
712
  case "unread":
622
- return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "unread", children: strings.unread });
713
+ return /* @__PURE__ */ jsx3("span", { style: muted, "data-cell-empty": "unread", children: strings.unread });
623
714
  case "no-data":
624
- return /* @__PURE__ */ jsx2("span", { style: muted, "data-cell-empty": "no-data", children: strings.noData });
715
+ return /* @__PURE__ */ jsx3("span", { style: muted, "data-cell-empty": "no-data", children: strings.noData });
625
716
  case "value": {
626
- const band = bands ? bandAt(bands, cell.value) : void 0;
627
- const color = levelColor(colors, band?.level);
717
+ const band = bands ? bandAt2(bands, cell.value) : void 0;
718
+ const color = levelColor2(colors, band?.level);
628
719
  const at = cell.sample.occurredTime ? Date.parse(cell.sample.occurredTime) : Number.NaN;
629
720
  const age = Number.isNaN(at) ? void 0 : ageSeconds(at, now);
630
721
  const stale = age !== void 0 && isStale(age, staleAfter);
631
722
  const showAge = age !== void 0 && (ageLag === void 0 || stale || newest === void 0 || newest - at >= ageLag * 1e3);
632
- return /* @__PURE__ */ jsxs2("span", { "data-stale": stale ? "" : void 0, style: { display: "inline-flex", flexDirection: "column", opacity: stale ? 0.6 : 1 }, children: [
633
- /* @__PURE__ */ jsxs2("span", { "data-threshold-level": band?.level, style: { fontVariantNumeric: "tabular-nums", color }, children: [
723
+ return /* @__PURE__ */ jsxs3("span", { "data-stale": stale ? "" : void 0, style: { display: "inline-flex", flexDirection: "column", opacity: stale ? 0.6 : 1 }, children: [
724
+ /* @__PURE__ */ jsxs3("span", { "data-threshold-level": band?.level, style: { fontVariantNumeric: "tabular-nums", color }, children: [
634
725
  formatValue2(cell.value, format?.precision),
635
726
  format?.unit ? ` ${format.unit}` : ""
636
727
  ] }),
637
- age !== void 0 && showAge && /* @__PURE__ */ jsxs2("span", { style: muted, "data-cell-age": "", children: [
728
+ age !== void 0 && showAge && /* @__PURE__ */ jsxs3("span", { style: muted, "data-cell-age": "", children: [
638
729
  formatAge(age),
639
730
  stale ? ` \xB7 ${strings.stale}` : ""
640
731
  ] })
@@ -644,18 +735,18 @@ function CellView({
644
735
  }
645
736
 
646
737
  // src/widgets/pie-chart.tsx
647
- import { useMemo as useMemo3 } from "react";
648
- 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";
649
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
738
+ import { useMemo as useMemo4 } from "react";
739
+ import { css as css4, formatValue as formatValue3, optNumber as optNumber3, optString as optString4, fontVar as fontVar3, spaceVar as spaceVar3, WidgetFrame as WidgetFrame4 } from "@devicechain/widget-sdk";
740
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
650
741
  var RAMP = ["chart-1", "chart-2", "chart-3", "chart-4", "chart-5"];
651
742
  function PieChart({ widget, data }) {
652
- const sort = parseSort(optString3(widget.options, "sort"));
653
- const items = useMemo3(() => categoryItems(widget, data, sort), [widget, data, sort]);
654
- const unit = optString3(widget.options, "unit");
743
+ const sort = parseSort(optString4(widget.options, "sort"));
744
+ const items = useMemo4(() => categoryItems(widget, data, sort), [widget, data, sort]);
745
+ const unit = optString4(widget.options, "unit");
655
746
  const precision = optNumber3(widget.options, "precision");
656
747
  const strings = aggregateStrings();
657
748
  const problem = items.length === 0 ? void 0 : pieProblem(items);
658
- 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 }) });
749
+ return /* @__PURE__ */ jsx4(WidgetFrame4, { title: optString4(widget.options, "title"), children: items.length === 0 ? /* @__PURE__ */ jsx4(LevelEmpty, { message: strings.noCategories }) : problem ? /* @__PURE__ */ jsx4(LevelEmpty, { message: problem === "negative" ? strings.pieNegative : problem === "noShare" ? strings.pieNoShare : strings.noData }) : /* @__PURE__ */ jsx4(Pie, { items, unit, precision }) });
659
750
  }
660
751
  function Pie({
661
752
  items,
@@ -665,8 +756,8 @@ function Pie({
665
756
  const slices = pieSlices(items.map((i) => i.value));
666
757
  const fmt = (v) => `${formatValue3(v, precision)}${unit ? ` ${unit}` : ""}`;
667
758
  const percent = (fraction) => `${Math.round(fraction * 100)}%`;
668
- return /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: spaceVar2(1) }, children: [
669
- /* @__PURE__ */ jsx3(
759
+ return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: spaceVar3(1) }, children: [
760
+ /* @__PURE__ */ jsx4(
670
761
  "svg",
671
762
  {
672
763
  "data-pie": "",
@@ -678,21 +769,21 @@ function Pie({
678
769
  children: slices.map((s) => {
679
770
  const d = wedgePath(50, 50, 48, s.from, s.to);
680
771
  const item = items[s.index];
681
- return d ? /* @__PURE__ */ jsx3(
772
+ return d ? /* @__PURE__ */ jsx4(
682
773
  "path",
683
774
  {
684
775
  "data-slice": item.label,
685
776
  "data-fraction": s.fraction,
686
777
  d,
687
- style: { fill: css3(RAMP[s.index % RAMP.length]), stroke: css3("card"), strokeWidth: 1 },
688
- children: /* @__PURE__ */ jsx3("title", { children: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)} (${percent(s.fraction)})` })
778
+ style: { fill: css4(RAMP[s.index % RAMP.length]), stroke: css4("card"), strokeWidth: 1 },
779
+ children: /* @__PURE__ */ jsx4("title", { children: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)} (${percent(s.fraction)})` })
689
780
  },
690
781
  item.label
691
782
  ) : null;
692
783
  })
693
784
  }
694
785
  ),
695
- /* @__PURE__ */ jsx3(
786
+ /* @__PURE__ */ jsx4(
696
787
  "ul",
697
788
  {
698
789
  "data-pie-legend": "",
@@ -706,12 +797,12 @@ function Pie({
706
797
  display: "flex",
707
798
  flexWrap: "wrap",
708
799
  justifyContent: "center",
709
- gap: `0 ${spaceVar2(3)}`,
710
- fontSize: fontVar2("xs"),
711
- color: css3("muted-foreground")
800
+ gap: `0 ${spaceVar3(3)}`,
801
+ fontSize: fontVar3("xs"),
802
+ color: css4("muted-foreground")
712
803
  },
713
- children: items.map((item, i) => /* @__PURE__ */ jsxs3("li", { title: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)}`, style: { whiteSpace: "nowrap" }, children: [
714
- /* @__PURE__ */ jsx3(
804
+ children: items.map((item, i) => /* @__PURE__ */ jsxs4("li", { title: `${item.label}: ${item.value === null ? "\u2014" : fmt(item.value)}`, style: { whiteSpace: "nowrap" }, children: [
805
+ /* @__PURE__ */ jsx4(
715
806
  "span",
716
807
  {
717
808
  "aria-hidden": "true",
@@ -721,7 +812,7 @@ function Pie({
721
812
  height: "0.7em",
722
813
  marginRight: "0.4em",
723
814
  borderRadius: 2,
724
- background: css3(RAMP[i % RAMP.length])
815
+ background: css4(RAMP[i % RAMP.length])
725
816
  }
726
817
  }
727
818
  ),
@@ -735,32 +826,33 @@ function Pie({
735
826
  }
736
827
 
737
828
  // src/widgets/stat-card.tsx
738
- import { useMemo as useMemo4 } from "react";
739
- import { spanLabel } from "@devicechain/dashboards";
829
+ import { useMemo as useMemo5 } from "react";
830
+ import { bucketSecondsFor, spanLabel } from "@devicechain/dashboards";
740
831
  import {
741
- bandAt as bandAt2,
742
- css as css4,
743
- FIT_MIN_FONT_PX,
744
- fontVar as fontVar3,
832
+ bandAt as bandAt3,
833
+ css as css6,
834
+ FIT_MIN_FONT_PX as FIT_MIN_FONT_PX2,
835
+ fontVar as fontVar4,
745
836
  formatValue as formatValue4,
746
- levelColor as levelColor2,
837
+ levelColor as levelColor3,
838
+ optBoolean as optBoolean2,
747
839
  optNumber as optNumber4,
748
- optString as optString4,
749
- optThresholds,
750
- spaceVar as spaceVar3,
751
- useSeverityColors as useSeverityColors2,
752
- WidgetFrame as WidgetFrame4,
840
+ optString as optString5,
841
+ optThresholds as optThresholds2,
842
+ spaceVar as spaceVar4,
843
+ useSeverityColors as useSeverityColors3,
844
+ WidgetFrame as WidgetFrame5,
753
845
  WidgetFreshnessProvider
754
846
  } from "@devicechain/widget-sdk";
755
847
 
756
848
  // src/aggregate-reading.ts
757
- import { pickSample, primaryMeasurementName } from "@devicechain/widget-sdk";
849
+ import { pickSample, primaryMeasurementName as primaryMeasurementName2 } from "@devicechain/widget-sdk";
758
850
  function parseStat(v) {
759
851
  return AGGREGATE_STATS.find((s) => s === v) ?? "avg";
760
852
  }
761
853
  function statReading(widget, data, stat) {
762
854
  const { aggregates } = data;
763
- const pinned = primaryMeasurementName(widget);
855
+ const pinned = primaryMeasurementName2(widget);
764
856
  if (widget.datasource?.kind === "unbound") return { kind: "empty", reason: "unbound", name: pinned };
765
857
  if (stat === "last") {
766
858
  const sample = pickSample(data.latest, pinned);
@@ -786,35 +878,127 @@ function statReading(widget, data, stat) {
786
878
  return { kind: "value", stat, name, value: a[stat], spanSeconds: aggregates.spanSeconds };
787
879
  }
788
880
 
881
+ // src/sparkline.tsx
882
+ import { css as css5 } from "@devicechain/widget-sdk";
883
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
884
+ function trendValue(point, stat) {
885
+ switch (stat) {
886
+ case "min":
887
+ return point.min;
888
+ case "max":
889
+ return point.max;
890
+ case "sum":
891
+ return point.sum;
892
+ case "count":
893
+ return point.count;
894
+ case "avg":
895
+ case "last":
896
+ return point.avg;
897
+ }
898
+ }
899
+ function trendPoints(series, stat) {
900
+ if (!series) return [];
901
+ const out = [];
902
+ for (const p of series) {
903
+ const at = Date.parse(p.bucketStart);
904
+ const value = trendValue(p, stat);
905
+ if (Number.isFinite(at) && Number.isFinite(value)) out.push({ at, value });
906
+ }
907
+ return out.sort((a, b) => a.at - b.at);
908
+ }
909
+ var VIEW_WIDTH = 100;
910
+ var PAD = 3;
911
+ function sparklineGeometry(points, height, bucketSeconds) {
912
+ if (points.length === 0) return { d: "", end: void 0, points: 0 };
913
+ const t0 = points[0].at;
914
+ const t1 = points[points.length - 1].at;
915
+ let lo = Infinity;
916
+ let hi = -Infinity;
917
+ for (const p of points) {
918
+ lo = Math.min(lo, p.value);
919
+ hi = Math.max(hi, p.value);
920
+ }
921
+ const x = (at) => t1 === t0 ? VIEW_WIDTH / 2 : PAD + (at - t0) / (t1 - t0) * (VIEW_WIDTH - 2 * PAD);
922
+ const y = (v) => hi === lo ? height / 2 : PAD + (1 - (v - lo) / (hi - lo)) * (height - 2 * PAD);
923
+ const steps = points.slice(1).map((p, i) => p.at - points[i].at).sort((a, b) => a - b);
924
+ const usual = steps.length > 0 ? steps[Math.floor(steps.length / 2)] : 0;
925
+ const gap = Math.max(bucketSeconds * 2e3, usual * 3);
926
+ let d = "";
927
+ let prev;
928
+ for (const p of points) {
929
+ const cmd = prev === void 0 || p.at - prev > gap ? "M" : "L";
930
+ d += `${d ? " " : ""}${cmd}${x(p.at).toFixed(2)} ${y(p.value).toFixed(2)}`;
931
+ prev = p.at;
932
+ }
933
+ const last = points[points.length - 1];
934
+ return { d, end: { left: x(last.at) / VIEW_WIDTH * 100, top: y(last.value) / height * 100 }, points: points.length };
935
+ }
936
+ function Sparkline({
937
+ points,
938
+ height,
939
+ bucketSeconds,
940
+ label
941
+ }) {
942
+ const g = sparklineGeometry(points, height, bucketSeconds);
943
+ if (g.points === 0) return null;
944
+ return /* @__PURE__ */ jsxs5("div", { "data-sparkline": g.points, role: "img", "aria-label": label, title: label, style: { position: "relative", height, width: "100%", flex: "0 0 auto" }, children: [
945
+ /* @__PURE__ */ jsx5("svg", { viewBox: `0 0 ${VIEW_WIDTH} ${height}`, preserveAspectRatio: "none", width: "100%", height, style: { display: "block", overflow: "visible" }, "aria-hidden": "true", children: g.points > 1 ? /* @__PURE__ */ jsx5("path", { d: g.d, fill: "none", stroke: css5("muted-foreground"), strokeWidth: 1.5, strokeLinejoin: "round", strokeLinecap: "round", vectorEffect: "non-scaling-stroke" }) : null }),
946
+ g.end ? /* @__PURE__ */ jsx5(
947
+ "span",
948
+ {
949
+ "data-sparkline-end": "",
950
+ style: {
951
+ position: "absolute",
952
+ left: `${g.end.left}%`,
953
+ top: `${g.end.top}%`,
954
+ width: 6,
955
+ height: 6,
956
+ marginLeft: -3,
957
+ marginTop: -3,
958
+ borderRadius: "50%",
959
+ background: css5("muted-foreground")
960
+ }
961
+ }
962
+ ) : null
963
+ ] });
964
+ }
965
+
789
966
  // src/widgets/stat-card.tsx
790
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
791
- var CAPTION_ROOM_PX = 22;
967
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
968
+ var CAPTION_ROOM_PX2 = 22;
969
+ var SPARK_ROOM_PX = 28;
792
970
  var BESIDE_FIGURE_SHARE = 0.6;
793
971
  var AGE_ONLY = { at: void 0, staleAfterSeconds: void 0 };
794
972
  function StatCard({ widget, data }) {
795
- const severity = useSeverityColors2();
973
+ const severity = useSeverityColors3();
796
974
  const strings = aggregateStrings();
797
- const stat = parseStat(optString4(widget.options, "stat"));
975
+ const stat = parseStat(optString5(widget.options, "stat"));
798
976
  const reading = statReading(widget, data, stat);
799
- const thresholds = useMemo4(() => optThresholds(widget.options, "thresholds"), [widget.options]);
800
- const unit = optString4(widget.options, "unit");
977
+ const thresholds = useMemo5(() => optThresholds2(widget.options, "thresholds"), [widget.options]);
978
+ const unit = optString5(widget.options, "unit");
801
979
  const precision = optNumber4(widget.options, "precision");
802
- const title = optString4(widget.options, "title") ?? reading.name;
803
- const card = /* @__PURE__ */ jsx4(WidgetFrame4, { title, sampleTime: stat === "last" ? reading.kind === "value" ? reading.time ?? null : null : void 0, children: ({ size, scale }) => {
980
+ const title = optString5(widget.options, "title") ?? reading.name;
981
+ const wantsTrend = optBoolean2(widget.options, "sparkline");
982
+ const card = /* @__PURE__ */ jsx6(WidgetFrame5, { title, sampleTime: stat === "last" ? reading.kind === "value" ? reading.time ?? null : null : void 0, children: ({ size, scale }) => {
804
983
  if (reading.kind === "empty") {
805
- return /* @__PURE__ */ jsx4(LevelEmpty, { message: emptyMessage(reading, strings) });
984
+ return /* @__PURE__ */ jsx6(LevelEmpty, { message: emptyMessage(reading, strings) });
806
985
  }
807
986
  const isCount = reading.stat === "count";
808
- const band = bandAt2(thresholds, reading.value);
809
- const room = Math.round(CAPTION_ROOM_PX * scale);
810
- const beside = size.height > 0 && size.height - room < 2 * Math.round(FIT_MIN_FONT_PX * scale);
811
- const figure = beside ? { width: Math.round(size.width * BESIDE_FIGURE_SHARE), height: size.height } : { width: size.width, height: Math.max(0, size.height - room) };
812
- return /* @__PURE__ */ jsxs4(
987
+ const band = bandAt3(thresholds, reading.value);
988
+ const room = Math.round(CAPTION_ROOM_PX2 * scale);
989
+ const floor2 = 2 * Math.round(FIT_MIN_FONT_PX2 * scale);
990
+ const sparkHeight = Math.round(SPARK_ROOM_PX * scale);
991
+ const points = wantsTrend && data.aggregates.status === "ready" ? trendPoints(data.aggregates.series?.[reading.name ?? ""], reading.stat) : [];
992
+ const showTrend = points.length > 0 && size.height - room - sparkHeight >= floor2;
993
+ const beside = size.height > 0 && size.height - room < 2 * Math.round(FIT_MIN_FONT_PX2 * scale);
994
+ const figure = beside ? { width: Math.round(size.width * BESIDE_FIGURE_SHARE), height: size.height } : { width: size.width, height: Math.max(0, size.height - room - (showTrend ? sparkHeight : 0)) };
995
+ return /* @__PURE__ */ jsxs6(
813
996
  "div",
814
997
  {
815
998
  "data-stat": reading.stat,
816
999
  "data-stat-span": reading.spanSeconds,
817
1000
  "data-stat-layout": beside ? "beside" : "under",
1001
+ "data-stat-trend": showTrend ? points.length : void 0,
818
1002
  "data-threshold-level": band?.level,
819
1003
  title: band?.label,
820
1004
  style: {
@@ -822,38 +1006,47 @@ function StatCard({ widget, data }) {
822
1006
  flexDirection: beside ? "row" : "column",
823
1007
  alignItems: beside ? "center" : void 0,
824
1008
  justifyContent: "safe center",
825
- gap: beside ? spaceVar3(2) : void 0,
1009
+ gap: beside ? spaceVar4(2) : void 0,
826
1010
  height: "100%",
827
1011
  overflow: "hidden"
828
1012
  },
829
1013
  children: [
830
- /* @__PURE__ */ jsx4(
1014
+ /* @__PURE__ */ jsx6(
831
1015
  "div",
832
1016
  {
833
1017
  style: beside ? { flex: "0 1 auto", minWidth: 0, display: "flex", alignItems: "center" } : { flex: "1 1 0", minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" },
834
- children: /* @__PURE__ */ jsx4(
1018
+ children: /* @__PURE__ */ jsx6(
835
1019
  LevelReadout,
836
1020
  {
837
1021
  text: formatValue4(reading.value, isCount ? 0 : precision),
838
1022
  unit: isCount ? void 0 : unit,
839
- color: levelColor2(severity, band?.level),
1023
+ color: levelColor3(severity, band?.level),
840
1024
  size: figure,
841
1025
  scale
842
1026
  }
843
1027
  )
844
1028
  }
845
1029
  ),
846
- /* @__PURE__ */ jsx4(
1030
+ showTrend && data.aggregates.status === "ready" ? /* @__PURE__ */ jsx6(
1031
+ Sparkline,
1032
+ {
1033
+ points,
1034
+ height: sparkHeight,
1035
+ bucketSeconds: bucketSecondsFor(data.aggregates.spanSeconds),
1036
+ label: strings.trend(strings.stats[reading.stat === "last" ? "avg" : reading.stat], spanLabel(data.aggregates.spanSeconds))
1037
+ }
1038
+ ) : null,
1039
+ /* @__PURE__ */ jsx6(
847
1040
  "div",
848
1041
  {
849
1042
  "data-stat-caption": "",
850
1043
  style: {
851
1044
  flex: beside ? "0 1 auto" : "0 0 auto",
852
1045
  minWidth: 0,
853
- marginTop: beside ? void 0 : spaceVar3(1),
1046
+ marginTop: beside ? void 0 : spaceVar4(1),
854
1047
  textAlign: "center",
855
- fontSize: fontVar3("sm"),
856
- color: css4("muted-foreground"),
1048
+ fontSize: fontVar4("sm"),
1049
+ color: css6("muted-foreground"),
857
1050
  whiteSpace: "nowrap",
858
1051
  overflow: "hidden",
859
1052
  textOverflow: "ellipsis"
@@ -865,7 +1058,7 @@ function StatCard({ widget, data }) {
865
1058
  }
866
1059
  );
867
1060
  } });
868
- return stat === "last" ? /* @__PURE__ */ jsx4(WidgetFreshnessProvider, { freshness: AGE_ONLY, children: card }) : card;
1061
+ return stat === "last" ? /* @__PURE__ */ jsx6(WidgetFreshnessProvider, { freshness: AGE_ONLY, children: card }) : card;
869
1062
  }
870
1063
  function caption(r, stats) {
871
1064
  const name = stats[r.stat];
@@ -889,8 +1082,9 @@ function emptyMessage(r, strings) {
889
1082
  }
890
1083
  export {
891
1084
  BarChart,
1085
+ FleetCount,
892
1086
  FleetTable,
893
1087
  PieChart,
894
1088
  StatCard
895
1089
  };
896
- //# sourceMappingURL=aggregate-widgets-HZQI5HTX.js.map
1090
+ //# sourceMappingURL=aggregate-widgets-2ZQBW3YF.js.map