{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "safe-area",
  "title": "Safe area",
  "description": "A pointer safe triangle that keeps submenus and flyouts open while the cursor travels diagonally toward them.",
  "dependencies": [],
  "registryDependencies": [
    "https://hextaui.com/r/theme.json"
  ],
  "files": [
    {
      "path": "components/ui/safe-area.tsx",
      "content": "\"use client\"\n\nimport * as React from \"react\"\nimport { createPortal } from \"react-dom\"\n\ntype SafeAreaPoint = { x: number; y: number }\n\ntype SafeAreaRect = { x: number; y: number; width: number; height: number }\n\ntype SafeAreaShape = {\n  polygon: SafeAreaPoint[]\n  trough: SafeAreaRect\n  inside: boolean\n}\n\ntype SafeAreaElements = { reference: Element; floating: Element }\n\nconst buffer = 0.5\n\nfunction crosses(point: SafeAreaPoint, a: SafeAreaPoint, b: SafeAreaPoint) {\n  return (\n    a.y >= point.y !== b.y >= point.y &&\n    point.x <= ((b.x - a.x) * (point.y - a.y)) / (b.y - a.y) + a.x\n  )\n}\n\nfunction inPolygon(point: SafeAreaPoint, polygon: SafeAreaPoint[]) {\n  let inside = false\n  for (let i = 0; i < polygon.length; i++) {\n    if (crosses(point, polygon[i], polygon[(i + 1) % polygon.length])) {\n      inside = !inside\n    }\n  }\n  return inside\n}\n\nfunction inRect(point: SafeAreaPoint, rect: SafeAreaRect) {\n  return (\n    point.x >= rect.x &&\n    point.x <= rect.x + rect.width &&\n    point.y >= rect.y &&\n    point.y <= rect.y + rect.height\n  )\n}\n\nfunction box(rect: DOMRect): SafeAreaRect {\n  return { x: rect.left, y: rect.top, width: rect.width, height: rect.height }\n}\n\nfunction safeAreaShape(\n  exit: SafeAreaPoint,\n  point: SafeAreaPoint,\n  ref: DOMRect,\n  rect: DOMRect\n): SafeAreaShape {\n  const side =\n    rect.left >= ref.right - 1\n      ? \"right\"\n      : rect.right <= ref.left + 1\n        ? \"left\"\n        : rect.top >= ref.bottom - 1\n          ? \"bottom\"\n          : \"top\"\n  const { x, y } = exit\n  const fromRight = x > rect.right - rect.width / 2\n  const fromBottom = y > rect.bottom - rect.height / 2\n  const wider = rect.width > ref.width\n  const taller = rect.height > ref.height\n  const left = (wider ? ref : rect).left\n  const right = (wider ? ref : rect).right\n  const top = (taller ? ref : rect).top\n  const bottom = (taller ? ref : rect).bottom\n  let polygon: SafeAreaPoint[]\n  let trough: SafeAreaRect\n\n  if (side === \"left\" || side === \"right\") {\n    const offset = taller ? buffer / 2 : buffer * 4\n    const one = taller ? y + offset : fromBottom ? y + offset : y - offset\n    const two = taller ? y - offset : fromBottom ? y + offset : y - offset\n    const near = side === \"right\" ? rect.left + buffer : rect.right - buffer\n    const far = side === \"right\" ? rect.right : rect.left\n    const edgeTop = fromBottom ? near : taller ? near : far\n    const edgeBottom = fromBottom ? (taller ? near : far) : near\n    const cursorX = side === \"right\" ? x - buffer : x + buffer + 1\n    polygon = [\n      { x: cursorX, y: one },\n      { x: cursorX, y: two },\n      { x: edgeTop, y: rect.top },\n      { x: edgeBottom, y: rect.bottom },\n    ]\n    const from = side === \"right\" ? ref.right - 1 : rect.right - 1\n    const to = side === \"right\" ? rect.left + 1 : ref.left + 1\n    trough = { x: from, y: top, width: to - from, height: bottom - top }\n  } else {\n    const offset = wider ? buffer / 2 : buffer * 4\n    const one = wider ? x + offset : fromRight ? x + offset : x - offset\n    const two = wider ? x - offset : fromRight ? x + offset : x - offset\n    const near = side === \"bottom\" ? rect.top + buffer : rect.bottom - buffer\n    const far = side === \"bottom\" ? rect.bottom : rect.top\n    const edgeLeft = fromRight ? near : wider ? near : far\n    const edgeRight = fromRight ? (wider ? near : far) : near\n    const cursorY = side === \"bottom\" ? y - buffer : y + buffer + 1\n    polygon = [\n      { x: one, y: cursorY },\n      { x: two, y: cursorY },\n      { x: rect.left, y: edgeLeft },\n      { x: rect.right, y: edgeRight },\n    ]\n    const from = side === \"bottom\" ? ref.bottom - 1 : rect.bottom - 1\n    const to = side === \"bottom\" ? rect.top + 1 : ref.top + 1\n    trough = { x: left, y: from, width: right - left, height: to - from }\n  }\n\n  return {\n    polygon,\n    trough,\n    inside:\n      inRect(point, box(ref)) ||\n      inRect(point, box(rect)) ||\n      inRect(point, trough) ||\n      inPolygon(point, polygon),\n  }\n}\n\nfunction subscribe() {\n  return () => {}\n}\n\nfunction SafeAreaOverlay({\n  getElements,\n}: {\n  getElements: () => SafeAreaElements | null\n}) {\n  const client = React.useSyncExternalStore(\n    subscribe,\n    () => true,\n    () => false\n  )\n  const svgRef = React.useRef<SVGSVGElement | null>(null)\n  const polygonRef = React.useRef<SVGPolygonElement | null>(null)\n  const troughRef = React.useRef<SVGRectElement | null>(null)\n  const dotRef = React.useRef<SVGCircleElement | null>(null)\n  const getElementsRef = React.useRef(getElements)\n\n  React.useEffect(() => {\n    getElementsRef.current = getElements\n  })\n\n  React.useEffect(() => {\n    if (!client) {\n      return\n    }\n    let point: SafeAreaPoint | null = null\n    let exit: { reference: Element; point: SafeAreaPoint } | null = null\n    let frame = 0\n\n    const draw = () => {\n      frame = requestAnimationFrame(draw)\n      const svg = svgRef.current\n      const polygon = polygonRef.current\n      const trough = troughRef.current\n      const dot = dotRef.current\n      const elements = getElementsRef.current()\n      if (!svg || !polygon || !trough || !dot || !point || !elements) {\n        exit = null\n        svg?.removeAttribute(\"data-visible\")\n        return\n      }\n      const ref = elements.reference.getBoundingClientRect()\n      const rect = elements.floating.getBoundingClientRect()\n      if (exit?.reference !== elements.reference) {\n        exit = { reference: elements.reference, point }\n      }\n      const shape = safeAreaShape(exit.point, point, ref, rect)\n      polygon.setAttribute(\n        \"points\",\n        shape.polygon.map((p) => `${p.x},${p.y}`).join(\" \")\n      )\n      trough.setAttribute(\"x\", String(shape.trough.x))\n      trough.setAttribute(\"y\", String(shape.trough.y))\n      trough.setAttribute(\"width\", String(Math.max(0, shape.trough.width)))\n      trough.setAttribute(\"height\", String(Math.max(0, shape.trough.height)))\n      dot.setAttribute(\"cx\", String(point.x))\n      dot.setAttribute(\"cy\", String(point.y))\n      svg.toggleAttribute(\"data-inside\", shape.inside)\n      svg.setAttribute(\"data-visible\", \"\")\n    }\n\n    const onMove = (event: PointerEvent) => {\n      if (event.pointerType === \"touch\") {\n        return\n      }\n      point = { x: event.clientX, y: event.clientY }\n      const elements = getElementsRef.current()\n      if (\n        elements &&\n        (exit?.reference !== elements.reference ||\n          inRect(point, box(elements.reference.getBoundingClientRect())))\n      ) {\n        exit = { reference: elements.reference, point }\n      }\n    }\n\n    document.addEventListener(\"pointermove\", onMove, {\n      capture: true,\n      passive: true,\n    })\n    frame = requestAnimationFrame(draw)\n    return () => {\n      cancelAnimationFrame(frame)\n      document.removeEventListener(\"pointermove\", onMove, { capture: true })\n    }\n  }, [client])\n\n  if (!client) {\n    return null\n  }\n\n  return createPortal(\n    <svg\n      ref={svgRef}\n      aria-hidden=\"true\"\n      data-slot=\"safe-area-overlay\"\n      className=\"group/safe-area pointer-events-none invisible fixed inset-0 z-100 size-full overflow-visible data-visible:visible\"\n    >\n      <rect\n        ref={troughRef}\n        strokeDasharray=\"3 3\"\n        className=\"fill-success/8 stroke-success/50\"\n      />\n      <polygon\n        ref={polygonRef}\n        fillRule=\"evenodd\"\n        strokeLinejoin=\"round\"\n        className=\"fill-success/12 stroke-success/70\"\n      />\n      <circle\n        ref={dotRef}\n        r={4}\n        className=\"fill-destructive stroke-background stroke-2 group-data-inside/safe-area:fill-success\"\n      />\n    </svg>,\n    document.body\n  )\n}\n\nexport { SafeAreaOverlay }\nexport type { SafeAreaElements }\n",
      "type": "registry:ui"
    }
  ],
  "type": "registry:ui"
}