{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "reference-area",
  "type": "registry:component",
  "title": "Reference Area",
  "description": "Shaded data-coordinate band for target ranges and thresholds on time-series charts",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "@bklit/chart-context",
    "@bklit/background"
  ],
  "files": [
    {
      "path": "src/charts/reference-area.tsx",
      "content": "\"use client\";\n\nimport { motion } from \"motion/react\";\nimport { useId, useLayoutEffect, useMemo } from \"react\";\nimport { chartCssVars, useChartStable, useYScale } from \"./chart-context\";\nimport { type PatternPresetId, renderPatternPreset } from \"./pattern-preset\";\nimport {\n  computeReferenceAreaRect,\n  type ReferenceAreaIfOverflow,\n} from \"./reference-area-geometry\";\nimport { useReferenceAreaRegistration } from \"./reference-area-registration-context\";\nimport { normalizeYAxisId } from \"./y-axis-scales\";\nimport { isReferenceAreaVisiblePhase } from \"./y-domain-utils\";\n\nconst DEFAULT_FILL =\n  \"color-mix(in oklch, var(--chart-foreground-muted) 12%, transparent)\";\n\nexport type ReferenceAreaStrokeStyle = \"solid\" | \"dashed\";\n\nexport interface ReferenceAreaProps {\n  /** Lower Y data bound (extends to plot top when omitted). */\n  y1?: number;\n  /** Upper Y data bound (extends to plot bottom when omitted). */\n  y2?: number;\n  /** Starting X data coordinate (extends to plot left when omitted). */\n  x1?: Date | number;\n  /** Ending X data coordinate (extends to plot right when omitted). */\n  x2?: Date | number;\n  /** Y-scale group id. Default: `\"left\"`. */\n  yAxisId?: string | number;\n  /** Solid fill when `pattern` is `\"none\"`. */\n  fill?: string;\n  fillOpacity?: number;\n  /** Pattern preset. `\"none\"` uses solid `fill`. */\n  pattern?: PatternPresetId;\n  /** Pattern stroke / tile color. */\n  patternColor?: string;\n  patternScale?: number;\n  patternStrokeWidth?: number;\n  patternRadius?: number;\n  patternComplement?: boolean;\n  patternFill?: string;\n  patternDotFill?: boolean;\n  patternTileBackground?: string;\n  stroke?: string;\n  strokeWidth?: number;\n  strokeStyle?: ReferenceAreaStrokeStyle;\n  /** Dash array when `strokeStyle` is `\"dashed\"`. Default: `\"4,4\"`. */\n  strokeDasharray?: string;\n  /** Fade fill and edge lines at left/right. Default: true */\n  fadeEdges?: boolean;\n  /** Horizontal fade zone as % of plot width per edge. Default: 10 */\n  fadeEdgesLength?: number;\n  /** Y-axis tick label color for values inside this band. */\n  axisLabelColor?: string;\n  /** Inward bracket markers at the horizontal center of the band. */\n  showMarkers?: boolean;\n  markerColor?: string;\n  markerSize?: number;\n  ifOverflow?: ReferenceAreaIfOverflow;\n  className?: string;\n}\n\nconst ENTER_FADE_MS = 420;\n\nfunction clampFadeLength(length: number): number {\n  return Math.min(45, Math.max(0, length));\n}\n\nfunction bracketMarkerPath(\n  centerX: number,\n  edgeY: number,\n  size: number,\n  direction: \"down\" | \"up\"\n): string {\n  const half = size / 2;\n  if (direction === \"down\") {\n    return `M ${centerX - half} ${edgeY} L ${centerX + half} ${edgeY} L ${centerX} ${edgeY + size} Z`;\n  }\n  return `M ${centerX - half} ${edgeY} L ${centerX + half} ${edgeY} L ${centerX} ${edgeY - size} Z`;\n}\n\nexport function ReferenceArea({\n  y1,\n  y2,\n  x1,\n  x2,\n  yAxisId,\n  fill = DEFAULT_FILL,\n  fillOpacity = 1,\n  pattern = \"none\",\n  patternColor = chartCssVars.foregroundMuted,\n  patternScale = 1,\n  patternStrokeWidth,\n  patternRadius,\n  patternComplement,\n  patternFill,\n  patternDotFill,\n  patternTileBackground,\n  stroke = chartCssVars.foregroundMuted,\n  strokeWidth = 1,\n  strokeStyle = \"dashed\",\n  strokeDasharray = \"4,4\",\n  fadeEdges = true,\n  fadeEdgesLength = 10,\n  axisLabelColor,\n  showMarkers = false,\n  markerColor = \"var(--chart-1)\",\n  markerSize = 6,\n  ifOverflow = \"hidden\",\n  className,\n}: ReferenceAreaProps) {\n  const { innerWidth, innerHeight, xScale, chartPhase, enterTransition } =\n    useChartStable();\n  const yScale = useYScale(yAxisId);\n  const uniqueId = useId().replace(/:/g, \"\");\n  const registration = useReferenceAreaRegistration();\n\n  useLayoutEffect(() => {\n    if (!registration) {\n      return;\n    }\n    registration.registerReferenceArea(uniqueId, {\n      yAxisId: normalizeYAxisId(yAxisId),\n      y1,\n      y2,\n      axisLabelColor,\n    });\n    return () => registration.unregisterReferenceArea(uniqueId);\n  }, [registration, uniqueId, yAxisId, y1, y2, axisLabelColor]);\n\n  const patternId = `chart-reference-area-pattern-${uniqueId}`;\n  const hMaskId = `chart-reference-area-fade-${uniqueId}`;\n  const hGradientId = `${hMaskId}-gradient`;\n\n  const rect = useMemo(\n    () =>\n      computeReferenceAreaRect({\n        innerWidth,\n        innerHeight,\n        x1,\n        x2,\n        y1,\n        y2,\n        ifOverflow,\n        xScale,\n        yScale,\n      }),\n    [innerWidth, innerHeight, x1, x2, y1, y2, ifOverflow, xScale, yScale]\n  );\n\n  const usesPattern = pattern !== \"none\";\n  const patternNode = useMemo(() => {\n    if (!usesPattern) {\n      return null;\n    }\n    return renderPatternPreset(pattern, patternId, {\n      color: patternColor,\n      scale: patternScale,\n      strokeWidth: patternStrokeWidth,\n      radius: patternRadius,\n      complement: patternComplement,\n      fill: patternFill,\n      dotFill: patternDotFill,\n      tileBackground: patternTileBackground,\n    });\n  }, [\n    usesPattern,\n    pattern,\n    patternId,\n    patternColor,\n    patternScale,\n    patternStrokeWidth,\n    patternRadius,\n    patternComplement,\n    patternFill,\n    patternDotFill,\n    patternTileBackground,\n  ]);\n\n  const fadeEdge = clampFadeLength(fadeEdgesLength);\n  const edgeMask = fadeEdges ? `url(#${hMaskId})` : undefined;\n  const lineDash = strokeStyle === \"dashed\" ? strokeDasharray : undefined;\n\n  if (!rect) {\n    return null;\n  }\n\n  const { x, y, width, height } = rect;\n  const topEdgeY = y;\n  const bottomEdgeY = y + height;\n  const centerX = x + width / 2;\n  const visible = isReferenceAreaVisiblePhase(chartPhase);\n  const fadeTransition =\n    enterTransition && typeof enterTransition === \"object\"\n      ? enterTransition\n      : { duration: ENTER_FADE_MS / 1000, ease: \"easeOut\" as const };\n  const areaFill = usesPattern && patternNode ? `url(#${patternId})` : fill;\n\n  return (\n    // biome-ignore lint/a11y/noAriaHiddenOnFocusable: decorative reference band\n    <motion.g\n      animate={{ opacity: visible ? 1 : 0 }}\n      aria-hidden=\"true\"\n      className={className ?? \"chart-reference-area\"}\n      initial={{ opacity: 0 }}\n      transition={fadeTransition}\n    >\n      {edgeMask ? (\n        <defs>\n          <linearGradient id={hGradientId} x1=\"0%\" x2=\"100%\" y1=\"0%\" y2=\"0%\">\n            <stop offset=\"0%\" style={{ stopColor: \"white\", stopOpacity: 0 }} />\n            <stop\n              offset={`${fadeEdge}%`}\n              style={{ stopColor: \"white\", stopOpacity: 1 }}\n            />\n            <stop\n              offset={`${100 - fadeEdge}%`}\n              style={{ stopColor: \"white\", stopOpacity: 1 }}\n            />\n            <stop\n              offset=\"100%\"\n              style={{ stopColor: \"white\", stopOpacity: 0 }}\n            />\n          </linearGradient>\n          <mask id={hMaskId}>\n            <rect\n              fill={`url(#${hGradientId})`}\n              height={innerHeight}\n              width={innerWidth}\n              x={0}\n              y={0}\n            />\n          </mask>\n        </defs>\n      ) : null}\n      {patternNode ? <defs>{patternNode}</defs> : null}\n      <rect\n        fill={areaFill}\n        fillOpacity={fillOpacity}\n        height={height}\n        mask={edgeMask}\n        width={width}\n        x={x}\n        y={y}\n      />\n      <g mask={edgeMask}>\n        <line\n          stroke={stroke}\n          strokeDasharray={lineDash}\n          strokeWidth={strokeWidth}\n          x1={x}\n          x2={x + width}\n          y1={topEdgeY}\n          y2={topEdgeY}\n        />\n        <line\n          stroke={stroke}\n          strokeDasharray={lineDash}\n          strokeWidth={strokeWidth}\n          x1={x}\n          x2={x + width}\n          y1={bottomEdgeY}\n          y2={bottomEdgeY}\n        />\n      </g>\n      {showMarkers ? (\n        <>\n          <path\n            d={bracketMarkerPath(centerX, topEdgeY, markerSize, \"down\")}\n            fill={markerColor}\n          />\n          <path\n            d={bracketMarkerPath(centerX, bottomEdgeY, markerSize, \"up\")}\n            fill={markerColor}\n          />\n        </>\n      ) : null}\n    </motion.g>\n  );\n}\n\nReferenceArea.displayName = \"ReferenceArea\";\n\nexport default ReferenceArea;\n",
      "type": "registry:component",
      "target": "components/charts/reference-area.tsx"
    },
    {
      "path": "src/charts/reference-area-geometry.ts",
      "content": "export type ReferenceAreaIfOverflow = \"hidden\" | \"visible\" | \"discard\";\n\nexport interface ReferenceAreaRect {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}\n\nexport interface ComputeReferenceAreaRectOptions {\n  innerWidth: number;\n  innerHeight: number;\n  x1?: Date | number;\n  x2?: Date | number;\n  y1?: number;\n  y2?: number;\n  ifOverflow?: ReferenceAreaIfOverflow;\n  xScale: (value: Date) => number;\n  yScale: (value: number) => number;\n}\n\nfunction toDate(value: Date | number): Date {\n  return value instanceof Date ? value : new Date(value);\n}\n\nfunction resolveXPixel(\n  xScale: (value: Date) => number,\n  value: Date | number | undefined,\n  fallback: number\n): number {\n  if (value == null) {\n    return fallback;\n  }\n  return xScale(toDate(value));\n}\n\nfunction resolveYPixel(\n  yScale: (value: number) => number,\n  value: number | undefined,\n  fallback: number\n): number {\n  if (value == null) {\n    return fallback;\n  }\n  return yScale(value);\n}\n\nfunction clampRectToPlot(\n  rect: ReferenceAreaRect,\n  innerWidth: number,\n  innerHeight: number\n): ReferenceAreaRect | null {\n  const x1 = Math.max(0, rect.x);\n  const y1 = Math.max(0, rect.y);\n  const x2 = Math.min(innerWidth, rect.x + rect.width);\n  const y2 = Math.min(innerHeight, rect.y + rect.height);\n  const width = x2 - x1;\n  const height = y2 - y1;\n  if (width <= 0 || height <= 0) {\n    return null;\n  }\n  return { x: x1, y: y1, width, height };\n}\n\nfunction isFullyInsidePlot(\n  rect: ReferenceAreaRect,\n  innerWidth: number,\n  innerHeight: number\n): boolean {\n  return (\n    rect.x >= 0 &&\n    rect.y >= 0 &&\n    rect.x + rect.width <= innerWidth &&\n    rect.y + rect.height <= innerHeight\n  );\n}\n\n/** Map data bounds to plot pixels for a reference rectangle. */\nexport function computeReferenceAreaRect(\n  options: ComputeReferenceAreaRectOptions\n): ReferenceAreaRect | null {\n  const {\n    innerWidth,\n    innerHeight,\n    x1,\n    x2,\n    y1,\n    y2,\n    ifOverflow = \"hidden\",\n    xScale,\n    yScale,\n  } = options;\n\n  if (innerWidth <= 0 || innerHeight <= 0) {\n    return null;\n  }\n\n  const left = resolveXPixel(xScale, x1, 0);\n  const right = resolveXPixel(xScale, x2, innerWidth);\n  const top = resolveYPixel(yScale, y1, 0);\n  const bottom = resolveYPixel(yScale, y2, innerHeight);\n\n  const x = Math.min(left, right);\n  const y = Math.min(top, bottom);\n  const width = Math.abs(right - left);\n  const height = Math.abs(bottom - top);\n\n  if (width <= 0 || height <= 0) {\n    return null;\n  }\n\n  const rect: ReferenceAreaRect = { x, y, width, height };\n\n  if (ifOverflow === \"visible\") {\n    return rect;\n  }\n\n  if (ifOverflow === \"discard\") {\n    return isFullyInsidePlot(rect, innerWidth, innerHeight) ? rect : null;\n  }\n\n  return clampRectToPlot(rect, innerWidth, innerHeight);\n}\n\n/** Inclusive data range for axis label highlighting. */\nexport function resolveReferenceDataRange(\n  y1: number | undefined,\n  y2: number | undefined,\n  domain: [number, number]\n): [number, number] {\n  const dMin = Math.min(domain[0], domain[1]);\n  const dMax = Math.max(domain[0], domain[1]);\n  const low = y1 ?? dMin;\n  const high = y2 ?? dMax;\n  return [Math.min(low, high), Math.max(low, high)];\n}\n",
      "type": "registry:component",
      "target": "components/charts/reference-area-geometry.ts"
    },
    {
      "path": "src/charts/reference-area-registration-context.tsx",
      "content": "\"use client\";\n\nimport { createContext, useContext } from \"react\";\nimport type { ReferenceAreaConfig } from \"./reference-area-config\";\n\nexport interface ReferenceAreaRegistrationContextValue {\n  registerReferenceArea: (id: string, config: ReferenceAreaConfig) => void;\n  unregisterReferenceArea: (id: string) => void;\n}\n\nexport const ReferenceAreaRegistrationContext =\n  createContext<ReferenceAreaRegistrationContextValue | null>(null);\n\nexport function useReferenceAreaRegistration(): ReferenceAreaRegistrationContextValue | null {\n  return useContext(ReferenceAreaRegistrationContext);\n}\n",
      "type": "registry:component",
      "target": "components/charts/reference-area-registration-context.tsx"
    }
  ]
}