@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
@@ -0,0 +1,722 @@
1
+ // src/widgets/severity.ts
2
+ import { css } from "@devicechain/widget-sdk";
3
+ var LEVEL_OF = {
4
+ CRITICAL: "critical",
5
+ MAJOR: "major",
6
+ MINOR: "minor",
7
+ WARNING: "warning"
8
+ };
9
+ var SEVERITY_ORDER = {
10
+ CRITICAL: 0,
11
+ MAJOR: 1,
12
+ MINOR: 2,
13
+ WARNING: 3,
14
+ INDETERMINATE: 4
15
+ };
16
+ function severityColor(colors, severity) {
17
+ const level = LEVEL_OF[severity];
18
+ return level ? colors[level] : css("muted-foreground");
19
+ }
20
+ function severityRank(severity) {
21
+ return SEVERITY_ORDER[severity] ?? 99;
22
+ }
23
+ function severityLabel(severity) {
24
+ if (!severity) return "";
25
+ return severity.charAt(0).toUpperCase() + severity.slice(1).toLowerCase();
26
+ }
27
+ function highestSeverity(severities) {
28
+ let best;
29
+ for (const s of severities) {
30
+ if (best === void 0 || severityRank(s) < severityRank(best)) best = s;
31
+ }
32
+ return best;
33
+ }
34
+
35
+ // src/widgets/alarm-table.tsx
36
+ import { Fragment, useEffect, useId, useState } from "react";
37
+ import {
38
+ css as css3,
39
+ fontVar as fontVar2,
40
+ FlashValue,
41
+ formatDateTime,
42
+ optBoolean,
43
+ optNumber,
44
+ optString,
45
+ useElementSize,
46
+ useSeverityColors as useSeverityColors2,
47
+ useWidgetSelect,
48
+ useWidgetTimeZone,
49
+ WidgetFrame
50
+ } from "@devicechain/widget-sdk";
51
+
52
+ // src/alarm-strings.ts
53
+ import { viewerLocale } from "@devicechain/widget-sdk";
54
+ var ALARM_STRINGS = {
55
+ en: {
56
+ tabsLabel: "Alarms by device type",
57
+ all: "All",
58
+ deviceType: "Device type",
59
+ activeBadge: (n, worst) => `${n} active ${n === 1 ? "alarm" : "alarms"}, worst ${worst}`,
60
+ severity: { CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor", WARNING: "Warning", INDETERMINATE: "Indeterminate" },
61
+ breakdown: {
62
+ label: "Alarms by severity",
63
+ total: (n) => `${n} ${n === 1 ? "alarm" : "alarms"}`,
64
+ ofType: (n, type) => `${n} ${n === 1 ? "alarm" : "alarms"} on ${type} devices`,
65
+ item: (severity, n) => `${severity}: ${n}`,
66
+ loading: "Loading alarms\u2026",
67
+ unsupported: "This data source cannot count alarms by severity.",
68
+ failed: "The alarm counts could not be read."
69
+ }
70
+ },
71
+ es: {
72
+ tabsLabel: "Alarmas por tipo de dispositivo",
73
+ all: "Todas",
74
+ deviceType: "Tipo de dispositivo",
75
+ activeBadge: (n, worst) => `${n} ${n === 1 ? "alarma activa" : "alarmas activas"}, la peor: ${worst}`,
76
+ severity: { CRITICAL: "Cr\xEDtica", MAJOR: "Mayor", MINOR: "Menor", WARNING: "Advertencia", INDETERMINATE: "Indeterminada" },
77
+ breakdown: {
78
+ label: "Alarmas por severidad",
79
+ total: (n) => `${n} ${n === 1 ? "alarma" : "alarmas"}`,
80
+ ofType: (n, type) => `${n} ${n === 1 ? "alarma" : "alarmas"} en dispositivos de tipo ${type}`,
81
+ item: (severity, n) => `${severity}: ${n}`,
82
+ loading: "Cargando alarmas\u2026",
83
+ unsupported: "Esta fuente de datos no puede contar alarmas por severidad.",
84
+ failed: "No se pudieron leer los recuentos de alarmas."
85
+ }
86
+ }
87
+ };
88
+ function alarmStrings(locale = viewerLocale()) {
89
+ return locale?.toLowerCase().split("-")[0] === "es" ? ALARM_STRINGS.es : ALARM_STRINGS.en;
90
+ }
91
+
92
+ // src/widgets/alarm-type-tabs.tsx
93
+ import { css as css2, fontVar, useSeverityColors } from "@devicechain/widget-sdk";
94
+ import { useRef } from "react";
95
+ import { jsx, jsxs } from "react/jsx-runtime";
96
+ var tabStyle = (active) => ({
97
+ flex: "0 0 auto",
98
+ display: "inline-flex",
99
+ alignItems: "center",
100
+ gap: 6,
101
+ padding: "6px 10px",
102
+ border: "none",
103
+ borderBottom: `2px solid ${active ? css2("primary") : "transparent"}`,
104
+ background: "none",
105
+ font: "inherit",
106
+ fontSize: fontVar("md"),
107
+ fontWeight: active ? 600 : 400,
108
+ color: active ? css2("foreground") : css2("muted-foreground"),
109
+ cursor: "pointer",
110
+ whiteSpace: "nowrap"
111
+ });
112
+ var tabDomId = (idBase, token) => `${idBase}-tab-${token ?? ""}`;
113
+ var panelDomId = (idBase) => `${idBase}-panel`;
114
+ function AlarmTypeTabsStrip({ tabs, selected, onSelect, idBase }) {
115
+ const strings = alarmStrings();
116
+ const severity = useSeverityColors();
117
+ const refs = useRef(/* @__PURE__ */ new Map());
118
+ const order = [null, ...tabs.map((t) => t.token)];
119
+ const current = order.includes(selected) ? selected : null;
120
+ const move = (event, from) => {
121
+ const to = event.key === "ArrowRight" ? (from + 1) % order.length : event.key === "ArrowLeft" ? (from - 1 + order.length) % order.length : event.key === "Home" ? 0 : event.key === "End" ? order.length - 1 : -1;
122
+ if (to < 0) return;
123
+ event.preventDefault();
124
+ refs.current.get(order[to])?.focus();
125
+ };
126
+ return /* @__PURE__ */ jsx(
127
+ "div",
128
+ {
129
+ role: "tablist",
130
+ "aria-label": strings.tabsLabel,
131
+ style: {
132
+ display: "flex",
133
+ flexShrink: 0,
134
+ overflowX: "auto",
135
+ overflowY: "hidden",
136
+ borderBottom: `1px solid ${css2("border")}`
137
+ },
138
+ children: order.map((token, i) => {
139
+ const tab = token === null ? void 0 : tabs[i - 1];
140
+ const active = token === current;
141
+ return /* @__PURE__ */ jsxs(
142
+ "button",
143
+ {
144
+ ref: (el) => {
145
+ if (el) refs.current.set(token, el);
146
+ else refs.current.delete(token);
147
+ },
148
+ type: "button",
149
+ role: "tab",
150
+ id: tabDomId(idBase, token),
151
+ "aria-selected": active,
152
+ "aria-controls": panelDomId(idBase),
153
+ tabIndex: active ? 0 : -1,
154
+ onClick: () => onSelect(token),
155
+ onKeyDown: (e) => move(e, i),
156
+ style: tabStyle(active),
157
+ children: [
158
+ token === null ? strings.all : tab?.name ?? token,
159
+ tab && tab.active > 0 ? /* @__PURE__ */ jsx(Badge, { tab, colors: severity, strings }) : null
160
+ ]
161
+ },
162
+ token ?? "\0all"
163
+ );
164
+ })
165
+ }
166
+ );
167
+ }
168
+ function Badge({
169
+ tab,
170
+ colors,
171
+ strings
172
+ }) {
173
+ const worst = tab.worstSeverity ?? "INDETERMINATE";
174
+ return /* @__PURE__ */ jsxs(
175
+ "span",
176
+ {
177
+ "data-testid": "alarm-tab-badge",
178
+ role: "img",
179
+ "aria-label": strings.activeBadge(tab.active, strings.severity[worst] ?? worst),
180
+ style: {
181
+ display: "inline-flex",
182
+ alignItems: "center",
183
+ gap: 4,
184
+ fontSize: fontVar("xs"),
185
+ fontWeight: 600,
186
+ fontVariantNumeric: "tabular-nums",
187
+ color: css2("foreground")
188
+ },
189
+ children: [
190
+ /* @__PURE__ */ jsx(
191
+ "span",
192
+ {
193
+ "data-testid": "alarm-tab-badge-dot",
194
+ "aria-hidden": "true",
195
+ style: { width: 8, height: 8, borderRadius: "50%", background: severityColor(colors, worst), flexShrink: 0 }
196
+ }
197
+ ),
198
+ /* @__PURE__ */ jsx("span", { "aria-hidden": "true", children: tab.active })
199
+ ]
200
+ }
201
+ );
202
+ }
203
+
204
+ // src/widgets/alarm-table.tsx
205
+ import { Fragment as Fragment2, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
206
+ var cell = { padding: "6px 10px", textAlign: "left", whiteSpace: "nowrap" };
207
+ var headCell = {
208
+ ...cell,
209
+ position: "sticky",
210
+ top: 0,
211
+ background: css3("card"),
212
+ fontSize: fontVar2("xs"),
213
+ fontWeight: 600,
214
+ textTransform: "uppercase",
215
+ letterSpacing: "0.04em",
216
+ color: css3("muted-foreground"),
217
+ borderBottom: `1px solid ${css3("border")}`
218
+ };
219
+ var ALARM_TABLE_NARROW_PX = 480;
220
+ var narrowCell = {
221
+ ...cell,
222
+ padding: "6px 6px",
223
+ whiteSpace: "normal",
224
+ overflowWrap: "break-word",
225
+ wordBreak: "normal"
226
+ };
227
+ function hyphenBreaks(text) {
228
+ const parts = text.split("-").map((p, i, all) => i < all.length - 1 ? `${p}-` : p);
229
+ return parts.map((part, i) => /* @__PURE__ */ jsxs2(Fragment, { children: [
230
+ i > 0 ? /* @__PURE__ */ jsx2("wbr", {}) : null,
231
+ part
232
+ ] }, i));
233
+ }
234
+ function shownTabs(discovered, pinned) {
235
+ if (pinned.length === 0) return [...discovered];
236
+ const byToken = new Map(discovered.map((t) => [t.token, t]));
237
+ const seen = /* @__PURE__ */ new Set();
238
+ const out = [];
239
+ for (const token of pinned) {
240
+ if (seen.has(token)) continue;
241
+ seen.add(token);
242
+ out.push(byToken.get(token) ?? { token, name: null, active: 0, worstSeverity: null });
243
+ }
244
+ return out;
245
+ }
246
+ function pinnedTypes(widget) {
247
+ const list = widget.options?.deviceTypes;
248
+ if (!Array.isArray(list)) return [];
249
+ return list.flatMap((item) => {
250
+ const token = item?.token;
251
+ return typeof token === "string" && token !== "" ? [token] : [];
252
+ });
253
+ }
254
+ function statusLabel(alarm) {
255
+ if (alarm.state === "CLEARED") return "Cleared";
256
+ return alarm.acknowledged ? "Acknowledged" : "Active";
257
+ }
258
+ function AlarmTable({ widget, data, actions }) {
259
+ const { alarms, total, loading } = data;
260
+ const canAct = actions?.can("alarm:write") ?? false;
261
+ const [sizeRef, size] = useElementSize();
262
+ const severity = useSeverityColors2();
263
+ const narrow = size.width > 0 && size.width < ALARM_TABLE_NARROW_PX;
264
+ const typeTabs = data.typeTabs;
265
+ const tabs = typeTabs ? shownTabs(typeTabs.tabs, pinnedTypes(widget)) : [];
266
+ const stripShown = typeTabs !== void 0 && tabs.length > 0;
267
+ const selectTab = typeTabs?.select;
268
+ const selectedType = typeTabs?.selected ?? null;
269
+ const dropped = stripShown && selectedType !== null && !tabs.some((t) => t.token === selectedType);
270
+ useEffect(() => {
271
+ if (dropped) selectTab?.(null);
272
+ }, [dropped, selectTab]);
273
+ const idBase = useId();
274
+ const typeColumn = stripShown && selectedType === null && !narrow;
275
+ const columns = 5 + (narrow ? 0 : 2) + (typeColumn ? 1 : 0) + (canAct ? 1 : 0);
276
+ const text = narrow ? narrowCell : cell;
277
+ const precision = optNumber(widget.options, "precision");
278
+ const flash = optBoolean(widget.options, "flashOnChange");
279
+ const select = useWidgetSelect();
280
+ const timeZone = useWidgetTimeZone();
281
+ const selectionTarget = optString(widget.options, "selectionTarget");
282
+ const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : void 0;
283
+ return /* @__PURE__ */ jsx2(WidgetFrame, { title: optString(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
284
+ stripShown && typeTabs ? /* @__PURE__ */ jsx2(
285
+ AlarmTypeTabsStrip,
286
+ {
287
+ tabs,
288
+ selected: dropped ? null : selectedType,
289
+ onSelect: typeTabs.select,
290
+ idBase
291
+ }
292
+ ) : null,
293
+ /* @__PURE__ */ jsx2(
294
+ "div",
295
+ {
296
+ ref: sizeRef,
297
+ style: { flex: 1, minHeight: 0, overflow: "auto" },
298
+ ...stripShown ? { role: "tabpanel", id: panelDomId(idBase), "aria-labelledby": tabDomId(idBase, dropped ? null : selectedType) } : {},
299
+ children: /* @__PURE__ */ jsxs2(
300
+ "table",
301
+ {
302
+ style: {
303
+ width: "100%",
304
+ borderCollapse: "collapse",
305
+ fontSize: fontVar2("md"),
306
+ color: css3("foreground"),
307
+ fontVariantNumeric: "tabular-nums"
308
+ },
309
+ children: [
310
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
311
+ /* @__PURE__ */ jsx2("th", { style: { ...headCell, width: 4, padding: 0 }, "aria-hidden": "true" }),
312
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Severity" }),
313
+ narrow ? null : /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Status" }),
314
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Originator" }),
315
+ typeColumn ? /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: alarmStrings().deviceType }) : null,
316
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Alarm" }),
317
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Value" }),
318
+ narrow ? null : /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Raised" }),
319
+ canAct ? /* @__PURE__ */ jsx2("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Actions" }) : null
320
+ ] }) }),
321
+ /* @__PURE__ */ jsx2("tbody", { children: alarms.length === 0 ? /* @__PURE__ */ jsx2("tr", { children: /* @__PURE__ */ jsx2("td", { colSpan: columns, style: { ...cell, textAlign: "center", color: css3("muted-foreground") }, children: loading ? "Loading\u2026" : "No alarms" }) }) : alarms.map((alarm) => {
322
+ const color = severityColor(severity, alarm.severity);
323
+ return /* @__PURE__ */ jsxs2("tr", { style: { borderBottom: `1px solid ${css3("border")}` }, children: [
324
+ /* @__PURE__ */ jsx2("td", { "aria-hidden": "true", style: { padding: 0, width: 4, background: color } }),
325
+ /* @__PURE__ */ jsx2("td", { style: cell, children: /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
326
+ /* @__PURE__ */ jsx2(
327
+ "span",
328
+ {
329
+ "aria-hidden": "true",
330
+ style: { width: 8, height: 8, borderRadius: "50%", background: color, flexShrink: 0 }
331
+ }
332
+ ),
333
+ severityLabel(alarm.severity)
334
+ ] }) }),
335
+ narrow ? null : /* @__PURE__ */ jsx2("td", { style: { ...cell, color: css3("muted-foreground") }, children: statusLabel(alarm) }),
336
+ /* @__PURE__ */ jsx2("td", { style: text, children: /* @__PURE__ */ jsx2(OriginatorCell, { alarm, drillTo }) }),
337
+ typeColumn ? /* @__PURE__ */ jsx2("td", { style: { ...cell, color: css3("muted-foreground") }, children: alarm.deviceType ? alarm.deviceType.name ?? alarm.deviceType.token : "\u2014" }) : null,
338
+ /* @__PURE__ */ jsx2("td", { style: text, children: hyphenBreaks(alarm.alarmKey) }),
339
+ /* @__PURE__ */ jsx2("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx2(
340
+ FlashValue,
341
+ {
342
+ value: alarm.lastValue,
343
+ precision,
344
+ enabled: flash,
345
+ identity: alarm.token
346
+ }
347
+ ) }),
348
+ narrow ? null : /* @__PURE__ */ jsx2("td", { style: { ...cell, color: css3("muted-foreground") }, children: formatDateTime(alarm.raisedTime, timeZone) }),
349
+ canAct ? /* @__PURE__ */ jsx2("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx2(AlarmRowActions, { alarm, actions }) }) : null
350
+ ] }, alarm.token);
351
+ }) })
352
+ ]
353
+ }
354
+ )
355
+ }
356
+ ),
357
+ total > alarms.length ? /* @__PURE__ */ jsxs2(
358
+ "div",
359
+ {
360
+ style: {
361
+ padding: "4px 10px",
362
+ fontSize: fontVar2("xs"),
363
+ color: css3("muted-foreground"),
364
+ borderTop: `1px solid ${css3("border")}`
365
+ },
366
+ children: [
367
+ "Showing ",
368
+ alarms.length,
369
+ " of ",
370
+ total
371
+ ]
372
+ }
373
+ ) : null
374
+ ] }) });
375
+ }
376
+ function OriginatorCell({
377
+ alarm,
378
+ drillTo
379
+ }) {
380
+ if (drillTo && alarm.originatorType === "device" && alarm.originatorToken) {
381
+ const token = alarm.originatorToken;
382
+ return /* @__PURE__ */ jsx2(
383
+ "button",
384
+ {
385
+ type: "button",
386
+ onClick: () => drillTo.select({ slot: drillTo.slot, binding: { kind: "device", deviceToken: token } }),
387
+ title: `Show ${token} in the linked widgets`,
388
+ style: {
389
+ padding: 0,
390
+ border: "none",
391
+ background: "none",
392
+ font: "inherit",
393
+ color: css3("primary"),
394
+ cursor: "pointer",
395
+ textDecoration: "underline",
396
+ textUnderlineOffset: 2
397
+ },
398
+ children: hyphenBreaks(token)
399
+ }
400
+ );
401
+ }
402
+ return /* @__PURE__ */ jsx2(Fragment2, { children: hyphenBreaks(alarm.originatorToken ?? alarm.originatorType) });
403
+ }
404
+ var actionButton = {
405
+ padding: "2px 8px",
406
+ fontSize: fontVar2("sm"),
407
+ lineHeight: 1.4,
408
+ borderRadius: 4,
409
+ border: `1px solid ${css3("border")}`,
410
+ background: css3("card"),
411
+ color: css3("foreground"),
412
+ cursor: "pointer"
413
+ };
414
+ function AlarmRowActions({ alarm, actions }) {
415
+ const severity = useSeverityColors2();
416
+ const [pending, setPending] = useState(null);
417
+ const [failed, setFailed] = useState(false);
418
+ if (alarm.state !== "ACTIVE") {
419
+ return /* @__PURE__ */ jsx2("span", { style: { color: css3("muted-foreground") }, children: "\u2014" });
420
+ }
421
+ const run = (kind, op) => {
422
+ setPending(kind);
423
+ setFailed(false);
424
+ let promise;
425
+ try {
426
+ promise = op();
427
+ } catch {
428
+ setFailed(true);
429
+ setPending(null);
430
+ return;
431
+ }
432
+ promise.catch(() => setFailed(true)).finally(() => setPending(null));
433
+ };
434
+ return /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", gap: 6, justifyContent: "flex-end", alignItems: "center" }, children: [
435
+ failed ? /* @__PURE__ */ jsx2(
436
+ "span",
437
+ {
438
+ role: "img",
439
+ "aria-label": "Action failed \u2014 retry",
440
+ title: "Action failed \u2014 retry",
441
+ style: { color: severityColor(severity, "CRITICAL"), fontSize: fontVar2("sm") },
442
+ children: "\u26A0"
443
+ }
444
+ ) : null,
445
+ !alarm.acknowledged ? /* @__PURE__ */ jsx2(
446
+ "button",
447
+ {
448
+ type: "button",
449
+ style: { ...actionButton, opacity: pending ? 0.6 : 1 },
450
+ disabled: pending !== null,
451
+ "aria-busy": pending === "ack",
452
+ "aria-label": `Acknowledge alarm ${alarm.alarmKey}`,
453
+ onClick: () => run("ack", () => actions.acknowledgeAlarm(alarm.token)),
454
+ children: pending === "ack" ? "\u2026" : "Ack"
455
+ }
456
+ ) : null,
457
+ /* @__PURE__ */ jsx2(
458
+ "button",
459
+ {
460
+ type: "button",
461
+ style: { ...actionButton, opacity: pending ? 0.6 : 1 },
462
+ disabled: pending !== null,
463
+ "aria-busy": pending === "clear",
464
+ "aria-label": `Clear alarm ${alarm.alarmKey}`,
465
+ onClick: () => run("clear", () => actions.clearAlarm(alarm.token)),
466
+ children: pending === "clear" ? "\u2026" : "Clear"
467
+ }
468
+ )
469
+ ] });
470
+ }
471
+
472
+ // src/widgets/alarm-count.tsx
473
+ import {
474
+ css as css4,
475
+ fitValueFont,
476
+ fitValueFontCss,
477
+ FIT_MIN_FONT_PX,
478
+ fontVar as fontVar3,
479
+ optString as optString2,
480
+ spaceVar,
481
+ useSeverityColors as useSeverityColors3,
482
+ useStickyChars,
483
+ useWidgetScale,
484
+ WidgetFrame as WidgetFrame2
485
+ } from "@devicechain/widget-sdk";
486
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
487
+ var ALARM_COUNT_CAPTION_ROOM_PX = 20;
488
+ function AlarmCountBody({ data }) {
489
+ const { size, scale } = useWidgetScale();
490
+ const severity = useSeverityColors3();
491
+ const { total, alarms, loading } = data;
492
+ const worst = highestSeverity(alarms.map((a) => a.severity));
493
+ const alerting = total > 0 && worst != null;
494
+ const color = alerting ? severityColor(severity, worst) : css4("muted-foreground");
495
+ const display = loading && total === 0 ? "\u2014" : String(total);
496
+ const chars = useStickyChars(display.length);
497
+ const floor = Math.round(FIT_MIN_FONT_PX * scale);
498
+ const measured = size.width > 0 && size.height > 0;
499
+ const valueFont = measured ? fitValueFont(
500
+ { width: size.width, height: Math.max(1, size.height - ALARM_COUNT_CAPTION_ROOM_PX * scale) },
501
+ chars,
502
+ { floor }
503
+ ) : (
504
+ // First frame: the same curve in cq units, resolved by the browser against the frame's
505
+ // column (it does not take the caption's room off; the first measure does).
506
+ fitValueFontCss(chars, { floor })
507
+ );
508
+ const unit = total === 1 ? "alarm" : "alarms";
509
+ const ariaLabel = alerting ? `${total} ${unit}, worst severity ${severityLabel(worst)}` : `${total} ${unit}`;
510
+ return /* @__PURE__ */ jsxs3(
511
+ "div",
512
+ {
513
+ style: {
514
+ display: "flex",
515
+ flexDirection: "column",
516
+ justifyContent: "safe center",
517
+ alignItems: "center",
518
+ height: "100%",
519
+ boxSizing: "border-box",
520
+ overflow: "hidden",
521
+ textAlign: "center"
522
+ },
523
+ children: [
524
+ /* @__PURE__ */ jsx3(
525
+ "div",
526
+ {
527
+ "aria-label": ariaLabel,
528
+ style: {
529
+ fontSize: valueFont,
530
+ fontWeight: 700,
531
+ lineHeight: 1,
532
+ whiteSpace: "nowrap",
533
+ fontVariantNumeric: "tabular-nums",
534
+ color
535
+ },
536
+ children: display
537
+ }
538
+ ),
539
+ /* @__PURE__ */ jsxs3(
540
+ "div",
541
+ {
542
+ style: {
543
+ fontSize: fontVar3("xs"),
544
+ color: css4("muted-foreground"),
545
+ marginTop: spaceVar(1),
546
+ textTransform: "uppercase",
547
+ letterSpacing: "0.05em"
548
+ },
549
+ children: [
550
+ /* @__PURE__ */ jsx3("span", { children: unit }),
551
+ alerting ? /* @__PURE__ */ jsx3("span", { children: ` \xB7 worst ${severityLabel(worst)}` }) : null
552
+ ]
553
+ }
554
+ )
555
+ ]
556
+ }
557
+ );
558
+ }
559
+ function AlarmCount({ widget, data }) {
560
+ return /* @__PURE__ */ jsx3(WidgetFrame2, { title: optString2(widget.options, "title"), children: /* @__PURE__ */ jsx3(AlarmCountBody, { data }) });
561
+ }
562
+
563
+ // src/widgets/alarm-severity.tsx
564
+ import {
565
+ css as css5,
566
+ fontVar as fontVar4,
567
+ optString as optString3,
568
+ spaceVar as spaceVar2,
569
+ useSeverityColors as useSeverityColors4,
570
+ WidgetFrame as WidgetFrame3
571
+ } from "@devicechain/widget-sdk";
572
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
573
+ var SEVERITY_SCALE = ["CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"];
574
+ function severityCounts(source) {
575
+ const counts = { CRITICAL: 0, MAJOR: 0, MINOR: 0, WARNING: 0, INDETERMINATE: 0 };
576
+ for (const c of source) {
577
+ if (!(c.count > 0)) continue;
578
+ const key = SEVERITY_SCALE.includes(c.severity) ? c.severity : "INDETERMINATE";
579
+ counts[key] += c.count;
580
+ }
581
+ return counts;
582
+ }
583
+ function Message({ title, text }) {
584
+ return /* @__PURE__ */ jsx4(WidgetFrame3, { title, children: /* @__PURE__ */ jsx4(
585
+ "div",
586
+ {
587
+ "data-alarm-severity": "message",
588
+ style: {
589
+ display: "flex",
590
+ alignItems: "center",
591
+ justifyContent: "center",
592
+ height: "100%",
593
+ textAlign: "center",
594
+ fontSize: fontVar4("sm"),
595
+ color: css5("muted-foreground")
596
+ },
597
+ children: text
598
+ }
599
+ ) });
600
+ }
601
+ function AlarmSeverity({ widget, data }) {
602
+ const strings = alarmStrings();
603
+ const colors = useSeverityColors4();
604
+ const title = optString3(widget.options, "title");
605
+ const deviceType = optString3(widget.options, "deviceType") || void 0;
606
+ if (!data.severityCounts) {
607
+ const text = data.loading ? strings.breakdown.loading : data.error ? strings.breakdown.failed : strings.breakdown.unsupported;
608
+ return /* @__PURE__ */ jsx4(Message, { title, text });
609
+ }
610
+ const counts = severityCounts(data.severityCounts);
611
+ const total = SEVERITY_SCALE.reduce((n, s) => n + counts[s], 0);
612
+ const words = (s) => strings.severity[s] ?? s;
613
+ const caption = deviceType ? strings.breakdown.ofType(total, deviceType) : strings.breakdown.total(total);
614
+ const listLabel = `${strings.breakdown.label}, ${caption}`;
615
+ return /* @__PURE__ */ jsx4(WidgetFrame3, { title, children: /* @__PURE__ */ jsxs4(
616
+ "div",
617
+ {
618
+ "data-alarm-severity": "breakdown",
619
+ "data-total": total,
620
+ style: {
621
+ display: "flex",
622
+ flexDirection: "column",
623
+ justifyContent: "safe center",
624
+ gap: spaceVar2(2),
625
+ height: "100%",
626
+ boxSizing: "border-box",
627
+ overflow: "hidden"
628
+ },
629
+ children: [
630
+ /* @__PURE__ */ jsx4(
631
+ "div",
632
+ {
633
+ "aria-hidden": "true",
634
+ "data-alarm-severity-bar": "",
635
+ style: {
636
+ display: "flex",
637
+ flex: "0 0 auto",
638
+ height: spaceVar2(3),
639
+ borderRadius: spaceVar2(1),
640
+ overflow: "hidden",
641
+ background: css5("muted")
642
+ },
643
+ children: SEVERITY_SCALE.filter((s) => counts[s] > 0).map((s) => /* @__PURE__ */ jsx4("div", { "data-segment": s, style: { flex: `${counts[s]} 1 0`, minWidth: 2, background: severityColor(colors, s) } }, s))
644
+ }
645
+ ),
646
+ /* @__PURE__ */ jsx4(
647
+ "div",
648
+ {
649
+ "data-alarm-severity-total": "",
650
+ style: { flex: "0 0 auto", fontSize: fontVar4("sm"), color: css5("muted-foreground"), fontVariantNumeric: "tabular-nums" },
651
+ children: caption
652
+ }
653
+ ),
654
+ /* @__PURE__ */ jsx4(
655
+ "ul",
656
+ {
657
+ "aria-label": listLabel,
658
+ style: {
659
+ listStyle: "none",
660
+ margin: 0,
661
+ padding: 0,
662
+ display: "grid",
663
+ gridTemplateColumns: "repeat(auto-fit, minmax(min(100%, 7.5rem), 1fr))",
664
+ gap: `${spaceVar2(1)} ${spaceVar2(3)}`,
665
+ minHeight: 0,
666
+ overflow: "hidden"
667
+ },
668
+ children: SEVERITY_SCALE.map((s) => {
669
+ const zero = counts[s] === 0;
670
+ return /* @__PURE__ */ jsxs4(
671
+ "li",
672
+ {
673
+ "data-severity": s,
674
+ "data-zero": zero ? "true" : void 0,
675
+ "aria-label": strings.breakdown.item(words(s), counts[s]),
676
+ style: {
677
+ display: "flex",
678
+ alignItems: "center",
679
+ gap: spaceVar2(2),
680
+ minWidth: 0,
681
+ fontSize: fontVar4("md"),
682
+ color: css5(zero ? "muted-foreground" : "foreground")
683
+ },
684
+ children: [
685
+ /* @__PURE__ */ jsx4(
686
+ "span",
687
+ {
688
+ "aria-hidden": "true",
689
+ style: {
690
+ flex: "0 0 auto",
691
+ width: spaceVar2(3),
692
+ height: spaceVar2(3),
693
+ borderRadius: "50%",
694
+ background: severityColor(colors, s),
695
+ opacity: zero ? 0.35 : 1
696
+ }
697
+ }
698
+ ),
699
+ /* @__PURE__ */ jsx4("span", { style: { flex: "1 1 auto", minWidth: 0, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }, children: words(s) }),
700
+ /* @__PURE__ */ jsx4("span", { style: { flex: "0 0 auto", fontWeight: zero ? 400 : 700, fontVariantNumeric: "tabular-nums" }, children: counts[s] })
701
+ ]
702
+ },
703
+ s
704
+ );
705
+ })
706
+ }
707
+ )
708
+ ]
709
+ }
710
+ ) });
711
+ }
712
+
713
+ export {
714
+ severityColor,
715
+ severityRank,
716
+ severityLabel,
717
+ highestSeverity,
718
+ AlarmTable,
719
+ AlarmCount,
720
+ AlarmSeverity
721
+ };
722
+ //# sourceMappingURL=chunk-546TK2QG.js.map