clear-react-router 1.6.4 → 1.6.7
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 +135 -22
- package/dist/components/Form.d.ts +9 -0
- package/dist/components/Router.d.ts +1 -1
- package/dist/components/RouterProvider.d.ts +1 -2
- package/dist/context/FormContext.d.ts +4 -0
- package/dist/context/RouterProviderContext.d.ts +2 -17
- package/dist/hooks/useAction.d.ts +1 -0
- package/dist/hooks/useBlocker.d.ts +1 -1
- package/dist/hooks/useFormContext.d.ts +1 -0
- package/dist/hooks/useInvalidate.d.ts +1 -1
- package/dist/hooks/useLoader.d.ts +2 -9
- package/dist/hooks/useNavigate.d.ts +1 -1
- package/dist/hooks/useNavigation.d.ts +13 -0
- package/dist/hooks/useRouterContext.d.ts +1 -1
- package/dist/hooks/useServiceContext.d.ts +0 -2
- package/dist/hooks/useSetInitialContext.d.ts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +227 -203
- package/dist/provider/FormProvider.d.ts +3 -0
- package/dist/provider/Provider.d.ts +3 -3
- package/dist/state/createState.d.ts +3 -0
- package/dist/state/state.d.ts +20 -0
- package/dist/types/global.d.ts +7 -4
- package/package.json +1 -1
- package/dist/hooks/useHandleNavigation.d.ts +0 -26
package/README.md
CHANGED
|
@@ -47,7 +47,8 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
|
|
|
47
47
|
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback (for loader) |
|
|
48
48
|
| `errorElement` | `ReactElement \| () => ReactElement` | Error fallback |
|
|
49
49
|
| `staleTime` | `number` | Time in ms before cached data is considered stale and re-fetched in the background. If not provided, data never expires (cached forever) |
|
|
50
|
-
| `
|
|
50
|
+
| `actions` | `({ params, context, invalidate, setContext }) => Record<string, (formData: FormData) => unknown \| Promise<unknown>>` | Defines route actions for data mutations. Actions receive `FormData`, can update context via `setContext`, and can refresh loader data using the router-provided `invalidate`. |
|
|
51
|
+
|
|
51
52
|
|
|
52
53
|
### `RouterProvider`
|
|
53
54
|
|
|
@@ -56,10 +57,9 @@ The root component that provides routing context to the application. Place stati
|
|
|
56
57
|
| Prop | Type | Default | Description |
|
|
57
58
|
|------|------|---------|-------------|
|
|
58
59
|
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
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}>
|
|
@@ -86,9 +86,10 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
|
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
|
+
| `context` | `object` | `{}` | Initial context (user, theme, etc.) |
|
|
89
90
|
| `errorBoundary` | `ComponentType<{ children: ReactNode }>` | `undefined` | Custom error boundary component for catching render errors in route components |
|
|
90
91
|
|
|
91
|
-
```
|
|
92
|
+
```tsx
|
|
92
93
|
<RouterProvider routes={routes}>
|
|
93
94
|
<Navbar />
|
|
94
95
|
<Router spinner={false} isAnimated /> {/* disable the spinner */}
|
|
@@ -119,7 +120,7 @@ Component for client-side navigation with prefetch support.
|
|
|
119
120
|
|
|
120
121
|
**Example:**
|
|
121
122
|
|
|
122
|
-
```
|
|
123
|
+
```tsx
|
|
123
124
|
import { RouterProvider, Router, Link } from 'clear-react-router';
|
|
124
125
|
|
|
125
126
|
// Global prefetch: hover with 100ms delay
|
|
@@ -145,7 +146,7 @@ Function provided to `beforeLoad` for programmatic redirection.
|
|
|
145
146
|
|
|
146
147
|
**Type:** `(arg: Location | string) => Promise<void>`
|
|
147
148
|
|
|
148
|
-
```
|
|
149
|
+
```tsx
|
|
149
150
|
import type { createRouter } from 'clear-react-router';
|
|
150
151
|
|
|
151
152
|
const routes = createRouter([
|
|
@@ -189,7 +190,9 @@ const routes = createRouter([
|
|
|
189
190
|
|
|
190
191
|
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.
|
|
191
192
|
|
|
192
|
-
```
|
|
193
|
+
```tsx
|
|
194
|
+
import type { createRouter } from 'clear-react-router';
|
|
195
|
+
|
|
193
196
|
const routes = createRouter([
|
|
194
197
|
{
|
|
195
198
|
path: '/user/:userId',
|
|
@@ -217,6 +220,114 @@ const routes = createRouter([
|
|
|
217
220
|
]);
|
|
218
221
|
```
|
|
219
222
|
|
|
223
|
+
## Route Actions
|
|
224
|
+
|
|
225
|
+
Defines route-specific actions for handling data mutations such as creating, updating, or deleting resources.
|
|
226
|
+
|
|
227
|
+
Actions are available through the `Form` component and the `useAction` hook. After a successful action, the current route is automatically invalidated, causing both `beforeLoad` and `loader` to run again in the background.
|
|
228
|
+
|
|
229
|
+
```tsx
|
|
230
|
+
actions?: ({ context, params, invalidate, setContext }) => ({
|
|
231
|
+
save: async (formData) => {
|
|
232
|
+
await api.updatePost(params.id, formData);
|
|
233
|
+
},
|
|
234
|
+
|
|
235
|
+
remove: async () => {
|
|
236
|
+
await api.deletePost(params.id);
|
|
237
|
+
},
|
|
238
|
+
})
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
#### Arguments
|
|
242
|
+
|
|
243
|
+
| Property | Type | Description |
|
|
244
|
+
| ------------ | --------------------------------------------------- | -------------------------------------------------------------------------------------------- |
|
|
245
|
+
| `context` | `Record<string, unknown>` | Current router context. |
|
|
246
|
+
| `params` | `Record<string, string>` | Route parameters. |
|
|
247
|
+
| `invalidate` | `(path?: string) => Promise<void>` | Invalidates the current route or a specific route, re-running its `beforeLoad` and `loader`. |
|
|
248
|
+
| `setContext` | `Dispatch<SetStateAction<Record<string, unknown>>>` | Updates the router context. |
|
|
249
|
+
|
|
250
|
+
#### Returns
|
|
251
|
+
|
|
252
|
+
A record where each key is an action name and each value is a function accepting a `FormData` instance.
|
|
253
|
+
|
|
254
|
+
These action names are referenced by both `Form` and `useAction`.
|
|
255
|
+
|
|
256
|
+
```tsx
|
|
257
|
+
<Form action="save" />
|
|
258
|
+
|
|
259
|
+
const save = useAction('save');
|
|
260
|
+
```
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
Actions can be executed declaratively with `<Form />` or imperatively with `useAction()`.
|
|
264
|
+
|
|
265
|
+
## Form
|
|
266
|
+
|
|
267
|
+
`Form` automatically creates a `FormData` object, executes the specified route action, invalidates the current route, and optionally resets the form.
|
|
268
|
+
|
|
269
|
+
`isSubmitting` value available inside the `Form` component from the `useFormContext` hook
|
|
270
|
+
|
|
271
|
+
```tsx
|
|
272
|
+
import { Form, useFormContext } from '../clear-router';
|
|
273
|
+
|
|
274
|
+
const SubmitButton = () => {
|
|
275
|
+
const {isSubmitting} = useFormContext()
|
|
276
|
+
return <button disabled={isSubmitting} type='submit'>Save</button>
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
<Form action="save" onSuccess={() => console.log('Saved')} onError={console.error}>
|
|
280
|
+
<input name="title" />
|
|
281
|
+
<SubmitButton />
|
|
282
|
+
</Form>
|
|
283
|
+
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
### Props
|
|
287
|
+
|
|
288
|
+
| Prop | Type | Description |
|
|
289
|
+
| ----------- | --------------------------- | -------------------------------------------------------------------------------- |
|
|
290
|
+
| `action` | `string` | Name of the route action to execute. |
|
|
291
|
+
| `onSuccess` | `(result: unknown) => void` | Called when the action completes successfully. Receives the action return value. |
|
|
292
|
+
| `onError` | `(error: unknown) => void` | Called when the action throws. |
|
|
293
|
+
| `autoReset` | `boolean` | Automatically resets the form after a successful submission. Default: `true`. |
|
|
294
|
+
|
|
295
|
+
During submission, `Form` exposes the current submission state through `useFormContext()`.
|
|
296
|
+
|
|
297
|
+
After a successful action:
|
|
298
|
+
|
|
299
|
+
* the current route is invalidated;
|
|
300
|
+
* `beforeLoad` is executed again;
|
|
301
|
+
* `loader` is executed again;
|
|
302
|
+
* fresh loader data becomes available.
|
|
303
|
+
|
|
304
|
+
## useAction
|
|
305
|
+
|
|
306
|
+
`useAction` provides direct access to a route action without rendering a `<Form />`.
|
|
307
|
+
|
|
308
|
+
```tsx
|
|
309
|
+
const save = useAction('save');
|
|
310
|
+
|
|
311
|
+
const handleClick = async () => {
|
|
312
|
+
const data = new FormData();
|
|
313
|
+
|
|
314
|
+
data.append('title', 'Hello');
|
|
315
|
+
|
|
316
|
+
await save(data);
|
|
317
|
+
};
|
|
318
|
+
```
|
|
319
|
+
|
|
320
|
+
```tsx
|
|
321
|
+
<button onClick={handleClick}>
|
|
322
|
+
Save
|
|
323
|
+
</button>
|
|
324
|
+
```
|
|
325
|
+
|
|
326
|
+
`useAction` automatically invalidates the current route after a successful action, causing both `beforeLoad` and `loader` to run again in the background.
|
|
327
|
+
|
|
328
|
+
This hook is useful when the mutation is triggered programmatically, such as from dialogs, context menus, drag-and-drop interactions, keyboard shortcuts, or custom UI components.
|
|
329
|
+
|
|
330
|
+
|
|
220
331
|
### Error Boundaries
|
|
221
332
|
|
|
222
333
|
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.
|
|
@@ -238,19 +349,21 @@ const App = () => (
|
|
|
238
349
|
|
|
239
350
|
### `useNavigate()`
|
|
240
351
|
|
|
241
|
-
Returns function to navigate programmatically. Accepts a string (pathname), an object
|
|
352
|
+
Returns function to navigate programmatically. Accepts a string (pathname), an object of type Location, or `-1` to go back.
|
|
353
|
+
|
|
354
|
+
```tsx
|
|
355
|
+
type Location = { pathname: string; search?: string; state?: unknown }
|
|
242
356
|
|
|
243
|
-
```
|
|
244
357
|
const navigate = useNavigate();
|
|
245
358
|
|
|
246
359
|
navigate('/about'); // string
|
|
247
|
-
navigate({ pathname: '/user/123', state: { from: 'home' } }); //
|
|
360
|
+
navigate({ pathname: '/user/123', state: { from: 'home' } }); // Location
|
|
248
361
|
navigate(-1); // go back
|
|
249
362
|
```
|
|
250
363
|
|
|
251
364
|
**Note:** Navigation state can be accessed via `useLocation()`:
|
|
252
365
|
|
|
253
|
-
```
|
|
366
|
+
```tsx
|
|
254
367
|
const navigate = useNavigate();
|
|
255
368
|
navigate({ pathname: '/profile', state: { userId: 123 } });
|
|
256
369
|
|
|
@@ -263,7 +376,7 @@ console.log(state); // { userId: 123 }
|
|
|
263
376
|
|
|
264
377
|
Returns route parameters object.
|
|
265
378
|
|
|
266
|
-
```
|
|
379
|
+
```tsx
|
|
267
380
|
const params = useParams<{ userId: string }>();
|
|
268
381
|
// URL: /user/123 → params.userId === '123'
|
|
269
382
|
```
|
|
@@ -271,7 +384,7 @@ const params = useParams<{ userId: string }>();
|
|
|
271
384
|
### `useLocation()`
|
|
272
385
|
|
|
273
386
|
Returns current location `{ pathname, search, state }`.
|
|
274
|
-
```
|
|
387
|
+
```tsx
|
|
275
388
|
const { pathname, search, state } = useLocation();
|
|
276
389
|
```
|
|
277
390
|
|
|
@@ -287,7 +400,7 @@ Returns the cached data loaded by the current route's `loader`, along with any e
|
|
|
287
400
|
| `loaderError` | `Error \| null` | Error from the `loader` (if any) |
|
|
288
401
|
| `beforeLoadError` | `Error \| null` | Error from the `beforeLoad` hook (if any) |
|
|
289
402
|
|
|
290
|
-
```
|
|
403
|
+
```tsx
|
|
291
404
|
const UserProfile = () => {
|
|
292
405
|
const { data, loaderError, beforeLoadError } = useLoaderState<User>();
|
|
293
406
|
```
|
|
@@ -380,7 +493,7 @@ Blocks navigation when callback returns `true`.
|
|
|
380
493
|
| `process()` | `() => void` | Confirm navigation and proceed |
|
|
381
494
|
| `reset()` | `() => void` | Cancel navigation |
|
|
382
495
|
|
|
383
|
-
```
|
|
496
|
+
```tsx
|
|
384
497
|
const { state, process, reset } = useBlocker(() => hasUnsavedChanges);
|
|
385
498
|
|
|
386
499
|
useEffect(() => {
|
|
@@ -407,7 +520,7 @@ Executes a callback when the page is about to be closed or reloaded. Perfect for
|
|
|
407
520
|
|
|
408
521
|
**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.
|
|
409
522
|
|
|
410
|
-
```
|
|
523
|
+
```tsx
|
|
411
524
|
const [text, setText] = useState('');
|
|
412
525
|
const onSave = useCallback(() => {
|
|
413
526
|
localStorage.setItem('draft', text);
|
|
@@ -422,7 +535,7 @@ useBeforeUnload(text ? onSave : undefined);
|
|
|
422
535
|
|
|
423
536
|
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.
|
|
424
537
|
|
|
425
|
-
```
|
|
538
|
+
```tsx
|
|
426
539
|
import { useQueryParam, adapter } from 'clear-react-router';
|
|
427
540
|
|
|
428
541
|
const ProductPage = () => {
|
|
@@ -486,7 +599,7 @@ type Adapter<T> = {
|
|
|
486
599
|
### Using Zod Schemas
|
|
487
600
|
`useQueryParam` works seamlessly with Zod for complex validation:
|
|
488
601
|
|
|
489
|
-
```
|
|
602
|
+
```tsx
|
|
490
603
|
import { z } from 'zod';
|
|
491
604
|
import { useQueryParam, adapter } from 'clear-react-router';
|
|
492
605
|
|
|
@@ -518,7 +631,7 @@ function ProductFilter() {
|
|
|
518
631
|
### Custom Adapters
|
|
519
632
|
You can write your own adapter for any format:
|
|
520
633
|
|
|
521
|
-
```
|
|
634
|
+
```tsx
|
|
522
635
|
// Custom adapter for comma-separated values
|
|
523
636
|
const csvAdapter = {
|
|
524
637
|
parse: (params: string[]): string[] => {
|
|
@@ -537,7 +650,7 @@ const TagsFilter() {
|
|
|
537
650
|
### `useRouterContext()`
|
|
538
651
|
|
|
539
652
|
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.
|
|
540
|
-
```
|
|
653
|
+
```tsx
|
|
541
654
|
const { setContext, context } = useRouterContext();
|
|
542
655
|
const loginHandler = () => setContext({ ...context, user: { name: 'John' } });
|
|
543
656
|
```
|
|
@@ -546,7 +659,7 @@ const loginHandler = () => setContext({ ...context, user: { name: 'John' } });
|
|
|
546
659
|
|
|
547
660
|
Returns an object for working with URL query parameters. Supports reading and setting both single values and arrays.
|
|
548
661
|
|
|
549
|
-
```
|
|
662
|
+
```tsx
|
|
550
663
|
import { useSearchParams } from 'clear-react-router';
|
|
551
664
|
|
|
552
665
|
function ProductFilter() {
|
|
@@ -603,7 +716,7 @@ Returns an array of pathnames representing the user's actual navigation history.
|
|
|
603
716
|
## Lazy Loading
|
|
604
717
|
|
|
605
718
|
Clear Router supports code-splitting out of the box. Simply pass a function that returns a dynamic import:
|
|
606
|
-
```
|
|
719
|
+
```tsx
|
|
607
720
|
{
|
|
608
721
|
path: '/heavy-page',
|
|
609
722
|
element: () => import('./pages/HeavyComponent'),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { PropsWithChildren } from 'react';
|
|
2
|
+
type FormProps = {
|
|
3
|
+
action: string;
|
|
4
|
+
onSuccess?(arg: unknown): void;
|
|
5
|
+
onError?(arg: unknown): void;
|
|
6
|
+
autoReset?: boolean;
|
|
7
|
+
};
|
|
8
|
+
export declare const Form: ({ children, action: actionKey, onSuccess, onError, autoReset, }: PropsWithChildren<FormProps>) => import("react/jsx-runtime").JSX.Element;
|
|
9
|
+
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types/global';
|
|
2
|
-
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
2
|
+
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -3,7 +3,6 @@ import { RouteItem } from '../types/global';
|
|
|
3
3
|
type RouteProviderProps = {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
routes: RouteItem[];
|
|
6
|
-
context?: Record<string, unknown>;
|
|
7
6
|
};
|
|
8
|
-
export declare const RouterProvider: ({ children, routes
|
|
7
|
+
export declare const RouterProvider: ({ children, routes }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
9
8
|
export {};
|
|
@@ -1,22 +1,7 @@
|
|
|
1
|
-
import {
|
|
2
|
-
export type NavigationContextValue = {
|
|
3
|
-
blockerState: BlockerState;
|
|
4
|
-
routeItemData: RouteItemData;
|
|
5
|
-
currentLoaderFallback: RouteItem['loaderFallback'];
|
|
6
|
-
isLoading: boolean;
|
|
7
|
-
loaderState: LoaderState;
|
|
8
|
-
};
|
|
1
|
+
import { Location } from '../types/global';
|
|
9
2
|
export type ActionsContextValue = {
|
|
10
3
|
updateLocation(route: Location): Promise<void>;
|
|
11
|
-
updateBlockedRoute(arg: UpdateBlockedRouteProps): void;
|
|
12
4
|
prefetchLoader(arg: string): Promise<void>;
|
|
13
|
-
|
|
14
|
-
restoreScroll(): void;
|
|
15
|
-
invalidate(path?: string): void;
|
|
16
|
-
};
|
|
17
|
-
export type DataContextValue = {
|
|
18
|
-
context: Record<string, unknown>;
|
|
5
|
+
invalidate(path?: string): Promise<void>;
|
|
19
6
|
};
|
|
20
7
|
export declare const ActionsContext: import("react").Context<ActionsContextValue>;
|
|
21
|
-
export declare const DataContext: import("react").Context<DataContextValue>;
|
|
22
|
-
export declare const NavigationContext: import("react").Context<NavigationContextValue>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useAction: (actionKey: string, onError?: (args: unknown) => void) => (formData: FormData) => Promise<void>;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useFormContext: () => import("../context/FormContext").FormContextProps;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare const useInvalidate: () => (path?: string) => void
|
|
1
|
+
export declare const useInvalidate: () => (path?: string) => Promise<void>;
|
|
@@ -1,11 +1,5 @@
|
|
|
1
|
-
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
1
|
import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
|
|
3
|
-
|
|
4
|
-
routes: RouteItem[];
|
|
5
|
-
context: Record<string, unknown>;
|
|
6
|
-
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
7
|
-
};
|
|
8
|
-
export declare const useLoader: ({ routes, context, setContext }: UseLoaderParams) => {
|
|
2
|
+
export declare const useLoader: (routes: RouteItem[]) => {
|
|
9
3
|
prefetchLoader: (pathname: string) => Promise<void>;
|
|
10
4
|
revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown>;
|
|
11
5
|
isCacheItemFresh: ({ routeItem, pathname }: {
|
|
@@ -13,6 +7,5 @@ export declare const useLoader: ({ routes, context, setContext }: UseLoaderParam
|
|
|
13
7
|
pathname: string;
|
|
14
8
|
}) => boolean;
|
|
15
9
|
loaderStateRef: import("react").RefObject<LoaderState>;
|
|
16
|
-
|
|
10
|
+
invalidate: (pathname?: string) => Promise<void>;
|
|
17
11
|
};
|
|
18
|
-
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import type { Location } from '../types/global';
|
|
2
|
-
export declare const useNavigate: () => (arg: Location | string |
|
|
2
|
+
export declare const useNavigate: () => (arg: Location | string | -1) => Promise<void>;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { RefObject } from 'react';
|
|
2
|
+
import { LoaderState, Location, RevalidateCacheArgs, RouteItem } from '../types/global';
|
|
3
|
+
type UseHandleNavigation = {
|
|
4
|
+
routes: RouteItem[];
|
|
5
|
+
revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
|
|
6
|
+
isCacheItemFresh(arg: {
|
|
7
|
+
routeItem?: RouteItem;
|
|
8
|
+
pathname: string;
|
|
9
|
+
}): boolean;
|
|
10
|
+
loaderStateRef: RefObject<LoaderState>;
|
|
11
|
+
};
|
|
12
|
+
export declare const useNavigation: ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }: UseHandleNavigation) => (nextLocation: Location) => Promise<void>;
|
|
13
|
+
export {};
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
export declare const useNavigationState: () => import("../context/RouterProviderContext").NavigationContextValue;
|
|
2
1
|
export declare const useRouterActions: () => import("../context/RouterProviderContext").ActionsContextValue;
|
|
3
|
-
export declare const useRouterData: () => import("../context/RouterProviderContext").DataContextValue;
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare const useSetInitialContext: (initialContext?: Record<string, unknown>) => void;
|
package/dist/index.d.ts
CHANGED
|
@@ -12,6 +12,7 @@ export { useRouterContext } from './hooks/useRouterContext';
|
|
|
12
12
|
export { useQueryParam } from './hooks/useQueryParam';
|
|
13
13
|
export { useSearchParams } from './hooks/useSearchParams';
|
|
14
14
|
export { useHistoricalTrail } from './hooks/useHistoricalTrail';
|
|
15
|
+
export { useFormContext } from './hooks/useFormContext';
|
|
15
16
|
export { adapter } from './utils/adapter';
|
|
16
17
|
export { createRouter } from './utils/utils';
|
|
17
18
|
export type { RouteItem, BlockerState, Location, AdapterType, RouterProps } from './types/global';
|
package/dist/index.js
CHANGED
|
@@ -4,8 +4,6 @@ var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).expor
|
|
|
4
4
|
//#endregion
|
|
5
5
|
//#region context/RouterProviderContext.ts
|
|
6
6
|
var ActionsContext = createContext({});
|
|
7
|
-
var DataContext = createContext({});
|
|
8
|
-
var NavigationContext = createContext({});
|
|
9
7
|
//#endregion
|
|
10
8
|
//#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
|
|
11
9
|
/**
|
|
@@ -45,29 +43,14 @@ var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((expor
|
|
|
45
43
|
var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
46
44
|
module.exports = require_react_jsx_runtime_production();
|
|
47
45
|
})))();
|
|
48
|
-
var Provider = ({ children,
|
|
46
|
+
var Provider = ({ children, updateLocation, prefetchLoader, invalidate }) => {
|
|
49
47
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
|
|
50
48
|
value: {
|
|
51
49
|
updateLocation,
|
|
52
|
-
updateBlockedRoute,
|
|
53
50
|
prefetchLoader,
|
|
54
|
-
setContext,
|
|
55
|
-
restoreScroll,
|
|
56
51
|
invalidate
|
|
57
52
|
},
|
|
58
|
-
children
|
|
59
|
-
value: { context },
|
|
60
|
-
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavigationContext.Provider, {
|
|
61
|
-
value: {
|
|
62
|
-
blockerState,
|
|
63
|
-
routeItemData,
|
|
64
|
-
currentLoaderFallback,
|
|
65
|
-
isLoading,
|
|
66
|
-
loaderState
|
|
67
|
-
},
|
|
68
|
-
children
|
|
69
|
-
})
|
|
70
|
-
})
|
|
53
|
+
children
|
|
71
54
|
});
|
|
72
55
|
};
|
|
73
56
|
//#endregion
|
|
@@ -80,10 +63,56 @@ var useLatest = (value) => {
|
|
|
80
63
|
return ref;
|
|
81
64
|
};
|
|
82
65
|
//#endregion
|
|
66
|
+
//#region state/createState.ts
|
|
67
|
+
var create = (initialState) => {
|
|
68
|
+
let state = initialState;
|
|
69
|
+
const subscribers = /* @__PURE__ */ new Set();
|
|
70
|
+
const getState = () => state;
|
|
71
|
+
const setState = (action) => {
|
|
72
|
+
const prevState = state;
|
|
73
|
+
const nextState = typeof action === "function" ? action(state) : action;
|
|
74
|
+
if (!Object.is(state, nextState)) {
|
|
75
|
+
state = nextState;
|
|
76
|
+
subscribers.forEach((listener) => listener(state, prevState));
|
|
77
|
+
}
|
|
78
|
+
};
|
|
79
|
+
const subscribe = (listener) => {
|
|
80
|
+
subscribers.add(listener);
|
|
81
|
+
return () => subscribers.delete(listener);
|
|
82
|
+
};
|
|
83
|
+
return {
|
|
84
|
+
subscribe,
|
|
85
|
+
getState,
|
|
86
|
+
setState
|
|
87
|
+
};
|
|
88
|
+
};
|
|
89
|
+
var useGlobalState = ({ subscribe, getState, setState }) => {
|
|
90
|
+
return [useSyncExternalStore(subscribe, getState), setState];
|
|
91
|
+
};
|
|
92
|
+
var createState = (initialState) => {
|
|
93
|
+
const store = create(initialState);
|
|
94
|
+
return () => useGlobalState(store);
|
|
95
|
+
};
|
|
96
|
+
//#endregion
|
|
83
97
|
//#region constants.ts
|
|
84
98
|
var emptyLoaderState = {};
|
|
85
99
|
//#endregion
|
|
86
|
-
//#region
|
|
100
|
+
//#region state/state.ts
|
|
101
|
+
var useIsLoading = createState(false);
|
|
102
|
+
var useBlockedRoute = createState({
|
|
103
|
+
from: "",
|
|
104
|
+
to: ""
|
|
105
|
+
});
|
|
106
|
+
var useLoaderFallback = createState(void 0);
|
|
107
|
+
var useRouteItemData = createState({
|
|
108
|
+
routeItem: void 0,
|
|
109
|
+
location: {}
|
|
110
|
+
});
|
|
111
|
+
var useCurrentLoaderState = createState(emptyLoaderState);
|
|
112
|
+
var useScrollMap = createState({});
|
|
113
|
+
var useContextState = createState({});
|
|
114
|
+
//#endregion
|
|
115
|
+
//#region \0@oxc-project+runtime@0.132.0/helpers/typeof.js
|
|
87
116
|
function _typeof(o) {
|
|
88
117
|
"@babel/helpers - typeof";
|
|
89
118
|
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
@@ -93,7 +122,7 @@ function _typeof(o) {
|
|
|
93
122
|
}, _typeof(o);
|
|
94
123
|
}
|
|
95
124
|
//#endregion
|
|
96
|
-
//#region \0@oxc-project+runtime@0.
|
|
125
|
+
//#region \0@oxc-project+runtime@0.132.0/helpers/toPrimitive.js
|
|
97
126
|
function toPrimitive(t, r) {
|
|
98
127
|
if ("object" != _typeof(t) || !t) return t;
|
|
99
128
|
var e = t[Symbol.toPrimitive];
|
|
@@ -105,13 +134,13 @@ function toPrimitive(t, r) {
|
|
|
105
134
|
return ("string" === r ? String : Number)(t);
|
|
106
135
|
}
|
|
107
136
|
//#endregion
|
|
108
|
-
//#region \0@oxc-project+runtime@0.
|
|
137
|
+
//#region \0@oxc-project+runtime@0.132.0/helpers/toPropertyKey.js
|
|
109
138
|
function toPropertyKey(t) {
|
|
110
139
|
var i = toPrimitive(t, "string");
|
|
111
140
|
return "symbol" == _typeof(i) ? i : i + "";
|
|
112
141
|
}
|
|
113
142
|
//#endregion
|
|
114
|
-
//#region \0@oxc-project+runtime@0.
|
|
143
|
+
//#region \0@oxc-project+runtime@0.132.0/helpers/defineProperty.js
|
|
115
144
|
function _defineProperty(e, r, t) {
|
|
116
145
|
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
117
146
|
value: t,
|
|
@@ -186,34 +215,19 @@ var comparePaths = (el, pathname) => {
|
|
|
186
215
|
return splitElementPath.every((item, index) => item === splitPathname[index + (index ? 1 : 0)]) && splitPathname.length === splitElementPath.length + paramsLength;
|
|
187
216
|
};
|
|
188
217
|
//#endregion
|
|
189
|
-
//#region hooks/
|
|
218
|
+
//#region hooks/useNavigation.ts
|
|
190
219
|
var ALL_LOCATIONS = "*";
|
|
191
|
-
var
|
|
220
|
+
var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }) => {
|
|
221
|
+
const [, setIsLoading] = useIsLoading();
|
|
222
|
+
const [, setScrollMap] = useScrollMap();
|
|
223
|
+
const [, setRouteItemData] = useRouteItemData();
|
|
224
|
+
const [, setLoaderState] = useCurrentLoaderState();
|
|
225
|
+
const [, setCurrentLoaderFallback] = useLoaderFallback();
|
|
226
|
+
const [context, setContext] = useContextState();
|
|
227
|
+
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
192
228
|
const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
|
|
193
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
194
|
-
const [blockedRoute, setBlockedRoute] = useState({
|
|
195
|
-
from: "",
|
|
196
|
-
to: ""
|
|
197
|
-
});
|
|
198
|
-
const [routeItemData, setRouteItemData] = useState({
|
|
199
|
-
routeItem: void 0,
|
|
200
|
-
location: {}
|
|
201
|
-
});
|
|
202
|
-
const [scrollMap, setScrollMap] = useState({});
|
|
203
|
-
const [currentLoaderFallback, setCurrentLoaderFallback] = useState();
|
|
204
|
-
const [loaderState, setLoaderState] = useState(emptyLoaderState);
|
|
205
229
|
const prevPathname = useRef("");
|
|
206
230
|
const navigationSeq = useRef(0);
|
|
207
|
-
const scrollMapLatest = useLatest(scrollMap);
|
|
208
|
-
const restoreScroll = useCallback(() => {
|
|
209
|
-
if (!prevPathname.current || !scrollMapLatest.current[prevPathname.current]) return;
|
|
210
|
-
requestAnimationFrame(() => {
|
|
211
|
-
window.scrollTo({
|
|
212
|
-
top: scrollMapLatest.current[prevPathname.current],
|
|
213
|
-
behavior: "smooth"
|
|
214
|
-
});
|
|
215
|
-
});
|
|
216
|
-
}, [scrollMapLatest]);
|
|
217
231
|
const navigation = useCallback((nextLocation, routeItem) => {
|
|
218
232
|
setRouteItemData({
|
|
219
233
|
routeItem,
|
|
@@ -226,7 +240,13 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
|
|
|
226
240
|
const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
|
|
227
241
|
if (fullPath === window.location.pathname + window.location.search) return;
|
|
228
242
|
history.pushState(null, "", fullPath);
|
|
229
|
-
}, [
|
|
243
|
+
}, [
|
|
244
|
+
loaderStateRef,
|
|
245
|
+
setCurrentLoaderFallback,
|
|
246
|
+
setIsLoading,
|
|
247
|
+
setLoaderState,
|
|
248
|
+
setRouteItemData
|
|
249
|
+
]);
|
|
230
250
|
const transitionedNavigation = useCallback((nextLocation, routeItem) => {
|
|
231
251
|
if (!isAnimated) {
|
|
232
252
|
navigation(nextLocation, routeItem);
|
|
@@ -238,45 +258,6 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
|
|
|
238
258
|
navigation(nextLocation, routeItem);
|
|
239
259
|
}
|
|
240
260
|
}, [navigation, isAnimated]);
|
|
241
|
-
const invalidate = useCallback(async (pathname = routeItemData.location.pathname) => {
|
|
242
|
-
if (typeof pathname !== "string") return;
|
|
243
|
-
const routeItem = routes.find((el) => comparePaths(el, pathname));
|
|
244
|
-
const resultParams = getParamsObject({
|
|
245
|
-
params: routeItem?.params,
|
|
246
|
-
pathname
|
|
247
|
-
});
|
|
248
|
-
clearTimestamp(pathname);
|
|
249
|
-
try {
|
|
250
|
-
if (routeItem?.beforeLoad) await routeItem.beforeLoad({
|
|
251
|
-
context,
|
|
252
|
-
redirect: () => Promise.resolve(),
|
|
253
|
-
params: resultParams,
|
|
254
|
-
setContext
|
|
255
|
-
});
|
|
256
|
-
loaderStateRef.current = {
|
|
257
|
-
...loaderStateRef.current,
|
|
258
|
-
beforeLoadError: null
|
|
259
|
-
};
|
|
260
|
-
} catch (error) {
|
|
261
|
-
loaderStateRef.current = {
|
|
262
|
-
...loaderStateRef.current,
|
|
263
|
-
beforeLoadError: error
|
|
264
|
-
};
|
|
265
|
-
}
|
|
266
|
-
await revalidateCache({
|
|
267
|
-
routeItem,
|
|
268
|
-
pathname
|
|
269
|
-
});
|
|
270
|
-
if (pathname === routeItemData.location.pathname) setLoaderState(loaderStateRef.current);
|
|
271
|
-
}, [
|
|
272
|
-
clearTimestamp,
|
|
273
|
-
context,
|
|
274
|
-
loaderStateRef,
|
|
275
|
-
revalidateCache,
|
|
276
|
-
routeItemData.location.pathname,
|
|
277
|
-
routes,
|
|
278
|
-
setContext
|
|
279
|
-
]);
|
|
280
261
|
const navigationHandler = useCallback(async (nextLocation) => {
|
|
281
262
|
navigationSeq.current = navigationSeq.current + 1;
|
|
282
263
|
const seq = navigationSeq.current;
|
|
@@ -336,40 +317,20 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
|
|
|
336
317
|
});
|
|
337
318
|
}, [
|
|
338
319
|
context,
|
|
320
|
+
isAnimated,
|
|
321
|
+
isCacheItemFresh,
|
|
322
|
+
loaderStateRef,
|
|
339
323
|
revalidateCache,
|
|
340
324
|
routes,
|
|
341
|
-
transitionedNavigation,
|
|
342
325
|
setContext,
|
|
343
|
-
|
|
344
|
-
|
|
326
|
+
setCurrentLoaderFallback,
|
|
327
|
+
setIsLoading,
|
|
328
|
+
setScrollMap,
|
|
345
329
|
showFallback,
|
|
346
|
-
|
|
330
|
+
transitionedNavigation
|
|
347
331
|
]);
|
|
348
332
|
const setNextLocationRef = useLatest(navigationHandler);
|
|
349
|
-
const
|
|
350
|
-
if (prevState.from === payload && type === "charge") return prevState;
|
|
351
|
-
if (payload && prevState.from !== payload && type === "charge") return {
|
|
352
|
-
...prevState,
|
|
353
|
-
from: payload
|
|
354
|
-
};
|
|
355
|
-
if (type === "reset") return {
|
|
356
|
-
...prevState,
|
|
357
|
-
to: ""
|
|
358
|
-
};
|
|
359
|
-
if (type === "process") setNextLocationRef.current({ pathname: prevState.to });
|
|
360
|
-
if (!prevState.from && !prevState.to) return prevState;
|
|
361
|
-
return {
|
|
362
|
-
from: "",
|
|
363
|
-
to: ""
|
|
364
|
-
};
|
|
365
|
-
}), [setNextLocationRef]);
|
|
366
|
-
const updateLocation = useCallback(async (nextLocation) => {
|
|
367
|
-
if (blockedRoute.from) setBlockedRoute((prevState) => ({
|
|
368
|
-
...prevState,
|
|
369
|
-
to: nextLocation.pathname
|
|
370
|
-
}));
|
|
371
|
-
else await setNextLocationRef.current(nextLocation);
|
|
372
|
-
}, [blockedRoute.from, setNextLocationRef]);
|
|
333
|
+
const updateLocation = useCallback(async (nextLocation) => await setNextLocationRef.current(nextLocation), [setNextLocationRef]);
|
|
373
334
|
useEffect(() => {
|
|
374
335
|
const handler = async (event) => {
|
|
375
336
|
const newLocation = parseWindowLocation(event.target.location);
|
|
@@ -383,34 +344,28 @@ var useHandleNavigation = ({ routes, context, revalidateCache, setContext, isCac
|
|
|
383
344
|
};
|
|
384
345
|
window.addEventListener("popstate", handler);
|
|
385
346
|
return () => window.removeEventListener("popstate", handler);
|
|
386
|
-
}, [
|
|
347
|
+
}, [
|
|
348
|
+
blockedRoute.from,
|
|
349
|
+
setBlockedRoute,
|
|
350
|
+
setNextLocationRef
|
|
351
|
+
]);
|
|
387
352
|
useEffect(() => {
|
|
388
353
|
const currentLocation = parseWindowLocation(window.location);
|
|
389
354
|
setNextLocationRef.current(currentLocation);
|
|
390
355
|
prevPathname.current = currentLocation.pathname;
|
|
391
356
|
}, [setNextLocationRef]);
|
|
392
|
-
return
|
|
393
|
-
blockerState: useMemo(() => {
|
|
394
|
-
if (blockedRoute.from && blockedRoute.to) return "blocked";
|
|
395
|
-
if (blockedRoute.from) return "charged";
|
|
396
|
-
return "unblocked";
|
|
397
|
-
}, [blockedRoute]),
|
|
398
|
-
updateLocation,
|
|
399
|
-
updateBlockedRoute,
|
|
400
|
-
routeItemData,
|
|
401
|
-
restoreScroll,
|
|
402
|
-
currentLoaderFallback,
|
|
403
|
-
isLoading,
|
|
404
|
-
loaderState,
|
|
405
|
-
invalidate
|
|
406
|
-
};
|
|
357
|
+
return updateLocation;
|
|
407
358
|
};
|
|
408
359
|
//#endregion
|
|
409
360
|
//#region hooks/useLoader.ts
|
|
410
|
-
var useLoader = (
|
|
361
|
+
var useLoader = (routes) => {
|
|
362
|
+
const [, setLoaderState] = useCurrentLoaderState();
|
|
363
|
+
const [routeItemData] = useRouteItemData();
|
|
364
|
+
const [context, setContext] = useContextState();
|
|
365
|
+
const loaderStateRef = useRef(emptyLoaderState);
|
|
366
|
+
const latestPathname = useLatest(routeItemData.location.pathname);
|
|
411
367
|
const timestampMapRef = useRef(/* @__PURE__ */ new Map());
|
|
412
368
|
const loaderMapRef = useRef({});
|
|
413
|
-
const loaderStateRef = useRef(emptyLoaderState);
|
|
414
369
|
const loadingPromises = useRef(/* @__PURE__ */ new Map());
|
|
415
370
|
const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
|
|
416
371
|
if (!routeItem) return true;
|
|
@@ -476,70 +431,64 @@ var useLoader = ({ routes, context, setContext }) => {
|
|
|
476
431
|
revalidateCache,
|
|
477
432
|
isCacheItemFresh,
|
|
478
433
|
loaderStateRef,
|
|
479
|
-
|
|
434
|
+
invalidate: useCallback(async (pathname = latestPathname.current) => {
|
|
435
|
+
if (typeof pathname !== "string") return;
|
|
436
|
+
const routeItem = routes.find((el) => comparePaths(el, pathname));
|
|
437
|
+
const resultParams = getParamsObject({
|
|
438
|
+
params: routeItem?.params,
|
|
439
|
+
pathname
|
|
440
|
+
});
|
|
480
441
|
timestampMapRef.current.delete(pathname);
|
|
481
|
-
|
|
442
|
+
try {
|
|
443
|
+
if (routeItem?.beforeLoad) await routeItem.beforeLoad({
|
|
444
|
+
context,
|
|
445
|
+
redirect: () => Promise.resolve(),
|
|
446
|
+
params: resultParams,
|
|
447
|
+
setContext
|
|
448
|
+
});
|
|
449
|
+
loaderStateRef.current = {
|
|
450
|
+
...loaderStateRef.current,
|
|
451
|
+
beforeLoadError: null
|
|
452
|
+
};
|
|
453
|
+
} catch (error) {
|
|
454
|
+
loaderStateRef.current = {
|
|
455
|
+
...loaderStateRef.current,
|
|
456
|
+
beforeLoadError: error
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
await revalidateCache({
|
|
460
|
+
routeItem,
|
|
461
|
+
pathname
|
|
462
|
+
});
|
|
463
|
+
if (pathname === latestPathname.current) setLoaderState(loaderStateRef.current);
|
|
464
|
+
}, [
|
|
465
|
+
context,
|
|
466
|
+
loaderStateRef,
|
|
467
|
+
revalidateCache,
|
|
468
|
+
latestPathname,
|
|
469
|
+
routes,
|
|
470
|
+
setContext,
|
|
471
|
+
setLoaderState
|
|
472
|
+
])
|
|
482
473
|
};
|
|
483
474
|
};
|
|
484
475
|
//#endregion
|
|
485
476
|
//#region components/RouterProvider.tsx
|
|
486
|
-
var RouterProvider = ({ children, routes
|
|
487
|
-
const
|
|
488
|
-
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, clearTimestamp } = useLoader({
|
|
489
|
-
routes,
|
|
490
|
-
context,
|
|
491
|
-
setContext
|
|
492
|
-
});
|
|
493
|
-
const { blockerState, updateLocation, updateBlockedRoute, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, invalidate } = useHandleNavigation({
|
|
494
|
-
routes,
|
|
495
|
-
context,
|
|
496
|
-
setContext,
|
|
497
|
-
revalidateCache,
|
|
498
|
-
isCacheItemFresh,
|
|
499
|
-
loaderStateRef,
|
|
500
|
-
clearTimestamp
|
|
501
|
-
});
|
|
477
|
+
var RouterProvider = ({ children, routes }) => {
|
|
478
|
+
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, invalidate } = useLoader(routes);
|
|
502
479
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
restoreScroll,
|
|
512
|
-
currentLoaderFallback,
|
|
513
|
-
isLoading,
|
|
514
|
-
loaderState,
|
|
515
|
-
invalidate
|
|
516
|
-
}), [
|
|
517
|
-
blockerState,
|
|
518
|
-
context,
|
|
519
|
-
prefetchLoader,
|
|
520
|
-
routeItemData,
|
|
521
|
-
updateBlockedRoute,
|
|
522
|
-
updateLocation,
|
|
523
|
-
currentLoaderFallback,
|
|
524
|
-
restoreScroll,
|
|
525
|
-
isLoading,
|
|
526
|
-
loaderState,
|
|
527
|
-
invalidate
|
|
528
|
-
]),
|
|
480
|
+
updateLocation: useNavigation({
|
|
481
|
+
routes,
|
|
482
|
+
revalidateCache,
|
|
483
|
+
isCacheItemFresh,
|
|
484
|
+
loaderStateRef
|
|
485
|
+
}),
|
|
486
|
+
invalidate,
|
|
487
|
+
prefetchLoader,
|
|
529
488
|
children
|
|
530
489
|
});
|
|
531
490
|
};
|
|
532
491
|
//#endregion
|
|
533
|
-
//#region hooks/useServiceContext.ts
|
|
534
|
-
var useServiceState = (reactContext) => {
|
|
535
|
-
const context = useContext(reactContext);
|
|
536
|
-
if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
|
|
537
|
-
return context;
|
|
538
|
-
};
|
|
539
|
-
var useNavigationState = () => useServiceState(NavigationContext);
|
|
540
|
-
var useRouterActions = () => useServiceState(ActionsContext);
|
|
541
|
-
var useRouterData = () => useServiceState(DataContext);
|
|
542
|
-
//#endregion
|
|
543
492
|
//#region hooks/useApplyCustomAnimation.ts
|
|
544
493
|
var useApplyCustomAnimation = (animationDuration) => {
|
|
545
494
|
useEffect(() => {
|
|
@@ -579,8 +528,18 @@ var useApplyCustomAnimation = (animationDuration) => {
|
|
|
579
528
|
//#endregion
|
|
580
529
|
//#region hooks/usePreserveScroll.ts
|
|
581
530
|
var usePreserveScroll = (preserveScroll) => {
|
|
582
|
-
const
|
|
583
|
-
const
|
|
531
|
+
const [routeItemData] = useRouteItemData();
|
|
532
|
+
const [scrollMap] = useScrollMap();
|
|
533
|
+
const { pathname } = routeItemData.location;
|
|
534
|
+
const restoreScroll = useCallback(() => {
|
|
535
|
+
if (!pathname || !scrollMap[pathname]) return;
|
|
536
|
+
requestAnimationFrame(() => {
|
|
537
|
+
window.scrollTo({
|
|
538
|
+
top: scrollMap[pathname],
|
|
539
|
+
behavior: "smooth"
|
|
540
|
+
});
|
|
541
|
+
});
|
|
542
|
+
}, [pathname, scrollMap]);
|
|
584
543
|
useEffect(() => {
|
|
585
544
|
if (preserveScroll) restoreScroll();
|
|
586
545
|
}, [
|
|
@@ -595,6 +554,14 @@ var useSetRouterConfig = (routerProps) => {
|
|
|
595
554
|
useEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
596
555
|
};
|
|
597
556
|
//#endregion
|
|
557
|
+
//#region hooks/useSetInitialContext.ts
|
|
558
|
+
var useSetInitialContext = (initialContext) => {
|
|
559
|
+
const [, setContext] = useContextState();
|
|
560
|
+
useEffect(() => {
|
|
561
|
+
if (initialContext) setContext(initialContext);
|
|
562
|
+
}, [initialContext, setContext]);
|
|
563
|
+
};
|
|
564
|
+
//#endregion
|
|
598
565
|
//#region components/Spinner.tsx
|
|
599
566
|
var Spinner = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cr-spinner" });
|
|
600
567
|
//#endregion
|
|
@@ -606,8 +573,11 @@ var renderElement = (Component) => {
|
|
|
606
573
|
//#endregion
|
|
607
574
|
//#region components/Router.tsx
|
|
608
575
|
var EmptyBoundary = ({ children }) => children;
|
|
609
|
-
var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
|
|
610
|
-
const
|
|
576
|
+
var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext }) => {
|
|
577
|
+
const [isLoading] = useIsLoading();
|
|
578
|
+
const [currentLoaderFallback] = useLoaderFallback();
|
|
579
|
+
const [routeItemData] = useRouteItemData();
|
|
580
|
+
const [loaderState] = useCurrentLoaderState();
|
|
611
581
|
usePreserveScroll(preserveScroll);
|
|
612
582
|
useApplyCustomAnimation(animationDuration);
|
|
613
583
|
useSetRouterConfig({
|
|
@@ -616,9 +586,11 @@ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll =
|
|
|
616
586
|
prefetch,
|
|
617
587
|
hoverPrefetchDelay
|
|
618
588
|
});
|
|
589
|
+
useSetInitialContext(initialContext);
|
|
619
590
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
620
591
|
const showSpinner = spinner && isAnimated && isLoading;
|
|
621
592
|
const loadingContent = !showErrorElement && isLoading;
|
|
593
|
+
const { routeItem, location } = routeItemData;
|
|
622
594
|
if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback);
|
|
623
595
|
if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
|
|
624
596
|
if (!routeItem) return null;
|
|
@@ -629,22 +601,45 @@ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll =
|
|
|
629
601
|
});
|
|
630
602
|
};
|
|
631
603
|
//#endregion
|
|
604
|
+
//#region hooks/useServiceContext.ts
|
|
605
|
+
var useServiceState = (reactContext) => {
|
|
606
|
+
const context = useContext(reactContext);
|
|
607
|
+
if (!Object.keys(context).length) throw new Error("hooks and Router component must be used within RouterProvider");
|
|
608
|
+
return context;
|
|
609
|
+
};
|
|
610
|
+
var useRouterActions = () => useServiceState(ActionsContext);
|
|
611
|
+
//#endregion
|
|
632
612
|
//#region hooks/useLocation.ts
|
|
633
613
|
var useLocation = () => {
|
|
634
|
-
const
|
|
635
|
-
return location;
|
|
614
|
+
const [routeItemData] = useRouteItemData();
|
|
615
|
+
return routeItemData.location;
|
|
636
616
|
};
|
|
637
617
|
//#endregion
|
|
638
618
|
//#region hooks/useNavigate.ts
|
|
639
619
|
var useNavigate = () => {
|
|
620
|
+
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
640
621
|
const { updateLocation } = useRouterActions();
|
|
641
622
|
const locationRef = useLatest(useLocation());
|
|
623
|
+
const blockedRouteRef = useLatest(blockedRoute);
|
|
642
624
|
return useCallback(async (arg) => {
|
|
643
|
-
if (
|
|
625
|
+
if (arg !== -1 && blockedRouteRef.current.from) {
|
|
626
|
+
const to = typeof arg === "object" ? arg.pathname : arg;
|
|
627
|
+
setBlockedRoute((prevState) => ({
|
|
628
|
+
...prevState,
|
|
629
|
+
to
|
|
630
|
+
}));
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (arg === -1) return history.go(arg);
|
|
644
634
|
if (typeof arg === "string") {
|
|
645
635
|
if (arg !== locationRef.current.pathname) await updateLocation({ pathname: arg });
|
|
646
636
|
} else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await updateLocation(arg);
|
|
647
|
-
}, [
|
|
637
|
+
}, [
|
|
638
|
+
blockedRouteRef,
|
|
639
|
+
locationRef,
|
|
640
|
+
setBlockedRoute,
|
|
641
|
+
updateLocation
|
|
642
|
+
]);
|
|
648
643
|
};
|
|
649
644
|
//#endregion
|
|
650
645
|
//#region components/Link.tsx
|
|
@@ -711,7 +706,8 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
711
706
|
//#endregion
|
|
712
707
|
//#region hooks/useParams.ts
|
|
713
708
|
var useParams = () => {
|
|
714
|
-
const
|
|
709
|
+
const [routeItemData] = useRouteItemData();
|
|
710
|
+
const { routeItem, location: { pathname } } = routeItemData;
|
|
715
711
|
if (!routeItem) return void 0;
|
|
716
712
|
return getParamsObject({
|
|
717
713
|
params: routeItem?.params,
|
|
@@ -721,7 +717,7 @@ var useParams = () => {
|
|
|
721
717
|
//#endregion
|
|
722
718
|
//#region hooks/useLoaderState.ts
|
|
723
719
|
var useLoaderState = () => {
|
|
724
|
-
const
|
|
720
|
+
const [loaderState] = useCurrentLoaderState();
|
|
725
721
|
return loaderState;
|
|
726
722
|
};
|
|
727
723
|
//#endregion
|
|
@@ -733,9 +729,32 @@ var useInvalidate = () => {
|
|
|
733
729
|
//#endregion
|
|
734
730
|
//#region hooks/useBlocker.ts
|
|
735
731
|
var useBlocker = (blockerFn) => {
|
|
736
|
-
const
|
|
737
|
-
const {
|
|
738
|
-
const
|
|
732
|
+
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
733
|
+
const { updateLocation } = useRouterActions();
|
|
734
|
+
const [routeItemData] = useRouteItemData();
|
|
735
|
+
const { location: { pathname } } = routeItemData;
|
|
736
|
+
const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
|
|
737
|
+
if (prevState.from === payload && type === "charge") return prevState;
|
|
738
|
+
if (payload && prevState.from !== payload && type === "charge") return {
|
|
739
|
+
...prevState,
|
|
740
|
+
from: payload
|
|
741
|
+
};
|
|
742
|
+
if (type === "reset") return {
|
|
743
|
+
...prevState,
|
|
744
|
+
to: ""
|
|
745
|
+
};
|
|
746
|
+
if (type === "process") updateLocation({ pathname: prevState.to });
|
|
747
|
+
if (!prevState.from && !prevState.to) return prevState;
|
|
748
|
+
return {
|
|
749
|
+
from: "",
|
|
750
|
+
to: ""
|
|
751
|
+
};
|
|
752
|
+
}), [setBlockedRoute, updateLocation]);
|
|
753
|
+
const blockerState = useMemo(() => {
|
|
754
|
+
if (blockedRoute.from && blockedRoute.to) return "blocked";
|
|
755
|
+
if (blockedRoute.from) return "charged";
|
|
756
|
+
return "unblocked";
|
|
757
|
+
}, [blockedRoute]);
|
|
739
758
|
const shouldBlock = blockerFn();
|
|
740
759
|
useEffect(() => updateBlockedRoute(shouldBlock ? {
|
|
741
760
|
type: "charge",
|
|
@@ -767,8 +786,7 @@ var useBeforeUnload = (callback) => {
|
|
|
767
786
|
//#endregion
|
|
768
787
|
//#region hooks/useRouterContext.ts
|
|
769
788
|
var useRouterContext = () => {
|
|
770
|
-
const
|
|
771
|
-
const { setContext } = useRouterActions();
|
|
789
|
+
const [context, setContext] = useContextState();
|
|
772
790
|
return {
|
|
773
791
|
context,
|
|
774
792
|
setContext
|
|
@@ -874,6 +892,12 @@ var useHistoricalTrail = () => {
|
|
|
874
892
|
return trail;
|
|
875
893
|
};
|
|
876
894
|
//#endregion
|
|
895
|
+
//#region context/FormContext.ts
|
|
896
|
+
var FormContext = createContext({ isSubmitting: false });
|
|
897
|
+
//#endregion
|
|
898
|
+
//#region hooks/useFormContext.ts
|
|
899
|
+
var useFormContext = () => useContext(FormContext);
|
|
900
|
+
//#endregion
|
|
877
901
|
//#region utils/adapter.ts
|
|
878
902
|
var adapter = {
|
|
879
903
|
string: { parse: (params) => params[0] || "" },
|
|
@@ -936,4 +960,4 @@ var adapter = {
|
|
|
936
960
|
})
|
|
937
961
|
};
|
|
938
962
|
//#endregion
|
|
939
|
-
export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
|
963
|
+
export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useFormContext, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { type ReactNode } from 'react';
|
|
2
|
-
import { type ActionsContextValue
|
|
3
|
-
type ProviderProps =
|
|
2
|
+
import { type ActionsContextValue } from '../context/RouterProviderContext';
|
|
3
|
+
type ProviderProps = ActionsContextValue & {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
};
|
|
6
|
-
export declare const Provider: ({ children,
|
|
6
|
+
export declare const Provider: ({ children, updateLocation, prefetchLoader, invalidate }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export {};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { LoaderState, RouteItemData } from '../types/global';
|
|
2
|
+
export declare const useIsLoading: () => readonly [boolean, (action: boolean | ((prevState: boolean) => boolean)) => void];
|
|
3
|
+
export declare const useBlockedRoute: () => readonly [{
|
|
4
|
+
from: string;
|
|
5
|
+
to: string;
|
|
6
|
+
}, (action: {
|
|
7
|
+
from: string;
|
|
8
|
+
to: string;
|
|
9
|
+
} | ((prevState: {
|
|
10
|
+
from: string;
|
|
11
|
+
to: string;
|
|
12
|
+
}) => {
|
|
13
|
+
from: string;
|
|
14
|
+
to: string;
|
|
15
|
+
})) => void];
|
|
16
|
+
export declare const useLoaderFallback: () => readonly [(import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined, (action: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | ((prevState: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) => (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) | undefined) => void];
|
|
17
|
+
export declare const useRouteItemData: () => readonly [RouteItemData, (action: RouteItemData | ((prevState: RouteItemData) => RouteItemData)) => void];
|
|
18
|
+
export declare const useCurrentLoaderState: () => readonly [LoaderState, (action: LoaderState | ((prevState: LoaderState) => LoaderState)) => void];
|
|
19
|
+
export declare const useScrollMap: () => readonly [Record<string, number>, (action: Record<string, number> | ((prevState: Record<string, number>) => Record<string, number>)) => void];
|
|
20
|
+
export declare const useContextState: () => readonly [Record<string, unknown>, (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void];
|
package/dist/types/global.d.ts
CHANGED
|
@@ -27,6 +27,12 @@ export type ClientRouteItem = {
|
|
|
27
27
|
params: Record<string, string>;
|
|
28
28
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
29
29
|
}) => Promise<void>;
|
|
30
|
+
actions?: (arg: {
|
|
31
|
+
context: Record<string, unknown>;
|
|
32
|
+
params: Record<string, string>;
|
|
33
|
+
invalidate: (path?: string) => Promise<void>;
|
|
34
|
+
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
35
|
+
}) => Record<string, (arg: FormData) => Promise<unknown> | Promise<void> | void | unknown>;
|
|
30
36
|
};
|
|
31
37
|
export type RouteItem = ClientRouteItem & {
|
|
32
38
|
element: Element;
|
|
@@ -42,10 +48,6 @@ export type Location = {
|
|
|
42
48
|
state?: unknown;
|
|
43
49
|
};
|
|
44
50
|
export type BlockerState = 'blocked' | 'unblocked' | 'charged';
|
|
45
|
-
export type UpdateBlockedRouteProps = {
|
|
46
|
-
type: 'process' | 'reset' | 'charge' | 'unblock';
|
|
47
|
-
payload?: string;
|
|
48
|
-
};
|
|
49
51
|
export type RevalidateCacheArgs = {
|
|
50
52
|
pathname: string;
|
|
51
53
|
routeItem?: RouteItem;
|
|
@@ -74,5 +76,6 @@ export type RouterProps = {
|
|
|
74
76
|
errorBoundary?: ComponentType<{
|
|
75
77
|
children: ReactNode;
|
|
76
78
|
}>;
|
|
79
|
+
context?: Record<string, unknown>;
|
|
77
80
|
};
|
|
78
81
|
export {};
|
package/package.json
CHANGED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { type Dispatch, RefObject, type SetStateAction } from 'react';
|
|
2
|
-
import { BlockerState, LoaderState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
3
|
-
type UseHandleNavigation = {
|
|
4
|
-
routes: RouteItem[];
|
|
5
|
-
context: Record<string, unknown>;
|
|
6
|
-
revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
|
|
7
|
-
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
8
|
-
isCacheItemFresh(arg: {
|
|
9
|
-
routeItem?: RouteItem;
|
|
10
|
-
pathname: string;
|
|
11
|
-
}): boolean;
|
|
12
|
-
loaderStateRef: RefObject<LoaderState>;
|
|
13
|
-
clearTimestamp(path: string): void;
|
|
14
|
-
};
|
|
15
|
-
export declare const useHandleNavigation: ({ routes, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, clearTimestamp, }: UseHandleNavigation) => {
|
|
16
|
-
blockerState: BlockerState;
|
|
17
|
-
updateLocation: (nextLocation: Location) => Promise<void>;
|
|
18
|
-
updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
|
|
19
|
-
routeItemData: RouteItemData;
|
|
20
|
-
restoreScroll: () => void;
|
|
21
|
-
currentLoaderFallback: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined;
|
|
22
|
-
isLoading: boolean;
|
|
23
|
-
loaderState: LoaderState;
|
|
24
|
-
invalidate: (pathname?: string) => Promise<void>;
|
|
25
|
-
};
|
|
26
|
-
export {};
|