@tanstack/router-core 1.151.6 → 1.153.2

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.
@@ -47,7 +47,6 @@ class RouterCore {
47
47
  this.tempLocationKey = `${Math.round(
48
48
  Math.random() * 1e7
49
49
  )}`;
50
- this.resetNextScroll = true;
51
50
  this.shouldViewTransition = void 0;
52
51
  this.isViewTransitionTypesSupported = void 0;
53
52
  this.subscribers = /* @__PURE__ */ new Set();
@@ -213,7 +212,7 @@ class RouterCore {
213
212
  pathname: decodePath(url.pathname),
214
213
  searchStr,
215
214
  search: replaceEqualDeep(previousLocation?.search, parsedSearch),
216
- hash: url.hash.split("#").reverse()[0] ?? "",
215
+ hash: decodePath(url.hash.split("#").reverse()[0] ?? ""),
217
216
  state: replaceEqualDeep(previousLocation?.state, state)
218
217
  };
219
218
  };
@@ -315,9 +314,13 @@ class RouterCore {
315
314
  path: nextTo,
316
315
  params: nextParams
317
316
  }).interpolatedPath;
318
- const destRoutes = this.matchRoutes(interpolatedNextTo, void 0, {
317
+ const destMatches = this.matchRoutes(interpolatedNextTo, void 0, {
319
318
  _buildLocation: true
320
- }).map((d) => this.looseRoutesById[d.routeId]);
319
+ });
320
+ const destRoutes = destMatches.map(
321
+ (d) => this.looseRoutesById[d.routeId]
322
+ );
323
+ const globalNotFoundMatch = destMatches.find((m) => m.globalNotFound);
321
324
  if (Object.keys(nextParams).length > 0) {
322
325
  for (const route of destRoutes) {
323
326
  const fn = route.options.params?.stringify ?? route.options.stringifyParams;
@@ -368,19 +371,27 @@ class RouterCore {
368
371
  const hashStr = hash ? `#${hash}` : "";
369
372
  let nextState = dest.state === true ? currentLocation.state : dest.state ? functionalUpdate(dest.state, currentLocation.state) : {};
370
373
  nextState = replaceEqualDeep(currentLocation.state, nextState);
374
+ const matchSnapshot = buildMatchSnapshotFromRoutes({
375
+ routes: destRoutes,
376
+ params: nextParams,
377
+ searchStr,
378
+ globalNotFoundRouteId: globalNotFoundMatch?.routeId
379
+ });
371
380
  const fullPath = `${nextPathname}${searchStr}${hashStr}`;
372
381
  const url = new URL(fullPath, this.origin);
373
382
  const rewrittenUrl = executeRewriteOutput(this.rewrite, url);
383
+ const encodedHref = url.href.replace(url.origin, "");
374
384
  return {
375
385
  publicHref: rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
376
- href: fullPath,
386
+ href: encodedHref,
377
387
  url: rewrittenUrl,
378
388
  pathname: nextPathname,
379
389
  search: nextSearch,
380
390
  searchStr,
381
391
  state: nextState,
382
392
  hash: hash ?? "",
383
- unmaskOnReload: dest.unmaskOnReload
393
+ unmaskOnReload: dest.unmaskOnReload,
394
+ _matchSnapshot: matchSnapshot
384
395
  };
385
396
  };
386
397
  const buildWithMatches = (dest = {}, maskedDest) => {
@@ -423,7 +434,7 @@ class RouterCore {
423
434
  }
424
435
  return buildWithMatches(opts);
425
436
  };
426
- this.commitLocation = ({
437
+ this.commitLocation = async ({
427
438
  viewTransition,
428
439
  ignoreBlocker,
429
440
  ...next
@@ -452,53 +463,73 @@ class RouterCore {
452
463
  });
453
464
  if (isSameUrl && isSameState()) {
454
465
  this.load();
455
- } else {
456
- let {
457
- // eslint-disable-next-line prefer-const
458
- maskedLocation,
459
- // eslint-disable-next-line prefer-const
460
- hashScrollIntoView,
461
- // don't pass url into history since it is a URL instance that cannot be serialized
462
- // eslint-disable-next-line prefer-const
463
- url: _url,
464
- ...nextHistory
465
- } = next;
466
- if (maskedLocation) {
467
- nextHistory = {
468
- ...maskedLocation,
469
- state: {
470
- ...maskedLocation.state,
471
- __tempKey: void 0,
472
- __tempLocation: {
473
- ...nextHistory,
474
- search: nextHistory.searchStr,
475
- state: {
476
- ...nextHistory.state,
477
- __tempKey: void 0,
478
- __tempLocation: void 0,
479
- __TSR_key: void 0,
480
- key: void 0
481
- // TODO: Remove in v2 - use __TSR_key instead
482
- }
466
+ return this.commitLocationPromise;
467
+ }
468
+ let {
469
+ // eslint-disable-next-line prefer-const
470
+ maskedLocation,
471
+ // eslint-disable-next-line prefer-const
472
+ hashScrollIntoView,
473
+ // don't pass url into history since it is a URL instance that cannot be serialized
474
+ // eslint-disable-next-line prefer-const
475
+ url: _url,
476
+ ...nextHistory
477
+ } = next;
478
+ if (maskedLocation) {
479
+ nextHistory = {
480
+ ...maskedLocation,
481
+ state: {
482
+ ...maskedLocation.state,
483
+ __tempKey: void 0,
484
+ __tempLocation: {
485
+ ...nextHistory,
486
+ search: nextHistory.searchStr,
487
+ state: {
488
+ ...nextHistory.state,
489
+ __tempKey: void 0,
490
+ __tempLocation: void 0,
491
+ __TSR_key: void 0,
492
+ key: void 0
493
+ // TODO: Remove in v2 - use __TSR_key instead
483
494
  }
484
495
  }
485
- };
486
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
487
- nextHistory.state.__tempKey = this.tempLocationKey;
488
496
  }
497
+ };
498
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
499
+ nextHistory.state.__tempKey = this.tempLocationKey;
489
500
  }
490
- nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
491
- this.shouldViewTransition = viewTransition;
492
- this.history[next.replace ? "replace" : "push"](
493
- nextHistory.publicHref,
494
- nextHistory.state,
495
- { ignoreBlocker }
496
- );
497
501
  }
498
- this.resetNextScroll = next.resetScroll ?? true;
499
- if (!this.history.subscribers.size) {
500
- this.load();
502
+ nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
503
+ nextHistory.state.__TSR_resetScroll = next.resetScroll ?? true;
504
+ this.shouldViewTransition = viewTransition;
505
+ nextHistory.state.__TSR_sessionId = this.sessionId;
506
+ nextHistory.state.__TSR_matches = next._matchSnapshot ?? buildMatchSnapshot({
507
+ matchResult: this.getMatchedRoutes(next.pathname),
508
+ pathname: next.pathname,
509
+ searchStr: next.searchStr,
510
+ notFoundRoute: this.options.notFoundRoute,
511
+ notFoundMode: this.options.notFoundMode
512
+ });
513
+ const precomputedLocation = {
514
+ ...next,
515
+ publicHref: nextHistory.publicHref,
516
+ state: nextHistory.state,
517
+ maskedLocation
518
+ };
519
+ const result = await this.history[next.replace ? "replace" : "push"](
520
+ nextHistory.publicHref,
521
+ nextHistory.state,
522
+ { ignoreBlocker, skipTransitionerLoad: true }
523
+ );
524
+ if (result.type === "BLOCKED") {
525
+ this.commitLocationPromise?.resolve();
526
+ return this.commitLocationPromise;
527
+ }
528
+ if (this.history.location.href !== nextHistory.publicHref) {
529
+ return this.commitLocationPromise;
501
530
  }
531
+ this.latestLocation = precomputedLocation;
532
+ this.load({ _skipUpdateLatestLocation: true });
502
533
  return this.commitLocationPromise;
503
534
  };
504
535
  this.buildAndCommitLocation = ({
@@ -605,9 +636,11 @@ class RouterCore {
605
636
  _isNavigate: true
606
637
  });
607
638
  };
608
- this.beforeLoad = () => {
639
+ this.beforeLoad = (opts) => {
609
640
  this.cancelMatches();
610
- this.updateLatestLocation();
641
+ if (!opts?._skipUpdateLatestLocation) {
642
+ this.updateLatestLocation();
643
+ }
611
644
  if (this.isServer) {
612
645
  const nextLocation = this.buildLocation({
613
646
  to: this.latestLocation.pathname,
@@ -622,7 +655,8 @@ class RouterCore {
622
655
  throw redirect({ href });
623
656
  }
624
657
  }
625
- const pendingMatches = this.matchRoutes(this.latestLocation);
658
+ const snapshot = this.latestLocation.state.__TSR_sessionId === this.sessionId ? this.latestLocation.state.__TSR_matches : void 0;
659
+ const pendingMatches = this.matchRoutes(this.latestLocation, { snapshot });
626
660
  this.__store.setState((s) => ({
627
661
  ...s,
628
662
  status: "pending",
@@ -643,7 +677,9 @@ class RouterCore {
643
677
  loadPromise = new Promise((resolve) => {
644
678
  this.startTransition(async () => {
645
679
  try {
646
- this.beforeLoad();
680
+ this.beforeLoad({
681
+ _skipUpdateLatestLocation: opts?._skipUpdateLatestLocation
682
+ });
647
683
  const next = this.latestLocation;
648
684
  const prevLocation = this.state.resolvedLocation;
649
685
  if (!this.state.redirect) {
@@ -1000,6 +1036,7 @@ class RouterCore {
1000
1036
  (d) => d.status === "notFound" || d.globalNotFound
1001
1037
  );
1002
1038
  };
1039
+ this.sessionId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1003
1040
  this.update({
1004
1041
  defaultPreloadDelay: 50,
1005
1042
  defaultPendingMs: 1e3,
@@ -1028,46 +1065,67 @@ class RouterCore {
1028
1065
  return this.routesById;
1029
1066
  }
1030
1067
  matchRoutesInternal(next, opts) {
1031
- const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
1032
- const { foundRoute, routeParams, parsedParams } = matchedRoutesResult;
1033
- let { matchedRoutes } = matchedRoutesResult;
1034
- let isGlobalNotFound = false;
1035
- if (
1036
- // If we found a route, and it's not an index route and we have left over path
1037
- foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
1038
- // Or if we didn't find a route and we have left over path
1039
- trimPathRight(next.pathname)
1040
- )
1041
- ) {
1042
- if (this.options.notFoundRoute) {
1043
- matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
1044
- } else {
1045
- isGlobalNotFound = true;
1046
- }
1047
- }
1048
- const globalNotFoundRouteId = (() => {
1049
- if (!isGlobalNotFound) {
1050
- return void 0;
1068
+ const snapshot = opts?.snapshot;
1069
+ const snapshotValid = snapshot && snapshot.routeIds.length > 0 && snapshot.routeIds.every((id) => this.routesById[id]);
1070
+ let matchedRoutes;
1071
+ let routeParams;
1072
+ let globalNotFoundRouteId;
1073
+ let parsedParams;
1074
+ if (snapshotValid) {
1075
+ matchedRoutes = snapshot.routeIds.map((id) => this.routesById[id]);
1076
+ routeParams = { ...snapshot.params };
1077
+ globalNotFoundRouteId = snapshot.globalNotFoundRouteId;
1078
+ parsedParams = snapshot.parsedParams;
1079
+ } else {
1080
+ const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
1081
+ const { foundRoute, routeParams: rp } = matchedRoutesResult;
1082
+ routeParams = rp;
1083
+ matchedRoutes = matchedRoutesResult.matchedRoutes;
1084
+ parsedParams = matchedRoutesResult.parsedParams;
1085
+ let isGlobalNotFound = false;
1086
+ if (
1087
+ // If we found a route, and it's not an index route and we have left over path
1088
+ foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
1089
+ // Or if we didn't find a route and we have left over path
1090
+ trimPathRight(next.pathname)
1091
+ )
1092
+ ) {
1093
+ if (this.options.notFoundRoute) {
1094
+ matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
1095
+ } else {
1096
+ isGlobalNotFound = true;
1097
+ }
1051
1098
  }
1052
- if (this.options.notFoundMode !== "root") {
1053
- for (let i = matchedRoutes.length - 1; i >= 0; i--) {
1054
- const route = matchedRoutes[i];
1055
- if (route.children) {
1056
- return route.id;
1099
+ globalNotFoundRouteId = (() => {
1100
+ if (!isGlobalNotFound) {
1101
+ return void 0;
1102
+ }
1103
+ if (this.options.notFoundMode !== "root") {
1104
+ for (let i = matchedRoutes.length - 1; i >= 0; i--) {
1105
+ const route = matchedRoutes[i];
1106
+ if (route.children) {
1107
+ return route.id;
1108
+ }
1057
1109
  }
1058
1110
  }
1059
- }
1060
- return rootRouteId;
1061
- })();
1111
+ return rootRouteId;
1112
+ })();
1113
+ }
1062
1114
  const matches = [];
1063
1115
  const getParentContext = (parentMatch) => {
1064
1116
  const parentMatchId = parentMatch?.id;
1065
1117
  const parentContext = !parentMatchId ? this.options.context ?? void 0 : parentMatch.context ?? this.options.context ?? void 0;
1066
1118
  return parentContext;
1067
1119
  };
1120
+ const canUseCachedSearch = snapshotValid && snapshot.searchStr === next.searchStr && snapshot.validatedSearches?.length === matchedRoutes.length;
1121
+ const validatedSearchesToCache = [];
1068
1122
  matchedRoutes.forEach((route, index) => {
1069
1123
  const parentMatch = matches[index - 1];
1070
1124
  const [preMatchSearch, strictMatchSearch, searchError] = (() => {
1125
+ if (canUseCachedSearch) {
1126
+ const cached = snapshot.validatedSearches[index];
1127
+ return [cached.search, cached.strictSearch, void 0];
1128
+ }
1071
1129
  const parentSearch = parentMatch?.search ?? next.search;
1072
1130
  const parentStrictSearch = parentMatch?._strictSearch ?? void 0;
1073
1131
  try {
@@ -1093,6 +1151,12 @@ class RouterCore {
1093
1151
  return [parentSearch, {}, searchParamError];
1094
1152
  }
1095
1153
  })();
1154
+ if (!canUseCachedSearch) {
1155
+ validatedSearchesToCache.push({
1156
+ search: preMatchSearch,
1157
+ strictSearch: strictMatchSearch
1158
+ });
1159
+ }
1096
1160
  const loaderDeps = route.options.loaderDeps?.({
1097
1161
  search: preMatchSearch
1098
1162
  }) ?? "";
@@ -1206,6 +1270,13 @@ class RouterCore {
1206
1270
  };
1207
1271
  matches.push(match);
1208
1272
  });
1273
+ if (!canUseCachedSearch && validatedSearchesToCache.length > 0) {
1274
+ const existingSnapshot = next.state?.__TSR_matches;
1275
+ if (existingSnapshot) {
1276
+ existingSnapshot.searchStr = next.searchStr;
1277
+ existingSnapshot.validatedSearches = validatedSearchesToCache;
1278
+ }
1279
+ }
1209
1280
  matches.forEach((match, index) => {
1210
1281
  const route = this.looseRoutesById[match.routeId];
1211
1282
  const existingMatch = this.getMatch(match.id);
@@ -1293,7 +1364,7 @@ function getMatchedRoutes({
1293
1364
  const routeParams = {};
1294
1365
  const trimmedPath = trimPathRight(pathname);
1295
1366
  let foundRoute = void 0;
1296
- let parsedParams = void 0;
1367
+ let parsedParams = {};
1297
1368
  const match = findRouteMatch(trimmedPath, processedTree, true);
1298
1369
  if (match) {
1299
1370
  foundRoute = match.route;
@@ -1303,6 +1374,64 @@ function getMatchedRoutes({
1303
1374
  const matchedRoutes = match?.branch || [routesById[rootRouteId]];
1304
1375
  return { matchedRoutes, routeParams, foundRoute, parsedParams };
1305
1376
  }
1377
+ function buildMatchSnapshot({
1378
+ matchResult,
1379
+ pathname,
1380
+ searchStr,
1381
+ notFoundRoute,
1382
+ notFoundMode
1383
+ }) {
1384
+ const snapshot = {
1385
+ routeIds: matchResult.matchedRoutes.map((r) => r.id),
1386
+ params: matchResult.routeParams,
1387
+ parsedParams: matchResult.parsedParams,
1388
+ searchStr
1389
+ };
1390
+ const isGlobalNotFound = matchResult.foundRoute ? matchResult.foundRoute.path !== "/" && matchResult.routeParams["**"] : trimPathRight(pathname);
1391
+ if (isGlobalNotFound) {
1392
+ if (notFoundRoute) {
1393
+ snapshot.globalNotFoundRouteId = notFoundRoute.id;
1394
+ } else {
1395
+ if (notFoundMode !== "root") {
1396
+ for (let i = matchResult.matchedRoutes.length - 1; i >= 0; i--) {
1397
+ const route = matchResult.matchedRoutes[i];
1398
+ if (route.children) {
1399
+ snapshot.globalNotFoundRouteId = route.id;
1400
+ break;
1401
+ }
1402
+ }
1403
+ }
1404
+ if (!snapshot.globalNotFoundRouteId) {
1405
+ snapshot.globalNotFoundRouteId = rootRouteId;
1406
+ }
1407
+ }
1408
+ }
1409
+ return snapshot;
1410
+ }
1411
+ function buildMatchSnapshotFromRoutes({
1412
+ routes,
1413
+ params,
1414
+ searchStr,
1415
+ globalNotFoundRouteId
1416
+ }) {
1417
+ const stringParams = {};
1418
+ for (const key in params) {
1419
+ const value = params[key];
1420
+ if (value != null) {
1421
+ stringParams[key] = String(value);
1422
+ }
1423
+ }
1424
+ const snapshot = {
1425
+ routeIds: routes.map((r) => r.id),
1426
+ params: stringParams,
1427
+ parsedParams: params,
1428
+ searchStr
1429
+ };
1430
+ if (globalNotFoundRouteId) {
1431
+ snapshot.globalNotFoundRouteId = globalNotFoundRouteId;
1432
+ }
1433
+ return snapshot;
1434
+ }
1306
1435
  function applySearchMiddleware({
1307
1436
  search,
1308
1437
  dest,
@@ -1384,6 +1513,8 @@ export {
1384
1513
  PathParamError,
1385
1514
  RouterCore,
1386
1515
  SearchParamError,
1516
+ buildMatchSnapshot,
1517
+ buildMatchSnapshotFromRoutes,
1387
1518
  defaultSerializeError,
1388
1519
  getInitialRouterState,
1389
1520
  getLocationChangeInfo,