{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lib-use-anchored-position",
  "type": "registry:lib",
  "title": "lib/use-anchored-position",
  "description": "팝오버를 기준 요소 바로 아래에 position: fixed 로 배치합니다 (fixes.css F15).",
  "files": [
    {
      "path": "poc/design-system/implement/src/lib/use-anchored-position.ts",
      "content": "import { type RefObject, useLayoutEffect } from \"react\";\n\n/**\n * 팝오버를 기준 요소 바로 아래에 position: fixed 로 배치합니다 (fixes.css F15).\n * - 흐름 안 배치나 absolute 와 달리 주변 레이아웃 · 문서 스크롤 영역을 늘리지 않고, overflow 컨테이너에 잘리지 않습니다.\n * - 아래 공간이 모자라고 위가 더 넓으면 위로 엽니다.\n * - 오른쪽이 모자라면 기준 요소의 끝에 맞춥니다 (원본 --align_end 와 같은 역할).\n * 위치는 렌더 없이 style 에 직접 쓰고, 스크롤 · 리사이즈 때 다시 계산합니다.\n */\nexport function useAnchoredPosition(\n  open: boolean,\n  anchorRef: RefObject<HTMLElement | null>,\n  floatingRef: RefObject<HTMLElement | null>,\n  {\n    offset = 6,\n    margin = 8,\n  }: {\n    /** 기준 요소와 팝오버 사이 간격(px). 원본 CSS 의 margin-top / top 오프셋과 같은 6px */\n    offset?: number;\n    /** 화면 가장자리와 띄울 최소 간격(px) */\n    margin?: number;\n  } = {},\n) {\n  useLayoutEffect(() => {\n    const floating = floatingRef.current;\n    if (!open || !floating) return;\n\n    function update() {\n      const anchor = anchorRef.current;\n      if (!anchor || !floating) return;\n      const a = anchor.getBoundingClientRect();\n      floating.style.minWidth = `${a.width}px`;\n\n      const { width, height } = floating.getBoundingClientRect();\n      const viewportWidth = document.documentElement.clientWidth;\n      const viewportHeight = window.innerHeight;\n\n      const spaceBelow = viewportHeight - a.bottom - offset - margin;\n      const spaceAbove = a.top - offset - margin;\n      // 기준 요소가 화면 밖이면 뒤집지 않음 (열린 채 스크롤로 지나갈 때 엉뚱한 곳에 겹치지 않도록)\n      const anchorVisible = a.bottom > 0 && a.top < viewportHeight;\n      const placeAbove = anchorVisible && height > spaceBelow && spaceAbove > spaceBelow;\n      const top = placeAbove ? Math.max(margin, a.top - offset - height) : a.bottom + offset;\n\n      const alignEnd = a.left + width > viewportWidth - margin;\n      const left = Math.max(margin, alignEnd ? Math.min(a.right, viewportWidth - margin) - width : a.left);\n\n      floating.style.top = `${top}px`;\n      floating.style.left = `${left}px`;\n      floating.dataset.side = placeAbove ? \"top\" : \"bottom\";\n    }\n\n    update();\n    // capture: 중첩된 스크롤 컨테이너(Grid · Dialog 등)의 스크롤도 받음\n    window.addEventListener(\"scroll\", update, true);\n    window.addEventListener(\"resize\", update);\n    // 목록 길이가 바뀌면(Autocomplete 검색 등) 위로 연 팝오버의 위치도 바뀜. jsdom 에는 없음\n    const observer = typeof ResizeObserver === \"undefined\" ? null : new ResizeObserver(update);\n    observer?.observe(floating);\n    return () => {\n      window.removeEventListener(\"scroll\", update, true);\n      window.removeEventListener(\"resize\", update);\n      observer?.disconnect();\n    };\n  }, [open, anchorRef, floatingRef, offset, margin]);\n}\n",
      "type": "registry:lib",
      "target": "components/lib/use-anchored-position.ts"
    }
  ],
  "categories": [
    "internal"
  ],
  "meta": {
    "version": "0.1.0"
  }
}
