@tanstack/router-core 0.0.1-beta.5 → 0.0.1-beta.51

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 (71) hide show
  1. package/LICENSE +21 -0
  2. package/build/cjs/actions.js +94 -0
  3. package/build/cjs/actions.js.map +1 -0
  4. package/build/cjs/history.js +163 -0
  5. package/build/cjs/history.js.map +1 -0
  6. package/build/cjs/{packages/router-core/src/index.js → index.js} +26 -11
  7. package/build/cjs/{packages/router-core/src/index.js.map → index.js.map} +1 -1
  8. package/build/cjs/interop.js +175 -0
  9. package/build/cjs/interop.js.map +1 -0
  10. package/build/cjs/{packages/router-core/src/path.js → path.js} +23 -48
  11. package/build/cjs/path.js.map +1 -0
  12. package/build/cjs/{packages/router-core/src/qss.js → qss.js} +8 -13
  13. package/build/cjs/qss.js.map +1 -0
  14. package/build/cjs/route.js +33 -0
  15. package/build/cjs/route.js.map +1 -0
  16. package/build/cjs/{packages/router-core/src/routeConfig.js → routeConfig.js} +13 -18
  17. package/build/cjs/routeConfig.js.map +1 -0
  18. package/build/cjs/routeMatch.js +237 -0
  19. package/build/cjs/routeMatch.js.map +1 -0
  20. package/build/cjs/router.js +824 -0
  21. package/build/cjs/router.js.map +1 -0
  22. package/build/cjs/{packages/router-core/src/searchParams.js → searchParams.js} +10 -12
  23. package/build/cjs/searchParams.js.map +1 -0
  24. package/build/cjs/store.js +54 -0
  25. package/build/cjs/store.js.map +1 -0
  26. package/build/cjs/utils.js +47 -0
  27. package/build/cjs/utils.js.map +1 -0
  28. package/build/esm/index.js +1386 -2058
  29. package/build/esm/index.js.map +1 -1
  30. package/build/stats-html.html +59 -49
  31. package/build/stats-react.json +248 -193
  32. package/build/types/index.d.ts +385 -317
  33. package/build/umd/index.development.js +1489 -2142
  34. package/build/umd/index.development.js.map +1 -1
  35. package/build/umd/index.production.js +1 -1
  36. package/build/umd/index.production.js.map +1 -1
  37. package/package.json +6 -4
  38. package/src/actions.ts +157 -0
  39. package/src/frameworks.ts +2 -2
  40. package/src/history.ts +199 -0
  41. package/src/index.ts +4 -7
  42. package/src/interop.ts +169 -0
  43. package/src/link.ts +87 -44
  44. package/src/path.ts +12 -8
  45. package/src/route.ts +36 -229
  46. package/src/routeConfig.ts +99 -102
  47. package/src/routeInfo.ts +28 -25
  48. package/src/routeMatch.ts +293 -322
  49. package/src/router.ts +1060 -884
  50. package/src/store.ts +52 -0
  51. package/src/utils.ts +14 -72
  52. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js +0 -33
  53. package/build/cjs/_virtual/_rollupPluginBabelHelpers.js.map +0 -1
  54. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js +0 -33
  55. package/build/cjs/node_modules/@babel/runtime/helpers/esm/extends.js.map +0 -1
  56. package/build/cjs/node_modules/history/index.js +0 -815
  57. package/build/cjs/node_modules/history/index.js.map +0 -1
  58. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js +0 -30
  59. package/build/cjs/node_modules/tiny-invariant/dist/esm/tiny-invariant.js.map +0 -1
  60. package/build/cjs/packages/router-core/src/path.js.map +0 -1
  61. package/build/cjs/packages/router-core/src/qss.js.map +0 -1
  62. package/build/cjs/packages/router-core/src/route.js +0 -161
  63. package/build/cjs/packages/router-core/src/route.js.map +0 -1
  64. package/build/cjs/packages/router-core/src/routeConfig.js.map +0 -1
  65. package/build/cjs/packages/router-core/src/routeMatch.js +0 -266
  66. package/build/cjs/packages/router-core/src/routeMatch.js.map +0 -1
  67. package/build/cjs/packages/router-core/src/router.js +0 -797
  68. package/build/cjs/packages/router-core/src/router.js.map +0 -1
  69. package/build/cjs/packages/router-core/src/searchParams.js.map +0 -1
  70. package/build/cjs/packages/router-core/src/utils.js +0 -118
  71. package/build/cjs/packages/router-core/src/utils.js.map +0 -1
