@tanstack/router-core 1.131.26 → 1.131.28
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/load-matches.cjs +8 -23
- package/dist/cjs/load-matches.cjs.map +1 -1
- package/dist/cjs/qss.cjs +19 -19
- package/dist/cjs/qss.cjs.map +1 -1
- package/dist/cjs/qss.d.cts +6 -4
- package/dist/cjs/router.cjs +15 -22
- package/dist/cjs/router.cjs.map +1 -1
- package/dist/cjs/scroll-restoration.cjs +21 -26
- package/dist/cjs/scroll-restoration.cjs.map +1 -1
- package/dist/cjs/searchParams.cjs +7 -15
- package/dist/cjs/searchParams.cjs.map +1 -1
- package/dist/esm/load-matches.js +8 -23
- package/dist/esm/load-matches.js.map +1 -1
- package/dist/esm/qss.d.ts +6 -4
- package/dist/esm/qss.js +19 -19
- package/dist/esm/qss.js.map +1 -1
- package/dist/esm/router.js +15 -22
- package/dist/esm/router.js.map +1 -1
- package/dist/esm/scroll-restoration.js +21 -26
- package/dist/esm/scroll-restoration.js.map +1 -1
- package/dist/esm/searchParams.js +7 -15
- package/dist/esm/searchParams.js.map +1 -1
- package/package.json +1 -1
- package/src/load-matches.ts +12 -29
- package/src/qss.ts +27 -24
- package/src/router.ts +31 -36
- package/src/scroll-restoration.ts +25 -32
- package/src/searchParams.ts +8 -19
|
@@ -5,7 +5,6 @@ function getSafeSessionStorage() {
|
|
|
5
5
|
return window.sessionStorage;
|
|
6
6
|
}
|
|
7
7
|
} catch {
|
|
8
|
-
return void 0;
|
|
9
8
|
}
|
|
10
9
|
return void 0;
|
|
11
10
|
}
|
|
@@ -44,12 +43,12 @@ function getCssSelector(el) {
|
|
|
44
43
|
const path = [];
|
|
45
44
|
let parent;
|
|
46
45
|
while (parent = el.parentNode) {
|
|
47
|
-
path.
|
|
48
|
-
`${el.tagName}:nth-child(${
|
|
46
|
+
path.push(
|
|
47
|
+
`${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`
|
|
49
48
|
);
|
|
50
49
|
el = parent;
|
|
51
50
|
}
|
|
52
|
-
return `${path.join(" > ")}`.toLowerCase();
|
|
51
|
+
return `${path.reverse().join(" > ")}`.toLowerCase();
|
|
53
52
|
}
|
|
54
53
|
let ignoreScroll = false;
|
|
55
54
|
function restoreScroll({
|
|
@@ -60,7 +59,7 @@ function restoreScroll({
|
|
|
60
59
|
scrollToTopSelectors,
|
|
61
60
|
location
|
|
62
61
|
}) {
|
|
63
|
-
var _a;
|
|
62
|
+
var _a, _b;
|
|
64
63
|
let byKey;
|
|
65
64
|
try {
|
|
66
65
|
byKey = JSON.parse(sessionStorage.getItem(storageKey2) || "{}");
|
|
@@ -71,7 +70,7 @@ function restoreScroll({
|
|
|
71
70
|
const resolvedKey = key || ((_a = window.history.state) == null ? void 0 : _a.key);
|
|
72
71
|
const elementEntries = byKey[resolvedKey];
|
|
73
72
|
ignoreScroll = true;
|
|
74
|
-
|
|
73
|
+
scroll: {
|
|
75
74
|
if (shouldScrollRestoration && elementEntries && Object.keys(elementEntries).length > 0) {
|
|
76
75
|
for (const elementSelector in elementEntries) {
|
|
77
76
|
const entry = elementEntries[elementSelector];
|
|
@@ -89,33 +88,29 @@ function restoreScroll({
|
|
|
89
88
|
}
|
|
90
89
|
}
|
|
91
90
|
}
|
|
92
|
-
|
|
91
|
+
break scroll;
|
|
93
92
|
}
|
|
94
|
-
const hash = (location ?? window.location).hash.split("#")[1];
|
|
93
|
+
const hash = (location ?? window.location).hash.split("#", 2)[1];
|
|
95
94
|
if (hash) {
|
|
96
|
-
const hashScrollIntoViewOptions = (window.history.state
|
|
95
|
+
const hashScrollIntoViewOptions = ((_b = window.history.state) == null ? void 0 : _b.__hashScrollIntoViewOptions) ?? true;
|
|
97
96
|
if (hashScrollIntoViewOptions) {
|
|
98
97
|
const el = document.getElementById(hash);
|
|
99
98
|
if (el) {
|
|
100
99
|
el.scrollIntoView(hashScrollIntoViewOptions);
|
|
101
100
|
}
|
|
102
101
|
}
|
|
103
|
-
|
|
102
|
+
break scroll;
|
|
104
103
|
}
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
element.scrollTo(
|
|
112
|
-
top: 0,
|
|
113
|
-
left: 0,
|
|
114
|
-
behavior
|
|
115
|
-
});
|
|
104
|
+
const scrollOptions = { top: 0, left: 0, behavior };
|
|
105
|
+
window.scrollTo(scrollOptions);
|
|
106
|
+
if (scrollToTopSelectors) {
|
|
107
|
+
for (const selector of scrollToTopSelectors) {
|
|
108
|
+
if (selector === "window") continue;
|
|
109
|
+
const element = typeof selector === "function" ? selector() : document.querySelector(selector);
|
|
110
|
+
if (element) element.scrollTo(scrollOptions);
|
|
116
111
|
}
|
|
117
|
-
}
|
|
118
|
-
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
119
114
|
ignoreScroll = false;
|
|
120
115
|
}
|
|
121
116
|
function setupScrollRestoration(router, force) {
|
|
@@ -152,8 +147,8 @@ function setupScrollRestoration(router, force) {
|
|
|
152
147
|
}
|
|
153
148
|
const restoreKey = getKey(router.state.location);
|
|
154
149
|
scrollRestorationCache.set((state) => {
|
|
155
|
-
const keyEntry = state[restoreKey]
|
|
156
|
-
const elementEntry = keyEntry[elementSelector]
|
|
150
|
+
const keyEntry = state[restoreKey] || (state[restoreKey] = {});
|
|
151
|
+
const elementEntry = keyEntry[elementSelector] || (keyEntry[elementSelector] = {});
|
|
157
152
|
if (elementSelector === "window") {
|
|
158
153
|
elementEntry.scrollX = window.scrollX || 0;
|
|
159
154
|
elementEntry.scrollY = window.scrollY || 0;
|
|
@@ -186,7 +181,7 @@ function setupScrollRestoration(router, force) {
|
|
|
186
181
|
});
|
|
187
182
|
if (router.isScrollRestoring) {
|
|
188
183
|
scrollRestorationCache.set((state) => {
|
|
189
|
-
state[cacheKey]
|
|
184
|
+
state[cacheKey] || (state[cacheKey] = {});
|
|
190
185
|
return state;
|
|
191
186
|
});
|
|
192
187
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"scroll-restoration.js","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n return undefined\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | undefined {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return undefined\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function getCssSelector(el: any): string {\n const path = []\n let parent\n while ((parent = el.parentNode)) {\n path.unshift(\n `${el.tagName}:nth-child(${([].indexOf as any).call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.join(' > ')}`.toLowerCase()\n}\n\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error: any) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n ;(() => {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n return\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#')[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n (window.history.state || {}).__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n return\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n ;[\n 'window',\n ...(scrollToTopSelectors?.filter((d) => d !== 'window') ?? []),\n ].forEach((selector) => {\n const element =\n selector === 'window'\n ? window\n : typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) {\n element.scrollTo({\n top: 0,\n left: 0,\n behavior,\n })\n }\n })\n })()\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (scrollRestorationCache === undefined) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (typeof document === 'undefined' || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] =\n state[restoreKey] || ({} as ScrollRestorationByElement))\n\n const elementEntry = (keyEntry[elementSelector] =\n keyEntry[elementSelector] || ({} as ScrollRestorationEntry))\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] = state[cacheKey] || ({} as ScrollRestorationByElement)\n\n return state\n })\n }\n })\n}\n\n/**\n * @internal\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["storageKey"],"mappings":";AAqBA,SAAS,wBAAwB;AAC3B,MAAA;AACF,QACE,OAAO,WAAW,eAClB,OAAO,OAAO,mBAAmB,UACjC;AACA,aAAO,OAAO;AAAA,IAAA;AAAA,EAChB,QACM;AACC,WAAA;AAAA,EAAA;AAEF,SAAA;AACT;AAEO,MAAM,aAAa;AAE1B,MAAM,WAAW,CAAC,IAAmC,SAAiB;AAChE,MAAA;AACJ,SAAO,IAAI,SAAqB;AAC9B,QAAI,CAAC,SAAS;AACZ,gBAAU,WAAW,MAAM;AACzB,WAAG,GAAG,IAAI;AACA,kBAAA;AAAA,SACT,IAAI;AAAA,IAAA;AAAA,EAEX;AACF;AAEA,SAAS,+BAAmE;AAC1E,QAAM,qBAAqB,sBAAsB;AACjD,MAAI,CAAC,oBAAoB;AAChB,WAAA;AAAA,EAAA;AAGH,QAAA,iBAAiB,mBAAmB,QAAQ,UAAU;AAC5D,MAAI,QAAgC,iBAChC,KAAK,MAAM,cAAc,IACzB,CAAC;AAEE,SAAA;AAAA,IACL;AAAA;AAAA;AAAA;AAAA,IAIA,KAAK,CAAC,aACH,QAAQ,iBAAiB,SAAS,KAAK,KAAK,OAC7C,mBAAmB,QAAQ,YAAY,KAAK,UAAU,KAAK,CAAC;AAAA,EAEhE;AACF;AAEO,MAAM,yBAAyB,6BAA6B;AAStD,MAAA,iCAAiC,CAAC,aAA6B;AACnE,SAAA,SAAS,MAAM,aAAc,SAAS;AAC/C;AAEO,SAAS,eAAe,IAAiB;AAC9C,QAAM,OAAO,CAAC;AACV,MAAA;AACI,SAAA,SAAS,GAAG,YAAa;AAC1B,SAAA;AAAA,MACH,GAAG,GAAG,OAAO,cAAe,CAAA,EAAG,QAAgB,KAAK,OAAO,UAAU,EAAE,IAAI,CAAC;AAAA,IAC9E;AACK,SAAA;AAAA,EAAA;AAEP,SAAO,GAAG,KAAK,KAAK,KAAK,CAAC,GAAG,YAAY;AAC3C;AAEA,IAAI,eAAe;AAMZ,SAAS,cAAc;AAAA,EAC5B,YAAAA;AAAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,GAOG;;AACG,MAAA;AAEA,MAAA;AACF,YAAQ,KAAK,MAAM,eAAe,QAAQA,WAAU,KAAK,IAAI;AAAA,WACtD,OAAY;AACnB,YAAQ,MAAM,KAAK;AACnB;AAAA,EAAA;AAGF,QAAM,cAAc,SAAO,YAAO,QAAQ,UAAf,mBAAsB;AAC3C,QAAA,iBAAiB,MAAM,WAAW;AAGzB,iBAAA;AAGd,GAAC,MAAM;AAGN,QACE,2BACA,kBACA,OAAO,KAAK,cAAc,EAAE,SAAS,GACrC;AACA,iBAAW,mBAAmB,gBAAgB;AACtC,cAAA,QAAQ,eAAe,eAAe;AAC5C,YAAI,oBAAoB,UAAU;AAChC,iBAAO,SAAS;AAAA,YACd,KAAK,MAAM;AAAA,YACX,MAAM,MAAM;AAAA,YACZ;AAAA,UAAA,CACD;AAAA,mBACQ,iBAAiB;AACpB,gBAAA,UAAU,SAAS,cAAc,eAAe;AACtD,cAAI,SAAS;AACX,oBAAQ,aAAa,MAAM;AAC3B,oBAAQ,YAAY,MAAM;AAAA,UAAA;AAAA,QAC5B;AAAA,MACF;AAGF;AAAA,IAAA;AAOI,UAAA,QAAQ,YAAY,OAAO,UAAU,KAAK,MAAM,GAAG,EAAE,CAAC;AAE5D,QAAI,MAAM;AACR,YAAM,6BACH,OAAO,QAAQ,SAAS,CAAA,GAAI,+BAA+B;AAE9D,UAAI,2BAA2B;AACvB,cAAA,KAAK,SAAS,eAAe,IAAI;AACvC,YAAI,IAAI;AACN,aAAG,eAAe,yBAAyB;AAAA,QAAA;AAAA,MAC7C;AAGF;AAAA,IAAA;AAKD;AAAA,MACC;AAAA,MACA,IAAI,6DAAsB,OAAO,CAAC,MAAM,MAAM,cAAa,CAAA;AAAA,IAAC,EAC5D,QAAQ,CAAC,aAAa;AAChB,YAAA,UACJ,aAAa,WACT,SACA,OAAO,aAAa,aAClB,SAAS,IACT,SAAS,cAAc,QAAQ;AACvC,UAAI,SAAS;AACX,gBAAQ,SAAS;AAAA,UACf,KAAK;AAAA,UACL,MAAM;AAAA,UACN;AAAA,QAAA,CACD;AAAA,MAAA;AAAA,IACH,CACD;AAAA,EAAA,GACA;AAGY,iBAAA;AACjB;AAEgB,SAAA,uBAAuB,QAAmB,OAAiB;AACzE,MAAI,2BAA2B,QAAW;AACxC;AAAA,EAAA;AAEF,QAAM,0BACJ,SAAS,OAAO,QAAQ,qBAAqB;AAE/C,MAAI,yBAAyB;AAC3B,WAAO,oBAAoB;AAAA,EAAA;AAG7B,MAAI,OAAO,aAAa,eAAe,OAAO,0BAA0B;AACtE;AAAA,EAAA;AAGF,SAAO,2BAA2B;AAGnB,iBAAA;AAET,QAAA,SACJ,OAAO,QAAQ,2BAA2B;AAE5C,SAAO,QAAQ,oBAAoB;AAuC7B,QAAA,WAAW,CAAC,UAAiB;AAG7B,QAAA,gBAAgB,CAAC,OAAO,mBAAmB;AAC7C;AAAA,IAAA;AAGF,QAAI,kBAAkB;AAEtB,QAAI,MAAM,WAAW,YAAY,MAAM,WAAW,QAAQ;AACtC,wBAAA;AAAA,IAAA,OACb;AACC,YAAA,SAAU,MAAM,OAAmB;AAAA,QACvC;AAAA,MACF;AAEA,UAAI,QAAQ;AACV,0BAAkB,gCAAgC,MAAM;AAAA,MAAA,OACnD;AACa,0BAAA,eAAe,MAAM,MAAM;AAAA,MAAA;AAAA,IAC/C;AAGF,UAAM,aAAa,OAAO,OAAO,MAAM,QAAQ;AAExB,2BAAA,IAAI,CAAC,UAAU;AACpC,YAAM,WAAY,MAAM,UAAU,IAChC,MAAM,UAAU,KAAM,CAAC;AAEzB,YAAM,eAAgB,SAAS,eAAe,IAC5C,SAAS,eAAe,KAAM,CAAC;AAEjC,UAAI,oBAAoB,UAAU;AACnB,qBAAA,UAAU,OAAO,WAAW;AAC5B,qBAAA,UAAU,OAAO,WAAW;AAAA,iBAChC,iBAAiB;AACpB,cAAA,UAAU,SAAS,cAAc,eAAe;AACtD,YAAI,SAAS;AACE,uBAAA,UAAU,QAAQ,cAAc;AAChC,uBAAA,UAAU,QAAQ,aAAa;AAAA,QAAA;AAAA,MAC9C;AAGK,aAAA;AAAA,IAAA,CACR;AAAA,EACH;AAGI,MAAA,OAAO,aAAa,aAAa;AACnC,aAAS,iBAAiB,UAAU,SAAS,UAAU,GAAG,GAAG,IAAI;AAAA,EAAA;AAG5D,SAAA,UAAU,cAAc,CAAC,UAAU;AAGlC,UAAA,WAAW,OAAO,MAAM,UAAU;AAIpC,QAAA,CAAC,OAAO,iBAAiB;AAC3B,aAAO,kBAAkB;AACzB;AAAA,IAAA;AAGY,kBAAA;AAAA,MACZ;AAAA,MACA,KAAK;AAAA,MACL,UAAU,OAAO,QAAQ;AAAA,MACzB,yBAAyB,OAAO;AAAA,MAChC,sBAAsB,OAAO,QAAQ;AAAA,MACrC,UAAU,OAAO,QAAQ;AAAA,IAAA,CAC1B;AAED,QAAI,OAAO,mBAAmB;AAEL,6BAAA,IAAI,CAAC,UAAU;AACpC,cAAM,QAAQ,IAAI,MAAM,QAAQ,KAAM,CAAC;AAEhC,eAAA;AAAA,MAAA,CACR;AAAA,IAAA;AAAA,EACH,CACD;AACH;AAUO,SAAS,iBAAiB,QAAmB;AAClD,MAAI,OAAO,aAAa,eAAgB,SAAiB,eAAe;AACtE,UAAM,4BACJ,OAAO,MAAM,SAAS,MAAM,+BAA+B;AAE7D,QAAI,6BAA6B,OAAO,MAAM,SAAS,SAAS,IAAI;AAClE,YAAM,KAAK,SAAS,eAAe,OAAO,MAAM,SAAS,IAAI;AAC7D,UAAI,IAAI;AACN,WAAG,eAAe,yBAAyB;AAAA,MAAA;AAAA,IAC7C;AAAA,EACF;AAEJ;"}
|
|
1
|
+
{"version":3,"file":"scroll-restoration.js","sources":["../../src/scroll-restoration.ts"],"sourcesContent":["import { functionalUpdate } from './utils'\nimport type { AnyRouter } from './router'\nimport type { ParsedLocation } from './location'\nimport type { NonNullableUpdater } from './utils'\nimport type { HistoryLocation } from '@tanstack/history'\n\nexport type ScrollRestorationEntry = { scrollX: number; scrollY: number }\n\nexport type ScrollRestorationByElement = Record<string, ScrollRestorationEntry>\n\nexport type ScrollRestorationByKey = Record<string, ScrollRestorationByElement>\n\nexport type ScrollRestorationCache = {\n state: ScrollRestorationByKey\n set: (updater: NonNullableUpdater<ScrollRestorationByKey>) => void\n}\nexport type ScrollRestorationOptions = {\n getKey?: (location: ParsedLocation) => string\n scrollBehavior?: ScrollToOptions['behavior']\n}\n\nfunction getSafeSessionStorage() {\n try {\n if (\n typeof window !== 'undefined' &&\n typeof window.sessionStorage === 'object'\n ) {\n return window.sessionStorage\n }\n } catch {\n // silent\n }\n return undefined\n}\n\nexport const storageKey = 'tsr-scroll-restoration-v1_3'\n\nconst throttle = (fn: (...args: Array<any>) => void, wait: number) => {\n let timeout: any\n return (...args: Array<any>) => {\n if (!timeout) {\n timeout = setTimeout(() => {\n fn(...args)\n timeout = null\n }, wait)\n }\n }\n}\n\nfunction createScrollRestorationCache(): ScrollRestorationCache | undefined {\n const safeSessionStorage = getSafeSessionStorage()\n if (!safeSessionStorage) {\n return undefined\n }\n\n const persistedState = safeSessionStorage.getItem(storageKey)\n let state: ScrollRestorationByKey = persistedState\n ? JSON.parse(persistedState)\n : {}\n\n return {\n state,\n // This setter is simply to make sure that we set the sessionStorage right\n // after the state is updated. It doesn't necessarily need to be a functional\n // update.\n set: (updater) => (\n (state = functionalUpdate(updater, state) || state),\n safeSessionStorage.setItem(storageKey, JSON.stringify(state))\n ),\n }\n}\n\nexport const scrollRestorationCache = createScrollRestorationCache()\n\n/**\n * The default `getKey` function for `useScrollRestoration`.\n * It returns the `key` from the location state or the `href` of the location.\n *\n * The `location.href` is used as a fallback to support the use case where the location state is not available like the initial render.\n */\n\nexport const defaultGetScrollRestorationKey = (location: ParsedLocation) => {\n return location.state.__TSR_key! || location.href\n}\n\nexport function getCssSelector(el: any): string {\n const path = []\n let parent: HTMLElement\n while ((parent = el.parentNode)) {\n path.push(\n `${el.tagName}:nth-child(${Array.prototype.indexOf.call(parent.children, el) + 1})`,\n )\n el = parent\n }\n return `${path.reverse().join(' > ')}`.toLowerCase()\n}\n\nlet ignoreScroll = false\n\n// NOTE: This function must remain pure and not use any outside variables\n// unless they are passed in as arguments. Why? Because we need to be able to\n// toString() it into a script tag to execute as early as possible in the browser\n// during SSR. Additionally, we also call it from within the router lifecycle\nexport function restoreScroll({\n storageKey,\n key,\n behavior,\n shouldScrollRestoration,\n scrollToTopSelectors,\n location,\n}: {\n storageKey: string\n key?: string\n behavior?: ScrollToOptions['behavior']\n shouldScrollRestoration?: boolean\n scrollToTopSelectors?: Array<string | (() => Element | null | undefined)>\n location?: HistoryLocation\n}) {\n let byKey: ScrollRestorationByKey\n\n try {\n byKey = JSON.parse(sessionStorage.getItem(storageKey) || '{}')\n } catch (error) {\n console.error(error)\n return\n }\n\n const resolvedKey = key || window.history.state?.key\n const elementEntries = byKey[resolvedKey]\n\n //\n ignoreScroll = true\n\n //\n scroll: {\n // If we have a cached entry for this location state,\n // we always need to prefer that over the hash scroll.\n if (\n shouldScrollRestoration &&\n elementEntries &&\n Object.keys(elementEntries).length > 0\n ) {\n for (const elementSelector in elementEntries) {\n const entry = elementEntries[elementSelector]!\n if (elementSelector === 'window') {\n window.scrollTo({\n top: entry.scrollY,\n left: entry.scrollX,\n behavior,\n })\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n element.scrollLeft = entry.scrollX\n element.scrollTop = entry.scrollY\n }\n }\n }\n\n break scroll\n }\n\n // If we don't have a cached entry for the hash,\n // Which means we've never seen this location before,\n // we need to check if there is a hash in the URL.\n // If there is, we need to scroll it's ID into view.\n const hash = (location ?? window.location).hash.split('#', 2)[1]\n\n if (hash) {\n const hashScrollIntoViewOptions =\n window.history.state?.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions) {\n const el = document.getElementById(hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n\n break scroll\n }\n\n // If there is no cached entry for the hash and there is no hash in the URL,\n // we need to scroll to the top of the page for every scrollToTop element\n const scrollOptions = { top: 0, left: 0, behavior }\n window.scrollTo(scrollOptions)\n if (scrollToTopSelectors) {\n for (const selector of scrollToTopSelectors) {\n if (selector === 'window') continue\n const element =\n typeof selector === 'function'\n ? selector()\n : document.querySelector(selector)\n if (element) element.scrollTo(scrollOptions)\n }\n }\n }\n\n //\n ignoreScroll = false\n}\n\nexport function setupScrollRestoration(router: AnyRouter, force?: boolean) {\n if (scrollRestorationCache === undefined) {\n return\n }\n const shouldScrollRestoration =\n force ?? router.options.scrollRestoration ?? false\n\n if (shouldScrollRestoration) {\n router.isScrollRestoring = true\n }\n\n if (typeof document === 'undefined' || router.isScrollRestorationSetup) {\n return\n }\n\n router.isScrollRestorationSetup = true\n\n //\n ignoreScroll = false\n\n const getKey =\n router.options.getScrollRestorationKey || defaultGetScrollRestorationKey\n\n window.history.scrollRestoration = 'manual'\n\n // // Create a MutationObserver to monitor DOM changes\n // const mutationObserver = new MutationObserver(() => {\n // ;ignoreScroll = true\n // requestAnimationFrame(() => {\n // ;ignoreScroll = false\n\n // // Attempt to restore scroll position on each dom\n // // mutation until the user scrolls. We do this\n // // because dynamic content may come in at different\n // // ticks after the initial render and we want to\n // // keep up with that content as much as possible.\n // // As soon as the user scrolls, we no longer need\n // // to attempt router.\n // // console.log('mutation observer restoreScroll')\n // restoreScroll(\n // storageKey,\n // getKey(router.state.location),\n // router.options.scrollRestorationBehavior,\n // )\n // })\n // })\n\n // const observeDom = () => {\n // // Observe changes to the entire document\n // mutationObserver.observe(document, {\n // childList: true, // Detect added or removed child nodes\n // subtree: true, // Monitor all descendants\n // characterData: true, // Detect text content changes\n // })\n // }\n\n // const unobserveDom = () => {\n // mutationObserver.disconnect()\n // }\n\n // observeDom()\n\n const onScroll = (event: Event) => {\n // unobserveDom()\n\n if (ignoreScroll || !router.isScrollRestoring) {\n return\n }\n\n let elementSelector = ''\n\n if (event.target === document || event.target === window) {\n elementSelector = 'window'\n } else {\n const attrId = (event.target as Element).getAttribute(\n 'data-scroll-restoration-id',\n )\n\n if (attrId) {\n elementSelector = `[data-scroll-restoration-id=\"${attrId}\"]`\n } else {\n elementSelector = getCssSelector(event.target)\n }\n }\n\n const restoreKey = getKey(router.state.location)\n\n scrollRestorationCache.set((state) => {\n const keyEntry = (state[restoreKey] ||= {} as ScrollRestorationByElement)\n\n const elementEntry = (keyEntry[elementSelector] ||=\n {} as ScrollRestorationEntry)\n\n if (elementSelector === 'window') {\n elementEntry.scrollX = window.scrollX || 0\n elementEntry.scrollY = window.scrollY || 0\n } else if (elementSelector) {\n const element = document.querySelector(elementSelector)\n if (element) {\n elementEntry.scrollX = element.scrollLeft || 0\n elementEntry.scrollY = element.scrollTop || 0\n }\n }\n\n return state\n })\n }\n\n // Throttle the scroll event to avoid excessive updates\n if (typeof document !== 'undefined') {\n document.addEventListener('scroll', throttle(onScroll, 100), true)\n }\n\n router.subscribe('onRendered', (event) => {\n // unobserveDom()\n\n const cacheKey = getKey(event.toLocation)\n\n // If the user doesn't want to restore the scroll position,\n // we don't need to do anything.\n if (!router.resetNextScroll) {\n router.resetNextScroll = true\n return\n }\n\n restoreScroll({\n storageKey,\n key: cacheKey,\n behavior: router.options.scrollRestorationBehavior,\n shouldScrollRestoration: router.isScrollRestoring,\n scrollToTopSelectors: router.options.scrollToTopSelectors,\n location: router.history.location,\n })\n\n if (router.isScrollRestoring) {\n // Mark the location as having been seen\n scrollRestorationCache.set((state) => {\n state[cacheKey] ||= {} as ScrollRestorationByElement\n\n return state\n })\n }\n })\n}\n\n/**\n * @internal\n * Handles hash-based scrolling after navigation completes.\n * To be used in framework-specific <Transitioner> components during the onResolved event.\n *\n * Provides hash scrolling for programmatic navigation when default browser handling is prevented.\n * @param router The router instance containing current location and state\n */\nexport function handleHashScroll(router: AnyRouter) {\n if (typeof document !== 'undefined' && (document as any).querySelector) {\n const hashScrollIntoViewOptions =\n router.state.location.state.__hashScrollIntoViewOptions ?? true\n\n if (hashScrollIntoViewOptions && router.state.location.hash !== '') {\n const el = document.getElementById(router.state.location.hash)\n if (el) {\n el.scrollIntoView(hashScrollIntoViewOptions)\n }\n }\n }\n}\n"],"names":["storageKey"],"mappings":"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"}
|
package/dist/esm/searchParams.js
CHANGED
|
@@ -6,7 +6,7 @@ const defaultStringifySearch = stringifySearchWith(
|
|
|
6
6
|
);
|
|
7
7
|
function parseSearchWith(parser) {
|
|
8
8
|
return (searchStr) => {
|
|
9
|
-
if (searchStr
|
|
9
|
+
if (searchStr[0] === "?") {
|
|
10
10
|
searchStr = searchStr.substring(1);
|
|
11
11
|
}
|
|
12
12
|
const query = decode(searchStr);
|
|
@@ -15,7 +15,7 @@ function parseSearchWith(parser) {
|
|
|
15
15
|
if (typeof value === "string") {
|
|
16
16
|
try {
|
|
17
17
|
query[key] = parser(value);
|
|
18
|
-
} catch (
|
|
18
|
+
} catch (_err) {
|
|
19
19
|
}
|
|
20
20
|
}
|
|
21
21
|
}
|
|
@@ -23,32 +23,24 @@ function parseSearchWith(parser) {
|
|
|
23
23
|
};
|
|
24
24
|
}
|
|
25
25
|
function stringifySearchWith(stringify, parser) {
|
|
26
|
+
const hasParser = typeof parser === "function";
|
|
26
27
|
function stringifyValue(val) {
|
|
27
28
|
if (typeof val === "object" && val !== null) {
|
|
28
29
|
try {
|
|
29
30
|
return stringify(val);
|
|
30
|
-
} catch (
|
|
31
|
+
} catch (_err) {
|
|
31
32
|
}
|
|
32
|
-
} else if (
|
|
33
|
+
} else if (hasParser && typeof val === "string") {
|
|
33
34
|
try {
|
|
34
35
|
parser(val);
|
|
35
36
|
return stringify(val);
|
|
36
|
-
} catch (
|
|
37
|
+
} catch (_err) {
|
|
37
38
|
}
|
|
38
39
|
}
|
|
39
40
|
return val;
|
|
40
41
|
}
|
|
41
42
|
return (search) => {
|
|
42
|
-
|
|
43
|
-
Object.keys(search).forEach((key) => {
|
|
44
|
-
const val = search[key];
|
|
45
|
-
if (typeof val === "undefined" || val === void 0) {
|
|
46
|
-
delete search[key];
|
|
47
|
-
} else {
|
|
48
|
-
search[key] = stringifyValue(val);
|
|
49
|
-
}
|
|
50
|
-
});
|
|
51
|
-
const searchStr = encode(search).toString();
|
|
43
|
+
const searchStr = encode(search, stringifyValue);
|
|
52
44
|
return searchStr ? `?${searchStr}` : "";
|
|
53
45
|
};
|
|
54
46
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"searchParams.js","sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr
|
|
1
|
+
{"version":3,"file":"searchParams.js","sources":["../../src/searchParams.ts"],"sourcesContent":["import { decode, encode } from './qss'\nimport type { AnySchema } from './validators'\n\nexport const defaultParseSearch = parseSearchWith(JSON.parse)\nexport const defaultStringifySearch = stringifySearchWith(\n JSON.stringify,\n JSON.parse,\n)\n\nexport function parseSearchWith(parser: (str: string) => any) {\n return (searchStr: string): AnySchema => {\n if (searchStr[0] === '?') {\n searchStr = searchStr.substring(1)\n }\n\n const query: Record<string, unknown> = decode(searchStr)\n\n // Try to parse any query params that might be json\n for (const key in query) {\n const value = query[key]\n if (typeof value === 'string') {\n try {\n query[key] = parser(value)\n } catch (_err) {\n // silent\n }\n }\n }\n\n return query\n }\n}\n\nexport function stringifySearchWith(\n stringify: (search: any) => string,\n parser?: (str: string) => any,\n) {\n const hasParser = typeof parser === 'function'\n function stringifyValue(val: any) {\n if (typeof val === 'object' && val !== null) {\n try {\n return stringify(val)\n } catch (_err) {\n // silent\n }\n } else if (hasParser && typeof val === 'string') {\n try {\n // Check if it's a valid parseable string.\n // If it is, then stringify it again.\n parser(val)\n return stringify(val)\n } catch (_err) {\n // silent\n }\n }\n return val\n }\n\n return (search: Record<string, any>) => {\n const searchStr = encode(search, stringifyValue)\n return searchStr ? `?${searchStr}` : ''\n }\n}\n\nexport type SearchSerializer = (searchObj: Record<string, any>) => string\nexport type SearchParser = (searchStr: string) => Record<string, any>\n"],"names":[],"mappings":";AAGa,MAAA,qBAAqB,gBAAgB,KAAK,KAAK;AACrD,MAAM,yBAAyB;AAAA,EACpC,KAAK;AAAA,EACL,KAAK;AACP;AAEO,SAAS,gBAAgB,QAA8B;AAC5D,SAAO,CAAC,cAAiC;AACnC,QAAA,UAAU,CAAC,MAAM,KAAK;AACZ,kBAAA,UAAU,UAAU,CAAC;AAAA,IAAA;AAG7B,UAAA,QAAiC,OAAO,SAAS;AAGvD,eAAW,OAAO,OAAO;AACjB,YAAA,QAAQ,MAAM,GAAG;AACnB,UAAA,OAAO,UAAU,UAAU;AACzB,YAAA;AACI,gBAAA,GAAG,IAAI,OAAO,KAAK;AAAA,iBAClB,MAAM;AAAA,QAAA;AAAA,MAEf;AAAA,IACF;AAGK,WAAA;AAAA,EACT;AACF;AAEgB,SAAA,oBACd,WACA,QACA;AACM,QAAA,YAAY,OAAO,WAAW;AACpC,WAAS,eAAe,KAAU;AAChC,QAAI,OAAO,QAAQ,YAAY,QAAQ,MAAM;AACvC,UAAA;AACF,eAAO,UAAU,GAAG;AAAA,eACb,MAAM;AAAA,MAAA;AAAA,IAGN,WAAA,aAAa,OAAO,QAAQ,UAAU;AAC3C,UAAA;AAGF,eAAO,GAAG;AACV,eAAO,UAAU,GAAG;AAAA,eACb,MAAM;AAAA,MAAA;AAAA,IAEf;AAEK,WAAA;AAAA,EAAA;AAGT,SAAO,CAAC,WAAgC;AAChC,UAAA,YAAY,OAAO,QAAQ,cAAc;AACxC,WAAA,YAAY,IAAI,SAAS,KAAK;AAAA,EACvC;AACF;"}
|
package/package.json
CHANGED
package/src/load-matches.ts
CHANGED
|
@@ -147,11 +147,10 @@ const shouldSkipLoader = (
|
|
|
147
147
|
return true
|
|
148
148
|
}
|
|
149
149
|
|
|
150
|
-
if (inner.router.isServer) {
|
|
151
|
-
|
|
152
|
-
return true
|
|
153
|
-
}
|
|
150
|
+
if (inner.router.isServer && match.ssr === false) {
|
|
151
|
+
return true
|
|
154
152
|
}
|
|
153
|
+
|
|
155
154
|
return false
|
|
156
155
|
}
|
|
157
156
|
|
|
@@ -302,11 +301,11 @@ const setupPendingTimeout = (
|
|
|
302
301
|
}
|
|
303
302
|
}
|
|
304
303
|
|
|
305
|
-
const
|
|
304
|
+
const preBeforeLoadSetup = (
|
|
306
305
|
inner: InnerLoadContext,
|
|
307
306
|
matchId: string,
|
|
308
307
|
route: AnyRoute,
|
|
309
|
-
):
|
|
308
|
+
): void | Promise<void> => {
|
|
310
309
|
const existingMatch = inner.router.getMatch(matchId)!
|
|
311
310
|
|
|
312
311
|
// If we are in the middle of a load, either of these will be present
|
|
@@ -315,25 +314,21 @@ const shouldExecuteBeforeLoad = (
|
|
|
315
314
|
!existingMatch._nonReactive.beforeLoadPromise &&
|
|
316
315
|
!existingMatch._nonReactive.loaderPromise
|
|
317
316
|
)
|
|
318
|
-
return
|
|
317
|
+
return
|
|
319
318
|
|
|
320
319
|
setupPendingTimeout(inner, matchId, route, existingMatch)
|
|
321
320
|
|
|
322
321
|
const then = () => {
|
|
323
|
-
let result = true
|
|
324
322
|
const match = inner.router.getMatch(matchId)!
|
|
325
|
-
if (
|
|
326
|
-
result = true
|
|
327
|
-
} else if (
|
|
323
|
+
if (
|
|
328
324
|
match.preload &&
|
|
329
325
|
(match.status === 'redirected' || match.status === 'notFound')
|
|
330
326
|
) {
|
|
331
327
|
handleRedirectAndNotFound(inner, match, match.error)
|
|
332
328
|
}
|
|
333
|
-
return result
|
|
334
329
|
}
|
|
335
330
|
|
|
336
|
-
// Wait for the beforeLoad to resolve before we continue
|
|
331
|
+
// Wait for the previous beforeLoad to resolve before we continue
|
|
337
332
|
return existingMatch._nonReactive.beforeLoadPromise
|
|
338
333
|
? existingMatch._nonReactive.beforeLoadPromise.then(then)
|
|
339
334
|
: then()
|
|
@@ -494,24 +489,12 @@ const handleBeforeLoad = (
|
|
|
494
489
|
|
|
495
490
|
const queueExecution = () => {
|
|
496
491
|
if (shouldSkipLoader(inner, matchId)) return
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
matchId,
|
|
500
|
-
route,
|
|
501
|
-
)
|
|
502
|
-
return isPromise(shouldExecuteBeforeLoadResult)
|
|
503
|
-
? shouldExecuteBeforeLoadResult.then(execute)
|
|
504
|
-
: execute(shouldExecuteBeforeLoadResult)
|
|
505
|
-
}
|
|
506
|
-
|
|
507
|
-
const execute = (shouldExec: boolean) => {
|
|
508
|
-
if (shouldExec) {
|
|
509
|
-
// If we are not in the middle of a load OR the previous load failed, start it
|
|
510
|
-
return executeBeforeLoad(inner, matchId, index, route)
|
|
511
|
-
}
|
|
512
|
-
return
|
|
492
|
+
const result = preBeforeLoadSetup(inner, matchId, route)
|
|
493
|
+
return isPromise(result) ? result.then(execute) : execute()
|
|
513
494
|
}
|
|
514
495
|
|
|
496
|
+
const execute = () => executeBeforeLoad(inner, matchId, index, route)
|
|
497
|
+
|
|
515
498
|
return serverSsr()
|
|
516
499
|
}
|
|
517
500
|
|
package/src/qss.ts
CHANGED
|
@@ -6,12 +6,15 @@
|
|
|
6
6
|
* This reimplementation uses modern browser APIs
|
|
7
7
|
* (namely URLSearchParams) and TypeScript while still
|
|
8
8
|
* maintaining the original functionality and interface.
|
|
9
|
+
*
|
|
10
|
+
* Update: this implementation has also been mangled to
|
|
11
|
+
* fit exactly our use-case (single value per key in encoding).
|
|
9
12
|
*/
|
|
10
13
|
|
|
11
14
|
/**
|
|
12
15
|
* Encodes an object into a query string.
|
|
13
16
|
* @param obj - The object to encode into a query string.
|
|
14
|
-
* @param
|
|
17
|
+
* @param stringify - An optional custom stringify function.
|
|
15
18
|
* @returns The encoded query string.
|
|
16
19
|
* @example
|
|
17
20
|
* ```
|
|
@@ -19,18 +22,20 @@
|
|
|
19
22
|
* // Expected output: "token=foo&key=value"
|
|
20
23
|
* ```
|
|
21
24
|
*/
|
|
22
|
-
export function encode(
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
return [[key, String(value)]]
|
|
28
|
-
}
|
|
29
|
-
})
|
|
25
|
+
export function encode(
|
|
26
|
+
obj: Record<string, any>,
|
|
27
|
+
stringify: (value: any) => string = String,
|
|
28
|
+
): string {
|
|
29
|
+
const result = new URLSearchParams()
|
|
30
30
|
|
|
31
|
-
const
|
|
31
|
+
for (const key in obj) {
|
|
32
|
+
const val = obj[key]
|
|
33
|
+
if (val !== undefined) {
|
|
34
|
+
result.set(key, stringify(val))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
32
37
|
|
|
33
|
-
return
|
|
38
|
+
return result.toString()
|
|
34
39
|
}
|
|
35
40
|
|
|
36
41
|
/**
|
|
@@ -52,28 +57,26 @@ function toValue(str: unknown) {
|
|
|
52
57
|
/**
|
|
53
58
|
* Decodes a query string into an object.
|
|
54
59
|
* @param str - The query string to decode.
|
|
55
|
-
* @param [pfx] - An optional prefix to filter out from the query string.
|
|
56
60
|
* @returns The decoded key-value pairs in an object format.
|
|
57
61
|
* @example
|
|
58
62
|
* // Example input: decode("token=foo&key=value")
|
|
59
63
|
* // Expected output: { "token": "foo", "key": "value" }
|
|
60
64
|
*/
|
|
61
|
-
export function decode(str: any
|
|
62
|
-
const
|
|
63
|
-
const searchParams = new URLSearchParams(searchParamsPart)
|
|
65
|
+
export function decode(str: any): any {
|
|
66
|
+
const searchParams = new URLSearchParams(str)
|
|
64
67
|
|
|
65
|
-
const
|
|
68
|
+
const result: Record<string, unknown> = {}
|
|
66
69
|
|
|
67
|
-
|
|
68
|
-
const previousValue =
|
|
70
|
+
for (const [key, value] of searchParams.entries()) {
|
|
71
|
+
const previousValue = result[key]
|
|
69
72
|
if (previousValue == null) {
|
|
70
|
-
|
|
73
|
+
result[key] = toValue(value)
|
|
74
|
+
} else if (Array.isArray(previousValue)) {
|
|
75
|
+
previousValue.push(toValue(value))
|
|
71
76
|
} else {
|
|
72
|
-
|
|
73
|
-
? [...previousValue, toValue(value)]
|
|
74
|
-
: [previousValue, toValue(value)]
|
|
77
|
+
result[key] = [previousValue, toValue(value)]
|
|
75
78
|
}
|
|
79
|
+
}
|
|
76
80
|
|
|
77
|
-
|
|
78
|
-
}, {})
|
|
81
|
+
return result
|
|
79
82
|
}
|
package/src/router.ts
CHANGED
|
@@ -1414,50 +1414,44 @@ export class RouterCore<
|
|
|
1414
1414
|
_buildLocation: true,
|
|
1415
1415
|
})
|
|
1416
1416
|
|
|
1417
|
+
// Now let's find the starting pathname
|
|
1418
|
+
// This should default to the current location if no from is provided
|
|
1417
1419
|
const lastMatch = last(allCurrentLocationMatches)!
|
|
1418
1420
|
|
|
1419
|
-
//
|
|
1420
|
-
//
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
// If the route is changing we need to find the relative fromPath
|
|
1432
|
-
if (dest.unsafeRelative === 'path') {
|
|
1433
|
-
fromPath = currentLocation.pathname
|
|
1434
|
-
} else if (routeIsChanging && dest.from) {
|
|
1435
|
-
fromPath = dest.from
|
|
1436
|
-
|
|
1437
|
-
// do this check only on navigations during test or development
|
|
1438
|
-
if (process.env.NODE_ENV !== 'production' && dest._isNavigate) {
|
|
1439
|
-
const allFromMatches = this.getMatchedRoutes(
|
|
1440
|
-
dest.from,
|
|
1441
|
-
undefined,
|
|
1442
|
-
).matchedRoutes
|
|
1421
|
+
// check that from path exists in the current route tree
|
|
1422
|
+
// do this check only on navigations during test or development
|
|
1423
|
+
if (
|
|
1424
|
+
dest.from &&
|
|
1425
|
+
process.env.NODE_ENV !== 'production' &&
|
|
1426
|
+
dest._isNavigate
|
|
1427
|
+
) {
|
|
1428
|
+
const allFromMatches = this.getMatchedRoutes(
|
|
1429
|
+
dest.from,
|
|
1430
|
+
undefined,
|
|
1431
|
+
).matchedRoutes
|
|
1443
1432
|
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1433
|
+
const matchedFrom = findLast(allCurrentLocationMatches, (d) => {
|
|
1434
|
+
return comparePaths(d.fullPath, dest.from!)
|
|
1435
|
+
})
|
|
1447
1436
|
|
|
1448
|
-
|
|
1449
|
-
|
|
1450
|
-
|
|
1437
|
+
const matchedCurrent = findLast(allFromMatches, (d) => {
|
|
1438
|
+
return comparePaths(d.fullPath, lastMatch.fullPath)
|
|
1439
|
+
})
|
|
1451
1440
|
|
|
1452
|
-
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
}
|
|
1441
|
+
// for from to be invalid it shouldn't just be unmatched to currentLocation
|
|
1442
|
+
// but the currentLocation should also be unmatched to from
|
|
1443
|
+
if (!matchedFrom && !matchedCurrent) {
|
|
1444
|
+
console.warn(`Could not find match for from: ${dest.from}`)
|
|
1457
1445
|
}
|
|
1458
1446
|
}
|
|
1459
1447
|
|
|
1460
|
-
|
|
1448
|
+
const defaultedFromPath =
|
|
1449
|
+
dest.unsafeRelative === 'path'
|
|
1450
|
+
? currentLocation.pathname
|
|
1451
|
+
: (dest.from ?? lastMatch.fullPath)
|
|
1452
|
+
|
|
1453
|
+
// ensure this includes the basePath if set
|
|
1454
|
+
const fromPath = this.resolvePathWithBase(defaultedFromPath, '.')
|
|
1461
1455
|
|
|
1462
1456
|
// From search should always use the current location
|
|
1463
1457
|
const fromSearch = lastMatch.search
|
|
@@ -1465,6 +1459,7 @@ export class RouterCore<
|
|
|
1465
1459
|
const fromParams = { ...lastMatch.params }
|
|
1466
1460
|
|
|
1467
1461
|
// Resolve the next to
|
|
1462
|
+
// ensure this includes the basePath if set
|
|
1468
1463
|
const nextTo = dest.to
|
|
1469
1464
|
? this.resolvePathWithBase(fromPath, `${dest.to}`)
|
|
1470
1465
|
: this.resolvePathWithBase(fromPath, '.')
|