@tanstack/react-router 0.0.1-beta.210 → 0.0.1-beta.212

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.
@@ -275,7 +275,7 @@ function useRouteContext(opts) {
275
275
  select: match => opts?.select ? opts.select(match.context) : match.context
276
276
  });
277
277
  }
278
- const useLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
278
+ const useLayoutEffect$1 = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
279
279
 
280
280
  function joinPaths(paths) {
281
281
  return cleanPath(paths.filter(Boolean).join('/'));
@@ -777,8 +777,8 @@ class PathParamError extends Error {}
777
777
  function getInitialRouterState(location) {
778
778
  return {
779
779
  status: 'idle',
780
- resolvedLocation: undefined,
781
- location: location,
780
+ resolvedLocation: location,
781
+ location,
782
782
  matches: [],
783
783
  pendingMatches: [],
784
784
  lastUpdated: Date.now()
@@ -798,7 +798,7 @@ function RouterProvider({
798
798
  };
799
799
  const history = React.useState(() => options.history ?? createBrowserHistory())[0];
800
800
  const tempLocationKeyRef = React.useRef(`${Math.round(Math.random() * 10000000)}`);
801
- const resetNextScrollRef = React.useRef(false);
801
+ const resetNextScrollRef = React.useRef(true);
802
802
  const navigateTimeoutRef = React.useRef(null);
803
803
  const latestLoadPromiseRef = React.useRef(Promise.resolve());
804
804
  const checkLatest = promise => {
@@ -838,14 +838,22 @@ function RouterProvider({
838
838
  }
839
839
  return location;
840
840
  });
841
- const [preState, setState] = React.useState(() => getInitialRouterState(parseLocation()));
841
+ const latestLocationRef = React.useRef(parseLocation());
842
+ const [preState, setState] = React.useState(() => getInitialRouterState(latestLocationRef.current));
842
843
  const [isTransitioning, startReactTransition] = React.useTransition();
843
844
  const state = React.useMemo(() => ({
844
845
  ...preState,
845
- status: isTransitioning ? 'pending' : 'idle'
846
+ status: isTransitioning ? 'pending' : 'idle',
847
+ location: isTransitioning ? latestLocationRef.current : preState.location
846
848
  }), [preState, isTransitioning]);
847
849
  React.useLayoutEffect(() => {
848
850
  if (!isTransitioning && state.resolvedLocation !== state.location) {
851
+ router.emit({
852
+ type: 'onResolved',
853
+ fromLocation: state.resolvedLocation,
854
+ toLocation: state.location,
855
+ pathChanged: state.location.href !== state.resolvedLocation?.href
856
+ });
849
857
  setState(s => ({
850
858
  ...s,
851
859
  resolvedLocation: s.location
@@ -1298,7 +1306,6 @@ function RouterProvider({
1298
1306
  preload: !!preload,
1299
1307
  context: parentContext,
1300
1308
  location: state.location,
1301
- // TODO: This might need to be latestLocationRef.current...?
1302
1309
  navigate: opts => navigate({
1303
1310
  ...opts,
1304
1311
  from: match.pathname
@@ -1418,16 +1425,16 @@ function RouterProvider({
1418
1425
  const load = useStableCallback(async () => {
1419
1426
  const promise = new Promise(async (resolve, reject) => {
1420
1427
  const next = latestLocationRef.current;
1421
- const prevLocation = state.resolvedLocation || state.location;
1422
- const pathDidChange = !!(next && prevLocation.href !== next.href);
1428
+ const prevLocation = state.resolvedLocation;
1429
+ const pathDidChange = prevLocation.href !== next.href;
1423
1430
  let latestPromise;
1424
1431
 
1425
1432
  // Cancel any pending matches
1426
1433
  cancelMatches(state);
1427
1434
  router.emit({
1428
1435
  type: 'onBeforeLoad',
1429
- from: prevLocation,
1430
- to: next ?? state.location,
1436
+ fromLocation: prevLocation,
1437
+ toLocation: next,
1431
1438
  pathChanged: pathDidChange
1432
1439
  });
1433
1440
 
@@ -1435,10 +1442,13 @@ function RouterProvider({
1435
1442
  let matches = matchRoutes(next.pathname, next.search, {
1436
1443
  debug: true
1437
1444
  });
1445
+ const previousMatches = state.matches;
1438
1446
 
1439
1447
  // Ingest the new matches
1440
1448
  setState(s => ({
1441
1449
  ...s,
1450
+ status: 'pending',
1451
+ location: next,
1442
1452
  matches
1443
1453
  }));
1444
1454
  try {
@@ -1457,17 +1467,9 @@ function RouterProvider({
1457
1467
  if (latestPromise = checkLatest(promise)) {
1458
1468
  return latestPromise;
1459
1469
  }
1460
-
1461
- // TODO:
1462
- // const exitingMatchIds = previousMatches.filter(
1463
- // (id) => !state.pendingMatches.includes(id),
1464
- // )
1465
- // const enteringMatchIds = state.pendingMatches.filter(
1466
- // (id) => !previousMatches.includes(id),
1467
- // )
1468
- // const stayingMatchIds = previousMatches.filter((id) =>
1469
- // state.pendingMatches.includes(id),
1470
- // )
1470
+ const exitingMatchIds = previousMatches.filter(id => !state.pendingMatches.includes(id));
1471
+ const enteringMatchIds = state.pendingMatches.filter(id => !previousMatches.includes(id));
1472
+ const stayingMatchIds = previousMatches.filter(id => state.pendingMatches.includes(id))
1471
1473
 
1472
1474
  // setState((s) => ({
1473
1475
  // ...s,
@@ -1475,23 +1477,17 @@ function RouterProvider({
1475
1477
  // resolvedLocation: s.location,
1476
1478
  // }))
1477
1479
 
1478
- // TODO:
1479
- // ;(
1480
- // [
1481
- // [exitingMatchIds, 'onLeave'],
1482
- // [enteringMatchIds, 'onEnter'],
1483
- // [stayingMatchIds, 'onTransition'],
1484
- // ] as const
1485
- // ).forEach(([matches, hook]) => {
1486
- // matches.forEach((match) => {
1487
- // const route = this.getRoute(match.routeId)
1488
- // route.options[hook]?.(match)
1489
- // })
1490
- // })
1480
+ //
1481
+ ;
1482
+ [[exitingMatchIds, 'onLeave'], [enteringMatchIds, 'onEnter'], [stayingMatchIds, 'onTransition']].forEach(([matches, hook]) => {
1483
+ matches.forEach(match => {
1484
+ looseRoutesById[match.routeId].options[hook]?.(match);
1485
+ });
1486
+ });
1491
1487
  router.emit({
1492
1488
  type: 'onLoad',
1493
- from: prevLocation,
1494
- to: next,
1489
+ fromLocation: prevLocation,
1490
+ toLocation: next,
1495
1491
  pathChanged: pathDidChange
1496
1492
  });
1497
1493
  resolve();
@@ -1624,20 +1620,17 @@ function RouterProvider({
1624
1620
  disabled
1625
1621
  };
1626
1622
  });
1627
- const latestLocationRef = React.useRef(state.location);
1628
1623
  React.useLayoutEffect(() => {
1629
1624
  const unsub = history.subscribe(() => {
1630
1625
  latestLocationRef.current = parseLocation(latestLocationRef.current);
1631
- setState(s => ({
1632
- ...s,
1633
- status: 'pending'
1634
- }));
1635
1626
  if (state.location !== latestLocationRef.current) {
1636
- try {
1637
- load();
1638
- } catch (err) {
1639
- console.error(err);
1640
- }
1627
+ startReactTransition(() => {
1628
+ try {
1629
+ load();
1630
+ } catch (err) {
1631
+ console.error(err);
1632
+ }
1633
+ });
1641
1634
  }
1642
1635
  });
1643
1636
  const nextLocation = buildLocation({
@@ -1705,7 +1698,9 @@ function RouterProvider({
1705
1698
  options,
1706
1699
  history,
1707
1700
  load,
1708
- buildLocation
1701
+ buildLocation,
1702
+ subscribe: router.subscribe,
1703
+ resetNextScrollRef
1709
1704
  };
1710
1705
  return /*#__PURE__*/React.createElement(routerContext.Provider, {
1711
1706
  value: routerContextValue
@@ -2091,6 +2086,7 @@ function useLinkProps(options) {
2091
2086
  preloadDelay,
2092
2087
  replace,
2093
2088
  startTransition,
2089
+ resetScroll,
2094
2090
  // element props
2095
2091
  style,
2096
2092
  className,
@@ -2170,6 +2166,151 @@ const Link = /*#__PURE__*/React.forwardRef((props, ref) => {
2170
2166
  }));
2171
2167
  });
2172
2168
 
2169
+ const useLayoutEffect = typeof window !== 'undefined' ? React.useLayoutEffect : React.useEffect;
2170
+ const windowKey = 'window';
2171
+ const delimiter = '___';
2172
+ let weakScrolledElements = new WeakSet();
2173
+ let cache;
2174
+ const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2175
+ const defaultGetKey = location => location.state.key;
2176
+ function useScrollRestoration(options) {
2177
+ const {
2178
+ state,
2179
+ subscribe,
2180
+ resetNextScrollRef
2181
+ } = useRouter();
2182
+ useLayoutEffect(() => {
2183
+ const getKey = options?.getKey || defaultGetKey;
2184
+ if (sessionsStorage) {
2185
+ if (!cache) {
2186
+ cache = (() => {
2187
+ const storageKey = 'tsr-scroll-restoration-v2';
2188
+ const state = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null') || {
2189
+ cached: {},
2190
+ next: {}
2191
+ };
2192
+ return {
2193
+ state,
2194
+ set: updater => {
2195
+ cache.state = functionalUpdate(updater, cache.state);
2196
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache.state));
2197
+ }
2198
+ };
2199
+ })();
2200
+ }
2201
+ }
2202
+ const {
2203
+ history
2204
+ } = window;
2205
+ if (history.scrollRestoration) {
2206
+ history.scrollRestoration = 'manual';
2207
+ }
2208
+ const onScroll = event => {
2209
+ if (weakScrolledElements.has(event.target)) return;
2210
+ weakScrolledElements.add(event.target);
2211
+ const elementSelector = event.target === document || event.target === window ? windowKey : getCssSelector(event.target);
2212
+ if (!cache.state.next[elementSelector]) {
2213
+ cache.set(c => ({
2214
+ ...c,
2215
+ next: {
2216
+ ...c.next,
2217
+ [elementSelector]: {
2218
+ scrollX: NaN,
2219
+ scrollY: NaN
2220
+ }
2221
+ }
2222
+ }));
2223
+ }
2224
+ };
2225
+ const getCssSelector = el => {
2226
+ let path = [],
2227
+ parent;
2228
+ while (parent = el.parentNode) {
2229
+ path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2230
+ el = parent;
2231
+ }
2232
+ return `${path.join(' > ')}`.toLowerCase();
2233
+ };
2234
+ if (typeof document !== 'undefined') {
2235
+ document.addEventListener('scroll', onScroll, true);
2236
+ }
2237
+ const unsubOnBeforeLoad = subscribe('onBeforeLoad', event => {
2238
+ if (event.pathChanged) {
2239
+ const restoreKey = getKey(event.fromLocation);
2240
+ for (const elementSelector in cache.state.next) {
2241
+ const entry = cache.state.next[elementSelector];
2242
+ if (elementSelector === windowKey) {
2243
+ entry.scrollX = window.scrollX || 0;
2244
+ entry.scrollY = window.scrollY || 0;
2245
+ } else if (elementSelector) {
2246
+ const element = document.querySelector(elementSelector);
2247
+ entry.scrollX = element?.scrollLeft || 0;
2248
+ entry.scrollY = element?.scrollTop || 0;
2249
+ }
2250
+ cache.set(c => {
2251
+ const next = {
2252
+ ...c.next
2253
+ };
2254
+ delete next[elementSelector];
2255
+ return {
2256
+ ...c,
2257
+ next,
2258
+ cached: {
2259
+ ...c.cached,
2260
+ [[restoreKey, elementSelector].join(delimiter)]: entry
2261
+ }
2262
+ };
2263
+ });
2264
+ }
2265
+ }
2266
+ });
2267
+ const unsubOnResolved = subscribe('onResolved', event => {
2268
+ if (event.pathChanged) {
2269
+ if (!resetNextScrollRef.current) {
2270
+ return;
2271
+ }
2272
+ resetNextScrollRef.current = true;
2273
+ const getKey = options?.getKey || defaultGetKey;
2274
+ const restoreKey = getKey(event.toLocation);
2275
+ let windowRestored = false;
2276
+ for (const cacheKey in cache.state.cached) {
2277
+ const entry = cache.state.cached[cacheKey];
2278
+ const [key, elementSelector] = cacheKey.split(delimiter);
2279
+ if (key === restoreKey) {
2280
+ if (elementSelector === windowKey) {
2281
+ windowRestored = true;
2282
+ window.scrollTo(entry.scrollX, entry.scrollY);
2283
+ } else if (elementSelector) {
2284
+ const element = document.querySelector(elementSelector);
2285
+ if (element) {
2286
+ element.scrollLeft = entry.scrollX;
2287
+ element.scrollTop = entry.scrollY;
2288
+ }
2289
+ }
2290
+ }
2291
+ }
2292
+ if (!windowRestored) {
2293
+ window.scrollTo(0, 0);
2294
+ }
2295
+ cache.set(c => ({
2296
+ ...c,
2297
+ next: {}
2298
+ }));
2299
+ weakScrolledElements = new WeakSet();
2300
+ }
2301
+ });
2302
+ return () => {
2303
+ document.removeEventListener('scroll', onScroll);
2304
+ unsubOnBeforeLoad();
2305
+ unsubOnResolved();
2306
+ };
2307
+ }, []);
2308
+ }
2309
+ function ScrollRestoration(props) {
2310
+ useScrollRestoration(props);
2311
+ return null;
2312
+ }
2313
+
2173
2314
  function useBlocker(message, condition = true) {
2174
2315
  const {
2175
2316
  history
@@ -2220,7 +2361,7 @@ function Navigate(props) {
2220
2361
  const match = useMatch({
2221
2362
  strict: false
2222
2363
  });
2223
- useLayoutEffect(() => {
2364
+ useLayoutEffect$1(() => {
2224
2365
  navigate({
2225
2366
  from: props.to ? match.pathname : undefined,
2226
2367
  ...props
@@ -2229,5 +2370,5 @@ function Navigate(props) {
2229
2370
  return null;
2230
2371
  }
2231
2372
 
2232
- export { Block, CatchBoundary, CatchBoundaryImpl, ErrorComponent, FileRoute, Link, Match, MatchRoute, Matches, Navigate, Outlet, PathParamError, RootRoute, Route, Router, RouterProvider, SearchParamError, cleanPath, componentTypes, createRouteMask, decode, defaultParseSearch, defaultStringifySearch, encode, functionalUpdate, getInitialRouterState, getRouteMatch, interpolatePath, isPlainObject, isRedirect, isServer, joinPaths, last, lazyFn, lazyRouteComponent, matchByPath, matchPathname, matchesContext, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, rootRouteId, rootRouteWithContext, routerContext, shallow, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, typedNavigate, useBlocker, useLayoutEffect, useLinkProps, useMatch, useMatchRoute, useMatches, useNavigate, useParams, useRouteContext, useRouter, useRouterState, useSearch, useStableCallback };
2373
+ export { Block, CatchBoundary, CatchBoundaryImpl, ErrorComponent, FileRoute, Link, Match, MatchRoute, Matches, Navigate, Outlet, PathParamError, RootRoute, Route, Router, RouterProvider, ScrollRestoration, SearchParamError, cleanPath, componentTypes, createRouteMask, decode, defaultParseSearch, defaultStringifySearch, encode, functionalUpdate, getInitialRouterState, getRouteMatch, interpolatePath, isPlainObject, isRedirect, isServer, joinPaths, last, lazyFn, lazyRouteComponent, matchByPath, matchPathname, matchesContext, parsePathname, parseSearchWith, partialDeepEqual, pick, redirect, replaceEqualDeep, resolvePath, rootRouteId, rootRouteWithContext, routerContext, shallow, stringifySearchWith, trimPath, trimPathLeft, trimPathRight, typedNavigate, useBlocker, useLayoutEffect$1 as useLayoutEffect, useLinkProps, useMatch, useMatchRoute, useMatches, useNavigate, useParams, useRouteContext, useRouter, useRouterState, useScrollRestoration, useSearch, useStableCallback };
2233
2374
  //# sourceMappingURL=index.js.map