@devicechain/widgets 0.21.1 → 0.22.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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,833 @@
1
+ import {
2
+ bandSpans,
3
+ fillOf,
4
+ litBars,
5
+ readRange
6
+ } from "./chunk-ZR74ORIT.js";
7
+ import {
8
+ LevelEmpty,
9
+ LevelReadout
10
+ } from "./chunk-XQQQNTBX.js";
11
+
12
+ // src/widgets/battery.tsx
13
+ import { useMemo } from "react";
14
+ import {
15
+ css as css2,
16
+ optNumber,
17
+ optString,
18
+ optThresholds,
19
+ WidgetFrame
20
+ } from "@devicechain/widget-sdk";
21
+
22
+ // src/level-reading.ts
23
+ import {
24
+ bandAt,
25
+ css,
26
+ formatValue,
27
+ levelColor,
28
+ pickSample,
29
+ primaryMeasurementName,
30
+ useSeverityColors
31
+ } from "@devicechain/widget-sdk";
32
+
33
+ // src/level-strings.ts
34
+ import { viewerLocale } from "@devicechain/widget-sdk";
35
+ var LEVEL_STRINGS = {
36
+ en: {
37
+ noData: "No data",
38
+ setRange: "Set min and max",
39
+ badRange: "Max must be above min",
40
+ setThresholds: "Set at least two thresholds",
41
+ aboveScale: "Above scale",
42
+ belowScale: "Below scale",
43
+ on: "On",
44
+ off: "Off",
45
+ cardinals: ["N", "E", "S", "W"]
46
+ },
47
+ es: {
48
+ noData: "Sin datos",
49
+ setRange: "Defina m\xEDn. y m\xE1x.",
50
+ badRange: "El m\xE1x. debe superar el m\xEDn.",
51
+ setThresholds: "Defina al menos dos umbrales",
52
+ aboveScale: "Sobre la escala",
53
+ belowScale: "Bajo la escala",
54
+ on: "Encendido",
55
+ off: "Apagado",
56
+ cardinals: ["N", "E", "S", "O"]
57
+ }
58
+ };
59
+ function levelStrings(locale = viewerLocale()) {
60
+ return locale?.toLowerCase().split("-")[0] === "es" ? LEVEL_STRINGS.es : LEVEL_STRINGS.en;
61
+ }
62
+
63
+ // src/level-reading.ts
64
+ function useLevelReading(widget, data, input) {
65
+ const severity = useSeverityColors();
66
+ const name = primaryMeasurementName(widget);
67
+ const sample = pickSample(data.latest, name);
68
+ const { thresholds, unit, precision, range, normalize } = input;
69
+ const raw = sample?.value ?? void 0;
70
+ const value = normalize ? normalize(raw) : raw;
71
+ const band = bandAt(thresholds, value);
72
+ const bandColor = levelColor(severity, band?.level);
73
+ return {
74
+ name,
75
+ sample,
76
+ value,
77
+ unit,
78
+ precision,
79
+ display: formatValue(value, precision),
80
+ thresholds,
81
+ band,
82
+ levelColor: bandColor,
83
+ colorOf: (level) => levelColor(severity, level),
84
+ fillColor: bandColor ?? css("chart-1"),
85
+ range,
86
+ fill: range?.ok ? fillOf(value, range.range) : void 0,
87
+ strings: levelStrings()
88
+ };
89
+ }
90
+ function emptyReason(r) {
91
+ if (r.range && !r.range.ok) return r.range.reason === "missing" ? r.strings.setRange : r.strings.badRange;
92
+ if (r.value === void 0) return r.strings.noData;
93
+ return void 0;
94
+ }
95
+
96
+ // src/widgets/battery.tsx
97
+ import { jsx, jsxs } from "react/jsx-runtime";
98
+ var BATTERY_DEFAULT_RANGE = { min: 0, max: 100 };
99
+ var VIEW = { w: 120, h: 60 };
100
+ var BODY = { x: 3, y: 3, w: 104, h: 54, r: 9 };
101
+ var NUB = { x: 110, y: 20, w: 8, h: 20, r: 3 };
102
+ var INSET = 7;
103
+ var BATTERY_FILL_WIDTH = BODY.w - INSET * 2;
104
+ function Battery({ widget, data }) {
105
+ const thresholds = useMemo(() => optThresholds(widget.options, "thresholds"), [widget.options]);
106
+ const reading = useLevelReading(widget, data, {
107
+ thresholds,
108
+ unit: optString(widget.options, "unit"),
109
+ precision: optNumber(widget.options, "precision"),
110
+ range: readRange(optNumber(widget.options, "min"), optNumber(widget.options, "max"), BATTERY_DEFAULT_RANGE)
111
+ });
112
+ const title = optString(widget.options, "title") ?? reading.name;
113
+ return /* @__PURE__ */ jsx(WidgetFrame, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx(BatteryBody, { reading, scale }) });
114
+ }
115
+ function BatteryBody({ reading: r, scale: ws }) {
116
+ const reason = emptyReason(r);
117
+ if (reason !== void 0 || !r.range?.ok || !r.fill) return /* @__PURE__ */ jsx(LevelEmpty, { message: reason ?? r.strings.noData });
118
+ const range = r.range.range;
119
+ const fill = r.fill;
120
+ const readoutHeight = Math.max(ws.px("font-md") * 1.6, ws.size.height * 0.4);
121
+ return /* @__PURE__ */ jsxs(
122
+ "div",
123
+ {
124
+ "data-threshold-level": r.band?.level,
125
+ "data-out-of-range": fill.position === "within" ? void 0 : fill.position,
126
+ "data-fill": fill.fraction,
127
+ style: { height: "100%", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: ws.px("space-1") },
128
+ children: [
129
+ /* @__PURE__ */ jsx("div", { style: { flex: "1 1 0", minHeight: 0 }, children: /* @__PURE__ */ jsxs(
130
+ "svg",
131
+ {
132
+ role: "meter",
133
+ "aria-valuemin": range.min,
134
+ "aria-valuemax": range.max,
135
+ "aria-valuenow": range.min + fill.fraction * (range.max - range.min),
136
+ "aria-valuetext": `${r.display}${r.unit ? ` ${r.unit}` : ""}`,
137
+ viewBox: `0 0 ${VIEW.w} ${VIEW.h}`,
138
+ preserveAspectRatio: "xMidYMid meet",
139
+ style: { width: "100%", height: "100%", display: "block" },
140
+ children: [
141
+ /* @__PURE__ */ jsx(
142
+ "rect",
143
+ {
144
+ x: BODY.x,
145
+ y: BODY.y,
146
+ width: BODY.w,
147
+ height: BODY.h,
148
+ rx: BODY.r,
149
+ style: { fill: "none", stroke: css2("muted-foreground"), strokeWidth: 4 }
150
+ }
151
+ ),
152
+ /* @__PURE__ */ jsx("rect", { x: NUB.x, y: NUB.y, width: NUB.w, height: NUB.h, rx: NUB.r, style: { fill: css2("muted-foreground") } }),
153
+ fill.fraction > 0 ? /* @__PURE__ */ jsx(
154
+ "rect",
155
+ {
156
+ "data-fill-bar": "",
157
+ x: BODY.x + INSET,
158
+ y: BODY.y + INSET,
159
+ width: BATTERY_FILL_WIDTH * fill.fraction,
160
+ height: BODY.h - INSET * 2,
161
+ rx: 3,
162
+ style: { fill: r.fillColor }
163
+ }
164
+ ) : null
165
+ ]
166
+ }
167
+ ) }),
168
+ /* @__PURE__ */ jsx("div", { style: { flex: "0 0 auto", height: readoutHeight, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx(
169
+ LevelReadout,
170
+ {
171
+ text: r.display,
172
+ unit: r.unit,
173
+ color: r.levelColor,
174
+ position: fill.position,
175
+ size: { width: ws.size.width, height: readoutHeight },
176
+ scale: ws.scale,
177
+ strings: r.strings
178
+ }
179
+ ) })
180
+ ]
181
+ }
182
+ );
183
+ }
184
+
185
+ // src/widgets/compass.tsx
186
+ import { useMemo as useMemo2 } from "react";
187
+ import {
188
+ optNumber as optNumber2,
189
+ optString as optString2,
190
+ optThresholds as optThresholds2,
191
+ WidgetFrame as WidgetFrame2
192
+ } from "@devicechain/widget-sdk";
193
+ import { buildCompassOption, EChart, useChartTheme } from "@devicechain/widget-sdk/echarts";
194
+
195
+ // src/heading.ts
196
+ import { wrapHeading } from "@devicechain/widget-sdk/echarts";
197
+ function shownHeading(raw, precision) {
198
+ if (raw === void 0 || !Number.isFinite(raw)) return void 0;
199
+ const places = Math.min(100, Math.max(0, Math.trunc(precision)));
200
+ return wrapHeading(Number(raw.toFixed(places)));
201
+ }
202
+ var EIGHT_POINT = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
203
+ var LETTER_INDEX = { N: 0, E: 1, S: 2, W: 3 };
204
+ function cardinalOf(heading, letters) {
205
+ const point = EIGHT_POINT[Math.round(heading / 45) % 8];
206
+ return [...point].map((c) => letters[LETTER_INDEX[c]]).join("");
207
+ }
208
+
209
+ // src/widgets/compass.tsx
210
+ import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
211
+ var DEFAULT_PRECISION = 0;
212
+ var READOUT_SHARE = 0.22;
213
+ function Compass({ widget, data }) {
214
+ const thresholds = useMemo2(() => optThresholds2(widget.options, "thresholds"), [widget.options]);
215
+ const precision = optNumber2(widget.options, "precision") ?? DEFAULT_PRECISION;
216
+ const reading = useLevelReading(widget, data, {
217
+ thresholds,
218
+ unit: void 0,
219
+ precision,
220
+ normalize: (raw) => shownHeading(raw, precision)
221
+ });
222
+ const title = optString2(widget.options, "title") ?? reading.name;
223
+ return /* @__PURE__ */ jsx2(WidgetFrame2, { title, sampleTime: reading.sample?.occurredTime ?? null, flush: true, children: (scale) => /* @__PURE__ */ jsx2(CompassBody, { reading, title, scale }) });
224
+ }
225
+ function CompassBody({ reading: r, title, scale: ws }) {
226
+ const theme = useChartTheme();
227
+ const option = useMemo2(
228
+ () => buildCompassOption(r.value ?? null, theme, { cardinals: r.strings.cardinals, thresholds: r.thresholds, scale: ws.scale }, ws.size),
229
+ [r.value, theme, r.strings, r.thresholds, ws.scale, ws.size]
230
+ );
231
+ const reason = emptyReason(r);
232
+ if (reason !== void 0 || r.value === void 0) return /* @__PURE__ */ jsx2(LevelEmpty, { message: reason ?? r.strings.noData });
233
+ const point = cardinalOf(r.value, r.strings.cardinals);
234
+ const stripHeight = Math.max(ws.px("font-md") * 1.6, ws.size.height * READOUT_SHARE);
235
+ return /* @__PURE__ */ jsxs2(
236
+ "div",
237
+ {
238
+ role: "img",
239
+ "aria-label": `${title ? `${title}, ` : ""}${r.display}\xB0 ${point}`,
240
+ "data-threshold-level": r.band?.level,
241
+ style: { height: "100%", display: "flex", flexDirection: "column" },
242
+ children: [
243
+ /* @__PURE__ */ jsx2("div", { style: { flex: "1 1 0", minHeight: 0 }, children: /* @__PURE__ */ jsx2(EChart, { option }) }),
244
+ /* @__PURE__ */ jsx2("div", { style: { height: stripHeight, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }, children: /* @__PURE__ */ jsx2(
245
+ LevelReadout,
246
+ {
247
+ text: `${r.display}\xB0`,
248
+ unit: point,
249
+ color: r.levelColor,
250
+ size: { width: ws.size.width, height: stripHeight },
251
+ scale: ws.scale,
252
+ strings: r.strings
253
+ }
254
+ ) })
255
+ ]
256
+ }
257
+ );
258
+ }
259
+
260
+ // src/widgets/level-gauge.tsx
261
+ import { useMemo as useMemo3 } from "react";
262
+ import {
263
+ css as css3,
264
+ formatValue as formatValue2,
265
+ optNumber as optNumber3,
266
+ optString as optString3,
267
+ optThresholds as optThresholds3,
268
+ WidgetFrame as WidgetFrame3
269
+ } from "@devicechain/widget-sdk";
270
+ import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
271
+ var MUTED_BAND_OPACITY = 0.35;
272
+ function LevelGauge({ widget, data }) {
273
+ const thresholds = useMemo3(() => optThresholds3(widget.options, "thresholds"), [widget.options]);
274
+ const reading = useLevelReading(widget, data, {
275
+ thresholds,
276
+ unit: optString3(widget.options, "unit"),
277
+ precision: optNumber3(widget.options, "precision"),
278
+ range: readRange(optNumber3(widget.options, "min"), optNumber3(widget.options, "max"))
279
+ });
280
+ const title = optString3(widget.options, "title") ?? reading.name;
281
+ const percent = optString3(widget.options, "readout") === "percent";
282
+ return /* @__PURE__ */ jsx3(WidgetFrame3, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx3(TankBody, { reading, percent, scale }) });
283
+ }
284
+ function TankBody({ reading: r, percent, scale: ws }) {
285
+ const reason = emptyReason(r);
286
+ if (reason !== void 0 || !r.range?.ok || !r.fill) return /* @__PURE__ */ jsx3(LevelEmpty, { message: reason ?? r.strings.noData });
287
+ const range = r.range.range;
288
+ const fill = r.fill;
289
+ const wall = 2;
290
+ const stripe = ws.px("space-1");
291
+ const text = percent ? `${formatValue2(fill.ratio * 100, r.precision ?? 0)}` : r.display;
292
+ const unit = percent ? "%" : r.unit;
293
+ const bands = bandSpans(r.thresholds, range).flatMap((b) => {
294
+ const color = r.colorOf(b.level);
295
+ const active = fill.fraction >= b.from && (fill.fraction < b.to || b.to >= 1 && fill.fraction >= 1);
296
+ return color === void 0 ? [] : [{ from: b.from, to: b.to, color, active }];
297
+ });
298
+ const inner = { width: Math.max(0, ws.size.width - wall * 2 - stripe), height: Math.max(0, ws.size.height - wall * 2) };
299
+ return /* @__PURE__ */ jsxs3(
300
+ "div",
301
+ {
302
+ "data-threshold-level": r.band?.level,
303
+ "data-out-of-range": fill.position === "within" ? void 0 : fill.position,
304
+ "data-fill": fill.fraction,
305
+ style: { height: "100%", boxSizing: "border-box", display: "flex", gap: ws.px("space-1") },
306
+ children: [
307
+ /* @__PURE__ */ jsxs3(
308
+ "div",
309
+ {
310
+ role: "meter",
311
+ "aria-valuemin": range.min,
312
+ "aria-valuemax": range.max,
313
+ "aria-valuenow": range.min + fill.fraction * (range.max - range.min),
314
+ "aria-valuetext": `${r.display}${r.unit ? ` ${r.unit}` : ""}`,
315
+ style: {
316
+ position: "relative",
317
+ flex: "1 1 0",
318
+ minWidth: 0,
319
+ overflow: "hidden",
320
+ boxSizing: "border-box",
321
+ border: `${wall}px solid ${css3("border")}`,
322
+ borderRadius: ws.px("space-2"),
323
+ background: css3("muted")
324
+ },
325
+ children: [
326
+ /* @__PURE__ */ jsx3(
327
+ "div",
328
+ {
329
+ "data-liquid": "",
330
+ style: {
331
+ position: "absolute",
332
+ left: 0,
333
+ right: 0,
334
+ bottom: 0,
335
+ height: `${fill.fraction * 100}%`,
336
+ background: r.fillColor,
337
+ opacity: 0.55,
338
+ borderTop: fill.fraction > 0 && fill.fraction < 1 ? `2px solid ${r.fillColor}` : void 0
339
+ }
340
+ }
341
+ ),
342
+ /* @__PURE__ */ jsx3("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx3(
343
+ LevelReadout,
344
+ {
345
+ text,
346
+ unit,
347
+ color: r.levelColor,
348
+ position: fill.position,
349
+ size: inner,
350
+ scale: ws.scale,
351
+ strings: r.strings
352
+ }
353
+ ) })
354
+ ]
355
+ }
356
+ ),
357
+ /* @__PURE__ */ jsx3("div", { style: { position: "relative", width: stripe, flexShrink: 0, borderRadius: stripe / 2, background: css3("muted"), overflow: "hidden" }, children: bands.map((b, i) => /* @__PURE__ */ jsx3(
358
+ "div",
359
+ {
360
+ "data-band": "",
361
+ "data-band-active": b.active ? "" : void 0,
362
+ style: {
363
+ position: "absolute",
364
+ left: 0,
365
+ right: 0,
366
+ bottom: `${b.from * 100}%`,
367
+ height: `${(b.to - b.from) * 100}%`,
368
+ background: b.color,
369
+ opacity: b.active ? 1 : MUTED_BAND_OPACITY
370
+ }
371
+ },
372
+ i
373
+ )) })
374
+ ]
375
+ }
376
+ );
377
+ }
378
+
379
+ // src/widgets/linear-gauge.tsx
380
+ import { useMemo as useMemo4 } from "react";
381
+ import {
382
+ css as css4,
383
+ fontVar,
384
+ formatValue as formatValue3,
385
+ optNumber as optNumber4,
386
+ optString as optString4,
387
+ optThresholds as optThresholds4,
388
+ WidgetFrame as WidgetFrame4
389
+ } from "@devicechain/widget-sdk";
390
+ import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
391
+ function LinearGauge({ widget, data }) {
392
+ const thresholds = useMemo4(() => optThresholds4(widget.options, "thresholds"), [widget.options]);
393
+ const reading = useLevelReading(widget, data, {
394
+ thresholds,
395
+ unit: optString4(widget.options, "unit"),
396
+ precision: optNumber4(widget.options, "precision"),
397
+ range: readRange(optNumber4(widget.options, "min"), optNumber4(widget.options, "max"))
398
+ });
399
+ const title = optString4(widget.options, "title") ?? reading.name;
400
+ const thermometer = optString4(widget.options, "style") === "thermometer";
401
+ const vertical = thermometer || optString4(widget.options, "orientation") === "vertical";
402
+ return /* @__PURE__ */ jsx4(WidgetFrame4, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx4(LinearBody, { reading, thermometer, vertical, scale }) });
403
+ }
404
+ function LinearBody({ reading: r, thermometer, vertical, scale: ws }) {
405
+ const reason = emptyReason(r);
406
+ if (reason !== void 0 || !r.range?.ok || !r.fill) return /* @__PURE__ */ jsx4(LevelEmpty, { message: reason ?? r.strings.noData });
407
+ const range = r.range.range;
408
+ const fill = r.fill;
409
+ const { size, scale } = ws;
410
+ const thickness = ws.px("space-3");
411
+ const gap = ws.px("space-2");
412
+ const labelPx = ws.px("font-xs");
413
+ const minText = formatValue3(range.min, r.precision);
414
+ const maxText = formatValue3(range.max, r.precision);
415
+ const bulb = thermometer ? thickness * 2 : 0;
416
+ const bands = bandSpans(r.thresholds, range).flatMap((b) => {
417
+ const color = r.colorOf(b.level);
418
+ return color === void 0 ? [] : [{ from: b.from, to: b.to, color }];
419
+ });
420
+ const track = /* @__PURE__ */ jsx4(
421
+ Track,
422
+ {
423
+ vertical,
424
+ thickness,
425
+ fraction: fill.fraction,
426
+ color: r.fillColor,
427
+ bands,
428
+ bulb,
429
+ aria: {
430
+ min: range.min,
431
+ max: range.max,
432
+ now: range.min + fill.fraction * (range.max - range.min),
433
+ text: `${r.display}${r.unit ? ` ${r.unit}` : ""}`
434
+ }
435
+ }
436
+ );
437
+ const readout = (room) => /* @__PURE__ */ jsx4(
438
+ LevelReadout,
439
+ {
440
+ text: r.display,
441
+ unit: r.unit,
442
+ color: r.levelColor,
443
+ position: fill.position,
444
+ size: room,
445
+ scale,
446
+ strings: r.strings
447
+ }
448
+ );
449
+ const labelStyle = { fontSize: fontVar("xs"), color: css4("muted-foreground"), lineHeight: 1.1 };
450
+ const root = {
451
+ height: "100%",
452
+ boxSizing: "border-box",
453
+ overflow: "hidden",
454
+ display: "flex",
455
+ gap
456
+ };
457
+ const marks = {
458
+ "data-threshold-level": r.band?.level,
459
+ "data-out-of-range": fill.position === "within" ? void 0 : fill.position,
460
+ "data-fill": fill.fraction
461
+ };
462
+ if (!vertical) {
463
+ const trackRoom = thickness + labelPx * 1.4 + gap * 2;
464
+ return /* @__PURE__ */ jsxs4("div", { ...marks, style: { ...root, flexDirection: "column", justifyContent: "safe center" }, children: [
465
+ /* @__PURE__ */ jsx4("div", { style: { flex: "1 1 0", minHeight: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: readout({ width: size.width, height: Math.max(0, size.height - trackRoom) }) }),
466
+ track,
467
+ /* @__PURE__ */ jsxs4("div", { style: { display: "flex", justifyContent: "space-between", gap, ...labelStyle }, children: [
468
+ /* @__PURE__ */ jsx4("span", { children: minText }),
469
+ /* @__PURE__ */ jsx4("span", { children: maxText })
470
+ ] })
471
+ ] });
472
+ }
473
+ const labelRoom = Math.ceil(Math.max(minText.length, maxText.length) * labelPx * 0.62);
474
+ const sideRoom = Math.max(thickness, bulb) + labelRoom + gap * 2;
475
+ return /* @__PURE__ */ jsxs4("div", { ...marks, style: { ...root, flexDirection: "row", alignItems: "stretch" }, children: [
476
+ /* @__PURE__ */ jsxs4(
477
+ "div",
478
+ {
479
+ style: {
480
+ display: "flex",
481
+ flexDirection: "column",
482
+ justifyContent: "space-between",
483
+ alignItems: "flex-end",
484
+ paddingBottom: bulb,
485
+ ...labelStyle
486
+ },
487
+ children: [
488
+ /* @__PURE__ */ jsx4("span", { children: maxText }),
489
+ /* @__PURE__ */ jsx4("span", { children: minText })
490
+ ]
491
+ }
492
+ ),
493
+ track,
494
+ /* @__PURE__ */ jsx4("div", { style: { flex: "1 1 0", minWidth: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: readout({ width: Math.max(0, size.width - sideRoom), height: size.height }) })
495
+ ] });
496
+ }
497
+ function Track({ vertical, thickness, fraction, color, bands, bulb, aria }) {
498
+ const pct = (f) => `${f * 100}%`;
499
+ const radius = thickness / 2;
500
+ const tube = /* @__PURE__ */ jsxs4(
501
+ "div",
502
+ {
503
+ role: "meter",
504
+ "aria-valuemin": aria.min,
505
+ "aria-valuemax": aria.max,
506
+ "aria-valuenow": aria.now,
507
+ "aria-valuetext": aria.text,
508
+ style: {
509
+ position: "relative",
510
+ overflow: "hidden",
511
+ background: css4("muted"),
512
+ borderRadius: radius,
513
+ flexShrink: 0,
514
+ ...vertical ? { width: thickness, flex: "1 1 0", minHeight: 0 } : { width: "100%", height: thickness }
515
+ },
516
+ children: [
517
+ bands.map((b, i) => /* @__PURE__ */ jsx4(
518
+ "div",
519
+ {
520
+ "data-band": "",
521
+ style: {
522
+ position: "absolute",
523
+ background: b.color,
524
+ opacity: 0.35,
525
+ ...vertical ? { left: 0, right: 0, bottom: pct(b.from), height: pct(b.to - b.from) } : { top: 0, bottom: 0, left: pct(b.from), width: pct(b.to - b.from) }
526
+ }
527
+ },
528
+ i
529
+ )),
530
+ /* @__PURE__ */ jsx4(
531
+ "div",
532
+ {
533
+ "data-fill-bar": "",
534
+ style: {
535
+ position: "absolute",
536
+ background: color,
537
+ ...vertical ? { left: 0, right: 0, bottom: 0, height: pct(fraction) } : { top: 0, bottom: 0, left: 0, width: pct(fraction) }
538
+ }
539
+ }
540
+ ),
541
+ /* @__PURE__ */ jsx4(
542
+ "div",
543
+ {
544
+ "data-marker": "",
545
+ style: {
546
+ position: "absolute",
547
+ background: css4("foreground"),
548
+ ...vertical ? { left: 0, right: 0, height: 2, bottom: `calc(${pct(fraction)} - 1px)` } : { top: 0, bottom: 0, width: 2, left: `calc(${pct(fraction)} - 1px)` }
549
+ }
550
+ }
551
+ )
552
+ ]
553
+ }
554
+ );
555
+ if (!vertical) return tube;
556
+ return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", alignItems: "center", minHeight: 0, flex: "0 0 auto", width: Math.max(thickness, bulb) }, children: [
557
+ tube,
558
+ bulb > 0 ? /* @__PURE__ */ jsx4(
559
+ "div",
560
+ {
561
+ "data-bulb": "",
562
+ style: { width: bulb, height: bulb, borderRadius: "50%", background: color, marginTop: -radius, flexShrink: 0 }
563
+ }
564
+ ) : null
565
+ ] });
566
+ }
567
+
568
+ // src/widgets/progress-ring.tsx
569
+ import { useMemo as useMemo5 } from "react";
570
+ import {
571
+ css as css5,
572
+ formatValue as formatValue4,
573
+ optNumber as optNumber5,
574
+ optString as optString5,
575
+ optThresholds as optThresholds5,
576
+ WidgetFrame as WidgetFrame5
577
+ } from "@devicechain/widget-sdk";
578
+ import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
579
+ var RING_RADIUS = 42;
580
+ var RING_STROKE = 10;
581
+ var RING_CIRCUMFERENCE = 2 * Math.PI * RING_RADIUS;
582
+ var INNER = { width: 0.62, height: 0.34 };
583
+ function ProgressRing({ widget, data }) {
584
+ const thresholds = useMemo5(() => optThresholds5(widget.options, "thresholds"), [widget.options]);
585
+ const reading = useLevelReading(widget, data, {
586
+ thresholds,
587
+ unit: optString5(widget.options, "unit"),
588
+ precision: optNumber5(widget.options, "precision"),
589
+ range: readRange(optNumber5(widget.options, "min"), optNumber5(widget.options, "max"))
590
+ });
591
+ const title = optString5(widget.options, "title") ?? reading.name;
592
+ const percent = optString5(widget.options, "readout") !== "value";
593
+ return /* @__PURE__ */ jsx5(WidgetFrame5, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx5(RingBody, { reading, percent, scale }) });
594
+ }
595
+ function RingBody({ reading: r, percent, scale: ws }) {
596
+ const reason = emptyReason(r);
597
+ if (reason !== void 0 || !r.range?.ok || !r.fill) return /* @__PURE__ */ jsx5(LevelEmpty, { message: reason ?? r.strings.noData });
598
+ const range = r.range.range;
599
+ const fill = r.fill;
600
+ const text = percent ? formatValue4(fill.ratio * 100, r.precision ?? 0) : r.display;
601
+ const unit = percent ? "%" : r.unit;
602
+ const side = Math.max(0, Math.min(ws.size.width, ws.size.height));
603
+ return /* @__PURE__ */ jsx5(
604
+ "div",
605
+ {
606
+ role: "meter",
607
+ "aria-valuemin": range.min,
608
+ "aria-valuemax": range.max,
609
+ "aria-valuenow": range.min + fill.fraction * (range.max - range.min),
610
+ "aria-valuetext": `${r.display}${r.unit ? ` ${r.unit}` : ""}`,
611
+ "data-threshold-level": r.band?.level,
612
+ "data-out-of-range": fill.position === "within" ? void 0 : fill.position,
613
+ "data-fill": fill.fraction,
614
+ style: { height: "100%", display: "flex", alignItems: "center", justifyContent: "center" },
615
+ children: /* @__PURE__ */ jsxs5("div", { style: { position: "relative", width: side || "100%", height: side || "100%", aspectRatio: "1" }, children: [
616
+ /* @__PURE__ */ jsxs5("svg", { viewBox: "0 0 100 100", width: "100%", height: "100%", "aria-hidden": "true", style: { display: "block" }, children: [
617
+ /* @__PURE__ */ jsx5("circle", { cx: "50", cy: "50", r: RING_RADIUS, fill: "none", strokeWidth: RING_STROKE, style: { stroke: css5("muted") } }),
618
+ /* @__PURE__ */ jsx5(
619
+ "circle",
620
+ {
621
+ "data-ring-fill": "",
622
+ cx: "50",
623
+ cy: "50",
624
+ r: RING_RADIUS,
625
+ fill: "none",
626
+ strokeWidth: RING_STROKE,
627
+ strokeDasharray: `${fill.fraction * RING_CIRCUMFERENCE} ${RING_CIRCUMFERENCE}`,
628
+ transform: "rotate(-90 50 50)",
629
+ style: { stroke: r.fillColor }
630
+ }
631
+ )
632
+ ] }),
633
+ /* @__PURE__ */ jsx5("div", { style: { position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx5(
634
+ LevelReadout,
635
+ {
636
+ text,
637
+ unit,
638
+ color: r.levelColor,
639
+ position: fill.position,
640
+ size: { width: side * INNER.width, height: side * INNER.height },
641
+ scale: ws.scale,
642
+ strings: r.strings
643
+ }
644
+ ) })
645
+ ] })
646
+ }
647
+ );
648
+ }
649
+
650
+ // src/widgets/signal-bars.tsx
651
+ import { useMemo as useMemo6 } from "react";
652
+ import {
653
+ css as css6,
654
+ optNumber as optNumber6,
655
+ optString as optString6,
656
+ optThresholds as optThresholds6,
657
+ WidgetFrame as WidgetFrame6
658
+ } from "@devicechain/widget-sdk";
659
+ import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
660
+ var SIGNAL_MIN_BARS = 2;
661
+ var VIEW2 = { w: 100, h: 60 };
662
+ function SignalBars({ widget, data }) {
663
+ const thresholds = useMemo6(() => optThresholds6(widget.options, "thresholds"), [widget.options]);
664
+ const reading = useLevelReading(widget, data, {
665
+ thresholds,
666
+ unit: optString6(widget.options, "unit"),
667
+ precision: optNumber6(widget.options, "precision")
668
+ });
669
+ const title = optString6(widget.options, "title") ?? reading.name;
670
+ return /* @__PURE__ */ jsx6(WidgetFrame6, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx6(BarsBody, { reading, scale }) });
671
+ }
672
+ function BarsBody({ reading: r, scale: ws }) {
673
+ if (r.thresholds.length < SIGNAL_MIN_BARS) return /* @__PURE__ */ jsx6(LevelEmpty, { message: r.strings.setThresholds });
674
+ const bars = litBars(r.thresholds, r.value);
675
+ if (!bars) return /* @__PURE__ */ jsx6(LevelEmpty, { message: r.strings.noData });
676
+ const n = bars.total;
677
+ const barW = VIEW2.w / (n + (n - 1) / 3);
678
+ const gapW = barW / 3;
679
+ const readoutHeight = Math.max(ws.px("font-md") * 1.6, ws.size.height * 0.34);
680
+ return /* @__PURE__ */ jsxs6(
681
+ "div",
682
+ {
683
+ "data-threshold-level": r.band?.level,
684
+ "data-bars-lit": bars.lit,
685
+ "data-bars-total": bars.total,
686
+ style: { height: "100%", boxSizing: "border-box", display: "flex", flexDirection: "column", gap: ws.px("space-1") },
687
+ children: [
688
+ /* @__PURE__ */ jsx6("div", { style: { flex: "1 1 0", minHeight: 0 }, children: /* @__PURE__ */ jsx6(
689
+ "svg",
690
+ {
691
+ role: "meter",
692
+ "aria-valuemin": 0,
693
+ "aria-valuemax": bars.total,
694
+ "aria-valuenow": bars.lit,
695
+ "aria-valuetext": `${bars.lit}/${bars.total}`,
696
+ viewBox: `0 0 ${VIEW2.w} ${VIEW2.h}`,
697
+ preserveAspectRatio: "xMidYMax meet",
698
+ style: { width: "100%", height: "100%", display: "block" },
699
+ children: Array.from({ length: n }, (_, i) => {
700
+ const h = VIEW2.h * (i + 1) / n;
701
+ const lit = i < bars.lit;
702
+ return /* @__PURE__ */ jsx6(
703
+ "rect",
704
+ {
705
+ "data-bar": lit ? "lit" : "unlit",
706
+ x: i * (barW + gapW),
707
+ y: VIEW2.h - h,
708
+ width: barW,
709
+ height: h,
710
+ rx: Math.min(3, barW / 3),
711
+ style: { fill: lit ? r.fillColor : css6("muted") }
712
+ },
713
+ i
714
+ );
715
+ })
716
+ }
717
+ ) }),
718
+ /* @__PURE__ */ jsx6("div", { style: { flex: "0 0 auto", height: readoutHeight, display: "flex", alignItems: "center", justifyContent: "center" }, children: /* @__PURE__ */ jsx6(
719
+ LevelReadout,
720
+ {
721
+ text: r.display,
722
+ unit: r.unit,
723
+ color: r.levelColor,
724
+ size: { width: ws.size.width, height: readoutHeight },
725
+ scale: ws.scale,
726
+ strings: r.strings
727
+ }
728
+ ) })
729
+ ]
730
+ }
731
+ );
732
+ }
733
+
734
+ // src/widgets/status-light.tsx
735
+ import { useMemo as useMemo7 } from "react";
736
+ import {
737
+ css as css7,
738
+ fontVar as fontVar2,
739
+ optNumber as optNumber7,
740
+ optString as optString7,
741
+ optThresholds as optThresholds7,
742
+ WidgetFrame as WidgetFrame7
743
+ } from "@devicechain/widget-sdk";
744
+ import { jsx as jsx7, jsxs as jsxs7 } from "react/jsx-runtime";
745
+ function StatusLight({ widget, data }) {
746
+ const thresholds = useMemo7(() => optThresholds7(widget.options, "thresholds"), [widget.options]);
747
+ const reading = useLevelReading(widget, data, {
748
+ thresholds,
749
+ unit: optString7(widget.options, "unit"),
750
+ precision: optNumber7(widget.options, "precision")
751
+ });
752
+ const title = optString7(widget.options, "title") ?? reading.name;
753
+ const onAt = optNumber7(widget.options, "onAt");
754
+ return /* @__PURE__ */ jsx7(WidgetFrame7, { title, sampleTime: reading.sample?.occurredTime ?? null, children: (scale) => /* @__PURE__ */ jsx7(LampBody, { reading, onAt, scale }) });
755
+ }
756
+ function lampState(r, onAt) {
757
+ if (r.value === void 0) return { lit: void 0, caption: r.strings.noData };
758
+ const lit = onAt === void 0 ? true : r.value >= onAt;
759
+ const fallback = onAt === void 0 ? `${r.display}${r.unit ? ` ${r.unit}` : ""}` : lit ? r.strings.on : r.strings.off;
760
+ return { lit, caption: r.band?.label ?? fallback };
761
+ }
762
+ function LampBody({ reading: r, onAt, scale: ws }) {
763
+ const { lit, caption } = lampState(r, onAt);
764
+ const captionRoom = ws.px("font-md") * 2.8;
765
+ const smallest = ws.px("space-4");
766
+ const measured = ws.size.width > 0 && ws.size.height > 0;
767
+ const beside = measured && ws.size.height - captionRoom < smallest * 2;
768
+ const diameter = !measured ? smallest * 3 : beside ? Math.max(Math.min(smallest, ws.size.height), Math.floor(Math.min(ws.size.height, ws.size.width * 0.4) * 0.8)) : Math.max(smallest, Math.floor(Math.min(ws.size.width, ws.size.height - captionRoom) * 0.8));
769
+ const color = r.fillColor;
770
+ return /* @__PURE__ */ jsxs7(
771
+ "div",
772
+ {
773
+ "data-threshold-level": lit ? r.band?.level : void 0,
774
+ "data-lit": lit === void 0 ? void 0 : String(lit),
775
+ "data-lamp-layout": beside ? "beside" : "under",
776
+ style: {
777
+ height: "100%",
778
+ boxSizing: "border-box",
779
+ overflow: "hidden",
780
+ display: "flex",
781
+ flexDirection: beside ? "row" : "column",
782
+ alignItems: "center",
783
+ justifyContent: "safe center",
784
+ gap: ws.px("space-2"),
785
+ textAlign: "center"
786
+ },
787
+ children: [
788
+ /* @__PURE__ */ jsx7(
789
+ "div",
790
+ {
791
+ "data-lamp": "",
792
+ style: {
793
+ width: diameter,
794
+ height: diameter,
795
+ flexShrink: 0,
796
+ borderRadius: "50%",
797
+ boxSizing: "border-box",
798
+ background: lit ? color : "transparent",
799
+ border: `2px solid ${lit ? color : css7("muted-foreground")}`,
800
+ boxShadow: lit ? `0 0 ${Math.round(diameter / 4)}px ${color}` : "none"
801
+ }
802
+ }
803
+ ),
804
+ /* @__PURE__ */ jsx7(
805
+ "div",
806
+ {
807
+ "data-lamp-caption": "",
808
+ style: {
809
+ fontSize: fontVar2("md"),
810
+ fontWeight: 600,
811
+ lineHeight: 1.2,
812
+ color: lit === void 0 ? css7("muted-foreground") : css7("foreground"),
813
+ overflowWrap: "anywhere",
814
+ maxWidth: "100%",
815
+ minWidth: 0
816
+ },
817
+ children: caption
818
+ }
819
+ )
820
+ ]
821
+ }
822
+ );
823
+ }
824
+ export {
825
+ Battery,
826
+ Compass,
827
+ LevelGauge,
828
+ LinearGauge,
829
+ ProgressRing,
830
+ SignalBars,
831
+ StatusLight
832
+ };
833
+ //# sourceMappingURL=level-widgets-MJB7TOLU.js.map