@tanstack/router-core 1.171.12 → 1.171.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/router.cjs +2 -4
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +6 -3
- package/dist/cjs/scroll-restoration.cjs +26 -20
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/esm/router.d.ts +6 -3
- package/dist/esm/router.js +2 -4
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +26 -20
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/package.json +1 -1
- package/src/router.ts +7 -4
- package/src/scroll-restoration.ts +39 -29
|
@@ -76,10 +76,10 @@ function getScrollToTopElements(scrollToTopSelectors) {
|
|
|
76
76
|
return elements;
|
|
77
77
|
}
|
|
78
78
|
function setupScrollRestoration(router, force) {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
79
|
+
const shouldSetupScrollRestoration = force ?? router.options.scrollRestoration;
|
|
80
|
+
const scroll = router._scroll;
|
|
81
|
+
if (shouldSetupScrollRestoration) scroll.restoring = true;
|
|
82
|
+
if (isServer ?? router.isServer) return;
|
|
83
83
|
const getKey = router.options.getScrollRestorationKey || defaultGetScrollRestorationKey;
|
|
84
84
|
const trackedScrollEntries = /* @__PURE__ */ new Map();
|
|
85
85
|
const setTrackedScrollEntry = (target, scrollX, scrollY) => {
|
|
@@ -88,9 +88,8 @@ function setupScrollRestoration(router, force) {
|
|
|
88
88
|
entry.scrollY = scrollY;
|
|
89
89
|
trackedScrollEntries.set(target, entry);
|
|
90
90
|
};
|
|
91
|
-
history.scrollRestoration = "manual";
|
|
92
91
|
const onScroll = (event) => {
|
|
93
|
-
if (ignoreScroll || !
|
|
92
|
+
if (ignoreScroll || !scroll.restoring) return;
|
|
94
93
|
if (event.target === document) setTrackedScrollEntry(windowScrollTarget, scrollX, scrollY);
|
|
95
94
|
else {
|
|
96
95
|
const target = event.target;
|
|
@@ -98,31 +97,38 @@ function setupScrollRestoration(router, force) {
|
|
|
98
97
|
}
|
|
99
98
|
};
|
|
100
99
|
const snapshotCurrentScrollTargets = (restoreKey) => {
|
|
101
|
-
if (!
|
|
100
|
+
if (!scroll.restoring) return;
|
|
102
101
|
const keyEntry = scrollRestorationCache[restoreKey] ||= {};
|
|
103
102
|
for (const [target, position] of trackedScrollEntries) if (target === windowScrollTarget) keyEntry[windowScrollTarget] = position;
|
|
104
103
|
else if (target.isConnected) keyEntry[getScrollRestorationSelector(target)] = position;
|
|
105
104
|
};
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
105
|
+
if (shouldSetupScrollRestoration && !scroll.restoration) {
|
|
106
|
+
scroll.restoration = true;
|
|
107
|
+
ignoreScroll = false;
|
|
108
|
+
history.scrollRestoration = "manual";
|
|
109
|
+
document.addEventListener("scroll", onScroll, true);
|
|
110
|
+
router.subscribe("onBeforeLoad", (event) => {
|
|
111
|
+
if (event.fromLocation) snapshotCurrentScrollTargets(getKey(event.fromLocation));
|
|
112
|
+
trackedScrollEntries.clear();
|
|
113
|
+
});
|
|
114
|
+
addEventListener("pagehide", () => {
|
|
115
|
+
snapshotCurrentScrollTargets(getKey(router.stores.resolvedLocation.get() ?? router.stores.location.get()));
|
|
116
|
+
persistScrollRestorationCache();
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
if (scroll.reset) return;
|
|
120
|
+
scroll.reset = true;
|
|
115
121
|
router.subscribe("onRendered", (event) => {
|
|
116
122
|
const behavior = router.options.scrollRestorationBehavior;
|
|
117
123
|
const scrollToTopSelectors = router.options.scrollToTopSelectors;
|
|
118
|
-
const shouldResetScroll =
|
|
124
|
+
const shouldResetScroll = scroll.next;
|
|
119
125
|
let scrollToTopElements;
|
|
120
126
|
trackedScrollEntries.clear();
|
|
121
|
-
if (!shouldResetScroll)
|
|
127
|
+
if (!shouldResetScroll) scroll.next = true;
|
|
122
128
|
if (typeof router.options.scrollRestoration === "function" && !router.options.scrollRestoration({ location: router.latestLocation })) return;
|
|
123
129
|
const cacheKey = getKey(event.toLocation);
|
|
124
130
|
const fromCacheKey = event.fromLocation && getKey(event.fromLocation);
|
|
125
|
-
if (
|
|
131
|
+
if (scroll.restoring && fromCacheKey && fromCacheKey !== cacheKey) {
|
|
126
132
|
const fromElementEntries = scrollRestorationCache[fromCacheKey];
|
|
127
133
|
if (fromElementEntries) {
|
|
128
134
|
let toElementEntries = scrollRestorationCache[cacheKey];
|
|
@@ -150,7 +156,7 @@ function setupScrollRestoration(router, force) {
|
|
|
150
156
|
if (shouldResetScroll) {
|
|
151
157
|
const action = locationHistoryActions.get(event.toLocation);
|
|
152
158
|
const skipWindowRestore = hash && hashScrollIntoViewOptions && (action === "PUSH" || action === "REPLACE");
|
|
153
|
-
const elementEntries =
|
|
159
|
+
const elementEntries = scroll.restoring ? scrollRestorationCache[cacheKey] : void 0;
|
|
154
160
|
if (elementEntries) for (const elementSelector in elementEntries) {
|
|
155
161
|
const { scrollX, scrollY } = elementEntries[elementSelector];
|
|
156
162
|
if (elementSelector === windowScrollTarget) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-restoration.js","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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"}
|
|
1
|
+
{"version":3,"file":"scroll-restoration.js","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 const shouldSetupScrollRestoration = force ?? router.options.scrollRestoration\n const scroll = router._scroll\n\n if (shouldSetupScrollRestoration) {\n scroll.restoring = true\n }\n\n if (isServer ?? router.isServer) {\n return\n }\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 const onScroll = (event: Event) => {\n if (ignoreScroll || !scroll.restoring) {\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 (!scroll.restoring) {\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 if (shouldSetupScrollRestoration && !scroll.restoration) {\n scroll.restoration = true\n ignoreScroll = false\n\n history.scrollRestoration = 'manual'\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\n if (scroll.reset) {\n return\n }\n\n scroll.reset = true\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 = scroll.next\n let scrollToTopElements: Array<Element> | undefined\n trackedScrollEntries.clear()\n\n if (!shouldResetScroll) {\n scroll.next = 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 (scroll.restoring && 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 = scroll.restoring\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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"}
|
package/package.json
CHANGED
package/src/router.ts
CHANGED
|
@@ -964,13 +964,16 @@ export class RouterCore<
|
|
|
964
964
|
tempLocationKey: string | undefined = `${Math.round(
|
|
965
965
|
Math.random() * 10000000,
|
|
966
966
|
)}`
|
|
967
|
-
|
|
967
|
+
_scroll: {
|
|
968
|
+
next: boolean
|
|
969
|
+
restoring?: boolean
|
|
970
|
+
restoration?: boolean
|
|
971
|
+
reset?: boolean
|
|
972
|
+
} = { next: true }
|
|
968
973
|
shouldViewTransition?: boolean | ViewTransitionOptions = undefined
|
|
969
974
|
isViewTransitionTypesSupported?: boolean = undefined
|
|
970
975
|
subscribers = new Set<RouterListener<RouterEvent>>()
|
|
971
976
|
viewTransitionPromise?: ControlledPromise<true>
|
|
972
|
-
isScrollRestoring = false
|
|
973
|
-
isScrollRestorationSetup = false
|
|
974
977
|
|
|
975
978
|
// Must build in constructor
|
|
976
979
|
stores!: RouterStores<TRouteTree>
|
|
@@ -2227,7 +2230,7 @@ export class RouterCore<
|
|
|
2227
2230
|
)
|
|
2228
2231
|
}
|
|
2229
2232
|
|
|
2230
|
-
this.
|
|
2233
|
+
this._scroll.next = next.resetScroll ?? true
|
|
2231
2234
|
|
|
2232
2235
|
if (!this.history.subscribers.size) {
|
|
2233
2236
|
this.load(
|
|
@@ -167,18 +167,17 @@ function getScrollToTopElements(
|
|
|
167
167
|
|
|
168
168
|
export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
169
169
|
// Keep hash/top scrolling active even when sessionStorage is unavailable.
|
|
170
|
+
const shouldSetupScrollRestoration = force ?? router.options.scrollRestoration
|
|
171
|
+
const scroll = router._scroll
|
|
170
172
|
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
+
if (shouldSetupScrollRestoration) {
|
|
174
|
+
scroll.restoring = true
|
|
173
175
|
}
|
|
174
176
|
|
|
175
|
-
if (
|
|
177
|
+
if (isServer ?? router.isServer) {
|
|
176
178
|
return
|
|
177
179
|
}
|
|
178
180
|
|
|
179
|
-
router.isScrollRestorationSetup = true
|
|
180
|
-
ignoreScroll = false
|
|
181
|
-
|
|
182
181
|
const getKey =
|
|
183
182
|
router.options.getScrollRestorationKey || defaultGetScrollRestorationKey
|
|
184
183
|
const trackedScrollEntries = new Map<ScrollTarget, ScrollRestorationEntry>()
|
|
@@ -194,10 +193,8 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
194
193
|
trackedScrollEntries.set(target, entry)
|
|
195
194
|
}
|
|
196
195
|
|
|
197
|
-
history.scrollRestoration = 'manual'
|
|
198
|
-
|
|
199
196
|
const onScroll = (event: Event) => {
|
|
200
|
-
if (ignoreScroll || !
|
|
197
|
+
if (ignoreScroll || !scroll.restoring) {
|
|
201
198
|
return
|
|
202
199
|
}
|
|
203
200
|
|
|
@@ -211,7 +208,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
211
208
|
|
|
212
209
|
// Snapshot the current page's tracked scroll targets before navigation or unload.
|
|
213
210
|
const snapshotCurrentScrollTargets = (restoreKey: string) => {
|
|
214
|
-
if (!
|
|
211
|
+
if (!scroll.restoring) {
|
|
215
212
|
return
|
|
216
213
|
}
|
|
217
214
|
|
|
@@ -227,32 +224,45 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
227
224
|
}
|
|
228
225
|
}
|
|
229
226
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
)
|
|
242
|
-
)
|
|
243
|
-
|
|
244
|
-
|
|
227
|
+
if (shouldSetupScrollRestoration && !scroll.restoration) {
|
|
228
|
+
scroll.restoration = true
|
|
229
|
+
ignoreScroll = false
|
|
230
|
+
|
|
231
|
+
history.scrollRestoration = 'manual'
|
|
232
|
+
|
|
233
|
+
document.addEventListener('scroll', onScroll, true)
|
|
234
|
+
router.subscribe('onBeforeLoad', (event) => {
|
|
235
|
+
if (event.fromLocation) {
|
|
236
|
+
snapshotCurrentScrollTargets(getKey(event.fromLocation))
|
|
237
|
+
}
|
|
238
|
+
trackedScrollEntries.clear()
|
|
239
|
+
})
|
|
240
|
+
addEventListener('pagehide', () => {
|
|
241
|
+
snapshotCurrentScrollTargets(
|
|
242
|
+
getKey(
|
|
243
|
+
router.stores.resolvedLocation.get() ?? router.stores.location.get(),
|
|
244
|
+
),
|
|
245
|
+
)
|
|
246
|
+
persistScrollRestorationCache()
|
|
247
|
+
})
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
if (scroll.reset) {
|
|
251
|
+
return
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
scroll.reset = true
|
|
245
255
|
|
|
246
256
|
// Restore destination scroll after the new route has rendered.
|
|
247
257
|
router.subscribe('onRendered', (event) => {
|
|
248
258
|
const behavior = router.options.scrollRestorationBehavior
|
|
249
259
|
const scrollToTopSelectors = router.options.scrollToTopSelectors
|
|
250
|
-
const shouldResetScroll =
|
|
260
|
+
const shouldResetScroll = scroll.next
|
|
251
261
|
let scrollToTopElements: Array<Element> | undefined
|
|
252
262
|
trackedScrollEntries.clear()
|
|
253
263
|
|
|
254
264
|
if (!shouldResetScroll) {
|
|
255
|
-
|
|
265
|
+
scroll.next = true
|
|
256
266
|
}
|
|
257
267
|
|
|
258
268
|
if (
|
|
@@ -265,7 +275,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
265
275
|
const cacheKey = getKey(event.toLocation)
|
|
266
276
|
const fromCacheKey = event.fromLocation && getKey(event.fromLocation)
|
|
267
277
|
|
|
268
|
-
if (
|
|
278
|
+
if (scroll.restoring && fromCacheKey && fromCacheKey !== cacheKey) {
|
|
269
279
|
const fromElementEntries = scrollRestorationCache[fromCacheKey]
|
|
270
280
|
|
|
271
281
|
if (fromElementEntries) {
|
|
@@ -317,7 +327,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
317
327
|
hashScrollIntoViewOptions &&
|
|
318
328
|
(action === 'PUSH' || action === 'REPLACE')
|
|
319
329
|
|
|
320
|
-
const elementEntries =
|
|
330
|
+
const elementEntries = scroll.restoring
|
|
321
331
|
? scrollRestorationCache[cacheKey]
|
|
322
332
|
: undefined
|
|
323
333
|
|