clear-react-router 1.7.1 → 1.7.2
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 +20 -45
- package/dist/components/Router.d.ts +1 -1
- package/dist/config/routerConfig.d.ts +0 -2
- package/dist/hooks/useNavigation.d.ts +3 -1
- package/dist/hooks/useRuntime.d.ts +5 -0
- package/dist/hooks/useSetRouterConfig.d.ts +1 -1
- package/dist/hooks/useSetRuntime.d.ts +7 -0
- package/dist/index.d.ts +0 -1
- package/dist/index.js +259 -254
- package/dist/state/state.d.ts +8 -1
- package/dist/types/global.d.ts +1 -0
- package/package.json +1 -1
- package/dist/components/RouterProvider.d.ts +0 -8
- package/dist/context/RouterProviderContext.d.ts +0 -7
- package/dist/hooks/useServiceContext.d.ts +0 -1
- package/dist/provider/Provider.d.ts +0 -7
package/README.md
CHANGED
|
@@ -1,17 +1,23 @@
|
|
|
1
1
|
[](https://www.npmjs.com/package/clear-react-router)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
# Clear Router
|
|
4
|
+
|
|
5
|
+
A lightweight, type-safe routing library for React applications with nested routes, data loading, navigation blocking, prefetching, and route actions.
|
|
4
6
|
|
|
5
7
|
## Why Clear Router?
|
|
6
8
|
|
|
7
|
-
Most React routers focus on flexibility and ecosystem integrations.
|
|
8
|
-
|
|
9
|
+
Most React routers focus on flexibility and ecosystem integrations. Clear Router focuses on predictable navigation with a small, explicit API and minimal setup.
|
|
10
|
+
|
|
11
|
+
There is no `RouterProvider` or provider hierarchy to manage. Simply render `<Router />` once, and use router hooks anywhere in your application.
|
|
9
12
|
|
|
10
13
|
It provides first-class support for:
|
|
11
14
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
* Predictable routing
|
|
16
|
+
* Built-in data loading
|
|
17
|
+
* Route actions and forms
|
|
18
|
+
* Simple, provider-free architecture
|
|
19
|
+
* Small, explicit API
|
|
20
|
+
|
|
15
21
|
|
|
16
22
|
## Features
|
|
17
23
|
|
|
@@ -49,36 +55,11 @@ Normalizes route configuration. Handles wildcard `*` routes, extracts dynamic pa
|
|
|
49
55
|
| `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
56
|
| `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
57
|
|
|
52
|
-
|
|
53
|
-
### `RouterProvider`
|
|
54
|
-
|
|
55
|
-
The root component that provides routing context to the application. Place static UI elements (like navbar or footer) outside `<Router />` to prevent unnecessary re-renders.
|
|
56
|
-
|
|
57
|
-
| Prop | Type | Default | Description |
|
|
58
|
-
|------|------|---------|-------------|
|
|
59
|
-
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
60
|
-
| `children` | `ReactNode` | required | App content (must include `<Router />`) |
|
|
61
|
-
|
|
62
|
-
```tsx
|
|
63
|
-
function App() {
|
|
64
|
-
return (
|
|
65
|
-
<RouterProvider routeList={routes}>
|
|
66
|
-
<Navbar /> {/* Static */}
|
|
67
|
-
<main>
|
|
68
|
-
<Router isAnimated animationDuration={800} /> {/* Dynamic — renders current page */}
|
|
69
|
-
</main>
|
|
70
|
-
<Footer /> {/* Static */}
|
|
71
|
-
</RouterProvider>
|
|
72
|
-
);
|
|
73
|
-
}
|
|
74
|
-
```
|
|
75
|
-
|
|
76
58
|
### `Router`
|
|
77
59
|
|
|
78
|
-
Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
79
|
-
|
|
80
60
|
| Prop | Type | Default | Description |
|
|
81
61
|
|------|------|---------|-------------|
|
|
62
|
+
| `routes` | `RouteItem[]` | required | Array of route configurations |
|
|
82
63
|
| `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
|
|
83
64
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
84
65
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
@@ -90,10 +71,10 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
|
90
71
|
| `errorBoundary` | `ComponentType<{ children: ReactNode }>` | `undefined` | Custom error boundary component for catching render errors in route components |
|
|
91
72
|
|
|
92
73
|
```tsx
|
|
93
|
-
<
|
|
74
|
+
<div>
|
|
94
75
|
<Navbar />
|
|
95
|
-
<Router spinner={false} isAnimated /> {/* disable the spinner */}
|
|
96
|
-
</
|
|
76
|
+
<Router routes={routes} spinner={false} isAnimated /> {/* disable the spinner */}
|
|
77
|
+
</div>
|
|
97
78
|
```
|
|
98
79
|
|
|
99
80
|
> **Note:** When `isAnimated` is enabled, `loaderFallback` is not shown. Instead, a small spinner appears (if `spinner={true}`). On the initial page load, however, the route's loaderFallback is rendered if available.
|
|
@@ -121,12 +102,10 @@ Component for client-side navigation with prefetch support.
|
|
|
121
102
|
**Example:**
|
|
122
103
|
|
|
123
104
|
```tsx
|
|
124
|
-
import {
|
|
105
|
+
import { Router, Link } from 'clear-react-router';
|
|
125
106
|
|
|
126
107
|
// Global prefetch: hover with 100ms delay
|
|
127
|
-
<
|
|
128
|
-
<Router />
|
|
129
|
-
</RouterProvider>
|
|
108
|
+
<Router routes={routes} prefetch="hover" hoverPrefetchDelay={100} />
|
|
130
109
|
|
|
131
110
|
// Override for a specific link
|
|
132
111
|
<Link to="/heavy-page" prefetch="viewport">
|
|
@@ -334,15 +313,11 @@ This hook is useful when the mutation is triggered programmatically, such as fro
|
|
|
334
313
|
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.
|
|
335
314
|
|
|
336
315
|
```tsx
|
|
337
|
-
import {
|
|
316
|
+
import { Router } from 'clear-react-router';
|
|
338
317
|
import { routes } from './routes';
|
|
339
318
|
import { ErrorBoundary } from './components/ErrorBoundary';
|
|
340
319
|
|
|
341
|
-
const App = () =>
|
|
342
|
-
<RouterProvider routes={routes}>
|
|
343
|
-
<Router errorBoundary={ErrorBoundary} />
|
|
344
|
-
</RouterProvider>
|
|
345
|
-
);
|
|
320
|
+
const App = () => <Router routes={routes} errorBoundary={ErrorBoundary} />
|
|
346
321
|
```
|
|
347
322
|
**Note:** The `errorBoundary` prop only catches render-time errors in route components. It does not catch errors in `loader` or `beforeLoad` — those are handled by the router's `errorElement` mechanism.
|
|
348
323
|
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types/global';
|
|
2
|
-
export declare const Router: ({
|
|
2
|
+
export declare const Router: ({ routes, animationDuration, isAnimated, spinner, preserveScroll, showFallbackOnAnimation, prefetch, hoverPrefetchDelay, errorBoundary: ErrorBoundary, context: initialContext, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -8,6 +8,8 @@ type UseHandleNavigation = {
|
|
|
8
8
|
pathname: string;
|
|
9
9
|
}): boolean;
|
|
10
10
|
loaderStateRef: RefObject<LoaderState>;
|
|
11
|
+
isAnimated: boolean;
|
|
12
|
+
showFallbackOnAnimation: boolean;
|
|
11
13
|
};
|
|
12
|
-
export declare const useNavigation: ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }: UseHandleNavigation) => (nextLocation: Location) => Promise<void>;
|
|
14
|
+
export declare const useNavigation: ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef, isAnimated, showFallbackOnAnimation: showFallback, }: UseHandleNavigation) => (nextLocation: Location) => Promise<void>;
|
|
13
15
|
export {};
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types/global';
|
|
2
|
-
export declare const useSetRouterConfig: (routerProps: RouterProps) => void;
|
|
2
|
+
export declare const useSetRouterConfig: (routerProps: Omit<RouterProps, "routes">) => void;
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
type UseSetRuntime = {
|
|
2
|
+
updateLocation(arg: Location): Promise<void>;
|
|
3
|
+
prefetchLoader(arg: string): Promise<void>;
|
|
4
|
+
invalidate(arg?: string): Promise<void>;
|
|
5
|
+
};
|
|
6
|
+
export declare const useSetRouterRuntime: ({ updateLocation, prefetchLoader, invalidate }: UseSetRuntime) => void;
|
|
7
|
+
export {};
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -2,67 +2,6 @@ import { Suspense, createContext, lazy, useCallback, useContext, useEffect, useM
|
|
|
2
2
|
//#region \0rolldown/runtime.js
|
|
3
3
|
var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
|
|
4
4
|
//#endregion
|
|
5
|
-
//#region context/RouterProviderContext.ts
|
|
6
|
-
var ActionsContext = createContext({});
|
|
7
|
-
//#endregion
|
|
8
|
-
//#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
|
|
9
|
-
/**
|
|
10
|
-
* @license React
|
|
11
|
-
* react-jsx-runtime.production.js
|
|
12
|
-
*
|
|
13
|
-
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
14
|
-
*
|
|
15
|
-
* This source code is licensed under the MIT license found in the
|
|
16
|
-
* LICENSE file in the root directory of this source tree.
|
|
17
|
-
*/
|
|
18
|
-
var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
19
|
-
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
20
|
-
function jsxProd(type, config, maybeKey) {
|
|
21
|
-
var key = null;
|
|
22
|
-
void 0 !== maybeKey && (key = "" + maybeKey);
|
|
23
|
-
void 0 !== config.key && (key = "" + config.key);
|
|
24
|
-
if ("key" in config) {
|
|
25
|
-
maybeKey = {};
|
|
26
|
-
for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]);
|
|
27
|
-
} else maybeKey = config;
|
|
28
|
-
config = maybeKey.ref;
|
|
29
|
-
return {
|
|
30
|
-
$$typeof: REACT_ELEMENT_TYPE,
|
|
31
|
-
type,
|
|
32
|
-
key,
|
|
33
|
-
ref: void 0 !== config ? config : null,
|
|
34
|
-
props: maybeKey
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
exports.Fragment = REACT_FRAGMENT_TYPE;
|
|
38
|
-
exports.jsx = jsxProd;
|
|
39
|
-
exports.jsxs = jsxProd;
|
|
40
|
-
}));
|
|
41
|
-
//#endregion
|
|
42
|
-
//#region provider/Provider.tsx
|
|
43
|
-
var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
44
|
-
module.exports = require_react_jsx_runtime_production();
|
|
45
|
-
})))();
|
|
46
|
-
var Provider = ({ children, updateLocation, prefetchLoader, invalidate }) => {
|
|
47
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
|
|
48
|
-
value: {
|
|
49
|
-
updateLocation,
|
|
50
|
-
prefetchLoader,
|
|
51
|
-
invalidate
|
|
52
|
-
},
|
|
53
|
-
children
|
|
54
|
-
});
|
|
55
|
-
};
|
|
56
|
-
//#endregion
|
|
57
|
-
//#region hooks/useLatest.ts
|
|
58
|
-
var useLatest = (value) => {
|
|
59
|
-
const ref = useRef(value);
|
|
60
|
-
useEffect(() => {
|
|
61
|
-
ref.current = value;
|
|
62
|
-
}, [value]);
|
|
63
|
-
return ref;
|
|
64
|
-
};
|
|
65
|
-
//#endregion
|
|
66
5
|
//#region state/createState.ts
|
|
67
6
|
var create = (initialState) => {
|
|
68
7
|
let state = initialState;
|
|
@@ -111,60 +50,55 @@ var useRouteItemData = createState({
|
|
|
111
50
|
var useCurrentLoaderState = createState(emptyLoaderState);
|
|
112
51
|
var useScrollMap = createState({});
|
|
113
52
|
var useContextState = createState({});
|
|
53
|
+
var useActionState = createState({});
|
|
114
54
|
//#endregion
|
|
115
|
-
//#region
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
}
|
|
124
|
-
//#endregion
|
|
125
|
-
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
|
|
126
|
-
function toPrimitive(t, r) {
|
|
127
|
-
if ("object" != _typeof(t) || !t) return t;
|
|
128
|
-
var e = t[Symbol.toPrimitive];
|
|
129
|
-
if (void 0 !== e) {
|
|
130
|
-
var i = e.call(t, r || "default");
|
|
131
|
-
if ("object" != _typeof(i)) return i;
|
|
132
|
-
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
133
|
-
}
|
|
134
|
-
return ("string" === r ? String : Number)(t);
|
|
135
|
-
}
|
|
136
|
-
//#endregion
|
|
137
|
-
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
|
|
138
|
-
function toPropertyKey(t) {
|
|
139
|
-
var i = toPrimitive(t, "string");
|
|
140
|
-
return "symbol" == _typeof(i) ? i : i + "";
|
|
141
|
-
}
|
|
142
|
-
//#endregion
|
|
143
|
-
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
|
|
144
|
-
function _defineProperty(e, r, t) {
|
|
145
|
-
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
146
|
-
value: t,
|
|
147
|
-
enumerable: !0,
|
|
148
|
-
configurable: !0,
|
|
149
|
-
writable: !0
|
|
150
|
-
}) : e[r] = t, e;
|
|
151
|
-
}
|
|
55
|
+
//#region hooks/useLatest.ts
|
|
56
|
+
var useLatest = (value) => {
|
|
57
|
+
const ref = useRef(value);
|
|
58
|
+
useEffect(() => {
|
|
59
|
+
ref.current = value;
|
|
60
|
+
}, [value]);
|
|
61
|
+
return ref;
|
|
62
|
+
};
|
|
152
63
|
//#endregion
|
|
153
|
-
//#region
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
64
|
+
//#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
|
|
65
|
+
/**
|
|
66
|
+
* @license React
|
|
67
|
+
* react-jsx-runtime.production.js
|
|
68
|
+
*
|
|
69
|
+
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
70
|
+
*
|
|
71
|
+
* This source code is licensed under the MIT license found in the
|
|
72
|
+
* LICENSE file in the root directory of this source tree.
|
|
73
|
+
*/
|
|
74
|
+
var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
75
|
+
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
|
|
76
|
+
function jsxProd(type, config, maybeKey) {
|
|
77
|
+
var key = null;
|
|
78
|
+
void 0 !== maybeKey && (key = "" + maybeKey);
|
|
79
|
+
void 0 !== config.key && (key = "" + config.key);
|
|
80
|
+
if ("key" in config) {
|
|
81
|
+
maybeKey = {};
|
|
82
|
+
for (var propName in config) "key" !== propName && (maybeKey[propName] = config[propName]);
|
|
83
|
+
} else maybeKey = config;
|
|
84
|
+
config = maybeKey.ref;
|
|
85
|
+
return {
|
|
86
|
+
$$typeof: REACT_ELEMENT_TYPE,
|
|
87
|
+
type,
|
|
88
|
+
key,
|
|
89
|
+
ref: void 0 !== config ? config : null,
|
|
90
|
+
props: maybeKey
|
|
91
|
+
};
|
|
163
92
|
}
|
|
164
|
-
|
|
165
|
-
|
|
93
|
+
exports.Fragment = REACT_FRAGMENT_TYPE;
|
|
94
|
+
exports.jsx = jsxProd;
|
|
95
|
+
exports.jsxs = jsxProd;
|
|
96
|
+
}));
|
|
166
97
|
//#endregion
|
|
167
98
|
//#region utils/createLazyComponent.tsx
|
|
99
|
+
var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
100
|
+
module.exports = require_react_jsx_runtime_production();
|
|
101
|
+
})))();
|
|
168
102
|
var createLazyComponent = (importFn, fallback) => {
|
|
169
103
|
const LazyComp = lazy(() => importFn().then((module) => ({ default: module.default || module })));
|
|
170
104
|
return () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Suspense, {
|
|
@@ -215,9 +149,125 @@ var comparePaths = (el, pathname) => {
|
|
|
215
149
|
return splitElementPath.every((item, index) => item === splitPathname[index + (index ? 1 : 0)]) && splitPathname.length === splitElementPath.length + paramsLength;
|
|
216
150
|
};
|
|
217
151
|
//#endregion
|
|
152
|
+
//#region hooks/useLoader.ts
|
|
153
|
+
var useLoader = (routes) => {
|
|
154
|
+
const [, setLoaderState] = useCurrentLoaderState();
|
|
155
|
+
const [routeItemData] = useRouteItemData();
|
|
156
|
+
const [context, setContext] = useContextState();
|
|
157
|
+
const loaderStateRef = useRef(emptyLoaderState);
|
|
158
|
+
const latestPathname = useLatest(routeItemData.location.pathname);
|
|
159
|
+
const timestampMapRef = useRef(/* @__PURE__ */ new Map());
|
|
160
|
+
const loaderMapRef = useRef({});
|
|
161
|
+
const loadingPromises = useRef(/* @__PURE__ */ new Map());
|
|
162
|
+
const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
|
|
163
|
+
if (!routeItem) return true;
|
|
164
|
+
const currentCacheTimestamp = timestampMapRef.current.get(pathname);
|
|
165
|
+
if (!currentCacheTimestamp) return false;
|
|
166
|
+
if (!routeItem.staleTime) return true;
|
|
167
|
+
return Date.now() - currentCacheTimestamp < routeItem.staleTime;
|
|
168
|
+
}, []);
|
|
169
|
+
const revalidateCache = useCallback(async ({ routeItem, pathname }) => {
|
|
170
|
+
if (!routeItem?.loader) return;
|
|
171
|
+
if (loadingPromises.current.has(pathname)) return loadingPromises.current.get(pathname);
|
|
172
|
+
if (isCacheItemFresh({
|
|
173
|
+
routeItem,
|
|
174
|
+
pathname
|
|
175
|
+
})) {
|
|
176
|
+
loaderStateRef.current = loaderMapRef.current[pathname];
|
|
177
|
+
return;
|
|
178
|
+
}
|
|
179
|
+
const promise = (async () => {
|
|
180
|
+
if (!routeItem?.loader) return;
|
|
181
|
+
try {
|
|
182
|
+
const params = getParamsObject({
|
|
183
|
+
params: routeItem.params,
|
|
184
|
+
pathname
|
|
185
|
+
});
|
|
186
|
+
const result = await routeItem?.loader({
|
|
187
|
+
params,
|
|
188
|
+
context,
|
|
189
|
+
setContext
|
|
190
|
+
});
|
|
191
|
+
timestampMapRef.current.set(pathname, Date.now());
|
|
192
|
+
loaderStateRef.current = {
|
|
193
|
+
...loaderStateRef?.current,
|
|
194
|
+
data: result,
|
|
195
|
+
loaderError: null
|
|
196
|
+
};
|
|
197
|
+
loaderMapRef.current[pathname] = loaderStateRef.current;
|
|
198
|
+
} catch (error) {
|
|
199
|
+
loaderStateRef.current = {
|
|
200
|
+
...loaderStateRef?.current,
|
|
201
|
+
data: null,
|
|
202
|
+
loaderError: error
|
|
203
|
+
};
|
|
204
|
+
} finally {
|
|
205
|
+
loadingPromises.current.delete(pathname);
|
|
206
|
+
}
|
|
207
|
+
})();
|
|
208
|
+
loadingPromises.current.set(pathname, promise);
|
|
209
|
+
return promise;
|
|
210
|
+
}, [
|
|
211
|
+
context,
|
|
212
|
+
isCacheItemFresh,
|
|
213
|
+
setContext
|
|
214
|
+
]);
|
|
215
|
+
return {
|
|
216
|
+
prefetchLoader: useCallback(async (pathname) => {
|
|
217
|
+
const item = routes.find((el) => comparePaths(el, pathname));
|
|
218
|
+
if (item) await revalidateCache({
|
|
219
|
+
routeItem: item,
|
|
220
|
+
pathname
|
|
221
|
+
});
|
|
222
|
+
}, [revalidateCache, routes]),
|
|
223
|
+
revalidateCache,
|
|
224
|
+
isCacheItemFresh,
|
|
225
|
+
loaderStateRef,
|
|
226
|
+
invalidate: useCallback(async (pathname = latestPathname.current) => {
|
|
227
|
+
if (typeof pathname !== "string") return;
|
|
228
|
+
const routeItem = routes.find((el) => comparePaths(el, pathname));
|
|
229
|
+
const resultParams = getParamsObject({
|
|
230
|
+
params: routeItem?.params,
|
|
231
|
+
pathname
|
|
232
|
+
});
|
|
233
|
+
timestampMapRef.current.delete(pathname);
|
|
234
|
+
try {
|
|
235
|
+
if (routeItem?.beforeLoad) await routeItem.beforeLoad({
|
|
236
|
+
context,
|
|
237
|
+
redirect: () => Promise.resolve(),
|
|
238
|
+
params: resultParams,
|
|
239
|
+
setContext
|
|
240
|
+
});
|
|
241
|
+
loaderStateRef.current = {
|
|
242
|
+
...loaderStateRef.current,
|
|
243
|
+
beforeLoadError: null
|
|
244
|
+
};
|
|
245
|
+
} catch (error) {
|
|
246
|
+
loaderStateRef.current = {
|
|
247
|
+
...loaderStateRef.current,
|
|
248
|
+
beforeLoadError: error
|
|
249
|
+
};
|
|
250
|
+
}
|
|
251
|
+
await revalidateCache({
|
|
252
|
+
routeItem,
|
|
253
|
+
pathname
|
|
254
|
+
});
|
|
255
|
+
if (pathname === latestPathname.current) setLoaderState(loaderStateRef.current);
|
|
256
|
+
}, [
|
|
257
|
+
context,
|
|
258
|
+
loaderStateRef,
|
|
259
|
+
revalidateCache,
|
|
260
|
+
latestPathname,
|
|
261
|
+
routes,
|
|
262
|
+
setContext,
|
|
263
|
+
setLoaderState
|
|
264
|
+
])
|
|
265
|
+
};
|
|
266
|
+
};
|
|
267
|
+
//#endregion
|
|
218
268
|
//#region hooks/useNavigation.ts
|
|
219
269
|
var ALL_LOCATIONS = "*";
|
|
220
|
-
var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef }) => {
|
|
270
|
+
var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef, isAnimated, showFallbackOnAnimation: showFallback }) => {
|
|
221
271
|
const [, setIsLoading] = useIsLoading();
|
|
222
272
|
const [, setScrollMap] = useScrollMap();
|
|
223
273
|
const [, setRouteItemData] = useRouteItemData();
|
|
@@ -225,7 +275,6 @@ var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef
|
|
|
225
275
|
const [, setCurrentLoaderFallback] = useLoaderFallback();
|
|
226
276
|
const [context, setContext] = useContextState();
|
|
227
277
|
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
228
|
-
const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
|
|
229
278
|
const prevPathname = useRef("");
|
|
230
279
|
const navigationSeq = useRef(0);
|
|
231
280
|
const navigation = useCallback((nextLocation, routeItem) => {
|
|
@@ -248,7 +297,7 @@ var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef
|
|
|
248
297
|
setRouteItemData
|
|
249
298
|
]);
|
|
250
299
|
const transitionedNavigation = useCallback((nextLocation, routeItem) => {
|
|
251
|
-
if (!isAnimated) {
|
|
300
|
+
if (!isAnimated || !prevPathname.current) {
|
|
252
301
|
navigation(nextLocation, routeItem);
|
|
253
302
|
return;
|
|
254
303
|
}
|
|
@@ -357,136 +406,21 @@ var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef
|
|
|
357
406
|
return updateLocation;
|
|
358
407
|
};
|
|
359
408
|
//#endregion
|
|
360
|
-
//#region hooks/
|
|
361
|
-
var
|
|
362
|
-
const [,
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
const loadingPromises = useRef(/* @__PURE__ */ new Map());
|
|
370
|
-
const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
|
|
371
|
-
if (!routeItem) return true;
|
|
372
|
-
const currentCacheTimestamp = timestampMapRef.current.get(pathname);
|
|
373
|
-
if (!currentCacheTimestamp) return false;
|
|
374
|
-
if (!routeItem.staleTime) return true;
|
|
375
|
-
return Date.now() - currentCacheTimestamp < routeItem.staleTime;
|
|
376
|
-
}, []);
|
|
377
|
-
const revalidateCache = useCallback(async ({ routeItem, pathname }) => {
|
|
378
|
-
if (!routeItem?.loader) return;
|
|
379
|
-
if (loadingPromises.current.has(pathname)) return loadingPromises.current.get(pathname);
|
|
380
|
-
if (isCacheItemFresh({
|
|
381
|
-
routeItem,
|
|
382
|
-
pathname
|
|
383
|
-
})) {
|
|
384
|
-
loaderStateRef.current = loaderMapRef.current[pathname];
|
|
385
|
-
return;
|
|
386
|
-
}
|
|
387
|
-
const promise = (async () => {
|
|
388
|
-
if (!routeItem?.loader) return;
|
|
389
|
-
try {
|
|
390
|
-
const params = getParamsObject({
|
|
391
|
-
params: routeItem.params,
|
|
392
|
-
pathname
|
|
393
|
-
});
|
|
394
|
-
const result = await routeItem?.loader({
|
|
395
|
-
params,
|
|
396
|
-
context,
|
|
397
|
-
setContext
|
|
398
|
-
});
|
|
399
|
-
timestampMapRef.current.set(pathname, Date.now());
|
|
400
|
-
loaderStateRef.current = {
|
|
401
|
-
...loaderStateRef?.current,
|
|
402
|
-
data: result,
|
|
403
|
-
loaderError: null
|
|
404
|
-
};
|
|
405
|
-
loaderMapRef.current[pathname] = loaderStateRef.current;
|
|
406
|
-
} catch (error) {
|
|
407
|
-
loaderStateRef.current = {
|
|
408
|
-
...loaderStateRef?.current,
|
|
409
|
-
data: null,
|
|
410
|
-
loaderError: error
|
|
411
|
-
};
|
|
412
|
-
} finally {
|
|
413
|
-
loadingPromises.current.delete(pathname);
|
|
414
|
-
}
|
|
415
|
-
})();
|
|
416
|
-
loadingPromises.current.set(pathname, promise);
|
|
417
|
-
return promise;
|
|
409
|
+
//#region hooks/useSetRuntime.ts
|
|
410
|
+
var useSetRouterRuntime = ({ updateLocation, prefetchLoader, invalidate }) => {
|
|
411
|
+
const [, setCallbackState] = useActionState();
|
|
412
|
+
useEffect(() => {
|
|
413
|
+
setCallbackState({
|
|
414
|
+
updateLocation,
|
|
415
|
+
prefetchLoader,
|
|
416
|
+
invalidate
|
|
417
|
+
});
|
|
418
418
|
}, [
|
|
419
|
-
context,
|
|
420
|
-
isCacheItemFresh,
|
|
421
|
-
setContext
|
|
422
|
-
]);
|
|
423
|
-
return {
|
|
424
|
-
prefetchLoader: useCallback(async (pathname) => {
|
|
425
|
-
const item = routes.find((el) => comparePaths(el, pathname));
|
|
426
|
-
if (item) await revalidateCache({
|
|
427
|
-
routeItem: item,
|
|
428
|
-
pathname
|
|
429
|
-
});
|
|
430
|
-
}, [revalidateCache, routes]),
|
|
431
|
-
revalidateCache,
|
|
432
|
-
isCacheItemFresh,
|
|
433
|
-
loaderStateRef,
|
|
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
|
-
});
|
|
441
|
-
timestampMapRef.current.delete(pathname);
|
|
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
|
-
])
|
|
473
|
-
};
|
|
474
|
-
};
|
|
475
|
-
//#endregion
|
|
476
|
-
//#region components/RouterProvider.tsx
|
|
477
|
-
var RouterProvider = ({ children, routes }) => {
|
|
478
|
-
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, invalidate } = useLoader(routes);
|
|
479
|
-
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
|
|
480
|
-
updateLocation: useNavigation({
|
|
481
|
-
routes,
|
|
482
|
-
revalidateCache,
|
|
483
|
-
isCacheItemFresh,
|
|
484
|
-
loaderStateRef
|
|
485
|
-
}),
|
|
486
419
|
invalidate,
|
|
487
420
|
prefetchLoader,
|
|
488
|
-
|
|
489
|
-
|
|
421
|
+
setCallbackState,
|
|
422
|
+
updateLocation
|
|
423
|
+
]);
|
|
490
424
|
};
|
|
491
425
|
//#endregion
|
|
492
426
|
//#region hooks/useApplyCustomAnimation.ts
|
|
@@ -549,6 +483,56 @@ var usePreserveScroll = (preserveScroll) => {
|
|
|
549
483
|
]);
|
|
550
484
|
};
|
|
551
485
|
//#endregion
|
|
486
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
|
|
487
|
+
function _typeof(o) {
|
|
488
|
+
"@babel/helpers - typeof";
|
|
489
|
+
return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
|
|
490
|
+
return typeof o;
|
|
491
|
+
} : function(o) {
|
|
492
|
+
return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
|
|
493
|
+
}, _typeof(o);
|
|
494
|
+
}
|
|
495
|
+
//#endregion
|
|
496
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
|
|
497
|
+
function toPrimitive(t, r) {
|
|
498
|
+
if ("object" != _typeof(t) || !t) return t;
|
|
499
|
+
var e = t[Symbol.toPrimitive];
|
|
500
|
+
if (void 0 !== e) {
|
|
501
|
+
var i = e.call(t, r || "default");
|
|
502
|
+
if ("object" != _typeof(i)) return i;
|
|
503
|
+
throw new TypeError("@@toPrimitive must return a primitive value.");
|
|
504
|
+
}
|
|
505
|
+
return ("string" === r ? String : Number)(t);
|
|
506
|
+
}
|
|
507
|
+
//#endregion
|
|
508
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
|
|
509
|
+
function toPropertyKey(t) {
|
|
510
|
+
var i = toPrimitive(t, "string");
|
|
511
|
+
return "symbol" == _typeof(i) ? i : i + "";
|
|
512
|
+
}
|
|
513
|
+
//#endregion
|
|
514
|
+
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
|
|
515
|
+
function _defineProperty(e, r, t) {
|
|
516
|
+
return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
|
|
517
|
+
value: t,
|
|
518
|
+
enumerable: !0,
|
|
519
|
+
configurable: !0,
|
|
520
|
+
writable: !0
|
|
521
|
+
}) : e[r] = t, e;
|
|
522
|
+
}
|
|
523
|
+
//#endregion
|
|
524
|
+
//#region config/routerConfig.ts
|
|
525
|
+
var RouterConfig = class {
|
|
526
|
+
constructor() {
|
|
527
|
+
_defineProperty(this, "prefetch", "hover");
|
|
528
|
+
_defineProperty(this, "hoverPrefetchDelay", 150);
|
|
529
|
+
}
|
|
530
|
+
configure(config) {
|
|
531
|
+
Object.assign(this, config);
|
|
532
|
+
}
|
|
533
|
+
};
|
|
534
|
+
var routerConfig = new RouterConfig();
|
|
535
|
+
//#endregion
|
|
552
536
|
//#region hooks/useSetRouterConfig.ts
|
|
553
537
|
var useSetRouterConfig = (routerProps) => {
|
|
554
538
|
useEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
@@ -573,20 +557,33 @@ var renderElement = (Component) => {
|
|
|
573
557
|
//#endregion
|
|
574
558
|
//#region components/Router.tsx
|
|
575
559
|
var EmptyBoundary = ({ children }) => children;
|
|
576
|
-
var Router = ({
|
|
560
|
+
var Router = ({ routes, animationDuration, isAnimated = false, spinner = true, preserveScroll = true, showFallbackOnAnimation = false, prefetch = "hover", hoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary, context: initialContext }) => {
|
|
577
561
|
const [isLoading] = useIsLoading();
|
|
578
562
|
const [currentLoaderFallback] = useLoaderFallback();
|
|
579
563
|
const [routeItemData] = useRouteItemData();
|
|
580
564
|
const [loaderState] = useCurrentLoaderState();
|
|
581
|
-
|
|
582
|
-
|
|
565
|
+
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, invalidate } = useLoader(routes);
|
|
566
|
+
useSetRouterRuntime({
|
|
567
|
+
updateLocation: useNavigation({
|
|
568
|
+
routes,
|
|
569
|
+
revalidateCache,
|
|
570
|
+
isCacheItemFresh,
|
|
571
|
+
loaderStateRef,
|
|
572
|
+
isAnimated,
|
|
573
|
+
showFallbackOnAnimation
|
|
574
|
+
}),
|
|
575
|
+
prefetchLoader,
|
|
576
|
+
invalidate
|
|
577
|
+
});
|
|
583
578
|
useSetRouterConfig({
|
|
584
579
|
isAnimated,
|
|
585
580
|
showFallbackOnAnimation,
|
|
586
581
|
prefetch,
|
|
587
582
|
hoverPrefetchDelay
|
|
588
583
|
});
|
|
584
|
+
useApplyCustomAnimation(animationDuration);
|
|
589
585
|
useSetInitialContext(initialContext);
|
|
586
|
+
usePreserveScroll(preserveScroll);
|
|
590
587
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
591
588
|
const showSpinner = spinner && isAnimated && isLoading;
|
|
592
589
|
const loadingContent = !showErrorElement && isLoading;
|
|
@@ -601,13 +598,20 @@ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll =
|
|
|
601
598
|
});
|
|
602
599
|
};
|
|
603
600
|
//#endregion
|
|
604
|
-
//#region hooks/
|
|
605
|
-
var
|
|
606
|
-
const
|
|
607
|
-
|
|
608
|
-
return
|
|
601
|
+
//#region hooks/useRuntime.ts
|
|
602
|
+
var useRuntime = () => {
|
|
603
|
+
const [callbackState] = useActionState();
|
|
604
|
+
const { updateLocation, prefetchLoader, invalidate } = callbackState;
|
|
605
|
+
return useMemo(() => ({
|
|
606
|
+
updateLocation,
|
|
607
|
+
prefetchLoader,
|
|
608
|
+
invalidate
|
|
609
|
+
}), [
|
|
610
|
+
updateLocation,
|
|
611
|
+
prefetchLoader,
|
|
612
|
+
invalidate
|
|
613
|
+
]);
|
|
609
614
|
};
|
|
610
|
-
var useRouterActions = () => useServiceState(ActionsContext);
|
|
611
615
|
//#endregion
|
|
612
616
|
//#region hooks/useLocation.ts
|
|
613
617
|
var useLocation = () => {
|
|
@@ -618,10 +622,11 @@ var useLocation = () => {
|
|
|
618
622
|
//#region hooks/useNavigate.ts
|
|
619
623
|
var useNavigate = () => {
|
|
620
624
|
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
621
|
-
const { updateLocation } =
|
|
625
|
+
const { updateLocation } = useRuntime();
|
|
622
626
|
const locationRef = useLatest(useLocation());
|
|
623
627
|
const blockedRouteRef = useLatest(blockedRoute);
|
|
624
628
|
return useCallback(async (arg) => {
|
|
629
|
+
if (!updateLocation) throw new Error("Router has not been initialized. Did you forget to render <Router />?");
|
|
625
630
|
if (arg !== -1 && blockedRouteRef.current.from) {
|
|
626
631
|
const to = typeof arg === "object" ? arg.pathname : arg;
|
|
627
632
|
setBlockedRoute((prevState) => ({
|
|
@@ -647,7 +652,7 @@ var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
|
647
652
|
const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
|
|
648
653
|
const prefetch = prefetchLink || configPrefetch;
|
|
649
654
|
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
650
|
-
const { prefetchLoader } =
|
|
655
|
+
const { prefetchLoader } = useRuntime();
|
|
651
656
|
const navigate = useNavigate();
|
|
652
657
|
const timeout = useRef(0);
|
|
653
658
|
const ref = useRef(null);
|
|
@@ -715,7 +720,7 @@ var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_js
|
|
|
715
720
|
//#endregion
|
|
716
721
|
//#region hooks/useInvalidate.ts
|
|
717
722
|
var useInvalidate = () => {
|
|
718
|
-
const { invalidate } =
|
|
723
|
+
const { invalidate } = useRuntime();
|
|
719
724
|
return invalidate;
|
|
720
725
|
};
|
|
721
726
|
//#endregion
|
|
@@ -788,7 +793,7 @@ var useLoaderState = () => {
|
|
|
788
793
|
//#region hooks/useBlocker.ts
|
|
789
794
|
var useBlocker = (blockerFn) => {
|
|
790
795
|
const [blockedRoute, setBlockedRoute] = useBlockedRoute();
|
|
791
|
-
const { updateLocation } =
|
|
796
|
+
const { updateLocation } = useRuntime();
|
|
792
797
|
const [routeItemData] = useRouteItemData();
|
|
793
798
|
const { location: { pathname } } = routeItemData;
|
|
794
799
|
const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
|
|
@@ -1009,4 +1014,4 @@ var adapter = {
|
|
|
1009
1014
|
})
|
|
1010
1015
|
};
|
|
1011
1016
|
//#endregion
|
|
1012
|
-
export { Form, Link, Router,
|
|
1017
|
+
export { Form, Link, Router, adapter, createRouter, useAction, useBlocker, useFormContext, useInvalidate, useLoaderState, useLocation, useNavigate, useParams, useQueryParam, useRouterContext, useSearchParams };
|
package/dist/state/state.d.ts
CHANGED
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import { LoaderState, RouteItemData } from '../types/global';
|
|
1
|
+
import { LoaderState, Location, RouteItemData } from '../types/global';
|
|
2
|
+
type Runtime = {
|
|
3
|
+
updateLocation(route: Location): Promise<void>;
|
|
4
|
+
prefetchLoader(arg: string): Promise<void>;
|
|
5
|
+
invalidate(path?: string): Promise<void>;
|
|
6
|
+
};
|
|
2
7
|
export declare const useIsLoading: () => readonly [boolean, (action: boolean | ((prevState: boolean) => boolean)) => void];
|
|
3
8
|
export declare const useBlockedRoute: () => readonly [{
|
|
4
9
|
from: string;
|
|
@@ -18,3 +23,5 @@ export declare const useRouteItemData: () => readonly [RouteItemData, (action: R
|
|
|
18
23
|
export declare const useCurrentLoaderState: () => readonly [LoaderState, (action: LoaderState | ((prevState: LoaderState) => LoaderState)) => void];
|
|
19
24
|
export declare const useScrollMap: () => readonly [Record<string, number>, (action: Record<string, number> | ((prevState: Record<string, number>) => Record<string, number>)) => void];
|
|
20
25
|
export declare const useContextState: () => readonly [Record<string, unknown>, (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void];
|
|
26
|
+
export declare const useActionState: () => readonly [Runtime, (action: Runtime | ((prevState: Runtime) => Runtime)) => void];
|
|
27
|
+
export {};
|
package/dist/types/global.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
import { ReactNode } from 'react';
|
|
2
|
-
import { RouteItem } from '../types/global';
|
|
3
|
-
type RouteProviderProps = {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
routes: RouteItem[];
|
|
6
|
-
};
|
|
7
|
-
export declare const RouterProvider: ({ children, routes }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
8
|
-
export {};
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { Location } from '../types/global';
|
|
2
|
-
export type ActionsContextValue = {
|
|
3
|
-
updateLocation(route: Location): Promise<void>;
|
|
4
|
-
prefetchLoader(arg: string): Promise<void>;
|
|
5
|
-
invalidate(path?: string): Promise<void>;
|
|
6
|
-
};
|
|
7
|
-
export declare const ActionsContext: import("react").Context<ActionsContextValue>;
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const useRouterActions: () => import("../context/RouterProviderContext").ActionsContextValue;
|
|
@@ -1,7 +0,0 @@
|
|
|
1
|
-
import { type ReactNode } from 'react';
|
|
2
|
-
import { type ActionsContextValue } from '../context/RouterProviderContext';
|
|
3
|
-
type ProviderProps = ActionsContextValue & {
|
|
4
|
-
children: ReactNode;
|
|
5
|
-
};
|
|
6
|
-
export declare const Provider: ({ children, updateLocation, prefetchLoader, invalidate }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
|
-
export {};
|