@tanstack/solid-router 1.111.3 → 1.111.4

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 (118) hide show
  1. package/dist/source/Asset.d.ts +2 -0
  2. package/dist/source/Asset.jsx +22 -0
  3. package/dist/source/Asset.jsx.map +1 -0
  4. package/dist/source/CatchBoundary.d.ts +11 -0
  5. package/dist/source/CatchBoundary.jsx +43 -0
  6. package/dist/source/CatchBoundary.jsx.map +1 -0
  7. package/dist/source/HeadContent.d.ts +8 -0
  8. package/dist/source/HeadContent.jsx +125 -0
  9. package/dist/source/HeadContent.jsx.map +1 -0
  10. package/dist/source/Match.d.ts +8 -0
  11. package/dist/source/Match.jsx +256 -0
  12. package/dist/source/Match.jsx.map +1 -0
  13. package/dist/source/Matches.d.ts +67 -0
  14. package/dist/source/Matches.jsx +103 -0
  15. package/dist/source/Matches.jsx.map +1 -0
  16. package/dist/source/RouterProvider.d.ts +10 -0
  17. package/dist/source/RouterProvider.jsx +27 -0
  18. package/dist/source/RouterProvider.jsx.map +1 -0
  19. package/dist/source/SafeFragment.d.ts +1 -0
  20. package/dist/source/SafeFragment.jsx +4 -0
  21. package/dist/source/SafeFragment.jsx.map +1 -0
  22. package/dist/source/ScriptOnce.d.ts +5 -0
  23. package/dist/source/ScriptOnce.jsx +17 -0
  24. package/dist/source/ScriptOnce.jsx.map +1 -0
  25. package/dist/source/Scripts.d.ts +1 -0
  26. package/dist/source/Scripts.jsx +49 -0
  27. package/dist/source/Scripts.jsx.map +1 -0
  28. package/dist/source/ScrollRestoration.d.ts +15 -0
  29. package/dist/source/ScrollRestoration.jsx +36 -0
  30. package/dist/source/ScrollRestoration.jsx.map +1 -0
  31. package/dist/source/Transitioner.d.ts +1 -0
  32. package/dist/source/Transitioner.jsx +112 -0
  33. package/dist/source/Transitioner.jsx.map +1 -0
  34. package/dist/source/awaited.d.ts +11 -0
  35. package/dist/source/awaited.jsx +24 -0
  36. package/dist/source/awaited.jsx.map +1 -0
  37. package/dist/source/fileRoute.d.ts +46 -0
  38. package/dist/source/fileRoute.js +85 -0
  39. package/dist/source/fileRoute.js.map +1 -0
  40. package/dist/source/history.d.ts +8 -0
  41. package/dist/source/history.js +2 -0
  42. package/dist/source/history.js.map +1 -0
  43. package/dist/source/index.d.ts +52 -0
  44. package/dist/source/index.jsx +41 -0
  45. package/dist/source/index.jsx.map +1 -0
  46. package/dist/source/lazyRouteComponent.d.ts +7 -0
  47. package/dist/source/lazyRouteComponent.jsx +82 -0
  48. package/dist/source/lazyRouteComponent.jsx.map +1 -0
  49. package/dist/source/link.d.ts +41 -0
  50. package/dist/source/link.jsx +339 -0
  51. package/dist/source/link.jsx.map +1 -0
  52. package/dist/source/matchContext.d.ts +3 -0
  53. package/dist/source/matchContext.jsx +5 -0
  54. package/dist/source/matchContext.jsx.map +1 -0
  55. package/dist/source/not-found.d.ts +27 -0
  56. package/dist/source/not-found.jsx +39 -0
  57. package/dist/source/not-found.jsx.map +1 -0
  58. package/dist/source/redirects.d.ts +19 -0
  59. package/dist/source/redirects.js +25 -0
  60. package/dist/source/redirects.js.map +1 -0
  61. package/dist/source/renderRouteNotFound.d.ts +3 -0
  62. package/dist/source/renderRouteNotFound.jsx +15 -0
  63. package/dist/source/renderRouteNotFound.jsx.map +1 -0
  64. package/dist/source/route.d.ts +257 -0
  65. package/dist/source/route.js +240 -0
  66. package/dist/source/route.js.map +1 -0
  67. package/dist/source/router.d.ts +550 -0
  68. package/dist/source/router.js +1802 -0
  69. package/dist/source/router.js.map +1 -0
  70. package/dist/source/routerContext.d.ts +8 -0
  71. package/dist/source/routerContext.jsx +13 -0
  72. package/dist/source/routerContext.jsx.map +1 -0
  73. package/dist/source/scroll-restoration.d.ts +29 -0
  74. package/dist/source/scroll-restoration.jsx +244 -0
  75. package/dist/source/scroll-restoration.jsx.map +1 -0
  76. package/dist/source/typePrimitives.d.ts +70 -0
  77. package/dist/source/typePrimitives.js +2 -0
  78. package/dist/source/typePrimitives.js.map +1 -0
  79. package/dist/source/useBlocker.d.ts +68 -0
  80. package/dist/source/useBlocker.jsx +136 -0
  81. package/dist/source/useBlocker.jsx.map +1 -0
  82. package/dist/source/useCanGoBack.d.ts +1 -0
  83. package/dist/source/useCanGoBack.js +5 -0
  84. package/dist/source/useCanGoBack.js.map +1 -0
  85. package/dist/source/useLoaderData.d.ts +12 -0
  86. package/dist/source/useLoaderData.jsx +11 -0
  87. package/dist/source/useLoaderData.jsx.map +1 -0
  88. package/dist/source/useLoaderDeps.d.ts +11 -0
  89. package/dist/source/useLoaderDeps.jsx +11 -0
  90. package/dist/source/useLoaderDeps.jsx.map +1 -0
  91. package/dist/source/useLocation.d.ts +7 -0
  92. package/dist/source/useLocation.jsx +7 -0
  93. package/dist/source/useLocation.jsx.map +1 -0
  94. package/dist/source/useMatch.d.ts +14 -0
  95. package/dist/source/useMatch.jsx +19 -0
  96. package/dist/source/useMatch.jsx.map +1 -0
  97. package/dist/source/useNavigate.d.ts +7 -0
  98. package/dist/source/useNavigate.jsx +32 -0
  99. package/dist/source/useNavigate.jsx.map +1 -0
  100. package/dist/source/useParams.d.ts +14 -0
  101. package/dist/source/useParams.jsx +12 -0
  102. package/dist/source/useParams.jsx.map +1 -0
  103. package/dist/source/useRouteContext.d.ts +12 -0
  104. package/dist/source/useRouteContext.js +8 -0
  105. package/dist/source/useRouteContext.js.map +1 -0
  106. package/dist/source/useRouter.d.ts +4 -0
  107. package/dist/source/useRouter.jsx +9 -0
  108. package/dist/source/useRouter.jsx.map +1 -0
  109. package/dist/source/useRouterState.d.ts +8 -0
  110. package/dist/source/useRouterState.jsx +14 -0
  111. package/dist/source/useRouterState.jsx.map +1 -0
  112. package/dist/source/useSearch.d.ts +14 -0
  113. package/dist/source/useSearch.jsx +12 -0
  114. package/dist/source/useSearch.jsx.map +1 -0
  115. package/dist/source/utils.d.ts +43 -0
  116. package/dist/source/utils.js +59 -0
  117. package/dist/source/utils.js.map +1 -0
  118. package/package.json +5 -1
