@devicechain/widgets 0.19.0 → 0.20.0-rc.4

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.
package/dist/index.js CHANGED
@@ -3,420 +3,477 @@ import {
3
3
  activeBreakpoint,
4
4
  defaultHistoryWindow,
5
5
  fetchWidgetHistory,
6
- resolveWidgetBox
6
+ resolveWidgetBox,
7
+ resolveWidgetDatasource,
8
+ UNKNOWN_WIDGET_TYPE as UNKNOWN_WIDGET_TYPE2
7
9
  } from "@devicechain/dashboards";
8
- import { useEffect as useEffect7, useState as useState7 } from "react";
10
+ import { useEffect as useEffect4, useMemo as useMemo5, useRef as useRef3, useState as useState6 } from "react";
9
11
 
10
- // src/frame.tsx
11
- import { createContext, useContext } from "react";
12
+ // src/connected-widget.tsx
13
+ import {
14
+ UNKNOWN_WIDGET_TYPE
15
+ } from "@devicechain/dashboards";
16
+ import { WidgetCandidatesProvider, WidgetSelectProvider } from "@devicechain/widget-sdk";
17
+ import { useMemo as useMemo4 } from "react";
12
18
 
13
- // src/theme.ts
14
- function css(name) {
15
- return `hsl(var(--${name}))`;
16
- }
17
- var FALLBACK = {
18
- foreground: "hsl(240 10% 3.9%)",
19
- mutedForeground: "hsl(240 3.8% 46.1%)",
20
- border: "hsl(240 5.9% 90%)",
21
- series: [
22
- "hsl(220 70% 50%)",
23
- "hsl(160 60% 45%)",
24
- "hsl(30 80% 55%)",
25
- "hsl(280 65% 60%)",
26
- "hsl(340 75% 55%)"
27
- ]
28
- };
29
- function readVar(style, name, fallback) {
30
- const raw = style.getPropertyValue(`--${name}`).trim();
31
- return raw ? `hsl(${raw})` : fallback;
19
+ // src/binding-policy.ts
20
+ import { optNumber, optString } from "@devicechain/widget-sdk";
21
+ function numberValue(binding, options) {
22
+ if (!binding) return void 0;
23
+ let value;
24
+ if (typeof binding.const === "number") value = binding.const;
25
+ else if (binding.option !== void 0) value = optNumber(options, binding.option) ?? binding.default;
26
+ if (value === void 0) return void 0;
27
+ return binding.min !== void 0 ? Math.max(binding.min, value) : value;
28
+ }
29
+ function stringValue(binding, options) {
30
+ if (!binding || binding.option === void 0) return void 0;
31
+ return optString(options, binding.option) ?? binding.default;
32
+ }
33
+ function policy(descriptor) {
34
+ return descriptor.binding;
35
+ }
36
+ function measurementWindow(descriptor, widget) {
37
+ return numberValue(policy(descriptor).window, widget.options);
38
+ }
39
+ function parseAcknowledged(value) {
40
+ if (value === "true") return true;
41
+ if (value === "false") return false;
42
+ return void 0;
32
43
  }
33
- function resolveChartTheme(el) {
34
- const style = getComputedStyle(el);
44
+ function alarmSubscription(descriptor, widget) {
45
+ const p = policy(descriptor);
35
46
  return {
36
- foreground: readVar(style, "foreground", FALLBACK.foreground),
37
- mutedForeground: readVar(style, "muted-foreground", FALLBACK.mutedForeground),
38
- border: readVar(style, "border", FALLBACK.border),
39
- series: FALLBACK.series.map((fallback, i) => readVar(style, `chart-${i + 1}`, fallback))
47
+ datasource: widget.datasource,
48
+ state: stringValue(p.state, widget.options),
49
+ severity: stringValue(p.severity, widget.options),
50
+ acknowledged: parseAcknowledged(stringValue(p.acknowledged, widget.options)),
51
+ pageSize: numberValue(p.pageSize, widget.options) ?? 1
40
52
  };
41
53
  }
42
-
43
- // src/frame.tsx
44
- import { jsx, jsxs } from "react/jsx-runtime";
45
- var WidgetSubjectContext = createContext(void 0);
46
- function WidgetSubjectProvider({
47
- label,
48
- children
49
- }) {
50
- return /* @__PURE__ */ jsx(WidgetSubjectContext.Provider, { value: label, children });
51
- }
52
- var WidgetSelectContext = createContext(void 0);
53
- function WidgetSelectProvider({
54
- select,
55
- children
56
- }) {
57
- return /* @__PURE__ */ jsx(WidgetSelectContext.Provider, { value: select, children });
58
- }
59
- function useWidgetSelect() {
60
- return useContext(WidgetSelectContext);
61
- }
62
- var WidgetCandidatesContext = createContext(void 0);
63
- function WidgetCandidatesProvider({
64
- candidates,
65
- children
66
- }) {
67
- return /* @__PURE__ */ jsx(WidgetCandidatesContext.Provider, { value: candidates, children });
68
- }
69
- function useWidgetCandidates() {
70
- return useContext(WidgetCandidatesContext);
71
- }
72
- function WidgetFrame({ title, subtitle, children, bodyStyle }) {
73
- const subject = useContext(WidgetSubjectContext);
74
- const sub = subtitle ?? subject;
75
- return /* @__PURE__ */ jsxs(
76
- "div",
77
- {
78
- style: {
79
- display: "flex",
80
- flexDirection: "column",
81
- width: "100%",
82
- height: "100%",
83
- boxSizing: "border-box",
84
- background: css("card"),
85
- color: css("card-foreground"),
86
- border: `1px solid ${css("border")}`,
87
- borderRadius: "var(--radius, 8px)",
88
- overflow: "hidden"
89
- },
90
- children: [
91
- title || sub ? /* @__PURE__ */ jsxs(
92
- "div",
93
- {
94
- style: {
95
- padding: "8px 12px",
96
- borderBottom: `1px solid ${css("border")}`
97
- },
98
- children: [
99
- title ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, color: css("muted-foreground") }, children: title }) : null,
100
- sub ? /* @__PURE__ */ jsx(
101
- "div",
102
- {
103
- title: sub,
104
- style: {
105
- fontSize: 11,
106
- fontWeight: 400,
107
- color: css("muted-foreground"),
108
- opacity: 0.75,
109
- marginTop: title ? 2 : 0,
110
- whiteSpace: "nowrap",
111
- overflow: "hidden",
112
- textOverflow: "ellipsis"
113
- },
114
- children: sub
115
- }
116
- ) : null
117
- ]
118
- }
119
- ) : null,
120
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, ...bodyStyle }, children })
121
- ]
122
- }
123
- );
54
+ function commandSubscription(descriptor, widget) {
55
+ return {
56
+ datasource: widget.datasource,
57
+ pageSize: numberValue(policy(descriptor).pageSize, widget.options) ?? 1
58
+ };
124
59
  }
125
60
 
126
- // src/hooks.ts
61
+ // src/builtin-registry.ts
62
+ import { createWidgetRegistry } from "@devicechain/widget-sdk";
63
+
64
+ // src/widgets/alarm-count.tsx
65
+ import {
66
+ css,
67
+ defineWidget,
68
+ optString as optString2,
69
+ useElementSize,
70
+ WidgetFrame
71
+ } from "@devicechain/widget-sdk";
72
+
73
+ // src/builtin-descriptors.ts
127
74
  import {
128
- bindingsWithoutScopedSlots,
129
- hasScopedSlots,
130
- resolveContextBindings,
131
- resolveSlotCandidates
75
+ createWidgetCatalog,
76
+ defineWidgetDescriptor
132
77
  } from "@devicechain/dashboards";
