// biome-ignore-all lint/security/noDangerouslySetInnerHtml: The portable renderer escapes text and restricts color references; security tests cover this boundary. import { ChartPlot } from "@charcuterie/logic/charts/react" import { type ChartOptions, renderChartSvg, } from "@charcuterie/logic/core" import { type CSSProperties, type ReactNode, useEffect, useRef, useState, } from "react" import { toClassName } from "../toClassName.ts" export type ChartProps = ChartOptions & { className?: string /** A text table exposes every value without relying on color, hover, or SVG. */ isTableVisible?: boolean /** The portable renderer is an explicit fallback for constrained environments. */ renderer?: "tanstack" | "portable" isAnimated?: boolean } /** The same escaped SVG renderer is available without React for kiosk/image consumers. */ export const Chart = ({ className, isTableVisible = false, renderer = "tanstack", isAnimated = true, ...options }: ChartProps): ReactNode => { const container = useRef(null) const [measuredWidth, setMeasuredWidth] = useState( options.width ?? 640, ) useEffect(() => { const element = container.current if (!element || options.width !== undefined) return const frame = { id: 0 } const observer = new ResizeObserver(([entry]) => { cancelAnimationFrame(frame.id) frame.id = requestAnimationFrame(() => { if (entry.contentRect.width > 0) { setMeasuredWidth(entry.contentRect.width) } }) }) observer.observe(element) return () => { observer.disconnect() cancelAnimationFrame(frame.id) } }, [options.width]) return (
{options.title}
{renderer === "portable" ? (
) : ( )}
    {options.series.map((entry, index) => (
  • ))}
{options.description && (

{options.description}

)} {isTableVisible && (
{options.series.map((entry) => ( ))} {options.labels.map((label, index) => ( {options.series.map((entry) => ( ))} ))}
{options.title} values
Period {entry.label}
{label} {entry.values[index] ?? "Unavailable"}
)}
) }