clear-react-router 1.8.4 → 1.8.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 +33 -6
- package/dist/components/Link.d.ts +14 -6
- package/dist/hooks/useIsRoutePending.d.ts +1 -0
- package/dist/index.js +44 -10
- package/dist/types.d.ts +1 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,7 +70,7 @@ It provides first-class support for:
|
|
|
70
70
|
|
|
71
71
|
### `createRouter(routes)`
|
|
72
72
|
|
|
73
|
-
Normalizes route configuration
|
|
73
|
+
Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
74
74
|
|
|
75
75
|
| Property | Type | Description |
|
|
76
76
|
|----------|------|-------------|
|
|
@@ -88,14 +88,26 @@ Normalizes route configuration, extracts dynamic params, builds nested paths.
|
|
|
88
88
|
|
|
89
89
|
### `Link`
|
|
90
90
|
|
|
91
|
-
Component for client-side navigation with prefetch support.
|
|
91
|
+
Component for client-side navigation with prefetch support, active state detection, and pending state styling.
|
|
92
92
|
|
|
93
|
-
| Prop | Type | Default |
|
|
94
|
-
|
|
95
|
-
| `to` | `string` | required |
|
|
93
|
+
| Prop | Type | Default | Description |
|
|
94
|
+
|------|------|---------|-------------|
|
|
95
|
+
| `to` | `string` | required | Target path |
|
|
96
96
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
|
|
97
97
|
| `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
|
|
98
|
-
| `children` | `
|
|
98
|
+
| `children` | `ReactNode` | required | Content to render inside the link |
|
|
99
|
+
| `className` | `string \| ({ isActive, isPending }) => string` | `undefined` | CSS class name(s). Can be a function for dynamic styling |
|
|
100
|
+
| `style` | `CSSProperties \| ({ isActive, isPending }) => CSSProperties` | `undefined` | Inline styles. Can be a function for dynamic styling |
|
|
101
|
+
| `activeClassName` | `string` optional | `'active-link'` | Class name applied when the link matches the current URL |
|
|
102
|
+
| `pendingClassName` | `string` optional | `'pending-link'` | Class name applied when the link's target is loading |
|
|
103
|
+
| `onClick` | `() => void` | `undefined` | Callback fired before navigation |
|
|
104
|
+
|
|
105
|
+
**State values:**
|
|
106
|
+
|
|
107
|
+
| State | Type | Description |
|
|
108
|
+
|-------|------|-------------|
|
|
109
|
+
| `isActive` | `boolean` | `true` when the link's `to` matches the current URL |
|
|
110
|
+
| `isPending` | `boolean` | `true` when the target route is currently loading (loader is running) |
|
|
99
111
|
|
|
100
112
|
### Prefetch Strategies
|
|
101
113
|
|
|
@@ -123,6 +135,21 @@ import { Router, Link } from 'clear-react-router';
|
|
|
123
135
|
<Link to="/admin" prefetch="none">
|
|
124
136
|
Admin Panel
|
|
125
137
|
</Link>
|
|
138
|
+
|
|
139
|
+
// With custom active/pending classes
|
|
140
|
+
<Link to="/settings" activeClassName="active-nav-link" pendingClassName="loading-nav-link">
|
|
141
|
+
Settings
|
|
142
|
+
</Link>
|
|
143
|
+
|
|
144
|
+
// With dynamic className
|
|
145
|
+
<Link to="/dashboard" className={({ isActive, isPending }) => isActive ? 'text-blue-600' : isPending ? 'text-gray-400' : 'text-gray-600'}>
|
|
146
|
+
Dashboard
|
|
147
|
+
</Link>
|
|
148
|
+
|
|
149
|
+
// With dynamic style
|
|
150
|
+
<Link to="/profile" style={({ isActive }) => ({ fontWeight: isActive ? 'bold' : 'normal' })}>
|
|
151
|
+
Profile
|
|
152
|
+
</Link>
|
|
126
153
|
```
|
|
127
154
|
**Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
|
|
128
155
|
|
|
@@ -1,13 +1,21 @@
|
|
|
1
|
-
import { type
|
|
1
|
+
import { type CSSProperties, ReactNode } from 'react';
|
|
2
2
|
import { RouterProps } from '../types';
|
|
3
3
|
type LinkProps = {
|
|
4
4
|
to: string;
|
|
5
|
-
children:
|
|
6
|
-
onClick: (e: MouseEvent) => void;
|
|
7
|
-
style: CSSProperties;
|
|
8
|
-
}>;
|
|
5
|
+
children: ReactNode;
|
|
9
6
|
prefetch?: RouterProps['prefetch'];
|
|
10
7
|
hoverPrefetchDelay?: number;
|
|
8
|
+
style?: CSSProperties | (({ isActive }: {
|
|
9
|
+
isActive: boolean;
|
|
10
|
+
isPending: boolean;
|
|
11
|
+
}) => CSSProperties);
|
|
12
|
+
className?: string | (({ isActive }: {
|
|
13
|
+
isActive: boolean;
|
|
14
|
+
isPending: boolean;
|
|
15
|
+
}) => string);
|
|
16
|
+
activeClassName?: string;
|
|
17
|
+
pendingClassName?: string;
|
|
18
|
+
onClick?(): void;
|
|
11
19
|
};
|
|
12
|
-
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }: LinkProps) => import("react/jsx-runtime").JSX.Element;
|
|
20
|
+
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, onClick, activeClassName, pendingClassName, }: LinkProps) => import("react/jsx-runtime").JSX.Element;
|
|
13
21
|
export {};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useIsRoutePending: (routePath: string) => boolean;
|
package/dist/index.js
CHANGED
|
@@ -212,7 +212,7 @@ var findRoute = (pathname, includeAll) => {
|
|
|
212
212
|
//#region runtime/navigate.ts
|
|
213
213
|
var navigationSeq = 0;
|
|
214
214
|
var createNavigate = (routerState, revalidateCache) => {
|
|
215
|
-
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap } = routerState;
|
|
215
|
+
const { loaderStateRef, scrollMapState, prevPathnameRef, loaderFallbackState, isLoadingState, contextState, timestampMap, pendingPathRef } = routerState;
|
|
216
216
|
const commitNavigation = createCommitNavigation(createCommitState(routerState), prevPathnameRef);
|
|
217
217
|
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
218
218
|
const getContext = () => ({
|
|
@@ -269,10 +269,12 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
269
269
|
const loader = async (routeItem, location) => {
|
|
270
270
|
if (!routeItem?.loader) return;
|
|
271
271
|
isLoadingState.setState(true);
|
|
272
|
+
pendingPathRef.set(location.pathname);
|
|
272
273
|
await revalidateCache({
|
|
273
274
|
routeItem,
|
|
274
275
|
pathname: location.pathname
|
|
275
276
|
});
|
|
277
|
+
pendingPathRef.set("");
|
|
276
278
|
};
|
|
277
279
|
const afterLoad = async (routeItem, params) => {
|
|
278
280
|
const { afterLoad } = routerConfig;
|
|
@@ -383,10 +385,10 @@ var getRetry = (routeItem) => {
|
|
|
383
385
|
};
|
|
384
386
|
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
385
387
|
var createRevalidateCache = (routerState) => {
|
|
388
|
+
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
386
389
|
const revalidateCache = async ({ routeItem, pathname }, retried = 0) => {
|
|
387
390
|
if (!routeItem?.loader) return;
|
|
388
|
-
const isCacheItemFresh = createIsCacheItemFresh(
|
|
389
|
-
const { loaderStateRef, timestampMap, contextState } = routerState;
|
|
391
|
+
const isCacheItemFresh = createIsCacheItemFresh(timestampMap);
|
|
390
392
|
if (loadingPromises.has(pathname)) return loadingPromises.get(pathname);
|
|
391
393
|
if (isCacheItemFresh({
|
|
392
394
|
routeItem,
|
|
@@ -471,6 +473,7 @@ var createRouterInstance = () => {
|
|
|
471
473
|
}),
|
|
472
474
|
loaderStateRef: new Cell(emptyLoaderState),
|
|
473
475
|
prevPathnameRef: new Cell(""),
|
|
476
|
+
pendingPathRef: new Cell(""),
|
|
474
477
|
timestampMap: /* @__PURE__ */ new Map()
|
|
475
478
|
};
|
|
476
479
|
const revalidateCache = createRevalidateCache(routerState);
|
|
@@ -505,7 +508,8 @@ var createRouterInstance = () => {
|
|
|
505
508
|
scrollMapState: routerState.scrollMapState,
|
|
506
509
|
contextState: routerState.contextState,
|
|
507
510
|
blockedRouteState: routerState.blockedRouteState,
|
|
508
|
-
prevPathnameRef: routerState.prevPathnameRef
|
|
511
|
+
prevPathnameRef: routerState.prevPathnameRef,
|
|
512
|
+
pendingPathRef: routerState.pendingPathRef
|
|
509
513
|
},
|
|
510
514
|
runtime: {
|
|
511
515
|
navigate,
|
|
@@ -711,17 +715,26 @@ var Router = ({ routes, beforeLoad, afterLoad, animationDuration, isAnimated = f
|
|
|
711
715
|
});
|
|
712
716
|
};
|
|
713
717
|
//#endregion
|
|
718
|
+
//#region hooks/useIsRoutePending.ts
|
|
719
|
+
var useIsRoutePending = (routePath) => {
|
|
720
|
+
const { hooks: { useIsLoading }, state: { pendingPathRef } } = router;
|
|
721
|
+
const [isPending] = useIsLoading();
|
|
722
|
+
return isPending && pendingPathRef.value === routePath;
|
|
723
|
+
};
|
|
724
|
+
//#endregion
|
|
714
725
|
//#region hooks/useNavigate.ts
|
|
715
726
|
var useNavigate = router.hooks.useNavigate;
|
|
716
727
|
//#endregion
|
|
717
728
|
//#region components/Link.tsx
|
|
718
|
-
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
719
|
-
const
|
|
720
|
-
const
|
|
721
|
-
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
729
|
+
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, onClick, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
|
|
730
|
+
const isPending = useIsRoutePending(to);
|
|
731
|
+
const { pathname } = useLocation();
|
|
722
732
|
const navigate = useNavigate();
|
|
723
733
|
const timeout = useRef(0);
|
|
724
734
|
const ref = useRef(null);
|
|
735
|
+
const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
|
|
736
|
+
const prefetch = prefetchLink || configPrefetch;
|
|
737
|
+
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
725
738
|
const onMouseEnter = useCallback(() => {
|
|
726
739
|
if (prefetch !== "hover" || !prefetchDelay) return;
|
|
727
740
|
if (timeout.current) clearTimeout(timeout.current);
|
|
@@ -756,10 +769,31 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
756
769
|
if (element) observer.disconnect();
|
|
757
770
|
};
|
|
758
771
|
}, [prefetch, to]);
|
|
772
|
+
const isActive = to === pathname;
|
|
773
|
+
const normalizedClassName = typeof className === "function" ? className({
|
|
774
|
+
isActive,
|
|
775
|
+
isPending
|
|
776
|
+
}) : className;
|
|
777
|
+
const normalizedStyle = typeof style === "function" ? style({
|
|
778
|
+
isActive,
|
|
779
|
+
isPending
|
|
780
|
+
}) : style;
|
|
781
|
+
const resultClassName = [
|
|
782
|
+
isActive && activeClassName,
|
|
783
|
+
isPending && pendingClassName,
|
|
784
|
+
normalizedClassName
|
|
785
|
+
].filter(Boolean).join(" ");
|
|
786
|
+
const clickHandler = async (event) => {
|
|
787
|
+
event.preventDefault();
|
|
788
|
+
onClick?.();
|
|
789
|
+
await navigate(to);
|
|
790
|
+
};
|
|
759
791
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
792
|
+
href: to,
|
|
760
793
|
ref,
|
|
761
|
-
style:
|
|
762
|
-
|
|
794
|
+
style: normalizedStyle,
|
|
795
|
+
className: resultClassName,
|
|
796
|
+
onClick: clickHandler,
|
|
763
797
|
onMouseEnter,
|
|
764
798
|
onMouseLeave,
|
|
765
799
|
children
|
package/dist/types.d.ts
CHANGED