clear-react-router 1.6.3 → 1.6.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 +44 -25
- package/dist/components/Router.d.ts +1 -1
- package/dist/components/RouterProvider.d.ts +2 -2
- package/dist/config/routerConfig.d.ts +1 -1
- package/dist/hooks/useHandleNavigation.d.ts +3 -3
- package/dist/hooks/useLoader.d.ts +2 -2
- package/dist/index.d.ts +1 -1
- package/dist/index.js +27 -26
- package/dist/types/global.d.ts +12 -7
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -20,7 +20,7 @@ It provides first-class support for:
|
|
|
20
20
|
- **Navigation Blocking** - Prevent accidental navigation with `useBlocker`
|
|
21
21
|
- **Smooth Animations** - Page transitions with fade effect (customizable duration)
|
|
22
22
|
- **Static Layout** — Keep navbar, footer, and other elements outside the router to avoid unnecessary re-renders
|
|
23
|
-
- **
|
|
23
|
+
- **Programmatic Redirects** - Redirect from beforeLoad hook
|
|
24
24
|
- **Cache invalidation** - Manual route invalidation
|
|
25
25
|
- **Prefetching** - Preload data on hover for instant navigation
|
|
26
26
|
- **Lazy Loading** - Code-split your routes with dynamic imports for optimal performance
|
|
@@ -55,11 +55,11 @@ The root component that provides routing context to the application. Place stati
|
|
|
55
55
|
|
|
56
56
|
| Prop | Type | Default | Description |
|
|
57
57
|
|------|------|---------|-------------|
|
|
58
|
-
| `
|
|
58
|
+
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
59
59
|
| `context` | `object` | `{}` | Initial context (user, theme, etc.) |
|
|
60
60
|
| `children` | `ReactNode` | required | App content (must include `<Router />`) |
|
|
61
61
|
|
|
62
|
-
```
|
|
62
|
+
```tsx
|
|
63
63
|
function App() {
|
|
64
64
|
return (
|
|
65
65
|
<RouterProvider routeList={routes}>
|
|
@@ -83,12 +83,13 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
|
83
83
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
84
84
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
85
85
|
| `preserveScroll` | `boolean \| undefined` | `true` | Save and restore scroll position when navigating between pages |
|
|
86
|
-
| `
|
|
86
|
+
| `showFallbackOnAnimation` | `boolean \| undefined` | `false` | Show `loaderFallback` even when `isAnimated` is `true` (instead of spinner) |
|
|
87
87
|
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `'hover'` | Default prefetch strategy for all `<Link>` components |
|
|
88
88
|
| `hoverPrefetchDelay` | `number` | `150` | Delay in milliseconds before prefetching on hover (only for `'hover'` strategy) |
|
|
89
|
+
| `errorBoundary` | `ComponentType<{ children: ReactNode }>` | `undefined` | Custom error boundary component for catching render errors in route components |
|
|
89
90
|
|
|
90
|
-
```
|
|
91
|
-
<RouterProvider
|
|
91
|
+
```tsx
|
|
92
|
+
<RouterProvider routes={routes}>
|
|
92
93
|
<Navbar />
|
|
93
94
|
<Router spinner={false} isAnimated /> {/* disable the spinner */}
|
|
94
95
|
</RouterProvider>
|
|
@@ -118,11 +119,11 @@ Component for client-side navigation with prefetch support.
|
|
|
118
119
|
|
|
119
120
|
**Example:**
|
|
120
121
|
|
|
121
|
-
```
|
|
122
|
+
```tsx
|
|
122
123
|
import { RouterProvider, Router, Link } from 'clear-react-router';
|
|
123
124
|
|
|
124
125
|
// Global prefetch: hover with 100ms delay
|
|
125
|
-
<RouterProvider
|
|
126
|
+
<RouterProvider routes={routes} prefetch="hover" hoverPrefetchDelay={100}>
|
|
126
127
|
<Router />
|
|
127
128
|
</RouterProvider>
|
|
128
129
|
|
|
@@ -144,7 +145,7 @@ Function provided to `beforeLoad` for programmatic redirection.
|
|
|
144
145
|
|
|
145
146
|
**Type:** `(arg: Location | string) => Promise<void>`
|
|
146
147
|
|
|
147
|
-
```
|
|
148
|
+
```tsx
|
|
148
149
|
import type { createRouter } from 'clear-react-router';
|
|
149
150
|
|
|
150
151
|
const routes = createRouter([
|
|
@@ -188,7 +189,9 @@ const routes = createRouter([
|
|
|
188
189
|
|
|
189
190
|
The `loader`, `beforeLoad`, and `afterLoad` hooks receive `params` (extracted from the URL) and `context` as arguments. This allows you to handle route-specific logic directly in the route configuration, keeping your components focused on rendering.
|
|
190
191
|
|
|
191
|
-
```
|
|
192
|
+
```tsx
|
|
193
|
+
import type { createRouter } from 'clear-react-router';
|
|
194
|
+
|
|
192
195
|
const routes = createRouter([
|
|
193
196
|
{
|
|
194
197
|
path: '/user/:userId',
|
|
@@ -216,13 +219,30 @@ const routes = createRouter([
|
|
|
216
219
|
]);
|
|
217
220
|
```
|
|
218
221
|
|
|
222
|
+
### Error Boundaries
|
|
223
|
+
|
|
224
|
+
You can provide a custom error boundary to catch rendering errors in route components. This is useful for preventing the entire app from crashing when a specific route fails to render.
|
|
225
|
+
|
|
226
|
+
```tsx
|
|
227
|
+
import { RouterProvider, Router } from 'clear-react-router';
|
|
228
|
+
import { routes } from './routes';
|
|
229
|
+
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
230
|
+
|
|
231
|
+
const App = () => (
|
|
232
|
+
<RouterProvider routes={routes}>
|
|
233
|
+
<Router errorBoundary={ErrorBoundary} />
|
|
234
|
+
</RouterProvider>
|
|
235
|
+
);
|
|
236
|
+
```
|
|
237
|
+
**Note:** The `errorBoundary` prop only catches render-time errors in route components. It does not catch errors in `loader` or `beforeLoad` — those are handled by the router's `errorElement` mechanism.
|
|
238
|
+
|
|
219
239
|
## Hooks
|
|
220
240
|
|
|
221
241
|
### `useNavigate()`
|
|
222
242
|
|
|
223
243
|
Returns function to navigate programmatically. Accepts a string (pathname), an object with `pathname`, `search`, and `state`, or `-1` to go back.
|
|
224
244
|
|
|
225
|
-
```
|
|
245
|
+
```tsx
|
|
226
246
|
const navigate = useNavigate();
|
|
227
247
|
|
|
228
248
|
navigate('/about'); // string
|
|
@@ -232,7 +252,7 @@ navigate(-1); // go back
|
|
|
232
252
|
|
|
233
253
|
**Note:** Navigation state can be accessed via `useLocation()`:
|
|
234
254
|
|
|
235
|
-
```
|
|
255
|
+
```tsx
|
|
236
256
|
const navigate = useNavigate();
|
|
237
257
|
navigate({ pathname: '/profile', state: { userId: 123 } });
|
|
238
258
|
|
|
@@ -245,7 +265,7 @@ console.log(state); // { userId: 123 }
|
|
|
245
265
|
|
|
246
266
|
Returns route parameters object.
|
|
247
267
|
|
|
248
|
-
```
|
|
268
|
+
```tsx
|
|
249
269
|
const params = useParams<{ userId: string }>();
|
|
250
270
|
// URL: /user/123 → params.userId === '123'
|
|
251
271
|
```
|
|
@@ -253,7 +273,7 @@ const params = useParams<{ userId: string }>();
|
|
|
253
273
|
### `useLocation()`
|
|
254
274
|
|
|
255
275
|
Returns current location `{ pathname, search, state }`.
|
|
256
|
-
```
|
|
276
|
+
```tsx
|
|
257
277
|
const { pathname, search, state } = useLocation();
|
|
258
278
|
```
|
|
259
279
|
|
|
@@ -269,7 +289,7 @@ Returns the cached data loaded by the current route's `loader`, along with any e
|
|
|
269
289
|
| `loaderError` | `Error \| null` | Error from the `loader` (if any) |
|
|
270
290
|
| `beforeLoadError` | `Error \| null` | Error from the `beforeLoad` hook (if any) |
|
|
271
291
|
|
|
272
|
-
```
|
|
292
|
+
```tsx
|
|
273
293
|
const UserProfile = () => {
|
|
274
294
|
const { data, loaderError, beforeLoadError } = useLoaderState<User>();
|
|
275
295
|
```
|
|
@@ -285,7 +305,7 @@ const UserProfile = () => {
|
|
|
285
305
|
}
|
|
286
306
|
```
|
|
287
307
|
|
|
288
|
-
### `useInvalidate`
|
|
308
|
+
### `useInvalidate()`
|
|
289
309
|
|
|
290
310
|
Returns a function that marks route data as stale and re-runs the route lifecycle.
|
|
291
311
|
|
|
@@ -362,7 +382,7 @@ Blocks navigation when callback returns `true`.
|
|
|
362
382
|
| `process()` | `() => void` | Confirm navigation and proceed |
|
|
363
383
|
| `reset()` | `() => void` | Cancel navigation |
|
|
364
384
|
|
|
365
|
-
```
|
|
385
|
+
```tsx
|
|
366
386
|
const { state, process, reset } = useBlocker(() => hasUnsavedChanges);
|
|
367
387
|
|
|
368
388
|
useEffect(() => {
|
|
@@ -389,7 +409,7 @@ Executes a callback when the page is about to be closed or reloaded. Perfect for
|
|
|
389
409
|
|
|
390
410
|
**Note:** This hook does not show a browser confirmation dialog. It silently executes the callback, allowing you to save user data in the background before the page closes.
|
|
391
411
|
|
|
392
|
-
```
|
|
412
|
+
```tsx
|
|
393
413
|
const [text, setText] = useState('');
|
|
394
414
|
const onSave = useCallback(() => {
|
|
395
415
|
localStorage.setItem('draft', text);
|
|
@@ -404,7 +424,7 @@ useBeforeUnload(text ? onSave : undefined);
|
|
|
404
424
|
|
|
405
425
|
A flexible hook for working with typed query parameters. You provide an adapter object with `parse` and `serialize` functions, and it returns the parsed value and a setter.
|
|
406
426
|
|
|
407
|
-
```
|
|
427
|
+
```tsx
|
|
408
428
|
import { useQueryParam, adapter } from 'clear-react-router';
|
|
409
429
|
|
|
410
430
|
const ProductPage = () => {
|
|
@@ -468,7 +488,7 @@ type Adapter<T> = {
|
|
|
468
488
|
### Using Zod Schemas
|
|
469
489
|
`useQueryParam` works seamlessly with Zod for complex validation:
|
|
470
490
|
|
|
471
|
-
```
|
|
491
|
+
```tsx
|
|
472
492
|
import { z } from 'zod';
|
|
473
493
|
import { useQueryParam, adapter } from 'clear-react-router';
|
|
474
494
|
|
|
@@ -500,7 +520,7 @@ function ProductFilter() {
|
|
|
500
520
|
### Custom Adapters
|
|
501
521
|
You can write your own adapter for any format:
|
|
502
522
|
|
|
503
|
-
```
|
|
523
|
+
```tsx
|
|
504
524
|
// Custom adapter for comma-separated values
|
|
505
525
|
const csvAdapter = {
|
|
506
526
|
parse: (params: string[]): string[] => {
|
|
@@ -519,7 +539,7 @@ const TagsFilter() {
|
|
|
519
539
|
### `useRouterContext()`
|
|
520
540
|
|
|
521
541
|
Returns the router context object and a function to update it. Useful for accessing or modifying global state (like user authentication, theme, etc.) from anywhere in your app.
|
|
522
|
-
```
|
|
542
|
+
```tsx
|
|
523
543
|
const { setContext, context } = useRouterContext();
|
|
524
544
|
const loginHandler = () => setContext({ ...context, user: { name: 'John' } });
|
|
525
545
|
```
|
|
@@ -528,7 +548,7 @@ const loginHandler = () => setContext({ ...context, user: { name: 'John' } });
|
|
|
528
548
|
|
|
529
549
|
Returns an object for working with URL query parameters. Supports reading and setting both single values and arrays.
|
|
530
550
|
|
|
531
|
-
```
|
|
551
|
+
```tsx
|
|
532
552
|
import { useSearchParams } from 'clear-react-router';
|
|
533
553
|
|
|
534
554
|
function ProductFilter() {
|
|
@@ -566,7 +586,6 @@ function ProductFilter() {
|
|
|
566
586
|
|
|
567
587
|
- **Array support** — `getSearchParams` returns `string[]` when multiple values exist for the same key
|
|
568
588
|
- **Functional updates** — Update parameters based on previous state without losing other params
|
|
569
|
-
- **Type-safe** — Proper TypeScript support with overloads
|
|
570
589
|
- **Stable reference** — `setSearchParams` reference is stable and safe to use in `useEffect`
|
|
571
590
|
|
|
572
591
|
> **Note:** `getSearchParams` returns `string` for single values, `string[]` for multiple values, and `''` if the key is not found.
|
|
@@ -586,7 +605,7 @@ Returns an array of pathnames representing the user's actual navigation history.
|
|
|
586
605
|
## Lazy Loading
|
|
587
606
|
|
|
588
607
|
Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
|
|
589
|
-
```
|
|
608
|
+
```tsx
|
|
590
609
|
{
|
|
591
610
|
path: '/heavy-page',
|
|
592
611
|
element: () => import('./pages/HeavyComponent'),
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types/global';
|
|
2
|
-
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll,
|
|
2
|
+
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -2,8 +2,8 @@ import { ReactNode } from 'react';
|
|
|
2
2
|
import { RouteItem } from '../types/global';
|
|
3
3
|
type RouteProviderProps = {
|
|
4
4
|
children: ReactNode;
|
|
5
|
-
|
|
5
|
+
routes: RouteItem[];
|
|
6
6
|
context?: Record<string, unknown>;
|
|
7
7
|
};
|
|
8
|
-
export declare const RouterProvider: ({ children,
|
|
8
|
+
export declare const RouterProvider: ({ children, routes, context: initialContext }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
9
|
export {};
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouterProps } from '../types/global';
|
|
2
2
|
declare class RouterConfig {
|
|
3
3
|
isAnimated: boolean;
|
|
4
|
-
|
|
4
|
+
showFallbackOnAnimation: boolean;
|
|
5
5
|
prefetch: RouterProps['prefetch'];
|
|
6
6
|
hoverPrefetchDelay: number;
|
|
7
7
|
configure(config: Partial<RouterConfig>): void;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type Dispatch, RefObject, type SetStateAction } from 'react';
|
|
2
2
|
import { BlockerState, LoaderState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
3
3
|
type UseHandleNavigation = {
|
|
4
|
-
|
|
4
|
+
routes: RouteItem[];
|
|
5
5
|
context: Record<string, unknown>;
|
|
6
6
|
revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
|
|
7
7
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
@@ -12,13 +12,13 @@ type UseHandleNavigation = {
|
|
|
12
12
|
loaderStateRef: RefObject<LoaderState>;
|
|
13
13
|
clearTimestamp(path: string): void;
|
|
14
14
|
};
|
|
15
|
-
export declare const useHandleNavigation: ({
|
|
15
|
+
export declare const useHandleNavigation: ({ routes, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, clearTimestamp, }: UseHandleNavigation) => {
|
|
16
16
|
blockerState: BlockerState;
|
|
17
17
|
updateLocation: (nextLocation: Location) => Promise<void>;
|
|
18
18
|
updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
|
|
19
19
|
routeItemData: RouteItemData;
|
|
20
20
|
restoreScroll: () => void;
|
|
21
|
-
currentLoaderFallback: import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement) | undefined;
|
|
21
|
+
currentLoaderFallback: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined;
|
|
22
22
|
isLoading: boolean;
|
|
23
23
|
loaderState: LoaderState;
|
|
24
24
|
invalidate: (pathname?: string) => Promise<void>;
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
2
|
import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
|
|
3
3
|
type UseLoaderParams = {
|
|
4
|
-
|
|
4
|
+
routes: RouteItem[];
|
|
5
5
|
context: Record<string, unknown>;
|
|
6
6
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
7
7
|
};
|
|
8
|
-
export declare const useLoader: ({
|
|
8
|
+
export declare const useLoader: ({ routes, context, setContext }: UseLoaderParams) => {
|
|
9
9
|
prefetchLoader: (pathname: string) => Promise<void>;
|
|
10
10
|
revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown>;
|
|
11
11
|
isCacheItemFresh: ({ routeItem, pathname }: {
|
package/dist/index.d.ts
CHANGED
|
@@ -14,4 +14,4 @@ export { useSearchParams } from './hooks/useSearchParams';
|
|
|
14
14
|
export { useHistoricalTrail } from './hooks/useHistoricalTrail';
|
|
15
15
|
export { adapter } from './utils/adapter';
|
|
16
16
|
export { createRouter } from './utils/utils';
|
|
17
|
-
export type { RouteItem, BlockerState, Location, AdapterType } from './types/global';
|
|
17
|
+
export type { RouteItem, BlockerState, Location, AdapterType, RouterProps } from './types/global';
|
package/dist/index.js
CHANGED
|
@@ -125,7 +125,7 @@ function _defineProperty(e, r, t) {
|
|
|
125
125
|
var RouterConfig = class {
|
|
126
126
|
constructor() {
|
|
127
127
|
_defineProperty(this, "isAnimated", false);
|
|
128
|
-
_defineProperty(this, "
|
|
128
|
+
_defineProperty(this, "showFallbackOnAnimation", false);
|
|
129
129
|
_defineProperty(this, "prefetch", "hover");
|
|
130
130
|
_defineProperty(this, "hoverPrefetchDelay", 150);
|
|
131
131
|
}
|
|
@@ -188,8 +188,8 @@ var comparePaths = (el, pathname) => {
|
|
|
188
188
|
//#endregion
|
|
189
189
|
//#region hooks/useHandleNavigation.ts
|
|
190
190
|
var ALL_LOCATIONS = "*";
|
|
191
|
-
var useHandleNavigation = ({
|
|
192
|
-
const { isAnimated,
|
|
191
|
+
var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, clearTimestamp }) => {
|
|
192
|
+
const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
|
|
193
193
|
const [isLoading, setIsLoading] = useState(false);
|
|
194
194
|
const [blockedRoute, setBlockedRoute] = useState({
|
|
195
195
|
from: "",
|
|
@@ -240,7 +240,7 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
240
240
|
}, [navigation, isAnimated]);
|
|
241
241
|
const invalidate = useCallback(async (pathname = routeItemData.location.pathname) => {
|
|
242
242
|
if (typeof pathname !== "string") return;
|
|
243
|
-
const routeItem =
|
|
243
|
+
const routeItem = routes.find((el) => comparePaths(el, pathname));
|
|
244
244
|
const resultParams = getParamsObject({
|
|
245
245
|
params: routeItem?.params,
|
|
246
246
|
pathname
|
|
@@ -274,14 +274,14 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
274
274
|
loaderStateRef,
|
|
275
275
|
revalidateCache,
|
|
276
276
|
routeItemData.location.pathname,
|
|
277
|
-
|
|
277
|
+
routes,
|
|
278
278
|
setContext
|
|
279
279
|
]);
|
|
280
280
|
const navigationHandler = useCallback(async (nextLocation) => {
|
|
281
281
|
navigationSeq.current = navigationSeq.current + 1;
|
|
282
282
|
const seq = navigationSeq.current;
|
|
283
283
|
loaderStateRef.current = emptyLoaderState;
|
|
284
|
-
const nextItem =
|
|
284
|
+
const nextItem = routes.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, nextLocation.pathname));
|
|
285
285
|
const params = getParamsObject({
|
|
286
286
|
params: nextItem?.params,
|
|
287
287
|
pathname: nextLocation.pathname
|
|
@@ -337,7 +337,7 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
337
337
|
}, [
|
|
338
338
|
context,
|
|
339
339
|
revalidateCache,
|
|
340
|
-
|
|
340
|
+
routes,
|
|
341
341
|
transitionedNavigation,
|
|
342
342
|
setContext,
|
|
343
343
|
isCacheItemFresh,
|
|
@@ -407,7 +407,7 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
407
407
|
};
|
|
408
408
|
//#endregion
|
|
409
409
|
//#region hooks/useLoader.ts
|
|
410
|
-
var useLoader = ({
|
|
410
|
+
var useLoader = ({ routes, context, setContext }) => {
|
|
411
411
|
const timestampMapRef = useRef(/* @__PURE__ */ new Map());
|
|
412
412
|
const loaderMapRef = useRef({});
|
|
413
413
|
const loaderStateRef = useRef(emptyLoaderState);
|
|
@@ -467,12 +467,12 @@ var useLoader = ({ routeList, context, setContext }) => {
|
|
|
467
467
|
]);
|
|
468
468
|
return {
|
|
469
469
|
prefetchLoader: useCallback(async (pathname) => {
|
|
470
|
-
const item =
|
|
470
|
+
const item = routes.find((el) => comparePaths(el, pathname));
|
|
471
471
|
if (item) await revalidateCache({
|
|
472
472
|
routeItem: item,
|
|
473
473
|
pathname
|
|
474
474
|
});
|
|
475
|
-
}, [revalidateCache,
|
|
475
|
+
}, [revalidateCache, routes]),
|
|
476
476
|
revalidateCache,
|
|
477
477
|
isCacheItemFresh,
|
|
478
478
|
loaderStateRef,
|
|
@@ -483,15 +483,15 @@ var useLoader = ({ routeList, context, setContext }) => {
|
|
|
483
483
|
};
|
|
484
484
|
//#endregion
|
|
485
485
|
//#region components/RouterProvider.tsx
|
|
486
|
-
var RouterProvider = ({ children,
|
|
486
|
+
var RouterProvider = ({ children, routes, context: initialContext = {} }) => {
|
|
487
487
|
const [context, setContext] = useState(initialContext);
|
|
488
488
|
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, clearTimestamp } = useLoader({
|
|
489
|
-
|
|
489
|
+
routes,
|
|
490
490
|
context,
|
|
491
491
|
setContext
|
|
492
492
|
});
|
|
493
493
|
const { blockerState, updateLocation, updateBlockedRoute, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, invalidate } = useHandleNavigation({
|
|
494
|
-
|
|
494
|
+
routes,
|
|
495
495
|
context,
|
|
496
496
|
setContext,
|
|
497
497
|
revalidateCache,
|
|
@@ -590,6 +590,11 @@ var usePreserveScroll = (preserveScroll) => {
|
|
|
590
590
|
]);
|
|
591
591
|
};
|
|
592
592
|
//#endregion
|
|
593
|
+
//#region hooks/useSetRouterConfig.ts
|
|
594
|
+
var useSetRouterConfig = (routerProps) => {
|
|
595
|
+
useEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
596
|
+
};
|
|
597
|
+
//#endregion
|
|
593
598
|
//#region components/Spinner.tsx
|
|
594
599
|
var Spinner = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cr-spinner" });
|
|
595
600
|
//#endregion
|
|
@@ -599,32 +604,28 @@ var renderElement = (Component) => {
|
|
|
599
604
|
return typeof Component === "function" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, {}) : Component;
|
|
600
605
|
};
|
|
601
606
|
//#endregion
|
|
602
|
-
//#region hooks/useSetRouterConfig.ts
|
|
603
|
-
var useSetRouterConfig = (routerProps) => {
|
|
604
|
-
useEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
605
|
-
};
|
|
606
|
-
//#endregion
|
|
607
607
|
//#region components/Router.tsx
|
|
608
|
-
var
|
|
609
|
-
|
|
608
|
+
var EmptyBoundary = ({ children }) => children;
|
|
609
|
+
var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
|
|
610
|
+
const { routeItemData: { routeItem, location }, loaderState, currentLoaderFallback, isLoading } = useNavigationState();
|
|
610
611
|
usePreserveScroll(preserveScroll);
|
|
611
612
|
useApplyCustomAnimation(animationDuration);
|
|
612
613
|
useSetRouterConfig({
|
|
613
614
|
isAnimated,
|
|
614
|
-
|
|
615
|
+
showFallbackOnAnimation,
|
|
615
616
|
prefetch,
|
|
616
617
|
hoverPrefetchDelay
|
|
617
618
|
});
|
|
618
619
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
619
620
|
const showSpinner = spinner && isAnimated && isLoading;
|
|
620
621
|
const loadingContent = !showErrorElement && isLoading;
|
|
621
|
-
if ((
|
|
622
|
-
if (!
|
|
622
|
+
if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback);
|
|
623
|
+
if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
|
|
623
624
|
if (!routeItem) return null;
|
|
624
625
|
if (showErrorElement) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [renderElement(routeItem.errorElement), showSpinner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})] });
|
|
625
626
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
626
627
|
style: { viewTransitionName: "page" },
|
|
627
|
-
children: [renderElement(routeItem.element)
|
|
628
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, { children: renderElement(routeItem.element) }, location.pathname), showSpinner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
|
|
628
629
|
});
|
|
629
630
|
};
|
|
630
631
|
//#endregion
|
|
@@ -710,11 +711,11 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
710
711
|
//#endregion
|
|
711
712
|
//#region hooks/useParams.ts
|
|
712
713
|
var useParams = () => {
|
|
713
|
-
const { routeItemData: { routeItem } } = useNavigationState();
|
|
714
|
+
const { routeItemData: { routeItem, location: { pathname } } } = useNavigationState();
|
|
714
715
|
if (!routeItem) return void 0;
|
|
715
716
|
return getParamsObject({
|
|
716
717
|
params: routeItem?.params,
|
|
717
|
-
pathname
|
|
718
|
+
pathname
|
|
718
719
|
});
|
|
719
720
|
};
|
|
720
721
|
//#endregion
|
package/dist/types/global.d.ts
CHANGED
|
@@ -1,18 +1,19 @@
|
|
|
1
|
-
import type { ComponentType, Dispatch, ReactElement, SetStateAction } from 'react';
|
|
1
|
+
import type { ComponentType, Dispatch, ReactElement, ReactNode, SetStateAction } from 'react';
|
|
2
2
|
export type LazyComponent = () => Promise<{
|
|
3
3
|
default: ComponentType<unknown>;
|
|
4
4
|
}>;
|
|
5
|
+
type Element = (() => ReactElement) | ReactElement;
|
|
5
6
|
export type ClientRouteItem = {
|
|
6
7
|
path: string;
|
|
7
|
-
element:
|
|
8
|
+
element: Element | LazyComponent;
|
|
8
9
|
loader?(arg: {
|
|
9
10
|
params: Record<string, string>;
|
|
10
11
|
context: Record<string, unknown>;
|
|
11
12
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
12
13
|
}): Promise<unknown>;
|
|
13
|
-
loaderFallback?:
|
|
14
|
-
errorElement?:
|
|
15
|
-
fallback?:
|
|
14
|
+
loaderFallback?: Element;
|
|
15
|
+
errorElement?: Element;
|
|
16
|
+
fallback?: Element;
|
|
16
17
|
children?: ClientRouteItem[];
|
|
17
18
|
staleTime?: number;
|
|
18
19
|
beforeLoad?: (arg: {
|
|
@@ -28,7 +29,7 @@ export type ClientRouteItem = {
|
|
|
28
29
|
}) => Promise<void>;
|
|
29
30
|
};
|
|
30
31
|
export type RouteItem = ClientRouteItem & {
|
|
31
|
-
element:
|
|
32
|
+
element: Element;
|
|
32
33
|
params?: {
|
|
33
34
|
key: string;
|
|
34
35
|
value: string;
|
|
@@ -67,7 +68,11 @@ export type RouterProps = {
|
|
|
67
68
|
animationDuration?: number;
|
|
68
69
|
spinner?: boolean;
|
|
69
70
|
preserveScroll?: boolean;
|
|
70
|
-
|
|
71
|
+
showFallbackOnAnimation?: boolean;
|
|
71
72
|
prefetch?: 'hover' | 'render' | 'viewport' | 'none';
|
|
72
73
|
hoverPrefetchDelay?: number;
|
|
74
|
+
errorBoundary?: ComponentType<{
|
|
75
|
+
children: ReactNode;
|
|
76
|
+
}>;
|
|
73
77
|
};
|
|
78
|
+
export {};
|