@devicechain/widgets 0.23.0-rc.1 → 0.24.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (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
package/dist/index.js CHANGED
@@ -1,24 +1,30 @@
1
1
  import {
2
2
  Table
3
3
  } from "./chunks/chunk-USRYKW2J.js";
4
+ import {
5
+ ChunkLoadingFrame,
6
+ lazyChunk
7
+ } from "./chunks/chunk-BMFQOTYG.js";
4
8
  import {
5
9
  alarmSubscription,
6
10
  commandSubscription,
11
+ groupsAlarmsByDeviceType,
12
+ measurementIsMembersOnly,
7
13
  measurementSpan,
8
14
  measurementWindow,
9
- membersMode
10
- } from "./chunks/chunk-2D7JNKFE.js";
11
- import {
12
- accessStrings
13
- } from "./chunks/chunk-BRH66T5F.js";
15
+ membersMode,
16
+ presenceMode,
17
+ wantsEventMarkers
18
+ } from "./chunks/chunk-JFH4QMLA.js";
14
19
  import {
15
20
  AlarmCount,
21
+ AlarmSeverity,
16
22
  AlarmTable,
17
23
  highestSeverity,
18
24
  severityColor,
19
25
  severityLabel,
20
26
  severityRank
21
- } from "./chunks/chunk-6OGI4UFJ.js";
27
+ } from "./chunks/chunk-546TK2QG.js";
22
28
  import {
23
29
  CommandButton,
24
30
  buildPayload,
@@ -30,6 +36,12 @@ import {
30
36
  parseParameterSchema,
31
37
  validateParams
32
38
  } from "./chunks/chunk-XSLFHCNI.js";
39
+ import {
40
+ useHeld
41
+ } from "./chunks/chunk-XNEBAY4J.js";
42
+ import {
43
+ accessStrings
44
+ } from "./chunks/chunk-BRH66T5F.js";
33
45
  import {
34
46
  EntitySelector
35
47
  } from "./chunks/chunk-FM7OI5TQ.js";
@@ -64,7 +76,7 @@ import {
64
76
  WidgetTimeRangeProvider,
65
77
  WidgetTokenStyles
66
78
  } from "@devicechain/widget-sdk";
67
- import { Fragment, useCallback as useCallback2, useEffect as useEffect4, useLayoutEffect, useMemo as useMemo5, useRef as useRef2, useState as useState6 } from "react";
79
+ import { Fragment, useCallback as useCallback2, useEffect as useEffect3, useLayoutEffect, useMemo as useMemo5, useRef as useRef2, useState as useState4 } from "react";
68
80
 
69
81
  // src/connected-widget.tsx
70
82
  import {
@@ -79,22 +91,7 @@ import {
79
91
  useWidgetTimeRange as useWidgetTimeRange2
80
92
  } from "@devicechain/widget-sdk";
81
93
  import { isForbiddenError } from "@devicechain/client";
82
- import { useEffect as useEffect3, useMemo as useMemo4, useState as useState5 } from "react";
83
-
84
- // src/use-held.ts
85
- import { useState } from "react";
86
- function useHeld(value, hold, ready) {
87
- const [held, setHeld] = useState(void 0);
88
- if (!hold) {
89
- if (held !== void 0) setHeld(void 0);
90
- return value;
91
- }
92
- if (held === void 0 || ready(held.value) !== ready(value)) {
93
- setHeld({ value });
94
- return value;
95
- }
96
- return held.value;
97
- }
94
+ import { useEffect as useEffect2, useMemo as useMemo4, useState as useState3 } from "react";
98
95
 
99
96
  // src/builtin-registry.ts
100
97
  import { createWidgetRegistry } from "@devicechain/widget-sdk";
@@ -108,15 +105,14 @@ import {
108
105
  createWidgetCatalog,
109
106
  defineWidgetDescriptor
110
107
  } from "@devicechain/dashboards";
108
+ var defineRuntimeDescriptor = defineWidgetDescriptor;
111
109
  var TITLE = {
112
110
  kind: "string",
113
- labelId: "widgetLabelTitle",
114
- doc: "Heading shown in the widget frame. Blank hides the frame heading."
111
+ labelId: "widgetLabelTitle"
115
112
  };
116
113
  var TITLE_OR_MEASUREMENT = {
117
114
  kind: "string",
118
- labelId: "widgetLabelTitle",
119
- doc: "Heading shown in the widget frame. Defaults to the displayed measurement name."
115
+ labelId: "widgetLabelTitle"
120
116
  };
121
117
  var PRECISION = {
122
118
  kind: "number",
@@ -124,21 +120,18 @@ var PRECISION = {
124
120
  min: 0,
125
121
  max: 100,
126
122
  labelId: "widgetLabelPrecision",
127
- helpId: "widgetHintPrecision",
128
- doc: "Decimal places to round displayed values to. Unset rounds to at most 3 decimals (4 significant digits under 1)."
123
+ helpId: "widgetHintPrecision"
129
124
  };
130
125
  var THRESHOLDS = {
131
126
  kind: "list",
132
127
  maxItems: 12,
133
128
  labelId: "widgetLabelThresholds",
134
129
  helpId: "widgetHintThresholds",
135
- doc: "Severity bands over the value. Each entry starts a band at its value that runs to the next entry; below the first, and in a normal band, the widget keeps its own colour.",
136
130
  item: {
137
131
  value: {
138
132
  kind: "number",
139
133
  required: true,
140
- labelId: "widgetLabelThresholdValue",
141
- doc: "The value at which this band starts."
134
+ labelId: "widgetLabelThresholdValue"
142
135
  },
143
136
  level: {
144
137
  kind: "enum",
@@ -152,91 +145,78 @@ var THRESHOLDS = {
152
145
  major: "widgetSeverityMajor",
153
146
  critical: "widgetSeverityCritical"
154
147
  },
155
- placeholderId: "widgetPlaceholderThresholdLevel",
156
- doc: "The severity of this band. The theme decides the colour; normal has none."
148
+ placeholderId: "widgetPlaceholderThresholdLevel"
157
149
  },
158
150
  label: {
159
151
  kind: "string",
160
- labelId: "widgetLabelThresholdLabel",
161
- doc: "Text shown on the chart line for this threshold. Defaults to its value."
152
+ labelId: "widgetLabelThresholdLabel"
162
153
  }
163
154
  }
164
155
  };
165
156
  var FLASH_ON_CHANGE = {
166
157
  kind: "boolean",
167
158
  labelId: "widgetFlashOnChange",
168
- helpId: "widgetFlashOnChangeHint",
169
- doc: "Briefly tint a value green on a rise and red on a fall when it changes."
159
+ helpId: "widgetFlashOnChangeHint"
170
160
  };
171
161
  var WINDOW = {
172
162
  kind: "number",
173
163
  integer: true,
174
164
  min: 1,
175
165
  labelId: "widgetLabelWindow",
176
- helpId: "widgetHintWindow",
177
- doc: "Samples retained across ALL of the widget's measurements before the oldest are dropped. Bounds memory; only the chart draws them. Default 300."
166
+ helpId: "widgetHintWindow"
178
167
  };
179
168
  var STALE_AFTER_SECONDS = {
180
169
  kind: "number",
181
170
  integer: true,
182
171
  min: 1,
183
172
  labelId: "widgetLabelStaleAfter",
184
- helpId: "widgetHintStaleAfter",
185
- doc: "Seconds after which the newest sample is called stale: the value is greyed out and badged. Unset shows the sample age only and never marks the widget stale."
173
+ helpId: "widgetHintStaleAfter"
186
174
  };
187
175
  var UNIT = {
188
176
  kind: "string",
189
- labelId: "widgetLabelUnit",
190
- doc: "Unit suffix shown beside the value."
177
+ labelId: "widgetLabelUnit"
191
178
  };
192
179
  var SCALE_MIN = {
193
180
  kind: "number",
194
181
  required: true,
195
182
  labelId: "widgetLabelMin",
196
- helpId: "widgetHintScaleMin",
197
- doc: "Value at the empty end of the scale. Required: the widget does not guess a range."
183
+ helpId: "widgetHintScaleMin"
198
184
  };
199
185
  var SCALE_MAX = {
200
186
  kind: "number",
201
187
  required: true,
202
188
  labelId: "widgetLabelMax",
203
- helpId: "widgetHintScaleMax",
204
- doc: "Value at the full end of the scale. Required, and above min."
189
+ helpId: "widgetHintScaleMax"
205
190
  };
206
191
  var CONTROL_COMMAND_NAME = {
207
192
  kind: "string",
208
193
  required: true,
209
194
  labelId: "widgetCommandHeading",
210
195
  helpId: "widgetCommandFieldHint",
211
- control: "command-vocabulary",
212
- doc: "Command key issued to the target device. Without it the control cannot send."
196
+ control: "command-vocabulary"
213
197
  };
214
198
  var CONTROL_COMMAND_LABEL = {
215
199
  kind: "string",
216
200
  labelId: "widgetCommandHeading",
217
201
  helpId: "widgetCommandFieldHint",
218
- control: "command-vocabulary",
219
- doc: "Name of the control for assistive technology. Defaults to the command key."
202
+ control: "command-vocabulary"
220
203
  };
221
204
  var CONTROL_PARAMETER_SCHEMA = {
222
205
  kind: "string",
223
206
  format: "json",
224
207
  labelId: "widgetCommandHeading",
225
208
  helpId: "widgetCommandFieldHint",
226
- control: "command-vocabulary",
227
- doc: "The command's parameter descriptors, baked in as JSON at author time. Gives the control its range and its refusals."
209
+ control: "command-vocabulary"
228
210
  };
229
211
  var CONTROL_PARAMETER = {
230
212
  kind: "string",
231
213
  labelId: "widgetLabelParameter",
232
- helpId: "widgetHintControlParameter",
233
- doc: "Which parameter of the command this control sets. May be left blank when the command has exactly one parameter of the right type."
214
+ helpId: "widgetHintControlParameter"
234
215
  };
235
216
  var MEASUREMENT = {
236
217
  kind: "string",
237
218
  labelId: "widgetLabelMeasurement",
238
- helpId: "widgetHintMeasurement",
239
- doc: "Which measurement to display. Defaults to the first the datasource selects."
219
+ helpId: "widgetHintMeasurement"
240
220
  };
241
221
  var ALARM_STATE = {
242
222
  kind: "enum",
@@ -244,8 +224,7 @@ var ALARM_STATE = {
244
224
  labelId: "widgetLabelState",
245
225
  helpId: "widgetStateHint",
246
226
  valueLabelIds: { ACTIVE: "widgetAlarmStateActive", CLEARED: "widgetAlarmStateCleared" },
247
- placeholderId: "widgetPlaceholderAny",
248
- doc: "Show only alarms in this state. Unset shows every state."
227
+ placeholderId: "widgetPlaceholderAny"
249
228
  };
250
229
  var ALARM_SEVERITY = {
251
230
  kind: "enum",
@@ -253,8 +232,7 @@ var ALARM_SEVERITY = {
253
232
  labelId: "widgetLabelSeverity",
254
233
  helpId: "widgetSeverityHint",
255
234
  valueLabelIds: { CRITICAL: "widgetAlarmSeverityCritical", MAJOR: "widgetAlarmSeverityMajor", MINOR: "widgetAlarmSeverityMinor", WARNING: "widgetAlarmSeverityWarning", INDETERMINATE: "widgetAlarmSeverityIndeterminate" },
256
- placeholderId: "widgetPlaceholderAny",
257
- doc: "Show only alarms at this severity. Unset shows every severity."
235
+ placeholderId: "widgetPlaceholderAny"
258
236
  };
259
237
  var ALARM_ACKNOWLEDGED = {
260
238
  kind: "enum",
@@ -262,15 +240,13 @@ var ALARM_ACKNOWLEDGED = {
262
240
  labelId: "widgetLabelAcknowledged",
263
241
  helpId: "widgetAcknowledgedHint",
264
242
  valueLabelIds: { true: "widgetAlarmAckAcknowledged", false: "widgetAlarmAckUnacknowledged" },
265
- placeholderId: "widgetPlaceholderAny",
266
- doc: "Show only acknowledged ('true') or unacknowledged ('false') alarms. Unset shows both."
243
+ placeholderId: "widgetPlaceholderAny"
267
244
  };
268
245
  var SELECTION_TARGET = {
269
246
  kind: "string",
270
247
  labelId: "widgetLabelTargetSlot",
271
248
  helpId: "widgetTargetSlotHint",
272
- control: "slot-target",
273
- doc: "Name of the slot this widget re-points when the viewer picks an entity."
249
+ control: "slot-target"
274
250
  };
275
251
  var VERSION = "1.0.0";
276
252
  var ALL_SELECTORS = ["device", "anchor", "slot"];
@@ -281,38 +257,69 @@ var ALARM_FILTERS = {
281
257
  severity: { option: "severity" },
282
258
  acknowledged: { option: "acknowledged" }
283
259
  };
260
+ var MARK_ALARMS = {
261
+ kind: "boolean",
262
+ labelId: "widgetLabelMarkAlarms",
263
+ helpId: "widgetHintMarkAlarms"
264
+ };
265
+ var MARK_ALARMS_SCOPE = {
266
+ kind: "enum",
267
+ values: ["series", "device"],
268
+ labelId: "widgetLabelMarkAlarmsScope",
269
+ helpId: "widgetHintMarkAlarmsScope",
270
+ valueLabelIds: { series: "widgetMarkAlarmsScopeSeries", device: "widgetMarkAlarmsScopeDevice" },
271
+ placeholderId: "widgetPlaceholderWholeDevice"
272
+ };
273
+ var MARK_COMMANDS = {
274
+ kind: "boolean",
275
+ labelId: "widgetLabelMarkCommands",
276
+ helpId: "widgetHintMarkCommands"
277
+ };
278
+ var CHART_AXIS_MIN = {
279
+ kind: "number",
280
+ labelId: "widgetLabelMin",
281
+ helpId: "widgetHintChartAxisMin"
282
+ };
283
+ var CHART_AXIS_MAX = {
284
+ kind: "number",
285
+ labelId: "widgetLabelMax",
286
+ helpId: "widgetHintChartAxisMax"
287
+ };
288
+ var MAX_SERIES = {
289
+ kind: "number",
290
+ integer: true,
291
+ min: 1,
292
+ labelId: "widgetLabelMaxSeries",
293
+ helpId: "widgetHintMaxSeries"
294
+ };
284
295
  var WINDOW_SECONDS = {
285
296
  kind: "number",
286
297
  integer: true,
287
298
  min: 60,
288
299
  max: 604800,
289
300
  labelId: "widgetLabelWindowSeconds",
290
- helpId: "widgetHintWindowSeconds",
291
- doc: "Seconds of history the widget covers, back from now. Leave unset to follow the board\u2019s time range; set it to pin this widget to a fixed span that the viewer\u2019s range picker does not change. 60 to 604800."
301
+ helpId: "widgetHintWindowSeconds"
292
302
  };
293
303
  var CATEGORY_SORT = {
294
304
  kind: "enum",
295
305
  values: ["none", "ascending", "descending"],
296
306
  labelId: "widgetLabelSort",
297
307
  valueLabelIds: { none: "widgetSortNone", ascending: "widgetSortAscending", descending: "widgetSortDescending" },
298
- placeholderId: "widgetPlaceholderDefault",
299
- doc: "Order of the categories. Default: as the data source lists them."
308
+ placeholderId: "widgetPlaceholderDefault"
300
309
  };
301
310
  var FLEET_MEASUREMENT = {
302
311
  kind: "string",
303
312
  required: true,
304
- labelId: "widgetLabelMeasurement",
305
- doc: "The measurement column this entry applies to."
313
+ labelId: "widgetLabelMeasurement"
306
314
  };
307
315
  var FLEET_COLUMNS = {
308
316
  kind: "list",
309
317
  maxItems: 24,
310
318
  labelId: "widgetLabelColumns",
311
319
  helpId: "widgetHintColumns",
312
- doc: "Heading, unit and precision per measurement column; a column with none shows its name.",
313
320
  item: {
314
321
  measurement: FLEET_MEASUREMENT,
315
- label: { kind: "string", labelId: "widgetLabelColumnHeading", doc: "Column heading. Defaults to the measurement name." },
322
+ label: { kind: "string", labelId: "widgetLabelColumnHeading" },
316
323
  unit: { ...UNIT },
317
324
  precision: { ...PRECISION }
318
325
  }
@@ -325,29 +332,47 @@ var FLEET_THRESHOLDS = {
325
332
  }
326
333
  };
327
334
  var builtinWidgetDescriptors = {
328
- "timeseries-chart": defineWidgetDescriptor({
335
+ "timeseries-chart": defineRuntimeDescriptor({
329
336
  id: "timeseries-chart",
330
337
  version: VERSION,
331
338
  sdkApiVersion: 1,
332
339
  optionsVersion: 2,
333
340
  display: { title: "Timeseries chart", category: "Telemetry", titleId: "widgetTypeTimeseriesChart", categoryId: "widgetCategoryTelemetry" },
334
341
  channel: "measurement",
335
- datasource: { mode: "required", selectors: ONE_DEVICE },
336
- binding: { windowSeconds: { option: "windowSeconds" } },
342
+ // ALL_SELECTORS because of the compare mode (groupBy device): a group bound with the default mode
343
+ // is refused in words at runtime, exactly as for the bar chart.
344
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
345
+ binding: { windowSeconds: { option: "windowSeconds" }, members: { option: "groupBy", equals: "device" } },
337
346
  options: {
338
347
  title: TITLE,
339
348
  unit: UNIT,
340
349
  precision: PRECISION,
341
350
  thresholds: THRESHOLDS,
342
351
  staleAfterSeconds: STALE_AFTER_SECONDS,
343
- windowSeconds: WINDOW_SECONDS
352
+ windowSeconds: WINDOW_SECONDS,
353
+ groupBy: {
354
+ kind: "enum",
355
+ values: ["measurement", "device"],
356
+ labelId: "widgetLabelSeriesBy",
357
+ helpId: "widgetHintSeriesBy",
358
+ valueLabelIds: { measurement: "widgetGroupByMeasurement", device: "widgetGroupByDevice" },
359
+ placeholderId: "widgetPlaceholderDefault"
360
+ },
361
+ measurement: MEASUREMENT,
362
+ maxSeries: MAX_SERIES,
363
+ markAlarms: MARK_ALARMS,
364
+ markAlarmsScope: MARK_ALARMS_SCOPE,
365
+ markCommands: MARK_COMMANDS,
366
+ min: CHART_AXIS_MIN,
367
+ max: CHART_AXIS_MAX
344
368
  },
345
- defaults: { title: "Timeseries chart" },
369
+ constraints: [{ kind: "greater-than", key: "max", than: "min" }],
370
+ defaults: { title: "Timeseries chart", markAlarmsScope: "series" },
346
371
  interactions: {},
347
372
  freshness: true,
348
373
  layout: { defaultSpan: { cols: 6, rows: 4 }, minSpan: { cols: 4, rows: 3 } }
349
374
  }),
350
- "latest-card": defineWidgetDescriptor({
375
+ "latest-card": defineRuntimeDescriptor({
351
376
  id: "latest-card",
352
377
  version: VERSION,
353
378
  sdkApiVersion: 1,
@@ -361,8 +386,7 @@ var builtinWidgetDescriptors = {
361
386
  measurement: {
362
387
  kind: "string",
363
388
  labelId: "widgetLabelMeasurement",
364
- helpId: "widgetHintMeasurement",
365
- doc: "Which measurement to display. Defaults to the first the datasource selects."
389
+ helpId: "widgetHintMeasurement"
366
390
  },
367
391
  unit: UNIT,
368
392
  precision: PRECISION,
@@ -376,7 +400,7 @@ var builtinWidgetDescriptors = {
376
400
  freshness: true,
377
401
  layout: { defaultSpan: { cols: 3, rows: 2 }, minSpan: { cols: 3, rows: 2 }, maxSpan: { cols: 6, rows: 4 } }
378
402
  }),
379
- gauge: defineWidgetDescriptor({
403
+ gauge: defineRuntimeDescriptor({
380
404
  id: "gauge",
381
405
  version: VERSION,
382
406
  sdkApiVersion: 1,
@@ -390,18 +414,15 @@ var builtinWidgetDescriptors = {
390
414
  measurement: {
391
415
  kind: "string",
392
416
  labelId: "widgetLabelMeasurement",
393
- helpId: "widgetHintMeasurement",
394
- doc: "Which measurement to display. Defaults to the first the datasource selects."
417
+ helpId: "widgetHintMeasurement"
395
418
  },
396
419
  min: {
397
420
  kind: "number",
398
- labelId: "widgetLabelMin",
399
- doc: "Low end of the gauge scale. Default 0."
421
+ labelId: "widgetLabelMin"
400
422
  },
401
423
  max: {
402
424
  kind: "number",
403
- labelId: "widgetLabelMax",
404
- doc: "High end of the gauge scale. Default 100."
425
+ labelId: "widgetLabelMax"
405
426
  },
406
427
  unit: UNIT,
407
428
  precision: PRECISION,
@@ -416,7 +437,7 @@ var builtinWidgetDescriptors = {
416
437
  freshness: true,
417
438
  layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 6, rows: 6 } }
418
439
  }),
419
- table: defineWidgetDescriptor({
440
+ table: defineRuntimeDescriptor({
420
441
  id: "table",
421
442
  version: VERSION,
422
443
  sdkApiVersion: 1,
@@ -439,7 +460,7 @@ var builtinWidgetDescriptors = {
439
460
  }),
440
461
  // label and image are static: the `none` channel, no datasource, no subscription and no
441
462
  // history seed.
442
- label: defineWidgetDescriptor({
463
+ label: defineRuntimeDescriptor({
443
464
  id: "label",
444
465
  version: VERSION,
445
466
  sdkApiVersion: 1,
@@ -453,30 +474,27 @@ var builtinWidgetDescriptors = {
453
474
  text: {
454
475
  kind: "string",
455
476
  required: true,
456
- labelId: "widgetLabelText",
457
- doc: "The text to show. Rendered as plain text \u2014 markup is never interpreted."
477
+ labelId: "widgetLabelText"
458
478
  },
459
479
  align: {
460
480
  kind: "enum",
461
481
  values: ["left", "center", "right"],
462
482
  labelId: "widgetLabelAlign",
463
483
  valueLabelIds: { left: "widgetAlignLeft", center: "widgetAlignCenter", right: "widgetAlignRight" },
464
- placeholderId: "widgetPlaceholderDefault",
465
- doc: "Horizontal alignment of the text. Default center."
484
+ placeholderId: "widgetPlaceholderDefault"
466
485
  },
467
486
  fontSize: {
468
487
  kind: "number",
469
488
  min: 1,
470
489
  labelId: "widgetLabelFontSize",
471
- helpId: "widgetHintFontSize",
472
- doc: "Text size in pixels at the comfortable density; it scales with the board density. Default 16."
490
+ helpId: "widgetHintFontSize"
473
491
  }
474
492
  },
475
493
  defaults: { text: "New label" },
476
494
  interactions: {},
477
495
  layout: { defaultSpan: { cols: 3, rows: 1 }, minSpan: { cols: 1, rows: 1 }, maxSpan: { cols: 24, rows: 3 } }
478
496
  }),
479
- image: defineWidgetDescriptor({
497
+ image: defineRuntimeDescriptor({
480
498
  id: "image",
481
499
  version: VERSION,
482
500
  sdkApiVersion: 1,
@@ -490,28 +508,25 @@ var builtinWidgetDescriptors = {
490
508
  url: {
491
509
  kind: "string",
492
510
  required: true,
493
- labelId: "widgetLabelImageUrl",
494
- doc: "Source URL of the image."
511
+ labelId: "widgetLabelImageUrl"
495
512
  },
496
513
  alt: {
497
514
  kind: "string",
498
- labelId: "widgetLabelAltText",
499
- doc: "Alternative text describing the image for assistive technology."
515
+ labelId: "widgetLabelAltText"
500
516
  },
501
517
  fit: {
502
518
  kind: "enum",
503
519
  values: ["contain", "cover", "fill"],
504
520
  labelId: "widgetLabelFit",
505
521
  valueLabelIds: { contain: "widgetFitContain", cover: "widgetFitCover", fill: "widgetFitFill" },
506
- placeholderId: "widgetPlaceholderDefault",
507
- doc: "How the image fills its box. Default contain."
522
+ placeholderId: "widgetPlaceholderDefault"
508
523
  }
509
524
  },
510
525
  defaults: { title: "Image" },
511
526
  interactions: {},
512
527
  layout: { defaultSpan: { cols: 3, rows: 2 }, minSpan: { cols: 1, rows: 1 } }
513
528
  }),
514
- "alarm-table": defineWidgetDescriptor({
529
+ "alarm-table": defineRuntimeDescriptor({
515
530
  id: "alarm-table",
516
531
  version: VERSION,
517
532
  sdkApiVersion: 1,
@@ -519,19 +534,37 @@ var builtinWidgetDescriptors = {
519
534
  display: { title: "Alarm table", category: "Alarms", titleId: "widgetTypeAlarmTable", categoryId: "widgetCategoryAlarms" },
520
535
  channel: "alarm",
521
536
  datasource: { mode: "optional", selectors: ALL_SELECTORS },
522
- binding: { pageSize: { option: "maxRows", default: 50, min: 1 }, ...ALARM_FILTERS },
537
+ // groupBy 'deviceType' turns the table into one tab per device type (plus All).
538
+ binding: { pageSize: { option: "maxRows", default: 50, min: 1 }, ...ALARM_FILTERS, groupBy: { option: "groupBy", equals: "deviceType" } },
523
539
  options: {
524
540
  title: TITLE,
525
541
  state: ALARM_STATE,
526
542
  severity: ALARM_SEVERITY,
527
543
  acknowledged: ALARM_ACKNOWLEDGED,
544
+ groupBy: {
545
+ kind: "enum",
546
+ values: ["none", "deviceType"],
547
+ labelId: "widgetLabelAlarmGroupBy",
548
+ helpId: "widgetHintAlarmGroupBy",
549
+ valueLabelIds: { none: "widgetAlarmGroupByNone", deviceType: "widgetAlarmGroupByDeviceType" },
550
+ placeholderId: "widgetPlaceholderDefault"
551
+ },
552
+ deviceTypes: {
553
+ kind: "list",
554
+ maxItems: 24,
555
+ ordered: true,
556
+ labelId: "widgetLabelAlarmDeviceTypes",
557
+ helpId: "widgetHintAlarmDeviceTypes",
558
+ item: {
559
+ token: { kind: "string", control: "device-type", labelId: "widgetLabelAlarmDeviceTypeToken" }
560
+ }
561
+ },
528
562
  maxRows: {
529
563
  kind: "number",
530
564
  integer: true,
531
565
  min: 1,
532
566
  labelId: "widgetLabelMaxRows",
533
- helpId: "widgetMaxRowsAlarmHint",
534
- doc: "Alarms loaded into the table. Default 50."
567
+ helpId: "widgetMaxRowsAlarmHint"
535
568
  },
536
569
  precision: PRECISION,
537
570
  flashOnChange: FLASH_ON_CHANGE,
@@ -543,7 +576,7 @@ var builtinWidgetDescriptors = {
543
576
  }),
544
577
  // The count shows the SERVER total and reads a page only to sample the worst loaded
545
578
  // severity, so its page size is a constant rather than an authorable option.
546
- "alarm-count": defineWidgetDescriptor({
579
+ "alarm-count": defineRuntimeDescriptor({
547
580
  id: "alarm-count",
548
581
  version: VERSION,
549
582
  sdkApiVersion: 1,
@@ -565,7 +598,45 @@ var builtinWidgetDescriptors = {
565
598
  interactions: {},
566
599
  layout: { defaultSpan: { cols: 3, rows: 2 }, minSpan: { cols: 2, rows: 2 }, maxSpan: { cols: 6, rows: 4 } }
567
600
  }),
568
- "command-button": defineWidgetDescriptor({
601
+ // Alarms by severity, from ONE grouped count read (device-management's per-severity counts, every
602
+ // originator, exact past any page). Not a mode of alarm-count: a different shape and size, and no
603
+ // severity filter. `deviceType` narrows it to the alarms on one type's devices.
604
+ "alarm-severity": defineRuntimeDescriptor({
605
+ id: "alarm-severity",
606
+ version: VERSION,
607
+ sdkApiVersion: 1,
608
+ optionsVersion: 1,
609
+ display: {
610
+ title: "Alarm severity",
611
+ category: "Alarms",
612
+ titleId: "widgetTypeAlarmSeverity",
613
+ categoryId: "widgetCategoryAlarms"
614
+ },
615
+ channel: "alarm",
616
+ datasource: { mode: "optional", selectors: ALL_SELECTORS },
617
+ binding: {
618
+ pageSize: { const: 1 },
619
+ state: { option: "state" },
620
+ acknowledged: { option: "acknowledged" },
621
+ countBySeverity: { const: true },
622
+ deviceType: { option: "deviceType" }
623
+ },
624
+ options: {
625
+ title: TITLE,
626
+ state: ALARM_STATE,
627
+ acknowledged: ALARM_ACKNOWLEDGED,
628
+ deviceType: {
629
+ kind: "string",
630
+ control: "device-type",
631
+ labelId: "widgetLabelAlarmSeverityType",
632
+ helpId: "widgetHintAlarmSeverityType"
633
+ }
634
+ },
635
+ defaults: { title: "Alarm severity", state: "ACTIVE" },
636
+ interactions: {},
637
+ layout: { defaultSpan: { cols: 4, rows: 2 }, minSpan: { cols: 3, rows: 2 }, maxSpan: { cols: 12, rows: 4 } }
638
+ }),
639
+ "command-button": defineRuntimeDescriptor({
569
640
  id: "command-button",
570
641
  version: VERSION,
571
642
  sdkApiVersion: 1,
@@ -581,38 +652,34 @@ var builtinWidgetDescriptors = {
581
652
  required: true,
582
653
  labelId: "widgetCommandHeading",
583
654
  helpId: "widgetCommandFieldHint",
584
- control: "command-vocabulary",
585
- doc: "Command key issued to the target device. Without it the widget cannot send."
655
+ control: "command-vocabulary"
586
656
  },
587
657
  commandLabel: {
588
658
  kind: "string",
589
659
  labelId: "widgetCommandHeading",
590
660
  helpId: "widgetCommandFieldHint",
591
- control: "command-vocabulary",
592
- doc: "Text on the send button. Defaults to the command key."
661
+ control: "command-vocabulary"
593
662
  },
594
663
  parameterSchema: {
595
664
  kind: "string",
596
665
  format: "json",
597
666
  labelId: "widgetCommandHeading",
598
667
  helpId: "widgetCommandFieldHint",
599
- control: "command-vocabulary",
600
- doc: "The command's parameter descriptors, baked in as JSON at author time. Drives the typed form."
668
+ control: "command-vocabulary"
601
669
  },
602
670
  maxRows: {
603
671
  kind: "number",
604
672
  integer: true,
605
673
  min: 1,
606
674
  labelId: "widgetLabelMaxRows",
607
- helpId: "widgetMaxRowsCommandHint",
608
- doc: "Recent commands shown in the history. Default 20."
675
+ helpId: "widgetMaxRowsCommandHint"
609
676
  }
610
677
  },
611
678
  defaults: { title: "Command button" },
612
679
  interactions: { commandSend: true },
613
680
  layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 3, rows: 2 }, maxSpan: { cols: 6, rows: 6 } }
614
681
  }),
615
- "entity-selector": defineWidgetDescriptor({
682
+ "entity-selector": defineRuntimeDescriptor({
616
683
  id: "entity-selector",
617
684
  version: VERSION,
618
685
  sdkApiVersion: 1,
@@ -630,7 +697,7 @@ var builtinWidgetDescriptors = {
630
697
  // A select control is one line high, so its rows are pinned; it widens to fit long names.
631
698
  layout: { defaultSpan: { cols: 3, rows: 1 }, minSpan: { cols: 3, rows: 1 }, maxSpan: { cols: 12, rows: 1 }, resize: "horizontal" }
632
699
  }),
633
- map: defineWidgetDescriptor({
700
+ map: defineRuntimeDescriptor({
634
701
  id: "map",
635
702
  version: VERSION,
636
703
  sdkApiVersion: 1,
@@ -652,25 +719,26 @@ var builtinWidgetDescriptors = {
652
719
  kind: "string",
653
720
  labelId: "widgetLabelTileUrl",
654
721
  helpId: "widgetTileUrlHint",
655
- control: "basemap",
656
- doc: "Raster tile URL template, e.g. https://tiles.example.com/{z}/{x}/{y}.png. Leave empty to inherit the tenant's basemap; setting one overrides it for this board only."
722
+ control: "basemap"
657
723
  },
658
724
  attribution: {
659
725
  kind: "string",
660
726
  labelId: "widgetLabelAttribution",
661
727
  helpId: "widgetAttributionHint",
662
- control: "basemap",
663
- doc: "Credit line the tile source requires, shown on the map. Set whatever the provider you chose asks for."
664
- }
728
+ control: "basemap"
729
+ },
730
+ // Optional and additive: unset, a marker is only a mark. Set, clicking a marker re-points
731
+ // that slot at the device, exactly as a fleet-table row does.
732
+ selectionTarget: SELECTION_TARGET
665
733
  },
666
734
  defaults: { title: "Map" },
667
- interactions: {},
735
+ interactions: { selection: true },
668
736
  layout: { defaultSpan: { cols: 6, rows: 4 }, minSpan: { cols: 4, rows: 3 } }
669
737
  }),
670
738
  // ---- Levels and indicators ------------------------------------------------
671
739
  // DOM/SVG single-value widgets on the measurement channel. A threshold band colours the fill
672
740
  // or lamp; `normal` keeps the widget's neutral accent.
673
- "linear-gauge": defineWidgetDescriptor({
741
+ "linear-gauge": defineRuntimeDescriptor({
674
742
  id: "linear-gauge",
675
743
  version: VERSION,
676
744
  sdkApiVersion: 1,
@@ -689,16 +757,14 @@ var builtinWidgetDescriptors = {
689
757
  values: ["bar", "thermometer"],
690
758
  labelId: "widgetLabelLinearStyle",
691
759
  valueLabelIds: { bar: "widgetLinearStyleBar", thermometer: "widgetLinearStyleThermometer" },
692
- placeholderId: "widgetPlaceholderDefault",
693
- doc: "A plain bar (default) or a thermometer: a vertical tube with a bulb. A thermometer is always vertical."
760
+ placeholderId: "widgetPlaceholderDefault"
694
761
  },
695
762
  orientation: {
696
763
  kind: "enum",
697
764
  values: ["horizontal", "vertical"],
698
765
  labelId: "widgetLabelOrientation",
699
766
  valueLabelIds: { horizontal: "widgetOrientationHorizontal", vertical: "widgetOrientationVertical" },
700
- placeholderId: "widgetPlaceholderDefault",
701
- doc: "Direction of a bar: horizontal (default) or vertical. Ignored by a thermometer."
767
+ placeholderId: "widgetPlaceholderDefault"
702
768
  },
703
769
  unit: UNIT,
704
770
  precision: PRECISION,
@@ -712,7 +778,7 @@ var builtinWidgetDescriptors = {
712
778
  freshness: true,
713
779
  layout: { defaultSpan: { cols: 4, rows: 3 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 8, rows: 8 } }
714
780
  }),
715
- "level-gauge": defineWidgetDescriptor({
781
+ "level-gauge": defineRuntimeDescriptor({
716
782
  id: "level-gauge",
717
783
  version: VERSION,
718
784
  sdkApiVersion: 1,
@@ -731,8 +797,7 @@ var builtinWidgetDescriptors = {
731
797
  values: ["value", "percent"],
732
798
  labelId: "widgetLabelReadout",
733
799
  valueLabelIds: { value: "widgetReadoutValue", percent: "widgetReadoutPercent" },
734
- placeholderId: "widgetPlaceholderDefault",
735
- doc: "What the readout shows: the value with its unit (default) or the percent of the scale."
800
+ placeholderId: "widgetPlaceholderDefault"
736
801
  },
737
802
  unit: UNIT,
738
803
  precision: PRECISION,
@@ -746,7 +811,7 @@ var builtinWidgetDescriptors = {
746
811
  freshness: true,
747
812
  layout: { defaultSpan: { cols: 3, rows: 4 }, minSpan: { cols: 2, rows: 3 }, maxSpan: { cols: 6, rows: 8 } }
748
813
  }),
749
- battery: defineWidgetDescriptor({
814
+ battery: defineRuntimeDescriptor({
750
815
  id: "battery",
751
816
  version: VERSION,
752
817
  sdkApiVersion: 1,
@@ -761,14 +826,12 @@ var builtinWidgetDescriptors = {
761
826
  min: {
762
827
  kind: "number",
763
828
  labelId: "widgetLabelMin",
764
- helpId: "widgetHintBatteryRange",
765
- doc: "Value of an empty battery. Default 0 (the scale is percent unless you set min and max)."
829
+ helpId: "widgetHintBatteryRange"
766
830
  },
767
831
  max: {
768
832
  kind: "number",
769
833
  labelId: "widgetLabelMax",
770
- helpId: "widgetHintBatteryRange",
771
- doc: "Value of a full battery. Default 100 (the scale is percent unless you set min and max)."
834
+ helpId: "widgetHintBatteryRange"
772
835
  },
773
836
  unit: UNIT,
774
837
  precision: PRECISION,
@@ -782,7 +845,7 @@ var builtinWidgetDescriptors = {
782
845
  freshness: true,
783
846
  layout: { defaultSpan: { cols: 3, rows: 2 }, minSpan: { cols: 2, rows: 2 }, maxSpan: { cols: 6, rows: 4 } }
784
847
  }),
785
- "status-light": defineWidgetDescriptor({
848
+ "status-light": defineRuntimeDescriptor({
786
849
  id: "status-light",
787
850
  version: VERSION,
788
851
  sdkApiVersion: 1,
@@ -797,8 +860,7 @@ var builtinWidgetDescriptors = {
797
860
  onAt: {
798
861
  kind: "number",
799
862
  labelId: "widgetLabelOnAt",
800
- helpId: "widgetHintOnAt",
801
- doc: "For an on/off signal: the lamp is lit at or above this value and dark below it. Unset keeps the lamp lit while a value is present, coloured by its threshold band."
863
+ helpId: "widgetHintOnAt"
802
864
  },
803
865
  unit: UNIT,
804
866
  precision: PRECISION,
@@ -811,7 +873,7 @@ var builtinWidgetDescriptors = {
811
873
  freshness: true,
812
874
  layout: { defaultSpan: { cols: 2, rows: 2 }, minSpan: { cols: 2, rows: 2 }, maxSpan: { cols: 4, rows: 4 } }
813
875
  }),
814
- "signal-bars": defineWidgetDescriptor({
876
+ "signal-bars": defineRuntimeDescriptor({
815
877
  id: "signal-bars",
816
878
  version: VERSION,
817
879
  sdkApiVersion: 1,
@@ -830,8 +892,7 @@ var builtinWidgetDescriptors = {
830
892
  ...THRESHOLDS,
831
893
  maxItems: 8,
832
894
  required: true,
833
- helpId: "widgetHintSignalThresholds",
834
- doc: "The steps of the signal, lowest first: one bar per entry, lit once the value reaches it and coloured by its level. At least two, at most eight."
895
+ helpId: "widgetHintSignalThresholds"
835
896
  },
836
897
  staleAfterSeconds: STALE_AFTER_SECONDS,
837
898
  window: WINDOW
@@ -844,7 +905,7 @@ var builtinWidgetDescriptors = {
844
905
  // ---- Radial family ----------------------------------------------------------
845
906
  // A heading on a full turn, and a ring filled between a configured min and max. The compass
846
907
  // draws on the registered gauge series; the ring is SVG. Both put their number in DOM text.
847
- compass: defineWidgetDescriptor({
908
+ compass: defineRuntimeDescriptor({
848
909
  id: "compass",
849
910
  version: VERSION,
850
911
  sdkApiVersion: 1,
@@ -857,12 +918,10 @@ var builtinWidgetDescriptors = {
857
918
  title: TITLE_OR_MEASUREMENT,
858
919
  measurement: MEASUREMENT,
859
920
  precision: {
860
- ...PRECISION,
861
- doc: "Decimal places of the heading. Default 0 (whole degrees). The heading is rounded first, then folded onto 0 to 360, so it never reads 360."
921
+ ...PRECISION
862
922
  },
863
923
  thresholds: {
864
- ...THRESHOLDS,
865
- doc: "Severity bands over the heading in degrees (0 to 360, after folding). Each entry starts a band at its value that runs to the next entry; below the first, and in a normal band, the needle keeps its own colour."
924
+ ...THRESHOLDS
866
925
  },
867
926
  staleAfterSeconds: STALE_AFTER_SECONDS,
868
927
  window: WINDOW
@@ -872,7 +931,7 @@ var builtinWidgetDescriptors = {
872
931
  freshness: true,
873
932
  layout: { defaultSpan: { cols: 4, rows: 4 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 8, rows: 8 } }
874
933
  }),
875
- "progress-ring": defineWidgetDescriptor({
934
+ "progress-ring": defineRuntimeDescriptor({
876
935
  id: "progress-ring",
877
936
  version: VERSION,
878
937
  sdkApiVersion: 1,
@@ -891,8 +950,7 @@ var builtinWidgetDescriptors = {
891
950
  values: ["percent", "value"],
892
951
  labelId: "widgetLabelReadout",
893
952
  valueLabelIds: { percent: "widgetReadoutPercent", value: "widgetReadoutValue" },
894
- placeholderId: "widgetPlaceholderDefault",
895
- doc: "What the middle of the ring shows: the percent of the scale (default) or the value with its unit."
953
+ placeholderId: "widgetPlaceholderDefault"
896
954
  },
897
955
  unit: UNIT,
898
956
  precision: PRECISION,
@@ -906,7 +964,7 @@ var builtinWidgetDescriptors = {
906
964
  freshness: true,
907
965
  layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 2, rows: 2 }, maxSpan: { cols: 6, rows: 6 } }
908
966
  }),
909
- "command-slider": defineWidgetDescriptor({
967
+ "command-slider": defineRuntimeDescriptor({
910
968
  id: "command-slider",
911
969
  version: VERSION,
912
970
  sdkApiVersion: 1,
@@ -925,21 +983,25 @@ var builtinWidgetDescriptors = {
925
983
  min: {
926
984
  kind: "number",
927
985
  labelId: "widgetLabelMin",
928
- helpId: "widgetHintSliderRange",
929
- doc: "Lowest value, used only when the command's parameter declares no minimum."
986
+ helpId: "widgetHintSliderRange"
930
987
  },
931
988
  max: {
932
989
  kind: "number",
933
990
  labelId: "widgetLabelMax",
934
- helpId: "widgetHintSliderRange",
935
- doc: "Highest value, used only when the command's parameter declares no maximum."
991
+ helpId: "widgetHintSliderRange"
936
992
  },
937
993
  step: {
938
994
  kind: "number",
939
995
  min: 0,
940
996
  labelId: "widgetLabelStep",
941
- helpId: "widgetHintSliderStep",
942
- doc: "Distance between values. Defaults to 1 for a whole-number parameter, else a hundredth of the range."
997
+ helpId: "widgetHintSliderStep"
998
+ },
999
+ mode: {
1000
+ kind: "enum",
1001
+ values: ["slider", "stepper"],
1002
+ labelId: "widgetLabelControlMode",
1003
+ valueLabelIds: { slider: "widgetControlModeSlider", stepper: "widgetControlModeStepper" },
1004
+ placeholderId: "widgetPlaceholderDefault"
943
1005
  }
944
1006
  },
945
1007
  constraints: [{ kind: "greater-than", key: "max", than: "min" }],
@@ -947,7 +1009,7 @@ var builtinWidgetDescriptors = {
947
1009
  interactions: { commandSend: true },
948
1010
  layout: { defaultSpan: { cols: 4, rows: 2 }, minSpan: { cols: 3, rows: 2 }, maxSpan: { cols: 8, rows: 3 } }
949
1011
  }),
950
- "command-toggle": defineWidgetDescriptor({
1012
+ "command-toggle": defineRuntimeDescriptor({
951
1013
  id: "command-toggle",
952
1014
  version: VERSION,
953
1015
  sdkApiVersion: 1,
@@ -967,7 +1029,7 @@ var builtinWidgetDescriptors = {
967
1029
  interactions: { commandSend: true },
968
1030
  layout: { defaultSpan: { cols: 3, rows: 2 }, minSpan: { cols: 2, rows: 2 }, maxSpan: { cols: 6, rows: 3 } }
969
1031
  }),
970
- "stat-card": defineWidgetDescriptor({
1032
+ "stat-card": defineRuntimeDescriptor({
971
1033
  id: "stat-card",
972
1034
  version: VERSION,
973
1035
  sdkApiVersion: 1,
@@ -992,19 +1054,23 @@ var builtinWidgetDescriptors = {
992
1054
  count: "widgetStatCount",
993
1055
  last: "widgetStatLast"
994
1056
  },
995
- placeholderId: "widgetPlaceholderDefault",
996
- doc: "Which figure to show. Default average."
1057
+ placeholderId: "widgetPlaceholderDefault"
997
1058
  },
998
1059
  unit: UNIT,
999
1060
  precision: PRECISION,
1000
1061
  thresholds: THRESHOLDS,
1062
+ sparkline: {
1063
+ kind: "boolean",
1064
+ labelId: "widgetLabelSparkline",
1065
+ helpId: "widgetHintSparkline"
1066
+ },
1001
1067
  windowSeconds: WINDOW_SECONDS
1002
1068
  },
1003
1069
  defaults: { title: "Stat card" },
1004
1070
  interactions: {},
1005
1071
  layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 3, rows: 2 }, maxSpan: { cols: 6, rows: 4 } }
1006
1072
  }),
1007
- "bar-chart": defineWidgetDescriptor({
1073
+ "bar-chart": defineRuntimeDescriptor({
1008
1074
  id: "bar-chart",
1009
1075
  version: VERSION,
1010
1076
  sdkApiVersion: 1,
@@ -1022,8 +1088,7 @@ var builtinWidgetDescriptors = {
1022
1088
  labelId: "widgetLabelGroupBy",
1023
1089
  helpId: "widgetHintGroupBy",
1024
1090
  valueLabelIds: { measurement: "widgetGroupByMeasurement", device: "widgetGroupByDevice" },
1025
- placeholderId: "widgetPlaceholderDefault",
1026
- doc: "One bar per measurement (the default), or per device of the group for the measurement set below."
1091
+ placeholderId: "widgetPlaceholderDefault"
1027
1092
  },
1028
1093
  measurement: MEASUREMENT,
1029
1094
  unit: UNIT,
@@ -1034,7 +1099,7 @@ var builtinWidgetDescriptors = {
1034
1099
  interactions: {},
1035
1100
  layout: { defaultSpan: { cols: 6, rows: 4 }, minSpan: { cols: 4, rows: 3 } }
1036
1101
  }),
1037
- "pie-chart": defineWidgetDescriptor({
1102
+ "pie-chart": defineRuntimeDescriptor({
1038
1103
  id: "pie-chart",
1039
1104
  version: VERSION,
1040
1105
  sdkApiVersion: 1,
@@ -1051,7 +1116,7 @@ var builtinWidgetDescriptors = {
1051
1116
  // A new widget rather than a mode of `table`: that one is one device's measurements as rows, on a
1052
1117
  // channel that keys its values by name; this one is a fleet, a row per member and a column per
1053
1118
  // measurement, and reads its data by device.
1054
- "fleet-table": defineWidgetDescriptor({
1119
+ "fleet-table": defineRuntimeDescriptor({
1055
1120
  id: "fleet-table",
1056
1121
  version: VERSION,
1057
1122
  sdkApiVersion: 1,
@@ -1070,23 +1135,20 @@ var builtinWidgetDescriptors = {
1070
1135
  integer: true,
1071
1136
  min: 1,
1072
1137
  labelId: "widgetLabelAgeLag",
1073
- helpId: "widgetHintAgeLag",
1074
- doc: "Show a reading's age under it only when the reading is at least this many seconds older than the newest reading in the table (or stale). Unset shows every reading's age."
1138
+ helpId: "widgetHintAgeLag"
1075
1139
  },
1076
1140
  sortBy: {
1077
1141
  kind: "string",
1078
1142
  labelId: "widgetLabelSortBy",
1079
- helpId: "widgetHintSortBy",
1080
- doc: "The measurement the table is ordered by. Blank orders by device name."
1143
+ helpId: "widgetHintSortBy"
1081
1144
  },
1082
- sortDescending: { kind: "boolean", labelId: "widgetLabelSortDescending", doc: "Largest first." },
1145
+ sortDescending: { kind: "boolean", labelId: "widgetLabelSortDescending" },
1083
1146
  pageSize: {
1084
1147
  kind: "number",
1085
1148
  integer: true,
1086
1149
  min: 5,
1087
1150
  max: 100,
1088
- labelId: "widgetLabelPageSize",
1089
- doc: "Devices per page. Default 20."
1151
+ labelId: "widgetLabelPageSize"
1090
1152
  },
1091
1153
  selectionTarget: SELECTION_TARGET,
1092
1154
  windowSeconds: WINDOW_SECONDS
@@ -1096,44 +1158,82 @@ var builtinWidgetDescriptors = {
1096
1158
  freshness: true,
1097
1159
  interactions: { selection: true },
1098
1160
  layout: { defaultSpan: { cols: 12, rows: 6 }, minSpan: { cols: 6, rows: 3 } }
1161
+ }),
1162
+ // Counts a fleet rather than listing it: how many of its devices are in an abnormal band of one
1163
+ // measurement ("3 of 21"), or how many devices it has. A new widget rather than a mode of the
1164
+ // fleet table, which is a grid; this is the figure above one.
1165
+ "fleet-count": defineRuntimeDescriptor({
1166
+ id: "fleet-count",
1167
+ version: VERSION,
1168
+ sdkApiVersion: 1,
1169
+ optionsVersion: 1,
1170
+ display: { title: "Fleet count", category: "Telemetry", titleId: "widgetTypeFleetCount", categoryId: "widgetCategoryTelemetry" },
1171
+ channel: "measurement",
1172
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
1173
+ binding: { windowSeconds: { option: "windowSeconds" }, members: { const: true } },
1174
+ options: {
1175
+ title: TITLE,
1176
+ show: {
1177
+ kind: "enum",
1178
+ values: ["attention", "devices"],
1179
+ labelId: "widgetLabelFleetShow",
1180
+ helpId: "widgetHintFleetShow",
1181
+ valueLabelIds: { attention: "widgetFleetShowAttention", devices: "widgetFleetShowDevices" },
1182
+ placeholderId: "widgetPlaceholderDefault"
1183
+ },
1184
+ measurement: {
1185
+ ...MEASUREMENT
1186
+ },
1187
+ thresholds: THRESHOLDS,
1188
+ staleAfterSeconds: STALE_AFTER_SECONDS,
1189
+ windowSeconds: WINDOW_SECONDS
1190
+ },
1191
+ defaults: { title: "Fleet count" },
1192
+ freshness: true,
1193
+ interactions: {},
1194
+ layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 6, rows: 4 } }
1195
+ }),
1196
+ // One device's connectivity: Online or Offline, and when it was last seen. It reads device-state
1197
+ // (`binding.presence`), not the device's readings, and shows one device, so it takes no group.
1198
+ "device-status": defineRuntimeDescriptor({
1199
+ id: "device-status",
1200
+ version: VERSION,
1201
+ sdkApiVersion: 1,
1202
+ optionsVersion: 1,
1203
+ display: { title: "Device status", category: "Telemetry", titleId: "widgetTypeDeviceStatus", categoryId: "widgetCategoryTelemetry" },
1204
+ channel: "measurement",
1205
+ datasource: { mode: "required", selectors: ONE_DEVICE },
1206
+ binding: { presence: { const: true } },
1207
+ options: { title: TITLE, staleAfterSeconds: STALE_AFTER_SECONDS },
1208
+ defaults: { title: "Device status" },
1209
+ // The frame's age is the status's: when device-state was last read, not the device's last activity.
1210
+ freshness: true,
1211
+ interactions: {},
1212
+ layout: { defaultSpan: { cols: 3, rows: 3 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 6, rows: 4 } }
1213
+ }),
1214
+ // How many of a group's devices are online: a ring of online, offline and no status yet. A new
1215
+ // widget rather than a mode of the pie chart, which is shares of readings; this reads device-state.
1216
+ "online-doughnut": defineRuntimeDescriptor({
1217
+ id: "online-doughnut",
1218
+ version: VERSION,
1219
+ sdkApiVersion: 1,
1220
+ optionsVersion: 1,
1221
+ display: { title: "Online doughnut", category: "Telemetry", titleId: "widgetTypeOnlineDoughnut", categoryId: "widgetCategoryTelemetry" },
1222
+ channel: "measurement",
1223
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
1224
+ binding: { presence: { const: true } },
1225
+ options: { title: TITLE, staleAfterSeconds: STALE_AFTER_SECONDS },
1226
+ defaults: { title: "Online doughnut" },
1227
+ // The frame's age is the status's: when device-state was last read for the group.
1228
+ freshness: true,
1229
+ interactions: {},
1230
+ layout: { defaultSpan: { cols: 4, rows: 4 }, minSpan: { cols: 3, rows: 3 }, maxSpan: { cols: 8, rows: 8 } }
1099
1231
  })
1100
1232
  };
1101
1233
  var builtinWidgetCatalog = createWidgetCatalog(Object.values(builtinWidgetDescriptors));
1102
1234
 
1103
- // src/widgets/lazy-chunk.tsx
1104
- import { useEffect, useState as useState2 } from "react";
1105
- import { jsx } from "react/jsx-runtime";
1106
- function lazyChunk(importer) {
1107
- let loaded;
1108
- let loading;
1109
- const load = () => loading ?? (loading = importer().then(
1110
- (m) => loaded = m,
1111
- (err) => {
1112
- loading = void 0;
1113
- throw err;
1114
- }
1115
- ));
1116
- function component(name, alsoPreload) {
1117
- function LazyChunkWidget(props) {
1118
- const [mod, setMod] = useState2(loaded);
1119
- const [error, setError] = useState2();
1120
- useEffect(() => {
1121
- if (!mod) load().then(setMod, setError);
1122
- }, [mod]);
1123
- if (error !== void 0) throw error;
1124
- if (!mod) return null;
1125
- const Component2 = mod[name];
1126
- return /* @__PURE__ */ jsx(Component2, { ...props });
1127
- }
1128
- LazyChunkWidget.displayName = `Lazy(${name})`;
1129
- LazyChunkWidget.preload = alsoPreload ? () => Promise.all([load(), alsoPreload()]).then(([m]) => m) : load;
1130
- return LazyChunkWidget;
1131
- }
1132
- return { load, component };
1133
- }
1134
-
1135
1235
  // src/widgets/aggregate-lazy.tsx
1136
- var chunk = lazyChunk(() => import("./chunks/aggregate-widgets-HZQI5HTX.js"));
1236
+ var chunk = lazyChunk(() => import("./chunks/aggregate-widgets-2ZQBW3YF.js"));
1137
1237
  var loadAggregateWidgets = chunk.load;
1138
1238
  var statCardWidget = defineWidget({
1139
1239
  descriptor: builtinWidgetDescriptors["stat-card"],
@@ -1147,6 +1247,10 @@ var pieChartWidget = defineWidget({
1147
1247
  descriptor: builtinWidgetDescriptors["pie-chart"],
1148
1248
  implementation: { kind: "trusted-react", component: chunk.component("PieChart") }
1149
1249
  });
1250
+ var fleetCountWidget = defineWidget({
1251
+ descriptor: builtinWidgetDescriptors["fleet-count"],
1252
+ implementation: { kind: "trusted-react", component: chunk.component("FleetCount") }
1253
+ });
1150
1254
  var fleetTableWidget = defineWidget({
1151
1255
  descriptor: builtinWidgetDescriptors["fleet-table"],
1152
1256
  implementation: { kind: "trusted-react", component: chunk.component("FleetTable") }
@@ -1154,12 +1258,16 @@ var fleetTableWidget = defineWidget({
1154
1258
 
1155
1259
  // src/widgets/alarm-lazy.tsx
1156
1260
  import { defineWidget as defineWidget2 } from "@devicechain/widget-sdk";
1157
- var chunk2 = lazyChunk(() => import("./chunks/alarm-widgets-7UY6MIYZ.js"));
1261
+ var chunk2 = lazyChunk(() => import("./chunks/alarm-widgets-X4PX5MYJ.js"));
1158
1262
  var loadAlarmWidgets = chunk2.load;
1159
1263
  var alarmTableWidget = defineWidget2({
1160
1264
  descriptor: builtinWidgetDescriptors["alarm-table"],
1161
1265
  implementation: { kind: "trusted-react", component: chunk2.component("AlarmTable") }
1162
1266
  });
1267
+ var alarmSeverityWidget = defineWidget2({
1268
+ descriptor: builtinWidgetDescriptors["alarm-severity"],
1269
+ implementation: { kind: "trusted-react", component: chunk2.component("AlarmSeverity") }
1270
+ });
1163
1271
  var alarmCountWidget = defineWidget2({
1164
1272
  descriptor: builtinWidgetDescriptors["alarm-count"],
1165
1273
  implementation: { kind: "trusted-react", component: chunk2.component("AlarmCount") }
@@ -1167,7 +1275,7 @@ var alarmCountWidget = defineWidget2({
1167
1275
 
1168
1276
  // src/widgets/control-lazy.tsx
1169
1277
  import { defineWidget as defineWidget3 } from "@devicechain/widget-sdk";
1170
- var chunk3 = lazyChunk(() => import("./chunks/control-widgets-3V2LSSRF.js"));
1278
+ var chunk3 = lazyChunk(() => import("./chunks/control-widgets-UCTJJJ2D.js"));
1171
1279
  var loadControlWidgets = chunk3.load;
1172
1280
  var commandSliderWidget = defineWidget3({
1173
1281
  descriptor: builtinWidgetDescriptors["command-slider"],
@@ -1182,19 +1290,32 @@ var commandButtonWidget = defineWidget3({
1182
1290
  implementation: { kind: "trusted-react", component: chunk3.component("CommandButton") }
1183
1291
  });
1184
1292
 
1185
- // src/widgets/selection-lazy.tsx
1293
+ // src/widgets/presence-lazy.tsx
1186
1294
  import { defineWidget as defineWidget4 } from "@devicechain/widget-sdk";
1187
- var chunk4 = lazyChunk(() => import("./chunks/entity-selector-3JJFI4DT.js"));
1188
- var loadSelectionWidgets = chunk4.load;
1189
- var entitySelectorWidget = defineWidget4({
1295
+ var chunk4 = lazyChunk(() => import("./chunks/presence-widgets-2LIUKN4Y.js"));
1296
+ var loadPresenceWidgets = chunk4.load;
1297
+ var deviceStatusWidget = defineWidget4({
1298
+ descriptor: builtinWidgetDescriptors["device-status"],
1299
+ implementation: { kind: "trusted-react", component: chunk4.component("DeviceStatus") }
1300
+ });
1301
+ var onlineDoughnutWidget = defineWidget4({
1302
+ descriptor: builtinWidgetDescriptors["online-doughnut"],
1303
+ implementation: { kind: "trusted-react", component: chunk4.component("OnlineDoughnut") }
1304
+ });
1305
+
1306
+ // src/widgets/selection-lazy.tsx
1307
+ import { defineWidget as defineWidget5 } from "@devicechain/widget-sdk";
1308
+ var chunk5 = lazyChunk(() => import("./chunks/entity-selector-3JJFI4DT.js"));
1309
+ var loadSelectionWidgets = chunk5.load;
1310
+ var entitySelectorWidget = defineWidget5({
1190
1311
  descriptor: builtinWidgetDescriptors["entity-selector"],
1191
- implementation: { kind: "trusted-react", component: chunk4.component("EntitySelector") }
1312
+ implementation: { kind: "trusted-react", component: chunk5.component("EntitySelector") }
1192
1313
  });
1193
1314
 
1194
1315
  // src/widgets/gauge.tsx
1195
1316
  import { useMemo } from "react";
1196
1317
  import {
1197
- defineWidget as defineWidget5,
1318
+ defineWidget as defineWidget6,
1198
1319
  flashBackgroundStyle,
1199
1320
  formatValue,
1200
1321
  optBoolean,
@@ -1208,11 +1329,11 @@ import {
1208
1329
  WidgetFrame
1209
1330
  } from "@devicechain/widget-sdk";
1210
1331
  import { buildGaugeOption, EChart, gaugeReadoutBox, loadChartEngine, useChartTheme } from "@devicechain/widget-sdk/echarts";
1211
- import { jsx as jsx2, jsxs } from "react/jsx-runtime";
1332
+ import { jsx, jsxs } from "react/jsx-runtime";
1212
1333
  function Gauge({ widget, data }) {
1213
1334
  const name = primaryMeasurementName(widget);
1214
1335
  const sample = pickSample(data.latest, name);
1215
- return /* @__PURE__ */ jsx2(WidgetFrame, { title: optString(widget.options, "title") ?? name, sampleTime: sample?.occurredTime ?? null, flush: true, children: /* @__PURE__ */ jsx2(GaugeBody, { widget, data }) });
1336
+ return /* @__PURE__ */ jsx(WidgetFrame, { title: optString(widget.options, "title") ?? name, sampleTime: sample?.occurredTime ?? null, flush: true, children: /* @__PURE__ */ jsx(GaugeBody, { widget, data }) });
1216
1337
  }
1217
1338
  function GaugeBody({ widget, data }) {
1218
1339
  const theme = useChartTheme();
@@ -1244,8 +1365,8 @@ function GaugeBody({ widget, data }) {
1244
1365
  "aria-valuetext": `${formatValue(value, precision)}${unit ? ` ${unit}` : ""}`
1245
1366
  } : {};
1246
1367
  return /* @__PURE__ */ jsxs("div", { style: { position: "relative", width: "100%", height: "100%", ...flashBackgroundStyle(flashDirection) }, ...meter, children: [
1247
- /* @__PURE__ */ jsx2(EChart, { option }),
1248
- /* @__PURE__ */ jsx2(
1368
+ /* @__PURE__ */ jsx(EChart, { option }),
1369
+ /* @__PURE__ */ jsx(
1249
1370
  "div",
1250
1371
  {
1251
1372
  style: {
@@ -1259,7 +1380,7 @@ function GaugeBody({ widget, data }) {
1259
1380
  justifyContent: "center",
1260
1381
  pointerEvents: "none"
1261
1382
  },
1262
- children: /* @__PURE__ */ jsx2(
1383
+ children: /* @__PURE__ */ jsx(
1263
1384
  LevelReadout,
1264
1385
  {
1265
1386
  text: formatValue(value, precision),
@@ -1273,7 +1394,7 @@ function GaugeBody({ widget, data }) {
1273
1394
  ] });
1274
1395
  }
1275
1396
  Gauge.preload = loadChartEngine;
1276
- var gaugeWidget = defineWidget5({
1397
+ var gaugeWidget = defineWidget6({
1277
1398
  descriptor: builtinWidgetDescriptors["gauge"],
1278
1399
  implementation: { kind: "trusted-react", component: Gauge }
1279
1400
  });
@@ -1281,19 +1402,19 @@ var gaugeWidget = defineWidget5({
1281
1402
  // src/widgets/image.tsx
1282
1403
  import {
1283
1404
  css,
1284
- defineWidget as defineWidget6,
1405
+ defineWidget as defineWidget7,
1285
1406
  fontVar,
1286
1407
  optString as optString2,
1287
1408
  WidgetFrame as WidgetFrame2
1288
1409
  } from "@devicechain/widget-sdk";
1289
- import { jsx as jsx3 } from "react/jsx-runtime";
1410
+ import { jsx as jsx2 } from "react/jsx-runtime";
1290
1411
  var FITS = /* @__PURE__ */ new Set(["contain", "cover", "fill"]);
1291
1412
  function Image({ widget }) {
1292
1413
  const url = optString2(widget.options, "url");
1293
1414
  const alt = optString2(widget.options, "alt") ?? "";
1294
1415
  const fitOption = optString2(widget.options, "fit");
1295
1416
  const objectFit = fitOption && FITS.has(fitOption) ? fitOption : "contain";
1296
- return /* @__PURE__ */ jsx3(WidgetFrame2, { title: optString2(widget.options, "title"), flush: true, children: url ? /* @__PURE__ */ jsx3("img", { src: url, alt, style: { width: "100%", height: "100%", objectFit, display: "block" } }) : /* @__PURE__ */ jsx3(
1417
+ return /* @__PURE__ */ jsx2(WidgetFrame2, { title: optString2(widget.options, "title"), flush: true, children: url ? /* @__PURE__ */ jsx2("img", { src: url, alt, style: { width: "100%", height: "100%", objectFit, display: "block" } }) : /* @__PURE__ */ jsx2(
1297
1418
  "div",
1298
1419
  {
1299
1420
  style: {
@@ -1308,7 +1429,7 @@ function Image({ widget }) {
1308
1429
  }
1309
1430
  ) });
1310
1431
  }
1311
- var imageWidget = defineWidget6({
1432
+ var imageWidget = defineWidget7({
1312
1433
  descriptor: builtinWidgetDescriptors["image"],
1313
1434
  implementation: { kind: "trusted-react", component: Image }
1314
1435
  });
@@ -1316,14 +1437,14 @@ var imageWidget = defineWidget6({
1316
1437
  // src/widgets/label.tsx
1317
1438
  import {
1318
1439
  css as css2,
1319
- defineWidget as defineWidget7,
1440
+ defineWidget as defineWidget8,
1320
1441
  fontVar as fontVar2,
1321
1442
  spaceVar,
1322
1443
  optNumber as optNumber2,
1323
1444
  optString as optString3,
1324
1445
  WidgetFrame as WidgetFrame3
1325
1446
  } from "@devicechain/widget-sdk";
1326
- import { jsx as jsx4 } from "react/jsx-runtime";
1447
+ import { jsx as jsx3 } from "react/jsx-runtime";
1327
1448
  var justify = {
1328
1449
  left: "flex-start",
1329
1450
  center: "center",
@@ -1334,7 +1455,7 @@ function Label({ widget }) {
1334
1455
  const align = optString3(widget.options, "align") ?? "center";
1335
1456
  const authored = optNumber2(widget.options, "fontSize");
1336
1457
  const fontSize = authored !== void 0 ? `calc(${authored}px * var(--dc-scale, 1))` : fontVar2("lg");
1337
- return /* @__PURE__ */ jsx4(WidgetFrame3, { title: optString3(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsx4(
1458
+ return /* @__PURE__ */ jsx3(WidgetFrame3, { title: optString3(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsx3(
1338
1459
  "div",
1339
1460
  {
1340
1461
  style: {
@@ -1348,7 +1469,7 @@ function Label({ widget }) {
1348
1469
  // height or less, so it keeps a slim vertical inset and the usual side inset.
1349
1470
  padding: `${spaceVar(1)} ${spaceVar(3)}`
1350
1471
  },
1351
- children: /* @__PURE__ */ jsx4(
1472
+ children: /* @__PURE__ */ jsx3(
1352
1473
  "span",
1353
1474
  {
1354
1475
  style: {
@@ -1363,7 +1484,7 @@ function Label({ widget }) {
1363
1484
  }
1364
1485
  ) });
1365
1486
  }
1366
- var labelWidget = defineWidget7({
1487
+ var labelWidget = defineWidget8({
1367
1488
  descriptor: builtinWidgetDescriptors["label"],
1368
1489
  implementation: { kind: "trusted-react", component: Label }
1369
1490
  });
@@ -1373,7 +1494,7 @@ import { useMemo as useMemo2 } from "react";
1373
1494
  import {
1374
1495
  bandAt,
1375
1496
  css as css3,
1376
- defineWidget as defineWidget8,
1497
+ defineWidget as defineWidget9,
1377
1498
  fitValueFont,
1378
1499
  fitValueFontCss,
1379
1500
  FIT_MIN_FONT_PX,
@@ -1395,7 +1516,7 @@ import {
1395
1516
  useWidgetTimeZone,
1396
1517
  WidgetFrame as WidgetFrame4
1397
1518
  } from "@devicechain/widget-sdk";
1398
- import { jsx as jsx5, jsxs as jsxs2 } from "react/jsx-runtime";
1519
+ import { jsx as jsx4, jsxs as jsxs2 } from "react/jsx-runtime";
1399
1520
  var TIMESTAMP_ROOM_PX = 18;
1400
1521
  function LatestCard({ widget, data }) {
1401
1522
  const severity = useSeverityColors();
@@ -1415,7 +1536,7 @@ function LatestCard({ widget, data }) {
1415
1536
  sample && `${sample.deviceToken}:${sample.name}`
1416
1537
  );
1417
1538
  const chars = useStickyChars(display.length + (sample?.value != null && unit ? Math.ceil(unit.length / 2) + 1 : 0));
1418
- return /* @__PURE__ */ jsx5(WidgetFrame4, { title, sampleTime: sample?.occurredTime ?? null, children: ({ size, scale }) => {
1539
+ return /* @__PURE__ */ jsx4(WidgetFrame4, { title, sampleTime: sample?.occurredTime ?? null, children: ({ size, scale }) => {
1419
1540
  const floor = Math.round(FIT_MIN_FONT_PX * scale);
1420
1541
  const measured = size.width > 0 && size.height > 0;
1421
1542
  const valuePx = measured ? fitValueFont(size, chars, { floor }) : void 0;
@@ -1451,7 +1572,7 @@ function LatestCard({ widget, data }) {
1451
1572
  },
1452
1573
  children: [
1453
1574
  display,
1454
- sample?.value != null && unit ? /* @__PURE__ */ jsx5(
1575
+ sample?.value != null && unit ? /* @__PURE__ */ jsx4(
1455
1576
  "span",
1456
1577
  {
1457
1578
  style: {
@@ -1465,65 +1586,65 @@ function LatestCard({ widget, data }) {
1465
1586
  ]
1466
1587
  }
1467
1588
  ),
1468
- sample?.occurredTime && showTimestamp ? /* @__PURE__ */ jsx5("div", { style: { fontSize: fontVar3("xs"), color: css3("muted-foreground"), marginTop: spaceVar2(1) }, children: formatTimestamp(sample.occurredTime, timeZone) }) : null
1589
+ sample?.occurredTime && showTimestamp ? /* @__PURE__ */ jsx4("div", { style: { fontSize: fontVar3("xs"), color: css3("muted-foreground"), marginTop: spaceVar2(1) }, children: formatTimestamp(sample.occurredTime, timeZone) }) : null
1469
1590
  ]
1470
1591
  }
1471
1592
  );
1472
1593
  } });
1473
1594
  }
1474
- var latestCardWidget = defineWidget8({
1595
+ var latestCardWidget = defineWidget9({
1475
1596
  descriptor: builtinWidgetDescriptors["latest-card"],
1476
1597
  implementation: { kind: "trusted-react", component: LatestCard }
1477
1598
  });
1478
1599
 
1479
1600
  // src/widgets/level-lazy.tsx
1480
- import { defineWidget as defineWidget9 } from "@devicechain/widget-sdk";
1601
+ import { defineWidget as defineWidget10 } from "@devicechain/widget-sdk";
1481
1602
  import { loadChartEngine as loadChartEngine2 } from "@devicechain/widget-sdk/echarts";
1482
- var chunk5 = lazyChunk(() => import("./chunks/level-widgets-MJB7TOLU.js"));
1483
- var loadLevelWidgets = chunk5.load;
1484
- var lazyComponent = chunk5.component;
1485
- var linearGaugeWidget = defineWidget9({
1603
+ var chunk6 = lazyChunk(() => import("./chunks/level-widgets-MJB7TOLU.js"));
1604
+ var loadLevelWidgets = chunk6.load;
1605
+ var lazyComponent = chunk6.component;
1606
+ var linearGaugeWidget = defineWidget10({
1486
1607
  descriptor: builtinWidgetDescriptors["linear-gauge"],
1487
1608
  implementation: { kind: "trusted-react", component: lazyComponent("LinearGauge") }
1488
1609
  });
1489
- var levelGaugeWidget = defineWidget9({
1610
+ var levelGaugeWidget = defineWidget10({
1490
1611
  descriptor: builtinWidgetDescriptors["level-gauge"],
1491
1612
  implementation: { kind: "trusted-react", component: lazyComponent("LevelGauge") }
1492
1613
  });
1493
- var batteryWidget = defineWidget9({
1614
+ var batteryWidget = defineWidget10({
1494
1615
  descriptor: builtinWidgetDescriptors["battery"],
1495
1616
  implementation: { kind: "trusted-react", component: lazyComponent("Battery") }
1496
1617
  });
1497
- var statusLightWidget = defineWidget9({
1618
+ var statusLightWidget = defineWidget10({
1498
1619
  descriptor: builtinWidgetDescriptors["status-light"],
1499
1620
  implementation: { kind: "trusted-react", component: lazyComponent("StatusLight") }
1500
1621
  });
1501
- var signalBarsWidget = defineWidget9({
1622
+ var signalBarsWidget = defineWidget10({
1502
1623
  descriptor: builtinWidgetDescriptors["signal-bars"],
1503
1624
  implementation: { kind: "trusted-react", component: lazyComponent("SignalBars") }
1504
1625
  });
1505
- var compassWidget = defineWidget9({
1626
+ var compassWidget = defineWidget10({
1506
1627
  descriptor: builtinWidgetDescriptors["compass"],
1507
1628
  implementation: { kind: "trusted-react", component: lazyComponent("Compass", loadChartEngine2) }
1508
1629
  });
1509
- var progressRingWidget = defineWidget9({
1630
+ var progressRingWidget = defineWidget10({
1510
1631
  descriptor: builtinWidgetDescriptors["progress-ring"],
1511
1632
  implementation: { kind: "trusted-react", component: lazyComponent("ProgressRing") }
1512
1633
  });
1513
1634
 
1514
1635
  // src/widgets/map.tsx
1515
- import { useEffect as useEffect2, useRef, useState as useState3 } from "react";
1636
+ import { useEffect, useRef, useState } from "react";
1516
1637
  import { renderableBasemap, resolveBasemap } from "@devicechain/client";
1517
1638
 
1518
1639
  // src/basemap-context.tsx
1519
1640
  import { createContext, useContext } from "react";
1520
- import { jsx as jsx6 } from "react/jsx-runtime";
1641
+ import { jsx as jsx5 } from "react/jsx-runtime";
1521
1642
  var TenantBasemapContext = createContext(null);
1522
1643
  function TenantBasemapProvider({
1523
1644
  basemap,
1524
1645
  children
1525
1646
  }) {
1526
- return /* @__PURE__ */ jsx6(TenantBasemapContext.Provider, { value: basemap, children });
1647
+ return /* @__PURE__ */ jsx5(TenantBasemapContext.Provider, { value: basemap, children });
1527
1648
  }
1528
1649
  function useTenantBasemap() {
1529
1650
  return useContext(TenantBasemapContext);
@@ -1531,13 +1652,13 @@ function useTenantBasemap() {
1531
1652
 
1532
1653
  // src/map-runtime-context.tsx
1533
1654
  import { createContext as createContext2, useContext as useContext2 } from "react";
1534
- import { jsx as jsx7 } from "react/jsx-runtime";
1655
+ import { jsx as jsx6 } from "react/jsx-runtime";
1535
1656
  var MapRuntimeContext = createContext2(null);
1536
1657
  function MapRuntimeProvider({
1537
1658
  runtime,
1538
1659
  children
1539
1660
  }) {
1540
- return /* @__PURE__ */ jsx7(MapRuntimeContext.Provider, { value: runtime, children });
1661
+ return /* @__PURE__ */ jsx6(MapRuntimeContext.Provider, { value: runtime, children });
1541
1662
  }
1542
1663
  function useMapRuntime() {
1543
1664
  return useContext2(MapRuntimeContext);
@@ -1547,8 +1668,9 @@ function useMapRuntime() {
1547
1668
  import {
1548
1669
  css as css4,
1549
1670
  fontVar as fontVar4,
1550
- defineWidget as defineWidget10,
1671
+ defineWidget as defineWidget11,
1551
1672
  optString as optString5,
1673
+ useWidgetSelect,
1552
1674
  WidgetFrame as WidgetFrame5
1553
1675
  } from "@devicechain/widget-sdk";
1554
1676
 
@@ -1645,7 +1767,7 @@ function loadLandStyle() {
1645
1767
  }
1646
1768
 
1647
1769
  // src/widgets/map.tsx
1648
- import { jsx as jsx8, jsxs as jsxs3 } from "react/jsx-runtime";
1770
+ import { jsx as jsx7, jsxs as jsxs3 } from "react/jsx-runtime";
1649
1771
  var mapLibrePromise;
1650
1772
  function loadMapLibre(runtime) {
1651
1773
  if (!mapLibrePromise) {
@@ -1664,6 +1786,9 @@ function MapWidget({ widget, data }) {
1664
1786
  const title = optString5(widget.options, "title");
1665
1787
  const positions = placeable(data.locations);
1666
1788
  const runtime = useMapRuntime();
1789
+ const select = useWidgetSelect();
1790
+ const selectionTarget = optString5(widget.options, "selectionTarget");
1791
+ const pick = select && selectionTarget ? (deviceToken) => select({ slot: selectionTarget, binding: { kind: "device", deviceToken } }) : void 0;
1667
1792
  const basemap = renderableBasemap(
1668
1793
  resolveBasemap(
1669
1794
  {
@@ -1674,7 +1799,7 @@ function MapWidget({ widget, data }) {
1674
1799
  )
1675
1800
  );
1676
1801
  if (data.forbidden) {
1677
- return /* @__PURE__ */ jsx8(WidgetFrame5, { title, children: /* @__PURE__ */ jsx8(
1802
+ return /* @__PURE__ */ jsx7(WidgetFrame5, { title, children: /* @__PURE__ */ jsx7(
1678
1803
  MapNotice,
1679
1804
  {
1680
1805
  glyph: "\u{1F512}",
@@ -1684,7 +1809,7 @@ function MapWidget({ widget, data }) {
1684
1809
  ) });
1685
1810
  }
1686
1811
  if (!runtime) {
1687
- return /* @__PURE__ */ jsx8(WidgetFrame5, { title, children: /* @__PURE__ */ jsx8(
1812
+ return /* @__PURE__ */ jsx7(WidgetFrame5, { title, children: /* @__PURE__ */ jsx7(
1688
1813
  MapNotice,
1689
1814
  {
1690
1815
  glyph: "\u{1F9E9}",
@@ -1694,10 +1819,10 @@ function MapWidget({ widget, data }) {
1694
1819
  ) });
1695
1820
  }
1696
1821
  if (data.loading && positions.length === 0) {
1697
- return /* @__PURE__ */ jsx8(WidgetFrame5, { title, children: /* @__PURE__ */ jsx8(MapNotice, { heading: "Loading\u2026" }) });
1822
+ return /* @__PURE__ */ jsx7(WidgetFrame5, { title, children: /* @__PURE__ */ jsx7(MapNotice, { heading: "Loading\u2026" }) });
1698
1823
  }
1699
1824
  if (positions.length === 0) {
1700
- return /* @__PURE__ */ jsx8(WidgetFrame5, { title, children: /* @__PURE__ */ jsx8(
1825
+ return /* @__PURE__ */ jsx7(WidgetFrame5, { title, children: /* @__PURE__ */ jsx7(
1701
1826
  MapNotice,
1702
1827
  {
1703
1828
  heading: data.deviceTokens.length === 0 ? "No devices selected" : "No positions reported",
@@ -1705,9 +1830,13 @@ function MapWidget({ widget, data }) {
1705
1830
  }
1706
1831
  ) });
1707
1832
  }
1708
- return /* @__PURE__ */ jsx8(WidgetFrame5, { title, flush: true, children: /* @__PURE__ */ jsx8(LiveMap, { basemap, positions, runtime }) });
1833
+ return /* @__PURE__ */ jsx7(WidgetFrame5, { title, flush: true, children: /* @__PURE__ */ jsx7(LiveMap, { basemap, positions, runtime, pick }) });
1709
1834
  }
1710
- function PlainPanel({ positions, note }) {
1835
+ function PlainPanel({
1836
+ positions,
1837
+ note,
1838
+ pick
1839
+ }) {
1711
1840
  const points = projectToPanel(positions);
1712
1841
  return /* @__PURE__ */ jsxs3(
1713
1842
  "div",
@@ -1721,10 +1850,11 @@ function PlainPanel({ positions, note }) {
1721
1850
  background: css4("muted")
1722
1851
  },
1723
1852
  children: [
1724
- positions.map((position, i) => /* @__PURE__ */ jsx8(
1853
+ positions.map((position, i) => /* @__PURE__ */ jsx7(
1725
1854
  Marker,
1726
1855
  {
1727
1856
  position,
1857
+ pick,
1728
1858
  style: {
1729
1859
  position: "absolute",
1730
1860
  left: `${points[i].x * 100}%`,
@@ -1734,7 +1864,7 @@ function PlainPanel({ positions, note }) {
1734
1864
  },
1735
1865
  position.deviceToken
1736
1866
  )),
1737
- /* @__PURE__ */ jsx8(
1867
+ /* @__PURE__ */ jsx7(
1738
1868
  "div",
1739
1869
  {
1740
1870
  "data-testid": "map-no-tiles-note",
@@ -1761,15 +1891,19 @@ function PlainPanel({ positions, note }) {
1761
1891
  function LiveMap({
1762
1892
  basemap,
1763
1893
  positions,
1764
- runtime
1894
+ runtime,
1895
+ pick
1765
1896
  }) {
1766
1897
  const containerRef = useRef(null);
1767
1898
  const mapRef = useRef(null);
1768
1899
  const markersRef = useRef([]);
1769
- const [status, setStatus] = useState3("loading");
1900
+ const [status, setStatus] = useState("loading");
1901
+ const pickRef = useRef(pick);
1902
+ pickRef.current = pick;
1903
+ const selectable = pick !== void 0;
1770
1904
  const tileUrl = basemap?.tileUrl ?? null;
1771
1905
  const attribution = basemap?.attribution ?? null;
1772
- useEffect2(() => {
1906
+ useEffect(() => {
1773
1907
  let cancelled = false;
1774
1908
  setStatus("loading");
1775
1909
  Promise.all([
@@ -1797,7 +1931,7 @@ function LiveMap({
1797
1931
  mapRef.current = null;
1798
1932
  };
1799
1933
  }, [tileUrl, attribution, runtime.workerUrl]);
1800
- useEffect2(() => {
1934
+ useEffect(() => {
1801
1935
  const map = mapRef.current;
1802
1936
  if (status !== "ready" || !map) return;
1803
1937
  let live = true;
@@ -1810,7 +1944,24 @@ function LiveMap({
1810
1944
  el.setAttribute("data-device", position.deviceToken);
1811
1945
  el.setAttribute("title", describePosition(position));
1812
1946
  el.setAttribute("aria-label", describePosition(position));
1813
- Object.assign(el.style, MARKER_DOT_STYLE);
1947
+ if (selectable) {
1948
+ Object.assign(el.style, MARKER_HIT_STYLE);
1949
+ el.setAttribute("role", "button");
1950
+ el.setAttribute("tabindex", "0");
1951
+ const dot = document.createElement("div");
1952
+ Object.assign(dot.style, MARKER_DOT_STYLE, { cursor: "inherit" });
1953
+ el.appendChild(dot);
1954
+ const choose = () => pickRef.current?.(position.deviceToken);
1955
+ el.addEventListener("click", choose);
1956
+ el.addEventListener("keydown", (e) => {
1957
+ if (e.key === "Enter" || e.key === " ") {
1958
+ e.preventDefault();
1959
+ choose();
1960
+ }
1961
+ });
1962
+ } else {
1963
+ Object.assign(el.style, MARKER_DOT_STYLE);
1964
+ }
1814
1965
  return new maplibre.Marker({ element: el }).setLngLat([position.longitude, position.latitude]).addTo(map);
1815
1966
  });
1816
1967
  if (positions.length === 1) {
@@ -1824,21 +1975,30 @@ function LiveMap({
1824
1975
  return () => {
1825
1976
  live = false;
1826
1977
  };
1827
- }, [status, positionsKey(positions)]);
1978
+ }, [status, positionsKey(positions), selectable]);
1828
1979
  if (status === "unavailable") {
1829
- return /* @__PURE__ */ jsx8(
1980
+ return /* @__PURE__ */ jsx7(
1830
1981
  PlainPanel,
1831
1982
  {
1832
1983
  positions,
1833
- note: "Map renderer unavailable \u2014 positions are shown without a basemap."
1984
+ note: "Map renderer unavailable \u2014 positions are shown without a basemap.",
1985
+ pick
1834
1986
  }
1835
1987
  );
1836
1988
  }
1837
- return /* @__PURE__ */ jsx8("div", { ref: containerRef, "data-testid": "map-canvas", style: { width: "100%", height: "100%" } });
1989
+ return /* @__PURE__ */ jsx7("div", { ref: containerRef, "data-testid": "map-canvas", style: { width: "100%", height: "100%" } });
1838
1990
  }
1839
1991
  function positionsKey(positions) {
1840
1992
  return positions.map((p) => `${p.deviceToken}:${p.latitude}:${p.longitude}:${p.heading ?? ""}`).join("|");
1841
1993
  }
1994
+ var MARKER_HIT_STYLE = {
1995
+ width: "24px",
1996
+ height: "24px",
1997
+ display: "flex",
1998
+ alignItems: "center",
1999
+ justifyContent: "center",
2000
+ cursor: "pointer"
2001
+ };
1842
2002
  var MARKER_DOT_STYLE = {
1843
2003
  width: "12px",
1844
2004
  height: "12px",
@@ -1850,10 +2010,26 @@ var MARKER_DOT_STYLE = {
1850
2010
  };
1851
2011
  function Marker({
1852
2012
  position,
1853
- style
2013
+ style,
2014
+ pick
1854
2015
  }) {
1855
2016
  const label = describePosition(position);
1856
- return /* @__PURE__ */ jsx8(
2017
+ if (pick) {
2018
+ return /* @__PURE__ */ jsx7(
2019
+ "button",
2020
+ {
2021
+ type: "button",
2022
+ "data-testid": "map-marker",
2023
+ "data-device": position.deviceToken,
2024
+ title: label,
2025
+ "aria-label": label,
2026
+ onClick: () => pick(position.deviceToken),
2027
+ style: { ...MARKER_HIT_STYLE, padding: 0, border: 0, background: "transparent", ...style },
2028
+ children: /* @__PURE__ */ jsx7("span", { style: { ...MARKER_DOT_STYLE, cursor: "inherit", display: "block" } })
2029
+ }
2030
+ );
2031
+ }
2032
+ return /* @__PURE__ */ jsx7(
1857
2033
  "div",
1858
2034
  {
1859
2035
  "data-testid": "map-marker",
@@ -1882,25 +2058,25 @@ function MapNotice({ glyph, heading, detail }) {
1882
2058
  color: css4("muted-foreground")
1883
2059
  },
1884
2060
  children: [
1885
- glyph ? /* @__PURE__ */ jsx8("div", { style: { fontSize: fontVar4("xl"), lineHeight: 1 }, "aria-hidden": "true", children: glyph }) : null,
1886
- /* @__PURE__ */ jsx8("div", { style: { fontSize: fontVar4("md"), fontWeight: 600 }, children: heading }),
1887
- detail ? /* @__PURE__ */ jsx8("div", { style: { fontSize: fontVar4("sm"), opacity: 0.8 }, children: detail }) : null
2061
+ glyph ? /* @__PURE__ */ jsx7("div", { style: { fontSize: fontVar4("xl"), lineHeight: 1 }, "aria-hidden": "true", children: glyph }) : null,
2062
+ /* @__PURE__ */ jsx7("div", { style: { fontSize: fontVar4("md"), fontWeight: 600 }, children: heading }),
2063
+ detail ? /* @__PURE__ */ jsx7("div", { style: { fontSize: fontVar4("sm"), opacity: 0.8 }, children: detail }) : null
1888
2064
  ]
1889
2065
  }
1890
2066
  );
1891
2067
  }
1892
- var mapWidget = defineWidget10({
2068
+ var mapWidget = defineWidget11({
1893
2069
  descriptor: builtinWidgetDescriptors["map"],
1894
2070
  implementation: { kind: "trusted-react", component: MapWidget }
1895
2071
  });
1896
2072
 
1897
2073
  // src/widgets/table-lazy.tsx
1898
- import { defineWidget as defineWidget11 } from "@devicechain/widget-sdk";
1899
- var chunk6 = lazyChunk(() => import("./chunks/table-6EVV7QZH.js"));
1900
- var loadTableWidget = chunk6.load;
1901
- var tableWidget = defineWidget11({
2074
+ import { defineWidget as defineWidget12 } from "@devicechain/widget-sdk";
2075
+ var chunk7 = lazyChunk(() => import("./chunks/table-6EVV7QZH.js"));
2076
+ var loadTableWidget = chunk7.load;
2077
+ var tableWidget = defineWidget12({
1902
2078
  descriptor: builtinWidgetDescriptors["table"],
1903
- implementation: { kind: "trusted-react", component: chunk6.component("Table") }
2079
+ implementation: { kind: "trusted-react", component: chunk7.component("Table") }
1904
2080
  });
1905
2081
 
1906
2082
  // src/widgets/time-series-chart.tsx
@@ -1908,7 +2084,7 @@ import { useMemo as useMemo3 } from "react";
1908
2084
  import { isValidSpanSeconds, spanLabel } from "@devicechain/dashboards";
1909
2085
  import {
1910
2086
  css as css5,
1911
- defineWidget as defineWidget12,
2087
+ defineWidget as defineWidget13,
1912
2088
  fontVar as fontVar5,
1913
2089
  formatDateTime,
1914
2090
  formatTimestamp as formatTimestamp2,
@@ -1923,24 +2099,31 @@ import {
1923
2099
  WidgetFrame as WidgetFrame6
1924
2100
  } from "@devicechain/widget-sdk";
1925
2101
  import { buildLineOption, EChart as EChart2, loadChartEngine as loadChartEngine3, useChartTheme as useChartTheme2 } from "@devicechain/widget-sdk/echarts";
1926
- import { jsx as jsx9, jsxs as jsxs4 } from "react/jsx-runtime";
1927
- function TimeSeriesChart({ widget, data }) {
1928
- return /* @__PURE__ */ jsx9(WidgetFrame6, { title: optString6(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsx9(TimeSeriesBody, { widget, data }) });
2102
+ import { jsx as jsx8, jsxs as jsxs4 } from "react/jsx-runtime";
2103
+ function TimeSeriesChart({ widget, data, markers }) {
2104
+ return /* @__PURE__ */ jsx8(WidgetFrame6, { title: optString6(widget.options, "title"), flush: true, children: /* @__PURE__ */ jsx8(TimeSeriesBody, { widget, data, markers }) });
1929
2105
  }
1930
- function TimeSeriesBody({ widget, data }) {
2106
+ function TimeSeriesBody({ widget, data, markers }) {
1931
2107
  const theme = useChartTheme2();
1932
2108
  const { size, scale } = useWidgetScale2();
1933
2109
  const measurements = widget.datasource?.measurements;
1934
2110
  const unit = optString6(widget.options, "unit");
1935
2111
  const precision = optNumber4(widget.options, "precision");
1936
2112
  const thresholds = useMemo3(() => optThresholds3(widget.options, "thresholds"), [widget.options]);
1937
- const option = useMemo3(
1938
- () => buildLineOption(data.samples, theme, { measurements, unit, precision, thresholds, scale, gaps: data.gaps }, size),
1939
- [data.samples, data.gaps, theme, measurements, unit, precision, thresholds, scale, size]
1940
- );
1941
- return /* @__PURE__ */ jsxs4("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
1942
- /* @__PURE__ */ jsx9(EChart2, { option }),
1943
- /* @__PURE__ */ jsx9(SpanBadge, { widget, data })
2113
+ const min = optNumber4(widget.options, "min");
2114
+ const max = optNumber4(widget.options, "max");
2115
+ const option = useMemo3(() => {
2116
+ const base = buildLineOption(data.samples, theme, { measurements, unit, precision, thresholds, scale, gaps: data.gaps, min, max }, size);
2117
+ return markers ? markers.decorate(base, size) : base;
2118
+ }, [data.samples, data.gaps, theme, measurements, unit, precision, thresholds, scale, size, markers, min, max]);
2119
+ const chart = /* @__PURE__ */ jsxs4("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
2120
+ /* @__PURE__ */ jsx8(EChart2, { option }),
2121
+ /* @__PURE__ */ jsx8(SpanBadge, { widget, data })
2122
+ ] });
2123
+ if (!markers) return chart;
2124
+ return /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", width: "100%", height: "100%" }, children: [
2125
+ /* @__PURE__ */ jsx8("div", { style: { flex: 1, minHeight: 0 }, children: chart }),
2126
+ markers.footer
1944
2127
  ] });
1945
2128
  }
1946
2129
  var SPAN_STRINGS = {
@@ -1970,7 +2153,7 @@ function SpanBadge({ widget, data }) {
1970
2153
  const time = cutAt ? end - Date.parse(cutAt) > DAY_MS ? formatDateTime(cutAt, timeZone) : formatTimestamp2(cutAt, timeZone) : "";
1971
2154
  const text = [showSpan ? label : "", cutAt ? words.from(time) : ""].filter(Boolean).join(" \xB7 ");
1972
2155
  const title = [showSpan ? words.pinned(label) : "", cutAt ? words.cut(time, label) : ""].filter(Boolean).join(" ");
1973
- return /* @__PURE__ */ jsx9(
2156
+ return /* @__PURE__ */ jsx8(
1974
2157
  "span",
1975
2158
  {
1976
2159
  "data-chart-span": showSpan ? label : "",
@@ -1993,7 +2176,7 @@ function SpanBadge({ widget, data }) {
1993
2176
  );
1994
2177
  }
1995
2178
  TimeSeriesChart.preload = loadChartEngine3;
1996
- var timeSeriesChartWidget = defineWidget12({
2179
+ var timeSeriesChartWidget = defineWidget13({
1997
2180
  descriptor: builtinWidgetDescriptors["timeseries-chart"],
1998
2181
  implementation: { kind: "trusted-react", component: TimeSeriesChart }
1999
2182
  });
@@ -2008,6 +2191,7 @@ var builtinWidgetDefinitions = [
2008
2191
  imageWidget,
2009
2192
  alarmTableWidget,
2010
2193
  alarmCountWidget,
2194
+ alarmSeverityWidget,
2011
2195
  commandButtonWidget,
2012
2196
  entitySelectorWidget,
2013
2197
  mapWidget,
@@ -2023,7 +2207,10 @@ var builtinWidgetDefinitions = [
2023
2207
  statCardWidget,
2024
2208
  barChartWidget,
2025
2209
  pieChartWidget,
2026
- fleetTableWidget
2210
+ fleetTableWidget,
2211
+ fleetCountWidget,
2212
+ deviceStatusWidget,
2213
+ onlineDoughnutWidget
2027
2214
  ];
2028
2215
  function createBuiltinWidgetRegistry() {
2029
2216
  return createWidgetRegistry(builtinWidgetDefinitions);
@@ -2045,12 +2232,12 @@ import {
2045
2232
  useLocationStream,
2046
2233
  useMeasurementStream,
2047
2234
  useWidgetCandidates,
2048
- useWidgetSelect,
2235
+ useWidgetSelect as useWidgetSelect2,
2049
2236
  WidgetFrame as WidgetFrame8
2050
2237
  } from "@devicechain/widget-sdk";
2051
2238
 
2052
2239
  // src/widget-containment.tsx
2053
- import { Component, createContext as createContext3, useCallback, useContext as useContext3, useState as useState4 } from "react";
2240
+ import { Component, createContext as createContext3, useCallback, useContext as useContext3, useState as useState2 } from "react";
2054
2241
  import { WidgetFrame as WidgetFrame7 } from "@devicechain/widget-sdk";
2055
2242
 
2056
2243
  // src/widget-host.ts
@@ -2109,7 +2296,7 @@ function narrowCandidates(candidates, descriptor, report) {
2109
2296
  }
2110
2297
 
2111
2298
  // src/widget-containment.tsx
2112
- import { jsx as jsx10, jsxs as jsxs5 } from "react/jsx-runtime";
2299
+ import { jsx as jsx9, jsxs as jsxs5 } from "react/jsx-runtime";
2113
2300
  var MESSAGE_STYLE = {
2114
2301
  display: "flex",
2115
2302
  flexDirection: "column",
@@ -2153,22 +2340,22 @@ var WidgetErrorBoundary = class extends Component {
2153
2340
  render() {
2154
2341
  const { error } = this.state;
2155
2342
  if (!error) return this.props.children;
2156
- return /* @__PURE__ */ jsx10(WidgetFrame7, { title: this.props.title, children: /* @__PURE__ */ jsxs5("div", { style: MESSAGE_STYLE, role: "alert", children: [
2157
- /* @__PURE__ */ jsx10("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u26A0" }),
2158
- /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: "This widget failed to render" }),
2159
- /* @__PURE__ */ jsx10("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: error.message }),
2160
- /* @__PURE__ */ jsx10("button", { type: "button", style: BUTTON_STYLE, onClick: this.reset, children: "Reset widget" })
2343
+ return /* @__PURE__ */ jsx9(WidgetFrame7, { title: this.props.title, children: /* @__PURE__ */ jsxs5("div", { style: MESSAGE_STYLE, role: "alert", children: [
2344
+ /* @__PURE__ */ jsx9("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u26A0" }),
2345
+ /* @__PURE__ */ jsx9("div", { style: { fontSize: 13, fontWeight: 600 }, children: "This widget failed to render" }),
2346
+ /* @__PURE__ */ jsx9("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: error.message }),
2347
+ /* @__PURE__ */ jsx9("button", { type: "button", style: BUTTON_STYLE, onClick: this.reset, children: "Reset widget" })
2161
2348
  ] }) });
2162
2349
  }
2163
2350
  };
2164
2351
  function WidgetContainment({ title, resetKey, children }) {
2165
- const [messages, setMessages] = useState4([]);
2352
+ const [messages, setMessages] = useState2([]);
2166
2353
  const report = useCallback((message) => {
2167
2354
  setMessages((prev) => prev.includes(message) ? prev : [...prev, message].slice(-3));
2168
2355
  }, []);
2169
2356
  const clear = useCallback(() => setMessages([]), []);
2170
2357
  return /* @__PURE__ */ jsxs5("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
2171
- /* @__PURE__ */ jsx10(ReportContext.Provider, { value: report, children: /* @__PURE__ */ jsx10(WidgetErrorBoundary, { title, resetKey, onReset: clear, children }) }),
2358
+ /* @__PURE__ */ jsx9(ReportContext.Provider, { value: report, children: /* @__PURE__ */ jsx9(WidgetErrorBoundary, { title, resetKey, onReset: clear, children }) }),
2172
2359
  messages.length > 0 && /* @__PURE__ */ jsxs5(
2173
2360
  "div",
2174
2361
  {
@@ -2188,8 +2375,8 @@ function WidgetContainment({ title, resetKey, children }) {
2188
2375
  alignItems: "center"
2189
2376
  },
2190
2377
  children: [
2191
- /* @__PURE__ */ jsx10("span", { style: { flex: 1, wordBreak: "break-word" }, children: messages.join(" \xB7 ") }),
2192
- /* @__PURE__ */ jsx10("button", { type: "button", "aria-label": "Dismiss", onClick: clear, style: { cursor: "pointer" }, children: "\xD7" })
2378
+ /* @__PURE__ */ jsx9("span", { style: { flex: 1, wordBreak: "break-word" }, children: messages.join(" \xB7 ") }),
2379
+ /* @__PURE__ */ jsx9("button", { type: "button", "aria-label": "Dismiss", onClick: clear, style: { cursor: "pointer" }, children: "\xD7" })
2193
2380
  ]
2194
2381
  }
2195
2382
  )
@@ -2197,12 +2384,12 @@ function WidgetContainment({ title, resetKey, children }) {
2197
2384
  }
2198
2385
 
2199
2386
  // src/connected-widget.tsx
2200
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
2387
+ import { jsx as jsx10, jsxs as jsxs6 } from "react/jsx-runtime";
2201
2388
  function ConnectedWidget(props) {
2202
2389
  const { widget } = props;
2203
2390
  const registry = props.registry ?? defaultWidgetRegistry();
2204
2391
  const resetKey = JSON.stringify([widget.type, widget.options ?? null, widget.datasource ?? null]);
2205
- return /* @__PURE__ */ jsx11(WidgetContainment, { title: optString7(widget.options, "title"), resetKey, children: /* @__PURE__ */ jsx11(ConnectedWidgetBody, { ...props, registry }) });
2392
+ return /* @__PURE__ */ jsx10(WidgetContainment, { title: optString7(widget.options, "title"), resetKey, children: /* @__PURE__ */ jsx10(ConnectedWidgetBody, { ...props, registry }) });
2206
2393
  }
2207
2394
  function ConnectedWidgetBody({ widget, hub, registry, actions, initialSamples, initialSamplesAsOf, onReconnect }) {
2208
2395
  const unresolved = widget.type === UNKNOWN_WIDGET_TYPE;
@@ -2212,16 +2399,16 @@ function ConnectedWidgetBody({ widget, hub, registry, actions, initialSamples, i
2212
2399
  const availability = useDatasourceAvailability(hub, readsDatasource ? widget.datasource : void 0);
2213
2400
  const supported = useDatasourceSupported(hub, readsDatasource ? widget.datasource : void 0);
2214
2401
  if (!supported) {
2215
- return /* @__PURE__ */ jsx11(WidgetNotForCustomerUsersFrame, { widget });
2402
+ return /* @__PURE__ */ jsx10(WidgetNotForCustomerUsersFrame, { widget });
2216
2403
  }
2217
2404
  if (availability === "unavailable") {
2218
- return /* @__PURE__ */ jsx11(WidgetUnavailableFrame, { widget });
2405
+ return /* @__PURE__ */ jsx10(WidgetUnavailableFrame, { widget });
2219
2406
  }
2220
2407
  if (widget.type === UNKNOWN_WIDGET_TYPE) {
2221
- return /* @__PURE__ */ jsx11(UnresolvedWidgetFrame, { storedType: widget.raw.type, reason: widget.reason });
2408
+ return /* @__PURE__ */ jsx10(UnresolvedWidgetFrame, { storedType: widget.raw.type, reason: widget.reason });
2222
2409
  }
2223
2410
  if (!descriptor || !implementation) {
2224
- return /* @__PURE__ */ jsx11(
2411
+ return /* @__PURE__ */ jsx10(
2225
2412
  UnresolvedWidgetFrame,
2226
2413
  {
2227
2414
  storedType: widget.type,
@@ -2229,7 +2416,7 @@ function ConnectedWidgetBody({ widget, hub, registry, actions, initialSamples, i
2229
2416
  }
2230
2417
  );
2231
2418
  }
2232
- return /* @__PURE__ */ jsx11(HostScope, { descriptor, actions, children: (hostActions) => /* @__PURE__ */ jsx11(
2419
+ return /* @__PURE__ */ jsx10(HostScope, { descriptor, actions, children: (hostActions) => /* @__PURE__ */ jsx10(
2233
2420
  ChannelBody,
2234
2421
  {
2235
2422
  widget,
@@ -2245,8 +2432,8 @@ function ConnectedWidgetBody({ widget, hub, registry, actions, initialSamples, i
2245
2432
  }
2246
2433
  function useDatasourceSupported(hub, datasource) {
2247
2434
  const key = datasource ? JSON.stringify(datasource) : null;
2248
- const [refused, setRefused] = useState5(null);
2249
- useEffect3(() => {
2435
+ const [refused, setRefused] = useState3(null);
2436
+ useEffect2(() => {
2250
2437
  if (!datasource || !hub.isDatasourceSupported) return;
2251
2438
  let cancelled = false;
2252
2439
  hub.isDatasourceSupported(datasource).then(
@@ -2269,7 +2456,7 @@ function HostScope({
2269
2456
  children
2270
2457
  }) {
2271
2458
  const report = useReportRejection();
2272
- const select = useWidgetSelect();
2459
+ const select = useWidgetSelect2();
2273
2460
  const candidates = useWidgetCandidates();
2274
2461
  const narrowed = useMemo4(() => narrowActions(actions, descriptor, report), [actions, descriptor, report]);
2275
2462
  const narrowedSelect = useMemo4(() => narrowSelect(select, descriptor), [select, descriptor]);
@@ -2277,36 +2464,40 @@ function HostScope({
2277
2464
  () => narrowCandidates(candidates, descriptor, report),
2278
2465
  [candidates, descriptor, report]
2279
2466
  );
2280
- return /* @__PURE__ */ jsx11(WidgetSelectProvider, { select: narrowedSelect, children: /* @__PURE__ */ jsx11(WidgetCandidatesProvider, { candidates: narrowedCandidates, children: children(narrowed) }) });
2467
+ return /* @__PURE__ */ jsx10(WidgetSelectProvider, { select: narrowedSelect, children: /* @__PURE__ */ jsx10(WidgetCandidatesProvider, { candidates: narrowedCandidates, children: children(narrowed) }) });
2281
2468
  }
2282
2469
  function ChannelBody(props) {
2283
2470
  switch (props.descriptor.channel) {
2284
2471
  case "alarm":
2285
- return /* @__PURE__ */ jsx11(AlarmConnectedWidget, { ...props });
2472
+ return /* @__PURE__ */ jsx10(AlarmConnectedWidget, { ...props });
2286
2473
  case "control":
2287
- return /* @__PURE__ */ jsx11(ControlConnectedWidget, { ...props });
2474
+ return /* @__PURE__ */ jsx10(ControlConnectedWidget, { ...props });
2288
2475
  case "location":
2289
- return /* @__PURE__ */ jsx11(LocationConnectedWidget, { ...props });
2476
+ return /* @__PURE__ */ jsx10(LocationConnectedWidget, { ...props });
2290
2477
  case "aggregate":
2291
- return /* @__PURE__ */ jsx11(AggregateConnectedWidget, { ...props });
2478
+ return /* @__PURE__ */ jsx10(AggregateConnectedWidget, { ...props });
2292
2479
  case "selection":
2293
2480
  case "none":
2294
- return /* @__PURE__ */ jsx11(StaticConnectedWidget, { ...props });
2481
+ return /* @__PURE__ */ jsx10(StaticConnectedWidget, { ...props });
2295
2482
  case "measurement":
2296
- return /* @__PURE__ */ jsx11(MeasurementConnectedWidget, { ...props });
2483
+ return /* @__PURE__ */ jsx10(MeasurementConnectedWidget, { ...props });
2297
2484
  }
2298
2485
  }
2299
2486
  function StaticConnectedWidget({ widget, Component: Component2, actions }) {
2300
- return /* @__PURE__ */ jsx11(Component2, { widget, data: null, actions });
2487
+ return /* @__PURE__ */ jsx10(Component2, { widget, data: null, actions });
2301
2488
  }
2302
- var FleetFeed = lazyChunk(() => import("./chunks/fleet-feed-5QGDLOV5.js")).component("default");
2489
+ var FleetFeed = lazyChunk(() => import("./chunks/fleet-feed-2SKDR6ZQ.js")).component("default", void 0, ChunkLoadingFrame);
2490
+ var PresenceFeed = lazyChunk(() => import("./chunks/presence-widgets-2LIUKN4Y.js")).component("PresenceFeed");
2303
2491
  function MeasurementConnectedWidget(props) {
2492
+ if (presenceMode(props.descriptor)) {
2493
+ return /* @__PURE__ */ jsx10(PresenceFeed, { hub: props.hub, widget: props.widget, descriptor: props.descriptor, Component: props.Component, actions: props.actions, ErrorFrame: WidgetErrorFrame });
2494
+ }
2304
2495
  const { widget, hub, descriptor, Component: Component2, actions } = props;
2305
2496
  const members = membersMode(descriptor, widget);
2306
2497
  if (members || widget.datasource?.kind === "anchor") {
2307
- return /* @__PURE__ */ jsx11(FleetFeed, { hub, widget, descriptor, Component: Component2, actions, ErrorFrame: WidgetErrorFrame, refuse: !members });
2498
+ return /* @__PURE__ */ jsx10(FleetFeed, { hub, widget, descriptor, Component: Component2, actions, ErrorFrame: WidgetErrorFrame, refuse: !members });
2308
2499
  }
2309
- return /* @__PURE__ */ jsx11(StreamConnectedWidget, { ...props });
2500
+ return /* @__PURE__ */ jsx10(StreamConnectedWidget, { ...props });
2310
2501
  }
2311
2502
  function StreamConnectedWidget({ widget, hub, descriptor, Component: Component2, actions, initialSamples, initialSamplesAsOf, onReconnect }) {
2312
2503
  const range = useWidgetTimeRange2();
@@ -2322,42 +2513,58 @@ function StreamConnectedWidget({ widget, hub, descriptor, Component: Component2,
2322
2513
  });
2323
2514
  const freshness = useMemo4(() => {
2324
2515
  if (descriptor.freshness !== true) return void 0;
2325
- const pinned = optString7(widget.options, "measurement");
2516
+ const pinned = measurementIsMembersOnly(descriptor) ? void 0 : optString7(widget.options, "measurement");
2326
2517
  const names = pinned ? [pinned] : widget.datasource?.measurements;
2327
2518
  return {
2328
2519
  at: newestSampleTime(data.latest, names),
2329
2520
  staleAfterSeconds: resolveStaleAfterSeconds(widget.options)
2330
2521
  };
2331
2522
  }, [descriptor, widget.options, widget.datasource, data.latest]);
2332
- if (data.error) return /* @__PURE__ */ jsx11(WidgetErrorFrame, { widget, error: data.error });
2333
- return /* @__PURE__ */ jsx11(WidgetFreshnessProvider, { freshness, children: /* @__PURE__ */ jsx11(Component2, { widget, data, actions }) });
2523
+ if (data.error) return /* @__PURE__ */ jsx10(WidgetErrorFrame, { widget, error: data.error });
2524
+ return /* @__PURE__ */ jsx10(WidgetFreshnessProvider, { freshness, children: wantsEventMarkers(descriptor, widget) ? /* @__PURE__ */ jsx10(EventMarkersFeed, { hub, widget, descriptor, data, Component: Component2, actions }) : /* @__PURE__ */ jsx10(Component2, { widget, data, actions }) });
2334
2525
  }
2335
- var AggregateFeed = lazyChunk(() => import("./chunks/aggregate-feed-BD3CHNFU.js")).component("default");
2526
+ var EventMarkersFeed = lazyChunk(() => import("./chunks/event-markers-feed-GEGTIHXT.js")).component("default");
2527
+ var AggregateFeed = lazyChunk(() => import("./chunks/aggregate-feed-4FSBQYJL.js")).component("default");
2336
2528
  function AggregateConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2337
2529
  const props = { hub, widget, descriptor, Component: Component2, actions, ErrorFrame: WidgetErrorFrame };
2338
- return widget.datasource?.kind === "anchor" ? /* @__PURE__ */ jsx11(FleetFeed, { ...props, refuse: true }) : /* @__PURE__ */ jsx11(AggregateFeed, { ...props });
2530
+ return widget.datasource?.kind === "anchor" ? /* @__PURE__ */ jsx10(FleetFeed, { ...props, refuse: true }) : /* @__PURE__ */ jsx10(AggregateFeed, { ...props });
2339
2531
  }
2340
- function AlarmConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2532
+ var GroupedAlarm = lazyChunk(() => import("./chunks/alarm-widgets-X4PX5MYJ.js")).component("GroupedAlarm");
2533
+ function AlarmConnectedWidget(props) {
2534
+ return groupsAlarmsByDeviceType(props.descriptor, props.widget) ? /* @__PURE__ */ jsx10(
2535
+ GroupedAlarm,
2536
+ {
2537
+ hub: props.hub,
2538
+ widget: props.widget,
2539
+ base: alarmSubscription(props.descriptor, props.widget),
2540
+ useHeld,
2541
+ Component: props.Component,
2542
+ actions: props.actions,
2543
+ ErrorFrame: WidgetErrorFrame
2544
+ }
2545
+ ) : /* @__PURE__ */ jsx10(PlainAlarmConnectedWidget, { ...props });
2546
+ }
2547
+ function PlainAlarmConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2341
2548
  const range = useWidgetTimeRange2();
2342
2549
  const live = useAlarmStream(hub, alarmSubscription(descriptor, widget));
2343
2550
  const data = useHeld(live, range?.paused === true, (s) => !s.loading);
2344
2551
  if (data.error && data.alarms.length === 0 && data.total === 0) {
2345
- return /* @__PURE__ */ jsx11(WidgetErrorFrame, { widget, error: data.error });
2552
+ return /* @__PURE__ */ jsx10(WidgetErrorFrame, { widget, error: data.error });
2346
2553
  }
2347
- return /* @__PURE__ */ jsx11(Component2, { widget, data, actions });
2554
+ return /* @__PURE__ */ jsx10(Component2, { widget, data, actions });
2348
2555
  }
2349
2556
  function ControlConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2350
2557
  const data = useCommandStream(hub, commandSubscription(descriptor, widget));
2351
- return /* @__PURE__ */ jsx11(Component2, { widget, data, actions });
2558
+ return /* @__PURE__ */ jsx10(Component2, { widget, data, actions });
2352
2559
  }
2353
2560
  function LocationConnectedWidget({ widget, hub, Component: Component2, actions }) {
2354
2561
  const range = useWidgetTimeRange2();
2355
2562
  const live = useLocationStream(hub, { datasource: widget.datasource });
2356
2563
  const data = useHeld(live, range?.paused === true, (s) => !s.loading);
2357
2564
  if (data.error && data.locations.length === 0) {
2358
- return /* @__PURE__ */ jsx11(WidgetErrorFrame, { widget, error: data.error });
2565
+ return /* @__PURE__ */ jsx10(WidgetErrorFrame, { widget, error: data.error });
2359
2566
  }
2360
- return /* @__PURE__ */ jsx11(Component2, { widget, data, actions });
2567
+ return /* @__PURE__ */ jsx10(Component2, { widget, data, actions });
2361
2568
  }
2362
2569
  var STATE_STYLE = {
2363
2570
  display: "flex",
@@ -2378,64 +2585,64 @@ function UnresolvedWidgetFrame({
2378
2585
  }) {
2379
2586
  const storedType = typeof stored === "string" ? stored : "";
2380
2587
  const heading = (reason?.code ?? "unregistered") === "unregistered" ? "This widget needs a newer viewer" : "This widget cannot be shown here";
2381
- return /* @__PURE__ */ jsx11(WidgetFrame8, { children: /* @__PURE__ */ jsxs6(
2588
+ return /* @__PURE__ */ jsx10(WidgetFrame8, { children: /* @__PURE__ */ jsxs6(
2382
2589
  "div",
2383
2590
  {
2384
2591
  style: STATE_STYLE,
2385
2592
  children: [
2386
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: heading }),
2593
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: heading }),
2387
2594
  storedType && !reason?.message.includes(storedType) && /* @__PURE__ */ jsxs6("div", { style: { fontSize: 11, opacity: 0.8 }, children: [
2388
2595
  "Widget type: ",
2389
2596
  storedType
2390
2597
  ] }),
2391
- reason && /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: reason.message })
2598
+ reason && /* @__PURE__ */ jsx10("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: reason.message })
2392
2599
  ]
2393
2600
  }
2394
2601
  ) });
2395
2602
  }
2396
2603
  function WidgetForbiddenFrame({ widget }) {
2397
- return /* @__PURE__ */ jsx11(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6("div", { role: "status", style: STATE_STYLE, children: [
2398
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2399
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: accessStrings().forbidden })
2604
+ return /* @__PURE__ */ jsx10(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6("div", { role: "status", style: STATE_STYLE, children: [
2605
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2606
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: accessStrings().forbidden })
2400
2607
  ] }) });
2401
2608
  }
2402
2609
  function WidgetUnavailableFrame({ widget }) {
2403
- return /* @__PURE__ */ jsx11(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2610
+ return /* @__PURE__ */ jsx10(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2404
2611
  "div",
2405
2612
  {
2406
2613
  style: STATE_STYLE,
2407
2614
  children: [
2408
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2409
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Device unavailable" }),
2410
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, opacity: 0.8 }, children: "The bound device no longer exists." })
2615
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2616
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Device unavailable" }),
2617
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 11, opacity: 0.8 }, children: "The bound device no longer exists." })
2411
2618
  ]
2412
2619
  }
2413
2620
  ) });
2414
2621
  }
2415
2622
  function WidgetNotForCustomerUsersFrame({ widget }) {
2416
- return /* @__PURE__ */ jsx11(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2623
+ return /* @__PURE__ */ jsx10(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2417
2624
  "div",
2418
2625
  {
2419
2626
  role: "status",
2420
2627
  style: STATE_STYLE,
2421
2628
  children: [
2422
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2423
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Not available for customer users" }),
2424
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, opacity: 0.8 }, children: "This widget is bound to an area or an asset, which customer users cannot view." })
2629
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2630
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Not available for customer users" }),
2631
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 11, opacity: 0.8 }, children: "This widget is bound to an area or an asset, which customer users cannot view." })
2425
2632
  ]
2426
2633
  }
2427
2634
  ) });
2428
2635
  }
2429
2636
  function WidgetErrorFrame({ widget, error }) {
2430
- if (isForbiddenError(error)) return /* @__PURE__ */ jsx11(WidgetForbiddenFrame, { widget });
2431
- return /* @__PURE__ */ jsx11(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2637
+ if (isForbiddenError(error)) return /* @__PURE__ */ jsx10(WidgetForbiddenFrame, { widget });
2638
+ return /* @__PURE__ */ jsx10(WidgetFrame8, { title: optString7(widget.options, "title"), children: /* @__PURE__ */ jsxs6(
2432
2639
  "div",
2433
2640
  {
2434
2641
  style: STATE_STYLE,
2435
2642
  children: [
2436
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 20, lineHeight: 1 }, children: "\u26A0" }),
2437
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Data unavailable" }),
2438
- /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: String(error) })
2643
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 20, lineHeight: 1 }, children: "\u26A0" }),
2644
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Data unavailable" }),
2645
+ /* @__PURE__ */ jsx10("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: String(error) })
2439
2646
  ]
2440
2647
  }
2441
2648
  ) });
@@ -2449,7 +2656,7 @@ import {
2449
2656
  WidgetThemeRootProvider,
2450
2657
  WidgetTimeZoneProvider
2451
2658
  } from "@devicechain/widget-sdk";
2452
- import { Fragment as Fragment2, jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
2659
+ import { Fragment as Fragment2, jsx as jsx11, jsxs as jsxs7 } from "react/jsx-runtime";
2453
2660
  function DashboardRenderer({
2454
2661
  definition,
2455
2662
  hub,
@@ -2498,8 +2705,8 @@ function DashboardRenderer({
2498
2705
  "data-time-paused": range.paused ? "" : void 0,
2499
2706
  style: { ...sizingStyle(effectiveSizing, bg), ...tokenStyle(scale) },
2500
2707
  children: [
2501
- /* @__PURE__ */ jsx12(WidgetTokenStyles, {}),
2502
- /* @__PURE__ */ jsx12(WidgetDensityProvider, { scale, children: /* @__PURE__ */ jsx12(WidgetThemeRootProvider, { root: themeRoot, children: /* @__PURE__ */ jsx12(WidgetTimeZoneProvider, { timeZone, children: /* @__PURE__ */ jsx12(WidgetTimeRangeProvider, { range, children: /* @__PURE__ */ jsx12(WidgetSelectProvider2, { select, children: /* @__PURE__ */ jsx12(WidgetCandidatesProvider2, { candidates, children: /* @__PURE__ */ jsx12("div", { style: { padding: lengths.padding }, children: /* @__PURE__ */ jsx12("div", { style: { containerType: "inline-size" }, children: /* @__PURE__ */ jsx12(
2708
+ /* @__PURE__ */ jsx11(WidgetTokenStyles, {}),
2709
+ /* @__PURE__ */ jsx11(WidgetDensityProvider, { scale, children: /* @__PURE__ */ jsx11(WidgetThemeRootProvider, { root: themeRoot, children: /* @__PURE__ */ jsx11(WidgetTimeZoneProvider, { timeZone, children: /* @__PURE__ */ jsx11(WidgetTimeRangeProvider, { range, children: /* @__PURE__ */ jsx11(WidgetSelectProvider2, { select, children: /* @__PURE__ */ jsx11(WidgetCandidatesProvider2, { candidates, children: /* @__PURE__ */ jsx11("div", { style: { padding: lengths.padding }, children: /* @__PURE__ */ jsx11("div", { style: { containerType: "inline-size" }, children: /* @__PURE__ */ jsx11(
2503
2710
  "div",
2504
2711
  {
2505
2712
  "data-grid": "",
@@ -2512,7 +2719,7 @@ function DashboardRenderer({
2512
2719
  width: "100%",
2513
2720
  ...{ "--dc-cell": lengths.cell }
2514
2721
  },
2515
- children: /* @__PURE__ */ jsx12(
2722
+ children: /* @__PURE__ */ jsx11(
2516
2723
  PlacedWidgets,
2517
2724
  {
2518
2725
  widgets,
@@ -2520,7 +2727,7 @@ function DashboardRenderer({
2520
2727
  columns,
2521
2728
  grid: layout.grid,
2522
2729
  lengths,
2523
- render: (widget) => /* @__PURE__ */ jsx12(WidgetFrameStyleProvider, { frame: resolveFrame(canvasFrame, widgetFrame(widget)), children: /* @__PURE__ */ jsx12(WidgetSubjectProvider, { label: widgetSubjectLabel(widget), children: /* @__PURE__ */ jsx12(
2730
+ render: (widget) => /* @__PURE__ */ jsx11(WidgetFrameStyleProvider, { frame: resolveFrame(canvasFrame, widgetFrame(widget)), children: /* @__PURE__ */ jsx11(WidgetSubjectProvider, { label: widgetSubjectLabel(widget), children: /* @__PURE__ */ jsx11(
2524
2731
  ConnectedWidget,
2525
2732
  {
2526
2733
  widget,
@@ -2608,15 +2815,15 @@ function PlacedWidgets({
2608
2815
  relative(widget.id)
2609
2816
  ] }, widget.id)
2610
2817
  ));
2611
- return /* @__PURE__ */ jsx12(Fragment2, { children: placed.map(({ widget, placement }) => {
2818
+ return /* @__PURE__ */ jsx11(Fragment2, { children: placed.map(({ widget, placement }) => {
2612
2819
  if (placement.mode !== "grid") return null;
2613
2820
  const style = gridItemStyle(grid.get(widget.id) ?? placement, columns);
2614
2821
  return /* @__PURE__ */ jsxs7(Fragment, { children: [
2615
- /* @__PURE__ */ jsx12("div", { "data-widget": widget.id, style, children: render(widget) }),
2822
+ /* @__PURE__ */ jsx11("div", { "data-widget": widget.id, style, children: render(widget) }),
2616
2823
  dependents.has(widget.id) ? (
2617
2824
  // The host's area again, as a layer for its relative widgets to be positioned in.
2618
2825
  // It takes no pointer events itself, so the host underneath stays usable.
2619
- /* @__PURE__ */ jsx12("div", { style: { ...style, position: "relative", pointerEvents: "none", zIndex: RELATIVE_Z }, children: relative(widget.id) })
2826
+ /* @__PURE__ */ jsx11("div", { style: { ...style, position: "relative", pointerEvents: "none", zIndex: RELATIVE_Z }, children: relative(widget.id) })
2620
2827
  ) : null
2621
2828
  ] }, widget.id);
2622
2829
  }) });
@@ -2647,7 +2854,7 @@ function widgetSubjectLabel(widget) {
2647
2854
  }
2648
2855
  function useContainerWidth(keyOf) {
2649
2856
  const ref = useRef2(null);
2650
- const [width, setWidth] = useState6(0);
2857
+ const [width, setWidth] = useState4(0);
2651
2858
  const keyOfRef = useRef2(keyOf);
2652
2859
  const measure = useCallback2(() => {
2653
2860
  const el = ref.current;
@@ -2676,12 +2883,12 @@ function seedKey(widget, registry) {
2676
2883
  return JSON.stringify(widget.datasource ?? null);
2677
2884
  }
2678
2885
  function useWidgetHistories(widgets, enabled, registry, range) {
2679
- const [seeds, setSeeds] = useState6({});
2886
+ const [seeds, setSeeds] = useState4({});
2680
2887
  const requested = useRef2({});
2681
2888
  const reseeds = useRef2({});
2682
- const [reseedTick, bumpReseeds] = useState6(0);
2889
+ const [reseedTick, bumpReseeds] = useState4(0);
2683
2890
  const mounted = useRef2(true);
2684
- useEffect4(() => {
2891
+ useEffect3(() => {
2685
2892
  mounted.current = true;
2686
2893
  return () => {
2687
2894
  mounted.current = false;
@@ -2699,7 +2906,7 @@ function useWidgetHistories(widgets, enabled, registry, range) {
2699
2906
  return [[w.id, key, span]];
2700
2907
  });
2701
2908
  const wantedKey = JSON.stringify(wanted);
2702
- useEffect4(() => {
2909
+ useEffect3(() => {
2703
2910
  if (!enabled) {
2704
2911
  requested.current = {};
2705
2912
  setSeeds({});
@@ -2759,12 +2966,12 @@ import {
2759
2966
  spanLabel as spanLabel2,
2760
2967
  withSpan
2761
2968
  } from "@devicechain/dashboards";
2762
- import { useCallback as useCallback3, useEffect as useEffect5, useMemo as useMemo6, useRef as useRef3, useState as useState7 } from "react";
2763
- import { jsx as jsx13, jsxs as jsxs8 } from "react/jsx-runtime";
2969
+ import { useCallback as useCallback3, useEffect as useEffect4, useMemo as useMemo6, useRef as useRef3, useState as useState5 } from "react";
2970
+ import { jsx as jsx12, jsxs as jsxs8 } from "react/jsx-runtime";
2764
2971
  function useBoardTimeRange(defaultSeconds, link = {}) {
2765
- const [state, setState] = useState7(() => initialTimeRange2(defaultSeconds, link));
2972
+ const [state, setState] = useState5(() => initialTimeRange2(defaultSeconds, link));
2766
2973
  const lastDefault = useRef3(defaultSeconds);
2767
- useEffect5(() => {
2974
+ useEffect4(() => {
2768
2975
  if (lastDefault.current === defaultSeconds) return;
2769
2976
  lastDefault.current = defaultSeconds;
2770
2977
  setState((s) => withSpan(s, isValidSpanSeconds2(defaultSeconds) ? defaultSeconds : DEFAULT_TIME_RANGE_SECONDS));
@@ -2823,10 +3030,10 @@ function PausedIndicator({
2823
3030
  borderRadius: 4
2824
3031
  },
2825
3032
  children: [
2826
- /* @__PURE__ */ jsx13("span", { "aria-hidden": "true", children: "\u23F8" }),
3033
+ /* @__PURE__ */ jsx12("span", { "aria-hidden": "true", children: "\u23F8" }),
2827
3034
  labels.paused,
2828
- time ? /* @__PURE__ */ jsx13("span", { style: { fontWeight: 400, textTransform: "none" }, children: ` ${time}` }) : null,
2829
- /* @__PURE__ */ jsx13("span", { "data-time-range-actions-live": "", style: { fontWeight: 400, textTransform: "none" }, children: `\xB7 ${labels.actionsLive}` })
3035
+ time ? /* @__PURE__ */ jsx12("span", { style: { fontWeight: 400, textTransform: "none" }, children: ` ${time}` }) : null,
3036
+ /* @__PURE__ */ jsx12("span", { "data-time-range-actions-live": "", style: { fontWeight: 400, textTransform: "none" }, children: `\xB7 ${labels.actionsLive}` })
2830
3037
  ]
2831
3038
  }
2832
3039
  );
@@ -2846,8 +3053,8 @@ function TimeRangeBar({
2846
3053
  "aria-label": labels.range,
2847
3054
  style: { display: "inline-flex", alignItems: "center", gap: 6, flexWrap: "wrap" },
2848
3055
  children: [
2849
- preset === void 0 ? /* @__PURE__ */ jsx13("button", { type: "button", "aria-pressed": "true", "data-range": spanLabel2(state.spanSeconds), style: button(true), children: spanLabel2(state.spanSeconds) }) : null,
2850
- TIME_RANGE_PRESETS.map((p) => /* @__PURE__ */ jsx13(
3056
+ preset === void 0 ? /* @__PURE__ */ jsx12("button", { type: "button", "aria-pressed": "true", "data-range": spanLabel2(state.spanSeconds), style: button(true), children: spanLabel2(state.spanSeconds) }) : null,
3057
+ TIME_RANGE_PRESETS.map((p) => /* @__PURE__ */ jsx12(
2851
3058
  "button",
2852
3059
  {
2853
3060
  type: "button",
@@ -2859,12 +3066,348 @@ function TimeRangeBar({
2859
3066
  },
2860
3067
  p.id
2861
3068
  )),
2862
- /* @__PURE__ */ jsx13("button", { type: "button", "aria-pressed": state.paused, "data-time-range-pause": "", onClick: onPause, style: button(state.paused), children: state.paused ? labels.resume : labels.pause }),
2863
- state.paused ? /* @__PURE__ */ jsx13(PausedIndicator, { pausedAt: state.pausedAt, labels }) : null
3069
+ /* @__PURE__ */ jsx12("button", { type: "button", "aria-pressed": state.paused, "data-time-range-pause": "", onClick: onPause, style: button(state.paused), children: state.paused ? labels.resume : labels.pause }),
3070
+ state.paused ? /* @__PURE__ */ jsx12(PausedIndicator, { pausedAt: state.pausedAt, labels }) : null
3071
+ ]
3072
+ }
3073
+ );
3074
+ }
3075
+
3076
+ // src/use-bound-devices.ts
3077
+ import {
3078
+ bindParamsOf,
3079
+ bindsToSelection,
3080
+ parseBindParams,
3081
+ selectionToBinds,
3082
+ validateBinds,
3083
+ withBindParams
3084
+ } from "@devicechain/dashboards";
3085
+ import { useCallback as useCallback4, useEffect as useEffect5, useRef as useRef4, useState as useState6 } from "react";
3086
+ var NO_PROBLEMS = { issues: [], missing: [], failed: [] };
3087
+ function hasBindParams(search, definition) {
3088
+ const { binds, issues } = parseBindParams(search, definition);
3089
+ return Object.keys(binds).length > 0 || issues.length > 0;
3090
+ }
3091
+ function useBoundDevices({
3092
+ definition,
3093
+ resolver,
3094
+ address,
3095
+ enabled = true
3096
+ }) {
3097
+ const [core, setCore] = useState6(() => ({
3098
+ selection: {},
3099
+ synced: bindParamsOf(address.search)
3100
+ }));
3101
+ const { selection } = core;
3102
+ const [phase, setPhase] = useState6(
3103
+ () => hasBindParams(address.search, definition) ? "checking" : "ready"
3104
+ );
3105
+ const [problems, setProblems] = useState6(null);
3106
+ const [okBinds, setOkBinds] = useState6({});
3107
+ const definitionRef = useRef4(definition);
3108
+ definitionRef.current = definition;
3109
+ const resolverRef = useRef4(resolver);
3110
+ resolverRef.current = resolver;
3111
+ const replaceRef = useRef4(address.replaceSearch);
3112
+ replaceRef.current = address.replaceSearch;
3113
+ const seenRef = useRef4(null);
3114
+ const generation = useRef4(0);
3115
+ const [attempt, setAttempt] = useState6(0);
3116
+ const search = address.search;
3117
+ useEffect5(() => {
3118
+ if (!enabled) return;
3119
+ const slice = bindParamsOf(search);
3120
+ if (seenRef.current === slice) return;
3121
+ seenRef.current = slice;
3122
+ const parsed = parseBindParams(search, definitionRef.current);
3123
+ if (Object.keys(parsed.binds).length === 0 && parsed.issues.length === 0) {
3124
+ generation.current += 1;
3125
+ setCore({ selection: {}, synced: slice });
3126
+ setProblems(null);
3127
+ setPhase("ready");
3128
+ return;
3129
+ }
3130
+ const gen = ++generation.current;
3131
+ setPhase("checking");
3132
+ void validateBinds(parsed.binds, resolverRef.current).then((check) => {
3133
+ if (gen !== generation.current) return;
3134
+ setOkBinds(check.ok);
3135
+ if (parsed.issues.length === 0 && check.missing.length === 0 && check.failed.length === 0) {
3136
+ setCore({ selection: bindsToSelection(check.ok), synced: slice });
3137
+ setProblems(null);
3138
+ setPhase("ready");
3139
+ } else {
3140
+ setProblems({ issues: parsed.issues, missing: check.missing, failed: check.failed });
3141
+ setCore((c) => ({ ...c, synced: slice }));
3142
+ setPhase("refused");
3143
+ }
3144
+ });
3145
+ }, [search, enabled, attempt]);
3146
+ useEffect5(() => {
3147
+ if (!enabled || phase !== "ready") return;
3148
+ const current = bindParamsOf(search);
3149
+ if (current !== core.synced) return;
3150
+ const next = withBindParams(search, selectionToBinds(core.selection, definition));
3151
+ const nextSlice = bindParamsOf(next);
3152
+ if (nextSlice === current) return;
3153
+ seenRef.current = nextSlice;
3154
+ setCore((c) => ({ ...c, synced: nextSlice }));
3155
+ replaceRef.current(next);
3156
+ }, [core, definition, phase, enabled, search]);
3157
+ const select = useCallback4((t) => {
3158
+ setCore((c) => ({ ...c, selection: { ...c.selection, [t.slot]: t.binding } }));
3159
+ }, []);
3160
+ const reset = useCallback4(() => setCore((c) => ({ ...c, selection: {} })), []);
3161
+ const dismiss = useCallback4(() => {
3162
+ setCore((c) => ({ ...c, selection: bindsToSelection(okBinds) }));
3163
+ setProblems(null);
3164
+ setPhase("ready");
3165
+ }, [okBinds]);
3166
+ const retry = useCallback4(() => {
3167
+ seenRef.current = null;
3168
+ setAttempt((n) => n + 1);
3169
+ }, []);
3170
+ return { selection, select, reset, phase, problems: phase === "refused" ? problems ?? NO_PROBLEMS : null, dismiss, retry };
3171
+ }
3172
+
3173
+ // src/bound-devices-notice.tsx
3174
+ import { css as css6, fontVar as fontVar6 } from "@devicechain/widget-sdk";
3175
+ import { jsx as jsx13, jsxs as jsxs9 } from "react/jsx-runtime";
3176
+ function BoundDevicesNotice({ title, lines, dismissLabel, onDismiss, retryLabel, onRetry }) {
3177
+ const button2 = {
3178
+ minHeight: 44,
3179
+ padding: "0 16px",
3180
+ borderRadius: 8,
3181
+ border: `1px solid ${css6("border")}`,
3182
+ background: css6("card"),
3183
+ color: css6("foreground"),
3184
+ fontSize: fontVar6("md"),
3185
+ cursor: "pointer"
3186
+ };
3187
+ return /* @__PURE__ */ jsxs9(
3188
+ "div",
3189
+ {
3190
+ role: "alert",
3191
+ "data-testid": "bound-devices-notice",
3192
+ style: {
3193
+ boxSizing: "border-box",
3194
+ display: "flex",
3195
+ flexDirection: "column",
3196
+ alignItems: "flex-start",
3197
+ gap: 12,
3198
+ maxWidth: 560,
3199
+ margin: "24px auto",
3200
+ padding: 16,
3201
+ borderRadius: 8,
3202
+ border: `1px solid ${css6("border")}`,
3203
+ background: css6("card"),
3204
+ color: css6("foreground")
3205
+ },
3206
+ children: [
3207
+ /* @__PURE__ */ jsx13("div", { style: { fontSize: fontVar6("lg"), fontWeight: 600 }, children: title }),
3208
+ /* @__PURE__ */ jsx13("ul", { style: { margin: 0, paddingLeft: 20, display: "flex", flexDirection: "column", gap: 4, fontSize: fontVar6("md") }, children: lines.map((line, i) => /* @__PURE__ */ jsx13("li", { style: { overflowWrap: "anywhere" }, children: line }, i)) }),
3209
+ /* @__PURE__ */ jsxs9("div", { style: { display: "flex", flexWrap: "wrap", gap: 8 }, children: [
3210
+ retryLabel && onRetry ? /* @__PURE__ */ jsx13("button", { type: "button", style: button2, onClick: onRetry, "data-testid": "bound-devices-retry", children: retryLabel }) : null,
3211
+ /* @__PURE__ */ jsx13("button", { type: "button", style: button2, onClick: onDismiss, "data-testid": "bound-devices-dismiss", children: dismissLabel })
3212
+ ] })
2864
3213
  ]
2865
3214
  }
2866
3215
  );
2867
3216
  }
3217
+ var ECHO_MAX = 48;
3218
+ var echo = (s) => s.length > ECHO_MAX ? `${s.slice(0, ECHO_MAX)}\u2026` : s;
3219
+ function describeBoundDevicesProblems(problems, w) {
3220
+ const out = [];
3221
+ for (const i of problems.issues) {
3222
+ const slot = echo(i.slot);
3223
+ if (i.kind === "unknown-slot") out.push(w.unknownSlot(slot));
3224
+ else if (i.kind === "not-a-device-slot") out.push(w.notADeviceSlot(slot));
3225
+ else if (i.kind === "duplicate-slot") out.push(w.duplicateSlot(slot));
3226
+ else if (i.kind === "derived-slot") out.push(w.derivedSlot(slot));
3227
+ else if (i.kind === "invalid-token") out.push(w.invalidToken(slot, echo(i.token)));
3228
+ else {
3229
+ const unhandled = i;
3230
+ throw new Error(`describeBoundDevicesProblems: unworded link issue ${JSON.stringify(unhandled)}`);
3231
+ }
3232
+ }
3233
+ for (const m of problems.missing) out.push(w.missing(m.slot, m.token));
3234
+ for (const f of problems.failed) out.push(w.failed(f.slot, f.token));
3235
+ return out;
3236
+ }
3237
+
3238
+ // src/documented-descriptors.ts
3239
+ import { createWidgetCatalog as createWidgetCatalog2 } from "@devicechain/dashboards";
3240
+ import { createWidgetRegistry as createWidgetRegistry2 } from "@devicechain/widget-sdk";
3241
+
3242
+ // src/builtin-option-docs.ts
3243
+ var OPTION_DOCS = {
3244
+ title: "Heading shown in the widget frame. Blank hides the frame heading.",
3245
+ unit: "Unit suffix shown beside the value.",
3246
+ precision: "Decimal places to round displayed values to. Unset rounds to at most 3 decimals (4 significant digits under 1).",
3247
+ thresholds: "Severity bands over the value. Each entry starts a band at its value that runs to the next entry; below the first, and in a normal band, the widget keeps its own colour.",
3248
+ "thresholds.value": "The value at which this band starts.",
3249
+ "thresholds.level": "The severity of this band. The theme decides the colour; normal has none.",
3250
+ "thresholds.label": "Text shown on the chart line for this threshold. Defaults to its value.",
3251
+ staleAfterSeconds: "Seconds after which the newest sample is called stale: the value is greyed out and badged. Unset shows the sample age only and never marks the widget stale.",
3252
+ windowSeconds: "Seconds of history the widget covers, back from now. Leave unset to follow the board\u2019s time range; set it to pin this widget to a fixed span that the viewer\u2019s range picker does not change. 60 to 604800.",
3253
+ measurement: "Which measurement to display. Defaults to the first the datasource selects.",
3254
+ flashOnChange: "Briefly tint a value green on a rise and red on a fall when it changes.",
3255
+ window: "Samples retained across ALL of the widget's measurements before the oldest are dropped. Bounds memory; only the chart draws them. Default 300.",
3256
+ min: "Value at the empty end of the scale. Required: the widget does not guess a range.",
3257
+ max: "Value at the full end of the scale. Required, and above min.",
3258
+ text: "The text to show. Rendered as plain text \u2014 markup is never interpreted.",
3259
+ align: "Horizontal alignment of the text. Default center.",
3260
+ fontSize: "Text size in pixels at the comfortable density; it scales with the board density. Default 16.",
3261
+ url: "Source URL of the image.",
3262
+ alt: "Alternative text describing the image for assistive technology.",
3263
+ fit: "How the image fills its box. Default contain.",
3264
+ state: "Show only alarms in this state. Unset shows every state.",
3265
+ severity: "Show only alarms at this severity. Unset shows every severity.",
3266
+ acknowledged: "Show only acknowledged ('true') or unacknowledged ('false') alarms. Unset shows both.",
3267
+ groupBy: "deviceType: a tab per device type.",
3268
+ deviceType: "Only alarms on devices of this type (token).",
3269
+ deviceTypes: "Types to tab, in order; unset tabs all.",
3270
+ "deviceTypes.token": "Type token.",
3271
+ maxRows: "Alarms loaded into the table. Default 50.",
3272
+ selectionTarget: "Name of the slot this widget re-points when the viewer picks an entity.",
3273
+ commandName: "Command key issued to the target device. Without it the control cannot send.",
3274
+ commandLabel: "Name of the control for assistive technology. Defaults to the command key.",
3275
+ parameterSchema: "The command's parameter descriptors, baked in as JSON at author time. Gives the control its range and its refusals.",
3276
+ tileUrl: "Raster tile URL template, e.g. https://tiles.example.com/{z}/{x}/{y}.png. Leave empty to inherit the tenant's basemap; setting one overrides it for this board only.",
3277
+ attribution: "Credit line the tile source requires, shown on the map. Set whatever the provider you chose asks for.",
3278
+ style: "A plain bar (default) or a thermometer: a vertical tube with a bulb. A thermometer is always vertical.",
3279
+ markAlarms: "Mark this device\u2019s alarms on the chart, from raise to clear.",
3280
+ markAlarmsScope: "Which alarms to mark: only those about what this chart plots (series, the default for a new chart), or every alarm on the device. An alarm with no known measurement is never marked in series mode; it is listed under the chart as another alarm on the device. A chart saved before this option marks the whole device.",
3281
+ markCommands: "Mark the commands sent to this device on the chart.",
3282
+ orientation: "Direction of a bar: horizontal (default) or vertical. Ignored by a thermometer.",
3283
+ readout: "What the readout shows: the value with its unit (default) or the percent of the scale.",
3284
+ onAt: "For an on/off signal: the lamp is lit at or above this value and dark below it. Unset keeps the lamp lit while a value is present, coloured by its threshold band.",
3285
+ parameter: "Which parameter of the command this control sets. May be left blank when the command has exactly one parameter of the right type.",
3286
+ step: "Distance between values. Defaults to 1 for a whole-number parameter, else a hundredth of the range.",
3287
+ mode: "Slider (default) or stepper: \u2212 / + and Send.",
3288
+ stat: "Which figure to show. Default average.",
3289
+ sparkline: "Draw the measurement\u2019s trend over the span under the figure. Default off.",
3290
+ sort: "Order of the categories. Default: as the data source lists them.",
3291
+ columns: "Heading, unit and precision per measurement column; a column with none shows its name.",
3292
+ "columns.measurement": "The measurement column this entry applies to.",
3293
+ "columns.label": "Column heading. Defaults to the measurement name.",
3294
+ "columns.unit": "Unit suffix shown beside the value.",
3295
+ "columns.precision": "Decimal places to round displayed values to. Unset rounds to at most 3 decimals (4 significant digits under 1).",
3296
+ "thresholds.measurement": "The measurement column this entry applies to.",
3297
+ ageLagSeconds: "Show a reading's age under it only when the reading is at least this many seconds older than the newest reading in the table (or stale). Unset shows every reading's age.",
3298
+ sortBy: "The measurement the table is ordered by. Blank orders by device name.",
3299
+ sortDescending: "Largest first.",
3300
+ pageSize: "Devices per page. Default 20.",
3301
+ maxSeries: "Most lines drawn when the chart shows one line per device. Default 5, at most 12. Each line costs one stored-history read, and the chart colours repeat after five; a group larger than this shows the first devices of the group and says how many it left out.",
3302
+ show: "What to count: devices in an abnormal band of the measurement (default), or all devices."
3303
+ };
3304
+ var OPTION_DOC_OVERRIDES = {
3305
+ "latest-card": {
3306
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name."
3307
+ },
3308
+ gauge: {
3309
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name.",
3310
+ min: "Low end of the gauge scale. Default 0.",
3311
+ max: "High end of the gauge scale. Default 100."
3312
+ },
3313
+ "linear-gauge": {
3314
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name."
3315
+ },
3316
+ "level-gauge": {
3317
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name."
3318
+ },
3319
+ battery: {
3320
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name.",
3321
+ min: "Value of an empty battery. Default 0 (the scale is percent unless you set min and max).",
3322
+ max: "Value of a full battery. Default 100 (the scale is percent unless you set min and max)."
3323
+ },
3324
+ "status-light": {
3325
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name."
3326
+ },
3327
+ "signal-bars": {
3328
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name.",
3329
+ thresholds: "The steps of the signal, lowest first: one bar per entry, lit once the value reaches it and coloured by its level. At least two, at most eight."
3330
+ },
3331
+ compass: {
3332
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name.",
3333
+ precision: "Decimal places of the heading. Default 0 (whole degrees). The heading is rounded first, then folded onto 0 to 360, so it never reads 360.",
3334
+ thresholds: "Severity bands over the heading in degrees (0 to 360, after folding). Each entry starts a band at its value that runs to the next entry; below the first, and in a normal band, the needle keeps its own colour."
3335
+ },
3336
+ "progress-ring": {
3337
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name.",
3338
+ readout: "What the middle of the ring shows: the percent of the scale (default) or the value with its unit."
3339
+ },
3340
+ "stat-card": {
3341
+ title: "Heading shown in the widget frame. Defaults to the displayed measurement name."
3342
+ },
3343
+ "device-status": {
3344
+ staleAfterSeconds: "Marks the card stale once its status has not been read for longer than this many seconds (it is re-read every 15 seconds, so only reads that keep failing make it stale). A device that is merely silent is not stale: whether it is online is the status itself. Unset, the age is shown and nothing is marked stale."
3345
+ },
3346
+ "online-doughnut": {
3347
+ staleAfterSeconds: "Marks the ring stale once the status of its devices has not been read for longer than this many seconds (it is re-read every 15 seconds, so only reads that keep failing make it stale). Unset, the age is shown and nothing is marked stale."
3348
+ },
3349
+ "fleet-count": {
3350
+ measurement: "The measurement whose bands flag a device. Defaults to the first the datasource selects."
3351
+ },
3352
+ "command-slider": {
3353
+ min: "Lowest value, used only when the command's parameter declares no minimum.",
3354
+ max: "Highest value, used only when the command's parameter declares no maximum."
3355
+ },
3356
+ "timeseries-chart": {
3357
+ min: "Low end of the value axis. Unset, the axis starts where the chart picks. A reading or threshold below it still shows: the axis widens to take it in.",
3358
+ max: "High end of the value axis, above min. Unset, the chart picks. A reading or threshold above it still shows: the axis widens to take it in.",
3359
+ groupBy: "One line per measurement of one device (the default), or one line per device of a group for the measurement set below. Per device, stored history is drawn as the average of each time bucket (the chart says so), and thresholds are reference lines over it.",
3360
+ measurement: "The measurement compared across the group. Defaults to the first the datasource selects. Used only with one line per device.",
3361
+ markAlarms: "Mark this device\u2019s alarms on the chart, from raise to clear. Not drawn with one line per device.",
3362
+ markCommands: "Mark the commands sent to this device on the chart. Not drawn with one line per device."
3363
+ },
3364
+ "bar-chart": {
3365
+ groupBy: "One bar per measurement (the default), or per device of the group for the measurement set below."
3366
+ },
3367
+ "command-button": {
3368
+ maxRows: "Recent commands shown in the history. Default 20.",
3369
+ commandName: "Command key issued to the target device. Without it the widget cannot send.",
3370
+ commandLabel: "Text on the send button. Defaults to the command key.",
3371
+ parameterSchema: "The command's parameter descriptors, baked in as JSON at author time. Drives the typed form."
3372
+ }
3373
+ };
3374
+
3375
+ // src/documented-descriptors.ts
3376
+ function documentOptions(widgetId, specs, prefix) {
3377
+ const out = {};
3378
+ for (const [key, spec] of Object.entries(specs)) {
3379
+ const path = `${prefix}${key}`;
3380
+ const copy = { ...spec };
3381
+ const doc = OPTION_DOC_OVERRIDES[widgetId]?.[path] ?? OPTION_DOCS[path];
3382
+ if (doc !== void 0) copy.doc = doc;
3383
+ if ("item" in spec && spec.item) copy.item = documentOptions(widgetId, spec.item, `${path}.`);
3384
+ out[key] = copy;
3385
+ }
3386
+ return out;
3387
+ }
3388
+ function documentDescriptor(descriptor) {
3389
+ return { ...descriptor, options: documentOptions(descriptor.id, descriptor.options, "") };
3390
+ }
3391
+ function documentAll(descriptors) {
3392
+ return Object.fromEntries(Object.entries(descriptors).map(([id, d]) => [id, documentDescriptor(d)]));
3393
+ }
3394
+ var builtinWidgetDescriptors2 = /* @__PURE__ */ documentAll(builtinWidgetDescriptors);
3395
+ function catalogOf(descriptors) {
3396
+ return createWidgetCatalog2(Object.values(descriptors));
3397
+ }
3398
+ var builtinWidgetCatalog2 = /* @__PURE__ */ catalogOf(builtinWidgetDescriptors2);
3399
+ function documentDefinitions(definitions) {
3400
+ const byId = builtinWidgetDescriptors2;
3401
+ return definitions.map((d) => ({ ...d, descriptor: byId[d.descriptor.id] }));
3402
+ }
3403
+ var builtinWidgetDefinitions2 = /* @__PURE__ */ documentDefinitions(builtinWidgetDefinitions);
3404
+ function createBuiltinWidgetRegistry2() {
3405
+ return createWidgetRegistry2(builtinWidgetDefinitions2);
3406
+ }
3407
+ function optionsById(descriptors) {
3408
+ return Object.freeze(Object.fromEntries(Object.entries(descriptors).map(([id, d]) => [id, d.options])));
3409
+ }
3410
+ var WIDGET_OPTIONS = /* @__PURE__ */ optionsById(builtinWidgetDescriptors2);
2868
3411
 
2869
3412
  // src/preload.ts
2870
3413
  async function preloadWidgets(registry, types) {
@@ -2895,23 +3438,23 @@ import {
2895
3438
  numberSpec,
2896
3439
  validateOptions
2897
3440
  } from "@devicechain/dashboards";
2898
- var WIDGET_OPTIONS = Object.freeze(
3441
+ var WIDGET_OPTIONS2 = Object.freeze(
2899
3442
  Object.fromEntries(Object.entries(builtinWidgetDescriptors).map(([id, d]) => [id, d.options]))
2900
3443
  );
2901
3444
  function constraintsFor(widgetType) {
2902
3445
  return builtinWidgetCatalog.get(widgetType)?.constraints ?? [];
2903
3446
  }
2904
3447
  function validateWidgetOptions(widgetType, options) {
2905
- const specs = WIDGET_OPTIONS[widgetType];
3448
+ const specs = WIDGET_OPTIONS2[widgetType];
2906
3449
  return validateOptions(specs, options, { constraints: constraintsFor(widgetType), label: widgetType }).map(
2907
3450
  ({ key, code, message }) => ({ widgetType, key, code, message })
2908
3451
  );
2909
3452
  }
2910
3453
  function numberOptionSpec(type, key) {
2911
- return numberSpec(WIDGET_OPTIONS[type], key);
3454
+ return numberSpec(WIDGET_OPTIONS2[type], key);
2912
3455
  }
2913
3456
  function enumOptionValues(type, key) {
2914
- return enumValues(WIDGET_OPTIONS[type], key);
3457
+ return enumValues(WIDGET_OPTIONS2[type], key);
2915
3458
  }
2916
3459
 
2917
3460
  // src/definition-options.ts
@@ -2963,7 +3506,7 @@ import {
2963
3506
  WidgetFrame as WidgetFrame9,
2964
3507
  WidgetSubjectProvider as WidgetSubjectProvider2,
2965
3508
  WidgetSelectProvider as WidgetSelectProvider3,
2966
- useWidgetSelect as useWidgetSelect2,
3509
+ useWidgetSelect as useWidgetSelect3,
2967
3510
  WidgetTimeZoneProvider as WidgetTimeZoneProvider2,
2968
3511
  useWidgetTimeZone as useWidgetTimeZone3,
2969
3512
  WidgetThemeRootProvider as WidgetThemeRootProvider2,
@@ -2986,7 +3529,7 @@ import {
2986
3529
  useResolvedBindings,
2987
3530
  useSlotCandidates,
2988
3531
  useCandidates,
2989
- css as css6
3532
+ css as css7
2990
3533
  } from "@devicechain/widget-sdk";
2991
3534
  import {
2992
3535
  EChart as EChart3,
@@ -2997,8 +3540,10 @@ import {
2997
3540
  } from "@devicechain/widget-sdk/echarts";
2998
3541
  export {
2999
3542
  AlarmCount,
3543
+ AlarmSeverity,
3000
3544
  AlarmTable,
3001
3545
  BUNDLED_BASEMAP_ATTRIBUTION,
3546
+ BoundDevicesNotice,
3002
3547
  CommandButton,
3003
3548
  ConnectedWidget,
3004
3549
  DEFAULT_TIME_RANGE_LABELS,
@@ -3030,15 +3575,17 @@ export {
3030
3575
  buildGaugeOption2 as buildGaugeOption,
3031
3576
  buildLineOption2 as buildLineOption,
3032
3577
  buildPayload,
3033
- builtinWidgetCatalog,
3034
- builtinWidgetDefinitions,
3035
- builtinWidgetDescriptors,
3578
+ builtinWidgetCatalog2 as builtinWidgetCatalog,
3579
+ builtinWidgetDefinitions2 as builtinWidgetDefinitions,
3580
+ builtinWidgetDefinitions as builtinWidgetDefinitionsWithoutDocs,
3581
+ builtinWidgetDescriptors2 as builtinWidgetDescriptors,
3036
3582
  clampNumberOption,
3037
3583
  commandStatusColor,
3038
3584
  commandStatusLabel,
3039
- createBuiltinWidgetRegistry,
3040
- css6 as css,
3585
+ createBuiltinWidgetRegistry2 as createBuiltinWidgetRegistry,
3586
+ css7 as css,
3041
3587
  defaultValues,
3588
+ describeBoundDevicesProblems,
3042
3589
  describePosition,
3043
3590
  enumOptionValues,
3044
3591
  fitValueFont2 as fitValueFont,
@@ -3066,6 +3613,7 @@ export {
3066
3613
  stripUnknownOptions,
3067
3614
  useAlarmStream2 as useAlarmStream,
3068
3615
  useBoardTimeRange,
3616
+ useBoundDevices,
3069
3617
  useCandidates,
3070
3618
  useChartTheme3 as useChartTheme,
3071
3619
  useCommandStream2 as useCommandStream,
@@ -3080,7 +3628,7 @@ export {
3080
3628
  useSlotCandidates,
3081
3629
  useTenantBasemap,
3082
3630
  useWidgetCandidates2 as useWidgetCandidates,
3083
- useWidgetSelect2 as useWidgetSelect,
3631
+ useWidgetSelect3 as useWidgetSelect,
3084
3632
  useWidgetThemeRoot,
3085
3633
  useWidgetTimeZone3 as useWidgetTimeZone,
3086
3634
  validateDefinitionOptions,