clear-react-router 1.3.3 → 1.3.5

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
  ```
@@ -325,13 +327,25 @@ function ProductFilter() {
325
327
 
326
328
  **Key features:**
327
329
 
328
- - **Array support** — `getSearchParams` returns `string[]` when multiple values exist for the same key
329
- - **Functional updates** — Update parameters based on previous state without losing other params
330
- - **Type-safe** — Proper TypeScript support with overloads
331
- - **Stable reference** — `setSearchParams` reference is stable and safe to use in `useEffect`
330
+ - **Array support** — `getSearchParams` returns `string[]` when multiple values exist for the same key
331
+ - **Functional updates** — Update parameters based on previous state without losing other params
332
+ - **Type-safe** — Proper TypeScript support with overloads
333
+ - **Stable reference** — `setSearchParams` reference is stable and safe to use in `useEffect`
332
334
 
333
335
  > **Note:** `getSearchParams` returns `string` for single values, `string[]` for multiple values, and `''` if the key is not found.
334
336
 
337
+ ### `useHistoricalTrail()`
338
+
339
+ Returns an array of pathnames representing the user's actual navigation history. Perfect for **history-based breadcrumbs** in dashboards, admin panels, multi-step forms, or any app where users navigate non-linearly.
340
+
341
+ **Returns:** Array of pathnames in chronological visit order (e.g., `['/dashboard', '/users', '/settings']`)
342
+
343
+ **Key features:**
344
+ - **Chronological order** — Paths are stored in the order the user visited them
345
+ - **Unique entries** — Revisiting a page trims the trail to that point
346
+ - **Respects navigation blocking** — Only successful navigations are added
347
+ - **Redirect-safe** — Redirected pages are not added to the trail
348
+
335
349
  ## Lazy Loading
336
350
 
337
351
  Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
@@ -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 @@
1
+ export declare const useHistoricalTrail: () => string[];
@@ -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.d.ts CHANGED
@@ -9,5 +9,6 @@ export { useBlocker } from './hooks/useBlocker';
9
9
  export { useBeforeUnload } from './hooks/useBeforeUnload';
10
10
  export { useRouterContext } from './hooks/useRouterContext';
11
11
  export { useSearchParams } from './hooks/useSearchParams';
12
+ export { useHistoricalTrail } from './hooks/useHistoricalTrail';
12
13
  export { createRouter } from './utils/utils';
13
14
  export type { RouteItem, BlockerState, Location } from './types/global';
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(() => ({
@@ -610,4 +642,18 @@ var useSearchParams = () => {
610
642
  };
611
643
  };
612
644
  //#endregion
613
- export { Link, Router, RouterProvider, createRouter, useBeforeUnload, useBlocker, useLoaderState, useLocation, useNavigate, useParams, useRouterContext, useSearchParams };
645
+ //#region hooks/useHistoricalTrail.ts
646
+ var useHistoricalTrail = () => {
647
+ const { pathname } = useLocation();
648
+ const [trail, setTrail] = useState([]);
649
+ useEffect(() => {
650
+ if (!pathname) return;
651
+ setTrail((prevState) => {
652
+ const index = prevState.indexOf(pathname);
653
+ return index === -1 ? [...prevState, pathname] : prevState.slice(0, index + 1);
654
+ });
655
+ }, [pathname]);
656
+ return trail;
657
+ };
658
+ //#endregion
659
+ export { Link, Router, RouterProvider, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useLoaderState, useLocation, useNavigate, useParams, useRouterContext, useSearchParams };
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.5",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -12,7 +12,16 @@
12
12
  "routing",
13
13
  "typescript",
14
14
  "spa",
15
- "nested-routes"
15
+ "nested-routes",
16
+ "data-loading",
17
+ "navigation-blocker",
18
+ "scroll-restoration",
19
+ "breadcrumbs",
20
+ "search-params",
21
+ "lazy-loading",
22
+ "prefetching",
23
+ "view-transitions",
24
+ "animations"
16
25
  ],
17
26
  "repository": {
18
27
  "type": "git",