@tanstack/router-core 0.0.1-beta.184 → 0.0.1-beta.186

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.
@@ -32,6 +32,9 @@ const preloadWarning = 'Error preloading route! ☝️';
32
32
  class Router {
33
33
  #unsubHistory;
34
34
  resetNextScroll = false;
35
+ tempLocationKey = `${Math.round(Math.random() * 10000000)}`;
36
+ // nextTemporaryLocation?: ParsedLocation<FullSearchSchema<TRouteTree>>
37
+
35
38
  constructor(options) {
36
39
  this.options = {
37
40
  defaultPreloadDelay: 50,
@@ -80,15 +83,15 @@ class Router {
80
83
  });
81
84
  this.state = this.__store.state;
82
85
  this.update(options);
83
- const next = this.buildNext({
84
- hash: true,
85
- fromCurrent: true,
86
+ const nextLocation = this.buildLocation({
86
87
  search: true,
88
+ params: true,
89
+ hash: true,
87
90
  state: true
88
91
  });
89
- if (this.state.location.href !== next.href) {
92
+ if (this.state.location.href !== nextLocation.href) {
90
93
  this.#commitLocation({
91
- ...next,
94
+ ...nextLocation,
92
95
  replace: true
93
96
  });
94
97
  }
@@ -131,8 +134,10 @@ class Router {
131
134
  };
132
135
  };
133
136
  #onFocus = () => {
134
- if (this.options.refetchOnWindowFocus ?? true) {
135
- this.invalidate();
137
+ if (this.options.reloadOnWindowFocus ?? true) {
138
+ this.invalidate({
139
+ __fromFocus: true
140
+ });
136
141
  }
137
142
  };
138
143
  update = opts => {
@@ -171,14 +176,6 @@ class Router {
171
176
  }
172
177
  return this;
173
178
  };
174
- buildNext = opts => {
175
- const next = this.#buildLocation(opts);
176
- const __matches = this.matchRoutes(next.pathname, next.search);
177
- return this.#buildLocation({
178
- ...opts,
179
- __matches
180
- });
181
- };
182
179
  cancelMatches = () => {
183
180
  this.state.matches.forEach(match => {
184
181
  this.cancelMatch(match.id);
@@ -237,7 +234,7 @@ class Router {
237
234
  try {
238
235
  // Load the matches
239
236
  try {
240
- await this.loadMatches(pendingMatches);
237
+ await this.loadMatches(pendingMatches.map(d => d.id));
241
238
  } catch (err) {
242
239
  // swallow this error, since we'll display the
243
240
  // errors on the route components
@@ -286,10 +283,19 @@ class Router {
286
283
  return this.latestLoadPromise;
287
284
  };
288
285
  #mergeMatches = (prevMatchesById, nextMatches) => {
289
- return {
290
- ...prevMatchesById,
291
- ...Object.fromEntries(nextMatches.map(match => [match.id, match]))
286
+ let matchesById = {
287
+ ...prevMatchesById
292
288
  };
289
+ nextMatches.forEach(match => {
290
+ if (!matchesById[match.id]) {
291
+ matchesById[match.id] = match;
292
+ }
293
+ matchesById[match.id] = {
294
+ ...matchesById[match.id],
295
+ ...match
296
+ };
297
+ });
298
+ return matchesById;
293
299
  };
294
300
  getRoute = id => {
295
301
  const route = this.routesById[id];
@@ -297,7 +303,7 @@ class Router {
297
303
  return route;
298
304
  };
299
305
  preloadRoute = async (navigateOpts = this.state.location) => {
300
- const next = this.buildNext(navigateOpts);
306
+ let next = this.buildLocation(navigateOpts);
301
307
  const matches = this.matchRoutes(next.pathname, next.search, {
302
308
  throwOnError: true
303
309
  });
@@ -307,11 +313,11 @@ class Router {
307
313
  matchesById: this.#mergeMatches(s.matchesById, matches)
308
314
  };
309
315
  });
310
- await this.loadMatches(matches, {
316
+ await this.loadMatches(matches.map(d => d.id), {
311
317
  preload: true,
312
318
  maxAge: navigateOpts.maxAge
313
319
  });
314
- return matches;
320
+ return [utils.last(matches), matches];
315
321
  };
316
322
  cleanMatches = () => {
317
323
  const now = Date.now();
@@ -465,8 +471,8 @@ class Router {
465
471
  });
466
472
  return matches;
467
473
  };
468
- loadMatches = async (_resolvedMatches, opts) => {
469
- const getFreshMatches = () => _resolvedMatches.map(d => this.getRouteMatch(d.id));
474
+ loadMatches = async (matchIds, opts) => {
475
+ const getFreshMatches = () => matchIds.map(d => this.getRouteMatch(d));
470
476
  if (!opts?.preload) {
471
477
  getFreshMatches().forEach(match => {
472
478
  // Update each match with its latest route data
@@ -546,8 +552,9 @@ class Router {
546
552
  }
547
553
  }
548
554
  } catch (err) {
549
- if (!opts?.preload) {
550
- this.navigate(err);
555
+ if (isRedirect(err)) {
556
+ if (!opts?.preload) this.navigate(err);
557
+ return;
551
558
  }
552
559
  throw err;
553
560
  }
@@ -656,13 +663,13 @@ class Router {
656
663
  isExternal = true;
657
664
  } catch (e) {}
658
665
  invariant__default["default"](!isExternal, 'Attempting to navigate to external url with this.navigate!');
659
- return this.#commitLocation({
666
+ return this.#buildAndCommitLocation({
660
667
  from: fromString,
661
668
  to: toString,
662
669
  search,
663
670
  hash,
664
- replace,
665
671
  params,
672
+ replace,
666
673
  resetScroll
667
674
  });
668
675
  };
@@ -671,7 +678,7 @@ class Router {
671
678
  ...location,
672
679
  to: location.to ? this.resolvePath(location.from ?? '', location.to) : undefined
673
680
  };
674
- const next = this.buildNext(location);
681
+ const next = this.buildLocation(location);
675
682
  if (opts?.pending && this.state.status !== 'pending') {
676
683
  return false;
677
684
  }
@@ -704,6 +711,7 @@ class Router {
704
711
  preloadDelay: userPreloadDelay,
705
712
  disabled,
706
713
  state,
714
+ mask,
707
715
  resetScroll
708
716
  }) => {
709
717
  // If this link simply reloads the current route,
@@ -727,9 +735,10 @@ class Router {
727
735
  hash,
728
736
  replace,
729
737
  state,
738
+ mask,
730
739
  resetScroll
731
740
  };
732
- const next = this.buildNext(nextOpts);
741
+ const next = this.buildLocation(nextOpts);
733
742
  preload = preload ?? this.options.defaultPreload;
734
743
  const preloadDelay = userPreloadDelay ?? this.options.defaultPreloadDelay ?? 0;
735
744
 
@@ -751,7 +760,11 @@ class Router {
751
760
  e.preventDefault();
752
761
 
753
762
  // All is well? Navigate!
754
- this.#commitLocation(nextOpts);
763
+ this.#commitLocation({
764
+ ...next,
765
+ replace,
766
+ resetScroll
767
+ });
755
768
  }
756
769
  };
757
770
 
@@ -964,84 +977,174 @@ class Router {
964
977
  });
965
978
  };
966
979
  #parseLocation = previousLocation => {
967
- let {
980
+ const parse = ({
968
981
  pathname,
969
982
  search,
970
983
  hash,
971
984
  state
972
- } = this.history.location;
973
- const parsedSearch = this.options.parseSearch(search);
974
- return {
975
- pathname: pathname,
976
- searchStr: search,
977
- search: utils.replaceEqualDeep(previousLocation?.search, parsedSearch),
978
- hash: hash.split('#').reverse()[0] ?? '',
979
- href: `${pathname}${search}${hash}`,
980
- state: state,
981
- key: state?.key || '__init__'
982
- };
983
- };
984
- #buildLocation = (dest = {}) => {
985
- dest.fromCurrent = dest.fromCurrent ?? dest.to === '';
986
- const fromPathname = dest.fromCurrent ? this.state.location.pathname : dest.from ?? this.state.location.pathname;
987
- let pathname = path.resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
988
- const fromMatches = this.matchRoutes(this.state.location.pathname, this.state.location.search);
989
- const prevParams = {
990
- ...utils.last(fromMatches)?.params
985
+ }) => {
986
+ const parsedSearch = this.options.parseSearch(search);
987
+ return {
988
+ pathname: pathname,
989
+ searchStr: search,
990
+ search: utils.replaceEqualDeep(previousLocation?.search, parsedSearch),
991
+ hash: hash.split('#').reverse()[0] ?? '',
992
+ href: `${pathname}${search}${hash}`,
993
+ state: utils.replaceEqualDeep(previousLocation?.state, state)
994
+ };
991
995
  };
992
- let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
993
- if (nextParams) {
994
- dest.__matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
995
- nextParams = {
996
- ...nextParams,
997
- ...fn(nextParams)
998
- };
999
- });
996
+ const location = parse(this.history.location);
997
+ let {
998
+ __tempLocation,
999
+ __tempKey
1000
+ } = location.state;
1001
+ if (__tempLocation && (!__tempKey || __tempKey === this.tempLocationKey)) {
1002
+ // Sync up the location keys
1003
+ const parsedTempLocation = parse(__tempLocation);
1004
+ parsedTempLocation.state.key = location.state.key;
1005
+ delete parsedTempLocation.state.__tempLocation;
1006
+ return {
1007
+ ...parsedTempLocation,
1008
+ maskedLocation: location
1009
+ };
1000
1010
  }
1001
- pathname = path.interpolatePath(pathname, nextParams ?? {});
1002
- const preSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1003
- const postSearchFilters = dest.__matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1011
+ return location;
1012
+ };
1013
+ buildLocation = (opts = {}) => {
1014
+ const build = (dest = {}, matches) => {
1015
+ const from = this.state.location;
1016
+ const fromPathname = dest.from ?? from.pathname;
1017
+ let pathname = path.resolvePath(this.basepath ?? '/', fromPathname, `${dest.to ?? ''}`);
1018
+ const fromMatches = this.matchRoutes(from.pathname, from.search);
1019
+ const prevParams = {
1020
+ ...utils.last(fromMatches)?.params
1021
+ };
1022
+ let nextParams = (dest.params ?? true) === true ? prevParams : utils.functionalUpdate(dest.params, prevParams);
1023
+ if (nextParams) {
1024
+ matches?.map(d => this.getRoute(d.routeId).options.stringifyParams).filter(Boolean).forEach(fn => {
1025
+ nextParams = {
1026
+ ...nextParams,
1027
+ ...fn(nextParams)
1028
+ };
1029
+ });
1030
+ }
1031
+ pathname = path.interpolatePath(pathname, nextParams ?? {});
1032
+ const preSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.preSearchFilters ?? []).flat().filter(Boolean) ?? [];
1033
+ const postSearchFilters = matches?.map(match => this.getRoute(match.routeId).options.postSearchFilters ?? []).flat().filter(Boolean) ?? [];
1004
1034
 
1005
- // Pre filters first
1006
- const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), this.state.location.search) : this.state.location.search;
1035
+ // Pre filters first
1036
+ const preFilteredSearch = preSearchFilters?.length ? preSearchFilters?.reduce((prev, next) => next(prev), from.search) : from.search;
1007
1037
 
