@tanstack/router-core 1.154.13 → 1.155.0

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.
@@ -2,7 +2,7 @@ import { Store, batch } from "@tanstack/store";
2
2
  import { createBrowserHistory, parseHref } from "@tanstack/history";
3
3
  import { createControlledPromise, isDangerousProtocol, deepEqual, replaceEqualDeep, decodePath, functionalUpdate, last, findLast } from "./utils.js";
4
4
  import { processRouteTree, processRouteMasks, findSingleMatch, findRouteMatch, findFlatMatch } from "./new-process-route-tree.js";
5
- import { trimPath, resolvePath, cleanPath, trimPathRight, interpolatePath } from "./path.js";
5
+ import { compileDecodeCharMap, trimPath, resolvePath, cleanPath, trimPathRight, interpolatePath } from "./path.js";
6
6
  import { createLRUCache } from "./lru-cache.js";
7
7
  import { isNotFound } from "./not-found.js";
8
8
  import { setupScrollRestoration } from "./scroll-restoration.js";
@@ -47,6 +47,7 @@ class RouterCore {
47
47
  this.tempLocationKey = `${Math.round(
48
48
  Math.random() * 1e7
49
49
  )}`;
50
+ this.resetNextScroll = true;
50
51
  this.shouldViewTransition = void 0;
51
52
  this.isViewTransitionTypesSupported = void 0;
52
53
  this.subscribers = /* @__PURE__ */ new Set();
@@ -68,12 +69,10 @@ class RouterCore {
68
69
  ...newOptions
69
70
  };
70
71
  this.isServer = this.options.isServer ?? typeof document === "undefined";
71
- this.pathParamsDecodeCharMap = this.options.pathParamsAllowedCharacters ? new Map(
72
- this.options.pathParamsAllowedCharacters.map((char) => [
73
- encodeURIComponent(char),
74
- char
75
- ])
76
- ) : void 0;
72
+ if (this.options.pathParamsAllowedCharacters)
73
+ this.pathParamsDecoder = compileDecodeCharMap(
74
+ this.options.pathParamsAllowedCharacters
75
+ );
77
76
  if (!this.history || this.options.history && this.options.history !== this.history) {
78
77
  if (!this.options.history) {
79
78
  if (!this.isServer) {
@@ -248,9 +247,9 @@ class RouterCore {
248
247
  search: locationSearchOrOpts
249
248
  },
250
249
  opts
251
- ).matches;
250
+ );
252
251
  }
253
- return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts).matches;
252
+ return this.matchRoutesInternal(pathnameOrNext, locationSearchOrOpts);
254
253
  };
