clear-react-router 1.5.8 → 1.6.0
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 +31 -9
- package/dist/components/Link.d.ts +4 -3
- package/dist/components/Router.d.ts +2 -9
- package/dist/config/routerConfig.d.ts +3 -0
- package/dist/constants.d.ts +3 -0
- package/dist/context/RouterProviderContext.d.ts +2 -1
- package/dist/hooks/useHandleNavigation.d.ts +7 -5
- package/dist/hooks/useLoader.d.ts +3 -4
- package/dist/hooks/useSetRouterConfig.d.ts +2 -0
- package/dist/index.js +152 -102
- package/dist/provider/Provider.d.ts +1 -1
- package/dist/types/global.d.ts +10 -3
- package/package.json +1 -1
- package/dist/hooks/useSetIsAnimated.d.ts +0 -1
package/README.md
CHANGED
|
@@ -72,6 +72,8 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
|
|
|
72
72
|
| `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
|
|
73
73
|
| `preserveScroll` | `boolean \| undefined` | `true` | Save and restore scroll position when navigating between pages |
|
|
74
74
|
| `showFallbackIfAnimated` | `boolean \| undefined` | `false` | Show `loaderFallback` even when `isAnimated` is `true` (instead of spinner) |
|
|
75
|
+
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `'hover'` | Default prefetch strategy for all `<Link>` components |
|
|
76
|
+
| `hoverPrefetchDelay` | `number` | `150` | Delay in milliseconds before prefetching on hover (only for `'hover'` strategy) |
|
|
75
77
|
|
|
76
78
|
```
|
|
77
79
|
<RouterProvider routeList={routes} isAnimated>
|
|
@@ -89,20 +91,40 @@ Component for client-side navigation with prefetch support.
|
|
|
89
91
|
| Prop | Type | Default |
|
|
90
92
|
|------|------|---------|
|
|
91
93
|
| `to` | `string` | required |
|
|
92
|
-
| `prefetch` | `
|
|
93
|
-
| `
|
|
94
|
-
| `children` | `ReactElement` | required |
|
|
94
|
+
| `prefetch` | `'hover' \| 'render' \| 'viewport' \| 'none'` | `Router` config | Override the global prefetch strategy |
|
|
95
|
+
| `hoverPrefetchDelay` | `number` | `Router` config | Override the global hover delay |
|
|
96
|
+
| `children` | `ReactElement` | required | Content to render inside the link |
|
|
97
|
+
|
|
98
|
+
### Prefetch Strategies
|
|
99
|
+
|
|
100
|
+
| Strategy | Behavior |
|
|
101
|
+
|----------|----------|
|
|
102
|
+
| `'hover'` | Prefetches when the user hovers over the link (with configurable delay) |
|
|
103
|
+
| `'render'` | Prefetches immediately when the link is rendered |
|
|
104
|
+
| `'viewport'` | Prefetches when the link enters the viewport (using Intersection Observer) |
|
|
105
|
+
| `'none'` | No prefetching |
|
|
106
|
+
|
|
107
|
+
**Example:**
|
|
95
108
|
|
|
96
109
|
```
|
|
97
|
-
|
|
98
|
-
|
|
110
|
+
import { RouterProvider, Router, Link } from 'clear-react-router';
|
|
111
|
+
|
|
112
|
+
// Global prefetch: hover with 100ms delay
|
|
113
|
+
<RouterProvider routeList={routes} prefetch="hover" hoverPrefetchDelay={100}>
|
|
114
|
+
<Router />
|
|
115
|
+
</RouterProvider>
|
|
99
116
|
|
|
100
|
-
//
|
|
101
|
-
<Link to="/heavy-page"
|
|
117
|
+
// Override for a specific link
|
|
118
|
+
<Link to="/heavy-page" prefetch="viewport">
|
|
119
|
+
Heavy Page
|
|
120
|
+
</Link>
|
|
102
121
|
|
|
103
|
-
// Disable prefetch
|
|
104
|
-
<Link to="/
|
|
122
|
+
// Disable prefetch for a specific link
|
|
123
|
+
<Link to="/admin" prefetch="none">
|
|
124
|
+
Admin Panel
|
|
125
|
+
</Link>
|
|
105
126
|
```
|
|
127
|
+
**Important**: prefetch="render" should be used sparingly, as it preloads data immediately when the link is rendered, which may cause unnecessary network requests.
|
|
106
128
|
|
|
107
129
|
### `redirect`
|
|
108
130
|
|
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { type ReactElement, type MouseEvent, type CSSProperties } from 'react';
|
|
2
|
+
import { RouterProps } from '../types/global';
|
|
2
3
|
type LinkProps = {
|
|
3
4
|
to: string;
|
|
4
5
|
children: ReactElement<{
|
|
5
6
|
onClick: (e: MouseEvent) => void;
|
|
6
7
|
style: CSSProperties;
|
|
7
8
|
}>;
|
|
8
|
-
prefetch?:
|
|
9
|
-
|
|
9
|
+
prefetch?: RouterProps['prefetch'];
|
|
10
|
+
hoverPrefetchDelay?: number;
|
|
10
11
|
};
|
|
11
|
-
export declare const Link: ({ children, to, prefetch,
|
|
12
|
+
export declare const Link: ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }: LinkProps) => import("react/jsx-runtime").JSX.Element;
|
|
12
13
|
export {};
|
|
@@ -1,9 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
animationDuration?: number;
|
|
4
|
-
spinner?: boolean;
|
|
5
|
-
preserveScroll?: boolean;
|
|
6
|
-
showFallbackIfAnimated?: boolean;
|
|
7
|
-
};
|
|
8
|
-
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackIfAnimated, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
9
|
-
export {};
|
|
1
|
+
import { RouterProps } from '../types/global';
|
|
2
|
+
export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackIfAnimated, prefetch, hoverPrefetchDelay, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -1,6 +1,9 @@
|
|
|
1
|
+
import { RouterProps } from '../types/global';
|
|
1
2
|
declare class RouterConfig {
|
|
2
3
|
isAnimated: boolean;
|
|
3
4
|
showFallbackIfAnimated: boolean;
|
|
5
|
+
prefetch: RouterProps['prefetch'];
|
|
6
|
+
hoverPrefetchDelay: number;
|
|
4
7
|
configure(config: Partial<RouterConfig>): void;
|
|
5
8
|
}
|
|
6
9
|
export declare const routerConfig: RouterConfig;
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import { BlockerState, Location, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
1
|
+
import { BlockerState, LoaderState, Location, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
2
2
|
export type NavigationContextValue = {
|
|
3
3
|
blockerState: BlockerState;
|
|
4
4
|
routeItemData: RouteItemData;
|
|
5
5
|
currentLoaderFallback: RouteItem['loaderFallback'];
|
|
6
6
|
isLoading: boolean;
|
|
7
|
+
loaderState: LoaderState;
|
|
7
8
|
};
|
|
8
9
|
export type ActionsContextValue = {
|
|
9
10
|
updateLocation(route: Location): Promise<void>;
|
|
@@ -1,22 +1,24 @@
|
|
|
1
|
-
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
-
import { BlockerState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
1
|
+
import { type Dispatch, RefObject, type SetStateAction } from 'react';
|
|
2
|
+
import { BlockerState, LoaderState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
|
|
3
3
|
type UseHandleNavigation = {
|
|
4
4
|
routeList: RouteItem[];
|
|
5
5
|
context: Record<string, unknown>;
|
|
6
|
-
revalidateCache(arg: RevalidateCacheArgs): Promise<
|
|
6
|
+
revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
|
|
7
7
|
setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
|
|
8
8
|
isCacheItemFresh(arg: {
|
|
9
9
|
routeItem?: RouteItem;
|
|
10
10
|
pathname: string;
|
|
11
11
|
}): boolean;
|
|
12
|
-
|
|
12
|
+
loaderStateRef: RefObject<LoaderState>;
|
|
13
13
|
};
|
|
14
|
-
export declare const useHandleNavigation: ({ routeList, context, revalidateCache, setContext, isCacheItemFresh,
|
|
14
|
+
export declare const useHandleNavigation: ({ routeList, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef, }: UseHandleNavigation) => {
|
|
15
15
|
blockerState: BlockerState;
|
|
16
16
|
updateLocation: (nextLocation: Location) => Promise<void>;
|
|
17
17
|
updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
|
|
18
18
|
routeItemData: RouteItemData;
|
|
19
19
|
restoreScroll: () => void;
|
|
20
20
|
currentLoaderFallback: import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement) | undefined;
|
|
21
|
+
isLoading: boolean;
|
|
22
|
+
loaderState: LoaderState;
|
|
21
23
|
};
|
|
22
24
|
export {};
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type Dispatch, type SetStateAction } from 'react';
|
|
2
|
-
import type { RevalidateCacheArgs, RouteItem } from '../types/global';
|
|
2
|
+
import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
|
|
3
3
|
type UseLoaderParams = {
|
|
4
4
|
routeList: RouteItem[];
|
|
5
5
|
context: Record<string, unknown>;
|
|
@@ -7,12 +7,11 @@ type UseLoaderParams = {
|
|
|
7
7
|
};
|
|
8
8
|
export declare const useLoader: ({ routeList, context, setContext }: UseLoaderParams) => {
|
|
9
9
|
prefetchLoader: (pathname: string) => Promise<void>;
|
|
10
|
-
revalidateCache: ({ routeItem,
|
|
10
|
+
revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown>;
|
|
11
11
|
isCacheItemFresh: ({ routeItem, pathname }: {
|
|
12
12
|
routeItem?: RouteItem;
|
|
13
13
|
pathname: string;
|
|
14
14
|
}) => boolean;
|
|
15
|
-
|
|
16
|
-
setIsLoading: Dispatch<SetStateAction<boolean>>;
|
|
15
|
+
loaderStateRef: import("react").RefObject<LoaderState>;
|
|
17
16
|
};
|
|
18
17
|
export {};
|
package/dist/index.js
CHANGED
|
@@ -45,7 +45,7 @@ var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((expor
|
|
|
45
45
|
var import_jsx_runtime = (/* @__PURE__ */ __commonJSMin(((exports, module) => {
|
|
46
46
|
module.exports = require_react_jsx_runtime_production();
|
|
47
47
|
})))();
|
|
48
|
-
var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading }) => {
|
|
48
|
+
var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState }) => {
|
|
49
49
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
|
|
50
50
|
value: {
|
|
51
51
|
updateLocation,
|
|
@@ -61,7 +61,8 @@ var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocat
|
|
|
61
61
|
blockerState,
|
|
62
62
|
routeItemData,
|
|
63
63
|
currentLoaderFallback,
|
|
64
|
-
isLoading
|
|
64
|
+
isLoading,
|
|
65
|
+
loaderState
|
|
65
66
|
},
|
|
66
67
|
children
|
|
67
68
|
})
|
|
@@ -78,6 +79,9 @@ var useLatest = (value) => {
|
|
|
78
79
|
return ref;
|
|
79
80
|
};
|
|
80
81
|
//#endregion
|
|
82
|
+
//#region constants.ts
|
|
83
|
+
var emptyLoaderState = {};
|
|
84
|
+
//#endregion
|
|
81
85
|
//#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
|
|
82
86
|
function _typeof(o) {
|
|
83
87
|
"@babel/helpers - typeof";
|
|
@@ -121,6 +125,8 @@ var RouterConfig = class {
|
|
|
121
125
|
constructor() {
|
|
122
126
|
_defineProperty(this, "isAnimated", false);
|
|
123
127
|
_defineProperty(this, "showFallbackIfAnimated", false);
|
|
128
|
+
_defineProperty(this, "prefetch", "hover");
|
|
129
|
+
_defineProperty(this, "hoverPrefetchDelay", 150);
|
|
124
130
|
}
|
|
125
131
|
configure(config) {
|
|
126
132
|
Object.assign(this, config);
|
|
@@ -181,20 +187,20 @@ var comparePaths = (el, pathname) => {
|
|
|
181
187
|
//#endregion
|
|
182
188
|
//#region hooks/useHandleNavigation.ts
|
|
183
189
|
var ALL_LOCATIONS = "*";
|
|
184
|
-
var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, isCacheItemFresh,
|
|
190
|
+
var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, isCacheItemFresh, loaderStateRef }) => {
|
|
185
191
|
const { isAnimated, showFallbackIfAnimated: showFallback } = routerConfig;
|
|
192
|
+
const [isLoading, setIsLoading] = useState(false);
|
|
186
193
|
const [blockedRoute, setBlockedRoute] = useState({
|
|
187
194
|
from: "",
|
|
188
195
|
to: ""
|
|
189
196
|
});
|
|
190
197
|
const [routeItemData, setRouteItemData] = useState({
|
|
191
|
-
location: {},
|
|
192
198
|
routeItem: void 0,
|
|
193
|
-
|
|
199
|
+
location: {}
|
|
194
200
|
});
|
|
195
201
|
const [scrollMap, setScrollMap] = useState({});
|
|
196
202
|
const [currentLoaderFallback, setCurrentLoaderFallback] = useState();
|
|
197
|
-
const loaderState =
|
|
203
|
+
const [loaderState, setLoaderState] = useState(emptyLoaderState);
|
|
198
204
|
const prevPathname = useRef("");
|
|
199
205
|
const navigationSeq = useRef(0);
|
|
200
206
|
const scrollMapRef = useLatest(scrollMap);
|
|
@@ -209,17 +215,17 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
209
215
|
}, [scrollMapRef]);
|
|
210
216
|
const navigation = useCallback((nextLocation, routeItem) => {
|
|
211
217
|
setRouteItemData({
|
|
212
|
-
location: nextLocation,
|
|
213
218
|
routeItem,
|
|
214
|
-
|
|
219
|
+
location: nextLocation
|
|
215
220
|
});
|
|
221
|
+
setLoaderState(loaderStateRef.current);
|
|
216
222
|
setIsLoading(false);
|
|
217
223
|
setCurrentLoaderFallback(void 0);
|
|
218
224
|
prevPathname.current = nextLocation.pathname;
|
|
219
225
|
const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
|
|
220
226
|
if (fullPath === window.location.pathname + window.location.search) return;
|
|
221
227
|
history.pushState(null, "", fullPath);
|
|
222
|
-
}, []);
|
|
228
|
+
}, [loaderStateRef]);
|
|
223
229
|
const transitionedNavigation = useCallback((nextLocation, routeItem) => {
|
|
224
230
|
if (!isAnimated) {
|
|
225
231
|
navigation(nextLocation, routeItem);
|
|
@@ -234,7 +240,7 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
234
240
|
const navigationHandler = useCallback(async (nextLocation) => {
|
|
235
241
|
navigationSeq.current = navigationSeq.current + 1;
|
|
236
242
|
const seq = navigationSeq.current;
|
|
237
|
-
|
|
243
|
+
loaderStateRef.current = emptyLoaderState;
|
|
238
244
|
const nextItem = routeList.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, nextLocation.pathname));
|
|
239
245
|
const params = getParamsObject({
|
|
240
246
|
params: nextItem?.params,
|
|
@@ -248,13 +254,13 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
248
254
|
params,
|
|
249
255
|
setContext
|
|
250
256
|
});
|
|
251
|
-
|
|
252
|
-
...
|
|
257
|
+
loaderStateRef.current = {
|
|
258
|
+
...loaderStateRef.current,
|
|
253
259
|
beforeLoadError: null
|
|
254
260
|
};
|
|
255
261
|
} catch (error) {
|
|
256
|
-
|
|
257
|
-
...
|
|
262
|
+
loaderStateRef.current = {
|
|
263
|
+
...loaderStateRef.current,
|
|
258
264
|
beforeLoadError: error
|
|
259
265
|
};
|
|
260
266
|
return transitionedNavigation(nextLocation, nextItem);
|
|
@@ -272,11 +278,13 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
272
278
|
routeItem: nextItem,
|
|
273
279
|
pathname: nextLocation.pathname
|
|
274
280
|
}) || isAnimated && !showFallback ? void 0 : nextItem?.loaderFallback);
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
281
|
+
if (nextItem?.loader) {
|
|
282
|
+
setIsLoading(true);
|
|
283
|
+
await revalidateCache({
|
|
284
|
+
routeItem: nextItem,
|
|
285
|
+
pathname: nextLocation.pathname
|
|
286
|
+
});
|
|
287
|
+
}
|
|
280
288
|
if (seq !== navigationSeq.current) return;
|
|
281
289
|
transitionedNavigation(nextLocation, nextItem);
|
|
282
290
|
if (nextItem?.afterLoad) await nextItem.afterLoad({
|
|
@@ -292,7 +300,8 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
292
300
|
setContext,
|
|
293
301
|
isCacheItemFresh,
|
|
294
302
|
isAnimated,
|
|
295
|
-
showFallback
|
|
303
|
+
showFallback,
|
|
304
|
+
loaderStateRef
|
|
296
305
|
]);
|
|
297
306
|
const setNextLocationRef = useLatest(navigationHandler);
|
|
298
307
|
const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
|
|
@@ -348,67 +357,73 @@ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, is
|
|
|
348
357
|
updateBlockedRoute,
|
|
349
358
|
routeItemData,
|
|
350
359
|
restoreScroll,
|
|
351
|
-
currentLoaderFallback
|
|
360
|
+
currentLoaderFallback,
|
|
361
|
+
isLoading,
|
|
362
|
+
loaderState
|
|
352
363
|
};
|
|
353
364
|
};
|
|
354
365
|
//#endregion
|
|
355
366
|
//#region hooks/useLoader.ts
|
|
356
367
|
var useLoader = ({ routeList, context, setContext }) => {
|
|
357
|
-
const
|
|
358
|
-
const
|
|
359
|
-
const
|
|
368
|
+
const timestampMapRef = useRef({});
|
|
369
|
+
const loaderMapRef = useRef({});
|
|
370
|
+
const loaderStateRef = useRef(emptyLoaderState);
|
|
371
|
+
const loadingPromises = useRef(/* @__PURE__ */ new Map());
|
|
360
372
|
const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
|
|
361
373
|
if (!routeItem) return true;
|
|
362
|
-
const currentCacheTimestamp =
|
|
374
|
+
const currentCacheTimestamp = timestampMapRef.current[pathname];
|
|
363
375
|
if (!currentCacheTimestamp) return false;
|
|
364
376
|
if (!routeItem.staleTime) return true;
|
|
365
377
|
return Date.now() - currentCacheTimestamp < routeItem.staleTime;
|
|
366
378
|
}, []);
|
|
367
|
-
const revalidateCache = useCallback(async ({ routeItem,
|
|
368
|
-
if (!routeItem?.loader)
|
|
369
|
-
|
|
370
|
-
if (!routeItem?.beforeLoad) loaderState.current = {};
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
379
|
+
const revalidateCache = useCallback(async ({ routeItem, pathname }) => {
|
|
380
|
+
if (!routeItem?.loader) return;
|
|
381
|
+
if (loadingPromises.current.has(pathname)) return loadingPromises.current.get(pathname);
|
|
373
382
|
if (isCacheItemFresh({
|
|
374
383
|
routeItem,
|
|
375
384
|
pathname
|
|
376
385
|
})) {
|
|
377
|
-
|
|
386
|
+
loaderStateRef.current = loaderMapRef.current[pathname];
|
|
378
387
|
return;
|
|
379
388
|
}
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
params
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
389
|
+
const promise = (async () => {
|
|
390
|
+
if (!routeItem?.loader) return;
|
|
391
|
+
try {
|
|
392
|
+
const params = getParamsObject({
|
|
393
|
+
params: routeItem.params,
|
|
394
|
+
pathname
|
|
395
|
+
});
|
|
396
|
+
const result = await routeItem?.loader({
|
|
397
|
+
params,
|
|
398
|
+
context,
|
|
399
|
+
setContext
|
|
400
|
+
});
|
|
401
|
+
timestampMapRef.current = {
|
|
402
|
+
...timestampMapRef.current,
|
|
403
|
+
[pathname]: Date.now()
|
|
404
|
+
};
|
|
405
|
+
loaderMapRef.current[pathname] = {
|
|
406
|
+
data: result,
|
|
407
|
+
loaderError: null,
|
|
408
|
+
beforeLoadError: null
|
|
409
|
+
};
|
|
410
|
+
loaderStateRef.current = {
|
|
411
|
+
...loaderStateRef?.current,
|
|
412
|
+
data: result,
|
|
413
|
+
loaderError: null
|
|
414
|
+
};
|
|
415
|
+
} catch (error) {
|
|
416
|
+
loaderStateRef.current = {
|
|
417
|
+
...loaderStateRef?.current,
|
|
418
|
+
data: null,
|
|
419
|
+
loaderError: error
|
|
420
|
+
};
|
|
421
|
+
} finally {
|
|
422
|
+
loadingPromises.current.delete(pathname);
|
|
423
|
+
}
|
|
424
|
+
})();
|
|
425
|
+
loadingPromises.current.set(pathname, promise);
|
|
426
|
+
return promise;
|
|
412
427
|
}, [
|
|
413
428
|
context,
|
|
414
429
|
isCacheItemFresh,
|
|
@@ -424,26 +439,25 @@ var useLoader = ({ routeList, context, setContext }) => {
|
|
|
424
439
|
}, [revalidateCache, routeList]),
|
|
425
440
|
revalidateCache,
|
|
426
441
|
isCacheItemFresh,
|
|
427
|
-
|
|
428
|
-
setIsLoading
|
|
442
|
+
loaderStateRef
|
|
429
443
|
};
|
|
430
444
|
};
|
|
431
445
|
//#endregion
|
|
432
446
|
//#region components/RouterProvider.tsx
|
|
433
447
|
var RouterProvider = ({ children, routeList, context: initialContext = {} }) => {
|
|
434
448
|
const [context, setContext] = useState(initialContext);
|
|
435
|
-
const { prefetchLoader, revalidateCache, isCacheItemFresh,
|
|
449
|
+
const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef } = useLoader({
|
|
436
450
|
routeList,
|
|
437
451
|
context,
|
|
438
452
|
setContext
|
|
439
453
|
});
|
|
440
|
-
const { blockerState, updateLocation, updateBlockedRoute, routeItemData, restoreScroll, currentLoaderFallback } = useHandleNavigation({
|
|
454
|
+
const { blockerState, updateLocation, updateBlockedRoute, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState } = useHandleNavigation({
|
|
441
455
|
routeList,
|
|
442
456
|
context,
|
|
443
457
|
setContext,
|
|
444
458
|
revalidateCache,
|
|
445
459
|
isCacheItemFresh,
|
|
446
|
-
|
|
460
|
+
loaderStateRef
|
|
447
461
|
});
|
|
448
462
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
|
|
449
463
|
...useMemo(() => ({
|
|
@@ -456,7 +470,8 @@ var RouterProvider = ({ children, routeList, context: initialContext = {} }) =>
|
|
|
456
470
|
routeItemData,
|
|
457
471
|
restoreScroll,
|
|
458
472
|
currentLoaderFallback,
|
|
459
|
-
isLoading
|
|
473
|
+
isLoading,
|
|
474
|
+
loaderState
|
|
460
475
|
}), [
|
|
461
476
|
blockerState,
|
|
462
477
|
context,
|
|
@@ -466,7 +481,8 @@ var RouterProvider = ({ children, routeList, context: initialContext = {} }) =>
|
|
|
466
481
|
updateLocation,
|
|
467
482
|
currentLoaderFallback,
|
|
468
483
|
restoreScroll,
|
|
469
|
-
isLoading
|
|
484
|
+
isLoading,
|
|
485
|
+
loaderState
|
|
470
486
|
]),
|
|
471
487
|
children
|
|
472
488
|
});
|
|
@@ -532,14 +548,6 @@ var usePreserveScroll = (preserveScroll) => {
|
|
|
532
548
|
]);
|
|
533
549
|
};
|
|
534
550
|
//#endregion
|
|
535
|
-
//#region hooks/useSetIsAnimated.ts
|
|
536
|
-
var useSetIsAnimated = (isAnimated = false, showFallbackIfAnimated) => {
|
|
537
|
-
useEffect(() => routerConfig.configure({
|
|
538
|
-
isAnimated,
|
|
539
|
-
showFallbackIfAnimated
|
|
540
|
-
}), [isAnimated]);
|
|
541
|
-
};
|
|
542
|
-
//#endregion
|
|
543
551
|
//#region components/Spinner.tsx
|
|
544
552
|
var Spinner = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cr-spinner" });
|
|
545
553
|
//#endregion
|
|
@@ -549,12 +557,22 @@ var renderElement = (Component) => {
|
|
|
549
557
|
return typeof Component === "function" ? /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Component, {}) : Component;
|
|
550
558
|
};
|
|
551
559
|
//#endregion
|
|
560
|
+
//#region hooks/useSetRouterConfig.ts
|
|
561
|
+
var useSetRouterConfig = (routerProps) => {
|
|
562
|
+
useEffect(() => routerConfig.configure(routerProps), [routerProps]);
|
|
563
|
+
};
|
|
564
|
+
//#endregion
|
|
552
565
|
//#region components/Router.tsx
|
|
553
|
-
var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackIfAnimated = false }) => {
|
|
554
|
-
const { routeItemData: { routeItem, loaderState
|
|
566
|
+
var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackIfAnimated = false, prefetch = "hover", hoverPrefetchDelay = 150 }) => {
|
|
567
|
+
const { routeItemData: { routeItem }, loaderState, currentLoaderFallback, isLoading } = useNavigationState();
|
|
555
568
|
usePreserveScroll(preserveScroll);
|
|
556
569
|
useApplyCustomAnimation(animationDuration);
|
|
557
|
-
|
|
570
|
+
useSetRouterConfig({
|
|
571
|
+
isAnimated,
|
|
572
|
+
showFallbackIfAnimated,
|
|
573
|
+
prefetch,
|
|
574
|
+
hoverPrefetchDelay
|
|
575
|
+
});
|
|
558
576
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
559
577
|
const showSpinner = spinner && isAnimated && isLoading;
|
|
560
578
|
const loadingContent = !showErrorElement && isLoading;
|
|
@@ -587,31 +605,63 @@ var useNavigate = () => {
|
|
|
587
605
|
};
|
|
588
606
|
//#endregion
|
|
589
607
|
//#region components/Link.tsx
|
|
590
|
-
var
|
|
591
|
-
|
|
608
|
+
var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
|
|
609
|
+
const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
|
|
610
|
+
const prefetch = prefetchLink || configPrefetch;
|
|
611
|
+
const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
|
|
592
612
|
const { prefetchLoader } = useRouterActions();
|
|
593
613
|
const navigate = useNavigate();
|
|
594
614
|
const timeout = useRef(0);
|
|
615
|
+
const ref = useRef(null);
|
|
616
|
+
const onMouseEnter = useCallback(() => {
|
|
617
|
+
if (prefetch !== "hover" || !prefetchDelay) return;
|
|
618
|
+
if (timeout.current) clearTimeout(timeout.current);
|
|
619
|
+
timeout.current = window.setTimeout(() => prefetchLoader(to), prefetchDelay);
|
|
620
|
+
}, [
|
|
621
|
+
prefetch,
|
|
622
|
+
prefetchDelay,
|
|
623
|
+
prefetchLoader,
|
|
624
|
+
to
|
|
625
|
+
]);
|
|
626
|
+
const onMouseLeave = useCallback(() => {
|
|
627
|
+
if (prefetch !== "hover" || !prefetchDelay) return;
|
|
628
|
+
if (timeout.current) {
|
|
629
|
+
clearTimeout(timeout.current);
|
|
630
|
+
timeout.current = 0;
|
|
631
|
+
}
|
|
632
|
+
}, [prefetch, prefetchDelay]);
|
|
633
|
+
useEffect(() => {
|
|
634
|
+
if (prefetch !== "render") return;
|
|
635
|
+
(async () => {
|
|
636
|
+
await prefetchLoader(to);
|
|
637
|
+
})();
|
|
638
|
+
}, [
|
|
639
|
+
prefetch,
|
|
640
|
+
prefetchLoader,
|
|
641
|
+
to
|
|
642
|
+
]);
|
|
643
|
+
useEffect(() => {
|
|
644
|
+
if (prefetch !== "viewport") return;
|
|
645
|
+
const element = ref.current;
|
|
646
|
+
const observer = new IntersectionObserver(async () => {
|
|
647
|
+
await prefetchLoader(to);
|
|
648
|
+
observer.disconnect();
|
|
649
|
+
});
|
|
650
|
+
if (element) observer.observe(element);
|
|
651
|
+
return () => {
|
|
652
|
+
if (element) observer.disconnect();
|
|
653
|
+
};
|
|
654
|
+
}, [
|
|
655
|
+
prefetch,
|
|
656
|
+
prefetchLoader,
|
|
657
|
+
to
|
|
658
|
+
]);
|
|
595
659
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
|
|
660
|
+
ref,
|
|
596
661
|
style: { cursor: "pointer" },
|
|
597
662
|
onClick: () => navigate(to),
|
|
598
|
-
onMouseEnter
|
|
599
|
-
|
|
600
|
-
if (timeout.current) clearTimeout(timeout.current);
|
|
601
|
-
timeout.current = window.setTimeout(() => prefetchLoader(to), prefetchDelay);
|
|
602
|
-
}, [
|
|
603
|
-
prefetch,
|
|
604
|
-
prefetchDelay,
|
|
605
|
-
prefetchLoader,
|
|
606
|
-
to
|
|
607
|
-
]),
|
|
608
|
-
onMouseLeave: useCallback(() => {
|
|
609
|
-
if (!prefetch || !prefetchDelay) return;
|
|
610
|
-
if (timeout.current) {
|
|
611
|
-
clearTimeout(timeout.current);
|
|
612
|
-
timeout.current = 0;
|
|
613
|
-
}
|
|
614
|
-
}, [prefetch, prefetchDelay]),
|
|
663
|
+
onMouseEnter,
|
|
664
|
+
onMouseLeave,
|
|
615
665
|
children
|
|
616
666
|
});
|
|
617
667
|
};
|
|
@@ -628,7 +678,7 @@ var useParams = () => {
|
|
|
628
678
|
//#endregion
|
|
629
679
|
//#region hooks/useLoaderState.ts
|
|
630
680
|
var useLoaderState = () => {
|
|
631
|
-
const {
|
|
681
|
+
const { loaderState } = useNavigationState();
|
|
632
682
|
return loaderState;
|
|
633
683
|
};
|
|
634
684
|
//#endregion
|
|
@@ -3,5 +3,5 @@ import { type ActionsContextValue, type DataContextValue, type NavigationContext
|
|
|
3
3
|
type ProviderProps = NavigationContextValue & ActionsContextValue & DataContextValue & {
|
|
4
4
|
children: ReactNode;
|
|
5
5
|
};
|
|
6
|
-
export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
6
|
+
export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, prefetchLoader, blockerState, routeItemData, restoreScroll, currentLoaderFallback, isLoading, loaderState, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
|
|
7
7
|
export {};
|
package/dist/types/global.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ComponentType, Dispatch, ReactElement,
|
|
1
|
+
import type { ComponentType, Dispatch, ReactElement, SetStateAction } from 'react';
|
|
2
2
|
export type LazyComponent = () => Promise<{
|
|
3
3
|
default: ComponentType<unknown>;
|
|
4
4
|
}>;
|
|
@@ -48,7 +48,6 @@ export type UpdateBlockedRouteProps = {
|
|
|
48
48
|
export type RevalidateCacheArgs = {
|
|
49
49
|
pathname: string;
|
|
50
50
|
routeItem?: RouteItem;
|
|
51
|
-
loaderState?: RefObject<LoaderState>;
|
|
52
51
|
};
|
|
53
52
|
export type LoaderState = {
|
|
54
53
|
data: unknown;
|
|
@@ -62,5 +61,13 @@ export type AdapterType<T> = {
|
|
|
62
61
|
export type RouteItemData = {
|
|
63
62
|
location: Location;
|
|
64
63
|
routeItem: RouteItem | undefined;
|
|
65
|
-
|
|
64
|
+
};
|
|
65
|
+
export type RouterProps = {
|
|
66
|
+
isAnimated?: boolean;
|
|
67
|
+
animationDuration?: number;
|
|
68
|
+
spinner?: boolean;
|
|
69
|
+
preserveScroll?: boolean;
|
|
70
|
+
showFallbackIfAnimated?: boolean;
|
|
71
|
+
prefetch?: 'hover' | 'render' | 'viewport' | 'none';
|
|
72
|
+
hoverPrefetchDelay?: number;
|
|
66
73
|
};
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export declare const useSetIsAnimated: (isAnimated: boolean | undefined, showFallbackIfAnimated: boolean) => void;
|