{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "chart-parent-size",
  "type": "registry:component",
  "title": "Chart Parent Size",
  "description": "Responsive size wrapper for charts: measures before first paint, then tracks resizes (no overflow clipping).",
  "files": [
    {
      "path": "src/charts/chart-parent-size.tsx",
      "content": "\"use client\";\n\nimport {\n  type CSSProperties,\n  type ReactNode,\n  type RefObject,\n  useEffect,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from \"react\";\n\n/** `useLayoutEffect` in the browser, `useEffect` on the server (no SSR warning). */\nexport const useIsomorphicLayoutEffect =\n  typeof window === \"undefined\" ? useEffect : useLayoutEffect;\n\nexport interface ElementSize {\n  width: number;\n  height: number;\n}\n\nconst ZERO_SIZE: ElementSize = { width: 0, height: 0 };\n\n/**\n * Layout size of `element`, unaffected by CSS transforms. Fractional like\n * `ResizeObserver`'s `contentRect`, so the observer's first notification\n * matches and doesn't trigger a second render.\n */\nfunction readElementSize(element: HTMLElement): ElementSize {\n  const style = getComputedStyle(element);\n  const width = Number.parseFloat(style.width);\n  const height = Number.parseFloat(style.height);\n  return {\n    width: Number.isFinite(width) ? width : element.clientWidth,\n    height: Number.isFinite(height) ? height : element.clientHeight,\n  };\n}\n\nexport interface ElementSizeOptions {\n  /**\n   * - `\"layout\"` (default): measure in a layout effect so the sized render\n   *   commits before the first paint; later `ResizeObserver` changes are\n   *   throttled to one update per `debounceTime`.\n   * - `\"observer\"`: visx `useParentSize` timing, for charts whose enter\n   *   motion was tuned on it. Stays 0×0 until the observer's first\n   *   notification (applied in the next animation frame), reads the\n   *   content-box size (`contentRect`), and debounces like visx: the first\n   *   change applies at once, later ones once `debounceTime` passes without\n   *   another.\n   */\n  timing?: \"layout\" | \"observer\";\n}\n\nconst sameSize = (a: ElementSize, b: ElementSize) =>\n  a.width === b.width && a.height === b.height;\n\n/**\n * visx `debounce(fn, wait, { leading: true })`: fires on the first call, then\n * once more with the latest argument `wait` ms after the last call (only if\n * there were calls after the leading one).\n */\nfunction leadingDebounce<A>(fn: (arg: A) => void, wait: number) {\n  let timeout: ReturnType<typeof setTimeout> | undefined;\n  let pending: { arg: A } | null = null;\n  const debounced = (arg: A) => {\n    if (timeout === undefined) {\n      fn(arg);\n      pending = null;\n    } else {\n      pending = { arg };\n    }\n    clearTimeout(timeout);\n    timeout = setTimeout(() => {\n      timeout = undefined;\n      if (pending !== null) {\n        fn(pending.arg);\n        pending = null;\n      }\n    }, wait);\n  };\n  debounced.cancel = () => {\n    clearTimeout(timeout);\n    timeout = undefined;\n    pending = null;\n  };\n  return debounced;\n}\n\n/**\n * Tracks an element's size. The first measurement happens in a layout effect,\n * so the sized render commits before the browser paints its first frame\n * (no blank frame while a `ResizeObserver` round-trips). Later changes come\n * from a `ResizeObserver`; `debounceTime` coalesces bursts after an\n * immediate leading update. `timing: \"observer\"` keeps visx `useParentSize`\n * behaviour instead (see {@link ElementSizeOptions}).\n */\nexport function useElementSize<T extends HTMLElement>(\n  ref: RefObject<T | null>,\n  debounceTime = 0,\n  { timing = \"layout\" }: ElementSizeOptions = {}\n): ElementSize {\n  const [size, setSize] = useState<ElementSize>(ZERO_SIZE);\n\n  // visx useParentSize: passive effect, rAF-deferred observer callbacks.\n  useEffect(() => {\n    const element = ref.current;\n    if (\n      timing !== \"observer\" ||\n      !element ||\n      typeof ResizeObserver === \"undefined\"\n    ) {\n      return;\n    }\n    const resize = leadingDebounce<ElementSize>((next) => {\n      setSize((prev) => (sameSize(prev, next) ? prev : next));\n    }, debounceTime);\n    let frame = 0;\n    const observer = new ResizeObserver((entries) => {\n      for (const entry of entries) {\n        const { width, height } = entry.contentRect;\n        frame = requestAnimationFrame(() => resize({ width, height }));\n      }\n    });\n    observer.observe(element);\n    return () => {\n      cancelAnimationFrame(frame);\n      observer.disconnect();\n      resize.cancel();\n    };\n  }, [ref, debounceTime, timing]);\n\n  useIsomorphicLayoutEffect(() => {\n    const element = ref.current;\n    if (timing !== \"layout\" || !element) {\n      return;\n    }\n    const apply = () => {\n      const next = readElementSize(element);\n      setSize((prev) => (sameSize(prev, next) ? prev : next));\n    };\n    apply();\n\n    if (typeof ResizeObserver === \"undefined\") {\n      return;\n    }\n    let timeout: ReturnType<typeof setTimeout> | null = null;\n    let trailing = false;\n    const observer = new ResizeObserver(() => {\n      if (debounceTime <= 0) {\n        apply();\n        return;\n      }\n      if (timeout !== null) {\n        trailing = true;\n        return;\n      }\n      apply();\n      const flush = () => {\n        if (trailing) {\n          trailing = false;\n          apply();\n          timeout = setTimeout(flush, debounceTime);\n          return;\n        }\n        timeout = null;\n      };\n      timeout = setTimeout(flush, debounceTime);\n    });\n    observer.observe(element);\n    return () => {\n      observer.disconnect();\n      if (timeout !== null) {\n        clearTimeout(timeout);\n      }\n    };\n  }, [ref, debounceTime, timing]);\n\n  return size;\n}\n\nexport interface ChartParentSizeProps {\n  children: (size: { width: number; height: number }) => ReactNode;\n  className?: string;\n  debounceTime?: number;\n  style?: CSSProperties;\n}\n\n/**\n * Drop-in for visx `<ParentSize>` without its `overflow: hidden` measuring\n * layer, so tooltips, markers, and strokes at the chart edge aren't clipped.\n * Measures before first paint (see {@link useElementSize}).\n */\nexport function ParentSize({\n  children,\n  className,\n  debounceTime,\n  style,\n}: ChartParentSizeProps) {\n  const parentRef = useRef<HTMLDivElement>(null);\n  const { width, height } = useElementSize(parentRef, debounceTime ?? 300);\n  return (\n    <div\n      className={className}\n      style={{ position: \"relative\", width: \"100%\", height: \"100%\", ...style }}\n    >\n      <div ref={parentRef} style={{ position: \"absolute\", inset: 0 }}>\n        {children({ width, height })}\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/charts/chart-parent-size.tsx"
    }
  ]
}