255
254
  this.getMatchedRoutes = (pathname) => {
256
255
  return getMatchedRoutes({
@@ -316,14 +315,12 @@ class RouterCore {
316
315
  }).interpolatedPath;
317
316
  const destMatchResult = this.getMatchedRoutes(interpolatedNextTo);
318
317
  let destRoutes = destMatchResult.matchedRoutes;
319
- const rawParams = destMatchResult.routeParams;
320
318
  const isGlobalNotFound = destMatchResult.foundRoute ? destMatchResult.foundRoute.path !== "/" && destMatchResult.routeParams["**"] : trimPathRight(interpolatedNextTo);
321
- let globalNotFoundRouteId;
322
319
  if (isGlobalNotFound) {
323
320
  if (this.options.notFoundRoute) {
324
321
  destRoutes = [...destRoutes, this.options.notFoundRoute];
325
322
  } else {
326
- globalNotFoundRouteId = findGlobalNotFoundRouteId(
323
+ findGlobalNotFoundRouteId(
327
324
  this.options.notFoundMode,
328
325
  destRoutes
329
326
  );
@@ -345,7 +342,7 @@ class RouterCore {
345
342
  interpolatePath({
346
343
  path: nextTo,
347
344
  params: nextParams,
348
- decodeCharMap: this.pathParamsDecodeCharMap
345
+ decoder: this.pathParamsDecoder
349
346
  }).interpolatedPath
350
347
  );
351
348
  let nextSearch = fromSearch;
@@ -379,31 +376,19 @@ class RouterCore {
379
376
  const hashStr = hash ? `#${hash}` : "";
380
377
  let nextState = dest.state === true ? currentLocation.state : dest.state ? functionalUpdate(dest.state, currentLocation.state) : {};
381
378
  nextState = replaceEqualDeep(currentLocation.state, nextState);
382
- const snapshotParams = {
383
- ...rawParams,
384
- ...nextParams
385
- };
386
- const matchSnapshot = buildMatchSnapshotFromRoutes({
387
- routes: destRoutes,
388
- params: snapshotParams,
389
- searchStr,
390
- globalNotFoundRouteId
391
- });
392
379
  const fullPath = `${nextPathname}${searchStr}${hashStr}`;
393
380
  const url = new URL(fullPath, this.origin);
394
381
  const rewrittenUrl = executeRewriteOutput(this.rewrite, url);
395
- const encodedHref = url.href.replace(url.origin, "");
396
382
  return {
397
383
  publicHref: rewrittenUrl.pathname + rewrittenUrl.search + rewrittenUrl.hash,
398
- href: encodedHref,
384
+ href: fullPath,
399
385
  url: rewrittenUrl,
400
386
  pathname: nextPathname,
401
387
  search: nextSearch,
402
388
  searchStr,
403
389
  state: nextState,
404
390
  hash: hash ?? "",
405
- unmaskOnReload: dest.unmaskOnReload,
406
- _matchSnapshot: matchSnapshot
391
+ unmaskOnReload: dest.unmaskOnReload
407
392
  };
408
393
  };
409
394
  const buildWithMatches = (dest = {}, maskedDest) => {
@@ -475,73 +460,53 @@ class RouterCore {
475
460
  });
476
461
  if (isSameUrl && isSameState()) {
477
462
  this.load();
478
- return this.commitLocationPromise;
479
- }
480
- let {
481
- // eslint-disable-next-line prefer-const
482
- maskedLocation,
483
- // eslint-disable-next-line prefer-const
484
- hashScrollIntoView,
485
- // don't pass url into history since it is a URL instance that cannot be serialized
486
- // eslint-disable-next-line prefer-const
487
- url: _url,
488
- ...nextHistory
489
- } = next;
490
- if (maskedLocation) {
491
- nextHistory = {
492
- ...maskedLocation,
493
- state: {
494
- ...maskedLocation.state,
495
- __tempKey: void 0,
496
- __tempLocation: {
497
- ...nextHistory,
498
- search: nextHistory.searchStr,
499
- state: {
500
- ...nextHistory.state,
501
- __tempKey: void 0,
502
- __tempLocation: void 0,
503
- __TSR_key: void 0,
504
- key: void 0
505
- // TODO: Remove in v2 - use __TSR_key instead
463
+ } else {
464
+ let {
465
+ // eslint-disable-next-line prefer-const
466
+ maskedLocation,
467
+ // eslint-disable-next-line prefer-const
468
+ hashScrollIntoView,
469
+ // don't pass url into history since it is a URL instance that cannot be serialized
470
+ // eslint-disable-next-line prefer-const
471
+ url: _url,
472
+ ...nextHistory
473
+ } = next;
474
+ if (maskedLocation) {
475
+ nextHistory = {
476
+ ...maskedLocation,
477
+ state: {
478
+ ...maskedLocation.state,
479
+ __tempKey: void 0,
480
+ __tempLocation: {
481
+ ...nextHistory,
482
+ search: nextHistory.searchStr,
483
+ state: {
484
+ ...nextHistory.state,
485
+ __tempKey: void 0,
486
+ __tempLocation: void 0,
487
+ __TSR_key: void 0,
488
+ key: void 0
489
+ // TODO: Remove in v2 - use __TSR_key instead
490
+ }
506
491
  }
507
492
  }
493
+ };
494
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
495
+ nextHistory.state.__tempKey = this.tempLocationKey;
508
496
  }
509
- };
510
- if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
511
- nextHistory.state.__tempKey = this.tempLocationKey;
512
497
  }
498
+ nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
499
+ this.shouldViewTransition = viewTransition;
500
+ this.history[next.replace ? "replace" : "push"](
501
+ nextHistory.publicHref,
502
+ nextHistory.state,
503
+ { ignoreBlocker }
504
+ );
513
505
  }
514
- nextHistory.state.__hashScrollIntoViewOptions = hashScrollIntoView ?? this.options.defaultHashScrollIntoView ?? true;
515
- nextHistory.state.__TSR_resetScroll = next.resetScroll ?? true;
516
- this.shouldViewTransition = viewTransition;
517
- nextHistory.state.__TSR_sessionId = this.sessionId;
518
- nextHistory.state.__TSR_matches = next._matchSnapshot ?? buildMatchSnapshot({
519
- matchResult: this.getMatchedRoutes(next.pathname),
520
- pathname: next.pathname,
521
- searchStr: next.searchStr,
522
- notFoundRoute: this.options.notFoundRoute,
523
- notFoundMode: this.options.notFoundMode
524
- });
525
- const precomputedLocation = {
526
- ...next,
527
- publicHref: nextHistory.publicHref,
528
- state: nextHistory.state,
529
- maskedLocation
530
- };
531
- const result = await this.history[next.replace ? "replace" : "push"](
532
- nextHistory.publicHref,
533
- nextHistory.state,
534
- { ignoreBlocker, skipTransitionerLoad: true }
535
- );
536
- if (result.type === "BLOCKED") {
537
- this.commitLocationPromise?.resolve();
538
- return this.commitLocationPromise;
539
- }
540
- if (this.history.location.href !== nextHistory.publicHref) {
541
- return this.commitLocationPromise;
506
+ this.resetNextScroll = next.resetScroll ?? true;
507
+ if (!this.history.subscribers.size) {
508
+ this.load();
542
509
  }
543
- this.latestLocation = precomputedLocation;
544
- this.load({ _skipUpdateLatestLocation: true });
545
510
  return this.commitLocationPromise;
546
511
  };
547
512
  this.buildAndCommitLocation = ({
@@ -648,11 +613,9 @@ class RouterCore {
648
613
  _isNavigate: true
649
614
  });
650
615
  };
651
- this.beforeLoad = (opts) => {
616
+ this.beforeLoad = () => {
652
617
  this.cancelMatches();
653
- if (!opts?._skipUpdateLatestLocation) {
654
- this.updateLatestLocation();
655
- }
618
+ this.updateLatestLocation();
656
619
  if (this.isServer) {
657
620
  const nextLocation = this.buildLocation({
658
621
  to: this.latestLocation.pathname,
@@ -667,8 +630,7 @@ class RouterCore {
667
630
  throw redirect({ href });
668
631
  }
669
632
  }
670
- const snapshot = this.latestLocation.state.__TSR_sessionId === this.sessionId ? this.latestLocation.state.__TSR_matches : void 0;
671
- const pendingMatches = this.matchRoutes(this.latestLocation, { snapshot });
633
+ const pendingMatches = this.matchRoutes(this.latestLocation);
672
634
  this.__store.setState((s) => ({
673
635
  ...s,
674
636
  status: "pending",
@@ -689,9 +651,7 @@ class RouterCore {
689
651
  loadPromise = new Promise((resolve) => {
690
652
  this.startTransition(async () => {
691
653
  try {
692
- this.beforeLoad({
693
- _skipUpdateLatestLocation: opts?._skipUpdateLatestLocation
694
- });
654
+ this.beforeLoad();
695
655
  const next = this.latestLocation;
696
656
  const prevLocation = this.state.resolvedLocation;
697
657
  if (!this.state.redirect) {
@@ -1048,7 +1008,6 @@ class RouterCore {
1048
1008
  (d) => d.status === "notFound" || d.globalNotFound
1049
1009
  );
1050
1010
  };
1051
- this.sessionId = typeof crypto !== "undefined" && "randomUUID" in crypto ? crypto.randomUUID() : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
1052
1011
  this.update({
1053
1012
  defaultPreloadDelay: 50,
1054
1013
  defaultPendingMs: 1e3,
@@ -1077,57 +1036,33 @@ class RouterCore {
1077
1036
  return this.routesById;
1078
1037
  }
1079
1038
  matchRoutesInternal(next, opts) {
1080
- const snapshot = opts?.snapshot;
1081
- const snapshotValid = snapshot && snapshot.routeIds.length > 0 && snapshot.routeIds.every((id) => this.routesById[id]);
1082
- let matchedRoutes;
1083
- let routeParams;
1084
- let rawParams;
1085
- let globalNotFoundRouteId;
1086
- let parsedParams;
1087
- if (snapshotValid) {
1088
- matchedRoutes = snapshot.routeIds.map((id) => this.routesById[id]);
1089
- routeParams = { ...snapshot.params };
1090
- rawParams = { ...snapshot.params };
1091
- globalNotFoundRouteId = snapshot.globalNotFoundRouteId;
1092
- parsedParams = snapshot.parsedParams;
1093
- } else {
1094
- const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
1095
- const { foundRoute, routeParams: rp } = matchedRoutesResult;
1096
- routeParams = rp;
1097
- rawParams = { ...rp };
1098
- matchedRoutes = matchedRoutesResult.matchedRoutes;
1099
- parsedParams = matchedRoutesResult.parsedParams;
1100
- let isGlobalNotFound = false;
1101
- if (
1102
- // If we found a route, and it's not an index route and we have left over path
1103
- foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
1104
- // Or if we didn't find a route and we have left over path
1105
- trimPathRight(next.pathname)
1106
- )
1107
- ) {
1108
- if (this.options.notFoundRoute) {
1109
- matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
1110
- } else {
1111
- isGlobalNotFound = true;
1112
- }
1039
+ const matchedRoutesResult = this.getMatchedRoutes(next.pathname);
1040
+ const { foundRoute, routeParams, parsedParams } = matchedRoutesResult;
1041
+ let { matchedRoutes } = matchedRoutesResult;
1042
+ let isGlobalNotFound = false;
1043
+ if (
1044
+ // If we found a route, and it's not an index route and we have left over path
1045
+ foundRoute ? foundRoute.path !== "/" && routeParams["**"] : (
1046
+ // Or if we didn't find a route and we have left over path
1047
+ trimPathRight(next.pathname)
1048
+ )
1049
+ ) {
1050
+ if (this.options.notFoundRoute) {
1051
+ matchedRoutes = [...matchedRoutes, this.options.notFoundRoute];
1052
+ } else {
1053
+ isGlobalNotFound = true;
1113
1054
  }
1114
- globalNotFoundRouteId = isGlobalNotFound ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes) : void 0;
1115
1055
  }
1056
+ const globalNotFoundRouteId = isGlobalNotFound ? findGlobalNotFoundRouteId(this.options.notFoundMode, matchedRoutes) : void 0;
1116
1057
  const matches = [];
1117
1058
  const getParentContext = (parentMatch) => {
1118
1059
  const parentMatchId = parentMatch?.id;
1119
1060
  const parentContext = !parentMatchId ? this.options.context ?? void 0 : parentMatch.context ?? this.options.context ?? void 0;
1120
1061
  return parentContext;
1121
1062
  };
1122
- const canUseCachedSearch = snapshotValid && snapshot.searchStr === next.searchStr && snapshot.validatedSearches?.length === matchedRoutes.length;
1123
- const validatedSearchesToCache = [];
1124
1063
  matchedRoutes.forEach((route, index) => {
1125
1064
  const parentMatch = matches[index - 1];
1126
1065
  const [preMatchSearch, strictMatchSearch, searchError] = (() => {
1127
- if (canUseCachedSearch) {
1128
- const cached = snapshot.validatedSearches[index];
1129
- return [cached.search, cached.strictSearch, void 0];
1130
- }
1131
1066
  const parentSearch = parentMatch?.search ?? next.search;
1132
1067
  const parentStrictSearch = parentMatch?._strictSearch ?? void 0;
1133
1068
  try {
@@ -1153,12 +1088,6 @@ class RouterCore {
1153
1088
  return [parentSearch, {}, searchParamError];
1154
1089
  }
1155
1090
  })();
1156
- if (!canUseCachedSearch) {
1157
- validatedSearchesToCache.push({
1158
- search: preMatchSearch,
1159
- strictSearch: strictMatchSearch
1160
- });
1161
- }
1162
1091
  const loaderDeps = route.options.loaderDeps?.({
1163
1092
  search: preMatchSearch
1164
1093
  }) ?? "";
@@ -1166,7 +1095,7 @@ class RouterCore {
1166
1095
  const { interpolatedPath, usedParams } = interpolatePath({
1167
1096
  path: route.fullPath,
1168
1097
  params: routeParams,
1169
- decodeCharMap: this.pathParamsDecodeCharMap
1098
+ decoder: this.pathParamsDecoder
1170
1099
  });
1171
1100
  const matchId = (
1172
1101
  // route.id for disambiguation
@@ -1272,13 +1201,6 @@ class RouterCore {
1272
1201
  };
1273
1202
  matches.push(match);
1274
1203
  });
1275
- if (!canUseCachedSearch && validatedSearchesToCache.length > 0) {
1276
- const existingSnapshot = next.state?.__TSR_matches;
1277
- if (existingSnapshot) {
1278
- existingSnapshot.searchStr = next.searchStr;
1279
- existingSnapshot.validatedSearches = validatedSearchesToCache;
1280
- }
1281
- }
1282
1204
  matches.forEach((match, index) => {
1283
1205
  const route = this.looseRoutesById[match.routeId];
1284
1206
  const existingMatch = this.getMatch(match.id);
@@ -1307,7 +1229,7 @@ class RouterCore {
1307
1229
  };
1308
1230
  }
1309
1231
  });
1310
- return { matches, rawParams };
1232
+ return matches;
1311
1233
  }
1312
1234
  }
1313
1235
  class SearchParamError extends Error {
@@ -1366,7 +1288,7 @@ function getMatchedRoutes({
1366
1288
  const routeParams = {};
1367
1289
  const trimmedPath = trimPathRight(pathname);
1368
1290
  let foundRoute = void 0;
1369
- let parsedParams = {};
1291
+ let parsedParams = void 0;
1370
1292
  const match = findRouteMatch(trimmedPath, processedTree, true);
1371
1293
  if (match) {
1372
1294
  foundRoute = match.route;
@@ -1376,64 +1298,6 @@ function getMatchedRoutes({
1376
1298
  const matchedRoutes = match?.branch || [routesById[rootRouteId]];
1377
1299
  return { matchedRoutes, routeParams, foundRoute, parsedParams };
1378
1300
  }
1379
- function buildMatchSnapshot({
1380
- matchResult,
1381
- pathname,
1382
- searchStr,
1383
- notFoundRoute,
1384
- notFoundMode
1385
- }) {
1386
- const snapshot = {
1387
- routeIds: matchResult.matchedRoutes.map((r) => r.id),
1388
- params: matchResult.routeParams,
1389
- parsedParams: matchResult.parsedParams,
1390
- searchStr
1391
- };
1392
- const isGlobalNotFound = matchResult.foundRoute ? matchResult.foundRoute.path !== "/" && matchResult.routeParams["**"] : trimPathRight(pathname);
1393
- if (isGlobalNotFound) {
1394
- if (notFoundRoute) {
1395
- snapshot.globalNotFoundRouteId = notFoundRoute.id;
1396
- } else {
1397
- if (notFoundMode !== "root") {
1398
- for (let i = matchResult.matchedRoutes.length - 1; i >= 0; i--) {
1399
- const route = matchResult.matchedRoutes[i];
1400
- if (route.children) {
1401
- snapshot.globalNotFoundRouteId = route.id;
1402
- break;
1403
- }
1404
- }
1405
- }
1406
- if (!snapshot.globalNotFoundRouteId) {
1407
- snapshot.globalNotFoundRouteId = rootRouteId;
1408
- }
1409
- }
1410
- }
1411
- return snapshot;
1412
- }
1413
- function buildMatchSnapshotFromRoutes({
1414
- routes,
1415
- params,
1416
- searchStr,
1417
- globalNotFoundRouteId
1418
- }) {
1419
- const stringParams = {};
1420
- for (const key in params) {
1421
- const value = params[key];
1422
- if (value != null) {
1423
- stringParams[key] = String(value);
1424
- }
1425
- }
1426
- const snapshot = {
1427
- routeIds: routes.map((r) => r.id),
1428
- params: stringParams,
1429
- parsedParams: params,
1430
- searchStr
1431
- };
1432
- if (globalNotFoundRouteId) {
1433
- snapshot.globalNotFoundRouteId = globalNotFoundRouteId;
1434
- }
1435
- return snapshot;
1436
- }
1437
1301
  function applySearchMiddleware({
1438
1302
  search,
1439
1303
  dest,
@@ -1533,8 +1397,6 @@ export {
1533
1397
  PathParamError,
1534
1398
  RouterCore,
1535
1399
  SearchParamError,
1536
- buildMatchSnapshot,
1537
- buildMatchSnapshotFromRoutes,
1538
1400
  defaultSerializeError,
1539
1401
  getInitialRouterState,
1540
1402
  getLocationChangeInfo,