@tanstack/router-core 1.155.0 → 1.157.0
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/index.cjs +5 -0
- package/dist/cjs/index.cjs.map +1 -1
- package/dist/cjs/index.d.cts +1 -0
- package/dist/cjs/isServer.d.cts +24 -0
- package/dist/cjs/load-matches.cjs +8 -7
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.cjs.map +1 -1
- package/dist/cjs/new-process-route-tree.d.cts +13 -8
- package/dist/cjs/router.cjs +122 -54
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/router.d.cts +18 -4
- package/dist/cjs/scroll-restoration.cjs +3 -2
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.js +2 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/isServer.d.ts +24 -0
- package/dist/esm/load-matches.js +8 -7
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/new-process-route-tree.d.ts +13 -8
- package/dist/esm/new-process-route-tree.js.map +1 -1
- package/dist/esm/router.d.ts +18 -4
- package/dist/esm/router.js +123 -55
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +3 -2
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/package.json +3 -2
- package/src/index.ts +1 -0
- package/src/isServer.ts +24 -0
- package/src/load-matches.ts +8 -7
- package/src/new-process-route-tree.ts +12 -8
- package/src/router.ts +179 -53
- package/src/scroll-restoration.ts +3 -2
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { functionalUpdate } from "./utils.js";
|
|
2
|
+
import { isServer } from "@tanstack/router-is-server";
|
|
2
3
|
function getSafeSessionStorage() {
|
|
3
4
|
try {
|
|
4
5
|
if (typeof window !== "undefined" && typeof window.sessionStorage === "object") {
|
|
@@ -122,14 +123,14 @@ function restoreScroll({
|
|
|
122
123
|
ignoreScroll = false;
|
|
123
124
|
}
|
|
124
125
|
function setupScrollRestoration(router, force) {
|
|
125
|
-
if (!scrollRestorationCache && !router.isServer) {
|
|
126
|
+
if (!scrollRestorationCache && !(isServer ?? router.isServer)) {
|
|
126
127
|
return;
|
|
127
128
|
}
|
|
128
129
|
const shouldScrollRestoration = force ?? router.options.scrollRestoration ?? false;
|
|
129
130
|
if (shouldScrollRestoration) {
|
|
130
131
|
router.isScrollRestoring = true;
|
|
131
132
|
}
|
|
132
|
-
if (router.isServer || router.isScrollRestorationSetup || !scrollRestorationCache) {
|
|
133
|
+
if ((isServer ?? router.isServer) || router.isScrollRestorationSetup || !scrollRestorationCache) {
|
|
133
134
|
return;
|
|
134
135
|
}
|
|
135
136
|
router.isScrollRestorationSetup = true;
|
|
@@ -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 // silent\n }\n return undefined\n}\n\n/** SessionStorage key used to persist scroll restoration state. */\n/** SessionStorage key used to store scroll positions across navigations. */\n/** SessionStorage key used to store scroll positions across navigations. */\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 | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\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 try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n },\n }\n}\n\n/** In-memory handle to the persisted scroll restoration cache. */\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\n/**\n * Default scroll restoration cache key: location state key or full href.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\n/** Best-effort nth-child CSS selector for a given element. */\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?.__TSR_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\n/** Setup global listeners and hooks to support scroll restoration. */\n/** Setup global listeners and hooks to support scroll restoration. */\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !router.isServer) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n router.isServer ||\n router.isScrollRestorationSetup ||\n !scrollRestorationCache\n ) {\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 if (typeof router.options.scrollRestoration === 'function') {\n const shouldRestore = router.options.scrollRestoration({\n location: router.latestLocation,\n })\n if (!shouldRestore) {\n return\n }\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 * @private\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 */\n/**\n * @private\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific Transitioners.\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 { isServer } from './isServer'\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\n/** SessionStorage key used to persist scroll restoration state. */\n/** SessionStorage key used to store scroll positions across navigations. */\n/** SessionStorage key used to store scroll positions across navigations. */\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 | null {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return null\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 try {\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n } catch {\n console.warn(\n '[ts-router] Could not persist scroll restoration state to sessionStorage.',\n )\n }\n },\n }\n}\n\n/** In-memory handle to the persisted scroll restoration cache. */\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\n/**\n * Default scroll restoration cache key: location state key or full href.\n */\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\n/** Best-effort nth-child CSS selector for a given element. */\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?.__TSR_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\n/** Setup global listeners and hooks to support scroll restoration. */\n/** Setup global listeners and hooks to support scroll restoration. */\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (!scrollRestorationCache && !(isServer ?? router.isServer)) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (\n (isServer ?? router.isServer) ||\n router.isScrollRestorationSetup ||\n !scrollRestorationCache\n ) {\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 if (typeof router.options.scrollRestoration === 'function') {\n const shouldRestore = router.options.scrollRestoration({\n location: router.latestLocation,\n })\n if (!shouldRestore) {\n return\n }\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 * @private\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 */\n/**\n * @private\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific Transitioners.\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/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@tanstack/router-core",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.157.0",
|
|
4
4
|
"description": "Modern and scalable routing for React applications",
|
|
5
5
|
"author": "Tanner Linsley",
|
|
6
6
|
"license": "MIT",
|
|
@@ -70,7 +70,8 @@
|
|
|
70
70
|
"seroval-plugins": "^1.4.2",
|
|
71
71
|
"tiny-invariant": "^1.3.3",
|
|
72
72
|
"tiny-warning": "^1.0.3",
|
|
73
|
-
"@tanstack/history": "1.154.14"
|
|
73
|
+
"@tanstack/history": "1.154.14",
|
|
74
|
+
"@tanstack/router-is-server": "1.127.3"
|
|
74
75
|
},
|
|
75
76
|
"devDependencies": {
|
|
76
77
|
"esbuild": "^0.25.0"
|
package/src/index.ts
CHANGED
|
@@ -105,6 +105,7 @@ export {
|
|
|
105
105
|
export { encode, decode } from './qss'
|
|
106
106
|
export { rootRouteId } from './root'
|
|
107
107
|
export type { RootRouteId } from './root'
|
|
108
|
+
export { isServer } from './isServer'
|
|
108
109
|
|
|
109
110
|
export { BaseRoute, BaseRouteApi, BaseRootRoute } from './route'
|
|
110
111
|
export type {
|
package/src/isServer.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Static server/client detection for tree-shaking support.
|
|
3
|
+
*
|
|
4
|
+
* This file re-exports `isServer` from `@tanstack/router-is-server` which uses
|
|
5
|
+
* conditional exports to provide different values based on the environment:
|
|
6
|
+
*
|
|
7
|
+
* - `browser` condition → `false` (client)
|
|
8
|
+
* - `node`/`worker`/`deno`/`bun` → `true` (server)
|
|
9
|
+
* - `development` condition → `undefined` (for tests, falls back to router.isServer)
|
|
10
|
+
*
|
|
11
|
+
* The bundler resolves the correct file at build time based on export conditions,
|
|
12
|
+
* and since the value is a literal constant, dead code can be eliminated.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```typescript
|
|
16
|
+
* import { isServer } from '@tanstack/router-core'
|
|
17
|
+
*
|
|
18
|
+
* // The ?? operator provides fallback for development/test mode
|
|
19
|
+
* if (isServer ?? router.isServer) {
|
|
20
|
+
* // Server-only code - eliminated in client bundles
|
|
21
|
+
* }
|
|
22
|
+
* ```
|
|
23
|
+
*/
|
|
24
|
+
export { isServer } from '@tanstack/router-is-server'
|
package/src/load-matches.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { createControlledPromise, isPromise } from './utils'
|
|
|
4
4
|
import { isNotFound } from './not-found'
|
|
5
5
|
import { rootRouteId } from './root'
|
|
6
6
|
import { isRedirect } from './redirect'
|
|
7
|
+
import { isServer } from './isServer'
|
|
7
8
|
import type { NotFoundError } from './not-found'
|
|
8
9
|
import type { ParsedLocation } from './location'
|
|
9
10
|
import type {
|
|
@@ -169,11 +170,11 @@ const shouldSkipLoader = (
|
|
|
169
170
|
): boolean => {
|
|
170
171
|
const match = inner.router.getMatch(matchId)!
|
|
171
172
|
// upon hydration, we skip the loader if the match has been dehydrated on the server
|
|
172
|
-
if (!inner.router.isServer && match._nonReactive.dehydrated) {
|
|
173
|
+
if (!(isServer ?? inner.router.isServer) && match._nonReactive.dehydrated) {
|
|
173
174
|
return true
|
|
174
175
|
}
|
|
175
176
|
|
|
176
|
-
if (inner.router.isServer && match.ssr === false) {
|
|
177
|
+
if ((isServer ?? inner.router.isServer) && match.ssr === false) {
|
|
177
178
|
return true
|
|
178
179
|
}
|
|
179
180
|
|
|
@@ -306,7 +307,7 @@ const setupPendingTimeout = (
|
|
|
306
307
|
route.options.pendingMs ?? inner.router.options.defaultPendingMs
|
|
307
308
|
const shouldPending = !!(
|
|
308
309
|
inner.onReady &&
|
|
309
|
-
!inner.router.isServer &&
|
|
310
|
+
!(isServer ?? inner.router.isServer) &&
|
|
310
311
|
!resolvePreload(inner, matchId) &&
|
|
311
312
|
(route.options.loader ||
|
|
312
313
|
route.options.beforeLoad ||
|
|
@@ -520,7 +521,7 @@ const handleBeforeLoad = (
|
|
|
520
521
|
|
|
521
522
|
const serverSsr = () => {
|
|
522
523
|
// on the server, determine whether SSR the current match or not
|
|
523
|
-
if (inner.router.isServer) {
|
|
524
|
+
if (isServer ?? inner.router.isServer) {
|
|
524
525
|
const maybePromise = isBeforeLoadSsr(inner, matchId, index, route)
|
|
525
526
|
if (isPromise(maybePromise)) return maybePromise.then(queueExecution)
|
|
526
527
|
}
|
|
@@ -635,7 +636,7 @@ const runLoader = async (
|
|
|
635
636
|
|
|
636
637
|
// Actually run the loader and handle the result
|
|
637
638
|
try {
|
|
638
|
-
if (!inner.router.isServer || match.ssr === true) {
|
|
639
|
+
if (!(isServer ?? inner.router.isServer) || match.ssr === true) {
|
|
639
640
|
loadRouteChunk(route)
|
|
640
641
|
}
|
|
641
642
|
|
|
@@ -759,7 +760,7 @@ const loadRouteMatch = async (
|
|
|
759
760
|
const route = inner.router.looseRoutesById[routeId]!
|
|
760
761
|
|
|
761
762
|
if (shouldSkipLoader(inner, matchId)) {
|
|
762
|
-
if (inner.router.isServer) {
|
|
763
|
+
if (isServer ?? inner.router.isServer) {
|
|
763
764
|
return inner.router.getMatch(matchId)!
|
|
764
765
|
}
|
|
765
766
|
} else {
|
|
@@ -879,7 +880,7 @@ export async function loadMatches(arg: {
|
|
|
879
880
|
// make sure the pending component is immediately rendered when hydrating a match that is not SSRed
|
|
880
881
|
// the pending component was already rendered on the server and we want to keep it shown on the client until minPendingMs is reached
|
|
881
882
|
if (
|
|
882
|
-
!inner.router.isServer &&
|
|
883
|
+
!(isServer ?? inner.router.isServer) &&
|
|
883
884
|
inner.router.state.matches.some((d) => d._forcePending)
|
|
884
885
|
) {
|
|
885
886
|
triggerOnReady(inner)
|
|
@@ -778,6 +778,17 @@ export function trimPathRight(path: string) {
|
|
|
778
778
|
return path === '/' ? path : path.replace(/\/{1,}$/, '')
|
|
779
779
|
}
|
|
780
780
|
|
|
781
|
+
export interface ProcessRouteTreeResult<
|
|
782
|
+
TRouteLike extends Extract<RouteLike, { fullPath: string }> & { id: string },
|
|
783
|
+
> {
|
|
784
|
+
/** Should be considered a black box, needs to be provided to all matching functions in this module. */
|
|
785
|
+
processedTree: ProcessedTree<TRouteLike, any, any>
|
|
786
|
+
/** A lookup map of routes by their unique IDs. */
|
|
787
|
+
routesById: Record<string, TRouteLike>
|
|
788
|
+
/** A lookup map of routes by their trimmed full paths. */
|
|
789
|
+
routesByPath: Record<string, TRouteLike>
|
|
790
|
+
}
|
|
791
|
+
|
|
781
792
|
/**
|
|
782
793
|
* Processes a route tree into a segment trie for efficient path matching.
|
|
783
794
|
* Also builds lookup maps for routes by ID and by trimmed full path.
|
|
@@ -791,14 +802,7 @@ export function processRouteTree<
|
|
|
791
802
|
caseSensitive: boolean = false,
|
|
792
803
|
/** Optional callback invoked for each route during processing. */
|
|
793
804
|
initRoute?: (route: TRouteLike, index: number) => void,
|
|
794
|
-
): {
|
|
795
|
-
/** Should be considered a black box, needs to be provided to all matching functions in this module. */
|
|
796
|
-
processedTree: ProcessedTree<TRouteLike, any, any>
|
|
797
|
-
/** A lookup map of routes by their unique IDs. */
|
|
798
|
-
routesById: Record<string, TRouteLike>
|
|
799
|
-
/** A lookup map of routes by their trimmed full paths. */
|
|
800
|
-
routesByPath: Record<string, TRouteLike>
|
|
801
|
-
} {
|
|
805
|
+
): ProcessRouteTreeResult<TRouteLike> {
|
|
802
806
|
const segmentTree = createStaticNode<TRouteLike>(routeTree.fullPath)
|
|
803
807
|
const data = new Uint16Array(6)
|
|
804
808
|
const routesById = {} as Record<string, TRouteLike>
|
package/src/router.ts
CHANGED
|
@@ -25,6 +25,7 @@ import {
|
|
|
25
25
|
trimPath,
|
|
26
26
|
trimPathRight,
|
|
27
27
|
} from './path'
|
|
28
|
+
import { isServer } from './isServer'
|
|
28
29
|
import { createLRUCache } from './lru-cache'
|
|
29
30
|
import { isNotFound } from './not-found'
|
|
30
31
|
import { setupScrollRestoration } from './scroll-restoration'
|
|
@@ -38,7 +39,11 @@ import {
|
|
|
38
39
|
executeRewriteOutput,
|
|
39
40
|
rewriteBasepath,
|
|
40
41
|
} from './rewrite'
|
|
41
|
-
import type {
|
|
42
|
+
import type { LRUCache } from './lru-cache'
|
|
43
|
+
import type {
|
|
44
|
+
ProcessRouteTreeResult,
|
|
45
|
+
ProcessedTree,
|
|
46
|
+
} from './new-process-route-tree'
|
|
42
47
|
import type { SearchParser, SearchSerializer } from './searchParams'
|
|
43
48
|
import type { AnyRedirect, ResolvedRedirect } from './redirect'
|
|
44
49
|
import type {
|
|
@@ -589,7 +594,6 @@ export type SubscribeFn = <TType extends keyof RouterEvents>(
|
|
|
589
594
|
export interface MatchRoutesOpts {
|
|
590
595
|
preload?: boolean
|
|
591
596
|
throwOnError?: boolean
|
|
592
|
-
_buildLocation?: boolean
|
|
593
597
|
dest?: BuildNextOptions
|
|
594
598
|
}
|
|
595
599
|
|
|
@@ -873,6 +877,17 @@ export type CreateRouterFn = <
|
|
|
873
877
|
TDehydrated
|
|
874
878
|
>
|
|
875
879
|
|
|
880
|
+
declare global {
|
|
881
|
+
// eslint-disable-next-line no-var
|
|
882
|
+
var __TSR_CACHE__:
|
|
883
|
+
| {
|
|
884
|
+
routeTree: AnyRoute
|
|
885
|
+
processRouteTreeResult: ProcessRouteTreeResult<AnyRoute>
|
|
886
|
+
resolvePathCache: LRUCache<string, string>
|
|
887
|
+
}
|
|
888
|
+
| undefined
|
|
889
|
+
}
|
|
890
|
+
|
|
876
891
|
/**
|
|
877
892
|
* Core, framework-agnostic router engine that powers TanStack Router.
|
|
878
893
|
*
|
|
@@ -923,6 +938,7 @@ export class RouterCore<
|
|
|
923
938
|
routesById!: RoutesById<TRouteTree>
|
|
924
939
|
routesByPath!: RoutesByPath<TRouteTree>
|
|
925
940
|
processedTree!: ProcessedTree<TRouteTree, any, any>
|
|
941
|
+
resolvePathCache!: LRUCache<string, string>
|
|
926
942
|
isServer!: boolean
|
|
927
943
|
pathParamsDecoder?: (encoded: string) => string
|
|
928
944
|
|
|
@@ -1003,7 +1019,7 @@ export class RouterCore<
|
|
|
1003
1019
|
(this.options.history && this.options.history !== this.history)
|
|
1004
1020
|
) {
|
|
1005
1021
|
if (!this.options.history) {
|
|
1006
|
-
if (!this.isServer) {
|
|
1022
|
+
if (!(isServer ?? this.isServer)) {
|
|
1007
1023
|
this.history = createBrowserHistory() as TRouterHistory
|
|
1008
1024
|
}
|
|
1009
1025
|
} else {
|
|
@@ -1013,7 +1029,11 @@ export class RouterCore<
|
|
|
1013
1029
|
|
|
1014
1030
|
this.origin = this.options.origin
|
|
1015
1031
|
if (!this.origin) {
|
|
1016
|
-
if (
|
|
1032
|
+
if (
|
|
1033
|
+
!(isServer ?? this.isServer) &&
|
|
1034
|
+
window?.origin &&
|
|
1035
|
+
window.origin !== 'null'
|
|
1036
|
+
) {
|
|
1017
1037
|
this.origin = window.origin
|
|
1018
1038
|
} else {
|
|
1019
1039
|
// fallback for the server, can be overridden by calling router.update({origin}) on the server
|
|
@@ -1027,7 +1047,31 @@ export class RouterCore<
|
|
|
1027
1047
|
|
|
1028
1048
|
if (this.options.routeTree !== this.routeTree) {
|
|
1029
1049
|
this.routeTree = this.options.routeTree as TRouteTree
|
|
1030
|
-
|
|
1050
|
+
let processRouteTreeResult: ProcessRouteTreeResult<TRouteTree>
|
|
1051
|
+
if (
|
|
1052
|
+
(isServer ?? this.isServer) &&
|
|
1053
|
+
globalThis.__TSR_CACHE__ &&
|
|
1054
|
+
globalThis.__TSR_CACHE__.routeTree === this.routeTree
|
|
1055
|
+
) {
|
|
1056
|
+
const cached = globalThis.__TSR_CACHE__
|
|
1057
|
+
this.resolvePathCache = cached.resolvePathCache
|
|
1058
|
+
processRouteTreeResult = cached.processRouteTreeResult as any
|
|
1059
|
+
} else {
|
|
1060
|
+
this.resolvePathCache = createLRUCache(1000)
|
|
1061
|
+
processRouteTreeResult = this.buildRouteTree()
|
|
1062
|
+
// only cache if nothing else is cached yet
|
|
1063
|
+
if (
|
|
1064
|
+
(isServer ?? this.isServer) &&
|
|
1065
|
+
globalThis.__TSR_CACHE__ === undefined
|
|
1066
|
+
) {
|
|
1067
|
+
globalThis.__TSR_CACHE__ = {
|
|
1068
|
+
routeTree: this.routeTree,
|
|
1069
|
+
processRouteTreeResult: processRouteTreeResult as any,
|
|
1070
|
+
resolvePathCache: this.resolvePathCache,
|
|
1071
|
+
}
|
|
1072
|
+
}
|
|
1073
|
+
}
|
|
1074
|
+
this.setRoutes(processRouteTreeResult)
|
|
1031
1075
|
}
|
|
1032
1076
|
|
|
1033
1077
|
if (!this.__store && this.latestLocation) {
|
|
@@ -1110,7 +1154,7 @@ export class RouterCore<
|
|
|
1110
1154
|
}
|
|
1111
1155
|
|
|
1112
1156
|
buildRouteTree = () => {
|
|
1113
|
-
const
|
|
1157
|
+
const result = processRouteTree(
|
|
1114
1158
|
this.routeTree,
|
|
1115
1159
|
this.options.caseSensitive,
|
|
1116
1160
|
(route, i) => {
|
|
@@ -1120,9 +1164,17 @@ export class RouterCore<
|
|
|
1120
1164
|
},
|
|
1121
1165
|
)
|
|
1122
1166
|
if (this.options.routeMasks) {
|
|
1123
|
-
processRouteMasks(this.options.routeMasks, processedTree)
|
|
1167
|
+
processRouteMasks(this.options.routeMasks, result.processedTree)
|
|
1124
1168
|
}
|
|
1125
1169
|
|
|
1170
|
+
return result
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
setRoutes({
|
|
1174
|
+
routesById,
|
|
1175
|
+
routesByPath,
|
|
1176
|
+
processedTree,
|
|
1177
|
+
}: ProcessRouteTreeResult<TRouteTree>) {
|
|
1126
1178
|
this.routesById = routesById as RoutesById<TRouteTree>
|
|
1127
1179
|
this.routesByPath = routesByPath as RoutesByPath<TRouteTree>
|
|
1128
1180
|
this.processedTree = processedTree
|
|
@@ -1222,8 +1274,6 @@ export class RouterCore<
|
|
|
1222
1274
|
return location
|
|
1223
1275
|
}
|
|
1224
1276
|
|
|
1225
|
-
resolvePathCache = createLRUCache<string, string>(1000)
|
|
1226
|
-
|
|
1227
1277
|
/** Resolve a path against the router basepath and trailing-slash policy. */
|
|
1228
1278
|
resolvePathWithBase = (from: string, path: string) => {
|
|
1229
1279
|
const resolvedPath = resolvePath({
|
|
@@ -1390,35 +1440,19 @@ export class RouterCore<
|
|
|
1390
1440
|
let paramsError: unknown = undefined
|
|
1391
1441
|
|
|
1392
1442
|
if (!existingMatch) {
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
|
|
1396
|
-
|
|
1397
|
-
|
|
1443
|
+
try {
|
|
1444
|
+
extractStrictParams(route, usedParams, parsedParams!, strictParams)
|
|
1445
|
+
} catch (err: any) {
|
|
1446
|
+
if (isNotFound(err) || isRedirect(err)) {
|
|
1447
|
+
paramsError = err
|
|
1448
|
+
} else {
|
|
1449
|
+
paramsError = new PathParamError(err.message, {
|
|
1450
|
+
cause: err,
|
|
1451
|
+
})
|
|
1398
1452
|
}
|
|
1399
|
-
} else {
|
|
1400
|
-
const strictParseParams =
|
|
1401
|
-
route.options.params?.parse ?? route.options.parseParams
|
|
1402
|
-
|
|
1403
|
-
if (strictParseParams) {
|
|
1404
|
-
try {
|
|
1405
|
-
Object.assign(
|
|
1406
|
-
strictParams,
|
|
1407
|
-
strictParseParams(strictParams as Record<string, string>),
|
|
1408
|
-
)
|
|
1409
|
-
} catch (err: any) {
|
|
1410
|
-
if (isNotFound(err) || isRedirect(err)) {
|
|
1411
|
-
paramsError = err
|
|
1412
|
-
} else {
|
|
1413
|
-
paramsError = new PathParamError(err.message, {
|
|
1414
|
-
cause: err,
|
|
1415
|
-
})
|
|
1416
|
-
}
|
|
1417
1453
|
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
}
|
|
1421
|
-
}
|
|
1454
|
+
if (opts?.throwOnError) {
|
|
1455
|
+
throw paramsError
|
|
1422
1456
|
}
|
|
1423
1457
|
}
|
|
1424
1458
|
}
|
|
@@ -1453,7 +1487,7 @@ export class RouterCore<
|
|
|
1453
1487
|
|
|
1454
1488
|
match = {
|
|
1455
1489
|
id: matchId,
|
|
1456
|
-
ssr: this.isServer ? undefined : route.options.ssr,
|
|
1490
|
+
ssr: (isServer ?? this.isServer) ? undefined : route.options.ssr,
|
|
1457
1491
|
index,
|
|
1458
1492
|
routeId: route.id,
|
|
1459
1493
|
params: previousMatch
|
|
@@ -1519,7 +1553,7 @@ export class RouterCore<
|
|
|
1519
1553
|
|
|
1520
1554
|
// only execute `context` if we are not calling from router.buildLocation
|
|
1521
1555
|
|
|
1522
|
-
if (!existingMatch
|
|
1556
|
+
if (!existingMatch) {
|
|
1523
1557
|
const parentMatch = matches[index - 1]
|
|
1524
1558
|
const parentContext = getParentContext(parentMatch)
|
|
1525
1559
|
|
|
@@ -1563,6 +1597,80 @@ export class RouterCore<
|
|
|
1563
1597
|
})
|
|
1564
1598
|
}
|
|
1565
1599
|
|
|
1600
|
+
/**
|
|
1601
|
+
* Lightweight route matching for buildLocation.
|
|
1602
|
+
* Only computes fullPath, accumulated search, and params - skipping expensive
|
|
1603
|
+
* operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
|
|
1604
|
+
*/
|
|
1605
|
+
private matchRoutesLightweight(location: ParsedLocation): {
|
|
1606
|
+
matchedRoutes: ReadonlyArray<AnyRoute>
|
|
1607
|
+
fullPath: string
|
|
1608
|
+
search: Record<string, unknown>
|
|
1609
|
+
params: Record<string, unknown>
|
|
1610
|
+
} {
|
|
1611
|
+
const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(
|
|
1612
|
+
location.pathname,
|
|
1613
|
+
)
|
|
1614
|
+
const lastRoute = last(matchedRoutes)!
|
|
1615
|
+
|
|
1616
|
+
// I don't know if we should run the full search middleware chain, or just validateSearch
|
|
1617
|
+
// // Accumulate search validation through the route chain
|
|
1618
|
+
// const accumulatedSearch: Record<string, unknown> = applySearchMiddleware({
|
|
1619
|
+
// search: { ...location.search },
|
|
1620
|
+
// dest: location,
|
|
1621
|
+
// destRoutes: matchedRoutes,
|
|
1622
|
+
// _includeValidateSearch: true,
|
|
1623
|
+
// })
|
|
1624
|
+
|
|
1625
|
+
// Accumulate search validation through route chain
|
|
1626
|
+
const accumulatedSearch = { ...location.search }
|
|
1627
|
+
for (const route of matchedRoutes) {
|
|
1628
|
+
try {
|
|
1629
|
+
Object.assign(
|
|
1630
|
+
accumulatedSearch,
|
|
1631
|
+
validateSearch(route.options.validateSearch, accumulatedSearch),
|
|
1632
|
+
)
|
|
1633
|
+
} catch {
|
|
1634
|
+
// Ignore errors, we're not actually routing
|
|
1635
|
+
}
|
|
1636
|
+
}
|
|
1637
|
+
|
|
1638
|
+
// Determine params: reuse from state if possible, otherwise parse
|
|
1639
|
+
const lastStateMatch = last(this.state.matches)
|
|
1640
|
+
const canReuseParams =
|
|
1641
|
+
lastStateMatch &&
|
|
1642
|
+
lastStateMatch.routeId === lastRoute.id &&
|
|
1643
|
+
location.pathname === this.state.location.pathname
|
|
1644
|
+
|
|
1645
|
+
let params: Record<string, unknown>
|
|
1646
|
+
if (canReuseParams) {
|
|
1647
|
+
params = lastStateMatch.params
|
|
1648
|
+
} else {
|
|
1649
|
+
// Parse params through the route chain
|
|
1650
|
+
const strictParams: Record<string, unknown> = { ...routeParams }
|
|
1651
|
+
for (const route of matchedRoutes) {
|
|
1652
|
+
try {
|
|
1653
|
+
extractStrictParams(
|
|
1654
|
+
route,
|
|
1655
|
+
routeParams,
|
|
1656
|
+
parsedParams ?? {},
|
|
1657
|
+
strictParams,
|
|
1658
|
+
)
|
|
1659
|
+
} catch {
|
|
1660
|
+
// Ignore errors, we're not actually routing
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
params = strictParams
|
|
1664
|
+
}
|
|
1665
|
+
|
|
1666
|
+
return {
|
|
1667
|
+
matchedRoutes,
|
|
1668
|
+
fullPath: lastRoute.fullPath,
|
|
1669
|
+
search: accumulatedSearch,
|
|
1670
|
+
params,
|
|
1671
|
+
}
|
|
1672
|
+
}
|
|
1673
|
+
|
|
1566
1674
|
cancelMatch = (id: string) => {
|
|
1567
1675
|
const match = this.getMatch(id)
|
|
1568
1676
|
|
|
@@ -1607,13 +1715,9 @@ export class RouterCore<
|
|
|
1607
1715
|
const currentLocation =
|
|
1608
1716
|
dest._fromLocation || this.pendingBuiltLocation || this.latestLocation
|
|
1609
1717
|
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
// Now let's find the starting pathname
|
|
1615
|
-
// This should default to the current location if no from is provided
|
|
1616
|
-
const lastMatch = last(allCurrentLocationMatches)!
|
|
1718
|
+
// Use lightweight matching - only computes what buildLocation needs
|
|
1719
|
+
// (fullPath, search, params) without creating full match objects
|
|
1720
|
+
const lightweightResult = this.matchRoutesLightweight(currentLocation)
|
|
1617
1721
|
|
|
1618
1722
|
// check that from path exists in the current route tree
|
|
1619
1723
|
// do this check only on navigations during test or development
|
|
@@ -1624,12 +1728,12 @@ export class RouterCore<
|
|
|
1624
1728
|
) {
|
|
1625
1729
|
const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes
|
|
1626
1730
|
|
|
1627
|
-
const matchedFrom = findLast(
|
|
1731
|
+
const matchedFrom = findLast(lightweightResult.matchedRoutes, (d) => {
|
|
1628
1732
|
return comparePaths(d.fullPath, dest.from!)
|
|
1629
1733
|
})
|
|
1630
1734
|
|
|
1631
1735
|
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1632
|
-
return comparePaths(d.fullPath,
|
|
1736
|
+
return comparePaths(d.fullPath, lightweightResult.fullPath)
|
|
1633
1737
|
})
|
|
1634
1738
|
|
|
1635
1739
|
// for from to be invalid it shouldn't just be unmatched to currentLocation
|
|
@@ -1642,15 +1746,15 @@ export class RouterCore<
|
|
|
1642
1746
|
const defaultedFromPath =
|
|
1643
1747
|
dest.unsafeRelative === 'path'
|
|
1644
1748
|
? currentLocation.pathname
|
|
1645
|
-
: (dest.from ??
|
|
1749
|
+
: (dest.from ?? lightweightResult.fullPath)
|
|
1646
1750
|
|
|
1647
1751
|
// ensure this includes the basePath if set
|
|
1648
1752
|
const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
|
|
1649
1753
|
|
|
1650
1754
|
// From search should always use the current location
|
|
1651
|
-
const fromSearch =
|
|
1755
|
+
const fromSearch = lightweightResult.search
|
|
1652
1756
|
// Same with params. It can't hurt to provide as many as possible
|
|
1653
|
-
const fromParams = { ...
|
|
1757
|
+
const fromParams = { ...lightweightResult.params }
|
|
1654
1758
|
|
|
1655
1759
|
// Resolve the next to
|
|
1656
1760
|
// ensure this includes the basePath if set
|
|
@@ -2119,7 +2223,7 @@ export class RouterCore<
|
|
|
2119
2223
|
this.cancelMatches()
|
|
2120
2224
|
this.updateLatestLocation()
|
|
2121
2225
|
|
|
2122
|
-
if (this.isServer) {
|
|
2226
|
+
if (isServer ?? this.isServer) {
|
|
2123
2227
|
// for SPAs on the initial load, this is handled by the Transitioner
|
|
2124
2228
|
const nextLocation = this.buildLocation({
|
|
2125
2229
|
to: this.latestLocation.pathname,
|
|
@@ -2269,7 +2373,7 @@ export class RouterCore<
|
|
|
2269
2373
|
} catch (err) {
|
|
2270
2374
|
if (isRedirect(err)) {
|
|
2271
2375
|
redirect = err
|
|
2272
|
-
if (!this.isServer) {
|
|
2376
|
+
if (!(isServer ?? this.isServer)) {
|
|
2273
2377
|
this.navigate({
|
|
2274
2378
|
...redirect.options,
|
|
2275
2379
|
replace: true,
|
|
@@ -2799,7 +2903,7 @@ function applySearchMiddleware({
|
|
|
2799
2903
|
_includeValidateSearch,
|
|
2800
2904
|
}: {
|
|
2801
2905
|
search: any
|
|
2802
|
-
dest:
|
|
2906
|
+
dest: { search?: unknown }
|
|
2803
2907
|
destRoutes: ReadonlyArray<AnyRoute>
|
|
2804
2908
|
_includeValidateSearch: boolean | undefined
|
|
2805
2909
|
}) {
|
|
@@ -2934,3 +3038,25 @@ function findGlobalNotFoundRouteId(
|
|
|
2934
3038
|
}
|
|
2935
3039
|
return rootRouteId
|
|
2936
3040
|
}
|
|
3041
|
+
|
|
3042
|
+
function extractStrictParams(
|
|
3043
|
+
route: AnyRoute,
|
|
3044
|
+
referenceParams: Record<string, unknown>,
|
|
3045
|
+
parsedParams: Record<string, unknown>,
|
|
3046
|
+
accumulatedParams: Record<string, unknown>,
|
|
3047
|
+
) {
|
|
3048
|
+
const parseParams = route.options.params?.parse ?? route.options.parseParams
|
|
3049
|
+
if (parseParams) {
|
|
3050
|
+
if (route.options.skipRouteOnParseError) {
|
|
3051
|
+
// Use pre-parsed params from route matching for skipRouteOnParseError routes
|
|
3052
|
+
for (const key in referenceParams) {
|
|
3053
|
+
if (key in parsedParams) {
|
|
3054
|
+
accumulatedParams[key] = parsedParams[key]
|
|
3055
|
+
}
|
|
3056
|
+
}
|
|
3057
|
+
} else {
|
|
3058
|
+
const result = parseParams(accumulatedParams as Record<string, string>)
|
|
3059
|
+
Object.assign(accumulatedParams, result)
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
3062
|
+
}
|