{"version":3,"sources":["../../src/components/ScrollAfterNavigation.tsx"],"sourcesContent":["import { use, useEffect } from 'react';\n\nimport { TinkerableContext } from '../TinkerableContext';\nimport { scrollToId } from '../scrollToId';\n\n/**\n * Deep-linking Capability C (MARKDOWN_SYNTAX_SPEC §13.5): after an in-app navigation\n * whose URL carries a `#fragment`, scroll the target section into view.\n *\n * In-app navigation swaps the rendered file **asynchronously** — the destination\n * file's tree mounts *after* the route change — so the element the fragment\n * addresses does not exist at click time. This effect records the pending fragment\n * and retries until the target appears: an immediate attempt, a `MutationObserver`\n * over late-mounting subtrees, and a few timed retries (the `[120, 300, 600]ms`\n * cadence `grove/src/components/Toc.tsx` proves for late-mounting prose). If the\n * target never appears within the window it degrades to top-of-page — a missing\n * fragment is never a hard failure.\n *\n * It re-runs when the destination page **or** the fragment changes, so a fresh click\n * on the same target re-scrolls. Mounted once inside the navigation provider (see\n * `boot`'s `TinkerableApp`) so it is uniform for **every** MDX app — the SDK router\n * owns cross-page anchor navigation, not any one consumer (Grove).\n */\nexport const useScrollAfterNavigation = (): void => {\n  const { navigationState } = use(TinkerableContext);\n  const frag = navigationState.hash;\n  // Re-run when the destination page OR the fragment changes. The two are joined\n  // with a NUL — a byte neither a sandbox path nor a fragment can contain — so no\n  // pair of (path, fragment) values can collide into the same key. It is written as\n  // the \\u0000 ESCAPE, never as a raw byte: a literal NUL in the source makes the\n  // file binary to grep, diff and review tooling.\n  const navKey = `${navigationState.sandboxPath}\\u0000${frag}`;\n\n  useEffect(() => {\n    if (!frag || typeof document === 'undefined') return;\n    // Fast path: the target is already in the DOM (same page, or the tree mounted\n    // synchronously) — scroll now, no observer/timer churn.\n    if (scrollToId(frag)) return;\n\n    let done = false;\n    const finish = () => {\n      done = true;\n      observer.disconnect();\n      timers.forEach(clearTimeout);\n      clearTimeout(finalTimer);\n    };\n    const tryScroll = () => {\n      if (!done && scrollToId(frag)) finish();\n    };\n\n    const observer = new MutationObserver(tryScroll);\n    const timers = [120, 300, 600].map((ms) => setTimeout(tryScroll, ms));\n    // Final fallback once the retry window closes: if the fragment never resolved,\n    // scroll to the top rather than strand the reader at the previous page's offset.\n    const finalTimer = setTimeout(() => {\n      if (!done) {\n        finish();\n        window.scrollTo?.(0, 0);\n      }\n    }, 900);\n\n    observer.observe(document.body, { childList: true, subtree: true });\n    return finish;\n  }, [navKey, frag]);\n};\n\n/** Null-rendering mount point for {@link useScrollAfterNavigation} inside the\n *  navigation provider. */\nexport const ScrollAfterNavigation = (): null => {\n  useScrollAfterNavigation();\n  return null;\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,mBAA+B;AAE/B,+BAAkC;AAClC,wBAA2B;AAoBpB,MAAM,2BAA2B,MAAY;AAClD,QAAM,EAAE,gBAAgB,QAAI,kBAAI,0CAAiB;AACjD,QAAM,OAAO,gBAAgB;AAM7B,QAAM,SAAS,GAAG,gBAAgB,WAAW,KAAS,IAAI;AAE1D,8BAAU,MAAM;AACd,QAAI,CAAC,QAAQ,OAAO,aAAa,YAAa;AAG9C,YAAI,8BAAW,IAAI,EAAG;AAEtB,QAAI,OAAO;AACX,UAAM,SAAS,MAAM;AACnB,aAAO;AACP,eAAS,WAAW;AACpB,aAAO,QAAQ,YAAY;AAC3B,mBAAa,UAAU;AAAA,IACzB;AACA,UAAM,YAAY,MAAM;AACtB,UAAI,CAAC,YAAQ,8BAAW,IAAI,EAAG,QAAO;AAAA,IACxC;AAEA,UAAM,WAAW,IAAI,iBAAiB,SAAS;AAC/C,UAAM,SAAS,CAAC,KAAK,KAAK,GAAG,EAAE,IAAI,CAAC,OAAO,WAAW,WAAW,EAAE,CAAC;AAGpE,UAAM,aAAa,WAAW,MAAM;AAClC,UAAI,CAAC,MAAM;AACT,eAAO;AACP,eAAO,WAAW,GAAG,CAAC;AAAA,MACxB;AAAA,IACF,GAAG,GAAG;AAEN,aAAS,QAAQ,SAAS,MAAM,EAAE,WAAW,MAAM,SAAS,KAAK,CAAC;AAClE,WAAO;AAAA,EACT,GAAG,CAAC,QAAQ,IAAI,CAAC;AACnB;AAIO,MAAM,wBAAwB,MAAY;AAC/C,2BAAyB;AACzB,SAAO;AACT;","names":[]}