{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "recharts-brush",
  "dependencies": [
    "recharts"
  ],
  "registryDependencies": [
    "@evilcharts/recharts-chart"
  ],
  "files": [
    {
      "path": "src/registry/ui/recharts-brush.tsx",
      "content": "\"use client\";\n\nimport { motion, useMotionValue, useMotionValueEvent, useSpring, useTransform } from \"motion/react\";\nimport { ResponsiveContainer, AreaChart, Area, LineChart, Line, BarChart, Bar } from \"recharts\";\nimport { ChartStyle, getColorsCount, type ChartConfig } from \"@/registry/ui/recharts-chart\";\nimport { useCallback, useEffect, type ComponentProps, type FC } from \"react\";\nimport type { MotionValue } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\nimport * as React from \"react\";\n\n// ─── Types ──────────────────────────────────────────────────────────────────\n\ntype EvilBrushVariant = \"line\" | \"area\" | \"bar\";\ntype CurveType = ComponentProps<typeof Area>[\"type\"];\n\ninterface EvilBrushRange {\n  startIndex: number;\n  endIndex: number;\n}\n\n// ─── Brush marker — the declarative `<Chart.Brush/>` child ─────────────────────\n// Renders nothing; its PRESENCE turns the brush footer on (replacing the old\n// showBrush prop) and its props carry the brush's height, handle-label\n// formatter, and range callback. Shared so every cartesian chart attaches the\n// SAME component to its root.\n\nexport interface BrushProps {\n  height?: number; // brush preview strip height in px\n  formatLabel?: (value: unknown, index: number) => string; // formats the range-handle labels\n  onChange?: (range: EvilBrushRange) => void; // fires as the range moves\n}\n\n/** Declares the zoom brush below the chart. Presence renders it; renders nothing itself. */\nexport const Brush: FC<BrushProps> = () => null;\n\ninterface EvilBrushProps {\n  /** Full dataset – always rendered in the miniature chart */\n  data: Record<string, unknown>[];\n  /** Chart config with colour definitions */\n  chartConfig: ChartConfig;\n  /** Data keys to plot (default: all keys from chartConfig) */\n  dataKeys?: string[];\n  /** X-axis data key – used for handle labels */\n  xDataKey?: string;\n  /** Visual variant of the mini chart */\n  variant?: EvilBrushVariant;\n  /** Pixel height of the brush */\n  height?: number;\n  /** Extra className */\n  className?: string;\n  /** Whether areas/bars should be stacked in the mini chart */\n  stacked?: boolean;\n  /** Stroke variant for line / area strokes in the mini chart */\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  /** Whether to connect null data points in line / area variants */\n  connectNulls?: boolean;\n  /** Radius for bar corners in the bar variant */\n  barRadius?: number;\n\n  // ── Controlled mode ──────────────────────────────────────────────────\n  /** Controlled start index */\n  startIndex?: number;\n  /** Controlled end index */\n  endIndex?: number;\n\n  // ── Uncontrolled mode ────────────────────────────────────────────────\n  /** Initial start index (uncontrolled) */\n  defaultStartIndex?: number;\n  /** Initial end index (uncontrolled) */\n  defaultEndIndex?: number;\n\n  /** Fired whenever the visible range changes */\n  onChange?: (range: EvilBrushRange) => void;\n  /** Format the handle label from the xDataKey value */\n  formatLabel?: (value: unknown, index: number) => string;\n  /** Curve type for line / area variants */\n  curveType?: CurveType;\n  /** Minimum number of data points that must remain selected */\n  minSpan?: number;\n  /** Whether to render labels on the handles */\n  showLabels?: boolean;\n  /** Skip rendering own ChartStyle (when inside a ChartContainer that already provides CSS vars) */\n  skipStyle?: boolean;\n}\n\n// ─── Spring config ──────────────────────────────────────────────────────────\n\nconst SPRING_CONFIG = { stiffness: 300, damping: 35, mass: 0.8 };\n\n// ─── Pointer-capture drag hook ──────────────────────────────────────────────\n// Replaces raw addEventListener with the modern Pointer Events API.\n// setPointerCapture routes all pointer events to the originating element,\n// so we get mouse + touch + pen support with zero global listeners.\n\ntype DragType = \"left\" | \"right\" | \"middle\";\n\ninterface DragState {\n  type: DragType;\n  originX: number;\n  originRange: EvilBrushRange;\n}\n\nfunction useBrushDrag({\n  range,\n  totalPoints,\n  containerRef,\n  commit,\n}: {\n  range: EvilBrushRange;\n  totalPoints: number;\n  containerRef: React.RefObject<HTMLDivElement | null>;\n  commit: (next: EvilBrushRange, mode?: DragType) => void;\n}) {\n  const dragRef = React.useRef<DragState | null>(null);\n  const [isDragging, setIsDragging] = React.useState(false);\n\n  const toIndexDelta = useCallback(\n    (px: number) => {\n      if (!containerRef.current || totalPoints <= 1) return 0;\n      return Math.round(\n        (px / containerRef.current.getBoundingClientRect().width) * (totalPoints - 1),\n      );\n    },\n    [totalPoints, containerRef],\n  );\n\n  const onPointerDown = useCallback(\n    (e: React.PointerEvent, type: DragType) => {\n      e.preventDefault();\n      (e.target as HTMLElement).setPointerCapture(e.pointerId);\n      dragRef.current = { type, originX: e.clientX, originRange: { ...range } };\n      setIsDragging(true);\n    },\n    [range],\n  );\n\n  const onPointerMove = useCallback(\n    (e: React.PointerEvent) => {\n      const d = dragRef.current;\n      if (!d) return;\n\n      const delta = toIndexDelta(e.clientX - d.originX);\n      const { type, originRange: o } = d;\n\n      if (type === \"left\") {\n        commit({ startIndex: o.startIndex + delta, endIndex: o.endIndex }, \"left\");\n      } else if (type === \"right\") {\n        commit({ startIndex: o.startIndex, endIndex: o.endIndex + delta }, \"right\");\n      } else {\n        const span = o.endIndex - o.startIndex;\n        let s = o.startIndex + delta;\n        let e2 = s + span;\n        if (s < 0) {\n          s = 0;\n          e2 = span;\n        }\n        if (e2 > totalPoints - 1) {\n          e2 = totalPoints - 1;\n          s = Math.max(0, e2 - span);\n        }\n        commit({ startIndex: s, endIndex: e2 }, \"middle\");\n      }\n    },\n    [toIndexDelta, totalPoints, commit],\n  );\n\n  const onPointerUp = useCallback((e: React.PointerEvent) => {\n    (e.target as HTMLElement).releasePointerCapture(e.pointerId);\n    dragRef.current = null;\n    setIsDragging(false);\n  }, []);\n\n  // Helper to bind all three pointer handlers for a given drag type\n  const bind = useCallback(\n    (type: DragType) => ({\n      onPointerDown: (e: React.PointerEvent) => onPointerDown(e, type),\n      onPointerMove,\n      onPointerUp,\n    }),\n    [onPointerDown, onPointerMove, onPointerUp],\n  );\n\n  return { isDragging, bind };\n}\n\n// ─── EvilBrush ────────────────────────────────────────────────────────────\n\nfunction EvilBrush({\n  data,\n  chartConfig,\n  dataKeys,\n  xDataKey,\n  variant = \"area\",\n  height = 56,\n  className,\n  stacked = false,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n  startIndex: controlledStart,\n  endIndex: controlledEnd,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n  onChange,\n  formatLabel,\n  curveType = \"monotone\",\n  minSpan = 2,\n  showLabels = true,\n  skipStyle = false,\n}: EvilBrushProps) {\n  const containerRef = React.useRef<HTMLDivElement>(null);\n  const keys = React.useMemo(() => dataKeys ?? Object.keys(chartConfig), [dataKeys, chartConfig]);\n  const totalPoints = data.length;\n  const chartId = React.useId().replace(/:/g, \"\");\n\n  // ── Controlled vs uncontrolled ──────────────────────────────────────────\n\n  const isControlled = controlledStart !== undefined && controlledEnd !== undefined;\n\n  const [internalRange, setInternalRange] = React.useState<EvilBrushRange>(() => ({\n    startIndex: Math.max(0, Math.min(defaultStartIndex, totalPoints - 1)),\n    endIndex: Math.max(0, Math.min(defaultEndIndex ?? totalPoints - 1, totalPoints - 1)),\n  }));\n\n  // Track the last committed range to avoid duplicate updates when small\n  // mouse movements don't produce index changes (e.g., at boundaries)\n  const lastCommittedRef = React.useRef<EvilBrushRange>(internalRange);\n\n  useEffect(() => {\n    if (!isControlled) {\n      setInternalRange((prev) => {\n        const adjusted = {\n          startIndex: Math.min(prev.startIndex, Math.max(0, totalPoints - 1)),\n          endIndex: Math.min(prev.endIndex, Math.max(0, totalPoints - 1)),\n        };\n        lastCommittedRef.current = adjusted;\n        return adjusted;\n      });\n    }\n  }, [totalPoints, isControlled]);\n\n  // ── Clamping & committing ───────────────────────────────────────────────\n\n  const clampRange = useCallback(\n    (range: EvilBrushRange, mode?: DragType): EvilBrushRange => {\n      let { startIndex, endIndex } = range;\n      const maxIndex = Math.max(0, totalPoints - 1);\n\n      startIndex = Math.max(0, Math.min(startIndex, maxIndex));\n      endIndex = Math.max(0, Math.min(endIndex, maxIndex));\n\n      if (mode === \"left\") {\n        const maxStart = Math.max(0, endIndex - minSpan);\n        startIndex = Math.min(startIndex, maxStart);\n        return { startIndex, endIndex };\n      }\n\n      if (mode === \"right\") {\n        const minEnd = Math.min(maxIndex, startIndex + minSpan);\n        endIndex = Math.max(endIndex, minEnd);\n        return { startIndex, endIndex };\n      }\n\n      if (endIndex - startIndex < minSpan) {\n        endIndex = Math.min(startIndex + minSpan, maxIndex);\n        if (endIndex - startIndex < minSpan) {\n          startIndex = Math.max(0, endIndex - minSpan);\n        }\n      }\n      return { startIndex, endIndex };\n    },\n    [totalPoints, minSpan],\n  );\n\n  const commit = useCallback(\n    (next: EvilBrushRange, mode?: DragType) => {\n      const clamped = clampRange(next, mode);\n      const last = lastCommittedRef.current;\n\n      // Only update if the range has actually changed — avoids unnecessary\n      // re-renders when the brush is at a boundary and small mouse movements\n      // don't produce index changes\n      if (last.startIndex === clamped.startIndex && last.endIndex === clamped.endIndex) {\n        return;\n      }\n\n      lastCommittedRef.current = clamped;\n      setInternalRange(clamped);\n      // Defer the parent callback — chart re-render happens at lower priority,\n      // React can skip intermediate frames during fast drags\n      React.startTransition(() => {\n        onChange?.(clamped);\n      });\n    },\n    [clampRange, onChange],\n  );\n\n  // ── Drag ────────────────────────────────────────────────────────────────\n\n  const { isDragging, bind } = useBrushDrag({\n    range: internalRange,\n    totalPoints,\n    containerRef,\n    commit,\n  });\n\n  // Position always driven by internalRange (never lags behind controlled props)\n  const range = internalRange;\n\n  // Sync internalRange with controlled props when not dragging\n  useEffect(() => {\n    if (isControlled && !isDragging) {\n      const syncedRange = { startIndex: controlledStart, endIndex: controlledEnd };\n      // eslint-disable-next-line react-hooks/set-state-in-effect\n      setInternalRange(syncedRange);\n      lastCommittedRef.current = syncedRange;\n    }\n  }, [isControlled, controlledStart, controlledEnd, isDragging]);\n\n  // ── Computed positions (%) ──────────────────────────────────────────────\n\n  const leftPct = totalPoints > 1 ? (range.startIndex / (totalPoints - 1)) * 100 : 0;\n  const rightPct = totalPoints > 1 ? (range.endIndex / (totalPoints - 1)) * 100 : 100;\n\n  // Drive all moving brush UI from the same springed edge values.\n  const leftTarget = useMotionValue(leftPct);\n  const rightTarget = useMotionValue(rightPct);\n  if (leftTarget.get() !== leftPct) leftTarget.set(leftPct);\n  if (rightTarget.get() !== rightPct) rightTarget.set(rightPct);\n\n  const leftSpring = useSpring(leftTarget, SPRING_CONFIG);\n  const rightSpring = useSpring(rightTarget, SPRING_CONFIG);\n  const leftPosition = useTransform(leftSpring, (v) => `${v}%`);\n  const rightPosition = useTransform(rightSpring, (v) => `${v}%`);\n  const leftOverlayWidth = useTransform(leftSpring, (v) => `${v}%`);\n  const rightOverlayWidth = useTransform(rightSpring, (v) => `${Math.max(0, 100 - v)}%`);\n  const selectedWidth = useMotionValue(`${Math.max(0, rightPct - leftPct)}%`);\n\n  const updateSelectedWidth = useCallback(() => {\n    selectedWidth.set(`${Math.max(0, rightSpring.get() - leftSpring.get())}%`);\n  }, [leftSpring, rightSpring, selectedWidth]);\n\n  useMotionValueEvent(leftSpring, \"change\", updateSelectedWidth);\n  useMotionValueEvent(rightSpring, \"change\", updateSelectedWidth);\n\n  const getLabel = useCallback(\n    (idx: number) => {\n      if (!xDataKey) return String(idx);\n      const v = data[idx]?.[xDataKey];\n      return formatLabel ? formatLabel(v, idx) : String(v ?? idx);\n    },\n    [data, xDataKey, formatLabel],\n  );\n\n  // ── Render ──────────────────────────────────────────────────────────────\n\n  if (totalPoints === 0) return null;\n\n  return (\n    <div\n      ref={containerRef}\n      data-chart={skipStyle ? undefined : chartId}\n      className={cn(\"group relative select-none\", className)}\n      style={{ height }}\n    >\n      {!skipStyle && <ChartStyle id={chartId} config={chartConfig} />}\n\n      {/* Mini chart – always shows all data */}\n      <div className=\"absolute inset-0 overflow-hidden rounded-md\">\n        <MiniChart\n          data={data}\n          keys={keys}\n          chartConfig={chartConfig}\n          variant={variant}\n          curveType={curveType}\n          chartId={chartId}\n          stacked={stacked}\n          strokeVariant={strokeVariant === \"animated-dashed\" ? \"dashed\" : strokeVariant}\n          connectNulls={connectNulls}\n          barRadius={barRadius}\n        />\n      </div>\n\n      {/* Dim overlay – left */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 left-0 rounded-l-md backdrop-blur-[2px]\"\n        style={{ width: leftOverlayWidth }}\n      />\n      {/* Dim overlay – right */}\n      <motion.div\n        className=\"bg-background/70 pointer-events-none absolute inset-y-0 right-0 rounded-r-md backdrop-blur-[2px]\"\n        style={{ width: rightOverlayWidth }}\n      />\n\n      {/* Selected region – draggable to pan */}\n      <motion.div\n        className=\"absolute inset-y-0 cursor-grab touch-none rounded-sm border active:cursor-grabbing\"\n        style={{ left: leftPosition, width: selectedWidth }}\n        {...bind(\"middle\")}\n      />\n\n      {/* Left handle */}\n      <BrushHandle\n        side=\"left\"\n        position={leftPosition}\n        label={showLabels ? getLabel(range.startIndex) : undefined}\n        bind={bind(\"left\")}\n      />\n\n      {/* Right handle */}\n      <BrushHandle\n        side=\"right\"\n        position={rightPosition}\n        label={showLabels ? getLabel(range.endIndex) : undefined}\n        bind={bind(\"right\")}\n      />\n    </div>\n  );\n}\n\n// ─── Brush Handle ───────────────────────────────────────────────────────────\n\nfunction BrushHandle({\n  side,\n  position,\n  label,\n  bind,\n}: {\n  side: \"left\" | \"right\";\n  position: MotionValue<string>;\n  label?: string;\n  bind: {\n    onPointerDown: (e: React.PointerEvent) => void;\n    onPointerMove: (e: React.PointerEvent) => void;\n    onPointerUp: (e: React.PointerEvent) => void;\n  };\n}) {\n  const isLeft = side === \"left\";\n\n  return (\n    <motion.div className=\"absolute inset-y-0 z-10\" style={{ left: position }}>\n      <div\n        className={cn(\n          \"group absolute inset-y-0 flex w-3 cursor-ew-resize touch-none items-center justify-center after:absolute after:inset-y-0 after:-left-4 after:w-11 after:content-['']\",\n          isLeft ? \"\" : \"-translate-x-full\",\n        )}\n        {...bind}\n      >\n        <div\n          className={cn(\n            \"bg-muted-foreground group-hover:bg-foreground relative flex h-4 w-1.5 items-center justify-center rounded-md transition-colors\",\n            isLeft ? \"-left-[5.5px]\" : \"-right-[5.5px]\",\n          )}\n        >\n          <div className=\"flex flex-col gap-[2px]\">\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n            <div className=\"bg-background/70 h-[2px] w-[2px] rounded-full\" />\n          </div>\n        </div>\n      </div>\n\n      {label && (\n        <div\n          className={cn(\n            \"bg-foreground text-background pointer-events-none absolute -bottom-3 -translate-y-1/2 rounded-[3px] px-1 py-px text-[8px] leading-tight font-medium whitespace-nowrap opacity-0 group-hover:opacity-100\",\n            isLeft ? \"left-1.5\" : \"right-1.5\",\n          )}\n        >\n          {label}\n        </div>\n      )}\n    </motion.div>\n  );\n}\n\n// ─── Mini Chart ─────────────────────────────────────────────────────────────\n\nfunction MiniChart({\n  data,\n  keys,\n  chartConfig,\n  variant,\n  curveType,\n  chartId,\n  stacked,\n  strokeVariant = \"solid\",\n  connectNulls = false,\n  barRadius,\n}: {\n  data: Record<string, unknown>[];\n  keys: string[];\n  chartConfig: ChartConfig;\n  variant: EvilBrushVariant;\n  curveType: CurveType;\n  chartId: string;\n  stacked: boolean;\n  strokeVariant?: \"solid\" | \"dashed\" | \"animated-dashed\";\n  connectNulls?: boolean;\n  barRadius?: number;\n}) {\n  const gradients = React.useMemo(\n    () =>\n      Object.entries(chartConfig)\n        .filter(([key]) => keys.includes(key))\n        .map(([dataKey, config]) => ({\n          dataKey,\n          colorsCount: getColorsCount(config),\n        })),\n    [chartConfig, keys],\n  );\n\n  const dashArray =\n    strokeVariant === \"dashed\" || strokeVariant === \"animated-dashed\" ? \"4 4\" : undefined;\n\n  const defsContent = (\n    <>\n      {/* Vertical fade gradient for area fill mask */}\n      {variant === \"area\" && (\n        <linearGradient id={`${chartId}-zm-vertical-fade`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n          <stop offset=\"0%\" stopColor=\"white\" stopOpacity={0.15} />\n          <stop offset=\"100%\" stopColor=\"white\" stopOpacity={0} />\n        </linearGradient>\n      )}\n      {gradients.map(({ dataKey, colorsCount }) => {\n        const colorStops =\n          colorsCount === 1 ? (\n            <>\n              <stop offset=\"0%\" stopColor={`var(--color-${dataKey}-0)`} />\n              <stop offset=\"100%\" stopColor={`var(--color-${dataKey}-0)`} />\n            </>\n          ) : (\n            Array.from({ length: colorsCount }, (_, i) => (\n              <stop\n                key={i}\n                offset={`${(i / (colorsCount - 1)) * 100}%`}\n                stopColor={`var(--color-${dataKey}-${i}, var(--color-${dataKey}-0))`}\n              />\n            ))\n          );\n\n        return (\n          <React.Fragment key={dataKey}>\n            {/* Vertical color gradient (stroke + bar fill) */}\n            <linearGradient id={`${chartId}-zm-${dataKey}`} x1=\"0\" y1=\"0\" x2=\"0\" y2=\"1\">\n              {colorStops}\n            </linearGradient>\n\n            {/* Area fill: color gradient masked with vertical fade */}\n            {variant === \"area\" && (\n              <>\n                <mask id={`${chartId}-zm-fill-mask-${dataKey}`}>\n                  <rect width=\"100%\" height=\"100%\" fill={`url(#${chartId}-zm-vertical-fade)`} />\n                </mask>\n                <pattern\n                  id={`${chartId}-zm-fill-${dataKey}`}\n                  patternUnits=\"userSpaceOnUse\"\n                  width=\"100%\"\n                  height=\"100%\"\n                >\n                  <rect\n                    width=\"100%\"\n                    height=\"100%\"\n                    fill={`url(#${chartId}-zm-${dataKey})`}\n                    mask={`url(#${chartId}-zm-fill-mask-${dataKey})`}\n                  />\n                </pattern>\n              </>\n            )}\n          </React.Fragment>\n        );\n      })}\n    </>\n  );\n\n  if (variant === \"line\") {\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <LineChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Line\n              key={dk}\n              type={curveType}\n              dataKey={dk}\n              stroke={`url(#${chartId}-zm-${dk})`}\n              strokeWidth={1}\n              strokeOpacity={0.5}\n              strokeDasharray={dashArray}\n              connectNulls={connectNulls}\n              dot={false}\n              activeDot={false}\n              isAnimationActive={false}\n            />\n          ))}\n        </LineChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  if (variant === \"bar\") {\n    const r = barRadius ?? 3;\n    return (\n      <ResponsiveContainer width=\"100%\" height=\"100%\">\n        <BarChart\n          data={data}\n          margin={{ top: 2, right: 0, bottom: 0, left: 0 }}\n          barGap={2}\n          barSize={14}\n        >\n          <defs>{defsContent}</defs>\n          {keys.map((dk) => (\n            <Bar\n              key={dk}\n              dataKey={dk}\n              fill={`url(#${chartId}-zm-${dk})`}\n              fillOpacity={0.35}\n              stackId={stacked ? \"zm-stack\" : undefined}\n              isAnimationActive={false}\n              radius={[r, r, r, r]}\n            />\n          ))}\n        </BarChart>\n      </ResponsiveContainer>\n    );\n  }\n\n  // Default: area\n  return (\n    <ResponsiveContainer width=\"100%\" height=\"100%\">\n      <AreaChart data={data} margin={{ top: 4, right: 0, bottom: 0, left: 0 }}>\n        <defs>{defsContent}</defs>\n        {keys.map((dk) => (\n          <Area\n            key={dk}\n            type={curveType}\n            dataKey={dk}\n            stroke={`url(#${chartId}-zm-${dk})`}\n            fill={`url(#${chartId}-zm-fill-${dk})`}\n            strokeWidth={1}\n            strokeOpacity={0.5}\n            strokeDasharray={dashArray}\n            connectNulls={connectNulls}\n            fillOpacity={1}\n            stackId={stacked ? \"zm-stack\" : undefined}\n            dot={false}\n            activeDot={false}\n            isAnimationActive={false}\n          />\n        ))}\n      </AreaChart>\n    </ResponsiveContainer>\n  );\n}\n\n// ─── useEvilBrush Hook ──────────────────────────────────────────────────────\n\nfunction useEvilBrush<TData extends Record<string, unknown>>({\n  data,\n  defaultStartIndex = 0,\n  defaultEndIndex,\n}: {\n  data: TData[];\n  defaultStartIndex?: number;\n  defaultEndIndex?: number;\n}) {\n  const [range, setRange] = React.useState<EvilBrushRange>({\n    startIndex: defaultStartIndex,\n    endIndex: defaultEndIndex ?? Math.max(0, data.length - 1),\n  });\n\n  // Defer the range used for data slicing — the brush handles move at the\n\n  // immediate `range` cadence while the expensive chart re-render uses the\n  // deferred value.  React can skip intermediate slices during fast drags.\n  const deferredRange = React.useDeferredValue(range);\n\n  useEffect(() => {\n    // eslint-disable-next-line react-hooks/set-state-in-effect\n    setRange({\n      startIndex: 0,\n      endIndex: Math.max(0, data.length - 1),\n    });\n  }, [data.length]);\n\n  const visibleData = React.useMemo(\n    () => data.slice(deferredRange.startIndex, deferredRange.endIndex + 1),\n    [data, deferredRange.startIndex, deferredRange.endIndex],\n  );\n\n  return {\n    range,\n    visibleData,\n    brushProps: {\n      startIndex: range.startIndex,\n      endIndex: range.endIndex,\n      onChange: setRange,\n    } satisfies Pick<EvilBrushProps, \"startIndex\" | \"endIndex\" | \"onChange\">,\n  };\n}\n\nexport { EvilBrush, useEvilBrush, type EvilBrushProps, type EvilBrushRange, type EvilBrushVariant };\n",
      "type": "registry:component",
      "target": "components/evilcharts/ui/recharts-brush.tsx"
    }
  ],
  "type": "registry:component"
}