{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "toast-inset",
  "title": "Toast Inset",
  "description": "Helper used by the Get IT Done components.",
  "dependencies": [],
  "registryDependencies": [
    "https://zero-design.itdone.de/r/theme.json"
  ],
  "files": [
    {
      "path": "registry/lib/toast-inset.ts",
      "content": "'use client';\n\nimport * as React from 'react';\n\n// On phones the toast sits at the bottom edge. A sticky footer of an open Sheet, Dialog or Drawer, a cookie banner or any app-fixed bottom bar reports its inset here, so the toast stays above its actions.\n// The toast viewport reads it as --toast-inset-bottom on the document element (phones only). With several open layers the tallest footer wins.\nexport const TOAST_INSET_PROPERTY = '--toast-inset-bottom';\n\nconst footers = new Map<object | string, number>();\n\nconst publish = () => {\n  if (typeof document === 'undefined') return;\n  const height = Math.max(0, ...footers.values());\n  if (height > 0) document.documentElement.style.setProperty(TOAST_INSET_PROPERTY, `${height}px`);\n  else document.documentElement.style.removeProperty(TOAST_INSET_PROPERTY);\n};\n\n// Public API for app-fixed bottom bars (cookie banner, fixed footer, bottom toolbar). `id` is any stable key of the bar (a string or the element), `px` is the distance from the bottom edge of the viewport to the top edge of the bar. A value of 0 releases the entry, so always release on unmount. Several bars: the tallest wins.\nexport function reportToastInset(id: object | string, px: number) {\n  const height = Math.ceil(px);\n  if (height > 0) footers.set(id, height);\n  else footers.delete(id);\n  publish();\n}\n\n// A bar this close to the viewport bottom (margin, safe area) counts as docked there.\nconst EDGE_TOLERANCE = 48;\n\nexport interface UseToastInsetOptions {\n  // Turn the report off, for example while the bar is hidden.\n  enabled?: boolean;\n}\n\n// Reports the bottom inset of an app-fixed bar to the toast. Attach the ref to a bar that is `position: fixed` (or sticky) at the bottom edge: while it is, the phone toast rides above its top edge, so it never covers the bar's buttons. A bar that is not fixed or not at the bottom reports nothing, so the hook is safe on responsive bars (for example fixed only from lg).\n// The hook follows the element, not the first render: a bar that renders null first (session still loading, no decision open yet) is picked up when it appears, a bar that is removed or hidden releases its inset, and a bar that is swapped for another element moves the report. It re-measures on resize, orientation and visibility changes, on scrolling (a sticky bar that reaches the bottom edge only after scrolling reports then, and releases the inset when it scrolls away) and when content is added or removed (once per frame). The inset is always released on unmount.\nexport function useToastInset(ref: React.RefObject<HTMLElement | null>, { enabled = true }: UseToastInsetOptions = {}) {\n  React.useEffect(() => {\n    if (!enabled || typeof document === 'undefined') return undefined;\n    let element: HTMLElement | null = null;\n    let resizeObserver: ResizeObserver | null = null;\n    let attributeObserver: MutationObserver | null = null;\n    const report = () => {\n      if (!element) return;\n      const style = window.getComputedStyle(element);\n      const rect = element.getBoundingClientRect();\n      const visible = element.isConnected && style.display !== 'none' && style.visibility !== 'hidden';\n      const docked = visible && (style.position === 'fixed' || style.position === 'sticky') && rect.height > 0 && rect.top < window.innerHeight && rect.bottom >= window.innerHeight - EDGE_TOLERANCE;\n      reportToastInset(element, docked ? window.innerHeight - rect.top : 0);\n    };\n    const detach = () => {\n      if (!element) return;\n      resizeObserver?.disconnect();\n      attributeObserver?.disconnect();\n      reportToastInset(element, 0);\n      element = null;\n    };\n    const attach = (next: HTMLElement) => {\n      element = next;\n      if (typeof ResizeObserver !== 'undefined') {\n        resizeObserver = new ResizeObserver(report);\n        resizeObserver.observe(next);\n      }\n      // A bar that is hidden or shown by a class, style or hidden attribute changes its box, but not always its size observation (for example visibility).\n      attributeObserver = new MutationObserver(report);\n      attributeObserver.observe(next, { attributes: true, attributeFilter: ['class', 'style', 'hidden'] });\n      report();\n    };\n    // Cheap identity check, no layout read unless the element changed.\n    const sync = () => {\n      const current = ref.current;\n      if (current === element && (!element || element.isConnected)) return;\n      detach();\n      if (current?.isConnected) attach(current);\n    };\n    sync();\n    // Elements appear and disappear without the host re-rendering (a child renders the bar), so watch the tree for insertions and removals.\n    // The same insertions and removals can grow the content above a sticky bar and move it to or from the bottom edge without any resize, so the report is refreshed too, at most once per frame.\n    let frame = 0;\n    const schedule = () => {\n      if (frame) return;\n      frame = window.requestAnimationFrame(() => {\n        frame = 0;\n        report();\n      });\n    };\n    const treeObserver = new MutationObserver(() => {\n      sync();\n      schedule();\n    });\n    treeObserver.observe(document.documentElement, { childList: true, subtree: true });\n    const remeasure = () => {\n      sync();\n      report();\n    };\n    // A sticky bar reaches the bottom edge, or leaves it, by scrolling: no resize happens. Scroll events of any container are caught in the capture phase and measured once per frame.\n    document.addEventListener('scroll', schedule, { capture: true, passive: true });\n    window.addEventListener('resize', remeasure);\n    window.addEventListener('orientationchange', remeasure);\n    document.addEventListener('visibilitychange', remeasure);\n    return () => {\n      treeObserver.disconnect();\n      document.removeEventListener('scroll', schedule, { capture: true });\n      if (frame) window.cancelAnimationFrame(frame);\n      window.removeEventListener('resize', remeasure);\n      window.removeEventListener('orientationchange', remeasure);\n      document.removeEventListener('visibilitychange', remeasure);\n      detach();\n    };\n  }, [ref, enabled]);\n}\n",
      "type": "registry:lib"
    }
  ],
  "type": "registry:lib"
}