@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.
@@ -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.unshift(
48
- `${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`
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
- return;
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 || {}).__hashScrollIntoViewOptions ?? true;
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
- return;
102
+ break scroll;
104
103
  }
105
- [
106
- "window",
107
- ...(scrollToTopSelectors == null ? void 0 : scrollToTopSelectors.filter((d) => d !== "window")) ?? []
108
- ].forEach((selector) => {
109
- const element = selector === "window" ? window : typeof selector === "function" ? selector() : document.querySelector(selector);
110
- if (element) {
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] = state[restoreKey] || {};
156
- const elementEntry = keyEntry[elementSelector] = 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] = 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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"}
@@ -6,7 +6,7 @@ const defaultStringifySearch = stringifySearchWith(
6
6
  );
7
7
  function parseSearchWith(parser) {
8
8
  return (searchStr) => {
9
- if (searchStr.substring(0, 1) === "?") {
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 (err) {
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 (err) {
31
+ } catch (_err) {
31
32
  }
32
- } else if (typeof val === "string" && typeof parser === "function") {
33
+ } else if (hasParser && typeof val === "string") {
33
34
  try {
34
35
  parser(val);
35
36
  return stringify(val);
36
- } catch (err) {
37
+ } catch (_err) {
37
38
  }
38
39
  }
39
40
  return val;
40
41
  }
41
42
  return (search) => {
42
- search = { ...search };
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.substring(0, 1) === '?') {\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 //\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 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 (typeof val === 'string' && typeof parser === 'function') {\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 search = { ...search }\n\n Object.keys(search).forEach((key) => {\n const val = search[key]\n if (typeof val === 'undefined' || val === undefined) {\n delete search[key]\n } else {\n search[key] = stringifyValue(val)\n }\n })\n\n const searchStr = encode(search as Record<string, string>).toString()\n\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;AACvC,QAAI,UAAU,UAAU,GAAG,CAAC,MAAM,KAAK;AACzB,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,KAAK;AAAA,QAAA;AAAA,MAEd;AAAA,IACF;AAGK,WAAA;AAAA,EACT;AACF;AAEgB,SAAA,oBACd,WACA,QACA;AACA,WAAS,eAAe,KAAU;AAChC,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AACvC,UAAA;AACF,eAAO,UAAU,GAAG;AAAA,eACb,KAAK;AAAA,MAAA;AAAA,eAGL,OAAO,QAAQ,YAAY,OAAO,WAAW,YAAY;AAC9D,UAAA;AAGF,eAAO,GAAG;AACV,eAAO,UAAU,GAAG;AAAA,eACb,KAAK;AAAA,MAAA;AAAA,IAEd;AAEK,WAAA;AAAA,EAAA;AAGT,SAAO,CAAC,WAAgC;AAC7B,aAAA,EAAE,GAAG,OAAO;AAErB,WAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,QAAQ;AAC7B,YAAA,MAAM,OAAO,GAAG;AACtB,UAAI,OAAO,QAAQ,eAAe,QAAQ,QAAW;AACnD,eAAO,OAAO,GAAG;AAAA,MAAA,OACZ;AACE,eAAA,GAAG,IAAI,eAAe,GAAG;AAAA,MAAA;AAAA,IAClC,CACD;AAED,UAAM,YAAY,OAAO,MAAgC,EAAE,SAAS;AAE7D,WAAA,YAAY,IAAI,SAAS,KAAK;AAAA,EACvC;AACF;"}
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tanstack/router-core",
3
- "version": "1.131.25",
3
+ "version": "1.131.27",
4
4
  "description": "Modern and scalable routing for React applications",
5
5
  "author": "Tanner Linsley",
6
6
  "license": "MIT",
@@ -147,11 +147,10 @@ const shouldSkipLoader = (
147
147
  return true
148
148
  }
149
149
 
150
- if (inner.router.isServer) {
151
- if (match.ssr === false) {
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 shouldExecuteBeforeLoad = (
304
+ const preBeforeLoadSetup = (
306
305
  inner: InnerLoadContext,
307
306
  matchId: string,
308
307
  route: AnyRoute,
309
- ): boolean | Promise<boolean> => {
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 true
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 (match.status === 'error') {
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 shouldExecuteBeforeLoadResult = shouldExecuteBeforeLoad(
498
- inner,
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 [pfx] - An optional prefix to add before the query string.
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(obj: any, pfx?: string) {
23
- const normalizedObject = Object.entries(obj).flatMap(([key, value]) => {
24
- if (Array.isArray(value)) {
25
- return value.map((v) => [key, String(v)])
26
- } else {
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 searchParams = new URLSearchParams(normalizedObject)
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 (pfx || '') + searchParams.toString()
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, pfx?: string): any {
62
- const searchParamsPart = pfx ? str.slice(pfx.length) : str
63
- const searchParams = new URLSearchParams(searchParamsPart)
65
+ export function decode(str: any): any {
66
+ const searchParams = new URLSearchParams(str)
64
67
 
65
- const entries = [...searchParams.entries()]
68
+ const result: Record<string, unknown> = {}
66
69
 
67
- return entries.reduce<Record<string, unknown>>((acc, [key, value]) => {
68
- const previousValue = acc[key]
70
+ for (const [key, value] of searchParams.entries()) {
71
+ const previousValue = result[key]
69
72
  if (previousValue == null) {
70
- acc[key] = toValue(value)
73
+ result[key] = toValue(value)
74
+ } else if (Array.isArray(previousValue)) {
75
+ previousValue.push(toValue(value))
71
76
  } else {
72
- acc[key] = Array.isArray(previousValue)
73
- ? [...previousValue, toValue(value)]
74
- : [previousValue, toValue(value)]
77
+ result[key] = [previousValue, toValue(value)]
75
78
  }
79
+ }
76
80
 
77
- return acc
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(`${opts.href}`)
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
- return undefined
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.unshift(
91
- `${el.tagName}:nth-child(${([].indexOf as any).call(parent.children, el) + 1})`,
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: any) {
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
- return
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
- (window.history.state || {}).__hashScrollIntoViewOptions ?? true
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
- return
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
- 'window',
187
- ...(scrollToTopSelectors?.filter((d) => d !== 'window') ?? []),
188
- ].forEach((selector) => {
189
- const element =
190
- selector === 'window'
191
- ? window
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
- if (element) {
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
- keyEntry[elementSelector] || ({} as ScrollRestorationEntry))
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] = state[cacheKey] || ({} as ScrollRestorationByElement)
340
+ state[cacheKey] ||= {} as ScrollRestorationByElement
348
341
 
349
342
  return state
350
343
  })
@@ -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.substring(0, 1) === '?') {
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 (err) {
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 (err) {
43
+ } catch (_err) {
43
44
  // silent
44
45
  }
45
- } else if (typeof val === 'string' && typeof parser === 'function') {
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 (err) {
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
- search = { ...search }
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
  }