import { useLiveValue } from "@/components/Flow/liveStore" import { cn } from "@/lib/utils" import type { WidgetProps } from "./widgets" // Local copies: `widgets.tsx` imports this module, so its helpers cannot be // imported back without closing the cycle (`ChartWidget.tsx` does the same). const config = (widget: WidgetProps["widget"]) => (widget.config ?? {}) as Record const text = (value: unknown) => (value == null ? "" : String(value)) const num = (value: unknown, fallback: number) => { const parsed = Number(value) return Number.isFinite(parsed) ? parsed : fallback } /** Below this the reading no longer fits on the fill and moves off its end. */ const FITS = 0.3 /** * A level, drawn as a horizontal bar with its reading written on it. * * A second message can be nested inside the first — the PV share of an * inverter's input — and is drawn on top of the fill on the same scale, so * containment is what the picture shows rather than something to work out. */ export function BarWidget({ widget }: WidgetProps) { const cfg = config(widget) const message = text(cfg.message) const nested = text(cfg.inner) const outer = useLiveValue(message || undefined) const inner = useLiveValue(nested || undefined) if (!message) return

Pick a message.

const min = num(cfg.min, 0) const max = num(cfg.max, 100) const span = max - min || 1 const precision = cfg.precision === undefined ? 1 : num(cfg.precision, 1) const unit = cfg.unit ? text(cfg.unit) : "" const reading = (live: unknown) => (typeof live === "number" ? live : null) const fractionOf = (value: number | null) => value === null ? 0 : Math.min(1, Math.max(0, (value - min) / span)) const write = (value: number | null) => value === null ? "—" : `${value.toFixed(precision)}${unit}` const value = reading(outer?.value) const innerValue = nested ? reading(inner?.value) : null const fraction = fractionOf(value) const fits = fraction >= FITS return (
{innerValue === null ? null : (
)} {write(value)}
{innerValue === null ? null : (

{nested} {write(innerValue)}

)}
) }