@devicechain/widget-sdk 0.21.1 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +37 -1
- package/dist/chart/chart-options.d.ts +26 -0
- package/dist/chart/chart-theme.d.ts +2 -0
- package/dist/chart/echart-markers.d.ts +2 -0
- package/dist/chart/echart.d.ts +2 -0
- package/dist/chunks/chunk-A5BJ2ALW.js +318 -0
- package/dist/chunks/chunk-A5BJ2ALW.js.map +7 -0
- package/dist/chunks/echart-markers-JDFD265P.js +7 -0
- package/dist/chunks/echart-markers-JDFD265P.js.map +7 -0
- package/dist/connection-badge.d.ts +6 -0
- package/dist/context.d.ts +34 -1
- package/dist/echarts.d.ts +1 -1
- package/dist/echarts.js +222 -25
- package/dist/echarts.js.map +2 -2
- package/dist/fit-text.d.ts +17 -0
- package/dist/format.d.ts +2 -0
- package/dist/frame.d.ts +7 -2
- package/dist/freshness-readout.d.ts +22 -0
- package/dist/freshness.d.ts +13 -0
- package/dist/hooks.d.ts +14 -4
- package/dist/index.d.ts +11 -3
- package/dist/index.js +694 -168
- package/dist/index.js.map +4 -4
- package/dist/scale.d.ts +19 -0
- package/dist/severity.d.ts +18 -0
- package/dist/theme.d.ts +2 -1
- package/dist/thresholds.d.ts +16 -0
- package/dist/tokens.d.ts +25 -0
- package/package.json +3 -3
- package/dist/chunks/chunk-CC2V7LVI.js +0 -67
- package/dist/chunks/chunk-CC2V7LVI.js.map +0 -7
package/dist/index.js.map
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../src/define-widget.ts", "../src/registry.ts", "../src/
|
|
4
|
-
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n ChannelSnapshot,\n OptionsOf,\n WidgetChannel,\n WidgetDescriptor,\n WidgetOptionSpecs,\n} from '@devicechain/dashboards';\nimport type { ComponentType } from 'react';\n\nimport type { WidgetEditorProps, WidgetProps } from './widget';\n\n// The trusted half of a registration: the React code. Kept apart from the descriptor so\n// the descriptor stays portable plain data and headless validators never load a component.\nexport interface WidgetImplementation<C extends WidgetChannel, S extends WidgetOptionSpecs> {\n // The only execution kind in this release: React code the host bundled at build time,\n // running with the host page's privileges.\n kind: 'trusted-react';\n component: ComponentType<WidgetProps<ChannelSnapshot<C>>>;\n editor?: ComponentType<WidgetEditorProps<OptionsOf<S>>>;\n}\n\nexport interface WidgetDefinition<\n C extends WidgetChannel = WidgetChannel,\n S extends WidgetOptionSpecs = WidgetOptionSpecs,\n> {\n descriptor: WidgetDescriptor<C, S>;\n implementation: WidgetImplementation<C, S>;\n}\n\n// A component with its props type erased. `any` is deliberate and confined to this alias:\n// `ComponentType<never>` would be the principled erasure, but a class component's\n// `defaultProps` is covariant in its props and rejects it, and a registry has to hold\n// components of every channel's props.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ErasedComponent = ComponentType<any>;\n\n// A definition with its component types erased, which is what a registry stores: any\n// specific WidgetDefinition<C, S> is assignable to it.\nexport interface ErasedWidgetDefinition {\n descriptor: WidgetDescriptor;\n implementation: {\n kind: 'trusted-react';\n component: ErasedComponent;\n editor?: ErasedComponent;\n };\n}\n\n// defineWidget is an identity at runtime; it exists for inference. The channel and the\n// option schema are inferred from the literal, so a component whose data disagrees with\n// the channel, defaults that disagree with the schema, a binding that names a missing or\n// wrong-kind option, or an interaction the channel cannot carry is a COMPILE error \u2014 and\n// createWidgetRegistry checks the same things again at runtime for callers that bypass the\n// compiler. Every widget, built-in or third-party, is declared through this one helper.\nexport function defineWidget<const C extends WidgetChannel, const S extends WidgetOptionSpecs>(\n definition: WidgetDefinition<C, S>,\n): WidgetDefinition<C, S> {\n return definition;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n WidgetCatalogError,\n collectCatalogIssues,\n createWidgetCatalog,\n type DescriptorIssue,\n type WidgetCatalog,\n type WidgetDescriptor,\n} from '@devicechain/dashboards';\n\nimport type { ErasedWidgetDefinition } from './define-widget';\n\n// Thrown by createWidgetRegistry with EVERY problem found, so a host fixes its composition\n// in one pass instead of one failed start per fault.\nexport class WidgetRegistryError extends WidgetCatalogError {\n constructor(issues: readonly DescriptorIssue[]) {\n super(issues, 'widget registry');\n this.name = 'WidgetRegistryError';\n }\n}\n\n// A sealed registry: descriptors (through the React-free catalog the parser sees) plus the\n// trusted implementations. Immutable \u2014 there is no register(), no process-global state and\n// no replacement while a dashboard is mounted. Two registries are independent.\nexport interface WidgetRegistry {\n // The React-free view of this registry, for parsing and headless validation.\n readonly catalog: WidgetCatalog;\n descriptor(id: string): WidgetDescriptor | undefined;\n implementation(id: string): ErasedWidgetDefinition['implementation'] | undefined;\n // Definitions in the order they were supplied.\n list(): readonly ErasedWidgetDefinition[];\n}\n\nfunction isComponent(value: unknown): boolean {\n if (typeof value === 'function') return true;\n // memo / forwardRef / lazy components are objects tagged with $$typeof.\n return typeof value === 'object' && value !== null && '$$typeof' in value;\n}\n\ntype Loose = Partial<ErasedWidgetDefinition> | null | undefined;\n\n// createWidgetRegistry validates every definition (descriptor invariants, duplicate ids,\n// the implementation shape) and returns a deeply frozen registry, or throws one\n// WidgetRegistryError listing all the problems.\nexport function createWidgetRegistry(definitions: readonly ErasedWidgetDefinition[]): WidgetRegistry {\n if (!Array.isArray(definitions)) {\n throw new WidgetRegistryError([{ id: '#?', path: '', message: 'definitions must be an array' }]);\n }\n const issues: DescriptorIssue[] = collectCatalogIssues(definitions.map((d) => (d as Loose)?.descriptor));\n\n definitions.forEach((definition, index) => {\n const id: unknown = (definition as Loose)?.descriptor?.id;\n const label = typeof id === 'string' && id !== '' ? id : `#${index}`;\n const add = (path: string, message: string): void => {\n issues.push({ id: label, path, message });\n };\n const impl = (definition as Loose)?.implementation;\n if (typeof impl !== 'object' || impl === null) {\n add('implementation', 'a definition needs an implementation');\n return;\n }\n if (impl.kind !== 'trusted-react') {\n add('implementation.kind', `kind must be \"trusted-react\", got ${JSON.stringify(impl.kind)}`);\n }\n if (!isComponent(impl.component)) add('implementation.component', 'component must be a React component');\n if (impl.editor !== undefined && !isComponent(impl.editor)) {\n add('implementation.editor', 'editor, when given, must be a React component');\n }\n });\n\n if (issues.length > 0) throw new WidgetRegistryError(issues);\n\n // Through the one public, validating constructor: no unvalidated path to a catalog exists.\n // The descriptors were already checked above (so this cannot throw for them); checking\n // again is the price of not exporting a catalog builder that skips validation.\n //\n // \uD83D\uDD34 The catalog freezes each descriptor IN PLACE (deeply), so the caller's descriptor\n // objects are frozen after this call. That is deliberate: a built-in descriptor holds its\n // option schema BY IDENTITY with WIDGET_OPTIONS, and a copy would break that identity.\n const catalog = createWidgetCatalog(definitions.map((d) => d.descriptor));\n const byId = new Map<string, ErasedWidgetDefinition>();\n for (const definition of definitions) {\n // The implementation object, unlike the descriptor, is copied before freezing, so the\n // caller's implementation object is left mutable (the components it names are shared).\n byId.set(\n definition.descriptor.id,\n Object.freeze({\n descriptor: catalog.get(definition.descriptor.id) as WidgetDescriptor,\n implementation: Object.freeze({ ...definition.implementation }),\n }),\n );\n }\n const ordered: readonly ErasedWidgetDefinition[] = Object.freeze([...byId.values()]);\n\n return Object.freeze({\n catalog,\n descriptor: (id: string) => catalog.get(id),\n implementation: (id: string) => byId.get(id)?.implementation,\n list: () => ordered,\n });\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Theming without Tailwind (widgets are embeddable \u2014 no Tailwind leakage).\n//\n// Widgets style themselves with inline styles that reference the SAME CSS custom\n// properties the console (and any host) defines \u2014 `--card`, `--foreground`,\n// `--border`, `--chart-1..5`, etc. A host that sets those variables themes the\n// widgets for free (light/dark included); a host without Tailwind still works.\n//\n// DOM elements can use `hsl(var(--card))` directly. ECharts renders to a <canvas>,\n// which cannot resolve CSS variables \u2014 so for charts we read the variables off the\n// live computed style and hand ECharts concrete color strings (resolveChartTheme).\n\n// css returns an `hsl(var(--name))` reference for use in an inline style. The host\n// defines the variable as HSL channels (e.g. `--card: 0 0% 100%`), matching the\n// console's token set.\nexport function css(name: ThemeVar): string {\n return `hsl(var(--${name}))`;\n}\n\n// The subset of the console's design tokens widgets rely on. Kept small and\n// semantic so the contract a host must provide is explicit and stable.\nexport type ThemeVar =\n | 'background'\n | 'foreground'\n | 'card'\n | 'card-foreground'\n | 'muted'\n | 'muted-foreground'\n | 'primary'\n | 'border'\n | 'destructive';\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The shared card frame every widget renders inside \u2014 a bordered, themed surface\n// that fills its dashboard-canvas slot, with an optional title bar. Styled with\n// inline CSS-variable references so it themes from the host without Tailwind.\n\nimport { type CSSProperties, type ReactNode } from 'react';\n\nimport { useWidgetSubject } from './context';\nimport { css } from './theme';\n\nexport interface WidgetFrameProps {\n title?: string;\n // An explicit subject line; when omitted the frame falls back to the resolved-entity\n // subject from WidgetSubjectContext.\n subtitle?: string;\n children: ReactNode;\n // Extra style for the body region (e.g. removing padding for a chart canvas).\n bodyStyle?: CSSProperties;\n}\n\nexport function WidgetFrame({ title, subtitle, children, bodyStyle }: WidgetFrameProps) {\n const subject = useWidgetSubject();\n const sub = subtitle ?? subject;\n return (\n <div\n style={{\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n height: '100%',\n boxSizing: 'border-box',\n background: css('card'),\n color: css('card-foreground'),\n border: `1px solid ${css('border')}`,\n borderRadius: 'var(--radius, 8px)',\n overflow: 'hidden',\n }}\n >\n {title || sub ? (\n <div\n style={{\n padding: '8px 12px',\n borderBottom: `1px solid ${css('border')}`,\n }}\n >\n {title ? (\n <div style={{ fontSize: 12, fontWeight: 600, color: css('muted-foreground') }}>{title}</div>\n ) : null}\n {sub ? (\n <div\n title={sub}\n style={{\n fontSize: 11,\n fontWeight: 400,\n color: css('muted-foreground'),\n opacity: 0.75,\n marginTop: title ? 2 : 0,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {sub}\n </div>\n ) : null}\n </div>\n ) : null}\n <div style={{ flex: 1, minHeight: 0, ...bodyStyle }}>{children}</div>\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Both formatters take an optional IANA `timeZone`. Omitted, they are exactly what they\n// always were: the viewer's own zone and locale defaults, which is right for a live board.\n// A host that plays a RECORDING pins one (the renderer's `timeZone` prop), because the\n// recorded instants are the point and a viewer's zone would shift them across a date line;\n// a pinned zone also prints its short name, so the reading says which clock it is.\n\n// formatTimestamp renders an RFC3339 event time as a locale time string, leaving\n// an unparseable value untouched and an absent one blank.\nexport function formatTimestamp(iso: string | null, timeZone?: string): string {\n if (!iso) return '';\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return iso;\n return timeZone ? date.toLocaleTimeString(undefined, { timeZone, timeZoneName: 'short' }) : date.toLocaleTimeString();\n}\n\n// formatDateTime renders an RFC3339 time as a locale date+time string. Unlike\n// formatTimestamp (time-only, right for a live measurement tick) this keeps the date,\n// because an alarm can have been raised days ago and a bare \"09:14:32\" would mislead.\nexport function formatDateTime(iso: string | null, timeZone?: string): string {\n if (!iso) return '';\n const date = new Date(iso);\n if (Number.isNaN(date.getTime())) return iso;\n return timeZone ? date.toLocaleString(undefined, { timeZone, timeZoneName: 'short' }) : date.toLocaleString();\n}\n\n// formatValue renders a measurement value, applying a fixed precision when given\n// and showing an em dash for a missing value.\n//\n// The precision is clamped to toFixed's domain because it arrives from an opaque stored\n// definition: outside 0..100 toFixed throws a RangeError, which is an exception thrown\n// during render \u2014 it unmounts the widget's whole subtree rather than degrading, and every\n// other bad option value in this package degrades. The console's config panel can author\n// a negative one today, and a hand-edited definition always could.\nexport function formatValue(value: number | null | undefined, precision?: number): string {\n if (value == null) return '\u2014';\n if (precision == null) return String(value);\n const places = Math.min(100, Math.max(0, Math.trunc(precision)));\n return value.toFixed(places);\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Directional change-flash \u2014 an opt-in cue (widget option `flashOnChange`) that briefly\n// tints a numeric value green when it rises and red when it falls, then fades back, so a\n// glance catches direction of change on a live board (cf. a trading \"last\" column). Purely\n// presentational: no hub/model change, driven off the value the widget already renders.\n//\n// Single-value widgets (latest-card) color the number; a multi-row widget (table,\n// alarm-table) renders each value through <FlashValue>, so React's per-key component\n// instances give every row its own independent flash with no map bookkeeping. The gauge\n// (an opaque ECharts canvas whose number can't be CSS-transitioned) tints its container\n// instead \u2014 see flashBackgroundStyle.\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\n\nimport { formatValue } from './format';\n\nexport type FlashDirection = 'up' | 'down' | null;\n\n// Semantic up/down colors, mid-saturation so they read on both light and dark host themes;\n// a host may override with the --flash-up / --flash-down custom properties. The background\n// variants are translucent so a tint sits over the widget's own surface without hiding it.\nconst UP = {\n fg: 'var(--flash-up, hsl(142 72% 40%))',\n bg: 'var(--flash-up-bg, hsl(142 72% 45% / 0.16))',\n};\nconst DOWN = {\n fg: 'var(--flash-down, hsl(0 72% 51%))',\n bg: 'var(--flash-down-bg, hsl(0 72% 55% / 0.16))',\n};\n\n// How long (ms) the solid tint holds before fading, and how long the fade back takes.\nconst HOLD_MS = 400;\nconst FADE_MS = 600;\n\n// useFlashOnChange watches a numeric value and returns the direction of its most recent\n// change ('up' | 'down') for a short window, then null. It stays null when the feature is\n// off, on the first value (no flash on mount), on a non-finite value, or when `identity`\n// changes \u2014 the last so a widget bound to an ANCHOR (whose one measurement slot interleaves\n// values from many member devices) adopts the new device's value instead of reading the\n// device-to-device jump as a real rise/fall. Pass the value's owning entity as `identity`\n// (e.g. `deviceToken:name`); leave it undefined for a single-device value.\n//\n// The held direction auto-clears after HOLD_MS; the consumer fades it out via flashTextStyle.\n// Every early-return path resets `direction` too: the effect only re-runs on a dep change,\n// and each such change has already cancelled the pending timer, so not clearing here would\n// strand a tint (e.g. value \u2192 null, or toggled off then on, mid-hold).\nexport function useFlashOnChange(\n value: number | null | undefined,\n enabled: boolean,\n identity?: string | number,\n): FlashDirection {\n const prev = useRef<number | null | undefined>(value);\n const prevIdentity = useRef(identity);\n const [direction, setDirection] = useState<FlashDirection>(null);\n\n useEffect(() => {\n const before = prev.current;\n const sameEntity = prevIdentity.current === identity;\n prev.current = value;\n prevIdentity.current = identity;\n if (\n !enabled ||\n !sameEntity ||\n typeof value !== 'number' ||\n typeof before !== 'number' ||\n !Number.isFinite(value) ||\n !Number.isFinite(before) ||\n value === before\n ) {\n setDirection(null); // a no-op when already null (React bails out); clears a stale tint otherwise\n return;\n }\n setDirection(value > before ? 'up' : 'down');\n const timer = setTimeout(() => setDirection(null), HOLD_MS);\n return () => clearTimeout(timer);\n }, [value, enabled, identity]);\n\n // Mask any lingering state when disabled, so toggling off is immediate.\n return enabled ? direction : null;\n}\n\n// flashTextStyle colors text for the current direction: snap to the tint (no transition)\n// while flashing, then fade `color` back to the element's own color when it clears. Merge\n// it AFTER an explicit base `color` so the tint overrides while set and the base shows\n// through (and animates) once cleared.\nexport function flashTextStyle(direction: FlashDirection, fadeMs: number = FADE_MS): CSSProperties {\n if (direction) return { color: direction === 'up' ? UP.fg : DOWN.fg, transition: 'none' };\n return { transition: `color ${fadeMs}ms ease-out` };\n}\n\n// flashBackgroundStyle is the same cue as a translucent background tint, for a widget whose\n// value can't be recolored directly (the gauge's canvas). Snap in, fade the background back.\nexport function flashBackgroundStyle(direction: FlashDirection, fadeMs: number = FADE_MS): CSSProperties {\n if (direction) {\n return { backgroundColor: direction === 'up' ? UP.bg : DOWN.bg, transition: 'none' };\n }\n return { transition: `background-color ${fadeMs}ms ease-out` };\n}\n\n// FlashValue renders a formatted numeric value that flashes on change \u2014 the reusable cell\n// for multi-row widgets. Each rendered instance owns its own flash state, so keying rows by\n// entity (React's list key) gives every row an independent cue for free.\nexport function FlashValue({\n value,\n precision,\n enabled,\n identity,\n baseColor = 'inherit',\n style,\n}: {\n value: number | null | undefined;\n precision?: number;\n enabled: boolean;\n // The value's owning entity (see useFlashOnChange): when it changes, adopt the new value\n // rather than flash. Distinguishes a real per-device change from an anchor's cross-device\n // interleave. Undefined for a single-device value.\n identity?: string | number;\n // The color the value fades back to (defaults to the inherited text color).\n baseColor?: string;\n style?: CSSProperties;\n}) {\n const direction = useFlashOnChange(value, enabled, identity);\n return (\n <span style={{ color: baseColor, ...style, ...flashTextStyle(direction) }}>\n {formatValue(value, precision)}\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Small helpers for reading a widget's opaque `options` bag and picking the measurement a\n// single-value widget should show. They are the SDK's, not the built-ins': a third-party\n// widget reads its own options the same defensive way.\n\nimport type { MeasurementSample, WidgetInstance } from '@devicechain/dashboards';\n\n// pickSample chooses the sample a single-value widget (card, gauge) shows: the\n// named measurement, or the first available when no name is pinned.\nexport function pickSample(\n latest: Record<string, MeasurementSample>,\n name: string | undefined,\n): MeasurementSample | undefined {\n return name ? latest[name] : Object.values(latest)[0];\n}\n\n// --- options helpers ---------------------------------------------------------\n// A widget's `options` is opaque JSON (Record<string, unknown>) owned by the\n// widget; these read a key defensively, returning undefined on the wrong type\n// rather than throwing on a hand-edited definition.\n\ntype Options = Record<string, unknown> | undefined;\n\nexport function optString(options: Options, key: string): string | undefined {\n const value = options?.[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nexport function optNumber(options: Options, key: string): number | undefined {\n const value = options?.[key];\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n// A boolean flag option: true only when explicitly stored true (absent/other \u2192 false), so\n// an opt-in feature stays off by default and a hand-edited value can't accidentally enable it.\nexport function optBoolean(options: Options, key: string): boolean {\n return options?.[key] === true;\n}\n\n// primaryMeasurementName is the measurement a single-value widget (card, gauge)\n// displays: an explicit `options.measurement`, else the first measurement its\n// datasource selects, else undefined (fall back to whatever arrives first).\nexport function primaryMeasurementName(widget: WidgetInstance): string | undefined {\n const explicit = optString(widget.options, 'measurement');\n if (explicit) return explicit;\n const ds = widget.datasource;\n if (ds && ds.measurements.length > 0) return ds.measurements[0];\n return undefined;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// React bindings between the imperative DashboardHub / DOM theme and widget state.\n\nimport type {\n AlarmSubscription,\n CommandSubscription,\n DashboardDefinition,\n DatasourceSelector,\n DeviceResolver,\n EntityCandidateLister,\n LocationSubscription,\n MemberResolver,\n MeasurementSample,\n SelectionCandidate,\n SlotBinding,\n WidgetDataSource,\n} from '@devicechain/dashboards';\nimport type {\n AlarmStreamState,\n CommandStreamState,\n LocationStreamState,\n MeasurementStreamState,\n} from '@devicechain/dashboards';\nimport {\n bindingsWithoutScopedSlots,\n DashboardHub,\n hasScopedSlots,\n resolveContextBindings,\n resolveSlotCandidates,\n} from '@devicechain/dashboards';\nimport { useEffect, useMemo, useRef, useState, type RefObject } from 'react';\n\nimport { useWidgetCandidates, type WidgetCandidates } from './context';\n\n// The channel snapshots moved to @devicechain/dashboards (they are pure data over the hub's\n// row types); re-exported so every import site here keeps resolving.\nexport type { AlarmStreamState, CommandStreamState, LocationStreamState, MeasurementStreamState };\n\n// useDashboardHub gives a host ONE live hub per (resolver, authorities) \u2014 compared by\n// value, so a re-rendered but equal authorities array keeps the hub \u2014 and disposes it when\n// it is replaced or the host unmounts.\n//\n// It deliberately takes no bindings. The hub holds none: the renderer resolves every slot\n// through the settled bindings (resolveWidgetDatasource) before a widget subscribes, so a\n// selection re-subscribes only the widgets bound to the re-pointed slot. A hub rebuilt per\n// selection would tear down every subscription on the board and re-fetch every history.\nexport function useDashboardHub(resolver: DeviceResolver, authorities: string[] | undefined): DashboardHub {\n const authoritiesKey = JSON.stringify(authorities ?? []);\n const hub = useMemo(\n () => new DashboardHub({ resolver, authorities }),\n // authorities read via authoritiesKey (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [resolver, authoritiesKey],\n );\n useEffect(() => () => hub.disposeAll(), [hub]);\n return hub;\n}\n\n// useResolvedBindings runs the scoped-slot cascade (ADR-039 selection amendment) for a\n// host: it takes the synchronous base manifest (effectiveBindings) plus the accumulated\n// selection overlay and returns the settled slot\u2192binding map the host feeds to the\n// renderer. Selection state lives in the host; this hook just derives bindings from it.\n//\n// The common scope-FREE dashboard needs no async work \u2014 the synchronous overlay (base\n// with the selection laid over it) is already correct, and the hook returns it directly,\n// preserving today's behavior. Only a dashboard with scoped slots runs the async pass,\n// guarded by a monotonic generation so a slow members response can't overwrite a newer\n// selection; it keeps the last settled map until the new one arrives (one re-key per\n// walk, never a stale partial). A resolution error is fail-safe: keep the last map.\nexport function useResolvedBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n selection: Record<string, SlotBinding>,\n resolver: MemberResolver,\n): Record<string, SlotBinding> {\n const scoped = useMemo(() => hasScopedSlots(definition), [definition]);\n // Selection wins over base. Correct as-is for root + manual slots and every scope-free\n // dashboard; scoped `first` slots are refined by the async pass below.\n const overlaid = useMemo(() => ({ ...base, ...selection }), [base, selection]);\n // Seed the settled map with scoped slots STRIPPED, so a scoped dashboard's first paint\n // is unbound-not-stale (a scoped slot's default may be out of context) until the cascade\n // derives it. For a scope-free dashboard this equals `overlaid`.\n const [resolved, setResolved] = useState(() => bindingsWithoutScopedSlots(definition, overlaid));\n const genRef = useRef(0);\n\n useEffect(() => {\n // Scope-free: `overlaid` is authoritative. Keep `resolved` synced to it anyway so that\n // if the definition later gains a scope (an authoring edit), the flip to the `resolved`\n // path starts from the CURRENT bindings, not a stale mount-time snapshot.\n if (!scoped) {\n setResolved((prev) => (sameBindings(prev, overlaid) ? prev : overlaid));\n return;\n }\n const gen = ++genRef.current;\n let live = true;\n resolveContextBindings(definition, base, selection, resolver)\n .then((next) => {\n if (!live || gen !== genRef.current) return;\n // Only swap when the value actually changed, so an equal re-resolve doesn't churn\n // a new object reference (and re-render every widget) for nothing.\n setResolved((prev) => (sameBindings(prev, next) ? prev : next));\n })\n .catch(() => {\n /* fail-safe: keep the last settled map */\n });\n return () => {\n live = false;\n };\n }, [scoped, definition, base, selection, resolver, overlaid]);\n\n return scoped ? resolved : overlaid;\n}\n\n// sameBindings is a value compare of two slot\u2192binding maps (stable-key JSON), so the\n// hook can suppress a no-op re-key.\nfunction sameBindings(a: Record<string, SlotBinding>, b: Record<string, SlotBinding>): boolean {\n const ak = Object.keys(a).sort();\n const bk = Object.keys(b).sort();\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i += 1) {\n if (ak[i] !== bk[i]) return false;\n if (JSON.stringify(a[ak[i]]) !== JSON.stringify(b[bk[i]])) return false;\n }\n return true;\n}\n\n// useSlotCandidates builds the ambient candidate provider a host passes to the renderer\n// (ADR-039 selection amendment): a function slot\u2192candidates that a context/entity-selector\n// widget calls. It closes over the CURRENT resolved bindings, so it is rebuilt whenever a\n// binding changes \u2014 a selector's option set (a scoped child's members) then follows the\n// parent for free, and a widget re-fetches because the provider identity changed. Returns\n// undefined when no lister is supplied (a host that wires no selection), so the selector\n// stays inert. Keyed by the bindings' VALUE so an equal-but-new bindings object doesn't\n// churn the provider (and every open selector's fetch) each render.\nexport function useSlotCandidates(\n definition: DashboardDefinition,\n bindings: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister: EntityCandidateLister | undefined,\n): WidgetCandidates | undefined {\n const bindingsKey = JSON.stringify(bindings);\n return useMemo(\n () => {\n if (!lister) return undefined;\n return (slot: string) => resolveSlotCandidates(definition, slot, bindings, resolver, lister);\n },\n // bindings read via bindingsKey (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [definition, bindingsKey, resolver, lister],\n );\n}\n\n// The state a selector widget renders from: the target slot's current candidate options,\n// plus load/error flags. `wired` reports whether an ambient candidate provider exists \u2014 a\n// selector feature-detects on it (with the select callback) to stay inert where selection\n// isn't wired (edit/preview, a host that supplies no lister) rather than showing a dead picker.\nexport interface CandidatesState {\n candidates: SelectionCandidate[];\n loading: boolean;\n error: unknown | null;\n wired: boolean;\n}\n\n// useCandidates resolves the options a selector widget offers for `slot` through the ambient\n// candidate provider. Re-fetches when the provider (i.e. the resolved bindings) or the slot\n// changes, guarded by a monotonic generation so a slow list can't overwrite a newer one\n// (the parent-switch-then-open race). Fail-safe: an error yields an empty option set.\nexport function useCandidates(slot: string | undefined): CandidatesState {\n const provider = useWidgetCandidates();\n const wired = !!provider;\n // Seed loading true when a fetch is imminent (provider + slot present) so the first paint\n // reads \"Loading\u2026\", not a spurious \"No options\" for the frame before the effect runs.\n const [state, setState] = useState<Omit<CandidatesState, 'wired'>>(() => ({\n candidates: [],\n loading: Boolean(provider && slot),\n error: null,\n }));\n const genRef = useRef(0);\n\n useEffect(() => {\n if (!provider || !slot) {\n setState({ candidates: [], loading: false, error: null });\n return;\n }\n const gen = ++genRef.current;\n let live = true;\n // Clear any prior error when a fresh (healthy) fetch starts, so a retry doesn't show\n // \"Loading\u2026\" and a stale error banner together.\n setState((prev) => ({ ...prev, loading: true, error: null }));\n provider(slot)\n .then((candidates) => {\n if (live && gen === genRef.current) setState({ candidates, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (live && gen === genRef.current) setState({ candidates: [], loading: false, error });\n });\n return () => {\n live = false;\n };\n }, [provider, slot]);\n\n return { ...state, wired };\n}\n\nexport interface MeasurementStreamOptions {\n // Max samples retained in the rolling window (oldest dropped past this).\n window?: number;\n // Historical samples to seed the window with before live data arrives (from a\n // bucketedMeasurements backfill). Assumed chronological and older than the live\n // tail; live samples for the same measurement name override the seed's latest.\n initialSamples?: MeasurementSample[];\n}\n\nconst EMPTY: MeasurementStreamState = { latest: {}, samples: [], error: null };\n\n// Build a name\u2192last-sample map from a chronological list (last occurrence wins).\nfunction latestOf(samples: MeasurementSample[]): Record<string, MeasurementSample> {\n const latest: Record<string, MeasurementSample> = {};\n for (const s of samples) latest[s.name] = s;\n return latest;\n}\n\n// useMeasurementStream subscribes a widget's datasource through the hub and keeps\n// the latest value per measurement plus a bounded rolling window. It re-subscribes\n// when the datasource changes (compared by value, so a re-rendered-but-equal\n// selector doesn't churn the subscription) and tears down on unmount. When\n// initialSamples is given, it is merged ahead of the live tail so a chart shows\n// history immediately (and can arrive after the live stream opens).\nexport function useMeasurementStream(\n hub: WidgetDataSource,\n datasource: DatasourceSelector | undefined,\n options: MeasurementStreamOptions = {},\n): MeasurementStreamState {\n const windowSize = options.window ?? 300;\n const initialSamples = options.initialSamples;\n\n // Value-compare the selector so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = datasource ? JSON.stringify(datasource) : null;\n\n // The live buffer is TAGGED with the selector it was filled from. The effect below\n // resets it when the selector changes, but an effect runs after the render that\n // carried the new selector \u2014 so without the tag that render would show the previous\n // device's values under the new one. A buffer whose tag is not the current key is\n // treated as empty, synchronously.\n const [tagged, setTagged] = useState<{ key: string | null; state: MeasurementStreamState }>({\n key,\n state: EMPTY,\n });\n const live = tagged.key === key ? tagged.state : EMPTY;\n\n useEffect(() => {\n setTagged({ key, state: EMPTY }); // reset the live buffer whenever the datasource changes (or clears)\n if (!datasource) return;\n\n // A callback for a selector the buffer has already moved past (a sink that answers\n // after its subscription was released) leaves the buffer alone rather than re-tagging\n // it to the old selector, which would blank the current one's values.\n const update = (fn: (prev: MeasurementStreamState) => MeasurementStreamState) =>\n setTagged((prev) => (prev.key === key ? { key, state: fn(prev.state) } : prev));\n\n return hub.subscribeWidget(datasource, {\n next: (sample) =>\n update((prev) => {\n const samples = prev.samples.concat(sample);\n if (samples.length > windowSize) samples.splice(0, samples.length - windowSize);\n return { latest: { ...prev.latest, [sample.name]: sample }, samples, error: null };\n }),\n error: (err) => update((prev) => ({ ...prev, error: err })),\n });\n // `datasource` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key, windowSize]);\n\n // Layer the history seed under the live tail. Kept separate from the live buffer\n // so late-arriving history (async backfill) recomputes without touching the\n // subscription, and switching datasource clears live while the new seed applies.\n return useMemo(() => {\n if (!initialSamples || initialSamples.length === 0) return live;\n const merged = initialSamples.concat(live.samples);\n const samples =\n merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged;\n return {\n latest: { ...latestOf(initialSamples), ...live.latest },\n samples,\n error: live.error,\n };\n }, [initialSamples, live, windowSize]);\n}\n\nconst EMPTY_ALARMS: AlarmStreamState = { alarms: [], total: 0, loading: true, error: null };\n\n// useAlarmStream binds an alarm widget's scope+filters through the hub's alarm channel\n// and keeps the latest snapshot. It re-subscribes when the subscription changes\n// (value-compared, so a re-rendered-but-equal object doesn't churn) and tears down on\n// unmount. The hub delivers whole snapshots (query-then-reconcile), so this holds the\n// last one rather than accumulating \u2014 the query is the source of truth.\nexport function useAlarmStream(\n hub: WidgetDataSource,\n subscription: AlarmSubscription,\n): AlarmStreamState {\n const [state, setState] = useState<AlarmStreamState>(EMPTY_ALARMS);\n\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n\n useEffect(() => {\n setState(EMPTY_ALARMS); // reset to loading whenever the subscription changes\n return hub.subscribeAlarms(subscription, {\n next: (snapshot) =>\n setState({ alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }),\n error: (err) => setState((prev) => ({ ...prev, loading: false, error: err })),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\nconst EMPTY_COMMANDS: CommandStreamState = {\n deviceToken: null,\n commands: [],\n total: 0,\n loading: true,\n error: null,\n};\n\n// useCommandStream binds a command widget's scope through the hub's control channel and\n// keeps the latest command-history snapshot. It re-subscribes when the subscription\n// changes (value-compared) and tears down on unmount. The hub delivers whole snapshots\n// (poll-then-emit), so this holds the last one rather than accumulating.\nexport function useCommandStream(\n hub: WidgetDataSource,\n subscription: CommandSubscription,\n): CommandStreamState {\n const [state, setState] = useState<CommandStreamState>(EMPTY_COMMANDS);\n\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n\n useEffect(() => {\n setState(EMPTY_COMMANDS); // reset to loading whenever the subscription changes\n return hub.subscribeCommands(subscription, {\n next: (snapshot) =>\n setState({\n deviceToken: snapshot.deviceToken,\n commands: snapshot.commands,\n total: snapshot.total,\n loading: false,\n error: null,\n }),\n error: (err) => setState((prev) => ({ ...prev, loading: false, error: err })),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\nconst EMPTY_LOCATIONS: LocationStreamState = {\n locations: [],\n deviceTokens: [],\n forbidden: false,\n loading: true,\n error: null,\n};\n\n// useLocationStream binds a map widget's selector through the hub's location channel and\n// keeps the latest position snapshot. It re-subscribes when the subscription changes\n// (value-compared, so a re-rendered-but-equal object doesn't churn) and tears down on\n// unmount. The hub delivers whole snapshots (poll-then-emit), so this holds the last one\n// rather than accumulating.\n//\n// A `forbidden` snapshot CLEARS the positions as it sets the flag: an authority can be\n// revoked mid-session, and keeping the last-seen coordinates on screen under a\n// permission banner would leave a refused viewer looking at the very data they were\n// just refused.\nexport function useLocationStream(\n hub: WidgetDataSource,\n subscription: LocationSubscription,\n): LocationStreamState {\n const [state, setState] = useState<LocationStreamState>(EMPTY_LOCATIONS);\n\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n\n useEffect(() => {\n setState(EMPTY_LOCATIONS); // reset to loading whenever the subscription changes\n return hub.subscribeLocations(subscription, {\n next: (snapshot) =>\n setState(\n snapshot.kind === 'forbidden'\n ? { locations: [], deviceTokens: [], forbidden: true, loading: false, error: null }\n : {\n locations: snapshot.locations,\n deviceTokens: snapshot.deviceTokens,\n forbidden: false,\n loading: false,\n error: null,\n },\n ),\n error: (err) => setState((prev) => ({ ...prev, loading: false, error: err })),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\n// A widget's bound-entity availability: 'unknown' until the async check resolves\n// (render optimistically meanwhile), then 'available', or 'unavailable' when the bound\n// device no longer exists (a deleted device's stable token, ADR-044).\nexport type DatasourceAvailability = 'unknown' | 'available' | 'unavailable';\n\n// While a widget shows \"unavailable\", re-check on this cadence so a device deleted then\n// recreated with the same token (ADR-042 frees tokens on delete) recovers on a long-lived\n// viewer without a reload. Only runs while unavailable \u2014 a live device is checked once.\nconst AVAILABILITY_REVALIDATE_MS = 60_000;\n\n// useDatasourceAvailability resolves whether a widget's bound device still exists,\n// WITHOUT blocking the widget's data stream (optimistic + async): it starts 'unknown'\n// (the widget renders normally), then flips to 'available'/'unavailable' once the hub's\n// existence check resolves. Fails open (\u2192'available') so a check outage never falsely\n// blanks a live widget. A widget with no datasource is trivially available (no query).\n// Re-checks when the datasource changes (value-compared), and periodically while\n// unavailable so a recreated device recovers.\nexport function useDatasourceAvailability(\n hub: WidgetDataSource,\n datasource: DatasourceSelector | undefined,\n): DatasourceAvailability {\n const [state, setState] = useState<DatasourceAvailability>('unknown');\n const key = datasource ? JSON.stringify(datasource) : null;\n\n useEffect(() => {\n // No datasource (label/image, or tenant-wide) has nothing to validate \u2014 available\n // immediately, no query, no re-render churn.\n if (!datasource) {\n setState('available');\n return;\n }\n setState('unknown');\n let cancelled = false;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const stopTimer = (): void => {\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n\n const check = (): void => {\n hub\n .isDatasourceAvailable(datasource)\n .then((ok) => {\n if (cancelled) return;\n setState(ok ? 'available' : 'unavailable');\n if (ok) stopTimer(); // recovered (or was fine) \u2014 stop polling\n })\n .catch(() => {\n if (cancelled) return;\n setState('available'); // fail open\n stopTimer();\n });\n };\n\n check();\n // Poll only matters once unavailable; harmlessly runs until the first check clears it\n // for a live device (cleared in the resolver above, well before the first tick).\n timer = setInterval(check, AVAILABILITY_REVALIDATE_MS);\n\n return () => {\n cancelled = true;\n stopTimer();\n };\n // `datasource` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\nexport interface ElementSize {\n width: number;\n height: number;\n}\n\n// useElementSize tracks an element's content-box size via ResizeObserver, so a\n// widget can scale its contents (chart fonts, gauge ticks, a big number) to the\n// slot it was resized to \u2014 ECharts' resize() re-lays-out the canvas but never\n// scales absolute font sizes, so a small widget crams without this. Returns\n// {0,0} until measured (and where ResizeObserver is unavailable, e.g. jsdom); the\n// consumers treat a zero size as \"use default sizing\".\nexport function useElementSize<T extends HTMLElement>(): [RefObject<T | null>, ElementSize] {\n const ref = useRef<T>(null);\n const [size, setSize] = useState<ElementSize>({ width: 0, height: 0 });\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (!rect) return;\n setSize((prev) =>\n prev.width === rect.width && prev.height === rect.height\n ? prev\n : { width: rect.width, height: rect.height },\n );\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, []);\n\n return [ref, size];\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// @devicechain/widget-sdk \u2014 the supported surface for writing a dashboard widget.\n//\n// A widget is declared once with `defineWidget` (a portable, JSON-only descriptor plus a\n// trusted React component) and made available to a host by listing it in\n// `createWidgetRegistry`. The built-in widgets are declared through exactly this\n// interface. The descriptor vocabulary is React-free and lives in @devicechain/dashboards;\n// it is re-exported here so a widget package imports from one place.\n\nexport { defineWidget } from './define-widget';\nexport type { ErasedComponent, ErasedWidgetDefinition, WidgetDefinition, WidgetImplementation } from './define-widget';\nexport { createWidgetRegistry, WidgetRegistryError, type WidgetRegistry } from './registry';\nexport type {\n EditorCommandChoice,\n EditorCommandChoices,\n WidgetComponent,\n WidgetEditorProps,\n WidgetEditorServices,\n WidgetProps,\n} from './widget';\n\n// The ambient host contexts (selection, candidates, time zone, theme root, subject). Defined\n// in this package and nowhere else \u2014 see ./context.\nexport {\n WidgetCandidatesProvider,\n WidgetSelectProvider,\n WidgetSubjectProvider,\n WidgetThemeRootProvider,\n WidgetTimeZoneProvider,\n useWidgetCandidates,\n useWidgetSelect,\n useWidgetSubject,\n useWidgetThemeRoot,\n useWidgetTimeZone,\n type WidgetCandidates,\n type WidgetSelect,\n} from './context';\n\n// The building blocks every widget (built-in or third-party) composes from: the card frame,\n// theme-variable references, number/time formatting, the opt-in change-flash cue, option\n// readers, and the React bindings between the hub's channels and widget state. The chart\n// engine helpers are NOT here -- they are on the `/echarts` subpath, so importing this root\n// never pulls Apache ECharts (nor MapLibre, nor the built-in catalog).\nexport { WidgetFrame, type WidgetFrameProps } from './frame';\nexport { css, type ThemeVar } from './theme';\nexport { formatDateTime, formatTimestamp, formatValue } from './format';\nexport {\n FlashValue,\n flashBackgroundStyle,\n flashTextStyle,\n useFlashOnChange,\n type FlashDirection,\n} from './flash';\nexport { optBoolean, optNumber, optString, pickSample, primaryMeasurementName } from './widget-options';\nexport {\n useAlarmStream,\n useCandidates,\n useCommandStream,\n useDashboardHub,\n useDatasourceAvailability,\n useElementSize,\n useLocationStream,\n useMeasurementStream,\n useResolvedBindings,\n useSlotCandidates,\n type AlarmStreamState,\n type CandidatesState,\n type CommandStreamState,\n type DatasourceAvailability,\n type ElementSize,\n type LocationStreamState,\n type MeasurementStreamOptions,\n type MeasurementStreamState,\n} from './hooks';\n\n// The portable descriptor vocabulary.\nexport {\n SDK_API_VERSION,\n WidgetCatalogError,\n defineWidgetDescriptor,\n isBuiltinWidgetId,\n isNamespacedWidgetId,\n validateWidgetDescriptor,\n type AckOptionRef,\n type BooleanOptionSpec,\n type BuiltinWidgetId,\n type ChannelBindingPolicy,\n type ChannelSnapshot,\n type ConstBinding,\n type DatasourcePolicy,\n type DatasourceSelectorKind,\n type DefaultsOf,\n type DescriptorIssue,\n type EnumOptionSpec,\n type GreaterThanConstraint,\n type NamespacedWidgetId,\n type NumberOptionRef,\n type NumberOptionSpec,\n type OptionConstraint,\n type OptionSpec,\n type OptionsOf,\n type StringOptionRef,\n type StringOptionSpec,\n type WidgetCatalog,\n type WidgetChannel,\n type WidgetDescriptor,\n type WidgetDisplay,\n type WidgetId,\n type WidgetInteractionDeclaration,\n type WidgetOptionSpecs,\n} from '@devicechain/dashboards';\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": ["jsx", "useEffect", "
|
|
3
|
+
"sources": ["../src/define-widget.ts", "../src/registry.ts", "../src/frame.tsx", "../src/freshness-readout.tsx", "../src/widget-options.ts", "../src/freshness.ts", "../src/theme.ts", "../src/tokens.tsx", "../src/hooks.ts", "../src/scale.ts", "../src/connection-badge.tsx", "../src/flash.tsx", "../src/index.ts"],
|
|
4
|
+
"sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport type {\n ChannelSnapshot,\n OptionsOf,\n WidgetChannel,\n WidgetDescriptor,\n WidgetOptionSpecs,\n} from '@devicechain/dashboards';\nimport type { ComponentType } from 'react';\n\nimport type { WidgetEditorProps, WidgetProps } from './widget';\n\n// The trusted half of a registration: the React code. Kept apart from the descriptor so\n// the descriptor stays portable plain data and headless validators never load a component.\nexport interface WidgetImplementation<C extends WidgetChannel, S extends WidgetOptionSpecs> {\n // The only execution kind in this release: React code the host bundled at build time,\n // running with the host page's privileges.\n kind: 'trusted-react';\n component: ComponentType<WidgetProps<ChannelSnapshot<C>>>;\n editor?: ComponentType<WidgetEditorProps<OptionsOf<S>>>;\n}\n\nexport interface WidgetDefinition<\n C extends WidgetChannel = WidgetChannel,\n S extends WidgetOptionSpecs = WidgetOptionSpecs,\n> {\n descriptor: WidgetDescriptor<C, S>;\n implementation: WidgetImplementation<C, S>;\n}\n\n// A component with its props type erased. `any` is deliberate and confined to this alias:\n// `ComponentType<never>` would be the principled erasure, but a class component's\n// `defaultProps` is covariant in its props and rejects it, and a registry has to hold\n// components of every channel's props.\n// eslint-disable-next-line @typescript-eslint/no-explicit-any\nexport type ErasedComponent = ComponentType<any>;\n\n// A definition with its component types erased, which is what a registry stores: any\n// specific WidgetDefinition<C, S> is assignable to it.\nexport interface ErasedWidgetDefinition {\n descriptor: WidgetDescriptor;\n implementation: {\n kind: 'trusted-react';\n component: ErasedComponent;\n editor?: ErasedComponent;\n };\n}\n\n// defineWidget is an identity at runtime; it exists for inference. The channel and the\n// option schema are inferred from the literal, so a component whose data disagrees with\n// the channel, defaults that disagree with the schema, a binding that names a missing or\n// wrong-kind option, or an interaction the channel cannot carry is a COMPILE error \u2014 and\n// createWidgetRegistry checks the same things again at runtime for callers that bypass the\n// compiler. Every widget, built-in or third-party, is declared through this one helper.\nexport function defineWidget<const C extends WidgetChannel, const S extends WidgetOptionSpecs>(\n definition: WidgetDefinition<C, S>,\n): WidgetDefinition<C, S> {\n return definition;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\nimport {\n WidgetCatalogError,\n collectCatalogIssues,\n createWidgetCatalog,\n type DescriptorIssue,\n type WidgetCatalog,\n type WidgetDescriptor,\n} from '@devicechain/dashboards';\n\nimport type { ErasedWidgetDefinition } from './define-widget';\n\n// Thrown by createWidgetRegistry with EVERY problem found, so a host fixes its composition\n// in one pass instead of one failed start per fault.\nexport class WidgetRegistryError extends WidgetCatalogError {\n constructor(issues: readonly DescriptorIssue[]) {\n super(issues, 'widget registry');\n this.name = 'WidgetRegistryError';\n }\n}\n\n// A sealed registry: descriptors (through the React-free catalog the parser sees) plus the\n// trusted implementations. Immutable \u2014 there is no register(), no process-global state and\n// no replacement while a dashboard is mounted. Two registries are independent.\nexport interface WidgetRegistry {\n // The React-free view of this registry, for parsing and headless validation.\n readonly catalog: WidgetCatalog;\n descriptor(id: string): WidgetDescriptor | undefined;\n implementation(id: string): ErasedWidgetDefinition['implementation'] | undefined;\n // Definitions in the order they were supplied.\n list(): readonly ErasedWidgetDefinition[];\n}\n\nfunction isComponent(value: unknown): boolean {\n if (typeof value === 'function') return true;\n // memo / forwardRef / lazy components are objects tagged with $$typeof.\n return typeof value === 'object' && value !== null && '$$typeof' in value;\n}\n\ntype Loose = Partial<ErasedWidgetDefinition> | null | undefined;\n\n// createWidgetRegistry validates every definition (descriptor invariants, duplicate ids,\n// the implementation shape) and returns a deeply frozen registry, or throws one\n// WidgetRegistryError listing all the problems.\nexport function createWidgetRegistry(definitions: readonly ErasedWidgetDefinition[]): WidgetRegistry {\n if (!Array.isArray(definitions)) {\n throw new WidgetRegistryError([{ id: '#?', path: '', message: 'definitions must be an array' }]);\n }\n const issues: DescriptorIssue[] = collectCatalogIssues(definitions.map((d) => (d as Loose)?.descriptor));\n\n definitions.forEach((definition, index) => {\n const id: unknown = (definition as Loose)?.descriptor?.id;\n const label = typeof id === 'string' && id !== '' ? id : `#${index}`;\n const add = (path: string, message: string): void => {\n issues.push({ id: label, path, message });\n };\n const impl = (definition as Loose)?.implementation;\n if (typeof impl !== 'object' || impl === null) {\n add('implementation', 'a definition needs an implementation');\n return;\n }\n if (impl.kind !== 'trusted-react') {\n add('implementation.kind', `kind must be \"trusted-react\", got ${JSON.stringify(impl.kind)}`);\n }\n if (!isComponent(impl.component)) add('implementation.component', 'component must be a React component');\n if (impl.editor !== undefined && !isComponent(impl.editor)) {\n add('implementation.editor', 'editor, when given, must be a React component');\n }\n });\n\n if (issues.length > 0) throw new WidgetRegistryError(issues);\n\n // Through the one public, validating constructor: no unvalidated path to a catalog exists.\n // The descriptors were already checked above (so this cannot throw for them); checking\n // again is the price of not exporting a catalog builder that skips validation.\n //\n // \uD83D\uDD34 The catalog freezes each descriptor IN PLACE (deeply), so the caller's descriptor\n // objects are frozen after this call. That is deliberate: a built-in descriptor holds its\n // option schema BY IDENTITY with WIDGET_OPTIONS, and a copy would break that identity.\n const catalog = createWidgetCatalog(definitions.map((d) => d.descriptor));\n const byId = new Map<string, ErasedWidgetDefinition>();\n for (const definition of definitions) {\n // The implementation object, unlike the descriptor, is copied before freezing, so the\n // caller's implementation object is left mutable (the components it names are shared).\n byId.set(\n definition.descriptor.id,\n Object.freeze({\n descriptor: catalog.get(definition.descriptor.id) as WidgetDescriptor,\n implementation: Object.freeze({ ...definition.implementation }),\n }),\n );\n }\n const ordered: readonly ErasedWidgetDefinition[] = Object.freeze([...byId.values()]);\n\n return Object.freeze({\n catalog,\n descriptor: (id: string) => catalog.get(id),\n implementation: (id: string) => byId.get(id)?.implementation,\n list: () => ordered,\n });\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The shared card frame every widget renders inside \u2014 a bordered, themed surface\n// that fills its dashboard-canvas slot, with an optional title bar. Styled with\n// inline CSS-variable references so it themes from the host without Tailwind.\n//\n// The frame is SIZE-AWARE. Its content column -- the padded box the widget draws in -- is a\n// `container-type: size` query container, so HTML text can size itself with `cq` units on the\n// first frame, and the SAME box is measured (quantised to 4 px) and handed to everything inside\n// through context (useWidgetScale), so a canvas widget fits to the identical numbers. The header\n// gives way to the body as the widget shrinks: the subtitle goes first, then the title.\n//\n// What the card LOOKS like is the board's to decide (the frame a definition sets on its canvas\n// and per widget, resolved by the renderer and read here through useWidgetFrameStyle): a card, a\n// borderless flat panel, or nothing at all; its border width, radius and colour; its padding;\n// its background; and whether the header shows. Every value is a token. A widget mounted outside\n// a board gets the card.\n\nimport { resolveFrame, sameLabel, type FrameRadius, type ResolvedFrame, type SpaceTokenName } from '@devicechain/dashboards';\nimport { useMemo, type CSSProperties, type ReactNode } from 'react';\n\nimport {\n useWidgetDensityScale,\n useWidgetFrameStyle,\n useWidgetFreshness,\n useWidgetSubject,\n WidgetBodySizeProvider,\n} from './context';\nimport { FreshnessReadout, StaleBadge, useFreshness } from './freshness-readout';\nimport { useElementSize } from './hooks';\nimport { css } from './theme';\nimport { makeWidgetScale, type WidgetScale } from './scale';\nimport { fontVar, spaceVar, type SpaceToken } from './tokens';\n\n// Below either of these (px, content box) the age readout is dropped so a small widget keeps\n// its room for the value; a stale widget still shows its badge, in the corner.\nconst FRESHNESS_MIN_WIDTH = 150;\nconst FRESHNESS_MIN_HEIGHT = 110;\n\n// Frame heights (px at density 1, multiplied by the density scale) below which the header gives\n// way. The subtitle is the first thing to go; the title holds until there is hardly room for a\n// value at all. A dropped title stays available to assistive tech as the frame's aria-label.\nexport const FRAME_SUBTITLE_MIN_HEIGHT = 120;\nexport const FRAME_TITLE_MIN_HEIGHT = 76;\n\n// The content column's size is quantised to this many px before it reaches a widget.\nconst BODY_SIZE_STEP = 4;\n\n// The card a widget gets when no board says otherwise.\nconst DEFAULT_FRAME: ResolvedFrame = resolveFrame(undefined, undefined);\n\nconst RADIUS: Record<FrameRadius, string> = {\n none: '0px',\n sm: 'calc(var(--radius, 8px) / 2)',\n md: 'var(--radius, 8px)',\n lg: 'calc(var(--radius, 8px) * 1.5)',\n};\n\nfunction spaceTokenVar(token: SpaceTokenName): string {\n return token === 'space-0' ? '0px' : spaceVar(Number(token.slice('space-'.length)) as SpaceToken);\n}\n\nexport interface WidgetFrameProps {\n title?: string;\n // An explicit subject line; when omitted the frame falls back to the resolved-entity\n // subject from WidgetSubjectContext.\n subtitle?: string;\n // The widget's content. A function child receives the frame's measured scale (size class,\n // content-column size, token px), for a widget whose own render needs it: a component\n // rendered INSIDE the frame can call useWidgetScale() instead.\n children: ReactNode | ((scale: WidgetScale) => ReactNode);\n // Extra style for the body region.\n bodyStyle?: CSSProperties;\n // Remove the content column's padding, for a widget that fills its box edge to edge (a chart\n // canvas, a map, an image). The column is still the measured, queryable box.\n flush?: boolean;\n // The occurredTime of the ONE sample this widget is showing, for a widget that shows a single\n // value (null while it has none). When set, the age readout ages exactly that sample; left\n // undefined, it ages the newest sample the host holds for the widget. A single-value widget\n // must pass it: on a device reporting several measurements, the newest is often not the one\n // on screen, and the age of another measurement would make a frozen value look live.\n sampleTime?: string | null;\n}\n\n// The epoch-ms time of a sample timestamp, or undefined when it has none or is unparseable.\nfunction parseSampleTime(time: string | null): number | undefined {\n if (!time) return undefined;\n const at = Date.parse(time);\n return Number.isNaN(at) ? undefined : at;\n}\n\nexport function WidgetFrame({ title, subtitle, children, bodyStyle, flush, sampleTime }: WidgetFrameProps) {\n const subject = useWidgetSubject();\n // A subtitle that only repeats the title says nothing (a card titled by its device's\n // external id, under a subject line naming the same device).\n const rawSub = subtitle ?? subject;\n const dupSub = rawSub && sameLabel(rawSub, title) ? undefined : rawSub;\n\n // Present only for a widget whose descriptor opts in. Stale (a known threshold, passed) greys\n // the value out; with no threshold the age is shown and nothing is ever greyed.\n const freshness = useWidgetFreshness();\n const at = freshness === undefined ? undefined : sampleTime === undefined ? freshness.at : parseSampleTime(sampleTime);\n const reading = useFreshness(at, freshness?.staleAfterSeconds);\n const [rootRef, size] = useElementSize<HTMLDivElement>();\n const [columnRef, columnSize] = useElementSize<HTMLDivElement>(BODY_SIZE_STEP);\n const scale = useWidgetDensityScale();\n const frame = useWidgetFrameStyle() ?? DEFAULT_FRAME;\n // Unmeasured (first frame, jsdom) shows everything; the cq path needs no measure to be right.\n // `show` keeps the header whatever the size, `hide` drops it whatever the size.\n const measured = size.height > 0;\n const header = frame.header;\n const showSub = header === 'show' || (header === 'auto' && (!measured || size.height >= FRAME_SUBTITLE_MIN_HEIGHT * scale));\n const showTitle = header === 'show' || (header === 'auto' && (!measured || size.height >= FRAME_TITLE_MIN_HEIGHT * scale));\n const rule = frame.border.width > 0 ? `${frame.border.width}px solid ${css(frame.border.color)}` : 'none';\n const sub = showSub ? dupSub : undefined;\n const shownTitle = showTitle ? title : undefined;\n const widgetScale = useMemo(() => makeWidgetScale(columnSize, scale), [columnSize, scale]);\n const compact = size.width > 0 && (size.width < FRESHNESS_MIN_WIDTH || size.height < FRESHNESS_MIN_HEIGHT);\n const stale = reading?.stale === true;\n\n return (\n <div\n ref={rootRef}\n data-stale={stale ? 'true' : undefined}\n role={title && !showTitle ? 'group' : undefined}\n aria-label={title && !showTitle ? title : undefined}\n style={{\n position: 'relative',\n display: 'flex',\n flexDirection: 'column',\n width: '100%',\n height: '100%',\n boxSizing: 'border-box',\n background: frame.background === 'transparent' ? 'transparent' : css(frame.background),\n color: css('card-foreground'),\n border: rule,\n borderRadius: RADIUS[frame.border.radius],\n overflow: 'hidden',\n }}\n >\n {shownTitle || sub ? (\n <div\n style={{\n padding: `${spaceVar(2)} ${spaceVar(3)}`,\n borderBottom: rule,\n }}\n >\n {shownTitle ? (\n <div\n title={shownTitle}\n style={{\n fontSize: fontVar('sm'),\n fontWeight: 600,\n color: css('muted-foreground'),\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {shownTitle}\n </div>\n ) : null}\n {sub ? (\n <div\n title={sub}\n style={{\n fontSize: fontVar('xs'),\n fontWeight: 400,\n color: css('muted-foreground'),\n opacity: 0.75,\n marginTop: shownTitle ? 2 : 0,\n whiteSpace: 'nowrap',\n overflow: 'hidden',\n textOverflow: 'ellipsis',\n }}\n >\n {sub}\n </div>\n ) : null}\n </div>\n ) : null}\n {/* The body clips: the card root already stops a widget painting outside the card, and\n this stops one that outgrows its slot painting up over the title bar. minWidth 0\n lets it shrink inside the flex column. */}\n <div\n data-widget-body=\"\"\n style={{\n position: 'relative',\n flex: 1,\n minHeight: 0,\n minWidth: 0,\n overflow: 'hidden',\n ...bodyStyle,\n ...(stale ? { opacity: 0.45, filter: 'grayscale(1)' } : null),\n }}\n >\n {/* The content column: absolutely filling the body so its size never depends on its\n children (a size container must not), padded by the frame so every widget gets the\n same inset, and the box both the cq units and the measured size describe. */}\n <div\n ref={columnRef}\n data-widget-content=\"\"\n style={{\n position: 'absolute',\n inset: 0,\n boxSizing: 'border-box',\n containerType: 'size',\n overflow: 'hidden',\n padding: flush ? 0 : spaceTokenVar(frame.padding),\n }}\n >\n <WidgetBodySizeProvider size={columnSize}>\n {typeof children === 'function' ? children(widgetScale) : children}\n </WidgetBodySizeProvider>\n </div>\n </div>\n {reading && !compact ? (\n <div\n data-widget-freshness=\"\"\n style={{ padding: `${spaceVar(1)} ${spaceVar(3)}`, borderTop: rule, flex: '0 0 auto' }}\n >\n <FreshnessReadout reading={reading} />\n </div>\n ) : null}\n {reading && compact && stale ? (\n <div style={{ position: 'absolute', top: spaceVar(1), right: spaceVar(1) }}>\n <StaleBadge />\n </div>\n ) : null}\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The age readout: \"12 sec. ago\", ticking once a second, plus a \"stale\" badge when -- and only\n// when -- a threshold is known and passed. The frame draws it for any widget whose descriptor\n// opts in (`freshness: true`); a custom widget that wants it somewhere else can render\n// <Freshness> itself.\n\nimport { useEffect, useState, type CSSProperties } from 'react';\n\nimport { ageSeconds, formatAge, freshnessStrings, isStale } from './freshness';\nimport { css } from './theme';\nimport { fontVar } from './tokens';\n\n// One interval per period, shared by every subscriber: a board of two hundred widgets ticks\n// once a second, not two hundred times, and every readout on it moves together. The timer\n// exists only while something is subscribed.\ninterface Ticker {\n timer: ReturnType<typeof setInterval>;\n listeners: Set<(now: number) => void>;\n}\nconst tickers = new Map<number, Ticker>();\n\nfunction subscribeTick(everyMs: number, listener: (now: number) => void): () => void {\n let ticker = tickers.get(everyMs);\n if (!ticker) {\n const listeners = new Set<(now: number) => void>();\n const timer = setInterval(() => {\n const now = Date.now();\n for (const l of [...listeners]) l(now);\n }, everyMs);\n ticker = { timer, listeners };\n tickers.set(everyMs, ticker);\n }\n ticker.listeners.add(listener);\n const owned = ticker;\n return () => {\n owned.listeners.delete(listener);\n if (owned.listeners.size === 0 && tickers.get(everyMs) === owned) {\n clearInterval(owned.timer);\n tickers.delete(everyMs);\n }\n };\n}\n\n// useNow re-renders its caller every `everyMs` with the current time, off the shared ticker\n// for that period. One second is the finest unit the readout shows.\nexport function useNow(everyMs = 1000, enabled = true): number {\n const [now, setNow] = useState(() => Date.now());\n useEffect(() => {\n if (!enabled) return;\n setNow(Date.now());\n return subscribeTick(everyMs, setNow);\n }, [everyMs, enabled]);\n return now;\n}\n\nexport interface FreshnessReading {\n age: number;\n stale: boolean;\n}\n\n// useFreshness: the age of `at` (epoch ms) now, and whether it is stale against the threshold.\n// Undefined while there is no sample to age.\n//\n// \uD83D\uDD34 A sample time AHEAD of the viewer's clock (a device clock running fast) is aged from when\n// this viewer first saw it, not from the time it claims. Clamping the age at zero alone would\n// read \"now\" for as long as the device is ahead -- an hour-fast clock would keep a frozen\n// value looking live for an hour, which is exactly what the readout exists to prevent.\nexport function useFreshness(at: number | undefined, staleAfterSeconds: number | undefined): FreshnessReading | undefined {\n // No sample, no clock: a frame that does not opt in must not tick once a second.\n const now = useNow(1000, at !== undefined);\n // When each distinct sample time was first seen here; adjusted during render (React's\n // \"storing information from previous renders\" pattern) so the first paint is already right.\n const [seen, setSeen] = useState<{ at: number; seenAt: number } | undefined>(undefined);\n let current = seen;\n if (at !== undefined && seen?.at !== at) {\n current = { at, seenAt: Math.max(now, Date.now()) };\n setSeen(current);\n }\n if (at === undefined || current === undefined) return undefined;\n const age = ageSeconds(Math.min(at, current.seenAt), now);\n return { age, stale: isStale(age, staleAfterSeconds) };\n}\n\nexport function StaleBadge({ label }: { label?: string }) {\n return (\n <span\n data-stale-badge=\"\"\n style={{\n fontSize: fontVar('xs'),\n fontWeight: 600,\n textTransform: 'uppercase',\n letterSpacing: 0.4,\n padding: '0 5px',\n borderRadius: 4,\n border: `1px dashed ${css('muted-foreground')}`,\n color: css('foreground'),\n background: css('muted'),\n whiteSpace: 'nowrap',\n }}\n >\n {label ?? freshnessStrings().stale}\n </span>\n );\n}\n\nexport interface FreshnessProps {\n // The newest sample's time, epoch ms. Nothing renders without one.\n at: number | undefined;\n // The age past which the value is stale. Omit when unknown: the age is shown and nothing is\n // styled stale.\n staleAfterSeconds?: number;\n // Override the badge word (a host in a language the SDK does not ship).\n staleLabel?: string;\n style?: CSSProperties;\n}\n\nexport function Freshness({ at, staleAfterSeconds, staleLabel, style }: FreshnessProps) {\n const reading = useFreshness(at, staleAfterSeconds);\n if (!reading) return null;\n return <FreshnessReadout reading={reading} staleLabel={staleLabel} style={style} />;\n}\n\nexport function FreshnessReadout({\n reading,\n staleLabel,\n style,\n}: {\n reading: FreshnessReading;\n staleLabel?: string;\n style?: CSSProperties;\n}) {\n return (\n <span\n data-freshness=\"\"\n data-stale={reading.stale ? 'true' : undefined}\n title={freshnessStrings().ageLabel}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n fontSize: fontVar('xs'),\n color: css('muted-foreground'),\n fontVariantNumeric: 'tabular-nums',\n ...style,\n }}\n >\n <time>{formatAge(reading.age)}</time>\n {reading.stale ? <StaleBadge label={staleLabel} /> : null}\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Small helpers for reading a widget's opaque `options` bag and picking the measurement a\n// single-value widget should show. They are the SDK's, not the built-ins': a third-party\n// widget reads its own options the same defensive way.\n\nimport type { MeasurementSample, WidgetInstance } from '@devicechain/dashboards';\n\n// pickSample chooses the sample a single-value widget (card, gauge) shows: the\n// named measurement, or the first available when no name is pinned.\nexport function pickSample(\n latest: Record<string, MeasurementSample>,\n name: string | undefined,\n): MeasurementSample | undefined {\n return name ? latest[name] : Object.values(latest)[0];\n}\n\n// --- options helpers ---------------------------------------------------------\n// A widget's `options` is opaque JSON (Record<string, unknown>) owned by the\n// widget; these read a key defensively, returning undefined on the wrong type\n// rather than throwing on a hand-edited definition.\n\ntype Options = Record<string, unknown> | undefined;\n\nexport function optString(options: Options, key: string): string | undefined {\n const value = options?.[key];\n return typeof value === 'string' ? value : undefined;\n}\n\nexport function optNumber(options: Options, key: string): number | undefined {\n const value = options?.[key];\n return typeof value === 'number' && Number.isFinite(value) ? value : undefined;\n}\n\n// A boolean flag option: true only when explicitly stored true (absent/other \u2192 false), so\n// an opt-in feature stays off by default and a hand-edited value can't accidentally enable it.\nexport function optBoolean(options: Options, key: string): boolean {\n return options?.[key] === true;\n}\n\n// primaryMeasurementName is the measurement a single-value widget (card, gauge)\n// displays: an explicit `options.measurement`, else the first measurement its\n// datasource selects, else undefined (fall back to whatever arrives first).\nexport function primaryMeasurementName(widget: WidgetInstance): string | undefined {\n const explicit = optString(widget.options, 'measurement');\n if (explicit) return explicit;\n const ds = widget.datasource;\n if (ds && ds.measurements.length > 0) return ds.measurements[0];\n return undefined;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// How old a widget's newest sample is, whether that is \"stale\", and how to say so. Pure, so\n// the precedence and the wording are testable without a DOM or a clock.\n\nimport type { MeasurementSample } from '@devicechain/dashboards';\n\nimport { optNumber } from './widget-options';\n\n// resolveStaleAfterSeconds is the age, in seconds, past which a widget's value is called stale,\n// or undefined when no honest threshold is known. Precedence:\n//\n// 1. the widget's own `staleAfterSeconds` option;\n// 2. (no source today) the device profile's expected reporting interval x 3 -- a profile\n// carries no measurement reporting interval at present, only a location-fix interval that\n// is documented as descriptive, so nothing is read here and nothing is invented;\n// 3. otherwise undefined: show the age, never style anything stale.\n//\n// \uD83D\uDD34 There is deliberately NO default. A fleet that reports hourly would be painted stale all\n// day by any number chosen here, and \"stale\" painted on healthy data teaches viewers to ignore\n// the badge. A non-positive value is not a threshold and counts as none.\nexport function resolveStaleAfterSeconds(options: Record<string, unknown> | undefined): number | undefined {\n const configured = optNumber(options, 'staleAfterSeconds');\n return configured !== undefined && configured > 0 ? configured : undefined;\n}\n\n// newestSampleTime is the latest occurredTime (epoch ms) among the named measurements' latest\n// samples, or undefined when none carries a parseable time. `names` empty or omitted means\n// every measurement the widget holds.\nexport function newestSampleTime(\n latest: Record<string, MeasurementSample>,\n names?: readonly string[],\n): number | undefined {\n let newest: number | undefined;\n for (const [name, sample] of Object.entries(latest)) {\n if (names && names.length > 0 && !names.includes(name)) continue;\n if (!sample.occurredTime) continue;\n const at = Date.parse(sample.occurredTime);\n if (Number.isNaN(at)) continue;\n if (newest === undefined || at > newest) newest = at;\n }\n return newest;\n}\n\n// ageSeconds is whole seconds since `at`, never negative: a device clock slightly ahead of the\n// viewer's would otherwise read \"in 3 seconds\".\nexport function ageSeconds(at: number, now: number): number {\n return Math.max(0, Math.floor((now - at) / 1000));\n}\n\n// isStale is true only when a threshold is known and the age has passed it.\nexport function isStale(age: number, staleAfter: number | undefined): boolean {\n return staleAfter !== undefined && age > staleAfter;\n}\n\n// The viewer's language, as the host app set it on <html lang>, else the browser's.\nexport function viewerLocale(): string | undefined {\n if (typeof document !== 'undefined' && document.documentElement.lang) return document.documentElement.lang;\n if (typeof navigator !== 'undefined' && navigator.language) return navigator.language;\n return undefined;\n}\n\n// One formatter per locale tag: every readout on a board re-formats once a second, and\n// building an Intl formatter is far dearer than using one.\nconst formatters = new Map<string, Intl.RelativeTimeFormat>();\n\nfunction relativeTimeFormat(locale: string | undefined): Intl.RelativeTimeFormat {\n const key = locale ?? '';\n let formatter = formatters.get(key);\n if (!formatter) {\n try {\n formatter = new Intl.RelativeTimeFormat(locale, { numeric: 'auto', style: 'short' });\n } catch {\n // An unparseable tag in <html lang> must not take a widget down: the runtime's default.\n formatter = new Intl.RelativeTimeFormat(undefined, { numeric: 'auto', style: 'short' });\n }\n formatters.set(key, formatter);\n }\n return formatter;\n}\n\n// formatAge renders an age as \"12 sec. ago\" / \"hace 12 s\" in the viewer's locale, stepping up\n// to minutes, hours and days. Under a second it reads \"now\".\nexport function formatAge(age: number, locale: string | undefined = viewerLocale()): string {\n const formatter = relativeTimeFormat(locale);\n if (age < 60) return formatter.format(-age, 'second');\n if (age < 3600) return formatter.format(-Math.floor(age / 60), 'minute');\n if (age < 86400) return formatter.format(-Math.floor(age / 3600), 'hour');\n return formatter.format(-Math.floor(age / 86400), 'day');\n}\n\n// The SDK carries no i18n runtime, so the one word it adds to a board ships here, per\n// language, falling back to English. A host that wants another language passes `staleLabel`.\nexport interface FreshnessStrings {\n stale: string;\n ageLabel: string;\n}\n\nexport const FRESHNESS_STRINGS: Record<'en' | 'es', FreshnessStrings> = {\n en: { stale: 'stale', ageLabel: 'Last sample' },\n es: { stale: 'obsoleto', ageLabel: '\u00DAltima muestra' },\n};\n\nexport function freshnessStrings(locale: string | undefined = viewerLocale()): FreshnessStrings {\n const base = locale?.toLowerCase().split('-')[0];\n return base === 'es' ? FRESHNESS_STRINGS.es : FRESHNESS_STRINGS.en;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Theming without Tailwind (widgets are embeddable \u2014 no Tailwind leakage).\n//\n// Widgets style themselves with inline styles that reference the SAME CSS custom\n// properties the console (and any host) defines \u2014 `--card`, `--foreground`,\n// `--border`, `--chart-1..5`, etc. A host that sets those variables themes the\n// widgets for free (light/dark included); a host without Tailwind still works.\n//\n// DOM elements can use `hsl(var(--card))` directly. ECharts renders to a <canvas>,\n// which cannot resolve CSS variables \u2014 so for charts we read the variables off the\n// live computed style and hand ECharts concrete color strings (resolveChartTheme).\n\nimport type { ThemeColorToken } from '@devicechain/dashboards';\n\n// css returns an `hsl(var(--name))` reference for use in an inline style. The host\n// defines the variable as HSL channels (e.g. `--card: 0 0% 100%`), matching the\n// console's token set.\nexport function css(name: ThemeVar): string {\n return `hsl(var(--${name}))`;\n}\n\n// The subset of the console's design tokens widgets rely on. Kept small and\n// semantic so the contract a host must provide is explicit and stable.\n//\n// Colours only: sizes are SizeTokens (see ./tokens). The chart ramp and the severity hues are\n// part of the set, so a widget colours itself from a token rather than a literal; `normal` has\n// NO severity token (it renders in the ordinary foreground, see ./severity).\n//\n// The list itself lives with the dashboard format (THEME_COLOR_TOKENS), because a definition\n// names these too, for a frame's border and background: one list, so a colour a widget can use\n// is exactly a colour a board may name.\nexport type ThemeVar = ThemeColorToken;\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Size tokens: the type ramp and spacing steps a widget draws from instead of writing px\n// literals, and the density scale they all multiply by.\n//\n// --dc-scale the board's density scale (0.9 compact / 1 comfortable / 1.5 wall)\n// --dc-font-<xs|sm|md|lg|xl|display> calc(<base px> * var(--dc-scale))\n// --dc-space-<1..4> calc(<base px> * var(--dc-scale))\n//\n// The `dc-` prefix is load-bearing: these are set on a board that lives inside someone else's\n// page, and the bare names collide -- Tailwind v4 keeps font FAMILIES in `--font-*` (a host's\n// `--font-display: \"Inter\"` is its documented example), so `var(--font-display, 28px)` in such\n// a host resolves to a family name and the font size is thrown away.\n//\n// The renderer sets them on its root (tokenStyle), so every widget under it scales with the\n// board's density; `fontVar('md')` is `var(--dc-font-md, 13px)`, which still renders the base\n// size anywhere the tokens are not set (a host that mounts a bare WidgetFrame). Canvas widgets\n// cannot read a CSS variable and take the numeric equivalent from useWidgetScale().px().\n//\n// Colour tokens are the ThemeVar set in ./theme; the severity hues are generated from the\n// table in ./severity (WidgetTokenStyles below injects them), so no colour is typed twice.\n\nimport { DENSITY_SCALE, SPACE_TOKEN_PX, type CanvasDensity } from '@devicechain/dashboards';\nimport type { CSSProperties } from 'react';\n\nimport { severityTokenCss } from './severity';\n\nexport const FONT_TOKENS = {\n xs: 11,\n sm: 12,\n md: 13,\n lg: 16,\n xl: 20,\n display: 28,\n} as const;\nexport type FontToken = keyof typeof FONT_TOKENS;\n\n// The spacing steps a widget draws from, read from the dashboard format's table (which also has\n// `space-0`, for a seamless board's gutter and a frameless widget's padding).\nexport const SPACE_TOKENS = {\n 1: SPACE_TOKEN_PX['space-1'],\n 2: SPACE_TOKEN_PX['space-2'],\n 3: SPACE_TOKEN_PX['space-3'],\n 4: SPACE_TOKEN_PX['space-4'],\n} as const;\nexport type SpaceToken = keyof typeof SPACE_TOKENS;\n\n// 'font-md' | 'space-3' -- the names useWidgetScale().px() accepts.\nexport type SizeToken = `font-${FontToken}` | `space-${SpaceToken}`;\n\nexport function densityScale(density: CanvasDensity | undefined): number {\n return DENSITY_SCALE[density ?? 'comfortable'];\n}\n\nexport function fontVar(token: FontToken): string {\n return `var(--dc-font-${token}, ${FONT_TOKENS[token]}px)`;\n}\n\nexport function spaceVar(token: SpaceToken): string {\n return `var(--dc-space-${token}, ${SPACE_TOKENS[token]}px)`;\n}\n\n// The numeric px of a size token at a scale, for a canvas that cannot read the variable.\nexport function tokenPx(token: SizeToken, scale = 1): number {\n const [kind, name] = token.split('-') as ['font' | 'space', string];\n const base = kind === 'font' ? FONT_TOKENS[name as FontToken] : SPACE_TOKENS[Number(name) as SpaceToken];\n return Math.round(base * scale * 100) / 100;\n}\n\n// The custom properties a board root sets for a density scale.\nexport function tokenStyle(scale: number): CSSProperties {\n const vars: Record<string, string> = { '--dc-scale': String(scale) };\n for (const [name, px] of Object.entries(FONT_TOKENS)) vars[`--dc-font-${name}`] = `calc(${px}px * var(--dc-scale))`;\n for (const [name, px] of Object.entries(SPACE_TOKENS)) vars[`--dc-space-${name}`] = `calc(${px}px * var(--dc-scale))`;\n return vars as CSSProperties;\n}\n\n// The severity hues as a stylesheet at ZERO specificity (`:where`), so a host that defines\n// `--sev-<level>` itself (any ordinary `:root` / `.dark` rule) always wins and a host that\n// does not still gets the colours. They sit on the document root, where useSeverityColors\n// reads them -- nothing is scoped to a board element, so no WidgetThemeRoot registration is\n// needed for them to resolve.\nexport function WidgetTokenStyles() {\n return <style data-dc-tokens=\"\">{severityTokenCss({ hostWins: true })}</style>;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// React bindings between the imperative DashboardHub / DOM theme and widget state.\n\nimport type {\n AlarmSubscription,\n CommandSubscription,\n DashboardDefinition,\n DatasourceSelector,\n DeviceResolver,\n EntityCandidateLister,\n HubConnectionState,\n LocationSubscription,\n MemberResolver,\n MeasurementSample,\n SelectionCandidate,\n SlotBinding,\n WidgetDataSource,\n} from '@devicechain/dashboards';\nimport type {\n AggregateStreamState,\n AlarmStreamState,\n CommandStreamState,\n LocationStreamState,\n MeasurementStreamState,\n} from '@devicechain/dashboards';\nimport {\n bindingsWithoutScopedSlots,\n DashboardHub,\n hasScopedSlots,\n resolveContextBindings,\n resolveSlotCandidates,\n} from '@devicechain/dashboards';\nimport { useEffect, useMemo, useRef, useState, useSyncExternalStore, type RefObject } from 'react';\n\nimport { useWidgetCandidates, type WidgetCandidates } from './context';\n\n// The channel snapshots moved to @devicechain/dashboards (they are pure data over the hub's\n// row types); re-exported so every import site here keeps resolving.\nexport type { AggregateStreamState, AlarmStreamState, CommandStreamState, LocationStreamState, MeasurementStreamState };\n\n// useDashboardHub gives a host ONE live hub per (resolver, authorities) \u2014 compared by\n// value, so a re-rendered but equal authorities array keeps the hub \u2014 and disposes it when\n// it is replaced or the host unmounts.\n//\n// It deliberately takes no bindings. The hub holds none: the renderer resolves every slot\n// through the settled bindings (resolveWidgetDatasource) before a widget subscribes, so a\n// selection re-subscribes only the widgets bound to the re-pointed slot. A hub rebuilt per\n// selection would tear down every subscription on the board and re-fetch every history.\nexport function useDashboardHub(resolver: DeviceResolver, authorities: string[] | undefined): DashboardHub {\n const authoritiesKey = JSON.stringify(authorities ?? []);\n const hub = useMemo(\n () => new DashboardHub({ resolver, authorities }),\n // authorities read via authoritiesKey (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [resolver, authoritiesKey],\n );\n useEffect(() => () => hub.disposeAll(), [hub]);\n return hub;\n}\n\n// useResolvedBindings runs the scoped-slot cascade (ADR-039 selection amendment) for a\n// host: it takes the synchronous base manifest (effectiveBindings) plus the accumulated\n// selection overlay and returns the settled slot\u2192binding map the host feeds to the\n// renderer. Selection state lives in the host; this hook just derives bindings from it.\n//\n// The common scope-FREE dashboard needs no async work \u2014 the synchronous overlay (base\n// with the selection laid over it) is already correct, and the hook returns it directly,\n// preserving today's behavior. Only a dashboard with scoped slots runs the async pass,\n// guarded by a monotonic generation so a slow members response can't overwrite a newer\n// selection; it keeps the last settled map until the new one arrives (one re-key per\n// walk, never a stale partial). A resolution error is fail-safe: keep the last map.\nexport function useResolvedBindings(\n definition: DashboardDefinition,\n base: Record<string, SlotBinding>,\n selection: Record<string, SlotBinding>,\n resolver: MemberResolver,\n): Record<string, SlotBinding> {\n const scoped = useMemo(() => hasScopedSlots(definition), [definition]);\n // Selection wins over base. Correct as-is for root + manual slots and every scope-free\n // dashboard; scoped `first` slots are refined by the async pass below.\n const overlaid = useMemo(() => ({ ...base, ...selection }), [base, selection]);\n // Seed the settled map with scoped slots STRIPPED, so a scoped dashboard's first paint\n // is unbound-not-stale (a scoped slot's default may be out of context) until the cascade\n // derives it. For a scope-free dashboard this equals `overlaid`.\n const [resolved, setResolved] = useState(() => bindingsWithoutScopedSlots(definition, overlaid));\n const genRef = useRef(0);\n\n useEffect(() => {\n // Scope-free: `overlaid` is authoritative. Keep `resolved` synced to it anyway so that\n // if the definition later gains a scope (an authoring edit), the flip to the `resolved`\n // path starts from the CURRENT bindings, not a stale mount-time snapshot.\n if (!scoped) {\n setResolved((prev) => (sameBindings(prev, overlaid) ? prev : overlaid));\n return;\n }\n const gen = ++genRef.current;\n let live = true;\n resolveContextBindings(definition, base, selection, resolver)\n .then((next) => {\n if (!live || gen !== genRef.current) return;\n // Only swap when the value actually changed, so an equal re-resolve doesn't churn\n // a new object reference (and re-render every widget) for nothing.\n setResolved((prev) => (sameBindings(prev, next) ? prev : next));\n })\n .catch(() => {\n /* fail-safe: keep the last settled map */\n });\n return () => {\n live = false;\n };\n }, [scoped, definition, base, selection, resolver, overlaid]);\n\n return scoped ? resolved : overlaid;\n}\n\n// sameBindings is a value compare of two slot\u2192binding maps (stable-key JSON), so the\n// hook can suppress a no-op re-key.\nfunction sameBindings(a: Record<string, SlotBinding>, b: Record<string, SlotBinding>): boolean {\n const ak = Object.keys(a).sort();\n const bk = Object.keys(b).sort();\n if (ak.length !== bk.length) return false;\n for (let i = 0; i < ak.length; i += 1) {\n if (ak[i] !== bk[i]) return false;\n if (JSON.stringify(a[ak[i]]) !== JSON.stringify(b[bk[i]])) return false;\n }\n return true;\n}\n\n// useSlotCandidates builds the ambient candidate provider a host passes to the renderer\n// (ADR-039 selection amendment): a function slot\u2192candidates that a context/entity-selector\n// widget calls. It closes over the CURRENT resolved bindings, so it is rebuilt whenever a\n// binding changes \u2014 a selector's option set (a scoped child's members) then follows the\n// parent for free, and a widget re-fetches because the provider identity changed. Returns\n// undefined when no lister is supplied (a host that wires no selection), so the selector\n// stays inert. Keyed by the bindings' VALUE so an equal-but-new bindings object doesn't\n// churn the provider (and every open selector's fetch) each render.\nexport function useSlotCandidates(\n definition: DashboardDefinition,\n bindings: Record<string, SlotBinding>,\n resolver: MemberResolver,\n lister: EntityCandidateLister | undefined,\n): WidgetCandidates | undefined {\n const bindingsKey = JSON.stringify(bindings);\n return useMemo(\n () => {\n if (!lister) return undefined;\n return (slot: string) => resolveSlotCandidates(definition, slot, bindings, resolver, lister);\n },\n // bindings read via bindingsKey (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [definition, bindingsKey, resolver, lister],\n );\n}\n\n// The state a selector widget renders from: the target slot's current candidate options,\n// plus load/error flags. `wired` reports whether an ambient candidate provider exists \u2014 a\n// selector feature-detects on it (with the select callback) to stay inert where selection\n// isn't wired (edit/preview, a host that supplies no lister) rather than showing a dead picker.\nexport interface CandidatesState {\n candidates: SelectionCandidate[];\n loading: boolean;\n error: unknown | null;\n wired: boolean;\n}\n\n// useCandidates resolves the options a selector widget offers for `slot` through the ambient\n// candidate provider. Re-fetches when the provider (i.e. the resolved bindings) or the slot\n// changes, guarded by a monotonic generation so a slow list can't overwrite a newer one\n// (the parent-switch-then-open race). Fail-safe: an error yields an empty option set.\nexport function useCandidates(slot: string | undefined): CandidatesState {\n const provider = useWidgetCandidates();\n const wired = !!provider;\n // Seed loading true when a fetch is imminent (provider + slot present) so the first paint\n // reads \"Loading\u2026\", not a spurious \"No options\" for the frame before the effect runs.\n const [state, setState] = useState<Omit<CandidatesState, 'wired'>>(() => ({\n candidates: [],\n loading: Boolean(provider && slot),\n error: null,\n }));\n const genRef = useRef(0);\n\n useEffect(() => {\n if (!provider || !slot) {\n setState({ candidates: [], loading: false, error: null });\n return;\n }\n const gen = ++genRef.current;\n let live = true;\n // Clear any prior error when a fresh (healthy) fetch starts, so a retry doesn't show\n // \"Loading\u2026\" and a stale error banner together.\n setState((prev) => ({ ...prev, loading: true, error: null }));\n provider(slot)\n .then((candidates) => {\n if (live && gen === genRef.current) setState({ candidates, loading: false, error: null });\n })\n .catch((error: unknown) => {\n if (live && gen === genRef.current) setState({ candidates: [], loading: false, error });\n });\n return () => {\n live = false;\n };\n }, [provider, slot]);\n\n return { ...state, wired };\n}\n\nexport interface MeasurementStreamOptions {\n // Max samples retained in the rolling window (oldest dropped past this). With `windowSeconds`\n // this is the memory ceiling on a span (default MAX_SPAN_SAMPLES); without it, the whole\n // window (default 300).\n window?: number;\n // The window as a SPAN: only samples that occurred within this many seconds of now (or of\n // `pausedAt`) are kept, whatever their number, and `window` is demoted to a ceiling so a\n // busy device cannot grow the buffer without bound. Omit for the count-only window.\n windowSeconds?: number;\n // Freeze the stream at this instant (epoch ms). While set, live samples are BUFFERED, not merged:\n // the returned state does not change, its newest sample keeps its time (so an age readout\n // keeps counting), and the span is measured back from this instant. Clearing it merges\n // everything that arrived in the meantime at once -- the catch-up.\n pausedAt?: number;\n // The clock the span is measured against (epoch ms). Default: the wall clock. A data source that\n // plays a recorded run supplies its own, because the run's samples are dated by the run, not by\n // today, and a span measured from today would drop every one of them.\n now?: () => number;\n // Historical samples to seed the window with before live data arrives (from a\n // bucketedMeasurements backfill). Assumed chronological and older than the live\n // tail; live samples for the same measurement name override the seed's latest.\n initialSamples?: MeasurementSample[];\n}\n\n// The ceiling on retained samples when the window is a span. A span of days at a high event\n// rate would otherwise hold every raw reading; past this the OLDEST go first.\nexport const MAX_SPAN_SAMPLES = 2000;\n\nconst EMPTY: MeasurementStreamState = { latest: {}, samples: [], error: null };\n\n// Build a name\u2192last-sample map from a chronological list (last occurrence wins).\nfunction latestOf(samples: MeasurementSample[]): Record<string, MeasurementSample> {\n const latest: Record<string, MeasurementSample> = {};\n for (const s of samples) latest[s.name] = s;\n return latest;\n}\n\n// evictOldest keeps the newest `max` samples, recording in `evicted` the time of the newest one it\n// drops (the hook's mark for \"the window no longer reaches back to its start\").\nfunction evictOldest(samples: MeasurementSample[], max: number, evicted: { current: number }): MeasurementSample[] {\n if (samples.length <= max) return samples;\n const cut = samples.length - max;\n for (let i = 0; i < cut; i++) {\n const at = Date.parse(samples[i].occurredTime ?? '');\n if (at > evicted.current) evicted.current = at;\n }\n return samples.slice(cut);\n}\n\n// withinSpan drops the samples that occurred before `end - spanSeconds`. A sample whose time is\n// missing or unparseable is KEPT: it cannot be shown to be outside the window, and dropping it\n// would hide a reading the count ceiling would have kept.\nexport function withinSpan(samples: MeasurementSample[], spanSeconds: number, end: number): MeasurementSample[] {\n const from = end - spanSeconds * 1000;\n return samples.filter((s) => {\n if (!s.occurredTime) return true;\n const at = Date.parse(s.occurredTime);\n return Number.isNaN(at) || at >= from;\n });\n}\n\n// useMeasurementStream subscribes a widget's datasource through the hub and keeps\n// the latest value per measurement plus a bounded rolling window. It re-subscribes\n// when the datasource changes (compared by value, so a re-rendered-but-equal\n// selector doesn't churn the subscription) and tears down on unmount. When\n// initialSamples is given, it is merged ahead of the live tail so a chart shows\n// history immediately (and can arrive after the live stream opens).\nexport function useMeasurementStream(\n hub: WidgetDataSource,\n datasource: DatasourceSelector | undefined,\n options: MeasurementStreamOptions = {},\n): MeasurementStreamState {\n const { windowSeconds, pausedAt } = options;\n const windowSize = options.window ?? (windowSeconds !== undefined ? MAX_SPAN_SAMPLES : 300);\n const initialSamples = options.initialSamples;\n const paused = pausedAt !== undefined;\n\n // Value-compare the selector so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = datasource ? JSON.stringify(datasource) : null;\n\n // The span and the pause are read by the subscription's callback through refs, so changing the\n // range (or pausing) never tears the subscription down and loses what it has buffered.\n const spanRef = useRef(windowSeconds);\n const clockRef = useRef(options.now ?? Date.now);\n const pausedRef = useRef(paused);\n const queueRef = useRef<MeasurementSample[]>([]);\n // The newest QUEUED sample per measurement name. The queue is capped, so a long pause at a high\n // rate evicts its oldest entries -- and with them, possibly, the only sample of a measurement\n // that reports rarely. Without this, resuming would leave that measurement's latest value at\n // its pre-pause reading. Bounded by the number of measurement names, not by time.\n const queueLatestRef = useRef<Record<string, MeasurementSample>>({});\n // The time (epoch ms) of the newest sample the retention ceiling has evicted, live or queued.\n // While it is still inside the span, the window no longer covers the whole span and says so\n // (`truncatedBefore`), rather than drawing a short window under a long span's label.\n const evictedRef = useRef(-Infinity);\n const flushRef = useRef<() => void>(() => {});\n const clock = options.now ?? Date.now;\n useEffect(() => {\n spanRef.current = windowSeconds;\n clockRef.current = clock;\n }, [windowSeconds, clock]);\n\n // The live buffer is TAGGED with the selector it was filled from. The effect below\n // resets it when the selector changes, but an effect runs after the render that\n // carried the new selector \u2014 so without the tag that render would show the previous\n // device's values under the new one. A buffer whose tag is not the current key is\n // treated as empty, synchronously.\n const [tagged, setTagged] = useState<{ key: string | null; state: MeasurementStreamState }>({\n key,\n state: EMPTY,\n });\n const live = tagged.key === key ? tagged.state : EMPTY;\n\n useEffect(() => {\n setTagged({ key, state: EMPTY }); // reset the live buffer whenever the datasource changes (or clears)\n queueRef.current = [];\n queueLatestRef.current = {};\n evictedRef.current = -Infinity;\n flushRef.current = () => {};\n if (!datasource) return;\n\n // A callback for a selector the buffer has already moved past (a sink that answers\n // after its subscription was released) leaves the buffer alone rather than re-tagging\n // it to the old selector, which would blank the current one's values.\n const update = (fn: (prev: MeasurementStreamState) => MeasurementStreamState) =>\n setTagged((prev) => (prev.key === key ? { key, state: fn(prev.state) } : prev));\n\n const fold = (\n prev: MeasurementStreamState,\n incoming: MeasurementSample[],\n newest: Record<string, MeasurementSample> = latestOf(incoming),\n ): MeasurementStreamState => {\n let samples = prev.samples.concat(incoming);\n const span = spanRef.current;\n if (span !== undefined) samples = withinSpan(samples, span, clockRef.current());\n samples = evictOldest(samples, windowSize, evictedRef);\n return { latest: { ...prev.latest, ...newest }, samples, error: null };\n };\n\n flushRef.current = () => {\n const queued = queueRef.current;\n if (queued.length === 0) return;\n const newest = queueLatestRef.current;\n queueRef.current = [];\n queueLatestRef.current = {};\n update((prev) => fold(prev, queued, newest));\n };\n\n return hub.subscribeWidget(datasource, {\n next: (sample) => {\n if (pausedRef.current) {\n // Buffered, bounded: a pause that outlasts the ceiling keeps the newest.\n queueRef.current = evictOldest([...queueRef.current, sample], windowSize, evictedRef);\n queueLatestRef.current[sample.name] = sample;\n return;\n }\n update((prev) => fold(prev, [sample]));\n },\n error: (err) => update((prev) => ({ ...prev, error: err })),\n });\n // `datasource` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key, windowSize]);\n\n // The pause gate. Resuming merges what queued while frozen. The flag flips before the flush,\n // so a sample landing between the two is merged rather than queued behind it.\n useEffect(() => {\n pausedRef.current = paused;\n if (!paused) flushRef.current();\n }, [paused]);\n\n // Layer the history seed under the live tail. Kept separate from the live buffer\n // so late-arriving history (async backfill) recomputes without touching the\n // subscription, and switching datasource clears live while the new seed applies.\n return useMemo(() => {\n // The span is measured back from the freeze instant while paused, from now otherwise.\n const end = pausedAt ?? clock();\n if (windowSeconds === undefined) {\n if (!initialSamples || initialSamples.length === 0) return live;\n const merged = initialSamples.concat(live.samples);\n return {\n latest: { ...latestOf(initialSamples), ...live.latest },\n samples: merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged,\n error: live.error,\n };\n }\n // A span. Everything kept is inside it, so anything the ceiling cuts here -- or cut earlier\n // from the live buffer or the pause queue, while still inside it -- is a hole at the start of\n // the window. The ceiling drops the OLDEST, so what is kept is one contiguous run; it is\n // marked with where that run begins, so a chart can say it covers less than its span.\n const from = end - windowSeconds * 1000;\n const merged = withinSpan(initialSamples ? initialSamples.concat(live.samples) : live.samples, windowSeconds, end);\n const samples = merged.length > windowSize ? merged.slice(merged.length - windowSize) : merged;\n const cut = samples.length < merged.length || evictedRef.current >= from;\n const truncatedBefore = cut ? (samples[0]?.occurredTime ?? undefined) : undefined;\n const latest = initialSamples ? { ...latestOf(initialSamples), ...live.latest } : live.latest;\n if (!truncatedBefore && latest === live.latest && samples.length === live.samples.length) return live;\n const state: MeasurementStreamState = { latest, samples, error: live.error };\n if (truncatedBefore) state.truncatedBefore = truncatedBefore;\n return state;\n }, [initialSamples, live, windowSize, windowSeconds, pausedAt, clock]);\n}\n\nconst EMPTY_ALARMS: AlarmStreamState = { alarms: [], total: 0, loading: true, error: null };\n\n// useAlarmStream binds an alarm widget's scope+filters through the hub's alarm channel\n// and keeps the latest snapshot. It re-subscribes when the subscription changes\n// (value-compared, so a re-rendered-but-equal object doesn't churn) and tears down on\n// unmount. The hub delivers whole snapshots (query-then-reconcile), so this holds the\n// last one rather than accumulating \u2014 the query is the source of truth.\nexport function useAlarmStream(\n hub: WidgetDataSource,\n subscription: AlarmSubscription,\n): AlarmStreamState {\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n // The snapshot is TAGGED with the subscription it came from, as the measurement buffer is: the\n // effect that resets it runs after the render carrying the new subscription, and that render\n // must not show the previous scope's rows under it (nor hand them to a pause hold to keep).\n const [[at, state], setState] = useState<[string, AlarmStreamState]>([key, EMPTY_ALARMS]);\n\n useEffect(() => {\n setState([key, EMPTY_ALARMS]); // reset to loading whenever the subscription changes\n return hub.subscribeAlarms(subscription, {\n next: (snapshot) =>\n setState([key, { alarms: snapshot.alarms, total: snapshot.total, loading: false, error: null }]),\n error: (err) => setState(([, prev]) => [key, { ...prev, loading: false, error: err }]),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return at === key ? state : EMPTY_ALARMS;\n}\n\nconst EMPTY_COMMANDS: CommandStreamState = {\n deviceToken: null,\n commands: [],\n total: 0,\n loading: true,\n error: null,\n};\n\n// useCommandStream binds a command widget's scope through the hub's control channel and\n// keeps the latest command-history snapshot. It re-subscribes when the subscription\n// changes (value-compared) and tears down on unmount. The hub delivers whole snapshots\n// (poll-then-emit), so this holds the last one rather than accumulating.\nexport function useCommandStream(\n hub: WidgetDataSource,\n subscription: CommandSubscription,\n): CommandStreamState {\n const [state, setState] = useState<CommandStreamState>(EMPTY_COMMANDS);\n\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n\n useEffect(() => {\n setState(EMPTY_COMMANDS); // reset to loading whenever the subscription changes\n return hub.subscribeCommands(subscription, {\n next: (snapshot) =>\n setState({\n deviceToken: snapshot.deviceToken,\n commands: snapshot.commands,\n total: snapshot.total,\n loading: false,\n error: null,\n }),\n error: (err) => setState((prev) => ({ ...prev, loading: false, error: err })),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\nconst EMPTY_LOCATIONS: LocationStreamState = {\n locations: [],\n deviceTokens: [],\n forbidden: false,\n loading: true,\n error: null,\n};\n\n// useLocationStream binds a map widget's selector through the hub's location channel and\n// keeps the latest position snapshot. It re-subscribes when the subscription changes\n// (value-compared, so a re-rendered-but-equal object doesn't churn) and tears down on\n// unmount. The hub delivers whole snapshots (poll-then-emit), so this holds the last one\n// rather than accumulating.\n//\n// A `forbidden` snapshot CLEARS the positions as it sets the flag: an authority can be\n// revoked mid-session, and keeping the last-seen coordinates on screen under a\n// permission banner would leave a refused viewer looking at the very data they were\n// just refused.\nexport function useLocationStream(\n hub: WidgetDataSource,\n subscription: LocationSubscription,\n): LocationStreamState {\n // Value-compare the subscription so an unchanged-but-new object reference doesn't\n // resubscribe every render.\n const key = JSON.stringify(subscription);\n // Tagged with its subscription, as useAlarmStream's snapshot is: no render shows the previous\n // selector's markers under a new one.\n const [[at, state], setState] = useState<[string, LocationStreamState]>([key, EMPTY_LOCATIONS]);\n\n useEffect(() => {\n setState([key, EMPTY_LOCATIONS]); // reset to loading whenever the subscription changes\n return hub.subscribeLocations(subscription, {\n next: (snapshot) =>\n setState([\n key,\n snapshot.kind === 'forbidden'\n ? { locations: [], deviceTokens: [], forbidden: true, loading: false, error: null }\n : {\n locations: snapshot.locations,\n deviceTokens: snapshot.deviceTokens,\n forbidden: false,\n loading: false,\n error: null,\n },\n ]),\n error: (err) => setState(([, prev]) => [key, { ...prev, loading: false, error: err }]),\n });\n // `subscription` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return at === key ? state : EMPTY_LOCATIONS;\n}\n\n// A widget's bound-entity availability: 'unknown' until the async check resolves\n// (render optimistically meanwhile), then 'available', or 'unavailable' when the bound\n// device no longer exists (a deleted device's stable token, ADR-044).\nexport type DatasourceAvailability = 'unknown' | 'available' | 'unavailable';\n\n// While a widget shows \"unavailable\", re-check on this cadence so a device deleted then\n// recreated with the same token (ADR-042 frees tokens on delete) recovers on a long-lived\n// viewer without a reload. Only runs while unavailable \u2014 a live device is checked once.\nconst AVAILABILITY_REVALIDATE_MS = 60_000;\n\n// useDatasourceAvailability resolves whether a widget's bound device still exists,\n// WITHOUT blocking the widget's data stream (optimistic + async): it starts 'unknown'\n// (the widget renders normally), then flips to 'available'/'unavailable' once the hub's\n// existence check resolves. Fails open (\u2192'available') so a check outage never falsely\n// blanks a live widget. A widget with no datasource is trivially available (no query).\n// Re-checks when the datasource changes (value-compared), and periodically while\n// unavailable so a recreated device recovers.\nexport function useDatasourceAvailability(\n hub: WidgetDataSource,\n datasource: DatasourceSelector | undefined,\n): DatasourceAvailability {\n const [state, setState] = useState<DatasourceAvailability>('unknown');\n const key = datasource ? JSON.stringify(datasource) : null;\n\n useEffect(() => {\n // No datasource (label/image, or tenant-wide) has nothing to validate \u2014 available\n // immediately, no query, no re-render churn.\n if (!datasource) {\n setState('available');\n return;\n }\n setState('unknown');\n let cancelled = false;\n let timer: ReturnType<typeof setInterval> | undefined;\n\n const stopTimer = (): void => {\n if (timer) {\n clearInterval(timer);\n timer = undefined;\n }\n };\n\n const check = (): void => {\n hub\n .isDatasourceAvailable(datasource)\n .then((ok) => {\n if (cancelled) return;\n setState(ok ? 'available' : 'unavailable');\n if (ok) stopTimer(); // recovered (or was fine) \u2014 stop polling\n })\n .catch(() => {\n if (cancelled) return;\n setState('available'); // fail open\n stopTimer();\n });\n };\n\n check();\n // Poll only matters once unavailable; harmlessly runs until the first check clears it\n // for a live device (cleared in the resolver above, well before the first tick).\n timer = setInterval(check, AVAILABILITY_REVALIDATE_MS);\n\n return () => {\n cancelled = true;\n stopTimer();\n };\n // `datasource` is intentionally read via `key` (value identity), not reference.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [hub, key]);\n\n return state;\n}\n\nexport interface ElementSize {\n width: number;\n height: number;\n}\n\n// useElementSize tracks an element's content-box size via ResizeObserver, so a\n// widget can scale its contents (chart fonts, gauge ticks, a big number) to the\n// slot it was resized to \u2014 ECharts' resize() re-lays-out the canvas but never\n// scales absolute font sizes, so a small widget crams without this. Returns\n// {0,0} until measured (and where ResizeObserver is unavailable, e.g. jsdom); the\n// consumers treat a zero size as \"use default sizing\".\n//\n// `step` quantises each dimension DOWN to a multiple of it (default 1: exact), so a caller that\n// only needs a coarse size -- the frame feeding every widget's scale -- does not re-render for\n// sub-pixel or few-pixel jitter. Down, so text fitted to the quantised box always fits the\n// real one.\nexport function useElementSize<T extends HTMLElement>(step = 1): [RefObject<T | null>, ElementSize] {\n const ref = useRef<T>(null);\n const [size, setSize] = useState<ElementSize>({ width: 0, height: 0 });\n\n useEffect(() => {\n const el = ref.current;\n if (!el || typeof ResizeObserver === 'undefined') return;\n const observer = new ResizeObserver((entries) => {\n const rect = entries[0]?.contentRect;\n if (!rect) return;\n const width = step > 1 ? Math.floor(rect.width / step) * step : rect.width;\n const height = step > 1 ? Math.floor(rect.height / step) * step : rect.height;\n setSize((prev) => (prev.width === width && prev.height === height ? prev : { width, height }));\n });\n observer.observe(el);\n return () => observer.disconnect();\n }, [step]);\n\n return [ref, size];\n}\n\n// What useHubConnectionState needs of a hub: the live implementation (DashboardHub) has it, a\n// synthetic preview source does not, and a host holding only that reads `live`.\nexport interface ConnectionStateSource {\n readonly connectionState: HubConnectionState;\n onConnectionState(listener: () => void): () => void;\n}\n\nconst NO_CONNECTION_SOURCE: ConnectionStateSource = {\n connectionState: 'live',\n onConnectionState: () => () => {},\n};\n\n// useHubConnectionState follows a hub's socket state (live / reconnecting / offline) for a\n// header badge. It re-renders only when the state changes.\nexport function useHubConnectionState(hub: Partial<ConnectionStateSource> | undefined): HubConnectionState {\n // A source that reports no connection (a synthetic preview, a recording) is not \"down\":\n // there is no socket to lose, so it reads live.\n const source =\n hub && typeof hub.onConnectionState === 'function' ? (hub as ConnectionStateSource) : NO_CONNECTION_SOURCE;\n return useSyncExternalStore(\n (listener) => source.onConnectionState(listener),\n () => source.connectionState,\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// useWidgetScale -- the numeric half of the size contract. HTML text sizes itself with `cq`\n// units off the frame's content column (fitValueFontCss); a canvas (an ECharts gauge) cannot\n// read those, so it asks this hook for the same box in numbers. Both read the one curve in\n// ./fit-text and the one box -- the frame's padded content column -- so they agree.\n\nimport { useEffect, useMemo, useReducer, useRef } from 'react';\n\nimport { useWidgetBodySize, useWidgetDensityScale, type WidgetBodySize } from './context';\nimport { stickyChars, type StickyChars } from './fit-text';\nimport { tokenPx, type SizeToken } from './tokens';\n\nexport type SizeClass = 'compact' | 'regular' | 'large';\n\n// The content-column sizes (px, at density 1; multiplied by the density scale) that separate the\n// classes. Compact: too small for a full variant (a value and a caption); large: room for\n// extras (a sparkline, a legend).\nexport const SIZE_CLASS_BREAKS = {\n compactWidth: 120,\n compactHeight: 72,\n largeWidth: 420,\n largeHeight: 220,\n} as const;\n\nexport function sizeClassOf(size: WidgetBodySize, scale = 1): SizeClass {\n // Unmeasured (first frame, jsdom) is `regular`: the default rendering, not a degraded one.\n if (!(size.width > 0) || !(size.height > 0)) return 'regular';\n const b = SIZE_CLASS_BREAKS;\n if (size.width < b.compactWidth * scale || size.height < b.compactHeight * scale) return 'compact';\n if (size.width >= b.largeWidth * scale && size.height >= b.largeHeight * scale) return 'large';\n return 'regular';\n}\n\nexport interface WidgetScale {\n sizeClass: SizeClass;\n // The board's density scale (1 outside a renderer).\n scale: number;\n // The frame's content column in px, quantised to 4.\n size: WidgetBodySize;\n // The px of a size token at this density, for a canvas.\n px: (token: SizeToken) => number;\n}\n\nexport function makeWidgetScale(size: WidgetBodySize, scale: number): WidgetScale {\n return { sizeClass: sizeClassOf(size, scale), scale, size, px: (token: SizeToken) => tokenPx(token, scale) };\n}\n\n// useWidgetScale reads the frame's content column, so it works in a component RENDERED INSIDE a\n// WidgetFrame -- not in the component that renders the frame (the frame's context does not\n// reach above it). A widget that needs the numbers for its own top-level render passes\n// WidgetFrame a function child instead: `<WidgetFrame>{(scale) => ...}</WidgetFrame>`.\nexport function useWidgetScale(): WidgetScale {\n const size = useWidgetBodySize();\n const scale = useWidgetDensityScale();\n return useMemo(() => makeWidgetScale(size, scale), [size, scale]);\n}\n\n// How long a readout keeps its longest recent length after it shrinks.\nconst STICKY_HOLD_MS = 60_000;\n\n// useStickyChars -- the character count to hand the font curve for a live readout: the longest\n// seen in the last minute, so 9.99 -> 10.01 does not resize the number. Holding the LONGER\n// length can only make the font smaller than the live value needs, never larger, so it cannot\n// make a readout overflow.\nexport function useStickyChars(chars: number, holdMs = STICKY_HOLD_MS): number {\n const held = useRef<StickyChars | undefined>(undefined);\n const [tick, wake] = useReducer((n: number) => n + 1, 0);\n // Written during render on purpose: the value is a pure function of (prev, chars, time) and\n // an extra render recomputes the same answer, so there is nothing to commit or clean up.\n held.current = stickyChars(held.current, chars, Date.now(), holdMs);\n const { value, since } = held.current;\n // A hold is released only by a render. A value that shrinks and then stays put (an alarm\n // count falling from 1000 to 9 and holding there) brings no further render, so without this\n // it would keep the long length's small font indefinitely, not for a minute: render again\n // when the hold runs out. `tick` re-arms the timer should it ever fire a moment early.\n useEffect(() => {\n if (value <= chars) return;\n const timer = setTimeout(wake, Math.max(0, since + holdMs - Date.now()));\n return () => clearTimeout(timer);\n }, [value, since, chars, holdMs, tick]);\n return value;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// A small header badge for a hub's connection state. Presentational: the host supplies the\n// words (its own i18n), so the SDK adds no strings of its own here.\n\nimport type { HubConnectionState } from '@devicechain/dashboards';\n\nimport { css, type ThemeVar } from './theme';\nimport { fontVar } from './tokens';\n\nexport interface HubConnectionBadgeProps {\n state: HubConnectionState;\n labels: Record<HubConnectionState, string>;\n}\n\nconst DOT: Record<HubConnectionState, ThemeVar> = {\n live: 'primary',\n reconnecting: 'muted-foreground',\n offline: 'destructive',\n};\n\nexport function HubConnectionBadge({ state, labels }: HubConnectionBadgeProps) {\n return (\n <span\n role=\"status\"\n data-connection-state={state}\n style={{\n display: 'inline-flex',\n alignItems: 'center',\n gap: 6,\n fontSize: fontVar('sm'),\n padding: '2px 8px',\n borderRadius: 999,\n border: `1px solid ${css('border')}`,\n color: state === 'offline' ? css('destructive') : css('muted-foreground'),\n whiteSpace: 'nowrap',\n }}\n >\n <span\n aria-hidden\n style={{\n width: 8,\n height: 8,\n borderRadius: '50%',\n background: css(DOT[state]),\n opacity: state === 'reconnecting' ? 0.6 : 1,\n }}\n />\n {labels[state]}\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// Directional change-flash \u2014 an opt-in cue (widget option `flashOnChange`) that briefly\n// tints a numeric value green when it rises and red when it falls, then fades back, so a\n// glance catches direction of change on a live board (cf. a trading \"last\" column). Purely\n// presentational: no hub/model change, driven off the value the widget already renders.\n//\n// Single-value widgets (latest-card) color the number; a multi-row widget (table,\n// alarm-table) renders each value through <FlashValue>, so React's per-key component\n// instances give every row its own independent flash with no map bookkeeping. The gauge\n// (an opaque ECharts canvas whose number can't be CSS-transitioned) tints its container\n// instead \u2014 see flashBackgroundStyle.\n\nimport { useEffect, useRef, useState, type CSSProperties } from 'react';\n\nimport { formatValue } from './format';\n\nexport type FlashDirection = 'up' | 'down' | null;\n\n// Semantic up/down colors, mid-saturation so they read on both light and dark host themes;\n// a host may override with the --flash-up / --flash-down custom properties. The background\n// variants are translucent so a tint sits over the widget's own surface without hiding it.\nconst UP = {\n fg: 'var(--flash-up, hsl(142 72% 40%))',\n bg: 'var(--flash-up-bg, hsl(142 72% 45% / 0.16))',\n};\nconst DOWN = {\n fg: 'var(--flash-down, hsl(0 72% 51%))',\n bg: 'var(--flash-down-bg, hsl(0 72% 55% / 0.16))',\n};\n\n// How long (ms) the solid tint holds before fading, and how long the fade back takes.\nconst HOLD_MS = 400;\nconst FADE_MS = 600;\n\n// useFlashOnChange watches a numeric value and returns the direction of its most recent\n// change ('up' | 'down') for a short window, then null. It stays null when the feature is\n// off, on the first value (no flash on mount), on a non-finite value, or when `identity`\n// changes \u2014 the last so a widget bound to an ANCHOR (whose one measurement slot interleaves\n// values from many member devices) adopts the new device's value instead of reading the\n// device-to-device jump as a real rise/fall. Pass the value's owning entity as `identity`\n// (e.g. `deviceToken:name`); leave it undefined for a single-device value.\n//\n// The held direction auto-clears after HOLD_MS; the consumer fades it out via flashTextStyle.\n// Every early-return path resets `direction` too: the effect only re-runs on a dep change,\n// and each such change has already cancelled the pending timer, so not clearing here would\n// strand a tint (e.g. value \u2192 null, or toggled off then on, mid-hold).\nexport function useFlashOnChange(\n value: number | null | undefined,\n enabled: boolean,\n identity?: string | number,\n): FlashDirection {\n const prev = useRef<number | null | undefined>(value);\n const prevIdentity = useRef(identity);\n const [direction, setDirection] = useState<FlashDirection>(null);\n\n useEffect(() => {\n const before = prev.current;\n const sameEntity = prevIdentity.current === identity;\n prev.current = value;\n prevIdentity.current = identity;\n if (\n !enabled ||\n !sameEntity ||\n typeof value !== 'number' ||\n typeof before !== 'number' ||\n !Number.isFinite(value) ||\n !Number.isFinite(before) ||\n value === before\n ) {\n setDirection(null); // a no-op when already null (React bails out); clears a stale tint otherwise\n return;\n }\n setDirection(value > before ? 'up' : 'down');\n const timer = setTimeout(() => setDirection(null), HOLD_MS);\n return () => clearTimeout(timer);\n }, [value, enabled, identity]);\n\n // Mask any lingering state when disabled, so toggling off is immediate.\n return enabled ? direction : null;\n}\n\n// flashTextStyle colors text for the current direction: snap to the tint (no transition)\n// while flashing, then fade `color` back to the element's own color when it clears. Merge\n// it AFTER an explicit base `color` so the tint overrides while set and the base shows\n// through (and animates) once cleared.\nexport function flashTextStyle(direction: FlashDirection, fadeMs: number = FADE_MS): CSSProperties {\n if (direction) return { color: direction === 'up' ? UP.fg : DOWN.fg, transition: 'none' };\n return { transition: `color ${fadeMs}ms ease-out` };\n}\n\n// flashBackgroundStyle is the same cue as a translucent background tint, for a widget whose\n// value can't be recolored directly (the gauge's canvas). Snap in, fade the background back.\nexport function flashBackgroundStyle(direction: FlashDirection, fadeMs: number = FADE_MS): CSSProperties {\n if (direction) {\n return { backgroundColor: direction === 'up' ? UP.bg : DOWN.bg, transition: 'none' };\n }\n return { transition: `background-color ${fadeMs}ms ease-out` };\n}\n\n// FlashValue renders a formatted numeric value that flashes on change \u2014 the reusable cell\n// for multi-row widgets. Each rendered instance owns its own flash state, so keying rows by\n// entity (React's list key) gives every row an independent cue for free.\nexport function FlashValue({\n value,\n precision,\n enabled,\n identity,\n baseColor = 'inherit',\n style,\n}: {\n value: number | null | undefined;\n precision?: number;\n enabled: boolean;\n // The value's owning entity (see useFlashOnChange): when it changes, adopt the new value\n // rather than flash. Distinguishes a real per-device change from an anchor's cross-device\n // interleave. Undefined for a single-device value.\n identity?: string | number;\n // The color the value fades back to (defaults to the inherited text color).\n baseColor?: string;\n style?: CSSProperties;\n}) {\n const direction = useFlashOnChange(value, enabled, identity);\n return (\n <span style={{ color: baseColor, ...style, ...flashTextStyle(direction) }}>\n {formatValue(value, precision)}\n </span>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// @devicechain/widget-sdk \u2014 the supported surface for writing a dashboard widget.\n//\n// A widget is declared once with `defineWidget` (a portable, JSON-only descriptor plus a\n// trusted React component) and made available to a host by listing it in\n// `createWidgetRegistry`. The built-in widgets are declared through exactly this\n// interface. The descriptor vocabulary is React-free and lives in @devicechain/dashboards;\n// it is re-exported here so a widget package imports from one place.\n\nexport { defineWidget } from './define-widget';\nexport type { ErasedComponent, ErasedWidgetDefinition, WidgetDefinition, WidgetImplementation } from './define-widget';\nexport { createWidgetRegistry, WidgetRegistryError, type WidgetRegistry } from './registry';\nexport type {\n EditorCommandChoice,\n EditorCommandChoices,\n WidgetComponent,\n WidgetEditorProps,\n WidgetEditorServices,\n WidgetProps,\n} from './widget';\n\n// The ambient host contexts (selection, candidates, time zone, theme root, subject). Defined\n// in this package and nowhere else \u2014 see ./context.\nexport {\n WidgetCandidatesProvider,\n WidgetDensityProvider,\n WidgetFrameStyleProvider,\n WidgetFreshnessProvider,\n WidgetSelectProvider,\n WidgetSubjectProvider,\n WidgetThemeRootProvider,\n WidgetTimeRangeProvider,\n WidgetTimeZoneProvider,\n useWidgetCandidates,\n useWidgetFrameStyle,\n useWidgetFreshness,\n useWidgetSelect,\n useWidgetSubject,\n useWidgetThemeRoot,\n useWidgetTimeRange,\n useWidgetTimeZone,\n type WidgetBodySize,\n type WidgetCandidates,\n type WidgetFreshness,\n type WidgetSelect,\n} from './context';\n\n// The building blocks every widget (built-in or third-party) composes from: the card frame,\n// theme-variable references, number/time formatting, the opt-in change-flash cue, option\n// readers, and the React bindings between the hub's channels and widget state. The chart\n// engine helpers are NOT here -- they are on the `/echarts` subpath, so importing this root\n// never pulls Apache ECharts (nor MapLibre, nor the built-in catalog).\nexport { WidgetFrame, type WidgetFrameProps } from './frame';\nexport { css, type ThemeVar } from './theme';\n// The curve's constants and the pure sticky-length step stay internal: a widget reaches them\n// through fitValueFont / fitValueFontCss / useStickyChars, so the curve can be retuned.\nexport { FIT_MIN_FONT_PX, fitValueFont, fitValueFontCss, type FitOptions } from './fit-text';\nexport {\n FONT_TOKENS,\n SPACE_TOKENS,\n WidgetTokenStyles,\n densityScale,\n fontVar,\n spaceVar,\n tokenPx,\n tokenStyle,\n type FontToken,\n type SizeToken,\n type SpaceToken,\n} from './tokens';\n// The body-size and density contexts are plumbing between the frame, the renderer and this\n// hook: a widget reads them through useWidgetScale, and only the renderer provides density.\nexport { SIZE_CLASS_BREAKS, useStickyChars, useWidgetScale, type SizeClass, type WidgetScale } from './scale';\nexport { HubConnectionBadge, type HubConnectionBadgeProps } from './connection-badge';\nexport { Freshness, FreshnessReadout, StaleBadge, useFreshness, useNow, type FreshnessProps, type FreshnessReading } from './freshness-readout';\nexport {\n ageSeconds,\n formatAge,\n freshnessStrings,\n isStale,\n newestSampleTime,\n resolveStaleAfterSeconds,\n viewerLocale,\n} from './freshness';\nexport { formatDateTime, formatTimestamp, formatValue } from './format';\nexport {\n ALARM_LEVELS,\n SEVERITY_LEVELS,\n SEVERITY_TOKENS,\n isAlarmLevel,\n isSeverityLevel,\n levelColor,\n severityTokenCss,\n severityVar,\n useSeverityColors,\n type AlarmLevel,\n type SeverityLevel,\n} from './severity';\nexport {\n bandAt,\n optThresholds,\n parseThresholds,\n thresholdBands,\n type Threshold,\n type ThresholdBand,\n} from './thresholds';\nexport {\n FlashValue,\n flashBackgroundStyle,\n flashTextStyle,\n useFlashOnChange,\n type FlashDirection,\n} from './flash';\nexport { optBoolean, optNumber, optString, pickSample, primaryMeasurementName } from './widget-options';\nexport {\n useAlarmStream,\n useCandidates,\n useCommandStream,\n useDashboardHub,\n useDatasourceAvailability,\n useElementSize,\n useHubConnectionState,\n useLocationStream,\n useMeasurementStream,\n MAX_SPAN_SAMPLES,\n withinSpan,\n useResolvedBindings,\n useSlotCandidates,\n type AggregateStreamState,\n type AlarmStreamState,\n type CandidatesState,\n type CommandStreamState,\n type ConnectionStateSource,\n type DatasourceAvailability,\n type ElementSize,\n type LocationStreamState,\n type MeasurementStreamOptions,\n type MeasurementStreamState,\n} from './hooks';\n\n// The portable descriptor vocabulary.\nexport {\n SDK_API_VERSION,\n WidgetCatalogError,\n defineWidgetDescriptor,\n isBuiltinWidgetId,\n isNamespacedWidgetId,\n validateWidgetDescriptor,\n type AckOptionRef,\n type BooleanOptionSpec,\n type BuiltinWidgetId,\n type ChannelBindingPolicy,\n type ChannelSnapshot,\n type ConstBinding,\n type DatasourcePolicy,\n type DatasourceSelectorKind,\n type DefaultsOf,\n type DescriptorIssue,\n type EnumOptionSpec,\n type ListItemFieldSpec,\n type ListOptionSpec,\n type GreaterThanConstraint,\n type HubConnectionState,\n type NamespacedWidgetId,\n type NumberOptionRef,\n type NumberOptionSpec,\n type OptionConstraint,\n type OptionSpec,\n type OptionsOf,\n type StringOptionRef,\n type StringOptionSpec,\n type WidgetCatalog,\n type WidgetChannel,\n type WidgetDescriptor,\n type WidgetDisplay,\n type WidgetId,\n type WidgetInteractionDeclaration,\n type WidgetOptionSpecs,\n} from '@devicechain/dashboards';\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["useMemo", "jsx", "useEffect", "useState", "useEffect", "useState", "merged", "useEffect", "useMemo", "useRef", "useMemo", "useRef", "useEffect", "jsx", "jsxs", "useMemo", "jsx", "jsxs", "useEffect", "useRef", "useState", "jsx", "useRef", "useState", "useEffect", "WidgetCatalogError"]
|
|
7
7
|
}
|
package/dist/scale.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { type WidgetBodySize } from './context';
|
|
2
|
+
import { type SizeToken } from './tokens';
|
|
3
|
+
export type SizeClass = 'compact' | 'regular' | 'large';
|
|
4
|
+
export declare const SIZE_CLASS_BREAKS: {
|
|
5
|
+
readonly compactWidth: 120;
|
|
6
|
+
readonly compactHeight: 72;
|
|
7
|
+
readonly largeWidth: 420;
|
|
8
|
+
readonly largeHeight: 220;
|
|
9
|
+
};
|
|
10
|
+
export declare function sizeClassOf(size: WidgetBodySize, scale?: number): SizeClass;
|
|
11
|
+
export interface WidgetScale {
|
|
12
|
+
sizeClass: SizeClass;
|
|
13
|
+
scale: number;
|
|
14
|
+
size: WidgetBodySize;
|
|
15
|
+
px: (token: SizeToken) => number;
|
|
16
|
+
}
|
|
17
|
+
export declare function makeWidgetScale(size: WidgetBodySize, scale: number): WidgetScale;
|
|
18
|
+
export declare function useWidgetScale(): WidgetScale;
|
|
19
|
+
export declare function useStickyChars(chars: number, holdMs?: number): number;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export declare const SEVERITY_LEVELS: readonly ['normal', 'warning', 'minor', 'major', 'critical'];
|
|
2
|
+
export type SeverityLevel = (typeof SEVERITY_LEVELS)[number];
|
|
3
|
+
export declare function isSeverityLevel(value: unknown): value is SeverityLevel;
|
|
4
|
+
export declare const ALARM_LEVELS: readonly ['warning', 'minor', 'major', 'critical'];
|
|
5
|
+
export type AlarmLevel = (typeof ALARM_LEVELS)[number];
|
|
6
|
+
export declare function isAlarmLevel(level: SeverityLevel | undefined): level is AlarmLevel;
|
|
7
|
+
export declare function levelColor(colors: Readonly<Record<AlarmLevel, string>>, level: SeverityLevel | undefined): string | undefined;
|
|
8
|
+
export declare const SEVERITY_TOKENS: Readonly<Record<AlarmLevel, {
|
|
9
|
+
light: string;
|
|
10
|
+
dark: string;
|
|
11
|
+
}>>;
|
|
12
|
+
export declare function severityVar(level: AlarmLevel): string;
|
|
13
|
+
export declare function severityTokenCss(options?: {
|
|
14
|
+
hostWins?: boolean;
|
|
15
|
+
}): string;
|
|
16
|
+
export declare function severityFallbackColors(): Record<AlarmLevel, string>;
|
|
17
|
+
export declare function severityColorsAt(el: Element, style?: CSSStyleDeclaration): Record<AlarmLevel, string>;
|
|
18
|
+
export declare function useSeverityColors(): Record<AlarmLevel, string>;
|
package/dist/theme.d.ts
CHANGED
|
@@ -1,2 +1,3 @@
|
|
|
1
|
+
import type { ThemeColorToken } from '@devicechain/dashboards';
|
|
1
2
|
export declare function css(name: ThemeVar): string;
|
|
2
|
-
export type ThemeVar =
|
|
3
|
+
export type ThemeVar = ThemeColorToken;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type SeverityLevel } from './severity';
|
|
2
|
+
export interface Threshold {
|
|
3
|
+
value: number;
|
|
4
|
+
level: SeverityLevel;
|
|
5
|
+
label?: string;
|
|
6
|
+
}
|
|
7
|
+
export interface ThresholdBand {
|
|
8
|
+
from: number;
|
|
9
|
+
to: number;
|
|
10
|
+
level: SeverityLevel | undefined;
|
|
11
|
+
label?: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function parseThresholds(raw: unknown): Threshold[];
|
|
14
|
+
export declare function optThresholds(options: Record<string, unknown> | undefined, key?: string): Threshold[];
|
|
15
|
+
export declare function bandAt(thresholds: readonly Threshold[], value: number | null | undefined): Threshold | undefined;
|
|
16
|
+
export declare function thresholdBands(thresholds: readonly Threshold[], min: number, max: number): ThresholdBand[];
|
package/dist/tokens.d.ts
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type CanvasDensity } from '@devicechain/dashboards';
|
|
2
|
+
import type { CSSProperties } from 'react';
|
|
3
|
+
export declare const FONT_TOKENS: {
|
|
4
|
+
readonly xs: 11;
|
|
5
|
+
readonly sm: 12;
|
|
6
|
+
readonly md: 13;
|
|
7
|
+
readonly lg: 16;
|
|
8
|
+
readonly xl: 20;
|
|
9
|
+
readonly display: 28;
|
|
10
|
+
};
|
|
11
|
+
export type FontToken = keyof typeof FONT_TOKENS;
|
|
12
|
+
export declare const SPACE_TOKENS: {
|
|
13
|
+
readonly 1: 4;
|
|
14
|
+
readonly 2: 8;
|
|
15
|
+
readonly 3: 12;
|
|
16
|
+
readonly 4: 16;
|
|
17
|
+
};
|
|
18
|
+
export type SpaceToken = keyof typeof SPACE_TOKENS;
|
|
19
|
+
export type SizeToken = `font-${FontToken}` | `space-${SpaceToken}`;
|
|
20
|
+
export declare function densityScale(density: CanvasDensity | undefined): number;
|
|
21
|
+
export declare function fontVar(token: FontToken): string;
|
|
22
|
+
export declare function spaceVar(token: SpaceToken): string;
|
|
23
|
+
export declare function tokenPx(token: SizeToken, scale?: number): number;
|
|
24
|
+
export declare function tokenStyle(scale: number): CSSProperties;
|
|
25
|
+
export declare function WidgetTokenStyles(): import("react").JSX.Element;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@devicechain/widget-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.22.0",
|
|
4
4
|
"description": "The DeviceChain widget SDK: the typed contract for writing a dashboard widget as a React component, and the sealed registry that makes it available to a host. Declare a widget once (portable descriptor plus trusted component) and register it beside the built-ins.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"devicechain",
|
|
@@ -51,12 +51,12 @@
|
|
|
51
51
|
"test": "vitest run"
|
|
52
52
|
},
|
|
53
53
|
"peerDependencies": {
|
|
54
|
-
"@devicechain/dashboards": "0.
|
|
54
|
+
"@devicechain/dashboards": "0.22.0",
|
|
55
55
|
"echarts": "^6.0.0",
|
|
56
56
|
"react": "^19.3.0"
|
|
57
57
|
},
|
|
58
58
|
"devDependencies": {
|
|
59
|
-
"@devicechain/dashboards": "0.
|
|
59
|
+
"@devicechain/dashboards": "0.22.0",
|
|
60
60
|
"@testing-library/react": "^16.3.3",
|
|
61
61
|
"@types/node": "^26.6.5",
|
|
62
62
|
"@types/react": "^19.3.0",
|