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 +18 -4
- package/dist/components/RouterProvider.d.ts +2 -1
- package/dist/hooks/useHandleNavigation.d.ts +2 -1
- package/dist/hooks/useHistoricalTrail.d.ts +1 -0
- package/dist/hooks/usePreserveScroll.d.ts +6 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +51 -5
- package/package.json +11 -2
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
|
-
-
|
|
329
|
-
-
|
|
330
|
-
-
|
|
331
|
-
-
|
|
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
|
-
|
|
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
|
+
"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",
|