@tanstack/react-router 0.0.1-beta.222 → 0.0.1-beta.224

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.
@@ -12,15 +12,9 @@
12
12
 
13
13
  Object.defineProperty(exports, '__esModule', { value: true });
14
14
 
15
- var history = require('@tanstack/history');
16
15
  var React = require('react');
17
- var invariant = require('tiny-invariant');
18
16
  var warning = require('tiny-warning');
19
17
  var Matches = require('./Matches.js');
20
- var path = require('./path.js');
21
- var redirects = require('./redirects.js');
22
- var router = require('./router.js');
23
- var utils = require('./utils.js');
24
18
 
25
19
  function _interopDefaultLegacy (e) { return e && typeof e === 'object' && 'default' in e ? e : { 'default': e }; }
26
20
 
@@ -43,17 +37,36 @@ function _interopNamespace(e) {
43
37
  }
44
38
 
45
39
  var React__namespace = /*#__PURE__*/_interopNamespace(React);
46
- var invariant__default = /*#__PURE__*/_interopDefaultLegacy(invariant);
47
40
  var warning__default = /*#__PURE__*/_interopDefaultLegacy(warning);
48
41
 
42
+ // export type RouterContext<
43
+ // TRouteTree extends AnyRoute,
44
+ // // TDehydrated extends Record<string, any>,
45
+ // > = {
46
+ // buildLink: BuildLinkFn<TRouteTree>
47
+ // state: RouterState<TRouteTree>
48
+ // navigate: NavigateFn<TRouteTree>
49
+ // matchRoute: MatchRouteFn<TRouteTree>
50
+ // routeTree: TRouteTree
51
+ // routesById: RoutesById<TRouteTree>
52
+ // options: RouterOptions<TRouteTree>
53
+ // history: RouterHistory
54
+ // load: LoadFn
55
+ // buildLocation: BuildLocationFn<TRouteTree>
56
+ // subscribe: Router<TRouteTree>['subscribe']
57
+ // resetNextScrollRef: React.MutableRefObject<boolean>
58
+ // injectedHtmlRef: React.MutableRefObject<InjectedHtmlEntry[]>
59
+ // injectHtml: (entry: InjectedHtmlEntry) => void
60
+ // dehydrateData: <T>(
61
+ // key: any,
62
+ // getData: T | (() => Promise<T> | T),
63
+ // ) => () => void
64
+ // hydrateData: <T>(key: any) => T | undefined
65
+ // }
49
66
  const routerContext = /*#__PURE__*/React__namespace.createContext(null);
50
67
  if (typeof document !== 'undefined') {
51
68
  window.__TSR_ROUTER_CONTEXT__ = routerContext;
52
69
  }
53
- const preloadWarning = 'Error preloading route! ☝️';
54
- function isCtrlEvent(e) {
55
- return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
56
- }
57
70
  class SearchParamError extends Error {}
58
71
  class PathParamError extends Error {}
59
72
  function getInitialRouterState(location) {
@@ -67,903 +80,50 @@ function getInitialRouterState(location) {
67
80
  };
68
81
  }
