@tanstack/router-core 0.0.1-beta.2 → 0.0.1-beta.200

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 (79) hide show
  1. package/LICENSE +21 -0
  2. package/build/cjs/defer.js +39 -0
  3. package/build/cjs/defer.js.map +1 -0
  4. package/build/cjs/fileRoute.js +29 -0
  5. package/build/cjs/fileRoute.js.map +1 -0
  6. package/build/cjs/index.js +89 -0
  7. package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
  8. package/build/cjs/{packages/router-core/src/path.js → path.js} +45 -56
  9. package/build/cjs/path.js.map +1 -0
  10. package/build/cjs/{packages/router-core/src/qss.js → qss.js} +10 -15
  11. package/build/cjs/qss.js.map +1 -0
  12. package/build/cjs/route.js +114 -0
  13. package/build/cjs/route.js.map +1 -0
  14. package/build/cjs/router.js +1277 -0
  15. package/build/cjs/router.js.map +1 -0
  16. package/build/cjs/scroll-restoration.js +139 -0
  17. package/build/cjs/scroll-restoration.js.map +1 -0
  18. package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +32 -19
  19. package/build/cjs/searchParams.js.map +1 -0
  20. package/build/cjs/utils.js +132 -0
  21. package/build/cjs/utils.js.map +1 -0
  22. package/build/esm/index.js +1565 -2106
  23. package/build/esm/index.js.map +1 -1
  24. package/build/stats-html.html +59 -49
  25. package/build/stats-react.json +219 -241
  26. package/build/types/defer.d.ts +19 -0
  27. package/build/types/fileRoute.d.ts +35 -0
  28. package/build/types/index.d.ts +13 -611
  29. package/build/types/link.d.ts +96 -0
  30. package/build/types/path.d.ts +16 -0
  31. package/build/types/qss.d.ts +2 -0
  32. package/build/types/route.d.ts +261 -0
  33. package/build/types/routeInfo.d.ts +22 -0
  34. package/build/types/router.d.ts +265 -0
  35. package/build/types/scroll-restoration.d.ts +6 -0
  36. package/build/types/searchParams.d.ts +5 -0
  37. package/build/types/utils.d.ts +51 -0
  38. package/build/umd/index.development.js +1917 -2070
  39. package/build/umd/index.development.js.map +1 -1
  40. package/build/umd/index.production.js +23 -2
  41. package/build/umd/index.production.js.map +1 -1
  42. package/package.json +13 -7
  43. package/src/defer.ts +55 -0
  44. package/src/fileRoute.ts +156 -0
  45. package/src/index.ts +5 -11
  46. package/src/link.ts +149 -123
  47. package/src/path.ts +37 -17
  48. package/src/qss.ts +1 -1
  49. package/src/route.ts +861 -231
  50. package/src/routeInfo.ts +47 -205
  51. package/src/router.ts +1818 -952
  52. package/src/scroll-restoration.ts +179 -0
  53. package/src/searchParams.ts +31 -9
  54. package/src/utils.ts +106 -43
  55. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -33
  56. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
  57. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
  58. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
  59. package/build/cjs/node_modules/history/index.js +0 -815
  60. package/build/cjs/node_modules/history/index.js.map +0 -1
  61. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
  62. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
  63. package/build/cjs/packages/router-core/src/index.js +0 -58
  64. package/build/cjs/packages/router-core/src/path.js.map +0 -1
  65. package/build/cjs/packages/router-core/src/qss.js.map +0 -1
  66. package/build/cjs/packages/router-core/src/route.js +0 -161
  67. package/build/cjs/packages/router-core/src/route.js.map +0 -1
  68. package/build/cjs/packages/router-core/src/routeConfig.js +0 -69
  69. package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
  70. package/build/cjs/packages/router-core/src/routeMatch.js +0 -266
  71. package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
  72. package/build/cjs/packages/router-core/src/router.js +0 -789
  73. package/build/cjs/packages/router-core/src/router.js.map +0 -1
  74. package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
  75. package/build/cjs/packages/router-core/src/utils.js +0 -118
  76. package/build/cjs/packages/router-core/src/utils.js.map +0 -1
  77. package/src/frameworks.ts +0 -12
  78. package/src/routeConfig.ts +0 -495
  79. package/src/routeMatch.ts +0 -374
