clear-react-router 1.9.5 → 1.9.6
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 +54 -13
- package/dist/components/Link.d.ts +6 -18
- package/dist/index.d.ts +1 -1
- package/dist/index.js +4 -3
- package/dist/types.d.ts +11 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -107,7 +107,7 @@ 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
|
|
110
|
+
| `as` | `(props: ElementProps<T>, state: { isActive: boolean; isPending: boolean }) => ReactElement` | renders `<a>` | Render function for using a custom element/component instead of the default <a>. Receives the props to spread onto your element (href, ref, event handlers, className, style, children) as the first argument, and `{ isActive, isPending }` as a separate second argument — kept separate so these values are never accidentally forwarded to the DOM |
|
|
111
111
|
| `exact` | `boolean` | `false` | When `false`, the link is also considered active if the current URL starts with `to` (useful for nested routes) |
|
|
112
112
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
|
|
113
113
|
| `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
|
|
@@ -134,27 +134,68 @@ Component for client-side navigation with prefetch support, active state detecti
|
|
|
134
134
|
| `'viewport'` | Prefetches when the link enters the viewport (using Intersection Observer) |
|
|
135
135
|
| `'none'` | No prefetching |
|
|
136
136
|
|
|
137
|
+
### Custom elements via `as`
|
|
138
|
+
|
|
139
|
+
When using `as` to render a custom component instead of the default `<a>`, your component **must spread all received props onto the underlying host element** — including `ref`. If any prop is dropped, the corresponding feature silently stops working (no error is thrown):
|
|
140
|
+
|
|
141
|
+
- Missing `ref` → `viewport` prefetch never triggers (the `IntersectionObserver` has nothing to observe).
|
|
142
|
+
- Missing `onClick` → navigation doesn't happen, the link just does nothing.
|
|
143
|
+
- Missing `onMouseEnter`/`onMouseLeave` → `hover` prefetch doesn't trigger.
|
|
144
|
+
- Missing `href` → the link isn't reachable via keyboard, screen readers, "open in new tab", etc.
|
|
145
|
+
|
|
146
|
+
```tsx
|
|
147
|
+
// ✅ correct — every prop is forwarded to the host element
|
|
148
|
+
const Button = ({ children, ...props }: ElementProps<HTMLButtonElement>) => (
|
|
149
|
+
<button {...props}>{children}</button>
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
// ❌ wrong — ref, event handlers, and href are silently dropped
|
|
153
|
+
const Button = ({ children }: { children: ReactNode }) => (
|
|
154
|
+
<button>{children}</button>
|
|
155
|
+
);
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
If you only want to add or override specific props (e.g. add a `variant`), spread the received props first, then apply your own on top:
|
|
159
|
+
|
|
160
|
+
```tsx
|
|
161
|
+
const Button = ({ children, ...props }: ElementProps<HTMLButtonElement>) => (
|
|
162
|
+
<button {...props} className={`btn ${props.className ?? ''}`}>
|
|
163
|
+
{children}
|
|
164
|
+
</button>
|
|
165
|
+
);
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
> **Note:** Because `as` is called as a plain function rather than rendered via JSX, avoid using React hooks (`useState`, `useEffect`, etc.) inside the function you pass to `as` — it isn't tracked by React as a separate component in the fiber tree. A function written for `as` (like `Button` above, which takes a second `state` argument) also isn't a valid standalone React component and shouldn't be rendered directly as `<Button />` elsewhere.
|
|
169
|
+
|
|
137
170
|
**Example:**
|
|
138
171
|
|
|
139
172
|
```tsx
|
|
140
|
-
import {
|
|
173
|
+
import { Link, type ElementProps } from 'clear-react-router';
|
|
174
|
+
|
|
175
|
+
const Button = (
|
|
176
|
+
{ children, ...rest }: ElementProps<HTMLButtonElement>,
|
|
177
|
+
{ isActive }: { isActive: boolean }
|
|
178
|
+
) => (
|
|
179
|
+
<button {...rest} style={{ background: isActive ? 'tomato' : 'green' }}>
|
|
180
|
+
{children}
|
|
181
|
+
</button>
|
|
182
|
+
);
|
|
183
|
+
|
|
184
|
+
<Link to="/about" as={Button}>To about page</Link>
|
|
185
|
+
|
|
186
|
+
For third-party components (MUI, Chakra, etc.), wrap them in an inline arrow function — most of them accept a
|
|
187
|
+
single `props` argument and forward it to the host element themselves:
|
|
141
188
|
|
|
142
|
-
// Render a custom element/component via `as`. The function receives ref, event handlers, isActive/isPending and must render them itself
|
|
143
189
|
import { Button } from '@mui/material';
|
|
144
190
|
|
|
145
191
|
<Link
|
|
146
|
-
to="/
|
|
147
|
-
as={({ isActive
|
|
148
|
-
<Button
|
|
149
|
-
{...props}
|
|
150
|
-
variant={isActive ? 'contained' : 'outlined'}
|
|
151
|
-
sx={{ opacity: isPending ? 0.5 : 1 }}
|
|
152
|
-
/>
|
|
153
|
-
)}
|
|
192
|
+
to="/about"
|
|
193
|
+
as={(props, { isActive }) => <Button {...props} variant={isActive ? 'contained' : 'text'} />}
|
|
154
194
|
>
|
|
155
|
-
|
|
195
|
+
To about page
|
|
156
196
|
</Link>
|
|
157
|
-
|
|
197
|
+
```
|
|
198
|
+
```tsx
|
|
158
199
|
// Global prefetch: hover with 100ms delay
|
|
159
200
|
<Router routes={routes} prefetch="hover" hoverPrefetchDelay={100} />
|
|
160
201
|
|
|
@@ -1,32 +1,20 @@
|
|
|
1
|
-
import { type CSSProperties, ReactNode,
|
|
2
|
-
import { RouterProps } from '../types';
|
|
3
|
-
type
|
|
1
|
+
import { type CSSProperties, ReactNode, ReactElement } from 'react';
|
|
2
|
+
import { ElementProps, RouterProps } from '../types';
|
|
3
|
+
type ElementState = {
|
|
4
4
|
isActive: boolean;
|
|
5
5
|
isPending: boolean;
|
|
6
6
|
};
|
|
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
7
|
type LinkProps<T extends HTMLElement = HTMLAnchorElement> = {
|
|
20
8
|
to: string;
|
|
21
9
|
children?: ReactNode;
|
|
22
|
-
as?: (props: ElementProps<T
|
|
10
|
+
as?: (props: ElementProps<T>, state: ElementState) => ReactElement;
|
|
23
11
|
prefetch?: RouterProps['prefetch'];
|
|
24
12
|
hoverPrefetchDelay?: number;
|
|
25
|
-
className?: string | ((arg:
|
|
13
|
+
className?: string | ((arg: ElementState) => string);
|
|
26
14
|
activeClassName?: string;
|
|
27
15
|
pendingClassName?: string;
|
|
28
16
|
beforeNavigate?(): Promise<void>;
|
|
29
|
-
style?: CSSProperties | ((arg:
|
|
17
|
+
style?: CSSProperties | ((arg: ElementState) => CSSProperties);
|
|
30
18
|
exact?: boolean;
|
|
31
19
|
};
|
|
32
20
|
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>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,4 +12,4 @@ export { useRouterContext } from './hooks/useRouterContext';
|
|
|
12
12
|
export { useSearchParams } from './hooks/useSearchParams';
|
|
13
13
|
export { useFormContext } from './hooks/useFormContext';
|
|
14
14
|
export { createRouter } from './utils/utils';
|
|
15
|
-
export type { RouteItem, BlockerState, Location, RouterProps } from './types';
|
|
15
|
+
export type { RouteItem, BlockerState, Location, RouterProps, ElementProps } from './types';
|
package/dist/index.js
CHANGED
|
@@ -757,7 +757,7 @@ var useLocation = () => {
|
|
|
757
757
|
};
|
|
758
758
|
//#endregion
|
|
759
759
|
//#region components/Link.tsx
|
|
760
|
-
var defaultAs = (
|
|
760
|
+
var defaultAs = (props) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", { ...props });
|
|
761
761
|
var Link = ({ children, to, as = defaultAs, prefetch: prefetchLink, hoverPrefetchDelay, className, style, beforeNavigate, exact = false, activeClassName = "active-link", pendingClassName = "pending-link" }) => {
|
|
762
762
|
const isPending = useIsRoutePending(to);
|
|
763
763
|
const { pathname } = useLocation();
|
|
@@ -832,9 +832,10 @@ var Link = ({ children, to, as = defaultAs, prefetch: prefetchLink, hoverPrefetc
|
|
|
832
832
|
onClick: clickHandler,
|
|
833
833
|
onMouseEnter,
|
|
834
834
|
onMouseLeave,
|
|
835
|
-
isActive,
|
|
836
|
-
isPending,
|
|
837
835
|
children
|
|
836
|
+
}, {
|
|
837
|
+
isActive,
|
|
838
|
+
isPending
|
|
838
839
|
});
|
|
839
840
|
};
|
|
840
841
|
//#endregion
|
package/dist/types.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { ComponentType, type CSSProperties, Dispatch, MouseEvent, ReactElement, ReactNode, Ref, SetStateAction } from 'react';
|
|
2
2
|
import { Store, useGlobalState } from './create';
|
|
3
3
|
import { Cell } from './cell';
|
|
4
4
|
export type LazyComponent = () => Promise<{
|
|
@@ -154,4 +154,14 @@ export type InvalidateResult = {
|
|
|
154
154
|
path: string;
|
|
155
155
|
data: unknown;
|
|
156
156
|
};
|
|
157
|
+
export type ElementProps<T extends HTMLElement = HTMLElement> = {
|
|
158
|
+
ref: Ref<T>;
|
|
159
|
+
href: string;
|
|
160
|
+
className?: string;
|
|
161
|
+
style?: CSSProperties;
|
|
162
|
+
onClick(event: MouseEvent): void;
|
|
163
|
+
onMouseEnter(event: MouseEvent): void;
|
|
164
|
+
onMouseLeave(event: MouseEvent): void;
|
|
165
|
+
children?: ReactNode;
|
|
166
|
+
};
|
|
157
167
|
export {};
|