clear-react-router 2.0.5 → 2.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +3 -12
- package/dist/components/Router.d.ts +1 -1
- package/dist/config/routerConfig.d.ts +1 -0
- package/dist/hooks/useLoaderState.d.ts +1 -2
- package/dist/index.js +85 -100
- package/dist/runtime/invalidate.d.ts +1 -1
- package/dist/types.d.ts +7 -5
- package/dist/utils/commitNavigation.d.ts +1 -3
- package/dist/utils/commitState.d.ts +1 -1
- package/dist/utils/revalidateCache.d.ts +2 -8
- package/dist/utils/utils.d.ts +1 -0
- package/package.json +1 -1
- package/dist/cell.d.ts +0 -6
package/README.md
CHANGED
|
@@ -46,10 +46,10 @@ It provides first-class support for:
|
|
|
46
46
|
| `maxCacheSize` | `number \| undefined` | 60 for mobile, 150 for desktop | Maximum number of cached loader entries. Once the limit is reached, the least recently used entries are evicted |
|
|
47
47
|
| `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
|
|
48
48
|
| `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
|
|
49
|
-
| `
|
|
49
|
+
| `optimisticSpinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while optimistic route data revalidates in the background |
|
|
50
50
|
| `context` | `object` | `{}` | Initial context (user, theme, etc.) |
|
|
51
51
|
| `errorBoundary` | `ComponentType<{ children: ReactNode }>` | `undefined` | Custom error boundary component for catching render errors in route components |
|
|
52
|
-
| `
|
|
52
|
+
| `defaultMinLoaderDuration` | `number \| undefined` | `0` | Default minimum time the loader fallback stays visible, to avoid flickering |
|
|
53
53
|
| `defaultLoaderFallback` | `ReactElement \| () => ReactElement` | `optional` | Default loading fallback for every route loader |
|
|
54
54
|
| `defaultErrorElement` | `ReactElement \| () => ReactElement` | `optional` | Default error fallback for every route |
|
|
55
55
|
| `defaultRetry` | `number \| { count: number; delay: number }` | `optional` | Default cache revalidation retry policy for all routes |
|
|
@@ -63,14 +63,6 @@ It provides first-class support for:
|
|
|
63
63
|
|
|
64
64
|
> **Note:** Global lifecycle hooks wrap every route navigation. The global `defaultBeforeLoad` runs **before** the route-specific beforeLoad, while the global `defaultAfterLoad` runs **after** the route-specific afterLoad.
|
|
65
65
|
|
|
66
|
-
```tsx
|
|
67
|
-
<div>
|
|
68
|
-
<Navbar />
|
|
69
|
-
<Router routes={routes} spinner={false} isAnimated /> {/* disable the spinner */}
|
|
70
|
-
</div>
|
|
71
|
-
```
|
|
72
|
-
> **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.
|
|
73
|
-
|
|
74
66
|
### `createRouter(routes)`
|
|
75
67
|
|
|
76
68
|
Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
@@ -82,6 +74,7 @@ Normalizes route configuration. Extracts dynamic params, builds nested paths.
|
|
|
82
74
|
| `beforeLoad` | `({ params, context, redirect, setContext }) => Promise<unknown> \| undefined \| void` | Runs before every route navigation. Auth checks and redirects. Can update context via `setContext`. `redirect` is provided by the router |
|
|
83
75
|
| `loader` | `({ params, context, setContext, searchParams, signal }) => Promise<unknown>` | Fetch data using route params, search params, abort controller signal and context. Can update context via `setContext` |
|
|
84
76
|
| `afterLoad` | `({ params, context, setContext }) => Promise<void>` | Runs after a successful navigation once the route has finished loading. Analytics, side effects after data is loaded. Can update context via `setContext` |
|
|
77
|
+
| `minLoaderDuration` | `number \| undefined` | `undefined` | Minimum time the loader fallback stays visible, to avoid flickering |
|
|
85
78
|
| `fallback` | `ReactElement \| () => ReactElement` | Loading fallback (for lazy loading) |
|
|
86
79
|
| `loaderFallback` | `ReactElement \| () => ReactElement` | Loading fallback for the route's `loader`. Overrides the global `defaultLoaderFallback` set in `Router` |
|
|
87
80
|
| `retry` | `number \| { count: number; delay: number }` | Overrides the global cache revalidation retry policy for this route |
|
|
@@ -816,8 +809,6 @@ Clear Router supports smooth page transitions using the native View Transitions
|
|
|
816
809
|
## How It Works
|
|
817
810
|
|
|
818
811
|
- **Data loads first** — All `loader` and `beforeLoad` hooks complete before animation starts
|
|
819
|
-
- **No `loaderFallback`** — The `loaderFallback` is not shown during animated transitions
|
|
820
|
-
- **Subtle spinner** — A small spinner appears in the top-left corner while data is loading and `spinner` prop of `Router` component is on, so users know the app is responsive
|
|
821
812
|
- **Native API** — Uses `document.startViewTransition` for smooth, hardware-accelerated animations
|
|
822
813
|
|
|
823
814
|
## Browser Support
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { RouterProps } from '../types';
|
|
2
|
-
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated,
|
|
2
|
+
export declare const Router: ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated, optimisticSpinner, defaultPreserveScroll, defaultMinLoaderDuration, maxCacheSize, defaultPrefetch, defaultHoverPrefetchDelay, errorBoundary: ErrorBoundary, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
|
|
@@ -10,6 +10,7 @@ declare class RouterConfig {
|
|
|
10
10
|
defaultRetry?: RouterProps['defaultRetry'];
|
|
11
11
|
defaultStaleTime?: RouterProps['defaultStaleTime'];
|
|
12
12
|
defaultPreserveScroll?: RouterProps['defaultPreserveScroll'];
|
|
13
|
+
defaultMinLoaderDuration?: RouterProps['defaultMinLoaderDuration'];
|
|
13
14
|
configure(config: Partial<RouterConfig>): void;
|
|
14
15
|
}
|
|
15
16
|
export declare const routerConfig: RouterConfig;
|
|
@@ -1,2 +1 @@
|
|
|
1
|
-
|
|
2
|
-
export declare const useLoaderState: <T>() => LoaderState<T>;
|
|
1
|
+
export declare const useLoaderState: <T>() => import("../types").LoaderState<T>;
|
package/dist/index.js
CHANGED
|
@@ -30,12 +30,13 @@ var useGlobalState = ({ subscribe, getState, setState }) => {
|
|
|
30
30
|
};
|
|
31
31
|
//#endregion
|
|
32
32
|
//#region utils/commitState.ts
|
|
33
|
-
var createCommitState = ({ routeItemDataState, pendingState }) => (nextLocation, routeItem) => {
|
|
33
|
+
var createCommitState = ({ routeItemDataState, pendingState, isOptimisticLoading }) => (nextLocation, routeItem) => {
|
|
34
34
|
routeItemDataState.setState({
|
|
35
35
|
routeItem,
|
|
36
36
|
location: nextLocation
|
|
37
37
|
});
|
|
38
38
|
pendingState.setState(void 0);
|
|
39
|
+
isOptimisticLoading.setState(false);
|
|
39
40
|
const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
|
|
40
41
|
if (fullPath === window.location.pathname + window.location.search) return;
|
|
41
42
|
history.pushState(null, "", fullPath);
|
|
@@ -95,6 +96,7 @@ var RouterConfig = class {
|
|
|
95
96
|
_defineProperty(this, "defaultRetry", void 0);
|
|
96
97
|
_defineProperty(this, "defaultStaleTime", void 0);
|
|
97
98
|
_defineProperty(this, "defaultPreserveScroll", void 0);
|
|
99
|
+
_defineProperty(this, "defaultMinLoaderDuration", void 0);
|
|
98
100
|
}
|
|
99
101
|
configure(config) {
|
|
100
102
|
Object.assign(this, config);
|
|
@@ -103,13 +105,12 @@ var RouterConfig = class {
|
|
|
103
105
|
var routerConfig = new RouterConfig();
|
|
104
106
|
//#endregion
|
|
105
107
|
//#region utils/commitNavigation.ts
|
|
106
|
-
var
|
|
107
|
-
|
|
108
|
-
if (!routerConfig.isAnimated || isFirstLoad) return navigationExecutor(nextLocation, routeItem);
|
|
108
|
+
var commitNavigation = (callback) => {
|
|
109
|
+
if (!routerConfig.isAnimated) return callback();
|
|
109
110
|
try {
|
|
110
|
-
document.startViewTransition(
|
|
111
|
+
document.startViewTransition(callback);
|
|
111
112
|
} catch {
|
|
112
|
-
|
|
113
|
+
callback();
|
|
113
114
|
}
|
|
114
115
|
};
|
|
115
116
|
//#endregion
|
|
@@ -133,7 +134,7 @@ var createIsCacheItemFresh = (loaderMap) => (path) => {
|
|
|
133
134
|
* LICENSE file in the root directory of this source tree.
|
|
134
135
|
*/
|
|
135
136
|
var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
|
|
136
|
-
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element")
|
|
137
|
+
var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
|
|
137
138
|
function jsxProd(type, config, maybeKey) {
|
|
138
139
|
var key = null;
|
|
139
140
|
void 0 !== maybeKey && (key = "" + maybeKey);
|
|
@@ -151,7 +152,6 @@ var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((expor
|
|
|
151
152
|
props: maybeKey
|
|
152
153
|
};
|
|
153
154
|
}
|
|
154
|
-
exports.Fragment = REACT_FRAGMENT_TYPE;
|
|
155
155
|
exports.jsx = jsxProd;
|
|
156
156
|
exports.jsxs = jsxProd;
|
|
157
157
|
}));
|
|
@@ -215,6 +215,7 @@ var isMobile = () => {
|
|
|
215
215
|
const isSmallScreen = window.matchMedia("(max-width: 768px)").matches;
|
|
216
216
|
return hasCoarsePointer && isSmallScreen;
|
|
217
217
|
};
|
|
218
|
+
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
218
219
|
var findRoute = (pathname, includeAll) => {
|
|
219
220
|
if (includeAll) return routerConfig.routes.find((el) => el.path === "*" || comparePaths(el, pathname));
|
|
220
221
|
return routerConfig.routes.find((el) => comparePaths(el, pathname));
|
|
@@ -225,20 +226,21 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
225
226
|
let navigationSeq = 0;
|
|
226
227
|
let interval = 0;
|
|
227
228
|
let abortController = null;
|
|
228
|
-
const {
|
|
229
|
-
const
|
|
229
|
+
const { loaderState, scrollMapState, pendingState, contextState, loaderMap, routeItemDataState, isOptimisticLoading } = routerState;
|
|
230
|
+
const navigationExecutor = createCommitState(routerState);
|
|
230
231
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
231
232
|
const createSignal = () => {
|
|
232
233
|
abortController?.abort();
|
|
233
234
|
abortController = new AbortController();
|
|
234
235
|
return abortController.signal;
|
|
235
236
|
};
|
|
237
|
+
const getPath = (nextLocation) => `${nextLocation.pathname}${nextLocation.search ?? ""}`;
|
|
236
238
|
const getContext = () => ({
|
|
237
239
|
context: contextState.getState(),
|
|
238
240
|
setContext: contextState.setState
|
|
239
241
|
});
|
|
240
242
|
const routeResolve = (location) => {
|
|
241
|
-
|
|
243
|
+
loaderState.setState(emptyLoaderState);
|
|
242
244
|
const nextItem = findRoute(location.pathname, true);
|
|
243
245
|
return {
|
|
244
246
|
nextItem,
|
|
@@ -255,12 +257,12 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
255
257
|
redirect,
|
|
256
258
|
params
|
|
257
259
|
});
|
|
258
|
-
|
|
260
|
+
loaderState.setState((prev) => ({
|
|
259
261
|
...prev,
|
|
260
262
|
beforeLoadError: null
|
|
261
263
|
}));
|
|
262
264
|
} catch (error) {
|
|
263
|
-
|
|
265
|
+
loaderState.setState((prev) => ({
|
|
264
266
|
...prev,
|
|
265
267
|
beforeLoadError: error
|
|
266
268
|
}));
|
|
@@ -279,44 +281,51 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
279
281
|
[prevPathname]: scrollPosition
|
|
280
282
|
};
|
|
281
283
|
});
|
|
282
|
-
const path =
|
|
284
|
+
const path = getPath(location);
|
|
283
285
|
if (routeItem?.optimistic && loaderMap.has(path)) {
|
|
284
286
|
routeItemDataState.setState({
|
|
285
287
|
routeItem,
|
|
286
288
|
location
|
|
287
289
|
});
|
|
290
|
+
isOptimisticLoading.setState(true);
|
|
288
291
|
const currentLoaderState = loaderMap.get(path)?.state;
|
|
289
|
-
if (currentLoaderState)
|
|
290
|
-
} else {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
} : void 0);
|
|
296
|
-
}
|
|
292
|
+
if (currentLoaderState) loaderState.setState(currentLoaderState);
|
|
293
|
+
} else if (routeItem?.loader && !isCacheItemFresh(path)) commitNavigation(() => pendingState.setState({
|
|
294
|
+
routeItem,
|
|
295
|
+
location
|
|
296
|
+
}));
|
|
297
|
+
else pendingState.setState(void 0);
|
|
297
298
|
};
|
|
298
|
-
const polling = (routeItem,
|
|
299
|
+
const polling = (routeItem, nextLocation) => {
|
|
299
300
|
if (!routeItem?.pollingInterval) return;
|
|
300
301
|
const signal = createSignal();
|
|
301
302
|
interval = window.setInterval(() => revalidateCache({
|
|
302
303
|
routeItem,
|
|
303
|
-
pathname:
|
|
304
|
-
search:
|
|
304
|
+
pathname: nextLocation.pathname,
|
|
305
|
+
search: nextLocation.search,
|
|
305
306
|
signal
|
|
306
307
|
}), routeItem.pollingInterval);
|
|
307
308
|
};
|
|
308
|
-
const
|
|
309
|
+
const getLoaderDurationPromise = (routeItem, nextLocation) => {
|
|
310
|
+
const minLoaderDuration = routeItem?.minLoaderDuration ?? routerConfig.defaultMinLoaderDuration ?? 0;
|
|
311
|
+
return minLoaderDuration && !isCacheItemFresh(getPath(nextLocation)) ? sleep(minLoaderDuration) : Promise.resolve;
|
|
312
|
+
};
|
|
313
|
+
const loader = async (routeItem, nextLocation, seq) => {
|
|
309
314
|
if (!routeItem?.loader) return;
|
|
310
315
|
window.clearInterval(interval);
|
|
311
316
|
const signal = createSignal();
|
|
312
|
-
await revalidateCache({
|
|
317
|
+
const [result] = await Promise.all([revalidateCache({
|
|
313
318
|
routeItem,
|
|
314
|
-
pathname:
|
|
315
|
-
search:
|
|
319
|
+
pathname: nextLocation.pathname,
|
|
320
|
+
search: nextLocation.search,
|
|
316
321
|
signal
|
|
317
|
-
});
|
|
322
|
+
}), getLoaderDurationPromise(routeItem, nextLocation)]);
|
|
323
|
+
if (result) loaderState.setState((prev) => ({
|
|
324
|
+
...prev,
|
|
325
|
+
...result
|
|
326
|
+
}));
|
|
318
327
|
if (seq !== navigationSeq) return;
|
|
319
|
-
polling(routeItem,
|
|
328
|
+
polling(routeItem, nextLocation);
|
|
320
329
|
};
|
|
321
330
|
const afterLoad = async (routeItem, params) => {
|
|
322
331
|
const { defaultAfterLoad } = routerConfig;
|
|
@@ -338,7 +347,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
338
347
|
prepareNavigation(nextItem, nextLocation);
|
|
339
348
|
await loader(nextItem, nextLocation, seq);
|
|
340
349
|
if (seq !== navigationSeq) return;
|
|
341
|
-
commitNavigation(nextLocation, nextItem);
|
|
350
|
+
commitNavigation(() => navigationExecutor(nextLocation, nextItem));
|
|
342
351
|
await afterLoad(nextItem, params);
|
|
343
352
|
};
|
|
344
353
|
return navigate;
|
|
@@ -346,7 +355,7 @@ var createNavigate = (routerState, revalidateCache) => {
|
|
|
346
355
|
//#endregion
|
|
347
356
|
//#region runtime/invalidate.ts
|
|
348
357
|
var redirect = Promise.resolve;
|
|
349
|
-
var createInvalidate = ({ routeItemDataState,
|
|
358
|
+
var createInvalidate = ({ routeItemDataState, loaderState, loaderMap, contextState }, revalidateCache) => {
|
|
350
359
|
const invalidatePath = async (routeItem, pathname, options) => {
|
|
351
360
|
const routePathname = routeItemDataState.getState().location.pathname;
|
|
352
361
|
loaderMap.delete(pathname);
|
|
@@ -362,12 +371,12 @@ var createInvalidate = ({ routeItemDataState, loaderStateRef, loaderMap, current
|
|
|
362
371
|
setContext
|
|
363
372
|
});
|
|
364
373
|
}
|
|
365
|
-
|
|
374
|
+
loaderState.setState((prev) => ({
|
|
366
375
|
...prev,
|
|
367
376
|
beforeLoadError: null
|
|
368
377
|
}));
|
|
369
378
|
} catch (error) {
|
|
370
|
-
|
|
379
|
+
loaderState.setState((prev) => ({
|
|
371
380
|
...prev,
|
|
372
381
|
beforeLoadError: error
|
|
373
382
|
}));
|
|
@@ -376,7 +385,11 @@ var createInvalidate = ({ routeItemDataState, loaderStateRef, loaderMap, current
|
|
|
376
385
|
routeItem,
|
|
377
386
|
pathname
|
|
378
387
|
});
|
|
379
|
-
if (pathname === routePathname)
|
|
388
|
+
if (result && pathname === routePathname) loaderState.setState({
|
|
389
|
+
data: result.data,
|
|
390
|
+
loaderError: result.error,
|
|
391
|
+
beforeLoadError: null
|
|
392
|
+
});
|
|
380
393
|
return {
|
|
381
394
|
path: pathname,
|
|
382
395
|
...result
|
|
@@ -430,16 +443,8 @@ var getRetry = (routeItem) => {
|
|
|
430
443
|
delay: routeRetry ? routeRetry.delay : globalRetry?.delay || 0
|
|
431
444
|
};
|
|
432
445
|
};
|
|
433
|
-
var sleep = async (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
434
446
|
var createRevalidateCache = (routerState) => {
|
|
435
|
-
const {
|
|
436
|
-
const removeStaleItems = () => {
|
|
437
|
-
const deletedItems = [...loaderMap.entries()].filter(([, item]) => {
|
|
438
|
-
const staleTime = item.staleTime ?? routerConfig.defaultStaleTime;
|
|
439
|
-
return staleTime && staleTime + item.timestamp < Date.now();
|
|
440
|
-
});
|
|
441
|
-
if (deletedItems.length) deletedItems.forEach((item) => loaderMap.delete(item[0]));
|
|
442
|
-
};
|
|
447
|
+
const { contextState, loaderMap, loadingPromises } = routerState;
|
|
443
448
|
const evict = () => {
|
|
444
449
|
if (loaderMap.size <= routerConfig.maxCacheSize) return;
|
|
445
450
|
const oldestKey = loaderMap.keys().next().value;
|
|
@@ -456,7 +461,6 @@ var createRevalidateCache = (routerState) => {
|
|
|
456
461
|
const revalidateCache = async ({ routeItem, pathname, search = "", signal }, retried = 0) => {
|
|
457
462
|
if (!routeItem?.loader) return;
|
|
458
463
|
const isCacheItemFresh = createIsCacheItemFresh(loaderMap);
|
|
459
|
-
removeStaleItems();
|
|
460
464
|
const path = `${pathname}${search}`;
|
|
461
465
|
if (loadingPromises.has(path)) {
|
|
462
466
|
moveItemToLastPosition(path);
|
|
@@ -464,8 +468,14 @@ var createRevalidateCache = (routerState) => {
|
|
|
464
468
|
}
|
|
465
469
|
if (isCacheItemFresh(path)) {
|
|
466
470
|
const item = moveItemToLastPosition(path);
|
|
467
|
-
if (item?.state)
|
|
468
|
-
return
|
|
471
|
+
if (!item?.state) return void 0;
|
|
472
|
+
return item.state.loaderError ? {
|
|
473
|
+
data: null,
|
|
474
|
+
error: item.state.loaderError
|
|
475
|
+
} : {
|
|
476
|
+
data: item.state.data,
|
|
477
|
+
error: null
|
|
478
|
+
};
|
|
469
479
|
}
|
|
470
480
|
const promise = (async () => {
|
|
471
481
|
if (!routeItem?.loader) return;
|
|
@@ -482,13 +492,12 @@ var createRevalidateCache = (routerState) => {
|
|
|
482
492
|
searchParams,
|
|
483
493
|
signal: effectiveSignal
|
|
484
494
|
});
|
|
485
|
-
loaderStateRef.set((prev) => ({
|
|
486
|
-
...prev,
|
|
487
|
-
data: result,
|
|
488
|
-
loaderError: null
|
|
489
|
-
}));
|
|
490
495
|
loaderMap.set(path, {
|
|
491
|
-
state:
|
|
496
|
+
state: {
|
|
497
|
+
data: result,
|
|
498
|
+
beforeLoadError: null,
|
|
499
|
+
loaderError: null
|
|
500
|
+
},
|
|
492
501
|
timestamp: Date.now(),
|
|
493
502
|
staleTime: routeItem.staleTime
|
|
494
503
|
});
|
|
@@ -506,27 +515,16 @@ var createRevalidateCache = (routerState) => {
|
|
|
506
515
|
if (retry && retry.count > retried) {
|
|
507
516
|
loadingPromises.delete(path);
|
|
508
517
|
if (retry.delay) await sleep(retry.delay);
|
|
509
|
-
|
|
518
|
+
return revalidateCache({
|
|
510
519
|
routeItem,
|
|
511
520
|
pathname,
|
|
512
521
|
search,
|
|
513
522
|
signal
|
|
514
523
|
}, retried + 1);
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
} else {
|
|
520
|
-
loaderStateRef.set((prev) => ({
|
|
521
|
-
...prev,
|
|
522
|
-
data: null,
|
|
523
|
-
loaderError: error
|
|
524
|
-
}));
|
|
525
|
-
return {
|
|
526
|
-
data: null,
|
|
527
|
-
error
|
|
528
|
-
};
|
|
529
|
-
}
|
|
524
|
+
} else return {
|
|
525
|
+
data: null,
|
|
526
|
+
error
|
|
527
|
+
};
|
|
530
528
|
} finally {
|
|
531
529
|
loadingPromises.delete(path);
|
|
532
530
|
}
|
|
@@ -537,20 +535,6 @@ var createRevalidateCache = (routerState) => {
|
|
|
537
535
|
return revalidateCache;
|
|
538
536
|
};
|
|
539
537
|
//#endregion
|
|
540
|
-
//#region cell.ts
|
|
541
|
-
var Cell = class {
|
|
542
|
-
constructor(_value) {
|
|
543
|
-
_defineProperty(this, "_value", void 0);
|
|
544
|
-
this._value = _value;
|
|
545
|
-
}
|
|
546
|
-
get value() {
|
|
547
|
-
return this._value;
|
|
548
|
-
}
|
|
549
|
-
set(action) {
|
|
550
|
-
this._value = typeof action === "function" ? action(this._value) : action;
|
|
551
|
-
}
|
|
552
|
-
};
|
|
553
|
-
//#endregion
|
|
554
538
|
//#region utils/createRouterInstance.ts
|
|
555
539
|
var createRouterInstance = () => {
|
|
556
540
|
const routerState = {
|
|
@@ -559,14 +543,14 @@ var createRouterInstance = () => {
|
|
|
559
543
|
location: {}
|
|
560
544
|
}),
|
|
561
545
|
pendingState: create(void 0),
|
|
562
|
-
currentLoaderState: create(emptyLoaderState),
|
|
563
546
|
scrollMapState: create({}),
|
|
564
547
|
contextState: create({}),
|
|
565
548
|
blockedRouteState: create({
|
|
566
549
|
from: "",
|
|
567
550
|
to: ""
|
|
568
551
|
}),
|
|
569
|
-
|
|
552
|
+
isOptimisticLoading: create(false),
|
|
553
|
+
loaderState: create(emptyLoaderState),
|
|
570
554
|
loaderMap: /* @__PURE__ */ new Map(),
|
|
571
555
|
loadingPromises: /* @__PURE__ */ new Map()
|
|
572
556
|
};
|
|
@@ -606,6 +590,8 @@ var createRouterInstance = () => {
|
|
|
606
590
|
useScrollMap: () => useGlobalState(routerState.scrollMapState),
|
|
607
591
|
usePendingState: () => useGlobalState(routerState.pendingState),
|
|
608
592
|
useContextState: () => useGlobalState(routerState.contextState),
|
|
593
|
+
useOptimisticLoading: () => useGlobalState(routerState.isOptimisticLoading),
|
|
594
|
+
useLoaderState: () => useGlobalState(routerState.loaderState)[0],
|
|
609
595
|
useParams: () => getParamsObject(),
|
|
610
596
|
useNavigate: () => {
|
|
611
597
|
const { blockedRouteState } = routerState;
|
|
@@ -681,6 +667,9 @@ var useNavigation = () => {
|
|
|
681
667
|
}, [navigate]);
|
|
682
668
|
};
|
|
683
669
|
//#endregion
|
|
670
|
+
//#region hooks/useLoaderState.ts
|
|
671
|
+
var useLoaderState = router.hooks.useLoaderState;
|
|
672
|
+
//#endregion
|
|
684
673
|
//#region hooks/useApplyCustomAnimation.ts
|
|
685
674
|
var useApplyCustomAnimation = (animationDuration) => {
|
|
686
675
|
useEffect(() => {
|
|
@@ -758,12 +747,13 @@ var EmptyBoundary = ({ children }) => children;
|
|
|
758
747
|
var IS_MOBILE = isMobile();
|
|
759
748
|
var MOBILE_CACHE_SIZE = 60;
|
|
760
749
|
var DESKTOP_CACHE_SIZE = 150;
|
|
761
|
-
var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated = false,
|
|
762
|
-
const { useRouteItemData, usePendingState } = router.hooks;
|
|
750
|
+
var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration, defaultLoaderFallback, defaultErrorElement, defaultRetry, defaultStaleTime, context: initialContext, isAnimated = false, optimisticSpinner = true, defaultPreserveScroll = true, defaultMinLoaderDuration = 0, maxCacheSize = IS_MOBILE ? MOBILE_CACHE_SIZE : DESKTOP_CACHE_SIZE, defaultPrefetch = IS_MOBILE ? "viewport" : "hover", defaultHoverPrefetchDelay = 150, errorBoundary: ErrorBoundary = EmptyBoundary }) => {
|
|
751
|
+
const { useRouteItemData, usePendingState, useOptimisticLoading } = router.hooks;
|
|
763
752
|
const [routeItemData] = useRouteItemData();
|
|
764
|
-
const [
|
|
765
|
-
const
|
|
766
|
-
const
|
|
753
|
+
const [pendingRouteData] = usePendingState();
|
|
754
|
+
const [isOptimisticLoading] = useOptimisticLoading();
|
|
755
|
+
const loaderState = useLoaderState();
|
|
756
|
+
const isLoading = Boolean(pendingRouteData);
|
|
767
757
|
useNavigation();
|
|
768
758
|
useSetRouterConfig({
|
|
769
759
|
routes,
|
|
@@ -775,6 +765,7 @@ var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration,
|
|
|
775
765
|
defaultRetry,
|
|
776
766
|
defaultStaleTime,
|
|
777
767
|
defaultPreserveScroll,
|
|
768
|
+
defaultMinLoaderDuration,
|
|
778
769
|
maxCacheSize
|
|
779
770
|
});
|
|
780
771
|
useApplyCustomAnimation(animationDuration);
|
|
@@ -782,15 +773,12 @@ var Router = ({ routes, defaultBeforeLoad, defaultAfterLoad, animationDuration,
|
|
|
782
773
|
usePreserveScroll(routeItemData);
|
|
783
774
|
const { routeItem, location } = routeItemData;
|
|
784
775
|
const showErrorElement = !isLoading && Boolean(loaderState.loaderError || loaderState.beforeLoadError);
|
|
785
|
-
|
|
786
|
-
const loadingContent = !showErrorElement && isLoading;
|
|
787
|
-
if ((showFallbackOnAnimation || !isAnimated) && loadingContent) return renderElement(pendingState?.routeItem?.loaderFallback || defaultLoaderFallback);
|
|
788
|
-
if (!showFallbackOnAnimation && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
|
|
776
|
+
if (!showErrorElement && isLoading) return renderElement(pendingRouteData?.routeItem?.loaderFallback || defaultLoaderFallback);
|
|
789
777
|
if (!routeItem) return null;
|
|
790
|
-
if (showErrorElement) return
|
|
778
|
+
if (showErrorElement) return renderElement(routeItem.errorElement || defaultErrorElement);
|
|
791
779
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
|
|
792
780
|
style: { viewTransitionName: "page" },
|
|
793
|
-
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, { children: renderElement(routeItem.element) }, location.pathname),
|
|
781
|
+
children: [/* @__PURE__ */ (0, import_jsx_runtime.jsx)(ErrorBoundary, { children: renderElement(routeItem.element) }, location.pathname), optimisticSpinner && isOptimisticLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
|
|
794
782
|
});
|
|
795
783
|
};
|
|
796
784
|
//#endregion
|
|
@@ -932,9 +920,6 @@ var Form = ({ children, action, onSuccess, onError, autoReset = true }) => {
|
|
|
932
920
|
//#region hooks/useParams.ts
|
|
933
921
|
var useParams = router.hooks.useParams;
|
|
934
922
|
//#endregion
|
|
935
|
-
//#region hooks/useLoaderState.ts
|
|
936
|
-
var useLoaderState = () => router.state.loaderStateRef.value;
|
|
937
|
-
//#endregion
|
|
938
923
|
//#region hooks/useInvalidate.ts
|
|
939
924
|
var useInvalidate = () => router.runtime.invalidate;
|
|
940
925
|
//#endregion
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { type InvalidateOptions, InvalidateResult, RevalidateCache, RouterState } from '../types';
|
|
2
|
-
export declare const createInvalidate: ({ routeItemDataState,
|
|
2
|
+
export declare const createInvalidate: ({ routeItemDataState, loaderState, loaderMap, contextState }: RouterState, revalidateCache: RevalidateCache) => (pathList?: string | string[], options?: InvalidateOptions) => Promise<InvalidateResult[]>;
|
package/dist/types.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import { ComponentType, type CSSProperties, Dispatch, MouseEvent, ReactElement, ReactNode, Ref, SetStateAction } from 'react';
|
|
2
2
|
import { Store, useGlobalState } from './create';
|
|
3
|
-
import { Cell } from './cell';
|
|
4
3
|
export declare const LAZY_MARKER: unique symbol;
|
|
5
4
|
export type LazyComponent = {
|
|
6
5
|
readonly [LAZY_MARKER]: true;
|
|
@@ -33,6 +32,7 @@ export type ClientRouteItem = {
|
|
|
33
32
|
optimistic?: boolean;
|
|
34
33
|
pollingInterval?: number;
|
|
35
34
|
retry?: Retry;
|
|
35
|
+
minLoaderDuration?: number;
|
|
36
36
|
preserveScroll?: boolean;
|
|
37
37
|
beforeLoad?: BeforeLoad;
|
|
38
38
|
afterLoad?: (arg: {
|
|
@@ -85,15 +85,15 @@ export type RouterProps = {
|
|
|
85
85
|
routes: RouteItem[];
|
|
86
86
|
isAnimated?: boolean;
|
|
87
87
|
animationDuration?: number;
|
|
88
|
-
|
|
88
|
+
optimisticSpinner?: boolean;
|
|
89
89
|
defaultPreserveScroll?: boolean;
|
|
90
90
|
defaultRetry?: Retry;
|
|
91
91
|
defaultStaleTime?: number;
|
|
92
92
|
defaultLoaderFallback?: RenderElement;
|
|
93
93
|
defaultErrorElement?: RenderElement;
|
|
94
|
-
showFallbackOnAnimation?: boolean;
|
|
95
94
|
defaultPrefetch?: 'hover' | 'render' | 'viewport' | 'none';
|
|
96
95
|
defaultHoverPrefetchDelay?: number;
|
|
96
|
+
defaultMinLoaderDuration?: number;
|
|
97
97
|
maxCacheSize?: number;
|
|
98
98
|
errorBoundary?: ComponentType<{
|
|
99
99
|
children: ReactNode;
|
|
@@ -117,14 +117,14 @@ export type LoadingPromise = Promise<{
|
|
|
117
117
|
export type RouterState = {
|
|
118
118
|
routeItemDataState: Store<RouteItemData>;
|
|
119
119
|
pendingState: Store<RouteItemData | undefined>;
|
|
120
|
-
currentLoaderState: Store<LoaderState>;
|
|
121
120
|
scrollMapState: Store<Record<string, number>>;
|
|
122
121
|
contextState: Store<Record<string, unknown>>;
|
|
123
122
|
blockedRouteState: Store<{
|
|
124
123
|
from: string;
|
|
125
124
|
to: string;
|
|
126
125
|
}>;
|
|
127
|
-
|
|
126
|
+
isOptimisticLoading: Store<boolean>;
|
|
127
|
+
loaderState: Store<LoaderState>;
|
|
128
128
|
loaderMap: Map<string, LoaderStateItem>;
|
|
129
129
|
loadingPromises: Map<string, LoadingPromise>;
|
|
130
130
|
};
|
|
@@ -144,6 +144,8 @@ export type RouterType = {
|
|
|
144
144
|
useScrollMap: () => ReturnType<typeof useGlobalState<Record<string, number>>>;
|
|
145
145
|
usePendingState: () => ReturnType<typeof useGlobalState<RouteItemData | undefined>>;
|
|
146
146
|
useContextState: () => ReturnType<typeof useGlobalState<Record<string, unknown>>>;
|
|
147
|
+
useOptimisticLoading: () => ReturnType<typeof useGlobalState<boolean>>;
|
|
148
|
+
useLoaderState: <T>() => LoaderState<T>;
|
|
147
149
|
useParams: <T>() => T;
|
|
148
150
|
useNavigate: () => (arg: Location | string | -1) => Promise<void>;
|
|
149
151
|
useGetAction: (actionKey: string) => {
|
|
@@ -1,3 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import { Location, RouteItem, RouteItemData } from '../types';
|
|
3
|
-
export declare const createCommitNavigation: (navigationExecutor: (arg: Location, routeItem: RouteItem | undefined) => void, routeItemDataState: Store<RouteItemData>) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
|
|
1
|
+
export declare const commitNavigation: (callback: () => void) => void;
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { Location, RouteItem, RouterState } from '../types';
|
|
2
|
-
export declare const createCommitState: ({ routeItemDataState, pendingState }: RouterState) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
|
|
2
|
+
export declare const createCommitState: ({ routeItemDataState, pendingState, isOptimisticLoading }: RouterState) => (nextLocation: Location, routeItem: RouteItem | undefined) => void;
|
|
@@ -1,8 +1,2 @@
|
|
|
1
|
-
import { RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
-
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search, signal }: RevalidateCacheArgs, retried?: number) =>
|
|
3
|
-
data: unknown;
|
|
4
|
-
error: null;
|
|
5
|
-
} | {
|
|
6
|
-
data: null;
|
|
7
|
-
error: unknown;
|
|
8
|
-
} | undefined>;
|
|
1
|
+
import { LoadingPromise, RevalidateCacheArgs, RouterState } from '../types';
|
|
2
|
+
export declare const createRevalidateCache: (routerState: RouterState) => ({ routeItem, pathname, search, signal }: RevalidateCacheArgs, retried?: number) => LoadingPromise;
|
package/dist/utils/utils.d.ts
CHANGED
|
@@ -4,3 +4,4 @@ export declare const getParamsObject: (nextItem?: RouteItem, nextPathname?: stri
|
|
|
4
4
|
export declare const parseWindowLocation: (location: typeof window.location) => Location;
|
|
5
5
|
export declare const comparePaths: (route: RouteItem, pathname: string) => boolean;
|
|
6
6
|
export declare const isMobile: () => boolean;
|
|
7
|
+
export declare const sleep: (ms: number) => Promise<unknown>;
|
package/package.json
CHANGED