@tanstack/router-core 1.167.1 → 1.167.2

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.
Files changed (162) hide show
  1. package/dist/cjs/Matches.cjs +15 -12
  2. package/dist/cjs/Matches.cjs.map +1 -1
  3. package/dist/cjs/_virtual/_rolldown/runtime.cjs +23 -0
  4. package/dist/cjs/config.cjs +9 -8
  5. package/dist/cjs/config.cjs.map +1 -1
  6. package/dist/cjs/defer.cjs +37 -21
  7. package/dist/cjs/defer.cjs.map +1 -1
  8. package/dist/cjs/index.cjs +87 -89
  9. package/dist/cjs/isServer/client.cjs +5 -3
  10. package/dist/cjs/isServer/client.cjs.map +1 -1
  11. package/dist/cjs/isServer/development.cjs +5 -3
  12. package/dist/cjs/isServer/development.cjs.map +1 -1
  13. package/dist/cjs/isServer/server.cjs +5 -3
  14. package/dist/cjs/isServer/server.cjs.map +1 -1
  15. package/dist/cjs/link.cjs +5 -4
  16. package/dist/cjs/link.cjs.map +1 -1
  17. package/dist/cjs/load-matches.cjs +619 -766
  18. package/dist/cjs/load-matches.cjs.map +1 -1
  19. package/dist/cjs/lru-cache.cjs +67 -64
  20. package/dist/cjs/lru-cache.cjs.map +1 -1
  21. package/dist/cjs/new-process-route-tree.cjs +707 -792
  22. package/dist/cjs/new-process-route-tree.cjs.map +1 -1
  23. package/dist/cjs/not-found.cjs +20 -7
  24. package/dist/cjs/not-found.cjs.map +1 -1
  25. package/dist/cjs/path.cjs +221 -232
  26. package/dist/cjs/path.cjs.map +1 -1
  27. package/dist/cjs/qss.cjs +62 -28
  28. package/dist/cjs/qss.cjs.map +1 -1
  29. package/dist/cjs/redirect.cjs +44 -30
  30. package/dist/cjs/redirect.cjs.map +1 -1
  31. package/dist/cjs/rewrite.cjs +56 -56
  32. package/dist/cjs/rewrite.cjs.map +1 -1
  33. package/dist/cjs/root.cjs +6 -4
  34. package/dist/cjs/root.cjs.map +1 -1
  35. package/dist/cjs/route.cjs +96 -105
  36. package/dist/cjs/route.cjs.map +1 -1
  37. package/dist/cjs/router.cjs +1153 -1524
  38. package/dist/cjs/router.cjs.map +1 -1
  39. package/dist/cjs/scroll-restoration.cjs +189 -207
  40. package/dist/cjs/scroll-restoration.cjs.map +1 -1
  41. package/dist/cjs/searchMiddleware.cjs +48 -37
  42. package/dist/cjs/searchMiddleware.cjs.map +1 -1
  43. package/dist/cjs/searchParams.cjs +57 -45
  44. package/dist/cjs/searchParams.cjs.map +1 -1
  45. package/dist/cjs/ssr/client.cjs +6 -8
  46. package/dist/cjs/ssr/constants.cjs +6 -5
  47. package/dist/cjs/ssr/constants.cjs.map +1 -1
  48. package/dist/cjs/ssr/createRequestHandler.cjs +41 -59
  49. package/dist/cjs/ssr/createRequestHandler.cjs.map +1 -1
  50. package/dist/cjs/ssr/handlerCallback.cjs +5 -4
  51. package/dist/cjs/ssr/handlerCallback.cjs.map +1 -1
  52. package/dist/cjs/ssr/headers.cjs +17 -26
  53. package/dist/cjs/ssr/headers.cjs.map +1 -1
  54. package/dist/cjs/ssr/json.cjs +8 -4
  55. package/dist/cjs/ssr/json.cjs.map +1 -1
  56. package/dist/cjs/ssr/serializer/RawStream.cjs +268 -268
  57. package/dist/cjs/ssr/serializer/RawStream.cjs.map +1 -1
  58. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs +31 -32
  59. package/dist/cjs/ssr/serializer/ShallowErrorPlugin.cjs.map +1 -1
  60. package/dist/cjs/ssr/serializer/seroval-plugins.cjs +12 -12
  61. package/dist/cjs/ssr/serializer/seroval-plugins.cjs.map +1 -1
  62. package/dist/cjs/ssr/serializer/transformer.cjs +45 -41
  63. package/dist/cjs/ssr/serializer/transformer.cjs.map +1 -1
  64. package/dist/cjs/ssr/server.cjs +12 -14
  65. package/dist/cjs/ssr/ssr-client.cjs +173 -211
  66. package/dist/cjs/ssr/ssr-client.cjs.map +1 -1
  67. package/dist/cjs/ssr/ssr-match-id.cjs +6 -5
  68. package/dist/cjs/ssr/ssr-match-id.cjs.map +1 -1
  69. package/dist/cjs/ssr/ssr-server.cjs +266 -300
  70. package/dist/cjs/ssr/ssr-server.cjs.map +1 -1
  71. package/dist/cjs/ssr/transformStreamWithRouter.cjs +317 -337
  72. package/dist/cjs/ssr/transformStreamWithRouter.cjs.map +1 -1
  73. package/dist/cjs/ssr/tsrScript.cjs +6 -4
  74. package/dist/cjs/ssr/tsrScript.cjs.map +1 -1
  75. package/dist/cjs/utils/batch.cjs +13 -13
  76. package/dist/cjs/utils/batch.cjs.map +1 -1
  77. package/dist/cjs/utils.cjs +274 -208
  78. package/dist/cjs/utils.cjs.map +1 -1
  79. package/dist/esm/Matches.js +16 -13
  80. package/dist/esm/Matches.js.map +1 -1
  81. package/dist/esm/config.js +10 -9
  82. package/dist/esm/config.js.map +1 -1
  83. package/dist/esm/defer.js +37 -22
  84. package/dist/esm/defer.js.map +1 -1
  85. package/dist/esm/index.js +12 -82
  86. package/dist/esm/isServer/client.js +6 -5
  87. package/dist/esm/isServer/client.js.map +1 -1
  88. package/dist/esm/isServer/development.js +6 -5
  89. package/dist/esm/isServer/development.js.map +1 -1
  90. package/dist/esm/isServer/server.js +6 -5
  91. package/dist/esm/isServer/server.js.map +1 -1
  92. package/dist/esm/link.js +6 -5
  93. package/dist/esm/link.js.map +1 -1
  94. package/dist/esm/load-matches.js +614 -765
  95. package/dist/esm/load-matches.js.map +1 -1
  96. package/dist/esm/lru-cache.js +68 -65
  97. package/dist/esm/lru-cache.js.map +1 -1
  98. package/dist/esm/new-process-route-tree.js +705 -797
  99. package/dist/esm/new-process-route-tree.js.map +1 -1
  100. package/dist/esm/not-found.js +21 -9
  101. package/dist/esm/not-found.js.map +1 -1
  102. package/dist/esm/path.js +220 -241
  103. package/dist/esm/path.js.map +1 -1
  104. package/dist/esm/qss.js +63 -30
  105. package/dist/esm/qss.js.map +1 -1
  106. package/dist/esm/redirect.js +45 -34
  107. package/dist/esm/redirect.js.map +1 -1
  108. package/dist/esm/rewrite.js +57 -60
  109. package/dist/esm/rewrite.js.map +1 -1
  110. package/dist/esm/root.js +7 -5
  111. package/dist/esm/root.js.map +1 -1
  112. package/dist/esm/route.js +92 -105
  113. package/dist/esm/route.js.map +1 -1
  114. package/dist/esm/router.js +1147 -1527
  115. package/dist/esm/router.js.map +1 -1
  116. package/dist/esm/scroll-restoration.js +188 -213
  117. package/dist/esm/scroll-restoration.js.map +1 -1
  118. package/dist/esm/searchMiddleware.js +48 -38
  119. package/dist/esm/searchMiddleware.js.map +1 -1
  120. package/dist/esm/searchParams.js +57 -48
  121. package/dist/esm/searchParams.js.map +1 -1
  122. package/dist/esm/ssr/client.js +1 -6
  123. package/dist/esm/ssr/constants.js +7 -7
  124. package/dist/esm/ssr/constants.js.map +1 -1
  125. package/dist/esm/ssr/createRequestHandler.js +39 -58
  126. package/dist/esm/ssr/createRequestHandler.js.map +1 -1
  127. package/dist/esm/ssr/handlerCallback.js +6 -5
  128. package/dist/esm/ssr/handlerCallback.js.map +1 -1
  129. package/dist/esm/ssr/headers.js +16 -26
  130. package/dist/esm/ssr/headers.js.map +1 -1
  131. package/dist/esm/ssr/json.js +9 -5
  132. package/dist/esm/ssr/json.js.map +1 -1
  133. package/dist/esm/ssr/serializer/RawStream.js +267 -273
  134. package/dist/esm/ssr/serializer/RawStream.js.map +1 -1
  135. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js +31 -32
  136. package/dist/esm/ssr/serializer/ShallowErrorPlugin.js.map +1 -1
  137. package/dist/esm/ssr/serializer/seroval-plugins.js +10 -11
  138. package/dist/esm/ssr/serializer/seroval-plugins.js.map +1 -1
  139. package/dist/esm/ssr/serializer/transformer.js +44 -43
  140. package/dist/esm/ssr/serializer/transformer.js.map +1 -1
  141. package/dist/esm/ssr/server.js +2 -12
  142. package/dist/esm/ssr/ssr-client.js +169 -209
  143. package/dist/esm/ssr/ssr-client.js.map +1 -1
  144. package/dist/esm/ssr/ssr-match-id.js +7 -7
  145. package/dist/esm/ssr/ssr-match-id.js.map +1 -1
  146. package/dist/esm/ssr/ssr-server.js +262 -300
  147. package/dist/esm/ssr/ssr-server.js.map +1 -1
  148. package/dist/esm/ssr/transformStreamWithRouter.js +315 -338
  149. package/dist/esm/ssr/transformStreamWithRouter.js.map +1 -1
  150. package/dist/esm/ssr/tsrScript.js +6 -5
  151. package/dist/esm/ssr/tsrScript.js.map +1 -1
  152. package/dist/esm/utils/batch.js +13 -14
  153. package/dist/esm/utils/batch.js.map +1 -1
  154. package/dist/esm/utils.js +273 -224
  155. package/dist/esm/utils.js.map +1 -1
  156. package/package.json +2 -2
  157. package/dist/cjs/index.cjs.map +0 -1
  158. package/dist/cjs/ssr/client.cjs.map +0 -1
  159. package/dist/cjs/ssr/server.cjs.map +0 -1
  160. package/dist/esm/index.js.map +0 -1
  161. package/dist/esm/ssr/client.js.map +0 -1
  162. package/dist/esm/ssr/server.js.map +0 -1
@@ -1,1557 +1,1177 @@
1
- import { createStore } from "@tanstack/store";
2
- import { createBrowserHistory, parseHref } from "@tanstack/history";
3
- import { isServer } from "@tanstack/router-core/isServer";
4
- import { batch } from "./utils/batch.js";
5
- import { createControlledPromise, isDangerousProtocol, deepEqual, DEFAULT_PROTOCOL_ALLOWLIST, nullReplaceEqualDeep, replaceEqualDeep, last, decodePath, functionalUpdate, findLast, encodePathLikeUrl } from "./utils.js";
6
- import { processRouteTree, processRouteMasks, findSingleMatch, findRouteMatch, findFlatMatch } from "./new-process-route-tree.js";
7
- import { compileDecodeCharMap, trimPath, resolvePath, cleanPath, trimPathRight, interpolatePath } from "./path.js";
1
+ import { batch as batch$1 } from "./utils/batch.js";
2
+ import { DEFAULT_PROTOCOL_ALLOWLIST, createControlledPromise, decodePath, deepEqual, encodePathLikeUrl, findLast, functionalUpdate, isDangerousProtocol, last, nullReplaceEqualDeep, replaceEqualDeep } from "./utils.js";
8
3
  import { createLRUCache } from "./lru-cache.js";
4
+ import { findFlatMatch, findRouteMatch, findSingleMatch, processRouteMasks, processRouteTree } from "./new-process-route-tree.js";
5
+ import { cleanPath, compileDecodeCharMap, interpolatePath, resolvePath, trimPath, trimPathRight } from "./path.js";
9
6
  import { isNotFound } from "./not-found.js";
10
7
  import { setupScrollRestoration } from "./scroll-restoration.js";
11
8
  import { defaultParseSearch, defaultStringifySearch } from "./searchParams.js";
12
9
  import { rootRouteId } from "./root.js";
13
- import { redirect, isRedirect } from "./redirect.js";
10
+ import { isRedirect, redirect } from "./redirect.js";
14
11
  import { loadMatches, loadRouteChunk, routeNeedsPreload } from "./load-matches.js";
15
- import { rewriteBasepath, composeRewrites, executeRewriteInput, executeRewriteOutput } from "./rewrite.js";
12
+ import { composeRewrites, executeRewriteInput, executeRewriteOutput, rewriteBasepath } from "./rewrite.js";
13
+ import { createStore } from "@tanstack/store";
14
+ import { createBrowserHistory, parseHref } from "@tanstack/history";
15
+ import { isServer } from "@tanstack/router-core/isServer";
16
+ //#region src/router.ts
17
+ /**
18
+ * Convert an unknown error into a minimal, serializable object.
19
+ * Includes name and message (and stack in development).
20
+ */
16
21
  function defaultSerializeError(err) {
17
- if (err instanceof Error) {
18
- const obj = {
19
- name: err.name,
20
- message: err.message
21
- };
22
- if (process.env.NODE_ENV === "development") {
23
- obj.stack = err.stack;
24
- }
25
- return obj;
26
- }
27
- return {
28
- data: err
29
- };
22
+ if (err instanceof Error) {
23
+ const obj = {
24
+ name: err.name,
25
+ message: err.message
26
+ };
27
+ if (process.env.NODE_ENV === "development") obj.stack = err.stack;
28
+ return obj;
29
+ }
30
+ return { data: err };
30
31
  }
