@tanstack/react-router 0.0.1-beta.230 → 0.0.1-beta.232

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.
@@ -24,11 +24,6 @@ export type MatchRouteFn<TRouteTree extends AnyRoute> = <TFrom extends RoutePath
24
24
  export type BuildLocationFn<TRouteTree extends AnyRoute> = (opts: BuildNextOptions) => ParsedLocation;
25
25
  export type InjectedHtmlEntry = string | (() => Promise<string> | string);
26
26
  export declare const routerContext: React.Context<Router<any, Record<string, any>>>;
27
- export declare class SearchParamError extends Error {
28
- }
29
- export declare class PathParamError extends Error {
30
- }
31
- export declare function getInitialRouterState(location: ParsedLocation): RouterState<any>;
32
27
  export declare function RouterProvider<TRouteTree extends AnyRoute = RegisteredRouter['routeTree'], TDehydrated extends Record<string, any> = Record<string, any>>({ router, ...rest }: RouterProps<TRouteTree, TDehydrated>): React.JSX.Element;
33
28
  export declare function getRouteMatch<TRouteTree extends AnyRoute>(state: RouterState<TRouteTree>, id: string): undefined | RouteMatch<TRouteTree>;
34
29
  export declare function useRouterState<TSelected = RouterState<RegisteredRouter['routeTree']>>(opts?: {
@@ -46,8 +46,13 @@ export interface RouterOptions<TRouteTree extends AnyRoute, TDehydrated extends
46
46
  routeTree?: TRouteTree;
47
47
  basepath?: string;
48
48
  context?: TRouteTree['types']['routerContext'];
49
+ dehydrate?: () => TDehydrated;
50
+ hydrate?: (dehydrated: TDehydrated) => void;
49
51
  routeMasks?: RouteMask<TRouteTree>[];
50
52
  unmaskOnReload?: boolean;
53
+ Wrap?: (props: {
54
+ children: any;
55
+ }) => JSX.Element;
51
56
  }
52
57
  export interface RouterState<TRouteTree extends AnyRoute = AnyRoute> {
53
58
  status: 'pending' | 'idle';
@@ -128,8 +133,8 @@ export declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated
128
133
  flatRoutes: AnyRoute[];
129
134
  constructor(options: RouterConstructorOptions<TRouteTree, TDehydrated>);
130
135
  startReactTransition: (fn: () => void) => void;
131
- setState: (fn: (s: RouterState<TRouteTree>) => RouterState<TRouteTree>) => void;
132
- updateOptions: (newOptions: PickAsRequired<RouterOptions<TRouteTree, TDehydrated>, 'stringifySearch' | 'parseSearch' | 'context'>) => void;
136
+ setState: (updater: NonNullableUpdater<RouterState<TRouteTree>>) => void;
137
+ update: (newOptions: RouterConstructorOptions<TRouteTree, TDehydrated>) => void;
133
138
  buildRouteTree: () => void;
134
139
  subscribe: <TType extends keyof RouterEvents>(eventType: TType, fn: ListenerFn<RouterEvents[TType]>) => () => void;
135
140
  emit: (routerEvent: RouterEvent) => void;
@@ -163,5 +168,12 @@ export declare class Router<TRouteTree extends AnyRoute = AnyRoute, TDehydrated
163
168
  injectHtml: (html: string | (() => Promise<string> | string)) => Promise<void>;
164
169
  dehydrateData: <T>(key: any, getData: T | (() => T | Promise<T>)) => () => T | undefined;
165
170
  hydrateData: <T extends unknown = unknown>(key: any) => T | undefined;
171
+ dehydrate: () => DehydratedRouter;
172
+ hydrate: (__do_not_use_server_ctx?: HydrationCtx) => Promise<void>;
166
173
  }
167
174
  export declare function lazyFn<T extends Record<string, (...args: any[]) => any>, TKey extends keyof T = 'default'>(fn: () => Promise<T>, key?: TKey): (...args: Parameters<T[TKey]>) => Promise<ReturnType<T[TKey]>>;
175
+ export declare class SearchParamError extends Error {
176
+ }
177
+ export declare class PathParamError extends Error {
178
+ }
179
+ export declare function getInitialRouterState(location: ParsedLocation): RouterState<any>;
@@ -4,3 +4,15 @@ export type ScrollRestorationOptions = {
4
4
  };
5
5
  export declare function useScrollRestoration(options?: ScrollRestorationOptions): void;
6
6
  export declare function ScrollRestoration(props: ScrollRestorationOptions): null;
7
+ export declare function useElementScrollRestoration(options: ({
8
+ id: string;
9
+ getElement?: () => Element | undefined | null;
10
+ } | {
11
+ id?: string;
12
+ getElement: () => Element | undefined | null;
13
+ }) & {
14
+ getKey?: (location: ParsedLocation) => string;
15
+ }): {
16
+ scrollX: number;
17
+ scrollY: number;
18
+ } | undefined;
@@ -976,7 +976,10 @@
976
976
  const match = matches[0];
977
977
  const routeId = match?.routeId;
978
978
  const route = routesById[routeId];
979
- const locationKey = useRouterState().location.state?.key;
979
+ const router = useRouter();
980
+ const locationKey = router.latestLocation.state?.key;
981
+ // const locationKey = useRouterState().location.state?.key
982
+
980
983
  const PendingComponent = route.options.pendingComponent ?? options.defaultPendingComponent;
981
984
  const pendingElement = PendingComponent ? /*#__PURE__*/React__namespace.createElement(PendingComponent, {
982
985
  useMatch: route.useMatch,
@@ -1120,24 +1123,12 @@
1120
1123
  if (typeof document !== 'undefined') {
1121
1124
  window.__TSR_ROUTER_CONTEXT__ = routerContext;
1122
1125
  }
1123
- class SearchParamError extends Error {}
1124
- class PathParamError extends Error {}
1125
- function getInitialRouterState(location) {
1126
- return {
1127
- status: 'idle',
1128
- resolvedLocation: location,
1129
- location,
1130
- matches: [],
1131
- pendingMatches: [],
1132
- lastUpdated: Date.now()
1133
- };
1134
- }
1135
1126
  function RouterProvider({
1136
1127
  router,
1137
1128
  ...rest
1138
1129
  }) {
1139
1130
  // Allow the router to update options on the router instance
1140
- router.updateOptions({
1131
+ router.update({
1141
1132
  ...router.options,
1142
1133
  ...rest,
1143
1134
  context: {
@@ -1145,29 +1136,44 @@
1145
1136
  ...rest?.context
1146
1137
  }
1147
1138
  });
1139
+ const inner = /*#__PURE__*/React__namespace.createElement(RouterProviderInner, {
1140
+ router: router
1141
+ });
1142
+ if (router.options.Wrap) {
1143
+ return /*#__PURE__*/React__namespace.createElement(router.options.Wrap, null, inner);
1144
+ }
1145
+ return inner;
1146
+ }
1147
+ function RouterProviderInner({
1148
+ router
1149
+ }) {
1148
1150
  const [preState, setState] = React__namespace.useState(() => router.state);
1149
1151
  const [isTransitioning, startReactTransition] = React__namespace.useTransition();
1152
+ const isAnyTransitioning = isTransitioning || preState.matches.some(d => d.status === 'pending');
1150
1153
  const state = React__namespace.useMemo(() => ({
1151
1154
  ...preState,
1152
- status: isTransitioning ? 'pending' : 'idle',
1155
+ status: isAnyTransitioning ? 'pending' : 'idle',
1153
1156
  location: isTransitioning ? router.latestLocation : preState.location,
1154
1157
  pendingMatches: router.pendingMatches
1155
1158
  }), [preState, isTransitioning]);
1156
1159
  router.setState = setState;
1157
1160
  router.state = state;
1158
1161
  router.startReactTransition = startReactTransition;
1159
- React__namespace.useLayoutEffect(() => {
1162
+ const tryLoad = () => {
1163
+ if (state.location !== router.latestLocation) {
1164
+ startReactTransition(() => {
1165
+ try {
1166
+ router.load();
1167
+ } catch (err) {
1168
+ console.error(err);
1169
+ }
1170
+ });
1171
+ }
1172
+ };
1173
+ useLayoutEffect$1(() => {
1160
1174
  const unsub = router.history.subscribe(() => {
1161
1175
  router.latestLocation = router.parseLocation(router.latestLocation);
1162
- if (state.location !== router.latestLocation) {
1163
- startReactTransition(() => {
1164
- try {
1165
- router.load();
1166
- } catch (err) {
1167
- console.error(err);
1168
- }
1169
- });
1170
- }
1176
+ tryLoad();
1171
1177
  });
1172
1178
  const nextLocation = router.buildLocation({
1173
1179
  search: true,
@@ -1185,7 +1191,7 @@
1185
1191
  unsub();
1186
1192
  };
1187
1193
  }, [router.history]);
1188
- React__namespace.useLayoutEffect(() => {
1194
+ useLayoutEffect$1(() => {
1189
1195
  if (!isTransitioning && state.resolvedLocation !== state.location) {
1190
1196
  router.emit({
1191
1197
  type: 'onResolved',
@@ -1200,14 +1206,10 @@
1200
1206
  }));
1201
1207
  }
1202
1208
  });
1203
- React__namespace.useLayoutEffect(() => {
1204
- startReactTransition(() => {
1205
- try {
1206
- router.load();
1207
- } catch (err) {
1208
- console.error(err);
1209
- }
1210
- });
1209
+ useLayoutEffect$1(() => {
1210
+ if (!window.__TSR_DEHYDRATED__) {
1211
+ tryLoad();
1212
+ }
1211
1213
  }, []);
1212
1214
  return /*#__PURE__*/React__namespace.createElement(routerContext.Provider, {
1213
1215
  value: router
@@ -1224,7 +1226,7 @@
1224
1226
  return opts?.select ? opts.select(state) : state;
1225
1227
  }
1226
1228
  function useRouter() {
1227
- const resolvedContext = window.__TSR_ROUTER_CONTEXT__ || routerContext;
1229
+ const resolvedContext = typeof document !== 'undefined' ? window.__TSR_ROUTER_CONTEXT__ || routerContext : routerContext;
1228
1230
  const value = React__namespace.useContext(resolvedContext);
1229
1231
  warning(value, 'useRouter must be used inside a <RouterProvider> component!');
1230
1232
  return value;
@@ -1264,7 +1266,7 @@
1264
1266
  promise.__deferredState = state;
1265
1267
  }
1266
1268
  if (state.status === 'pending') {
1267
- throw promise;
1269
+ throw new Promise(r => setTimeout(r, 1)).then(() => promise);
1268
1270
  }
1269
1271
  if (state.status === 'error') {
1270
1272
  throw state.error;
@@ -1550,6 +1552,8 @@
1550
1552
  };
1551
1553
  }
1552
1554
 
1555
+ // import warning from 'tiny-warning'
1556
+
1553
1557
  //
1554
1558
 
1555
1559
  const componentTypes = ['component', 'errorComponent', 'pendingComponent'];
@@ -1567,7 +1571,7 @@
1567
1571
  // Must build in constructor
1568
1572
 
1569
1573
  constructor(options) {
1570
- this.updateOptions({
1574
+ this.update({
1571
1575
  defaultPreloadDelay: 50,
1572
1576
  defaultPendingMs: 1000,
1573
1577
  defaultPendingMinMs: 500,
@@ -1577,20 +1581,22 @@
1577
1581
  parseSearch: options?.parseSearch ?? defaultParseSearch
1578
1582
  });
1579
1583
  }
1580
- startReactTransition = () => {
1581
- warning(false, 'startReactTransition implementation is missing. If you see this, please file an issue.');
1582
- };
1583
- setState = () => {
1584
- warning(false, 'setState implementation is missing. If you see this, please file an issue.');
1584
+
1585
+ // These are default implementations that can optionally be overridden
1586
+ // by the router provider once rendered. We provide these so that the
1587
+ // router can be used in a non-react environment if necessary
1588
+ startReactTransition = fn => fn();
1589
+ setState = updater => {
1590
+ this.state = functionalUpdate(updater, this.state);
1585
1591
  };
1586
- updateOptions = newOptions => {
1592
+ update = newOptions => {
1587
1593
  this.options = {
1588
1594
  ...this.options,
1589
1595
  ...newOptions
1590
1596
  };
1591
1597
  this.basepath = `/${trimPath(newOptions.basepath ?? '') ?? ''}`;
1592
1598
  if (!this.history || this.options.history && this.options.history !== this.history) {
1593
- this.history = this.options.history ?? createBrowserHistory();
1599
+ this.history = this.options.history ?? (typeof document !== 'undefined' ? createBrowserHistory() : createMemoryHistory());
1594
1600
  this.latestLocation = this.parseLocation();
1595
1601
  }
1596
1602
  if (this.options.routeTree !== this.routeTree) {
@@ -2230,6 +2236,7 @@
2230
2236
  // forcefully show the pending component
2231
2237
  if (pendingPromise) {
2232
2238
  pendingPromise.then(() => {
2239
+ if (latestPromise = checkLatest()) return;
2233
2240
  didShowPending = true;
2234
2241
  matches[index] = match = {
2235
2242
  ...match,
@@ -2249,6 +2256,7 @@
2249
2256
  if (didShowPending && pendingMinMs) {
2250
2257
  await new Promise(r => setTimeout(r, pendingMinMs));
2251
2258
  }
2259
+ if (latestPromise = checkLatest()) return await latestPromise;
2252
2260
  matches[index] = match = {
2253
2261
  ...match,
2254
2262
  error: undefined,
@@ -2317,7 +2325,7 @@
2317
2325
  // Ingest the new matches
2318
2326
  this.setState(s => ({
2319
2327
  ...s,
2320
- status: 'pending',
2328
+ // status: 'pending',
2321
2329
  location: next,
2322
2330
  matches
2323
2331
  }));
@@ -2346,6 +2354,7 @@
2346
2354
  // ...s,
2347
2355
  // status: 'idle',
2348
2356
  // resolvedLocation: s.location,
2357
+ // matches,
2349
2358
  // }))
2350
2359
 
2351
2360
  //
@@ -2546,63 +2555,42 @@
2546
2555
  }
2547
2556
  return undefined;
2548
2557
  };
2549
-
2550
- // dehydrate = (): DehydratedRouter => {
2551
- // return {
2552
- // state: {
2553
- // dehydratedMatches: this.state.matches.map((d) =>
2554
- // pick(d, ['fetchedAt', 'invalid', 'id', 'status', 'updatedAt']),
2555
- // ),
2556
- // },
2557
- // }
2558
- // }
2559
-
2560
- // hydrate = async (__do_not_use_server_ctx?: HydrationCtx) => {
2561
- // let _ctx = __do_not_use_server_ctx
2562
- // // Client hydrates from window
2563
- // if (typeof document !== 'undefined') {
2564
- // _ctx = window.__TSR_DEHYDRATED__
2565
- // }
2566
-
2567
- // invariant(
2568
- // _ctx,
2569
- // 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?',
2570
- // )
2571
-
2572
- // const ctx = _ctx
2573
- // this.dehydratedData = ctx.payload as any
2574
- // this.options.hydrate?.(ctx.payload as any)
2575
- // const dehydratedState = ctx.router.state
2576
-
2577
- // let matches = this.matchRoutes(
2578
- // this.state.location.pathname,
2579
- // this.state.location.search,
2580
- // ).map((match) => {
2581
- // const dehydratedMatch = dehydratedState.dehydratedMatches.find(
2582
- // (d) => d.id === match.id,
2583
- // )
2584
-
2585
- // invariant(
2586
- // dehydratedMatch,
2587
- // `Could not find a client-side match for dehydrated match with id: ${match.id}!`,
2588
- // )
2589
-
2590
- // if (dehydratedMatch) {
2591
- // return {
2592
- // ...match,
2593
- // ...dehydratedMatch,
2594
- // }
2595
- // }
2596
- // return match
2597
- // })
2598
-
2599
- // this.setState((s) => {
2600
- // return {
2601
- // ...s,
2602
- // matches: dehydratedState.dehydratedMatches as any,
2603
- // }
2604
- // })
2605
- // }
2558
+ dehydrate = () => {
2559
+ return {
2560
+ state: {
2561
+ dehydratedMatches: this.state.matches.map(d => pick(d, ['fetchedAt', 'invalid', 'id', 'status', 'updatedAt', 'loaderData']))
2562
+ }
2563
+ };
2564
+ };
2565
+ hydrate = async __do_not_use_server_ctx => {
2566
+ let _ctx = __do_not_use_server_ctx;
2567
+ // Client hydrates from window
2568
+ if (typeof document !== 'undefined') {
2569
+ _ctx = window.__TSR_DEHYDRATED__;
2570
+ }
2571
+ invariant(_ctx, 'Expected to find a __TSR_DEHYDRATED__ property on window... but we did not. Did you forget to render <DehydrateRouter /> in your app?');
2572
+ const ctx = _ctx;
2573
+ this.dehydratedData = ctx.payload;
2574
+ this.options.hydrate?.(ctx.payload);
2575
+ const dehydratedState = ctx.router.state;
2576
+ let matches = this.matchRoutes(this.state.location.pathname, this.state.location.search).map(match => {
2577
+ const dehydratedMatch = dehydratedState.dehydratedMatches.find(d => d.id === match.id);
2578
+ invariant(dehydratedMatch, `Could not find a client-side match for dehydrated match with id: ${match.id}!`);
2579
+ if (dehydratedMatch) {
2580
+ return {
2581
+ ...match,
2582
+ ...dehydratedMatch
2583
+ };
2584
+ }
2585
+ return match;
2586
+ });
2587
+ this.setState(s => {
2588
+ return {
2589
+ ...s,
2590
+ matches: matches
2591
+ };
2592
+ });
2593
+ };
2606
2594
 
2607
2595
  // resolveMatchPromise = (matchId: string, key: string, value: any) => {
2608
2596
  // state.matches
@@ -2623,36 +2611,43 @@
2623
2611
  function isCtrlEvent(e) {
2624
2612
  return !!(e.metaKey || e.altKey || e.ctrlKey || e.shiftKey);
2625
2613
  }
2614
+ class SearchParamError extends Error {}
2615
+ class PathParamError extends Error {}
2616
+ function getInitialRouterState(location) {
2617
+ return {
2618
+ status: 'idle',
2619
+ resolvedLocation: location,
2620
+ location,
2621
+ matches: [],
2622
+ pendingMatches: [],
2623
+ lastUpdated: Date.now()
2624
+ };
2625
+ }
2626
2626
 
2627
2627
  const useLayoutEffect = typeof window !== 'undefined' ? React__namespace.useLayoutEffect : React__namespace.useEffect;
2628
2628
  const windowKey = 'window';
2629
2629
  const delimiter = '___';
2630
2630
  let weakScrolledElements = new WeakSet();
2631
- let cache;
2632
2631
  const sessionsStorage = typeof window !== 'undefined' && window.sessionStorage;
2632
+ let cache = sessionsStorage ? (() => {
2633
+ const storageKey = 'tsr-scroll-restoration-v2';
2634
+ const state = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null') || {
2635
+ cached: {},
2636
+ next: {}
2637
+ };
2638
+ return {
2639
+ state,
2640
+ set: updater => {
2641
+ cache.state = functionalUpdate(updater, cache.state);
2642
+ window.sessionStorage.setItem(storageKey, JSON.stringify(cache.state));
2643
+ }
2644
+ };
2645
+ })() : undefined;
2633
2646
  const defaultGetKey = location => location.state.key;
2634
2647
  function useScrollRestoration(options) {
2635
2648
  const router = useRouter();
2636
2649
  useLayoutEffect(() => {
2637
2650
  const getKey = options?.getKey || defaultGetKey;
2638
- if (sessionsStorage) {
2639
- if (!cache) {
2640
- cache = (() => {
2641
- const storageKey = 'tsr-scroll-restoration-v2';
2642
- const state = JSON.parse(window.sessionStorage.getItem(storageKey) || 'null') || {
2643
- cached: {},
2644
- next: {}
2645
- };
2646
- return {
2647
- state,
2648
- set: updater => {
2649
- cache.state = functionalUpdate(updater, cache.state);
2650
- window.sessionStorage.setItem(storageKey, JSON.stringify(cache.state));
2651
- }
2652
- };
2653
- })();
2654
- }
2655
- }
2656
2651
  const {
2657
2652
  history
2658
2653
  } = window;
@@ -2662,7 +2657,17 @@
2662
2657
  const onScroll = event => {
2663
2658
  if (weakScrolledElements.has(event.target)) return;
2664
2659
  weakScrolledElements.add(event.target);
2665
- const elementSelector = event.target === document || event.target === window ? windowKey : getCssSelector(event.target);
2660
+ let elementSelector = '';
2661
+ if (event.target === document || event.target === window) {
2662
+ elementSelector = windowKey;
2663
+ } else {
2664
+ const attrId = event.target.getAttribute('data-scroll-restoration-id');
2665
+ if (attrId) {
2666
+ elementSelector = `[data-scroll-restoration-id="${attrId}"]`;
2667
+ } else {
2668
+ elementSelector = getCssSelector(event.target);
2669
+ }
2670
+ }
2666
2671
  if (!cache.state.next[elementSelector]) {
2667
2672
  cache.set(c => ({
2668
2673
  ...c,
@@ -2676,15 +2681,6 @@
2676
2681
  }));
2677
2682
  }
2678
2683
  };
2679
- const getCssSelector = el => {
2680
- let path = [],
2681
- parent;
2682
- while (parent = el.parentNode) {
2683
- path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2684
- el = parent;
2685
- }
2686
- return `${path.join(' > ')}`.toLowerCase();
2687
- };
2688
2684
  if (typeof document !== 'undefined') {
2689
2685
  document.addEventListener('scroll', onScroll, true);
2690
2686
  }
@@ -2764,6 +2760,32 @@
2764
2760
  useScrollRestoration(props);
2765
2761
  return null;
2766
2762
  }
2763
+ function useElementScrollRestoration(options) {
2764
+ const router = useRouter();
2765
+ const getKey = options?.getKey || defaultGetKey;
2766
+ let elementSelector = '';
2767
+ if (options.id) {
2768
+ elementSelector = `[data-scroll-restoration-id="${options.id}"]`;
2769
+ } else {
2770
+ const element = options.getElement?.();
2771
+ if (!element) {
2772
+ return;
2773
+ }
2774
+ elementSelector = getCssSelector(element);
2775
+ }
2776
+ const restoreKey = getKey(router.latestLocation);
2777
+ const cacheKey = [restoreKey, elementSelector].join(delimiter);
2778
+ return cache.state.cached[cacheKey];
2779
+ }
2780
+ function getCssSelector(el) {
2781
+ let path = [],
2782
+ parent;
2783
+ while (parent = el.parentNode) {
2784
+ path.unshift(`${el.tagName}:nth-child(${[].indexOf.call(parent.children, el) + 1})`);
2785
+ el = parent;
2786
+ }
2787
+ return `${path.join(' > ')}`.toLowerCase();
2788
+ }
2767
2789
 
2768
2790
  function useBlocker(message, condition = true) {
2769
2791
  const {
@@ -2889,6 +2911,7 @@
2889
2911
  exports.typedNavigate = typedNavigate;
2890
2912
  exports.useAwaited = useAwaited;
2891
2913
  exports.useBlocker = useBlocker;
2914
+ exports.useElementScrollRestoration = useElementScrollRestoration;
2892
2915
  exports.useLayoutEffect = useLayoutEffect$1;
2893
2916
  exports.useLinkProps = useLinkProps;
2894
2917
  exports.useLoaderData = useLoaderData;