@devicechain/widgets 0.23.0 → 0.24.0-rc.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (97) hide show
  1. package/README.md +8 -3
  2. package/dist/aggregate-fold.d.ts +3 -2
  3. package/dist/aggregate-strings.d.ts +1 -0
  4. package/dist/alarm-strings.d.ts +18 -0
  5. package/dist/binding-policy.d.ts +4 -0
  6. package/dist/bound-devices-notice.d.ts +30 -0
  7. package/dist/builtin-descriptors.d.ts +290 -249
  8. package/dist/builtin-option-docs.d.ts +2 -0
  9. package/dist/builtin-registry.d.ts +249 -226
  10. package/dist/category-geometry.d.ts +1 -0
  11. package/dist/chunks/{aggregate-feed-BD3CHNFU.js → aggregate-feed-4FSBQYJL.js} +39 -11
  12. package/dist/chunks/aggregate-feed-4FSBQYJL.js.map +7 -0
  13. package/dist/chunks/{aggregate-widgets-HZQI5HTX.js → aggregate-widgets-2ZQBW3YF.js} +376 -182
  14. package/dist/chunks/aggregate-widgets-2ZQBW3YF.js.map +7 -0
  15. package/dist/chunks/alarm-widgets-X4PX5MYJ.js +72 -0
  16. package/dist/chunks/alarm-widgets-X4PX5MYJ.js.map +7 -0
  17. package/dist/chunks/chunk-546TK2QG.js +722 -0
  18. package/dist/chunks/chunk-546TK2QG.js.map +7 -0
  19. package/dist/chunks/{chunk-WBKHYZU2.js → chunk-62CHYCHI.js} +6 -62
  20. package/dist/chunks/chunk-62CHYCHI.js.map +7 -0
  21. package/dist/chunks/chunk-BMFQOTYG.js +41 -0
  22. package/dist/chunks/chunk-BMFQOTYG.js.map +7 -0
  23. package/dist/chunks/{chunk-2D7JNKFE.js → chunk-JFH4QMLA.js} +28 -4
  24. package/dist/chunks/chunk-JFH4QMLA.js.map +7 -0
  25. package/dist/chunks/chunk-QMGKB6J3.js +84 -0
  26. package/dist/chunks/chunk-QMGKB6J3.js.map +7 -0
  27. package/dist/chunks/chunk-XNEBAY4J.js +19 -0
  28. package/dist/chunks/chunk-XNEBAY4J.js.map +7 -0
  29. package/dist/chunks/chunk-YYJV54BV.js +58 -0
  30. package/dist/chunks/chunk-YYJV54BV.js.map +7 -0
  31. package/dist/chunks/compare-feed-AY3CVKUZ.js +346 -0
  32. package/dist/chunks/compare-feed-AY3CVKUZ.js.map +7 -0
  33. package/dist/chunks/{control-widgets-3V2LSSRF.js → control-widgets-UCTJJJ2D.js} +267 -78
  34. package/dist/chunks/control-widgets-UCTJJJ2D.js.map +7 -0
  35. package/dist/chunks/event-markers-feed-GEGTIHXT.js +373 -0
  36. package/dist/chunks/event-markers-feed-GEGTIHXT.js.map +7 -0
  37. package/dist/chunks/{fleet-feed-5QGDLOV5.js → fleet-feed-2SKDR6ZQ.js} +47 -8
  38. package/dist/chunks/fleet-feed-2SKDR6ZQ.js.map +7 -0
  39. package/dist/chunks/presence-widgets-2LIUKN4Y.js +350 -0
  40. package/dist/chunks/presence-widgets-2LIUKN4Y.js.map +7 -0
  41. package/dist/command-control-strings.d.ts +5 -0
  42. package/dist/compare-feed.d.ts +13 -0
  43. package/dist/compare-model.d.ts +38 -0
  44. package/dist/compare-strings.d.ts +13 -0
  45. package/dist/documented-descriptors.d.ts +4826 -0
  46. package/dist/event-markers-feed.d.ts +11 -0
  47. package/dist/event-markers-model.d.ts +38 -0
  48. package/dist/event-markers-strings.d.ts +20 -0
  49. package/dist/event-markers-types.d.ts +7 -0
  50. package/dist/fleet-count-model.d.ts +13 -0
  51. package/dist/fleet-feed.d.ts +1 -0
  52. package/dist/fleet-model.d.ts +1 -0
  53. package/dist/fleet-strings.d.ts +11 -0
  54. package/dist/index.d.ts +6 -3
  55. package/dist/index.js +946 -398
  56. package/dist/index.js.map +4 -4
  57. package/dist/presence-feed.d.ts +24 -0
  58. package/dist/presence-model.d.ts +13 -0
  59. package/dist/presence-strings.d.ts +19 -0
  60. package/dist/sparkline.d.ts +23 -0
  61. package/dist/use-bound-devices.d.ts +26 -0
  62. package/dist/widgets/aggregate-lazy.d.ts +81 -46
  63. package/dist/widgets/aggregate-widgets.d.ts +1 -0
  64. package/dist/widgets/alarm-grouped.d.ts +17 -0
  65. package/dist/widgets/alarm-lazy.d.ts +63 -16
  66. package/dist/widgets/alarm-severity.d.ts +8 -0
  67. package/dist/widgets/alarm-table.d.ts +2 -0
  68. package/dist/widgets/alarm-type-tabs.d.ts +10 -0
  69. package/dist/widgets/alarm-widgets.d.ts +2 -0
  70. package/dist/widgets/command-stepper.d.ts +22 -0
  71. package/dist/widgets/control-lazy.d.ts +16 -24
  72. package/dist/widgets/device-status.d.ts +4 -0
  73. package/dist/widgets/fleet-count.d.ts +2 -0
  74. package/dist/widgets/gauge.d.ts +2 -15
  75. package/dist/widgets/image.d.ts +2 -6
  76. package/dist/widgets/label.d.ts +2 -6
  77. package/dist/widgets/latest-card.d.ts +2 -13
  78. package/dist/widgets/lazy-chunk.d.ts +7 -2
  79. package/dist/widgets/level-lazy.d.ts +14 -96
  80. package/dist/widgets/map.d.ts +8 -5
  81. package/dist/widgets/online-doughnut.d.ts +2 -0
  82. package/dist/widgets/presence-lazy.d.ts +27 -0
  83. package/dist/widgets/presence-widgets.d.ts +3 -0
  84. package/dist/widgets/selection-lazy.d.ts +2 -4
  85. package/dist/widgets/table-lazy.d.ts +2 -7
  86. package/dist/widgets/time-series-chart.d.ts +62 -12
  87. package/package.json +13 -13
  88. package/dist/chunks/aggregate-feed-BD3CHNFU.js.map +0 -7
  89. package/dist/chunks/aggregate-widgets-HZQI5HTX.js.map +0 -7
  90. package/dist/chunks/alarm-widgets-7UY6MIYZ.js +0 -9
  91. package/dist/chunks/alarm-widgets-7UY6MIYZ.js.map +0 -7
  92. package/dist/chunks/chunk-2D7JNKFE.js.map +0 -7
  93. package/dist/chunks/chunk-6OGI4UFJ.js +0 -367
  94. package/dist/chunks/chunk-6OGI4UFJ.js.map +0 -7
  95. package/dist/chunks/chunk-WBKHYZU2.js.map +0 -7
  96. package/dist/chunks/control-widgets-3V2LSSRF.js.map +0 -7
  97. package/dist/chunks/fleet-feed-5QGDLOV5.js.map +0 -7
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../../src/widgets/command-slider.tsx", "../../src/command-control-core.ts", "../../src/widgets/command-stepper.tsx", "../../src/command-control.tsx", "../../src/command-control-strings.ts", "../../src/widgets/command-toggle.tsx"],
4
+ "sourcesContent": ["// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// command-slider \u2014 sends a command carrying ONE numeric parameter, chosen with a slider. The range\n// comes from the command definition baked into the options (the same snapshot the command button\n// renders its form from); the author's min/max/step fill only what the definition leaves open.\n//\n// \uD83D\uDD34 DRAGGING SENDS NOTHING. The thumb stages a value; releasing it on the slider (or pressing\n// Enter, for a keyboard user) sends it, once. A staged value that is not committed is DISCARDED,\n// never sent later: a cancelled pointer (the browser took the gesture for a scroll), a lost\n// capture or a release off the slider, Escape, or focus leaving the slider. While a send is in\n// flight the slider is aria-disabled and ignores input, so a second release cannot queue a second\n// command; it is not `disabled`, which would drop a keyboard user's focus on the page body.\n//\n// Keyboard: the arrow, Page and Home/End keys stage a value (the native range input's own\n// behaviour, so aria-valuetext follows the staged value) and Enter sends it. That departure from a\n// plain slider is announced: the input is described by a hint saying so, as well as by the\n// last-sent caption, which reads \"Not sent yet. ... press Enter to send\" while a value is staged.\n//\n// \uD83D\uDD34 IT SHOWS WHAT WAS SENT, NOT WHAT THE DEVICE IS DOING. The thumb rests at the last value sent\n// (read off the command history) and the caption says so with that command's delivery status.\n// With nothing sent the thumb rests at the bottom of the range and the readout is a dash.\n\nimport { useEffect, useId, useMemo, useRef, useState, type ReactNode } from 'react';\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fitValueFont,\n fitValueFontCss,\n fontVar,\n optNumber,\n optString,\n spaceVar,\n useStickyChars,\n WidgetFrame,\n type CommandStreamState,\n type WidgetProps,\n type WidgetScale,\n} from '@devicechain/widget-sdk';\n\nimport {\n bindControl,\n clampToRange,\n lastSent,\n payloadFor,\n type Binding,\n} from '../command-control-core';\nimport { StepperBody } from './command-stepper';\nimport {\n ControlEmpty,\n ControlNotes,\n readOnlyId,\n LastSentCaption,\n refusalText,\n TOUCH_TARGET_PX,\n useCommandSender,\n type CommandSender,\n} from '../command-control';\n\ntype Bound = Extract<Binding, { ok: true }>;\n\nconst withUnit = (v: number, unit: string | undefined) => (unit ? `${v} ${unit}` : String(v));\n\nexport function CommandSlider({ widget, data, actions }: WidgetProps<CommandStreamState>) {\n const commandName = optString(widget.options, 'commandName');\n const commandLabel = optString(widget.options, 'commandLabel') ?? commandName;\n const parameterSchema = optString(widget.options, 'parameterSchema') ?? null;\n const parameter = optString(widget.options, 'parameter');\n const min = optNumber(widget.options, 'min');\n const max = optNumber(widget.options, 'max');\n const step = optNumber(widget.options, 'step');\n // Additive: absent, or anything but 'stepper', is the slider as before.\n const stepper = optString(widget.options, 'mode') === 'stepper';\n\n const binding = useMemo(\n () => bindControl('number', { commandName, parameterSchema, parameter, min, max, step }),\n [commandName, parameterSchema, parameter, min, max, step],\n );\n const sender = useCommandSender(\n actions,\n data.deviceToken,\n commandName,\n `${commandName ?? ''}::${parameterSchema ?? ''}::${parameter ?? ''}`,\n );\n\n const last =\n binding.ok && commandName\n ? lastSent('number', data.commands, commandName, binding.param.name, sender.optimistic)\n : null;\n\n // The staged value. A ref mirrors it so a release reads what the last change set, not the value\n // the previous render closed over.\n const [draft, setDraftState] = useState<number | null>(null);\n const draftRef = useRef<number | null>(null);\n const setDraft = (v: number | null) => {\n draftRef.current = v;\n setDraftState(v);\n };\n const captionId = useId();\n const hintId = useId();\n\n // \uD83D\uDD34 A STAGED STEPPER VALUE DOES NOT OUTLIVE A NEWER COMMAND. The stepper keeps a value staged\n // until Send (a slider's lasts only as long as the drag), so it can sit on a wall display while\n // someone else sends this command. It was chosen relative to the value that was current then; a\n // Send after that would quietly overwrite the newer one. A new last-sent command discards it, and\n // the readout returns to what was actually sent. The control's own send clears the draft before\n // its command appears, so this never fires on it.\n const lastToken = last?.token;\n useEffect(() => {\n if (!stepper || draftRef.current === null) return;\n draftRef.current = null;\n setDraftState(null);\n }, [lastToken, stepper]);\n\n // A pointer pressed on the slider whose release has not been seen. A release anywhere but on\n // the slider, a cancel or a lost capture while it is set discards the staged value.\n const pressed = useRef(false);\n const offRelease = useRef<(() => void) | null>(null);\n useEffect(() => () => offRelease.current?.(), []);\n const discard = () => {\n pressed.current = false;\n offRelease.current?.();\n offRelease.current = null;\n if (draftRef.current !== null) setDraft(null);\n };\n const press = (input: HTMLInputElement) => {\n pressed.current = true;\n offRelease.current?.();\n const doc = input.ownerDocument;\n const onRelease = (e: PointerEvent) => {\n // The slider's own release commits through onPointerUp; this one sees releases elsewhere.\n if (e.target instanceof Node && input.contains(e.target)) return;\n discard();\n };\n doc.addEventListener('pointerup', onRelease, true);\n offRelease.current = () => doc.removeEventListener('pointerup', onRelease, true);\n };\n\n const commit = () => {\n if (!binding.ok) return;\n const staged = draftRef.current;\n if (staged === null || !sender.canSend) return;\n if (sender.send(payloadFor(binding, staged), staged)) setDraft(null);\n };\n const release = () => {\n pressed.current = false;\n offRelease.current?.();\n offRelease.current = null;\n commit();\n };\n\n const caption = binding.ok ? (\n <LastSentCaption\n id={captionId}\n last={last}\n strings={sender.strings}\n truncated={data.total > data.commands.length}\n format={(v) => (typeof v === 'number' ? withUnit(v, binding.param.unit) : sender.strings.noValue)}\n override={\n draft !== null\n ? stepper\n ? sender.strings.stepperUnsent\n : sender.strings.unsent\n : sender.pending\n ? sender.strings.sending\n : undefined\n }\n />\n ) : null;\n const onStage = (v: number) => {\n // A value moved while a send is in flight is not staged: the control is aria-disabled.\n if (binding.ok && !sender.pending) setDraft(clampToRange(binding.range!, v));\n };\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {(scale) => {\n if (!binding.ok) return <ControlEmpty message={refusalText(binding.refusal, sender.strings, 'number')} />;\n const common = {\n binding,\n label: commandLabel ?? binding.param.name,\n draft,\n lastValue: typeof last?.value === 'number' ? last.value : undefined,\n sender,\n captionId,\n hintId,\n caption,\n onStage,\n onCommit: commit,\n onDiscard: discard,\n scale,\n };\n return stepper ? (\n <StepperBody {...common} />\n ) : (\n <SliderBody\n {...common}\n onPress={press}\n onRelease={release}\n onLostCapture={() => {\n if (pressed.current) discard();\n }}\n />\n );\n }}\n </WidgetFrame>\n );\n}\n\nfunction SliderBody({\n binding,\n label,\n draft,\n lastValue,\n sender,\n captionId,\n hintId,\n caption,\n onStage,\n onCommit,\n onPress,\n onRelease,\n onDiscard,\n onLostCapture,\n scale: ws,\n}: {\n binding: Bound;\n label: string;\n draft: number | null;\n lastValue: number | undefined;\n sender: CommandSender;\n captionId: string;\n hintId: string;\n caption: ReactNode;\n onStage: (v: number) => void;\n onCommit: () => void;\n onPress: (input: HTMLInputElement) => void;\n onRelease: () => void;\n onDiscard: () => void;\n onLostCapture: () => void;\n scale: WidgetScale;\n}) {\n const range = binding.range!;\n const unit = binding.param.unit;\n // Read-only is `disabled`; in flight is only aria-disabled, so focus stays on the slider.\n const disabled = !sender.canSend;\n const busy = sender.pending;\n const rest = lastValue !== undefined ? clampToRange(range, lastValue) : range.min;\n const shown = draft ?? rest;\n const known = draft !== null || lastValue !== undefined;\n const readout = known ? String(shown) : '\u2014';\n\n // The readout fits the room left beside the track, with the same curve the value widgets use.\n const chars = useStickyChars(readout.length + (unit && known ? Math.ceil(unit.length / 2) + 1 : 0));\n const floor = Math.round(FIT_MIN_FONT_PX * ws.scale);\n const measured = ws.size.width > 0 && ws.size.height > 0;\n const room = {\n width: Math.floor(ws.size.width * 0.34),\n height: Math.max(TOUCH_TARGET_PX, Math.floor(ws.size.height * 0.5)),\n };\n const font = measured ? fitValueFont(room, chars, { floor }) : fitValueFontCss(chars, { floor });\n\n return (\n <div\n style={{\n height: '100%',\n boxSizing: 'border-box',\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'safe center',\n gap: spaceVar(1),\n overflow: 'hidden',\n }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: spaceVar(2), minHeight: TOUCH_TARGET_PX }}>\n <input\n type=\"range\"\n aria-label={label}\n aria-valuetext={withUnit(shown, unit)}\n aria-describedby={`${hintId} ${captionId}${sender.readOnlyReason ? ` ${readOnlyId(captionId)}` : ''}`}\n aria-disabled={busy || undefined}\n aria-busy={busy || undefined}\n min={range.min}\n max={range.max}\n step={range.step}\n value={shown}\n disabled={disabled}\n data-staged={draft !== null ? 'true' : undefined}\n onChange={(e) => onStage(Number(e.target.value))}\n onPointerDown={(e) => onPress(e.currentTarget)}\n onPointerUp={onRelease}\n onPointerCancel={onDiscard}\n onLostPointerCapture={onLostCapture}\n onBlur={onDiscard}\n onKeyDown={(e) => {\n if (e.key === 'Enter') onCommit();\n else if (e.key === 'Escape') onDiscard();\n }}\n style={{\n flex: '1 1 0',\n minWidth: 0,\n height: TOUCH_TARGET_PX,\n margin: 0,\n accentColor: css('primary'),\n cursor: disabled || busy ? 'not-allowed' : 'pointer',\n }}\n />\n <span id={hintId} hidden>\n {sender.strings.sliderHint}\n </span>\n <output\n aria-hidden=\"true\"\n data-slider-readout=\"\"\n style={{\n flex: '0 0 auto',\n fontSize: font,\n fontWeight: 600,\n lineHeight: 1.1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color: draft !== null ? css('foreground') : css('muted-foreground'),\n }}\n >\n {readout}\n {unit && known ? (\n <span style={{ fontSize: fontVar('sm'), marginLeft: spaceVar(1), color: css('muted-foreground') }}>{unit}</span>\n ) : null}\n </output>\n </div>\n {/* One row, so the short read-only caption costs no height at a two-row widget's minimum span. */}\n <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', columnGap: spaceVar(2) }}>\n {caption}\n <ControlNotes sender={sender} captionId={captionId} />\n </div>\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The pure half of the command slider and toggle: which parameter a control is bound to and why\n// it may refuse to be, the payload it sends, and the last value sent read back off the command\n// history. Nothing here reads a widget's options (the widgets hand the values in), so it is\n// tested on its own.\n//\n// \uD83D\uDD34 A CONTROL THAT CANNOT SEND SAYS SO. Every way the binding can be wrong is a named refusal\n// with the parameter it concerns, never a fallback value: a control that guesses at its range, its\n// type or a required parameter sends a plausible command the author did not mean.\n\nimport type { CommandParameter, CommandRow } from '@devicechain/dashboards';\n\nimport { buildPayload, isScalar, parseParameterSchema, validateParams } from './widgets/command-params';\n\nexport type ControlKind = 'number' | 'boolean';\n\nexport type Refusal =\n | { code: 'noCommand' }\n | { code: 'setParameter' }\n | { code: 'parameterMissing'; name: string }\n | { code: 'wrongType'; name: string; kind: ControlKind }\n | { code: 'fixedChoices'; name: string }\n | { code: 'otherRequired'; names: string[] }\n | { code: 'badDefault'; names: string[] }\n | { code: 'setRange' }\n | { code: 'badRange' };\n\nexport interface BindInput {\n commandName: string | undefined;\n parameterSchema: string | null | undefined;\n parameter: string | undefined;\n // Authored fallbacks for a slider: used only where the command definition leaves the end (or\n // the step) open. The definition is the authority on what the command accepts.\n min?: number;\n max?: number;\n step?: number;\n}\n\nexport interface Range {\n min: number;\n max: number;\n step: number;\n integer: boolean;\n}\n\nexport type Binding =\n | { ok: true; kind: ControlKind; param: CommandParameter; range?: Range; sent: CommandParameter[] }\n | { ok: false; refusal: Refusal };\n\nconst refuse = (refusal: Refusal): Binding => ({ ok: false, refusal });\n\n// 100 notches across a continuous range: the step a DOUBLE slider gets when nobody set one.\nconst DEFAULT_NOTCHES = 100;\n\nexport function bindControl(kind: ControlKind, input: BindInput): Binding {\n if (!input.commandName) return refuse({ code: 'noCommand' });\n const params = parseParameterSchema(input.parameterSchema);\n const wanted = (p: CommandParameter) =>\n isScalar(p) && (kind === 'boolean' ? p.dataType === 'BOOLEAN' : p.dataType === 'INT' || p.dataType === 'DOUBLE');\n\n let param: CommandParameter | undefined;\n if (input.parameter) {\n param = params.find((p) => p.name === input.parameter);\n if (!param) return refuse({ code: 'parameterMissing', name: input.parameter });\n if (!wanted(param)) return refuse({ code: 'wrongType', name: param.name, kind });\n } else {\n // Unambiguous or not at all: one candidate of the right type is the parameter; none or several\n // is the author's to resolve.\n const candidates = params.filter(wanted);\n if (candidates.length !== 1) return refuse({ code: 'setParameter' });\n param = candidates[0];\n }\n if (param.enum && param.enum.length > 0) return refuse({ code: 'fixedChoices', name: param.name });\n\n // Every other parameter either has a declared default (sent as is) or is optional (omitted).\n // A required one with neither would have to be invented, so the control refuses instead.\n const others = params.filter((p) => p !== param);\n const unsatisfied = others.filter((p) => p.required && (!isScalar(p) || p.default == null || p.default === ''));\n if (unsatisfied.length > 0) return refuse({ code: 'otherRequired', names: unsatisfied.map((p) => p.name) });\n const defaulted = others.filter((p) => isScalar(p) && p.default != null && p.default !== '');\n // A default is sent as written, so one the definition's own rules reject (not a number, not a\n // whole number, outside its range) would be a command the server refuses on every send.\n const invalid = new Set(\n Object.keys(validateParams(defaulted, Object.fromEntries(defaulted.map((p) => [p.name, p.default ?? ''])))),\n );\n for (const p of defaulted) if (p.enum && p.enum.length > 0 && !p.enum.includes(p.default ?? '')) invalid.add(p.name);\n if (invalid.size > 0) return refuse({ code: 'badDefault', names: defaulted.filter((p) => invalid.has(p.name)).map((p) => p.name) });\n const sent = [param, ...defaulted];\n\n if (kind === 'boolean') return { ok: true, kind, param, sent };\n\n const min = param.minValue ?? input.min;\n const max = param.maxValue ?? input.max;\n if (min === undefined || max === undefined) return refuse({ code: 'setRange' });\n if (!(max > min)) return refuse({ code: 'badRange' });\n const integer = param.dataType === 'INT';\n const step = input.step !== undefined && input.step > 0 ? input.step : integer ? 1 : (max - min) / DEFAULT_NOTCHES;\n return { ok: true, kind, param, range: { min, max, step, integer }, sent };\n}\n\n// The decimals a step carries, so snapped values do not pick up float noise (0.1 steps).\nfunction decimalsOf(n: number): number {\n const text = String(n);\n const e = /e-(\\d+)$/.exec(text);\n if (e) return Number(e[1]);\n const dot = text.indexOf('.');\n return dot < 0 ? 0 : text.length - dot - 1;\n}\n\n// clampToRange puts a raw value on the slider's grid: inside [min, max], on a whole step from min\n// (an INT rounds to a whole number whatever the step), and never NaN.\nexport function clampToRange(range: Range, raw: number): number {\n const finite = Number.isFinite(raw) ? raw : range.min;\n const bounded = Math.min(range.max, Math.max(range.min, finite));\n const steps = Math.round((bounded - range.min) / range.step);\n const decimals = Math.max(decimalsOf(range.step), decimalsOf(range.min));\n let snapped = Number((range.min + steps * range.step).toFixed(Math.min(decimals, 12)));\n if (snapped > range.max) snapped = Number((snapped - range.step).toFixed(Math.min(decimals, 12)));\n if (range.integer) snapped = Math.round(snapped);\n return Math.min(range.max, Math.max(range.min, snapped));\n}\n\n// The JSON the command carries: the bound parameter as its real type, the other parameters that\n// have a declared default, nothing else.\nexport function payloadFor(binding: Extract<Binding, { ok: true }>, value: number | boolean): string {\n const values: Record<string, string> = {};\n for (const p of binding.sent) {\n values[p.name] = p.default ?? '';\n if (p === binding.param) values[p.name] = String(value);\n }\n const payload = buildPayload(binding.sent, values);\n // sent always holds the bound parameter, so a payload always exists.\n return payload as string;\n}\n\nexport type DeliveryGroup = 'pending' | 'delivered' | 'failed' | 'unknown';\n\n// Which of the three things an operator asks of a command its status answers. An unrecognised\n// status is its own group, rendered as the raw word: it is not known to be pending.\nexport function deliveryGroup(status: string): DeliveryGroup {\n switch (status) {\n case 'QUEUED':\n case 'HELD':\n case 'SENT':\n case 'PARKED':\n return 'pending';\n case 'SUCCESSFUL':\n return 'delivered';\n case 'FAILED':\n case 'TIMEOUT':\n case 'EXPIRED':\n case 'CANCELLED':\n return 'failed';\n default:\n return 'unknown';\n }\n}\n\nexport interface LastSent {\n token: string;\n // The value the command carried for the bound parameter; undefined when the command (sent from\n // somewhere else) did not carry one this control can read.\n value: number | boolean | undefined;\n status: string;\n}\n\n// An accepted send not yet seen in the history.\nexport interface Optimistic {\n token: string;\n value: number | boolean;\n}\n\nfunction readValue(kind: ControlKind, payload: string | null, name: string): number | boolean | undefined {\n if (!payload) return undefined;\n let parsed: unknown;\n try {\n parsed = JSON.parse(payload);\n } catch {\n return undefined;\n }\n if (typeof parsed !== 'object' || parsed === null) return undefined;\n const v = (parsed as Record<string, unknown>)[name];\n if (kind === 'number') return typeof v === 'number' && Number.isFinite(v) ? v : undefined;\n if (typeof v === 'boolean') return v;\n return undefined;\n}\n\n// lastSent reads the newest command of this control's name off the history. It is the newest, not\n// the newest that happens to parse: skipping to an older row would report a value that has since\n// been superseded. A send the history has not caught up with yet stands in until it does.\nexport function lastSent(\n kind: ControlKind,\n commands: readonly CommandRow[],\n commandName: string,\n parameter: string,\n optimistic: Optimistic | null,\n): LastSent | null {\n let newest: CommandRow | undefined;\n for (const c of commands) {\n if (c.name !== commandName) continue;\n if (!newest || (c.queuedTime ?? '') > (newest.queuedTime ?? '')) newest = c;\n }\n if (optimistic && !commands.some((c) => c.token === optimistic.token)) {\n return { token: optimistic.token, value: optimistic.value, status: 'QUEUED' };\n }\n if (!newest) return null;\n return { token: newest.token, value: readValue(kind, newest.payload, parameter), status: newest.status };\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The command slider's stepper mode: \u2212 / value / + and a Send button, for a touch wall where\n// dragging a thumb is too fine. The range and step come from the command parameter's declared\n// schema, exactly as the slider's do (command-slider.tsx binds them and owns the staged value).\n//\n// \uD83D\uDD34 PRESSING \u2212 OR + SENDS NOTHING. It stages a value; only Send (or Enter on the value) sends it,\n// once. A command that steps a setpoint or a rate is acted on by the device, and a held-down or\n// double-tapped button must not become a burst of commands. The explicit Send is also where a\n// disruptive command is chosen on purpose (the platform then holds it for the maintenance window).\n// Escape discards a staged value. Focus moving to the Send button does NOT discard it, and nor\n// does focus leaving the widget: on a touch wall a tap elsewhere is not a decision, and a browser\n// that never focuses a tapped button (Safari) would blur the value on every tap of \u2212 or +. What does\n// discard it is a newer command than the one it was staged against (command-slider.tsx).\n\nimport type { KeyboardEvent, ReactNode } from 'react';\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fitValueFont,\n fitValueFontCss,\n fontVar,\n spaceVar,\n useStickyChars,\n type WidgetScale,\n} from '@devicechain/widget-sdk';\n\nimport { clampToRange, type Binding } from '../command-control-core';\nimport { ControlNotes, readOnlyId, TOUCH_TARGET_PX, type CommandSender } from '../command-control';\n\ntype Bound = Extract<Binding, { ok: true }>;\n\nconst PAGE_STEPS = 10;\n\nconst button = (off: boolean) =>\n ({\n flex: '0 0 auto',\n boxSizing: 'border-box',\n minWidth: TOUCH_TARGET_PX,\n height: TOUCH_TARGET_PX,\n padding: `0 ${spaceVar(2)}`,\n border: `1px solid ${css('border')}`,\n borderRadius: 6,\n background: css('card'),\n color: css('foreground'),\n font: 'inherit',\n fontSize: fontVar('md'),\n fontWeight: 600,\n cursor: off ? 'not-allowed' : 'pointer',\n opacity: off ? 0.5 : 1,\n }) as const;\n\nexport function StepperBody({\n binding,\n label,\n draft,\n lastValue,\n sender,\n captionId,\n hintId,\n caption,\n onStage,\n onCommit,\n onDiscard,\n scale: ws,\n}: {\n binding: Bound;\n label: string;\n draft: number | null;\n lastValue: number | undefined;\n sender: CommandSender;\n captionId: string;\n hintId: string;\n caption: ReactNode;\n onStage: (v: number) => void;\n onCommit: () => void;\n onDiscard: () => void;\n scale: WidgetScale;\n}) {\n const range = binding.range!;\n const unit = binding.param.unit;\n const busy = sender.pending;\n const inert = !sender.canSend || busy;\n const rest = lastValue !== undefined ? clampToRange(range, lastValue) : range.min;\n const shown = draft ?? rest;\n const known = draft !== null || lastValue !== undefined;\n const readout = known ? String(shown) : '\u2014';\n const text = unit && known ? `${shown} ${unit}` : String(shown);\n\n // clampToRange snaps to the grid from min and trims float noise, so 0.1 steps stay 0.3, not 0.30000000000000004.\n const by = (steps: number) => {\n if (inert) return;\n const next = clampToRange(range, shown + steps * range.step);\n if (next !== shown || draft === null) onStage(next);\n };\n const canSendNow = draft !== null && !inert;\n\n const chars = useStickyChars(readout.length + (unit && known ? Math.ceil(unit.length / 2) + 1 : 0));\n const floor = Math.round(FIT_MIN_FONT_PX * ws.scale);\n const measured = ws.size.width > 0 && ws.size.height > 0;\n const font = measured\n ? fitValueFont({ width: Math.floor(ws.size.width * 0.3), height: Math.max(TOUCH_TARGET_PX, Math.floor(ws.size.height * 0.5)) }, chars, { floor })\n : fitValueFontCss(chars, { floor });\n\n const key = (e: KeyboardEvent) => {\n const k = e.key;\n if (k === 'ArrowUp' || k === 'ArrowRight') by(1);\n else if (k === 'ArrowDown' || k === 'ArrowLeft') by(-1);\n else if (k === 'PageUp') by(PAGE_STEPS);\n else if (k === 'PageDown') by(-PAGE_STEPS);\n else if (k === 'Home') inert || onStage(range.min);\n else if (k === 'End') inert || onStage(range.max);\n else if (k === 'Enter') onCommit();\n else if (k === 'Escape') onDiscard();\n else return;\n e.preventDefault();\n };\n\n const atMin = shown <= range.min;\n const atMax = shown >= range.max;\n return (\n <div\n style={{ height: '100%', boxSizing: 'border-box', display: 'flex', flexDirection: 'column', justifyContent: 'safe center', gap: spaceVar(1), overflow: 'hidden' }}\n >\n <div style={{ display: 'flex', alignItems: 'center', gap: spaceVar(1), minHeight: TOUCH_TARGET_PX }}>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={sender.strings.decrease}\n aria-disabled={inert || atMin || undefined}\n data-step=\"down\"\n onClick={() => by(-1)}\n style={button(inert || atMin)}\n >\n \u2212\n </button>\n <div\n role=\"spinbutton\"\n tabIndex={0}\n aria-label={label}\n aria-valuemin={range.min}\n aria-valuemax={range.max}\n aria-valuenow={known ? shown : undefined}\n aria-valuetext={known ? text : sender.strings.noValue}\n aria-describedby={`${hintId} ${captionId}${sender.readOnlyReason ? ` ${readOnlyId(captionId)}` : ''}`}\n aria-disabled={inert || undefined}\n aria-busy={busy || undefined}\n data-staged={draft !== null ? 'true' : undefined}\n onKeyDown={key}\n style={{\n flex: '1 1 0',\n minWidth: 0,\n textAlign: 'center',\n fontSize: font,\n fontWeight: 600,\n lineHeight: 1.1,\n whiteSpace: 'nowrap',\n fontVariantNumeric: 'tabular-nums',\n color: draft !== null ? css('foreground') : css('muted-foreground'),\n }}\n >\n {readout}\n {unit && known ? (\n <span style={{ fontSize: fontVar('sm'), marginLeft: spaceVar(1), color: css('muted-foreground') }}>{unit}</span>\n ) : null}\n </div>\n <button\n type=\"button\"\n tabIndex={-1}\n aria-label={sender.strings.increase}\n aria-disabled={inert || atMax || undefined}\n data-step=\"up\"\n onClick={() => by(1)}\n style={button(inert || atMax)}\n >\n +\n </button>\n <button\n type=\"button\"\n aria-disabled={!canSendNow || undefined}\n data-step=\"send\"\n onClick={() => canSendNow && onCommit()}\n style={{ ...button(!canSendNow), background: canSendNow ? css('primary') : css('card'), color: canSendNow ? css('primary-foreground') : css('foreground') }}\n >\n {sender.strings.send}\n </button>\n <span id={hintId} hidden>\n {sender.strings.stepperHint}\n </span>\n </div>\n <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', columnGap: spaceVar(2) }}>\n {caption}\n <ControlNotes sender={sender} captionId={captionId} />\n </div>\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// What the command slider and toggle share beyond the pure logic: the send itself (one at a time,\n// the permission gate, the refusal's reason), the \"last sent\" caption with its delivery status, and\n// the explained empty state. Neither control shows device state: the caption names what was SENT.\n\nimport type { CommandDispatch, WidgetActions } from '@devicechain/dashboards';\nimport { useCallback, useEffect, useRef, useState, type CSSProperties } from 'react';\n\nimport { css, fontVar, spaceVar } from '@devicechain/widget-sdk';\n\nimport { controlStrings, type ControlStrings } from './command-control-strings';\nimport { deliveryGroup, type LastSent, type Optimistic, type Refusal } from './command-control-core';\nimport { commandStatusColor, commandStatusLabel } from './widgets/command-status';\n\n// The smallest hit area a touch control may have, in CSS px.\nexport const TOUCH_TARGET_PX = 44;\n\nexport function refusalText(r: Refusal, s: ControlStrings, expect: 'number' | 'boolean'): string {\n switch (r.code) {\n case 'noCommand':\n return s.noCommand;\n case 'setParameter':\n return s.setParameter;\n case 'parameterMissing':\n return s.parameterMissing(r.name);\n case 'wrongType':\n return s.wrongType(r.name, expect === 'number' ? s.numberKind : s.booleanKind);\n case 'fixedChoices':\n return s.fixedChoices(r.name);\n case 'otherRequired':\n return s.otherRequired(r.names);\n case 'badDefault':\n return s.badDefault(r.names);\n case 'setRange':\n return s.setRange;\n case 'badRange':\n return s.badRange;\n }\n}\n\nexport interface CommandSender {\n strings: ControlStrings;\n // Why the control is read-only, or undefined when the viewer may send.\n readOnlyReason: string | undefined;\n canSend: boolean;\n // A dispatch is in flight.\n pending: boolean;\n error: string | null;\n // A send the platform accepted, until the history shows it.\n optimistic: Optimistic | null;\n // Returns whether a command was dispatched. At most one is in flight at a time.\n send: (payload: string, value: number | boolean) => boolean;\n}\n\n// useCommandSender owns one control's sending. `resetKey` identifies the configured command: when\n// it changes, an in-flight send's result is dropped rather than landing under the new command.\nexport function useCommandSender(\n actions: WidgetActions | undefined,\n deviceToken: string | null | undefined,\n commandName: string | undefined,\n resetKey: string,\n): CommandSender {\n const strings = controlStrings();\n const canWrite = actions?.can('command:write') ?? false;\n const hasSeam = typeof actions?.sendCommand === 'function';\n const canSend = canWrite && hasSeam && !!deviceToken && !!commandName;\n\n // The in-flight flag is a ref, not state: two clicks in the same tick both see the state before\n // either has re-rendered, and only a ref closes that window.\n const inFlight = useRef(false);\n const generation = useRef(0);\n const [pending, setPending] = useState(false);\n const [error, setError] = useState<string | null>(null);\n const [optimistic, setOptimistic] = useState<Optimistic | null>(null);\n\n useEffect(() => {\n generation.current += 1;\n inFlight.current = false;\n setPending(false);\n setError(null);\n setOptimistic(null);\n }, [resetKey]);\n\n const send = useCallback(\n (payload: string, value: number | boolean): boolean => {\n if (!canSend || inFlight.current || !actions?.sendCommand || !deviceToken || !commandName) return false;\n const gen = generation.current;\n inFlight.current = true;\n setPending(true);\n setError(null);\n const settle = () => {\n if (gen !== generation.current) return;\n inFlight.current = false;\n setPending(false);\n };\n let promise: Promise<CommandDispatch>;\n try {\n promise = actions.sendCommand(deviceToken, commandName, payload);\n } catch {\n setError(strings.sendFailed);\n settle();\n return true;\n }\n promise\n .then((dispatch) => {\n if (gen !== generation.current) return;\n // A refusal is shown with the server's reason (it is client-safe and the operator can act\n // on it); a thrown error is an availability failure whose text is internal, so it gets\n // one fixed message instead. A refused value is not \"last sent\".\n if (dispatch.status === 'rejected') setError(dispatch.reason);\n else setOptimistic({ token: dispatch.token, value });\n })\n .catch(() => {\n if (gen === generation.current) setError(strings.sendFailed);\n })\n .finally(settle);\n return true;\n },\n [actions, canSend, commandName, deviceToken, strings.sendFailed],\n );\n\n // A view with no send seam at all (a read-only embed) is told so, rather than blamed on the\n // viewer's permissions.\n const readOnlyReason = !hasSeam\n ? strings.noSeam\n : !canWrite\n ? strings.noPermission\n : !deviceToken\n ? strings.noDevice\n : undefined;\n return { strings, readOnlyReason, canSend, pending, error, optimistic, send };\n}\n\nconst note: CSSProperties = {\n fontSize: fontVar('xs'),\n lineHeight: 1.25,\n color: css('muted-foreground'),\n overflowWrap: 'anywhere',\n};\n\n// The explained empty state: why the control has nothing to offer, as an alert so it is announced.\nexport function ControlEmpty({ message }: { message: string }) {\n return (\n <div\n role=\"alert\"\n data-control-empty=\"\"\n style={{\n display: 'flex',\n alignItems: 'center',\n justifyContent: 'center',\n height: '100%',\n textAlign: 'center',\n color: css('muted-foreground'),\n fontSize: fontVar('sm'),\n overflowWrap: 'anywhere',\n }}\n >\n {message}\n </div>\n );\n}\n\n// The id of the full read-only reason, derived from the caption's id so the control can name it in\n// aria-describedby. The visible caption is the short \"View only\" so it fits a two-row widget at\n// one-column width; the full sentence lives here, hidden from the eye but not from assistive tech.\nexport const readOnlyId = (captionId: string) => `${captionId}-ro`;\n\n// The notes under a control: a short \"View only\" caption (the full reason is its title and the\n// control's description), and the last send's error.\nexport function ControlNotes({ sender, captionId }: { sender: CommandSender; captionId: string }) {\n return (\n <>\n {sender.readOnlyReason ? (\n <span id={readOnlyId(captionId)} hidden>\n {sender.readOnlyReason}\n </span>\n ) : null}\n {sender.readOnlyReason ? (\n <div data-read-only=\"\" title={sender.readOnlyReason} style={{ ...note, whiteSpace: 'nowrap', overflow: 'hidden', textOverflow: 'ellipsis' }}>\n {sender.strings.viewOnly}\n </div>\n ) : null}\n {sender.error ? (\n <div role=\"alert\" style={{ ...note, color: commandStatusColor('FAILED') }}>\n {sender.error}\n </div>\n ) : null}\n </>\n );\n}\n\n// \"Last sent: 22 C \u00B7 Pending\". The words are the viewer's: pending, delivered or failed, with the\n// precise status on hover. `override` replaces the whole line (the slider's \"not sent yet\" while a\n// value is staged), so the caption never claims a staged value was sent.\nexport function LastSentCaption({\n id,\n last,\n format,\n strings,\n override,\n truncated = false,\n}: {\n id: string;\n last: LastSent | null;\n format: (value: number | boolean | undefined) => string;\n strings: ControlStrings;\n override?: string;\n // The device has more commands than the page read, so an empty match proves nothing.\n truncated?: boolean;\n}) {\n const group = last ? deliveryGroup(last.status) : undefined;\n const word =\n group === 'pending'\n ? strings.pending\n : group === 'delivered'\n ? strings.delivered\n : group === 'failed'\n ? strings.failed\n : last\n ? commandStatusLabel(last.status)\n : '';\n return (\n <div\n id={id}\n role=\"status\"\n aria-live=\"polite\"\n data-last-sent=\"\"\n data-delivery={group}\n style={{ ...note, fontSize: fontVar('sm'), display: 'flex', flexWrap: 'wrap', alignItems: 'center', columnGap: spaceVar(1) }}\n >\n {override ? (\n <span>{override}</span>\n ) : last ? (\n <>\n <span>{strings.lastSent}:</span>\n <span style={{ color: css('foreground'), fontWeight: 600 }}>{format(last.value)}</span>\n <span aria-hidden=\"true\">\u00B7</span>\n <span\n title={last.status}\n style={{ display: 'inline-flex', alignItems: 'center', gap: 4, color: commandStatusColor(last.status), fontWeight: 600 }}\n >\n <span aria-hidden=\"true\" style={{ width: 7, height: 7, borderRadius: '50%', background: commandStatusColor(last.status) }} />\n {word}\n </span>\n </>\n ) : (\n <span>{truncated ? strings.beyondHistory : strings.nothingSent}</span>\n )}\n </div>\n );\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// The fixed words the command slider and toggle render themselves: why they will not send, what\n// they last sent, and where that command is in its delivery. The widget packages carry no i18n\n// runtime, so this is the one small per-language table, in the manner of level-strings.ts; option\n// labels are translated in the console's catalogs, not here.\n//\n// The caption is worded as what was SENT. These controls know the history of the commands issued\n// to the device, not what the device is doing, and no string here may read as the latter.\n\nimport { viewerLocale } from '@devicechain/widget-sdk';\n\nexport interface ControlStrings {\n noCommand: string;\n setParameter: string;\n parameterMissing: (name: string) => string;\n wrongType: (name: string, expected: string) => string;\n fixedChoices: (name: string) => string;\n otherRequired: (names: string[]) => string;\n badDefault: (names: string[]) => string;\n setRange: string;\n badRange: string;\n numberKind: string;\n booleanKind: string;\n // The caption.\n lastSent: string;\n nothingSent: string;\n // The history is longer than the page this control reads, and none of this command is on it.\n beyondHistory: string;\n noValue: string;\n unsent: string;\n // The slider's keyboard description: arrows choose, Enter sends.\n sliderHint: string;\n // The stepper: its keyboard description, the unsent caption, and its buttons.\n stepperHint: string;\n stepperUnsent: string;\n send: string;\n decrease: string;\n increase: string;\n pending: string;\n delivered: string;\n failed: string;\n sending: string;\n on: string;\n off: string;\n // Read-only reasons.\n // The short visible caption beside a read-only control; the full reason is its description.\n viewOnly: string;\n noPermission: string;\n noSeam: string;\n noDevice: string;\n sendFailed: string;\n}\n\nexport const CONTROL_STRINGS: Record<'en' | 'es', ControlStrings> = {\n en: {\n noCommand: 'No command configured',\n setParameter: 'Set which parameter this control sends',\n parameterMissing: (n) => `The command has no parameter \u201C${n}\u201D`,\n wrongType: (n, e) => `Parameter \u201C${n}\u201D is not ${e}`,\n fixedChoices: (n) => `Parameter \u201C${n}\u201D takes fixed choices, not a range`,\n otherRequired: (ns) => `Not sent: the command also requires ${ns.map((n) => `\u201C${n}\u201D`).join(', ')}, which has no default value`,\n badDefault: (ns) => `Not sent: the default value of ${ns.map((n) => `\u201C${n}\u201D`).join(', ')} is not valid for the command`,\n setRange: 'Set min and max',\n badRange: 'Max must be above min',\n numberKind: 'a number',\n booleanKind: 'a boolean',\n lastSent: 'Last sent',\n nothingSent: 'Nothing sent yet',\n beyondHistory: 'Not among the recent commands',\n noValue: 'no value',\n unsent: 'Not sent yet. Release or press Enter to send',\n sliderHint: 'Arrow keys choose a value; press Enter to send it, or Escape to discard it.',\n stepperHint: 'Arrow keys and Page Up or Down change the value; press Enter to send it, or Escape to discard it.',\n stepperUnsent: 'Not sent yet. Press Send',\n send: 'Send',\n decrease: 'Decrease',\n increase: 'Increase',\n pending: 'Pending',\n delivered: 'Delivered',\n failed: 'Failed',\n sending: 'Sending\u2026',\n on: 'On',\n off: 'Off',\n viewOnly: 'View only',\n noPermission: 'You don\u2019t have permission to send commands.',\n noSeam: 'Commands aren\u2019t available in this view.',\n noDevice: 'No device is selected for this command.',\n sendFailed: 'Couldn\u2019t send the command. Try again.',\n },\n es: {\n noCommand: 'No hay un comando configurado',\n setParameter: 'Defina qu\u00E9 par\u00E1metro env\u00EDa este control',\n parameterMissing: (n) => `El comando no tiene el par\u00E1metro \u201C${n}\u201D`,\n wrongType: (n, e) => `El par\u00E1metro \u201C${n}\u201D no es ${e}`,\n fixedChoices: (n) => `El par\u00E1metro \u201C${n}\u201D admite opciones fijas, no un rango`,\n otherRequired: (ns) => `No se env\u00EDa: el comando tambi\u00E9n exige ${ns.map((n) => `\u201C${n}\u201D`).join(', ')}, que no tiene valor por defecto`,\n badDefault: (ns) => `No se env\u00EDa: el valor por defecto de ${ns.map((n) => `\u201C${n}\u201D`).join(', ')} no es v\u00E1lido para el comando`,\n setRange: 'Defina m\u00EDn. y m\u00E1x.',\n badRange: 'El m\u00E1x. debe superar el m\u00EDn.',\n numberKind: 'un n\u00FAmero',\n booleanKind: 'un booleano',\n lastSent: '\u00DAltimo enviado',\n nothingSent: 'A\u00FAn no se ha enviado nada',\n beyondHistory: 'No figura entre los comandos recientes',\n noValue: 'sin valor',\n unsent: 'A\u00FAn no enviado. Suelte o pulse Intro para enviar',\n sliderHint: 'Las flechas eligen un valor; pulse Intro para enviarlo o Escape para descartarlo.',\n stepperHint: 'Las flechas y Re P\u00E1g o Av P\u00E1g cambian el valor; pulse Intro para enviarlo o Escape para descartarlo.',\n stepperUnsent: 'A\u00FAn no enviado. Pulse Enviar',\n send: 'Enviar',\n decrease: 'Disminuir',\n increase: 'Aumentar',\n pending: 'Pendiente',\n delivered: 'Entregado',\n failed: 'Fallido',\n sending: 'Enviando\u2026',\n on: 'Encendido',\n off: 'Apagado',\n viewOnly: 'Solo lectura',\n noPermission: 'No tiene permiso para enviar comandos.',\n noSeam: 'Los comandos no est\u00E1n disponibles en esta vista.',\n noDevice: 'No hay un dispositivo seleccionado para este comando.',\n sendFailed: 'No se pudo enviar el comando. Int\u00E9ntelo de nuevo.',\n },\n};\n\nexport function controlStrings(locale: string | undefined = viewerLocale()): ControlStrings {\n return locale?.toLowerCase().split('-')[0] === 'es' ? CONTROL_STRINGS.es : CONTROL_STRINGS.en;\n}\n", "// Copyright The DeviceChain Authors\n// SPDX-License-Identifier: Apache-2.0\n\n// command-toggle \u2014 sends a command carrying ONE boolean parameter, one command per click.\n//\n// \uD83D\uDD34 THE SWITCH SHOWS THE LAST VALUE SENT, NOT THE DEVICE'S STATE. It rests where the newest\n// command left it (read off the command history) and the caption says \"Last sent\" with that\n// command's delivery status.\n//\n// \uD83D\uDD34 IT NEVER GUESSES WHICH WAY TO FLIP. A click sends the opposite of the last value sent only\n// when that value is a live intent: a command that is pending or was delivered, carrying a value\n// this control can read. With nothing sent, a history too short to show this command, a newest\n// command with no readable value, or a newest command that FAILED (its value never reached the\n// device, so flipping relative to it would send what the operator may already have), the switch\n// is replaced by two explicit buttons, On and Off, and the operator chooses.\n//\n// A click while a send is in flight does nothing, so rapid clicks make one command, not a queue of\n// alternating ones.\n\nimport { useId, useMemo, type ReactNode } from 'react';\n\nimport {\n css,\n FIT_MIN_FONT_PX,\n fitValueFont,\n fitValueFontCss,\n optString,\n spaceVar,\n useStickyChars,\n WidgetFrame,\n type CommandStreamState,\n type WidgetProps,\n type WidgetScale,\n} from '@devicechain/widget-sdk';\n\nimport { bindControl, deliveryGroup, lastSent, payloadFor, type Binding, type LastSent } from '../command-control-core';\nimport {\n ControlEmpty,\n ControlNotes,\n readOnlyId,\n LastSentCaption,\n refusalText,\n TOUCH_TARGET_PX,\n useCommandSender,\n type CommandSender,\n} from '../command-control';\n\ntype Bound = Extract<Binding, { ok: true }>;\n\nconst TRACK_W = 52;\nconst TRACK_H = 30;\nconst THUMB = 24;\n\nexport function CommandToggle({ widget, data, actions }: WidgetProps<CommandStreamState>) {\n const commandName = optString(widget.options, 'commandName');\n const commandLabel = optString(widget.options, 'commandLabel') ?? commandName;\n const parameterSchema = optString(widget.options, 'parameterSchema') ?? null;\n const parameter = optString(widget.options, 'parameter');\n\n const binding = useMemo(\n () => bindControl('boolean', { commandName, parameterSchema, parameter }),\n [commandName, parameterSchema, parameter],\n );\n const sender = useCommandSender(\n actions,\n data.deviceToken,\n commandName,\n `${commandName ?? ''}::${parameterSchema ?? ''}::${parameter ?? ''}`,\n );\n const last =\n binding.ok && commandName\n ? lastSent('boolean', data.commands, commandName, binding.param.name, sender.optimistic)\n : null;\n const captionId = useId();\n\n return (\n <WidgetFrame title={optString(widget.options, 'title')}>\n {(scale) =>\n !binding.ok ? (\n <ControlEmpty message={refusalText(binding.refusal, sender.strings, 'boolean')} />\n ) : (\n <ToggleBody\n binding={binding}\n label={commandLabel ?? binding.param.name}\n value={intentOf(last)}\n sender={sender}\n captionId={captionId}\n caption={\n <LastSentCaption\n id={captionId}\n last={last}\n strings={sender.strings}\n truncated={data.total > data.commands.length}\n format={(v) => (typeof v === 'boolean' ? (v ? sender.strings.on : sender.strings.off) : sender.strings.noValue)}\n override={sender.pending ? sender.strings.sending : undefined}\n />\n }\n scale={scale}\n />\n )\n }\n </WidgetFrame>\n );\n}\n\n// The value a flip may be computed from, or undefined when the operator has to choose. Only a\n// command that is still on its way, or arrived, states an intent the device is acting on.\nexport function intentOf(last: LastSent | null): boolean | undefined {\n if (!last || typeof last.value !== 'boolean') return undefined;\n const group = deliveryGroup(last.status);\n return group === 'pending' || group === 'delivered' ? last.value : undefined;\n}\n\nfunction ToggleBody({\n binding,\n label,\n value,\n sender,\n captionId,\n caption,\n scale: ws,\n}: {\n binding: Bound;\n label: string;\n value: boolean | undefined;\n sender: CommandSender;\n captionId: string;\n caption: ReactNode;\n scale: WidgetScale;\n}) {\n const checked = value === true;\n const unknown = value === undefined;\n const disabled = !sender.canSend;\n const word = checked ? sender.strings.on : sender.strings.off;\n\n const chars = useStickyChars(word.length);\n const floor = Math.round(FIT_MIN_FONT_PX * ws.scale);\n const measured = ws.size.width > 0 && ws.size.height > 0;\n const room = {\n width: Math.floor(ws.size.width - TRACK_W - 16),\n height: Math.max(TOUCH_TARGET_PX, Math.floor(ws.size.height * 0.5)),\n };\n const font = measured ? fitValueFont(room, chars, { floor }) : fitValueFontCss(chars, { floor });\n\n const sendValue = (next: boolean) => {\n sender.send(payloadFor(binding, next), next);\n };\n\n return (\n <div\n style={{\n height: '100%',\n boxSizing: 'border-box',\n display: 'flex',\n flexDirection: 'column',\n justifyContent: 'safe center',\n gap: spaceVar(1),\n overflow: 'hidden',\n }}\n >\n {unknown ? (\n <div role=\"group\" aria-label={label} aria-describedby={sender.readOnlyReason ? `${captionId} ${readOnlyId(captionId)}` : captionId} data-toggle-choose=\"\" style={{ display: 'flex', gap: spaceVar(2) }}>\n {([true, false] as const).map((v) => (\n <button\n key={String(v)}\n type=\"button\"\n disabled={disabled}\n aria-busy={sender.pending}\n onClick={() => sendValue(v)}\n style={{\n flex: '1 1 0',\n minHeight: TOUCH_TARGET_PX,\n minWidth: TOUCH_TARGET_PX,\n padding: `0 ${spaceVar(2)}`,\n borderRadius: 6,\n border: `1px solid ${css('border')}`,\n background: css('background'),\n color: css('foreground'),\n fontWeight: 600,\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.6 : 1,\n }}\n >\n {v ? sender.strings.on : sender.strings.off}\n </button>\n ))}\n </div>\n ) : (\n <button\n type=\"button\"\n role=\"switch\"\n aria-checked={checked}\n aria-label={label}\n aria-describedby={sender.readOnlyReason ? `${captionId} ${readOnlyId(captionId)}` : captionId}\n aria-busy={sender.pending}\n disabled={disabled}\n onClick={() => sendValue(!checked)}\n style={{\n display: 'flex',\n alignItems: 'center',\n gap: spaceVar(2),\n minHeight: TOUCH_TARGET_PX,\n minWidth: TOUCH_TARGET_PX,\n padding: 0,\n border: 'none',\n background: 'transparent',\n color: css('foreground'),\n cursor: disabled ? 'not-allowed' : 'pointer',\n opacity: disabled ? 0.6 : 1,\n textAlign: 'left',\n }}\n >\n <span\n aria-hidden=\"true\"\n style={{\n position: 'relative',\n flex: '0 0 auto',\n width: TRACK_W,\n height: TRACK_H,\n borderRadius: TRACK_H / 2,\n boxSizing: 'border-box',\n border: `1px solid ${css('border')}`,\n background: checked ? css('primary') : css('muted'),\n }}\n >\n <span\n style={{\n position: 'absolute',\n top: (TRACK_H - THUMB) / 2 - 1,\n left: checked ? TRACK_W - THUMB - 3 : 2,\n width: THUMB,\n height: THUMB,\n borderRadius: '50%',\n background: checked ? css('primary-foreground') : css('background'),\n border: `1px solid ${css('border')}`,\n boxSizing: 'border-box',\n }}\n />\n </span>\n <span\n data-toggle-word=\"\"\n style={{\n fontSize: font,\n fontWeight: 600,\n lineHeight: 1.1,\n whiteSpace: 'nowrap',\n color: css('foreground'),\n }}\n >\n {word}\n </span>\n </button>\n )}\n {/* One row, so the short read-only caption costs no height at a two-row widget's minimum span. */}\n <div style={{ display: 'flex', flexWrap: 'wrap', alignItems: 'baseline', columnGap: spaceVar(2) }}>\n {caption}\n <ControlNotes sender={sender} captionId={captionId} />\n </div>\n </div>\n );\n}\n"],
5
+ "mappings": "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",
6
+ "names": ["useEffect", "useRef", "useState", "css", "FIT_MIN_FONT_PX", "fitValueFont", "fitValueFontCss", "fontVar", "spaceVar", "useStickyChars", "css", "fontVar", "spaceVar", "jsx", "jsxs", "spaceVar", "css", "fontVar", "jsx", "jsxs", "useState", "useRef", "useEffect", "useStickyChars", "FIT_MIN_FONT_PX", "fitValueFont", "fitValueFontCss", "spaceVar", "css", "fontVar", "useId", "useMemo", "css", "FIT_MIN_FONT_PX", "fitValueFont", "fitValueFontCss", "optString", "spaceVar", "useStickyChars", "WidgetFrame", "jsx", "jsxs", "optString", "useMemo", "useId", "WidgetFrame", "useStickyChars", "FIT_MIN_FONT_PX", "fitValueFont", "fitValueFontCss", "spaceVar", "css"]
7
+ }
@@ -0,0 +1,373 @@
1
+ import {
2
+ measurementSpan
3
+ } from "./chunk-JFH4QMLA.js";
4
+ import {
5
+ useHeld
6
+ } from "./chunk-XNEBAY4J.js";
7
+
8
+ // src/event-markers-feed.tsx
9
+ import {
10
+ DEFAULT_TIME_RANGE_SECONDS
11
+ } from "@devicechain/dashboards";
12
+ import {
13
+ css,
14
+ fontVar,
15
+ formatDateTime,
16
+ formatTimestamp,
17
+ optBoolean,
18
+ optString,
19
+ spaceVar,
20
+ useWidgetTimeRange,
21
+ useWidgetTimeZone
22
+ } from "@devicechain/widget-sdk";
23
+ import { useChartTheme } from "@devicechain/widget-sdk/echarts";
24
+ import { useEffect, useMemo, useRef, useState } from "react";
25
+
26
+ // src/event-markers-model.ts
27
+ import { levelColor } from "@devicechain/widget-sdk";
28
+ import { severityFill } from "@devicechain/widget-sdk/echarts";
29
+ var MAX_EVENT_MARKERS = 30;
30
+ var SPAN_ALPHA = 0.05;
31
+ var STRIP_PX = 3;
32
+ var STRIP_ALPHA = 0.85;
33
+ var LABEL_MIN_WIDTH = 480;
34
+ var LABEL_MAX_LINES = 8;
35
+ var LEVELS = { CRITICAL: "critical", MAJOR: "major", MINOR: "minor", WARNING: "warning" };
36
+ function alarmLevel(severity) {
37
+ return LEVELS[severity.toUpperCase()];
38
+ }
39
+ function ms(iso) {
40
+ if (!iso) return null;
41
+ const t = Date.parse(iso);
42
+ return Number.isNaN(t) ? null : t;
43
+ }
44
+ function alarmEvents(rows, deviceToken, range, words, fmt) {
45
+ const out = [];
46
+ for (const r of rows) {
47
+ if (deviceToken !== void 0 && r.originatorToken !== deviceToken) continue;
48
+ const at = ms(r.raisedTime);
49
+ if (at === null) continue;
50
+ const end = ms(r.clearedTime);
51
+ if (at > range.to || (end ?? Infinity) < range.from) continue;
52
+ const sev = words.severity[r.severity.toUpperCase()] ?? r.severity;
53
+ out.push({
54
+ id: r.token,
55
+ kind: "alarm",
56
+ at,
57
+ end,
58
+ level: alarmLevel(r.severity),
59
+ name: r.alarmKey,
60
+ metric: r.metricKey,
61
+ title: `${words.alarm}: ${r.alarmKey}`,
62
+ detail: [sev, words.raised(fmt(at)), end === null ? words.stillActive : words.cleared(fmt(end))].join(" \xB7 ")
63
+ });
64
+ }
65
+ return out;
66
+ }
67
+ function alarmScope(value) {
68
+ return value === "series" ? "series" : "device";
69
+ }
70
+ function splitByScope(items, scope, charted) {
71
+ if (scope === "device") return { drawn: [...items], other: [] };
72
+ const drawn = [];
73
+ const other = [];
74
+ for (const e of items) (e.kind === "alarm" && !(e.metric && charted.has(e.metric)) ? other : drawn).push(e);
75
+ return { drawn, other };
76
+ }
77
+ function commandEvents(rows, range, words, fmt) {
78
+ const out = [];
79
+ for (const r of rows) {
80
+ const at = ms(r.sentTime) ?? ms(r.queuedTime);
81
+ if (at === null || at < range.from || at > range.to) continue;
82
+ out.push({
83
+ id: r.token,
84
+ kind: "command",
85
+ at,
86
+ end: null,
87
+ level: void 0,
88
+ name: r.name,
89
+ title: `${words.command}: ${r.name}`,
90
+ detail: [words.sent(fmt(at)), words.outcome[r.status] ?? r.status].join(" \xB7 ")
91
+ });
92
+ }
93
+ return out;
94
+ }
95
+ var HTML_ESCAPES = { "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" };
96
+ var escapeHtml = (s) => s.replace(/[&<>"']/g, (c) => HTML_ESCAPES[c]);
97
+ function planMarkers(items, range, theme) {
98
+ const byNewest = [...items].sort((a, b) => b.at - a.at || (a.id < b.id ? -1 : 1));
99
+ const shown = byNewest.slice(0, MAX_EVENT_MARKERS).reverse();
100
+ const hidden = byNewest.length - shown.length;
101
+ const neutral = theme.mutedForeground;
102
+ const tips = {};
103
+ const lines = [];
104
+ const areas = [];
105
+ const strips = [];
106
+ for (const e of shown) {
107
+ const tip = `<b>${escapeHtml(e.title)}</b><br/>${escapeHtml(e.detail)}`;
108
+ if (e.kind === "command") {
109
+ tips[e.id] = tip;
110
+ lines.push({ name: e.id, xAxis: e.at, lineStyle: { color: neutral, type: "dotted", width: 2 }, labelText: e.name });
111
+ continue;
112
+ }
113
+ const ink = levelColor(theme.severity, e.level) ?? neutral;
114
+ const start = Math.max(e.at, range.from);
115
+ const stop = Math.min(e.end ?? range.to, range.to);
116
+ tips[`${e.id}:span`] = tip;
117
+ areas.push([
118
+ { name: `${e.id}:span`, xAxis: start, itemStyle: { color: e.level ? severityFill(theme, e.level, SPAN_ALPHA) : theme.border } },
119
+ { xAxis: stop }
120
+ ]);
121
+ tips[`${e.id}:strip`] = tip;
122
+ strips.push((top) => [
123
+ { name: `${e.id}:strip`, xAxis: start, y: top, itemStyle: { color: e.level ? severityFill(theme, e.level, STRIP_ALPHA) : neutral } },
124
+ { xAxis: stop, y: top + STRIP_PX }
125
+ ]);
126
+ if (e.at >= range.from) {
127
+ tips[`${e.id}:raised`] = tip;
128
+ lines.push({ name: `${e.id}:raised`, xAxis: e.at, lineStyle: { color: ink, type: "solid", width: 2 } });
129
+ }
130
+ if (e.end !== null && e.end >= range.from && e.end <= range.to) {
131
+ tips[`${e.id}:cleared`] = tip;
132
+ lines.push({ name: `${e.id}:cleared`, xAxis: e.end, lineStyle: { color: neutral, type: "dashed", width: 1 } });
133
+ }
134
+ }
135
+ const tooltip = { trigger: "item", formatter: (p) => tips[p.name ?? ""] ?? "" };
136
+ const decorate = (option, size) => {
137
+ if (lines.length === 0 && areas.length === 0) return option;
138
+ const base = option;
139
+ const top = typeof base.grid?.top === "number" ? base.grid.top : 0;
140
+ const series = Array.isArray(base.series) ? base.series : base.series ? [base.series] : [];
141
+ const labelled = (size?.width ?? 0) >= LABEL_MIN_WIDTH && lines.length <= LABEL_MAX_LINES;
142
+ const fontSize = base.textStyle?.fontSize ?? 10;
143
+ const lineData = lines.map(
144
+ ({ labelText, ...line }) => labelled && typeof labelText === "string" ? { ...line, label: { show: true, position: "insideEndTop", rotate: 90, color: neutral, fontSize, formatter: labelText } } : { ...line, label: { show: false } }
145
+ );
146
+ return {
147
+ ...option,
148
+ xAxis: { ...base.xAxis, min: range.from, max: range.to },
149
+ // The legend lists the readings, not the helper series that carries the markers.
150
+ ...base.legend ? { legend: { ...base.legend, data: series.map((s) => s.name) } } : {},
151
+ series: [
152
+ ...series,
153
+ {
154
+ type: "line",
155
+ name: "",
156
+ data: [],
157
+ silent: false,
158
+ z: 1,
159
+ markArea: { silent: false, animation: false, label: { show: false }, tooltip, data: [...areas, ...strips.map((strip) => strip(top))] },
160
+ markLine: { silent: false, animation: false, symbol: "none", tooltip, data: lineData }
161
+ }
162
+ ]
163
+ };
164
+ };
165
+ return { shown, hidden, decorate };
166
+ }
167
+
168
+ // src/event-markers-strings.ts
169
+ import { viewerLocale } from "@devicechain/widget-sdk";
170
+ var EVENT_MARKER_STRINGS = {
171
+ en: {
172
+ events: (n) => `Events in this range (${n})`,
173
+ otherAlarms: (n) => `Other alarms on this device (${n})`,
174
+ noMeasurement: "no measurement known",
175
+ aboutMeasurement: (m) => `about ${m}`,
176
+ alarm: "Alarm",
177
+ command: "Command",
178
+ severity: { CRITICAL: "Critical", MAJOR: "Major", MINOR: "Minor", WARNING: "Warning", INDETERMINATE: "Indeterminate" },
179
+ raised: (t) => `raised ${t}`,
180
+ cleared: (t) => `cleared ${t}`,
181
+ stillActive: "still active",
182
+ sent: (t) => `sent ${t}`,
183
+ outcome: {
184
+ SUCCESSFUL: "succeeded",
185
+ FAILED: "failed",
186
+ TIMEOUT: "timed out",
187
+ EXPIRED: "expired",
188
+ CANCELLED: "cancelled",
189
+ QUEUED: "queued",
190
+ SENT: "sent, awaiting reply",
191
+ HELD: "held",
192
+ PARKED: "parked"
193
+ },
194
+ more: (n) => `${n} more not shown`,
195
+ notInRecording: { alarm: "Alarm markers: not in this recording", command: "Command markers: not in this recording" },
196
+ failed: { alarm: "Alarm markers could not be loaded", command: "Command markers could not be loaded" }
197
+ },
198
+ es: {
199
+ events: (n) => `Eventos en este intervalo (${n})`,
200
+ otherAlarms: (n) => `Otras alarmas de este dispositivo (${n})`,
201
+ noMeasurement: "medici\xF3n desconocida",
202
+ aboutMeasurement: (m) => `sobre ${m}`,
203
+ alarm: "Alarma",
204
+ command: "Comando",
205
+ severity: { CRITICAL: "Cr\xEDtica", MAJOR: "Mayor", MINOR: "Menor", WARNING: "Advertencia", INDETERMINATE: "Indeterminada" },
206
+ raised: (t) => `activada ${t}`,
207
+ cleared: (t) => `despejada ${t}`,
208
+ stillActive: "sigue activa",
209
+ sent: (t) => `enviado ${t}`,
210
+ outcome: {
211
+ SUCCESSFUL: "correcto",
212
+ FAILED: "fallido",
213
+ TIMEOUT: "sin respuesta a tiempo",
214
+ EXPIRED: "caducado",
215
+ CANCELLED: "cancelado",
216
+ QUEUED: "en cola",
217
+ SENT: "enviado, esperando respuesta",
218
+ HELD: "retenido",
219
+ PARKED: "en espera"
220
+ },
221
+ more: (n) => `${n} m\xE1s sin mostrar`,
222
+ notInRecording: { alarm: "Marcadores de alarmas: no est\xE1n en esta grabaci\xF3n", command: "Marcadores de comandos: no est\xE1n en esta grabaci\xF3n" },
223
+ failed: { alarm: "No se pudieron cargar los marcadores de alarmas", command: "No se pudieron cargar los marcadores de comandos" }
224
+ }
225
+ };
226
+ function eventMarkerStrings(locale = viewerLocale()) {
227
+ return locale?.toLowerCase().split("-")[0] === "es" ? EVENT_MARKER_STRINGS.es : EVENT_MARKER_STRINGS.en;
228
+ }
229
+
230
+ // src/event-markers-feed.tsx
231
+ import { jsx, jsxs } from "react/jsx-runtime";
232
+ var DAY_MS = 864e5;
233
+ function useChannel(subscribe, key, paused) {
234
+ const [state, setState] = useState({ key: "" });
235
+ const subscribeRef = useRef(subscribe);
236
+ subscribeRef.current = subscribe;
237
+ const enabled = subscribe !== null;
238
+ useEffect(() => {
239
+ const open = subscribeRef.current;
240
+ if (!open) return;
241
+ return open({
242
+ next: (value) => setState({ key, value }),
243
+ // A later error keeps the rows already read: the channel polls and heals.
244
+ error: (error) => setState((prev) => ({ key, value: prev.key === key ? prev.value : void 0, error }))
245
+ });
246
+ }, [key, enabled]);
247
+ const live = enabled && state.key === key ? state : { key };
248
+ return useHeld(live, paused, (c) => c.value !== void 0 || c.error !== void 0);
249
+ }
250
+ var isNotInRecording = (err) => err instanceof Error && err.name === "NotInRecordingError";
251
+ function EventMarkersFeed({ hub, widget, descriptor, data, Component, actions }) {
252
+ const range = useWidgetTimeRange();
253
+ const timeZone = useWidgetTimeZone();
254
+ const theme = useChartTheme();
255
+ const words = eventMarkerStrings();
256
+ const now = useMemo(() => hub.now ? () => hub.now() : Date.now, [hub]);
257
+ const spanSeconds = measurementSpan(descriptor, widget, range?.spanSeconds) ?? DEFAULT_TIME_RANGE_SECONDS;
258
+ const paused = range?.paused === true;
259
+ const pausedAt = paused ? range?.pausedAt : void 0;
260
+ const ds = widget.datasource;
261
+ const bound = ds !== void 0 && ds.kind !== "unbound";
262
+ const wantAlarms = bound && optBoolean(widget.options, "markAlarms") === true;
263
+ const scope = alarmScope(optString(widget.options, "markAlarmsScope"));
264
+ const wantCommands = bound && optBoolean(widget.options, "markCommands") === true;
265
+ const key = JSON.stringify([ds ?? null, spanSeconds, pausedAt ?? null]);
266
+ const window = { spanSeconds, ...pausedAt !== void 0 ? { endAt: pausedAt } : {} };
267
+ const alarms = useChannel(
268
+ wantAlarms ? (sink) => hub.subscribeAlarms({ datasource: ds, pageSize: MAX_EVENT_MARKERS, window }, sink) : null,
269
+ `a${key}`,
270
+ paused
271
+ );
272
+ const commands = useChannel(
273
+ wantCommands ? (sink) => hub.subscribeCommands({ datasource: ds, pageSize: MAX_EVENT_MARKERS, window }, sink) : null,
274
+ `c${key}`,
275
+ paused
276
+ );
277
+ const view = useMemo(() => {
278
+ if (!wantAlarms && !wantCommands) return void 0;
279
+ const to = pausedAt ?? now();
280
+ const span = { from: to - spanSeconds * 1e3, to };
281
+ const fmt = (at) => spanSeconds * 1e3 > DAY_MS ? formatDateTime(new Date(at).toISOString(), timeZone) : formatTimestamp(new Date(at).toISOString(), timeZone);
282
+ const named = ds?.measurements ?? [];
283
+ const charted = new Set(named.length > 0 ? named : data.samples.map((s) => s.name));
284
+ const deviceToken = ds?.kind === "device" ? ds.deviceToken : void 0;
285
+ const found = [];
286
+ const notes = [];
287
+ let unloaded = 0;
288
+ const channel = (kind, c) => {
289
+ if (c.value === void 0 && c.error !== void 0) {
290
+ notes.push(isNotInRecording(c.error) ? words.notInRecording[kind] : words.failed[kind]);
291
+ }
292
+ };
293
+ if (wantAlarms) {
294
+ channel("alarm", alarms);
295
+ const snap = alarms.value;
296
+ if (snap) {
297
+ found.push(...alarmEvents(snap.alarms, deviceToken, span, words, fmt));
298
+ unloaded += Math.max(0, snap.total - snap.alarms.length);
299
+ }
300
+ }
301
+ if (wantCommands) {
302
+ channel("command", commands);
303
+ const snap = commands.value;
304
+ if (snap) {
305
+ found.push(...commandEvents(snap.commands, span, words, fmt));
306
+ unloaded += Math.max(0, snap.total - snap.commands.length);
307
+ }
308
+ }
309
+ const { drawn, other } = splitByScope(found, scope, charted);
310
+ const plan = planMarkers(drawn, span, theme);
311
+ const more = plan.hidden + unloaded;
312
+ if (more > 0) notes.push(words.more(more));
313
+ const listed = other.map((e) => ({
314
+ ...e,
315
+ detail: `${e.detail} \xB7 ${e.metric ? words.aboutMeasurement(e.metric) : words.noMeasurement}`
316
+ }));
317
+ return { decorate: plan.decorate, footer: footerOf(plan.shown, listed, notes, theme.severity) };
318
+ }, [wantAlarms, wantCommands, scope, alarms, commands, data.samples, theme, words, spanSeconds, pausedAt, now, timeZone, key]);
319
+ return /* @__PURE__ */ jsx(Component, { widget, data, actions, markers: view });
320
+ }
321
+ var FOOTER_STYLE = {
322
+ flex: "none",
323
+ fontSize: fontVar("xs"),
324
+ color: css("muted-foreground"),
325
+ padding: `0 ${spaceVar(2)}`
326
+ };
327
+ function footerOf(shown, other, notes, severity) {
328
+ if (shown.length === 0 && other.length === 0 && notes.length === 0) return null;
329
+ const words = eventMarkerStrings();
330
+ return /* @__PURE__ */ jsxs("div", { "data-chart-events": "", style: FOOTER_STYLE, children: [
331
+ notes.map((note) => /* @__PURE__ */ jsx("div", { "data-chart-events-note": "", children: note }, note)),
332
+ shown.length > 0 && /* @__PURE__ */ jsx(EventList, { events: shown, label: words.events(shown.length), severity }),
333
+ other.length > 0 && /* @__PURE__ */ jsx(EventList, { events: other, label: words.otherAlarms(other.length), severity, other: true })
334
+ ] });
335
+ }
336
+ function EventList({
337
+ events,
338
+ label,
339
+ severity,
340
+ other
341
+ }) {
342
+ return (
343
+ // A native disclosure: reachable and operable from the keyboard, with the canvas's tooltips
344
+ // as its pointer-and-touch counterpart.
345
+ /* @__PURE__ */ jsxs("details", { "data-chart-events-other": other ? "" : void 0, children: [
346
+ /* @__PURE__ */ jsx("summary", { style: { cursor: "pointer" }, children: label }),
347
+ /* @__PURE__ */ jsx("ul", { tabIndex: 0, "aria-label": label, style: { margin: 0, padding: 0, listStyle: "none", maxHeight: 96, overflowY: "auto" }, children: [...events].reverse().map((e) => /* @__PURE__ */ jsxs("li", { "data-chart-event": e.kind, style: { display: "flex", gap: spaceVar(1), alignItems: "baseline" }, children: [
348
+ /* @__PURE__ */ jsx(
349
+ "span",
350
+ {
351
+ "aria-hidden": "true",
352
+ style: {
353
+ flex: "none",
354
+ width: 8,
355
+ height: 8,
356
+ background: e.level ? severity[e.level] : css("muted-foreground"),
357
+ borderRadius: e.kind === "command" ? 4 : 0
358
+ }
359
+ }
360
+ ),
361
+ /* @__PURE__ */ jsxs("span", { children: [
362
+ e.title,
363
+ " \xB7 ",
364
+ e.detail
365
+ ] })
366
+ ] }, e.id)) })
367
+ ] })
368
+ );
369
+ }
370
+ export {
371
+ EventMarkersFeed as default
372
+ };
373
+ //# sourceMappingURL=event-markers-feed-GEGTIHXT.js.map