31
- const trailingSlashOptions = {
32
- always: "always",
33
- never: "never",
34
- preserve: "preserve"
32
+ /** Options for configuring trailing-slash behavior. */
33
+ var trailingSlashOptions = {
34
+ always: "always",
35
+ never: "never",
36
+ preserve: "preserve"
35
37
  };
38
+ /**
39
+ * Compute whether path, href or hash changed between previous and current
40
+ * resolved locations in router state.
41
+ */
36
42
  function getLocationChangeInfo(routerState) {
37
- const fromLocation = routerState.resolvedLocation;
38
- const toLocation = routerState.location;
39
- const pathChanged = fromLocation?.pathname !== toLocation.pathname;
40
- const hrefChanged = fromLocation?.href !== toLocation.href;
41
- const hashChanged = fromLocation?.hash !== toLocation.hash;
42
- return { fromLocation, toLocation, pathChanged, hrefChanged, hashChanged };
43
+ const fromLocation = routerState.resolvedLocation;
44
+ const toLocation = routerState.location;
45
+ return {
46
+ fromLocation,
47
+ toLocation,
48
+ pathChanged: fromLocation?.pathname !== toLocation.pathname,
49
+ hrefChanged: fromLocation?.href !== toLocation.href,
50
+ hashChanged: fromLocation?.hash !== toLocation.hash
51
+ };
43
52
  }
44
53
  function filterRedirectedCachedMatches(matches) {
45
- const filtered = matches.filter((d) => d.status !== "redirected");
46
- return filtered.length === matches.length ? matches : filtered;
54
+ const filtered = matches.filter((d) => d.status !== "redirected");
55
+ return filtered.length === matches.length ? matches : filtered;
47
56
  }
48
57
  function createServerStore(initialState) {
49
- const store = {
50
- state: initialState,
51
- setState: (updater) => {
52
- store.state = updater(store.state);
53
- }
54
- };
55
- return store;
58
+ const store = {
59
+ state: initialState,
60
+ setState: (updater) => {
61
+ store.state = updater(store.state);
62
+ }
63
+ };
64
+ return store;
56
65
  }
57
- class RouterCore {
58
- /**
59
- * @deprecated Use the `createRouter` function instead
60
- */
61
- constructor(options) {
62
- this.tempLocationKey = `${Math.round(
63
- Math.random() * 1e7
64
- )}`;
65
- this.resetNextScroll = true;
66
- this.shouldViewTransition = void 0;
67
- this.isViewTransitionTypesSupported = void 0;
68
- this.subscribers = /* @__PURE__ */ new Set();
69
- this.isScrollRestoring = false;
70
- this.isScrollRestorationSetup = false;
71
- this.startTransition = (fn) => fn();
72
- this.update = (newOptions) => {
73
- if (process.env.NODE_ENV !== "production") {
74
- if (newOptions.notFoundRoute) {
75
- console.warn(
76
- "The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info."
77
- );
78
- }
79
- }
80
- const prevOptions = this.options;
81
- const prevBasepath = this.basepath ?? prevOptions?.basepath ?? "/";
82
- const basepathWasUnset = this.basepath === void 0;
83
- const prevRewriteOption = prevOptions?.rewrite;
84
- this.options = {
85
- ...prevOptions,
86
- ...newOptions
87
- };
88
- this.isServer = this.options.isServer ?? typeof document === "undefined";
89
- this.protocolAllowlist = new Set(this.options.protocolAllowlist);
90
- if (this.options.pathParamsAllowedCharacters)
91
- this.pathParamsDecoder = compileDecodeCharMap(
92
- this.options.pathParamsAllowedCharacters
93
- );
94
- if (!this.history || this.options.history && this.options.history !== this.history) {
95
- if (!this.options.history) {
96
- if (!(isServer ?? this.isServer)) {
97
- this.history = createBrowserHistory();
98
- }
99
- } else {
100
- this.history = this.options.history;
101
- }
102
- }
103
- this.origin = this.options.origin;
104
- if (!this.origin) {
105
- if (!(isServer ?? this.isServer) && window?.origin && window.origin !== "null") {
106
- this.origin = window.origin;
107
- } else {
108
- this.origin = "http://localhost";
109
- }
110
- }
111
- if (this.history) {
112
- this.updateLatestLocation();
113
- }
114
- if (this.options.routeTree !== this.routeTree) {
115
- this.routeTree = this.options.routeTree;
116
- let processRouteTreeResult;
117
- if ((isServer ?? this.isServer) && process.env.NODE_ENV !== "development" && globalThis.__TSR_CACHE__ && globalThis.__TSR_CACHE__.routeTree === this.routeTree) {
118
- const cached = globalThis.__TSR_CACHE__;
119
- this.resolvePathCache = cached.resolvePathCache;
120
- processRouteTreeResult = cached.processRouteTreeResult;
121
- } else {
122
- this.resolvePathCache = createLRUCache(1e3);
123
- processRouteTreeResult = this.buildRouteTree();
124
- if ((isServer ?? this.isServer) && process.env.NODE_ENV !== "development" && globalThis.__TSR_CACHE__ === void 0) {
125
- globalThis.__TSR_CACHE__ = {
126
- routeTree: this.routeTree,
127
- processRouteTreeResult,
128
- resolvePathCache: this.resolvePathCache
129
- };
130
- }
131
- }
132
- this.setRoutes(processRouteTreeResult);
133
- }
134
- if (!this.__store && this.latestLocation) {
135
- if (isServer ?? this.isServer) {
136
- this.__store = createServerStore(
137
- getInitialRouterState(this.latestLocation)
138
- );
139
- } else {
140
- this.__store = createStore(getInitialRouterState(this.latestLocation));
141
- setupScrollRestoration(this);
142
- }
143
- }
144
- let needsLocationUpdate = false;
145
- const nextBasepath = this.options.basepath ?? "/";
146
- const nextRewriteOption = this.options.rewrite;
147
- const basepathChanged = basepathWasUnset || prevBasepath !== nextBasepath;
148
- const rewriteChanged = prevRewriteOption !== nextRewriteOption;
149
- if (basepathChanged || rewriteChanged) {
150
- this.basepath = nextBasepath;
151
- const rewrites = [];
152
- const trimmed = trimPath(nextBasepath);
153
- if (trimmed && trimmed !== "/") {
154
- rewrites.push(
155
- rewriteBasepath({
156
- basepath: nextBasepath
157
- })
158
- );
159
- }
160
- if (nextRewriteOption) {
161
- rewrites.push(nextRewriteOption);
162
- }
163
- this.rewrite = rewrites.length === 0 ? void 0 : rewrites.length === 1 ? rewrites[0] : composeRewrites(rewrites);
164
- if (this.history) {
165
- this.updateLatestLocation();
166
- }
167
- needsLocationUpdate = true;
168
- }
169
- if (needsLocationUpdate && this.__store) {
170
- this.__store.setState((s) => ({
171
- ...s,
172
- location: this.latestLocation
173
- }));
174
- }
175
- if (typeof window !== "undefined" && "CSS" in window && typeof window.CSS?.supports === "function") {
176
- this.isViewTransitionTypesSupported = window.CSS.supports(
177
- "selector(:active-view-transition-type(a)"
178
- );
179
- }
180
- };
181
- this.updateLatestLocation = () => {
182
- this.latestLocation = this.parseLocation(
183
- this.history.location,
184
- this.latestLocation
185
- );
186
- };
187
- this.buildRouteTree = () => {
188
- const result = processRouteTree(
189
- this.routeTree,
190
- this.options.caseSensitive,
191
- (route, i) => {
192
- route.init({
193
- originalIndex: i
194
- });
195
- }
196
- );
197
- if (this.options.routeMasks) {
198
- processRouteMasks(this.options.routeMasks, result.processedTree);
199
- }
200
- return result;
201
- };
202
- this.subscribe = (eventType, fn) => {
203
- const listener = {
204
- eventType,
205
- fn
206
- };
207
- this.subscribers.add(listener);
208
- return () => {
209
- this.subscribers.delete(listener);
210
- };
211
- };
212
- this.emit = (routerEvent) => {
213
- this.subscribers.forEach((listener) => {
214
- if (listener.eventType === routerEvent.type) {
215
- listener.fn(routerEvent);
216
- }
217
- });
218
- };
219
- this.parseLocation = (locationToParse, previousLocation) => {
220
- const parse = ({
221
- pathname,
222
- search,
223
- hash,
224
- href,
225
- state
226
- }) => {
227
- if (!this.rewrite && !/[ \x00-\x1f\x7f\u0080-\uffff]/.test(pathname)) {
228
- const parsedSearch2 = this.options.parseSearch(search);
229
- const searchStr2 = this.options.stringifySearch(parsedSearch2);
230
- return {
231
- href: pathname + searchStr2 + hash,
232
- publicHref: href,
233
- pathname: decodePath(pathname).path,
234
- external: false,
235
- searchStr: searchStr2,
236
- search: nullReplaceEqualDeep(
237
- previousLocation?.search,
238
- parsedSearch2
239
- ),
240
- hash: decodePath(hash.slice(1)).path,
241
- state: replaceEqualDeep(previousLocation?.state, state)
242
- };
243
- }
244
- const fullUrl = new URL(href, this.origin);
245
- const url = executeRewriteInput(this.rewrite, fullUrl);
246
- const parsedSearch = this.options.parseSearch(url.search);
247
- const searchStr = this.options.stringifySearch(parsedSearch);
248
- url.search = searchStr;
249
- const fullPath = url.href.replace(url.origin, "");
250
- return {
251
- href: fullPath,
252
- publicHref: href,
253
- pathname: decodePath(url.pathname).path,
254
- external: !!this.rewrite && url.origin !== this.origin,
255
- searchStr,
256
- search: nullReplaceEqualDeep(
257
- previousLocation?.search,
258
- parsedSearch
259
- ),
260
- hash: decodePath(url.hash.slice(1)).path,
261
- state: replaceEqualDeep(previousLocation?.state, state)
262
- };
263
- };
264
- const location = parse(locationToParse);
265
- const { __tempLocation, __tempKey } = location.state;
266
- if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
267
- const parsedTempLocation = parse(__tempLocation);
268
- parsedTempLocation.state.key = location.state.key;
269
- parsedTempLocation.state.__TSR_key = location.state.__TSR_key;
270
- delete parsedTempLocation.state.__tempLocation;
271
- return {
272
- ...parsedTempLocation,
273
- maskedLocation: location
274
- };
275
- }
276
- return location;
277
- };
278
- this.resolvePathWithBase = (from, path) => {
279
- const resolvedPath = resolvePath({
280
- base: from,
281
- to: cleanPath(path),
282
- trailingSlash: this.options.trailingSlash,
283
- cache: this.resolvePathCache
284
- });
285
- return resolvedPath;
286
- };
287
- this.matchRoutes = (pathnameOrNext, locationSearchOrOpts, opts) => {
288
- if (typeof pathnameOrNext === "string") {
289
- return this.matchRoutesInternal(
290
- {
291
- pathname: pathnameOrNext,
292
- search: locationSearchOrOpts
293
- },
294
- opts
295
- );
296
- }
297
- return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts);
298
- };
299
- this.getMatchedRoutes = (pathname) => {
300
- return getMatchedRoutes({
301
- pathname,
302
- routesById: this.routesById,
303
- processedTree: this.processedTree
304
- });
305
- };
306
- this.cancelMatch = (id) => {
307
- const match = this.getMatch(id);
308
- if (!match) return;
309
- match.abortController.abort();
310
- clearTimeout(match._nonReactive.pendingTimeout);
311
- match._nonReactive.pendingTimeout = void 0;
312
- };
313
- this.cancelMatches = () => {
314
- const currentPendingMatches = this.state.matches.filter(
315
- (match) => match.status === "pending"
316
- );
317
- const currentLoadingMatches = this.state.matches.filter(
318
- (match) => match.isFetching === "loader"
319
- );
320
- const matchesToCancelArray = /* @__PURE__ */ new Set([
321
- ...this.state.pendingMatches ?? [],
322
- ...currentPendingMatches,
323
- ...currentLoadingMatches
324
- ]);
325
- matchesToCancelArray.forEach((match) => {
326
- this.cancelMatch(match.id);
327
- });
328
- };
329
- this.buildLocation = (opts) => {
330
- const build = (dest = {}) => {
331
- const currentLocation = dest._fromLocation || this.pendingBuiltLocation || this.latestLocation;
332
- const lightweightResult = this.matchRoutesLightweight(currentLocation);
333
- if (dest.from && process.env.NODE_ENV !== "production" && dest._isNavigate) {
334
- const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes;
335
- const matchedFrom = findLast(lightweightResult.matchedRoutes, (d) => {
336
- return comparePaths(d.fullPath, dest.from);
337
- });
338
- const matchedCurrent = findLast(allFromMatches, (d) => {
339
- return comparePaths(d.fullPath, lightweightResult.fullPath);
340
- });
341
- if (!matchedFrom && !matchedCurrent) {
342
- console.warn(`Could not find match for from: ${dest.from}`);
343
- }
344
- }
345
- const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ?? lightweightResult.fullPath;
346
- const fromPath = this.resolvePathWithBase(defaultedFromPath, ".");
347
- const fromSearch = lightweightResult.search;
348
- const fromParams = Object.assign(
349
- /* @__PURE__ */ Object.create(null),
350
- lightweightResult.params
351
- );
352
- const nextTo = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, ".");
353
- const nextParams = dest.params === false || dest.params === null ? /* @__PURE__ */ Object.create(null) : (dest.params ?? true) === true ? fromParams : Object.assign(
354
- fromParams,
355
- functionalUpdate(dest.params, fromParams)
356
- );
357
- const destMatchResult = this.getMatchedRoutes(nextTo);
358
- let destRoutes = destMatchResult.matchedRoutes;
359
- const isGlobalNotFound = !destMatchResult.foundRoute || destMatchResult.foundRoute.path !== "/" && destMatchResult.routeParams["**"];
360
- if (isGlobalNotFound && this.options.notFoundRoute) {
361
- destRoutes = [...destRoutes, this.options.notFoundRoute];
362
- }
363
- if (Object.keys(nextParams).length > 0) {
364
- for (const route of destRoutes) {
365
- const fn = route.options.params?.stringify ?? route.options.stringifyParams;
366
- if (fn) {
367
- try {
368
- Object.assign(nextParams, fn(nextParams));
369
- } catch {
370
- }
371
- }
372
- }
373
- }
374
- const nextPathname = opts.leaveParams ? (
375
- // Use the original template path for interpolation
376
- // This preserves the original parameter syntax including optional parameters
377
- nextTo
378
- ) : decodePath(
379
- interpolatePath({
380
- path: nextTo,
381
- params: nextParams,
382
- decoder: this.pathParamsDecoder,
383
- server: this.isServer
384
- }).interpolatedPath
385
- ).path;
386
- let nextSearch = fromSearch;
387
- if (opts._includeValidateSearch && this.options.search?.strict) {
388
- const validatedSearch = {};
389
- destRoutes.forEach((route) => {
390
- if (route.options.validateSearch) {
391
- try {
392
- Object.assign(
393
- validatedSearch,
394
- validateSearch(route.options.validateSearch, {
395
- ...validatedSearch,
396
- ...nextSearch
397
- })
398
- );
399
- } catch {
400
- }
401
- }
402
- });
403
- nextSearch = validatedSearch;
404
- }
405
- nextSearch = applySearchMiddleware({
406
- search: nextSearch,
407
- dest,
408
- destRoutes,
409
- _includeValidateSearch: opts._includeValidateSearch
410
- });
411
- nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch);
412
- const searchStr = this.options.stringifySearch(nextSearch);
413
- const hash = dest.hash === true ? currentLocation.hash : dest.hash ? functionalUpdate(dest.hash, currentLocation.hash) : void 0;
414
- const hashStr = hash ? `#${hash}` : "";
415
- let nextState = dest.state === true ? currentLocation.state : dest.state ? functionalUpdate(dest.state, currentLocation.state) : {};
416
- nextState = replaceEqualDeep(currentLocation.state, nextState);
417
- const fullPath = `${nextPathname}${searchStr}${hashStr}`;
418
- let href;
419
- let publicHref;
420
- let external = false;
421
- if (this.rewrite) {
422
- const url = new URL(fullPath, this.origin);
423
- const rewrittenUrl = executeRewriteOutput(this.rewrite, url);
424
- href = url.href.replace(url.origin, "");
425
- if (rewrittenUrl.origin !== this.origin) {
426
- publicHref = rewrittenUrl.href;
427
- external = true;
428
- } else {
429
- publicHref = rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash;
430
- }
431
- } else {
432
- href = encodePathLikeUrl(fullPath);
433
- publicHref = href;
434
- }
435
- return {
436
- publicHref,
437
- href,
438
- pathname: nextPathname,
439
- search: nextSearch,
440
- searchStr,
441
- state: nextState,
442
- hash: hash ?? "",
443
- external,
444
- unmaskOnReload: dest.unmaskOnReload
445
- };
446
- };
447
- const buildWithMatches = (dest = {}, maskedDest) => {
448
- const next = build(dest);
449
- let maskedNext = maskedDest ? build(maskedDest) : void 0;
450
- if (!maskedNext) {
451
- const params = /* @__PURE__ */ Object.create(null);
452
- if (this.options.routeMasks) {
453
- const match = findFlatMatch(
454
- next.pathname,
455
- this.processedTree
456
- );
457
- if (match) {
458
- Object.assign(params, match.rawParams);
459
- const {
460
- from: _from,
461
- params: maskParams,
462
- ...maskProps
463
- } = match.route;
464
- const nextParams = maskParams === false || maskParams === null ? /* @__PURE__ */ Object.create(null) : (maskParams ?? true) === true ? params : Object.assign(params, functionalUpdate(maskParams, params));
465
- maskedDest = {
466
- from: opts.from,
467
- ...maskProps,
468
- params: nextParams
469
- };
470
- maskedNext = build(maskedDest);
471
- }
472
- }
473
- }
474
- if (maskedNext) {
475
- next.maskedLocation = maskedNext;
476
- }
477
- return next;
478
- };
479
- if (opts.mask) {
480
- return buildWithMatches(opts, {
481
- from: opts.from,
482
- ...opts.mask
483
- });
484
- }
485
- return buildWithMatches(opts);
486
- };
487
- this.commitLocation = async ({
488
- viewTransition,
489
- ignoreBlocker,
490
- ...next
491
- }) => {
492
- const isSameState = () => {
493
- const ignoredProps = [
494
- "key",
495
- // TODO: Remove in v2 - use __TSR_key instead
496
- "__TSR_key",
497
- "__TSR_index",
498
- "__hashScrollIntoViewOptions"
499
- ];
500
- ignoredProps.forEach((prop) => {
501
- next.state[prop] = this.latestLocation.state[prop];
502
- });
503
- const isEqual = deepEqual(next.state, this.latestLocation.state);
504
- ignoredProps.forEach((prop) => {
505
- delete next.state[prop];
506
- });
507
- return isEqual;
508
- };
509
- const isSameUrl = trimPathRight(this.latestLocation.href) === trimPathRight(next.href);
510
- const previousCommitPromise = this.commitLocationPromise;
511
- this.commitLocationPromise = createControlledPromise(() => {
512
- previousCommitPromise?.resolve();
513
- });
514
- if (isSameUrl && isSameState()) {
515
- this.load();
516
- } else {
517
- let {
518
- // eslint-disable-next-line prefer-const
519
- maskedLocation,
520
- // eslint-disable-next-line prefer-const
521
- hashScrollIntoView,
522
- ...nextHistory
523
- } = next;
524
- if (maskedLocation) {
525
- nextHistory = {
526
- ...maskedLocation,
527
- state: {
528
- ...maskedLocation.state,
529
- __tempKey: void 0,
530
- __tempLocation: {
531
- ...nextHistory,
532
- search: nextHistory.searchStr,
533
- state: {
534
- ...nextHistory.state,
535
- __tempKey: void 0,
536
- __tempLocation: void 0,
537
- __TSR_key: void 0,
538
- key: void 0
539
- // TODO: Remove in v2 - use __TSR_key instead
540
- }
541
- }
542
- }
543
- };
544
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
545
- nextHistory.state.__tempKey = this.tempLocationKey;
546
- }
547
- }
548
- nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
549
- this.shouldViewTransition = viewTransition;
550
- this.history[next.replace ? "replace" : "push"](
551
- nextHistory.publicHref,
552
- nextHistory.state,
553
- { ignoreBlocker }
554
- );
555
- }
556
- this.resetNextScroll = next.resetScroll ?? true;
557
- if (!this.history.subscribers.size) {
558
- this.load();
559
- }
560
- return this.commitLocationPromise;
561
- };
562
- this.buildAndCommitLocation = ({
563
- replace,
564
- resetScroll,
565
- hashScrollIntoView,
566
- viewTransition,
567
- ignoreBlocker,
568
- href,
569
- ...rest
570
- } = {}) => {
571
- if (href) {
572
- const currentIndex = this.history.location.state.__TSR_index;
573
- const parsed = parseHref(href, {
574
- __TSR_index: replace ? currentIndex : currentIndex + 1
575
- });
576
- const hrefUrl = new URL(parsed.pathname, this.origin);
577
- const rewrittenUrl = executeRewriteInput(this.rewrite, hrefUrl);
578
- rest.to = rewrittenUrl.pathname;
579
- rest.search = this.options.parseSearch(parsed.search);
580
- rest.hash = parsed.hash.slice(1);
581
- }
582
- const location = this.buildLocation({
583
- ...rest,
584
- _includeValidateSearch: true
585
- });
586
- this.pendingBuiltLocation = location;
587
- const commitPromise = this.commitLocation({
588
- ...location,
589
- viewTransition,
590
- replace,
591
- resetScroll,
592
- hashScrollIntoView,
593
- ignoreBlocker
594
- });
595
- Promise.resolve().then(() => {
596
- if (this.pendingBuiltLocation === location) {
597
- this.pendingBuiltLocation = void 0;
598
- }
599
- });
600
- return commitPromise;
601
- };
602
- this.navigate = async ({
603
- to,
604
- reloadDocument,
605
- href,
606
- publicHref,
607
- ...rest
608
- }) => {
609
- let hrefIsUrl = false;
610
- if (href) {
611
- try {
612
- new URL(`${href}`);
613
- hrefIsUrl = true;
614
- } catch {
615
- }
616
- }
617
- if (hrefIsUrl && !reloadDocument) {
618
- reloadDocument = true;
619
- }
620
- if (reloadDocument) {
621
- if (to !== void 0 || !href) {
622
- const location = this.buildLocation({ to, ...rest });
623
- href = href ?? location.publicHref;
624
- publicHref = publicHref ?? location.publicHref;
625
- }
626
- const reloadHref = !hrefIsUrl && publicHref ? publicHref : href;
627
- if (isDangerousProtocol(reloadHref, this.protocolAllowlist)) {
628
- if (process.env.NODE_ENV !== "production") {
629
- console.warn(
630
- `Blocked navigation to dangerous protocol: ${reloadHref}`
631
- );
632
- }
633
- return Promise.resolve();
634
- }
635
- if (!rest.ignoreBlocker) {
636
- const historyWithBlockers = this.history;
637
- const blockers = historyWithBlockers.getBlockers?.() ?? [];
638
- for (const blocker of blockers) {
639
- if (blocker?.blockerFn) {
640
- const shouldBlock = await blocker.blockerFn({
641
- currentLocation: this.latestLocation,
642
- nextLocation: this.latestLocation,
643
- // External URLs don't have a next location in our router
644
- action: "PUSH"
645
- });
646
- if (shouldBlock) {
647
- return Promise.resolve();
648
- }
649
- }
650
- }
651
- }
652
- if (rest.replace) {
653
- window.location.replace(reloadHref);
654
- } else {
655
- window.location.href = reloadHref;
656
- }
657
- return Promise.resolve();
658
- }
659
- return this.buildAndCommitLocation({
660
- ...rest,
661
- href,
662
- to,
663
- _isNavigate: true
664
- });
665
- };
666
- this.beforeLoad = () => {
667
- this.cancelMatches();
668
- this.updateLatestLocation();
669
- if (isServer ?? this.isServer) {
670
- const nextLocation = this.buildLocation({
671
- to: this.latestLocation.pathname,
672
- search: true,
673
- params: true,
674
- hash: true,
675
- state: true,
676
- _includeValidateSearch: true
677
- });
678
- if (this.latestLocation.publicHref !== nextLocation.publicHref) {
679
- const href = this.getParsedLocationHref(nextLocation);
680
- if (nextLocation.external) {
681
- throw redirect({ href });
682
- } else {
683
- throw redirect({ href, _builtLocation: nextLocation });
684
- }
685
- }
686
- }
687
- const pendingMatches = this.matchRoutes(this.latestLocation);
688
- this.__store.setState((s) => ({
689
- ...s,
690
- status: "pending",
691
- statusCode: 200,
692
- isLoading: true,
693
- location: this.latestLocation,
694
- pendingMatches,
695
- // If a cached moved to pendingMatches, remove it from cachedMatches
696
- cachedMatches: s.cachedMatches.filter(
697
- (d) => !pendingMatches.some((e) => e.id === d.id)
698
- )
699
- }));
700
- };
701
- this.load = async (opts) => {
702
- let redirect2;
703
- let notFound;
704
- let loadPromise;
705
- const previousLocation = this.state.resolvedLocation ?? this.state.location;
706
- loadPromise = new Promise((resolve) => {
707
- this.startTransition(async () => {
708
- try {
709
- this.beforeLoad();
710
- const next = this.latestLocation;
711
- const prevLocation = this.state.resolvedLocation;
712
- if (!this.state.redirect) {
713
- this.emit({
714
- type: "onBeforeNavigate",
715
- ...getLocationChangeInfo({
716
- resolvedLocation: prevLocation,
717
- location: next
718
- })
719
- });
720
- }
721
- this.emit({
722
- type: "onBeforeLoad",
723
- ...getLocationChangeInfo({
724
- resolvedLocation: prevLocation,
725
- location: next
726
- })
727
- });
728
- await loadMatches({
729
- router: this,
730
- sync: opts?.sync,
731
- forceStaleReload: previousLocation.href === next.href,
732
- matches: this.state.pendingMatches,
733
- location: next,
734
- updateMatch: this.updateMatch,
735
- // eslint-disable-next-line @typescript-eslint/require-await
736
- onReady: async () => {
737
- this.startTransition(() => {
738
- this.startViewTransition(async () => {
739
- let exitingMatches = [];
740
- let hookExitingMatches = [];
741
- let hookEnteringMatches = [];
742
- let hookStayingMatches = [];
743
- batch(() => {
744
- this.__store.setState((s) => {
745
- const previousMatches = s.matches;
746
- const newMatches = s.pendingMatches || s.matches;
747
- exitingMatches = previousMatches.filter(
748
- (match) => !newMatches.some((d) => d.id === match.id)
749
- );
750
- hookExitingMatches = previousMatches.filter(
751
- (match) => !newMatches.some((d) => d.routeId === match.routeId)
752
- );
753
- hookEnteringMatches = newMatches.filter(
754
- (match) => !previousMatches.some(
755
- (d) => d.routeId === match.routeId
756
- )
757
- );
758
- hookStayingMatches = newMatches.filter(
759
- (match) => previousMatches.some(
760
- (d) => d.routeId === match.routeId
761
- )
762
- );
763
- return {
764
- ...s,
765
- isLoading: false,
766
- loadedAt: Date.now(),
767
- matches: newMatches,
768
- pendingMatches: void 0,
769
- /**
770
- * When committing new matches, cache any exiting matches that are still usable.
771
- * Routes that resolved with `status: 'error'` or `status: 'notFound'` are
772
- * deliberately excluded from `cachedMatches` so that subsequent invalidations
773
- * or reloads re-run their loaders instead of reusing the failed/not-found data.
774
- */
775
- cachedMatches: [
776
- ...s.cachedMatches,
777
- ...exitingMatches.filter(
778
- (d) => d.status !== "error" && d.status !== "notFound" && d.status !== "redirected"
779
- )
780
- ]
781
- };
782
- });
783
- this.clearExpiredCache();
784
- });
785
- [
786
- [hookExitingMatches, "onLeave"],
787
- [hookEnteringMatches, "onEnter"],
788
- [hookStayingMatches, "onStay"]
789
- ].forEach(([matches, hook]) => {
790
- matches.forEach((match) => {
791
- this.looseRoutesById[match.routeId].options[hook]?.(
792
- match
793
- );
794
- });
795
- });
796
- });
797
- });
798
- }
799
- });
800
- } catch (err) {
801
- if (isRedirect(err)) {
802
- redirect2 = err;
803
- if (!(isServer ?? this.isServer)) {
804
- this.navigate({
805
- ...redirect2.options,
806
- replace: true,
807
- ignoreBlocker: true
808
- });
809
- }
810
- } else if (isNotFound(err)) {
811
- notFound = err;
812
- }
813
- this.__store.setState((s) => ({
814
- ...s,
815
- statusCode: redirect2 ? redirect2.status : notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
816
- redirect: redirect2
817
- }));
818
- }
819
- if (this.latestLoadPromise === loadPromise) {
820
- this.commitLocationPromise?.resolve();
821
- this.latestLoadPromise = void 0;
822
- this.commitLocationPromise = void 0;
823
- }
824
- resolve();
825
- });
826
- });
827
- this.latestLoadPromise = loadPromise;
828
- await loadPromise;
829
- while (this.latestLoadPromise && loadPromise !== this.latestLoadPromise) {
830
- await this.latestLoadPromise;
831
- }
832
- let newStatusCode = void 0;
833
- if (this.hasNotFoundMatch()) {
834
- newStatusCode = 404;
835
- } else if (this.__store.state.matches.some((d) => d.status === "error")) {
836
- newStatusCode = 500;
837
- }
838
- if (newStatusCode !== void 0) {
839
- this.__store.setState((s) => ({
840
- ...s,
841
- statusCode: newStatusCode
842
- }));
843
- }
844
- };
845
- this.startViewTransition = (fn) => {
846
- const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
847
- this.shouldViewTransition = void 0;
848
- if (shouldViewTransition && typeof document !== "undefined" && "startViewTransition" in document && typeof document.startViewTransition === "function") {
849
- let startViewTransitionParams;
850
- if (typeof shouldViewTransition === "object" && this.isViewTransitionTypesSupported) {
851
- const next = this.latestLocation;
852
- const prevLocation = this.state.resolvedLocation;
853
- const resolvedViewTransitionTypes = typeof shouldViewTransition.types === "function" ? shouldViewTransition.types(
854
- getLocationChangeInfo({
855
- resolvedLocation: prevLocation,
856
- location: next
857
- })
858
- ) : shouldViewTransition.types;
859
- if (resolvedViewTransitionTypes === false) {
860
- fn();
861
- return;
862
- }
863
- startViewTransitionParams = {
864
- update: fn,
865
- types: resolvedViewTransitionTypes
866
- };
867
- } else {
868
- startViewTransitionParams = fn;
869
- }
870
- document.startViewTransition(startViewTransitionParams);
871
- } else {
872
- fn();
873
- }
874
- };
875
- this.updateMatch = (id, updater) => {
876
- this.startTransition(() => {
877
- const matchesKey = this.state.pendingMatches?.some((d) => d.id === id) ? "pendingMatches" : this.state.matches.some((d) => d.id === id) ? "matches" : this.state.cachedMatches.some((d) => d.id === id) ? "cachedMatches" : "";
878
- if (matchesKey) {
879
- if (matchesKey === "cachedMatches") {
880
- this.__store.setState((s) => ({
881
- ...s,
882
- cachedMatches: filterRedirectedCachedMatches(
883
- s.cachedMatches.map((d) => d.id === id ? updater(d) : d)
884
- )
885
- }));
886
- } else {
887
- this.__store.setState((s) => ({
888
- ...s,
889
- [matchesKey]: s[matchesKey]?.map(
890
- (d) => d.id === id ? updater(d) : d
891
- )
892
- }));
893
- }
894
- }
895
- });
896
- };
897
- this.getMatch = (matchId) => {
898
- const findFn = (d) => d.id === matchId;
899
- return this.state.cachedMatches.find(findFn) ?? this.state.pendingMatches?.find(findFn) ?? this.state.matches.find(findFn);
900
- };
901
- this.invalidate = (opts) => {
902
- const invalidate = (d) => {
903
- if (opts?.filter?.(d) ?? true) {
904
- return {
905
- ...d,
906
- invalid: true,
907
- ...opts?.forcePending || d.status === "error" || d.status === "notFound" ? { status: "pending", error: void 0 } : void 0
908
- };
909
- }
910
- return d;
911
- };
912
- this.__store.setState((s) => ({
913
- ...s,
914
- matches: s.matches.map(invalidate),
915
- cachedMatches: s.cachedMatches.map(invalidate),
916
- pendingMatches: s.pendingMatches?.map(invalidate)
917
- }));
918
- this.shouldViewTransition = false;
919
- return this.load({ sync: opts?.sync });
920
- };
921
- this.getParsedLocationHref = (location) => {
922
- return location.publicHref || "/";
923
- };
924
- this.resolveRedirect = (redirect2) => {
925
- const locationHeader = redirect2.headers.get("Location");
926
- if (!redirect2.options.href || redirect2.options._builtLocation) {
927
- const location = redirect2.options._builtLocation ?? this.buildLocation(redirect2.options);
928
- const href = this.getParsedLocationHref(location);
929
- redirect2.options.href = href;
930
- redirect2.headers.set("Location", href);
931
- } else if (locationHeader) {
932
- try {
933
- const url = new URL(locationHeader);
934
- if (this.origin && url.origin === this.origin) {
935
- const href = url.pathname + url.search + url.hash;
936
- redirect2.options.href = href;
937
- redirect2.headers.set("Location", href);
938
- }
939
- } catch {
940
- }
941
- }
942
- if (redirect2.options.href && !redirect2.options._builtLocation && // Check for dangerous protocols before processing the redirect
943
- isDangerousProtocol(redirect2.options.href, this.protocolAllowlist)) {
944
- throw new Error(
945
- process.env.NODE_ENV !== "production" ? `Redirect blocked: unsafe protocol in href "${redirect2.options.href}". Allowed protocols: ${Array.from(this.protocolAllowlist).join(", ")}.` : "Redirect blocked: unsafe protocol"
946
- );
947
- }
948
- if (!redirect2.headers.get("Location")) {
949
- redirect2.headers.set("Location", redirect2.options.href);
950
- }
951
- return redirect2;
952
- };
953
- this.clearCache = (opts) => {
954
- const filter = opts?.filter;
955
- if (filter !== void 0) {
956
- this.__store.setState((s) => {
957
- return {
958
- ...s,
959
- cachedMatches: s.cachedMatches.filter(
960
- (m) => !filter(m)
961
- )
962
- };
963
- });
964
- } else {
965
- this.__store.setState((s) => {
966
- return {
967
- ...s,
968
- cachedMatches: []
969
- };
970
- });
971
- }
972
- };
973
- this.clearExpiredCache = () => {
974
- const filter = (d) => {
975
- const route = this.looseRoutesById[d.routeId];
976
- if (!route.options.loader) {
977
- return true;
978
- }
979
- const gcTime = (d.preload ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route.options.gcTime ?? this.options.defaultGcTime) ?? 5 * 60 * 1e3;
980
- const isError = d.status === "error";
981
- if (isError) return true;
982
- const gcEligible = Date.now() - d.updatedAt >= gcTime;
983
- return gcEligible;
984
- };
985
- this.clearCache({ filter });
986
- };
987
- this.loadRouteChunk = loadRouteChunk;
988
- this.preloadRoute = async (opts) => {
989
- const next = opts._builtLocation ?? this.buildLocation(opts);
990
- let matches = this.matchRoutes(next, {
991
- throwOnError: true,
992
- preload: true,
993
- dest: opts
994
- });
995
- const activeMatchIds = new Set(
996
- [...this.state.matches, ...this.state.pendingMatches ?? []].map(
997
- (d) => d.id
998
- )
999
- );
1000
- const loadedMatchIds = /* @__PURE__ */ new Set([
1001
- ...activeMatchIds,
1002
- ...this.state.cachedMatches.map((d) => d.id)
1003
- ]);
1004
- batch(() => {
1005
- matches.forEach((match) => {
1006
- if (!loadedMatchIds.has(match.id)) {
1007
- this.__store.setState((s) => ({
1008
- ...s,
1009
- cachedMatches: [...s.cachedMatches, match]
1010
- }));
1011
- }
1012
- });
1013
- });
1014
- try {
1015
- matches = await loadMatches({
1016
- router: this,
1017
- matches,
1018
- location: next,
1019
- preload: true,
1020
- updateMatch: (id, updater) => {
1021
- if (activeMatchIds.has(id)) {
1022
- matches = matches.map((d) => d.id === id ? updater(d) : d);
1023
- } else {
1024
- this.updateMatch(id, updater);
1025
- }
1026
- }
1027
- });
1028
- return matches;
1029
- } catch (err) {
1030
- if (isRedirect(err)) {
1031
- if (err.options.reloadDocument) {
1032
- return void 0;
1033
- }
1034
- return await this.preloadRoute({
1035
- ...err.options,
1036
- _fromLocation: next
1037
- });
1038
- }
1039
- if (!isNotFound(err)) {
1040
- console.error(err);
1041
- }
1042
- return void 0;
1043
- }
1044
- };
1045
- this.matchRoute = (location, opts) => {
1046
- const matchLocation = {
1047
- ...location,
1048
- to: location.to ? this.resolvePathWithBase(location.from || "", location.to) : void 0,
1049
- params: location.params || {},
1050
- leaveParams: true
1051
- };
1052
- const next = this.buildLocation(matchLocation);
1053
- if (opts?.pending && this.state.status !== "pending") {
1054
- return false;
1055
- }
1056
- const pending = opts?.pending === void 0 ? !this.state.isLoading : opts.pending;
1057
- const baseLocation = pending ? this.latestLocation : this.state.resolvedLocation || this.state.location;
1058
- const match = findSingleMatch(
1059
- next.pathname,
1060
- opts?.caseSensitive ?? false,
1061
- opts?.fuzzy ?? false,
1062
- baseLocation.pathname,
1063
- this.processedTree
1064
- );
1065
- if (!match) {
1066
- return false;
1067
- }
1068
- if (location.params) {
1069
- if (!deepEqual(match.rawParams, location.params, { partial: true })) {
1070
- return false;
1071
- }
1072
- }
1073
- if (opts?.includeSearch ?? true) {
1074
- return deepEqual(baseLocation.search, next.search, { partial: true }) ? match.rawParams : false;
1075
- }
1076
- return match.rawParams;
1077
- };
1078
- this.hasNotFoundMatch = () => {
1079
- return this.__store.state.matches.some(
1080
- (d) => d.status === "notFound" || d.globalNotFound
1081
- );
1082
- };
1083
- this.update({
1084
- defaultPreloadDelay: 50,
1085
- defaultPendingMs: 1e3,
1086
- defaultPendingMinMs: 500,
1087
- context: void 0,
1088
- ...options,
1089
- caseSensitive: options.caseSensitive ?? false,
1090
- notFoundMode: options.notFoundMode ?? "fuzzy",
1091
- stringifySearch: options.stringifySearch ?? defaultStringifySearch,
1092
- parseSearch: options.parseSearch ?? defaultParseSearch,
1093
- protocolAllowlist: options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST
1094
- });
1095
- if (typeof document !== "undefined") {
1096
- self.__TSR_ROUTER__ = this;
1097
- }
1098
- }
1099
- isShell() {
1100
- return !!this.options.isShell;
1101
- }
1102
- isPrerendering() {
1103
- return !!this.options.isPrerendering;
1104
- }
1105
- get state() {
1106
- return this.__store.state;
1107
- }
1108
- setRoutes({
1109
- routesById,
1110
- routesByPath,
1111
- processedTree
1112
- }) {
1113
- this.routesById = routesById;
1114
- this.routesByPath = routesByPath;
1115
- this.processedTree = processedTree;
1116
- const notFoundRoute = this.options.notFoundRoute;
1117
- if (notFoundRoute) {
1118
- notFoundRoute.init({
1119
- originalIndex: 99999999999
1120
- });
1121
- this.routesById[notFoundRoute.id] = notFoundRoute;
1122
- }
1123
- }
1124
- get looseRoutesById() {
1125
- return this.routesById;
1126
- }
1127
- getParentContext(parentMatch) {
1128
- const parentMatchId = parentMatch?.id;
1129
- const parentContext = !parentMatchId ? this.options.context ?? void 0 : parentMatch.context ?? this.options.context ?? void 0;
1130
- return parentContext;
1131
- }
1132
- matchRoutesInternal(next, opts) {
1133
- const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
1134
- const { foundRoute, routeParams, parsedParams } = matchedRoutesResult;
1135
- let { matchedRoutes } = matchedRoutesResult;
1136
- let isGlobalNotFound = false;
1137
- if (
1138
- // If we found a route, and it's not an index route and we have left over path
1139
- foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
1140
- // Or if we didn't find a route and we have left over path
1141
- trimPathRight(next.pathname)
1142
- )
1143
- ) {
1144
- if (this.options.notFoundRoute) {
1145
- matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
1146
- } else {
1147
- isGlobalNotFound = true;
1148
- }
1149
- }
1150
- const globalNotFoundRouteId = isGlobalNotFound ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes) : void 0;
1151
- const matches = new Array(matchedRoutes.length);
1152
- const previousMatchesByRouteId = new Map(
1153
- this.state.matches.map((match) => [match.routeId, match])
1154
- );
1155
- for (let index = 0; index < matchedRoutes.length; index++) {
1156
- const route = matchedRoutes[index];
1157
- const parentMatch = matches[index - 1];
1158
- let preMatchSearch;
1159
- let strictMatchSearch;
1160
- let searchError;
1161
- {
1162
- const parentSearch = parentMatch?.search ?? next.search;
1163
- const parentStrictSearch = parentMatch?._strictSearch ?? void 0;
1164
- try {
1165
- const strictSearch = validateSearch(route.options.validateSearch, { ...parentSearch }) ?? void 0;
1166
- preMatchSearch = {
1167
- ...parentSearch,
1168
- ...strictSearch
1169
- };
1170
- strictMatchSearch = { ...parentStrictSearch, ...strictSearch };
1171
- searchError = void 0;
1172
- } catch (err) {
1173
- let searchParamError = err;
1174
- if (!(err instanceof SearchParamError)) {
1175
- searchParamError = new SearchParamError(err.message, {
1176
- cause: err
1177
- });
1178
- }
1179
- if (opts?.throwOnError) {
1180
- throw searchParamError;
1181
- }
1182
- preMatchSearch = parentSearch;
1183
- strictMatchSearch = {};
1184
- searchError = searchParamError;
1185
- }
1186
- }
1187
- const loaderDeps = route.options.loaderDeps?.({
1188
- search: preMatchSearch
1189
- }) ?? "";
1190
- const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : "";
1191
- const { interpolatedPath, usedParams } = interpolatePath({
1192
- path: route.fullPath,
1193
- params: routeParams,
1194
- decoder: this.pathParamsDecoder,
1195
- server: this.isServer
1196
- });
1197
- const matchId = (
1198
- // route.id for disambiguation
1199
- route.id + // interpolatedPath for param changes
1200
- interpolatedPath + // explicit deps
1201
- loaderDepsHash
1202
- );
1203
- const existingMatch = this.getMatch(matchId);
1204
- const previousMatch = previousMatchesByRouteId.get(route.id);
1205
- const strictParams = existingMatch?._strictParams ?? usedParams;
1206
- let paramsError = void 0;
1207
- if (!existingMatch) {
1208
- try {
1209
- extractStrictParams(route, usedParams, parsedParams, strictParams);
1210
- } catch (err) {
1211
- if (isNotFound(err) || isRedirect(err)) {
1212
- paramsError = err;
1213
- } else {
1214
- paramsError = new PathParamError(err.message, {
1215
- cause: err
1216
- });
1217
- }
1218
- if (opts?.throwOnError) {
1219
- throw paramsError;
1220
- }
1221
- }
1222
- }
1223
- Object.assign(routeParams, strictParams);
1224
- const cause = previousMatch ? "stay" : "enter";
1225
- let match;
1226
- if (existingMatch) {
1227
- match = {
1228
- ...existingMatch,
1229
- cause,
1230
- params: previousMatch?.params ?? routeParams,
1231
- _strictParams: strictParams,
1232
- search: previousMatch ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
1233
- _strictSearch: strictMatchSearch
1234
- };
1235
- } else {
1236
- const status = route.options.loader || route.options.beforeLoad || route.lazyFn || routeNeedsPreload(route) ? "pending" : "success";
1237
- match = {
1238
- id: matchId,
1239
- ssr: isServer ?? this.isServer ? void 0 : route.options.ssr,
1240
- index,
1241
- routeId: route.id,
1242
- params: previousMatch?.params ?? routeParams,
1243
- _strictParams: strictParams,
1244
- pathname: interpolatedPath,
1245
- updatedAt: Date.now(),
1246
- search: previousMatch ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : preMatchSearch,
1247
- _strictSearch: strictMatchSearch,
1248
- searchError: void 0,
1249
- status,
1250
- isFetching: false,
1251
- error: void 0,
1252
- paramsError,
1253
- __routeContext: void 0,
1254
- _nonReactive: {
1255
- loadPromise: createControlledPromise()
1256
- },
1257
- __beforeLoadContext: void 0,
1258
- context: {},
1259
- abortController: new AbortController(),
1260
- fetchCount: 0,
1261
- cause,
1262
- loaderDeps: previousMatch ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps) : loaderDeps,
1263
- invalid: false,
1264
- preload: false,
1265
- links: void 0,
1266
- scripts: void 0,
1267
- headScripts: void 0,
1268
- meta: void 0,
1269
- staticData: route.options.staticData || {},
1270
- fullPath: route.fullPath
1271
- };
1272
- }
1273
- if (!opts?.preload) {
1274
- match.globalNotFound = globalNotFoundRouteId === route.id;
1275
- }
1276
- match.searchError = searchError;
1277
- const parentContext = this.getParentContext(parentMatch);
1278
- match.context = {
1279
- ...parentContext,
1280
- ...match.__routeContext,
1281
- ...match.__beforeLoadContext
1282
- };
1283
- matches[index] = match;
1284
- }
1285
- for (let index = 0; index < matches.length; index++) {
1286
- const match = matches[index];
1287
- const route = this.looseRoutesById[match.routeId];
1288
- const existingMatch = this.getMatch(match.id);
1289
- const previousMatch = previousMatchesByRouteId.get(match.routeId);
1290
- match.params = previousMatch ? nullReplaceEqualDeep(previousMatch.params, routeParams) : routeParams;
1291
- if (!existingMatch) {
1292
- const parentMatch = matches[index - 1];
1293
- const parentContext = this.getParentContext(parentMatch);
1294
- if (route.options.context) {
1295
- const contextFnContext = {
1296
- deps: match.loaderDeps,
1297
- params: match.params,
1298
- context: parentContext ?? {},
1299
- location: next,
1300
- navigate: (opts2) => this.navigate({ ...opts2, _fromLocation: next }),
1301
- buildLocation: this.buildLocation,
1302
- cause: match.cause,
1303
- abortController: match.abortController,
1304
- preload: !!match.preload,
1305
- matches,
1306
- routeId: route.id
1307
- };
1308
- match.__routeContext = route.options.context(contextFnContext) ?? void 0;
1309
- }
1310
- match.context = {
1311
- ...parentContext,
1312
- ...match.__routeContext,
1313
- ...match.__beforeLoadContext
1314
- };
1315
- }
1316
- }
1317
- return matches;
1318
- }
1319
- /**
1320
- * Lightweight route matching for buildLocation.
1321
- * Only computes fullPath, accumulated search, and params - skipping expensive
1322
- * operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
1323
- */
1324
- matchRoutesLightweight(location) {
1325
- const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(
1326
- location.pathname
1327
- );
1328
- const lastRoute = last(matchedRoutes);
1329
- const accumulatedSearch = { ...location.search };
1330
- for (const route of matchedRoutes) {
1331
- try {
1332
- Object.assign(
1333
- accumulatedSearch,
1334
- validateSearch(route.options.validateSearch, accumulatedSearch)
1335
- );
1336
- } catch {
1337
- }
1338
- }
1339
- const lastStateMatch = last(this.state.matches);
1340
- const canReuseParams = lastStateMatch && lastStateMatch.routeId === lastRoute.id && location.pathname === this.state.location.pathname;
1341
- let params;
1342
- if (canReuseParams) {
1343
- params = lastStateMatch.params;
1344
- } else {
1345
- const strictParams = Object.assign(
1346
- /* @__PURE__ */ Object.create(null),
1347
- routeParams
1348
- );
1349
- for (const route of matchedRoutes) {
1350
- try {
1351
- extractStrictParams(
1352
- route,
1353
- routeParams,
1354
- parsedParams ?? {},
1355
- strictParams
1356
- );
1357
- } catch {
1358
- }
1359
- }
1360
- params = strictParams;
1361
- }
1362
- return {
1363
- matchedRoutes,
1364
- fullPath: lastRoute.fullPath,
1365
- search: accumulatedSearch,
1366
- params
1367
- };
1368
- }
1369
- }
1370
- class SearchParamError extends Error {
1371
- }
1372
- class PathParamError extends Error {
1373
- }
1374
- const normalize = (str) => str.endsWith("/") && str.length > 1 ? str.slice(0, -1) : str;
66
+ var RouterCore = class {
67
+ /**
68
+ * @deprecated Use the `createRouter` function instead
69
+ */
70
+ constructor(options) {
71
+ this.tempLocationKey = `${Math.round(Math.random() * 1e7)}`;
72
+ this.resetNextScroll = true;
73
+ this.shouldViewTransition = void 0;
74
+ this.isViewTransitionTypesSupported = void 0;
75
+ this.subscribers = /* @__PURE__ */ new Set();
76
+ this.isScrollRestoring = false;
77
+ this.isScrollRestorationSetup = false;
78
+ this.startTransition = (fn) => fn();
79
+ this.update = (newOptions) => {
80
+ if (process.env.NODE_ENV !== "production") {
81
+ if (newOptions.notFoundRoute) console.warn("The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/framework/react/guide/not-found-errors#migrating-from-notfoundroute for more info.");
82
+ }
83
+ const prevOptions = this.options;
84
+ const prevBasepath = this.basepath ?? prevOptions?.basepath ?? "/";
85
+ const basepathWasUnset = this.basepath === void 0;
86
+ const prevRewriteOption = prevOptions?.rewrite;
87
+ this.options = {
88
+ ...prevOptions,
89
+ ...newOptions
90
+ };
91
+ this.isServer = this.options.isServer ?? typeof document === "undefined";
92
+ this.protocolAllowlist = new Set(this.options.protocolAllowlist);
93
+ if (this.options.pathParamsAllowedCharacters) this.pathParamsDecoder = compileDecodeCharMap(this.options.pathParamsAllowedCharacters);
94
+ if (!this.history || this.options.history && this.options.history !== this.history) if (!this.options.history) {
95
+ if (!(isServer ?? this.isServer)) this.history = createBrowserHistory();
96
+ } else this.history = this.options.history;
97
+ this.origin = this.options.origin;
98
+ if (!this.origin) if (!(isServer ?? this.isServer) && window?.origin && window.origin !== "null") this.origin = window.origin;
99
+ else this.origin = "http://localhost";
100
+ if (this.history) this.updateLatestLocation();
101
+ if (this.options.routeTree !== this.routeTree) {
102
+ this.routeTree = this.options.routeTree;
103
+ let processRouteTreeResult;
104
+ if ((isServer ?? this.isServer) && process.env.NODE_ENV !== "development" && globalThis.__TSR_CACHE__ && globalThis.__TSR_CACHE__.routeTree === this.routeTree) {
105
+ const cached = globalThis.__TSR_CACHE__;
106
+ this.resolvePathCache = cached.resolvePathCache;
107
+ processRouteTreeResult = cached.processRouteTreeResult;
108
+ } else {
109
+ this.resolvePathCache = createLRUCache(1e3);
110
+ processRouteTreeResult = this.buildRouteTree();
111
+ if ((isServer ?? this.isServer) && process.env.NODE_ENV !== "development" && globalThis.__TSR_CACHE__ === void 0) globalThis.__TSR_CACHE__ = {
112
+ routeTree: this.routeTree,
113
+ processRouteTreeResult,
114
+ resolvePathCache: this.resolvePathCache
115
+ };
116
+ }
117
+ this.setRoutes(processRouteTreeResult);
118
+ }
119
+ if (!this.__store && this.latestLocation) if (isServer ?? this.isServer) this.__store = createServerStore(getInitialRouterState(this.latestLocation));
120
+ else {
121
+ this.__store = createStore(getInitialRouterState(this.latestLocation));
122
+ setupScrollRestoration(this);
123
+ }
124
+ let needsLocationUpdate = false;
125
+ const nextBasepath = this.options.basepath ?? "/";
126
+ const nextRewriteOption = this.options.rewrite;
127
+ if (basepathWasUnset || prevBasepath !== nextBasepath || prevRewriteOption !== nextRewriteOption) {
128
+ this.basepath = nextBasepath;
129
+ const rewrites = [];
130
+ const trimmed = trimPath(nextBasepath);
131
+ if (trimmed && trimmed !== "/") rewrites.push(rewriteBasepath({ basepath: nextBasepath }));
132
+ if (nextRewriteOption) rewrites.push(nextRewriteOption);
133
+ this.rewrite = rewrites.length === 0 ? void 0 : rewrites.length === 1 ? rewrites[0] : composeRewrites(rewrites);
134
+ if (this.history) this.updateLatestLocation();
135
+ needsLocationUpdate = true;
136
+ }
137
+ if (needsLocationUpdate && this.__store) this.__store.setState((s) => ({
138
+ ...s,
139
+ location: this.latestLocation
140
+ }));
141
+ if (typeof window !== "undefined" && "CSS" in window && typeof window.CSS?.supports === "function") this.isViewTransitionTypesSupported = window.CSS.supports("selector(:active-view-transition-type(a)");
142
+ };
143
+ this.updateLatestLocation = () => {
144
+ this.latestLocation = this.parseLocation(this.history.location, this.latestLocation);
145
+ };
146
+ this.buildRouteTree = () => {
147
+ const result = processRouteTree(this.routeTree, this.options.caseSensitive, (route, i) => {
148
+ route.init({ originalIndex: i });
149
+ });
150
+ if (this.options.routeMasks) processRouteMasks(this.options.routeMasks, result.processedTree);
151
+ return result;
152
+ };
153
+ this.subscribe = (eventType, fn) => {
154
+ const listener = {
155
+ eventType,
156
+ fn
157
+ };
158
+ this.subscribers.add(listener);
159
+ return () => {
160
+ this.subscribers.delete(listener);
161
+ };
162
+ };
163
+ this.emit = (routerEvent) => {
164
+ this.subscribers.forEach((listener) => {
165
+ if (listener.eventType === routerEvent.type) listener.fn(routerEvent);
166
+ });
167
+ };
168
+ this.parseLocation = (locationToParse, previousLocation) => {
169
+ const parse = ({ pathname, search, hash, href, state }) => {
170
+ if (!this.rewrite && !/[ \x00-\x1f\x7f\u0080-\uffff]/.test(pathname)) {
171
+ const parsedSearch = this.options.parseSearch(search);
172
+ const searchStr = this.options.stringifySearch(parsedSearch);
173
+ return {
174
+ href: pathname + searchStr + hash,
175
+ publicHref: href,
176
+ pathname: decodePath(pathname).path,
177
+ external: false,
178
+ searchStr,
179
+ search: nullReplaceEqualDeep(previousLocation?.search, parsedSearch),
180
+ hash: decodePath(hash.slice(1)).path,
181
+ state: replaceEqualDeep(previousLocation?.state, state)
182
+ };
183
+ }
184
+ const fullUrl = new URL(href, this.origin);
185
+ const url = executeRewriteInput(this.rewrite, fullUrl);
186
+ const parsedSearch = this.options.parseSearch(url.search);
187
+ const searchStr = this.options.stringifySearch(parsedSearch);
188
+ url.search = searchStr;
189
+ return {
190
+ href: url.href.replace(url.origin, ""),
191
+ publicHref: href,
192
+ pathname: decodePath(url.pathname).path,
193
+ external: !!this.rewrite && url.origin !== this.origin,
194
+ searchStr,
195
+ search: nullReplaceEqualDeep(previousLocation?.search, parsedSearch),
196
+ hash: decodePath(url.hash.slice(1)).path,
197
+ state: replaceEqualDeep(previousLocation?.state, state)
198
+ };
199
+ };
200
+ const location = parse(locationToParse);
201
+ const { __tempLocation, __tempKey } = location.state;
202
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
203
+ const parsedTempLocation = parse(__tempLocation);
204
+ parsedTempLocation.state.key = location.state.key;
205
+ parsedTempLocation.state.__TSR_key = location.state.__TSR_key;
206
+ delete parsedTempLocation.state.__tempLocation;
207
+ return {
208
+ ...parsedTempLocation,
209
+ maskedLocation: location
210
+ };
211
+ }
212
+ return location;
213
+ };
214
+ this.resolvePathWithBase = (from, path) => {
215
+ return resolvePath({
216
+ base: from,
217
+ to: cleanPath(path),
218
+ trailingSlash: this.options.trailingSlash,
219
+ cache: this.resolvePathCache
220
+ });
221
+ };
222
+ this.matchRoutes = (pathnameOrNext, locationSearchOrOpts, opts) => {
223
+ if (typeof pathnameOrNext === "string") return this.matchRoutesInternal({
224
+ pathname: pathnameOrNext,
225
+ search: locationSearchOrOpts
226
+ }, opts);
227
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts);
228
+ };
229
+ this.getMatchedRoutes = (pathname) => {
230
+ return getMatchedRoutes({
231
+ pathname,
232
+ routesById: this.routesById,
233
+ processedTree: this.processedTree
234
+ });
235
+ };
236
+ this.cancelMatch = (id) => {
237
+ const match = this.getMatch(id);
238
+ if (!match) return;
239
+ match.abortController.abort();
240
+ clearTimeout(match._nonReactive.pendingTimeout);
241
+ match._nonReactive.pendingTimeout = void 0;
242
+ };
243
+ this.cancelMatches = () => {
244
+ const currentPendingMatches = this.state.matches.filter((match) => match.status === "pending");
245
+ const currentLoadingMatches = this.state.matches.filter((match) => match.isFetching === "loader");
246
+ new Set([
247
+ ...this.state.pendingMatches ?? [],
248
+ ...currentPendingMatches,
249
+ ...currentLoadingMatches
250
+ ]).forEach((match) => {
251
+ this.cancelMatch(match.id);
252
+ });
253
+ };
254
+ this.buildLocation = (opts) => {
255
+ const build = (dest = {}) => {
256
+ const currentLocation = dest._fromLocation || this.pendingBuiltLocation || this.latestLocation;
257
+ const lightweightResult = this.matchRoutesLightweight(currentLocation);
258
+ if (dest.from && process.env.NODE_ENV !== "production" && dest._isNavigate) {
259
+ const allFromMatches = this.getMatchedRoutes(dest.from).matchedRoutes;
260
+ const matchedFrom = findLast(lightweightResult.matchedRoutes, (d) => {
261
+ return comparePaths(d.fullPath, dest.from);
262
+ });
263
+ const matchedCurrent = findLast(allFromMatches, (d) => {
264
+ return comparePaths(d.fullPath, lightweightResult.fullPath);
265
+ });
266
+ if (!matchedFrom && !matchedCurrent) console.warn(`Could not find match for from: ${dest.from}`);
267
+ }
268
+ const defaultedFromPath = dest.unsafeRelative === "path" ? currentLocation.pathname : dest.from ?? lightweightResult.fullPath;
269
+ const fromPath = this.resolvePathWithBase(defaultedFromPath, ".");
270
+ const fromSearch = lightweightResult.search;
271
+ const fromParams = Object.assign(Object.create(null), lightweightResult.params);
272
+ const nextTo = dest.to ? this.resolvePathWithBase(fromPath, `${dest.to}`) : this.resolvePathWithBase(fromPath, ".");
273
+ const nextParams = dest.params === false || dest.params === null ? Object.create(null) : (dest.params ?? true) === true ? fromParams : Object.assign(fromParams, functionalUpdate(dest.params, fromParams));
274
+ const destMatchResult = this.getMatchedRoutes(nextTo);
275
+ let destRoutes = destMatchResult.matchedRoutes;
276
+ if ((!destMatchResult.foundRoute || destMatchResult.foundRoute.path !== "/" && destMatchResult.routeParams["**"]) && this.options.notFoundRoute) destRoutes = [...destRoutes, this.options.notFoundRoute];
277
+ if (Object.keys(nextParams).length > 0) for (const route of destRoutes) {
278
+ const fn = route.options.params?.stringify ?? route.options.stringifyParams;
279
+ if (fn) try {
280
+ Object.assign(nextParams, fn(nextParams));
281
+ } catch {}
282
+ }
283
+ const nextPathname = opts.leaveParams ? nextTo : decodePath(interpolatePath({
284
+ path: nextTo,
285
+ params: nextParams,
286
+ decoder: this.pathParamsDecoder,
287
+ server: this.isServer
288
+ }).interpolatedPath).path;
289
+ let nextSearch = fromSearch;
290
+ if (opts._includeValidateSearch && this.options.search?.strict) {
291
+ const validatedSearch = {};
292
+ destRoutes.forEach((route) => {
293
+ if (route.options.validateSearch) try {
294
+ Object.assign(validatedSearch, validateSearch(route.options.validateSearch, {
295
+ ...validatedSearch,
296
+ ...nextSearch
297
+ }));
298
+ } catch {}
299
+ });
300
+ nextSearch = validatedSearch;
301
+ }
302
+ nextSearch = applySearchMiddleware({
303
+ search: nextSearch,
304
+ dest,
305
+ destRoutes,
306
+ _includeValidateSearch: opts._includeValidateSearch
307
+ });
308
+ nextSearch = nullReplaceEqualDeep(fromSearch, nextSearch);
309
+ const searchStr = this.options.stringifySearch(nextSearch);
310
+ const hash = dest.hash === true ? currentLocation.hash : dest.hash ? functionalUpdate(dest.hash, currentLocation.hash) : void 0;
311
+ const hashStr = hash ? `#${hash}` : "";
312
+ let nextState = dest.state === true ? currentLocation.state : dest.state ? functionalUpdate(dest.state, currentLocation.state) : {};
313
+ nextState = replaceEqualDeep(currentLocation.state, nextState);
314
+ const fullPath = `${nextPathname}${searchStr}${hashStr}`;
315
+ let href;
316
+ let publicHref;
317
+ let external = false;
318
+ if (this.rewrite) {
319
+ const url = new URL(fullPath, this.origin);
320
+ const rewrittenUrl = executeRewriteOutput(this.rewrite, url);
321
+ href = url.href.replace(url.origin, "");
322
+ if (rewrittenUrl.origin !== this.origin) {
323
+ publicHref = rewrittenUrl.href;
324
+ external = true;
325
+ } else publicHref = rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash;
326
+ } else {
327
+ href = encodePathLikeUrl(fullPath);
328
+ publicHref = href;
329
+ }
330
+ return {
331
+ publicHref,
332
+ href,
333
+ pathname: nextPathname,
334
+ search: nextSearch,
335
+ searchStr,
336
+ state: nextState,
337
+ hash: hash ?? "",
338
+ external,
339
+ unmaskOnReload: dest.unmaskOnReload
340
+ };
341
+ };
342
+ const buildWithMatches = (dest = {}, maskedDest) => {
343
+ const next = build(dest);
344
+ let maskedNext = maskedDest ? build(maskedDest) : void 0;
345
+ if (!maskedNext) {
346
+ const params = Object.create(null);
347
+ if (this.options.routeMasks) {
348
+ const match = findFlatMatch(next.pathname, this.processedTree);
349
+ if (match) {
350
+ Object.assign(params, match.rawParams);
351
+ const { from: _from, params: maskParams, ...maskProps } = match.route;
352
+ const nextParams = maskParams === false || maskParams === null ? Object.create(null) : (maskParams ?? true) === true ? params : Object.assign(params, functionalUpdate(maskParams, params));
353
+ maskedDest = {
354
+ from: opts.from,
355
+ ...maskProps,
356
+ params: nextParams
357
+ };
358
+ maskedNext = build(maskedDest);
359
+ }
360
+ }
361
+ }
362
+ if (maskedNext) next.maskedLocation = maskedNext;
363
+ return next;
364
+ };
365
+ if (opts.mask) return buildWithMatches(opts, {
366
+ from: opts.from,
367
+ ...opts.mask
368
+ });
369
+ return buildWithMatches(opts);
370
+ };
371
+ this.commitLocation = async ({ viewTransition, ignoreBlocker, ...next }) => {
372
+ const isSameState = () => {
373
+ const ignoredProps = [
374
+ "key",
375
+ "__TSR_key",
376
+ "__TSR_index",
377
+ "__hashScrollIntoViewOptions"
378
+ ];
379
+ ignoredProps.forEach((prop) => {
380
+ next.state[prop] = this.latestLocation.state[prop];
381
+ });
382
+ const isEqual = deepEqual(next.state, this.latestLocation.state);
383
+ ignoredProps.forEach((prop) => {
384
+ delete next.state[prop];
385
+ });
386
+ return isEqual;
387
+ };
388
+ const isSameUrl = trimPathRight(this.latestLocation.href) === trimPathRight(next.href);
389
+ const previousCommitPromise = this.commitLocationPromise;
390
+ this.commitLocationPromise = createControlledPromise(() => {
391
+ previousCommitPromise?.resolve();
392
+ });
393
+ if (isSameUrl && isSameState()) this.load();
394
+ else {
395
+ let { maskedLocation, hashScrollIntoView, ...nextHistory } = next;
396
+ if (maskedLocation) {
397
+ nextHistory = {
398
+ ...maskedLocation,
399
+ state: {
400
+ ...maskedLocation.state,
401
+ __tempKey: void 0,
402
+ __tempLocation: {
403
+ ...nextHistory,
404
+ search: nextHistory.searchStr,
405
+ state: {
406
+ ...nextHistory.state,
407
+ __tempKey: void 0,
408
+ __tempLocation: void 0,
409
+ __TSR_key: void 0,
410
+ key: void 0
411
+ }
412
+ }
413
+ }
414
+ };
415
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) nextHistory.state.__tempKey = this.tempLocationKey;
416
+ }
417
+ nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
418
+ this.shouldViewTransition = viewTransition;
419
+ this.history[next.replace ? "replace" : "push"](nextHistory.publicHref, nextHistory.state, { ignoreBlocker });
420
+ }
421
+ this.resetNextScroll = next.resetScroll ?? true;
422
+ if (!this.history.subscribers.size) this.load();
423
+ return this.commitLocationPromise;
424
+ };
425
+ this.buildAndCommitLocation = ({ replace, resetScroll, hashScrollIntoView, viewTransition, ignoreBlocker, href, ...rest } = {}) => {
426
+ if (href) {
427
+ const currentIndex = this.history.location.state.__TSR_index;
428
+ const parsed = parseHref(href, { __TSR_index: replace ? currentIndex : currentIndex + 1 });
429
+ const hrefUrl = new URL(parsed.pathname, this.origin);
430
+ rest.to = executeRewriteInput(this.rewrite, hrefUrl).pathname;
431
+ rest.search = this.options.parseSearch(parsed.search);
432
+ rest.hash = parsed.hash.slice(1);
433
+ }
434
+ const location = this.buildLocation({
435
+ ...rest,
436
+ _includeValidateSearch: true
437
+ });
438
+ this.pendingBuiltLocation = location;
439
+ const commitPromise = this.commitLocation({
440
+ ...location,
441
+ viewTransition,
442
+ replace,
443
+ resetScroll,
444
+ hashScrollIntoView,
445
+ ignoreBlocker
446
+ });
447
+ Promise.resolve().then(() => {
448
+ if (this.pendingBuiltLocation === location) this.pendingBuiltLocation = void 0;
449
+ });
450
+ return commitPromise;
451
+ };
452
+ this.navigate = async ({ to, reloadDocument, href, publicHref, ...rest }) => {
453
+ let hrefIsUrl = false;
454
+ if (href) try {
455
+ new URL(`${href}`);
456
+ hrefIsUrl = true;
457
+ } catch {}
458
+ if (hrefIsUrl && !reloadDocument) reloadDocument = true;
459
+ if (reloadDocument) {
460
+ if (to !== void 0 || !href) {
461
+ const location = this.buildLocation({
462
+ to,
463
+ ...rest
464
+ });
465
+ href = href ?? location.publicHref;
466
+ publicHref = publicHref ?? location.publicHref;
467
+ }
468
+ const reloadHref = !hrefIsUrl && publicHref ? publicHref : href;
469
+ if (isDangerousProtocol(reloadHref, this.protocolAllowlist)) {
470
+ if (process.env.NODE_ENV !== "production") console.warn(`Blocked navigation to dangerous protocol: ${reloadHref}`);
471
+ return Promise.resolve();
472
+ }
473
+ if (!rest.ignoreBlocker) {
474
+ const blockers = this.history.getBlockers?.() ?? [];
475
+ for (const blocker of blockers) if (blocker?.blockerFn) {
476
+ if (await blocker.blockerFn({
477
+ currentLocation: this.latestLocation,
478
+ nextLocation: this.latestLocation,
479
+ action: "PUSH"
480
+ })) return Promise.resolve();
481
+ }
482
+ }
483
+ if (rest.replace) window.location.replace(reloadHref);
484
+ else window.location.href = reloadHref;
485
+ return Promise.resolve();
486
+ }
487
+ return this.buildAndCommitLocation({
488
+ ...rest,
489
+ href,
490
+ to,
491
+ _isNavigate: true
492
+ });
493
+ };
494
+ this.beforeLoad = () => {
495
+ this.cancelMatches();
496
+ this.updateLatestLocation();
497
+ if (isServer ?? this.isServer) {
498
+ const nextLocation = this.buildLocation({
499
+ to: this.latestLocation.pathname,
500
+ search: true,
501
+ params: true,
502
+ hash: true,
503
+ state: true,
504
+ _includeValidateSearch: true
505
+ });
506
+ if (this.latestLocation.publicHref !== nextLocation.publicHref) {
507
+ const href = this.getParsedLocationHref(nextLocation);
508
+ if (nextLocation.external) throw redirect({ href });
509
+ else throw redirect({
510
+ href,
511
+ _builtLocation: nextLocation
512
+ });
513
+ }
514
+ }
515
+ const pendingMatches = this.matchRoutes(this.latestLocation);
516
+ this.__store.setState((s) => ({
517
+ ...s,
518
+ status: "pending",
519
+ statusCode: 200,
520
+ isLoading: true,
521
+ location: this.latestLocation,
522
+ pendingMatches,
523
+ cachedMatches: s.cachedMatches.filter((d) => !pendingMatches.some((e) => e.id === d.id))
524
+ }));
525
+ };
526
+ this.load = async (opts) => {
527
+ let redirect;
528
+ let notFound;
529
+ let loadPromise;
530
+ const previousLocation = this.state.resolvedLocation ?? this.state.location;
531
+ loadPromise = new Promise((resolve) => {
532
+ this.startTransition(async () => {
533
+ try {
534
+ this.beforeLoad();
535
+ const next = this.latestLocation;
536
+ const prevLocation = this.state.resolvedLocation;
537
+ if (!this.state.redirect) this.emit({
538
+ type: "onBeforeNavigate",
539
+ ...getLocationChangeInfo({
540
+ resolvedLocation: prevLocation,
541
+ location: next
542
+ })
543
+ });
544
+ this.emit({
545
+ type: "onBeforeLoad",
546
+ ...getLocationChangeInfo({
547
+ resolvedLocation: prevLocation,
548
+ location: next
549
+ })
550
+ });
551
+ await loadMatches({
552
+ router: this,
553
+ sync: opts?.sync,
554
+ forceStaleReload: previousLocation.href === next.href,
555
+ matches: this.state.pendingMatches,
556
+ location: next,
557
+ updateMatch: this.updateMatch,
558
+ onReady: async () => {
559
+ this.startTransition(() => {
560
+ this.startViewTransition(async () => {
561
+ let exitingMatches = [];
562
+ let hookExitingMatches = [];
563
+ let hookEnteringMatches = [];
564
+ let hookStayingMatches = [];
565
+ batch$1(() => {
566
+ this.__store.setState((s) => {
567
+ const previousMatches = s.matches;
568
+ const newMatches = s.pendingMatches || s.matches;
569
+ exitingMatches = previousMatches.filter((match) => !newMatches.some((d) => d.id === match.id));
570
+ hookExitingMatches = previousMatches.filter((match) => !newMatches.some((d) => d.routeId === match.routeId));
571
+ hookEnteringMatches = newMatches.filter((match) => !previousMatches.some((d) => d.routeId === match.routeId));
572
+ hookStayingMatches = newMatches.filter((match) => previousMatches.some((d) => d.routeId === match.routeId));
573
+ return {
574
+ ...s,
575
+ isLoading: false,
576
+ loadedAt: Date.now(),
577
+ matches: newMatches,
578
+ pendingMatches: void 0,
579
+ cachedMatches: [...s.cachedMatches, ...exitingMatches.filter((d) => d.status !== "error" && d.status !== "notFound" && d.status !== "redirected")]
580
+ };
581
+ });
582
+ this.clearExpiredCache();
583
+ });
584
+ [
585
+ [hookExitingMatches, "onLeave"],
586
+ [hookEnteringMatches, "onEnter"],
587
+ [hookStayingMatches, "onStay"]
588
+ ].forEach(([matches, hook]) => {
589
+ matches.forEach((match) => {
590
+ this.looseRoutesById[match.routeId].options[hook]?.(match);
591
+ });
592
+ });
593
+ });
594
+ });
595
+ }
596
+ });
597
+ } catch (err) {
598
+ if (isRedirect(err)) {
599
+ redirect = err;
600
+ if (!(isServer ?? this.isServer)) this.navigate({
601
+ ...redirect.options,
602
+ replace: true,
603
+ ignoreBlocker: true
604
+ });
605
+ } else if (isNotFound(err)) notFound = err;
606
+ this.__store.setState((s) => ({
607
+ ...s,
608
+ statusCode: redirect ? redirect.status : notFound ? 404 : s.matches.some((d) => d.status === "error") ? 500 : 200,
609
+ redirect
610
+ }));
611
+ }
612
+ if (this.latestLoadPromise === loadPromise) {
613
+ this.commitLocationPromise?.resolve();
614
+ this.latestLoadPromise = void 0;
615
+ this.commitLocationPromise = void 0;
616
+ }
617
+ resolve();
618
+ });
619
+ });
620
+ this.latestLoadPromise = loadPromise;
621
+ await loadPromise;
622
+ while (this.latestLoadPromise && loadPromise !== this.latestLoadPromise) await this.latestLoadPromise;
623
+ let newStatusCode = void 0;
624
+ if (this.hasNotFoundMatch()) newStatusCode = 404;
625
+ else if (this.__store.state.matches.some((d) => d.status === "error")) newStatusCode = 500;
626
+ if (newStatusCode !== void 0) this.__store.setState((s) => ({
627
+ ...s,
628
+ statusCode: newStatusCode
629
+ }));
630
+ };
631
+ this.startViewTransition = (fn) => {
632
+ const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
633
+ this.shouldViewTransition = void 0;
634
+ if (shouldViewTransition && typeof document !== "undefined" && "startViewTransition" in document && typeof document.startViewTransition === "function") {
635
+ let startViewTransitionParams;
636
+ if (typeof shouldViewTransition === "object" && this.isViewTransitionTypesSupported) {
637
+ const next = this.latestLocation;
638
+ const prevLocation = this.state.resolvedLocation;
639
+ const resolvedViewTransitionTypes = typeof shouldViewTransition.types === "function" ? shouldViewTransition.types(getLocationChangeInfo({
640
+ resolvedLocation: prevLocation,
641
+ location: next
642
+ })) : shouldViewTransition.types;
643
+ if (resolvedViewTransitionTypes === false) {
644
+ fn();
645
+ return;
646
+ }
647
+ startViewTransitionParams = {
648
+ update: fn,
649
+ types: resolvedViewTransitionTypes
650
+ };
651
+ } else startViewTransitionParams = fn;
652
+ document.startViewTransition(startViewTransitionParams);
653
+ } else fn();
654
+ };
655
+ this.updateMatch = (id, updater) => {
656
+ this.startTransition(() => {
657
+ const matchesKey = this.state.pendingMatches?.some((d) => d.id === id) ? "pendingMatches" : this.state.matches.some((d) => d.id === id) ? "matches" : this.state.cachedMatches.some((d) => d.id === id) ? "cachedMatches" : "";
658
+ if (matchesKey) if (matchesKey === "cachedMatches") this.__store.setState((s) => ({
659
+ ...s,
660
+ cachedMatches: filterRedirectedCachedMatches(s.cachedMatches.map((d) => d.id === id ? updater(d) : d))
661
+ }));
662
+ else this.__store.setState((s) => ({
663
+ ...s,
664
+ [matchesKey]: s[matchesKey]?.map((d) => d.id === id ? updater(d) : d)
665
+ }));
666
+ });
667
+ };
668
+ this.getMatch = (matchId) => {
669
+ const findFn = (d) => d.id === matchId;
670
+ return this.state.cachedMatches.find(findFn) ?? this.state.pendingMatches?.find(findFn) ?? this.state.matches.find(findFn);
671
+ };
672
+ this.invalidate = (opts) => {
673
+ const invalidate = (d) => {
674
+ if (opts?.filter?.(d) ?? true) return {
675
+ ...d,
676
+ invalid: true,
677
+ ...opts?.forcePending || d.status === "error" || d.status === "notFound" ? {
678
+ status: "pending",
679
+ error: void 0
680
+ } : void 0
681
+ };
682
+ return d;
683
+ };
684
+ this.__store.setState((s) => ({
685
+ ...s,
686
+ matches: s.matches.map(invalidate),
687
+ cachedMatches: s.cachedMatches.map(invalidate),
688
+ pendingMatches: s.pendingMatches?.map(invalidate)
689
+ }));
690
+ this.shouldViewTransition = false;
691
+ return this.load({ sync: opts?.sync });
692
+ };
693
+ this.getParsedLocationHref = (location) => {
694
+ return location.publicHref || "/";
695
+ };
696
+ this.resolveRedirect = (redirect) => {
697
+ const locationHeader = redirect.headers.get("Location");
698
+ if (!redirect.options.href || redirect.options._builtLocation) {
699
+ const location = redirect.options._builtLocation ?? this.buildLocation(redirect.options);
700
+ const href = this.getParsedLocationHref(location);
701
+ redirect.options.href = href;
702
+ redirect.headers.set("Location", href);
703
+ } else if (locationHeader) try {
704
+ const url = new URL(locationHeader);
705
+ if (this.origin && url.origin === this.origin) {
706
+ const href = url.pathname + url.search + url.hash;
707
+ redirect.options.href = href;
708
+ redirect.headers.set("Location", href);
709
+ }
710
+ } catch {}
711
+ if (redirect.options.href && !redirect.options._builtLocation && isDangerousProtocol(redirect.options.href, this.protocolAllowlist)) throw new Error(process.env.NODE_ENV !== "production" ? `Redirect blocked: unsafe protocol in href "${redirect.options.href}". Allowed protocols: ${Array.from(this.protocolAllowlist).join(", ")}.` : "Redirect blocked: unsafe protocol");
712
+ if (!redirect.headers.get("Location")) redirect.headers.set("Location", redirect.options.href);
713
+ return redirect;
714
+ };
715
+ this.clearCache = (opts) => {
716
+ const filter = opts?.filter;
717
+ if (filter !== void 0) this.__store.setState((s) => {
718
+ return {
719
+ ...s,
720
+ cachedMatches: s.cachedMatches.filter((m) => !filter(m))
721
+ };
722
+ });
723
+ else this.__store.setState((s) => {
724
+ return {
725
+ ...s,
726
+ cachedMatches: []
727
+ };
728
+ });
729
+ };
730
+ this.clearExpiredCache = () => {
731
+ const filter = (d) => {
732
+ const route = this.looseRoutesById[d.routeId];
733
+ if (!route.options.loader) return true;
734
+ const gcTime = (d.preload ? route.options.preloadGcTime ?? this.options.defaultPreloadGcTime : route.options.gcTime ?? this.options.defaultGcTime) ?? 300 * 1e3;
735
+ if (d.status === "error") return true;
736
+ return Date.now() - d.updatedAt >= gcTime;
737
+ };
738
+ this.clearCache({ filter });
739
+ };
740
+ this.loadRouteChunk = loadRouteChunk;
741
+ this.preloadRoute = async (opts) => {
742
+ const next = opts._builtLocation ?? this.buildLocation(opts);
743
+ let matches = this.matchRoutes(next, {
744
+ throwOnError: true,
745
+ preload: true,
746
+ dest: opts
747
+ });
748
+ const activeMatchIds = new Set([...this.state.matches, ...this.state.pendingMatches ?? []].map((d) => d.id));
749
+ const loadedMatchIds = new Set([...activeMatchIds, ...this.state.cachedMatches.map((d) => d.id)]);
750
+ batch$1(() => {
751
+ matches.forEach((match) => {
752
+ if (!loadedMatchIds.has(match.id)) this.__store.setState((s) => ({
753
+ ...s,
754
+ cachedMatches: [...s.cachedMatches, match]
755
+ }));
756
+ });
757
+ });
758
+ try {
759
+ matches = await loadMatches({
760
+ router: this,
761
+ matches,
762
+ location: next,
763
+ preload: true,
764
+ updateMatch: (id, updater) => {
765
+ if (activeMatchIds.has(id)) matches = matches.map((d) => d.id === id ? updater(d) : d);
766
+ else this.updateMatch(id, updater);
767
+ }
768
+ });
769
+ return matches;
770
+ } catch (err) {
771
+ if (isRedirect(err)) {
772
+ if (err.options.reloadDocument) return;
773
+ return await this.preloadRoute({
774
+ ...err.options,
775
+ _fromLocation: next
776
+ });
777
+ }
778
+ if (!isNotFound(err)) console.error(err);
779
+ return;
780
+ }
781
+ };
782
+ this.matchRoute = (location, opts) => {
783
+ const matchLocation = {
784
+ ...location,
785
+ to: location.to ? this.resolvePathWithBase(location.from || "", location.to) : void 0,
786
+ params: location.params || {},
787
+ leaveParams: true
788
+ };
789
+ const next = this.buildLocation(matchLocation);
790
+ if (opts?.pending && this.state.status !== "pending") return false;
791
+ const baseLocation = (opts?.pending === void 0 ? !this.state.isLoading : opts.pending) ? this.latestLocation : this.state.resolvedLocation || this.state.location;
792
+ const match = findSingleMatch(next.pathname, opts?.caseSensitive ?? false, opts?.fuzzy ?? false, baseLocation.pathname, this.processedTree);
793
+ if (!match) return false;
794
+ if (location.params) {
795
+ if (!deepEqual(match.rawParams, location.params, { partial: true })) return false;
796
+ }
797
+ if (opts?.includeSearch ?? true) return deepEqual(baseLocation.search, next.search, { partial: true }) ? match.rawParams : false;
798
+ return match.rawParams;
799
+ };
800
+ this.hasNotFoundMatch = () => {
801
+ return this.__store.state.matches.some((d) => d.status === "notFound" || d.globalNotFound);
802
+ };
803
+ this.update({
804
+ defaultPreloadDelay: 50,
805
+ defaultPendingMs: 1e3,
806
+ defaultPendingMinMs: 500,
807
+ context: void 0,
808
+ ...options,
809
+ caseSensitive: options.caseSensitive ?? false,
810
+ notFoundMode: options.notFoundMode ?? "fuzzy",
811
+ stringifySearch: options.stringifySearch ?? defaultStringifySearch,
812
+ parseSearch: options.parseSearch ?? defaultParseSearch,
813
+ protocolAllowlist: options.protocolAllowlist ?? DEFAULT_PROTOCOL_ALLOWLIST
814
+ });
815
+ if (typeof document !== "undefined") self.__TSR_ROUTER__ = this;
816
+ }
817
+ isShell() {
818
+ return !!this.options.isShell;
819
+ }
820
+ isPrerendering() {
821
+ return !!this.options.isPrerendering;
822
+ }
823
+ get state() {
824
+ return this.__store.state;
825
+ }
826
+ setRoutes({ routesById, routesByPath, processedTree }) {
827
+ this.routesById = routesById;
828
+ this.routesByPath = routesByPath;
829
+ this.processedTree = processedTree;
830
+ const notFoundRoute = this.options.notFoundRoute;
831
+ if (notFoundRoute) {
832
+ notFoundRoute.init({ originalIndex: 99999999999 });
833
+ this.routesById[notFoundRoute.id] = notFoundRoute;
834
+ }
835
+ }
836
+ get looseRoutesById() {
837
+ return this.routesById;
838
+ }
839
+ getParentContext(parentMatch) {
840
+ return !parentMatch?.id ? this.options.context ?? void 0 : parentMatch.context ?? this.options.context ?? void 0;
841
+ }
842
+ matchRoutesInternal(next, opts) {
843
+ const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
844
+ const { foundRoute, routeParams, parsedParams } = matchedRoutesResult;
845
+ let { matchedRoutes } = matchedRoutesResult;
846
+ let isGlobalNotFound = false;
847
+ if (foundRoute ? foundRoute.path !== "/" && routeParams["**"] : trimPathRight(next.pathname)) if (this.options.notFoundRoute) matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
848
+ else isGlobalNotFound = true;
849
+ const globalNotFoundRouteId = isGlobalNotFound ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes) : void 0;
850
+ const matches = new Array(matchedRoutes.length);
851
+ const previousMatchesByRouteId = new Map(this.state.matches.map((match) => [match.routeId, match]));
852
+ for (let index = 0; index < matchedRoutes.length; index++) {
853
+ const route = matchedRoutes[index];
854
+ const parentMatch = matches[index - 1];
855
+ let preMatchSearch;
856
+ let strictMatchSearch;
857
+ let searchError;
858
+ {
859
+ const parentSearch = parentMatch?.search ?? next.search;
860
+ const parentStrictSearch = parentMatch?._strictSearch ?? void 0;
861
+ try {
862
+ const strictSearch = validateSearch(route.options.validateSearch, { ...parentSearch }) ?? void 0;
863
+ preMatchSearch = {
864
+ ...parentSearch,
865
+ ...strictSearch
866
+ };
867
+ strictMatchSearch = {
868
+ ...parentStrictSearch,
869
+ ...strictSearch
870
+ };
871
+ searchError = void 0;
872
+ } catch (err) {
873
+ let searchParamError = err;
874
+ if (!(err instanceof SearchParamError)) searchParamError = new SearchParamError(err.message, { cause: err });
875
+ if (opts?.throwOnError) throw searchParamError;
876
+ preMatchSearch = parentSearch;
877
+ strictMatchSearch = {};
878
+ searchError = searchParamError;
879
+ }
880
+ }
881
+ const loaderDeps = route.options.loaderDeps?.({ search: preMatchSearch }) ?? "";
882
+ const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : "";
883
+ const { interpolatedPath, usedParams } = interpolatePath({
884
+ path: route.fullPath,
885
+ params: routeParams,
886
+ decoder: this.pathParamsDecoder,
887
+ server: this.isServer
888
+ });
889
+ const matchId = route.id + interpolatedPath + loaderDepsHash;
890
+ const existingMatch = this.getMatch(matchId);
891
+ const previousMatch = previousMatchesByRouteId.get(route.id);
892
+ const strictParams = existingMatch?._strictParams ?? usedParams;
893
+ let paramsError = void 0;
894
+ if (!existingMatch) try {
895
+ extractStrictParams(route, usedParams, parsedParams, strictParams);
896
+ } catch (err) {
897
+ if (isNotFound(err) || isRedirect(err)) paramsError = err;
898
+ else paramsError = new PathParamError(err.message, { cause: err });
899
+ if (opts?.throwOnError) throw paramsError;
900
+ }
901
+ Object.assign(routeParams, strictParams);
902
+ const cause = previousMatch ? "stay" : "enter";
903
+ let match;
904
+ if (existingMatch) match = {
905
+ ...existingMatch,
906
+ cause,
907
+ params: previousMatch?.params ?? routeParams,
908
+ _strictParams: strictParams,
909
+ search: previousMatch ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : nullReplaceEqualDeep(existingMatch.search, preMatchSearch),
910
+ _strictSearch: strictMatchSearch
911
+ };
912
+ else {
913
+ const status = route.options.loader || route.options.beforeLoad || route.lazyFn || routeNeedsPreload(route) ? "pending" : "success";
914
+ match = {
915
+ id: matchId,
916
+ ssr: isServer ?? this.isServer ? void 0 : route.options.ssr,
917
+ index,
918
+ routeId: route.id,
919
+ params: previousMatch?.params ?? routeParams,
920
+ _strictParams: strictParams,
921
+ pathname: interpolatedPath,
922
+ updatedAt: Date.now(),
923
+ search: previousMatch ? nullReplaceEqualDeep(previousMatch.search, preMatchSearch) : preMatchSearch,
924
+ _strictSearch: strictMatchSearch,
925
+ searchError: void 0,
926
+ status,
927
+ isFetching: false,
928
+ error: void 0,
929
+ paramsError,
930
+ __routeContext: void 0,
931
+ _nonReactive: { loadPromise: createControlledPromise() },
932
+ __beforeLoadContext: void 0,
933
+ context: {},
934
+ abortController: new AbortController(),
935
+ fetchCount: 0,
936
+ cause,
937
+ loaderDeps: previousMatch ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps) : loaderDeps,
938
+ invalid: false,
939
+ preload: false,
940
+ links: void 0,
941
+ scripts: void 0,
942
+ headScripts: void 0,
943
+ meta: void 0,
944
+ staticData: route.options.staticData || {},
945
+ fullPath: route.fullPath
946
+ };
947
+ }
948
+ if (!opts?.preload) match.globalNotFound = globalNotFoundRouteId === route.id;
949
+ match.searchError = searchError;
950
+ const parentContext = this.getParentContext(parentMatch);
951
+ match.context = {
952
+ ...parentContext,
953
+ ...match.__routeContext,
954
+ ...match.__beforeLoadContext
955
+ };
956
+ matches[index] = match;
957
+ }
958
+ for (let index = 0; index < matches.length; index++) {
959
+ const match = matches[index];
960
+ const route = this.looseRoutesById[match.routeId];
961
+ const existingMatch = this.getMatch(match.id);
962
+ const previousMatch = previousMatchesByRouteId.get(match.routeId);
963
+ match.params = previousMatch ? nullReplaceEqualDeep(previousMatch.params, routeParams) : routeParams;
964
+ if (!existingMatch) {
965
+ const parentMatch = matches[index - 1];
966
+ const parentContext = this.getParentContext(parentMatch);
967
+ if (route.options.context) {
968
+ const contextFnContext = {
969
+ deps: match.loaderDeps,
970
+ params: match.params,
971
+ context: parentContext ?? {},
972
+ location: next,
973
+ navigate: (opts) => this.navigate({
974
+ ...opts,
975
+ _fromLocation: next
976
+ }),
977
+ buildLocation: this.buildLocation,
978
+ cause: match.cause,
979
+ abortController: match.abortController,
980
+ preload: !!match.preload,
981
+ matches,
982
+ routeId: route.id
983
+ };
984
+ match.__routeContext = route.options.context(contextFnContext) ?? void 0;
985
+ }
986
+ match.context = {
987
+ ...parentContext,
988
+ ...match.__routeContext,
989
+ ...match.__beforeLoadContext
990
+ };
991
+ }
992
+ }
993
+ return matches;
994
+ }
995
+ /**
996
+ * Lightweight route matching for buildLocation.
997
+ * Only computes fullPath, accumulated search, and params - skipping expensive
998
+ * operations like AbortController, ControlledPromise, loaderDeps, and full match objects.
999
+ */
1000
+ matchRoutesLightweight(location) {
1001
+ const { matchedRoutes, routeParams, parsedParams } = this.getMatchedRoutes(location.pathname);
1002
+ const lastRoute = last(matchedRoutes);
1003
+ const accumulatedSearch = { ...location.search };
1004
+ for (const route of matchedRoutes) try {
1005
+ Object.assign(accumulatedSearch, validateSearch(route.options.validateSearch, accumulatedSearch));
1006
+ } catch {}
1007
+ const lastStateMatch = last(this.state.matches);
1008
+ const canReuseParams = lastStateMatch && lastStateMatch.routeId === lastRoute.id && location.pathname === this.state.location.pathname;
1009
+ let params;
1010
+ if (canReuseParams) params = lastStateMatch.params;
1011
+ else {
1012
+ const strictParams = Object.assign(Object.create(null), routeParams);
1013
+ for (const route of matchedRoutes) try {
1014
+ extractStrictParams(route, routeParams, parsedParams ?? {}, strictParams);
1015
+ } catch {}
1016
+ params = strictParams;
1017
+ }
1018
+ return {
1019
+ matchedRoutes,
1020
+ fullPath: lastRoute.fullPath,
1021
+ search: accumulatedSearch,
1022
+ params
1023
+ };
1024
+ }
1025
+ };
1026
+ /** Error thrown when search parameter validation fails. */
1027
+ var SearchParamError = class extends Error {};
1028
+ /** Error thrown when path parameter parsing/validation fails. */
1029
+ var PathParamError = class extends Error {};
1030
+ var normalize = (str) => str.endsWith("/") && str.length > 1 ? str.slice(0, -1) : str;
1375
1031
  function comparePaths(a, b) {
1376
- return normalize(a) === normalize(b);
1032
+ return normalize(a) === normalize(b);
1377
1033
  }