1008
- // Then the link/navigate function
1009
- const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1010
- : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1011
- : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1012
- : {};
1038
+ // Then the link/navigate function
1039
+ const destSearch = dest.search === true ? preFilteredSearch // Preserve resolvedFrom true
1040
+ : dest.search ? utils.functionalUpdate(dest.search, preFilteredSearch) ?? {} // Updater
1041
+ : preSearchFilters?.length ? preFilteredSearch // Preserve resolvedFrom filters
1042
+ : {};
1013
1043
 
1014
- // Then post filters
1015
- const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1016
- const search = utils.replaceEqualDeep(this.state.location.search, postFilteredSearch);
1017
- const searchStr = this.options.stringifySearch(search);
1018
- const hash = dest.hash === true ? this.state.location.hash : utils.functionalUpdate(dest.hash, this.state.location.hash);
1019
- const hashStr = hash ? `#${hash}` : '';
1020
- const nextState = dest.state === true ? this.state.location.state : utils.functionalUpdate(dest.state, this.state.location.state);
1021
- return {
1022
- pathname,
1023
- search,
1024
- searchStr,
1025
- state: nextState,
1026
- hash,
1027
- href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1028
- key: dest.key
1044
+ // Then post filters
1045
+ const postFilteredSearch = postSearchFilters?.length ? postSearchFilters.reduce((prev, next) => next(prev), destSearch) : destSearch;
1046
+ const search = utils.replaceEqualDeep(from.search, postFilteredSearch);
1047
+ const searchStr = this.options.stringifySearch(search);
1048
+ const hash = dest.hash === true ? from.hash : dest.hash ? utils.functionalUpdate(dest.hash, from.hash) : from.hash;
1049
+ const hashStr = hash ? `#${hash}` : '';
1050
+ let nextState = dest.state === true ? from.state : dest.state ? utils.functionalUpdate(dest.state, from.state) : from.state;
1051
+ nextState = utils.replaceEqualDeep(from.state, nextState);
1052
+ return {
1053
+ pathname,
1054
+ search,
1055
+ searchStr,
1056
+ state: nextState,
1057
+ hash,
1058
+ href: this.history.createHref(`${pathname}${searchStr}${hashStr}`),
1059
+ unmaskOnReload: dest.unmaskOnReload
1060
+ };
1061
+ };
1062
+ const buildWithMatches = (dest = {}, maskedDest) => {
1063
+ let next = build(dest);
1064
+ let maskedNext = maskedDest ? build(maskedDest) : undefined;
1065
+ if (!maskedNext) {
1066
+ const foundMask = this.options.routeMasks?.find(d => {
1067
+ const match = path.matchPathname(this.basepath, next.pathname, {
1068
+ to: d.from,
1069
+ fuzzy: false
1070
+ });
1071
+ if (match) {
1072
+ return match;
1073
+ }
1074
+ return false;
1075
+ });
1076
+ if (foundMask) {
1077
+ maskedDest = foundMask;
1078
+ maskedNext = build(maskedDest);
1079
+ }
1080
+ }
1081
+ const nextMatches = this.matchRoutes(next.pathname, next.search);
1082
+ const maskedMatches = maskedNext ? this.matchRoutes(maskedNext.pathname, maskedNext.search) : undefined;
1083
+ const maskedFinal = maskedNext ? build(maskedDest, maskedMatches) : undefined;
1084
+ const final = build(dest, nextMatches);
1085
+ if (maskedFinal) {
1086
+ final.maskedLocation = maskedFinal;
1087
+ }
1088
+ return final;
1029
1089
  };
