@tanstack/router-core 1.131.25 → 1.131.27
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/load-matches.cjs +8 -23
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/qss.cjs +19 -19
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/qss.d.cts +6 -4
- package/dist/cjs/redirect.cjs +3 -3
- package/dist/cjs/redirect.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +21 -26
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +7 -15
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/esm/load-matches.js +8 -23
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/qss.d.ts +6 -4
- package/dist/esm/qss.js +19 -19
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/redirect.js +3 -3
- package/dist/esm/redirect.js.map +1 -1
- package/dist/esm/scroll-restoration.js +21 -26
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +7 -15
- package/dist/esm/searchParams.js.map +1 -1
- package/package.json +1 -1
- package/src/load-matches.ts +12 -29
- package/src/qss.ts +27 -24
- package/src/redirect.ts +3 -3
- package/src/scroll-restoration.ts +25 -32
- package/src/searchParams.ts +8 -19
|
@@ -5,7 +5,6 @@ function getSafeSessionStorage() {
|
|
|
5
5
|
return window.sessionStorage;
|
|
6
6
|
}
|
|
7
7
|
} catch {
|
|
8
|
-
return void 0;
|
|
9
8
|
}
|
|
10
9
|
return void 0;
|
|
11
10
|
}
|
|
@@ -44,12 +43,12 @@ function getCssSelector(el) {
|
|
|
44
43
|
const path = [];
|
|
45
44
|
let parent;
|
|
46
45
|
while (parent = el.parentNode) {
|
|
47
|
-
path.
|
|
48
|
-
`${el.tagName}:nth-child(${
|
|
46
|
+
path.push(
|
|
47
|
+
`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`
|
|
49
48
|
);
|
|
50
49
|
el = parent;
|
|
51
50
|
}
|
|
52
|
-
return `${path.join(" > ")}`.toLowerCase();
|
|
51
|
+
return `${path.reverse().join(" > ")}`.toLowerCase();
|
|
53
52
|
}
|
|
54
53
|
let ignoreScroll = false;
|
|
55
54
|
function restoreScroll({
|
|
@@ -60,7 +59,7 @@ function restoreScroll({
|
|
|
60
59
|
scrollToTopSelectors,
|
|
61
60
|
location
|
|
62
61
|
}) {
|
|
63
|
-
var _a;
|
|
62
|
+
var _a, _b;
|
|
64
63
|
let byKey;
|
|
65
64
|
try {
|
|
66
65
|
byKey = JSON.parse(sessionStorage.getItem(storageKey2) || "{}");
|
|
@@ -71,7 +70,7 @@ function restoreScroll({
|
|
|
71
70
|
const resolvedKey = key || ((_a = window.history.state) == null ? void 0 : _a.key);
|
|
72
71
|
const elementEntries = byKey[resolvedKey];
|
|
73
72
|
ignoreScroll = true;
|
|
74
|
-
|
|
73
|
+
scroll: {
|
|
75
74
|
if (shouldScrollRestoration && elementEntries && Object.keys(elementEntries).length > 0) {
|
|
76
75
|
for (const elementSelector in elementEntries) {
|
|
77
76
|
const entry = elementEntries[elementSelector];
|
|
@@ -89,33 +88,29 @@ function restoreScroll({
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
|
-
|
|
91
|
+
break scroll;
|
|
93
92
|
}
|
|
94
|
-
const hash = (location ?? window.location).hash.split("#")[1];
|
|
93
|
+
const hash = (location ?? window.location).hash.split("#", 2)[1];
|
|
95
94
|
if (hash) {
|
|
96
|
-
const hashScrollIntoViewOptions = (window.history.state
|
|
95
|
+
const hashScrollIntoViewOptions = ((_b = window.history.state) == null ? void 0 : _b.__hashScrollIntoViewOptions) ?? true;
|
|
97
96
|
if (hashScrollIntoViewOptions) {
|
|
98
97
|
const el = document.getElementById(hash);
|
|
99
98
|
if (el) {
|
|
100
99
|
el.scrollIntoView(hashScrollIntoViewOptions);
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
|
-
|
|
102
|
+
break scroll;
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
element.scrollTo(
|
|
112
|
-
top: 0,
|
|
113
|
-
left: 0,
|
|
114
|
-
behavior
|
|
115
|
-
});
|
|
104
|
+
const scrollOptions = { top: 0, left: 0, behavior };
|
|
105
|
+
window.scrollTo(scrollOptions);
|
|
106
|
+
if (scrollToTopSelectors) {
|
|
107
|
+
for (const selector of scrollToTopSelectors) {
|
|
108
|
+
if (selector === "window") continue;
|
|
109
|
+
const element = typeof selector === "function" ? selector() : document.querySelector(selector);
|
|
110
|
+
if (element) element.scrollTo(scrollOptions);
|
|
116
111
|
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
119
114
|
ignoreScroll = false;
|
|
120
115
|
}
|
|
121
116
|
function setupScrollRestoration(router, force) {
|
|
@@ -152,8 +147,8 @@ function setupScrollRestoration(router, force) {
|
|
|
152
147
|
}
|
|
153
148
|
const restoreKey = getKey(router.state.location);
|
|
154
149
|
scrollRestorationCache.set((state) => {
|
|
155
|
-
const keyEntry = state[restoreKey]
|
|
156
|
-
const elementEntry = keyEntry[elementSelector]
|
|
150
|
+
const keyEntry = state[restoreKey] || (state[restoreKey] = {});
|
|
151
|
+
const elementEntry = keyEntry[elementSelector] || (keyEntry[elementSelector] = {});
|
|
157
152
|
if (elementSelector === "window") {
|
|
158
153
|
elementEntry.scrollX = window.scrollX || 0;
|
|
159
154
|
elementEntry.scrollY = window.scrollY || 0;
|
|
@@ -186,7 +181,7 @@ function setupScrollRestoration(router, force) {
|
|
|
186
181
|
});
|
|
187
182
|
if (router.isScrollRestoring) {
|
|
188
183
|
scrollRestorationCache.set((state) => {
|
|
189
|
-
state[cacheKey]
|
|
184
|
+
state[cacheKey] || (state[cacheKey] = {});
|
|
190
185
|
return state;
|
|
191
186
|
});
|
|
192
187
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-restoration.js","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n return undefined\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | undefined {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return undefined\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\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 */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function getCssSelector(el: any): string {\n const path = []\n let parent\n while ((parent = el.parentNode)) {\n path.unshift(\n `${el.tagName}:nth-child(${([].indexOf as any).call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.join(' > ')}`.toLowerCase()\n}\n\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error: any) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n ;(() => {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n return\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#')[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n (window.history.state || {}).__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n ;[\n 'window',\n ...(scrollToTopSelectors?.filter((d) => d !== 'window') ?? []),\n ].forEach((selector) => {\n const element =\n selector === 'window'\n ? window\n : typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo({\n top: 0,\n left: 0,\n behavior,\n })\n }\n })\n })()\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (scrollRestorationCache === undefined) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (typeof document === 'undefined' || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] =\n state[restoreKey] || ({} as ScrollRestorationByElement))\n\n const elementEntry = (keyEntry[elementSelector] =\n keyEntry[elementSelector] || ({} as ScrollRestorationEntry))\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] = state[cacheKey] || ({} as ScrollRestorationByElement)\n\n return state\n })\n }\n })\n}\n\n/**\n * @internal\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["storageKey"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"scroll-restoration.js","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n // silent\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | undefined {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return undefined\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\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 */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n scroll: {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n break scroll\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n break scroll\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n const scrollOptions = { top: 0, left: 0, behavior }\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === 'window') continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) element.scrollTo(scrollOptions)\n }\n }\n }\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (scrollRestorationCache === undefined) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (typeof document === 'undefined' || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)\n\n const elementEntry = (keyEntry[elementSelector] ||=\n {} as ScrollRestorationEntry)\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n\n return state\n })\n }\n })\n}\n\n/**\n * @internal\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["storageKey"],"mappings":"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"}
|
package/dist/esm/searchParams.js
CHANGED
|
@@ -6,7 +6,7 @@ const defaultStringifySearch = stringifySearchWith(
|
|
|
6
6
|
);
|
|
7
7
|
function parseSearchWith(parser) {
|
|
8
8
|
return (searchStr) => {
|
|
9
|
-
if (searchStr
|
|
9
|
+
if (searchStr[0] === "?") {
|
|
10
10
|
searchStr = searchStr.substring(1);
|
|
11
11
|
}
|
|
12
12
|
const query = decode(searchStr);
|
|
@@ -15,7 +15,7 @@ function parseSearchWith(parser) {
|
|
|
15
15
|
if (typeof value === "string") {
|
|
16
16
|
try {
|
|
17
17
|
query[key] = parser(value);
|
|
18
|
-
} catch (
|
|
18
|
+
} catch (_err) {
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -23,32 +23,24 @@ function parseSearchWith(parser) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function stringifySearchWith(stringify, parser) {
|
|
26
|
+
const hasParser = typeof parser === "function";
|
|
26
27
|
function stringifyValue(val) {
|
|
27
28
|
if (typeof val === "object" && val !== null) {
|
|
28
29
|
try {
|
|
29
30
|
return stringify(val);
|
|
30
|
-
} catch (
|
|
31
|
+
} catch (_err) {
|
|
31
32
|
}
|
|
32
|
-
} else if (
|
|
33
|
+
} else if (hasParser && typeof val === "string") {
|
|
33
34
|
try {
|
|
34
35
|
parser(val);
|
|
35
36
|
return stringify(val);
|
|
36
|
-
} catch (
|
|
37
|
+
} catch (_err) {
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
return val;
|
|
40
41
|
}
|
|
41
42
|
return (search) => {
|
|
42
|
-
|
|
43
|
-
Object.keys(search).forEach((key) => {
|
|
44
|
-
const val = search[key];
|
|
45
|
-
if (typeof val === "undefined" || val === void 0) {
|
|
46
|
-
delete search[key];
|
|
47
|
-
} else {
|
|
48
|
-
search[key] = stringifyValue(val);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
const searchStr = encode(search).toString();
|
|
43
|
+
const searchStr = encode(search, stringifyValue);
|
|
52
44
|
return searchStr ? `?${searchStr}` : "";
|
|
53
45
|
};
|
|
54
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchParams.js","sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr
|
|
1
|
+
{"version":3,"file":"searchParams.js","sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const hasParser = typeof parser === 'function'\n function stringifyValue(val: any) {\n if (typeof val === 'object' && val !== null) {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (hasParser && typeof val === 'string') {\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"names":[],"mappings":";AAGa,MAAA,qBAAqB,gBAAgB,KAAK,KAAK;AACrD,MAAM,yBAAyB;AAAA,EACpC,KAAK;AAAA,EACL,KAAK;AACP;AAEO,SAAS,gBAAgB,QAA8B;AAC5D,SAAO,CAAC,cAAiC;AACnC,QAAA,UAAU,CAAC,MAAM,KAAK;AACZ,kBAAA,UAAU,UAAU,CAAC;AAAA,IAAA;AAG7B,UAAA,QAAiC,OAAO,SAAS;AAGvD,eAAW,OAAO,OAAO;AACjB,YAAA,QAAQ,MAAM,GAAG;AACnB,UAAA,OAAO,UAAU,UAAU;AACzB,YAAA;AACI,gBAAA,GAAG,IAAI,OAAO,KAAK;AAAA,iBAClB,MAAM;AAAA,QAAA;AAAA,MAEf;AAAA,IACF;AAGK,WAAA;AAAA,EACT;AACF;AAEgB,SAAA,oBACd,WACA,QACA;AACM,QAAA,YAAY,OAAO,WAAW;AACpC,WAAS,eAAe,KAAU;AAChC,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AACvC,UAAA;AACF,eAAO,UAAU,GAAG;AAAA,eACb,MAAM;AAAA,MAAA;AAAA,IAGN,WAAA,aAAa,OAAO,QAAQ,UAAU;AAC3C,UAAA;AAGF,eAAO,GAAG;AACV,eAAO,UAAU,GAAG;AAAA,eACb,MAAM;AAAA,MAAA;AAAA,IAEf;AAEK,WAAA;AAAA,EAAA;AAGT,SAAO,CAAC,WAAgC;AAChC,UAAA,YAAY,OAAO,QAAQ,cAAc;AACxC,WAAA,YAAY,IAAI,SAAS,KAAK;AAAA,EACvC;AACF;"}
|
package/package.json
CHANGED
package/src/load-matches.ts
CHANGED
|
@@ -147,11 +147,10 @@ const shouldSkipLoader = (
|
|
|
147
147
|
return true
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
if (inner.router.isServer) {
|
|
151
|
-
|
|
152
|
-
return true
|
|
153
|
-
}
|
|
150
|
+
if (inner.router.isServer && match.ssr === false) {
|
|
151
|
+
return true
|
|
154
152
|
}
|
|
153
|
+
|
|
155
154
|
return false
|
|
156
155
|
}
|
|
157
156
|
|
|
@@ -302,11 +301,11 @@ const setupPendingTimeout = (
|
|
|
302
301
|
}
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
const
|
|
304
|
+
const preBeforeLoadSetup = (
|
|
306
305
|
inner: InnerLoadContext,
|
|
307
306
|
matchId: string,
|
|
308
307
|
route: AnyRoute,
|
|
309
|
-
):
|
|
308
|
+
): void | Promise<void> => {
|
|
310
309
|
const existingMatch = inner.router.getMatch(matchId)!
|
|
311
310
|
|
|
312
311
|
// If we are in the middle of a load, either of these will be present
|
|
@@ -315,25 +314,21 @@ const shouldExecuteBeforeLoad = (
|
|
|
315
314
|
!existingMatch._nonReactive.beforeLoadPromise &&
|
|
316
315
|
!existingMatch._nonReactive.loaderPromise
|
|
317
316
|
)
|
|
318
|
-
return
|
|
317
|
+
return
|
|
319
318
|
|
|
320
319
|
setupPendingTimeout(inner, matchId, route, existingMatch)
|
|
321
320
|
|
|
322
321
|
const then = () => {
|
|
323
|
-
let result = true
|
|
324
322
|
const match = inner.router.getMatch(matchId)!
|
|
325
|
-
if (
|
|
326
|
-
result = true
|
|
327
|
-
} else if (
|
|
323
|
+
if (
|
|
328
324
|
match.preload &&
|
|
329
325
|
(match.status === 'redirected' || match.status === 'notFound')
|
|
330
326
|
) {
|
|
331
327
|
handleRedirectAndNotFound(inner, match, match.error)
|
|
332
328
|
}
|
|
333
|
-
return result
|
|
334
329
|
}
|
|
335
330
|
|
|
336
|
-
// Wait for the beforeLoad to resolve before we continue
|
|
331
|
+
// Wait for the previous beforeLoad to resolve before we continue
|
|
337
332
|
return existingMatch._nonReactive.beforeLoadPromise
|
|
338
333
|
? existingMatch._nonReactive.beforeLoadPromise.then(then)
|
|
339
334
|
: then()
|
|
@@ -494,24 +489,12 @@ const handleBeforeLoad = (
|
|
|
494
489
|
|
|
495
490
|
const queueExecution = () => {
|
|
496
491
|
if (shouldSkipLoader(inner, matchId)) return
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
matchId,
|
|
500
|
-
route,
|
|
501
|
-
)
|
|
502
|
-
return isPromise(shouldExecuteBeforeLoadResult)
|
|
503
|
-
? shouldExecuteBeforeLoadResult.then(execute)
|
|
504
|
-
: execute(shouldExecuteBeforeLoadResult)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const execute = (shouldExec: boolean) => {
|
|
508
|
-
if (shouldExec) {
|
|
509
|
-
// If we are not in the middle of a load OR the previous load failed, start it
|
|
510
|
-
return executeBeforeLoad(inner, matchId, index, route)
|
|
511
|
-
}
|
|
512
|
-
return
|
|
492
|
+
const result = preBeforeLoadSetup(inner, matchId, route)
|
|
493
|
+
return isPromise(result) ? result.then(execute) : execute()
|
|
513
494
|
}
|
|
514
495
|
|
|
496
|
+
const execute = () => executeBeforeLoad(inner, matchId, index, route)
|
|
497
|
+
|
|
515
498
|
return serverSsr()
|
|
516
499
|
}
|
|
517
500
|
|
package/src/qss.ts
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* This reimplementation uses modern browser APIs
|
|
7
7
|
* (namely URLSearchParams) and TypeScript while still
|
|
8
8
|
* maintaining the original functionality and interface.
|
|
9
|
+
*
|
|
10
|
+
* Update: this implementation has also been mangled to
|
|
11
|
+
* fit exactly our use-case (single value per key in encoding).
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Encodes an object into a query string.
|
|
13
16
|
* @param obj - The object to encode into a query string.
|
|
14
|
-
* @param
|
|
17
|
+
* @param stringify - An optional custom stringify function.
|
|
15
18
|
* @returns The encoded query string.
|
|
16
19
|
* @example
|
|
17
20
|
* ```
|
|
@@ -19,18 +22,20 @@
|
|
|
19
22
|
* // Expected output: "token=foo&key=value"
|
|
20
23
|
* ```
|
|
21
24
|
*/
|
|
22
|
-
export function encode(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return [[key, String(value)]]
|
|
28
|
-
}
|
|
29
|
-
})
|
|
25
|
+
export function encode(
|
|
26
|
+
obj: Record<string, any>,
|
|
27
|
+
stringify: (value: any) => string = String,
|
|
28
|
+
): string {
|
|
29
|
+
const result = new URLSearchParams()
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
for (const key in obj) {
|
|
32
|
+
const val = obj[key]
|
|
33
|
+
if (val !== undefined) {
|
|
34
|
+
result.set(key, stringify(val))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
32
37
|
|
|
33
|
-
return
|
|
38
|
+
return result.toString()
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -52,28 +57,26 @@ function toValue(str: unknown) {
|
|
|
52
57
|
/**
|
|
53
58
|
* Decodes a query string into an object.
|
|
54
59
|
* @param str - The query string to decode.
|
|
55
|
-
* @param [pfx] - An optional prefix to filter out from the query string.
|
|
56
60
|
* @returns The decoded key-value pairs in an object format.
|
|
57
61
|
* @example
|
|
58
62
|
* // Example input: decode("token=foo&key=value")
|
|
59
63
|
* // Expected output: { "token": "foo", "key": "value" }
|
|
60
64
|
*/
|
|
61
|
-
export function decode(str: any
|
|
62
|
-
const
|
|
63
|
-
const searchParams = new URLSearchParams(searchParamsPart)
|
|
65
|
+
export function decode(str: any): any {
|
|
66
|
+
const searchParams = new URLSearchParams(str)
|
|
64
67
|
|
|
65
|
-
const
|
|
68
|
+
const result: Record<string, unknown> = {}
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
const previousValue =
|
|
70
|
+
for (const [key, value] of searchParams.entries()) {
|
|
71
|
+
const previousValue = result[key]
|
|
69
72
|
if (previousValue == null) {
|
|
70
|
-
|
|
73
|
+
result[key] = toValue(value)
|
|
74
|
+
} else if (Array.isArray(previousValue)) {
|
|
75
|
+
previousValue.push(toValue(value))
|
|
71
76
|
} else {
|
|
72
|
-
|
|
73
|
-
? [...previousValue, toValue(value)]
|
|
74
|
-
: [previousValue, toValue(value)]
|
|
77
|
+
result[key] = [previousValue, toValue(value)]
|
|
75
78
|
}
|
|
79
|
+
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
}, {})
|
|
81
|
+
return result
|
|
79
82
|
}
|
package/src/redirect.ts
CHANGED
|
@@ -65,9 +65,9 @@ export function redirect<
|
|
|
65
65
|
): Redirect<TRouter, TFrom, TTo, TMaskFrom, TMaskTo> {
|
|
66
66
|
opts.statusCode = opts.statusCode || opts.code || 307
|
|
67
67
|
|
|
68
|
-
if (!opts.reloadDocument) {
|
|
68
|
+
if (!opts.reloadDocument && typeof opts.href === 'string') {
|
|
69
69
|
try {
|
|
70
|
-
new URL(
|
|
70
|
+
new URL(opts.href)
|
|
71
71
|
opts.reloadDocument = true
|
|
72
72
|
} catch {}
|
|
73
73
|
}
|
|
@@ -103,7 +103,7 @@ export function isResolvedRedirect(
|
|
|
103
103
|
}
|
|
104
104
|
|
|
105
105
|
export function parseRedirect(obj: any) {
|
|
106
|
-
if (typeof obj === 'object' && obj.isSerializedRedirect) {
|
|
106
|
+
if (obj !== null && typeof obj === 'object' && obj.isSerializedRedirect) {
|
|
107
107
|
return redirect(obj)
|
|
108
108
|
}
|
|
109
109
|
|
|
@@ -28,7 +28,7 @@ function getSafeSessionStorage() {
|
|
|
28
28
|
return window.sessionStorage
|
|
29
29
|
}
|
|
30
30
|
} catch {
|
|
31
|
-
|
|
31
|
+
// silent
|
|
32
32
|
}
|
|
33
33
|
return undefined
|
|
34
34
|
}
|
|
@@ -85,14 +85,14 @@ export const defaultGetScrollRestorationKey = (location: ParsedLocation) => {
|
|
|
85
85
|
|
|
86
86
|
export function getCssSelector(el: any): string {
|
|
87
87
|
const path = []
|
|
88
|
-
let parent
|
|
88
|
+
let parent: HTMLElement
|
|
89
89
|
while ((parent = el.parentNode)) {
|
|
90
|
-
path.
|
|
91
|
-
`${el.tagName}:nth-child(${
|
|
90
|
+
path.push(
|
|
91
|
+
`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,
|
|
92
92
|
)
|
|
93
93
|
el = parent
|
|
94
94
|
}
|
|
95
|
-
return `${path.join(' > ')}`.toLowerCase()
|
|
95
|
+
return `${path.reverse().join(' > ')}`.toLowerCase()
|
|
96
96
|
}
|
|
97
97
|
|
|
98
98
|
let ignoreScroll = false
|
|
@@ -120,7 +120,7 @@ export function restoreScroll({
|
|
|
120
120
|
|
|
121
121
|
try {
|
|
122
122
|
byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')
|
|
123
|
-
} catch (error
|
|
123
|
+
} catch (error) {
|
|
124
124
|
console.error(error)
|
|
125
125
|
return
|
|
126
126
|
}
|
|
@@ -132,7 +132,7 @@ export function restoreScroll({
|
|
|
132
132
|
ignoreScroll = true
|
|
133
133
|
|
|
134
134
|
//
|
|
135
|
-
|
|
135
|
+
scroll: {
|
|
136
136
|
// If we have a cached entry for this location state,
|
|
137
137
|
// we always need to prefer that over the hash scroll.
|
|
138
138
|
if (
|
|
@@ -157,18 +157,18 @@ export function restoreScroll({
|
|
|
157
157
|
}
|
|
158
158
|
}
|
|
159
159
|
|
|
160
|
-
|
|
160
|
+
break scroll
|
|
161
161
|
}
|
|
162
162
|
|
|
163
163
|
// If we don't have a cached entry for the hash,
|
|
164
164
|
// Which means we've never seen this location before,
|
|
165
165
|
// we need to check if there is a hash in the URL.
|
|
166
166
|
// If there is, we need to scroll it's ID into view.
|
|
167
|
-
const hash = (location ?? window.location).hash.split('#')[1]
|
|
167
|
+
const hash = (location ?? window.location).hash.split('#', 2)[1]
|
|
168
168
|
|
|
169
169
|
if (hash) {
|
|
170
170
|
const hashScrollIntoViewOptions =
|
|
171
|
-
|
|
171
|
+
window.history.state?.__hashScrollIntoViewOptions ?? true
|
|
172
172
|
|
|
173
173
|
if (hashScrollIntoViewOptions) {
|
|
174
174
|
const el = document.getElementById(hash)
|
|
@@ -177,30 +177,24 @@ export function restoreScroll({
|
|
|
177
177
|
}
|
|
178
178
|
}
|
|
179
179
|
|
|
180
|
-
|
|
180
|
+
break scroll
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
// If there is no cached entry for the hash and there is no hash in the URL,
|
|
184
184
|
// we need to scroll to the top of the page for every scrollToTop element
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
: typeof selector === 'function'
|
|
185
|
+
const scrollOptions = { top: 0, left: 0, behavior }
|
|
186
|
+
window.scrollTo(scrollOptions)
|
|
187
|
+
if (scrollToTopSelectors) {
|
|
188
|
+
for (const selector of scrollToTopSelectors) {
|
|
189
|
+
if (selector === 'window') continue
|
|
190
|
+
const element =
|
|
191
|
+
typeof selector === 'function'
|
|
193
192
|
? selector()
|
|
194
193
|
: document.querySelector(selector)
|
|
195
|
-
|
|
196
|
-
element.scrollTo({
|
|
197
|
-
top: 0,
|
|
198
|
-
left: 0,
|
|
199
|
-
behavior,
|
|
200
|
-
})
|
|
194
|
+
if (element) element.scrollTo(scrollOptions)
|
|
201
195
|
}
|
|
202
|
-
}
|
|
203
|
-
}
|
|
196
|
+
}
|
|
197
|
+
}
|
|
204
198
|
|
|
205
199
|
//
|
|
206
200
|
ignoreScroll = false
|
|
@@ -294,11 +288,10 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
294
288
|
const restoreKey = getKey(router.state.location)
|
|
295
289
|
|
|
296
290
|
scrollRestorationCache.set((state) => {
|
|
297
|
-
const keyEntry = (state[restoreKey]
|
|
298
|
-
state[restoreKey] || ({} as ScrollRestorationByElement))
|
|
291
|
+
const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)
|
|
299
292
|
|
|
300
|
-
const elementEntry = (keyEntry[elementSelector]
|
|
301
|
-
|
|
293
|
+
const elementEntry = (keyEntry[elementSelector] ||=
|
|
294
|
+
{} as ScrollRestorationEntry)
|
|
302
295
|
|
|
303
296
|
if (elementSelector === 'window') {
|
|
304
297
|
elementEntry.scrollX = window.scrollX || 0
|
|
@@ -344,7 +337,7 @@ export function setupScrollRestoration(router: AnyRouter, force?: boolean) {
|
|
|
344
337
|
if (router.isScrollRestoring) {
|
|
345
338
|
// Mark the location as having been seen
|
|
346
339
|
scrollRestorationCache.set((state) => {
|
|
347
|
-
state[cacheKey]
|
|
340
|
+
state[cacheKey] ||= {} as ScrollRestorationByElement
|
|
348
341
|
|
|
349
342
|
return state
|
|
350
343
|
})
|
package/src/searchParams.ts
CHANGED
|
@@ -9,7 +9,7 @@ export const defaultStringifySearch = stringifySearchWith(
|
|
|
9
9
|
|
|
10
10
|
export function parseSearchWith(parser: (str: string) => any) {
|
|
11
11
|
return (searchStr: string): AnySchema => {
|
|
12
|
-
if (searchStr
|
|
12
|
+
if (searchStr[0] === '?') {
|
|
13
13
|
searchStr = searchStr.substring(1)
|
|
14
14
|
}
|
|
15
15
|
|
|
@@ -21,8 +21,8 @@ export function parseSearchWith(parser: (str: string) => any) {
|
|
|
21
21
|
if (typeof value === 'string') {
|
|
22
22
|
try {
|
|
23
23
|
query[key] = parser(value)
|
|
24
|
-
} catch (
|
|
25
|
-
//
|
|
24
|
+
} catch (_err) {
|
|
25
|
+
// silent
|
|
26
26
|
}
|
|
27
27
|
}
|
|
28
28
|
}
|
|
@@ -35,20 +35,21 @@ export function stringifySearchWith(
|
|
|
35
35
|
stringify: (search: any) => string,
|
|
36
36
|
parser?: (str: string) => any,
|
|
37
37
|
) {
|
|
38
|
+
const hasParser = typeof parser === 'function'
|
|
38
39
|
function stringifyValue(val: any) {
|
|
39
40
|
if (typeof val === 'object' && val !== null) {
|
|
40
41
|
try {
|
|
41
42
|
return stringify(val)
|
|
42
|
-
} catch (
|
|
43
|
+
} catch (_err) {
|
|
43
44
|
// silent
|
|
44
45
|
}
|
|
45
|
-
} else if (
|
|
46
|
+
} else if (hasParser && typeof val === 'string') {
|
|
46
47
|
try {
|
|
47
48
|
// Check if it's a valid parseable string.
|
|
48
49
|
// If it is, then stringify it again.
|
|
49
50
|
parser(val)
|
|
50
51
|
return stringify(val)
|
|
51
|
-
} catch (
|
|
52
|
+
} catch (_err) {
|
|
52
53
|
// silent
|
|
53
54
|
}
|
|
54
55
|
}
|
|
@@ -56,19 +57,7 @@ export function stringifySearchWith(
|
|
|
56
57
|
}
|
|
57
58
|
|
|
58
59
|
return (search: Record<string, any>) => {
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
Object.keys(search).forEach((key) => {
|
|
62
|
-
const val = search[key]
|
|
63
|
-
if (typeof val === 'undefined' || val === undefined) {
|
|
64
|
-
delete search[key]
|
|
65
|
-
} else {
|
|
66
|
-
search[key] = stringifyValue(val)
|
|
67
|
-
}
|
|
68
|
-
})
|
|
69
|
-
|
|
70
|
-
const searchStr = encode(search as Record<string, string>).toString()
|
|
71
|
-
|
|
60
|
+
const searchStr = encode(search, stringifyValue)
|
|
72
61
|
return searchStr ? `?${searchStr}` : ''
|
|
73
62
|
}
|
|
74
63
|
}
|