{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-element-size-map",
  "title": "useElementSizeMap",
  "description": "A client-safe React hook that measures multiple keyed element sizes with ResizeObserver so animated shells can morph to active content.",
  "files": [
    {
      "path": "registry/base/hooks/use-element-size-map.ts",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nexport type ElementSize = {\n  width: number;\n  height: number;\n};\n\n/**\n * Tracks the rendered size of multiple keyed elements with ResizeObserver.\n *\n * Use it when an animated shell, indicator, toolbar, or floating panel needs to\n * move toward the measured width/height of whichever keyed element is active.\n *\n * @example\n *   const { setMeasureRef, sizes } = useElementSizeMap<HTMLDivElement>();\n *   const activeSize = sizes[activeId];\n *\n *   return (\n *     <>\n *       <div aria-hidden className=\"invisible absolute\">\n *         {items.map((item) => (\n *           <div key={item.id} ref={setMeasureRef(item.id)}>\n *             {item.content}\n *           </div>\n *         ))}\n *       </div>\n *       <motion.div animate={activeSize}>\n *         {activeItem.content}\n *       </motion.div>\n *     </>\n *   );\n */\nexport function useElementSizeMap<T extends HTMLElement>(threshold = 0.5) {\n  const [sizes, setSizes] = useState<Record<string, ElementSize>>({});\n  const observerRef = useRef<ResizeObserver | null>(null);\n  const elements = useRef(new Map<string, T>());\n  const idsByElement = useRef(new WeakMap<Element, string>());\n  const callbacks = useRef(new Map<string, (node: T | null) => void>());\n  const thresholdRef = useRef(threshold);\n\n  useEffect(() => {\n    thresholdRef.current = threshold;\n  }, [threshold]);\n\n  useEffect(() => {\n    const activeElements = elements.current;\n\n    return () => {\n      observerRef.current?.disconnect();\n      observerRef.current = null;\n      activeElements.clear();\n      idsByElement.current = new WeakMap();\n    };\n  }, []);\n\n  const updateSizes = useCallback(\n    (updates: ReadonlyArray<readonly [string, ElementSize]>) => {\n      setSizes((current) => {\n        let next = current;\n\n        for (const [id, nextSize] of updates) {\n          const currentSize = current[id];\n\n          if (\n            currentSize &&\n            Math.abs(currentSize.width - nextSize.width) <=\n              thresholdRef.current &&\n            Math.abs(currentSize.height - nextSize.height) <=\n              thresholdRef.current\n          ) {\n            continue;\n          }\n\n          if (next === current) {\n            next = { ...current };\n          }\n\n          next[id] = nextSize;\n        }\n\n        return next;\n      });\n    },\n    [],\n  );\n\n  const getObserver = useCallback(() => {\n    if (typeof ResizeObserver === \"undefined\") return null;\n\n    if (!observerRef.current) {\n      observerRef.current = new ResizeObserver((entries) => {\n        const updates: Array<readonly [string, ElementSize]> = [];\n\n        for (const entry of entries) {\n          const id = idsByElement.current.get(entry.target);\n\n          if (id === undefined) continue;\n\n          updates.push([id, readElementSize(entry.target, entry)]);\n        }\n\n        if (updates.length > 0) {\n          updateSizes(updates);\n        }\n      });\n    }\n\n    return observerRef.current;\n  }, [updateSizes]);\n\n  const disconnect = useCallback((id: string) => {\n    const element = elements.current.get(id);\n\n    if (!element) return;\n\n    observerRef.current?.unobserve(element);\n    idsByElement.current.delete(element);\n    elements.current.delete(id);\n  }, []);\n\n  const updateSize = useCallback(\n    (id: string, nextSize: ElementSize) => {\n      updateSizes([[id, nextSize]]);\n    },\n    [updateSizes],\n  );\n\n  const setMeasureRef = useCallback(\n    (id: string) => {\n      const current = callbacks.current.get(id);\n\n      if (current) {\n        return current;\n      }\n\n      const ref = (node: T | null) => {\n        disconnect(id);\n\n        if (!node) {\n          return;\n        }\n\n        updateSize(id, readElementSize(node));\n\n        const observer = getObserver();\n\n        if (observer) {\n          elements.current.set(id, node);\n          idsByElement.current.set(node, id);\n          observer.observe(node);\n        }\n      };\n\n      callbacks.current.set(id, ref);\n\n      return ref;\n    },\n    [disconnect, getObserver, updateSize],\n  );\n\n  return { setMeasureRef, sizes } as const;\n}\n\nfunction readElementSize(\n  element: Element,\n  entry?: ResizeObserverEntry,\n): ElementSize {\n  const borderBoxSize = Array.isArray(entry?.borderBoxSize)\n    ? entry.borderBoxSize[0]\n    : entry?.borderBoxSize;\n\n  if (borderBoxSize) {\n    return {\n      width: borderBoxSize.inlineSize,\n      height: borderBoxSize.blockSize,\n    };\n  }\n\n  const rect = element.getBoundingClientRect();\n\n  return {\n    width: rect.width,\n    height: rect.height,\n  };\n}\n",
      "type": "registry:hook",
      "target": "@hooks/use-element-size-map.ts"
    }
  ],
  "meta": {
    "tags": [
      "resize-observer",
      "auto-size",
      "measure",
      "width-animation",
      "height-animation",
      "layout"
    ],
    "effects": [
      "morph",
      "size-animation",
      "layout-animation"
    ]
  },
  "categories": [
    "measurement"
  ],
  "type": "registry:hook"
}