@@ -0,0 +1,1277 @@
1
+ /**
2
+ * @tanstack/router-core/src/index.ts
3
+ *
4
+ * Copyright (c) TanStack
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
11
+ 'use strict';
12
+
13
+ Object.defineProperty(exports, '__esModule', { value: true });
14
+
15
+ var store = require('@tanstack/store');
16
+ var invariant = require('tiny-invariant');
17
+ var history = require('@tanstack/history');
18
+ var path = require('./path.js');
19
+ var searchParams = require('./searchParams.js');
20
+ var utils = require('./utils.js');
21
+
22
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
23
+
24
+ var invariant__default = /*#__PURE__*/_interopDefaultLegacy(invariant);
25
+
26
+ //
27
+
28
+ const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
29
+ const visibilityChangeEvent = 'visibilitychange';
30
+ const focusEvent = 'focus';
31
+ const preloadWarning = 'Error preloading route! ☝️';
32
+ class Router {
33
+ #unsubHistory;
34
+ resetNextScroll = false;
35
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
36
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
37
+
38
+ constructor(options) {
39
+ this.options = {
40
+ defaultPreloadDelay: 50,
41
+ context: undefined,
42
+ ...options,
43
+ stringifySearch: options?.stringifySearch ?? searchParams.defaultStringifySearch,
44
+ parseSearch: options?.parseSearch ?? searchParams.defaultParseSearch
45
+ // fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn,
46
+ };
47
+
48
+ this.__store = new store.Store(getInitialRouterState(), {
49
+ onUpdate: () => {
50
+ const prev = this.state;
51
+ const next = this.__store.state;
52
+ const matchesByIdChanged = prev.matchesById !== next.matchesById;
53
+ let matchesChanged;
54
+ let pendingMatchesChanged;
55
+ if (!matchesByIdChanged) {
56
+ matchesChanged = prev.matchIds.length !== next.matchIds.length || prev.matchIds.some((d, i) => d !== next.matchIds[i]);
57
+ pendingMatchesChanged = prev.pendingMatchIds.length !== next.pendingMatchIds.length || prev.pendingMatchIds.some((d, i) => d !== next.pendingMatchIds[i]);
58
+ }
59
+ if (matchesByIdChanged || matchesChanged) {
60
+ next.matches = next.matchIds.map(id => {
61
+ return next.matchesById[id];
62
+ });
63
+ }
64
+ if (matchesByIdChanged || pendingMatchesChanged) {
65
+ next.pendingMatches = next.pendingMatchIds.map(id => {
66
+ return next.matchesById[id];
67
+ });
68
+ }
69
+ if (matchesByIdChanged || matchesChanged || pendingMatchesChanged) {
70
+ const hasPendingComponent = next.pendingMatches.some(d => {
71
+ const route = this.getRoute(d.routeId);
72
+ return !!route?.options.pendingComponent;
73
+ });
74
+ next.renderedMatchIds = hasPendingComponent ? next.pendingMatchIds : next.matchIds;
75
+ next.renderedMatches = next.renderedMatchIds.map(id => {
76
+ return next.matchesById[id];
77
+ });
78
+ }
79
+ next.isFetching = [...next.matches, ...next.pendingMatches].some(d => d.isFetching);
80
+ this.state = next;
81
+ },
82
+ defaultPriority: 'low'
83
+ });
84
+ this.state = this.__store.state;
85
+ this.update(options);
86
+ const nextLocation = this.buildLocation({
87
+ search: true,
88
+ params: true,
89
+ hash: true,
90
+ state: true
91
+ });
92
+ if (this.state.location.href !== nextLocation.href) {
93
+ this.#commitLocation({
94
+ ...nextLocation,
95
+ replace: true
96
+ });
97
+ }
98
+ }
99
+ subscribers = new Set();
100
+ subscribe = (eventType, fn) => {
101
+ const listener = {
102
+ eventType,
103
+ fn
104
+ };
105
+ this.subscribers.add(listener);
106
+ return () => {
107
+ this.subscribers.delete(listener);
108
+ };
109
+ };
110
+ #emit = routerEvent => {
111
+ this.subscribers.forEach(listener => {
112
+ if (listener.eventType === routerEvent.type) {
113
+ listener.fn(routerEvent);
114
+ }
115
+ });
116
+ };
117
+ reset = () => {
118
+ this.__store.setState(s => Object.assign(s, getInitialRouterState()));
119
+ };
120
+ mount = () => {
121
+ // addEventListener does not exist in React Native, but window does
122
+ // In the future, we might need to invert control here for more adapters
123
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
124
+ if (typeof window !== 'undefined' && window.addEventListener) {
125
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
126
+ window.addEventListener(focusEvent, this.#onFocus, false);
127
+ }
128
+ this.safeLoad();
129
+ return () => {
130
+ if (typeof window !== 'undefined' && window.removeEventListener) {
131
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus);
132
+ window.removeEventListener(focusEvent, this.#onFocus);
133
+ }
134
+ };
135
+ };
136
+ #onFocus = () => {
137
+ if (this.options.reloadOnWindowFocus ?? true) {
138
+ this.invalidate({
139
+ __fromFocus: true
140
+ });
141
+ }
142
+ };
143
+ update = opts => {
144
+ this.options = {
145
+ ...this.options,
146
+ ...opts,
147
+ context: {
148
+ ...this.options.context,
149
+ ...opts?.context
150
+ }
151
+ };
152
+ if (!this.history || this.options.history && this.options.history !== this.history) {
153
+ if (this.#unsubHistory) {
154
+ this.#unsubHistory();
155
+ }
156
+ this.history = this.options.history ?? (isServer ? history.createMemoryHistory() : history.createBrowserHistory());
157
+ const parsedLocation = this.#parseLocation();
158
+ this.__store.setState(s => ({
159
+ ...s,
160
+ resolvedLocation: parsedLocation,
161
+ location: parsedLocation
162
+ }));
163
+ this.#unsubHistory = this.history.subscribe(() => {
164
+ this.safeLoad({
165
+ next: this.#parseLocation(this.state.location)
166
+ });
167
+ });
168
+ }
169
+ const {
170
+ basepath,
171
+ routeTree
172
+ } = this.options;
173
+ this.basepath = `/${path.trimPath(basepath ?? '') ?? ''}`;
174
+ if (routeTree && routeTree !== this.routeTree) {
175
+ this.#processRoutes(routeTree);
176
+ }
177
+ return this;
178
+ };
179
+ cancelMatches = () => {
180
+ this.state.matches.forEach(match => {
181
+ this.cancelMatch(match.id);
182
+ });
183
+ };
184
+ cancelMatch = id => {
185
+ this.getRouteMatch(id)?.abortController?.abort();
186
+ };
187
+ safeLoad = async opts => {
188
+ try {
189
+ return this.load(opts);
190
+ } catch (err) {
191
+ // Don't do anything
192
+ }
193
+ };
194
+ latestLoadPromise = Promise.resolve();
195
+ load = async opts => {
196
+ const promise = new Promise(async (resolve, reject) => {
197
+ const prevLocation = this.state.resolvedLocation;
198
+ const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
199
+ let latestPromise;
200
+ const checkLatest = () => {
201
+ return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
202
+ };
203
+
204
+ // Cancel any pending matches
205
+
206
+ let pendingMatches;
207
+ this.#emit({
208
+ type: 'onBeforeLoad',
209
+ from: prevLocation,
210
+ to: opts?.next ?? this.state.location,
211
+ pathChanged: pathDidChange
212
+ });
213
+ this.__store.batch(() => {
214
+ if (opts?.next) {
215
+ // Ingest the new location
216
+ this.__store.setState(s => ({
217
+ ...s,
218
+ location: opts.next
219
+ }));
220
+ }
221
+
222
+ // Match the routes
223
+ pendingMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search, {
224
+ throwOnError: opts?.throwOnError,
225
+ debug: true
226
+ });
227
+ this.__store.setState(s => ({
228
+ ...s,
229
+ status: 'pending',
230
+ pendingMatchIds: pendingMatches.map(d => d.id),
231
+ matchesById: this.#mergeMatches(s.matchesById, pendingMatches)
232
+ }));
233
+ });
234
+ try {
235
+ // Load the matches
236
+ try {
237
+ await this.loadMatches(pendingMatches.map(d => d.id));
238
+ } catch (err) {
239
+ // swallow this error, since we'll display the
240
+ // errors on the route components
241
+ }
242
+
243
+ // Only apply the latest transition
244
+ if (latestPromise = checkLatest()) {
245
+ return latestPromise;
246
+ }
247
+ const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
248
+ const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
249
+ const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
250
+ this.__store.setState(s => ({
251
+ ...s,
252
+ status: 'idle',
253
+ resolvedLocation: s.location,
254
+ matchIds: s.pendingMatchIds,
255
+ pendingMatchIds: []
256
+ }));
257
+ [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
258
+ matchIds.forEach(id => {
259
+ const match = this.getRouteMatch(id);
260
+ const route = this.getRoute(match.routeId);
261
+ route.options[hook]?.(match);
262
+ });
263
+ });
264
+ this.#emit({
265
+ type: 'onLoad',
266
+ from: prevLocation,
267
+ to: this.state.location,
268
+ pathChanged: pathDidChange
269
+ });
270
+ resolve();
271
+ } catch (err) {
272
+ // Only apply the latest transition
273
+ if (latestPromise = checkLatest()) {
274
+ return latestPromise;
275
+ }
276
+ reject(err);
277
+ }
278
+ });
279
+ this.latestLoadPromise = promise;
280
+ this.latestLoadPromise.then(() => {
281
+ this.cleanMatches();
282
+ });
283
+ return this.latestLoadPromise;
284
+ };
285
+ #mergeMatches = (prevMatchesById, nextMatches) => {
286
+ let matchesById = {
287
+ ...prevMatchesById
288
+ };
289
+ nextMatches.forEach(match => {
290
+ if (!matchesById[match.id]) {
291
+ matchesById[match.id] = match;
292
+ }
293
+ matchesById[match.id] = {
294
+ ...matchesById[match.id],
295
+ ...match
296
+ };
297
+ });
298
+ return matchesById;
299
+ };
300
+ getRoute = id => {
301
+ const route = this.routesById[id];
302
+ invariant__default["default"](route, `Route with id "${id}" not found`);
303
+ return route;
304
+ };
305
+ preloadRoute = async (navigateOpts = this.state.location) => {
306
+ let next = this.buildLocation(navigateOpts);
307
+ const matches = this.matchRoutes(next.pathname, next.search, {
308
+ throwOnError: true
309
+ });
310
+ this.__store.setState(s => {
311
+ return {
312
+ ...s,
313
+ matchesById: this.#mergeMatches(s.matchesById, matches)
314
+ };
315
+ });
316
+ await this.loadMatches(matches.map(d => d.id), {
317
+ preload: true,
318
+ maxAge: navigateOpts.maxAge
319
+ });
320
+ return [utils.last(matches), matches];
321
+ };
322
+ cleanMatches = () => {
323
+ const now = Date.now();
324
+ const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
325
+ const route = this.getRoute(match.routeId);
326
+ return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
327
+ }).map(d => d.id);
328
+ if (outdatedMatchIds.length) {
329
+ this.__store.setState(s => {
330
+ const matchesById = {
331
+ ...s.matchesById
332
+ };
333
+ outdatedMatchIds.forEach(id => {
334
+ delete matchesById[id];
335
+ });
336
+ return {
337
+ ...s,
338
+ matchesById
339
+ };
340
+ });
341
+ }
342
+ };
343
+ matchRoutes = (pathname, locationSearch, opts) => {
344
+ let routeParams = {};
345
+ let foundRoute = this.flatRoutes.find(route => {
346
+ const matchedParams = path.matchPathname(this.basepath, path.trimPathRight(pathname), {
347
+ to: route.fullPath,
348
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
349
+ });
350
+ if (matchedParams) {
351
+ routeParams = matchedParams;
352
+ return true;
353
+ }
354
+ return false;
355
+ });
356
+ let routeCursor = foundRoute || this.routesById['__root__'];
357
+ let matchedRoutes = [routeCursor];
358
+ // let includingLayouts = true
359
+ while (routeCursor?.parentRoute) {
360
+ routeCursor = routeCursor.parentRoute;
361
+ if (routeCursor) matchedRoutes.unshift(routeCursor);
362
+ }
363
+
364
+ // Existing matches are matches that are already loaded along with
365
+ // pending matches that are still loading
366
+
367
+ const parseErrors = matchedRoutes.map(route => {
368
+ let parsedParamsError;
369
+ if (route.options.parseParams) {
370
+ try {
371
+ const parsedParams = route.options.parseParams(routeParams);
372
+ // Add the parsed params to the accumulated params bag
373
+ Object.assign(routeParams, parsedParams);
374
+ } catch (err) {
375
+ parsedParamsError = new PathParamError(err.message, {
376
+ cause: err
377
+ });
378
+ if (opts?.throwOnError) {
379
+ throw parsedParamsError;
380
+ }
381
+ return parsedParamsError;
382
+ }
383
+ }
384
+ return;
385
+ });
386
+ const matches = matchedRoutes.map((route, index) => {
387
+ const interpolatedPath = path.interpolatePath(route.path, routeParams);
388
+ const loaderContext = route.options.loaderContext ? route.options.loaderContext({
389
+ search: locationSearch
390
+ }) : undefined;
391
+ const matchId = JSON.stringify([path.interpolatePath(route.id, routeParams, true), loaderContext].filter(d => d !== undefined), (key, value) => {
392
+ if (typeof value === 'function') {
393
+ console.info(route);
394
+ invariant__default["default"](false, `Cannot return functions and other non-serializable values from routeOptions.loaderContext! Please use routeOptions.beforeLoad to do this. Route info is logged above 👆`);
395
+ }
396
+ if (typeof value === 'object' && value !== null) {
397
+ return Object.fromEntries(Object.keys(value).sort().map(key => [key, value[key]]));
398
+ }
399
+ return value;
400
+ });
401
+
402
+ // Waste not, want not. If we already have a match for this route,
403
+ // reuse it. This is important for layout routes, which might stick
404
+ // around between navigation actions that only change leaf routes.
405
+ const existingMatch = this.getRouteMatch(matchId);
406
+ if (existingMatch) {
407
+ return {
408
+ ...existingMatch
409
+ };
410
+ }
411
+
412
+ // Create a fresh route match
413
+ const hasLoaders = !!(route.options.loader || componentTypes.some(d => route.options[d]?.preload));
414
+ const routeMatch = {
415
+ id: matchId,
416
+ loaderContext,
417
+ routeId: route.id,
418
+ params: routeParams,
419
+ pathname: path.joinPaths([this.basepath, interpolatedPath]),
420
+ updatedAt: Date.now(),
421
+ maxAge: -1,
422
+ preloadMaxAge: -1,
423
+ routeSearch: {},
424
+ search: {},
425
+ status: hasLoaders ? 'pending' : 'success',
426
+ isFetching: false,
427
+ invalid: false,
428
+ error: undefined,
429
+ paramsError: parseErrors[index],
430
+ searchError: undefined,
431
+ loaderData: undefined,
432
+ loadPromise: Promise.resolve(),
433
+ context: undefined,
434
+ abortController: new AbortController(),
435
+ fetchedAt: 0
436
+ };
437
+ return routeMatch;
438
+ });
439
+
440
+ // Take each match and resolve its search params and context
441
+ // This has to happen after the matches are created or found
442
+ // so that we can use the parent match's search params and context
443
+ matches.forEach((match, i) => {
444
+ const parentMatch = matches[i - 1];
445
+ const route = this.getRoute(match.routeId);
446
+ const searchInfo = (() => {
447
+ // Validate the search params and stabilize them
448
+ const parentSearchInfo = {
449
+ search: parentMatch?.search ?? locationSearch,
450
+ routeSearch: parentMatch?.routeSearch ?? locationSearch
451
+ };
452
+ try {
453
+ const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
454
+ let routeSearch = validator?.(parentSearchInfo.search) ?? {};
455
+ let search = {
456
+ ...parentSearchInfo.search,
457
+ ...routeSearch
458
+ };
459
+ routeSearch = utils.replaceEqualDeep(match.routeSearch, routeSearch);
460
+ search = utils.replaceEqualDeep(match.search, search);
461
+ return {
462
+ routeSearch,
463
+ search,
464
+ searchDidChange: match.routeSearch !== routeSearch
465
+ };
466
+ } catch (err) {
467
+ match.searchError = new SearchParamError(err.message, {
468
+ cause: err
469
+ });
470
+ if (opts?.throwOnError) {
471
+ throw match.searchError;
472
+ }
473
+ return parentSearchInfo;
474
+ }
475
+ })();
476
+ Object.assign(match, searchInfo);
477
+ });
478
+ return matches;
479
+ };
480
+ loadMatches = async (matchIds, opts) => {
481
+ const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
482
+ if (!opts?.preload) {
483
+ getFreshMatches().forEach(match => {
484
+ // Update each match with its latest route data
485
+ this.setRouteMatch(match.id, s => ({
486
+ ...s,
487
+ routeSearch: match.routeSearch,
488
+ search: match.search,
489
+ context: match.context,
490
+ error: match.error,
491
+ paramsError: match.paramsError,
492
+ searchError: match.searchError,
493
+ params: match.params,
494
+ preloadMaxAge: 0
495
+ }));
496
+ });
497
+ }
498
+ let firstBadMatchIndex;
499
+
500
+ // Check each match middleware to see if the route can be accessed
501
+ try {
502
+ for (const [index, match] of getFreshMatches().entries()) {
503
+ const parentMatch = getFreshMatches()[index - 1];
504
+ const route = this.getRoute(match.routeId);
505
+ const handleError = (err, code) => {
506
+ err.routerCode = code;
507
+ firstBadMatchIndex = firstBadMatchIndex ?? index;
508
+ if (isRedirect(err)) {
509
+ throw err;
510
+ }
511
+ try {
512
+ route.options.onError?.(err);
513
+ } catch (errorHandlerErr) {
514
+ err = errorHandlerErr;
515
+ if (isRedirect(errorHandlerErr)) {
516
+ throw errorHandlerErr;
517
+ }
518
+ }
519
+ this.setRouteMatch(match.id, s => ({
520
+ ...s,
521
+ error: err,
522
+ status: 'error',
523
+ updatedAt: Date.now()
524
+ }));
525
+ };
526
+ if (match.paramsError) {
527
+ handleError(match.paramsError, 'PARSE_PARAMS');
528
+ }
529
+ if (match.searchError) {
530
+ handleError(match.searchError, 'VALIDATE_SEARCH');
531
+ }
532
+ let didError = false;
533
+ const parentContext = parentMatch?.context ?? this?.options.context ?? {};
534
+ try {
535
+ const beforeLoadContext = (await route.options.beforeLoad?.({
536
+ abortController: match.abortController,
537
+ params: match.params,
538
+ preload: !!opts?.preload,
539
+ context: {
540
+ ...parentContext,
541
+ ...match.loaderContext
542
+ }
543
+ })) ?? {};
544
+ const context = {
545
+ ...parentContext,
546
+ ...match.loaderContext,
547
+ ...beforeLoadContext
548
+ };
549
+ this.setRouteMatch(match.id, s => ({
550
+ ...s,
551
+ context: utils.replaceEqualDeep(s.context, context)
552
+ }));
553
+ } catch (err) {
554
+ handleError(err, 'BEFORE_LOAD');
555
+ didError = true;
556
+ }
557
+
558
+ // If we errored, do not run the next matches' middleware
559
+ if (didError) {
560
+ break;
561
+ }
562
+ }
563
+ } catch (err) {
564
+ if (isRedirect(err)) {
565
+ if (!opts?.preload) this.navigate(err);
566
+ return;
567
+ }
568
+ throw err;
569
+ }
570
+ const validResolvedMatches = getFreshMatches().slice(0, firstBadMatchIndex);
571
+ const matchPromises = [];
572
+ validResolvedMatches.forEach((match, index) => {
573
+ matchPromises.push((async () => {
574
+ const parentMatchPromise = matchPromises[index - 1];
575
+ const route = this.getRoute(match.routeId);
576
+ if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
577
+ preload: opts?.preload
578
+ })) {
579
+ return this.getRouteMatch(match.id)?.loadPromise;
580
+ }
581
+ const fetchedAt = Date.now();
582
+ const checkLatest = () => {
583
+ const latest = this.getRouteMatch(match.id);
584
+ return latest && latest.fetchedAt !== fetchedAt ? latest.loadPromise : undefined;
585
+ };
586
+ const handleIfRedirect = err => {
587
+ if (isRedirect(err)) {
588
+ if (!opts?.preload) {
589
+ this.navigate(err);
590
+ }
591
+ return true;
592
+ }
593
+ return false;
594
+ };
595
+ const load = async () => {
596
+ let latestPromise;
597
+ try {
598
+ const componentsPromise = Promise.all(componentTypes.map(async type => {
599
+ const component = route.options[type];
600
+ if (component?.preload) {
601
+ await component.preload();
602
+ }
603
+ }));
604
+ const loaderPromise = route.options.loader?.({
605
+ params: match.params,
606
+ preload: !!opts?.preload,
607
+ parentMatchPromise,
608
+ abortController: match.abortController,
609
+ context: match.context
610
+ });
611
+ const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
612
+ if (latestPromise = checkLatest()) return await latestPromise;
613
+ this.setRouteMatchData(match.id, () => loader, opts);
614
+ } catch (error) {
615
+ if (latestPromise = checkLatest()) return await latestPromise;
616
+ if (handleIfRedirect(error)) return;
617
+ try {
618
+ route.options.onError?.(error);
619
+ } catch (onErrorError) {
620
+ error = onErrorError;
621
+ if (handleIfRedirect(onErrorError)) return;
622
+ }
623
+ this.setRouteMatch(match.id, s => ({
624
+ ...s,
625
+ error,
626
+ status: 'error',
627
+ isFetching: false,
628
+ updatedAt: Date.now()
629
+ }));
630
+ }
631
+ };
632
+ let loadPromise;
633
+ this.__store.batch(() => {
634
+ this.setRouteMatch(match.id, s => ({
635
+ ...s,
636
+ // status: s.status !== 'success' ? 'pending' : s.status,
637
+ isFetching: true,
638
+ fetchedAt,
639
+ invalid: false
640
+ }));
641
+ loadPromise = load();
642
+ this.setRouteMatch(match.id, s => ({
643
+ ...s,
644
+ loadPromise
645
+ }));
646
+ });
647
+ await loadPromise;
648
+ })());
649
+ });
650
+ await Promise.all(matchPromises);
651
+ };
652
+ resolvePath = (from, path$1) => {
653
+ return path.resolvePath(this.basepath, from, path.cleanPath(path$1));
654
+ };
655
+ navigate = async ({
656
+ from,
657
+ to = '',
658
+ ...rest
659
+ }) => {
660
+ // If this link simply reloads the current route,
661
+ // make sure it has a new key so it will trigger a data refresh
662
+
663
+ // If this `to` is a valid external URL, return
664
+ // null for LinkUtils
665
+ const toString = String(to);
666
+ const fromString = typeof from === 'undefined' ? from : String(from);
667
+ let isExternal;
668
+ try {
669
+ new URL(`${toString}`);
670
+ isExternal = true;
671
+ } catch (e) {}
672
+ invariant__default["default"](!isExternal, 'Attempting to navigate to external url with this.navigate!');
673
+ return this.#buildAndCommitLocation({
674
+ ...rest,
675
+ from: fromString,
676
+ to: toString
677
+ });
678
+ };
679
+ matchRoute = (location, opts) => {
680
+ location = {
681
+ ...location,
682
+ to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
683
+ };
684
+ const next = this.buildLocation(location);
685
+ if (opts?.pending && this.state.status !== 'pending') {
686
+ return false;
687
+ }
688
+ const baseLocation = opts?.pending ? this.state.location : this.state.resolvedLocation;
689
+ if (!baseLocation) {
690
+ return false;
691
+ }
692
+ const match = path.matchPathname(this.basepath, baseLocation.pathname, {
693
+ ...opts,
694
+ to: next.pathname
695
+ });
696
+ if (!match) {
697
+ return false;
698
+ }
699
+ if (opts?.includeSearch ?? true) {
700
+ return utils.partialDeepEqual(baseLocation.search, next.search) ? match : false;
701
+ }
702
+ return match;
703
+ };
704
+ buildLink = dest => {
705
+ // If this link simply reloads the current route,
706
+ // make sure it has a new key so it will trigger a data refresh
707
+
708
+ // If this `to` is a valid external URL, return
709
+ // null for LinkUtils
710
+
711
+ const {
712
+ to,
713
+ preload: userPreload,
714
+ preloadDelay: userPreloadDelay,
715
+ activeOptions,
716
+ disabled,
717
+ target,
718
+ replace,
719
+ resetScroll
720
+ } = dest;
721
+ try {
722
+ new URL(`${to}`);
723
+ return {
724
+ type: 'external',
725
+ href: to
726
+ };
727
+ } catch (e) {}
728
+ const nextOpts = dest;
729
+ const next = this.buildLocation(nextOpts);
730
+ const preload = userPreload ?? this.options.defaultPreload;
731
+ const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
732
+
733
+ // Compare path/hash for matches
734
+ const currentPathSplit = this.state.location.pathname.split('/');
735
+ const nextPathSplit = next.pathname.split('/');
736
+ const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
737
+ // Combine the matches based on user options
738
+ const pathTest = activeOptions?.exact ? this.state.location.pathname === next.pathname : pathIsFuzzyEqual;
739
+ const hashTest = activeOptions?.includeHash ? this.state.location.hash === next.hash : true;
740
+ const searchTest = activeOptions?.includeSearch ?? true ? utils.partialDeepEqual(this.state.location.search, next.search) : true;
741
+
742
+ // The final "active" test
743
+ const isActive = pathTest && hashTest && searchTest;
744
+
745
+ // The click handler
746
+ const handleClick = e => {
747
+ if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
748
+ e.preventDefault();
749
+
750
+ // All is well? Navigate!
751
+ this.#commitLocation({
752
+ ...next,
753
+ replace,
754
+ resetScroll
755
+ });
756
+ }
757
+ };
758
+
759
+ // The click handler
760
+ const handleFocus = e => {
761
+ if (preload) {
762
+ this.preloadRoute(nextOpts).catch(err => {
763
+ console.warn(err);
764
+ console.warn(preloadWarning);
765
+ });
766
+ }
767
+ };
768
+ const handleTouchStart = e => {
769
+ this.preloadRoute(nextOpts).catch(err => {
770
+ console.warn(err);
771
+ console.warn(preloadWarning);
772
+ });
773
+ };
774
+ const handleEnter = e => {
775
+ const target = e.target || {};
776
+ if (preload) {
777
+ if (target.preloadTimeout) {
778
+ return;
779
+ }
780
+ target.preloadTimeout = setTimeout(() => {
781
+ target.preloadTimeout = null;
782
+ this.preloadRoute(nextOpts).catch(err => {
783
+ console.warn(err);
784
+ console.warn(preloadWarning);
785
+ });
786
+ }, preloadDelay);
787
+ }
788
+ };
789
+ const handleLeave = e => {
790
+ const target = e.target || {};
791
+ if (target.preloadTimeout) {
792
+ clearTimeout(target.preloadTimeout);
793
+ target.preloadTimeout = null;
794
+ }
795
+ };
796
+ return {
797
+ type: 'internal',
798
+ next,
799
+ handleFocus,
800
+ handleClick,
801
+ handleEnter,
802
+ handleLeave,
803
+ handleTouchStart,
804
+ isActive,
805
+ disabled
806
+ };
807
+ };
808
+ dehydrate = () => {
809
+ return {
810
+ state: {
811
+ matchIds: this.state.matchIds,
812
+ dehydratedMatches: this.state.matches.map(d => utils.pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
813
+ }
814
+ };
815
+ };
816
+ hydrate = async __do_not_use_server_ctx => {
817
+ let _ctx = __do_not_use_server_ctx;
818
+ // Client hydrates from window
819
+ if (typeof document !== 'undefined') {
820
+ _ctx = window.__TSR_DEHYDRATED__;
821
+ }
822
+ invariant__default["default"](_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
823
+ const ctx = _ctx;
824
+ this.dehydratedData = ctx.payload;
825
+ this.options.hydrate?.(ctx.payload);
826
+ const dehydratedState = ctx.router.state;
827
+ let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
828
+ const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
829
+ invariant__default["default"](dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
830
+ if (dehydratedMatch) {
831
+ return {
832
+ ...match,
833
+ ...dehydratedMatch
834
+ };
835
+ }
836
+ return match;
837
+ });
838
+ this.__store.setState(s => {
839
+ return {
840
+ ...s,
841
+ matchIds: dehydratedState.matchIds,
842
+ matches,
843
+ matchesById: this.#mergeMatches(s.matchesById, matches)
844
+ };
845
+ });
846
+ };
847
+ injectedHtml = [];
848
+ injectHtml = async html => {
849
+ this.injectedHtml.push(html);
850
+ };
851
+ dehydrateData = (key, getData) => {
852
+ if (typeof document === 'undefined') {
853
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
854
+ this.injectHtml(async () => {
855
+ const id = `__TSR_DEHYDRATED__${strKey}`;
856
+ const data = typeof getData === 'function' ? await getData() : getData;
857
+ return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${escapeJSON(strKey)}"] = ${JSON.stringify(data)}
858
+ ;(() => {
859
+ var el = document.getElementById('${id}')
860
+ el.parentElement.removeChild(el)
861
+ })()
862
+ </script>`;
863
+ });
864
+ return () => this.hydrateData(key);
865
+ }
866
+ return () => undefined;
867
+ };
868
+ hydrateData = key => {
869
+ if (typeof document !== 'undefined') {
870
+ const strKey = typeof key === 'string' ? key : JSON.stringify(key);
871
+ return window[`__TSR_DEHYDRATED__${strKey}`];
872
+ }
873
+ return undefined;
874
+ };
875
+
876
+ // resolveMatchPromise = (matchId: string, key: string, value: any) => {
877
+ // this.state.matches
878
+ // .find((d) => d.id === matchId)
879
+ // ?.__promisesByKey[key]?.resolve(value)
880
+ // }
881
+
882
+ #processRoutes = routeTree => {
883
+ this.routeTree = routeTree;
884
+ this.routesById = {};
885
+ this.routesByPath = {};
886
+ this.flatRoutes = [];
887
+ const recurseRoutes = routes => {
888
+ routes.forEach((route, i) => {
889
+ route.init({
890
+ originalIndex: i,
891
+ router: this
892
+ });
893
+ const existingRoute = this.routesById[route.id];
894
+ invariant__default["default"](!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
895
+ this.routesById[route.id] = route;
896
+ if (!route.isRoot && route.path) {
897
+ const trimmedFullPath = path.trimPathRight(route.fullPath);
898
+ if (!this.routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
899
+ this.routesByPath[trimmedFullPath] = route;
900
+ }
901
+ }
902
+ const children = route.children;
903
+ if (children?.length) {
904
+ recurseRoutes(children);
905
+ }
906
+ });
907
+ };
908
+ recurseRoutes([routeTree]);
909
+ this.flatRoutes = Object.values(this.routesByPath).map((d, i) => {
910
+ const trimmed = path.trimPath(d.fullPath);
911
+ const parsed = path.parsePathname(trimmed);
912
+ while (parsed.length > 1 && parsed[0]?.value === '/') {
913
+ parsed.shift();
914
+ }
915
+ const score = parsed.map(d => {
916
+ if (d.type === 'param') {
917
+ return 0.5;
918
+ }
919
+ if (d.type === 'wildcard') {
920
+ return 0.25;
921
+ }
922
+ return 1;
923
+ });
924
+ return {
925
+ child: d,
926
+ trimmed,
927
+ parsed,
928
+ index: i,
929
+ score
930
+ };
931
+ }).sort((a, b) => {
932
+ let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
933
+ if (isIndex !== 0) return isIndex;
934
+ const length = Math.min(a.score.length, b.score.length);
935
+
936
+ // Sort by length of score
937
+ if (a.score.length !== b.score.length) {
938
+ return b.score.length - a.score.length;
939
+ }
940
+
941
+ // Sort by min available score
942
+ for (let i = 0; i < length; i++) {
943
+ if (a.score[i] !== b.score[i]) {
944
+ return b.score[i] - a.score[i];
945
+ }
946
+ }
947
+
948
+ // Sort by min available parsed value
949
+ for (let i = 0; i < length; i++) {
950
+ if (a.parsed[i].value !== b.parsed[i].value) {
951
+ return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
952
+ }
953
+ }
954
+
955
+ // Sort by length of trimmed full path
956
+ if (a.trimmed !== b.trimmed) {
957
+ return a.trimmed > b.trimmed ? 1 : -1;
958
+ }
959
+
960
+ // Sort by original index
961
+ return a.index - b.index;
962
+ }).map((d, i) => {
963
+ d.child.rank = i;
964
+ return d.child;
965
+ });
966
+ };
967
+ #parseLocation = previousLocation => {
968
+ const parse = ({
969
+ pathname,
970
+ search,
971
+ hash,
972
+ state
973
+ }) => {
974
+ const parsedSearch = this.options.parseSearch(search);
975
+ return {
976
+ pathname: pathname,
977
+ searchStr: search,
978
+ search: utils.replaceEqualDeep(previousLocation?.search, parsedSearch),
979
+ hash: hash.split('#').reverse()[0] ?? '',
980
+ href: `${pathname}${search}${hash}`,
981
+ state: utils.replaceEqualDeep(previousLocation?.state, state)
982
+ };
983
+ };
984
+ const location = parse(this.history.location);
985
+ let {
986
+ __tempLocation,
987
+ __tempKey
988
+ } = location.state;
989
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
990
+ // Sync up the location keys
991
+ const parsedTempLocation = parse(__tempLocation);
992
+ parsedTempLocation.state.key = location.state.key;
993
+ delete parsedTempLocation.state.__tempLocation;
994
+ return {
995
+ ...parsedTempLocation,
996
+ maskedLocation: location
997
+ };
998
+ }
999
+ return location;
1000
+ };
1001
+ buildLocation = (opts = {}) => {
1002
+ const build = (dest = {}, matches) => {
1003
+ const from = this.state.location;
1004
+ const fromPathname = dest.from ?? from.pathname;
1005
+ let pathname = path.resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1006
+ const fromMatches = this.matchRoutes(fromPathname, from.search);
1007
+ const prevParams = {
1008
+ ...utils.last(fromMatches)?.params
1009
+ };
1010
+ let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
1011
+ if (nextParams) {
1012
+ matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1013
+ nextParams = {
1014
+ ...nextParams,
1015
+ ...fn(nextParams)
1016
+ };
1017
+ });
1018
+ }
1019
+ pathname = path.interpolatePath(pathname, nextParams ?? {});
1020
+ const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1021
+ const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1022
+
1023
+ // Pre filters first
1024
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1025
+
1026
+ // Then the link/navigate function
1027
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1028
+ : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1029
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1030
+ : {};
1031
+
1032
+ // Then post filters
1033
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1034
+ const search = utils.replaceEqualDeep(from.search, postFilteredSearch);
1035
+ const searchStr = this.options.stringifySearch(search);
1036
+ const hash = dest.hash === true ? from.hash : dest.hash ? utils.functionalUpdate(dest.hash, from.hash) : from.hash;
1037
+ const hashStr = hash ? `#${hash}` : '';
1038
+ let nextState = dest.state === true ? from.state : dest.state ? utils.functionalUpdate(dest.state, from.state) : from.state;
1039
+ nextState = utils.replaceEqualDeep(from.state, nextState);
1040
+ return {
1041
+ pathname,
1042
+ search,
1043
+ searchStr,
1044
+ state: nextState,
1045
+ hash,
1046
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1047
+ unmaskOnReload: dest.unmaskOnReload
1048
+ };
1049
+ };
1050
+ const buildWithMatches = (dest = {}, maskedDest) => {
1051
+ let next = build(dest);
1052
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
1053
+ if (!maskedNext) {
1054
+ let params = {};
1055
+ let foundMask = this.options.routeMasks?.find(d => {
1056
+ const match = path.matchPathname(this.basepath, next.pathname, {
1057
+ to: d.from,
1058
+ fuzzy: false
1059
+ });
1060
+ if (match) {
1061
+ params = match;
1062
+ return true;
1063
+ }
1064
+ return false;
1065
+ });
1066
+ if (foundMask) {
1067
+ foundMask = {
1068
+ ...foundMask,
1069
+ from: path.interpolatePath(foundMask.from, params)
1070
+ };
1071
+ maskedDest = foundMask;
1072
+ maskedNext = build(maskedDest);
1073
+ }
1074
+ }
1075
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1076
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1077
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1078
+ const final = build(dest, nextMatches);
1079
+ if (maskedFinal) {
1080
+ final.maskedLocation = maskedFinal;
1081
+ }
1082
+ return final;
1083
+ };
1084
+ if (opts.mask) {
1085
+ return buildWithMatches(opts, {
1086
+ ...utils.pick(opts, ['from']),
1087
+ ...opts.mask
1088
+ });
1089
+ }
1090
+ return buildWithMatches(opts);
1091
+ };
1092
+ #buildAndCommitLocation = ({
1093
+ replace,
1094
+ resetScroll,
1095
+ ...rest
1096
+ } = {}) => {
1097
+ const location = this.buildLocation(rest);
1098
+ return this.#commitLocation({
1099
+ ...location,
1100
+ replace,
1101
+ resetScroll
1102
+ });
1103
+ };
1104
+ #commitLocation = async next => {
1105
+ if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1106
+ const isSameUrl = this.state.location.href === next.href;
1107
+
1108
+ // If the next urls are the same and we're not replacing,
1109
+ // do nothing
1110
+ if (isSameUrl && !next.replace) {
1111
+ return;
1112
+ }
1113
+ let {
1114
+ maskedLocation,
1115
+ ...nextHistory
1116
+ } = next;
1117
+ if (maskedLocation) {
1118
+ nextHistory = {
1119
+ ...maskedLocation,
1120
+ state: {
1121
+ ...maskedLocation.state,
1122
+ __tempKey: undefined,
1123
+ __tempLocation: {
1124
+ ...nextHistory,
1125
+ search: nextHistory.searchStr,
1126
+ state: {
1127
+ ...nextHistory.state,
1128
+ __tempKey: undefined,
1129
+ __tempLocation: undefined,
1130
+ key: undefined
1131
+ }
1132
+ }
1133
+ }
1134
+ };
1135
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
1136
+ nextHistory.state.__tempKey = this.tempLocationKey;
1137
+ }
1138
+ }
1139
+ this.history[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
1140
+ this.resetNextScroll = next.resetScroll ?? true;
1141
+ return this.latestLoadPromise;
1142
+ };
1143
+ getRouteMatch = id => {
1144
+ return this.state.matchesById[id];
1145
+ };
1146
+ setRouteMatch = (id, updater) => {
1147
+ this.__store.setState(prev => {
1148
+ if (!prev.matchesById[id]) {
1149
+ return prev;
1150
+ }
1151
+ return {
1152
+ ...prev,
1153
+ matchesById: {
1154
+ ...prev.matchesById,
1155
+ [id]: updater(prev.matchesById[id])
1156
+ }
1157
+ };
1158
+ });
1159
+ };
1160
+ setRouteMatchData = (id, updater, opts) => {
1161
+ const match = this.getRouteMatch(id);
1162
+ if (!match) return;
1163
+ const route = this.getRoute(match.routeId);
1164
+ const updatedAt = opts?.updatedAt ?? Date.now();
1165
+ const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
1166
+ const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
1167
+ this.setRouteMatch(id, s => ({
1168
+ ...s,
1169
+ error: undefined,
1170
+ status: 'success',
1171
+ isFetching: false,
1172
+ updatedAt: updatedAt,
1173
+ loaderData: utils.functionalUpdate(updater, s.loaderData),
1174
+ preloadMaxAge,
1175
+ maxAge
1176
+ }));
1177
+ };
1178
+ invalidate = async opts => {
1179
+ if (opts?.matchId) {
1180
+ this.setRouteMatch(opts.matchId, s => ({
1181
+ ...s,
1182
+ invalid: true
1183
+ }));
1184
+ const matchIndex = this.state.matches.findIndex(d => d.id === opts.matchId);
1185
+ const childMatch = this.state.matches[matchIndex + 1];
1186
+ if (childMatch) {
1187
+ return this.invalidate({
1188
+ matchId: childMatch.id,
1189
+ reload: false,
1190
+ __fromFocus: opts.__fromFocus
1191
+ });
1192
+ }
1193
+ } else {
1194
+ this.__store.batch(() => {
1195
+ Object.values(this.state.matchesById).forEach(match => {
1196
+ const route = this.getRoute(match.routeId);
1197
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
1198
+ if (shouldInvalidate) {
1199
+ this.setRouteMatch(match.id, s => ({
1200
+ ...s,
1201
+ invalid: true
1202
+ }));
1203
+ }
1204
+ });
1205
+ });
1206
+ }
1207
+ if (opts?.reload ?? true) {
1208
+ return this.load();
1209
+ }
1210
+ };
1211
+ }
1212
+
1213
+ // Detect if we're in the DOM
1214
+ const isServer = typeof window === 'undefined' || !window.document.createElement;
1215
+ function getInitialRouterState() {
1216
+ return {
1217
+ status: 'idle',
1218
+ isFetching: false,
1219
+ resolvedLocation: null,
1220
+ location: null,
1221
+ matchesById: {},
1222
+ matchIds: [],
1223
+ pendingMatchIds: [],
1224
+ matches: [],
1225
+ pendingMatches: [],
1226
+ renderedMatchIds: [],
1227
+ renderedMatches: [],
1228
+ lastUpdated: Date.now()
1229
+ };
1230
+ }
1231
+ function isCtrlEvent(e) {
1232
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
1233
+ }
1234
+ function redirect(opts) {
1235
+ opts.isRedirect = true;
1236
+ return opts;
1237
+ }
1238
+ function isRedirect(obj) {
1239
+ return !!obj?.isRedirect;
1240
+ }
1241
+ class SearchParamError extends Error {}
1242
+ class PathParamError extends Error {}
1243
+ function escapeJSON(jsonString) {
1244
+ return jsonString.replace(/\\/g, '\\\\') // Escape backslashes
1245
+ .replace(/'/g, "\\'") // Escape single quotes
1246
+ .replace(/"/g, '\\"'); // Escape double quotes
1247
+ }
1248
+
1249
+ // A function that takes an import() argument which is a function and returns a new function that will
1250
+ // proxy arguments from the caller to the imported function, retaining all type
1251
+ // information along the way
1252
+ function lazyFn(fn, key) {
1253
+ return async (...args) => {
1254
+ const imported = await fn();
1255
+ return imported[key || 'default'](...args);
1256
+ };
1257
+ }
1258
+ function isMatchInvalid(match, opts) {
1259
+ const now = Date.now();
1260
+ if (match.invalid) {
1261
+ return true;
1262
+ }
1263
+ if (opts?.preload) {
1264
+ return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
1265
+ }
1266
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
1267
+ }
1268
+
1269
+ exports.PathParamError = PathParamError;
1270
+ exports.Router = Router;
1271
+ exports.SearchParamError = SearchParamError;
1272
+ exports.componentTypes = componentTypes;
1273
+ exports.isMatchInvalid = isMatchInvalid;
1274
+ exports.isRedirect = isRedirect;
1275
+ exports.lazyFn = lazyFn;
1276
+ exports.redirect = redirect;
1277
+ //# sourceMappingURL=router.js.map