clear-react-router 1.6.5 → 1.6.8
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 +115 -4
- 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 +3 -0
- package/dist/index.js +314 -209
- 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,7 +57,6 @@ 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
|
|
@@ -86,6 +86,7 @@ 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
|
|
@@ -219,6 +220,114 @@ const routes = createRouter([
|
|
|
219
220
|
]);
|
|
220
221
|
```
|
|
221
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
|
+
|
|
222
331
|
### Error Boundaries
|
|
223
332
|
|
|
224
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.
|
|
@@ -240,13 +349,15 @@ const App = () => (
|
|
|
240
349
|
|
|
241
350
|
### `useNavigate()`
|
|
242
351
|
|
|
243
|
-
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.
|
|
244
353
|
|
|
245
354
|
```tsx
|
|
355
|
+
type Location = { pathname: string; search?: string; state?: unknown }
|
|
356
|
+
|
|
246
357
|
const navigate = useNavigate();
|
|
247
358
|
|
|
248
359
|
navigate('/about'); // string
|
|
249
|
-
navigate({ pathname: '/user/123', state: { from: 'home' } }); //
|
|
360
|
+
navigate({ pathname: '/user/123', state: { from: 'home' } }); // Location
|
|
250
361
|
navigate(-1); // go back
|
|
251
362
|
```
|
|
252
363
|
|
|
@@ -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
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
export { RouterProvider } from './components/RouterProvider';
|
|
2
2
|
export { Router } from './components/Router';
|
|
3
3
|
export { Link } from './components/Link';
|
|
4
|
+
export { Form } from './components/Form';
|
|
4
5
|
export { useNavigate } from './hooks/useNavigate';
|
|
5
6
|
export { useParams } from './hooks/useParams';
|
|
6
7
|
export { useLocation } from './hooks/useLocation';
|
|
7
8
|
export { useLoaderState } from './hooks/useLoaderState';
|
|
8
9
|
export { useInvalidate } from './hooks/useInvalidate';
|
|
9
10
|
export { useBlocker } from './hooks/useBlocker';
|
|
11
|
+
export { useAction } from './hooks/useAction';
|
|
10
12
|
export { useBeforeUnload } from './hooks/useBeforeUnload';
|
|
11
13
|
export { useRouterContext } from './hooks/useRouterContext';
|
|
12
14
|
export { useQueryParam } from './hooks/useQueryParam';
|
|
13
15
|
export { useSearchParams } from './hooks/useSearchParams';
|
|
14
16
|
export { useHistoricalTrail } from './hooks/useHistoricalTrail';
|
|
17
|
+
export { useFormContext } from './hooks/useFormContext';
|
|
15
18
|
export { adapter } from './utils/adapter';
|
|
16
19
|
export { createRouter } from './utils/utils';
|
|
17
20
|
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
|
|
@@ -709,9 +704,16 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
709
704
|
});
|
|
710
705
|
};
|
|
711
706
|
//#endregion
|
|
707
|
+
//#region hooks/useInvalidate.ts
|
|
708
|
+
var useInvalidate = () => {
|
|
709
|
+
const { invalidate } = useRouterActions();
|
|
710
|
+
return invalidate;
|
|
711
|
+
};
|
|
712
|
+
//#endregion
|
|
712
713
|
//#region hooks/useParams.ts
|
|
713
714
|
var useParams = () => {
|
|
714
|
-
const
|
|
715
|
+
const [routeItemData] = useRouteItemData();
|
|
716
|
+
const { routeItem, location: { pathname } } = routeItemData;
|
|
715
717
|
if (!routeItem) return void 0;
|
|
716
718
|
return getParamsObject({
|
|
717
719
|
params: routeItem?.params,
|
|
@@ -719,23 +721,89 @@ var useParams = () => {
|
|
|
719
721
|
});
|
|
720
722
|
};
|
|
721
723
|
//#endregion
|
|
724
|
+
//#region context/FormContext.ts
|
|
725
|
+
var FormContext = createContext({ isSubmitting: false });
|
|
726
|
+
//#endregion
|
|
727
|
+
//#region provider/FormProvider.tsx
|
|
728
|
+
var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormContext.Provider, {
|
|
729
|
+
value: { isSubmitting },
|
|
730
|
+
children
|
|
731
|
+
});
|
|
732
|
+
//#endregion
|
|
733
|
+
//#region components/Form.tsx
|
|
734
|
+
var Form = ({ children, action: actionKey, onSuccess, onError, autoReset = true }) => {
|
|
735
|
+
const invalidate = useInvalidate();
|
|
736
|
+
const [routeItemData] = useRouteItemData();
|
|
737
|
+
const [context, setContext] = useContextState();
|
|
738
|
+
const params = useParams();
|
|
739
|
+
const { routeItem } = routeItemData;
|
|
740
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
|
741
|
+
const onSubmit = async (evt) => {
|
|
742
|
+
evt.preventDefault();
|
|
743
|
+
if (!routeItem) throw new Error("route not found");
|
|
744
|
+
if (!routeItem.actions) throw new Error("route action creator not found");
|
|
745
|
+
const action = routeItem.actions({
|
|
746
|
+
context,
|
|
747
|
+
setContext,
|
|
748
|
+
params,
|
|
749
|
+
invalidate
|
|
750
|
+
})[actionKey];
|
|
751
|
+
if (!action) throw new Error("action not found");
|
|
752
|
+
try {
|
|
753
|
+
setIsSubmitting(true);
|
|
754
|
+
const result = await action(new FormData(evt.target));
|
|
755
|
+
await invalidate();
|
|
756
|
+
if (autoReset) evt.target.reset();
|
|
757
|
+
onSuccess?.(result);
|
|
758
|
+
} catch (error) {
|
|
759
|
+
onError?.(error);
|
|
760
|
+
} finally {
|
|
761
|
+
setIsSubmitting(false);
|
|
762
|
+
}
|
|
763
|
+
};
|
|
764
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(FormProvider, {
|
|
765
|
+
isSubmitting,
|
|
766
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("form", {
|
|
767
|
+
onSubmit,
|
|
768
|
+
children
|
|
769
|
+
})
|
|
770
|
+
});
|
|
771
|
+
};
|
|
772
|
+
//#endregion
|
|
722
773
|
//#region hooks/useLoaderState.ts
|
|
723
774
|
var useLoaderState = () => {
|
|
724
|
-
const
|
|
775
|
+
const [loaderState] = useCurrentLoaderState();
|
|
725
776
|
return loaderState;
|
|
726
777
|
};
|
|
727
778
|
//#endregion
|
|
728
|
-
//#region hooks/useInvalidate.ts
|
|
729
|
-
var useInvalidate = () => {
|
|
730
|
-
const { invalidate } = useRouterActions();
|
|
731
|
-
return invalidate;
|
|
732
|
-
};
|
|
733
|
-
//#endregion
|
|
734
779
|
//#region hooks/useBlocker.ts
|
|
735
780
|
var useBlocker = (blockerFn) => {
|
|
736
|
-
const
|
|
737
|
-
const {
|
|
738
|
-
const
|
|
781
|
+
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
782
|
+
const { updateLocation } = useRouterActions();
|
|
783
|
+
const [routeItemData] = useRouteItemData();
|
|
784
|
+
const { location: { pathname } } = routeItemData;
|
|
785
|
+
const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
|
|
786
|
+
if (prevState.from === payload && type === "charge") return prevState;
|
|
787
|
+
if (payload && prevState.from !== payload && type === "charge") return {
|
|
788
|
+
...prevState,
|
|
789
|
+
from: payload
|
|
790
|
+
};
|
|
791
|
+
if (type === "reset") return {
|
|
792
|
+
...prevState,
|
|
793
|
+
to: ""
|
|
794
|
+
};
|
|
795
|
+
if (type === "process") updateLocation({ pathname: prevState.to });
|
|
796
|
+
if (!prevState.from && !prevState.to) return prevState;
|
|
797
|
+
return {
|
|
798
|
+
from: "",
|
|
799
|
+
to: ""
|
|
800
|
+
};
|
|
801
|
+
}), [setBlockedRoute, updateLocation]);
|
|
802
|
+
const blockerState = useMemo(() => {
|
|
803
|
+
if (blockedRoute.from && blockedRoute.to) return "blocked";
|
|
804
|
+
if (blockedRoute.from) return "charged";
|
|
805
|
+
return "unblocked";
|
|
806
|
+
}, [blockedRoute]);
|
|
739
807
|
const shouldBlock = blockerFn();
|
|
740
808
|
useEffect(() => updateBlockedRoute(shouldBlock ? {
|
|
741
809
|
type: "charge",
|
|
@@ -752,6 +820,41 @@ var useBlocker = (blockerFn) => {
|
|
|
752
820
|
};
|
|
753
821
|
};
|
|
754
822
|
//#endregion
|
|
823
|
+
//#region hooks/useAction.ts
|
|
824
|
+
var useAction = (actionKey, onError) => {
|
|
825
|
+
const invalidate = useInvalidate();
|
|
826
|
+
const [routeItemData] = useRouteItemData();
|
|
827
|
+
const [context, setContext] = useContextState();
|
|
828
|
+
const params = useParams();
|
|
829
|
+
const { routeItem } = routeItemData;
|
|
830
|
+
const latestContext = useLatest(context);
|
|
831
|
+
return useCallback(async (formData) => {
|
|
832
|
+
if (!routeItem) throw new Error("route not found");
|
|
833
|
+
if (!routeItem.actions) throw new Error("route action creator not found");
|
|
834
|
+
const action = routeItem.actions({
|
|
835
|
+
context: latestContext.current,
|
|
836
|
+
setContext,
|
|
837
|
+
params,
|
|
838
|
+
invalidate
|
|
839
|
+
})[actionKey];
|
|
840
|
+
if (!action) throw new Error("action not found");
|
|
841
|
+
try {
|
|
842
|
+
await action(formData);
|
|
843
|
+
await invalidate();
|
|
844
|
+
} catch (error) {
|
|
845
|
+
onError?.(error);
|
|
846
|
+
}
|
|
847
|
+
}, [
|
|
848
|
+
actionKey,
|
|
849
|
+
latestContext,
|
|
850
|
+
invalidate,
|
|
851
|
+
onError,
|
|
852
|
+
params,
|
|
853
|
+
routeItem,
|
|
854
|
+
setContext
|
|
855
|
+
]);
|
|
856
|
+
};
|
|
857
|
+
//#endregion
|
|
755
858
|
//#region hooks/useBeforeUnload.ts
|
|
756
859
|
var useBeforeUnload = (callback) => {
|
|
757
860
|
useEffect(() => {
|
|
@@ -767,8 +870,7 @@ var useBeforeUnload = (callback) => {
|
|
|
767
870
|
//#endregion
|
|
768
871
|
//#region hooks/useRouterContext.ts
|
|
769
872
|
var useRouterContext = () => {
|
|
770
|
-
const
|
|
771
|
-
const { setContext } = useRouterActions();
|
|
873
|
+
const [context, setContext] = useContextState();
|
|
772
874
|
return {
|
|
773
875
|
context,
|
|
774
876
|
setContext
|
|
@@ -874,6 +976,9 @@ var useHistoricalTrail = () => {
|
|
|
874
976
|
return trail;
|
|
875
977
|
};
|
|
876
978
|
//#endregion
|
|
979
|
+
//#region hooks/useFormContext.ts
|
|
980
|
+
var useFormContext = () => useContext(FormContext);
|
|
981
|
+
//#endregion
|
|
877
982
|
//#region utils/adapter.ts
|
|
878
983
|
var adapter = {
|
|
879
984
|
string: { parse: (params) => params[0] || "" },
|
|
@@ -936,4 +1041,4 @@ var adapter = {
|
|
|
936
1041
|
})
|
|
937
1042
|
};
|
|
938
1043
|
//#endregion
|
|
939
|
-
export { Link, Router, RouterProvider, adapter, createRouter, useBeforeUnload, useBlocker, useHistoricalTrail, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
|
1044
|
+
export { Form, Link, Router, RouterProvider, adapter, createRouter, useAction, 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 {};
|