1090
+ if (opts.mask) {
1091
+ return buildWithMatches(opts, {
1092
+ ...utils.pick(opts, ['from']),
1093
+ ...opts.mask
1094
+ });
1095
+ }
1096
+ return buildWithMatches(opts);
1030
1097
  };
1031
- #commitLocation = async location => {
1032
- const next = this.buildNext(location);
1098
+ #buildAndCommitLocation = ({
1099
+ replace,
1100
+ resetScroll,
1101
+ ...rest
1102
+ } = {}) => {
1103
+ const location = this.buildLocation(rest);
1104
+ return this.#commitLocation({
1105
+ ...location,
1106
+ replace,
1107
+ resetScroll
1108
+ });
1109
+ };
1110
+ #commitLocation = async next => {
1033
1111
  if (this.navigateTimeout) clearTimeout(this.navigateTimeout);
1034
1112
  let nextAction = 'replace';
1035
- if (!location.replace) {
1113
+ if (!next.replace) {
1036
1114
  nextAction = 'push';
1037
1115
  }
1038
1116
  const isSameUrl = this.state.location.href === next.href;
1039
- if (isSameUrl && !next.key) {
1117
+ if (isSameUrl) {
1040
1118
  nextAction = 'replace';
1041
1119
  }
1042
- const href = `${next.pathname}${next.searchStr}${next.hash ? `#${next.hash}` : ''}`;
1043
- this.history[nextAction === 'push' ? 'push' : 'replace'](href, next.state);
1044
- this.resetNextScroll = location.resetScroll ?? true;
1120
+ let {
1121
+ maskedLocation,
1122
+ ...nextHistory
1123
+ } = next;
1124
+ if (maskedLocation) {
1125
+ nextHistory = {
1126
+ ...maskedLocation,
1127
+ state: {
1128
+ ...maskedLocation.state,
1129
+ __tempKey: undefined,
1130
+ __tempLocation: {
1131
+ ...nextHistory,
1132
+ search: nextHistory.searchStr,
1133
+ state: {
1134
+ ...nextHistory.state,
1135
+ __tempKey: undefined,
1136
+ __tempLocation: undefined,
1137
+ key: undefined
1138
+ }
1139
+ }
1140
+ }
1141
+ };
1142
+ if (nextHistory.unmaskOnReload ?? this.options.unmaskOnReload ?? false) {
1143
+ nextHistory.state.__tempKey = this.tempLocationKey;
1144
+ }
1145
+ }
1146
+ this.history[nextAction === 'push' ? 'push' : 'replace'](nextHistory.href, nextHistory.state);
1147
+ this.resetNextScroll = next.resetScroll ?? true;
1045
1148
  return this.latestLoadPromise;
1046
1149
  };
1047
1150
  getRouteMatch = id => {
@@ -1090,25 +1193,26 @@ class Router {
1090
1193
  if (childMatch) {
1091
1194
  return this.invalidate({
1092
1195
  matchId: childMatch.id,
1093
- reload: false
1196
+ reload: false,
1197
+ __fromFocus: opts.__fromFocus
1094
1198
  });
1095
1199
  }
1096
1200
  } else {
1097
1201
  this.__store.batch(() => {
1098
1202
  Object.values(this.state.matchesById).forEach(match => {
1099
- this.setRouteMatch(match.id, s => ({
1100
- ...s,
1101
- invalid: true
1102
- }));
1203
+ const route = this.getRoute(match.routeId);
1204
+ const shouldInvalidate = opts?.__fromFocus ? route.options.reloadOnWindowFocus ?? true : true;
1205
+ if (shouldInvalidate) {
1206
+ this.setRouteMatch(match.id, s => ({
1207
+ ...s,
1208
+ invalid: true
1209
+ }));
1210
+ }
1103
1211
  });
1104
1212
  });
1105
1213
  }
1106
1214
  if (opts?.reload ?? true) {
1107
- return this.navigate({
1108
- fromCurrent: true,
1109
- replace: true,
1110
- search: true
1111
- });
1215
+ return this.load();
1112
1216
  }
1113
1217
  };
1114
1218
  }