1034
+ /**
1035
+ * Lazily import a module function and forward arguments to it, retaining
1036
+ * parameter and return types for the selected export key.
1037
+ */
1378
1038
  function lazyFn(fn, key) {
1379
- return async (...args) => {
1380
- const imported = await fn();
1381
- return imported[key || "default"](...args);
1382
- };
1039
+ return async (...args) => {
1040
+ return (await fn())[key || "default"](...args);
1041
+ };
1383
1042
  }
1043
+ /** Create an initial RouterState from a parsed location. */
1384
1044
  function getInitialRouterState(location) {
1385
- return {
1386
- loadedAt: 0,
1387
- isLoading: false,
1388
- isTransitioning: false,
1389
- status: "idle",
1390
- resolvedLocation: void 0,
1391
- location,
1392
- matches: [],
1393
- pendingMatches: [],
1394
- cachedMatches: [],
1395
- statusCode: 200
1396
- };
1045
+ return {
1046
+ loadedAt: 0,
1047
+ isLoading: false,
1048
+ isTransitioning: false,
1049
+ status: "idle",
1050
+ resolvedLocation: void 0,
1051
+ location,
1052
+ matches: [],
1053
+ pendingMatches: [],
1054
+ cachedMatches: [],
1055
+ statusCode: 200
1056
+ };
1397
1057
  }
