{"version":3,"file":"scroll-restoration.cjs","names":[],"sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { isServer } from '@tanstack/router-core/isServer'\nimport { locationHistoryActions } from './router'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\ntype ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\ntype ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationOptions = {\n  getKey?: (location: ParsedLocation) => string\n  scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n  try {\n    // Accessing sessionStorage itself can throw SecurityError in locked-down\n    // contexts, e.g. sandboxed/opaque origins or blocked storage policies.\n    return sessionStorage\n  } catch {\n    return\n  }\n}\n\n// SessionStorage key used to store scroll positions across navigations.\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\nconst safeSessionStorage = getSafeSessionStorage()\n\nfunction createScrollRestorationCache() {\n  try {\n    return JSON.parse(\n      safeSessionStorage?.getItem(storageKey) || '{}',\n    ) as ScrollRestorationByKey\n  } catch {\n    // ignore invalid session storage payloads\n    return {}\n  }\n}\n\nfunction persistScrollRestorationCache() {\n  try {\n    safeSessionStorage?.setItem(\n      storageKey,\n      JSON.stringify(scrollRestorationCache),\n    )\n  } catch {\n    if (process.env.NODE_ENV !== 'production') {\n      console.warn(\n        '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n      )\n    }\n  }\n}\n\nconst scrollRestorationCache = /* @__PURE__ */ createScrollRestorationCache()\nconst scrollRestorationIdAttribute = 'data-scroll-restoration-id'\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n  return location.state.__TSR_key! || location.href\n}\n\nfunction getScrollRestorationSelector(element: Element): string {\n  const attrId = element.getAttribute(scrollRestorationIdAttribute)\n  if (attrId) {\n    return `[${scrollRestorationIdAttribute}=\"${attrId}\"]`\n  }\n\n  let selector = ''\n  let el: any = element\n  let parent: HTMLElement\n\n  while ((parent = el.parentNode)) {\n    let index = 1\n    let sibling = el\n    while ((sibling = sibling.previousElementSibling)) {\n      index++\n    }\n\n    const part = `${el.localName}:nth-child(${index})`\n    selector = selector ? `${part} > ${selector}` : part\n    el = parent\n  }\n\n  return selector\n}\n\nexport function getElementScrollRestorationEntry(\n  router: AnyRouter,\n  options: (\n    | {\n        id: string\n        getElement?: () => Window | Element | undefined | null\n      }\n    | {\n        id?: string\n        getElement: () => Window | Element | undefined | null\n      }\n  ) & {\n    getKey?: (location: ParsedLocation) => string\n  },\n): ScrollRestorationEntry | undefined {\n  const getKey = options.getKey || defaultGetScrollRestorationKey\n  const restoreKey = getKey(router.latestLocation)\n  const entries = scrollRestorationCache[restoreKey]\n\n  if (!entries) {\n    return\n  }\n\n  if (options.id) {\n    return entries[`[${scrollRestorationIdAttribute}=\"${options.id}\"]`]\n  }\n\n  const element = options.getElement?.()\n  if (!element) {\n    return\n  }\n\n  return entries[\n    element === window\n      ? windowScrollTarget\n      : getScrollRestorationSelector(element as Element)\n  ]\n}\n\nlet ignoreScroll = false\nconst windowScrollTarget = 'window'\ntype ScrollTarget = typeof windowScrollTarget | Element\n\nfunction getElement(selector: string | (() => Element | null | undefined)) {\n  try {\n    return typeof selector === 'function'\n      ? selector()\n      : document.querySelector(selector)\n  } catch {}\n  return\n}\n\nfunction getScrollToTopElements(\n  scrollToTopSelectors: NonNullable<\n    AnyRouter['options']['scrollToTopSelectors']\n  >,\n): Array<Element> {\n  const elements: Array<Element> = []\n\n  for (const selector of scrollToTopSelectors) {\n    if (selector === windowScrollTarget) {\n      continue\n    }\n\n    const element = getElement(selector)\n    if (element) {\n      elements.push(element)\n    }\n  }\n\n  return elements\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n  // Keep hash/top scrolling active even when sessionStorage is unavailable.\n\n  if (force ?? router.options.scrollRestoration) {\n    router.isScrollRestoring = true\n  }\n\n  if ((isServer ?? router.isServer) || router.isScrollRestorationSetup) {\n    return\n  }\n\n  router.isScrollRestorationSetup = true\n  ignoreScroll = false\n\n  const getKey =\n    router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n  const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()\n  const setTrackedScrollEntry = (\n    target: ScrollTarget,\n    scrollX: number,\n    scrollY: number,\n  ) => {\n    const entry =\n      trackedScrollEntries.get(target) || ({} as ScrollRestorationEntry)\n    entry.scrollX = scrollX\n    entry.scrollY = scrollY\n    trackedScrollEntries.set(target, entry)\n  }\n\n  history.scrollRestoration = 'manual'\n\n  const onScroll = (event: Event) => {\n    if (ignoreScroll || !router.isScrollRestoring) {\n      return\n    }\n\n    if (event.target === document) {\n      setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY)\n    } else {\n      const target = event.target as Element\n      setTrackedScrollEntry(target, target.scrollLeft, target.scrollTop)\n    }\n  }\n\n  // Snapshot the current page's tracked scroll targets before navigation or unload.\n  const snapshotCurrentScrollTargets = (restoreKey: string) => {\n    if (!router.isScrollRestoring) {\n      return\n    }\n\n    const keyEntry = (scrollRestorationCache[restoreKey] ||=\n      {} as ScrollRestorationByElement)\n\n    for (const [target, position] of trackedScrollEntries) {\n      if (target === windowScrollTarget) {\n        keyEntry[windowScrollTarget] = position\n      } else if (target.isConnected) {\n        keyEntry[getScrollRestorationSelector(target)] = position\n      }\n    }\n  }\n\n  document.addEventListener('scroll', onScroll, true)\n  router.subscribe('onBeforeLoad', (event) => {\n    if (event.fromLocation) {\n      snapshotCurrentScrollTargets(getKey(event.fromLocation))\n    }\n    trackedScrollEntries.clear()\n  })\n  addEventListener('pagehide', () => {\n    snapshotCurrentScrollTargets(\n      getKey(\n        router.stores.resolvedLocation.get() ?? router.stores.location.get(),\n      ),\n    )\n    persistScrollRestorationCache()\n  })\n\n  // Restore destination scroll after the new route has rendered.\n  router.subscribe('onRendered', (event) => {\n    const behavior = router.options.scrollRestorationBehavior\n    const scrollToTopSelectors = router.options.scrollToTopSelectors\n    const shouldResetScroll = router.resetNextScroll\n    let scrollToTopElements: Array<Element> | undefined\n    trackedScrollEntries.clear()\n\n    if (!shouldResetScroll) {\n      router.resetNextScroll = true\n    }\n\n    if (\n      typeof router.options.scrollRestoration === 'function' &&\n      !router.options.scrollRestoration({ location: router.latestLocation })\n    ) {\n      return\n    }\n\n    const cacheKey = getKey(event.toLocation)\n    const fromCacheKey = event.fromLocation && getKey(event.fromLocation)\n\n    if (router.isScrollRestoring && fromCacheKey && fromCacheKey !== cacheKey) {\n      const fromElementEntries = scrollRestorationCache[fromCacheKey]\n\n      if (fromElementEntries) {\n        let toElementEntries = scrollRestorationCache[cacheKey]\n\n        for (const elementSelector in fromElementEntries) {\n          if (elementSelector === windowScrollTarget) {\n            if (shouldResetScroll) {\n              continue\n            }\n          } else {\n            const element = getElement(elementSelector)\n            if (!element) {\n              continue\n            }\n\n            if (shouldResetScroll && scrollToTopSelectors) {\n              scrollToTopElements ??=\n                getScrollToTopElements(scrollToTopSelectors)\n              if (scrollToTopElements.includes(element)) {\n                continue\n              }\n            }\n          }\n\n          if (!toElementEntries) {\n            toElementEntries = scrollRestorationCache[cacheKey] =\n              {} as ScrollRestorationByElement\n          }\n\n          toElementEntries[elementSelector] ??=\n            fromElementEntries[elementSelector]!\n        }\n      }\n    }\n\n    ignoreScroll = true\n\n    try {\n      const hash = event.toLocation.hash\n      const hashScrollIntoViewOptions =\n        event.toLocation.state.__hashScrollIntoViewOptions ?? true\n      let windowRestored = false\n\n      if (shouldResetScroll) {\n        const action = locationHistoryActions.get(event.toLocation)\n        const skipWindowRestore =\n          hash &&\n          hashScrollIntoViewOptions &&\n          (action === 'PUSH' || action === 'REPLACE')\n\n        const elementEntries = router.isScrollRestoring\n          ? scrollRestorationCache[cacheKey]\n          : undefined\n\n        if (elementEntries) {\n          for (const elementSelector in elementEntries) {\n            const { scrollX, scrollY } = elementEntries[elementSelector]!\n\n            if (elementSelector === windowScrollTarget) {\n              if (skipWindowRestore) {\n                continue\n              }\n\n              scrollTo({\n                top: scrollY,\n                left: scrollX,\n                behavior,\n              })\n              windowRestored = true\n            } else {\n              const element = getElement(elementSelector)\n              if (element) {\n                element.scrollLeft = scrollX\n                element.scrollTop = scrollY\n              }\n            }\n          }\n        }\n\n        if (!windowRestored && !hash) {\n          const scrollOptions = {\n            top: 0,\n            left: 0,\n            behavior,\n          }\n\n          scrollTo(scrollOptions)\n          if (scrollToTopSelectors) {\n            scrollToTopElements ??= getScrollToTopElements(scrollToTopSelectors)\n            for (const element of scrollToTopElements) {\n              element.scrollTo(scrollOptions)\n            }\n          }\n        }\n      }\n\n      if (!windowRestored && hash && hashScrollIntoViewOptions) {\n        document.getElementById(hash)?.scrollIntoView(hashScrollIntoViewOptions)\n      }\n    } finally {\n      ignoreScroll = false\n    }\n  })\n}\n"],"mappings":"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"}