clear-react-router 1.9.3 → 1.9.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 +25 -1
- package/dist/components/Link.d.ts +19 -5
- package/dist/index.js +28 -18
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,6 +107,8 @@ Component for client-side navigation with prefetch support, active state detecti
|
|
|
107
107
|
| Prop | Type | Default | Description |
|
|
108
108
|
|------|------|---------|-------------|
|
|
109
109
|
| `to` | `string` | required | Target path |
|
|
110
|
+
| `as` | `(props: ElementProps<T>) => ReactElement` | renders `<a>` | Render prop for using a custom element/component instead of the default `<a>`. Receives the computed isActive and isPending values, event handlers, and ref to attach to your own element |
|
|
111
|
+
| `exact` | `boolean` | `false` | When `false`, the link is also considered active if the current URL starts with `to` (useful for nested routes) |
|
|
110
112
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
|
|
111
113
|
| `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
|
|
112
114
|
| `children` | `ReactNode` | required | Content to render inside the link |
|
|
@@ -120,7 +122,7 @@ Component for client-side navigation with prefetch support, active state detecti
|
|
|
120
122
|
|
|
121
123
|
| State | Type | Description |
|
|
122
124
|
|-------|------|-------------|
|
|
123
|
-
| `isActive` | `boolean` | `true` when the link's `to` matches the current URL |
|
|
125
|
+
| `isActive` | `boolean` | `true` when the link's `to` matches the current URL considering `exact` value |
|
|
124
126
|
| `isPending` | `boolean` | `true` when the target route is currently loading (loader is running) |
|
|
125
127
|
|
|
126
128
|
### Prefetch Strategies
|
|
@@ -137,6 +139,22 @@ Component for client-side navigation with prefetch support, active state detecti
|
|
|
137
139
|
```tsx
|
|
138
140
|
import { Router, Link } from 'clear-react-router';
|
|
139
141
|
|
|
142
|
+
// Render a custom element/component via `as`. The function receives ref, event handlers, isActive/isPending and must render them itself
|
|
143
|
+
import { Button } from '@mui/material';
|
|
144
|
+
|
|
145
|
+
<Link
|
|
146
|
+
to="/dashboard"
|
|
147
|
+
as={({ isActive, isPending, ...props }) => (
|
|
148
|
+
<Button
|
|
149
|
+
{...props}
|
|
150
|
+
variant={isActive ? 'contained' : 'outlined'}
|
|
151
|
+
sx={{ opacity: isPending ? 0.5 : 1 }}
|
|
152
|
+
/>
|
|
153
|
+
)}
|
|
154
|
+
>
|
|
155
|
+
Dashboard
|
|
156
|
+
</Link>
|
|
157
|
+
|
|
140
158
|
// Global prefetch: hover with 100ms delay
|
|
141
159
|
<Router routes={routes} prefetch="hover" hoverPrefetchDelay={100} />
|
|
142
160
|
|
|
@@ -169,6 +187,12 @@ import { Router, Link } from 'clear-react-router';
|
|
|
169
187
|
<Link to="/details" beforeNavigate={saveDashboardData}>
|
|
170
188
|
Admin Panel
|
|
171
189
|
</Link>
|
|
190
|
+
|
|
191
|
+
// `exact={false}` — active for nested routes too
|
|
192
|
+
// e.g. active when current URL is "/settings" or "/settings/profile"
|
|
193
|
+
<Link to="/settings" exact={false}>
|
|
194
|
+
Settings
|
|
195
|
+
</Link>
|
|
172
196
|
```
|
|
173
197
|
**Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
|
|
174
198
|
|
|
@@ -1,19 +1,33 @@
|
|
|
1
|
-
import { type CSSProperties, ReactNode,
|
|
1
|
+
import { type CSSProperties, ReactNode, MouseEvent, ReactElement, Ref } from 'react';
|
|
2
2
|
import { RouterProps } from '../types';
|
|
3
3
|
type States = {
|
|
4
4
|
isActive: boolean;
|
|
5
5
|
isPending: boolean;
|
|
6
6
|
};
|
|
7
|
-
type
|
|
7
|
+
type ElementProps<T extends HTMLElement = HTMLElement> = {
|
|
8
|
+
ref: Ref<T>;
|
|
9
|
+
href: string;
|
|
10
|
+
isActive: boolean;
|
|
11
|
+
isPending: boolean;
|
|
12
|
+
onClick(event: MouseEvent): void;
|
|
13
|
+
onMouseEnter(event: MouseEvent): void;
|
|
14
|
+
onMouseLeave(event: MouseEvent): void;
|
|
15
|
+
className?: string;
|
|
16
|
+
style?: CSSProperties;
|
|
17
|
+
children?: ReactNode;
|
|
18
|
+
};
|
|
19
|
+
type LinkProps<T extends HTMLElement = HTMLAnchorElement> = {
|
|
8
20
|
to: string;
|
|
9
|
-
children
|
|
21
|
+
children?: ReactNode;
|
|
22
|
+
as?: (props: ElementProps<T>) => ReactElement;
|
|
10
23
|
prefetch?: RouterProps['prefetch'];
|
|
11
24
|
hoverPrefetchDelay?: number;
|
|
12
|
-
style?: CSSProperties | ((arg: States) => CSSProperties);
|
|
13
25
|
className?: string | ((arg: States) => string);
|
|
14
26
|
activeClassName?: string;
|
|
15
27
|
pendingClassName?: string;
|
|
16
28
|
beforeNavigate?(): Promise<void>;
|
|
29
|
+
style?: CSSProperties | ((arg: States) => CSSProperties);
|
|
30
|
+
exact?: boolean;
|
|
17
31
|
};
|
|
18
|
-
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, activeClassName, pendingClassName, }: LinkProps) => import("react
|
|
32
|
+
export declare const Link: <T extends HTMLElement = HTMLAnchorElement>({ children, to, as, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, exact, activeClassName, pendingClassName, }: LinkProps<T>) => ReactElement<unknown, string | import("react").JSXElementConstructor<any>>;
|
|
19
33
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -44,7 +44,7 @@ var createCommitState = ({ routeItemDataState, pendingState }) => (nextLocation,
|
|
|
44
44
|
//#region constants.ts
|
|
45
45
|
var emptyLoaderState = {};
|
|
46
46
|
//#endregion
|
|
47
|
-
//#region \0@oxc-project+runtime@0.
|
|
47
|
+
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/typeof.js
|
|
48
48
|
function _typeof(o) {
|
|
49
49
|
"@babel/helpers - typeof";
|
|
50
50
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
@@ -54,7 +54,7 @@ function _typeof(o) {
|
|
|
54
54
|
}, _typeof(o);
|
|
55
55
|
}
|
|
56
56
|
//#endregion
|
|
57
|
-
//#region \0@oxc-project+runtime@0.
|
|
57
|
+
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/toPrimitive.js
|
|
58
58
|
function toPrimitive(t, r) {
|
|
59
59
|
if ("object" != _typeof(t) || !t) return t;
|
|
60
60
|
var e = t[Symbol.toPrimitive];
|
|
@@ -66,13 +66,13 @@ function toPrimitive(t, r) {
|
|
|
66
66
|
return ("string" === r ? String : Number)(t);
|
|
67
67
|
}
|
|
68
68
|
//#endregion
|
|
69
|
-
//#region \0@oxc-project+runtime@0.
|
|
69
|
+
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/toPropertyKey.js
|
|
70
70
|
function toPropertyKey(t) {
|
|
71
71
|
var i = toPrimitive(t, "string");
|
|
72
72
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
73
73
|
}
|
|
74
74
|
//#endregion
|
|
75
|
-
//#region \0@oxc-project+runtime@0.
|
|
75
|
+
//#region \0@oxc-project+runtime@0.143.0/helpers/esm/defineProperty.js
|
|
76
76
|
function _defineProperty(e, r, t) {
|
|
77
77
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
78
78
|
value: t,
|
|
@@ -133,7 +133,8 @@ var createIsCacheItemFresh = (loaderMap) => ({ routeItem, pathname }) => {
|
|
|
133
133
|
* LICENSE file in the root directory of this source tree.
|
|
134
134
|
*/
|
|
135
135
|
var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
136
|
-
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element")
|
|
136
|
+
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
|
|
137
|
+
var REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
137
138
|
function jsxProd(type, config, maybeKey) {
|
|
138
139
|
var key = null;
|
|
139
140
|
void 0 !== maybeKey && (key = "" + maybeKey);
|
|
@@ -756,12 +757,13 @@ var useLocation = () => {
|
|
|
756
757
|
};
|
|
757
758
|
//#endregion
|
|
758
759
|
//#region components/Link.tsx
|
|
759
|
-
var
|
|
760
|
+
var defaultAs = ({ isActive, isPending, ...props }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ...props });
|
|
761
|
+
var Link = ({ children, to, as = defaultAs, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, exact = false, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
|
|
760
762
|
const isPending = useIsRoutePending(to);
|
|
761
763
|
const { pathname } = useLocation();
|
|
762
764
|
const navigate = useNavigate();
|
|
763
765
|
const timeout = useRef(0);
|
|
764
|
-
const
|
|
766
|
+
const elementRef = useRef(null);
|
|
765
767
|
const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
|
|
766
768
|
const prefetch = prefetchLink || configPrefetch;
|
|
767
769
|
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
@@ -789,17 +791,20 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, classNam
|
|
|
789
791
|
}, [prefetch, to]);
|
|
790
792
|
useEffect(() => {
|
|
791
793
|
if (prefetch !== "viewport") return;
|
|
792
|
-
const element =
|
|
793
|
-
|
|
794
|
+
const element = elementRef.current;
|
|
795
|
+
if (!element) return;
|
|
796
|
+
const observer = new IntersectionObserver(async ([entry]) => {
|
|
797
|
+
if (!entry.isIntersecting) return;
|
|
794
798
|
await router.runtime.prefetch(to);
|
|
795
799
|
observer.disconnect();
|
|
796
800
|
});
|
|
797
|
-
|
|
798
|
-
return () =>
|
|
799
|
-
if (element) observer.disconnect();
|
|
800
|
-
};
|
|
801
|
+
observer.observe(element);
|
|
802
|
+
return () => observer.disconnect();
|
|
801
803
|
}, [prefetch, to]);
|
|
802
|
-
|
|
804
|
+
useEffect(() => () => {
|
|
805
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
806
|
+
}, []);
|
|
807
|
+
const isActive = to === "/" ? pathname === "/" : exact ? pathname === to : pathname === to || pathname?.startsWith(`${to}/`);
|
|
803
808
|
const normalizedClassName = typeof className === "function" ? className({
|
|
804
809
|
isActive,
|
|
805
810
|
isPending
|
|
@@ -814,18 +819,21 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay, classNam
|
|
|
814
819
|
normalizedClassName
|
|
815
820
|
].filter(Boolean).join(" ");
|
|
816
821
|
const clickHandler = async (event) => {
|
|
822
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
817
823
|
event.preventDefault();
|
|
818
824
|
await beforeNavigate?.();
|
|
819
825
|
await navigate(to);
|
|
820
826
|
};
|
|
821
|
-
return
|
|
827
|
+
return as({
|
|
828
|
+
ref: elementRef,
|
|
822
829
|
href: to,
|
|
823
|
-
ref,
|
|
824
830
|
style: normalizedStyle,
|
|
825
831
|
className: resultClassName,
|
|
826
832
|
onClick: clickHandler,
|
|
827
833
|
onMouseEnter,
|
|
828
834
|
onMouseLeave,
|
|
835
|
+
isActive,
|
|
836
|
+
isPending,
|
|
829
837
|
children
|
|
830
838
|
});
|
|
831
839
|
};
|
|
@@ -984,8 +992,10 @@ var useSearchParams = () => {
|
|
|
984
992
|
currentParams.delete(param);
|
|
985
993
|
(Array.isArray(value) ? value : [value]).forEach((v) => currentParams.append(param, v));
|
|
986
994
|
navigateWithSearchParams(currentParams);
|
|
987
|
-
} else if (typeof param === "function")
|
|
988
|
-
|
|
995
|
+
} else if (typeof param === "function") {
|
|
996
|
+
const newParams = param(currentParams);
|
|
997
|
+
navigateWithSearchParams(newParams);
|
|
998
|
+
} else throw new Error("useSearchParams first argument must be either function or string");
|
|
989
999
|
}, [navigateWithSearchParams, search])
|
|
990
1000
|
};
|
|
991
1001
|
};
|