1398
- function validateSearch(validateSearch2, input) {
1399
- if (validateSearch2 == null) return {};
1400
- if ("~standard" in validateSearch2) {
1401
- const result = validateSearch2["~standard"].validate(input);
1402
- if (result instanceof Promise)
1403
- throw new SearchParamError("Async validation not supported");
1404
- if (result.issues)
1405
- throw new SearchParamError(JSON.stringify(result.issues, void 0, 2), {
1406
- cause: result
1407
- });
1408
- return result.value;
1409
- }
1410
- if ("parse" in validateSearch2) {
1411
- return validateSearch2.parse(input);
1412
- }
1413
- if (typeof validateSearch2 === "function") {
1414
- return validateSearch2(input);
1415
- }
1416
- return {};
1058
+ function validateSearch(validateSearch, input) {
1059
+ if (validateSearch == null) return {};
1060
+ if ("~standard" in validateSearch) {
1061
+ const result = validateSearch["~standard"].validate(input);
1062
+ if (result instanceof Promise) throw new SearchParamError("Async validation not supported");
1063
+ if (result.issues) throw new SearchParamError(JSON.stringify(result.issues, void 0, 2), { cause: result });
1064
+ return result.value;
1065
+ }
1066
+ if ("parse" in validateSearch) return validateSearch.parse(input);
1067
+ if (typeof validateSearch === "function") return validateSearch(input);
1068
+ return {};
1417
1069
  }
1418
- function getMatchedRoutes({
1419
- pathname,
1420
- routesById,
1421
- processedTree
1422
- }) {
1423
- const routeParams = /* @__PURE__ */ Object.create(null);
1424
- const trimmedPath = trimPathRight(pathname);
1425
- let foundRoute = void 0;
1426
- let parsedParams = void 0;
1427
- const match = findRouteMatch(trimmedPath, processedTree, true);
1428
- if (match) {
1429
- foundRoute = match.route;
1430
- Object.assign(routeParams, match.rawParams);
1431
- parsedParams = Object.assign(/* @__PURE__ */ Object.create(null), match.parsedParams);
1432
- }
1433
- const matchedRoutes = match?.branch || [routesById[rootRouteId]];
1434
- return { matchedRoutes, routeParams, foundRoute, parsedParams };
1070
+ /**
1071
+ * Build the matched route chain and extract params for a pathname.
1072
+ * Falls back to the root route if no specific route is found.
1073
+ */
1074
+ function getMatchedRoutes({ pathname, routesById, processedTree }) {
1075
+ const routeParams = Object.create(null);
1076
+ const trimmedPath = trimPathRight(pathname);
1077
+ let foundRoute = void 0;
1078
+ let parsedParams = void 0;
1079
+ const match = findRouteMatch(trimmedPath, processedTree, true);
1080
+ if (match) {
1081
+ foundRoute = match.route;
1082
+ Object.assign(routeParams, match.rawParams);
1083
+ parsedParams = Object.assign(Object.create(null), match.parsedParams);
1084
+ }
1085
+ return {
1086
+ matchedRoutes: match?.branch || [routesById["__root__"]],
1087
+ routeParams,
1088
+ foundRoute,
1089
+ parsedParams
1090
+ };
1435
1091
  }
1436
- function applySearchMiddleware({
1437
- search,
1438
- dest,
1439
- destRoutes,
1440
- _includeValidateSearch
1441
- }) {
1442
- const middleware = buildMiddlewareChain(destRoutes);
1443
- return middleware(search, dest, _includeValidateSearch ?? false);
1092
+ /**
1093
+ * TODO: once caches are persisted across requests on the server,
1094
+ * we can cache the built middleware chain using `last(destRoutes)` as the key
1095
+ */
1096
+ function applySearchMiddleware({ search, dest, destRoutes, _includeValidateSearch }) {
1097
+ return buildMiddlewareChain(destRoutes)(search, dest, _includeValidateSearch ?? false);
1444
1098
  }
1445
1099
  function buildMiddlewareChain(destRoutes) {
1446
- const context = {
1447
- dest: null,
1448
- _includeValidateSearch: false,
1449
- middlewares: []
1450
- };
1451
- for (const route of destRoutes) {
1452
- if ("search" in route.options) {
1453
- if (route.options.search?.middlewares) {
1454
- context.middlewares.push(...route.options.search.middlewares);
1455
- }
1456
- } else if (route.options.preSearchFilters || route.options.postSearchFilters) {
1457
- const legacyMiddleware = ({ search, next }) => {
1458
- let nextSearch = search;
1459
- if ("preSearchFilters" in route.options && route.options.preSearchFilters) {
1460
- nextSearch = route.options.preSearchFilters.reduce(
1461
- (prev, next2) => next2(prev),
1462
- search
1463
- );
1464
- }
1465
- const result = next(nextSearch);
1466
- if ("postSearchFilters" in route.options && route.options.postSearchFilters) {
1467
- return route.options.postSearchFilters.reduce(
1468
- (prev, next2) => next2(prev),
1469
- result
1470
- );
1471
- }
1472
- return result;
1473
- };
1474
- context.middlewares.push(legacyMiddleware);
1475
- }
1476
- if (route.options.validateSearch) {
1477
- const validate = ({ search, next }) => {
1478
- const result = next(search);
1479
- if (!context._includeValidateSearch) return result;
1480
- try {
1481
- const validatedSearch = {
1482
- ...result,
1483
- ...validateSearch(route.options.validateSearch, result) ?? void 0
1484
- };
1485
- return validatedSearch;
1486
- } catch {
1487
- return result;
1488
- }
1489
- };
1490
- context.middlewares.push(validate);
1491
- }
1492
- }
1493
- const final = ({ search }) => {
1494
- const dest = context.dest;
1495
- if (!dest.search) {
1496
- return {};
1497
- }
1498
- if (dest.search === true) {
1499
- return search;
1500
- }
1501
- return functionalUpdate(dest.search, search);
1502
- };
1503
- context.middlewares.push(final);
1504
- const applyNext = (index, currentSearch, middlewares) => {
1505
- if (index >= middlewares.length) {
1506
- return currentSearch;
1507
- }
1508
- const middleware = middlewares[index];
1509
- const next = (newSearch) => {
1510
- return applyNext(index + 1, newSearch, middlewares);
1511
- };
1512
- return middleware({ search: currentSearch, next });
1513
- };
1514
- return function middleware(search, dest, _includeValidateSearch) {
1515
- context.dest = dest;
1516
- context._includeValidateSearch = _includeValidateSearch;
1517
- return applyNext(0, search, context.middlewares);
1518
- };
1100
+ const context = {
1101
+ dest: null,
1102
+ _includeValidateSearch: false,
1103
+ middlewares: []
1104
+ };
1105
+ for (const route of destRoutes) {
1106
+ if ("search" in route.options) {
1107
+ if (route.options.search?.middlewares) context.middlewares.push(...route.options.search.middlewares);
1108
+ } else if (route.options.preSearchFilters || route.options.postSearchFilters) {
1109
+ const legacyMiddleware = ({ search, next }) => {
1110
+ let nextSearch = search;
1111
+ if ("preSearchFilters" in route.options && route.options.preSearchFilters) nextSearch = route.options.preSearchFilters.reduce((prev, next) => next(prev), search);
1112
+ const result = next(nextSearch);
1113
+ if ("postSearchFilters" in route.options && route.options.postSearchFilters) return route.options.postSearchFilters.reduce((prev, next) => next(prev), result);
1114
+ return result;
1115
+ };
1116
+ context.middlewares.push(legacyMiddleware);
1117
+ }
1118
+ if (route.options.validateSearch) {
1119
+ const validate = ({ search, next }) => {
1120
+ const result = next(search);
1121
+ if (!context._includeValidateSearch) return result;
1122
+ try {
1123
+ return {
1124
+ ...result,
1125
+ ...validateSearch(route.options.validateSearch, result) ?? void 0
1126
+ };
1127
+ } catch {
1128
+ return result;
1129
+ }
1130
+ };
1131
+ context.middlewares.push(validate);
1132
+ }
1133
+ }
1134
+ const final = ({ search }) => {
1135
+ const dest = context.dest;
1136
+ if (!dest.search) return {};
1137
+ if (dest.search === true) return search;
1138
+ return functionalUpdate(dest.search, search);
1139
+ };
1140
+ context.middlewares.push(final);
1141
+ const applyNext = (index, currentSearch, middlewares) => {
1142
+ if (index >= middlewares.length) return currentSearch;
1143
+ const middleware = middlewares[index];
1144
+ const next = (newSearch) => {
1145
+ return applyNext(index + 1, newSearch, middlewares);
1146
+ };
1147
+ return middleware({
1148
+ search: currentSearch,
1149
+ next
1150
+ });
1151
+ };
1152
+ return function middleware(search, dest, _includeValidateSearch) {
1153
+ context.dest = dest;
1154
+ context._includeValidateSearch = _includeValidateSearch;
1155
+ return applyNext(0, search, context.middlewares);
1156
+ };
1519
1157
  }
1520
1158
  function findGlobalNotFoundRouteId(notFoundMode, routes) {
1521
- if (notFoundMode !== "root") {
1522
- for (let i = routes.length - 1; i >= 0; i--) {
1523
- const route = routes[i];
1524
- if (route.children) {
1525
- return route.id;
1526
- }
1527
- }
1528
- }
1529
- return rootRouteId;
1159
+ if (notFoundMode !== "root") for (let i = routes.length - 1; i >= 0; i--) {
1160
+ const route = routes[i];
1161
+ if (route.children) return route.id;
1162
+ }
1163
+ return rootRouteId;
1530
1164
  }
1531
1165
  function extractStrictParams(route, referenceParams, parsedParams, accumulatedParams) {
1532
- const parseParams = route.options.params?.parse ?? route.options.parseParams;
1533
- if (parseParams) {
1534
- if (route.options.skipRouteOnParseError) {
1535
- for (const key in referenceParams) {
1536
- if (key in parsedParams) {
1537
- accumulatedParams[key] = parsedParams[key];
1538
- }
1539
- }
1540
- } else {
1541
- const result = parseParams(accumulatedParams);
1542
- Object.assign(accumulatedParams, result);
1543
- }
1544
- }
1166
+ const parseParams = route.options.params?.parse ?? route.options.parseParams;
1167
+ if (parseParams) if (route.options.skipRouteOnParseError) {
1168
+ for (const key in referenceParams) if (key in parsedParams) accumulatedParams[key] = parsedParams[key];
1169
+ } else {
1170
+ const result = parseParams(accumulatedParams);
1171
+ Object.assign(accumulatedParams, result);
1172
+ }
1545
1173
  }
1546
- export {
1547
- PathParamError,
1548
- RouterCore,
1549
- SearchParamError,
1550
- defaultSerializeError,
1551
- getInitialRouterState,
1552
- getLocationChangeInfo,
1553
- getMatchedRoutes,
1554
- lazyFn,
1555
- trailingSlashOptions
1556
- };
1557
- //# sourceMappingURL=router.js.map
1174
+ //#endregion
1175
+ export { PathParamError, RouterCore, SearchParamError, defaultSerializeError, getInitialRouterState, getLocationChangeInfo, getMatchedRoutes, lazyFn, trailingSlashOptions };
1176
+
1177
+ //# sourceMappingURL=router.js.map