@tanstack/router-core 0.0.1-beta.173 → 0.0.1-beta.175

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.
@@ -11,7 +11,7 @@
11
11
  import invariant from 'tiny-invariant';
12
12
  export { default as invariant } from 'tiny-invariant';
13
13
  export { default as warning } from 'tiny-warning';
14
- import { Store } from '@tanstack/react-store';
14
+ import { Store } from '@tanstack/store';
15
15
 
16
16
  // While the public API was clearly inspired by the "history" npm package,
17
17
  // This implementation attempts to be more lightweight by
@@ -709,6 +709,8 @@ function stringifySearchWith(stringify, parser) {
709
709
  //
710
710
 
711
711
  const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
712
+ const visibilityChangeEvent = 'visibilitychange';
713
+ const focusEvent = 'focus';
712
714
  class Router {
713
715
  #unsubHistory;
714
716
  resetNextScroll = false;
@@ -785,12 +787,26 @@ class Router {
785
787
  this.__store.setState(s => Object.assign(s, getInitialRouterState()));
786
788
  };
787
789
  mount = () => {
788
- // If the router matches are empty, start loading the matches
789
- // if (!this.state.matches.length) {
790
+ // addEventListener does not exist in React Native, but window does
791
+ // In the future, we might need to invert control here for more adapters
792
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition
793
+ if (typeof window !== 'undefined' && window.addEventListener) {
794
+ window.addEventListener(visibilityChangeEvent, this.#onFocus, false);
795
+ window.addEventListener(focusEvent, this.#onFocus, false);
796
+ }
790
797
  this.safeLoad();
791
- // }
798
+ return () => {
799
+ if (typeof window !== 'undefined' && window.removeEventListener) {
800
+ window.removeEventListener(visibilityChangeEvent, this.#onFocus);
801
+ window.removeEventListener(focusEvent, this.#onFocus);
802
+ }
803
+ };
804
+ };
805
+ #onFocus = () => {
806
+ if (this.options.refetchOnWindowFocus ?? true) {
807
+ this.reload();
808
+ }
792
809
  };
793
-
794
810
  update = opts => {
795
811
  this.options = {
796
812
  ...this.options,
@@ -861,7 +877,6 @@ class Router {
861
877
  };
862
878
 
863
879
  // Cancel any pending matches
864
- // this.cancelMatches()
865
880
 
866
881
  let pendingMatches;
867
882
  this.#emit({
@@ -904,6 +919,9 @@ class Router {
904
919
  if (latestPromise = checkLatest()) {
905
920
  return latestPromise;
906
921
  }
922
+ const exitingMatchIds = this.state.matchIds.filter(id => !this.state.pendingMatchIds.includes(id));
923
+ const enteringMatchIds = this.state.pendingMatchIds.filter(id => !this.state.matchIds.includes(id));
924
+ const stayingMatchIds = this.state.matchIds.filter(id => this.state.pendingMatchIds.includes(id));
907
925
  this.__store.setState(s => ({
908
926
  ...s,
909
927
  status: 'idle',
@@ -911,6 +929,13 @@ class Router {
911
929
  matchIds: s.pendingMatchIds,
912
930
  pendingMatchIds: []
913
931
  }));
932
+ [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matchIds, hook]) => {
933
+ matchIds.forEach(id => {
934
+ const match = this.getRouteMatch(id);
935
+ const route = this.getRoute(match.routeId);
936
+ route.options[hook]?.(match);
937
+ });
938
+ });
914
939
  this.#emit({
915
940
  type: 'onLoad',
916
941
  from: prevLocation,
@@ -927,6 +952,9 @@ class Router {
927
952
  }
928
953
  });
929
954
  this.latestLoadPromise = promise;
955
+ this.latestLoadPromise.then(() => {
956
+ this.cleanMatches();
957
+ });
930
958
  return this.latestLoadPromise;
931
959
  };
932
960
  #mergeMatches = (prevMatchesById, nextMatches) => {
@@ -971,7 +999,7 @@ class Router {
971
999
  const now = Date.now();
972
1000
  const outdatedMatchIds = Object.values(this.state.matchesById).filter(match => {
973
1001
  const route = this.getRoute(match.routeId);
974
- return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && match.preloadInvalidAt < now && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
1002
+ return !this.state.matchIds.includes(match.id) && !this.state.pendingMatchIds.includes(match.id) && (match.preloadMaxAge > -1 ? match.updatedAt + match.preloadMaxAge < now : true) && (route.options.gcMaxAge ? match.updatedAt + route.options.gcMaxAge < now : true);
975
1003
  }).map(d => d.id);
976
1004
  if (outdatedMatchIds.length) {
977
1005
  this.__store.setState(s => {
@@ -1059,8 +1087,8 @@ class Router {
1059
1087
  params: routeParams,
1060
1088
  pathname: joinPaths([this.basepath, interpolatedPath]),
1061
1089
  updatedAt: Date.now(),
1062
- invalidAt: Infinity,
1063
- preloadInvalidAt: Infinity,
1090
+ maxAge: -1,
1091
+ preloadMaxAge: -1,
1064
1092
  routeSearch: {},
1065
1093
  search: {},
1066
1094
  status: hasLoaders ? 'pending' : 'success',
@@ -1156,11 +1184,13 @@ class Router {
1156
1184
  paramsError: match.paramsError,
1157
1185
  searchError: match.searchError,
1158
1186
  params: match.params,
1159
- preloadInvalidAt: 0
1187
+ preloadMaxAge: 0
1160
1188
  }));
1161
1189
  });
1190
+ } else {
1191
+ // If we're preloading, clean preload matches before we try and use them
1192
+ this.cleanMatches();
1162
1193
  }
1163
- this.cleanMatches();
1164
1194
  let firstBadMatchIndex;
1165
1195
 
1166
1196
  // Check each match middleware to see if the route can be accessed
@@ -1222,8 +1252,7 @@ class Router {
1222
1252
  matchPromises.push((async () => {
1223
1253
  const parentMatchPromise = matchPromises[index - 1];
1224
1254
  const route = this.getRoute(match.routeId);
1225
- if (match.isFetching || match.status === 'success' && !this.getIsInvalid({
1226
- matchId: match.id,
1255
+ if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1227
1256
  preload: opts?.preload
1228
1257
  })) {
1229
1258
  return this.getRouteMatch(match.id)?.loadPromise;
@@ -1296,7 +1325,6 @@ class Router {
1296
1325
  })());
1297
1326
  });
1298
1327
  await Promise.all(matchPromises);
1299
- this.cleanMatches();
1300
1328
  };
1301
1329
  reload = () => {
1302
1330
  return this.navigate({
@@ -1481,7 +1509,7 @@ class Router {
1481
1509
  dehydrate = () => {
1482
1510
  return {
1483
1511
  state: {
1484
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'invalidAt', 'id', 'loaderData', 'status', 'updatedAt']))
1512
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1485
1513
  }
1486
1514
  };
1487
1515
  };
@@ -1720,7 +1748,6 @@ class Router {
1720
1748
  ...next.state
1721
1749
  });
1722
1750
  this.resetNextScroll = location.resetScroll ?? true;
1723
- console.log('resetScroll', this.resetNextScroll);
1724
1751
  return this.latestLoadPromise;
1725
1752
  };
1726
1753
  getRouteMatch = id => {
@@ -1745,17 +1772,17 @@ class Router {
1745
1772
  if (!match) return;
1746
1773
  const route = this.getRoute(match.routeId);
1747
1774
  const updatedAt = opts?.updatedAt ?? Date.now();
1748
- const preloadInvalidAt = updatedAt + (opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000);
1749
- const invalidAt = updatedAt + (opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? Infinity);
1775
+ const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
1776
+ const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
1750
1777
  this.setRouteMatch(id, s => ({
1751
1778
  ...s,
1752
1779
  error: undefined,
1753
1780
  status: 'success',
1754
1781
  isFetching: false,
1755
- updatedAt: Date.now(),
1782
+ updatedAt: updatedAt,
1756
1783
  loaderData: functionalUpdate(updater, s.loaderData),
1757
- preloadInvalidAt,
1758
- invalidAt
1784
+ preloadMaxAge,
1785
+ maxAge
1759
1786
  }));
1760
1787
  };
1761
1788
  invalidate = async opts => {
@@ -1786,20 +1813,6 @@ class Router {
1786
1813
  return this.reload();
1787
1814
  }
1788
1815
  };
1789
- getIsInvalid = opts => {
1790
- if (!opts?.matchId) {
1791
- return !!this.state.matches.find(d => this.getIsInvalid({
1792
- matchId: d.id,
1793
- preload: opts?.preload
1794
- }));
1795
- }
1796
- const match = this.getRouteMatch(opts?.matchId);
1797
- if (!match) {
1798
- return false;
1799
- }
1800
- const now = Date.now();
1801
- return match.invalid || (opts?.preload ? match.preloadInvalidAt : match.invalidAt) < now;
1802
- };
1803
1816
  }
1804
1817
 
1805
1818
  // Detect if we're in the DOM
@@ -1845,6 +1858,16 @@ function lazyFn(fn, key) {
1845
1858
  return imported[key || 'default'](...args);
1846
1859
  };
1847
1860
  }
1861
+ function isMatchInvalid(match, opts) {
1862
+ const now = Date.now();
1863
+ if (match.invalid) {
1864
+ return true;
1865
+ }
1866
+ if (opts?.preload) {
1867
+ return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
1868
+ }
1869
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
1870
+ }
1848
1871
 
1849
1872
  const windowKey = 'window';
1850
1873
  const delimiter = '___';
@@ -1990,5 +2013,5 @@ function isDehydratedDeferred(obj) {
1990
2013
  return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
1991
2014
  }
1992
2015
 
1993
- export { FileRoute, PathParamError, RootRoute, Route, Router, RouterContext, SearchParamError, cleanPath, componentTypes, createBrowserHistory, createHashHistory, createMemoryHistory, decode, defaultParseSearch, defaultStringifySearch, defer, encode, functionalUpdate, interpolatePath, isDehydratedDeferred, isPlainObject, isRedirect, joinPaths, last, lazyFn, matchByPath, matchPathname, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, restoreScrollPositions, rootRouteId, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, watchScrollPositions };
2016
+ export { FileRoute, PathParamError, RootRoute, Route, Router, RouterContext, SearchParamError, cleanPath, componentTypes, createBrowserHistory, createHashHistory, createMemoryHistory, decode, defaultParseSearch, defaultStringifySearch, defer, encode, functionalUpdate, interpolatePath, isDehydratedDeferred, isMatchInvalid, isPlainObject, isRedirect, joinPaths, last, lazyFn, matchByPath, matchPathname, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, restoreScrollPositions, rootRouteId, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, watchScrollPositions };
1994
2017
  //# sourceMappingURL=index.js.map