{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "use-scroll-region",
  "type": "registry:hook",
  "title": "useScrollRegion / useScrollReset",
  "description": "Scroll the app's real scroll region back to the top – on navigation, or on demand – whichever scroller the Layout chose.",
  "categories": [
    "hooks",
    "navigation"
  ],
  "files": [
    {
      "path": "hooks/use-scroll-region.ts",
      "type": "registry:hook",
      "target": "hooks/use-scroll-region.ts",
      "content": "import * as React from \"react\";\n\n/**\n * The scroll region an app's content actually lives in.\n *\n * `AppShell scroll=\"shell\"` moves the scroller off the window and onto\n * `AppMain`, so `window.scrollTo(0, 0)` -- the reflex for \"go back to the top\"\n * -- silently does nothing there: the window has no overflow left to scroll.\n * It fails quietly, which is what makes it expensive. A multi-page shell app\n * looks correct until you follow a link from halfway down a long page and land\n * halfway down the next one.\n *\n * `AppMain` publishes the right scroller here, so a page nested anywhere inside\n * it can ask for the top without knowing which scroll model its Layout chose.\n * Outside an `AppMain` the region falls back to the window, which is what a\n * document-scroll page wanted anyway.\n */\nexport interface ScrollRegion {\n  /** Scroll the app's content region back to the top. */\n  scrollToTop: () => void;\n}\n\nconst WINDOW_REGION: ScrollRegion = {\n  scrollToTop: () => window.scrollTo(0, 0),\n};\n\nconst ScrollRegionContext = React.createContext<ScrollRegion>(WINDOW_REGION);\n\n/**\n * Publishes a scroll region to the subtree below it. `AppMain` does this for\n * you with whichever scroller its `scroll` prop selected; reach for it directly\n * only when an app owns a scroller the design system does not.\n */\nexport const ScrollRegionProvider = ScrollRegionContext.Provider;\n\n/**\n * The scroll region this subtree lives in. For the common \"put me back at the\n * top when X changes\" case use `useScrollReset`; this is the imperative escape\n * hatch (a back-to-top button, scrolling after a submit).\n */\nexport function useScrollRegion(): ScrollRegion {\n  return React.useContext(ScrollRegionContext);\n}\n\n/**\n * Scrolls the region back to the top whenever `key` changes, skipping the\n * initial mount.\n *\n * The counterpart to `useRouteFocus`, and deliberately a separate hook: that\n * one moves focus with `preventScroll` so navigation does not jump, which\n * leaves the scroll position exactly where the last page left it. A Layout\n * wants both, on the same key and the same ref:\n *\n * ```tsx\n * useRouteFocus(location.pathname, mainRef);\n * useScrollReset(location.pathname, mainRef);\n * ```\n *\n * **Why the ref is a parameter and not just the context**: the Layout that\n * renders `AppMain` sits *above* the provider `AppMain` installs, so it cannot\n * read it -- a component never consumes a context it renders. The Layout is\n * also exactly where route resets belong, so the hook takes the ref it already\n * has (and already passes to `AppMain`), matching `useRouteFocus`'s shape.\n *\n * Omit the ref *below* an `AppMain` and the region comes from context instead,\n * which is what a page wants: it has no ref, and no business knowing which\n * scroll model the Layout chose. Pages pass their own key when they swap\n * content in place -- a lesson moving from reading to quiz changes no URL, but\n * it is still a new screenful and belongs at the top of one.\n *\n * The scroll is instant, never smooth: an animated scroll on arrival races the\n * render of the page just asked for, and reduced-motion users would have to sit\n * out an animation carrying no information.\n */\nexport function useScrollReset(\n  key: unknown,\n  ref?: React.RefObject<HTMLElement | null>,\n): void {\n  const region = useScrollRegion();\n  const isFirstRender = React.useRef(true);\n\n  React.useEffect(() => {\n    if (isFirstRender.current) {\n      isFirstRender.current = false;\n      return;\n    }\n    if (ref) {\n      const el = ref.current;\n      // Overflow of its own is what makes an element the scroller. A\n      // document-scroll Layout hands us the same `<main>` ref, but there the\n      // page scrolls and the element does not -- so ask, rather than making\n      // the caller restate a scroll model it already declared on `AppMain`.\n      if (el && el.scrollHeight > el.clientHeight) el.scrollTop = 0;\n      else window.scrollTo(0, 0);\n      return;\n    }\n    region.scrollToTop();\n  }, [key, ref, region]);\n}\n"
    }
  ],
  "docs": "In a shell-scroll app the scroller is AppMain, so window.scrollTo(0, 0) does nothing and fails silently – follow a link from halfway down a page and you land halfway down the next one. In a Layout call useScrollReset(location.pathname, mainRef) beside useRouteFocus(location.pathname, mainRef), which focuses with preventScroll and leaves scrolling to this hook; the ref is a parameter because a Layout renders AppMain and so cannot read the context AppMain publishes. Below an AppMain omit the ref and the scroller comes from that context – which is what a page wants when it swaps content in place and passes its own key rather than a route. useScrollRegion() is the imperative escape hatch for a back-to-top button. Either way both scroll models resolve themselves, so a document-scroll app needs no change.",
  "meta": {
    "group": "hooks",
    "related": [
      "app-shell",
      "use-route-focus"
    ],
    "exports": [
      "useScrollRegion",
      "useScrollReset",
      "ScrollRegionProvider"
    ],
    "siteSlug": "use-scroll-region"
  }
}
