{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "brush",
  "type": "registry:component",
  "title": "Brush",
  "description": "Time-range brush for zooming area and line charts: a mini-chart strip with a draggable selection and handles (ChartBrush, ChartBrushLayout)",
  "dependencies": [],
  "registryDependencies": [
    "@bklit/chart-context",
    "@bklit/patterns",
    "@bklit/utils"
  ],
  "files": [
    {
      "path": "src/charts/chart-brush.tsx",
      "content": "\"use client\";\n\nimport type React from \"react\";\nimport {\n  Fragment,\n  memo,\n  useCallback,\n  useEffect,\n  useLayoutEffect,\n  useMemo,\n  useRef,\n  useState,\n} from \"react\";\nimport {\n  ChartBrushHandleOverlay,\n  renderChartBrushHandle,\n} from \"./chart-brush-handle\";\nimport {\n  ChartBrushSelectionOverlay,\n  type ChartBrushSelectionPattern,\n} from \"./chart-brush-selection-overlay\";\nimport {\n  type BrushDirection,\n  type BrushDomainBounds,\n  type BrushInitialPosition,\n  type BrushingType,\n  type BrushMoveMode,\n  type BrushPoint,\n  type BrushState,\n  beginBrushDrag,\n  beginBrushSelect,\n  brushExtentToDomain,\n  brushHandleRects,\n  brushSelectionRect,\n  createBrushState,\n  dragBrush,\n  endBrush,\n  idleBrushState,\n} from \"./chart-brush-state\";\nimport {\n  ChartBrushTrackOverlay,\n  type ChartBrushTrackOverlayStyle,\n} from \"./chart-brush-track-overlay\";\nimport { chartCssVars, useChartStable } from \"./chart-context\";\nimport { localPoint } from \"./local-point\";\n\ntype Bounds = BrushDomainBounds;\n\ninterface BrushMargin {\n  top: number;\n  left: number;\n  right: number;\n  bottom: number;\n}\n\nconst ZERO_MARGIN: BrushMargin = { top: 0, left: 0, right: 0, bottom: 0 };\n\nconst DEFAULT_SELECTED_BOX_STYLE: React.SVGProps<SVGRectElement> = {\n  fill: \"transparent\",\n  fillOpacity: 0,\n  stroke: chartCssVars.brushBorder,\n  strokeWidth: 1,\n};\n\n/** `@visx/brush` debounces window move events by 1 ms; kept for parity. */\nconst WINDOW_MOVE_DEBOUNCE_MS = 1;\n\ninterface BrushInteractionProps {\n  xScale: unknown;\n  width: number;\n  height: number;\n  brushDirection: BrushDirection;\n  handleSize: number;\n  initialBrushPosition?: BrushInitialPosition;\n  /** Offset subtracted from the SVG pointer position when a selection starts. */\n  startMargin: BrushMargin;\n  moveMode: BrushMoveMode;\n  selectedBoxStyle: React.SVGProps<SVGRectElement>;\n  onChange: (bounds: Bounds | null) => void;\n  onBrushEnd: (bounds: Bounds | null) => void;\n}\n\ninterface ActiveBrushDrag {\n  pointerId: number;\n  target: Element;\n  pageX: number;\n  pageY: number;\n  /** SVG-space pointer position at pointer down. */\n  local: BrushPoint;\n}\n\nfunction scaleInverter(scale: unknown): (value: number) => unknown {\n  const invert = (scale as { invert?: (value: number) => unknown } | null)\n    ?.invert;\n  // Without `invert` visx reports `{ x0: 0, x1: 0 }` (NaN -> `|| 0`).\n  return typeof invert === \"function\"\n    ? (value) => invert.call(scale, value)\n    : () => Number.NaN;\n}\n\nfunction toDomain(state: BrushState, scale: unknown): Bounds | null {\n  return brushExtentToDomain(state.extent, scaleInverter(scale));\n}\n\n/**\n * Hand-written replacement for `@visx/brush` `Brush`: one transparent hit rect\n * for the track (draw a new selection, or move the current one), the visible\n * selection rect, and the two edge handles. Pointer capture replaces visx's\n * window listeners; `chart-brush-state.ts` holds the parity rules.\n */\nconst ChartBrushInteraction = memo(function ChartBrushInteraction({\n  xScale,\n  width,\n  height,\n  brushDirection,\n  handleSize,\n  initialBrushPosition,\n  startMargin,\n  moveMode,\n  selectedBoxStyle,\n  onChange,\n  onBrushEnd,\n}: BrushInteractionProps) {\n  const stage = useMemo(\n    () => ({ width, height, direction: brushDirection }),\n    [width, height, brushDirection]\n  );\n  const [state, setState] = useState(() =>\n    createBrushState(stage, initialBrushPosition)\n  );\n  const stateRef = useRef(state);\n  const dragRef = useRef<ActiveBrushDrag | null>(null);\n  const moveTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n  const pendingMoveRef = useRef<{ pageX: number; pageY: number } | null>(null);\n\n  // Latest props for the debounced window-move timer.\n  const latestRef = useRef({ stage, xScale, onChange });\n  useLayoutEffect(() => {\n    latestRef.current = { stage, xScale, onChange };\n  });\n\n  useEffect(\n    () => () => {\n      if (moveTimerRef.current) {\n        clearTimeout(moveTimerRef.current);\n      }\n    },\n    []\n  );\n\n  const commitState = useCallback((next: BrushState) => {\n    stateRef.current = next;\n    setState(next);\n  }, []);\n\n  const startDrag = useCallback(\n    (\n      event: React.PointerEvent<Element>,\n      next: BrushState,\n      local: BrushPoint\n    ) => {\n      const target = event.target as Element;\n      try {\n        target.setPointerCapture(event.pointerId);\n      } catch {\n        // Pointer already gone (synthetic event); moves still bubble here.\n      }\n      dragRef.current = {\n        pointerId: event.pointerId,\n        target,\n        pageX: event.pageX,\n        pageY: event.pageY,\n        local,\n      };\n      commitState(next);\n      onChange(toDomain(next, xScale));\n    },\n    [commitState, onChange, xScale]\n  );\n\n  // Which drag starts is decided by DOM hit-testing, as in visx: the overlay\n  // draws a new selection, the selection rect moves it, a handle resizes.\n  const handleOverlayPointerDown = useCallback(\n    (event: React.PointerEvent<SVGRectElement>) => {\n      if (dragRef.current) {\n        return;\n      }\n      const point = localPoint(event) ?? { x: 0, y: 0 };\n      const next = beginBrushSelect(stateRef.current, {\n        x: point.x - startMargin.left,\n        y: point.y - startMargin.top,\n      });\n      startDrag(event, next, point);\n    },\n    [startDrag, startMargin.left, startMargin.top]\n  );\n\n  const handleDragPointerDown = useCallback(\n    (\n      type: Exclude<BrushingType, \"select\">,\n      event: React.PointerEvent<Element>\n    ) => {\n      if (dragRef.current) {\n        return;\n      }\n      const point = localPoint(event) ?? { x: 0, y: 0 };\n      startDrag(event, beginBrushDrag(stateRef.current, type), point);\n    },\n    [startDrag]\n  );\n\n  const applyOffset = useCallback(\n    (offset: BrushPoint) => {\n      const latest = latestRef.current;\n      const current = stateRef.current;\n      if (!current.isBrushing) {\n        return;\n      }\n      const next = dragBrush(current, offset, latest.stage, moveMode);\n      commitState(next);\n      latest.onChange(toDomain(next, latest.xScale));\n    },\n    [commitState, moveMode]\n  );\n\n  const handlePointerMove = useCallback(\n    (event: React.PointerEvent<Element>) => {\n      const drag = dragRef.current;\n      if (!drag || event.pointerId !== drag.pointerId) {\n        return;\n      }\n      if (moveMode === \"local\") {\n        const point = localPoint(event) ?? { x: 0, y: 0 };\n        applyOffset({ x: point.x - drag.local.x, y: point.y - drag.local.y });\n        return;\n      }\n      pendingMoveRef.current = { pageX: event.pageX, pageY: event.pageY };\n      if (moveTimerRef.current) {\n        clearTimeout(moveTimerRef.current);\n      }\n      moveTimerRef.current = setTimeout(() => {\n        moveTimerRef.current = null;\n        const pending = pendingMoveRef.current;\n        const active = dragRef.current;\n        if (!(pending && active)) {\n          return;\n        }\n        applyOffset({\n          x: pending.pageX - active.pageX,\n          y: pending.pageY - active.pageY,\n        });\n      }, WINDOW_MOVE_DEBOUNCE_MS);\n    },\n    [applyOffset, moveMode]\n  );\n\n  const handlePointerEnd = useCallback(\n    (event: React.PointerEvent<Element>) => {\n      const drag = dragRef.current;\n      if (!drag || event.pointerId !== drag.pointerId) {\n        return;\n      }\n      // Like visx, a window move still waiting on its debounce is dropped.\n      if (moveTimerRef.current) {\n        clearTimeout(moveTimerRef.current);\n        moveTimerRef.current = null;\n      }\n      const result = endBrush(stateRef.current, moveMode);\n      if (result) {\n        commitState(result.state);\n      }\n      dragRef.current = null;\n      try {\n        if (drag.target.hasPointerCapture(drag.pointerId)) {\n          drag.target.releasePointerCapture(drag.pointerId);\n        }\n      } catch {\n        // Element already detached.\n      }\n      if (!result) {\n        return;\n      }\n      onBrushEnd(toDomain(result.state, xScale));\n      for (let i = 0; i < result.changeCount; i++) {\n        onChange(toDomain(result.state, xScale));\n      }\n    },\n    [commitState, moveMode, onBrushEnd, onChange, xScale]\n  );\n\n  // visx resets on a double-click of the overlay (not the selection).\n  const handleOverlayDoubleClick = useCallback(() => {\n    const next = idleBrushState();\n    commitState(next);\n    onChange(toDomain(next, xScale));\n  }, [commitState, onChange, xScale]);\n\n  const selection = brushSelectionRect(state.extent);\n  const isBrushActive = state.extent.x0 !== -1 && state.extent.x1 !== -1;\n  const { brushingType } = state;\n  const selectionLocked = state.isBrushing || state.activeHandle !== null;\n  const fill = \"transparent\";\n\n  // Same tree as visx 4.0.0 BaseBrush (Group > overlay Drag, BrushSelection,\n  // right / left BrushHandle), including the transparent \"dragging\" rects\n  // each @visx/drag <Drag> adds while its drag is in progress. Moves and\n  // pointer-up reach the group through pointer capture on the pressed target.\n  return (\n    <g\n      className=\"visx-group visx-brush\"\n      onLostPointerCapture={handlePointerEnd}\n      onPointerCancel={handlePointerEnd}\n      onPointerMove={handlePointerMove}\n      onPointerUp={handlePointerEnd}\n      transform=\"translate(0, 0)\"\n    >\n      {/* Attribute objects keep visx's attribute order in the DOM. */}\n      {brushingType === \"select\" ? <rect {...{ width, height, fill }} /> : null}\n      <rect\n        {...{\n          className: \"visx-bar visx-brush-overlay\",\n          fill,\n          x: 0,\n          y: 0,\n          width,\n          height,\n          onDoubleClick: handleOverlayDoubleClick,\n          onPointerDown: handleOverlayPointerDown,\n          style: { cursor: \"crosshair\" },\n        }}\n      />\n      {brushingType === \"move\" ? (\n        <rect {...{ width: selection.width, height: selection.height, fill }} />\n      ) : null}\n      <g>\n        {brushingType === \"move\" ? (\n          <rect {...{ width, height, fill, style: { cursor: \"move\" } }} />\n        ) : null}\n        <rect\n          {...{\n            ...selection,\n            className: \"visx-brush-selection\",\n            onPointerDown: (event: React.PointerEvent<SVGRectElement>) =>\n              handleDragPointerDown(\"move\", event),\n            style: {\n              pointerEvents: selectionLocked ? \"none\" : \"all\",\n              cursor: \"move\",\n            },\n            ...selectedBoxStyle,\n          }}\n        />\n      </g>\n      {brushHandleRects(state.extent, handleSize, height).map((handle) => (\n        <Fragment key={`handle-${handle.type}`}>\n          {brushingType === handle.type ? (\n            <rect {...{ width, height, fill }} />\n          ) : null}\n          <g>\n            {brushingType === handle.type ? (\n              <rect\n                {...{ fill, width, height, style: { cursor: \"ew-resize\" } }}\n              />\n            ) : null}\n            <g\n              onPointerDown={(event) =>\n                handleDragPointerDown(handle.type, event)\n              }\n            >\n              {renderChartBrushHandle({\n                x: handle.x,\n                y: handle.y,\n                width: handle.width,\n                height: handle.height,\n                className: `visx-brush-handle-${handle.type}`,\n                isBrushActive,\n              })}\n            </g>\n          </g>\n        </Fragment>\n      ))}\n    </g>\n  );\n});\n\nexport interface ChartBrushSelection {\n  start: Date;\n  end: Date;\n}\n\nexport interface ChartBrushProps {\n  /** Callback when brush selection ends. Domain is the selected time range. */\n  onSelectionChange?: (domain: ChartBrushSelection | null) => void;\n  /** Brush direction. Default: \"horizontal\" for time range selection. */\n  brushDirection?: \"horizontal\" | \"vertical\" | \"both\";\n  /** Fill style for the selected region. Default: chart segment style. */\n  selectedBoxStyle?: React.SVGProps<SVGRectElement>;\n  /** Initial selection so the brush overlay is visible on load (e.g. first 50% of range). */\n  initialSelection?: ChartBrushSelection | null;\n  /** Current selection (e.g. from parent state). When set, a visible selection rect is drawn. */\n  selection?: ChartBrushSelection | null;\n  /** Use window move events for brush (can fix coordinate offset when SVG is in transformed container). Default: true for brush-in-strip. */\n  useWindowMoveEvents?: boolean;\n  /** Backdrop blur on dimmed track (0–5 px). Default: 1.5 */\n  blurPx?: number;\n  /** Fade dimmed regions at the outer track edges. Default: true */\n  fadeOuterEdges?: boolean;\n  /** Optional pattern overlay inside the brush selection window. */\n  selectionPattern?: ChartBrushSelectionPattern;\n}\n\ninterface ChartBrushInnerProps extends ChartBrushTrackOverlayStyle {\n  brushDirection?: ChartBrushProps[\"brushDirection\"];\n  selectedBoxStyle?: ChartBrushProps[\"selectedBoxStyle\"];\n  initialSelection?: ChartBrushProps[\"initialSelection\"];\n  useWindowMoveEvents?: ChartBrushProps[\"useWindowMoveEvents\"];\n  xScale: ReturnType<typeof useChartStable>[\"xScale\"];\n  innerWidth: number;\n  innerHeight: number;\n  margin: ReturnType<typeof useChartStable>[\"margin\"];\n  onBrushPreview?: (bounds: Bounds | null) => void;\n  onBrushCommit: (bounds: Bounds | null) => void;\n  selectionPattern?: ChartBrushSelectionPattern;\n}\n\nfunction toDate(value: number | Date | unknown): Date {\n  if (value instanceof Date) {\n    return value;\n  }\n  if (typeof value === \"number\") {\n    return new Date(value);\n  }\n  return new Date(Number(value));\n}\n\nfunction boundsToPixelExtent(\n  bounds: Bounds | null,\n  xScale: ReturnType<typeof useChartStable>[\"xScale\"],\n  innerWidth: number\n): { x0: number; x1: number } | null {\n  if (\n    !bounds ||\n    typeof bounds.x0 === \"undefined\" ||\n    typeof bounds.x1 === \"undefined\"\n  ) {\n    return null;\n  }\n\n  const start = toDate(bounds.x0);\n  const end = toDate(bounds.x1);\n  const xScaleFn = xScale as (d: Date) => number;\n  const x0 = Math.max(0, xScaleFn(start < end ? start : end) ?? 0);\n  const x1 = Math.min(\n    innerWidth,\n    xScaleFn(end > start ? end : start) ?? innerWidth\n  );\n\n  if (x1 <= x0) {\n    return null;\n  }\n\n  return { x0, x1 };\n}\n\nconst ChartBrushInner = memo(function ChartBrushInner({\n  brushDirection = \"horizontal\",\n  selectedBoxStyle,\n  initialSelection,\n  useWindowMoveEvents = true,\n  xScale,\n  innerWidth,\n  innerHeight,\n  margin,\n  onBrushPreview,\n  onBrushCommit,\n  blurPx,\n  fadeOuterEdges,\n  selectionPattern,\n}: ChartBrushInnerProps) {\n  const initialBrushPosition = useMemo(() => {\n    if (!initialSelection || innerWidth <= 0 || innerHeight <= 0) {\n      return undefined;\n    }\n    const xScaleFn = xScale as (d: Date) => number;\n    const x0 = Math.max(0, xScaleFn(initialSelection.start) ?? 0);\n    const x1 = Math.min(\n      innerWidth,\n      xScaleFn(initialSelection.end) ?? innerWidth\n    );\n    if (x1 <= x0) {\n      return undefined;\n    }\n    return {\n      start: { x: x0, y: 0 },\n      end: { x: x1, y: innerHeight },\n    };\n  }, [initialSelection, xScale, innerWidth, innerHeight]);\n\n  const [pixelExtent, setPixelExtent] = useState(() => ({\n    x0: initialBrushPosition?.start.x ?? 0,\n    x1: initialBrushPosition?.end.x ?? innerWidth,\n  }));\n\n  // Reset the extent when the initial selection / width change — adjusted\n  // during render (\"store previous props in state\"), not setState in an effect.\n  const [extentSource, setExtentSource] = useState({\n    initialBrushPosition,\n    innerWidth,\n  });\n  if (\n    extentSource.initialBrushPosition !== initialBrushPosition ||\n    extentSource.innerWidth !== innerWidth\n  ) {\n    setExtentSource({ initialBrushPosition, innerWidth });\n    setPixelExtent({\n      x0: initialBrushPosition?.start.x ?? 0,\n      x1: initialBrushPosition?.end.x ?? innerWidth,\n    });\n  }\n\n  const updatePixelExtent = useCallback(\n    (bounds: Bounds | null) => {\n      const pixels = boundsToPixelExtent(bounds, xScale, innerWidth);\n      if (pixels) {\n        setPixelExtent(pixels);\n      }\n    },\n    [innerWidth, xScale]\n  );\n\n  const handleBrushPreview = useCallback(\n    (bounds: Bounds | null) => {\n      updatePixelExtent(bounds);\n      onBrushPreview?.(bounds);\n    },\n    [onBrushPreview, updatePixelExtent]\n  );\n\n  const handleBrushCommit = useCallback(\n    (bounds: Bounds | null) => {\n      updatePixelExtent(bounds);\n      onBrushCommit(bounds);\n    },\n    [onBrushCommit, updatePixelExtent]\n  );\n\n  return (\n    <g className=\"chart-brush\">\n      <ChartBrushTrackOverlay\n        blurPx={blurPx}\n        fadeOuterEdges={fadeOuterEdges}\n        innerHeight={innerHeight}\n        innerWidth={innerWidth}\n        selectionX0={pixelExtent.x0}\n        selectionX1={pixelExtent.x1}\n      />\n      <ChartBrushSelectionOverlay\n        innerHeight={innerHeight}\n        innerWidth={innerWidth}\n        pattern={selectionPattern}\n        selectionX0={pixelExtent.x0}\n        selectionX1={pixelExtent.x1}\n      />\n      <ChartBrushHandleOverlay\n        innerHeight={innerHeight}\n        innerWidth={innerWidth}\n        selectionX0={pixelExtent.x0}\n        selectionX1={pixelExtent.x1}\n      />\n      <ChartBrushInteraction\n        brushDirection={brushDirection}\n        handleSize={8}\n        height={innerHeight}\n        initialBrushPosition={initialBrushPosition}\n        key={`brush-${innerWidth}-${innerHeight}`}\n        moveMode={useWindowMoveEvents ? \"window\" : \"local\"}\n        onBrushEnd={handleBrushCommit}\n        onChange={handleBrushPreview}\n        selectedBoxStyle={selectedBoxStyle ?? DEFAULT_SELECTED_BOX_STYLE}\n        startMargin={useWindowMoveEvents ? margin : ZERO_MARGIN}\n        width={innerWidth}\n        xScale={xScale}\n      />\n    </g>\n  );\n});\n\nexport function ChartBrush({\n  onSelectionChange,\n  brushDirection = \"horizontal\",\n  selectedBoxStyle,\n  initialSelection,\n  selection: _selection,\n  useWindowMoveEvents = true,\n  blurPx,\n  fadeOuterEdges,\n  selectionPattern,\n}: ChartBrushProps) {\n  const { xScale, innerWidth, innerHeight, margin, isLoaded } =\n    useChartStable();\n\n  const boundsToSelection = useCallback(\n    (bounds: Bounds | null): ChartBrushSelection | null => {\n      if (\n        !bounds ||\n        typeof bounds.x0 === \"undefined\" ||\n        typeof bounds.x1 === \"undefined\"\n      ) {\n        return null;\n      }\n      const start = toDate(bounds.x0);\n      const end = toDate(bounds.x1);\n      if (start.getTime() === end.getTime()) {\n        return null;\n      }\n      return {\n        start: start < end ? start : end,\n        end: end > start ? end : start,\n      };\n    },\n    []\n  );\n\n  const notifySelectionChange = useCallback(\n    (bounds: Bounds | null) => {\n      if (!onSelectionChange) {\n        return;\n      }\n      onSelectionChange(boundsToSelection(bounds));\n    },\n    [onSelectionChange, boundsToSelection]\n  );\n\n  const handleBrushPreview = useCallback(\n    (bounds: Bounds | null) => {\n      notifySelectionChange(bounds);\n    },\n    [notifySelectionChange]\n  );\n\n  const handleBrushCommit = useCallback(\n    (bounds: Bounds | null) => {\n      notifySelectionChange(bounds);\n    },\n    [notifySelectionChange]\n  );\n\n  if (!isLoaded || innerWidth <= 0 || innerHeight <= 0) {\n    return null;\n  }\n\n  return (\n    <ChartBrushInner\n      blurPx={blurPx}\n      brushDirection={brushDirection}\n      fadeOuterEdges={fadeOuterEdges}\n      initialSelection={initialSelection}\n      innerHeight={innerHeight}\n      innerWidth={innerWidth}\n      margin={margin}\n      onBrushCommit={handleBrushCommit}\n      onBrushPreview={handleBrushPreview}\n      selectedBoxStyle={selectedBoxStyle}\n      selectionPattern={selectionPattern}\n      useWindowMoveEvents={useWindowMoveEvents}\n      xScale={xScale}\n    />\n  );\n}\n\nChartBrush.displayName = \"ChartBrush\";\n\nexport default ChartBrush;\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush.tsx"
    },
    {
      "path": "src/charts/chart-brush-handle.tsx",
      "content": "\"use client\";\n\nimport { createPortal } from \"react-dom\";\nimport type { ChartBrushOverlayHost } from \"./chart-brush-track-overlay\";\nimport { useChartPortalContainer, useChartStable } from \"./chart-context\";\n\n/** Matches shadcn `ResizableHandle` withHandle pill (`h-6 w-1 rounded-lg`). */\nconst HANDLE_HEIGHT_PX = 24;\nconst HANDLE_WIDTH_PX = 4;\n\n/** Props passed to a brush handle renderer (same shape as visx `BrushHandleRenderProps`). */\nexport interface BrushHandleRenderProps {\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n  /** `visx-brush-handle-left` / `visx-brush-handle-right`. */\n  className: string;\n  /** False while the brush is empty (extent -1). */\n  isBrushActive: boolean;\n}\n\n/**\n * Invisible hit target for a brush resize handle.\n * Visible pills render in `ChartBrushHandleOverlay` above the blur layer.\n */\nfunction brushHandleCursor(className: string) {\n  return className.includes(\"left\") || className.includes(\"right\")\n    ? \"ew-resize\"\n    : \"ns-resize\";\n}\n\nexport function renderChartBrushHandle({\n  x,\n  y,\n  width,\n  height,\n  className,\n}: BrushHandleRenderProps) {\n  return (\n    <rect\n      className={className}\n      fill=\"transparent\"\n      height={height}\n      style={{ cursor: brushHandleCursor(className) }}\n      width={width}\n      x={x}\n      y={y}\n    />\n  );\n}\n\nexport interface ChartBrushHandleOverlayProps {\n  innerWidth: number;\n  innerHeight: number;\n  /** Pixel x of the selection start within the plot area. */\n  selectionX0: number;\n  /** Pixel x of the selection end within the plot area. */\n  selectionX1: number;\n}\n\n/** Renders brush resize pills above the outside blur portal (`z-[2]`). */\nexport function ChartBrushHandleOverlayContent({\n  containerRef,\n  margin,\n  innerWidth,\n  innerHeight,\n  selectionX0,\n  selectionX1,\n}: ChartBrushHandleOverlayProps & ChartBrushOverlayHost) {\n  const container = useChartPortalContainer(containerRef);\n  if (!container) {\n    return null;\n  }\n\n  const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));\n  const x1 = Math.max(\n    x0,\n    Math.min(Math.max(selectionX0, selectionX1), innerWidth)\n  );\n  const edges = x0 === x1 ? [x0] : [x0, x1];\n\n  const plotLeft = margin.left;\n  const plotTop = margin.top;\n  const handleTop = plotTop + (innerHeight - HANDLE_HEIGHT_PX) / 2;\n\n  return createPortal(\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute inset-0 z-[2]\"\n    >\n      {edges.map((edgeX) => (\n        <div\n          className=\"absolute shrink-0 rounded-lg\"\n          key={edgeX}\n          style={{\n            top: handleTop,\n            left: plotLeft + edgeX - HANDLE_WIDTH_PX / 2,\n            width: HANDLE_WIDTH_PX,\n            height: HANDLE_HEIGHT_PX,\n            backgroundColor: \"var(--chart-brush-border)\",\n          }}\n        />\n      ))}\n    </div>,\n    container\n  );\n}\n\nexport function ChartBrushHandleOverlay(props: ChartBrushHandleOverlayProps) {\n  const { containerRef, margin } = useChartStable();\n\n  return (\n    <ChartBrushHandleOverlayContent\n      {...props}\n      containerRef={containerRef}\n      margin={margin}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush-handle.tsx"
    },
    {
      "path": "src/charts/chart-brush-layout.tsx",
      "content": "\"use client\";\n\nimport {\n  memo,\n  type ReactNode,\n  useCallback,\n  useEffect,\n  useMemo,\n  useState,\n} from \"react\";\nimport { cn } from \"@/lib/utils\";\nimport type { ChartBrushSelection } from \"./chart-brush\";\nimport { resolveBrushTrackXExtent } from \"./filter-data-by-x-domain\";\n\nexport interface ChartBrushLayoutState {\n  xDomain: [Date, Date] | undefined;\n  xDomainSlotCount: number | undefined;\n  brushSelection: ChartBrushSelection | null;\n  onBrushSelectionChange: (selection: ChartBrushSelection | null) => void;\n}\n\nexport interface ChartBrushLayoutProps {\n  /** Full dataset backing the brush strip and main chart. */\n  data: Record<string, unknown>[];\n  xDataKey?: string;\n  /** Extends the brush track max beyond the last data row (e.g. projection horizon). */\n  xExtentMax?: Date;\n  /** When false, children render without brush zoom state. */\n  enabled: boolean;\n  /** Fixed height of the brush strip in pixels. */\n  height: number;\n  /** When true, the main chart area hugs content instead of growing to fill space. */\n  fitMainContent?: boolean;\n  className?: string;\n  children: (layout: ChartBrushLayoutState) => ReactNode;\n  /** Mini chart + brush handles rendered below the main chart when `enabled`. */\n  brushStrip?: (layout: ChartBrushLayoutState) => ReactNode;\n}\n\nfunction createXAccessor(xDataKey: string) {\n  return (d: Record<string, unknown>): Date => {\n    const value = d[xDataKey];\n    return value instanceof Date ? value : new Date(value as string | number);\n  };\n}\n\nexport const ChartBrushLayout = memo(function ChartBrushLayout({\n  data,\n  xDataKey = \"date\",\n  xExtentMax,\n  enabled,\n  height,\n  fitMainContent = false,\n  className,\n  children,\n  brushStrip,\n}: ChartBrushLayoutProps) {\n  const xAccessor = useMemo(() => createXAccessor(xDataKey), [xDataKey]);\n  const fullExtent = useMemo(\n    () => resolveBrushTrackXExtent(data, xAccessor, xExtentMax),\n    [data, xAccessor, xExtentMax]\n  );\n  const [brushSelection, setBrushSelection] =\n    useState<ChartBrushSelection | null>(null);\n\n  useEffect(() => {\n    if (!fullExtent) {\n      setBrushSelection(null);\n      return;\n    }\n    setBrushSelection({ start: fullExtent[0], end: fullExtent[1] });\n  }, [fullExtent]);\n\n  const handleBrushSelectionChange = useCallback(\n    (selection: ChartBrushSelection | null) => {\n      if (!selection) {\n        if (fullExtent) {\n          setBrushSelection({ start: fullExtent[0], end: fullExtent[1] });\n        }\n        return;\n      }\n      setBrushSelection(selection);\n    },\n    [fullExtent]\n  );\n\n  const layoutState = useMemo<ChartBrushLayoutState>(\n    () => ({\n      xDomain:\n        enabled && brushSelection\n          ? ([brushSelection.start, brushSelection.end] as [Date, Date])\n          : undefined,\n      xDomainSlotCount: enabled ? data.length : undefined,\n      brushSelection,\n      onBrushSelectionChange: handleBrushSelectionChange,\n    }),\n    [brushSelection, data.length, enabled, handleBrushSelectionChange]\n  );\n\n  return (\n    <div\n      className={cn(\n        \"flex size-full min-h-0 min-w-0 flex-col\",\n        fitMainContent ? \"justify-start gap-1\" : \"gap-3\",\n        className\n      )}\n    >\n      <div\n        className={cn(\n          \"min-h-0 min-w-0\",\n          fitMainContent ? \"shrink-0\" : \"flex-1\"\n        )}\n      >\n        {children(layoutState)}\n      </div>\n      {enabled && brushStrip ? (\n        <div className=\"min-h-0 shrink-0\" style={{ height }}>\n          {brushStrip(layoutState)}\n        </div>\n      ) : null}\n    </div>\n  );\n});\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush-layout.tsx"
    },
    {
      "path": "src/charts/chart-brush-selection-overlay.tsx",
      "content": "\"use client\";\n\nimport { useId } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { ChartBrushOverlayHost } from \"./chart-brush-track-overlay\";\nimport { useChartPortalContainer, useChartStable } from \"./chart-context\";\nimport {\n  type PatternPresetId,\n  type PatternPresetOptions,\n  renderPatternPreset,\n} from \"./pattern-preset\";\n\nexport type ChartBrushPatternPreset = PatternPresetId;\n\nexport interface ChartBrushSelectionPattern extends PatternPresetOptions {\n  preset: ChartBrushPatternPreset;\n  color: string;\n  opacity?: number;\n}\n\nexport interface ChartBrushSelectionOverlayProps {\n  innerWidth: number;\n  innerHeight: number;\n  selectionX0: number;\n  selectionX1: number;\n  pattern?: ChartBrushSelectionPattern;\n}\n\n/** Pattern fill between brush handles (`z-[1]`, above blur panes). */\nexport function ChartBrushSelectionOverlayContent({\n  containerRef,\n  margin,\n  innerWidth,\n  innerHeight,\n  selectionX0,\n  selectionX1,\n  pattern,\n}: ChartBrushSelectionOverlayProps & ChartBrushOverlayHost) {\n  const patternId = useId().replace(/:/g, \"\");\n\n  const container = useChartPortalContainer(containerRef);\n  if (!(container && pattern && pattern.preset !== \"none\")) {\n    return null;\n  }\n\n  const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));\n  const x1 = Math.max(\n    x0,\n    Math.min(Math.max(selectionX0, selectionX1), innerWidth)\n  );\n  const selectionWidth = x1 - x0;\n  if (selectionWidth <= 0) {\n    return null;\n  }\n\n  const plotLeft = margin.left;\n  const plotTop = margin.top;\n  const patternNode = renderPatternPreset(pattern.preset, patternId, {\n    color: pattern.color,\n    scale: pattern.scale,\n    strokeWidth: pattern.strokeWidth,\n    radius: pattern.radius,\n    complement: pattern.complement,\n    fill: pattern.fill,\n    tileBackground: pattern.tileBackground,\n  });\n  if (!patternNode) {\n    return null;\n  }\n\n  return createPortal(\n    <svg\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute inset-0 z-[1]\"\n      height=\"100%\"\n      width=\"100%\"\n    >\n      <defs>{patternNode}</defs>\n      <rect\n        fill={`url(#${patternId})`}\n        fillOpacity={pattern.opacity ?? 1}\n        height={innerHeight}\n        width={selectionWidth}\n        x={plotLeft + x0}\n        y={plotTop}\n      />\n    </svg>,\n    container\n  );\n}\n\nexport function ChartBrushSelectionOverlay(\n  props: ChartBrushSelectionOverlayProps\n) {\n  const { containerRef, margin } = useChartStable();\n\n  return (\n    <ChartBrushSelectionOverlayContent\n      {...props}\n      containerRef={containerRef}\n      margin={margin}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush-selection-overlay.tsx"
    },
    {
      "path": "src/charts/chart-brush-state.ts",
      "content": "/**\n * Pure state machine behind `ChartBrush`, reproducing `@visx/brush` 4.0.0\n * (`Brush` + `BaseBrush` + `BrushSelection` + `BrushHandle` + `@visx/drag`)\n * step for step, so the emitted selections stay identical:\n *\n * - Pixel state is `start` / `end` (committed corners) plus `extent` (what is\n *   drawn). An empty brush has `extent` -1 on every side.\n * - Pointer down on the track starts a new selection at the pointer\n *   (`extent` -1, so the brush emits `null`); down on the selection moves it;\n *   down on a handle resizes from that edge. Each start emits `onChange`.\n * - Moves are offsets from the pointer-down position. With window move events\n *   (the bklit default) the offset is in page pixels and every mode is clamped\n *   to the track; without, it is in SVG units and only `move` / the right\n *   handle are clamped (visx behaviour).\n * - Pointer up commits: `onBrushEnd` then `onChange` (local mode emits one\n *   more `onChange` for `move` / handle drags). A click without a drag on the\n *   track commits `null`.\n * - Pixels convert to the domain through `scale.invert`, widened by visx's\n *   `SAFE_PIXEL` (2 px) on each side; a zero-width extent inverts to\n *   start === end, which `ChartBrush` reports as `null`.\n */\n\nexport type BrushDirection = \"horizontal\" | \"vertical\" | \"both\";\n\n/** What a pointer drag does: draw a new selection, move it, or resize it. */\nexport type BrushingType = \"select\" | \"move\" | \"left\" | \"right\";\n\nexport type BrushHandleType = \"left\" | \"right\";\n\nexport interface BrushPoint {\n  x: number;\n  y: number;\n}\n\nexport interface BrushExtent {\n  x0: number;\n  x1: number;\n  y0: number;\n  y1: number;\n}\n\nexport interface BrushStage {\n  width: number;\n  height: number;\n  direction: BrushDirection;\n}\n\nexport interface BrushState {\n  start: BrushPoint;\n  end: BrushPoint;\n  extent: BrushExtent;\n  isBrushing: boolean;\n  brushingType: BrushingType | undefined;\n  /** Only set by local-mode handle drags (visx `activeHandle`). */\n  activeHandle: BrushHandleType | null;\n}\n\n/**\n * `true` = window move events (page-pixel offsets, everything clamped to the\n * track); `false` = local pointer events on the brush (SVG-unit offsets).\n */\nexport type BrushMoveMode = \"window\" | \"local\";\n\n/** visx `SAFE_PIXEL`: the selection is widened by this many px before inverting. */\nexport const BRUSH_SAFE_PIXEL = 2;\n\nconst EMPTY_EXTENT: BrushExtent = { x0: -1, x1: -1, y0: -1, y1: -1 };\n\nfunction clamp(value: number, min: number, max: number): number {\n  return Math.min(Math.max(value, min), max);\n}\n\n/** visx `BaseBrush.getExtent`: normalise two corners for the brush direction. */\nexport function getBrushExtent(\n  start: Partial<BrushPoint>,\n  end: Partial<BrushPoint>,\n  stage: BrushStage\n): BrushExtent {\n  const { direction, width, height } = stage;\n  return {\n    x0: direction === \"vertical\" ? 0 : Math.min(start.x || 0, end.x || 0),\n    x1: direction === \"vertical\" ? width : Math.max(start.x || 0, end.x || 0),\n    y0: direction === \"horizontal\" ? 0 : Math.min(start.y || 0, end.y || 0),\n    y1:\n      direction === \"horizontal\" ? height : Math.max(start.y || 0, end.y || 0),\n  };\n}\n\nexport interface BrushInitialPosition {\n  start?: { x?: number; y?: number };\n  end?: { x?: number; y?: number };\n}\n\n/** visx `BaseBrush` constructor state (`initialBrushPosition` in pixels). */\nexport function createBrushState(\n  stage: BrushStage,\n  initial?: BrushInitialPosition | null\n): BrushState {\n  const extent = initial\n    ? getBrushExtent(initial.start ?? {}, initial.end ?? {}, stage)\n    : { ...EMPTY_EXTENT };\n  return {\n    start: { x: Math.max(0, extent.x0), y: Math.max(0, extent.y0) },\n    end: { x: Math.max(0, extent.x1), y: Math.max(0, extent.y1) },\n    extent,\n    isBrushing: false,\n    brushingType: undefined,\n    activeHandle: null,\n  };\n}\n\n/** visx `BaseBrush.getIdleState` (double-click on the track). */\nexport function idleBrushState(): BrushState {\n  return {\n    start: { x: 0, y: 0 },\n    end: { x: 0, y: 0 },\n    extent: { ...EMPTY_EXTENT },\n    isBrushing: false,\n    brushingType: undefined,\n    activeHandle: null,\n  };\n}\n\n/** Pointer down on the track: start a new selection at `point` (plot px). */\nexport function beginBrushSelect(\n  state: BrushState,\n  point: BrushPoint\n): BrushState {\n  return {\n    ...state,\n    start: point,\n    end: { ...point },\n    extent: { ...EMPTY_EXTENT },\n    isBrushing: true,\n    brushingType: \"select\",\n  };\n}\n\n/** Pointer down on the selection (`move`) or a handle. */\nexport function beginBrushDrag(\n  state: BrushState,\n  type: Exclude<BrushingType, \"select\">\n): BrushState {\n  return { ...state, brushingType: type, isBrushing: true };\n}\n\nfunction moveSelection(\n  state: BrushState,\n  offset: BrushPoint,\n  stage: BrushStage\n): BrushState {\n  const { start, end } = state;\n  const validDx =\n    offset.x > 0\n      ? Math.min(offset.x, stage.width - end.x)\n      : Math.max(offset.x, 0 - start.x);\n  const validDy =\n    offset.y > 0\n      ? Math.min(offset.y, stage.height - end.y)\n      : Math.max(offset.y, 0 - start.y);\n  return {\n    ...state,\n    isBrushing: true,\n    extent: {\n      ...state.extent,\n      x0: start.x + validDx,\n      x1: end.x + validDx,\n      y0: start.y + validDy,\n      y1: end.y + validDy,\n    },\n  };\n}\n\n/**\n * Pointer moved by `offset` since pointer down. Returns `state` unchanged when\n * no drag is in progress.\n */\nexport function dragBrush(\n  state: BrushState,\n  offset: BrushPoint,\n  stage: BrushStage,\n  mode: BrushMoveMode\n): BrushState {\n  const { brushingType } = state;\n  if (!(state.isBrushing && brushingType)) {\n    return state;\n  }\n  if (brushingType === \"move\") {\n    return moveSelection(state, offset, stage);\n  }\n  if (brushingType === \"select\") {\n    const end =\n      mode === \"window\"\n        ? {\n            x: clamp(state.start.x + offset.x, 0, stage.width),\n            y: clamp(state.start.y + offset.y, 0, stage.height),\n          }\n        : { x: state.start.x + offset.x, y: state.start.y + offset.y };\n    return { ...state, end, extent: getBrushExtent(state.start, end, stage) };\n  }\n\n  // Resize from the left / right handle.\n  if (mode === \"window\") {\n    const { x: x0, y: y0 } = state.start;\n    const { x: x1, y: y1 } = state.end;\n    return {\n      ...state,\n      isBrushing: true,\n      extent: {\n        ...state.extent,\n        ...getBrushExtent(\n          {\n            x:\n              brushingType === \"left\"\n                ? clamp(x0 + offset.x, 0, stage.width)\n                : x0,\n            y: y0,\n          },\n          {\n            x:\n              brushingType === \"right\"\n                ? clamp(x1 + offset.x, 0, stage.width)\n                : x1,\n            y: y1,\n          },\n          stage\n        ),\n      },\n    };\n  }\n  const { start, end } = state;\n  if (brushingType === \"right\") {\n    const move = Math.max(start.x, end.x) + offset.x;\n    return {\n      ...state,\n      activeHandle: \"right\",\n      extent: {\n        ...state.extent,\n        x0: Math.max(Math.min(move, start.x), 0),\n        x1: Math.min(Math.max(move, start.x), stage.width),\n      },\n    };\n  }\n  const move = Math.min(start.x, end.x) + offset.x;\n  return {\n    ...state,\n    activeHandle: \"left\",\n    extent: {\n      ...state.extent,\n      x0: Math.min(move, end.x),\n      x1: Math.max(move, end.x),\n    },\n  };\n}\n\nexport interface BrushEndResult {\n  state: BrushState;\n  /** How many `onChange` calls follow the single `onBrushEnd`. */\n  changeCount: 1 | 2;\n}\n\n/** Pointer up: commit the drawn extent. `null` when no drag was in progress. */\nexport function endBrush(\n  state: BrushState,\n  mode: BrushMoveMode\n): BrushEndResult | null {\n  const { brushingType, extent } = state;\n  if (!brushingType) {\n    return null;\n  }\n  const idle = {\n    isBrushing: false,\n    brushingType: undefined,\n    activeHandle: null,\n  } as const;\n\n  if (mode === \"window\" || brushingType === \"move\") {\n    return {\n      state: {\n        ...state,\n        ...idle,\n        start: {\n          x: Math.min(extent.x0, extent.x1),\n          y: mode === \"window\" ? extent.y0 : Math.min(extent.y0, extent.y1),\n        },\n        end: {\n          x: Math.max(extent.x0, extent.x1),\n          y: Math.max(extent.y0, extent.y1),\n        },\n      },\n      changeCount: mode === \"window\" ? 1 : 2,\n    };\n  }\n  if (brushingType === \"select\") {\n    return {\n      state: {\n        ...state,\n        ...idle,\n        start: { x: extent.x0, y: extent.y0 },\n        end: { x: extent.x1, y: extent.y1 },\n      },\n      changeCount: 1,\n    };\n  }\n  const start = { x: Math.min(extent.x0, extent.x1), y: extent.y0 };\n  const end = {\n    x: Math.max(extent.x0, extent.x1),\n    y: Math.max(extent.y0, extent.y1),\n  };\n  return {\n    state: {\n      ...state,\n      ...idle,\n      start,\n      end,\n      extent: {\n        x0: Math.min(start.x, end.x),\n        x1: Math.max(start.x, end.x),\n        y0: Math.min(start.y, end.y),\n        y1: Math.max(start.y, end.y),\n      },\n    },\n    changeCount: 2,\n  };\n}\n\n/** visx selection rect geometry (`getBrushWidth` / `getBrushHeight`). */\nexport function brushSelectionRect(extent: BrushExtent) {\n  return {\n    x: Math.min(extent.x0, extent.x1),\n    y: Math.min(extent.y0, extent.y1),\n    width: Math.max(\n      Math.max(extent.x0, extent.x1) - Math.min(extent.x0, extent.x1),\n      0\n    ),\n    height: Math.max(\n      Math.max(extent.y0, extent.y1) - Math.min(extent.y0, extent.y1),\n      0\n    ),\n  };\n}\n\n/**\n * Handle hit rects, in visx DOM order (`right` then `left`, so `left` wins\n * where they overlap). Height is the full stage, as visx passes\n * `height: stageHeight` to `renderBrushHandle`.\n */\nexport function brushHandleRects(\n  extent: BrushExtent,\n  handleSize: number,\n  stageHeight: number\n): {\n  type: BrushHandleType;\n  x: number;\n  y: number;\n  width: number;\n  height: number;\n}[] {\n  const offset = handleSize / 2;\n  return [\n    {\n      type: \"right\",\n      x: extent.x1 - offset,\n      y: extent.y0 - offset,\n      width: handleSize,\n      height: stageHeight,\n    },\n    {\n      type: \"left\",\n      x: extent.x0 - offset,\n      y: extent.y0 - offset,\n      width: handleSize,\n      height: stageHeight,\n    },\n  ];\n}\n\nexport interface BrushDomainBounds {\n  x0: number;\n  x1: number;\n}\n\ntype InvertibleScale = (value: number) => unknown;\n\nfunction invertToNumber(invert: InvertibleScale, value: number): number {\n  return Number((invert(value) as { valueOf(): number }).valueOf());\n}\n\n/**\n * visx `Brush.convertRangeToDomain` for the x axis: `null` for an empty brush,\n * otherwise the extent widened by `SAFE_PIXEL` and inverted (numbers, e.g.\n * epoch ms for a time scale; NaN falls back to 0 like visx's `|| 0`).\n */\nexport function brushExtentToDomain(\n  extent: BrushExtent,\n  invert: InvertibleScale\n): BrushDomainBounds | null {\n  const { x0, x1 } = extent;\n  if (typeof x0 === \"undefined\" || x0 < 0) {\n    return null;\n  }\n  const start = x0 || 0;\n  const end = x1 || 0;\n  const invertedStart = invertToNumber(\n    invert,\n    start + (start < end ? -BRUSH_SAFE_PIXEL : BRUSH_SAFE_PIXEL)\n  );\n  const invertedEnd = invertToNumber(\n    invert,\n    end + (end < start ? -BRUSH_SAFE_PIXEL : BRUSH_SAFE_PIXEL)\n  );\n  return {\n    x0: Math.min(invertedStart, invertedEnd) || 0,\n    x1: Math.max(invertedStart, invertedEnd) || 0,\n  };\n}\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush-state.ts"
    },
    {
      "path": "src/charts/chart-brush-track-overlay.tsx",
      "content": "\"use client\";\n\nimport type { CSSProperties, RefObject } from \"react\";\nimport { createPortal } from \"react-dom\";\nimport type { Margin } from \"./chart-context\";\nimport { useChartPortalContainer, useChartStable } from \"./chart-context\";\n\n/** Fade dimming panes at the outer track ends — matches series edge fade (15%). */\nconst BRUSH_TRACK_OUTER_FADE = 0.15;\n\nexport interface ChartBrushTrackOverlayStyle {\n  /** Backdrop blur in px (0–5). Default: 1.5 */\n  blurPx?: number;\n  /** Fade blur at the outer left/right track ends. Default: true */\n  fadeOuterEdges?: boolean;\n}\n\nfunction outsidePaneStyle(\n  edge: \"left\" | \"right\",\n  style: Required<ChartBrushTrackOverlayStyle>\n): CSSProperties {\n  const fadeStop = `${BRUSH_TRACK_OUTER_FADE * 100}%`;\n  let mask: string | undefined;\n  if (style.fadeOuterEdges) {\n    mask =\n      edge === \"left\"\n        ? `linear-gradient(to right, transparent 0%, black ${fadeStop}, black 100%)`\n        : `linear-gradient(to left, transparent 0%, black ${fadeStop}, black 100%)`;\n  }\n\n  return {\n    pointerEvents: \"none\",\n    backdropFilter: style.blurPx > 0 ? `blur(${style.blurPx}px)` : undefined,\n    WebkitBackdropFilter:\n      style.blurPx > 0 ? `blur(${style.blurPx}px)` : undefined,\n    maskImage: mask,\n    WebkitMaskImage: mask,\n  };\n}\n\nexport interface ChartBrushTrackOverlayProps\n  extends ChartBrushTrackOverlayStyle {\n  innerWidth: number;\n  innerHeight: number;\n  /** Pixel x of the selection start within the plot area. */\n  selectionX0: number;\n  /** Pixel x of the selection end within the plot area. */\n  selectionX1: number;\n}\n\nexport interface ChartBrushOverlayHost {\n  containerRef: RefObject<HTMLDivElement | null>;\n  margin: Margin;\n}\n\nexport function ChartBrushTrackOverlayContent({\n  containerRef,\n  margin,\n  innerWidth,\n  innerHeight,\n  selectionX0,\n  selectionX1,\n  blurPx = 1.5,\n  fadeOuterEdges = true,\n}: ChartBrushTrackOverlayProps & ChartBrushOverlayHost) {\n  const overlayStyle: Required<ChartBrushTrackOverlayStyle> = {\n    blurPx: Math.min(5, Math.max(0, blurPx)),\n    fadeOuterEdges,\n  };\n\n  const container = useChartPortalContainer(containerRef);\n  if (!container) {\n    return null;\n  }\n\n  const x0 = Math.max(0, Math.min(selectionX0, selectionX1, innerWidth));\n  const x1 = Math.max(\n    x0,\n    Math.min(Math.max(selectionX0, selectionX1), innerWidth)\n  );\n  const leftWidth = Math.max(0, x0);\n  const rightWidth = Math.max(0, innerWidth - x1);\n\n  if (leftWidth <= 0 && rightWidth <= 0) {\n    return null;\n  }\n\n  const plotLeft = margin.left;\n  const plotTop = margin.top;\n\n  return createPortal(\n    <div\n      aria-hidden=\"true\"\n      className=\"pointer-events-none absolute inset-0 z-[1]\"\n    >\n      {leftWidth > 0 ? (\n        <div\n          className=\"absolute\"\n          style={{\n            ...outsidePaneStyle(\"left\", overlayStyle),\n            top: plotTop,\n            left: plotLeft,\n            width: leftWidth,\n            height: innerHeight,\n          }}\n        />\n      ) : null}\n      {rightWidth > 0 ? (\n        <div\n          className=\"absolute\"\n          style={{\n            ...outsidePaneStyle(\"right\", overlayStyle),\n            top: plotTop,\n            left: plotLeft + x1,\n            width: rightWidth,\n            height: innerHeight,\n          }}\n        />\n      ) : null}\n    </div>,\n    container\n  );\n}\n\nexport function ChartBrushTrackOverlay(props: ChartBrushTrackOverlayProps) {\n  const { containerRef, margin } = useChartStable();\n\n  return (\n    <ChartBrushTrackOverlayContent\n      {...props}\n      containerRef={containerRef}\n      margin={margin}\n    />\n  );\n}\n",
      "type": "registry:component",
      "target": "components/charts/chart-brush-track-overlay.tsx"
    }
  ]
}