@devicechain/widget-sdk 0.0.0-bootstrap.0 → 0.0.0-stage

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 DELETED
@@ -1,546 +0,0 @@
1
- import {
2
- WidgetCandidatesProvider,
3
- WidgetSelectProvider,
4
- WidgetSubjectProvider,
5
- WidgetThemeRootProvider,
6
- WidgetTimeZoneProvider,
7
- useWidgetCandidates,
8
- useWidgetSelect,
9
- useWidgetSubject,
10
- useWidgetThemeRoot,
11
- useWidgetTimeZone
12
- } from "./chunks/chunk-CC2V7LVI.js";
13
-
14
- // src/define-widget.ts
15
- function defineWidget(definition) {
16
- return definition;
17
- }
18
-
19
- // src/registry.ts
20
- import {
21
- WidgetCatalogError,
22
- collectCatalogIssues,
23
- createWidgetCatalog
24
- } from "@devicechain/dashboards";
25
- var WidgetRegistryError = class extends WidgetCatalogError {
26
- constructor(issues) {
27
- super(issues, "widget registry");
28
- this.name = "WidgetRegistryError";
29
- }
30
- };
31
- function isComponent(value) {
32
- if (typeof value === "function") return true;
33
- return typeof value === "object" && value !== null && "$$typeof" in value;
34
- }
35
- function createWidgetRegistry(definitions) {
36
- if (!Array.isArray(definitions)) {
37
- throw new WidgetRegistryError([{ id: "#?", path: "", message: "definitions must be an array" }]);
38
- }
39
- const issues = collectCatalogIssues(definitions.map((d) => d?.descriptor));
40
- definitions.forEach((definition, index) => {
41
- const id = definition?.descriptor?.id;
42
- const label = typeof id === "string" && id !== "" ? id : `#${index}`;
43
- const add = (path, message) => {
44
- issues.push({ id: label, path, message });
45
- };
46
- const impl = definition?.implementation;
47
- if (typeof impl !== "object" || impl === null) {
48
- add("implementation", "a definition needs an implementation");
49
- return;
50
- }
51
- if (impl.kind !== "trusted-react") {
52
- add("implementation.kind", `kind must be "trusted-react", got ${JSON.stringify(impl.kind)}`);
53
- }
54
- if (!isComponent(impl.component)) add("implementation.component", "component must be a React component");
55
- if (impl.editor !== void 0 && !isComponent(impl.editor)) {
56
- add("implementation.editor", "editor, when given, must be a React component");
57
- }
58
- });
59
- if (issues.length > 0) throw new WidgetRegistryError(issues);
60
- const catalog = createWidgetCatalog(definitions.map((d) => d.descriptor));
61
- const byId = /* @__PURE__ */ new Map();
62
- for (const definition of definitions) {
63
- byId.set(
64
- definition.descriptor.id,
65
- Object.freeze({
66
- descriptor: catalog.get(definition.descriptor.id),
67
- implementation: Object.freeze({ ...definition.implementation })
68
- })
69
- );
70
- }
71
- const ordered = Object.freeze([...byId.values()]);
72
- return Object.freeze({
73
- catalog,
74
- descriptor: (id) => catalog.get(id),
75
- implementation: (id) => byId.get(id)?.implementation,
76
- list: () => ordered
77
- });
78
- }
79
-
80
- // src/theme.ts
81
- function css(name) {
82
- return `hsl(var(--${name}))`;
83
- }
84
-
85
- // src/frame.tsx
86
- import { jsx, jsxs } from "react/jsx-runtime";
87
- function WidgetFrame({ title, subtitle, children, bodyStyle }) {
88
- const subject = useWidgetSubject();
89
- const sub = subtitle ?? subject;
90
- return /* @__PURE__ */ jsxs(
91
- "div",
92
- {
93
- style: {
94
- display: "flex",
95
- flexDirection: "column",
96
- width: "100%",
97
- height: "100%",
98
- boxSizing: "border-box",
99
- background: css("card"),
100
- color: css("card-foreground"),
101
- border: `1px solid ${css("border")}`,
102
- borderRadius: "var(--radius, 8px)",
103
- overflow: "hidden"
104
- },
105
- children: [
106
- title || sub ? /* @__PURE__ */ jsxs(
107
- "div",
108
- {
109
- style: {
110
- padding: "8px 12px",
111
- borderBottom: `1px solid ${css("border")}`
112
- },
113
- children: [
114
- title ? /* @__PURE__ */ jsx("div", { style: { fontSize: 12, fontWeight: 600, color: css("muted-foreground") }, children: title }) : null,
115
- sub ? /* @__PURE__ */ jsx(
116
- "div",
117
- {
118
- title: sub,
119
- style: {
120
- fontSize: 11,
121
- fontWeight: 400,
122
- color: css("muted-foreground"),
123
- opacity: 0.75,
124
- marginTop: title ? 2 : 0,
125
- whiteSpace: "nowrap",
126
- overflow: "hidden",
127
- textOverflow: "ellipsis"
128
- },
129
- children: sub
130
- }
131
- ) : null
132
- ]
133
- }
134
- ) : null,
135
- /* @__PURE__ */ jsx("div", { style: { flex: 1, minHeight: 0, ...bodyStyle }, children })
136
- ]
137
- }
138
- );
139
- }
140
-
141
- // src/format.ts
142
- function formatTimestamp(iso, timeZone) {
143
- if (!iso) return "";
144
- const date = new Date(iso);
145
- if (Number.isNaN(date.getTime())) return iso;
146
- return timeZone ? date.toLocaleTimeString(void 0, { timeZone, timeZoneName: "short" }) : date.toLocaleTimeString();
147
- }
148
- function formatDateTime(iso, timeZone) {
149
- if (!iso) return "";
150
- const date = new Date(iso);
151
- if (Number.isNaN(date.getTime())) return iso;
152
- return timeZone ? date.toLocaleString(void 0, { timeZone, timeZoneName: "short" }) : date.toLocaleString();
153
- }
154
- function formatValue(value, precision) {
155
- if (value == null) return "\u2014";
156
- if (precision == null) return String(value);
157
- const places = Math.min(100, Math.max(0, Math.trunc(precision)));
158
- return value.toFixed(places);
159
- }
160
-
161
- // src/flash.tsx
162
- import { useEffect, useRef, useState } from "react";
163
- import { jsx as jsx2 } from "react/jsx-runtime";
164
- var UP = {
165
- fg: "var(--flash-up, hsl(142 72% 40%))",
166
- bg: "var(--flash-up-bg, hsl(142 72% 45% / 0.16))"
167
- };
168
- var DOWN = {
169
- fg: "var(--flash-down, hsl(0 72% 51%))",
170
- bg: "var(--flash-down-bg, hsl(0 72% 55% / 0.16))"
171
- };
172
- var HOLD_MS = 400;
173
- var FADE_MS = 600;
174
- function useFlashOnChange(value, enabled, identity) {
175
- const prev = useRef(value);
176
- const prevIdentity = useRef(identity);
177
- const [direction, setDirection] = useState(null);
178
- useEffect(() => {
179
- const before = prev.current;
180
- const sameEntity = prevIdentity.current === identity;
181
- prev.current = value;
182
- prevIdentity.current = identity;
183
- if (!enabled || !sameEntity || typeof value !== "number" || typeof before !== "number" || !Number.isFinite(value) || !Number.isFinite(before) || value === before) {
184
- setDirection(null);
185
- return;
186
- }
187
- setDirection(value > before ? "up" : "down");
188
- const timer = setTimeout(() => setDirection(null), HOLD_MS);
189
- return () => clearTimeout(timer);
190
- }, [value, enabled, identity]);
191
- return enabled ? direction : null;
192
- }
193
- function flashTextStyle(direction, fadeMs = FADE_MS) {
194
- if (direction) return { color: direction === "up" ? UP.fg : DOWN.fg, transition: "none" };
195
- return { transition: `color ${fadeMs}ms ease-out` };
196
- }
197
- function flashBackgroundStyle(direction, fadeMs = FADE_MS) {
198
- if (direction) {
199
- return { backgroundColor: direction === "up" ? UP.bg : DOWN.bg, transition: "none" };
200
- }
201
- return { transition: `background-color ${fadeMs}ms ease-out` };
202
- }
203
- function FlashValue({
204
- value,
205
- precision,
206
- enabled,
207
- identity,
208
- baseColor = "inherit",
209
- style
210
- }) {
211
- const direction = useFlashOnChange(value, enabled, identity);
212
- return /* @__PURE__ */ jsx2("span", { style: { color: baseColor, ...style, ...flashTextStyle(direction) }, children: formatValue(value, precision) });
213
- }
214
-
215
- // src/widget-options.ts
216
- function pickSample(latest, name) {
217
- return name ? latest[name] : Object.values(latest)[0];
218
- }
219
- function optString(options, key) {
220
- const value = options?.[key];
221
- return typeof value === "string" ? value : void 0;
222
- }
223
- function optNumber(options, key) {
224
- const value = options?.[key];
225
- return typeof value === "number" && Number.isFinite(value) ? value : void 0;
226
- }
227
- function optBoolean(options, key) {
228
- return options?.[key] === true;
229
- }
230
- function primaryMeasurementName(widget) {
231
- const explicit = optString(widget.options, "measurement");
232
- if (explicit) return explicit;
233
- const ds = widget.datasource;
234
- if (ds && ds.measurements.length > 0) return ds.measurements[0];
235
- return void 0;
236
- }
237
-
238
- // src/hooks.ts
239
- import {
240
- bindingsWithoutScopedSlots,
241
- DashboardHub,
242
- hasScopedSlots,
243
- resolveContextBindings,
244
- resolveSlotCandidates
245
- } from "@devicechain/dashboards";
246
- import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
247
- function useDashboardHub(resolver, authorities) {
248
- const authoritiesKey = JSON.stringify(authorities ?? []);
249
- const hub = useMemo(
250
- () => new DashboardHub({ resolver, authorities }),
251
- // authorities read via authoritiesKey (value identity), not reference.
252
- // eslint-disable-next-line react-hooks/exhaustive-deps
253
- [resolver, authoritiesKey]
254
- );
255
- useEffect2(() => () => hub.disposeAll(), [hub]);
256
- return hub;
257
- }
258
- function useResolvedBindings(definition, base, selection, resolver) {
259
- const scoped = useMemo(() => hasScopedSlots(definition), [definition]);
260
- const overlaid = useMemo(() => ({ ...base, ...selection }), [base, selection]);
261
- const [resolved, setResolved] = useState2(() => bindingsWithoutScopedSlots(definition, overlaid));
262
- const genRef = useRef2(0);
263
- useEffect2(() => {
264
- if (!scoped) {
265
- setResolved((prev) => sameBindings(prev, overlaid) ? prev : overlaid);
266
- return;
267
- }
268
- const gen = ++genRef.current;
269
- let live = true;
270
- resolveContextBindings(definition, base, selection, resolver).then((next) => {
271
- if (!live || gen !== genRef.current) return;
272
- setResolved((prev) => sameBindings(prev, next) ? prev : next);
273
- }).catch(() => {
274
- });
275
- return () => {
276
- live = false;
277
- };
278
- }, [scoped, definition, base, selection, resolver, overlaid]);
279
- return scoped ? resolved : overlaid;
280
- }
281
- function sameBindings(a, b) {
282
- const ak = Object.keys(a).sort();
283
- const bk = Object.keys(b).sort();
284
- if (ak.length !== bk.length) return false;
285
- for (let i = 0; i < ak.length; i += 1) {
286
- if (ak[i] !== bk[i]) return false;
287
- if (JSON.stringify(a[ak[i]]) !== JSON.stringify(b[bk[i]])) return false;
288
- }
289
- return true;
290
- }
291
- function useSlotCandidates(definition, bindings, resolver, lister) {
292
- const bindingsKey = JSON.stringify(bindings);
293
- return useMemo(
294
- () => {
295
- if (!lister) return void 0;
296
- return (slot) => resolveSlotCandidates(definition, slot, bindings, resolver, lister);
297
- },
298
- // bindings read via bindingsKey (value identity), not reference.
299
- // eslint-disable-next-line react-hooks/exhaustive-deps
300
- [definition, bindingsKey, resolver, lister]
301
- );
302
- }
303
- function useCandidates(slot) {
304
- const provider = useWidgetCandidates();
305
- const wired = !!provider;
306
- const [state, setState] = useState2(() => ({
307
- candidates: [],
308
- loading: Boolean(provider && slot),
309
- error: null
310
- }));
311
- const genRef = useRef2(0);
312
- useEffect2(() => {
313
- if (!provider || !slot) {
314
- setState({ candidates: [], loading: false, error: null });
315
- return;
316
- }
317
- const gen = ++genRef.current;
318
- let live = true;
319
- setState((prev) => ({ ...prev, loading: true, error: null }));
320
- provider(slot).then((candidates) => {
321
- if (live && gen === genRef.current) setState({ candidates, loading: false, error: null });
322
- }).catch((error) => {
323
- if (live && gen === genRef.current) setState({ candidates: [], loading: false, error });
324
- });
325
- return () => {
326
- live = false;
327
- };
328
- }, [provider, slot]);
329
- return { ...state, wired };
330
- }
331
- var EMPTY = { latest: {}, samples: [], error: null };
332
- function latestOf(samples) {
333
- const latest = {};
334
- for (const s of samples) latest[s.name] = s;
335
- return latest;
336
- }
337
- function useMeasurementStream(hub, datasource, options = {}) {
338
- const windowSize = options.window ?? 300;
339
- const initialSamples = options.initialSamples;
340
- const key = datasource ? JSON.stringify(datasource) : null;
341
- const [tagged, setTagged] = useState2({
342
- key,
343
- state: EMPTY
344
- });
345
- const live = tagged.key === key ? tagged.state : EMPTY;
346
- useEffect2(() => {
347
- setTagged({ key, state: EMPTY });
348
- if (!datasource) return;
349
- const update = (fn) => setTagged((prev) => prev.key === key ? { key, state: fn(prev.state) } : prev);
350
- return hub.subscribeWidget(datasource, {
351
- next: (sample) => update((prev) => {
352
- const samples = prev.samples.concat(sample);
353
- if (samples.length > windowSize) samples.splice(0, samples.length - windowSize);
354
- return { latest: { ...prev.latest, [sample.name]: sample }, samples, error: null };
355
- }),
356
- error: (err) => update((prev) => ({ ...prev, error: err }))
357
- });
358
- }, [hub, key, windowSize]);
359
- return useMemo(() => {
360
- if (!initialSamples || initialSamples.length === 0) return live;
361
- const merged = initialSamples.concat(live.samples);
362
- const samples = merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged;
363
- return {
364
- latest: { ...latestOf(initialSamples), ...live.latest },
365
- samples,
366
- error: live.error
367
- };
368
- }, [initialSamples, live, windowSize]);
369
- }
370
- var EMPTY_ALARMS = { alarms: [], total: 0, loading: true, error: null };
371
- function useAlarmStream(hub, subscription) {
372
- const [state, setState] = useState2(EMPTY_ALARMS);
373
- const key = JSON.stringify(subscription);
374
- useEffect2(() => {
375
- setState(EMPTY_ALARMS);
376
- return hub.subscribeAlarms(subscription, {
377
- next: (snapshot) => setState({ alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }),
378
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
379
- });
380
- }, [hub, key]);
381
- return state;
382
- }
383
- var EMPTY_COMMANDS = {
384
- deviceToken: null,
385
- commands: [],
386
- total: 0,
387
- loading: true,
388
- error: null
389
- };
390
- function useCommandStream(hub, subscription) {
391
- const [state, setState] = useState2(EMPTY_COMMANDS);
392
- const key = JSON.stringify(subscription);
393
- useEffect2(() => {
394
- setState(EMPTY_COMMANDS);
395
- return hub.subscribeCommands(subscription, {
396
- next: (snapshot) => setState({
397
- deviceToken: snapshot.deviceToken,
398
- commands: snapshot.commands,
399
- total: snapshot.total,
400
- loading: false,
401
- error: null
402
- }),
403
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
404
- });
405
- }, [hub, key]);
406
- return state;
407
- }
408
- var EMPTY_LOCATIONS = {
409
- locations: [],
410
- deviceTokens: [],
411
- forbidden: false,
412
- loading: true,
413
- error: null
414
- };
415
- function useLocationStream(hub, subscription) {
416
- const [state, setState] = useState2(EMPTY_LOCATIONS);
417
- const key = JSON.stringify(subscription);
418
- useEffect2(() => {
419
- setState(EMPTY_LOCATIONS);
420
- return hub.subscribeLocations(subscription, {
421
- next: (snapshot) => setState(
422
- snapshot.kind === "forbidden" ? { locations: [], deviceTokens: [], forbidden: true, loading: false, error: null } : {
423
- locations: snapshot.locations,
424
- deviceTokens: snapshot.deviceTokens,
425
- forbidden: false,
426
- loading: false,
427
- error: null
428
- }
429
- ),
430
- error: (err) => setState((prev) => ({ ...prev, loading: false, error: err }))
431
- });
432
- }, [hub, key]);
433
- return state;
434
- }
435
- var AVAILABILITY_REVALIDATE_MS = 6e4;
436
- function useDatasourceAvailability(hub, datasource) {
437
- const [state, setState] = useState2("unknown");
438
- const key = datasource ? JSON.stringify(datasource) : null;
439
- useEffect2(() => {
440
- if (!datasource) {
441
- setState("available");
442
- return;
443
- }
444
- setState("unknown");
445
- let cancelled = false;
446
- let timer;
447
- const stopTimer = () => {
448
- if (timer) {
449
- clearInterval(timer);
450
- timer = void 0;
451
- }
452
- };
453
- const check = () => {
454
- hub.isDatasourceAvailable(datasource).then((ok) => {
455
- if (cancelled) return;
456
- setState(ok ? "available" : "unavailable");
457
- if (ok) stopTimer();
458
- }).catch(() => {
459
- if (cancelled) return;
460
- setState("available");
461
- stopTimer();
462
- });
463
- };
464
- check();
465
- timer = setInterval(check, AVAILABILITY_REVALIDATE_MS);
466
- return () => {
467
- cancelled = true;
468
- stopTimer();
469
- };
470
- }, [hub, key]);
471
- return state;
472
- }
473
- function useElementSize() {
474
- const ref = useRef2(null);
475
- const [size, setSize] = useState2({ width: 0, height: 0 });
476
- useEffect2(() => {
477
- const el = ref.current;
478
- if (!el || typeof ResizeObserver === "undefined") return;
479
- const observer = new ResizeObserver((entries) => {
480
- const rect = entries[0]?.contentRect;
481
- if (!rect) return;
482
- setSize(
483
- (prev) => prev.width === rect.width && prev.height === rect.height ? prev : { width: rect.width, height: rect.height }
484
- );
485
- });
486
- observer.observe(el);
487
- return () => observer.disconnect();
488
- }, []);
489
- return [ref, size];
490
- }
491
-
492
- // src/index.ts
493
- import {
494
- SDK_API_VERSION,
495
- WidgetCatalogError as WidgetCatalogError2,
496
- defineWidgetDescriptor,
497
- isBuiltinWidgetId,
498
- isNamespacedWidgetId,
499
- validateWidgetDescriptor
500
- } from "@devicechain/dashboards";
501
- export {
502
- FlashValue,
503
- SDK_API_VERSION,
504
- WidgetCandidatesProvider,
505
- WidgetCatalogError2 as WidgetCatalogError,
506
- WidgetFrame,
507
- WidgetRegistryError,
508
- WidgetSelectProvider,
509
- WidgetSubjectProvider,
510
- WidgetThemeRootProvider,
511
- WidgetTimeZoneProvider,
512
- createWidgetRegistry,
513
- css,
514
- defineWidget,
515
- defineWidgetDescriptor,
516
- flashBackgroundStyle,
517
- flashTextStyle,
518
- formatDateTime,
519
- formatTimestamp,
520
- formatValue,
521
- isBuiltinWidgetId,
522
- isNamespacedWidgetId,
523
- optBoolean,
524
- optNumber,
525
- optString,
526
- pickSample,
527
- primaryMeasurementName,
528
- useAlarmStream,
529
- useCandidates,
530
- useCommandStream,
531
- useDashboardHub,
532
- useDatasourceAvailability,
533
- useElementSize,
534
- useFlashOnChange,
535
- useLocationStream,
536
- useMeasurementStream,
537
- useResolvedBindings,
538
- useSlotCandidates,
539
- useWidgetCandidates,
540
- useWidgetSelect,
541
- useWidgetSubject,
542
- useWidgetThemeRoot,
543
- useWidgetTimeZone,
544
- validateWidgetDescriptor
545
- };
546
- //# sourceMappingURL=index.js.map