@tanstack/react-router 0.0.1-beta.162 → 0.0.1-beta.164

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.
@@ -208,7 +208,7 @@
208
208
  function createHistory(opts) {
209
209
  let location = opts.getLocation();
210
210
  let unsub = () => {};
211
- let listeners = new Set();
211
+ let subscribers = new Set();
212
212
  let blockers = [];
213
213
  let queue = [];
214
214
  const tryFlush = () => {
@@ -222,7 +222,7 @@
222
222
  while (queue.length) {
223
223
  queue.shift()?.();
224
224
  }
225
- if (!opts.listener) {
225
+ if (!opts.subscriber) {
226
226
  onUpdate();
227
227
  }
228
228
  };
@@ -232,20 +232,20 @@
232
232
  };
233
233
  const onUpdate = () => {
234
234
  location = opts.getLocation();
235
- listeners.forEach(listener => listener());
235
+ subscribers.forEach(subscriber => subscriber());
236
236
  };
237
237
  return {
238
238
  get location() {
239
239
  return location;
240
240
  },
241
- listen: cb => {
242
- if (listeners.size === 0) {
243
- unsub = typeof opts.listener === 'function' ? opts.listener(onUpdate) : () => {};
241
+ subscribe: cb => {
242
+ if (subscribers.size === 0) {
243
+ unsub = typeof opts.subscriber === 'function' ? opts.subscriber(onUpdate) : () => {};
244
244
  }
245
- listeners.add(cb);
245
+ subscribers.add(cb);
246
246
  return () => {
247
- listeners.delete(cb);
248
- if (listeners.size === 0) {
247
+ subscribers.delete(cb);
248
+ if (subscribers.size === 0) {
249
249
  unsub();
250
250
  }
251
251
  };
@@ -298,7 +298,7 @@
298
298
  const getLocation = () => parseLocation(getHref(), history.state);
299
299
  return createHistory({
300
300
  getLocation,
301
- listener: onUpdate => {
301
+ subscriber: onUpdate => {
302
302
  window.addEventListener(pushStateEvent, onUpdate);
303
303
  window.addEventListener(popStateEvent, onUpdate);
304
304
  var pushState = window.history.pushState;
@@ -353,7 +353,7 @@
353
353
  const getLocation = () => parseLocation(entries[index], currentState);
354
354
  return createHistory({
355
355
  getLocation,
356
- listener: false,
356
+ subscriber: false,
357
357
  pushState: (path, state) => {
358
358
  currentState = {
359
359
  ...state,
@@ -928,6 +928,24 @@
928
928
  });
929
929
  }
930
930
  }
931
+ subscribers = new Set();
932
+ subscribe = (eventType, fn) => {
933
+ const listener = {
934
+ eventType,
935
+ fn
936
+ };
937
+ this.subscribers.add(listener);
938
+ return () => {
939
+ this.subscribers.delete(listener);
940
+ };
941
+ };
942
+ #emit = routerEvent => {
943
+ this.subscribers.forEach(listener => {
944
+ if (listener.eventType === routerEvent.type) {
945
+ listener.fn(routerEvent);
946
+ }
947
+ });
948
+ };
931
949
  reset = () => {
932
950
  this.__store.setState(s => Object.assign(s, getInitialRouterState()));
933
951
  };
@@ -958,7 +976,7 @@
958
976
  resolvedLocation: parsedLocation,
959
977
  location: parsedLocation
960
978
  }));
961
- this.#unsubHistory = this.history.listen(() => {
979
+ this.#unsubHistory = this.history.subscribe(() => {
962
980
  this.safeLoad({
963
981
  next: this.#parseLocation(this.state.location)
964
982
  });
@@ -1000,6 +1018,8 @@
1000
1018
  latestLoadPromise = Promise.resolve();
1001
1019
  load = async opts => {
1002
1020
  const promise = new Promise(async (resolve, reject) => {
1021
+ const prevLocation = this.state.resolvedLocation;
1022
+ const pathDidChange = !!(opts?.next && prevLocation.href !== opts.next.href);
1003
1023
  let latestPromise;
1004
1024
  const checkLatest = () => {
1005
1025
  return this.latestLoadPromise !== promise ? this.latestLoadPromise : undefined;
@@ -1009,6 +1029,12 @@
1009
1029
  // this.cancelMatches()
1010
1030
 
1011
1031
  let pendingMatches;
1032
+ this.#emit({
1033
+ type: 'onBeforeLoad',
1034
+ from: prevLocation,
1035
+ to: opts?.next ?? this.state.location,
1036
+ pathChanged: pathDidChange
1037
+ });
1012
1038
  this.__store.batch(() => {
1013
1039
  if (opts?.next) {
1014
1040
  // Ingest the new location
@@ -1043,7 +1069,6 @@
1043
1069
  if (latestPromise = checkLatest()) {
1044
1070
  return latestPromise;
1045
1071
  }
1046
- const prevLocation = this.state.resolvedLocation;
1047
1072
  this.__store.setState(s => ({
1048
1073
  ...s,
1049
1074
  status: 'idle',
@@ -1051,9 +1076,12 @@
1051
1076
  matchIds: s.pendingMatchIds,
1052
1077
  pendingMatchIds: []
1053
1078
  }));
1054
- if (prevLocation.href !== this.state.location.href) {
1055
- this.options.onRouteChange?.();
1056
- }
1079
+ this.#emit({
1080
+ type: 'onLoad',
1081
+ from: prevLocation,
1082
+ to: this.state.location,
1083
+ pathChanged: pathDidChange
1084
+ });
1057
1085
  resolve();
1058
1086
  } catch (err) {
1059
1087
  // Only apply the latest transition
@@ -1303,14 +1331,14 @@
1303
1331
  try {
1304
1332
  for (const [index, match] of resolvedMatches.entries()) {
1305
1333
  const route = this.getRoute(match.routeId);
1306
- const handleError = (err, handler) => {
1334
+ const handleError = (err, code) => {
1335
+ err.routerCode = code;
1307
1336
  firstBadMatchIndex = firstBadMatchIndex ?? index;
1308
- handler = handler || route.options.onError;
1309
1337
  if (isRedirect(err)) {
1310
1338
  throw err;
1311
1339
  }
1312
1340
  try {
1313
- handler?.(err);
1341
+ route.options.onError?.(err);
1314
1342
  } catch (errorHandlerErr) {
1315
1343
  err = errorHandlerErr;
1316
1344
  if (isRedirect(errorHandlerErr)) {
@@ -1325,10 +1353,10 @@
1325
1353
  }));
1326
1354
  };
1327
1355
  if (match.paramsError) {
1328
- handleError(match.paramsError, route.options.onParseParamsError);
1356
+ handleError(match.paramsError, 'PARSE_PARAMS');
1329
1357
  }
1330
1358
  if (match.searchError) {
1331
- handleError(match.searchError, route.options.onValidateSearchError);
1359
+ handleError(match.searchError, 'VALIDATE_SEARCH');
1332
1360
  }
1333
1361
  let didError = false;
1334
1362
  try {
@@ -1337,7 +1365,7 @@
1337
1365
  preload: !!opts?.preload
1338
1366
  });
1339
1367
  } catch (err) {
1340
- handleError(err, route.options.onBeforeLoadError);
1368
+ handleError(err, 'BEFORE_LOAD');
1341
1369
  didError = true;
1342
1370
  }
1343
1371
 
@@ -1395,28 +1423,18 @@
1395
1423
  const [_, loader] = await Promise.all([componentsPromise, loaderPromise]);
1396
1424
  if (latestPromise = checkLatest()) return await latestPromise;
1397
1425
  this.setRouteMatchData(match.id, () => loader, opts);
1398
- } catch (loaderError) {
1399
- let latestError = loaderError;
1426
+ } catch (error) {
1400
1427
  if (latestPromise = checkLatest()) return await latestPromise;
1401
- if (handleIfRedirect(loaderError)) return;
1402
- if (route.options.onLoadError) {
1403
- try {
1404
- route.options.onLoadError(loaderError);
1405
- } catch (onLoadError) {
1406
- latestError = onLoadError;
1407
- if (handleIfRedirect(onLoadError)) return;
1408
- }
1409
- }
1410
- if ((!route.options.onLoadError || latestError !== loaderError) && route.options.onError) {
1411
- try {
1412
- route.options.onError(latestError);
1413
- } catch (onErrorError) {
1414
- if (handleIfRedirect(onErrorError)) return;
1415
- }
1428
+ if (handleIfRedirect(error)) return;
1429
+ try {
1430
+ route.options.onError?.(error);
1431
+ } catch (onErrorError) {
1432
+ error = onErrorError;
1433
+ if (handleIfRedirect(onErrorError)) return;
1416
1434
  }
1417
1435
  this.setRouteMatch(match.id, s => ({
1418
1436
  ...s,
1419
- error: loaderError,
1437
+ error,
1420
1438
  status: 'error',
1421
1439
  isFetching: false,
1422
1440
  updatedAt: Date.now()
@@ -1973,6 +1991,140 @@
1973
1991
  };
1974
1992
  }
1975
1993
 
1994
+ const windowKey = 'window';
1995
+ const delimiter = '___';
1996
+ let weakScrolledElementsByRestoreKey = {};
1997
+ let cache;
1998
+ let pathDidChange = false;
1999
+ const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2000
+ const defaultGetKey = location => location.key;
2001
+ function watchScrollPositions(router, opts) {
2002
+ const getKey = opts?.getKey || defaultGetKey;
2003
+ if (sessionsStorage) {
2004
+ if (!cache) {
2005
+ cache = (() => {
2006
+ const storageKey = 'tsr-scroll-restoration-v1';
2007
+ const current = JSON.parse(window.sessionStorage.getItem(storageKey) || '{}');
2008
+ return {
2009
+ current,
2010
+ set: (key, value) => {
2011
+ current[key] = value;
2012
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache));
2013
+ }
2014
+ };
2015
+ })();
2016
+ }
2017
+ }
2018
+ const {
2019
+ history
2020
+ } = window;
2021
+ if (history.scrollRestoration) {
2022
+ history.scrollRestoration = 'manual';
2023
+ }
2024
+ const onScroll = event => {
2025
+ const restoreKey = getKey(router.state.resolvedLocation);
2026
+ if (!weakScrolledElementsByRestoreKey[restoreKey]) {
2027
+ weakScrolledElementsByRestoreKey[restoreKey] = new WeakSet();
2028
+ }
2029
+ const set = weakScrolledElementsByRestoreKey[restoreKey];
2030
+ if (set.has(event.target)) return;
2031
+ set.add(event.target);
2032
+ const cacheKey = [restoreKey, event.target === document || event.target === window ? windowKey : getCssSelector(event.target)].join(delimiter);
2033
+ if (!cache.current[cacheKey]) {
2034
+ cache.set(cacheKey, {
2035
+ scrollX: NaN,
2036
+ scrollY: NaN
2037
+ });
2038
+ }
2039
+ };
2040
+ const getCssSelector = el => {
2041
+ let path = [],
2042
+ parent;
2043
+ while (parent = el.parentNode) {
2044
+ path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2045
+ el = parent;
2046
+ }
2047
+ return `${path.join(' > ')}`.toLowerCase();
2048
+ };
2049
+ const onPathWillChange = from => {
2050
+ const restoreKey = getKey(from);
2051
+ for (const cacheKey in cache.current) {
2052
+ const entry = cache.current[cacheKey];
2053
+ const [key, elementSelector] = cacheKey.split(delimiter);
2054
+ if (restoreKey === key) {
2055
+ if (elementSelector === windowKey) {
2056
+ entry.scrollX = window.scrollX || 0;
2057
+ entry.scrollY = window.scrollY || 0;
2058
+ } else if (elementSelector) {
2059
+ const element = document.querySelector(elementSelector);
2060
+ entry.scrollX = element?.scrollLeft || 0;
2061
+ entry.scrollY = element?.scrollTop || 0;
2062
+ }
2063
+ cache.set(cacheKey, entry);
2064
+ }
2065
+ }
2066
+ };
2067
+ const onPathChange = () => {
2068
+ pathDidChange = true;
2069
+ };
2070
+ if (typeof document !== 'undefined') {
2071
+ document.addEventListener('scroll', onScroll, true);
2072
+ }
2073
+ const unsubOnBeforeLoad = router.subscribe('onBeforeLoad', event => {
2074
+ if (event.pathChanged) onPathWillChange(event.from);
2075
+ });
2076
+ const unsubOnLoad = router.subscribe('onLoad', event => {
2077
+ if (event.pathChanged) onPathChange();
2078
+ });
2079
+ return () => {
2080
+ document.removeEventListener('scroll', onScroll);
2081
+ unsubOnBeforeLoad();
2082
+ unsubOnLoad();
2083
+ };
2084
+ }
2085
+ function restoreScrollPositions(router, opts) {
2086
+ if (pathDidChange) {
2087
+ const getKey = opts?.getKey || defaultGetKey;
2088
+ pathDidChange = false;
2089
+ const restoreKey = getKey(router.state.location);
2090
+ let windowRestored = false;
2091
+ for (const cacheKey in cache.current) {
2092
+ const entry = cache.current[cacheKey];
2093
+ const [key, elementSelector] = cacheKey.split(delimiter);
2094
+ if (key === restoreKey) {
2095
+ if (elementSelector === windowKey) {
2096
+ windowRestored = true;
2097
+ window.scrollTo(entry.scrollX, entry.scrollY);
2098
+ } else if (elementSelector) {
2099
+ const element = document.querySelector(elementSelector);
2100
+ if (element) {
2101
+ element.scrollLeft = entry.scrollX;
2102
+ element.scrollTop = entry.scrollY;
2103
+ }
2104
+ }
2105
+ }
2106
+ }
2107
+ if (!windowRestored) {
2108
+ window.scrollTo(0, 0);
2109
+ }
2110
+ }
2111
+ }
2112
+
2113
+ const useLayoutEffect = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2114
+ function useScrollRestoration(options) {
2115
+ const router = useRouter();
2116
+ useLayoutEffect(() => {
2117
+ return watchScrollPositions(router, options);
2118
+ }, []);
2119
+ useLayoutEffect(() => {
2120
+ restoreScrollPositions(router, options);
2121
+ });
2122
+ }
2123
+ function ScrollRestoration(props) {
2124
+ useScrollRestoration(props);
2125
+ return null;
2126
+ }
2127
+
1976
2128
  Route.__onInit = route => {
1977
2129
  Object.assign(route, {
1978
2130
  useMatch: (opts = {}) => {
@@ -2551,6 +2703,7 @@
2551
2703
  exports.Router = Router;
2552
2704
  exports.RouterContext = RouterContext;
2553
2705
  exports.RouterProvider = RouterProvider;
2706
+ exports.ScrollRestoration = ScrollRestoration;
2554
2707
  exports.SearchParamError = SearchParamError;
2555
2708
  exports.cleanPath = cleanPath;
2556
2709
  exports.componentTypes = componentTypes;
@@ -2580,6 +2733,7 @@
2580
2733
  exports.redirect = redirect;
2581
2734
  exports.replaceEqualDeep = replaceEqualDeep;
2582
2735
  exports.resolvePath = resolvePath;
2736
+ exports.restoreScrollPositions = restoreScrollPositions;
2583
2737
  exports.rootRouteId = rootRouteId;
2584
2738
  exports.routerContext = routerContext;
2585
2739
  exports.shallow = shallow;
@@ -2602,9 +2756,11 @@
2602
2756
  exports.useRouter = useRouter;
2603
2757
  exports.useRouterContext = useRouterContext;
2604
2758
  exports.useRouterState = useRouterState;
2759
+ exports.useScrollRestoration = useScrollRestoration;
2605
2760
  exports.useSearch = useSearch;
2606
2761
  exports.useStore = useStore;
2607
2762
  exports.warning = warning;
2763
+ exports.watchScrollPositions = watchScrollPositions;
2608
2764
 
2609
2765
  Object.defineProperty(exports, '__esModule', { value: true });
2610
2766