@tanstack/router-core 0.0.1-beta.174 → 0.0.1-beta.176

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,10 @@ 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
  });
1162
1190
  }
1163
- this.cleanMatches();
1164
1191
  let firstBadMatchIndex;
1165
1192
 
1166
1193
  // Check each match middleware to see if the route can be accessed
@@ -1222,8 +1249,7 @@ class Router {
1222
1249
  matchPromises.push((async () => {
1223
1250
  const parentMatchPromise = matchPromises[index - 1];
1224
1251
  const route = this.getRoute(match.routeId);
1225
- if (match.isFetching || match.status === 'success' && !this.getIsInvalid({
1226
- matchId: match.id,
1252
+ if (match.isFetching || match.status === 'success' && !isMatchInvalid(match, {
1227
1253
  preload: opts?.preload
1228
1254
  })) {
1229
1255
  return this.getRouteMatch(match.id)?.loadPromise;
@@ -1296,7 +1322,6 @@ class Router {
1296
1322
  })());
1297
1323
  });
1298
1324
  await Promise.all(matchPromises);
1299
- this.cleanMatches();
1300
1325
  };
1301
1326
  reload = () => {
1302
1327
  return this.navigate({
@@ -1481,7 +1506,7 @@ class Router {
1481
1506
  dehydrate = () => {
1482
1507
  return {
1483
1508
  state: {
1484
- dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'invalidAt', 'id', 'loaderData', 'status', 'updatedAt']))
1509
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'preloadMaxAge', 'maxAge', 'id', 'loaderData', 'status', 'updatedAt']))
1485
1510
  }
1486
1511
  };
1487
1512
  };
@@ -1720,7 +1745,6 @@ class Router {
1720
1745
  ...next.state
1721
1746
  });
1722
1747
  this.resetNextScroll = location.resetScroll ?? true;
1723
- console.log('resetScroll', this.resetNextScroll);
1724
1748
  return this.latestLoadPromise;
1725
1749
  };
1726
1750
  getRouteMatch = id => {
@@ -1745,17 +1769,17 @@ class Router {
1745
1769
  if (!match) return;
1746
1770
  const route = this.getRoute(match.routeId);
1747
1771
  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);
1772
+ const preloadMaxAge = opts?.maxAge ?? route.options.preloadMaxAge ?? this.options.defaultPreloadMaxAge ?? 5000;
1773
+ const maxAge = opts?.maxAge ?? route.options.maxAge ?? this.options.defaultMaxAge ?? -1;
1750
1774
  this.setRouteMatch(id, s => ({
1751
1775
  ...s,
1752
1776
  error: undefined,
1753
1777
  status: 'success',
1754
1778
  isFetching: false,
1755
- updatedAt: Date.now(),
1779
+ updatedAt: updatedAt,
1756
1780
  loaderData: functionalUpdate(updater, s.loaderData),
1757
- preloadInvalidAt,
1758
- invalidAt
1781
+ preloadMaxAge,
1782
+ maxAge
1759
1783
  }));
1760
1784
  };
1761
1785
  invalidate = async opts => {
@@ -1786,20 +1810,6 @@ class Router {
1786
1810
  return this.reload();
1787
1811
  }
1788
1812
  };
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
1813
  }
1804
1814
 
1805
1815
  // Detect if we're in the DOM
@@ -1845,6 +1855,16 @@ function lazyFn(fn, key) {
1845
1855
  return imported[key || 'default'](...args);
1846
1856
  };
1847
1857
  }
1858
+ function isMatchInvalid(match, opts) {
1859
+ const now = Date.now();
1860
+ if (match.invalid) {
1861
+ return true;
1862
+ }
1863
+ if (opts?.preload) {
1864
+ return match.preloadMaxAge < 0 ? false : match.updatedAt + match.preloadMaxAge < now;
1865
+ }
1866
+ return match.maxAge < 0 ? false : match.updatedAt + match.maxAge < now;
1867
+ }
1848
1868
 
1849
1869
  const windowKey = 'window';
1850
1870
  const delimiter = '___';
@@ -1990,5 +2010,5 @@ function isDehydratedDeferred(obj) {
1990
2010
  return typeof obj === 'object' && obj !== null && !(obj instanceof Promise) && !obj.then && '__deferredState' in obj;
1991
2011
  }
1992
2012
 
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 };
2013
+ 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
2014
  //# sourceMappingURL=index.js.map