{"version":3,"sources":["../src/scrollRestore.ts"],"sourcesContent":["// Deciding WHEN a remembered scroll offset may be applied after a back/forward\n// traversal (R3-627). Pure: no DOM, no React, no timers — the caller samples the\n// geometry and this says what to do with the sample.\n//\n// The failure this exists to prevent: an app's document grows as it renders, so an\n// offset applied on arrival is clamped to whatever height exists at that instant and\n// the reader lands near the top — the very defect the feature is meant to remove. So\n// the restore waits until the content is tall enough to hold the offset, and gives up\n// rather than fighting either the clock or the reader.\n\n/** A geometry sample of the scroller, plus how the attempt is going. */\nexport interface RestoreSample {\n  /** The remembered offset we are trying to reach. */\n  target: number;\n  /** The scroller's full scrollable height right now. */\n  scrollHeight: number;\n  /** The scroller's visible height right now. */\n  clientHeight: number;\n  /** Where the scroller is right now. */\n  current: number;\n  /** Milliseconds since the restore began. */\n  elapsedMs: number;\n  /** Whether the reader has scrolled since the restore began. */\n  userScrolled: boolean;\n}\n\n/** What the caller should do with this sample. */\nexport type RestoreAction = 'apply' | 'wait' | 'abandon';\n\n/** How long to keep waiting for the content to grow before giving up. Matches the\n *  give-up window `ScrollAfterNavigation` already uses for fragments, so the two\n *  navigation-scroll behaviours settle on the same timescale. */\nexport const RESTORE_DEADLINE_MS = 900;\n\n/** How close counts as arrived. Sub-pixel differences and fractional device pixels\n *  must not keep a restore looping. */\nexport const RESTORE_EPSILON_PX = 2;\n\n/**\n * The one decision, given a sample.\n *\n * - `abandon` — the reader has taken over, or the deadline passed. Never fight a\n *   user, and never scroll a page they have already started reading.\n * - `apply` — the content can hold the offset (or the offset is already reached,\n *   within {@link RESTORE_EPSILON_PX}); scroll and finish.\n * - `wait` — the content is still too short; sample again.\n *\n * A non-finite or negative target is treated as nothing to restore (`abandon`), so a\n * corrupt scratch value can never move the page.\n */\nexport const nextRestoreAction = (sample: RestoreSample): RestoreAction => {\n  const { target, scrollHeight, clientHeight, current, elapsedMs, userScrolled } = sample;\n  if (!Number.isFinite(target) || target <= 0) return 'abandon';\n  if (userScrolled) return 'abandon';\n  if (Math.abs(current - target) <= RESTORE_EPSILON_PX) return 'apply';\n  if (elapsedMs >= RESTORE_DEADLINE_MS) return 'abandon';\n  // The furthest this scroller can currently reach. Applying before the content is\n  // this tall is what clamps the reader to the top.\n  const reachable = Math.max(0, scrollHeight - clientHeight);\n  return reachable >= target ? 'apply' : 'wait';\n};\n"],"mappings":";;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAgCO,MAAM,sBAAsB;AAI5B,MAAM,qBAAqB;AAc3B,MAAM,oBAAoB,CAAC,WAAyC;AACzE,QAAM,EAAE,QAAQ,cAAc,cAAc,SAAS,WAAW,aAAa,IAAI;AACjF,MAAI,CAAC,OAAO,SAAS,MAAM,KAAK,UAAU,EAAG,QAAO;AACpD,MAAI,aAAc,QAAO;AACzB,MAAI,KAAK,IAAI,UAAU,MAAM,KAAK,mBAAoB,QAAO;AAC7D,MAAI,aAAa,oBAAqB,QAAO;AAG7C,QAAM,YAAY,KAAK,IAAI,GAAG,eAAe,YAAY;AACzD,SAAO,aAAa,SAAS,UAAU;AACzC;","names":[]}