69
82
  function RouterProvider({
70
- router: router$1,
83
+ router,
71
84
  ...rest
72
85
  }) {
73
- const options = {
74
- ...router$1.options,
86
+ // Allow the router to update options on the router instance
87
+ router.updateOptions({
88
+ ...router.options,
75
89
  ...rest,
76
90
  context: {
77
- ...router$1.options.context,
91
+ ...router.options.context,
78
92
  ...rest?.context
79
93
  }
80
- };
81
- const history$1 = React__namespace.useState(() => options.history ?? history.createBrowserHistory())[0];
82
- const tempLocationKeyRef = React__namespace.useRef(`${Math.round(Math.random() * 10000000)}`);
83
- const resetNextScrollRef = React__namespace.useRef(true);
84
- const navigateTimeoutRef = React__namespace.useRef(null);
85
- const latestLoadPromiseRef = React__namespace.useRef(Promise.resolve());
86
- const checkLatest = promise => {
87
- return latestLoadPromiseRef.current !== promise ? latestLoadPromiseRef.current : undefined;
88
- };
89
- const parseLocation = utils.useStableCallback(previousLocation => {
90
- const parse = ({
91
- pathname,
92
- search,
93
- hash,
94
- state
95
- }) => {
96
- const parsedSearch = options.parseSearch(search);
97
- return {
98
- pathname: pathname,
99
- searchStr: search,
100
- search: utils.replaceEqualDeep(previousLocation?.search, parsedSearch),
101
- hash: hash.split('#').reverse()[0] ?? '',
102
- href: `${pathname}${search}${hash}`,
103
- state: utils.replaceEqualDeep(previousLocation?.state, state)
104
- };
105
- };
106
- const location = parse(history$1.location);
107
- let {
108
- __tempLocation,
109
- __tempKey
110
- } = location.state;
111
- if (__tempLocation && (!__tempKey || __tempKey === tempLocationKeyRef.current)) {
112
- // Sync up the location keys
113
- const parsedTempLocation = parse(__tempLocation);
114
- parsedTempLocation.state.key = location.state.key;
115
- delete parsedTempLocation.state.__tempLocation;
116
- return {
117
- ...parsedTempLocation,
118
- maskedLocation: location
119
- };
120
- }
121
- return location;
122
94
  });
123
- const latestLocationRef = React__namespace.useRef(parseLocation());
124
- const [preState, setState] = React__namespace.useState(() => getInitialRouterState(latestLocationRef.current));
95
+ const [preState, setState] = React__namespace.useState(() => router.state);
125
96
  const [isTransitioning, startReactTransition] = React__namespace.useTransition();
126
- const pendingMatchesRef = React__namespace.useRef([]);
127
97
  const state = React__namespace.useMemo(() => ({
128
98
  ...preState,
129
99
  status: isTransitioning ? 'pending' : 'idle',
130
- location: isTransitioning ? latestLocationRef.current : preState.location,
131
- pendingMatches: pendingMatchesRef.current
100
+ location: isTransitioning ? router.latestLocation : preState.location,
101
+ pendingMatches: router.pendingMatches
132
102
  }), [preState, isTransitioning]);
103
+ router.setState = setState;
104
+ router.state = state;
105
+ router.startReactTransition = startReactTransition;
133
106
  React__namespace.useLayoutEffect(() => {
134
- if (!isTransitioning && state.resolvedLocation !== state.location) {
135
- router$1.emit({
136
- type: 'onResolved',
137
- fromLocation: state.resolvedLocation,
138
- toLocation: state.location,
139
- pathChanged: state.location.href !== state.resolvedLocation?.href
140
- });
141
- pendingMatchesRef.current = [];
142
- setState(s => ({
143
- ...s,
144
- resolvedLocation: s.location
145
- }));
146
- }
147
- });
148
- const basepath = `/${path.trimPath(options.basepath ?? '') ?? ''}`;
149
- const resolvePathWithBase = utils.useStableCallback((from, path$1) => {
150
- return path.resolvePath(basepath, from, path.cleanPath(path$1));
151
- });
152
- const [routesById, routesByPath] = React__namespace.useMemo(() => {
153
- const routesById = {};
154
- const routesByPath = {};
155
- const recurseRoutes = routes => {
156
- routes.forEach((route, i) => {
157
- route.init({
158
- originalIndex: i
159
- });
160
- const existingRoute = routesById[route.id];
161
- invariant__default["default"](!existingRoute, `Duplicate routes found with id: ${String(route.id)}`);
162
- routesById[route.id] = route;
163
- if (!route.isRoot && route.path) {
164
- const trimmedFullPath = path.trimPathRight(route.fullPath);
165
- if (!routesByPath[trimmedFullPath] || route.fullPath.endsWith('/')) {
166
- routesByPath[trimmedFullPath] = route;
167
- }
168
- }
169
- const children = route.children;
170
- if (children?.length) {
171
- recurseRoutes(children);
172
- }
173
- });
174
- };
175
- recurseRoutes([router$1.routeTree]);
176
- return [routesById, routesByPath];
177
- }, []);
178
- const looseRoutesById = routesById;
179
- const flatRoutes = React__namespace.useMemo(() => Object.values(routesByPath).map((d, i) => {
180
- const trimmed = path.trimPath(d.fullPath);
181
- const parsed = path.parsePathname(trimmed);
182
- while (parsed.length > 1 && parsed[0]?.value === '/') {
183
- parsed.shift();
184
- }
185
- const score = parsed.map(d => {
186
- if (d.type === 'param') {
187
- return 0.5;
188
- }
189
- if (d.type === 'wildcard') {
190
- return 0.25;
191
- }
192
- return 1;
193
- });
194
- return {
195
- child: d,
196
- trimmed,
197
- parsed,
198
- index: i,
199
- score
200
- };
201
- }).sort((a, b) => {
202
- let isIndex = a.trimmed === '/' ? 1 : b.trimmed === '/' ? -1 : 0;
203
- if (isIndex !== 0) return isIndex;
204
- const length = Math.min(a.score.length, b.score.length);
205
-
206
- // Sort by length of score
207
- if (a.score.length !== b.score.length) {
208
- return b.score.length - a.score.length;
209
- }
210
-
211
- // Sort by min available score
212
- for (let i = 0; i < length; i++) {
213
- if (a.score[i] !== b.score[i]) {
214
- return b.score[i] - a.score[i];
215
- }
216
- }
217
-
218
- // Sort by min available parsed value
219
- for (let i = 0; i < length; i++) {
220
- if (a.parsed[i].value !== b.parsed[i].value) {
221
- return a.parsed[i].value > b.parsed[i].value ? 1 : -1;
222
- }
223
- }
224
-
225
- // Sort by length of trimmed full path
226
- if (a.trimmed !== b.trimmed) {
227
- return a.trimmed > b.trimmed ? 1 : -1;
228
- }
229
-
230
- // Sort by original index
231
- return a.index - b.index;
232
- }).map((d, i) => {
233
- d.child.rank = i;
234
- return d.child;
235
- }), [routesByPath]);
236
- const matchRoutes = utils.useStableCallback((pathname, locationSearch, opts) => {
237
- let routeParams = {};
238
- let foundRoute = flatRoutes.find(route => {
239
- const matchedParams = path.matchPathname(basepath, path.trimPathRight(pathname), {
240
- to: route.fullPath,
241
- caseSensitive: route.options.caseSensitive ?? options.caseSensitive,
242
- fuzzy: false
243
- });
244
- if (matchedParams) {
245
- routeParams = matchedParams;
246
- return true;
247
- }
248
- return false;
249
- });
250
- let routeCursor = foundRoute || routesById['__root__'];
251
- let matchedRoutes = [routeCursor];
252
- // let includingLayouts = true
253
- while (routeCursor?.parentRoute) {
254
- routeCursor = routeCursor.parentRoute;
255
- if (routeCursor) matchedRoutes.unshift(routeCursor);
256
- }
257
-
258
- // Existing matches are matches that are already loaded along with
259
- // pending matches that are still loading
260
-
261
- const parseErrors = matchedRoutes.map(route => {
262
- let parsedParamsError;
263
- if (route.options.parseParams) {
264
- try {
265
- const parsedParams = route.options.parseParams(routeParams);
266
- // Add the parsed params to the accumulated params bag
267
- Object.assign(routeParams, parsedParams);
268
- } catch (err) {
269
- parsedParamsError = new PathParamError(err.message, {
270
- cause: err
271
- });
272
- if (opts?.throwOnError) {
273
- throw parsedParamsError;
274
- }
275
- return parsedParamsError;
276
- }
277
- }
278
- return;
279
- });
280
- const matches = matchedRoutes.map((route, index) => {
281
- const interpolatedPath = path.interpolatePath(route.path, routeParams);
282
- const matchId = path.interpolatePath(route.id, routeParams, true);
283
-
284
- // Waste not, want not. If we already have a match for this route,
285
- // reuse it. This is important for layout routes, which might stick
286
- // around between navigation actions that only change leaf routes.
287
- const existingMatch = getRouteMatch(state, matchId);
288
- if (existingMatch) {
289
- return {
290
- ...existingMatch
291
- };
292
- }
293
-
294
- // Create a fresh route match
295
- const hasLoaders = !!(route.options.loader || router.componentTypes.some(d => route.options[d]?.preload));
296
- const routeMatch = {
297
- id: matchId,
298
- routeId: route.id,
299
- params: routeParams,
300
- pathname: path.joinPaths([basepath, interpolatedPath]),
301
- updatedAt: Date.now(),
302
- routeSearch: {},
303
- search: {},
304
- status: hasLoaders ? 'pending' : 'success',
305
- isFetching: false,
306
- invalid: false,
307
- error: undefined,
308
- paramsError: parseErrors[index],
309
- searchError: undefined,
310
- loadPromise: Promise.resolve(),
311
- context: undefined,
312
- abortController: new AbortController(),
313
- shouldReloadDeps: undefined,
314
- fetchedAt: 0
315
- };
316
- return routeMatch;
317
- });
318
-
319
- // Take each match and resolve its search params and context
320
- // This has to happen after the matches are created or found
321
- // so that we can use the parent match's search params and context
322
- matches.forEach((match, i) => {
323
- const parentMatch = matches[i - 1];
324
- const route = looseRoutesById[match.routeId];
325
- const searchInfo = (() => {
326
- // Validate the search params and stabilize them
327
- const parentSearchInfo = {
328
- search: parentMatch?.search ?? locationSearch,
329
- routeSearch: parentMatch?.routeSearch ?? locationSearch
330
- };
331
- try {
332
- const validator = typeof route.options.validateSearch === 'object' ? route.options.validateSearch.parse : route.options.validateSearch;
333
- let routeSearch = validator?.(parentSearchInfo.search) ?? {};
334
- let search = {
335
- ...parentSearchInfo.search,
336
- ...routeSearch
337
- };
338
- routeSearch = utils.replaceEqualDeep(match.routeSearch, routeSearch);
339
- search = utils.replaceEqualDeep(match.search, search);
340
- return {
341
- routeSearch,
342
- search,
343
- searchDidChange: match.routeSearch !== routeSearch
344
- };
345
- } catch (err) {
346
- match.searchError = new SearchParamError(err.message, {
347
- cause: err
348
- });
349
- if (opts?.throwOnError) {
350
- throw match.searchError;
351
- }
352
- return parentSearchInfo;
353
- }
354
- })();
355
- Object.assign(match, searchInfo);
356
- });
357
- return matches;
358
- });
359
- const cancelMatch = utils.useStableCallback(id => {
360
- getRouteMatch(state, id)?.abortController?.abort();
361
- });
362
- const cancelMatches = utils.useStableCallback(state => {
363
- state.matches.forEach(match => {
364
- cancelMatch(match.id);
365
- });
366
- });
367
- const buildLocation = utils.useStableCallback(opts => {
368
- const build = (dest = {}, matches) => {
369
- const from = latestLocationRef.current;
370
- const fromPathname = dest.from ?? from.pathname;
371
- let pathname = resolvePathWithBase(fromPathname, `${dest.to ?? ''}`);
372
- const fromMatches = matchRoutes(fromPathname, from.search);
373
- const stayingMatches = matches?.filter(d => fromMatches?.find(e => e.routeId === d.routeId));
374
- const prevParams = {
375
- ...utils.last(fromMatches)?.params
376
- };
377
- let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
378
- if (nextParams) {
379
- matches?.map(d => looseRoutesById[d.routeId].options.stringifyParams).filter(Boolean).forEach(fn => {
380
- nextParams = {
381
- ...nextParams,
382
- ...fn(nextParams)
383
- };
384
- });
385
- }
386
- pathname = path.interpolatePath(pathname, nextParams ?? {});
387
- const preSearchFilters = stayingMatches?.map(match => looseRoutesById[match.routeId].options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
388
- const postSearchFilters = stayingMatches?.map(match => looseRoutesById[match.routeId].options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
389
-
390
- // Pre filters first
391
- const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
392
-
393
- // Then the link/navigate function
394
- const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
395
- : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
396
- : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
397
- : {};
398
-
399
- // Then post filters
400
- const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
401
- const search = utils.replaceEqualDeep(from.search, postFilteredSearch);
402
- const searchStr = options.stringifySearch(search);
403
- const hash = dest.hash === true ? from.hash : dest.hash ? utils.functionalUpdate(dest.hash, from.hash) : from.hash;
404
- const hashStr = hash ? `#${hash}` : '';
405
- let nextState = dest.state === true ? from.state : dest.state ? utils.functionalUpdate(dest.state, from.state) : from.state;
406
- nextState = utils.replaceEqualDeep(from.state, nextState);
407
- return {
408
- pathname,
409
- search,
410
- searchStr,
411
- state: nextState,
412
- hash,
413
- href: history$1.createHref(`${pathname}${searchStr}${hashStr}`),
414
- unmaskOnReload: dest.unmaskOnReload
415
- };
416
- };
417
- const buildWithMatches = (dest = {}, maskedDest) => {
418
- let next = build(dest);
419
- let maskedNext = maskedDest ? build(maskedDest) : undefined;
420
- if (!maskedNext) {
421
- let params = {};
422
- let foundMask = options.routeMasks?.find(d => {
423
- const match = path.matchPathname(basepath, next.pathname, {
424
- to: d.from,
425
- caseSensitive: false,
426
- fuzzy: false
427
- });
428
- if (match) {
429
- params = match;
430
- return true;
431
- }
432
- return false;
433
- });
434
- if (foundMask) {
435
- foundMask = {
436
- ...foundMask,
437
- from: path.interpolatePath(foundMask.from, params)
438
- };
439
- maskedDest = foundMask;
440
- maskedNext = build(maskedDest);
441
- }
442
- }
443
- const nextMatches = matchRoutes(next.pathname, next.search);
444
- const maskedMatches = maskedNext ? matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
445
- const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
446
- const final = build(dest, nextMatches);
447
- if (maskedFinal) {
448
- final.maskedLocation = maskedFinal;
449
- }
450
- return final;
451
- };
452
- if (opts.mask) {
453
- return buildWithMatches(opts, {
454
- ...utils.pick(opts, ['from']),
455
- ...opts.mask
456
- });
457
- }
458
- return buildWithMatches(opts);
459
- });
460
- const commitLocation = utils.useStableCallback(async ({
461
- startTransition,
462
- ...next
463
- }) => {
464
- if (navigateTimeoutRef.current) clearTimeout(navigateTimeoutRef.current);
465
- const isSameUrl = latestLocationRef.current.href === next.href;
466
-
467
- // If the next urls are the same and we're not replacing,
468
- // do nothing
469
- if (!isSameUrl || !next.replace) {
470
- let {
471
- maskedLocation,
472
- ...nextHistory
473
- } = next;
474
- if (maskedLocation) {
475
- nextHistory = {
476
- ...maskedLocation,
477
- state: {
478
- ...maskedLocation.state,
479
- __tempKey: undefined,
480
- __tempLocation: {
481
- ...nextHistory,
482
- search: nextHistory.searchStr,
483
- state: {
484
- ...nextHistory.state,
485
- __tempKey: undefined,
486
- __tempLocation: undefined,
487
- key: undefined
488
- }
489
- }
490
- }
491
- };
492
- if (nextHistory.unmaskOnReload ?? options.unmaskOnReload ?? false) {
493
- nextHistory.state.__tempKey = tempLocationKeyRef.current;
494
- }
495
- }
496
- const apply = () => {
497
- history$1[next.replace ? 'replace' : 'push'](nextHistory.href, nextHistory.state);
498
- };
499
- if (startTransition ?? true) {
500
- startReactTransition(apply);
501
- } else {
502
- apply();
503
- }
504
- }
505
- resetNextScrollRef.current = next.resetScroll ?? true;
506
- return latestLoadPromiseRef.current;
507
- });
508
- const buildAndCommitLocation = utils.useStableCallback(({
509
- replace,
510
- resetScroll,
511
- startTransition,
512
- ...rest
513
- } = {}) => {
514
- const location = buildLocation(rest);
515
- return commitLocation({
516
- ...location,
517
- startTransition,
518
- replace,
519
- resetScroll
520
- });
521
- });
522
- const navigate = utils.useStableCallback(({
523
- from,
524
- to = '',
525
- ...rest
526
- }) => {
527
- // If this link simply reloads the current route,
528
- // make sure it has a new key so it will trigger a data refresh
529
-
530
- // If this `to` is a valid external URL, return
531
- // null for LinkUtils
532
- const toString = String(to);
533
- const fromString = typeof from === 'undefined' ? from : String(from);
534
- let isExternal;
535
- try {
536
- new URL(`${toString}`);
537
- isExternal = true;
538
- } catch (e) {}
539
- invariant__default["default"](!isExternal, 'Attempting to navigate to external url with this.navigate!');
540
- return buildAndCommitLocation({
541
- ...rest,
542
- from: fromString,
543
- to: toString
544
- });
545
- });
546
- const loadMatches = utils.useStableCallback(async ({
547
- checkLatest,
548
- matches,
549
- preload
550
- }) => {
551
- let latestPromise;
552
- let firstBadMatchIndex;
553
-
554
- // Check each match middleware to see if the route can be accessed
555
- try {
556
- for (let [index, match] of matches.entries()) {
557
- const parentMatch = matches[index - 1];
558
- const route = looseRoutesById[match.routeId];
559
- const handleError = (err, code) => {
560
- err.routerCode = code;
561
- firstBadMatchIndex = firstBadMatchIndex ?? index;
562
- if (redirects.isRedirect(err)) {
563
- throw err;
564
- }
565
- try {
566
- route.options.onError?.(err);
567
- } catch (errorHandlerErr) {
568
- err = errorHandlerErr;
569
- if (redirects.isRedirect(errorHandlerErr)) {
570
- throw errorHandlerErr;
571
- }
572
- }
573
- matches[index] = match = {
574
- ...match,
575
- error: err,
576
- status: 'error',
577
- updatedAt: Date.now()
578
- };
579
- };
580
- try {
581
- if (match.paramsError) {
582
- handleError(match.paramsError, 'PARSE_PARAMS');
583
- }
584
- if (match.searchError) {
585
- handleError(match.searchError, 'VALIDATE_SEARCH');
586
- }
587
- const parentContext = parentMatch?.context ?? options.context ?? {};
588
- const beforeLoadContext = (await route.options.beforeLoad?.({
589
- search: match.search,
590
- abortController: match.abortController,
591
- params: match.params,
592
- preload: !!preload,
593
- context: parentContext,
594
- location: state.location,
595
- navigate: opts => navigate({
596
- ...opts,
597
- from: match.pathname
598
- }),
599
- buildLocation
600
- })) ?? {};
601
- const context = {
602
- ...parentContext,
603
- ...beforeLoadContext
604
- };
605
- matches[index] = match = {
606
- ...match,
607
- context: utils.replaceEqualDeep(match.context, context)
608
- };
609
- } catch (err) {
610
- handleError(err, 'BEFORE_LOAD');
611
- break;
612
- }
613
- }
614
- } catch (err) {
615
- if (redirects.isRedirect(err)) {
616
- if (!preload) navigate(err);
617
- return matches;
618
- }
619
- throw err;
620
- }
621
- const validResolvedMatches = matches.slice(0, firstBadMatchIndex);
622
- const matchPromises = [];
623
- validResolvedMatches.forEach((match, index) => {
624
- matchPromises.push((async () => {
625
- const parentMatchPromise = matchPromises[index - 1];
626
- const route = looseRoutesById[match.routeId];
627
- const handleIfRedirect = err => {
628
- if (redirects.isRedirect(err)) {
629
- if (!preload) {
630
- navigate(err);
631
- }
632
- return true;
633
- }
634
- return false;
635
- };
636
- let loadPromise;
637
- matches[index] = match = {
638
- ...match,
639
- fetchedAt: Date.now(),
640
- invalid: false
641
- };
642
- if (match.isFetching) {
643
- loadPromise = getRouteMatch(state, match.id)?.loadPromise;
644
- } else {
645
- const cause = state.matches.find(d => d.id === match.id) ? 'stay' : 'enter';
646
- const loaderContext = {
647
- params: match.params,
648
- search: match.search,
649
- preload: !!preload,
650
- parentMatchPromise,
651
- abortController: match.abortController,
652
- context: match.context,
653
- location: state.location,
654
- navigate: opts => navigate({
655
- ...opts,
656
- from: match.pathname
657
- }),
658
- cause
659
- };
660
-
661
- // Default to reloading the route all the time
662
- let shouldReload = true;
663
- let shouldReloadDeps = typeof route.options.shouldReload === 'function' ? route.options.shouldReload?.(loaderContext) : !!(route.options.shouldReload ?? true);
664
- if (cause === 'enter') {
665
- match.shouldReloadDeps = shouldReloadDeps;
666
- } else if (cause === 'stay') {
667
- if (typeof shouldReloadDeps === 'object') {
668
- // compare the deps to see if they've changed
669
- shouldReload = !utils.deepEqual(shouldReloadDeps, match.shouldReloadDeps);
670
- match.shouldReloadDeps = shouldReloadDeps;
671
- } else {
672
- shouldReload = !!shouldReloadDeps;
673
- }
674
- }
675
-
676
- // If the user doesn't want the route to reload, just
677
- // resolve with the existing loader data
678
-
679
- if (!shouldReload) {
680
- loadPromise = Promise.resolve(match.loaderData);
681
- } else {
682
- // Otherwise, load the route
683
- matches[index] = match = {
684
- ...match,
685
- isFetching: true
686
- };
687
- const componentsPromise = Promise.all(router.componentTypes.map(async type => {
688
- const component = route.options[type];
689
- if (component?.preload) {
690
- await component.preload();
691
- }
692
- }));
693
- const loaderPromise = route.options.loader?.(loaderContext);
694
- loadPromise = Promise.all([componentsPromise, loaderPromise]).then(d => d[1]);
695
- }
696
- }
697
- matches[index] = match = {
698
- ...match,
699
- loadPromise
700
- };
701
- if (!preload) {
702
- setState(s => ({
703
- ...s,
704
- matches: s.matches.map(d => d.id === match.id ? match : d)
705
- }));
706
- }
707
- try {
708
- const loaderData = await loadPromise;
709
- if (latestPromise = checkLatest()) return await latestPromise;
710
- matches[index] = match = {
711
- ...match,
712
- error: undefined,
713
- status: 'success',
714
- isFetching: false,
715
- updatedAt: Date.now(),
716
- loaderData,
717
- loadPromise: undefined
718
- };
719
- } catch (error) {
720
- if (latestPromise = checkLatest()) return await latestPromise;
721
- if (handleIfRedirect(error)) return;
722
- try {
723
- route.options.onError?.(error);
724
- } catch (onErrorError) {
725
- error = onErrorError;
726
- if (handleIfRedirect(onErrorError)) return;
727
- }
728
- matches[index] = match = {
729
- ...match,
730
- error,
731
- status: 'error',
732
- isFetching: false,
733
- updatedAt: Date.now()
734
- };
735
- }
736
- if (!preload) {
737
- setState(s => ({
738
- ...s,
739
- matches: s.matches.map(d => d.id === match.id ? match : d)
740
- }));
741
- }
742
- })());
743
- });
744
- await Promise.all(matchPromises);
745
- return matches;
746
- });
747
- const load = utils.useStableCallback(async () => {
748
- const promise = new Promise(async (resolve, reject) => {
749
- const next = latestLocationRef.current;
750
- const prevLocation = state.resolvedLocation;
751
- const pathDidChange = prevLocation.href !== next.href;
752
- let latestPromise;
753
-
754
- // Cancel any pending matches
755
- cancelMatches(state);
756
- router$1.emit({
757
- type: 'onBeforeLoad',
758
- fromLocation: prevLocation,
759
- toLocation: next,
760
- pathChanged: pathDidChange
761
- });
762
-
763
- // Match the routes
764
- let matches = matchRoutes(next.pathname, next.search, {
765
- debug: true
766
- });
767
- pendingMatchesRef.current = matches;
768
- const previousMatches = state.matches;
769
-
770
- // Ingest the new matches
771
- setState(s => ({
772
- ...s,
773
- status: 'pending',
774
- location: next,
775
- matches
776
- }));
777
- try {
778
- try {
779
- // Load the matches
780
- await loadMatches({
781
- matches,
782
- checkLatest: () => checkLatest(promise)
783
- });
784
- } catch (err) {
785
- // swallow this error, since we'll display the
786
- // errors on the route components
787
- }
788
-
789
- // Only apply the latest transition
790
- if (latestPromise = checkLatest(promise)) {
791
- return latestPromise;
792
- }
793
- const exitingMatchIds = previousMatches.filter(id => !pendingMatchesRef.current.includes(id));
794
- const enteringMatchIds = pendingMatchesRef.current.filter(id => !previousMatches.includes(id));
795
- const stayingMatchIds = previousMatches.filter(id => pendingMatchesRef.current.includes(id))
796
-
797
- // setState((s) => ({
798
- // ...s,
799
- // status: 'idle',
800
- // resolvedLocation: s.location,
801
- // }))
802
-
803
- //
804
- ;
805
- [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matches, hook]) => {
806
- matches.forEach(match => {
807
- looseRoutesById[match.routeId].options[hook]?.(match);
808
- });
809
- });
810
- router$1.emit({
811
- type: 'onLoad',
812
- fromLocation: prevLocation,
813
- toLocation: next,
814
- pathChanged: pathDidChange
815
- });
816
- resolve();
817
- } catch (err) {
818
- // Only apply the latest transition
819
- if (latestPromise = checkLatest(promise)) {
820
- return latestPromise;
821
- }
822
- reject(err);
823
- }
824
- });
825
- latestLoadPromiseRef.current = promise;
826
- return latestLoadPromiseRef.current;
827
- });
828
- const preloadRoute = utils.useStableCallback(async (navigateOpts = state.location) => {
829
- let next = buildLocation(navigateOpts);
830
- let matches = matchRoutes(next.pathname, next.search, {
831
- throwOnError: true
832
- });
833
- await loadMatches({
834
- matches,
835
- preload: true,
836
- checkLatest: () => undefined
837
- });
838
- return [utils.last(matches), matches];
839
- });
840
- const buildLink = utils.useStableCallback(dest => {
841
- // If this link simply reloads the current route,
842
- // make sure it has a new key so it will trigger a data refresh
843
-
844
- // If this `to` is a valid external URL, return
845
- // null for LinkUtils
846
-
847
- const {
848
- to,
849
- preload: userPreload,
850
- preloadDelay: userPreloadDelay,
851
- activeOptions,
852
- disabled,
853
- target,
854
- replace,
855
- resetScroll,
856
- startTransition
857
- } = dest;
858
- try {
859
- new URL(`${to}`);
860
- return {
861
- type: 'external',
862
- href: to
863
- };
864
- } catch (e) {}
865
- const nextOpts = dest;
866
- const next = buildLocation(nextOpts);
867
- const preload = userPreload ?? options.defaultPreload;
868
- const preloadDelay = userPreloadDelay ?? options.defaultPreloadDelay ?? 0;
869
-
870
- // Compare path/hash for matches
871
- const currentPathSplit = latestLocationRef.current.pathname.split('/');
872
- const nextPathSplit = next.pathname.split('/');
873
- const pathIsFuzzyEqual = nextPathSplit.every((d, i) => d === currentPathSplit[i]);
874
- // Combine the matches based on user options
875
- const pathTest = activeOptions?.exact ? latestLocationRef.current.pathname === next.pathname : pathIsFuzzyEqual;
876
- const hashTest = activeOptions?.includeHash ? latestLocationRef.current.hash === next.hash : true;
877
- const searchTest = activeOptions?.includeSearch ?? true ? utils.deepEqual(latestLocationRef.current.search, next.search, true) : true;
878
-
879
- // The final "active" test
880
- const isActive = pathTest && hashTest && searchTest;
881
-
882
- // The click handler
883
- const handleClick = e => {
884
- if (!disabled && !isCtrlEvent(e) && !e.defaultPrevented && (!target || target === '_self') && e.button === 0) {
885
- e.preventDefault();
886
-
887
- // All is well? Navigate!
888
- commitLocation({
889
- ...next,
890
- replace,
891
- resetScroll,
892
- startTransition
893
- });
894
- }
895
- };
896
-
897
- // The click handler
898
- const handleFocus = e => {
899
- if (preload) {
900
- preloadRoute(nextOpts).catch(err => {
901
- console.warn(err);
902
- console.warn(preloadWarning);
903
- });
904
- }
905
- };
906
- const handleTouchStart = e => {
907
- preloadRoute(nextOpts).catch(err => {
908
- console.warn(err);
909
- console.warn(preloadWarning);
910
- });
911
- };
912
- const handleEnter = e => {
913
- const target = e.target || {};
914
- if (preload) {
915
- if (target.preloadTimeout) {
916
- return;
917
- }
918
- target.preloadTimeout = setTimeout(() => {
919
- target.preloadTimeout = null;
920
- preloadRoute(nextOpts).catch(err => {
921
- console.warn(err);
922
- console.warn(preloadWarning);
923
- });
924
- }, preloadDelay);
925
- }
926
- };
927
- const handleLeave = e => {
928
- const target = e.target || {};
929
- if (target.preloadTimeout) {
930
- clearTimeout(target.preloadTimeout);
931
- target.preloadTimeout = null;
932
- }
933
- };
934
- return {
935
- type: 'internal',
936
- next,
937
- handleFocus,
938
- handleClick,
939
- handleEnter,
940
- handleLeave,
941
- handleTouchStart,
942
- isActive,
943
- disabled
944
- };
945
- });
946
- React__namespace.useLayoutEffect(() => {
947
- const unsub = history$1.subscribe(() => {
948
- latestLocationRef.current = parseLocation(latestLocationRef.current);
949
- if (state.location !== latestLocationRef.current) {
107
+ const unsub = router.history.subscribe(() => {
108
+ router.latestLocation = router.parseLocation(router.latestLocation);
109
+ if (state.location !== router.latestLocation) {
950
110
  startReactTransition(() => {
951
111
  try {
952
- load();
112
+ router.load();
953
113
  } catch (err) {
954
114
  console.error(err);
955
115
  }
956
116
  });
957
117
  }
958
118
  });
959
- const nextLocation = buildLocation({
119
+ const nextLocation = router.buildLocation({
960
120
  search: true,
961
121
  params: true,
962
122
  hash: true,
963
123
  state: true
964
124
  });
965
125
  if (state.location.href !== nextLocation.href) {
966
- commitLocation({
126
+ router.commitLocation({
967
127
  ...nextLocation,
968
128
  replace: true
969
129
  });
@@ -971,92 +131,33 @@ function RouterProvider({
971
131
  return () => {
972
132
  unsub();
973
133
  };
974
- }, [history$1]);
975
- const matchRoute = utils.useStableCallback((location, opts) => {
976
- location = {
977
- ...location,
978
- to: location.to ? resolvePathWithBase(location.from || '', location.to) : undefined
979
- };
980
- const next = buildLocation(location);
981
- if (opts?.pending && state.status !== 'pending') {
982
- return false;
983
- }
984
- const baseLocation = opts?.pending ? latestLocationRef.current : state.resolvedLocation;
985
-
986
- // const baseLocation = state.resolvedLocation
987
-
988
- if (!baseLocation) {
989
- return false;
990
- }
991
- const match = path.matchPathname(basepath, baseLocation.pathname, {
992
- ...opts,
993
- to: next.pathname
994
- });
995
- if (!match) {
996
- return false;
997
- }
998
- if (match && (opts?.includeSearch ?? true)) {
999
- return utils.deepEqual(baseLocation.search, next.search, true) ? match : false;
1000
- }
1001
- return match;
1002
- });
1003
- const injectedHtmlRef = React__namespace.useRef([]);
1004
- const injectHtml = utils.useStableCallback(async html => {
1005
- injectedHtmlRef.current.push(html);
1006
- });
1007
- const dehydrateData = utils.useStableCallback((key, getData) => {
1008
- if (typeof document === 'undefined') {
1009
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1010
- injectHtml(async () => {
1011
- const id = `__TSR_DEHYDRATED__${strKey}`;
1012
- const data = typeof getData === 'function' ? await getData() : getData;
1013
- return `<script id='${id}' suppressHydrationWarning>window["__TSR_DEHYDRATED__${utils.escapeJSON(strKey)}"] = ${JSON.stringify(data)}
1014
- ;(() => {
1015
- var el = document.getElementById('${id}')
1016
- el.parentElement.removeChild(el)
1017
- })()
1018
- </script>`;
134
+ }, [history]);
135
+ React__namespace.useLayoutEffect(() => {
136
+ if (!isTransitioning && state.resolvedLocation !== state.location) {
137
+ router.emit({
138
+ type: 'onResolved',
139
+ fromLocation: state.resolvedLocation,
140
+ toLocation: state.location,
141
+ pathChanged: state.location.href !== state.resolvedLocation?.href
1019
142
  });
1020
- return () => hydrateData(key);
1021
- }
1022
- return () => undefined;
1023
- });
1024
- const hydrateData = utils.useStableCallback(key => {
1025
- if (typeof document !== 'undefined') {
1026
- const strKey = typeof key === 'string' ? key : JSON.stringify(key);
1027
- return window[`__TSR_DEHYDRATED__${strKey}`];
143
+ router.pendingMatches = [];
144
+ setState(s => ({
145
+ ...s,
146
+ resolvedLocation: s.location
147
+ }));
1028
148
  }
1029
- return undefined;
1030
149
  });
1031
150
  React__namespace.useLayoutEffect(() => {
1032
151
  startReactTransition(() => {
1033
152
  try {
1034
- load();
153
+ router.load();
1035
154
  } catch (err) {
1036
155
  console.error(err);
1037
156
  }
1038
157
  });
1039
158
  }, []);
1040
- const routerContextValue = {
1041
- routeTree: router$1.routeTree,
1042
- navigate,
1043
- buildLink,
1044
- state,
1045
- matchRoute,
1046
- routesById,
1047
- options,
1048
- history: history$1,
1049
- load,
1050
- buildLocation,
1051
- subscribe: router$1.subscribe,
1052
- resetNextScrollRef,
1053
- injectedHtmlRef,
1054
- injectHtml,
1055
- dehydrateData,
1056
- hydrateData
1057
- };
1058
159
  return /*#__PURE__*/React__namespace.createElement(routerContext.Provider, {
1059
- value: routerContextValue
160
+ value: router
1060
161
  }, /*#__PURE__*/React__namespace.createElement(Matches.Matches, null));
1061
162
  }
1062
163
  function getRouteMatch(state, id) {