clear-react-router 1.3.3 → 1.3.4

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.
package/README.md CHANGED
@@ -12,6 +12,7 @@ A lightweight, type-safe routing library for React applications with nested rout
12
12
  - 🎯 **Type-safe Redirects** - Redirect from beforeLoad hook
13
13
  - 📦 **Prefetching** - Preload data on hover for instant navigation
14
14
  - 🚀 **Lazy Loading** - Code-split your routes with dynamic imports for optimal performance
15
+ - 📍 **Scroll Restoration** — Automatically saves and restores scroll position when navigating back to a page (preserves user's scroll position)
15
16
  - 🎨 **Flexible API** - Use components or hooks as you prefer
16
17
  - 📱 **Browser History** - Full support for browser back/forward buttons
17
18
  - 🧠 **Context-aware** - Pass and update context through routes
@@ -45,6 +46,7 @@ The root component that provides routing context to the application. Place stati
45
46
  | `context` | `object` | `{}` | Initial context (user, theme, etc.) |
46
47
  | `isAnimated` | `boolean` | `false` | Enable smooth page transitions |
47
48
  | `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
49
+ | `preserveScroll` | `boolean` | `true` | Save and restore scroll position when navigating between pages |
48
50
  | `children` | `ReactNode` | required | App content (must include `<Router />`) |
49
51
 
50
52
  ```
@@ -6,6 +6,7 @@ type RouteProviderProps = {
6
6
  context?: Record<string, unknown>;
7
7
  isAnimated?: boolean;
8
8
  animationDuration?: number;
9
+ preserveScroll?: boolean;
9
10
  };
10
- export declare const RouterProvider: ({ children, routeList, context: initialContext, isAnimated, animationDuration, }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
11
+ export declare const RouterProvider: ({ children, routeList, context: initialContext, isAnimated, animationDuration, preserveScroll, }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
11
12
  export {};
@@ -7,8 +7,9 @@ type UseHandleNavigation = {
7
7
  revalidateCache(arg: RevalidateCacheArgs): Promise<void>;
8
8
  isAnimated: boolean;
9
9
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
10
+ setScrollMap: Dispatch<SetStateAction<Record<string, number>>>;
10
11
  };
11
- export declare const useHandleNavigation: ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext, }: UseHandleNavigation) => {
12
+ export declare const useHandleNavigation: ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext, setScrollMap, }: UseHandleNavigation) => {
12
13
  blockerState: BlockerState;
13
14
  updateLocation: (nextLocation: Location) => Promise<void>;
14
15
  updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
@@ -0,0 +1,6 @@
1
+ type UsePreserveScrollParams = {
2
+ pathname: string;
3
+ preserveScroll: boolean;
4
+ };
5
+ export declare const usePreserveScroll: ({ pathname, preserveScroll }: UsePreserveScrollParams) => import("react").Dispatch<import("react").SetStateAction<Record<string, number>>>;
6
+ export {};
package/dist/index.js CHANGED
@@ -138,7 +138,7 @@ var comparePaths = (el, pathname) => {
138
138
  };
139
139
  //#endregion
140
140
  //#region hooks/useHandleNavigation.ts
141
- var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext }) => {
141
+ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext, setScrollMap }) => {
142
142
  const [blockedRoute, setBlockedRoute] = useState({
143
143
  from: "",
144
144
  to: ""
@@ -146,6 +146,14 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
146
146
  const [beforeLoadError, setBeforeLoadError] = useState(false);
147
147
  const prevPathname = useRef("");
148
148
  const navigationSeq = useRef(0);
149
+ const updateScrollMap = useCallback(() => setScrollMap((prevState) => {
150
+ const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
151
+ if (!scrollPosition || prevState[prevPathname.current] === scrollPosition) return prevState;
152
+ return {
153
+ ...prevState,
154
+ [prevPathname.current]: scrollPosition
155
+ };
156
+ }), [setScrollMap]);
149
157
  const navigation = useCallback((nextLocation) => {
150
158
  setLocation(nextLocation);
151
159
  prevPathname.current = nextLocation.pathname;
@@ -164,6 +172,7 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
164
172
  const navigationHandler = useCallback(async (nextLocation, isFirstCall) => {
165
173
  navigationSeq.current = navigationSeq.current + 1;
166
174
  const seq = navigationSeq.current;
175
+ updateScrollMap();
167
176
  const nextItem = routeList.find((el) => comparePaths(el, nextLocation.pathname));
168
177
  const params = getParamsObject({
169
178
  routeItem: nextItem,
@@ -209,7 +218,8 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
209
218
  routeList,
210
219
  transitionedNavigation,
211
220
  isAnimated,
212
- setContext
221
+ setContext,
222
+ updateScrollMap
213
223
  ]);
214
224
  const setNextLocationRef = useLatest(navigationHandler);
215
225
  const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
@@ -341,6 +351,23 @@ var useLoader = ({ routeList, context, setContext }) => {
341
351
  };
342
352
  };
343
353
  //#endregion
354
+ //#region hooks/usePreserveScroll.ts
355
+ var usePreserveScroll = ({ pathname, preserveScroll }) => {
356
+ const [scrollMap, setScrollMap] = useState({});
357
+ useEffect(() => {
358
+ if (!preserveScroll || !pathname || !scrollMap[pathname]) return;
359
+ document.scrollingElement?.scrollTo({
360
+ top: scrollMap[pathname],
361
+ behavior: "smooth"
362
+ });
363
+ }, [
364
+ pathname,
365
+ scrollMap,
366
+ preserveScroll
367
+ ]);
368
+ return setScrollMap;
369
+ };
370
+ //#endregion
344
371
  //#region hooks/useApplyCustomAnimation.ts
345
372
  var useApplyCustomAnimation = (animationDuration) => {
346
373
  useEffect(() => {
@@ -379,10 +406,14 @@ var useApplyCustomAnimation = (animationDuration) => {
379
406
  };
380
407
  //#endregion
381
408
  //#region components/RouterProvider.tsx
382
- var RouterProvider = ({ children, routeList, context: initialContext = {}, isAnimated = false, animationDuration }) => {
409
+ var RouterProvider = ({ children, routeList, context: initialContext = {}, isAnimated = false, animationDuration, preserveScroll = true }) => {
383
410
  const [location, setLocation] = useState({});
384
411
  const [context, setContext] = useState(initialContext);
385
412
  useApplyCustomAnimation(animationDuration);
413
+ const setScrollMap = usePreserveScroll({
414
+ pathname: location.pathname,
415
+ preserveScroll
416
+ });
386
417
  const { loaderError, loaderCache, prefetchLoader, revalidateCache, isLoading } = useLoader({
387
418
  routeList,
388
419
  context,
@@ -394,7 +425,8 @@ var RouterProvider = ({ children, routeList, context: initialContext = {}, isAni
394
425
  context,
395
426
  setContext,
396
427
  revalidateCache,
397
- isAnimated
428
+ isAnimated,
429
+ setScrollMap
398
430
  });
399
431
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
400
432
  ...useMemo(() => ({
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.3.3",
3
+ "version": "1.3.4",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {