{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart",
  "type": "registry:component",
  "title": "Charts",
  "description": "Plots one or more lines on a 204px field. 1px grid, textured series, and one optional spot color.",
  "registryDependencies": [
    "https://vlak.dev/r/vlak-base.json",
    "https://vlak.dev/r/inter.json",
    "https://vlak.dev/r/vlak-lib.json"
  ],
  "dependencies": [
    "@stylexjs/stylex"
  ],
  "devDependencies": [
    "@stylexjs/babel-plugin"
  ],
  "docs": "Vlak leaves are StyleX. Compile them with @stylexjs/babel-plugin (Vite: @stylexjs/unplugin, Next: @stylexjs/nextjs-plugin). If you would rather not run a compiler, import @noorddev/vlak-react instead: it ships precompiled with one stylesheet.",
  "files": [
    {
      "path": "vlak/chart.tsx",
      "content": "export * from \"./charts/index\";\n",
      "type": "registry:component",
      "target": "components/vlak/chart.tsx"
    },
    {
      "path": "vlak/charts/index.ts",
      "content": "export { LineChart, type LineChartProps } from \"./line\";\nexport { AreaChart, type AreaChartProps } from \"./area\";\nexport { BarChart, type BarChartProps, type BarOrientation } from \"./bar\";\nexport { ScatterChart, type ScatterChartProps } from \"./scatter\";\nexport { Donut, Share, type DonutProps, type ShareProps, type ShareSlice } from \"./donut\";\nexport { Histogram, type HistogramProps, type HistogramBin } from \"./histogram\";\nexport { SmallMultiples, type SmallMultiplesProps, type SmallMultiple } from \"./multiples\";\nexport { Sparkline, type SparklineProps } from \"./sparkline\";\nexport type { ChartSeries, ChartAnnotation, ChartPoint } from \"./frame\";\n",
      "type": "registry:file",
      "target": "components/vlak/charts/index.ts"
    },
    {
      "path": "vlak/charts/line.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  ChartCanvas,\n  ChartField,\n  ChartHead,\n  ChartLegend,\n  ChartLegendItem,\n  ChartTip,\n  ChartTitle,\n  LegendSwatch,\n  MAX_SERIES,\n  PLOT,\n  SrTable,\n  areaMark,\n  chartStyles,\n  defaultFormat,\n  describePlot,\n  lineMark,\n  niceMax,\n  plotName,\n  plotProps,\n  stackedRows,\n  stepCursor,\n  ticksBetween,\n  useChartWidth,\n  type ChartAnnotation,\n  type ChartSeries,\n} from \"./frame\";\nimport { rs } from \"../rs\";\n\nexport interface LineChartProps extends React.HTMLAttributes<HTMLDivElement> {\n  series: ChartSeries[];\n  labels?: string[];\n  height?: number;\n  /** Fill under the first series. Prefer AreaChart for a dedicated area. */\n  area?: boolean;\n  stacked?: boolean;\n  inverted?: boolean;\n  grid?: boolean;\n  ticks?: number;\n  unit?: string;\n  yLabel?: string;\n  xLabel?: string;\n  annotations?: ChartAnnotation[];\n  domain?: [number, number];\n  spot?: boolean | string;\n  valueFormat?: (value: number) => string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const LineChart = React.forwardRef<HTMLDivElement, LineChartProps>(function LineChart({\n  series,\n  labels,\n  height = 204,\n  area,\n  stacked,\n  inverted,\n  grid = true,\n  ticks = 4,\n  unit,\n  yLabel,\n  xLabel,\n  annotations,\n  domain,\n  spot,\n  valueFormat,\n  locale,\n  className,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: LineChartProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const shown = series.slice(0, MAX_SERIES);\n  const n = Math.max(0, ...shown.map((s) => s.values.length));\n  const stacks = stacked ? stackedRows(shown) : null;\n  const extent = (stacks ? stacks.flat() : shown.flatMap((s) => s.values)).filter((v): v is number => typeof v === \"number\" && Number.isFinite(v));\n  const validDomain = domain?.every(Number.isFinite) && domain[0] !== domain[1] ? domain : undefined;\n  const min = validDomain ? Math.min(...validDomain) : Math.min(0, ...extent);\n  const max = validDomain ? Math.max(...validDomain) : niceMax(Math.max(0, ...extent));\n  const span = max - min || 1;\n  const format = valueFormat ?? ((v: number) => defaultFormat(v, undefined, locale));\n  const tip = valueFormat ?? ((v: number) => defaultFormat(v, unit, locale));\n  const [hover, setHover] = React.useState<number | null>(null);\n  const overlayRef = React.useRef<SVGRectElement>(null);\n  const [canvasRef, W] = useChartWidth<HTMLDivElement>();\n  const titleId = React.useId();\n  const clipId = React.useId();\n\n  const { ML, MR, MT, MB } = PLOT;\n  const plotW = W - ML - MR;\n  const plotH = height - MT - MB;\n  const x = (i: number) => ML + (n <= 1 ? 0 : (i / (n - 1)) * plotW);\n  const y = (v: number) => {\n    const t = (v - min) / span;\n    return inverted ? MT + t * plotH : MT + plotH - t * plotH;\n  };\n  const segments = (values: Array<number | null>) => {\n    const groups: Array<Array<{ v: number; i: number }>> = [];\n    let current: Array<{ v: number; i: number }> = [];\n    values.forEach((v, i) => {\n      if (typeof v === \"number\" && Number.isFinite(v)) current.push({ v, i });\n      else if (current.length) { groups.push(current); current = []; }\n    });\n    if (current.length) groups.push(current);\n    return groups;\n  };\n  const path = (values: Array<number | null>, fill = false) => segments(values).map((group) =>\n    group.map(({ v, i }, j) => `${j ? \"L\" : \"M\"}${x(i)} ${y(v)}`).join(\" \") +\n    (fill ? ` L${x(group[group.length - 1]!.i)} ${y(Math.max(min, Math.min(max, 0)))} L${x(group[0]!.i)} ${y(Math.max(min, Math.min(max, 0)))} Z` : \"\")\n  ).join(\" \");\n  const tickVals = ticksBetween(min, max, ticks);\n  const tickLabels = Array.from({ length: n }, (_, i) => labels?.[i] ?? `${i + 1}`);\n\n  const locate = (clientX: number): number | null => {\n    const rect = overlayRef.current?.getBoundingClientRect();\n    if (!rect || rect.width === 0 || !extent.length) return null;\n    const ratio = Math.min(1, Math.max(0, (clientX - rect.left) / rect.width));\n    return Math.round(ratio * (n - 1));\n  };\n\n  const svg = rs([\"rs-chart-svg\"], chartStyles.svg);\n  const plot = rs([\"rs-chart-plot\"], chartStyles.plot);\n  const gridSx = rs([\"rs-chart-grid\"], chartStyles.grid);\n  const axis = rs([\"rs-chart-axis\"], chartStyles.axis);\n  const baseline = rs([\"rs-chart-baseline\"], chartStyles.baseline);\n  const cursor = rs([\"rs-chart-cursor\"], chartStyles.cursor);\n  const ann = rs([\"rs-chart-ann\"], chartStyles.ann);\n  const dot = rs([\"rs-chart-dot\"], chartStyles.dot);\n\n  const name = plotName(\n    { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy },\n    yLabel ? titleId : undefined,\n    describePlot(area ? \"Area chart\" : \"Line chart\", shown.map((s) => s.name), unit),\n  );\n  const a11y = plotProps(\n    name,\n    (key) => {\n      const next = stepCursor(key, hover, extent.length ? n : 0);\n      if (next === undefined) return false;\n      setHover(next);\n      return true;\n    },\n    () => setHover(null),\n  );\n\n  return (\n    <ChartField ref={ref} spot={spot} className={className} {...props}>\n      {!extent.length && <p role=\"status\">No data to display</p>}\n      {(yLabel || xLabel) && (\n        <ChartHead>\n          {yLabel ? <ChartTitle id={titleId}>{yLabel}</ChartTitle> : <span />}\n          {xLabel ? <ChartTitle>{xLabel}</ChartTitle> : null}\n        </ChartHead>\n      )}\n      <ChartCanvas ref={canvasRef}>\n        <svg className={svg.className} style={svg.style} viewBox={`0 0 ${W} ${height}`} {...a11y}>\n          <defs><clipPath id={clipId}><rect x={ML} y={MT} width={plotW} height={plotH} /></clipPath></defs>\n          <g className={plot.className} style={plot.style} aria-hidden=\"true\">\n            {grid &&\n              tickVals.map((t) => (\n                <g key={t}>\n                  <line className={gridSx.className} style={gridSx.style} x1={ML} x2={W - MR} y1={y(t)} y2={y(t)} />\n                  <text className={axis.className} style={axis.style} x={ML - 6} y={y(t) + 3.5} textAnchor=\"end\">\n                    {format(t)}\n                  </text>\n                </g>\n              ))}\n            <line className={baseline.className} style={baseline.style} x1={ML} x2={W - MR} y1={y(min)} y2={y(min)} />\n            {area && shown[0] && !stacked && (\n              <path {...areaMark(Boolean(spot))} clipPath={`url(#${clipId})`} d={path(shown[0].values, true)} />\n            )}\n            {stacked && stacks\n              ? shown.map((s, si) => {\n                  const top = stacks.map((row) => row[si] ?? 0);\n                  return <path key={s.name} {...lineMark(si, Boolean(spot))} clipPath={`url(#${clipId})`} d={path(top)} />;\n                })\n              : shown.map((s, si) => <path key={s.name} {...lineMark(si, Boolean(spot))} clipPath={`url(#${clipId})`} d={path(s.values)} />)}\n            {annotations?.map((a) => (\n              <g key={`${a.at}-${a.label}`}>\n                <line className={cursor.className} style={cursor.style} x1={x(a.at)} x2={x(a.at)} y1={MT} y2={y(min)} />\n                <text className={ann.className} style={ann.style} x={x(a.at) + 4} y={MT + 10}>\n                  {a.label}\n                </text>\n              </g>\n            ))}\n            {hover != null && hover < n && extent.length > 0 && (\n              <g>\n                <line className={cursor.className} style={cursor.style} x1={x(hover)} x2={x(hover)} y1={MT} y2={y(min)} />\n                {shown.map((s, si) => typeof s.values[hover] === \"number\" && Number.isFinite(s.values[hover]) ? (\n                  <circle\n                    key={si}\n                    className={dot.className}\n                    style={dot.style}\n                    cx={x(hover)}\n                    cy={y(stacked && stacks ? (stacks[hover]?.[si] ?? 0) : (s.values[hover] ?? 0))}\n                    r={3}\n                    clipPath={`url(#${clipId})`}\n                  />\n                ) : null)}\n              </g>\n            )}\n            <text className={axis.className} style={axis.style} x={ML} y={height - 4} textAnchor=\"start\">\n              {tickLabels[0]}\n            </text>\n            <text className={axis.className} style={axis.style} x={W - MR} y={height - 4} textAnchor=\"end\">\n              {tickLabels[n - 1]}\n            </text>\n            <rect\n              ref={overlayRef}\n              x={ML}\n              y={0}\n              width={plotW}\n              height={height}\n              fill=\"transparent\"\n              onPointerMove={(e) => setHover(locate(e.clientX))}\n              onPointerLeave={() => setHover(null)}\n            />\n          </g>\n        </svg>\n        {hover != null && hover < n && extent.length > 0 && (\n          <ChartTip\n            left={x(hover)}\n            top={MT}\n            label={tickLabels[hover] ?? \"\"}\n            rows={shown.map((s) => ({ name: s.name, value: s.values[hover] != null && Number.isFinite(s.values[hover]) ? tip(s.values[hover]!) : \"No data\" }))}\n          />\n        )}\n      </ChartCanvas>\n      {(shown.length > 1 || unit) && (\n        <ChartLegend>\n          {shown.map((s, si) => (\n            <ChartLegendItem key={s.name}>\n              <LegendSwatch seriesIndex={si} spot={Boolean(spot)} />\n              {s.name}\n            </ChartLegendItem>\n          ))}\n          {unit ? <ChartLegendItem>{unit}</ChartLegendItem> : null}\n        </ChartLegend>\n      )}\n      <SrTable caption={yLabel ?? describePlot(\"Chart data\", shown.map((s) => s.name), unit)} labels={tickLabels} series={shown} />\n    </ChartField>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/line.tsx"
    },
    {
      "path": "vlak/charts/area.tsx",
      "content": "import * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"../tokens.stylex\";\nimport { LineChart, type LineChartProps } from \"./line\";\n\nconst styles = stylex.create({\n  area: {\n    borderRadius: 0,\n    boxShadow: \"none\",\n    backgroundColor: vlak.paper,\n  },\n});\n\nexport type AreaChartProps = Omit<LineChartProps, \"area\">;\n\n/** Filled field. Same law as the line: hairlines, texture, one optional spot. */\nexport const AreaChart = React.forwardRef<HTMLDivElement, AreaChartProps>(function AreaChart(props, ref) {\n  void styles.area;\n  return <LineChart ref={ref} {...props} area />;\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/area.tsx"
    },
    {
      "path": "vlak/charts/bar.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  ChartCanvas,\n  ChartField,\n  ChartHead,\n  ChartLegend,\n  ChartLegendItem,\n  ChartTip,\n  ChartTitle,\n  PLOT,\n  SrTable,\n  barMark,\n  chartStyles,\n  defaultFormat,\n  describePlot,\n  niceMax,\n  plotName,\n  plotProps,\n  stackedRows,\n  stepCursor,\n  ticksBetween,\n  useChartWidth,\n  type ChartSeries,\n} from \"./frame\";\nimport { rs } from \"../rs\";\n\nexport type BarOrientation = \"vertical\" | \"horizontal\";\n\nexport interface BarChartProps extends React.HTMLAttributes<HTMLDivElement> {\n  data?: Array<{ label: string; value: number }>;\n  series?: ChartSeries[];\n  labels?: string[];\n  height?: number;\n  orientation?: BarOrientation;\n  stacked?: boolean;\n  inverted?: boolean;\n  grid?: boolean;\n  ticks?: number;\n  unit?: string;\n  yLabel?: string;\n  spot?: boolean | string;\n  valueFormat?: (value: number) => string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const BarChart = React.forwardRef<HTMLDivElement, BarChartProps>(function BarChart({\n  data,\n  series,\n  labels,\n  height = 204,\n  orientation = \"vertical\",\n  stacked,\n  inverted,\n  grid = true,\n  ticks = 4,\n  unit,\n  yLabel,\n  spot,\n  valueFormat,\n  locale,\n  className,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: BarChartProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const fromData: ChartSeries[] = data ? [{ name: \"Value\", values: data.map((d) => d.value) }] : [];\n  const shown = (series ?? fromData).slice(0, 4);\n  const n = Math.max(0, labels?.length ?? 0, ...shown.map((s) => s.values.length));\n  const tickLabels = Array.from({ length: n }, (_, i) => labels?.[i] ?? data?.[i]?.label ?? `${i + 1}`);\n  const stacks = stacked && shown.length > 1 ? stackedRows(shown) : null;\n  const extent = (stacks ? stacks.flat() : shown.flatMap((s) => s.values)).filter((v): v is number => typeof v === \"number\" && Number.isFinite(v));\n  const min = Math.min(0, ...extent);\n  const max = niceMax(Math.max(0, ...extent));\n  const format = valueFormat ?? ((v: number) => defaultFormat(v, undefined, locale));\n  const tip = valueFormat ?? ((v: number) => defaultFormat(v, unit, locale));\n  const [hover, setHover] = React.useState<number | null>(null);\n  const hoverSample = hover == null ? 0 : shown[0]?.values[hover];\n  const hoverValue = typeof hoverSample === \"number\" && Number.isFinite(hoverSample) ? hoverSample : 0;\n  const [canvasRef, W] = useChartWidth<HTMLDivElement>();\n  const titleId = React.useId();\n\n  const { ML, MR, MT, MB } = PLOT;\n  const plotW = W - ML - MR;\n  const plotH = height - MT - MB;\n  const horizontal = orientation === \"horizontal\";\n  const band = (horizontal ? plotH : plotW) / Math.max(1, n);\n  const barThick = Math.min(8, Math.max(1, band / (stacked ? 1 : shown.length || 1) - 2));\n  const inset = (band - barThick) / 2;\n  const yV = (v: number) => {\n    const t = (v - min) / (max - min);\n    return inverted ? MT + t * plotH : MT + plotH - t * plotH;\n  };\n  const xV = (v: number) => ML + (inverted ? 1 - (v - min) / (max - min) : (v - min) / (max - min)) * plotW;\n  const tickVals = ticksBetween(min, max, ticks);\n  const labelEvery = Math.ceil(n / 8);\n\n  const svg = rs([\"rs-chart-svg\"], chartStyles.svg);\n  const plot = rs([\"rs-chart-plot\"], chartStyles.plot);\n  const gridSx = rs([\"rs-chart-grid\"], chartStyles.grid);\n  const axis = rs([\"rs-chart-axis\"], chartStyles.axis);\n  const baseline = rs([\"rs-chart-baseline\"], chartStyles.baseline);\n\n  const name = plotName(\n    { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy },\n    yLabel ? titleId : undefined,\n    describePlot(\"Bar chart\", shown.map((s) => s.name), unit),\n  );\n  const a11y = plotProps(\n    name,\n    (key) => {\n      const next = stepCursor(key, hover, extent.length ? n : 0);\n      if (next === undefined) return false;\n      setHover(next);\n      return true;\n    },\n    () => setHover(null),\n  );\n  const hoverProps = (i: number) => ({\n    onPointerEnter: () => setHover(i),\n    onPointerLeave: () => setHover(null),\n  });\n\n  return (\n    <ChartField ref={ref} spot={spot} className={className} {...props}>\n      {!extent.length && <p role=\"status\">No data to display</p>}\n      {yLabel && (\n        <ChartHead>\n          <ChartTitle id={titleId}>{yLabel}</ChartTitle>\n        </ChartHead>\n      )}\n      <ChartCanvas ref={canvasRef}>\n        <svg className={svg.className} style={svg.style} viewBox={`0 0 ${W} ${height}`} {...a11y}>\n          <g className={plot.className} style={plot.style} aria-hidden=\"true\">\n            {grid &&\n              tickVals.map((t) =>\n                horizontal ? (\n                  <g key={t}>\n                    <line className={gridSx.className} style={gridSx.style} x1={xV(t)} x2={xV(t)} y1={MT} y2={height - MB} />\n                    <text className={axis.className} style={axis.style} x={xV(t)} y={height - 4} textAnchor=\"middle\">\n                      {format(t)}\n                    </text>\n                  </g>\n                ) : (\n                  <g key={t}>\n                    <line className={gridSx.className} style={gridSx.style} x1={ML} x2={W - MR} y1={yV(t)} y2={yV(t)} />\n                    <text className={axis.className} style={axis.style} x={ML - 6} y={yV(t) + 3.5} textAnchor=\"end\">\n                      {format(t)}\n                    </text>\n                  </g>\n                ),\n              )}\n            {tickLabels.map((label, i) => {\n              let positive = 0;\n              let negative = 0;\n              return shown.map((s, si) => {\n                const value = s.values[i];\n                if (typeof value !== \"number\" || !Number.isFinite(value)) return null;\n                const before = stacked ? value < 0 ? negative : positive : 0;\n                if (value < 0) negative += value; else positive += value;\n                const after = before + value;\n                const a = horizontal ? xV(before) : yV(before);\n                const b = horizontal ? xV(after) : yV(after);\n                const offset = stacked ? inset : (band - shown.length * (barThick + 2)) / 2 + si * (barThick + 2);\n                const mark = barMark({ spot: Boolean(spot) && si === 0, muted: hover != null && hover !== i });\n                return <rect\n                  key={`${i}-${si}`}\n                  {...mark}\n                  data-series={s.name}\n                  opacity={1 - si * 0.18}\n                  x={horizontal ? Math.min(a, b) : ML + i * band + offset}\n                  y={horizontal ? MT + i * band + offset : Math.min(a, b)}\n                  width={horizontal ? Math.abs(b - a) : barThick}\n                  height={horizontal ? barThick : Math.abs(b - a)}\n                  {...hoverProps(i)}\n                />;\n              });\n            })}\n            <line\n              className={baseline.className}\n              style={baseline.style}\n              x1={horizontal ? xV(0) : ML}\n              x2={horizontal ? xV(0) : W - MR}\n              y1={horizontal ? MT : yV(0)}\n              y2={horizontal ? height - MB : yV(0)}\n            />\n            {tickLabels.map(\n              (label, i) =>\n                i % labelEvery === 0 && (\n                  <text\n                    key={label}\n                    className={axis.className}\n                    style={axis.style}\n                    x={horizontal ? ML - 6 : ML + i * band + band / 2}\n                    y={horizontal ? MT + i * band + band / 2 + 3 : height - 4}\n                    textAnchor={horizontal ? \"end\" : \"middle\"}\n                  >\n                    {label}\n                  </text>\n                ),\n            )}\n          </g>\n        </svg>\n        {hover != null && tickLabels[hover] && (\n          <ChartTip\n            left={horizontal ? ML + (xV(hoverValue) - ML) / 2 : ML + hover * band + band / 2}\n            top={horizontal ? MT + hover * band + inset : yV(hoverValue)}\n            label={tickLabels[hover]}\n            rows={shown.map((s) => ({ name: shown.length > 1 ? s.name : undefined, value: Number.isFinite(s.values[hover]) && s.values[hover] != null ? tip(s.values[hover]!) : \"No data\" }))}\n          />\n        )}\n      </ChartCanvas>\n      {(unit || shown.length > 1) && (\n        <ChartLegend aria-hidden=\"true\">\n          {shown.length > 1 && shown.map((s, i) => <ChartLegendItem key={s.name}><svg width=\"8\" height=\"8\" aria-hidden=\"true\"><rect width=\"8\" height=\"8\" {...barMark({ spot: Boolean(spot) && i === 0 })} opacity={1 - i * 0.18} /></svg>{s.name}</ChartLegendItem>)}\n          {unit && <ChartLegendItem>{unit}</ChartLegendItem>}\n        </ChartLegend>\n      )}\n      <SrTable caption={yLabel ?? describePlot(\"Chart data\", shown.map((s) => s.name), unit)} labels={tickLabels} series={shown} />\n    </ChartField>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/bar.tsx"
    },
    {
      "path": "vlak/charts/scatter.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport {\n  ChartCanvas,\n  ChartField,\n  ChartHead,\n  ChartLegend,\n  ChartLegendItem,\n  ChartTip,\n  ChartTitle,\n  LegendSwatch,\n  PLOT,\n  SrTable,\n  chartStyles,\n  defaultFormat,\n  describePlot,\n  plotName,\n  plotProps,\n  scatterMark,\n  stepCursor,\n  ticksBetween,\n  useChartWidth,\n  type ChartAnnotation,\n  type ChartPoint,\n} from \"./frame\";\nimport { rs } from \"../rs\";\n\nexport type ScatterChartProps = React.HTMLAttributes<HTMLDivElement> & {\n  points: ChartPoint[];\n  height?: number;\n  unit?: string;\n  xLabel?: string;\n  yLabel?: string;\n  xDomain?: [number, number];\n  yDomain?: [number, number];\n  grid?: boolean;\n  ticks?: number;\n  annotations?: ChartAnnotation[];\n  valueFormat?: (n: number) => string;\n  spot?: boolean | string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n};\n\nfunction extent(values: number[], pad = 0.08): [number, number] {\n  if (!values.length) return [0, 1];\n  const lo = Math.min(...values);\n  const hi = Math.max(...values);\n  const span = hi - lo || 1;\n  return [lo - span * pad, hi + span * pad];\n}\n\nexport const ScatterChart = React.forwardRef<HTMLDivElement, ScatterChartProps>(function ScatterChart({\n  points: inputPoints,\n  height = 204,\n  unit,\n  xLabel,\n  yLabel,\n  xDomain,\n  yDomain,\n  grid = true,\n  ticks = 4,\n  annotations = [],\n  valueFormat,\n  className,\n  spot,\n  locale,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: ScatterChartProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const points = inputPoints.filter((point) => Number.isFinite(point.x) && Number.isFinite(point.y));\n  const format = valueFormat ?? ((v: number) => defaultFormat(v, undefined, locale));\n  const [hover, setHover] = React.useState<number | null>(null);\n  const [canvasRef, W] = useChartWidth<HTMLDivElement>();\n  const titleId = React.useId();\n  const { ML, MR, MT, MB } = PLOT;\n  const plotW = W - ML - MR;\n  const plotH = height - MT - MB;\n  const xs = points.map((p) => p.x);\n  const ys = points.map((p) => p.y);\n  const safeDomain = (domain: [number, number] | undefined, fallback: [number, number]): [number, number] => domain?.every(Number.isFinite) && domain[0] !== domain[1] ? [Math.min(...domain), Math.max(...domain)] : fallback;\n  const [xMin, xMax] = safeDomain(xDomain, extent(xs));\n  const [yMin, yMax] = safeDomain(yDomain, extent(ys));\n  const yTicks = ticksBetween(yMin, yMax, ticks);\n  const xTicks = ticksBetween(xMin, xMax, ticks);\n  const toX = (x: number) => ML + ((x - xMin) / (xMax - xMin || 1)) * plotW;\n  const toY = (y: number) => MT + plotH - ((y - yMin) / (yMax - yMin || 1)) * plotH;\n  const groups = [...new Set(points.map((p) => p.group).filter(Boolean))] as string[];\n  const active = hover != null ? points[hover] : null;\n\n  const svg = rs([\"rs-chart-svg\"], chartStyles.svg);\n  const plot = rs([\"rs-chart-plot\"], chartStyles.plot);\n  const gridSx = rs([\"rs-chart-grid\"], chartStyles.grid);\n  const axis = rs([\"rs-chart-axis\"], chartStyles.axis);\n  const baseline = rs([\"rs-chart-baseline\"], chartStyles.baseline);\n  const cursor = rs([\"rs-chart-cursor\"], chartStyles.cursor);\n  const ann = rs([\"rs-chart-ann\"], chartStyles.ann);\n  const mark = scatterMark(Boolean(spot));\n\n  const name = plotName(\n    { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy },\n    yLabel ? titleId : undefined,\n    describePlot(\"Scatter chart\", [xLabel ?? `${points.length} points`, ...(yLabel ? [yLabel] : [])], unit),\n  );\n  const a11y = plotProps(\n    name,\n    (key) => {\n      const next = stepCursor(key, hover, points.length);\n      if (next === undefined) return false;\n      setHover(next);\n      return true;\n    },\n    () => setHover(null),\n  );\n\n  return (\n    <ChartField ref={ref} spot={spot} className={className} {...props}>\n      {!points.length && <p role=\"status\">No data to display</p>}\n      {(yLabel || xLabel) && (\n        <ChartHead>\n          {yLabel ? <ChartTitle id={titleId}>{yLabel}</ChartTitle> : <span />}\n          {xLabel ? <ChartTitle>{xLabel}</ChartTitle> : null}\n        </ChartHead>\n      )}\n      <ChartCanvas ref={canvasRef}>\n        <svg className={svg.className} style={svg.style} viewBox={`0 0 ${W} ${height}`} {...a11y}>\n          <g className={plot.className} style={plot.style} aria-hidden=\"true\">\n            {grid &&\n              yTicks.map((t) => (\n                <g key={`yg-${t}`}>\n                  <line className={gridSx.className} style={gridSx.style} x1={ML} x2={W - MR} y1={toY(t)} y2={toY(t)} />\n                  <text className={axis.className} style={axis.style} x={ML - 6} y={toY(t) + 3.5} textAnchor=\"end\">\n                    {format(t)}\n                  </text>\n                </g>\n              ))}\n            {grid &&\n              xTicks.map((t) => (\n                <line key={`xg-${t}`} className={gridSx.className} style={gridSx.style} x1={toX(t)} x2={toX(t)} y1={MT} y2={MT + plotH} />\n              ))}\n            <line className={baseline.className} style={baseline.style} x1={ML} x2={ML} y1={MT} y2={MT + plotH} />\n            <line className={baseline.className} style={baseline.style} x1={ML} x2={W - MR} y1={MT + plotH} y2={MT + plotH} />\n            {xTicks.map((t) => (\n              <text key={`xl-${t}`} className={axis.className} style={axis.style} x={toX(t)} y={height - 4} textAnchor=\"middle\">\n                {format(t)}\n              </text>\n            ))}\n            {annotations.map((a) => (\n              <g key={`${a.at}-${a.label}`}>\n                <line className={cursor.className} style={cursor.style} x1={toX(a.at)} x2={toX(a.at)} y1={MT} y2={MT + plotH} />\n                <text className={ann.className} style={ann.style} x={toX(a.at) + 4} y={MT + 10}>\n                  {a.label}\n                </text>\n              </g>\n            ))}\n            {points.map((p, i) => (\n              <circle\n                key={i}\n                className={mark.className}\n                style={mark.style}\n                cx={toX(p.x)}\n                cy={toY(p.y)}\n                r={hover === i ? 3.5 : 2.25}\n                onPointerEnter={() => setHover(i)}\n                onPointerLeave={() => setHover(null)}\n              />\n            ))}\n          </g>\n        </svg>\n        {active && (\n          <ChartTip\n            left={toX(active.x)}\n            top={toY(active.y)}\n            label={active.label ?? `${format(active.x)} · ${format(active.y)}`}\n            rows={[{ name: active.group, value: format(active.y) }]}\n          />\n        )}\n      </ChartCanvas>\n      {(groups.length > 0 || unit) && (\n        <ChartLegend aria-hidden=\"true\">\n          {groups.map((g) => (\n            <ChartLegendItem key={g}>\n              <LegendSwatch seriesIndex={0} spot={Boolean(spot)} />\n              {g}\n            </ChartLegendItem>\n          ))}\n          {unit ? <ChartLegendItem>{unit}</ChartLegendItem> : null}\n        </ChartLegend>\n      )}\n      <SrTable\n        caption={yLabel ?? \"Scatter data\"}\n        labels={points.map((p) => p.label ?? `${p.x}`)}\n        series={[\n          { name: xLabel ?? \"x\", values: points.map((p) => p.x) },\n          { name: yLabel ?? \"y\", values: points.map((p) => p.y) },\n          ...(groups.length ? [{ name: \"Group\", values: points.map((p) => p.group ?? \"\") }] : []),\n        ]}\n      />\n    </ChartField>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/scatter.tsx"
    },
    {
      "path": "vlak/charts/donut.tsx",
      "content": "import * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"../tokens.stylex\";\nimport { rs } from \"../rs\";\nimport { ChartField, ChartLegend, ChartLegendItem, ChartTitle, SrTable, chartStyles, defaultFormat } from \"./frame\";\n\nconst styles = stylex.create({\n  donut: {\n    display: \"block\",\n  },\n  track: {\n    fill: \"none\",\n    stroke: vlak.divider,\n    strokeWidth: 1,\n  },\n  value: {\n    fill: \"none\",\n    stroke: \"var(--rs-chart-spot)\",\n    strokeWidth: 1,\n    strokeLinecap: \"butt\",\n  },\n  /* Laid out in CSS px: the viewBox equals the rendered size, so type does not scale. */\n  label: {\n    fill: vlak.ink,\n    fontSize: \"1.25rem\",\n    fontWeight: 500,\n    letterSpacing: \"-0.03em\",\n    fontVariantNumeric: \"tabular-nums\",\n  },\n  caption: {\n    fill: vlak.gray,\n    fontSize: \"0.6875rem\",\n    fontWeight: 500,\n    letterSpacing: \"0.06em\",\n  },\n  share: {\n    display: \"flex\",\n    height: \"0.5rem\",\n    width: \"100%\",\n    borderWidth: vlak.hairline,\n    borderStyle: \"solid\",\n    borderColor: vlak.divider,\n    borderRadius: 0,\n    boxShadow: \"none\",\n  },\n  seg: {\n    height: \"100%\",\n    borderRadius: 0,\n    backgroundColor: {\n      default: vlak.ink,\n      \":nth-child(2)\": vlak.gray,\n      \":nth-child(3)\": vlak.divider,\n    },\n    transition: \"fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)\",\n  },\n  segSpot: {\n    backgroundColor: \"var(--rs-chart-spot)\",\n  },\n});\n\nexport interface DonutProps extends React.HTMLAttributes<HTMLDivElement> {\n  value: number;\n  max?: number;\n  size?: number;\n  label?: React.ReactNode;\n  unit?: string;\n  valueFormat?: (value: number) => string;\n  spot?: boolean | string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const Donut = React.forwardRef<HTMLDivElement, DonutProps>(function Donut({\n  value,\n  max = 100,\n  size = 184,\n  label,\n  unit,\n  valueFormat,\n  className,\n  spot,\n  locale,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: DonutProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const valid = Number.isFinite(value) && Number.isFinite(max) && max > 0 && value >= 0;\n  const format = valueFormat ?? ((v: number) => `${Math.round((v / max) * 100)}%`);\n  const pct = valid ? Math.max(0, Math.min(1, value / max)) : 0;\n  const caption = typeof label === \"string\" ? label : undefined;\n  const ids = React.useId();\n  const valueId = `${ids}-value`;\n  const captionId = `${ids}-caption`;\n  const titleId = `${ids}-title`;\n  const c = size / 2;\n  const r = size * 0.375;\n  const circumference = 2 * Math.PI * r;\n  const shown = valid ? `${format(value)}${unit ? ` ${unit}` : \"\"}` : \"No data\";\n  const svg = rs([\"rs-chart-plot\", \"rs-chart-donut\"], chartStyles.svg, chartStyles.plot, styles.donut);\n  const track = rs([\"rs-chart-donut-track\"], styles.track);\n  const valueSx = rs([\"rs-chart-donut-value\"], styles.value);\n  const labelSx = rs([\"rs-chart-donut-label\"], styles.label);\n  const captionSx = rs([\"rs-chart-donut-caption\"], styles.caption);\n  const name = ariaLabelledBy\n    ? { \"aria-labelledby\": ariaLabelledBy }\n    : ariaLabel\n      ? { \"aria-label\": ariaLabel }\n      : {\n          \"aria-labelledby\": [caption ? captionId : label ? titleId : null, valueId].filter(Boolean).join(\" \"),\n        };\n  return (\n    <ChartField ref={ref} spot={spot} className={className ? `rs-chart-donut-wrap ${className}` : \"rs-chart-donut-wrap\"} {...props}>\n      <svg\n        className={svg.className}\n        style={svg.style}\n        width={size}\n        height={size}\n        viewBox={`0 0 ${size} ${size}`}\n        role=\"img\"\n        {...name}\n      >\n        <circle className={track.className} style={track.style} cx={c} cy={c} r={r} />\n        <circle\n          className={valueSx.className}\n          style={valueSx.style}\n          cx={c}\n          cy={c}\n          r={r}\n          strokeDasharray={`${pct * circumference} ${circumference}`}\n          transform={`rotate(-90 ${c} ${c})`}\n        />\n        <text id={valueId} className={labelSx.className} style={labelSx.style} x={c} y={caption ? c - 3 : c + 7} textAnchor=\"middle\">\n          {shown}\n        </text>\n        {caption && (\n          <text id={captionId} className={captionSx.className} style={captionSx.style} x={c} y={c + 16} textAnchor=\"middle\">\n            {caption}\n          </text>\n        )}\n      </svg>\n      {label && !caption ? <ChartTitle id={titleId}>{label}</ChartTitle> : null}\n      <SrTable\n        caption={caption ?? \"Donut\"}\n        labels={[caption ?? \"Value\"]}\n        series={[\n          { name: unit ?? \"Value\", values: [defaultFormat(value, undefined, locale)] },\n          { name: \"Of\", values: [defaultFormat(max, undefined, locale)] },\n          { name: \"Share\", values: [valid ? `${Math.round(pct * 100)}%` : \"No data\"] },\n        ]}\n      />\n    </ChartField>\n  );\n});\n\nexport type ShareSlice = { label: string; value: number };\n\nexport interface ShareProps extends React.HTMLAttributes<HTMLDivElement> {\n  slices: ShareSlice[];\n  unit?: string;\n  valueFormat?: (n: number) => string;\n  spot?: boolean | string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const Share = React.forwardRef<HTMLDivElement, ShareProps>(function Share({\n  slices: inputSlices,\n  unit,\n  valueFormat,\n  className,\n  spot,\n  locale,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: ShareProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const slices = inputSlices.filter((slice) => Number.isFinite(slice.value) && slice.value >= 0);\n  const format = valueFormat ?? ((v: number) => defaultFormat(v, unit, locale));\n  const total = slices.reduce((s, x) => s + x.value, 0) || 1;\n  const percent = (v: number) => `${defaultFormat((v / total) * 100, undefined, locale)}%`;\n  const share = rs([\"rs-chart-share\"], styles.share);\n  const name = ariaLabelledBy\n    ? { \"aria-labelledby\": ariaLabelledBy }\n    : {\n        \"aria-label\": ariaLabel ?? `Share: ${slices.map((s) => `${s.label} ${percent(s.value)}`).join(\", \")}`,\n      };\n  return (\n    <ChartField ref={ref} spot={spot} className={className} {...props}>\n      {!slices.some((slice) => slice.value > 0) && <p role=\"status\">No data to display</p>}\n      <div className={share.className} style={share.style} role=\"img\" {...name}>\n        {slices.map((s, i) => {\n          const seg = rs(\n            [\"rs-chart-share-seg\", i === 0 && \"rs-chart-share-seg-spot\"],\n            styles.seg,\n            i === 0 && styles.segSpot,\n          );\n          return (\n            <div\n              key={s.label}\n              className={seg.className}\n              style={{ ...seg.style, flex: `${s.value} 1 0` }}\n              title={`${s.label} ${format(s.value)}`}\n            />\n          );\n        })}\n      </div>\n      <ChartLegend aria-hidden=\"true\">\n        {slices.map((s) => (\n          <ChartLegendItem key={s.label}>\n            {s.label} {percent(s.value)}\n          </ChartLegendItem>\n        ))}\n      </ChartLegend>\n      <SrTable\n        caption=\"Share\"\n        labels={slices.map((s) => s.label)}\n        series={[\n          { name: unit ?? \"Value\", values: slices.map((s) => format(s.value)) },\n          { name: \"Share\", values: slices.map((s) => percent(s.value)) },\n        ]}\n      />\n    </ChartField>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/donut.tsx"
    },
    {
      "path": "vlak/charts/histogram.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"../tokens.stylex\";\nimport { rs } from \"../rs\";\nimport {\n  ChartCanvas,\n  ChartField,\n  ChartHead,\n  ChartLegend,\n  ChartLegendItem,\n  ChartTip,\n  ChartTitle,\n  PLOT,\n  SrTable,\n  chartStyles,\n  defaultFormat,\n  describePlot,\n  niceMax,\n  plotName,\n  plotProps,\n  stepCursor,\n  ticksFor,\n  useChartWidth,\n} from \"./frame\";\n\nconst styles = stylex.create({\n  hist: {\n    fill: vlak.divider,\n    stroke: \"none\",\n    borderRadius: 0,\n  },\n});\n\nexport type HistogramBin = { label: string; count: number };\n\nexport interface HistogramProps extends React.HTMLAttributes<HTMLDivElement> {\n  bins: HistogramBin[];\n  height?: number;\n  unit?: string;\n  yLabel?: string;\n  grid?: boolean;\n  ticks?: number;\n  valueFormat?: (n: number) => string;\n  spot?: boolean | string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const Histogram = React.forwardRef<HTMLDivElement, HistogramProps>(function Histogram({\n  bins: inputBins,\n  height = 204,\n  unit,\n  yLabel,\n  grid = true,\n  ticks = 4,\n  valueFormat,\n  className,\n  spot,\n  locale,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}: HistogramProps, ref: React.ForwardedRef<HTMLDivElement>) {\n  const bins = inputBins.filter((bin) => Number.isFinite(bin.count) && bin.count >= 0);\n  const format = valueFormat ?? ((v: number) => defaultFormat(v, undefined, locale));\n  const tip = valueFormat ?? ((v: number) => defaultFormat(v, unit, locale));\n  const [hover, setHover] = React.useState<number | null>(null);\n  const [canvasRef, W] = useChartWidth<HTMLDivElement>();\n  const titleId = React.useId();\n  const { ML, MR, MT, MB } = PLOT;\n  const plotW = W - ML - MR;\n  const plotH = height - MT - MB;\n  const max = niceMax(Math.max(...bins.map((b) => b.count), 1));\n  const yTicks = ticksFor(max, ticks);\n  const gap = 1;\n  const bw = Math.max(0, (plotW - gap * Math.max(0, bins.length - 1)) / Math.max(1, bins.length));\n  const active = hover != null ? bins[hover] : null;\n\n  const svg = rs([\"rs-chart-svg\"], chartStyles.svg);\n  const plot = rs([\"rs-chart-plot\"], chartStyles.plot);\n  const gridSx = rs([\"rs-chart-grid\"], chartStyles.grid);\n  const axis = rs([\"rs-chart-axis\"], chartStyles.axis);\n  const baseline = rs([\"rs-chart-baseline\"], chartStyles.baseline);\n  const hist = rs([\"rs-chart-hist\", Boolean(spot) && \"rs-chart-bar-spot\"], styles.hist, Boolean(spot) && chartStyles.barSpot);\n\n  const name = plotName(\n    { \"aria-label\": ariaLabel, \"aria-labelledby\": ariaLabelledBy },\n    yLabel ? titleId : undefined,\n    describePlot(\"Histogram\", [`${bins.length} bins`], unit),\n  );\n  const a11y = plotProps(\n    name,\n    (key) => {\n      const next = stepCursor(key, hover, bins.length);\n      if (next === undefined) return false;\n      setHover(next);\n      return true;\n    },\n    () => setHover(null),\n  );\n\n  return (\n    <ChartField ref={ref} spot={spot} className={className} {...props}>\n      {!bins.length && <p role=\"status\">No data to display</p>}\n      {yLabel && (\n        <ChartHead>\n          <ChartTitle id={titleId}>{yLabel}</ChartTitle>\n        </ChartHead>\n      )}\n      <ChartCanvas ref={canvasRef}>\n        <svg className={svg.className} style={svg.style} viewBox={`0 0 ${W} ${height}`} {...a11y}>\n          <g className={plot.className} style={plot.style} aria-hidden=\"true\">\n            {grid &&\n              yTicks.map((t) => {\n                const y = MT + plotH - (t / max) * plotH;\n                return (\n                  <g key={t}>\n                    <line className={gridSx.className} style={gridSx.style} x1={ML} x2={W - MR} y1={y} y2={y} />\n                    <text className={axis.className} style={axis.style} x={ML - 6} y={y + 3.5} textAnchor=\"end\">\n                      {format(t)}\n                    </text>\n                  </g>\n                );\n              })}\n            <line className={baseline.className} style={baseline.style} x1={ML} x2={W - MR} y1={MT + plotH} y2={MT + plotH} />\n            {bins.map((b, i) => {\n              const h = (b.count / max) * plotH;\n              const x = ML + i * (bw + gap);\n              const y = MT + plotH - h;\n              return (\n                <g key={b.label} onPointerEnter={() => setHover(i)} onPointerLeave={() => setHover(null)}>\n                  <rect className={hist.className} style={hist.style} x={x} y={y} width={bw} height={h} />\n                  <text className={axis.className} style={axis.style} x={x + bw / 2} y={height - 4} textAnchor=\"middle\">\n                    {b.label}\n                  </text>\n                </g>\n              );\n            })}\n          </g>\n        </svg>\n        {active && (\n          <ChartTip\n            left={ML + (hover ?? 0) * (bw + gap) + bw / 2}\n            top={MT + plotH - (active.count / max) * plotH}\n            label={active.label}\n            rows={[{ value: tip(active.count) }]}\n          />\n        )}\n      </ChartCanvas>\n      {unit && (\n        <ChartLegend aria-hidden=\"true\">\n          <ChartLegendItem>{unit}</ChartLegendItem>\n        </ChartLegend>\n      )}\n      <SrTable\n        caption={yLabel ?? \"Histogram\"}\n        labels={bins.map((b) => b.label)}\n        series={[{ name: unit ?? \"Count\", values: bins.map((b) => b.count) }]}\n      />\n    </ChartField>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/histogram.tsx"
    },
    {
      "path": "vlak/charts/multiples.tsx",
      "content": "import * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak } from \"../tokens.stylex\";\nimport { rs } from \"../rs\";\nimport { LineChart, type LineChartProps } from \"./line\";\nimport { niceMax } from \"./frame\";\n\nconst styles = stylex.create({\n  multi: {\n    display: \"grid\",\n    gridTemplateColumns: \"repeat(auto-fill, minmax(184px, 1fr))\",\n    gap: \"1.25rem\",\n  },\n  item: { margin: 0, minWidth: 0 },\n  cap: {\n    marginTop: 0,\n    marginInlineEnd: 0,\n    marginBottom: \"0.5rem\",\n    marginInlineStart: 0,\n    fontSize: \"0.75rem\",\n    fontWeight: 600,\n    letterSpacing: \"-0.01em\",\n    color: vlak.ink,\n  },\n});\n\nexport type SmallMultiple = {\n  title: string;\n  series: LineChartProps[\"series\"];\n  labels: string[];\n};\n\nexport interface SmallMultiplesProps extends React.HTMLAttributes<HTMLDivElement> {\n  panels: SmallMultiple[];\n  height?: number;\n  unit?: string;\n  grid?: boolean;\n  ticks?: number;\n  spot?: boolean | string;\n  /** Compare panels on one scale by default. Disable only for independent trends. */\n  sharedDomain?: boolean;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const SmallMultiples = React.forwardRef<HTMLDivElement, SmallMultiplesProps>(function SmallMultiples({\n  panels,\n  height = 136,\n  unit,\n  grid = true,\n  ticks = 3,\n  className,\n  spot,\n  sharedDomain = true,\n  locale,\n  style,\n  ...props\n}, ref) {\n  const sx = rs([\"rs-chart-multi\", className], styles.multi);\n  const item = rs([\"rs-chart-multi-item\"], styles.item);\n  const values = panels.flatMap((panel) => panel.series.flatMap((series) => series.values)).filter((value): value is number => typeof value === \"number\" && Number.isFinite(value));\n  const domain: [number, number] | undefined = sharedDomain ? [Math.min(0, ...values), niceMax(Math.max(0, ...values))] : undefined;\n  const idBase = React.useId();\n  return (\n    <div ref={ref} {...props} className={sx.className} style={{ ...sx.style, ...style }}>\n      {panels.map((p, i) => {\n        const capId = `${idBase}-cap-${i}`;\n        return (\n          <figure key={p.title} className={item.className} style={item.style}>\n            <figcaption id={capId} {...rs([\"rs-chart-multi-cap\"], styles.cap)}>\n              {p.title}\n            </figcaption>\n            <LineChart\n              series={p.series}\n              labels={p.labels}\n              height={height}\n              unit={unit}\n              grid={grid}\n              ticks={ticks}\n              spot={spot}\n              locale={locale}\n              domain={domain}\n              aria-labelledby={capId}\n            />\n          </figure>\n        );\n      })}\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/multiples.tsx"
    },
    {
      "path": "vlak/charts/sparkline.tsx",
      "content": "import * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { rs } from \"../rs\";\nimport { CROUWEL_SPOT, SrTable, chartStyles, defaultFormat, lineMark } from \"./frame\";\n\nconst styles = stylex.create({\n  spark: {\n    display: \"inline-block\",\n    verticalAlign: \"middle\",\n  },\n  svg: {\n    display: \"block\",\n    overflow: \"visible\",\n  },\n});\n\nexport interface SparklineProps extends React.HTMLAttributes<HTMLSpanElement> {\n  values: Array<number | null>;\n  width?: number;\n  height?: number;\n  spot?: boolean | string;\n  /** What the trend is of; leads the accessible name and the table. */\n  label?: string;\n  unit?: string;\n  /** BCP 47 tag for number formatting; undefined is the reader's own. */\n  locale?: string;\n}\n\nexport const Sparkline = React.forwardRef<HTMLSpanElement, SparklineProps>(function Sparkline({\n  values,\n  width = 120,\n  height = 28,\n  className,\n  spot,\n  style,\n  label,\n  unit,\n  locale,\n  \"aria-label\": ariaLabel,\n  \"aria-labelledby\": ariaLabelledBy,\n  ...props\n}, ref) {\n  const finite = values.filter((v): v is number => typeof v === \"number\" && Number.isFinite(v));\n  const max = finite.length ? Math.max(...finite) : 1;\n  const min = finite.length ? Math.min(...finite) : 0;\n  const span = max - min || 1;\n  const x = (i: number) => (i / Math.max(values.length - 1, 1)) * (width - 4) + 2;\n  const y = (v: number) => 2 + (1 - (v - min) / span) * (height - 4);\n  let penDown = false;\n  const d = values.map((v, i) => {\n    if (typeof v !== \"number\" || !Number.isFinite(v)) { penDown = false; return \"\"; }\n    const command = `${penDown ? \"L\" : \"M\"}${x(i)} ${y(v)}`;\n    penDown = true;\n    return command;\n  }).join(\" \");\n  const last = values[values.length - 1];\n  const color = spot === true ? CROUWEL_SPOT : typeof spot === \"string\" ? spot : undefined;\n  const sx = rs([\"rs-spark\", className], styles.spark);\n  const svg = rs([\"rs-chart-svg\"], styles.svg);\n  const line = lineMark(0, Boolean(spot));\n  const dot = rs([\"rs-chart-dot\"], chartStyles.dot);\n  const name = ariaLabelledBy\n    ? { \"aria-labelledby\": ariaLabelledBy }\n    : {\n        \"aria-label\":\n          ariaLabel ??\n          (finite.length ? `${label ? `${label}: ` : \"\"}trend of ${finite.length} values ending at ${last != null ? defaultFormat(last, unit, locale) : \"No data\"}` : `${label ? `${label}: ` : \"\"}No data to display`),\n      };\n  return (\n    <span\n      ref={ref}\n      {...props}\n      className={sx.className}\n      style={{\n        ...sx.style,\n        ...(style as React.CSSProperties),\n        ...(color ? ({ [\"--rs-chart-spot\" as string]: color } as React.CSSProperties) : null),\n      }}\n    >\n      <svg\n        className={svg.className}\n        style={svg.style}\n        width={width}\n        height={height}\n        viewBox={`0 0 ${width} ${height}`}\n        role=\"img\"\n        {...name}\n      >\n        {finite.length > 0 && <path className={line.className} style={line.style} d={d} />}\n        {last != null && Number.isFinite(last) && <circle className={dot.className} style={dot.style} cx={x(values.length - 1)} cy={y(last)} r={2} />}\n      </svg>\n      <SrTable\n        caption={label ?? \"Trend\"}\n        labels={values.map((_, i) => `${i + 1}`)}\n        series={[{ name: unit ?? label ?? \"Value\", values }]}\n      />\n    </span>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/vlak/charts/sparkline.tsx"
    },
    {
      "path": "vlak/charts/frame.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport * as stylex from \"@stylexjs/stylex\";\nimport { vlak, mq } from \"../tokens.stylex\";\nimport { rs } from \"../rs\";\nimport { hidden } from \"../hidden.stylex\";\n\nexport interface ChartSeries {\n  name: string;\n  values: Array<number | null>;\n}\n\nexport interface ChartAnnotation {\n  /** Index into the labels (line/bar) or an x value (scatter). */\n  at: number;\n  label: string;\n}\n\nexport interface ChartPoint {\n  x: number;\n  y: number;\n  label?: string;\n  group?: string;\n}\n\nconst styles = stylex.create({\n  chart: {\n    position: \"relative\",\n    width: \"100%\",\n    borderRadius: 0,\n    boxShadow: \"none\",\n  },\n  field: {\n    isolation: \"isolate\",\n    backgroundColor: vlak.paper,\n    borderWidth: vlak.hairline,\n    borderStyle: \"solid\",\n    borderColor: vlak.divider,\n    borderRadius: 0,\n    boxShadow: \"none\",\n    padding: \"1rem\",\n  },\n  /* The canvas is measured; the svg's viewBox equals its rendered width, so type stays CSS-px sized. */\n  canvas: {\n    position: \"relative\",\n    width: \"100%\",\n    minWidth: 0,\n  },\n  svg: {\n    display: \"block\",\n    width: \"100%\",\n    height: \"auto\",\n    overflow: \"visible\",\n    outlineWidth: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n    outlineStyle: {\n      default: null,\n      \":focus-visible\": \"solid\",\n    },\n    outlineColor: {\n      default: null,\n      \":focus-visible\": vlak.ink,\n    },\n    outlineOffset: {\n      default: null,\n      \":focus-visible\": 2,\n    },\n  },\n  plot: {\n    transformOrigin: \"center\",\n  },\n  head: {\n    display: \"flex\",\n    justifyContent: \"space-between\",\n    alignItems: \"baseline\",\n    gap: \"0.75rem\",\n    marginBottom: \"0.625rem\",\n  },\n  title: {\n    fontSize: \"0.8125rem\",\n    fontWeight: 600,\n    letterSpacing: \"-0.01em\",\n    color: vlak.ink,\n  },\n  grid: {\n    stroke: vlak.divider,\n    strokeWidth: 1,\n  },\n  baseline: {\n    stroke: vlak.divider,\n    strokeWidth: 1,\n  },\n  axis: {\n    fill: vlak.gray,\n    fontSize: \"0.6875rem\",\n    fontWeight: 500,\n    letterSpacing: 0,\n    fontVariantNumeric: \"tabular-nums\",\n  },\n  line: {\n    fill: \"none\",\n    stroke: vlak.ink,\n    strokeWidth: 1,\n    strokeLinecap: \"butt\",\n    strokeLinejoin: \"miter\",\n  },\n  lineDashed: {\n    strokeDasharray: \"4 4\",\n  },\n  lineMuted: {\n    stroke: vlak.gray,\n  },\n  lineDotted: {\n    stroke: vlak.gray,\n    strokeDasharray: \"0.1 5\",\n    strokeWidth: 1.5,\n    strokeLinecap: \"butt\",\n  },\n  lineSpot: {\n    stroke: \"var(--rs-chart-spot)\",\n  },\n  area: {\n    fill: vlak.ink,\n    opacity: 0.08,\n    stroke: \"none\",\n  },\n  areaSpot: {\n    fill: \"var(--rs-chart-spot)\",\n    opacity: 0.16,\n    stroke: \"none\",\n  },\n  bar: {\n    fill: vlak.ink,\n    borderRadius: 0,\n    transition: {\n      default: \"fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)\",\n      [mq.reduce]: \"none\",\n    },\n  },\n  barMuted: {\n    fill: vlak.divider,\n  },\n  barSpot: {\n    fill: \"var(--rs-chart-spot)\",\n  },\n  cursor: {\n    stroke: vlak.divider,\n    strokeWidth: 1,\n  },\n  dot: {\n    fill: vlak.paper,\n    stroke: vlak.ink,\n    strokeWidth: 1,\n  },\n  mark: {\n    fill: vlak.ink,\n    borderRadius: 0,\n    transition: {\n      default: \"fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)\",\n      [mq.reduce]: \"none\",\n    },\n  },\n  markSpot: {\n    fill: \"var(--rs-chart-spot)\",\n  },\n  ann: {\n    fill: vlak.gray,\n    fontSize: \"0.6875rem\",\n    fontWeight: 500,\n    letterSpacing: \"-0.01em\",\n  },\n  tip: {\n    position: \"absolute\",\n    pointerEvents: \"none\",\n    backgroundColor: vlak.paper,\n    borderWidth: vlak.hairline,\n    borderStyle: \"solid\",\n    borderColor: vlak.divider,\n    borderRadius: 0,\n    boxShadow: \"none\",\n    paddingBlock: \"0.375rem\",\n    paddingInline: \"0.5rem\",\n    fontSize: \"0.75rem\",\n    letterSpacing: \"-0.01em\",\n    color: vlak.ink,\n    whiteSpace: \"nowrap\",\n    zIndex: vlak.zFloat,\n    transform: \"translate(-50%, calc(-100% - 8px))\",\n  },\n  tipLabel: {\n    display: \"block\",\n    fontSize: \"0.6875rem\",\n    color: vlak.gray,\n    marginBottom: 2,\n  },\n  tipRow: {\n    display: \"flex\",\n    alignItems: \"center\",\n    justifyContent: \"space-between\",\n    gap: \"0.625rem\",\n  },\n  tipName: {\n    color: vlak.gray,\n  },\n  tipVal: {\n    fontWeight: 600,\n    fontVariantNumeric: \"tabular-nums\",\n  },\n  legend: {\n    display: \"flex\",\n    flexWrap: \"wrap\",\n    gap: \"0.875rem\",\n    marginTop: \"0.625rem\",\n  },\n  legendItem: {\n    display: \"flex\",\n    alignItems: \"center\",\n    gap: \"0.4375rem\",\n    fontSize: \"0.75rem\",\n    fontWeight: 500,\n    letterSpacing: \"-0.01em\",\n    color: vlak.gray,\n  },\n  legendSvg: {\n    display: \"block\",\n  },\n});\n\nexport const chartStyles = styles;\n\nexport function ticksBetween(min: number, max: number, count = 4): number[] {\n  if (!Number.isFinite(min) || !Number.isFinite(max)) return [];\n  if (min > max) [min, max] = [max, min];\n  const span = max - min || 1;\n  const step = niceStep(span, count);\n  const start = Math.ceil(min / step) * step;\n  const values: number[] = [];\n  for (let t = start; t <= max + step * 1e-6; t += step) values.push(+t.toFixed(8));\n  return values.length ? values : [+min.toFixed(2), +max.toFixed(2)];\n}\n\nexport const LINE_CLASS = [\n  \"rs-chart-line\",\n  \"rs-chart-line rs-chart-line-dashed\",\n  \"rs-chart-line rs-chart-line-muted\",\n  \"rs-chart-line rs-chart-line-dotted\",\n] as const;\n\nconst LINE_SX = [null, styles.lineDashed, styles.lineMuted, styles.lineDotted] as const;\n\nexport const MAX_SERIES = LINE_CLASS.length;\n\n/** Crouwel-adjacent red. Applied only as an inline field variable, never in CSS. */\nexport const CROUWEL_SPOT = \"#E30613\";\n\nexport function niceMax(raw: number): number {\n  if (!Number.isFinite(raw) || raw <= 0) return 1;\n  const pow = 10 ** Math.floor(Math.log10(raw));\n  for (const f of [1, 2, 2.5, 5, 10]) {\n    if (f * pow >= raw) return f * pow;\n  }\n  return 10 * pow;\n}\n\nconst formatters = new Map<string, Intl.NumberFormat>();\n\nfunction numberFormat(locale: string | undefined, options: Intl.NumberFormatOptions): Intl.NumberFormat {\n  const key = `${locale ?? \"\"}|${JSON.stringify(options)}`;\n  let f = formatters.get(key);\n  if (!f) {\n    f = new Intl.NumberFormat(locale, options);\n    formatters.set(key, f);\n  }\n  return f;\n}\n\n/**\n * Locale-aware number: compact from a thousand (1.2K, 3M), else at most one\n * decimal. `locale` undefined means the reader's own.\n */\nexport function defaultFormat(v: number, unit?: string, locale?: string): string {\n  if (!Number.isFinite(v)) return \"No data\";\n  const n = numberFormat(\n    locale,\n    Math.abs(v) >= 1000\n      ? { notation: \"compact\", maximumFractionDigits: 1 }\n      : { maximumFractionDigits: Number.isInteger(v) ? 0 : 1 },\n  ).format(v);\n  return unit ? `${n} ${unit}` : n;\n}\n\nexport function niceStep(span: number, count = 4): number {\n  const raw = span / (Number.isFinite(count) ? Math.max(1, Math.min(100, count)) : 4);\n  if (raw <= 0) return 1;\n  const pow = 10 ** Math.floor(Math.log10(raw));\n  for (const f of [1, 2, 2.5, 5, 10]) {\n    if (f * pow >= raw) return f * pow;\n  }\n  return 10 * pow;\n}\n\nexport function ticksFor(max: number, count = 4, inverted = false): number[] {\n  const step = niceStep(max, count);\n  const values: number[] = [];\n  for (let t = step; t <= max + step * 1e-6; t += step) values.push(+t.toFixed(8));\n  return inverted ? values.reverse() : values;\n}\n\nexport function stackedRows(series: ChartSeries[]): number[][] {\n  const n = Math.max(0, ...series.map((s) => s.values.length));\n  return Array.from({ length: n }, (_, i) => {\n    let positive = 0;\n    let negative = 0;\n    return series.map((s) => {\n      const value = s.values[i];\n      if (typeof value !== \"number\" || !Number.isFinite(value)) return Number.NaN;\n      if (value < 0) { negative += value; return negative; }\n      positive += value;\n      return positive;\n    });\n  });\n}\n\nexport function lineMark(seriesIndex: number, spot?: boolean) {\n  if (spot && seriesIndex === 0) {\n    return rs([\"rs-chart-line\", \"rs-chart-line-spot\"], styles.line, styles.lineSpot);\n  }\n  const classes = LINE_CLASS[seriesIndex] ?? LINE_CLASS[0];\n  return rs(classes.split(/\\s+/), styles.line, LINE_SX[seriesIndex]);\n}\n\nexport function areaMark(spot?: boolean) {\n  return rs([spot ? \"rs-chart-area-spot\" : \"rs-chart-area\"], spot ? styles.areaSpot : styles.area);\n}\n\nexport function barMark(opts: { spot?: boolean; muted?: boolean } = {}) {\n  return rs(\n    [\"rs-chart-bar\", opts.spot && \"rs-chart-bar-spot\", opts.muted && \"rs-chart-bar-muted\"],\n    styles.bar,\n    opts.spot && styles.barSpot,\n    opts.muted && styles.barMuted,\n  );\n}\n\nexport function scatterMark(spot?: boolean) {\n  return rs([\"rs-chart-mark\", spot && \"rs-chart-mark-spot\"], styles.mark, spot && styles.markSpot);\n}\n\nexport interface SrSeries {\n  name: string;\n  values: Array<number | string | null>;\n}\n\n/** The data behind a plot, as a screen-reader table. */\nexport function SrTable({\n  caption,\n  labels,\n  series,\n}: {\n  caption: string;\n  labels: string[];\n  series: SrSeries[];\n}) {\n  const sx = rs([\"rs-sr\"], hidden.sr);\n  return (\n    <table className={sx.className} style={sx.style}>\n      <caption>{caption}</caption>\n      <thead>\n        <tr>\n          <th scope=\"col\">Label</th>\n          {series.map((s) => (\n            <th key={s.name} scope=\"col\">\n              {s.name}\n            </th>\n          ))}\n        </tr>\n      </thead>\n      <tbody>\n        {labels.map((label, i) => (\n          <tr key={i}>\n            <th scope=\"row\">{label}</th>\n            {series.map((s) => (\n              <td key={s.name}>{s.values[i] == null || (typeof s.values[i] === \"number\" && !Number.isFinite(s.values[i])) ? \"No data\" : s.values[i]}</td>\n            ))}\n          </tr>\n        ))}\n      </tbody>\n    </table>\n  );\n}\n\nexport function LegendSwatch({ seriesIndex, spot }: { seriesIndex: number; spot?: boolean }) {\n  const svg = rs([\"rs-chart-legend-svg\"], styles.legendSvg);\n  return (\n    <svg width=\"18\" height=\"4\" viewBox=\"0 0 18 4\" aria-hidden=\"true\" className={svg.className} style={svg.style}>\n      <path d=\"M0 2h18\" {...lineMark(seriesIndex, spot)} />\n    </svg>\n  );\n}\n\nexport function ChartHead({ className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  const sx = rs([\"rs-chart-head\", className], styles.head);\n  return <div {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n}\n\nexport function ChartTitle({ className, style, ...props }: React.HTMLAttributes<HTMLParagraphElement>) {\n  const sx = rs([\"rs-chart-title\", className], styles.title);\n  return <p {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n}\n\nexport function ChartLegend({ className, style, ...props }: React.HTMLAttributes<HTMLDivElement>) {\n  const sx = rs([\"rs-chart-legend\", className], styles.legend);\n  return <div {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n}\n\nexport function ChartLegendItem({ className, style, ...props }: React.HTMLAttributes<HTMLSpanElement>) {\n  const sx = rs([\"rs-chart-legend-item\", className], styles.legendItem);\n  return <span {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n}\n\nexport function ChartTip({\n  left,\n  top,\n  label,\n  rows,\n}: {\n  left: number | string;\n  top: number | string;\n  label: string;\n  rows: Array<{ name?: string; value: string }>;\n}) {\n  const tip = rs([\"rs-chart-tip\"], styles.tip);\n  const tipLabel = rs([\"rs-chart-tip-label\"], styles.tipLabel);\n  return (\n    <div role=\"status\" className={tip.className} style={{ ...tip.style, left, top }}>\n      <span className={tipLabel.className} style={tipLabel.style}>\n        {label}\n      </span>\n      {rows.map((row, i) => {\n        const tipRow = rs([\"rs-chart-tip-row\"], styles.tipRow);\n        const tipName = rs([\"rs-chart-tip-name\"], styles.tipName);\n        const tipVal = rs([\"rs-chart-tip-val\"], styles.tipVal);\n        return (\n          <div key={i} className={tipRow.className} style={tipRow.style}>\n            {row.name ? (\n              <span className={tipName.className} style={tipName.style}>\n                {row.name}\n              </span>\n            ) : null}\n            <span className={tipVal.className} style={tipVal.style}>\n              {row.value}\n            </span>\n          </div>\n        );\n      })}\n    </div>\n  );\n}\n\nexport interface ChartFieldProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** One Crouwel spot on the field. Chrome stays ink. */\n  spot?: boolean | string;\n}\n\nexport const ChartField = React.forwardRef<HTMLDivElement, ChartFieldProps>(function ChartField(\n  { spot, className, style, children, ...props },\n  ref,\n) {\n  const color = spot === true ? CROUWEL_SPOT : typeof spot === \"string\" ? spot : undefined;\n  const sx = rs([\"rs-chart\", \"rs-chart-field\", className], styles.chart, styles.field);\n  return (\n    <div\n      ref={ref}\n      {...props}\n      className={sx.className}\n      style={{\n        ...sx.style,\n        ...(style as React.CSSProperties),\n        ...(color ? ({ [\"--rs-chart-spot\" as string]: color } as React.CSSProperties) : null),\n      }}\n    >\n      {children}\n    </div>\n  );\n});\n\n/** The canvas wraps a plot: measured for width, and the tooltip's positioning box. */\nexport const ChartCanvas = React.forwardRef<HTMLDivElement, React.HTMLAttributes<HTMLDivElement>>(\n  function ChartCanvas({ className, style, ...props }, ref) {\n    const sx = rs([\"rs-chart-canvas\", className], styles.canvas);\n    return <div ref={ref} {...props} className={sx.className} style={{ ...sx.style, ...style }} />;\n  },\n);\n\nexport const PLOT = { W: 408, ML: 36, MR: 8, MT: 8, MB: 22 } as const;\n\n/**\n * Rendered width of the canvas in CSS px, so the plot lays out in real\n * pixels and its type keeps its size. PLOT.W on the server, before the\n * first measure, and where ResizeObserver is missing.\n */\nexport function useChartWidth<T extends HTMLElement>(fallback: number = PLOT.W): [React.RefObject<T | null>, number] {\n  const ref = React.useRef<T | null>(null);\n  const [width, setWidth] = React.useState(fallback);\n  React.useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    const apply = (w: number) => {\n      if (w > 0) setWidth((prev) => (Math.abs(prev - w) < 0.5 ? prev : Math.round(w)));\n    };\n    apply(el.clientWidth);\n    if (typeof ResizeObserver === \"undefined\") return;\n    const observer = new ResizeObserver((entries) => {\n      for (const entry of entries) apply(entry.contentRect.width);\n    });\n    observer.observe(el);\n    return () => observer.disconnect();\n  }, []);\n  return [ref, width];\n}\n\n/**\n * Keyboard cursor over `count` points: arrows step, Home/End jump, Escape\n * clears. Returns undefined for keys it does not handle.\n */\nexport function stepCursor(key: string, current: number | null, count: number): number | null | undefined {\n  if (count <= 0) return undefined;\n  const last = count - 1;\n  if (key === \"ArrowRight\") return current == null ? 0 : Math.min(last, current + 1);\n  if (key === \"ArrowLeft\") return current == null ? last : Math.max(0, current - 1);\n  if (key === \"Home\") return 0;\n  if (key === \"End\") return last;\n  if (key === \"Escape\") return null;\n  return undefined;\n}\n\nexport interface PlotNameProps {\n  \"aria-label\"?: string;\n  \"aria-labelledby\"?: string;\n}\n\n/**\n * The plot's accessible name: an explicit label or labelledby wins, then the\n * visible ChartTitle by id, then a composed description. Never a bare kind.\n */\nexport function plotName(explicit: PlotNameProps, titleId: string | undefined, fallback: string): PlotNameProps {\n  if (explicit[\"aria-labelledby\"]) return { \"aria-labelledby\": explicit[\"aria-labelledby\"] };\n  if (explicit[\"aria-label\"]) return { \"aria-label\": explicit[\"aria-label\"] };\n  if (titleId) return { \"aria-labelledby\": titleId };\n  return { \"aria-label\": fallback };\n}\n\n/** \"Bar chart of Sheets and Proofs, in units\". */\nexport function describePlot(kind: string, names: string[], unit?: string): string {\n  const what = names.filter(Boolean);\n  const list = what.length > 1 ? `${what.slice(0, -1).join(\", \")} and ${what[what.length - 1]}` : what[0];\n  return `${kind}${list ? ` of ${list}` : \"\"}${unit ? `, in ${unit}` : \"\"}`;\n}\n\n/** Props that make a plot a focusable, keyboard-driven group. */\nexport function plotProps(\n  name: PlotNameProps,\n  onKey: (key: string) => boolean,\n  onLeave: () => void,\n): React.SVGAttributes<SVGSVGElement> {\n  return {\n    role: \"group\",\n    tabIndex: 0,\n    \"aria-roledescription\": \"interactive chart\",\n    ...name,\n    onKeyDown: (e) => {\n      if (onKey(e.key)) e.preventDefault();\n    },\n    onBlur: onLeave,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/vlak/charts/frame.tsx"
    },
    {
      "path": "vlak/styles/chart.css",
      "content": "/* ── chart: generated from packages/react/src/components/charts/frame.tsx ── */\n.rs-chart{position:relative;width:100%;border-radius:0;box-shadow:none}\n.rs-chart-field{isolation:isolate;background-color:var(--bg);border-width:1px;border-style:solid;border-color:var(--divider);border-radius:0;box-shadow:none;padding:1rem}\n.rs-chart-canvas{position:relative;width:100%;min-width:0}\n.rs-chart-svg,.rs-chart-plot.rs-chart-donut{display:block;width:100%;height:auto;overflow:visible}\n.rs-chart-svg:focus-visible,.rs-chart-plot.rs-chart-donut:focus-visible{outline-width:2px;outline-style:solid;outline-color:var(--text);outline-offset:2px}\n.rs-chart-plot,.rs-chart-plot.rs-chart-donut{transform-origin:center}\n.rs-chart-head{display:flex;justify-content:space-between;align-items:baseline;gap:0.75rem;margin-bottom:0.625rem}\n.rs-chart-title{font-size:0.8125rem;font-weight:600;letter-spacing:-0.01em;color:var(--text)}\n.rs-chart-grid{stroke:var(--divider);stroke-width:1}\n.rs-chart-baseline{stroke:var(--divider);stroke-width:1}\n.rs-chart-axis{fill:var(--text-secondary);font-size:0.6875rem;font-weight:500;letter-spacing:0;font-variant-numeric:tabular-nums}\n.rs-chart-line{fill:none;stroke:var(--text);stroke-width:1;stroke-linecap:butt;stroke-linejoin:miter}\n.rs-chart-line-dashed{stroke-dasharray:4 4}\n.rs-chart-line-muted{stroke:var(--text-secondary)}\n.rs-chart-line-dotted{stroke:var(--text-secondary);stroke-dasharray:0.1 5;stroke-width:1.5;stroke-linecap:butt}\n.rs-chart-line-spot{stroke:var(--rs-chart-spot)}\n.rs-chart-area{fill:var(--text);opacity:0.08;stroke:none}\n.rs-chart-area-spot{fill:var(--rs-chart-spot);opacity:0.16;stroke:none}\n.rs-chart-bar{fill:var(--text);border-radius:0;transition:fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)}\n@media (prefers-reduced-motion: reduce){.rs-chart-bar{transition:none}}\n.rs-chart-bar-muted{fill:var(--divider)}\n.rs-chart-bar-spot{fill:var(--rs-chart-spot)}\n.rs-chart-cursor{stroke:var(--divider);stroke-width:1}\n.rs-chart-dot{fill:var(--bg);stroke:var(--text);stroke-width:1}\n.rs-chart-mark{fill:var(--text);border-radius:0;transition:fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)}\n@media (prefers-reduced-motion: reduce){.rs-chart-mark{transition:none}}\n.rs-chart-mark-spot{fill:var(--rs-chart-spot)}\n.rs-chart-ann{fill:var(--text-secondary);font-size:0.6875rem;font-weight:500;letter-spacing:-0.01em}\n.rs-chart-tip{position:absolute;pointer-events:none;background-color:var(--bg);border-width:1px;border-style:solid;border-color:var(--divider);border-radius:0;box-shadow:none;padding-block:0.375rem;padding-inline:0.5rem;font-size:0.75rem;letter-spacing:-0.01em;color:var(--text);white-space:nowrap;z-index:var(--z-float);transform:translate(-50%, calc(-100% - 8px))}\n.rs-chart-tip-label{display:block;font-size:0.6875rem;color:var(--text-secondary);margin-bottom:2px}\n.rs-chart-tip-row{display:flex;align-items:center;justify-content:space-between;gap:0.625rem}\n.rs-chart-tip-name{color:var(--text-secondary)}\n.rs-chart-tip-val{font-weight:600;font-variant-numeric:tabular-nums}\n.rs-chart-legend{display:flex;flex-wrap:wrap;gap:0.875rem;margin-top:0.625rem}\n.rs-chart-legend-item{display:flex;align-items:center;gap:0.4375rem;font-size:0.75rem;font-weight:500;letter-spacing:-0.01em;color:var(--text-secondary)}\n.rs-chart-legend-svg{display:block}\n.rs-chart-plot.rs-chart-donut{display:block}\n.rs-chart-donut-track{fill:none;stroke:var(--divider);stroke-width:1}\n.rs-chart-donut-value{fill:none;stroke:var(--rs-chart-spot);stroke-width:1;stroke-linecap:butt}\n.rs-chart-donut-label{fill:var(--text);font-size:1.25rem;font-weight:500;letter-spacing:-0.03em;font-variant-numeric:tabular-nums}\n.rs-chart-donut-caption{fill:var(--text-secondary);font-size:0.6875rem;font-weight:500;letter-spacing:0.06em}\n.rs-chart-share{display:flex;height:0.5rem;width:100%;border-width:1px;border-style:solid;border-color:var(--divider);border-radius:0;box-shadow:none}\n.rs-chart-share-seg{height:100%;border-radius:0;background-color:var(--text);transition:fill var(--duration-snap) var(--ease), opacity var(--duration-snap) var(--ease)}\n.rs-chart-share-seg:nth-child(2){background-color:var(--text-secondary)}\n.rs-chart-share-seg:nth-child(3){background-color:var(--divider)}\n.rs-chart-share-seg-spot{background-color:var(--rs-chart-spot)}\n.rs-chart-hist{fill:var(--divider);stroke:none;border-radius:0}\n.rs-chart-multi{display:grid;grid-template-columns:repeat(auto-fill, minmax(184px, 1fr));gap:1.25rem}\n.rs-chart-multi-item{margin:0;min-width:0}\n.rs-chart-multi-cap{margin-top:0;margin-inline-end:0;margin-bottom:0.5rem;margin-inline-start:0;font-size:0.75rem;font-weight:600;letter-spacing:-0.01em;color:var(--text)}\n.rs-spark{display:inline-block;vertical-align:middle}\n.rs-chart-svg{display:block;overflow:visible}\n",
      "type": "registry:file",
      "target": "styles/vlak/chart.css"
    }
  ],
  "meta": {
    "vlak": {
      "category": "charts",
      "classes": [
        "rs-chart",
        "rs-chart-canvas",
        "rs-chart-line",
        "rs-chart-grid",
        "rs-chart-axis",
        "rs-chart-bar",
        "rs-spark",
        "rs-chart-legend-svg",
        "rs-chart-svg"
      ],
      "snippet": "<div class=\"rs-chart\"><svg viewBox=\"0 0 240 64\" width=\"240\" height=\"64\"><line class=\"rs-chart-grid\" x1=\"0\" x2=\"240\" y1=\"56\" y2=\"56\"/><path class=\"rs-chart-line\" d=\"M0 44 L40 36 L80 40 L120 22 L160 26 L200 12 L240 16\"/></svg></div>",
      "cssOnly": false,
      "registryDependencies": [
        "vlak-lib"
      ],
      "aliases": [
        "Chart",
        "Line chart",
        "Sparkline",
        "Trend line"
      ],
      "example": "import { LineChart, Sparkline } from \"@noorddev/vlak-react\";\n\n<LineChart\n  height={204}\n  labels={days}\n  series={[\n    { name: \"Sheets\", values: sheets },\n    { name: \"Proofs\", values: proofs },\n  ]}\n  unit=\"sheets\"\n  yLabel=\"Output\"\n  annotations={[{ at: 3, label: \"Press\" }]}\n  spot\n/>\n\n<Sparkline values={[3, 5, 4, 8, 7, 9]} label=\"Sheets\" unit=\"k\" />",
      "usage": {
        "use": [
          "Up to four series over time on one field; solid, dashed, gray, and dotted keep them apart without hue.",
          "spot for the one accent the chart is allowed; Sparkline for a trend inside a row."
        ],
        "avoid": [
          "Categories; use BarChart.",
          "More than four series; split them into SmallMultiples."
        ]
      },
      "keyboard": [
        {
          "keys": "Tab",
          "does": "Focuses the plot"
        },
        {
          "keys": "Arrow right, Arrow left",
          "does": "Moves the cursor across the points; a status tooltip reads the values"
        },
        {
          "keys": "Home, End",
          "does": "First or last point"
        },
        {
          "keys": "Escape",
          "does": "Clears the cursor"
        }
      ],
      "a11y": [
        "The plot is a focusable <svg role=\"group\"> with aria-roledescription=\"interactive chart\", named by aria-label, aria-labelledby, yLabel, or the series names.",
        "A visually hidden table carries every value, so the data reads without the picture; the tooltip is role=\"status\".",
        "Sparkline is role=\"img\" with a name built from label, the count, and the last value, plus the same hidden table.",
        "Numbers format through Intl.NumberFormat; locale picks the reader's own by default. Marks stay visible in forced colors.",
        "Null or non-finite samples create gaps instead of fabricated zeros. Missing values read as No data; empty plots announce their state, and an empty Sparkline has no invented last point.",
        "Explicit domains generate ticks within their bounds and clip marks to the plot. Without a domain, negative values remain visible."
      ]
    }
  }
}