133
- import { useEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
134
- function useResolvedBindings(definition, base, selection, resolver) {
135
- const scoped = useMemo(() => hasScopedSlots(definition), [definition]);
136
- const overlaid = useMemo(() => ({ ...base, ...selection }), [base, selection]);
137
- const [resolved, setResolved] = useState(() => bindingsWithoutScopedSlots(definition, overlaid));
138
- const genRef = useRef(0);
139
- useEffect(() => {
140
- if (!scoped) {
141
- setResolved((prev) => sameBindings(prev, overlaid) ? prev : overlaid);
142
- return;
143
- }
144
- const gen = ++genRef.current;
145
- let live = true;
146
- resolveContextBindings(definition, base, selection, resolver).then((next) => {
147
- if (!live || gen !== genRef.current) return;
148
- setResolved((prev) => sameBindings(prev, next) ? prev : next);
149
- }).catch(() => {
150
- });
151
- return () => {
152
- live = false;
153
- };
154
- }, [scoped, definition, base, selection, resolver, overlaid]);
155
- return scoped ? resolved : overlaid;
156
- }
157
- function sameBindings(a, b) {
158
- const ak = Object.keys(a).sort();
159
- const bk = Object.keys(b).sort();
160
- if (ak.length !== bk.length) return false;
161
- for (let i = 0; i < ak.length; i += 1) {
162
- if (ak[i] !== bk[i]) return false;
163
- if (JSON.stringify(a[ak[i]]) !== JSON.stringify(b[bk[i]])) return false;
164
- }
165
- return true;
166
- }
167
- function useSlotCandidates(definition, bindings, resolver, lister) {
168
- const bindingsKey = JSON.stringify(bindings);
169
- return useMemo(
170
- () => {
171
- if (!lister) return void 0;
172
- return (slot) => resolveSlotCandidates(definition, slot, bindings, resolver, lister);
173
- },
174
- // bindings read via bindingsKey (value identity), not reference.
175
- // eslint-disable-next-line react-hooks/exhaustive-deps
176
- [definition, bindingsKey, resolver, lister]
177
- );
178
- }
179
- function useCandidates(slot) {
180
- const provider = useWidgetCandidates();
181
- const wired = !!provider;
182
- const [state, setState] = useState(() => ({
183
- candidates: [],
184
- loading: Boolean(provider && slot),
185
- error: null
186
- }));
187
- const genRef = useRef(0);
188
- useEffect(() => {
189
- if (!provider || !slot) {
190
- setState({ candidates: [], loading: false, error: null });
191
- return;
192
- }
193
- const gen = ++genRef.current;
194
- let live = true;
195
- setState((prev) => ({ ...prev, loading: true, error: null }));
196
- provider(slot).then((candidates) => {
197
- if (live && gen === genRef.current) setState({ candidates, loading: false, error: null });
198
- }).catch((error) => {
199
- if (live && gen === genRef.current) setState({ candidates: [], loading: false, error });
200
- });
201
- return () => {
202
- live = false;
203
- };
204
- }, [provider, slot]);
205
- return { ...state, wired };
206
- }
207
- var EMPTY = { latest: {}, samples: [], error: null };
208
- function latestOf(samples) {
209
- const latest = {};
210
- for (const s of samples) latest[s.name] = s;
211
- return latest;
212
- }
213
- function useMeasurementStream(hub, datasource, options = {}) {
214
- const windowSize = options.window ?? 300;
215
- const initialSamples = options.initialSamples;
216
- const [live, setLive] = useState(EMPTY);
217
- const key = datasource ? JSON.stringify(datasource) : null;
218
- useEffect(() => {
219
- setLive(EMPTY);
220
- if (!datasource) return;
221
- return hub.subscribeWidget(datasource, {
222
- next: (sample) => setLive((prev) => {
223
- const samples = prev.samples.concat(sample);
224
- if (samples.length > windowSize) samples.splice(0, samples.length - windowSize);
225
- return { latest: { ...prev.latest, [sample.name]: sample }, samples, error: null };
226
- }),
227
- error: (err) => setLive((prev) => ({ ...prev, error: err }))
228
- });
229
- }, [hub, key, windowSize]);
230
- return useMemo(() => {
231
- if (!initialSamples || initialSamples.length === 0) return live;
232
- const merged = initialSamples.concat(live.samples);
233
- const samples = merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged;
234
- return {
235
- latest: { ...latestOf(initialSamples), ...live.latest },
236
- samples,
237
- error: live.error
238
- };
239
- }, [initialSamples, live, windowSize]);
240
- }
241
- var EMPTY_ALARMS = { alarms: [], total: 0, loading: true, error: null };
242
- function useAlarmStream(hub, subscription) {
243
- const [state, setState] = useState(EMPTY_ALARMS);
244
- const key = JSON.stringify(subscription);
245
- useEffect(() => {
246
- setState(EMPTY_ALARMS);
247
- return hub.subscribeAlarms(subscription, {
248
- next: (snapshot) => setState({ alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }),
249
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
250
- });
251
- }, [hub, key]);
252
- return state;
253
- }
254
- var EMPTY_COMMANDS = {
255
- deviceToken: null,
256
- commands: [],
257
- total: 0,
258
- loading: true,
259
- error: null
78
+ var TITLE = {
79
+ kind: "string",
80
+ labelId: "widgetLabelTitle",
81
+ doc: "Heading shown in the widget frame. Blank hides the frame heading."
260
82
  };
261
- function useCommandStream(hub, subscription) {
262
- const [state, setState] = useState(EMPTY_COMMANDS);
263
- const key = JSON.stringify(subscription);
264
- useEffect(() => {
265
- setState(EMPTY_COMMANDS);
266
- return hub.subscribeCommands(subscription, {
267
- next: (snapshot) => setState({
268
- deviceToken: snapshot.deviceToken,
269
- commands: snapshot.commands,
270
- total: snapshot.total,
271
- loading: false,
272
- error: null
273
- }),
274
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
275
- });
276
- }, [hub, key]);
277
- return state;
278
- }
279
- var EMPTY_LOCATIONS = {
280
- locations: [],
281
- deviceTokens: [],
282
- forbidden: false,
283
- loading: true,
284
- error: null
83
+ var TITLE_OR_MEASUREMENT = {
84
+ kind: "string",
85
+ labelId: "widgetLabelTitle",
86
+ doc: "Heading shown in the widget frame. Defaults to the displayed measurement name."
285
87
  };
286
- function useLocationStream(hub, subscription) {
287
- const [state, setState] = useState(EMPTY_LOCATIONS);
288
- const key = JSON.stringify(subscription);
289
- useEffect(() => {
290
- setState(EMPTY_LOCATIONS);
291
- return hub.subscribeLocations(subscription, {
292
- next: (snapshot) => setState(
293
- snapshot.kind === "forbidden" ? { locations: [], deviceTokens: [], forbidden: true, loading: false, error: null } : {
294
- locations: snapshot.locations,
295
- deviceTokens: snapshot.deviceTokens,
296
- forbidden: false,
297
- loading: false,
298
- error: null
299
- }
300
- ),
301
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
302
- });
303
- }, [hub, key]);
304
- return state;
305
- }
306
- var AVAILABILITY_REVALIDATE_MS = 6e4;
307
- function useDatasourceAvailability(hub, datasource) {
308
- const [state, setState] = useState("unknown");
309
- const key = datasource ? JSON.stringify(datasource) : null;
310
- useEffect(() => {
311
- if (!datasource) {
312
- setState("available");
313
- return;
314
- }
315
- setState("unknown");
316
- let cancelled = false;
317
- let timer;
318
- const stopTimer = () => {
319
- if (timer) {
320
- clearInterval(timer);
321
- timer = void 0;
88
+ var PRECISION = {
89
+ kind: "number",
90
+ integer: true,
91
+ min: 0,
92
+ max: 100,
93
+ labelId: "widgetLabelPrecision",
94
+ helpId: "widgetHintPrecision",
95
+ doc: "Decimal places to round displayed values to. Unset shows the raw value."
96
+ };
97
+ var FLASH_ON_CHANGE = {
98
+ kind: "boolean",
99
+ labelId: "widgetFlashOnChange",
100
+ helpId: "widgetFlashOnChangeHint",
101
+ doc: "Briefly tint a value green on a rise and red on a fall when it changes."
102
+ };
103
+ var WINDOW = {
104
+ kind: "number",
105
+ integer: true,
106
+ min: 1,
107
+ labelId: "widgetLabelWindow",
108
+ helpId: "widgetHintWindow",
109
+ doc: "Samples retained across ALL of the widget's measurements before the oldest are dropped. Bounds memory; only the chart draws them. Default 300."
110
+ };
111
+ var UNIT = {
112
+ kind: "string",
113
+ labelId: "widgetLabelUnit",
114
+ doc: "Unit suffix shown beside the value."
115
+ };
116
+ var ALARM_STATE = {
117
+ kind: "enum",
118
+ values: ["ACTIVE", "CLEARED"],
119
+ labelId: "widgetLabelState",
120
+ helpId: "widgetStateHint",
121
+ valueLabelIds: { ACTIVE: "widgetAlarmStateActive", CLEARED: "widgetAlarmStateCleared" },
122
+ placeholderId: "widgetPlaceholderAny",
123
+ doc: "Show only alarms in this state. Unset shows every state."
124
+ };
125
+ var ALARM_SEVERITY = {
126
+ kind: "enum",
127
+ values: ["CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"],
128
+ labelId: "widgetLabelSeverity",
129
+ helpId: "widgetSeverityHint",
130
+ valueLabelIds: { CRITICAL: "widgetAlarmSeverityCritical", MAJOR: "widgetAlarmSeverityMajor", MINOR: "widgetAlarmSeverityMinor", WARNING: "widgetAlarmSeverityWarning", INDETERMINATE: "widgetAlarmSeverityIndeterminate" },
131
+ placeholderId: "widgetPlaceholderAny",
132
+ doc: "Show only alarms at this severity. Unset shows every severity."
133
+ };
134
+ var ALARM_ACKNOWLEDGED = {
135
+ kind: "enum",
136
+ values: ["true", "false"],
137
+ labelId: "widgetLabelAcknowledged",
138
+ helpId: "widgetAcknowledgedHint",
139
+ valueLabelIds: { true: "widgetAlarmAckAcknowledged", false: "widgetAlarmAckUnacknowledged" },
140
+ placeholderId: "widgetPlaceholderAny",
141
+ doc: "Show only acknowledged ('true') or unacknowledged ('false') alarms. Unset shows both."
142
+ };
143
+ var SELECTION_TARGET = {
144
+ kind: "string",
145
+ labelId: "widgetLabelTargetSlot",
146
+ helpId: "widgetTargetSlotHint",
147
+ control: "slot-target",
148
+ doc: "Name of the slot this widget re-points when the viewer picks an entity."
149
+ };
150
+ var VERSION = "1.0.0";
151
+ var ALL_SELECTORS = ["device", "anchor", "slot"];
152
+ var NO_DATASOURCE = { mode: "none" };
153
+ var ALARM_FILTERS = {
154
+ state: { option: "state" },
155
+ severity: { option: "severity" },
156
+ acknowledged: { option: "acknowledged" }
157
+ };
158
+ var builtinWidgetDescriptors = {
159
+ "timeseries-chart": defineWidgetDescriptor({
160
+ id: "timeseries-chart",
161
+ version: VERSION,
162
+ sdkApiVersion: 1,
163
+ optionsVersion: 1,
164
+ display: { title: "Timeseries chart", category: "Telemetry", titleId: "widgetTypeTimeseriesChart", categoryId: "widgetCategoryTelemetry" },
165
+ channel: "measurement",
166
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
167
+ binding: { window: { option: "window", min: 1 } },
168
+ options: {
169
+ title: TITLE,
170
+ window: WINDOW
171
+ },
172
+ defaults: { title: "Timeseries chart" },
173
+ interactions: {}
174
+ }),
175
+ "latest-card": defineWidgetDescriptor({
176
+ id: "latest-card",
177
+ version: VERSION,
178
+ sdkApiVersion: 1,
179
+ optionsVersion: 1,
180
+ display: { title: "Latest card", category: "Telemetry", titleId: "widgetTypeLatestCard", categoryId: "widgetCategoryTelemetry" },
181
+ channel: "measurement",
182
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
183
+ binding: { window: { option: "window", min: 1 } },
184
+ options: {
185
+ title: TITLE_OR_MEASUREMENT,
186
+ measurement: {
187
+ kind: "string",
188
+ labelId: "widgetLabelMeasurement",
189
+ helpId: "widgetHintMeasurement",
190
+ doc: "Which measurement to display. Defaults to the first the datasource selects."
191
+ },
192
+ unit: UNIT,
193
+ precision: PRECISION,
194
+ flashOnChange: FLASH_ON_CHANGE,
195
+ window: WINDOW
196
+ },
197
+ defaults: { title: "Latest card" },
198
+ interactions: {}
199
+ }),
200
+ gauge: defineWidgetDescriptor({
201
+ id: "gauge",
202
+ version: VERSION,
203
+ sdkApiVersion: 1,
204
+ optionsVersion: 1,
205
+ display: { title: "Gauge", category: "Telemetry", titleId: "widgetTypeGauge", categoryId: "widgetCategoryTelemetry" },
206
+ channel: "measurement",
207
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
208
+ binding: { window: { option: "window", min: 1 } },
209
+ options: {
210
+ title: TITLE_OR_MEASUREMENT,
211
+ measurement: {
212
+ kind: "string",
213
+ labelId: "widgetLabelMeasurement",
214
+ helpId: "widgetHintMeasurement",
215
+ doc: "Which measurement to display. Defaults to the first the datasource selects."
216
+ },
217
+ min: {
218
+ kind: "number",
219
+ labelId: "widgetLabelMin",
220
+ doc: "Low end of the gauge scale. Default 0."
221
+ },
222
+ max: {
223
+ kind: "number",
224
+ labelId: "widgetLabelMax",
225
+ doc: "High end of the gauge scale. Default 100."
226
+ },
227
+ unit: UNIT,
228
+ flashOnChange: FLASH_ON_CHANGE,
229
+ window: WINDOW
230
+ },
231
+ constraints: [{ kind: "greater-than", key: "max", than: "min" }],
232
+ defaults: { title: "Gauge" },
233
+ interactions: {}
234
+ }),
235
+ table: defineWidgetDescriptor({
236
+ id: "table",
237
+ version: VERSION,
238
+ sdkApiVersion: 1,
239
+ optionsVersion: 1,
240
+ display: { title: "Table", category: "Telemetry", titleId: "widgetTypeTable", categoryId: "widgetCategoryTelemetry" },
241
+ channel: "measurement",
242
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
243
+ binding: { window: { option: "window", min: 1 } },
244
+ options: {
245
+ title: TITLE,
246
+ precision: PRECISION,
247
+ flashOnChange: FLASH_ON_CHANGE,
248
+ window: WINDOW
249
+ },
250
+ defaults: { title: "Table" },
251
+ interactions: {}
252
+ }),
253
+ // label and image are static: the `none` channel, no datasource, no subscription and no
254
+ // history seed.
255
+ label: defineWidgetDescriptor({
256
+ id: "label",
257
+ version: VERSION,
258
+ sdkApiVersion: 1,
259
+ optionsVersion: 1,
260
+ display: { title: "Label", category: "Static", titleId: "widgetTypeLabel", categoryId: "widgetCategoryStatic" },
261
+ channel: "none",
262
+ datasource: NO_DATASOURCE,
263
+ binding: {},
264
+ options: {
265
+ title: TITLE,
266
+ text: {
267
+ kind: "string",
268
+ required: true,
269
+ labelId: "widgetLabelText",
270
+ doc: "The text to show. Rendered as plain text \u2014 markup is never interpreted."
271
+ },
272
+ align: {
273
+ kind: "enum",
274
+ values: ["left", "center", "right"],
275
+ labelId: "widgetLabelAlign",
276
+ valueLabelIds: { left: "widgetAlignLeft", center: "widgetAlignCenter", right: "widgetAlignRight" },
277
+ placeholderId: "widgetPlaceholderDefault",
278
+ doc: "Horizontal alignment of the text. Default center."
279
+ },
280
+ fontSize: {
281
+ kind: "number",
282
+ min: 1,
283
+ labelId: "widgetLabelFontSize",
284
+ helpId: "widgetHintFontSize",
285
+ doc: "Text size in pixels. Default 16."
322
286
  }
323
- };
324
- const check = () => {
325
- hub.isDatasourceAvailable(datasource).then((ok) => {
326
- if (cancelled) return;
327
- setState(ok ? "available" : "unavailable");
328
- if (ok) stopTimer();
329
- }).catch(() => {
330
- if (cancelled) return;
331
- setState("available");
332
- stopTimer();
333
- });
334
- };
335
- check();
336
- timer = setInterval(check, AVAILABILITY_REVALIDATE_MS);
337
- return () => {
338
- cancelled = true;
339
- stopTimer();
340
- };
341
- }, [hub, key]);
342
- return state;
343
- }
344
- function useElementSize() {
345
- const ref = useRef(null);
346
- const [size, setSize] = useState({ width: 0, height: 0 });
347
- useEffect(() => {
348
- const el = ref.current;
349
- if (!el || typeof ResizeObserver === "undefined") return;
350
- const observer = new ResizeObserver((entries) => {
351
- const rect = entries[0]?.contentRect;
352
- if (!rect) return;
353
- setSize(
354
- (prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height }
355
- );
356
- });
357
- observer.observe(el);
358
- return () => observer.disconnect();
359
- }, []);
360
- return [ref, size];
361
- }
362
- var sharedTheme = null;
363
- var themeListeners = /* @__PURE__ */ new Set();
364
- var themeObserver = null;
365
- function recomputeTheme() {
366
- sharedTheme = resolveChartTheme(document.documentElement);
367
- }
368
- function subscribeTheme(listener) {
369
- if (!themeObserver) {
370
- recomputeTheme();
371
- themeObserver = new MutationObserver(() => {
372
- recomputeTheme();
373
- for (const l of themeListeners) l();
374
- });
375
- themeObserver.observe(document.documentElement, {
376
- attributes: true,
377
- attributeFilter: ["class", "style"]
378
- });
379
- }
380
- themeListeners.add(listener);
381
- return () => {
382
- themeListeners.delete(listener);
383
- if (themeListeners.size === 0 && themeObserver) {
384
- themeObserver.disconnect();
385
- themeObserver = null;
386
- sharedTheme = null;
387
- }
388
- };
389
- }
390
- function getThemeSnapshot() {
391
- if (!sharedTheme) recomputeTheme();
392
- return sharedTheme;
393
- }
394
- function useChartTheme() {
395
- return useSyncExternalStore(subscribeTheme, getThemeSnapshot, getThemeSnapshot);
396
- }
397
-
398
- // src/widget.ts
399
- function pickSample(latest, name) {
400
- return name ? latest[name] : Object.values(latest)[0];
401
- }
402
- function optString(options, key) {
403
- const value = options?.[key];
404
- return typeof value === "string" ? value : void 0;
405
- }
406
- function optNumber(options, key) {
407
- const value = options?.[key];
408
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
409
- }
410
- function optBoolean(options, key) {
411
- return options?.[key] === true;
412
- }
413
- function primaryMeasurementName(widget) {
414
- const explicit = optString(widget.options, "measurement");
415
- if (explicit) return explicit;
416
- const ds = widget.datasource;
417
- if (ds && ds.measurements.length > 0) return ds.measurements[0];
418
- return void 0;
419
- }
287
+ },
288
+ defaults: { text: "New label" },
289
+ interactions: {}
290
+ }),
291
+ image: defineWidgetDescriptor({
292
+ id: "image",
293
+ version: VERSION,
294
+ sdkApiVersion: 1,
295
+ optionsVersion: 1,
296
+ display: { title: "Image", category: "Static", titleId: "widgetTypeImage", categoryId: "widgetCategoryStatic" },
297
+ channel: "none",
298
+ datasource: NO_DATASOURCE,
299
+ binding: {},
300
+ options: {
301
+ title: TITLE,
302
+ url: {
303
+ kind: "string",
304
+ required: true,
305
+ labelId: "widgetLabelImageUrl",
306
+ doc: "Source URL of the image."
307
+ },
308
+ alt: {
309
+ kind: "string",
310
+ labelId: "widgetLabelAltText",
311
+ doc: "Alternative text describing the image for assistive technology."
312
+ },
313
+ fit: {
314
+ kind: "enum",
315
+ values: ["contain", "cover", "fill"],
316
+ labelId: "widgetLabelFit",
317
+ valueLabelIds: { contain: "widgetFitContain", cover: "widgetFitCover", fill: "widgetFitFill" },
318
+ placeholderId: "widgetPlaceholderDefault",
319
+ doc: "How the image fills its box. Default contain."
320
+ }
321
+ },
322
+ defaults: { title: "Image" },
323
+ interactions: {}
324
+ }),
325
+ "alarm-table": defineWidgetDescriptor({
326
+ id: "alarm-table",
327
+ version: VERSION,
328
+ sdkApiVersion: 1,
329
+ optionsVersion: 1,
330
+ display: { title: "Alarm table", category: "Alarms", titleId: "widgetTypeAlarmTable", categoryId: "widgetCategoryAlarms" },
331
+ channel: "alarm",
332
+ datasource: { mode: "optional", selectors: ALL_SELECTORS },
333
+ binding: { pageSize: { option: "maxRows", default: 50, min: 1 }, ...ALARM_FILTERS },
334
+ options: {
335
+ title: TITLE,
336
+ state: ALARM_STATE,
337
+ severity: ALARM_SEVERITY,
338
+ acknowledged: ALARM_ACKNOWLEDGED,
339
+ maxRows: {
340
+ kind: "number",
341
+ integer: true,
342
+ min: 1,
343
+ labelId: "widgetLabelMaxRows",
344
+ helpId: "widgetMaxRowsAlarmHint",
345
+ doc: "Alarms loaded into the table. Default 50."
346
+ },
347
+ precision: PRECISION,
348
+ flashOnChange: FLASH_ON_CHANGE,
349
+ selectionTarget: SELECTION_TARGET
350
+ },
351
+ defaults: { title: "Alarm table", state: "ACTIVE" },
352
+ interactions: { alarmActions: true, selection: true }
353
+ }),
354
+ // The count shows the SERVER total and reads a page only to sample the worst loaded
355
+ // severity, so its page size is a constant rather than an authorable option.
356
+ "alarm-count": defineWidgetDescriptor({
357
+ id: "alarm-count",
358
+ version: VERSION,
359
+ sdkApiVersion: 1,
360
+ optionsVersion: 1,
361
+ display: { title: "Alarm count", category: "Alarms", titleId: "widgetTypeAlarmCount", categoryId: "widgetCategoryAlarms" },
362
+ channel: "alarm",
363
+ datasource: { mode: "optional", selectors: ALL_SELECTORS },
364
+ binding: { pageSize: { const: 50 }, ...ALARM_FILTERS },
365
+ // No maxRows: alarmSubscription() forces the default page size for a count, because the
366
+ // number it shows is the SERVER total and the page is read only to sample the worst
367
+ // loaded severity. An authored maxRows here would be read by nothing.
368
+ options: {
369
+ title: TITLE,
370
+ state: ALARM_STATE,
371
+ severity: ALARM_SEVERITY,
372
+ acknowledged: ALARM_ACKNOWLEDGED
373
+ },
374
+ defaults: { title: "Alarm count", state: "ACTIVE" },
375
+ interactions: {}
376
+ }),
377
+ "command-button": defineWidgetDescriptor({
378
+ id: "command-button",
379
+ version: VERSION,
380
+ sdkApiVersion: 1,
381
+ optionsVersion: 1,
382
+ display: { title: "Command button", category: "Control", titleId: "widgetTypeCommandButton", categoryId: "widgetCategoryControl" },
383
+ channel: "control",
384
+ datasource: { mode: "required", selectors: ["device", "slot"] },
385
+ binding: { pageSize: { option: "maxRows", default: 20, min: 1 } },
386
+ options: {
387
+ title: TITLE,
388
+ commandName: {
389
+ kind: "string",
390
+ required: true,
391
+ labelId: "widgetCommandHeading",
392
+ helpId: "widgetCommandFieldHint",
393
+ control: "command-vocabulary",
394
+ doc: "Command key issued to the target device. Without it the widget cannot send."
395
+ },
396
+ commandLabel: {
397
+ kind: "string",
398
+ labelId: "widgetCommandHeading",
399
+ helpId: "widgetCommandFieldHint",
400
+ control: "command-vocabulary",
401
+ doc: "Text on the send button. Defaults to the command key."
402
+ },
403
+ parameterSchema: {
404
+ kind: "string",
405
+ format: "json",
406
+ labelId: "widgetCommandHeading",
407
+ helpId: "widgetCommandFieldHint",
408
+ control: "command-vocabulary",
409
+ doc: "The command's parameter descriptors, baked in as JSON at author time. Drives the typed form."
410
+ },
411
+ maxRows: {
412
+ kind: "number",
413
+ integer: true,
414
+ min: 1,
415
+ labelId: "widgetLabelMaxRows",
416
+ helpId: "widgetMaxRowsCommandHint",
417
+ doc: "Recent commands shown in the history. Default 20."
418
+ }
419
+ },
420
+ defaults: { title: "Command button" },
421
+ interactions: { commandSend: true }
422
+ }),
423
+ "entity-selector": defineWidgetDescriptor({
424
+ id: "entity-selector",
425
+ version: VERSION,
426
+ sdkApiVersion: 1,
427
+ optionsVersion: 1,
428
+ display: { title: "Entity selector", category: "Navigation", titleId: "widgetTypeEntitySelector", categoryId: "widgetCategoryNavigation" },
429
+ channel: "selection",
430
+ datasource: NO_DATASOURCE,
431
+ binding: {},
432
+ options: {
433
+ title: TITLE,
434
+ selectionTarget: { ...SELECTION_TARGET, required: true }
435
+ },
436
+ defaults: { title: "Entity selector" },
437
+ interactions: { selection: true, candidates: true }
438
+ }),
439
+ map: defineWidgetDescriptor({
440
+ id: "map",
441
+ version: VERSION,
442
+ sdkApiVersion: 1,
443
+ optionsVersion: 1,
444
+ display: { title: "Map", category: "Location", titleId: "widgetTypeMap", categoryId: "widgetCategoryLocation" },
445
+ channel: "location",
446
+ datasource: { mode: "required", selectors: ALL_SELECTORS },
447
+ binding: {},
448
+ // 🔴 tileUrl is NOT `required`, and that is the decision rather than an oversight.
449
+ // `required` means "absent leaves the widget with nothing to show", and a map without
450
+ // a basemap still shows every position it was asked to — on a plain panel, saying why
451
+ // there is no map behind them. Marking it required would report a correctly-rendering
452
+ // widget as broken on every board that names no provider of its own — which is most
453
+ // of them, because the usual and recommended answer is to inherit the tenant's
454
+ // basemap rather than to configure tiles per board.
455
+ options: {
456
+ title: TITLE,
457
+ tileUrl: {
458
+ kind: "string",
459
+ labelId: "widgetLabelTileUrl",
460
+ helpId: "widgetTileUrlHint",
461
+ control: "basemap",
462
+ 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."
463
+ },
464
+ attribution: {
465
+ kind: "string",
466
+ labelId: "widgetLabelAttribution",
467
+ helpId: "widgetAttributionHint",
468
+ control: "basemap",
469
+ doc: "Credit line the tile source requires, shown on the map. Set whatever the provider you chose asks for."
470
+ }
471
+ },
472
+ defaults: { title: "Map" },
473
+ interactions: {}
474
+ })
475
+ };
476
+ var builtinWidgetCatalog = createWidgetCatalog(Object.values(builtinWidgetDescriptors));
420
477
 
421
478
  // src/widgets/severity.ts
422
479
  var SEVERITY_COLORS = {
@@ -458,7 +515,7 @@ function highestSeverity(severities) {
458
515
  }
459
516
 
460
517
  // src/widgets/alarm-count.tsx
461
- import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
518
+ import { jsx, jsxs } from "react/jsx-runtime";
462
519
  var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
463
520
  function AlarmCount({ widget, data }) {
464
521
  const [sizeRef, size] = useElementSize();
@@ -470,7 +527,7 @@ function AlarmCount({ widget, data }) {
470
527
  const display = loading && total === 0 ? "\u2014" : String(total);
471
528
  const unit = total === 1 ? "alarm" : "alarms";
472
529
  const ariaLabel = alerting ? `${total} ${unit}, worst severity ${severityLabel(worst)}` : `${total} ${unit}`;
473
- return /* @__PURE__ */ jsx2(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsxs2(
530
+ return /* @__PURE__ */ jsx(WidgetFrame, { title: optString2(widget.options, "title"), children: /* @__PURE__ */ jsxs(
474
531
  "div",
475
532
  {
476
533
  ref: sizeRef,
@@ -484,8 +541,8 @@ function AlarmCount({ widget, data }) {
484
541
  textAlign: "center"
485
542
  },
486
543
  children: [
487
- /* @__PURE__ */ jsx2("div", { "aria-label": ariaLabel, style: { fontSize: valueFont, fontWeight: 700, lineHeight: 1, color }, children: display }),
488
- /* @__PURE__ */ jsxs2(
544
+ /* @__PURE__ */ jsx("div", { "aria-label": ariaLabel, style: { fontSize: valueFont, fontWeight: 700, lineHeight: 1, color }, children: display }),
545
+ /* @__PURE__ */ jsxs(
489
546
  "div",
490
547
  {
491
548
  style: {
@@ -496,8 +553,8 @@ function AlarmCount({ widget, data }) {
496
553
  letterSpacing: "0.05em"
497
554
  },
498
555
  children: [
499
- /* @__PURE__ */ jsx2("span", { children: unit }),
500
- alerting ? /* @__PURE__ */ jsx2("span", { children: ` \xB7 worst ${severityLabel(worst)}` }) : null
556
+ /* @__PURE__ */ jsx("span", { children: unit }),
557
+ alerting ? /* @__PURE__ */ jsx("span", { children: ` \xB7 worst ${severityLabel(worst)}` }) : null
501
558
  ]
502
559
  }
503
560
  )
@@ -505,98 +562,38 @@ function AlarmCount({ widget, data }) {
505
562
  }
506
563
  ) });
507
564
  }
565
+ var alarmCountWidget = defineWidget({
566
+ descriptor: builtinWidgetDescriptors["alarm-count"],
567
+ implementation: { kind: "trusted-react", component: AlarmCount }
568
+ });
508
569
 
509
570
  // src/widgets/alarm-table.tsx
510
- import { useState as useState3 } from "react";
511
-
512
- // src/flash.tsx
513
- import { useEffect as useEffect2, useRef as useRef2, useState as useState2 } from "react";
514
-
515
- // src/format.ts
516
- function formatTimestamp(iso) {
517
- if (!iso) return "";
518
- const date = new Date(iso);
519
- return Number.isNaN(date.getTime()) ? iso : date.toLocaleTimeString();
520
- }
521
- function formatDateTime(iso) {
522
- if (!iso) return "";
523
- const date = new Date(iso);
524
- return Number.isNaN(date.getTime()) ? iso : date.toLocaleString();
525
- }
526
- function formatValue(value, precision) {
527
- if (value == null) return "\u2014";
528
- if (precision == null) return String(value);
529
- const places = Math.min(100, Math.max(0, Math.trunc(precision)));
530
- return value.toFixed(places);
531
- }
532
-
533
- // src/flash.tsx
534
- import { jsx as jsx3 } from "react/jsx-runtime";
535
- var UP = {
536
- fg: "var(--flash-up, hsl(142 72% 40%))",
537
- bg: "var(--flash-up-bg, hsl(142 72% 45% / 0.16))"
538
- };
539
- var DOWN = {
540
- fg: "var(--flash-down, hsl(0 72% 51%))",
541
- bg: "var(--flash-down-bg, hsl(0 72% 55% / 0.16))"
542
- };
543
- var HOLD_MS = 400;
544
- var FADE_MS = 600;
545
- function useFlashOnChange(value, enabled, identity) {
546
- const prev = useRef2(value);
547
- const prevIdentity = useRef2(identity);
548
- const [direction, setDirection] = useState2(null);
549
- useEffect2(() => {
550
- const before = prev.current;
551
- const sameEntity = prevIdentity.current === identity;
552
- prev.current = value;
553
- prevIdentity.current = identity;
554
- if (!enabled || !sameEntity || typeof value !== "number" || typeof before !== "number" || !Number.isFinite(value) || !Number.isFinite(before) || value === before) {
555
- setDirection(null);
556
- return;
557
- }
558
- setDirection(value > before ? "up" : "down");
559
- const timer = setTimeout(() => setDirection(null), HOLD_MS);
560
- return () => clearTimeout(timer);
561
- }, [value, enabled, identity]);
562
- return enabled ? direction : null;
563
- }
564
- function flashTextStyle(direction, fadeMs = FADE_MS) {
565
- if (direction) return { color: direction === "up" ? UP.fg : DOWN.fg, transition: "none" };
566
- return { transition: `color ${fadeMs}ms ease-out` };
567
- }
568
- function flashBackgroundStyle(direction, fadeMs = FADE_MS) {
569
- if (direction) {
570
- return { backgroundColor: direction === "up" ? UP.bg : DOWN.bg, transition: "none" };
571
- }
572
- return { transition: `background-color ${fadeMs}ms ease-out` };
573
- }
574
- function FlashValue({
575
- value,
576
- precision,
577
- enabled,
578
- identity,
579
- baseColor = "inherit",
580
- style
581
- }) {
582
- const direction = useFlashOnChange(value, enabled, identity);
583
- return /* @__PURE__ */ jsx3("span", { style: { color: baseColor, ...style, ...flashTextStyle(direction) }, children: formatValue(value, precision) });
584
- }
585
-
586
- // src/widgets/alarm-table.tsx
587
- import { Fragment, jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
571
+ import { useState } from "react";
572
+ import {
573
+ css as css2,
574
+ defineWidget as defineWidget2,
575
+ FlashValue,
576
+ formatDateTime,
577
+ optBoolean,
578
+ optNumber as optNumber2,
579
+ optString as optString3,
580
+ useWidgetSelect,
581
+ useWidgetTimeZone,
582
+ WidgetFrame as WidgetFrame2
583
+ } from "@devicechain/widget-sdk";
584
+ import { Fragment, jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
588
585
  var cell = { padding: "6px 10px", textAlign: "left", whiteSpace: "nowrap" };
589
586
  var headCell = {
590
587
  ...cell,
591
588
  position: "sticky",
592
589
  top: 0,
593
- background: css("card"),
590
+ background: css2("card"),
594
591
  fontSize: 11,
595
592
  fontWeight: 600,
596
593
  textTransform: "uppercase",
597
594
  letterSpacing: "0.04em",
598
- color: css("muted-foreground"),
599
- borderBottom: `1px solid ${css("border")}`
595
+ color: css2("muted-foreground"),
596
+ borderBottom: `1px solid ${css2("border")}`
600
597
  };
601
598
  function statusLabel(alarm) {
602
599
  if (alarm.state === "CLEARED") return "Cleared";
@@ -606,39 +603,40 @@ function AlarmTable({ widget, data, actions }) {
606
603
  const { alarms, total, loading } = data;
607
604
  const canAct = actions?.can("alarm:write") ?? false;
608
605
  const columns = canAct ? 8 : 7;
609
- const precision = optNumber(widget.options, "precision");
606
+ const precision = optNumber2(widget.options, "precision");
610
607
  const flash = optBoolean(widget.options, "flashOnChange");
611
608
  const select = useWidgetSelect();
612
- const selectionTarget = optString(widget.options, "selectionTarget");
609
+ const timeZone = useWidgetTimeZone();
610
+ const selectionTarget = optString3(widget.options, "selectionTarget");
613
611
  const drillTo = select && selectionTarget ? { select, slot: selectionTarget } : void 0;
614
- return /* @__PURE__ */ jsx4(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
615
- /* @__PURE__ */ jsx4("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsxs3(
612
+ return /* @__PURE__ */ jsx2(WidgetFrame2, { title: optString3(widget.options, "title"), children: /* @__PURE__ */ jsxs2("div", { style: { display: "flex", flexDirection: "column", height: "100%" }, children: [
613
+ /* @__PURE__ */ jsx2("div", { style: { flex: 1, minHeight: 0, overflow: "auto" }, children: /* @__PURE__ */ jsxs2(
616
614
  "table",
617
615
  {
618
616
  style: {
619
617
  width: "100%",
620
618
  borderCollapse: "collapse",
621
619
  fontSize: 13,
622
- color: css("foreground"),
620
+ color: css2("foreground"),
623
621
  fontVariantNumeric: "tabular-nums"
624
622
  },
625
623
  children: [
626
- /* @__PURE__ */ jsx4("thead", { children: /* @__PURE__ */ jsxs3("tr", { children: [
627
- /* @__PURE__ */ jsx4("th", { style: { ...headCell, width: 4, padding: 0 }, "aria-hidden": "true" }),
628
- /* @__PURE__ */ jsx4("th", { scope: "col", style: headCell, children: "Severity" }),
629
- /* @__PURE__ */ jsx4("th", { scope: "col", style: headCell, children: "Status" }),
630
- /* @__PURE__ */ jsx4("th", { scope: "col", style: headCell, children: "Originator" }),
631
- /* @__PURE__ */ jsx4("th", { scope: "col", style: headCell, children: "Alarm" }),
632
- /* @__PURE__ */ jsx4("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Value" }),
633
- /* @__PURE__ */ jsx4("th", { scope: "col", style: headCell, children: "Raised" }),
634
- canAct ? /* @__PURE__ */ jsx4("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Actions" }) : null
624
+ /* @__PURE__ */ jsx2("thead", { children: /* @__PURE__ */ jsxs2("tr", { children: [
625
+ /* @__PURE__ */ jsx2("th", { style: { ...headCell, width: 4, padding: 0 }, "aria-hidden": "true" }),
626
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Severity" }),
627
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Status" }),
628
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Originator" }),
629
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Alarm" }),
630
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Value" }),
631
+ /* @__PURE__ */ jsx2("th", { scope: "col", style: headCell, children: "Raised" }),
632
+ canAct ? /* @__PURE__ */ jsx2("th", { scope: "col", style: { ...headCell, textAlign: "right" }, children: "Actions" }) : null
635
633
  ] }) }),
636
- /* @__PURE__ */ jsx4("tbody", { children: alarms.length === 0 ? /* @__PURE__ */ jsx4("tr", { children: /* @__PURE__ */ jsx4("td", { colSpan: columns, style: { ...cell, textAlign: "center", color: css("muted-foreground") }, children: loading ? "Loading\u2026" : "No alarms" }) }) : alarms.map((alarm) => {
634
+ /* @__PURE__ */ jsx2("tbody", { children: alarms.length === 0 ? /* @__PURE__ */ jsx2("tr", { children: /* @__PURE__ */ jsx2("td", { colSpan: columns, style: { ...cell, textAlign: "center", color: css2("muted-foreground") }, children: loading ? "Loading\u2026" : "No alarms" }) }) : alarms.map((alarm) => {
637
635
  const color = severityColor(alarm.severity);
638
- return /* @__PURE__ */ jsxs3("tr", { style: { borderBottom: `1px solid ${css("border")}` }, children: [
639
- /* @__PURE__ */ jsx4("td", { "aria-hidden": "true", style: { padding: 0, width: 4, background: color } }),
640
- /* @__PURE__ */ jsx4("td", { style: cell, children: /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
641
- /* @__PURE__ */ jsx4(
636
+ return /* @__PURE__ */ jsxs2("tr", { style: { borderBottom: `1px solid ${css2("border")}` }, children: [
637
+ /* @__PURE__ */ jsx2("td", { "aria-hidden": "true", style: { padding: 0, width: 4, background: color } }),
638
+ /* @__PURE__ */ jsx2("td", { style: cell, children: /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", alignItems: "center", gap: 6 }, children: [
639
+ /* @__PURE__ */ jsx2(
642
640
  "span",
643
641
  {
644
642
  "aria-hidden": "true",
@@ -647,10 +645,10 @@ function AlarmTable({ widget, data, actions }) {
647
645
  ),
648
646
  severityLabel(alarm.severity)
649
647
  ] }) }),
650
- /* @__PURE__ */ jsx4("td", { style: { ...cell, color: css("muted-foreground") }, children: statusLabel(alarm) }),
651
- /* @__PURE__ */ jsx4("td", { style: cell, children: /* @__PURE__ */ jsx4(OriginatorCell, { alarm, drillTo }) }),
652
- /* @__PURE__ */ jsx4("td", { style: cell, children: alarm.alarmKey }),
653
- /* @__PURE__ */ jsx4("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx4(
648
+ /* @__PURE__ */ jsx2("td", { style: { ...cell, color: css2("muted-foreground") }, children: statusLabel(alarm) }),
649
+ /* @__PURE__ */ jsx2("td", { style: cell, children: /* @__PURE__ */ jsx2(OriginatorCell, { alarm, drillTo }) }),
650
+ /* @__PURE__ */ jsx2("td", { style: cell, children: alarm.alarmKey }),
651
+ /* @__PURE__ */ jsx2("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx2(
654
652
  FlashValue,
655
653
  {
656
654
  value: alarm.lastValue,
@@ -659,21 +657,21 @@ function AlarmTable({ widget, data, actions }) {
659
657
  identity: alarm.token
660
658
  }
661
659
  ) }),
662
- /* @__PURE__ */ jsx4("td", { style: { ...cell, color: css("muted-foreground") }, children: formatDateTime(alarm.raisedTime) }),
663
- canAct ? /* @__PURE__ */ jsx4("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx4(AlarmRowActions, { alarm, actions }) }) : null
660
+ /* @__PURE__ */ jsx2("td", { style: { ...cell, color: css2("muted-foreground") }, children: formatDateTime(alarm.raisedTime, timeZone) }),
661
+ canAct ? /* @__PURE__ */ jsx2("td", { style: { ...cell, textAlign: "right" }, children: /* @__PURE__ */ jsx2(AlarmRowActions, { alarm, actions }) }) : null
664
662
  ] }, alarm.token);
665
663
  }) })
666
664
  ]
667
665
  }
668
666
  ) }),
669
- total > alarms.length ? /* @__PURE__ */ jsxs3(
667
+ total > alarms.length ? /* @__PURE__ */ jsxs2(
670
668
  "div",
671
669
  {
672
670
  style: {
673
671
  padding: "4px 10px",
674
672
  fontSize: 11,
675
- color: css("muted-foreground"),
676
- borderTop: `1px solid ${css("border")}`
673
+ color: css2("muted-foreground"),
674
+ borderTop: `1px solid ${css2("border")}`
677
675
  },
678
676
  children: [
679
677
  "Showing ",
@@ -691,7 +689,7 @@ function OriginatorCell({
691
689
  }) {
692
690
  if (drillTo && alarm.originatorType === "device" && alarm.originatorToken) {
693
691
  const token = alarm.originatorToken;
694
- return /* @__PURE__ */ jsx4(
692
+ return /* @__PURE__ */ jsx2(
695
693
  "button",
696
694
  {
697
695
  type: "button",
@@ -702,7 +700,7 @@ function OriginatorCell({
702
700
  border: "none",
703
701
  background: "none",
704
702
  font: "inherit",
705
- color: css("primary"),
703
+ color: css2("primary"),
706
704
  cursor: "pointer",
707
705
  textDecoration: "underline",
708
706
  textUnderlineOffset: 2
@@ -711,23 +709,23 @@ function OriginatorCell({
711
709
  }
712
710
  );
713
711
  }
714
- return /* @__PURE__ */ jsx4(Fragment, { children: alarm.originatorToken ?? alarm.originatorType });
712
+ return /* @__PURE__ */ jsx2(Fragment, { children: alarm.originatorToken ?? alarm.originatorType });
715
713
  }
716
714
  var actionButton = {
717
715
  padding: "2px 8px",
718
716
  fontSize: 12,
719
717
  lineHeight: 1.4,
720
718
  borderRadius: 4,
721
- border: `1px solid ${css("border")}`,
722
- background: css("card"),
723
- color: css("foreground"),
719
+ border: `1px solid ${css2("border")}`,
720
+ background: css2("card"),
721
+ color: css2("foreground"),
724
722
  cursor: "pointer"
725
723
  };
726
724
  function AlarmRowActions({ alarm, actions }) {
727
- const [pending, setPending] = useState3(null);
728
- const [failed, setFailed] = useState3(false);
725
+ const [pending, setPending] = useState(null);
726
+ const [failed, setFailed] = useState(false);
729
727
  if (alarm.state !== "ACTIVE") {
730
- return /* @__PURE__ */ jsx4("span", { style: { color: css("muted-foreground") }, children: "\u2014" });
728
+ return /* @__PURE__ */ jsx2("span", { style: { color: css2("muted-foreground") }, children: "\u2014" });
731
729
  }
732
730
  const run = (kind, op) => {
733
731
  setPending(kind);
@@ -742,8 +740,8 @@ function AlarmRowActions({ alarm, actions }) {
742
740
  }
743
741
  promise.catch(() => setFailed(true)).finally(() => setPending(null));
744
742
  };
745
- return /* @__PURE__ */ jsxs3("span", { style: { display: "inline-flex", gap: 6, justifyContent: "flex-end", alignItems: "center" }, children: [
746
- failed ? /* @__PURE__ */ jsx4(
743
+ return /* @__PURE__ */ jsxs2("span", { style: { display: "inline-flex", gap: 6, justifyContent: "flex-end", alignItems: "center" }, children: [
744
+ failed ? /* @__PURE__ */ jsx2(
747
745
  "span",
748
746
  {
749
747
  role: "img",
@@ -753,7 +751,7 @@ function AlarmRowActions({ alarm, actions }) {
753
751
  children: "\u26A0"
754
752
  }
755
753
  ) : null,
756
- !alarm.acknowledged ? /* @__PURE__ */ jsx4(
754
+ !alarm.acknowledged ? /* @__PURE__ */ jsx2(
757
755
  "button",
758
756
  {
759
757
  type: "button",
@@ -765,7 +763,7 @@ function AlarmRowActions({ alarm, actions }) {
765
763
  children: pending === "ack" ? "\u2026" : "Ack"
766
764
  }
767
765
  ) : null,
768
- /* @__PURE__ */ jsx4(
766
+ /* @__PURE__ */ jsx2(
769
767
  "button",
770
768
  {
771
769
  type: "button",
@@ -779,9 +777,21 @@ function AlarmRowActions({ alarm, actions }) {
779
777
  )
780
778
  ] });
781
779
  }
780
+ var alarmTableWidget = defineWidget2({
781
+ descriptor: builtinWidgetDescriptors["alarm-table"],
782
+ implementation: { kind: "trusted-react", component: AlarmTable }
783
+ });
782
784
 
783
785
  // src/widgets/command-button.tsx
784
- import { useEffect as useEffect3, useMemo as useMemo2, useRef as useRef3, useState as useState4 } from "react";
786
+ import { useEffect, useMemo, useRef, useState as useState2 } from "react";
787
+ import {
788
+ css as css3,
789
+ defineWidget as defineWidget3,
790
+ formatDateTime as formatDateTime2,
791
+ optString as optString4,
792
+ useWidgetTimeZone as useWidgetTimeZone2,
793
+ WidgetFrame as WidgetFrame3
794
+ } from "@devicechain/widget-sdk";
785
795
 
786
796
  // src/widgets/command-status.ts
787
797
  import { isTerminalCommandStatus } from "@devicechain/dashboards";
@@ -929,20 +939,20 @@ function coerce(param, raw) {
929
939
  }
930
940
 
931
941
  // src/widgets/command-button.tsx
932
- import { Fragment as Fragment2, jsx as jsx5, jsxs as jsxs4 } from "react/jsx-runtime";
942
+ import { Fragment as Fragment2, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
933
943
  function CommandButton({ widget, data, actions }) {
934
- const commandName = optString(widget.options, "commandName");
935
- const commandLabel = optString(widget.options, "commandLabel") ?? commandName;
936
- const parameterSchema = optString(widget.options, "parameterSchema") ?? null;
937
- const params = useMemo2(() => parseParameterSchema(parameterSchema), [parameterSchema]);
944
+ const commandName = optString4(widget.options, "commandName");
945
+ const commandLabel = optString4(widget.options, "commandLabel") ?? commandName;
946
+ const parameterSchema = optString4(widget.options, "parameterSchema") ?? null;
947
+ const params = useMemo(() => parseParameterSchema(parameterSchema), [parameterSchema]);
938
948
  const schemaKey = `${commandName ?? ""}::${parameterSchema ?? ""}`;
939
- const [values, setValues] = useState4(() => defaultValues(params));
940
- const [errors, setErrors] = useState4({});
941
- const [pending, setPending] = useState4(false);
942
- const [sendError, setSendError] = useState4(null);
943
- const [sentToken, setSentToken] = useState4(null);
944
- const sendGen = useRef3(0);
945
- useEffect3(() => {
949
+ const [values, setValues] = useState2(() => defaultValues(params));
950
+ const [errors, setErrors] = useState2({});
951
+ const [pending, setPending] = useState2(false);
952
+ const [sendError, setSendError] = useState2(null);
953
+ const [sentToken, setSentToken] = useState2(null);
954
+ const sendGen = useRef(0);
955
+ useEffect(() => {
946
956
  sendGen.current += 1;
947
957
  setValues(defaultValues(params));
948
958
  setErrors({});
@@ -992,10 +1002,10 @@ function CommandButton({ widget, data, actions }) {
992
1002
  if (gen === sendGen.current) setPending(false);
993
1003
  });
994
1004
  };
995
- return /* @__PURE__ */ jsx5(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx5("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: 10 }, children: !commandName ? /* @__PURE__ */ jsx5("div", { style: { ...hint, margin: "auto" }, children: "No command configured." }) : /* @__PURE__ */ jsxs4(Fragment2, { children: [
996
- /* @__PURE__ */ jsxs4("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
1005
+ return /* @__PURE__ */ jsx3(WidgetFrame3, { title: optString4(widget.options, "title"), children: /* @__PURE__ */ jsx3("div", { style: { display: "flex", flexDirection: "column", height: "100%", gap: 10 }, children: !commandName ? /* @__PURE__ */ jsx3("div", { style: { ...hint, margin: "auto" }, children: "No command configured." }) : /* @__PURE__ */ jsxs3(Fragment2, { children: [
1006
+ /* @__PURE__ */ jsxs3("div", { style: { display: "flex", flexDirection: "column", gap: 8 }, children: [
997
1007
  params.map(
998
- (param) => isScalar(param) ? /* @__PURE__ */ jsx5(
1008
+ (param) => isScalar(param) ? /* @__PURE__ */ jsx3(
999
1009
  ParamField,
1000
1010
  {
1001
1011
  widgetId: widget.id,
@@ -1009,7 +1019,7 @@ function CommandButton({ widget, data, actions }) {
1009
1019
  ) : (
1010
1020
  // A structured (OBJECT) parameter isn't form-editable. A REQUIRED one
1011
1021
  // blocks sending (validateParams flags it); flag that in the note.
1012
- /* @__PURE__ */ jsxs4("div", { style: { ...hint, color: param.required ? commandStatusColor("FAILED") : css("muted-foreground") }, children: [
1022
+ /* @__PURE__ */ jsxs3("div", { style: { ...hint, color: param.required ? commandStatusColor("FAILED") : css3("muted-foreground") }, children: [
1013
1023
  param.name,
1014
1024
  ": ",
1015
1025
  param.required ? "required " : "",
@@ -1017,7 +1027,7 @@ function CommandButton({ widget, data, actions }) {
1017
1027
  ] }, param.name)
1018
1028
  )
1019
1029
  ),
1020
- /* @__PURE__ */ jsx5(
1030
+ /* @__PURE__ */ jsx3(
1021
1031
  "button",
1022
1032
  {
1023
1033
  type: "button",
@@ -1028,10 +1038,10 @@ function CommandButton({ widget, data, actions }) {
1028
1038
  children: pending ? "Sending\u2026" : `Send ${commandLabel}`
1029
1039
  }
1030
1040
  ),
1031
- !canWrite ? /* @__PURE__ */ jsx5("div", { style: hint, children: "You don\u2019t have permission to issue commands." }) : !hasSeam ? /* @__PURE__ */ jsx5("div", { style: hint, children: "Commands aren\u2019t available in this view." }) : !deviceToken ? /* @__PURE__ */ jsx5("div", { style: hint, children: "No device is selected for this command." }) : null,
1032
- sendError ? /* @__PURE__ */ jsx5("div", { role: "alert", style: { ...hint, color: commandStatusColor("FAILED") }, children: sendError }) : null
1041
+ !canWrite ? /* @__PURE__ */ jsx3("div", { style: hint, children: "You don\u2019t have permission to issue commands." }) : !hasSeam ? /* @__PURE__ */ jsx3("div", { style: hint, children: "Commands aren\u2019t available in this view." }) : !deviceToken ? /* @__PURE__ */ jsx3("div", { style: hint, children: "No device is selected for this command." }) : null,
1042
+ sendError ? /* @__PURE__ */ jsx3("div", { role: "alert", style: { ...hint, color: commandStatusColor("FAILED") }, children: sendError }) : null
1033
1043
  ] }),
1034
- /* @__PURE__ */ jsx5(CommandHistory, { commands: data.commands, sentToken, loading: data.loading, error: data.error })
1044
+ /* @__PURE__ */ jsx3(CommandHistory, { commands: data.commands, sentToken, loading: data.loading, error: data.error })
1035
1045
  ] }) }) });
1036
1046
  }
1037
1047
  function ParamField({
@@ -1044,14 +1054,14 @@ function ParamField({
1044
1054
  }) {
1045
1055
  const labelId = `cmd-${widgetId}-${param.name}`;
1046
1056
  const unit = param.unit ? ` (${param.unit})` : "";
1047
- return /* @__PURE__ */ jsxs4("label", { htmlFor: labelId, style: { display: "flex", flexDirection: "column", gap: 3 }, children: [
1048
- /* @__PURE__ */ jsxs4("span", { style: { fontSize: 12, color: css("muted-foreground") }, children: [
1057
+ return /* @__PURE__ */ jsxs3("label", { htmlFor: labelId, style: { display: "flex", flexDirection: "column", gap: 3 }, children: [
1058
+ /* @__PURE__ */ jsxs3("span", { style: { fontSize: 12, color: css3("muted-foreground") }, children: [
1049
1059
  param.name,
1050
1060
  unit,
1051
- param.required ? /* @__PURE__ */ jsx5("span", { style: { color: commandStatusColor("FAILED") }, children: " *" }) : null
1061
+ param.required ? /* @__PURE__ */ jsx3("span", { style: { color: commandStatusColor("FAILED") }, children: " *" }) : null
1052
1062
  ] }),
1053
- /* @__PURE__ */ jsx5(ParamInput, { id: labelId, param, value, disabled, onChange }),
1054
- error ? /* @__PURE__ */ jsx5("span", { style: { fontSize: 11, color: commandStatusColor("FAILED") }, children: error }) : null
1063
+ /* @__PURE__ */ jsx3(ParamInput, { id: labelId, param, value, disabled, onChange }),
1064
+ error ? /* @__PURE__ */ jsx3("span", { style: { fontSize: 11, color: commandStatusColor("FAILED") }, children: error }) : null
1055
1065
  ] });
1056
1066
  }
1057
1067
  function ParamInput({
@@ -1062,13 +1072,13 @@ function ParamInput({
1062
1072
  onChange
1063
1073
  }) {
1064
1074
  if (param.enum && param.enum.length > 0) {
1065
- return /* @__PURE__ */ jsxs4("select", { id, style: inputStyle, value, disabled, onChange: (e) => onChange(e.target.value), children: [
1066
- !param.required ? /* @__PURE__ */ jsx5("option", { value: "", children: "\u2014" }) : null,
1067
- param.enum.map((opt) => /* @__PURE__ */ jsx5("option", { value: opt, children: opt }, opt))
1075
+ return /* @__PURE__ */ jsxs3("select", { id, style: inputStyle, value, disabled, onChange: (e) => onChange(e.target.value), children: [
1076
+ !param.required ? /* @__PURE__ */ jsx3("option", { value: "", children: "\u2014" }) : null,
1077
+ param.enum.map((opt) => /* @__PURE__ */ jsx3("option", { value: opt, children: opt }, opt))
1068
1078
  ] });
1069
1079
  }
1070
1080
  if (param.dataType === "BOOLEAN") {
1071
- return /* @__PURE__ */ jsx5(
1081
+ return /* @__PURE__ */ jsx3(
1072
1082
  "input",
1073
1083
  {
1074
1084
  id,
@@ -1081,7 +1091,7 @@ function ParamInput({
1081
1091
  );
1082
1092
  }
1083
1093
  const numeric = param.dataType === "INT" || param.dataType === "DOUBLE";
1084
- return /* @__PURE__ */ jsx5(
1094
+ return /* @__PURE__ */ jsx3(
1085
1095
  "input",
1086
1096
  {
1087
1097
  id,
@@ -1104,17 +1114,18 @@ function CommandHistory({
1104
1114
  error
1105
1115
  }) {
1106
1116
  const empty = commands.length === 0;
1107
- return /* @__PURE__ */ jsx5(
1117
+ const timeZone = useWidgetTimeZone2();
1118
+ return /* @__PURE__ */ jsx3(
1108
1119
  "div",
1109
1120
  {
1110
1121
  style: {
1111
1122
  flex: 1,
1112
1123
  minHeight: 0,
1113
1124
  overflow: "auto",
1114
- borderTop: `1px solid ${css("border")}`,
1125
+ borderTop: `1px solid ${css3("border")}`,
1115
1126
  paddingTop: 6
1116
1127
  },
1117
- children: empty ? /* @__PURE__ */ jsx5("div", { style: { ...hint, textAlign: "center", color: error ? commandStatusColor("FAILED") : css("muted-foreground") }, children: error ? "Couldn\u2019t load command history." : loading ? "Loading\u2026" : "No commands issued yet." }) : /* @__PURE__ */ jsx5("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 4 }, children: commands.map((command) => /* @__PURE__ */ jsxs4(
1128
+ children: empty ? /* @__PURE__ */ jsx3("div", { style: { ...hint, textAlign: "center", color: error ? commandStatusColor("FAILED") : css3("muted-foreground") }, children: error ? "Couldn\u2019t load command history." : loading ? "Loading\u2026" : "No commands issued yet." }) : /* @__PURE__ */ jsx3("ul", { style: { listStyle: "none", margin: 0, padding: 0, display: "flex", flexDirection: "column", gap: 4 }, children: commands.map((command) => /* @__PURE__ */ jsxs3(
1118
1129
  "li",
1119
1130
  {
1120
1131
  style: {
@@ -1126,9 +1137,9 @@ function CommandHistory({
1126
1137
  fontWeight: command.token === sentToken ? 600 : 400
1127
1138
  },
1128
1139
  children: [
1129
- /* @__PURE__ */ jsx5(StatusBadge, { status: command.status }),
1130
- /* @__PURE__ */ jsx5("span", { style: { color: css("foreground"), whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: command.name }),
1131
- /* @__PURE__ */ jsx5("span", { style: { marginLeft: "auto", color: css("muted-foreground"), whiteSpace: "nowrap" }, children: formatDateTime(command.queuedTime) })
1140
+ /* @__PURE__ */ jsx3(StatusBadge, { status: command.status }),
1141
+ /* @__PURE__ */ jsx3("span", { style: { color: css3("foreground"), whiteSpace: "nowrap", overflow: "hidden", textOverflow: "ellipsis" }, children: command.name }),
1142
+ /* @__PURE__ */ jsx3("span", { style: { marginLeft: "auto", color: css3("muted-foreground"), whiteSpace: "nowrap" }, children: formatDateTime2(command.queuedTime, timeZone) })
1132
1143
  ]
1133
1144
  },
1134
1145
  command.token
@@ -1138,7 +1149,7 @@ function CommandHistory({
1138
1149
  }
1139
1150
  function StatusBadge({ status }) {
1140
1151
  const color = commandStatusColor(status);
1141
- return /* @__PURE__ */ jsxs4(
1152
+ return /* @__PURE__ */ jsxs3(
1142
1153
  "span",
1143
1154
  {
1144
1155
  style: {
@@ -1151,13 +1162,13 @@ function StatusBadge({ status }) {
1151
1162
  color
1152
1163
  },
1153
1164
  children: [
1154
- /* @__PURE__ */ jsx5("span", { "aria-hidden": "true", style: { width: 7, height: 7, borderRadius: "50%", background: color } }),
1165
+ /* @__PURE__ */ jsx3("span", { "aria-hidden": "true", style: { width: 7, height: 7, borderRadius: "50%", background: color } }),
1155
1166
  commandStatusLabel(status)
1156
1167
  ]
1157
1168
  }
1158
1169
  );
1159
1170
  }
1160
- var hint = { fontSize: 11, color: css("muted-foreground") };
1171
+ var hint = { fontSize: 11, color: css3("muted-foreground") };
1161
1172
  var SEND_FAILED = "Couldn\u2019t issue the command. Try again.";
1162
1173
  var inputStyle = {
1163
1174
  width: "100%",
@@ -1165,9 +1176,9 @@ var inputStyle = {
1165
1176
  padding: "4px 8px",
1166
1177
  fontSize: 13,
1167
1178
  borderRadius: 4,
1168
- border: `1px solid ${css("border")}`,
1169
- background: css("background"),
1170
- color: css("foreground")
1179
+ border: `1px solid ${css3("border")}`,
1180
+ background: css3("background"),
1181
+ color: css3("foreground")
1171
1182
  };
1172
1183
  var sendButton = {
1173
1184
  padding: "6px 12px",
@@ -1175,14 +1186,26 @@ var sendButton = {
1175
1186
  fontWeight: 600,
1176
1187
  borderRadius: 4,
1177
1188
  border: "none",
1178
- background: css("primary"),
1179
- color: css("background"),
1189
+ background: css3("primary"),
1190
+ color: css3("background"),
1180
1191
  marginTop: 2
1181
1192
  };
1193
+ var commandButtonWidget = defineWidget3({
1194
+ descriptor: builtinWidgetDescriptors["command-button"],
1195
+ implementation: { kind: "trusted-react", component: CommandButton }
1196
+ });
1182
1197
 
1183
1198
  // src/widgets/entity-selector.tsx
1184
- import { useEffect as useEffect4, useState as useState5 } from "react";
1185
- import { Fragment as Fragment3, jsx as jsx6, jsxs as jsxs5 } from "react/jsx-runtime";
1199
+ import { useEffect as useEffect2, useState as useState3 } from "react";
1200
+ import {
1201
+ css as css4,
1202
+ defineWidget as defineWidget4,
1203
+ optString as optString5,
1204
+ useCandidates,
1205
+ useWidgetSelect as useWidgetSelect2,
1206
+ WidgetFrame as WidgetFrame4
1207
+ } from "@devicechain/widget-sdk";
1208
+ import { Fragment as Fragment3, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
1186
1209
  function tokenOf(b) {
1187
1210
  return b.kind === "device" ? b.deviceToken : b.anchor.targetToken;
1188
1211
  }
@@ -1191,23 +1214,23 @@ var control = {
1191
1214
  fontSize: 14,
1192
1215
  padding: "8px 10px",
1193
1216
  borderRadius: 6,
1194
- border: `1px solid ${css("border")}`,
1195
- background: css("background"),
1196
- color: css("foreground")
1217
+ border: `1px solid ${css4("border")}`,
1218
+ background: css4("background"),
1219
+ color: css4("foreground")
1197
1220
  };
1198
1221
  var hint2 = {
1199
1222
  fontSize: 12,
1200
- color: css("muted-foreground")
1223
+ color: css4("muted-foreground")
1201
1224
  };
1202
1225
  function EntitySelector({ widget }) {
1203
- const select = useWidgetSelect();
1204
- const target = optString(widget.options, "selectionTarget");
1226
+ const select = useWidgetSelect2();
1227
+ const target = optString5(widget.options, "selectionTarget");
1205
1228
  const { candidates, loading, error, wired } = useCandidates(target);
1206
- const title = optString(widget.options, "title");
1207
- const [pending, setPending] = useState5(null);
1229
+ const title = optString5(widget.options, "title");
1230
+ const [pending, setPending] = useState3(null);
1208
1231
  const selected = candidates.find((c) => c.selected);
1209
1232
  const selectedToken = selected ? tokenOf(selected.binding) : "";
1210
- useEffect4(() => {
1233
+ useEffect2(() => {
1211
1234
  if (pending !== null && (selectedToken === pending || !candidates.some((c) => tokenOf(c.binding) === pending))) {
1212
1235
  setPending(null);
1213
1236
  }
@@ -1223,8 +1246,8 @@ function EntitySelector({ widget }) {
1223
1246
  select({ slot: target, binding: candidate.binding });
1224
1247
  }
1225
1248
  };
1226
- return /* @__PURE__ */ jsx6(WidgetFrame, { title, children: /* @__PURE__ */ jsx6("div", { style: { display: "flex", flexDirection: "column", gap: 8, padding: "12px 16px", height: "100%" }, children: inert ? /* @__PURE__ */ jsx6("span", { style: hint2, children: select && target ? "Selection isn\u2019t available here." : select ? "No target slot configured." : "Selection is available on the live dashboard." }) : /* @__PURE__ */ jsxs5(Fragment3, { children: [
1227
- /* @__PURE__ */ jsxs5(
1249
+ return /* @__PURE__ */ jsx4(WidgetFrame4, { title, children: /* @__PURE__ */ jsx4("div", { style: { display: "flex", flexDirection: "column", gap: 8, padding: "12px 16px", height: "100%" }, children: inert ? /* @__PURE__ */ jsx4("span", { style: hint2, children: select && target ? "Selection isn\u2019t available here." : select ? "No target slot configured." : "Selection is available on the live dashboard." }) : /* @__PURE__ */ jsxs4(Fragment3, { children: [
1250
+ /* @__PURE__ */ jsxs4(
1228
1251
  "select",
1229
1252
  {
1230
1253
  style: { ...control, opacity: loading ? 0.6 : 1 },
@@ -1233,165 +1256,74 @@ function EntitySelector({ widget }) {
1233
1256
  onChange,
1234
1257
  "aria-label": title || `Select ${target}`,
1235
1258
  children: [
1236
- /* @__PURE__ */ jsx6("option", { value: "", disabled: true, children: loading ? "Loading\u2026" : candidates.length === 0 ? "No options" : "Select\u2026" }),
1237
- candidates.map((c) => /* @__PURE__ */ jsx6("option", { value: tokenOf(c.binding), children: c.label }, tokenOf(c.binding)))
1259
+ /* @__PURE__ */ jsx4("option", { value: "", disabled: true, children: loading ? "Loading\u2026" : candidates.length === 0 ? "No options" : "Select\u2026" }),
1260
+ candidates.map((c) => /* @__PURE__ */ jsx4("option", { value: tokenOf(c.binding), children: c.label }, tokenOf(c.binding)))
1238
1261
  ]
1239
1262
  }
1240
1263
  ),
1241
- error ? /* @__PURE__ */ jsx6("span", { style: hint2, children: "Couldn\u2019t load options." }) : null
1264
+ error ? /* @__PURE__ */ jsx4("span", { style: hint2, children: "Couldn\u2019t load options." }) : null
1242
1265
  ] }) }) });
1243
1266
  }
1267
+ var entitySelectorWidget = defineWidget4({
1268
+ descriptor: builtinWidgetDescriptors["entity-selector"],
1269
+ implementation: { kind: "trusted-react", component: EntitySelector }
1270
+ });
1244
1271
 
1245
1272
  // src/widgets/gauge.tsx
1246
- import { useMemo as useMemo3 } from "react";
1247
-
1248
- // src/chart-options.ts
1249
- function distinctNames(samples) {
1250
- const seen = [];
1251
- for (const s of samples) {
1252
- if (!seen.includes(s.name)) seen.push(s.name);
1253
- }
1254
- return seen;
1255
- }
1256
- var clamp2 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1257
- function shortSide(size) {
1258
- const s = Math.min(size?.width ?? 0, size?.height ?? 0);
1259
- return s > 0 ? s : 200;
1260
- }
1261
- function buildLineOption(samples, theme, options = {}, size) {
1262
- const names = options.measurements?.length ? options.measurements : distinctNames(samples);
1263
- const axisFont = clamp2(Math.round(shortSide(size) / 22), 8, 12);
1264
- const series = names.map((name, i) => {
1265
- const color = theme.series[i % theme.series.length];
1266
- return {
1267
- name,
1268
- type: "line",
1269
- showSymbol: false,
1270
- lineStyle: { color },
1271
- itemStyle: { color },
1272
- data: samples.filter((s) => s.name === name && s.value != null && s.occurredTime != null).map((s) => [s.occurredTime, s.value])
1273
- };
1274
- });
1275
- return {
1276
- animation: false,
1277
- textStyle: { color: theme.foreground, fontSize: axisFont },
1278
- grid: { left: 8, right: 12, top: names.length > 1 ? 28 : 12, bottom: 8, containLabel: true },
1279
- tooltip: { trigger: "axis" },
1280
- legend: names.length > 1 ? { textStyle: { color: theme.mutedForeground, fontSize: axisFont }, top: 0 } : void 0,
1281
- xAxis: {
1282
- type: "time",
1283
- axisLine: { lineStyle: { color: theme.border } },
1284
- axisLabel: { color: theme.mutedForeground, fontSize: axisFont, hideOverlap: true }
1285
- },
1286
- yAxis: {
1287
- type: "value",
1288
- splitLine: { lineStyle: { color: theme.border } },
1289
- axisLabel: { color: theme.mutedForeground, fontSize: axisFont }
1290
- },
1291
- series
1292
- };
1293
- }
1294
- function buildGaugeOption(value, theme, options = {}, size) {
1295
- const min = options.min ?? 0;
1296
- const max = options.max ?? 100;
1297
- const accent = theme.series[0];
1298
- const unitSuffix = options.unit ? ` ${options.unit}` : "";
1299
- const s = shortSide(size);
1300
- const arcWidth = clamp2(Math.round(s / 26), 4, 12);
1301
- const labelFont = clamp2(Math.round(s / 20), 8, 13);
1302
- const valueFont = clamp2(Math.round(s / 7), 14, 40);
1303
- return {
1304
- animation: false,
1305
- series: [
1306
- {
1307
- type: "gauge",
1308
- min,
1309
- max,
1310
- splitNumber: s < 160 ? 4 : 5,
1311
- // fewer ticks than the default 10 → no cram
1312
- radius: "95%",
1313
- center: ["50%", "58%"],
1314
- progress: { show: true, width: arcWidth, itemStyle: { color: accent } },
1315
- pointer: { itemStyle: { color: accent }, length: "55%", width: clamp2(Math.round(s / 50), 2, 6) },
1316
- anchor: { show: false },
1317
- axisLine: { lineStyle: { width: arcWidth, color: [[1, theme.border]] } },
1318
- axisTick: { show: false },
1319
- splitLine: { length: arcWidth, lineStyle: { color: theme.border, width: 1 } },
1320
- axisLabel: { color: theme.mutedForeground, fontSize: labelFont, distance: arcWidth + 2 },
1321
- detail: {
1322
- valueAnimation: false,
1323
- color: theme.foreground,
1324
- fontSize: valueFont,
1325
- offsetCenter: [0, "30%"],
1326
- formatter: (v) => value == null ? "\u2014" : `${v}${unitSuffix}`
1327
- },
1328
- data: [{ value: value ?? min }]
1329
- }
1330
- ]
1331
- };
1332
- }
1333
-
1334
- // src/echart.tsx
1335
- import { GaugeChart, LineChart } from "echarts/charts";
1336
- import { GridComponent, TooltipComponent } from "echarts/components";
1337
- import * as echarts from "echarts/core";
1338
- import { CanvasRenderer } from "echarts/renderers";
1339
- import { useEffect as useEffect5, useRef as useRef4 } from "react";
1340
- import { jsx as jsx7 } from "react/jsx-runtime";
1341
- echarts.use([LineChart, GaugeChart, GridComponent, TooltipComponent, CanvasRenderer]);
1342
- function EChart({ option, className, style }) {
1343
- const containerRef = useRef4(null);
1344
- const chartRef = useRef4(null);
1345
- useEffect5(() => {
1346
- const el = containerRef.current;
1347
- if (!el) return;
1348
- const chart = echarts.init(el);
1349
- chartRef.current = chart;
1350
- const observer = new ResizeObserver(() => chart.resize());
1351
- observer.observe(el);
1352
- return () => {
1353
- observer.disconnect();
1354
- chart.dispose();
1355
- chartRef.current = null;
1356
- };
1357
- }, []);
1358
- useEffect5(() => {
1359
- chartRef.current?.setOption(option, { notMerge: true });
1360
- }, [option]);
1361
- return /* @__PURE__ */ jsx7("div", { ref: containerRef, className, style: { width: "100%", height: "100%", ...style } });
1362
- }
1363
-
1364
- // src/widgets/gauge.tsx
1365
- import { jsx as jsx8 } from "react/jsx-runtime";
1366
- function Gauge({ widget, data }) {
1367
- const theme = useChartTheme();
1368
- const [sizeRef, size] = useElementSize();
1369
- const name = primaryMeasurementName(widget);
1370
- const sample = pickSample(data.latest, name);
1371
- const min = optNumber(widget.options, "min");
1372
- const max = optNumber(widget.options, "max");
1373
- const unit = optString(widget.options, "unit");
1374
- const option = useMemo3(
1375
- () => buildGaugeOption(sample?.value ?? null, theme, { min, max, unit }, size),
1376
- [sample?.value, theme, min, max, unit, size.width, size.height]
1377
- );
1378
- const flashDirection = useFlashOnChange(
1379
- sample?.value,
1380
- optBoolean(widget.options, "flashOnChange"),
1381
- sample && `${sample.deviceToken}:${sample.name}`
1382
- );
1383
- return /* @__PURE__ */ jsx8(WidgetFrame, { title: optString(widget.options, "title") ?? name, children: /* @__PURE__ */ jsx8("div", { ref: sizeRef, style: { width: "100%", height: "100%", ...flashBackgroundStyle(flashDirection) }, children: /* @__PURE__ */ jsx8(EChart, { option }) }) });
1273
+ import { useMemo as useMemo2 } from "react";
1274
+ import {
1275
+ defineWidget as defineWidget5,
1276
+ flashBackgroundStyle,
1277
+ optBoolean as optBoolean2,
1278
+ optNumber as optNumber3,
1279
+ optString as optString6,
1280
+ pickSample,
1281
+ primaryMeasurementName,
1282
+ useElementSize as useElementSize2,
1283
+ useFlashOnChange,
1284
+ WidgetFrame as WidgetFrame5
1285
+ } from "@devicechain/widget-sdk";
1286
+ import { buildGaugeOption, EChart, useChartTheme } from "@devicechain/widget-sdk/echarts";
1287
+ import { jsx as jsx5 } from "react/jsx-runtime";
1288
+ function Gauge({ widget, data }) {
1289
+ const theme = useChartTheme();
1290
+ const [sizeRef, size] = useElementSize2();
1291
+ const name = primaryMeasurementName(widget);
1292
+ const sample = pickSample(data.latest, name);
1293
+ const min = optNumber3(widget.options, "min");
1294
+ const max = optNumber3(widget.options, "max");
1295
+ const unit = optString6(widget.options, "unit");
1296
+ const option = useMemo2(
1297
+ () => buildGaugeOption(sample?.value ?? null, theme, { min, max, unit }, size),
1298
+ [sample?.value, theme, min, max, unit, size.width, size.height]
1299
+ );
1300
+ const flashDirection = useFlashOnChange(
1301
+ sample?.value,
1302
+ optBoolean2(widget.options, "flashOnChange"),
1303
+ sample && `${sample.deviceToken}:${sample.name}`
1304
+ );
1305
+ return /* @__PURE__ */ jsx5(WidgetFrame5, { title: optString6(widget.options, "title") ?? name, children: /* @__PURE__ */ jsx5("div", { ref: sizeRef, style: { width: "100%", height: "100%", ...flashBackgroundStyle(flashDirection) }, children: /* @__PURE__ */ jsx5(EChart, { option }) }) });
1384
1306
  }
1307
+ var gaugeWidget = defineWidget5({
1308
+ descriptor: builtinWidgetDescriptors["gauge"],
1309
+ implementation: { kind: "trusted-react", component: Gauge }
1310
+ });
1385
1311
 
1386
1312
  // src/widgets/image.tsx
1387
- import { jsx as jsx9 } from "react/jsx-runtime";
1313
+ import {
1314
+ css as css5,
1315
+ defineWidget as defineWidget6,
1316
+ optString as optString7,
1317
+ WidgetFrame as WidgetFrame6
1318
+ } from "@devicechain/widget-sdk";
1319
+ import { jsx as jsx6 } from "react/jsx-runtime";
1388
1320
  var FITS = /* @__PURE__ */ new Set(["contain", "cover", "fill"]);
1389
1321
  function Image({ widget }) {
1390
- const url = optString(widget.options, "url");
1391
- const alt = optString(widget.options, "alt") ?? "";
1392
- const fitOption = optString(widget.options, "fit");
1322
+ const url = optString7(widget.options, "url");
1323
+ const alt = optString7(widget.options, "alt") ?? "";
1324
+ const fitOption = optString7(widget.options, "fit");
1393
1325
  const objectFit = fitOption && FITS.has(fitOption) ? fitOption : "contain";
1394
- return /* @__PURE__ */ jsx9(WidgetFrame, { title: optString(widget.options, "title"), children: url ? /* @__PURE__ */ jsx9("img", { src: url, alt, style: { width: "100%", height: "100%", objectFit, display: "block" } }) : /* @__PURE__ */ jsx9(
1326
+ return /* @__PURE__ */ jsx6(WidgetFrame6, { title: optString7(widget.options, "title"), children: url ? /* @__PURE__ */ jsx6("img", { src: url, alt, style: { width: "100%", height: "100%", objectFit, display: "block" } }) : /* @__PURE__ */ jsx6(
1395
1327
  "div",
1396
1328
  {
1397
1329
  style: {
@@ -1399,26 +1331,37 @@ function Image({ widget }) {
1399
1331
  alignItems: "center",
1400
1332
  justifyContent: "center",
1401
1333
  height: "100%",
1402
- color: css("muted-foreground"),
1334
+ color: css5("muted-foreground"),
1403
1335
  fontSize: 13
1404
1336
  },
1405
1337
  children: "No image URL"
1406
1338
  }
1407
1339
  ) });
1408
1340
  }
1341
+ var imageWidget = defineWidget6({
1342
+ descriptor: builtinWidgetDescriptors["image"],
1343
+ implementation: { kind: "trusted-react", component: Image }
1344
+ });
1409
1345
 
1410
1346
  // src/widgets/label.tsx
1411
- import { jsx as jsx10 } from "react/jsx-runtime";
1347
+ import {
1348
+ css as css6,
1349
+ defineWidget as defineWidget7,
1350
+ optNumber as optNumber4,
1351
+ optString as optString8,
1352
+ WidgetFrame as WidgetFrame7
1353
+ } from "@devicechain/widget-sdk";
1354
+ import { jsx as jsx7 } from "react/jsx-runtime";
1412
1355
  var justify = {
1413
1356
  left: "flex-start",
1414
1357
  center: "center",
1415
1358
  right: "flex-end"
1416
1359
  };
1417
1360
  function Label({ widget }) {
1418
- const text = optString(widget.options, "text") ?? "";
1419
- const align = optString(widget.options, "align") ?? "center";
1420
- const fontSize = optNumber(widget.options, "fontSize") ?? 16;
1421
- return /* @__PURE__ */ jsx10(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx10(
1361
+ const text = optString8(widget.options, "text") ?? "";
1362
+ const align = optString8(widget.options, "align") ?? "center";
1363
+ const fontSize = optNumber4(widget.options, "fontSize") ?? 16;
1364
+ return /* @__PURE__ */ jsx7(WidgetFrame7, { title: optString8(widget.options, "title"), children: /* @__PURE__ */ jsx7(
1422
1365
  "div",
1423
1366
  {
1424
1367
  style: {
@@ -1428,12 +1371,12 @@ function Label({ widget }) {
1428
1371
  height: "100%",
1429
1372
  padding: 12
1430
1373
  },
1431
- children: /* @__PURE__ */ jsx10(
1374
+ children: /* @__PURE__ */ jsx7(
1432
1375
  "span",
1433
1376
  {
1434
1377
  style: {
1435
1378
  fontSize,
1436
- color: css("foreground"),
1379
+ color: css6("foreground"),
1437
1380
  textAlign: align,
1438
1381
  whiteSpace: "pre-wrap"
1439
1382
  },
@@ -1443,25 +1386,46 @@ function Label({ widget }) {
1443
1386
  }
1444
1387
  ) });
1445
1388
  }
1389
+ var labelWidget = defineWidget7({
1390
+ descriptor: builtinWidgetDescriptors["label"],
1391
+ implementation: { kind: "trusted-react", component: Label }
1392
+ });
1446
1393
 
1447
1394
  // src/widgets/latest-card.tsx
1448
- import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1449
- var clamp3 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1395
+ import {
1396
+ css as css7,
1397
+ defineWidget as defineWidget8,
1398
+ flashTextStyle,
1399
+ formatTimestamp,
1400
+ formatValue,
1401
+ optBoolean as optBoolean3,
1402
+ optNumber as optNumber5,
1403
+ optString as optString9,
1404
+ pickSample as pickSample2,
1405
+ primaryMeasurementName as primaryMeasurementName2,
1406
+ useElementSize as useElementSize3,
1407
+ useFlashOnChange as useFlashOnChange2,
1408
+ useWidgetTimeZone as useWidgetTimeZone3,
1409
+ WidgetFrame as WidgetFrame8
1410
+ } from "@devicechain/widget-sdk";
1411
+ import { jsx as jsx8, jsxs as jsxs5 } from "react/jsx-runtime";
1412
+ var clamp2 = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
1450
1413
  function LatestCard({ widget, data }) {
1451
- const [sizeRef, size] = useElementSize();
1452
- const name = primaryMeasurementName(widget);
1453
- const sample = pickSample(data.latest, name);
1454
- const title = optString(widget.options, "title") ?? name;
1455
- const unit = optString(widget.options, "unit");
1456
- const precision = optNumber(widget.options, "precision");
1414
+ const [sizeRef, size] = useElementSize3();
1415
+ const timeZone = useWidgetTimeZone3();
1416
+ const name = primaryMeasurementName2(widget);
1417
+ const sample = pickSample2(data.latest, name);
1418
+ const title = optString9(widget.options, "title") ?? name;
1419
+ const unit = optString9(widget.options, "unit");
1420
+ const precision = optNumber5(widget.options, "precision");
1457
1421
  const display = formatValue(sample?.value, precision);
1458
- const flashDirection = useFlashOnChange(
1422
+ const flashDirection = useFlashOnChange2(
1459
1423
  sample?.value,
1460
- optBoolean(widget.options, "flashOnChange"),
1424
+ optBoolean3(widget.options, "flashOnChange"),
1461
1425
  sample && `${sample.deviceToken}:${sample.name}`
1462
1426
  );
1463
- const valueFont = size.height > 0 ? clamp3(Math.round(size.height / 3.5), 16, 44) : 32;
1464
- return /* @__PURE__ */ jsx11(WidgetFrame, { title, children: /* @__PURE__ */ jsxs6(
1427
+ const valueFont = size.height > 0 ? clamp2(Math.round(size.height / 3.5), 16, 44) : 32;
1428
+ return /* @__PURE__ */ jsx8(WidgetFrame8, { title, children: /* @__PURE__ */ jsxs5(
1465
1429
  "div",
1466
1430
  {
1467
1431
  ref: sizeRef,
@@ -1473,60 +1437,72 @@ function LatestCard({ widget, data }) {
1473
1437
  padding: "12px 16px"
1474
1438
  },
1475
1439
  children: [
1476
- /* @__PURE__ */ jsxs6(
1440
+ /* @__PURE__ */ jsxs5(
1477
1441
  "div",
1478
1442
  {
1479
1443
  style: {
1480
1444
  fontSize: valueFont,
1481
1445
  fontWeight: 600,
1482
1446
  lineHeight: 1.1,
1483
- color: css("foreground"),
1447
+ color: css7("foreground"),
1484
1448
  ...flashTextStyle(flashDirection)
1485
1449
  },
1486
1450
  children: [
1487
1451
  display,
1488
- sample?.value != null && unit ? /* @__PURE__ */ jsx11("span", { style: { fontSize: Math.round(valueFont / 2), color: css("muted-foreground"), marginLeft: 6 }, children: unit }) : null
1452
+ sample?.value != null && unit ? /* @__PURE__ */ jsx8("span", { style: { fontSize: Math.round(valueFont / 2), color: css7("muted-foreground"), marginLeft: 6 }, children: unit }) : null
1489
1453
  ]
1490
1454
  }
1491
1455
  ),
1492
- sample?.occurredTime ? /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, color: css("muted-foreground"), marginTop: 4 }, children: formatTimestamp(sample.occurredTime) }) : null
1456
+ sample?.occurredTime ? /* @__PURE__ */ jsx8("div", { style: { fontSize: 11, color: css7("muted-foreground"), marginTop: 4 }, children: formatTimestamp(sample.occurredTime, timeZone) }) : null
1493
1457
  ]
1494
1458
  }
1495
1459
  ) });
1496
1460
  }
1461
+ var latestCardWidget = defineWidget8({
1462
+ descriptor: builtinWidgetDescriptors["latest-card"],
1463
+ implementation: { kind: "trusted-react", component: LatestCard }
1464
+ });
1497
1465
 
1498
1466
  // src/widgets/map.tsx
1499
- import { useEffect as useEffect6, useRef as useRef5, useState as useState6 } from "react";
1467
+ import { useEffect as useEffect3, useRef as useRef2, useState as useState4 } from "react";
1500
1468
  import { renderableBasemap, resolveBasemap } from "@devicechain/client";
1501
1469
 
1502
1470
  // src/basemap-context.tsx
1503
- import { createContext as createContext2, useContext as useContext2 } from "react";
1504
- import { jsx as jsx12 } from "react/jsx-runtime";
1505
- var TenantBasemapContext = createContext2(null);
1471
+ import { createContext, useContext } from "react";
1472
+ import { jsx as jsx9 } from "react/jsx-runtime";
1473
+ var TenantBasemapContext = createContext(null);
1506
1474
  function TenantBasemapProvider({
1507
1475
  basemap,
1508
1476
  children
1509
1477
  }) {
1510
- return /* @__PURE__ */ jsx12(TenantBasemapContext.Provider, { value: basemap, children });
1478
+ return /* @__PURE__ */ jsx9(TenantBasemapContext.Provider, { value: basemap, children });
1511
1479
  }
1512
1480
  function useTenantBasemap() {
1513
- return useContext2(TenantBasemapContext);
1481
+ return useContext(TenantBasemapContext);
1514
1482
  }
1515
1483
 
1516
1484
  // src/map-runtime-context.tsx
1517
- import { createContext as createContext3, useContext as useContext3 } from "react";
1518
- import { jsx as jsx13 } from "react/jsx-runtime";
1519
- var MapRuntimeContext = createContext3(null);
1485
+ import { createContext as createContext2, useContext as useContext2 } from "react";
1486
+ import { jsx as jsx10 } from "react/jsx-runtime";
1487
+ var MapRuntimeContext = createContext2(null);
1520
1488
  function MapRuntimeProvider({
1521
1489
  runtime,
1522
1490
  children
1523
1491
  }) {
1524
- return /* @__PURE__ */ jsx13(MapRuntimeContext.Provider, { value: runtime, children });
1492
+ return /* @__PURE__ */ jsx10(MapRuntimeContext.Provider, { value: runtime, children });
1525
1493
  }
1526
1494
  function useMapRuntime() {
1527
- return useContext3(MapRuntimeContext);
1495
+ return useContext2(MapRuntimeContext);
1528
1496
  }
1529
1497
 
1498
+ // src/widgets/map.tsx
1499
+ import {
1500
+ css as css8,
1501
+ defineWidget as defineWidget9,
1502
+ optString as optString10,
1503
+ WidgetFrame as WidgetFrame9
1504
+ } from "@devicechain/widget-sdk";
1505
+
1530
1506
  // src/widgets/map-geometry.ts
1531
1507
  function placeable(samples) {
1532
1508
  return samples.filter(
@@ -1620,7 +1596,7 @@ function loadLandStyle() {
1620
1596
  }
1621
1597
 
1622
1598
  // src/widgets/map.tsx
1623
- import { jsx as jsx14, jsxs as jsxs7 } from "react/jsx-runtime";
1599
+ import { jsx as jsx11, jsxs as jsxs6 } from "react/jsx-runtime";
1624
1600
  var mapLibrePromise;
1625
1601
  function loadMapLibre(runtime) {
1626
1602
  if (!mapLibrePromise) {
@@ -1636,20 +1612,20 @@ function loadMapLibre(runtime) {
1636
1612
  }
1637
1613
  var SINGLE_DEVICE_ZOOM = 14;
1638
1614
  function MapWidget({ widget, data }) {
1639
- const title = optString(widget.options, "title");
1615
+ const title = optString10(widget.options, "title");
1640
1616
  const positions = placeable(data.locations);
1641
1617
  const runtime = useMapRuntime();
1642
1618
  const basemap = renderableBasemap(
1643
1619
  resolveBasemap(
1644
1620
  {
1645
- tileUrl: optString(widget.options, "tileUrl"),
1646
- attribution: optString(widget.options, "attribution")
1621
+ tileUrl: optString10(widget.options, "tileUrl"),
1622
+ attribution: optString10(widget.options, "attribution")
1647
1623
  },
1648
1624
  useTenantBasemap()
1649
1625
  )
1650
1626
  );
1651
1627
  if (data.forbidden) {
1652
- return /* @__PURE__ */ jsx14(WidgetFrame, { title, children: /* @__PURE__ */ jsx14(
1628
+ return /* @__PURE__ */ jsx11(WidgetFrame9, { title, children: /* @__PURE__ */ jsx11(
1653
1629
  MapNotice,
1654
1630
  {
1655
1631
  glyph: "\u{1F512}",
@@ -1659,7 +1635,7 @@ function MapWidget({ widget, data }) {
1659
1635
  ) });
1660
1636
  }
1661
1637
  if (!runtime) {
1662
- return /* @__PURE__ */ jsx14(WidgetFrame, { title, children: /* @__PURE__ */ jsx14(
1638
+ return /* @__PURE__ */ jsx11(WidgetFrame9, { title, children: /* @__PURE__ */ jsx11(
1663
1639
  MapNotice,
1664
1640
  {
1665
1641
  glyph: "\u{1F9E9}",
@@ -1669,10 +1645,10 @@ function MapWidget({ widget, data }) {
1669
1645
  ) });
1670
1646
  }
1671
1647
  if (data.loading && positions.length === 0) {
1672
- return /* @__PURE__ */ jsx14(WidgetFrame, { title, children: /* @__PURE__ */ jsx14(MapNotice, { heading: "Loading\u2026" }) });
1648
+ return /* @__PURE__ */ jsx11(WidgetFrame9, { title, children: /* @__PURE__ */ jsx11(MapNotice, { heading: "Loading\u2026" }) });
1673
1649
  }
1674
1650
  if (positions.length === 0) {
1675
- return /* @__PURE__ */ jsx14(WidgetFrame, { title, children: /* @__PURE__ */ jsx14(
1651
+ return /* @__PURE__ */ jsx11(WidgetFrame9, { title, children: /* @__PURE__ */ jsx11(
1676
1652
  MapNotice,
1677
1653
  {
1678
1654
  heading: data.deviceTokens.length === 0 ? "No devices selected" : "No positions reported",
@@ -1680,11 +1656,11 @@ function MapWidget({ widget, data }) {
1680
1656
  }
1681
1657
  ) });
1682
1658
  }
1683
- return /* @__PURE__ */ jsx14(WidgetFrame, { title, bodyStyle: { padding: 0 }, children: /* @__PURE__ */ jsx14(LiveMap, { basemap, positions, runtime }) });
1659
+ return /* @__PURE__ */ jsx11(WidgetFrame9, { title, bodyStyle: { padding: 0 }, children: /* @__PURE__ */ jsx11(LiveMap, { basemap, positions, runtime }) });
1684
1660
  }
1685
1661
  function PlainPanel({ positions, note }) {
1686
1662
  const points = projectToPanel(positions);
1687
- return /* @__PURE__ */ jsxs7(
1663
+ return /* @__PURE__ */ jsxs6(
1688
1664
  "div",
1689
1665
  {
1690
1666
  "data-testid": "map-plain-panel",
@@ -1693,10 +1669,10 @@ function PlainPanel({ positions, note }) {
1693
1669
  width: "100%",
1694
1670
  height: "100%",
1695
1671
  overflow: "hidden",
1696
- background: css("muted")
1672
+ background: css8("muted")
1697
1673
  },
1698
1674
  children: [
1699
- positions.map((position, i) => /* @__PURE__ */ jsx14(
1675
+ positions.map((position, i) => /* @__PURE__ */ jsx11(
1700
1676
  Marker,
1701
1677
  {
1702
1678
  position,
@@ -1709,7 +1685,7 @@ function PlainPanel({ positions, note }) {
1709
1685
  },
1710
1686
  position.deviceToken
1711
1687
  )),
1712
- /* @__PURE__ */ jsx14(
1688
+ /* @__PURE__ */ jsx11(
1713
1689
  "div",
1714
1690
  {
1715
1691
  "data-testid": "map-no-tiles-note",
@@ -1722,8 +1698,8 @@ function PlainPanel({ positions, note }) {
1722
1698
  fontSize: 10,
1723
1699
  lineHeight: 1.3,
1724
1700
  textAlign: "center",
1725
- color: css("muted-foreground"),
1726
- background: css("card"),
1701
+ color: css8("muted-foreground"),
1702
+ background: css8("card"),
1727
1703
  opacity: 0.9
1728
1704
  },
1729
1705
  children: note
@@ -1738,13 +1714,13 @@ function LiveMap({
1738
1714
  positions,
1739
1715
  runtime
1740
1716
  }) {
1741
- const containerRef = useRef5(null);
1742
- const mapRef = useRef5(null);
1743
- const markersRef = useRef5([]);
1744
- const [status, setStatus] = useState6("loading");
1717
+ const containerRef = useRef2(null);
1718
+ const mapRef = useRef2(null);
1719
+ const markersRef = useRef2([]);
1720
+ const [status, setStatus] = useState4("loading");
1745
1721
  const tileUrl = basemap?.tileUrl ?? null;
1746
1722
  const attribution = basemap?.attribution ?? null;
1747
- useEffect6(() => {
1723
+ useEffect3(() => {
1748
1724
  let cancelled = false;
1749
1725
  setStatus("loading");
1750
1726
  Promise.all([
@@ -1772,7 +1748,7 @@ function LiveMap({
1772
1748
  mapRef.current = null;
1773
1749
  };
1774
1750
  }, [tileUrl, attribution, runtime.workerUrl]);
1775
- useEffect6(() => {
1751
+ useEffect3(() => {
1776
1752
  const map = mapRef.current;
1777
1753
  if (status !== "ready" || !map) return;
1778
1754
  let live = true;
@@ -1801,7 +1777,7 @@ function LiveMap({
1801
1777
  };
1802
1778
  }, [status, positionsKey(positions)]);
1803
1779
  if (status === "unavailable") {
1804
- return /* @__PURE__ */ jsx14(
1780
+ return /* @__PURE__ */ jsx11(
1805
1781
  PlainPanel,
1806
1782
  {
1807
1783
  positions,
@@ -1809,7 +1785,7 @@ function LiveMap({
1809
1785
  }
1810
1786
  );
1811
1787
  }
1812
- return /* @__PURE__ */ jsx14("div", { ref: containerRef, "data-testid": "map-canvas", style: { width: "100%", height: "100%" } });
1788
+ return /* @__PURE__ */ jsx11("div", { ref: containerRef, "data-testid": "map-canvas", style: { width: "100%", height: "100%" } });
1813
1789
  }
1814
1790
  function positionsKey(positions) {
1815
1791
  return positions.map((p) => `${p.deviceToken}:${p.latitude}:${p.longitude}:${p.heading ?? ""}`).join("|");
@@ -1818,8 +1794,8 @@ var MARKER_DOT_STYLE = {
1818
1794
  width: "12px",
1819
1795
  height: "12px",
1820
1796
  borderRadius: "50%",
1821
- background: css("primary"),
1822
- border: `2px solid ${css("card")}`,
1797
+ background: css8("primary"),
1798
+ border: `2px solid ${css8("card")}`,
1823
1799
  boxShadow: "0 0 0 1px rgba(0,0,0,0.25)",
1824
1800
  cursor: "default"
1825
1801
  };
@@ -1828,7 +1804,7 @@ function Marker({
1828
1804
  style
1829
1805
  }) {
1830
1806
  const label = describePosition(position);
1831
- return /* @__PURE__ */ jsx14(
1807
+ return /* @__PURE__ */ jsx11(
1832
1808
  "div",
1833
1809
  {
1834
1810
  "data-testid": "map-marker",
@@ -1840,7 +1816,7 @@ function Marker({
1840
1816
  );
1841
1817
  }
1842
1818
  function MapNotice({ glyph, heading, detail }) {
1843
- return /* @__PURE__ */ jsxs7(
1819
+ return /* @__PURE__ */ jsxs6(
1844
1820
  "div",
1845
1821
  {
1846
1822
  style: {
@@ -1854,19 +1830,34 @@ function MapNotice({ glyph, heading, detail }) {
1854
1830
  padding: 12,
1855
1831
  boxSizing: "border-box",
1856
1832
  textAlign: "center",
1857
- color: css("muted-foreground")
1833
+ color: css8("muted-foreground")
1858
1834
  },
1859
1835
  children: [
1860
- glyph ? /* @__PURE__ */ jsx14("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: glyph }) : null,
1861
- /* @__PURE__ */ jsx14("div", { style: { fontSize: 13, fontWeight: 600 }, children: heading }),
1862
- detail ? /* @__PURE__ */ jsx14("div", { style: { fontSize: 11, opacity: 0.8 }, children: detail }) : null
1836
+ glyph ? /* @__PURE__ */ jsx11("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: glyph }) : null,
1837
+ /* @__PURE__ */ jsx11("div", { style: { fontSize: 13, fontWeight: 600 }, children: heading }),
1838
+ detail ? /* @__PURE__ */ jsx11("div", { style: { fontSize: 11, opacity: 0.8 }, children: detail }) : null
1863
1839
  ]
1864
1840
  }
1865
1841
  );
1866
1842
  }
1843
+ var mapWidget = defineWidget9({
1844
+ descriptor: builtinWidgetDescriptors["map"],
1845
+ implementation: { kind: "trusted-react", component: MapWidget }
1846
+ });
1867
1847
 
1868
1848
  // src/widgets/table.tsx
1869
- import { jsx as jsx15, jsxs as jsxs8 } from "react/jsx-runtime";
1849
+ import {
1850
+ css as css9,
1851
+ defineWidget as defineWidget10,
1852
+ FlashValue as FlashValue2,
1853
+ formatTimestamp as formatTimestamp2,
1854
+ optBoolean as optBoolean4,
1855
+ optNumber as optNumber6,
1856
+ optString as optString11,
1857
+ useWidgetTimeZone as useWidgetTimeZone4,
1858
+ WidgetFrame as WidgetFrame10
1859
+ } from "@devicechain/widget-sdk";
1860
+ import { jsx as jsx12, jsxs as jsxs7 } from "react/jsx-runtime";
1870
1861
  var cell2 = { padding: "6px 12px", textAlign: "left" };
1871
1862
  var headCell2 = {
1872
1863
  ...cell2,
@@ -1874,23 +1865,24 @@ var headCell2 = {
1874
1865
  fontWeight: 600,
1875
1866
  textTransform: "uppercase",
1876
1867
  letterSpacing: "0.04em",
1877
- color: css("muted-foreground"),
1878
- borderBottom: `1px solid ${css("border")}`
1868
+ color: css9("muted-foreground"),
1869
+ borderBottom: `1px solid ${css9("border")}`
1879
1870
  };
1880
1871
  function Table({ widget, data }) {
1872
+ const timeZone = useWidgetTimeZone4();
1881
1873
  const rows = Object.values(data.latest).sort((a, b) => a.name.localeCompare(b.name));
1882
- const precision = optNumber(widget.options, "precision");
1883
- const flash = optBoolean(widget.options, "flashOnChange");
1884
- return /* @__PURE__ */ jsx15(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx15("div", { style: { height: "100%", overflow: "auto" }, children: /* @__PURE__ */ jsxs8("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13, color: css("foreground") }, children: [
1885
- /* @__PURE__ */ jsx15("thead", { children: /* @__PURE__ */ jsxs8("tr", { children: [
1886
- /* @__PURE__ */ jsx15("th", { style: headCell2, children: "Measurement" }),
1887
- /* @__PURE__ */ jsx15("th", { style: headCell2, children: "Value" }),
1888
- /* @__PURE__ */ jsx15("th", { style: headCell2, children: "Time" })
1874
+ const precision = optNumber6(widget.options, "precision");
1875
+ const flash = optBoolean4(widget.options, "flashOnChange");
1876
+ return /* @__PURE__ */ jsx12(WidgetFrame10, { title: optString11(widget.options, "title"), children: /* @__PURE__ */ jsx12("div", { style: { height: "100%", overflow: "auto" }, children: /* @__PURE__ */ jsxs7("table", { style: { width: "100%", borderCollapse: "collapse", fontSize: 13, color: css9("foreground") }, children: [
1877
+ /* @__PURE__ */ jsx12("thead", { children: /* @__PURE__ */ jsxs7("tr", { children: [
1878
+ /* @__PURE__ */ jsx12("th", { style: headCell2, children: "Measurement" }),
1879
+ /* @__PURE__ */ jsx12("th", { style: headCell2, children: "Value" }),
1880
+ /* @__PURE__ */ jsx12("th", { style: headCell2, children: "Time" })
1889
1881
  ] }) }),
1890
- /* @__PURE__ */ jsx15("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx15("tr", { children: /* @__PURE__ */ jsx15("td", { colSpan: 3, style: { ...cell2, textAlign: "center", color: css("muted-foreground") }, children: "No data yet" }) }) : rows.map((row) => /* @__PURE__ */ jsxs8("tr", { children: [
1891
- /* @__PURE__ */ jsx15("td", { style: cell2, children: row.name }),
1892
- /* @__PURE__ */ jsx15("td", { style: { ...cell2, fontVariantNumeric: "tabular-nums" }, children: /* @__PURE__ */ jsx15(
1893
- FlashValue,
1882
+ /* @__PURE__ */ jsx12("tbody", { children: rows.length === 0 ? /* @__PURE__ */ jsx12("tr", { children: /* @__PURE__ */ jsx12("td", { colSpan: 3, style: { ...cell2, textAlign: "center", color: css9("muted-foreground") }, children: "No data yet" }) }) : rows.map((row) => /* @__PURE__ */ jsxs7("tr", { children: [
1883
+ /* @__PURE__ */ jsx12("td", { style: cell2, children: row.name }),
1884
+ /* @__PURE__ */ jsx12("td", { style: { ...cell2, fontVariantNumeric: "tabular-nums" }, children: /* @__PURE__ */ jsx12(
1885
+ FlashValue2,
1894
1886
  {
1895
1887
  value: row.value,
1896
1888
  precision,
@@ -1898,176 +1890,361 @@ function Table({ widget, data }) {
1898
1890
  identity: `${row.deviceToken}:${row.name}`
1899
1891
  }
1900
1892
  ) }),
1901
- /* @__PURE__ */ jsx15("td", { style: { ...cell2, color: css("muted-foreground") }, children: formatTimestamp(row.occurredTime) })
1893
+ /* @__PURE__ */ jsx12("td", { style: { ...cell2, color: css9("muted-foreground") }, children: formatTimestamp2(row.occurredTime, timeZone) })
1902
1894
  ] }, row.name)) })
1903
1895
  ] }) }) });
1904
1896
  }
1897
+ var tableWidget = defineWidget10({
1898
+ descriptor: builtinWidgetDescriptors["table"],
1899
+ implementation: { kind: "trusted-react", component: Table }
1900
+ });
1905
1901
 
1906
1902
  // src/widgets/time-series-chart.tsx
1907
- import { useMemo as useMemo4 } from "react";
1908
- import { jsx as jsx16 } from "react/jsx-runtime";
1903
+ import { useMemo as useMemo3 } from "react";
1904
+ import {
1905
+ defineWidget as defineWidget11,
1906
+ optString as optString12,
1907
+ useElementSize as useElementSize4,
1908
+ WidgetFrame as WidgetFrame11
1909
+ } from "@devicechain/widget-sdk";
1910
+ import { buildLineOption, EChart as EChart2, useChartTheme as useChartTheme2 } from "@devicechain/widget-sdk/echarts";
1911
+ import { jsx as jsx13 } from "react/jsx-runtime";
1909
1912
  function TimeSeriesChart({ widget, data }) {
1910
- const theme = useChartTheme();
1911
- const [sizeRef, size] = useElementSize();
1913
+ const theme = useChartTheme2();
1914
+ const [sizeRef, size] = useElementSize4();
1912
1915
  const measurements = widget.datasource?.measurements;
1913
- const option = useMemo4(
1916
+ const option = useMemo3(
1914
1917
  () => buildLineOption(data.samples, theme, { measurements }, size),
1915
1918
  [data.samples, theme, measurements, size.width, size.height]
1916
1919
  );
1917
- return /* @__PURE__ */ jsx16(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsx16("div", { ref: sizeRef, style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx16(EChart, { option }) }) });
1920
+ return /* @__PURE__ */ jsx13(WidgetFrame11, { title: optString12(widget.options, "title"), children: /* @__PURE__ */ jsx13("div", { ref: sizeRef, style: { width: "100%", height: "100%" }, children: /* @__PURE__ */ jsx13(EChart2, { option }) }) });
1918
1921
  }
1922
+ var timeSeriesChartWidget = defineWidget11({
1923
+ descriptor: builtinWidgetDescriptors["timeseries-chart"],
1924
+ implementation: { kind: "trusted-react", component: TimeSeriesChart }
1925
+ });
1919
1926
 
1920
- // src/registry.ts
1921
- var WIDGET_CHANNEL = {
1922
- "timeseries-chart": "measurement",
1923
- "latest-card": "measurement",
1924
- gauge: "measurement",
1925
- table: "measurement",
1926
- label: "measurement",
1927
- image: "measurement",
1928
- "alarm-table": "alarm",
1929
- "alarm-count": "alarm",
1930
- "command-button": "control",
1931
- "entity-selector": "selection",
1932
- map: "location"
1933
- };
1934
- var WIDGET_REGISTRY = {
1935
- "timeseries-chart": TimeSeriesChart,
1936
- "latest-card": LatestCard,
1937
- gauge: Gauge,
1938
- table: Table,
1939
- label: Label,
1940
- image: Image
1941
- };
1942
- var ALARM_WIDGET_REGISTRY = {
1943
- "alarm-table": AlarmTable,
1944
- "alarm-count": AlarmCount
1945
- };
1946
- var CONTROL_WIDGET_REGISTRY = {
1947
- "command-button": CommandButton
1948
- };
1949
- var SELECTION_WIDGET_REGISTRY = {
1950
- "entity-selector": EntitySelector
1927
+ // src/builtin-registry.ts
1928
+ var builtinWidgetDefinitions = [
1929
+ timeSeriesChartWidget,
1930
+ latestCardWidget,
1931
+ gaugeWidget,
1932
+ tableWidget,
1933
+ labelWidget,
1934
+ imageWidget,
1935
+ alarmTableWidget,
1936
+ alarmCountWidget,
1937
+ commandButtonWidget,
1938
+ entitySelectorWidget,
1939
+ mapWidget
1940
+ ];
1941
+ function createBuiltinWidgetRegistry() {
1942
+ return createWidgetRegistry(builtinWidgetDefinitions);
1943
+ }
1944
+
1945
+ // src/default-registry.ts
1946
+ var builtin;
1947
+ function defaultWidgetRegistry() {
1948
+ builtin ?? (builtin = createBuiltinWidgetRegistry());
1949
+ return builtin;
1950
+ }
1951
+
1952
+ // src/connected-widget.tsx
1953
+ import {
1954
+ optString as optString13,
1955
+ useAlarmStream,
1956
+ useCommandStream,
1957
+ useDatasourceAvailability,
1958
+ useLocationStream,
1959
+ useMeasurementStream,
1960
+ useWidgetCandidates,
1961
+ useWidgetSelect as useWidgetSelect3,
1962
+ WidgetFrame as WidgetFrame13
1963
+ } from "@devicechain/widget-sdk";
1964
+
1965
+ // src/widget-containment.tsx
1966
+ import { Component, createContext as createContext3, useCallback, useContext as useContext3, useState as useState5 } from "react";
1967
+ import { WidgetFrame as WidgetFrame12 } from "@devicechain/widget-sdk";
1968
+
1969
+ // src/widget-host.ts
1970
+ function describeError(error) {
1971
+ if (error instanceof Error) return error.message || error.name;
1972
+ return String(error);
1973
+ }
1974
+ function guardAsync(fn, report) {
1975
+ return (...args) => {
1976
+ let pending;
1977
+ try {
1978
+ pending = Promise.resolve(fn(...args));
1979
+ } catch (e) {
1980
+ report(describeError(e));
1981
+ return Promise.reject(e);
1982
+ }
1983
+ return pending.catch((e) => {
1984
+ report(describeError(e));
1985
+ throw e;
1986
+ });
1987
+ };
1988
+ }
1989
+ var ALARM_AUTHORITY = "alarm:write";
1990
+ var COMMAND_AUTHORITY = "command:write";
1991
+ function undeclared(capability, report) {
1992
+ return guardAsync(
1993
+ () => Promise.reject(new Error(`this widget did not declare the ${capability} interaction`)),
1994
+ report
1995
+ );
1996
+ }
1997
+ function narrowActions(actions, descriptor, report) {
1998
+ if (!actions) return void 0;
1999
+ const alarm = descriptor.interactions.alarmActions === true;
2000
+ const command = descriptor.interactions.commandSend === true;
2001
+ if (!alarm && !command) return void 0;
2002
+ const narrowed = {
2003
+ acknowledgeAlarm: alarm ? guardAsync((t) => actions.acknowledgeAlarm(t), report) : undeclared("alarmActions", report),
2004
+ clearAlarm: alarm ? guardAsync((t) => actions.clearAlarm(t), report) : undeclared("alarmActions", report),
2005
+ can: (authority) => {
2006
+ if (authority === ALARM_AUTHORITY && !alarm) return false;
2007
+ if (authority === COMMAND_AUTHORITY && !command) return false;
2008
+ return actions.can(authority);
2009
+ }
2010
+ };
2011
+ if (command && typeof actions.sendCommand === "function") {
2012
+ narrowed.sendCommand = guardAsync((d, n, p) => actions.sendCommand(d, n, p), report);
2013
+ }
2014
+ return narrowed;
2015
+ }
2016
+ function narrowSelect(select, descriptor) {
2017
+ return descriptor.interactions.selection === true ? select : void 0;
2018
+ }
2019
+ function narrowCandidates(candidates, descriptor, report) {
2020
+ if (!candidates || descriptor.interactions.candidates !== true) return void 0;
2021
+ return guardAsync((slot) => candidates(slot), report);
2022
+ }
2023
+
2024
+ // src/widget-containment.tsx
2025
+ import { jsx as jsx14, jsxs as jsxs8 } from "react/jsx-runtime";
2026
+ var MESSAGE_STYLE = {
2027
+ display: "flex",
2028
+ flexDirection: "column",
2029
+ alignItems: "center",
2030
+ justifyContent: "center",
2031
+ gap: 4,
2032
+ width: "100%",
2033
+ height: "100%",
2034
+ padding: 12,
2035
+ boxSizing: "border-box",
2036
+ textAlign: "center",
2037
+ color: "hsl(var(--muted-foreground))"
1951
2038
  };
1952
- var LOCATION_WIDGET_REGISTRY = {
1953
- map: MapWidget
2039
+ var BUTTON_STYLE = {
2040
+ marginTop: 4,
2041
+ fontSize: 11,
2042
+ padding: "2px 8px",
2043
+ cursor: "pointer"
1954
2044
  };
1955
- var WIDGET_BINDS_DATASOURCE = {
1956
- "timeseries-chart": true,
1957
- "latest-card": true,
1958
- gauge: true,
1959
- table: true,
1960
- label: false,
1961
- image: false,
1962
- "alarm-table": true,
1963
- "alarm-count": true,
1964
- "command-button": true,
1965
- "entity-selector": false,
1966
- // A map binds a datasource, and needs it: the selector names both the devices whose
1967
- // positions to plot and (in its own field) the location series to read.
1968
- map: true
2045
+ var ReportContext = createContext3(() => {
2046
+ });
2047
+ function useReportRejection() {
2048
+ return useContext3(ReportContext);
2049
+ }
2050
+ var WidgetErrorBoundary = class extends Component {
2051
+ constructor() {
2052
+ super(...arguments);
2053
+ this.state = { error: null, resetKey: this.props.resetKey };
2054
+ this.reset = () => {
2055
+ this.props.onReset();
2056
+ this.setState({ error: null });
2057
+ };
2058
+ }
2059
+ static getDerivedStateFromError(error) {
2060
+ return { error: { message: describeError(error) } };
2061
+ }
2062
+ static getDerivedStateFromProps(props, state) {
2063
+ if (props.resetKey === state.resetKey) return null;
2064
+ return { error: null, resetKey: props.resetKey };
2065
+ }
2066
+ render() {
2067
+ const { error } = this.state;
2068
+ if (!error) return this.props.children;
2069
+ return /* @__PURE__ */ jsx14(WidgetFrame12, { title: this.props.title, children: /* @__PURE__ */ jsxs8("div", { style: MESSAGE_STYLE, role: "alert", children: [
2070
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u26A0" }),
2071
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 13, fontWeight: 600 }, children: "This widget failed to render" }),
2072
+ /* @__PURE__ */ jsx14("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: error.message }),
2073
+ /* @__PURE__ */ jsx14("button", { type: "button", style: BUTTON_STYLE, onClick: this.reset, children: "Reset widget" })
2074
+ ] }) });
2075
+ }
1969
2076
  };
2077
+ function WidgetContainment({ title, resetKey, children }) {
2078
+ const [messages, setMessages] = useState5([]);
2079
+ const report = useCallback((message) => {
2080
+ setMessages((prev) => prev.includes(message) ? prev : [...prev, message].slice(-3));
2081
+ }, []);
2082
+ const clear = useCallback(() => setMessages([]), []);
2083
+ return /* @__PURE__ */ jsxs8("div", { style: { position: "relative", width: "100%", height: "100%" }, children: [
2084
+ /* @__PURE__ */ jsx14(ReportContext.Provider, { value: report, children: /* @__PURE__ */ jsx14(WidgetErrorBoundary, { title, resetKey, onReset: clear, children }) }),
2085
+ messages.length > 0 && /* @__PURE__ */ jsxs8(
2086
+ "div",
2087
+ {
2088
+ role: "status",
2089
+ style: {
2090
+ position: "absolute",
2091
+ left: 4,
2092
+ right: 4,
2093
+ bottom: 4,
2094
+ fontSize: 11,
2095
+ padding: "2px 6px",
2096
+ borderRadius: 4,
2097
+ background: "hsl(var(--destructive, 0 70% 45%))",
2098
+ color: "hsl(var(--destructive-foreground, 0 0% 100%))",
2099
+ display: "flex",
2100
+ gap: 6,
2101
+ alignItems: "center"
2102
+ },
2103
+ children: [
2104
+ /* @__PURE__ */ jsx14("span", { style: { flex: 1, wordBreak: "break-word" }, children: messages.join(" \xB7 ") }),
2105
+ /* @__PURE__ */ jsx14("button", { type: "button", "aria-label": "Dismiss", onClick: clear, style: { cursor: "pointer" }, children: "\xD7" })
2106
+ ]
2107
+ }
2108
+ )
2109
+ ] });
2110
+ }
1970
2111
 
1971
2112
  // src/connected-widget.tsx
1972
- import { jsx as jsx17, jsxs as jsxs9 } from "react/jsx-runtime";
1973
- var DEFAULT_ALARM_ROWS = 50;
1974
- var DEFAULT_COMMAND_ROWS = 20;
1975
- function ConnectedWidget({ widget, hub, actions, initialSamples }) {
1976
- const availability = useDatasourceAvailability(hub, widget.datasource);
2113
+ import { jsx as jsx15, jsxs as jsxs9 } from "react/jsx-runtime";
2114
+ function ConnectedWidget(props) {
2115
+ const { widget } = props;
2116
+ const registry = props.registry ?? defaultWidgetRegistry();
2117
+ const resetKey = JSON.stringify([widget.type, widget.options ?? null, widget.datasource ?? null]);
2118
+ return /* @__PURE__ */ jsx15(WidgetContainment, { title: optString13(widget.options, "title"), resetKey, children: /* @__PURE__ */ jsx15(ConnectedWidgetBody, { ...props, registry }) });
2119
+ }
2120
+ function ConnectedWidgetBody({ widget, hub, registry, actions, initialSamples }) {
2121
+ const unresolved = widget.type === UNKNOWN_WIDGET_TYPE;
2122
+ const descriptor = unresolved ? void 0 : registry.descriptor(widget.type);
2123
+ const implementation = unresolved ? void 0 : registry.implementation(widget.type);
2124
+ const readsDatasource = descriptor !== void 0 && descriptor.datasource.mode !== "none";
2125
+ const availability = useDatasourceAvailability(hub, readsDatasource ? widget.datasource : void 0);
1977
2126
  if (availability === "unavailable") {
1978
- return /* @__PURE__ */ jsx17(WidgetUnavailableFrame, { widget });
2127
+ return /* @__PURE__ */ jsx15(WidgetUnavailableFrame, { widget });
1979
2128
  }
1980
- const channel = WIDGET_CHANNEL[widget.type];
1981
- if (channel === "alarm") {
1982
- return /* @__PURE__ */ jsx17(AlarmConnectedWidget, { widget, hub, actions });
2129
+ if (widget.type === UNKNOWN_WIDGET_TYPE) {
2130
+ return /* @__PURE__ */ jsx15(UnresolvedWidgetFrame, { storedType: widget.raw.type, reason: widget.reason });
1983
2131
  }
1984
- if (channel === "control") {
1985
- return /* @__PURE__ */ jsx17(ControlConnectedWidget, { widget, hub, actions });
1986
- }
1987
- if (channel === "selection") {
1988
- return /* @__PURE__ */ jsx17(SelectionConnectedWidget, { widget });
2132
+ if (!descriptor || !implementation) {
2133
+ return /* @__PURE__ */ jsx15(
2134
+ UnresolvedWidgetFrame,
2135
+ {
2136
+ storedType: widget.type,
2137
+ reason: { code: "unregistered", message: `"${widget.type}" is not registered in this host's widget registry` }
2138
+ }
2139
+ );
1989
2140
  }
1990
- if (channel === "location") {
1991
- return /* @__PURE__ */ jsx17(LocationConnectedWidget, { widget, hub });
2141
+ return /* @__PURE__ */ jsx15(HostScope, { descriptor, actions, children: (hostActions) => /* @__PURE__ */ jsx15(
2142
+ ChannelBody,
2143
+ {
2144
+ widget,
2145
+ hub,
2146
+ descriptor,
2147
+ Component: implementation.component,
2148
+ actions: hostActions,
2149
+ initialSamples
2150
+ }
2151
+ ) });
2152
+ }
2153
+ function HostScope({
2154
+ descriptor,
2155
+ actions,
2156
+ children
2157
+ }) {
2158
+ const report = useReportRejection();
2159
+ const select = useWidgetSelect3();
2160
+ const candidates = useWidgetCandidates();
2161
+ const narrowed = useMemo4(() => narrowActions(actions, descriptor, report), [actions, descriptor, report]);
2162
+ const narrowedSelect = useMemo4(() => narrowSelect(select, descriptor), [select, descriptor]);
2163
+ const narrowedCandidates = useMemo4(
2164
+ () => narrowCandidates(candidates, descriptor, report),
2165
+ [candidates, descriptor, report]
2166
+ );
2167
+ return /* @__PURE__ */ jsx15(WidgetSelectProvider, { select: narrowedSelect, children: /* @__PURE__ */ jsx15(WidgetCandidatesProvider, { candidates: narrowedCandidates, children: children(narrowed) }) });
2168
+ }
2169
+ function ChannelBody(props) {
2170
+ switch (props.descriptor.channel) {
2171
+ case "alarm":
2172
+ return /* @__PURE__ */ jsx15(AlarmConnectedWidget, { ...props });
2173
+ case "control":
2174
+ return /* @__PURE__ */ jsx15(ControlConnectedWidget, { ...props });
2175
+ case "location":
2176
+ return /* @__PURE__ */ jsx15(LocationConnectedWidget, { ...props });
2177
+ case "selection":
2178
+ case "none":
2179
+ return /* @__PURE__ */ jsx15(StaticConnectedWidget, { ...props });
2180
+ case "measurement":
2181
+ return /* @__PURE__ */ jsx15(MeasurementConnectedWidget, { ...props });
1992
2182
  }
1993
- return /* @__PURE__ */ jsx17(MeasurementConnectedWidget, { widget, hub, initialSamples });
1994
2183
  }
1995
- function SelectionConnectedWidget({ widget }) {
1996
- const Component = SELECTION_WIDGET_REGISTRY[widget.type];
1997
- if (!Component) return null;
1998
- return /* @__PURE__ */ jsx17(Component, { widget, data: null });
2184
+ function StaticConnectedWidget({ widget, Component: Component2, actions }) {
2185
+ return /* @__PURE__ */ jsx15(Component2, { widget, data: null, actions });
1999
2186
  }
2000
- function MeasurementConnectedWidget({ widget, hub, initialSamples }) {
2187
+ function MeasurementConnectedWidget({ widget, hub, descriptor, Component: Component2, actions, initialSamples }) {
2001
2188
  const data = useMeasurementStream(hub, widget.datasource, {
2002
- window: optNumber(widget.options, "window"),
2189
+ window: measurementWindow(descriptor, widget),
2003
2190
  initialSamples
2004
2191
  });
2005
- if (data.error) return /* @__PURE__ */ jsx17(WidgetErrorFrame, { widget, error: data.error });
2006
- const Component = WIDGET_REGISTRY[widget.type];
2007
- if (!Component) return null;
2008
- return /* @__PURE__ */ jsx17(Component, { widget, data });
2192
+ if (data.error) return /* @__PURE__ */ jsx15(WidgetErrorFrame, { widget, error: data.error });
2193
+ return /* @__PURE__ */ jsx15(Component2, { widget, data, actions });
2009
2194
  }
2010
- function AlarmConnectedWidget({
2011
- widget,
2012
- hub,
2013
- actions
2014
- }) {
2015
- const data = useAlarmStream(hub, alarmSubscription(widget));
2195
+ function AlarmConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2196
+ const data = useAlarmStream(hub, alarmSubscription(descriptor, widget));
2016
2197
  if (data.error && data.alarms.length === 0 && data.total === 0) {
2017
- return /* @__PURE__ */ jsx17(WidgetErrorFrame, { widget, error: data.error });
2198
+ return /* @__PURE__ */ jsx15(WidgetErrorFrame, { widget, error: data.error });
2018
2199
  }
2019
- const Component = ALARM_WIDGET_REGISTRY[widget.type];
2020
- if (!Component) return null;
2021
- return /* @__PURE__ */ jsx17(Component, { widget, data, actions });
2200
+ return /* @__PURE__ */ jsx15(Component2, { widget, data, actions });
2022
2201
  }
2023
- function ControlConnectedWidget({
2024
- widget,
2025
- hub,
2026
- actions
2027
- }) {
2028
- const data = useCommandStream(hub, commandSubscription(widget));
2029
- const Component = CONTROL_WIDGET_REGISTRY[widget.type];
2030
- if (!Component) return null;
2031
- return /* @__PURE__ */ jsx17(Component, { widget, data, actions });
2032
- }
2033
- function LocationConnectedWidget({
2034
- widget,
2035
- hub
2036
- }) {
2037
- const data = useLocationStream(hub, locationSubscription(widget));
2202
+ function ControlConnectedWidget({ widget, hub, descriptor, Component: Component2, actions }) {
2203
+ const data = useCommandStream(hub, commandSubscription(descriptor, widget));
2204
+ return /* @__PURE__ */ jsx15(Component2, { widget, data, actions });
2205
+ }
2206
+ function LocationConnectedWidget({ widget, hub, Component: Component2, actions }) {
2207
+ const data = useLocationStream(hub, { datasource: widget.datasource });
2038
2208
  if (data.error && data.locations.length === 0) {
2039
- return /* @__PURE__ */ jsx17(WidgetErrorFrame, { widget, error: data.error });
2209
+ return /* @__PURE__ */ jsx15(WidgetErrorFrame, { widget, error: data.error });
2040
2210
  }
2041
- const Component = LOCATION_WIDGET_REGISTRY[widget.type];
2042
- if (!Component) return null;
2043
- return /* @__PURE__ */ jsx17(Component, { widget, data });
2044
- }
2045
- function locationSubscription(widget) {
2046
- return { datasource: widget.datasource };
2047
- }
2048
- function commandSubscription(widget) {
2049
- return {
2050
- datasource: widget.datasource,
2051
- pageSize: Math.max(1, optNumber(widget.options, "maxRows") ?? DEFAULT_COMMAND_ROWS)
2052
- };
2053
- }
2054
- function alarmSubscription(widget) {
2055
- const pageSize = widget.type === "alarm-count" ? DEFAULT_ALARM_ROWS : Math.max(1, optNumber(widget.options, "maxRows") ?? DEFAULT_ALARM_ROWS);
2056
- return {
2057
- datasource: widget.datasource,
2058
- state: optString(widget.options, "state"),
2059
- severity: optString(widget.options, "severity"),
2060
- acknowledged: parseAcknowledged(optString(widget.options, "acknowledged")),
2061
- pageSize
2062
- };
2211
+ return /* @__PURE__ */ jsx15(Component2, { widget, data, actions });
2063
2212
  }
2064
- function parseAcknowledged(value) {
2065
- if (value === "true") return true;
2066
- if (value === "false") return false;
2067
- return void 0;
2213
+ function UnresolvedWidgetFrame({
2214
+ storedType: stored,
2215
+ reason
2216
+ }) {
2217
+ const storedType = typeof stored === "string" ? stored : "";
2218
+ const heading = (reason?.code ?? "unregistered") === "unregistered" ? "This widget needs a newer viewer" : "This widget cannot be shown here";
2219
+ return /* @__PURE__ */ jsx15(WidgetFrame13, { children: /* @__PURE__ */ jsxs9(
2220
+ "div",
2221
+ {
2222
+ style: {
2223
+ display: "flex",
2224
+ flexDirection: "column",
2225
+ alignItems: "center",
2226
+ justifyContent: "center",
2227
+ gap: 4,
2228
+ width: "100%",
2229
+ height: "100%",
2230
+ padding: 12,
2231
+ boxSizing: "border-box",
2232
+ textAlign: "center",
2233
+ color: "hsl(var(--muted-foreground))"
2234
+ },
2235
+ children: [
2236
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 13, fontWeight: 600 }, children: heading }),
2237
+ storedType && !reason?.message.includes(storedType) && /* @__PURE__ */ jsxs9("div", { style: { fontSize: 11, opacity: 0.8 }, children: [
2238
+ "Widget type: ",
2239
+ storedType
2240
+ ] }),
2241
+ reason && /* @__PURE__ */ jsx15("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: reason.message })
2242
+ ]
2243
+ }
2244
+ ) });
2068
2245
  }
2069
2246
  function WidgetUnavailableFrame({ widget }) {
2070
- return /* @__PURE__ */ jsx17(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsxs9(
2247
+ return /* @__PURE__ */ jsx15(WidgetFrame13, { title: optString13(widget.options, "title"), children: /* @__PURE__ */ jsxs9(
2071
2248
  "div",
2072
2249
  {
2073
2250
  style: {
@@ -2084,15 +2261,15 @@ function WidgetUnavailableFrame({ widget }) {
2084
2261
  color: "hsl(var(--muted-foreground))"
2085
2262
  },
2086
2263
  children: [
2087
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2088
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Device unavailable" }),
2089
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 11, opacity: 0.8 }, children: "The bound device no longer exists." })
2264
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 20, lineHeight: 1 }, "aria-hidden": "true", children: "\u2298" }),
2265
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Device unavailable" }),
2266
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 11, opacity: 0.8 }, children: "The bound device no longer exists." })
2090
2267
  ]
2091
2268
  }
2092
2269
  ) });
2093
2270
  }
2094
2271
  function WidgetErrorFrame({ widget, error }) {
2095
- return /* @__PURE__ */ jsx17(WidgetFrame, { title: optString(widget.options, "title"), children: /* @__PURE__ */ jsxs9(
2272
+ return /* @__PURE__ */ jsx15(WidgetFrame13, { title: optString13(widget.options, "title"), children: /* @__PURE__ */ jsxs9(
2096
2273
  "div",
2097
2274
  {
2098
2275
  style: {
@@ -2109,33 +2286,45 @@ function WidgetErrorFrame({ widget, error }) {
2109
2286
  color: "hsl(var(--muted-foreground))"
2110
2287
  },
2111
2288
  children: [
2112
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 20, lineHeight: 1 }, children: "\u26A0" }),
2113
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Data unavailable" }),
2114
- /* @__PURE__ */ jsx17("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: String(error) })
2289
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 20, lineHeight: 1 }, children: "\u26A0" }),
2290
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 13, fontWeight: 600 }, children: "Data unavailable" }),
2291
+ /* @__PURE__ */ jsx15("div", { style: { fontSize: 11, opacity: 0.8, wordBreak: "break-word" }, children: String(error) })
2115
2292
  ]
2116
2293
  }
2117
2294
  ) });
2118
2295
  }
2119
2296
 
2120
2297
  // src/dashboard-renderer.tsx
2121
- import { jsx as jsx18 } from "react/jsx-runtime";
2298
+ import {
2299
+ WidgetCandidatesProvider as WidgetCandidatesProvider2,
2300
+ WidgetSelectProvider as WidgetSelectProvider2,
2301
+ WidgetSubjectProvider,
2302
+ WidgetThemeRootProvider,
2303
+ WidgetTimeZoneProvider
2304
+ } from "@devicechain/widget-sdk";
2305
+ import { jsx as jsx16 } from "react/jsx-runtime";
2122
2306
  function DashboardRenderer({
2123
2307
  definition,
2124
2308
  hub,
2309
+ registry: registryProp,
2125
2310
  actions,
2126
2311
  seedHistory = true,
2127
2312
  bindings,
2128
2313
  sizing,
2129
2314
  select,
2130
- candidates
2315
+ candidates,
2316
+ timeZone,
2317
+ themeRoot
2131
2318
  }) {
2132
2319
  const breakpoint = useActiveBreakpoint(definition.canvas.breakpoints);
2133
- const histories = useWidgetHistories(definition.widgets, seedHistory, bindings);
2320
+ const widgets = useMemo5(() => resolveWidgets(definition.widgets, bindings), [definition.widgets, bindings]);
2321
+ const registry = registryProp ?? defaultWidgetRegistry();
2322
+ const histories = useWidgetHistories(widgets, seedHistory, registry);
2134
2323
  const { grid, background: bg } = definition.canvas;
2135
2324
  const rowGap = typeof grid.gap === "number" ? grid.gap : grid.gap.row;
2136
2325
  const colGap = typeof grid.gap === "number" ? grid.gap : grid.gap.col;
2137
2326
  const effectiveSizing = sizing ?? definition.canvas.sizing;
2138
- return /* @__PURE__ */ jsx18("div", { style: sizingStyle(effectiveSizing, bg), children: /* @__PURE__ */ jsx18(WidgetSelectProvider, { select, children: /* @__PURE__ */ jsx18(WidgetCandidatesProvider, { candidates, children: /* @__PURE__ */ jsx18(
2327
+ return /* @__PURE__ */ jsx16("div", { style: sizingStyle(effectiveSizing, bg), children: /* @__PURE__ */ jsx16(WidgetThemeRootProvider, { root: themeRoot, children: /* @__PURE__ */ jsx16(WidgetTimeZoneProvider, { timeZone, children: /* @__PURE__ */ jsx16(WidgetSelectProvider2, { select, children: /* @__PURE__ */ jsx16(WidgetCandidatesProvider2, { candidates, children: /* @__PURE__ */ jsx16(
2139
2328
  "div",
2140
2329
  {
2141
2330
  style: {
@@ -2146,20 +2335,21 @@ function DashboardRenderer({
2146
2335
  rowGap,
2147
2336
  width: "100%"
2148
2337
  },
2149
- children: definition.widgets.map((widget) => {
2338
+ children: widgets.map((widget) => {
2150
2339
  const box = resolveWidgetBox(widget.layout, breakpoint);
2151
- return /* @__PURE__ */ jsx18("div", { style: gridItemStyle(box, grid.columns), children: /* @__PURE__ */ jsx18(WidgetSubjectProvider, { label: widgetSubjectLabel(widget, definition.slots, bindings), children: /* @__PURE__ */ jsx18(
2340
+ return /* @__PURE__ */ jsx16("div", { style: gridItemStyle(box, grid.columns), children: /* @__PURE__ */ jsx16(WidgetSubjectProvider, { label: widgetSubjectLabel(widget), children: /* @__PURE__ */ jsx16(
2152
2341
  ConnectedWidget,
2153
2342
  {
2154
2343
  widget,
2155
2344
  hub,
2345
+ registry,
2156
2346
  actions,
2157
2347
  initialSamples: histories[widget.id]
2158
2348
  }
2159
2349
  ) }) }, widget.id);
2160
2350
  })
2161
2351
  }
2162
- ) }) }) });
2352
+ ) }) }) }) }) });
2163
2353
  }
2164
2354
  function gridItemStyle(box, columns) {
2165
2355
  const col = Math.min(Math.max(0, box.col), columns - 1);
@@ -2186,401 +2376,157 @@ function sizingStyle(sizing, bg) {
2186
2376
  if ("width" in sizing) return { ...base, width: sizing.width, maxWidth: "100%", height: "100%" };
2187
2377
  return { ...base, width: "100%", height: sizing.height };
2188
2378
  }
2189
- function ownGet(map, key) {
2190
- return map && Object.prototype.hasOwnProperty.call(map, key) ? map[key] : void 0;
2379
+ function resolveWidgets(widgets, bindings) {
2380
+ return widgets.map((widget) => {
2381
+ if (widget.type === UNKNOWN_WIDGET_TYPE2 || widget.datasource?.kind !== "slot") return widget;
2382
+ return { ...widget, datasource: resolveWidgetDatasource(widget.datasource, bindings) };
2383
+ });
2191
2384
  }
2192
- function widgetSubjectLabel(widget, slots, bindings) {
2385
+ function widgetSubjectLabel(widget) {
2193
2386
  const ds = widget.datasource;
2194
2387
  if (!ds) return void 0;
2195
- let binding;
2196
- if (ds.kind === "device") {
2197
- binding = ds.deviceToken ? { kind: "device", deviceToken: ds.deviceToken } : void 0;
2198
- } else if (ds.kind === "anchor") {
2199
- binding = { kind: "anchor", anchor: ds.anchor };
2200
- } else if (ds.kind === "slot") {
2201
- const slot = ownGet(slots, ds.slot);
2202
- binding = slot?.scope ? ownGet(bindings, ds.slot) : ownGet(bindings, ds.slot) ?? slot?.defaultBinding;
2203
- }
2204
- if (binding?.kind === "device") return binding.deviceToken;
2205
- if (binding?.kind === "anchor") return binding.anchor.targetToken;
2388
+ if (ds.kind === "device") return ds.deviceToken || void 0;
2389
+ if (ds.kind === "anchor") return ds.anchor.targetToken;
2206
2390
  return void 0;
2207
2391
  }
2208
2392
  function useActiveBreakpoint(breakpoints) {
2209
- const [width, setWidth] = useState7(
2393
+ const [width, setWidth] = useState6(
2210
2394
  () => typeof window === "undefined" ? 0 : window.innerWidth
2211
2395
  );
2212
- useEffect7(() => {
2396
+ useEffect4(() => {
2213
2397
  const onResize = () => setWidth(window.innerWidth);
2214
2398
  window.addEventListener("resize", onResize);
2215
2399
  return () => window.removeEventListener("resize", onResize);
2216
2400
  }, []);
2217
2401
  return activeBreakpoint(breakpoints, width);
2218
2402
  }
2219
- function useWidgetHistories(widgets, enabled, bindings) {
2220
- const [histories, setHistories] = useState7({});
2221
- const bindingsKey = bindings ? JSON.stringify(bindings) : null;
2222
- useEffect7(() => {
2403
+ function seedKey(widget, registry) {
2404
+ if (widget.type === UNKNOWN_WIDGET_TYPE2 || registry.descriptor(widget.type)?.channel !== "measurement") {
2405
+ return void 0;
2406
+ }
2407
+ return JSON.stringify(widget.datasource ?? null);
2408
+ }
2409
+ function useWidgetHistories(widgets, enabled, registry) {
2410
+ const [seeds, setSeeds] = useState6({});
2411
+ const requested = useRef3({});
2412
+ const mounted = useRef3(true);
2413
+ useEffect4(() => {
2414
+ mounted.current = true;
2415
+ return () => {
2416
+ mounted.current = false;
2417
+ };
2418
+ }, []);
2419
+ const wanted = widgets.flatMap((w) => {
2420
+ const key = seedKey(w, registry);
2421
+ return key === void 0 ? [] : [[w.id, key]];
2422
+ });
2423
+ const wantedKey = JSON.stringify(wanted);
2424
+ useEffect4(() => {
2223
2425
  if (!enabled) {
2224
- setHistories({});
2426
+ requested.current = {};
2427
+ setSeeds({});
2225
2428
  return;
2226
2429
  }
2227
- let cancelled = false;
2228
2430
  const historyWindow = defaultHistoryWindow();
2229
- Promise.all(
2230
- widgets.map(
2231
- // Only measurement widgets consume a history seed; a non-measurement widget
2232
- // (alarm, command) reconciles from its own channel, so backfilling it would fire
2233
- // a wasted (and all-measurements) bucketedMeasurements query it ignores.
2234
- async (w) => [
2235
- w.id,
2236
- WIDGET_CHANNEL[w.type] === "measurement" ? await fetchWidgetHistory(w, historyWindow, bindings) : []
2237
- ]
2238
- )
2239
- ).then((entries) => {
2240
- if (!cancelled) setHistories(Object.fromEntries(entries));
2241
- });
2242
- return () => {
2243
- cancelled = true;
2244
- };
2245
- }, [widgets, enabled, bindingsKey]);
2246
- return histories;
2431
+ const byId = new Map(widgets.map((w) => [w.id, w]));
2432
+ const next = {};
2433
+ for (const [id, key] of wanted) {
2434
+ next[id] = key;
2435
+ if (requested.current[id] === key) continue;
2436
+ const widget = byId.get(id);
2437
+ if (!widget) continue;
2438
+ void fetchWidgetHistory(widget, historyWindow).then((samples) => {
2439
+ if (!mounted.current || requested.current[id] !== key) return;
2440
+ setSeeds((prev) => ({ ...prev, [id]: { key, samples } }));
2441
+ });
2442
+ }
2443
+ requested.current = next;
2444
+ }, [wantedKey, enabled]);
2445
+ return useMemo5(() => {
2446
+ const out = {};
2447
+ if (!enabled) return out;
2448
+ for (const w of widgets) {
2449
+ const seed = seeds[w.id];
2450
+ out[w.id] = seed && seed.key === seedKey(w, registry) ? seed.samples : void 0;
2451
+ }
2452
+ return out;
2453
+ }, [widgets, seeds, enabled, registry]);
2247
2454
  }
2248
2455
 
2456
+ // src/registry.ts
2457
+ var entries = Object.entries(builtinWidgetDescriptors);
2458
+ var WIDGET_CHANNEL = Object.freeze(
2459
+ Object.fromEntries(entries.map(([id, d]) => [id, d.channel]))
2460
+ );
2461
+ var WIDGET_BINDS_DATASOURCE = Object.freeze(
2462
+ Object.fromEntries(entries.map(([id, d]) => [id, d.datasource.mode !== "none"]))
2463
+ );
2464
+
2465
+ // src/index.ts
2466
+ import { pickSample as pickSample3, optBoolean as optBoolean5 } from "@devicechain/widget-sdk";
2467
+
2249
2468
  // src/options.ts
2250
- var TITLE = {
2251
- kind: "string",
2252
- doc: "Heading shown in the widget frame. Blank hides the frame heading."
2253
- };
2254
- var TITLE_OR_MEASUREMENT = {
2255
- kind: "string",
2256
- doc: "Heading shown in the widget frame. Defaults to the displayed measurement name."
2257
- };
2258
- var PRECISION = {
2259
- kind: "number",
2260
- integer: true,
2261
- min: 0,
2262
- max: 100,
2263
- doc: "Decimal places to round displayed values to. Unset shows the raw value."
2264
- };
2265
- var FLASH_ON_CHANGE = {
2266
- kind: "boolean",
2267
- doc: "Briefly tint a value green on a rise and red on a fall when it changes."
2268
- };
2269
- var WINDOW = {
2270
- kind: "number",
2271
- integer: true,
2272
- min: 1,
2273
- doc: "Samples retained across ALL of the widget's measurements before the oldest are dropped. Bounds memory; only the chart draws them. Default 300."
2274
- };
2275
- var UNIT = {
2276
- kind: "string",
2277
- doc: "Unit suffix shown beside the value."
2278
- };
2279
- var ALARM_STATE = {
2280
- kind: "enum",
2281
- values: ["ACTIVE", "CLEARED"],
2282
- doc: "Show only alarms in this state. Unset shows every state."
2283
- };
2284
- var ALARM_SEVERITY = {
2285
- kind: "enum",
2286
- values: ["CRITICAL", "MAJOR", "MINOR", "WARNING", "INDETERMINATE"],
2287
- doc: "Show only alarms at this severity. Unset shows every severity."
2288
- };
2289
- var ALARM_ACKNOWLEDGED = {
2290
- kind: "enum",
2291
- values: ["true", "false"],
2292
- doc: "Show only acknowledged ('true') or unacknowledged ('false') alarms. Unset shows both."
2293
- };
2294
- var SELECTION_TARGET = {
2295
- kind: "string",
2296
- doc: "Name of the slot this widget re-points when the viewer picks an entity."
2297
- };
2298
- var WIDGET_OPTIONS = {
2299
- "timeseries-chart": {
2300
- title: TITLE,
2301
- window: WINDOW
2302
- },
2303
- "latest-card": {
2304
- title: TITLE_OR_MEASUREMENT,
2305
- measurement: {
2306
- kind: "string",
2307
- doc: "Which measurement to display. Defaults to the first the datasource selects."
2308
- },
2309
- unit: UNIT,
2310
- precision: PRECISION,
2311
- flashOnChange: FLASH_ON_CHANGE,
2312
- window: WINDOW
2313
- },
2314
- gauge: {
2315
- title: TITLE_OR_MEASUREMENT,
2316
- measurement: {
2317
- kind: "string",
2318
- doc: "Which measurement to display. Defaults to the first the datasource selects."
2319
- },
2320
- min: {
2321
- kind: "number",
2322
- doc: "Low end of the gauge scale. Default 0."
2323
- },
2324
- max: {
2325
- kind: "number",
2326
- doc: "High end of the gauge scale. Default 100."
2327
- },
2328
- unit: UNIT,
2329
- flashOnChange: FLASH_ON_CHANGE,
2330
- window: WINDOW
2331
- },
2332
- table: {
2333
- title: TITLE,
2334
- precision: PRECISION,
2335
- flashOnChange: FLASH_ON_CHANGE,
2336
- window: WINDOW
2337
- },
2338
- // label and image carry no datasource: they sit on the measurement channel only
2339
- // because that is ConnectedWidget's default branch, and read no stream data at all.
2340
- label: {
2341
- title: TITLE,
2342
- text: {
2343
- kind: "string",
2344
- required: true,
2345
- doc: "The text to show. Rendered as plain text \u2014 markup is never interpreted."
2346
- },
2347
- align: {
2348
- kind: "enum",
2349
- values: ["left", "center", "right"],
2350
- doc: "Horizontal alignment of the text. Default center."
2351
- },
2352
- fontSize: {
2353
- kind: "number",
2354
- min: 1,
2355
- doc: "Text size in pixels. Default 16."
2356
- }
2357
- },
2358
- image: {
2359
- title: TITLE,
2360
- url: {
2361
- kind: "string",
2362
- required: true,
2363
- doc: "Source URL of the image."
2364
- },
2365
- alt: {
2366
- kind: "string",
2367
- doc: "Alternative text describing the image for assistive technology."
2368
- },
2369
- fit: {
2370
- kind: "enum",
2371
- values: ["contain", "cover", "fill"],
2372
- doc: "How the image fills its box. Default contain."
2373
- }
2374
- },
2375
- "alarm-table": {
2376
- title: TITLE,
2377
- state: ALARM_STATE,
2378
- severity: ALARM_SEVERITY,
2379
- acknowledged: ALARM_ACKNOWLEDGED,
2380
- maxRows: {
2381
- kind: "number",
2382
- integer: true,
2383
- min: 1,
2384
- doc: "Alarms loaded into the table. Default 50."
2385
- },
2386
- precision: PRECISION,
2387
- flashOnChange: FLASH_ON_CHANGE,
2388
- selectionTarget: SELECTION_TARGET
2389
- },
2390
- // No maxRows: alarmSubscription() forces the default page size for a count, because the
2391
- // number it shows is the SERVER total and the page is read only to sample the worst
2392
- // loaded severity. An authored maxRows here would be read by nothing.
2393
- "alarm-count": {
2394
- title: TITLE,
2395
- state: ALARM_STATE,
2396
- severity: ALARM_SEVERITY,
2397
- acknowledged: ALARM_ACKNOWLEDGED
2398
- },
2399
- "command-button": {
2400
- title: TITLE,
2401
- commandName: {
2402
- kind: "string",
2403
- required: true,
2404
- doc: "Command key issued to the target device. Without it the widget cannot send."
2405
- },
2406
- commandLabel: {
2407
- kind: "string",
2408
- doc: "Text on the send button. Defaults to the command key."
2409
- },
2410
- parameterSchema: {
2411
- kind: "string",
2412
- format: "json",
2413
- doc: "The command's parameter descriptors, baked in as JSON at author time. Drives the typed form."
2414
- },
2415
- maxRows: {
2416
- kind: "number",
2417
- integer: true,
2418
- min: 1,
2419
- doc: "Recent commands shown in the history. Default 20."
2420
- }
2421
- },
2422
- "entity-selector": {
2423
- title: TITLE,
2424
- selectionTarget: { ...SELECTION_TARGET, required: true }
2425
- },
2426
- // 🔴 tileUrl is NOT `required`, and that is the decision rather than an oversight.
2427
- // `required` means "absent leaves the widget with nothing to show", and a map without
2428
- // a basemap still shows every position it was asked to — on a plain panel, saying why
2429
- // there is no map behind them. Marking it required would report a correctly-rendering
2430
- // widget as broken on every board that names no provider of its own — which is most
2431
- // of them, because the usual and recommended answer is to inherit the tenant's
2432
- // basemap rather than to configure tiles per board.
2433
- map: {
2434
- title: TITLE,
2435
- tileUrl: {
2436
- kind: "string",
2437
- 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."
2438
- },
2439
- attribution: {
2440
- kind: "string",
2441
- doc: "Credit line the tile source requires, shown on the map. Set whatever the provider you chose asks for."
2442
- }
2443
- }
2444
- };
2445
- var CROSS_FIELD_RULES = {
2446
- gauge: [
2447
- (options) => {
2448
- const min = options.min;
2449
- const max = options.max;
2450
- if (typeof min !== "number" || typeof max !== "number") return void 0;
2451
- if (!Number.isFinite(min) || !Number.isFinite(max)) return void 0;
2452
- if (max > min) return void 0;
2453
- return {
2454
- key: "max",
2455
- code: "invariant",
2456
- message: `max (${max}) must be greater than min (${min})`
2457
- };
2458
- }
2459
- ]
2460
- };
2469
+ import {
2470
+ clampNumberOption,
2471
+ enumValues,
2472
+ numberSpec,
2473
+ validateOptions
2474
+ } from "@devicechain/dashboards";
2475
+ var WIDGET_OPTIONS = Object.freeze(
2476
+ Object.fromEntries(Object.entries(builtinWidgetDescriptors).map(([id, d]) => [id, d.options]))
2477
+ );
2478
+ function constraintsFor(widgetType) {
2479
+ return builtinWidgetCatalog.get(widgetType)?.constraints ?? [];
2480
+ }
2461
2481
  function validateWidgetOptions(widgetType, options) {
2462
2482
  const specs = WIDGET_OPTIONS[widgetType];
2463
- const issues = [];
2464
- const bag = options ?? {};
2465
- for (const [key, spec] of Object.entries(specs)) {
2466
- const value = bag[key];
2467
- if (value === void 0 || spec.required && value === "") {
2468
- if (spec.required) {
2469
- issues.push({ widgetType, key, code: "missing", message: `${key} is required but not set` });
2470
- }
2471
- continue;
2472
- }
2473
- issues.push(...checkValue(widgetType, key, spec, value));
2474
- }
2475
- for (const key of Object.keys(bag)) {
2476
- if (Object.prototype.hasOwnProperty.call(specs, key)) continue;
2477
- issues.push({
2478
- widgetType,
2479
- key,
2480
- code: "unknown",
2481
- message: `${key} is not read by a ${widgetType} widget`
2482
- });
2483
- }
2484
- for (const rule of CROSS_FIELD_RULES[widgetType] ?? []) {
2485
- const issue = rule(bag);
2486
- if (issue) issues.push({ widgetType, ...issue });
2487
- }
2488
- return issues;
2489
- }
2490
- function checkValue(widgetType, key, spec, value) {
2491
- const issue = (code, message) => [{ widgetType, key, code, message }];
2492
- switch (spec.kind) {
2493
- case "string":
2494
- if (typeof value !== "string") {
2495
- return issue("type", `${key} must be a string, got ${describe(value)}`);
2496
- }
2497
- if (spec.format === "json" && !parsesAsJson(value)) {
2498
- return issue("json", `${key} must be valid JSON`);
2499
- }
2500
- return [];
2501
- case "number":
2502
- if (typeof value !== "number" || !Number.isFinite(value)) {
2503
- return issue("type", `${key} must be a finite number, got ${describe(value)}`);
2504
- }
2505
- if (spec.integer && !Number.isInteger(value)) {
2506
- return issue("range", `${key} must be a whole number, got ${value}`);
2507
- }
2508
- if (spec.min !== void 0 && value < spec.min) {
2509
- return issue("range", `${key} must be at least ${spec.min}, got ${value}`);
2510
- }
2511
- if (spec.max !== void 0 && value > spec.max) {
2512
- return issue("range", `${key} must be at most ${spec.max}, got ${value}`);
2513
- }
2514
- return [];
2515
- case "boolean":
2516
- if (typeof value !== "boolean") {
2517
- return issue("type", `${key} must be a boolean, got ${describe(value)}`);
2518
- }
2519
- return [];
2520
- case "enum":
2521
- if (typeof value !== "string") {
2522
- return issue("type", `${key} must be a string, got ${describe(value)}`);
2523
- }
2524
- if (!spec.values.includes(value)) {
2525
- return issue("enum", `${key} must be one of ${spec.values.join(", ")}, got ${JSON.stringify(value)}`);
2526
- }
2527
- return [];
2528
- }
2529
- }
2530
- function parsesAsJson(value) {
2531
- try {
2532
- JSON.parse(value);
2533
- return true;
2534
- } catch {
2535
- return false;
2536
- }
2537
- }
2538
- function describe(value) {
2539
- if (value === null) return "null";
2540
- if (typeof value === "string") return `string ${JSON.stringify(value)}`;
2541
- if (Array.isArray(value)) return "an array";
2542
- return `${typeof value} ${JSON.stringify(value)}`;
2483
+ return validateOptions(specs, options, { constraints: constraintsFor(widgetType), label: widgetType }).map(
2484
+ ({ key, code, message }) => ({ widgetType, key, code, message })
2485
+ );
2543
2486
  }
2544
2487
  function numberOptionSpec(type, key) {
2545
- const specs = WIDGET_OPTIONS[type];
2546
- if (!Object.prototype.hasOwnProperty.call(specs, key)) return void 0;
2547
- const spec = specs[key];
2548
- return spec.kind === "number" ? spec : void 0;
2549
- }
2550
- function clampNumberOption(spec, value) {
2551
- if (!Number.isFinite(value)) return void 0;
2552
- if (!spec) return value;
2553
- let next = spec.integer ? Math.trunc(value) : value;
2554
- if (spec.min !== void 0) next = Math.max(spec.min, next);
2555
- if (spec.max !== void 0) next = Math.min(spec.max, next);
2556
- return next;
2488
+ return numberSpec(WIDGET_OPTIONS[type], key);
2557
2489
  }
2558
2490
  function enumOptionValues(type, key) {
2559
- const specs = WIDGET_OPTIONS[type];
2560
- if (!Object.prototype.hasOwnProperty.call(specs, key)) return void 0;
2561
- const spec = specs[key];
2562
- return spec.kind === "enum" ? spec.values : void 0;
2491
+ return enumValues(WIDGET_OPTIONS[type], key);
2563
2492
  }
2564
2493
 
2565
2494
  // src/definition-options.ts
2566
- function validateDefinitionOptions(definition) {
2567
- return definition.widgets.flatMap(
2568
- (widget) => validateWidgetOptions(widget.type, widget.options).map((issue) => ({ ...issue, widgetId: widget.id }))
2569
- );
2495
+ import {
2496
+ UNKNOWN_WIDGET_TYPE as UNKNOWN_WIDGET_TYPE3,
2497
+ validateOptions as validateOptions2
2498
+ } from "@devicechain/dashboards";
2499
+ function validateDefinitionOptions(definition, options = {}) {
2500
+ return definition.widgets.flatMap((widget) => {
2501
+ if (widget.type === UNKNOWN_WIDGET_TYPE3) return [];
2502
+ const type = widget.type;
2503
+ const descriptor = descriptorFor(type, options.catalog);
2504
+ return validateOptions2(descriptor.options, widget.options, {
2505
+ constraints: descriptor.constraints,
2506
+ label: type
2507
+ }).map(({ key, code, message }) => ({ widgetType: type, key, code, message, widgetId: widget.id }));
2508
+ });
2509
+ }
2510
+ function descriptorFor(type, catalog) {
2511
+ const descriptor = catalog?.get(type) ?? builtinWidgetCatalog.get(type);
2512
+ if (!descriptor) {
2513
+ throw new Error(`no widget descriptor for "${type}"; validate with the catalog the definition was parsed with`);
2514
+ }
2515
+ return descriptor;
2570
2516
  }
2571
- function stripUnknownOptions(definition) {
2517
+ function stripUnknownOptions(definition, options = {}) {
2572
2518
  let changed = false;
2573
2519
  const widgets = definition.widgets.map((widget) => {
2574
- const stripped = stripUnknownWidgetOptions(widget);
2520
+ const stripped = stripUnknownWidgetOptions(widget, options.catalog);
2575
2521
  if (stripped !== widget) changed = true;
2576
2522
  return stripped;
2577
2523
  });
2578
2524
  return changed ? { ...definition, widgets } : definition;
2579
2525
  }
2580
- function stripUnknownWidgetOptions(widget) {
2526
+ function stripUnknownWidgetOptions(widget, catalog) {
2581
2527
  const bag = widget.options;
2582
2528
  if (!bag) return widget;
2583
- const specs = WIDGET_OPTIONS[widget.type];
2529
+ const specs = descriptorFor(widget.type, catalog).options;
2584
2530
  const keys = Object.keys(bag);
2585
2531
  const kept = keys.filter((key) => Object.prototype.hasOwnProperty.call(specs, key));
2586
2532
  if (kept.length === keys.length) return widget;
@@ -2588,49 +2534,85 @@ function stripUnknownWidgetOptions(widget) {
2588
2534
  for (const key of kept) options[key] = bag[key];
2589
2535
  return { ...widget, options };
2590
2536
  }
2537
+
2538
+ // src/index.ts
2539
+ import {
2540
+ WidgetFrame as WidgetFrame14,
2541
+ WidgetSubjectProvider as WidgetSubjectProvider2,
2542
+ WidgetSelectProvider as WidgetSelectProvider3,
2543
+ useWidgetSelect as useWidgetSelect4,
2544
+ WidgetTimeZoneProvider as WidgetTimeZoneProvider2,
2545
+ useWidgetTimeZone as useWidgetTimeZone5,
2546
+ WidgetThemeRootProvider as WidgetThemeRootProvider2,
2547
+ useWidgetThemeRoot,
2548
+ WidgetCandidatesProvider as WidgetCandidatesProvider3,
2549
+ useWidgetCandidates as useWidgetCandidates2,
2550
+ useFlashOnChange as useFlashOnChange3,
2551
+ flashTextStyle as flashTextStyle2,
2552
+ flashBackgroundStyle as flashBackgroundStyle2,
2553
+ FlashValue as FlashValue3,
2554
+ useMeasurementStream as useMeasurementStream2,
2555
+ useAlarmStream as useAlarmStream2,
2556
+ useCommandStream as useCommandStream2,
2557
+ useLocationStream as useLocationStream2,
2558
+ useDatasourceAvailability as useDatasourceAvailability2,
2559
+ useDashboardHub,
2560
+ useResolvedBindings,
2561
+ useSlotCandidates,
2562
+ useCandidates as useCandidates2,
2563
+ css as css10
2564
+ } from "@devicechain/widget-sdk";
2565
+ import {
2566
+ EChart as EChart3,
2567
+ buildLineOption as buildLineOption2,
2568
+ buildGaugeOption as buildGaugeOption2,
2569
+ resolveChartTheme,
2570
+ useChartTheme as useChartTheme3
2571
+ } from "@devicechain/widget-sdk/echarts";
2591
2572
  export {
2592
- ALARM_WIDGET_REGISTRY,
2593
2573
  AlarmCount,
2594
2574
  AlarmTable,
2595
2575
  BUNDLED_BASEMAP_ATTRIBUTION,
2596
- CONTROL_WIDGET_REGISTRY,
2597
2576
  CommandButton,
2598
2577
  ConnectedWidget,
2599
2578
  DashboardRenderer,
2600
- EChart,
2579
+ EChart3 as EChart,
2601
2580
  EntitySelector,
2602
- FlashValue,
2581
+ FlashValue3 as FlashValue,
2603
2582
  Gauge,
2604
2583
  Image,
2605
- LOCATION_WIDGET_REGISTRY,
2606
2584
  Label,
2607
2585
  LatestCard,
2608
2586
  MapRuntimeProvider,
2609
2587
  MapWidget,
2610
- SELECTION_WIDGET_REGISTRY,
2611
2588
  Table,
2612
2589
  TenantBasemapProvider,
2613
2590
  TimeSeriesChart,
2614
2591
  WIDGET_BINDS_DATASOURCE,
2615
2592
  WIDGET_CHANNEL,
2616
2593
  WIDGET_OPTIONS,
2617
- WIDGET_REGISTRY,
2618
- WidgetCandidatesProvider,
2619
- WidgetFrame,
2620
- WidgetSelectProvider,
2621
- WidgetSubjectProvider,
2622
- buildGaugeOption,
2623
- buildLineOption,
2594
+ WidgetCandidatesProvider3 as WidgetCandidatesProvider,
2595
+ WidgetFrame14 as WidgetFrame,
2596
+ WidgetSelectProvider3 as WidgetSelectProvider,
2597
+ WidgetSubjectProvider2 as WidgetSubjectProvider,
2598
+ WidgetThemeRootProvider2 as WidgetThemeRootProvider,
2599
+ WidgetTimeZoneProvider2 as WidgetTimeZoneProvider,
2600
+ buildGaugeOption2 as buildGaugeOption,
2601
+ buildLineOption2 as buildLineOption,
2624
2602
  buildPayload,
2603
+ builtinWidgetCatalog,
2604
+ builtinWidgetDefinitions,
2605
+ builtinWidgetDescriptors,
2625
2606
  clampNumberOption,
2626
2607
  commandStatusColor,
2627
2608
  commandStatusLabel,
2628
- css,
2609
+ createBuiltinWidgetRegistry,
2610
+ css10 as css,
2629
2611
  defaultValues,
2630
2612
  describePosition,
2631
2613
  enumOptionValues,
2632
- flashBackgroundStyle,
2633
- flashTextStyle,
2614
+ flashBackgroundStyle2 as flashBackgroundStyle,
2615
+ flashTextStyle2 as flashTextStyle,
2634
2616
  formatDegrees,
2635
2617
  highestSeverity,
2636
2618
  isScalar,
@@ -2638,31 +2620,35 @@ export {
2638
2620
  landStyleFrom,
2639
2621
  loadLandStyle,
2640
2622
  numberOptionSpec,
2641
- optBoolean,
2623
+ optBoolean5 as optBoolean,
2642
2624
  parseParameterSchema,
2643
- pickSample,
2625
+ pickSample3 as pickSample,
2644
2626
  placeable,
2645
2627
  projectToPanel,
2646
2628
  rasterStyleFor,
2647
2629
  resolveChartTheme,
2630
+ resolveWidgets,
2648
2631
  severityColor,
2649
2632
  severityLabel,
2650
2633
  severityRank,
2651
2634
  stripUnknownOptions,
2652
- useAlarmStream,
2653
- useCandidates,
2654
- useChartTheme,
2655
- useCommandStream,
2656
- useDatasourceAvailability,
2657
- useFlashOnChange,
2658
- useLocationStream,
2635
+ useAlarmStream2 as useAlarmStream,
2636
+ useCandidates2 as useCandidates,
2637
+ useChartTheme3 as useChartTheme,
2638
+ useCommandStream2 as useCommandStream,
2639
+ useDashboardHub,
2640
+ useDatasourceAvailability2 as useDatasourceAvailability,
2641
+ useFlashOnChange3 as useFlashOnChange,
2642
+ useLocationStream2 as useLocationStream,
2659
2643
  useMapRuntime,
2660
- useMeasurementStream,
2644
+ useMeasurementStream2 as useMeasurementStream,
2661
2645
  useResolvedBindings,
2662
2646
  useSlotCandidates,
2663
2647
  useTenantBasemap,
2664
- useWidgetCandidates,
2665
- useWidgetSelect,
2648
+ useWidgetCandidates2 as useWidgetCandidates,
2649
+ useWidgetSelect4 as useWidgetSelect,
2650
+ useWidgetThemeRoot,
2651
+ useWidgetTimeZone5 as useWidgetTimeZone,
2666
2652
  validateDefinitionOptions,
2667
2653
  validateParams,
2668
2654
  validateWidgetOptions,