@tanstack/router-core 0.0.1-beta.41 → 0.0.1-beta.49

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