@@ -0,0 +1,824 @@
1
+ /**
2
+ * router-core
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 invariant = require('tiny-invariant');
16
+ var path = require('./path.js');
17
+ var route = require('./route.js');
18
+ var routeMatch = require('./routeMatch.js');
19
+ var searchParams = require('./searchParams.js');
20
+ var store = require('./store.js');
21
+ var utils = require('./utils.js');
22
+ var interop = require('./interop.js');
23
+ var history = require('./history.js');
24
+
25
+ function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
26
+
27
+ var invariant__default = /*#__PURE__*/_interopDefaultLegacy(invariant);
28
+
29
+ const defaultFetchServerDataFn = async ({
30
+ router,
31
+ routeMatch
32
+ }) => {
33
+ const next = router.buildNext({
34
+ to: '.',
35
+ search: d => ({
36
+ ...(d ?? {}),
37
+ __data: {
38
+ matchId: routeMatch.id
39
+ }
40
+ })
41
+ });
42
+ const res = await fetch(next.href, {
43
+ method: 'GET',
44
+ signal: routeMatch.abortController.signal
45
+ });
46
+ if (res.ok) {
47
+ return res.json();
48
+ }
49
+ throw new Error('Failed to fetch match data');
50
+ };
51
+ class Router {
52
+ // __location: Location<TAllRouteInfo['fullSearchSchema']>
53
+
54
+ startedLoadingAt = Date.now();
55
+ resolveNavigation = () => {};
56
+ constructor(options) {
57
+ this.options = {
58
+ defaultLoaderGcMaxAge: 5 * 60 * 1000,
59
+ defaultLoaderMaxAge: 0,
60
+ defaultPreloadMaxAge: 2000,
61
+ defaultPreloadDelay: 50,
62
+ context: undefined,
63
+ ...options,
64
+ stringifySearch: options?.stringifySearch ?? searchParams.defaultStringifySearch,
65
+ parseSearch: options?.parseSearch ?? searchParams.defaultParseSearch,
66
+ fetchServerDataFn: options?.fetchServerDataFn ?? defaultFetchServerDataFn
67
+ };
68
+ this.store = store.createStore(getInitialRouterState());
69
+ this.basepath = '';
70
+ this.update(options);
71
+
72
+ // Allow frameworks to hook into the router creation
73
+ this.options.Router?.(this);
74
+ }
75
+ reset = () => {
76
+ this.store.setState(s => Object.assign(s, getInitialRouterState()));
77
+ };
78
+ mount = () => {
79
+ // Mount only does anything on the client
80
+ if (!isServer) {
81
+ // If the router matches are empty, load the matches
82
+ if (!this.store.state.currentMatches.length) {
83
+ this.load();
84
+ }
85
+ const unsubHistory = this.history.listen(() => {
86
+ this.load(this.#parseLocation(this.store.state.latestLocation));
87
+ });
88
+ const visibilityChangeEvent = 'visibilitychange';
89
+ const focusEvent = 'focus';
90
+
91
+ // addEventListener does not exist in React Native, but window does
92
+ // In the future, we might need to invert control here for more adapters
93
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
94
+ if (window.addEventListener) {
95
+ // Listen to visibilitychange and focus
96
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
97
+ window.addEventListener(focusEvent, this.#onFocus, false);
98
+ }
99
+ return () => {
100
+ unsubHistory();
101
+ if (window.removeEventListener) {
102
+ // Be sure to unsubscribe if a new handler is set
103
+
104
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus);
105
+ window.removeEventListener(focusEvent, this.#onFocus);
106
+ }
107
+ };
108
+ }
109
+ return () => {};
110
+ };
111
+ update = opts => {
112
+ Object.assign(this.options, opts);
113
+ if (!this.history || this.options.history && this.options.history !== this.history) {
114
+ this.history = this.options?.history ?? isServer ? history.createMemoryHistory() : history.createBrowserHistory();
115
+ this.store.setState(s => {
116
+ s.latestLocation = this.#parseLocation();
117
+ s.currentLocation = s.latestLocation;
118
+ });
119
+ }
120
+ const {
121
+ basepath,
122
+ routeConfig
123
+ } = this.options;
124
+ this.basepath = `/${path.trimPath(basepath ?? '') ?? ''}`;
125
+ if (routeConfig) {
126
+ this.routesById = {};
127
+ this.routeTree = this.#buildRouteTree(routeConfig);
128
+ }
129
+ return this;
130
+ };
131
+ buildNext = opts => {
132
+ const next = this.#buildLocation(opts);
133
+ const matches = this.matchRoutes(next.pathname);
134
+ const __preSearchFilters = matches.map(match => match.route.options.preSearchFilters ?? []).flat().filter(Boolean);
135
+ const __postSearchFilters = matches.map(match => match.route.options.postSearchFilters ?? []).flat().filter(Boolean);
136
+ return this.#buildLocation({
137
+ ...opts,
138
+ __preSearchFilters,
139
+ __postSearchFilters
140
+ });
141
+ };
142
+ cancelMatches = () => {
143
+ [...this.store.state.currentMatches, ...(this.store.state.pendingMatches || [])].forEach(match => {
144
+ match.cancel();
145
+ });
146
+ };
147
+ load = async next => {
148
+ let now = Date.now();
149
+ const startedAt = now;
150
+ this.startedLoadingAt = startedAt;
151
+
152
+ // Cancel any pending matches
153
+ this.cancelMatches();
154
+ let matches;
155
+ store.batch(() => {
156
+ if (next) {
157
+ // Ingest the new location
158
+ this.store.setState(s => {
159
+ s.latestLocation = next;
160
+ });
161
+ }
162
+
163
+ // Match the routes
164
+ matches = this.matchRoutes(this.store.state.latestLocation.pathname, {
165
+ strictParseParams: true
166
+ });
167
+ this.store.setState(s => {
168
+ s.status = 'loading';
169
+ s.pendingMatches = matches;
170
+ s.pendingLocation = this.store.state.latestLocation;
171
+ });
172
+ });
173
+
174
+ // Load the matches
175
+ try {
176
+ await this.loadMatches(matches);
177
+ } catch (err) {
178
+ console.warn(err);
179
+ invariant__default["default"](false, 'Matches failed to load due to error above ☝️. Navigation cancelled!');
180
+ }
181
+ if (this.startedLoadingAt !== startedAt) {
182
+ // Ignore side-effects of outdated side-effects
183
+ return this.navigationPromise;
184
+ }
185
+ const previousMatches = this.store.state.currentMatches;
186
+ const exiting = [],
187
+ staying = [];
188
+ previousMatches.forEach(d => {
189
+ if (matches.find(dd => dd.id === d.id)) {
190
+ staying.push(d);
191
+ } else {
192
+ exiting.push(d);
193
+ }
194
+ });
195
+ const entering = matches.filter(d => {
196
+ return !previousMatches.find(dd => dd.id === d.id);
197
+ });
198
+ now = Date.now();
199
+ exiting.forEach(d => {
200
+ d.__onExit?.({
201
+ params: d.params,
202
+ search: d.store.state.routeSearch
203
+ });
204
+
205
+ // Clear non-loading error states when match leaves
206
+ if (d.store.state.status === 'error' && !d.store.state.isFetching) {
207
+ d.store.setState(s => {
208
+ s.status = 'idle';
209
+ s.error = undefined;
210
+ });
211
+ }
212
+ const gc = Math.max(d.route.options.loaderGcMaxAge ?? this.options.defaultLoaderGcMaxAge ?? 0, d.route.options.loaderMaxAge ?? this.options.defaultLoaderMaxAge ?? 0);
213
+ if (gc > 0) {
214
+ this.store.setState(s => {
215
+ s.matchCache[d.id] = {
216
+ gc: gc == Infinity ? Number.MAX_SAFE_INTEGER : now + gc,
217
+ match: d
218
+ };
219
+ });
220
+ }
221
+ });
222
+ staying.forEach(d => {
223
+ d.route.options.onTransition?.({
224
+ params: d.params,
225
+ search: d.store.state.routeSearch
226
+ });
227
+ });
228
+ entering.forEach(d => {
229
+ d.__onExit = d.route.options.onLoaded?.({
230
+ params: d.params,
231
+ search: d.store.state.search
232
+ });
233
+ delete this.store.state.matchCache[d.id];
234
+ });
235
+ this.store.setState(s => {
236
+ Object.assign(s, {
237
+ status: 'idle',
238
+ currentLocation: this.store.state.latestLocation,
239
+ currentMatches: matches,
240
+ pendingLocation: undefined,
241
+ pendingMatches: undefined
242
+ });
243
+ });
244
+ this.options.onRouteChange?.();
245
+ this.resolveNavigation();
246
+ };
247
+ cleanMatchCache = () => {
248
+ const now = Date.now();
249
+ this.store.setState(s => {
250
+ Object.keys(s.matchCache).forEach(matchId => {
251
+ const entry = s.matchCache[matchId];
252
+
253
+ // Don't remove loading matches
254
+ if (entry.match.store.state.status === 'loading') {
255
+ return;
256
+ }
257
+
258
+ // Do not remove successful matches that are still valid
259
+ if (entry.gc > 0 && entry.gc > now) {
260
+ return;
261
+ }
262
+
263
+ // Everything else gets removed
264
+ delete s.matchCache[matchId];
265
+ });
266
+ });
267
+ };
268
+ getRoute = id => {
269
+ const route = this.routesById[id];
270
+ invariant__default["default"](route, `Route with id "${id}" not found`);
271
+ return route;
272
+ };
273
+ loadRoute = async (navigateOpts = this.store.state.latestLocation) => {
274
+ const next = this.buildNext(navigateOpts);
275
+ const matches = this.matchRoutes(next.pathname, {
276
+ strictParseParams: true
277
+ });
278
+ await this.loadMatches(matches);
279
+ return matches;
280
+ };
281
+ preloadRoute = async (navigateOpts = this.store.state.latestLocation, loaderOpts) => {
282
+ const next = this.buildNext(navigateOpts);
283
+ const matches = this.matchRoutes(next.pathname, {
284
+ strictParseParams: true
285
+ });
286
+ await this.loadMatches(matches, {
287
+ preload: true,
288
+ maxAge: loaderOpts.maxAge ?? this.options.defaultPreloadMaxAge ?? this.options.defaultLoaderMaxAge ?? 0,
289
+ gcMaxAge: loaderOpts.gcMaxAge ?? this.options.defaultPreloadGcMaxAge ?? this.options.defaultLoaderGcMaxAge ?? 0
290
+ });
291
+ return matches;
292
+ };
293
+ matchRoutes = (pathname, opts) => {
294
+ const matches = [];
295
+ if (!this.routeTree) {
296
+ return matches;
297
+ }
298
+ const existingMatches = [...this.store.state.currentMatches, ...(this.store.state.pendingMatches ?? [])];
299
+ const recurse = async routes => {
300
+ const parentMatch = utils.last(matches);
301
+ let params = parentMatch?.params ?? {};
302
+ const filteredRoutes = this.options.filterRoutes?.(routes) ?? routes;
303
+ let foundRoutes = [];
304
+ const findMatchInRoutes = (parentRoutes, routes) => {
305
+ routes.some(route => {
306
+ if (!route.path && route.childRoutes?.length) {
307
+ return findMatchInRoutes([...foundRoutes, route], route.childRoutes);
308
+ }
309
+ const fuzzy = !!(route.path !== '/' || route.childRoutes?.length);
310
+ const matchParams = path.matchPathname(this.basepath, pathname, {
311
+ to: route.fullPath,
312
+ fuzzy,
313
+ caseSensitive: route.options.caseSensitive ?? this.options.caseSensitive
314
+ });
315
+ if (matchParams) {
316
+ let parsedParams;
317
+ try {
318
+ parsedParams = route.options.parseParams?.(matchParams) ?? matchParams;
319
+ } catch (err) {
320
+ if (opts?.strictParseParams) {
321
+ throw err;
322
+ }
323
+ }
324
+ params = {
325
+ ...params,
326
+ ...parsedParams
327
+ };
328
+ }
329
+ if (!!matchParams) {
330
+ foundRoutes = [...parentRoutes, route];
331
+ }
332
+ return !!foundRoutes.length;
333
+ });
334
+ return !!foundRoutes.length;
335
+ };
336
+ findMatchInRoutes([], filteredRoutes);
337
+ if (!foundRoutes.length) {
338
+ return;
339
+ }
340
+ foundRoutes.forEach(foundRoute => {
341
+ const interpolatedPath = path.interpolatePath(foundRoute.path, params);
342
+ const matchId = path.interpolatePath(foundRoute.id, params, true);
343
+ const match = existingMatches.find(d => d.id === matchId) || this.store.state.matchCache[matchId]?.match || new routeMatch.RouteMatch(this, foundRoute, {
344
+ id: matchId,
345
+ params,
346
+ pathname: path.joinPaths([this.basepath, interpolatedPath])
347
+ });
348
+ matches.push(match);
349
+ });
350
+ const foundRoute = utils.last(foundRoutes);
351
+ if (foundRoute.childRoutes?.length) {
352
+ recurse(foundRoute.childRoutes);
353
+ }
354
+ };
355
+ recurse([this.routeTree]);
356
+ linkMatches(matches);
357
+ return matches;
358
+ };
359
+ loadMatches = async (resolvedMatches, loaderOpts) => {
360
+ this.cleanMatchCache();
361
+ resolvedMatches.forEach(async match => {
362
+ // Validate the match (loads search params etc)
363
+ match.__validate();
364
+ });
365
+
366
+ // Check each match middleware to see if the route can be accessed
367
+ await Promise.all(resolvedMatches.map(async match => {
368
+ try {
369
+ await match.route.options.beforeLoad?.({
370
+ router: this,
371
+ match
372
+ });
373
+ } catch (err) {
374
+ if (!loaderOpts?.preload) {
375
+ match.route.options.onLoadError?.(err);
376
+ }
377
+ throw err;
378
+ }
379
+ }));
380
+ const matchPromises = resolvedMatches.map(async (match, index) => {
381
+ const prevMatch = resolvedMatches[1];
382
+ const search = match.store.state.search;
383
+ if (search.__data?.matchId && search.__data.matchId !== match.id) {
384
+ return;
385
+ }
386
+ match.load(loaderOpts);
387
+ if (match.store.state.status !== 'success' && match.__loadPromise) {
388
+ // Wait for the first sign of activity from the match
389
+ await match.__loadPromise;
390
+ }
391
+ if (prevMatch) {
392
+ await prevMatch.__loadPromise;
393
+ }
394
+ });
395
+ await Promise.all(matchPromises);
396
+ };
397
+ loadMatchData = async routeMatch => {
398
+ if (isServer || !this.options.useServerData) {
399
+ return (await routeMatch.route.options.loader?.({
400
+ // parentLoaderPromise: routeMatch.parentMatch.dataPromise,
401
+ params: routeMatch.params,
402
+ search: routeMatch.store.state.routeSearch,
403
+ signal: routeMatch.abortController.signal
404
+ })) || {};
405
+ } else {
406
+ // Refresh:
407
+ // '/dashboard'
408
+ // '/dashboard/invoices/'
409
+ // '/dashboard/invoices/123'
410
+
411
+ // New:
412
+ // '/dashboard/invoices/456'
413
+
414
+ // TODO: batch requests when possible
415
+
416
+ const res = await this.options.fetchServerDataFn({
417
+ router: this,
418
+ routeMatch
419
+ });
420
+ return res;
421
+ }
422
+ };
423
+ invalidateRoute = async opts => {
424
+ const next = this.buildNext(opts);
425
+ const unloadedMatchIds = this.matchRoutes(next.pathname).map(d => d.id);
426
+ await Promise.allSettled([...this.store.state.currentMatches, ...(this.store.state.pendingMatches ?? [])].map(async match => {
427
+ if (unloadedMatchIds.includes(match.id)) {
428
+ return match.invalidate();
429
+ }
430
+ }));
431
+ };
432
+ reload = () => {
433
+ this.navigate({
434
+ fromCurrent: true,
435
+ replace: true,
436
+ search: true
437
+ });
438
+ };
439
+ resolvePath = (from, path$1) => {
440
+ return path.resolvePath(this.basepath, from, path.cleanPath(path$1));
441
+ };
442
+ navigate = async ({
443
+ from,
444
+ to = '.',
445
+ search,
446
+ hash,
447
+ replace,
448
+ params
449
+ }) => {
450
+ // If this link simply reloads the current route,
451
+ // make sure it has a new key so it will trigger a data refresh
452
+
453
+ // If this `to` is a valid external URL, return
454
+ // null for LinkUtils
455
+ const toString = String(to);
456
+ const fromString = String(from);
457
+ let isExternal;
458
+ try {
459
+ new URL(`${toString}`);
460
+ isExternal = true;
461
+ } catch (e) {}
462
+ invariant__default["default"](!isExternal, 'Attempting to navigate to external url with this.navigate!');
463
+ return this.#commitLocation({
464
+ from: fromString,
465
+ to: toString,
466
+ search,
467
+ hash,
468
+ replace,
469
+ params
470
+ });
471
+ };
472
+ matchRoute = (location, opts) => {
473
+ location = {
474
+ ...location,
475
+ to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
476
+ };
477
+ const next = this.buildNext(location);
478
+ if (opts?.pending) {
479
+ if (!this.store.state.pendingLocation) {
480
+ return false;
481
+ }
482
+ return path.matchPathname(this.basepath, this.store.state.pendingLocation.pathname, {
483
+ ...opts,
484
+ to: next.pathname
485
+ });
486
+ }
487
+ return path.matchPathname(this.basepath, this.store.state.currentLocation.pathname, {
488
+ ...opts,
489
+ to: next.pathname
490
+ });
491
+ };
492
+ buildLink = ({
493
+ from,
494
+ to = '.',
495
+ search,
496
+ params,
497
+ hash,
498
+ target,
499
+ replace,
500
+ activeOptions,
501
+ preload,
502
+ preloadMaxAge: userPreloadMaxAge,
503
+ preloadGcMaxAge: userPreloadGcMaxAge,
504
+ preloadDelay: userPreloadDelay,
505
+ disabled
506
+ }) => {
507
+ // If this link simply reloads the current route,
508
+ // make sure it has a new key so it will trigger a data refresh
509
+
510
+ // If this `to` is a valid external URL, return
511
+ // null for LinkUtils
512
+
513
+ try {
514
+ new URL(`${to}`);
515
+ return {
516
+ type: 'external',
517
+ href: to
518
+ };
519
+ } catch (e) {}
520
+ const nextOpts = {
521
+ from,
522
+ to,
523
+ search,
524
+ params,
525
+ hash,
526
+ replace
527
+ };
528
+ const next = this.buildNext(nextOpts);
529
+ preload = preload ?? this.options.defaultPreload;
530
+ const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
531
+
532
+ // Compare path/hash for matches
533
+ const pathIsEqual = this.store.state.currentLocation.pathname === next.pathname;
534
+ const currentPathSplit = this.store.state.currentLocation.pathname.split('/');
535
+ const nextPathSplit = next.pathname.split('/');
536
+ const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
537
+ const hashIsEqual = this.store.state.currentLocation.hash === next.hash;
538
+ // Combine the matches based on user options
539
+ const pathTest = activeOptions?.exact ? pathIsEqual : pathIsFuzzyEqual;
540
+ const hashTest = activeOptions?.includeHash ? hashIsEqual : true;
541
+
542
+ // The final "active" test
543
+ const isActive = pathTest && hashTest;
544
+
545
+ // The click handler
546
+ const handleClick = e => {
547
+ if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
548
+ e.preventDefault();
549
+ if (pathIsEqual && !search && !hash) {
550
+ this.invalidateRoute(nextOpts);
551
+ }
552
+
553
+ // All is well? Navigate!
554
+ this.#commitLocation(nextOpts);
555
+ }
556
+ };
557
+
558
+ // The click handler
559
+ const handleFocus = e => {
560
+ if (preload) {
561
+ this.preloadRoute(nextOpts, {
562
+ maxAge: userPreloadMaxAge,
563
+ gcMaxAge: userPreloadGcMaxAge
564
+ }).catch(err => {
565
+ console.warn(err);
566
+ console.warn('Error preloading route! ☝️');
567
+ });
568
+ }
569
+ };
570
+ const handleEnter = e => {
571
+ const target = e.target || {};
572
+ if (preload) {
573
+ if (target.preloadTimeout) {
574
+ return;
575
+ }
576
+ target.preloadTimeout = setTimeout(() => {
577
+ target.preloadTimeout = null;
578
+ this.preloadRoute(nextOpts, {
579
+ maxAge: userPreloadMaxAge,
580
+ gcMaxAge: userPreloadGcMaxAge
581
+ }).catch(err => {
582
+ console.warn(err);
583
+ console.warn('Error preloading route! ☝️');
584
+ });
585
+ }, preloadDelay);
586
+ }
587
+ };
588
+ const handleLeave = e => {
589
+ const target = e.target || {};
590
+ if (target.preloadTimeout) {
591
+ clearTimeout(target.preloadTimeout);
592
+ target.preloadTimeout = null;
593
+ }
594
+ };
595
+ return {
596
+ type: 'internal',
597
+ next,
598
+ handleFocus,
599
+ handleClick,
600
+ handleEnter,
601
+ handleLeave,
602
+ isActive,
603
+ disabled
604
+ };
605
+ };
606
+ dehydrate = () => {
607
+ return {
608
+ state: {
609
+ ...utils.pick(this.store.state, ['latestLocation', 'currentLocation', 'status', 'lastUpdated']),
610
+ currentMatches: this.store.state.currentMatches.map(match => ({
611
+ id: match.id,
612
+ state: {
613
+ ...utils.pick(match.store.state, ['status', 'routeLoaderData', 'invalidAt', 'invalid'])
614
+ }
615
+ }))
616
+ },
617
+ context: this.options.context
618
+ };
619
+ };
620
+ hydrate = dehydratedRouter => {
621
+ this.store.setState(s => {
622
+ // Update the context TODO: make this part of state?
623
+ this.options.context = dehydratedRouter.context;
624
+
625
+ // Match the routes
626
+ const currentMatches = this.matchRoutes(dehydratedRouter.state.latestLocation.pathname, {
627
+ strictParseParams: true
628
+ });
629
+ currentMatches.forEach((match, index) => {
630
+ const dehydratedMatch = dehydratedRouter.state.currentMatches[index];
631
+ invariant__default["default"](dehydratedMatch && dehydratedMatch.id === match.id, 'Oh no! There was a hydration mismatch when attempting to rethis.store the state of the router! 😬');
632
+ Object.assign(match, dehydratedMatch);
633
+ });
634
+ currentMatches.forEach(match => match.__validate());
635
+ Object.assign(s, {
636
+ ...dehydratedRouter.state,
637
+ currentMatches
638
+ });
639
+ });
640
+ };
641
+ getLoader = opts => {
642
+ const id = opts.from || '/';
643
+ const route = this.getRoute(id);
644
+ if (!route) return undefined;
645
+ let loader = this.store.state.loaders[id] || (() => {
646
+ this.store.setState(s => {
647
+ s.loaders[id] = {
648
+ pending: [],
649
+ fetch: async loaderContext => {
650
+ if (!route) {
651
+ return;
652
+ }
653
+ const loaderState = {
654
+ loadedAt: Date.now(),
655
+ loaderContext
656
+ };
657
+ this.store.setState(s => {
658
+ s.loaders[id].current = loaderState;
659
+ s.loaders[id].latest = loaderState;
660
+ s.loaders[id].pending.push(loaderState);
661
+ });
662
+ try {
663
+ return await route.options.loader?.(loaderContext);
664
+ } finally {
665
+ this.store.setState(s => {
666
+ s.loaders[id].pending = s.loaders[id].pending.filter(d => d !== loaderState);
667
+ });
668
+ }
669
+ }
670
+ };
671
+ });
672
+ return this.store.state.loaders[id];
673
+ })();
674
+ return loader;
675
+ };
676
+ #buildRouteTree = rootRouteConfig => {
677
+ const recurseRoutes = (routeConfigs, parent) => {
678
+ return routeConfigs.map((routeConfig, i) => {
679
+ const routeOptions = routeConfig.options;
680
+ const route$1 = new route.Route(routeConfig, routeOptions, i, parent, this);
681
+ const existingRoute = this.routesById[route$1.id];
682
+ if (existingRoute) {
683
+ if (process.env.NODE_ENV !== 'production') {
684
+ console.warn(`Duplicate routes found with id: ${String(route$1.id)}`, this.routesById, route$1);
685
+ }
686
+ throw new Error();
687
+ }
688
+ this.routesById[route$1.id] = route$1;
689
+ const children = routeConfig.children;
690
+ route$1.childRoutes = children.length ? recurseRoutes(children, route$1) : undefined;
691
+ return route$1;
692
+ });
693
+ };
694
+ const routes = recurseRoutes([rootRouteConfig]);
695
+ return routes[0];
696
+ };
697
+ #parseLocation = previousLocation => {
698
+ let {
699
+ pathname,
700
+ search,
701
+ hash,
702
+ state
703
+ } = this.history.location;
704
+ const parsedSearch = this.options.parseSearch(search);
705
+ return {
706
+ pathname: pathname,
707
+ searchStr: search,
708
+ search: interop.replaceEqualDeep(previousLocation?.search, parsedSearch),
709
+ hash: hash.split('#').reverse()[0] ?? '',
710
+ href: `${pathname}${search}${hash}`,
711
+ state: state,
712
+ key: state?.key || '__init__'
713
+ };
714
+ };
715
+ #onFocus = () => {
716
+ this.load();
717
+ };
718
+ #buildLocation = (dest = {}) => {
719
+ const fromPathname = dest.fromCurrent ? this.store.state.latestLocation.pathname : dest.from ?? this.store.state.latestLocation.pathname;
720
+ let pathname = path.resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? '.'}`);
721
+ const fromMatches = this.matchRoutes(this.store.state.latestLocation.pathname, {
722
+ strictParseParams: true
723
+ });
724
+ const toMatches = this.matchRoutes(pathname);
725
+ const prevParams = {
726
+ ...utils.last(fromMatches)?.params
727
+ };
728
+ let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
729
+ if (nextParams) {
730
+ toMatches.map(d => d.route.options.stringifyParams).filter(Boolean).forEach(fn => {
731
+ Object.assign({}, nextParams, fn(nextParams));
732
+ });
733
+ }
734
+ pathname = path.interpolatePath(pathname, nextParams ?? {});
735
+
736
+ // Pre filters first
737
+ const preFilteredSearch = dest.__preSearchFilters?.length ? dest.__preSearchFilters?.reduce((prev, next) => next(prev), this.store.state.latestLocation.search) : this.store.state.latestLocation.search;
738
+
739
+ // Then the link/navigate function
740
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
741
+ : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
742
+ : dest.__preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
743
+ : {};
744
+
745
+ // Then post filters
746
+ const postFilteredSearch = dest.__postSearchFilters?.length ? dest.__postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
747
+ const search = interop.replaceEqualDeep(this.store.state.latestLocation.search, postFilteredSearch);
748
+ const searchStr = this.options.stringifySearch(search);
749
+ let hash = dest.hash === true ? this.store.state.latestLocation.hash : utils.functionalUpdate(dest.hash, this.store.state.latestLocation.hash);
750
+ hash = hash ? `#${hash}` : '';
751
+ return {
752
+ pathname,
753
+ search,
754
+ searchStr,
755
+ state: this.store.state.latestLocation.state,
756
+ hash,
757
+ href: `${pathname}${searchStr}${hash}`,
758
+ key: dest.key
759
+ };
760
+ };
761
+ #commitLocation = location => {
762
+ const next = this.buildNext(location);
763
+ const id = '' + Date.now() + Math.random();
764
+ if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
765
+ let nextAction = 'replace';
766
+ if (!location.replace) {
767
+ nextAction = 'push';
768
+ }
769
+ const isSameUrl = this.store.state.latestLocation.href === next.href;
770
+ if (isSameUrl && !next.key) {
771
+ nextAction = 'replace';
772
+ }
773
+ const href = `${next.pathname}${next.searchStr}${next.hash ? `#${next.hash}` : ''}`;
774
+ this.history[nextAction === 'push' ? 'push' : 'replace'](href, {
775
+ id,
776
+ ...next.state
777
+ });
778
+
779
+ // this.load(this.#parseLocation(this.store.state.latestLocation))
780
+
781
+ return this.navigationPromise = new Promise(resolve => {
782
+ const previousNavigationResolve = this.resolveNavigation;
783
+ this.resolveNavigation = () => {
784
+ previousNavigationResolve();
785
+ resolve();
786
+ };
787
+ });
788
+ };
789
+ }
790
+
791
+ // Detect if we're in the DOM
792
+ const isServer = typeof window === 'undefined' || !window.document.createElement;
793
+ function getInitialRouterState() {
794
+ return {
795
+ status: 'idle',
796
+ latestLocation: null,
797
+ currentLocation: null,
798
+ currentMatches: [],
799
+ loaders: {},
800
+ lastUpdated: Date.now(),
801
+ matchCache: {},
802
+ get isFetching() {
803
+ return this.status === 'loading' || this.currentMatches.some(d => d.store.state.isFetching);
804
+ },
805
+ get isPreloading() {
806
+ return Object.values(this.matchCache).some(d => d.match.store.state.isFetching && !this.currentMatches.find(dd => dd.id === d.match.id));
807
+ }
808
+ };
809
+ }
810
+ function isCtrlEvent(e) {
811
+ return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
812
+ }
813
+ function linkMatches(matches) {
814
+ matches.forEach((match, index) => {
815
+ const parent = matches[index - 1];
816
+ if (parent) {
817
+ match.__setParentMatch(parent);
818
+ }
819
+ });
820
+ }
821
+
822
+ exports.Router = Router;
823
+ exports.defaultFetchServerDataFn = defaultFetchServerDataFn;
824
+ //# sourceMappingURL=router.js.map