@@ -0,0 +1,1802 @@
1
+ import { createBrowserHistory, createMemoryHistory, parseHref, } from '@tanstack/history';
2
+ import { Store, batch } from '@tanstack/solid-store';
3
+ import invariant from 'tiny-invariant';
4
+ import { cleanPath, createControlledPromise, deepEqual, defaultParseSearch, defaultStringifySearch, functionalUpdate, getLocationChangeInfo, interpolatePath, joinPaths, last, matchPathname, parsePathname, pick, replaceEqualDeep, resolvePath, rootRouteId, trimPath, trimPathLeft, trimPathRight, } from '@tanstack/router-core';
5
+ import { isRedirect, isResolvedRedirect } from './redirects';
6
+ import { isNotFound } from './not-found';
7
+ import { setupScrollRestoration } from './scroll-restoration';
8
+ export const componentTypes = [
9
+ 'component',
10
+ 'errorComponent',
11
+ 'pendingComponent',
12
+ 'notFoundComponent',
13
+ ];
14
+ function routeNeedsPreload(route) {
15
+ for (const componentType of componentTypes) {
16
+ if (route.options[componentType]?.preload) {
17
+ return true;
18
+ }
19
+ }
20
+ return false;
21
+ }
22
+ function validateSearch(validateSearch, input) {
23
+ if (validateSearch == null)
24
+ return {};
25
+ if ('~standard' in validateSearch) {
26
+ const result = validateSearch['~standard'].validate(input);
27
+ if (result instanceof Promise)
28
+ throw new SearchParamError('Async validation not supported');
29
+ if (result.issues)
30
+ throw new SearchParamError(JSON.stringify(result.issues, undefined, 2), {
31
+ cause: result,
32
+ });
33
+ return result.value;
34
+ }
35
+ if ('parse' in validateSearch) {
36
+ return validateSearch.parse(input);
37
+ }
38
+ if (typeof validateSearch === 'function') {
39
+ return validateSearch(input);
40
+ }
41
+ return {};
42
+ }
43
+ export function createRouter(options) {
44
+ return new Router(options);
45
+ }
46
+ export class Router {
47
+ /**
48
+ * @deprecated Use the `createRouter` function instead
49
+ */
50
+ constructor(options) {
51
+ // Option-independent properties
52
+ this.tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
53
+ this.resetNextScroll = true;
54
+ this.shouldViewTransition = undefined;
55
+ this.isViewTransitionTypesSupported = undefined;
56
+ this.subscribers = new Set();
57
+ this.isScrollRestoring = false;
58
+ this.isScrollRestorationSetup = false;
59
+ // These are default implementations that can optionally be overridden
60
+ // by the router provider once rendered. We provide these so that the
61
+ // router can be used in a non-react environment if necessary
62
+ this.startSolidTransition = (fn) => fn();
63
+ this.update = (newOptions) => {
64
+ if (newOptions.notFoundRoute) {
65
+ console.warn('The notFoundRoute API is deprecated and will be removed in the next major version. See https://tanstack.com/router/v1/docs/guide/not-found-errors#migrating-from-notfoundroute for more info.');
66
+ }
67
+ const previousOptions = this.options;
68
+ this.options = {
69
+ ...this.options,
70
+ ...newOptions,
71
+ };
72
+ this.isServer = this.options.isServer ?? typeof document === 'undefined';
73
+ this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters
74
+ ? new Map(this.options.pathParamsAllowedCharacters.map((char) => [
75
+ encodeURIComponent(char),
76
+ char,
77
+ ]))
78
+ : undefined;
79
+ if (!this.basepath ||
80
+ (newOptions.basepath && newOptions.basepath !== previousOptions.basepath)) {
81
+ if (newOptions.basepath === undefined ||
82
+ newOptions.basepath === '' ||
83
+ newOptions.basepath === '/') {
84
+ this.basepath = '/';
85
+ }
86
+ else {
87
+ this.basepath = `/${trimPath(newOptions.basepath)}`;
88
+ }
89
+ }
90
+ if (!this.history ||
91
+ (this.options.history && this.options.history !== this.history)) {
92
+ this.history =
93
+ this.options.history ??
94
+ (this.isServer
95
+ ? createMemoryHistory({
96
+ initialEntries: [this.basepath || '/'],
97
+ })
98
+ : createBrowserHistory());
99
+ this.latestLocation = this.parseLocation();
100
+ }
101
+ if (this.options.routeTree !== this.routeTree) {
102
+ this.routeTree = this.options.routeTree;
103
+ this.buildRouteTree();
104
+ }
105
+ if (!this.__store) {
106
+ this.__store = new Store(getInitialRouterState(this.latestLocation), {
107
+ onUpdate: () => {
108
+ this.__store.state = {
109
+ ...this.state,
110
+ cachedMatches: this.state.cachedMatches.filter((d) => !['redirected'].includes(d.status)),
111
+ };
112
+ },
113
+ });
114
+ setupScrollRestoration(this);
115
+ }
116
+ if (typeof window !== 'undefined' &&
117
+ 'CSS' in window &&
118
+ typeof window.CSS?.supports === 'function') {
119
+ this.isViewTransitionTypesSupported = window.CSS.supports('selector(:active-view-transition-type(a)');
120
+ }
121
+ };
122
+ this.buildRouteTree = () => {
123
+ this.routesById = {};
124
+ this.routesByPath = {};
125
+ const notFoundRoute = this.options.notFoundRoute;
126
+ if (notFoundRoute) {
127
+ notFoundRoute.init({
128
+ originalIndex: 99999999999,
129
+ defaultSsr: this.options.defaultSsr,
130
+ });
131
+ this.routesById[notFoundRoute.id] = notFoundRoute;
132
+ }
133
+ const recurseRoutes = (childRoutes) => {
134
+ childRoutes.forEach((childRoute, i) => {
135
+ childRoute.init({
136
+ originalIndex: i,
137
+ defaultSsr: this.options.defaultSsr,
138
+ });
139
+ const existingRoute = this.routesById[childRoute.id];
140
+ invariant(!existingRoute, `Duplicate routes found with id: ${String(childRoute.id)}`);
141
+ this.routesById[childRoute.id] = childRoute;
142
+ if (!childRoute.isRoot && childRoute.path) {
143
+ const trimmedFullPath = trimPathRight(childRoute.fullPath);
144
+ if (!this.routesByPath[trimmedFullPath] ||
145
+ childRoute.fullPath.endsWith('/')) {
146
+ ;
147
+ this.routesByPath[trimmedFullPath] = childRoute;
148
+ }
149
+ }
150
+ const children = childRoute.children;
151
+ if (children?.length) {
152
+ recurseRoutes(children);
153
+ }
154
+ });
155
+ };
156
+ recurseRoutes([this.routeTree]);
157
+ const scoredRoutes = [];
158
+ const routes = Object.values(this.routesById);
159
+ routes.forEach((d, i) => {
160
+ if (d.isRoot || !d.path) {
161
+ return;
162
+ }
163
+ const trimmed = trimPathLeft(d.fullPath);
164
+ const parsed = parsePathname(trimmed);
165
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
166
+ parsed.shift();
167
+ }
168
+ const scores = parsed.map((segment) => {
169
+ if (segment.value === '/') {
170
+ return 0.75;
171
+ }
172
+ if (segment.type === 'param') {
173
+ return 0.5;
174
+ }
175
+ if (segment.type === 'wildcard') {
176
+ return 0.25;
177
+ }
178
+ return 1;
179
+ });
180
+ scoredRoutes.push({ child: d, trimmed, parsed, index: i, scores });
181
+ });
182
+ this.flatRoutes = scoredRoutes
183
+ .sort((a, b) => {
184
+ const minLength = Math.min(a.scores.length, b.scores.length);
185
+ // Sort by min available score
186
+ for (let i = 0; i < minLength; i++) {
187
+ if (a.scores[i] !== b.scores[i]) {
188
+ return b.scores[i] - a.scores[i];
189
+ }
190
+ }
191
+ // Sort by length of score
192
+ if (a.scores.length !== b.scores.length) {
193
+ return b.scores.length - a.scores.length;
194
+ }
195
+ // Sort by min available parsed value
196
+ for (let i = 0; i < minLength; i++) {
197
+ if (a.parsed[i].value !== b.parsed[i].value) {
198
+ return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
199
+ }
200
+ }
201
+ // Sort by original index
202
+ return a.index - b.index;
203
+ })
204
+ .map((d, i) => {
205
+ d.child.rank = i;
206
+ return d.child;
207
+ });
208
+ };
209
+ this.subscribe = (eventType, fn) => {
210
+ const listener = {
211
+ eventType,
212
+ fn,
213
+ };
214
+ this.subscribers.add(listener);
215
+ return () => {
216
+ this.subscribers.delete(listener);
217
+ };
218
+ };
219
+ this.emit = (routerEvent) => {
220
+ this.subscribers.forEach((listener) => {
221
+ if (listener.eventType === routerEvent.type) {
222
+ listener.fn(routerEvent);
223
+ }
224
+ });
225
+ };
226
+ this.parseLocation = (previousLocation, locationToParse) => {
227
+ const parse = ({ pathname, search, hash, state, }) => {
228
+ const parsedSearch = this.options.parseSearch(search);
229
+ const searchStr = this.options.stringifySearch(parsedSearch);
230
+ return {
231
+ pathname,
232
+ searchStr,
233
+ search: replaceEqualDeep(previousLocation?.search, parsedSearch),
234
+ hash: hash.split('#').reverse()[0] ?? '',
235
+ href: `${pathname}${searchStr}${hash}`,
236
+ state: replaceEqualDeep(previousLocation?.state, state),
237
+ };
238
+ };
239
+ const location = parse(locationToParse ?? this.history.location);
240
+ const { __tempLocation, __tempKey } = location.state;
241
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
242
+ // Sync up the location keys
243
+ const parsedTempLocation = parse(__tempLocation);
244
+ parsedTempLocation.state.key = location.state.key;
245
+ delete parsedTempLocation.state.__tempLocation;
246
+ return {
247
+ ...parsedTempLocation,
248
+ maskedLocation: location,
249
+ };
250
+ }
251
+ return location;
252
+ };
253
+ this.resolvePathWithBase = (from, path) => {
254
+ const resolvedPath = resolvePath({
255
+ basepath: this.basepath,
256
+ base: from,
257
+ to: cleanPath(path),
258
+ trailingSlash: this.options.trailingSlash,
259
+ caseSensitive: this.options.caseSensitive,
260
+ });
261
+ return resolvedPath;
262
+ };
263
+ this.getMatchedRoutes = (next, dest) => {
264
+ let routeParams = {};
265
+ const trimmedPath = trimPathRight(next.pathname);
266
+ const getMatchedParams = (route) => {
267
+ const result = matchPathname(this.basepath, trimmedPath, {
268
+ to: route.fullPath,
269
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive,
270
+ fuzzy: true,
271
+ });
272
+ return result;
273
+ };
274
+ let foundRoute = dest?.to !== undefined ? this.routesByPath[dest.to] : undefined;
275
+ if (foundRoute) {
276
+ routeParams = getMatchedParams(foundRoute);
277
+ }
278
+ else {
279
+ foundRoute = this.flatRoutes.find((route) => {
280
+ const matchedParams = getMatchedParams(route);
281
+ if (matchedParams) {
282
+ routeParams = matchedParams;
283
+ return true;
284
+ }
285
+ return false;
286
+ });
287
+ }
288
+ let routeCursor = foundRoute || this.routesById[rootRouteId];
289
+ const matchedRoutes = [routeCursor];
290
+ while (routeCursor.parentRoute) {
291
+ routeCursor = routeCursor.parentRoute;
292
+ matchedRoutes.unshift(routeCursor);
293
+ }
294
+ return { matchedRoutes, routeParams, foundRoute };
295
+ };
296
+ this.cancelMatch = (id) => {
297
+ const match = this.getMatch(id);
298
+ if (!match)
299
+ return;
300
+ match.abortController.abort();
301
+ clearTimeout(match.pendingTimeout);
302
+ };
303
+ this.cancelMatches = () => {
304
+ this.state.pendingMatches?.forEach((match) => {
305
+ this.cancelMatch(match.id);
306
+ });
307
+ };
308
+ this.buildLocation = (opts) => {
309
+ const build = (dest = {}, matchedRoutesResult) => {
310
+ const fromMatches = dest._fromLocation
311
+ ? this.matchRoutes(dest._fromLocation, { _buildLocation: true })
312
+ : this.state.matches;
313
+ const fromMatch = dest.from != null
314
+ ? fromMatches.find((d) => matchPathname(this.basepath, trimPathRight(d.pathname), {
315
+ to: dest.from,
316
+ caseSensitive: false,
317
+ fuzzy: false,
318
+ }))
319
+ : undefined;
320
+ const fromPath = fromMatch?.pathname || this.latestLocation.pathname;
321
+ invariant(dest.from == null || fromMatch != null, 'Could not find match for from: ' + dest.from);
322
+ const fromSearch = this.state.pendingMatches?.length
323
+ ? last(this.state.pendingMatches)?.search
324
+ : last(fromMatches)?.search || this.latestLocation.search;
325
+ const stayingMatches = matchedRoutesResult?.matchedRoutes.filter((d) => fromMatches.find((e) => e.routeId === d.id));
326
+ let pathname;
327
+ if (dest.to) {
328
+ const resolvePathTo = fromMatch?.fullPath || this.latestLocation.pathname;
329
+ pathname = this.resolvePathWithBase(resolvePathTo, `${dest.to}`);
330
+ }
331
+ else {
332
+ const fromRouteByFromPathRouteId = this.routesById[stayingMatches?.find((route) => {
333
+ const interpolatedPath = interpolatePath({
334
+ path: route.fullPath,
335
+ params: matchedRoutesResult?.routeParams ?? {},
336
+ decodeCharMap: this.pathParamsDecodeCharMap,
337
+ }).interpolatedPath;
338
+ const pathname = joinPaths([this.basepath, interpolatedPath]);
339
+ return pathname === fromPath;
340
+ })?.id];
341
+ pathname = this.resolvePathWithBase(fromPath, fromRouteByFromPathRouteId?.to ?? fromPath);
342
+ }
343
+ const prevParams = { ...last(fromMatches)?.params };
344
+ let nextParams = (dest.params ?? true) === true
345
+ ? prevParams
346
+ : {
347
+ ...prevParams,
348
+ ...functionalUpdate(dest.params, prevParams),
349
+ };
350
+ if (Object.keys(nextParams).length > 0) {
351
+ matchedRoutesResult?.matchedRoutes
352
+ .map((route) => {
353
+ return (route.options.params?.stringify ?? route.options.stringifyParams);
354
+ })
355
+ .filter(Boolean)
356
+ .forEach((fn) => {
357
+ nextParams = { ...nextParams, ...fn(nextParams) };
358
+ });
359
+ }
360
+ pathname = interpolatePath({
361
+ path: pathname,
362
+ params: nextParams ?? {},
363
+ leaveWildcards: false,
364
+ leaveParams: opts.leaveParams,
365
+ decodeCharMap: this.pathParamsDecodeCharMap,
366
+ }).interpolatedPath;
367
+ let search = fromSearch;
368
+ if (opts._includeValidateSearch && this.options.search?.strict) {
369
+ let validatedSearch = {};
370
+ matchedRoutesResult?.matchedRoutes.forEach((route) => {
371
+ try {
372
+ if (route.options.validateSearch) {
373
+ validatedSearch = {
374
+ ...validatedSearch,
375
+ ...(validateSearch(route.options.validateSearch, {
376
+ ...validatedSearch,
377
+ ...search,
378
+ }) ?? {}),
379
+ };
380
+ }
381
+ }
382
+ catch {
383
+ // ignore errors here because they are already handled in matchRoutes
384
+ }
385
+ });
386
+ search = validatedSearch;
387
+ }
388
+ const applyMiddlewares = (search) => {
389
+ const allMiddlewares = matchedRoutesResult?.matchedRoutes.reduce((acc, route) => {
390
+ const middlewares = [];
391
+ if ('search' in route.options) {
392
+ if (route.options.search?.middlewares) {
393
+ middlewares.push(...route.options.search.middlewares);
394
+ }
395
+ }
396
+ // TODO remove preSearchFilters and postSearchFilters in v2
397
+ else if (route.options.preSearchFilters ||
398
+ route.options.postSearchFilters) {
399
+ const legacyMiddleware = ({ search, next, }) => {
400
+ let nextSearch = search;
401
+ if ('preSearchFilters' in route.options &&
402
+ route.options.preSearchFilters) {
403
+ nextSearch = route.options.preSearchFilters.reduce((prev, next) => next(prev), search);
404
+ }
405
+ const result = next(nextSearch);
406
+ if ('postSearchFilters' in route.options &&
407
+ route.options.postSearchFilters) {
408
+ return route.options.postSearchFilters.reduce((prev, next) => next(prev), result);
409
+ }
410
+ return result;
411
+ };
412
+ middlewares.push(legacyMiddleware);
413
+ }
414
+ if (opts._includeValidateSearch && route.options.validateSearch) {
415
+ const validate = ({ search, next }) => {
416
+ const result = next(search);
417
+ try {
418
+ const validatedSearch = {
419
+ ...result,
420
+ ...(validateSearch(route.options.validateSearch, result) ?? {}),
421
+ };
422
+ return validatedSearch;
423
+ }
424
+ catch {
425
+ // ignore errors here because they are already handled in matchRoutes
426
+ return result;
427
+ }
428
+ };
429
+ middlewares.push(validate);
430
+ }
431
+ return acc.concat(middlewares);
432
+ }, []) ?? [];
433
+ // the chain ends here since `next` is not called
434
+ const final = ({ search }) => {
435
+ if (!dest.search) {
436
+ return {};
437
+ }
438
+ if (dest.search === true) {
439
+ return search;
440
+ }
441
+ return functionalUpdate(dest.search, search);
442
+ };
443
+ allMiddlewares.push(final);
444
+ const applyNext = (index, currentSearch) => {
445
+ // no more middlewares left, return the current search
446
+ if (index >= allMiddlewares.length) {
447
+ return currentSearch;
448
+ }
449
+ const middleware = allMiddlewares[index];
450
+ const next = (newSearch) => {
451
+ return applyNext(index + 1, newSearch);
452
+ };
453
+ return middleware({ search: currentSearch, next });
454
+ };
455
+ // Start applying middlewares
456
+ return applyNext(0, search);
457
+ };
458
+ search = applyMiddlewares(search);
459
+ search = replaceEqualDeep(fromSearch, search);
460
+ const searchStr = this.options.stringifySearch(search);
461
+ const hash = dest.hash === true
462
+ ? this.latestLocation.hash
463
+ : dest.hash
464
+ ? functionalUpdate(dest.hash, this.latestLocation.hash)
465
+ : undefined;
466
+ const hashStr = hash ? `#${hash}` : '';
467
+ let nextState = dest.state === true
468
+ ? this.latestLocation.state
469
+ : dest.state
470
+ ? functionalUpdate(dest.state, this.latestLocation.state)
471
+ : {};
472
+ nextState = replaceEqualDeep(this.latestLocation.state, nextState);
473
+ return {
474
+ pathname,
475
+ search,
476
+ searchStr,
477
+ state: nextState,
478
+ hash: hash ?? '',
479
+ href: `${pathname}${searchStr}${hashStr}`,
480
+ unmaskOnReload: dest.unmaskOnReload,
481
+ };
482
+ };
483
+ const buildWithMatches = (dest = {}, maskedDest) => {
484
+ const next = build(dest);
485
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
486
+ if (!maskedNext) {
487
+ let params = {};
488
+ const foundMask = this.options.routeMasks?.find((d) => {
489
+ const match = matchPathname(this.basepath, next.pathname, {
490
+ to: d.from,
491
+ caseSensitive: false,
492
+ fuzzy: false,
493
+ });
494
+ if (match) {
495
+ params = match;
496
+ return true;
497
+ }
498
+ return false;
499
+ });
500
+ if (foundMask) {
501
+ const { from: _from, ...maskProps } = foundMask;
502
+ maskedDest = {
503
+ ...pick(opts, ['from']),
504
+ ...maskProps,
505
+ params,
506
+ };
507
+ maskedNext = build(maskedDest);
508
+ }
509
+ }
510
+ const nextMatches = this.getMatchedRoutes(next, dest);
511
+ const final = build(dest, nextMatches);
512
+ if (maskedNext) {
513
+ const maskedMatches = this.getMatchedRoutes(maskedNext, maskedDest);
514
+ const maskedFinal = build(maskedDest, maskedMatches);
515
+ final.maskedLocation = maskedFinal;
516
+ }
517
+ return final;
518
+ };
519
+ if (opts.mask) {
520
+ return buildWithMatches(opts, {
521
+ ...pick(opts, ['from']),
522
+ ...opts.mask,
523
+ });
524
+ }
525
+ return buildWithMatches(opts);
526
+ };
527
+ this.commitLocation = ({ viewTransition, ignoreBlocker, ...next }) => {
528
+ const isSameState = () => {
529
+ // the following props are ignored but may still be provided when navigating,
530
+ // temporarily add the previous values to the next state so they don't affect
531
+ // the comparison
532
+ const ignoredProps = [
533
+ 'key',
534
+ '__TSR_index',
535
+ '__hashScrollIntoViewOptions',
536
+ ];
537
+ ignoredProps.forEach((prop) => {
538
+ ;
539
+ next.state[prop] = this.latestLocation.state[prop];
540
+ });
541
+ const isEqual = deepEqual(next.state, this.latestLocation.state);
542
+ ignoredProps.forEach((prop) => {
543
+ delete next.state[prop];
544
+ });
545
+ return isEqual;
546
+ };
547
+ const isSameUrl = this.latestLocation.href === next.href;
548
+ const previousCommitPromise = this.commitLocationPromise;
549
+ this.commitLocationPromise = createControlledPromise(() => {
550
+ previousCommitPromise?.resolve();
551
+ });
552
+ // Don't commit to history if nothing changed
553
+ if (isSameUrl && isSameState()) {
554
+ this.load();
555
+ }
556
+ else {
557
+ // eslint-disable-next-line prefer-const
558
+ let { maskedLocation, hashScrollIntoView, ...nextHistory } = next;
559
+ if (maskedLocation) {
560
+ nextHistory = {
561
+ ...maskedLocation,
562
+ state: {
563
+ ...maskedLocation.state,
564
+ __tempKey: undefined,
565
+ __tempLocation: {
566
+ ...nextHistory,
567
+ search: nextHistory.searchStr,
568
+ state: {
569
+ ...nextHistory.state,
570
+ __tempKey: undefined,
571
+ __tempLocation: undefined,
572
+ key: undefined,
573
+ },
574
+ },
575
+ },
576
+ };
577
+ if (nextHistory.unmaskOnReload ??
578
+ this.options.unmaskOnReload ??
579
+ false) {
580
+ nextHistory.state.__tempKey = this.tempLocationKey;
581
+ }
582
+ }
583
+ nextHistory.state.__hashScrollIntoViewOptions =
584
+ hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
585
+ this.shouldViewTransition = viewTransition;
586
+ this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state, { ignoreBlocker });
587
+ }
588
+ this.resetNextScroll = next.resetScroll ?? true;
589
+ if (!this.history.subscribers.size) {
590
+ this.load();
591
+ }
592
+ return this.commitLocationPromise;
593
+ };
594
+ this.buildAndCommitLocation = ({ replace, resetScroll, hashScrollIntoView, viewTransition, ignoreBlocker, href, ...rest } = {}) => {
595
+ if (href) {
596
+ const currentIndex = this.history.location.state.__TSR_index;
597
+ const parsed = parseHref(href, {
598
+ __TSR_index: replace ? currentIndex : currentIndex + 1,
599
+ });
600
+ rest.to = parsed.pathname;
601
+ rest.search = this.options.parseSearch(parsed.search);
602
+ // remove the leading `#` from the hash
603
+ rest.hash = parsed.hash.slice(1);
604
+ }
605
+ const location = this.buildLocation({
606
+ ...rest,
607
+ _includeValidateSearch: true,
608
+ });
609
+ return this.commitLocation({
610
+ ...location,
611
+ viewTransition,
612
+ replace,
613
+ resetScroll,
614
+ hashScrollIntoView,
615
+ ignoreBlocker,
616
+ });
617
+ };
618
+ this.navigate = ({ to, reloadDocument, href, ...rest }) => {
619
+ if (reloadDocument) {
620
+ if (!href) {
621
+ const location = this.buildLocation({ to, ...rest });
622
+ href = this.history.createHref(location.href);
623
+ }
624
+ if (rest.replace) {
625
+ window.location.replace(href);
626
+ }
627
+ else {
628
+ window.location.href = href;
629
+ }
630
+ return;
631
+ }
632
+ return this.buildAndCommitLocation({
633
+ ...rest,
634
+ href,
635
+ to: to,
636
+ });
637
+ };
638
+ this.load = async (opts) => {
639
+ this.latestLocation = this.parseLocation(this.latestLocation);
640
+ let redirect;
641
+ let notFound;
642
+ let loadPromise;
643
+ // eslint-disable-next-line prefer-const
644
+ loadPromise = new Promise((resolve) => {
645
+ this.startSolidTransition(async () => {
646
+ try {
647
+ const next = this.latestLocation;
648
+ const prevLocation = this.state.resolvedLocation;
649
+ // Cancel any pending matches
650
+ this.cancelMatches();
651
+ let pendingMatches;
652
+ batch(() => {
653
+ // this call breaks a route context of destination route after a redirect
654
+ // we should be fine not eagerly calling this since we call it later
655
+ // this.clearExpiredCache()
656
+ // Match the routes
657
+ pendingMatches = this.matchRoutes(next);
658
+ // Ingest the new matches
659
+ this.__store.setState((s) => ({
660
+ ...s,
661
+ status: 'pending',
662
+ isLoading: true,
663
+ location: next,
664
+ pendingMatches,
665
+ // If a cached moved to pendingMatches, remove it from cachedMatches
666
+ cachedMatches: s.cachedMatches.filter((d) => {
667
+ return !pendingMatches.find((e) => e.id === d.id);
668
+ }),
669
+ }));
670
+ });
671
+ if (!this.state.redirect) {
672
+ this.emit({
673
+ type: 'onBeforeNavigate',
674
+ ...getLocationChangeInfo({
675
+ resolvedLocation: prevLocation,
676
+ location: next,
677
+ }),
678
+ });
679
+ }
680
+ this.emit({
681
+ type: 'onBeforeLoad',
682
+ ...getLocationChangeInfo({
683
+ resolvedLocation: prevLocation,
684
+ location: next,
685
+ }),
686
+ });
687
+ await this.loadMatches({
688
+ sync: opts?.sync,
689
+ matches: pendingMatches,
690
+ location: next,
691
+ // eslint-disable-next-line @typescript-eslint/require-await
692
+ onReady: async () => {
693
+ // eslint-disable-next-line @typescript-eslint/require-await
694
+ this.startViewTransition(async () => {
695
+ // this.viewTransitionPromise = createControlledPromise<true>()
696
+ // Commit the pending matches. If a previous match was
697
+ // removed, place it in the cachedMatches
698
+ let exitingMatches;
699
+ let enteringMatches;
700
+ let stayingMatches;
701
+ batch(() => {
702
+ this.__store.setState((s) => {
703
+ const previousMatches = s.matches;
704
+ const newMatches = s.pendingMatches || s.matches;
705
+ exitingMatches = previousMatches.filter((match) => !newMatches.find((d) => d.id === match.id));
706
+ enteringMatches = newMatches.filter((match) => !previousMatches.find((d) => d.id === match.id));
707
+ stayingMatches = previousMatches.filter((match) => newMatches.find((d) => d.id === match.id));
708
+ return {
709
+ ...s,
710
+ isLoading: false,
711
+ loadedAt: Date.now(),
712
+ matches: newMatches,
713
+ pendingMatches: undefined,
714
+ cachedMatches: [
715
+ ...s.cachedMatches,
716
+ ...exitingMatches.filter((d) => d.status !== 'error'),
717
+ ],
718
+ };
719
+ });
720
+ this.clearExpiredCache();
721
+ });
722
+ [
723
+ [exitingMatches, 'onLeave'],
724
+ [enteringMatches, 'onEnter'],
725
+ [stayingMatches, 'onStay'],
726
+ ].forEach(([matches, hook]) => {
727
+ matches.forEach((match) => {
728
+ this.looseRoutesById[match.routeId].options[hook]?.(match);
729
+ });
730
+ });
731
+ });
732
+ },
733
+ });
734
+ }
735
+ catch (err) {
736
+ if (isResolvedRedirect(err)) {
737
+ redirect = err;
738
+ if (!this.isServer) {
739
+ this.navigate({
740
+ ...redirect,
741
+ replace: true,
742
+ ignoreBlocker: true,
743
+ });
744
+ }
745
+ }
746
+ else if (isNotFound(err)) {
747
+ notFound = err;
748
+ }
749
+ this.__store.setState((s) => ({
750
+ ...s,
751
+ statusCode: redirect
752
+ ? redirect.statusCode
753
+ : notFound
754
+ ? 404
755
+ : s.matches.some((d) => d.status === 'error')
756
+ ? 500
757
+ : 200,
758
+ redirect,
759
+ }));
760
+ }
761
+ if (this.latestLoadPromise === loadPromise) {
762
+ this.commitLocationPromise?.resolve();
763
+ this.latestLoadPromise = undefined;
764
+ this.commitLocationPromise = undefined;
765
+ }
766
+ resolve();
767
+ });
768
+ });
769
+ this.latestLoadPromise = loadPromise;
770
+ await loadPromise;
771
+ while (this.latestLoadPromise &&
772
+ loadPromise !== this.latestLoadPromise) {
773
+ await this.latestLoadPromise;
774
+ }
775
+ if (this.hasNotFoundMatch()) {
776
+ this.__store.setState((s) => ({
777
+ ...s,
778
+ statusCode: 404,
779
+ }));
780
+ }
781
+ };
782
+ this.startViewTransition = (fn) => {
783
+ // Determine if we should start a view transition from the navigation
784
+ // or from the router default
785
+ const shouldViewTransition = this.shouldViewTransition ?? this.options.defaultViewTransition;
786
+ // Reset the view transition flag
787
+ delete this.shouldViewTransition;
788
+ // Attempt to start a view transition (or just apply the changes if we can't)
789
+ if (shouldViewTransition &&
790
+ typeof document !== 'undefined' &&
791
+ 'startViewTransition' in document &&
792
+ typeof document.startViewTransition === 'function') {
793
+ // lib.dom.ts doesn't support viewTransition types variant yet.
794
+ // TODO: Fix this when dom types are updated
795
+ let startViewTransitionParams;
796
+ if (typeof shouldViewTransition === 'object' &&
797
+ this.isViewTransitionTypesSupported) {
798
+ startViewTransitionParams = {
799
+ update: fn,
800
+ types: shouldViewTransition.types,
801
+ };
802
+ }
803
+ else {
804
+ startViewTransitionParams = fn;
805
+ }
806
+ document.startViewTransition(startViewTransitionParams);
807
+ }
808
+ else {
809
+ fn();
810
+ }
811
+ };
812
+ this.updateMatch = (id, updater) => {
813
+ let updated;
814
+ const isPending = this.state.pendingMatches?.find((d) => d.id === id);
815
+ const isMatched = this.state.matches.find((d) => d.id === id);
816
+ const isCached = this.state.cachedMatches.find((d) => d.id === id);
817
+ const matchesKey = isPending
818
+ ? 'pendingMatches'
819
+ : isMatched
820
+ ? 'matches'
821
+ : isCached
822
+ ? 'cachedMatches'
823
+ : '';
824
+ if (matchesKey) {
825
+ this.__store.setState((s) => ({
826
+ ...s,
827
+ [matchesKey]: s[matchesKey]?.map((d) => d.id === id ? (updated = updater(d)) : d),
828
+ }));
829
+ }
830
+ return updated;
831
+ };
832
+ this.getMatch = (matchId) => {
833
+ return [
834
+ ...this.state.cachedMatches,
835
+ ...(this.state.pendingMatches ?? []),
836
+ ...this.state.matches,
837
+ ].find((d) => d.id === matchId);
838
+ };
839
+ this.loadMatches = async ({ location, matches, preload: allPreload, onReady, updateMatch = this.updateMatch, sync, }) => {
840
+ let firstBadMatchIndex;
841
+ let rendered = false;
842
+ const triggerOnReady = async () => {
843
+ if (!rendered) {
844
+ rendered = true;
845
+ await onReady?.();
846
+ }
847
+ };
848
+ const resolvePreload = (matchId) => {
849
+ return !!(allPreload && !this.state.matches.find((d) => d.id === matchId));
850
+ };
851
+ if (!this.isServer && !this.state.matches.length) {
852
+ triggerOnReady();
853
+ }
854
+ const handleRedirectAndNotFound = (match, err) => {
855
+ if (isResolvedRedirect(err)) {
856
+ if (!err.reloadDocument) {
857
+ throw err;
858
+ }
859
+ }
860
+ if (isRedirect(err) || isNotFound(err)) {
861
+ updateMatch(match.id, (prev) => ({
862
+ ...prev,
863
+ status: isRedirect(err)
864
+ ? 'redirected'
865
+ : isNotFound(err)
866
+ ? 'notFound'
867
+ : 'error',
868
+ isFetching: false,
869
+ error: err,
870
+ beforeLoadPromise: undefined,
871
+ loaderPromise: undefined,
872
+ }));
873
+ if (!err.routeId) {
874
+ ;
875
+ err.routeId = match.routeId;
876
+ }
877
+ match.beforeLoadPromise?.resolve();
878
+ match.loaderPromise?.resolve();
879
+ match.loadPromise?.resolve();
880
+ if (isRedirect(err)) {
881
+ rendered = true;
882
+ err = this.resolveRedirect({ ...err, _fromLocation: location });
883
+ throw err;
884
+ }
885
+ else if (isNotFound(err)) {
886
+ this._handleNotFound(matches, err, {
887
+ updateMatch,
888
+ });
889
+ this.serverSsr?.onMatchSettled({
890
+ router: this,
891
+ match: this.getMatch(match.id),
892
+ });
893
+ throw err;
894
+ }
895
+ }
896
+ };
897
+ try {
898
+ await new Promise((resolveAll, rejectAll) => {
899
+ ;
900
+ (async () => {
901
+ try {
902
+ const handleSerialError = (index, err, routerCode) => {
903
+ const { id: matchId, routeId } = matches[index];
904
+ const route = this.looseRoutesById[routeId];
905
+ // Much like suspense, we use a promise here to know if
906
+ // we've been outdated by a new loadMatches call and
907
+ // should abort the current async operation
908
+ if (err instanceof Promise) {
909
+ throw err;
910
+ }
911
+ err.routerCode = routerCode;
912
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
913
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
914
+ try {
915
+ route.options.onError?.(err);
916
+ }
917
+ catch (errorHandlerErr) {
918
+ err = errorHandlerErr;
919
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
920
+ }
921
+ updateMatch(matchId, (prev) => {
922
+ prev.beforeLoadPromise?.resolve();
923
+ prev.loadPromise?.resolve();
924
+ return {
925
+ ...prev,
926
+ error: err,
927
+ status: 'error',
928
+ isFetching: false,
929
+ updatedAt: Date.now(),
930
+ abortController: new AbortController(),
931
+ beforeLoadPromise: undefined,
932
+ };
933
+ });
934
+ };
935
+ for (const [index, { id: matchId, routeId }] of matches.entries()) {
936
+ const existingMatch = this.getMatch(matchId);
937
+ const parentMatchId = matches[index - 1]?.id;
938
+ const route = this.looseRoutesById[routeId];
939
+ const pendingMs = route.options.pendingMs ?? this.options.defaultPendingMs;
940
+ const shouldPending = !!(onReady &&
941
+ !this.isServer &&
942
+ !resolvePreload(matchId) &&
943
+ (route.options.loader || route.options.beforeLoad) &&
944
+ typeof pendingMs === 'number' &&
945
+ pendingMs !== Infinity &&
946
+ (route.options.pendingComponent ??
947
+ this.options.defaultPendingComponent));
948
+ let executeBeforeLoad = true;
949
+ if (
950
+ // If we are in the middle of a load, either of these will be present
951
+ // (not to be confused with `loadPromise`, which is always defined)
952
+ existingMatch.beforeLoadPromise ||
953
+ existingMatch.loaderPromise) {
954
+ if (shouldPending) {
955
+ setTimeout(() => {
956
+ try {
957
+ // Update the match and prematurely resolve the loadMatches promise so that
958
+ // the pending component can start rendering
959
+ triggerOnReady();
960
+ }
961
+ catch { }
962
+ }, pendingMs);
963
+ }
964
+ // Wait for the beforeLoad to resolve before we continue
965
+ await existingMatch.beforeLoadPromise;
966
+ executeBeforeLoad = this.getMatch(matchId).status !== 'success';
967
+ }
968
+ if (executeBeforeLoad) {
969
+ // If we are not in the middle of a load OR the previous load failed, start it
970
+ try {
971
+ updateMatch(matchId, (prev) => ({
972
+ ...prev,
973
+ loadPromise: createControlledPromise(() => {
974
+ prev.loadPromise?.resolve();
975
+ }),
976
+ beforeLoadPromise: createControlledPromise(),
977
+ }));
978
+ const abortController = new AbortController();
979
+ let pendingTimeout;
980
+ if (shouldPending) {
981
+ // If we might show a pending component, we need to wait for the
982
+ // pending promise to resolve before we start showing that state
983
+ pendingTimeout = setTimeout(() => {
984
+ try {
985
+ // Update the match and prematurely resolve the loadMatches promise so that
986
+ // the pending component can start rendering
987
+ triggerOnReady();
988
+ }
989
+ catch { }
990
+ }, pendingMs);
991
+ }
992
+ const { paramsError, searchError } = this.getMatch(matchId);
993
+ if (paramsError) {
994
+ handleSerialError(index, paramsError, 'PARSE_PARAMS');
995
+ }
996
+ if (searchError) {
997
+ handleSerialError(index, searchError, 'VALIDATE_SEARCH');
998
+ }
999
+ const getParentMatchContext = () => parentMatchId
1000
+ ? this.getMatch(parentMatchId).context
1001
+ : (this.options.context ?? {});
1002
+ updateMatch(matchId, (prev) => ({
1003
+ ...prev,
1004
+ isFetching: 'beforeLoad',
1005
+ fetchCount: prev.fetchCount + 1,
1006
+ abortController,
1007
+ pendingTimeout,
1008
+ context: {
1009
+ ...getParentMatchContext(),
1010
+ ...prev.__routeContext,
1011
+ },
1012
+ }));
1013
+ const { search, params, context, cause } = this.getMatch(matchId);
1014
+ const preload = resolvePreload(matchId);
1015
+ const beforeLoadFnContext = {
1016
+ search,
1017
+ abortController,
1018
+ params,
1019
+ preload,
1020
+ context,
1021
+ location,
1022
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
1023
+ buildLocation: this.buildLocation,
1024
+ cause: preload ? 'preload' : cause,
1025
+ matches,
1026
+ };
1027
+ const beforeLoadContext = (await route.options.beforeLoad?.(beforeLoadFnContext)) ??
1028
+ {};
1029
+ if (isRedirect(beforeLoadContext) ||
1030
+ isNotFound(beforeLoadContext)) {
1031
+ handleSerialError(index, beforeLoadContext, 'BEFORE_LOAD');
1032
+ }
1033
+ updateMatch(matchId, (prev) => {
1034
+ return {
1035
+ ...prev,
1036
+ __beforeLoadContext: beforeLoadContext,
1037
+ context: {
1038
+ ...getParentMatchContext(),
1039
+ ...prev.__routeContext,
1040
+ ...beforeLoadContext,
1041
+ },
1042
+ abortController,
1043
+ };
1044
+ });
1045
+ }
1046
+ catch (err) {
1047
+ handleSerialError(index, err, 'BEFORE_LOAD');
1048
+ }
1049
+ updateMatch(matchId, (prev) => {
1050
+ prev.beforeLoadPromise?.resolve();
1051
+ return {
1052
+ ...prev,
1053
+ beforeLoadPromise: undefined,
1054
+ isFetching: false,
1055
+ };
1056
+ });
1057
+ }
1058
+ }
1059
+ const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
1060
+ const matchPromises = [];
1061
+ validResolvedMatches.forEach(({ id: matchId, routeId }, index) => {
1062
+ matchPromises.push((async () => {
1063
+ const { loaderPromise: prevLoaderPromise } = this.getMatch(matchId);
1064
+ let loaderShouldRunAsync = false;
1065
+ let loaderIsRunningAsync = false;
1066
+ if (prevLoaderPromise) {
1067
+ await prevLoaderPromise;
1068
+ const match = this.getMatch(matchId);
1069
+ if (match.error) {
1070
+ handleRedirectAndNotFound(match, match.error);
1071
+ }
1072
+ }
1073
+ else {
1074
+ const parentMatchPromise = matchPromises[index - 1];
1075
+ const route = this.looseRoutesById[routeId];
1076
+ const getLoaderContext = () => {
1077
+ const { params, loaderDeps, abortController, context, cause, } = this.getMatch(matchId);
1078
+ const preload = resolvePreload(matchId);
1079
+ return {
1080
+ params,
1081
+ deps: loaderDeps,
1082
+ preload: !!preload,
1083
+ parentMatchPromise,
1084
+ abortController: abortController,
1085
+ context,
1086
+ location,
1087
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: location }),
1088
+ cause: preload ? 'preload' : cause,
1089
+ route,
1090
+ };
1091
+ };
1092
+ // This is where all of the stale-while-revalidate magic happens
1093
+ const age = Date.now() - this.getMatch(matchId).updatedAt;
1094
+ const preload = resolvePreload(matchId);
1095
+ const staleAge = preload
1096
+ ? (route.options.preloadStaleTime ??
1097
+ this.options.defaultPreloadStaleTime ??
1098
+ 30000) // 30 seconds for preloads by default
1099
+ : (route.options.staleTime ??
1100
+ this.options.defaultStaleTime ??
1101
+ 0);
1102
+ const shouldReloadOption = route.options.shouldReload;
1103
+ // Default to reloading the route all the time
1104
+ // Allow shouldReload to get the last say,
1105
+ // if provided.
1106
+ const shouldReload = typeof shouldReloadOption === 'function'
1107
+ ? shouldReloadOption(getLoaderContext())
1108
+ : shouldReloadOption;
1109
+ updateMatch(matchId, (prev) => ({
1110
+ ...prev,
1111
+ loaderPromise: createControlledPromise(),
1112
+ preload: !!preload &&
1113
+ !this.state.matches.find((d) => d.id === matchId),
1114
+ }));
1115
+ const runLoader = async () => {
1116
+ try {
1117
+ // If the Matches component rendered
1118
+ // the pending component and needs to show it for
1119
+ // a minimum duration, we''ll wait for it to resolve
1120
+ // before committing to the match and resolving
1121
+ // the loadPromise
1122
+ const potentialPendingMinPromise = async () => {
1123
+ const latestMatch = this.getMatch(matchId);
1124
+ if (latestMatch.minPendingPromise) {
1125
+ await latestMatch.minPendingPromise;
1126
+ }
1127
+ };
1128
+ // Actually run the loader and handle the result
1129
+ try {
1130
+ this.loadRouteChunk(route);
1131
+ updateMatch(matchId, (prev) => ({
1132
+ ...prev,
1133
+ isFetching: 'loader',
1134
+ }));
1135
+ // Kick off the loader!
1136
+ const loaderData = await route.options.loader?.(getLoaderContext());
1137
+ handleRedirectAndNotFound(this.getMatch(matchId), loaderData);
1138
+ // Lazy option can modify the route options,
1139
+ // so we need to wait for it to resolve before
1140
+ // we can use the options
1141
+ await route._lazyPromise;
1142
+ await potentialPendingMinPromise();
1143
+ const assetContext = {
1144
+ matches,
1145
+ match: this.getMatch(matchId),
1146
+ params: this.getMatch(matchId).params,
1147
+ loaderData,
1148
+ };
1149
+ const headFnContent = route.options.head?.(assetContext);
1150
+ const meta = headFnContent?.meta;
1151
+ const links = headFnContent?.links;
1152
+ const headScripts = headFnContent?.scripts;
1153
+ const scripts = route.options.scripts?.(assetContext);
1154
+ const headers = route.options.headers?.({
1155
+ loaderData,
1156
+ });
1157
+ updateMatch(matchId, (prev) => ({
1158
+ ...prev,
1159
+ error: undefined,
1160
+ status: 'success',
1161
+ isFetching: false,
1162
+ updatedAt: Date.now(),
1163
+ loaderData,
1164
+ meta,
1165
+ links,
1166
+ headScripts,
1167
+ headers,
1168
+ scripts,
1169
+ }));
1170
+ }
1171
+ catch (e) {
1172
+ let error = e;
1173
+ await potentialPendingMinPromise();
1174
+ handleRedirectAndNotFound(this.getMatch(matchId), e);
1175
+ try {
1176
+ route.options.onError?.(e);
1177
+ }
1178
+ catch (onErrorError) {
1179
+ error = onErrorError;
1180
+ handleRedirectAndNotFound(this.getMatch(matchId), onErrorError);
1181
+ }
1182
+ updateMatch(matchId, (prev) => ({
1183
+ ...prev,
1184
+ error,
1185
+ status: 'error',
1186
+ isFetching: false,
1187
+ }));
1188
+ }
1189
+ this.serverSsr?.onMatchSettled({
1190
+ router: this,
1191
+ match: this.getMatch(matchId),
1192
+ });
1193
+ // Last but not least, wait for the the components
1194
+ // to be preloaded before we resolve the match
1195
+ await route._componentsPromise;
1196
+ }
1197
+ catch (err) {
1198
+ updateMatch(matchId, (prev) => ({
1199
+ ...prev,
1200
+ loaderPromise: undefined,
1201
+ }));
1202
+ handleRedirectAndNotFound(this.getMatch(matchId), err);
1203
+ }
1204
+ };
1205
+ // If the route is successful and still fresh, just resolve
1206
+ const { status, invalid } = this.getMatch(matchId);
1207
+ loaderShouldRunAsync =
1208
+ status === 'success' &&
1209
+ (invalid || (shouldReload ?? age > staleAge));
1210
+ if (preload && route.options.preload === false) {
1211
+ // Do nothing
1212
+ }
1213
+ else if (loaderShouldRunAsync && !sync) {
1214
+ loaderIsRunningAsync = true;
1215
+ (async () => {
1216
+ try {
1217
+ await runLoader();
1218
+ const { loaderPromise, loadPromise } = this.getMatch(matchId);
1219
+ loaderPromise?.resolve();
1220
+ loadPromise?.resolve();
1221
+ updateMatch(matchId, (prev) => ({
1222
+ ...prev,
1223
+ loaderPromise: undefined,
1224
+ }));
1225
+ }
1226
+ catch (err) {
1227
+ if (isResolvedRedirect(err)) {
1228
+ await this.navigate(err);
1229
+ }
1230
+ }
1231
+ })();
1232
+ }
1233
+ else if (status !== 'success' ||
1234
+ (loaderShouldRunAsync && sync)) {
1235
+ await runLoader();
1236
+ }
1237
+ }
1238
+ if (!loaderIsRunningAsync) {
1239
+ const { loaderPromise, loadPromise } = this.getMatch(matchId);
1240
+ loaderPromise?.resolve();
1241
+ loadPromise?.resolve();
1242
+ }
1243
+ updateMatch(matchId, (prev) => ({
1244
+ ...prev,
1245
+ isFetching: loaderIsRunningAsync ? prev.isFetching : false,
1246
+ loaderPromise: loaderIsRunningAsync
1247
+ ? prev.loaderPromise
1248
+ : undefined,
1249
+ invalid: false,
1250
+ }));
1251
+ return this.getMatch(matchId);
1252
+ })());
1253
+ });
1254
+ await Promise.all(matchPromises);
1255
+ resolveAll();
1256
+ }
1257
+ catch (err) {
1258
+ rejectAll(err);
1259
+ }
1260
+ })();
1261
+ });
1262
+ await triggerOnReady();
1263
+ }
1264
+ catch (err) {
1265
+ if (isRedirect(err) || isNotFound(err)) {
1266
+ if (isNotFound(err) && !allPreload) {
1267
+ await triggerOnReady();
1268
+ }
1269
+ throw err;
1270
+ }
1271
+ }
1272
+ return matches;
1273
+ };
1274
+ this.invalidate = (opts) => {
1275
+ const invalidate = (d) => {
1276
+ if (opts?.filter?.(d) ?? true) {
1277
+ return {
1278
+ ...d,
1279
+ invalid: true,
1280
+ ...(d.status === 'error'
1281
+ ? { status: 'pending', error: undefined }
1282
+ : {}),
1283
+ };
1284
+ }
1285
+ return d;
1286
+ };
1287
+ this.__store.setState((s) => ({
1288
+ ...s,
1289
+ matches: s.matches.map(invalidate),
1290
+ cachedMatches: s.cachedMatches.map(invalidate),
1291
+ pendingMatches: s.pendingMatches?.map(invalidate),
1292
+ }));
1293
+ return this.load({ sync: opts?.sync });
1294
+ };
1295
+ this.resolveRedirect = (err) => {
1296
+ const redirect = err;
1297
+ if (!redirect.href) {
1298
+ redirect.href = this.buildLocation(redirect).href;
1299
+ }
1300
+ return redirect;
1301
+ };
1302
+ this.clearCache = (opts) => {
1303
+ const filter = opts?.filter;
1304
+ if (filter !== undefined) {
1305
+ this.__store.setState((s) => {
1306
+ return {
1307
+ ...s,
1308
+ cachedMatches: s.cachedMatches.filter((m) => !filter(m)),
1309
+ };
1310
+ });
1311
+ }
1312
+ else {
1313
+ this.__store.setState((s) => {
1314
+ return {
1315
+ ...s,
1316
+ cachedMatches: [],
1317
+ };
1318
+ });
1319
+ }
1320
+ };
1321
+ this.clearExpiredCache = () => {
1322
+ // This is where all of the garbage collection magic happens
1323
+ const filter = (d) => {
1324
+ const route = this.looseRoutesById[d.routeId];
1325
+ if (!route.options.loader) {
1326
+ return true;
1327
+ }
1328
+ // If the route was preloaded, use the preloadGcTime
1329
+ // otherwise, use the gcTime
1330
+ const gcTime = (d.preload
1331
+ ? (route.options.preloadGcTime ?? this.options.defaultPreloadGcTime)
1332
+ : (route.options.gcTime ?? this.options.defaultGcTime)) ??
1333
+ 5 * 60 * 1000;
1334
+ return !(d.status !== 'error' && Date.now() - d.updatedAt < gcTime);
1335
+ };
1336
+ this.clearCache({ filter });
1337
+ };
1338
+ this.loadRouteChunk = (route) => {
1339
+ if (route._lazyPromise === undefined) {
1340
+ if (route.lazyFn) {
1341
+ route._lazyPromise = route.lazyFn().then((lazyRoute) => {
1342
+ // explicitly don't copy over the lazy route's id
1343
+ const { id: _id, ...options } = lazyRoute.options;
1344
+ Object.assign(route.options, options);
1345
+ });
1346
+ }
1347
+ else {
1348
+ route._lazyPromise = Promise.resolve();
1349
+ }
1350
+ }
1351
+ // If for some reason lazy resolves more lazy components...
1352
+ // We'll wait for that before pre attempt to preload any
1353
+ // components themselves.
1354
+ if (route._componentsPromise === undefined) {
1355
+ route._componentsPromise = route._lazyPromise.then(() => Promise.all(componentTypes.map(async (type) => {
1356
+ const component = route.options[type];
1357
+ if (component?.preload) {
1358
+ await component.preload();
1359
+ }
1360
+ })));
1361
+ }
1362
+ return route._componentsPromise;
1363
+ };
1364
+ this.preloadRoute = async (opts) => {
1365
+ const next = this.buildLocation(opts);
1366
+ let matches = this.matchRoutes(next, {
1367
+ throwOnError: true,
1368
+ preload: true,
1369
+ dest: opts,
1370
+ });
1371
+ const activeMatchIds = new Set([...this.state.matches, ...(this.state.pendingMatches ?? [])].map((d) => d.id));
1372
+ const loadedMatchIds = new Set([
1373
+ ...activeMatchIds,
1374
+ ...this.state.cachedMatches.map((d) => d.id),
1375
+ ]);
1376
+ // If the matches are already loaded, we need to add them to the cachedMatches
1377
+ batch(() => {
1378
+ matches.forEach((match) => {
1379
+ if (!loadedMatchIds.has(match.id)) {
1380
+ this.__store.setState((s) => ({
1381
+ ...s,
1382
+ cachedMatches: [...s.cachedMatches, match],
1383
+ }));
1384
+ }
1385
+ });
1386
+ });
1387
+ try {
1388
+ matches = await this.loadMatches({
1389
+ matches,
1390
+ location: next,
1391
+ preload: true,
1392
+ updateMatch: (id, updater) => {
1393
+ // Don't update the match if it's currently loaded
1394
+ if (activeMatchIds.has(id)) {
1395
+ matches = matches.map((d) => (d.id === id ? updater(d) : d));
1396
+ }
1397
+ else {
1398
+ this.updateMatch(id, updater);
1399
+ }
1400
+ },
1401
+ });
1402
+ return matches;
1403
+ }
1404
+ catch (err) {
1405
+ if (isRedirect(err)) {
1406
+ if (err.reloadDocument) {
1407
+ return undefined;
1408
+ }
1409
+ return await this.preloadRoute({
1410
+ ...err,
1411
+ _fromLocation: next,
1412
+ });
1413
+ }
1414
+ if (!isNotFound(err)) {
1415
+ // Preload errors are not fatal, but we should still log them
1416
+ console.error(err);
1417
+ }
1418
+ return undefined;
1419
+ }
1420
+ };
1421
+ this.matchRoute = (location, opts) => {
1422
+ const matchLocation = {
1423
+ ...location,
1424
+ to: location.to
1425
+ ? this.resolvePathWithBase((location.from || ''), location.to)
1426
+ : undefined,
1427
+ params: location.params || {},
1428
+ leaveParams: true,
1429
+ };
1430
+ const next = this.buildLocation(matchLocation);
1431
+ if (opts?.pending && this.state.status !== 'pending') {
1432
+ return false;
1433
+ }
1434
+ const pending = opts?.pending === undefined ? !this.state.isLoading : opts.pending;
1435
+ const baseLocation = pending
1436
+ ? this.latestLocation
1437
+ : this.state.resolvedLocation || this.state.location;
1438
+ const match = matchPathname(this.basepath, baseLocation.pathname, {
1439
+ ...opts,
1440
+ to: next.pathname,
1441
+ });
1442
+ if (!match) {
1443
+ return false;
1444
+ }
1445
+ if (location.params) {
1446
+ if (!deepEqual(match, location.params, { partial: true })) {
1447
+ return false;
1448
+ }
1449
+ }
1450
+ if (match && (opts?.includeSearch ?? true)) {
1451
+ return deepEqual(baseLocation.search, next.search, { partial: true })
1452
+ ? match
1453
+ : false;
1454
+ }
1455
+ return match;
1456
+ };
1457
+ this._handleNotFound = (matches, err, { updateMatch = this.updateMatch, } = {}) => {
1458
+ const matchesByRouteId = Object.fromEntries(matches.map((match) => [match.routeId, match]));
1459
+ // Start at the route that errored or default to the root route
1460
+ let routeCursor = (err.global
1461
+ ? this.looseRoutesById[rootRouteId]
1462
+ : this.looseRoutesById[err.routeId]) ||
1463
+ this.looseRoutesById[rootRouteId];
1464
+ // Go up the tree until we find a route with a notFoundComponent or we hit the root
1465
+ while (!routeCursor.options.notFoundComponent &&
1466
+ !this.options.defaultNotFoundComponent &&
1467
+ routeCursor.id !== rootRouteId) {
1468
+ routeCursor = routeCursor.parentRoute;
1469
+ invariant(routeCursor, 'Found invalid route tree while trying to find not-found handler.');
1470
+ }
1471
+ const match = matchesByRouteId[routeCursor.id];
1472
+ invariant(match, 'Could not find match for route: ' + routeCursor.id);
1473
+ // Assign the error to the match
1474
+ updateMatch(match.id, (prev) => ({
1475
+ ...prev,
1476
+ status: 'notFound',
1477
+ error: err,
1478
+ isFetching: false,
1479
+ }));
1480
+ if (err.routerCode === 'BEFORE_LOAD' && routeCursor.parentRoute) {
1481
+ err.routeId = routeCursor.parentRoute.id;
1482
+ this._handleNotFound(matches, err, {
1483
+ updateMatch,
1484
+ });
1485
+ }
1486
+ };
1487
+ this.hasNotFoundMatch = () => {
1488
+ return this.__store.state.matches.some((d) => d.status === 'notFound' || d.globalNotFound);
1489
+ };
1490
+ this.update({
1491
+ defaultPreloadDelay: 50,
1492
+ defaultPendingMs: 1000,
1493
+ defaultPendingMinMs: 500,
1494
+ context: undefined,
1495
+ ...options,
1496
+ caseSensitive: options.caseSensitive ?? false,
1497
+ notFoundMode: options.notFoundMode ?? 'fuzzy',
1498
+ stringifySearch: options.stringifySearch ?? defaultStringifySearch,
1499
+ parseSearch: options.parseSearch ?? defaultParseSearch,
1500
+ });
1501
+ if (typeof document !== 'undefined') {
1502
+ ;
1503
+ window.__TSR_ROUTER__ = this;
1504
+ }
1505
+ }
1506
+ get state() {
1507
+ return this.__store.state;
1508
+ }
1509
+ get looseRoutesById() {
1510
+ return this.routesById;
1511
+ }
1512
+ matchRoutes(pathnameOrNext, locationSearchOrOpts, opts) {
1513
+ if (typeof pathnameOrNext === 'string') {
1514
+ return this.matchRoutesInternal({
1515
+ pathname: pathnameOrNext,
1516
+ search: locationSearchOrOpts,
1517
+ }, opts);
1518
+ }
1519
+ else {
1520
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts);
1521
+ }
1522
+ }
1523
+ matchRoutesInternal(next, opts) {
1524
+ const { foundRoute, matchedRoutes, routeParams } = this.getMatchedRoutes(next, opts?.dest);
1525
+ let isGlobalNotFound = false;
1526
+ // Check to see if the route needs a 404 entry
1527
+ if (
1528
+ // If we found a route, and it's not an index route and we have left over path
1529
+ foundRoute
1530
+ ? foundRoute.path !== '/' && routeParams['**']
1531
+ : // Or if we didn't find a route and we have left over path
1532
+ trimPathRight(next.pathname)) {
1533
+ // If the user has defined an (old) 404 route, use it
1534
+ if (this.options.notFoundRoute) {
1535
+ matchedRoutes.push(this.options.notFoundRoute);
1536
+ }
1537
+ else {
1538
+ // If there is no routes found during path matching
1539
+ isGlobalNotFound = true;
1540
+ }
1541
+ }
1542
+ const globalNotFoundRouteId = (() => {
1543
+ if (!isGlobalNotFound) {
1544
+ return undefined;
1545
+ }
1546
+ if (this.options.notFoundMode !== 'root') {
1547
+ for (let i = matchedRoutes.length - 1; i >= 0; i--) {
1548
+ const route = matchedRoutes[i];
1549
+ if (route.children) {
1550
+ return route.id;
1551
+ }
1552
+ }
1553
+ }
1554
+ return rootRouteId;
1555
+ })();
1556
+ const parseErrors = matchedRoutes.map((route) => {
1557
+ let parsedParamsError;
1558
+ const parseParams = route.options.params?.parse ?? route.options.parseParams;
1559
+ if (parseParams) {
1560
+ try {
1561
+ const parsedParams = parseParams(routeParams);
1562
+ // Add the parsed params to the accumulated params bag
1563
+ Object.assign(routeParams, parsedParams);
1564
+ }
1565
+ catch (err) {
1566
+ parsedParamsError = new PathParamError(err.message, {
1567
+ cause: err,
1568
+ });
1569
+ if (opts?.throwOnError) {
1570
+ throw parsedParamsError;
1571
+ }
1572
+ return parsedParamsError;
1573
+ }
1574
+ }
1575
+ return;
1576
+ });
1577
+ const matches = [];
1578
+ const getParentContext = (parentMatch) => {
1579
+ const parentMatchId = parentMatch?.id;
1580
+ const parentContext = !parentMatchId
1581
+ ? (this.options.context ?? {})
1582
+ : (parentMatch.context ?? this.options.context ?? {});
1583
+ return parentContext;
1584
+ };
1585
+ matchedRoutes.forEach((route, index) => {
1586
+ // Take each matched route and resolve + validate its search params
1587
+ // This has to happen serially because each route's search params
1588
+ // can depend on the parent route's search params
1589
+ // It must also happen before we create the match so that we can
1590
+ // pass the search params to the route's potential key function
1591
+ // which is used to uniquely identify the route match in state
1592
+ const parentMatch = matches[index - 1];
1593
+ const [preMatchSearch, strictMatchSearch, searchError] = (() => {
1594
+ // Validate the search params and stabilize them
1595
+ const parentSearch = parentMatch?.search ?? next.search;
1596
+ const parentStrictSearch = parentMatch?._strictSearch ?? {};
1597
+ try {
1598
+ const strictSearch = validateSearch(route.options.validateSearch, { ...parentSearch }) ??
1599
+ {};
1600
+ return [
1601
+ {
1602
+ ...parentSearch,
1603
+ ...strictSearch,
1604
+ },
1605
+ { ...parentStrictSearch, ...strictSearch },
1606
+ undefined,
1607
+ ];
1608
+ }
1609
+ catch (err) {
1610
+ let searchParamError = err;
1611
+ if (!(err instanceof SearchParamError)) {
1612
+ searchParamError = new SearchParamError(err.message, {
1613
+ cause: err,
1614
+ });
1615
+ }
1616
+ if (opts?.throwOnError) {
1617
+ throw searchParamError;
1618
+ }
1619
+ return [parentSearch, {}, searchParamError];
1620
+ }
1621
+ })();
1622
+ // This is where we need to call route.options.loaderDeps() to get any additional
1623
+ // deps that the route's loader function might need to run. We need to do this
1624
+ // before we create the match so that we can pass the deps to the route's
1625
+ // potential key function which is used to uniquely identify the route match in state
1626
+ const loaderDeps = route.options.loaderDeps?.({
1627
+ search: preMatchSearch,
1628
+ }) ?? '';
1629
+ const loaderDepsHash = loaderDeps ? JSON.stringify(loaderDeps) : '';
1630
+ const { usedParams, interpolatedPath } = interpolatePath({
1631
+ path: route.fullPath,
1632
+ params: routeParams,
1633
+ decodeCharMap: this.pathParamsDecodeCharMap,
1634
+ });
1635
+ const matchId = interpolatePath({
1636
+ path: route.id,
1637
+ params: routeParams,
1638
+ leaveWildcards: true,
1639
+ decodeCharMap: this.pathParamsDecodeCharMap,
1640
+ }).interpolatedPath + loaderDepsHash;
1641
+ // Waste not, want not. If we already have a match for this route,
1642
+ // reuse it. This is important for layout routes, which might stick
1643
+ // around between navigation actions that only change leaf routes.
1644
+ // Existing matches are matches that are already loaded along with
1645
+ // pending matches that are still loading
1646
+ const existingMatch = this.getMatch(matchId);
1647
+ const previousMatch = this.state.matches.find((d) => d.routeId === route.id);
1648
+ const cause = previousMatch ? 'stay' : 'enter';
1649
+ let match;
1650
+ if (existingMatch) {
1651
+ match = {
1652
+ ...existingMatch,
1653
+ cause,
1654
+ params: previousMatch
1655
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1656
+ : routeParams,
1657
+ _strictParams: usedParams,
1658
+ search: previousMatch
1659
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1660
+ : replaceEqualDeep(existingMatch.search, preMatchSearch),
1661
+ _strictSearch: strictMatchSearch,
1662
+ };
1663
+ }
1664
+ else {
1665
+ const status = route.options.loader ||
1666
+ route.options.beforeLoad ||
1667
+ route.lazyFn ||
1668
+ routeNeedsPreload(route)
1669
+ ? 'pending'
1670
+ : 'success';
1671
+ match = {
1672
+ id: matchId,
1673
+ index,
1674
+ routeId: route.id,
1675
+ params: previousMatch
1676
+ ? replaceEqualDeep(previousMatch.params, routeParams)
1677
+ : routeParams,
1678
+ _strictParams: usedParams,
1679
+ pathname: joinPaths([this.basepath, interpolatedPath]),
1680
+ updatedAt: Date.now(),
1681
+ search: previousMatch
1682
+ ? replaceEqualDeep(previousMatch.search, preMatchSearch)
1683
+ : preMatchSearch,
1684
+ _strictSearch: strictMatchSearch,
1685
+ searchError: undefined,
1686
+ status,
1687
+ isFetching: false,
1688
+ error: undefined,
1689
+ paramsError: parseErrors[index],
1690
+ __routeContext: {},
1691
+ __beforeLoadContext: {},
1692
+ context: {},
1693
+ abortController: new AbortController(),
1694
+ fetchCount: 0,
1695
+ cause,
1696
+ loaderDeps: previousMatch
1697
+ ? replaceEqualDeep(previousMatch.loaderDeps, loaderDeps)
1698
+ : loaderDeps,
1699
+ invalid: false,
1700
+ preload: false,
1701
+ links: undefined,
1702
+ scripts: undefined,
1703
+ headScripts: undefined,
1704
+ meta: undefined,
1705
+ staticData: route.options.staticData || {},
1706
+ loadPromise: createControlledPromise(),
1707
+ fullPath: route.fullPath,
1708
+ };
1709
+ }
1710
+ if (!opts?.preload) {
1711
+ // If we have a global not found, mark the right match as global not found
1712
+ match.globalNotFound = globalNotFoundRouteId === route.id;
1713
+ }
1714
+ // update the searchError if there is one
1715
+ match.searchError = searchError;
1716
+ const parentContext = getParentContext(parentMatch);
1717
+ match.context = {
1718
+ ...parentContext,
1719
+ ...match.__routeContext,
1720
+ ...match.__beforeLoadContext,
1721
+ };
1722
+ matches.push(match);
1723
+ });
1724
+ matches.forEach((match, index) => {
1725
+ const route = this.looseRoutesById[match.routeId];
1726
+ const existingMatch = this.getMatch(match.id);
1727
+ // only execute `context` if we are not just building a location
1728
+ if (!existingMatch && opts?._buildLocation !== true) {
1729
+ const parentMatch = matches[index - 1];
1730
+ const parentContext = getParentContext(parentMatch);
1731
+ // Update the match's context
1732
+ const contextFnContext = {
1733
+ deps: match.loaderDeps,
1734
+ params: match.params,
1735
+ context: parentContext,
1736
+ location: next,
1737
+ navigate: (opts) => this.navigate({ ...opts, _fromLocation: next }),
1738
+ buildLocation: this.buildLocation,
1739
+ cause: match.cause,
1740
+ abortController: match.abortController,
1741
+ preload: !!match.preload,
1742
+ matches,
1743
+ };
1744
+ // Get the route context
1745
+ match.__routeContext = route.options.context?.(contextFnContext) ?? {};
1746
+ match.context = {
1747
+ ...parentContext,
1748
+ ...match.__routeContext,
1749
+ ...match.__beforeLoadContext,
1750
+ };
1751
+ }
1752
+ // If it's already a success, update headers and head content
1753
+ // These may get updated again if the match is refreshed
1754
+ // due to being stale
1755
+ if (match.status === 'success') {
1756
+ match.headers = route.options.headers?.({
1757
+ loaderData: match.loaderData,
1758
+ });
1759
+ const assetContext = {
1760
+ matches,
1761
+ match,
1762
+ params: match.params,
1763
+ loaderData: match.loaderData,
1764
+ };
1765
+ const headFnContent = route.options.head?.(assetContext);
1766
+ match.links = headFnContent?.links;
1767
+ match.headScripts = headFnContent?.scripts;
1768
+ match.meta = headFnContent?.meta;
1769
+ match.scripts = route.options.scripts?.(assetContext);
1770
+ }
1771
+ });
1772
+ return matches;
1773
+ }
1774
+ }
1775
+ // A function that takes an import() argument which is a function and returns a new function that will
1776
+ // proxy arguments from the caller to the imported function, retaining all type
1777
+ // information along the way
1778
+ export function lazyFn(fn, key) {
1779
+ return async (...args) => {
1780
+ const imported = await fn();
1781
+ return imported[key || 'default'](...args);
1782
+ };
1783
+ }
1784
+ export class SearchParamError extends Error {
1785
+ }
1786
+ export class PathParamError extends Error {
1787
+ }
1788
+ export function getInitialRouterState(location) {
1789
+ return {
1790
+ loadedAt: 0,
1791
+ isLoading: false,
1792
+ isTransitioning: false,
1793
+ status: 'idle',
1794
+ resolvedLocation: undefined,
1795
+ location,
1796
+ matches: [],
1797
+ pendingMatches: [],
1798
+ cachedMatches: [],
1799
+ statusCode: 200,
1800
+ };
1801
+ }
1802
+ //# sourceMappingURL=router.js.map