clear-react-router 1.4.2 → 1.4.4

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 CHANGED
@@ -45,20 +45,18 @@ The root component that provides routing context to the application. Place stati
45
45
  |------|------|---------|-------------|
46
46
  | `routeList` | `RouteItem[]` | required | Array of route configurations |
47
47
  | `context` | `object` | `{}` | Initial context (user, theme, etc.) |
48
- | `isAnimated` | `boolean` | `false` | Enable smooth page transitions |
49
- | `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
50
48
  | `preserveScroll` | `boolean` | `true` | Save and restore scroll position when navigating between pages |
51
49
  | `children` | `ReactNode` | required | App content (must include `<Router />`) |
52
50
 
53
51
  ```
54
52
  function App() {
55
53
  return (
56
- <RouterProvider routeList={routes} isAnimated animationDuration={800}>
57
- <Navbar /> {/* Static — won't re-render on route change */}
54
+ <RouterProvider routeList={routes}>
55
+ <Navbar /> {/* Static */}
58
56
  <main>
59
- <Router /> {/* Dynamic — renders current page */}
57
+ <Router isAnimated animationDuration={800} /> {/* Dynamic — renders current page */}
60
58
  </main>
61
- <Footer /> {/* Static — won't re-render on route change */}
59
+ <Footer /> {/* Static */}
62
60
  </RouterProvider>
63
61
  );
64
62
  }
@@ -70,7 +68,9 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
70
68
 
71
69
  | Prop | Type | Default | Description |
72
70
  |------|------|---------|-------------|
73
- | `spinner` | `boolean /| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
71
+ | `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
72
+ | `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
73
+ | `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
74
74
 
75
75
  ```
76
76
  <RouterProvider routeList={routes} isAnimated>
@@ -1,3 +1,7 @@
1
- export declare const Router: ({ spinner }: {
1
+ type RouterProps = {
2
+ isAnimated?: boolean;
3
+ animationDuration?: number;
2
4
  spinner?: boolean;
3
- }) => import("react/jsx-runtime").JSX.Element | null;
5
+ };
6
+ export declare const Router: ({ isAnimated, animationDuration, spinner }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
7
+ export {};
@@ -4,9 +4,7 @@ type RouteProviderProps = {
4
4
  children: ReactNode;
5
5
  routeList: RouteItem[];
6
6
  context?: Record<string, unknown>;
7
- isAnimated?: boolean;
8
- animationDuration?: number;
9
7
  preserveScroll?: boolean;
10
8
  };
11
- export declare const RouterProvider: ({ children, routeList, context: initialContext, isAnimated, animationDuration, preserveScroll, }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
9
+ export declare const RouterProvider: ({ children, routeList, context: initialContext, preserveScroll, }: RouteProviderProps) => import("react/jsx-runtime").JSX.Element;
12
10
  export {};
@@ -2,12 +2,12 @@ import { Dispatch, SetStateAction } from 'react';
2
2
  import { BlockerState, LoaderState, Location, RouteItem, UpdateBlockedRouteProps } from '../types/global';
3
3
  export type PropsContextValue = {
4
4
  routeList: RouteItem[];
5
- isAnimated: boolean;
6
5
  };
7
6
  export type NavigationContextValue = {
8
7
  location: Location;
9
8
  blockerState: BlockerState;
10
9
  isLoading: boolean;
10
+ nextRouteItem: RouteItem | undefined;
11
11
  };
12
12
  export type ActionsContextValue = {
13
13
  setLocation: Dispatch<SetStateAction<Location>>;
@@ -5,14 +5,14 @@ type UseHandleNavigation = {
5
5
  setLocation: (arg: Location) => void;
6
6
  context: Record<string, unknown>;
7
7
  revalidateCache(arg: RevalidateCacheArgs): Promise<void>;
8
- isAnimated: boolean;
9
8
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
10
9
  setScrollMap: Dispatch<SetStateAction<Record<string, number>>>;
11
10
  setLoaderState: Dispatch<SetStateAction<LoaderState>>;
12
11
  };
13
- export declare const useHandleNavigation: ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext, setScrollMap, setLoaderState, }: UseHandleNavigation) => {
12
+ export declare const useHandleNavigation: ({ setLocation, routeList, context, revalidateCache, setContext, setScrollMap, setLoaderState, }: UseHandleNavigation) => {
14
13
  blockerState: BlockerState;
15
14
  updateLocation: (nextLocation: Location) => Promise<void>;
16
15
  updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
16
+ nextRouteItem: RouteItem | undefined;
17
17
  };
18
18
  export {};
package/dist/index.js CHANGED
@@ -45,12 +45,9 @@ 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, location, setLocation, prefetchLoader, loaderState, blockerState, routeList, isLoading, isAnimated }) => {
48
+ var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocation, location, setLocation, prefetchLoader, loaderState, blockerState, routeList, isLoading, nextRouteItem }) => {
49
49
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PropsContext.Provider, {
50
- value: {
51
- routeList,
52
- isAnimated
53
- },
50
+ value: { routeList },
54
51
  children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
55
52
  value: {
56
53
  setLocation,
@@ -68,7 +65,8 @@ var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocat
68
65
  value: {
69
66
  blockerState,
70
67
  location,
71
- isLoading
68
+ isLoading,
69
+ nextRouteItem
72
70
  },
73
71
  children
74
72
  })
@@ -138,11 +136,12 @@ var comparePaths = (el, pathname) => {
138
136
  };
139
137
  //#endregion
140
138
  //#region hooks/useHandleNavigation.ts
141
- var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, isAnimated, setContext, setScrollMap, setLoaderState }) => {
139
+ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, setContext, setScrollMap, setLoaderState }) => {
142
140
  const [blockedRoute, setBlockedRoute] = useState({
143
141
  from: "",
144
142
  to: ""
145
143
  });
144
+ const [nextRouteItem, setNextRouteItem] = useState();
146
145
  const prevPathname = useRef("");
147
146
  const navigationSeq = useRef(0);
148
147
  const updateScrollMap = useCallback(() => setScrollMap((prevState) => {
@@ -160,19 +159,19 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
160
159
  if (fullPath === window.location.pathname + window.location.search) return;
161
160
  history.pushState(null, "", fullPath);
162
161
  }, [setLocation]);
163
- const transitionedNavigation = useCallback(({ nextLocation, isFirstCall, isAnimated }) => {
164
- if (isAnimated && !isFirstCall) try {
162
+ const transitionedNavigation = useCallback((nextLocation) => {
163
+ try {
165
164
  document.startViewTransition(() => navigation(nextLocation));
166
165
  } catch {
167
166
  navigation(nextLocation);
168
167
  }
169
- else navigation(nextLocation);
170
168
  }, [navigation]);
171
- const navigationHandler = useCallback(async (nextLocation, isFirstCall) => {
169
+ const navigationHandler = useCallback(async (nextLocation) => {
172
170
  navigationSeq.current = navigationSeq.current + 1;
173
171
  const seq = navigationSeq.current;
174
172
  updateScrollMap();
175
173
  const nextItem = routeList.find((el) => comparePaths(el, nextLocation.pathname));
174
+ setNextRouteItem(nextItem);
176
175
  const params = getParamsObject({
177
176
  routeItem: nextItem,
178
177
  pathname: nextLocation.pathname
@@ -200,10 +199,7 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
200
199
  beforeLoadError: error
201
200
  }
202
201
  }));
203
- transitionedNavigation({
204
- nextLocation,
205
- isAnimated: false
206
- });
202
+ transitionedNavigation(nextLocation);
207
203
  return;
208
204
  }
209
205
  if (seq !== navigationSeq.current) return;
@@ -213,11 +209,7 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
213
209
  pathname: nextLocation.pathname
214
210
  });
215
211
  if (seq !== navigationSeq.current) return;
216
- transitionedNavigation({
217
- nextLocation,
218
- isFirstCall,
219
- isAnimated
220
- });
212
+ transitionedNavigation(nextLocation);
221
213
  if (nextItem?.afterLoad) await nextItem.afterLoad({
222
214
  context,
223
215
  params,
@@ -228,10 +220,9 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
228
220
  revalidateCache,
229
221
  routeList,
230
222
  transitionedNavigation,
231
- isAnimated,
232
223
  setContext,
233
- updateScrollMap,
234
- setLoaderState
224
+ setLoaderState,
225
+ updateScrollMap
235
226
  ]);
236
227
  const setNextLocationRef = useLatest(navigationHandler);
237
228
  const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
@@ -274,7 +265,7 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
274
265
  }, [blockedRoute.from, setNextLocationRef]);
275
266
  useEffect(() => {
276
267
  const currentLocation = parseWindowLocation(window.location);
277
- setNextLocationRef.current(currentLocation, true);
268
+ setNextLocationRef.current(currentLocation);
278
269
  prevPathname.current = currentLocation.pathname;
279
270
  }, [setNextLocationRef]);
280
271
  return {
@@ -284,7 +275,8 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, i
284
275
  return "unblocked";
285
276
  }, [blockedRoute]),
286
277
  updateLocation,
287
- updateBlockedRoute
278
+ updateBlockedRoute,
279
+ nextRouteItem
288
280
  };
289
281
  };
290
282
  //#endregion
@@ -338,12 +330,13 @@ var useLoader = ({ routeList, context, setContext, setLoaderState }) => {
338
330
  }
339
331
  }));
340
332
  } finally {
341
- if (isCurrentRoute) setIsLoading(false);
333
+ setTimeout(() => setIsLoading(false), 10);
342
334
  }
343
335
  }, [
344
336
  context,
345
337
  isCacheItemFresh,
346
- setContext
338
+ setContext,
339
+ setLoaderState
347
340
  ]);
348
341
  return {
349
342
  prefetchLoader: useCallback(async (pathname) => {
@@ -375,49 +368,11 @@ var usePreserveScroll = ({ pathname, preserveScroll }) => {
375
368
  return setScrollMap;
376
369
  };
377
370
  //#endregion
378
- //#region hooks/useApplyCustomAnimation.ts
379
- var useApplyCustomAnimation = (animationDuration) => {
380
- useEffect(() => {
381
- const style = document.createElement("style");
382
- style.id = "spinner-style";
383
- style.textContent = `
384
- @keyframes cr-spin {
385
- 0% { transform: rotate(0deg); }
386
- 100% { transform: rotate(360deg); }
387
- }
388
-
389
- .cr-spinner {
390
- position: fixed;
391
- top: 5px;
392
- left: 5px;
393
- z-index: 9999;
394
- width: 1rem;
395
- height: 1rem;
396
- border: 2px solid gray;
397
- border-bottom-color: transparent;
398
- border-radius: 50%;
399
- animation: cr-spin 1s linear infinite;
400
- }
401
- `;
402
- document.head.appendChild(style);
403
- return () => style.remove();
404
- }, []);
405
- useEffect(() => {
406
- if (!animationDuration) return;
407
- const style = document.createElement("style");
408
- style.id = "dynamic-view-transition-duration-style";
409
- style.textContent = `::view-transition-group(page) { animation-duration: ${animationDuration}ms; }`;
410
- document.head.appendChild(style);
411
- return () => style.remove();
412
- }, [animationDuration]);
413
- };
414
- //#endregion
415
371
  //#region components/RouterProvider.tsx
416
- var RouterProvider = ({ children, routeList, context: initialContext = {}, isAnimated = false, animationDuration, preserveScroll = true }) => {
372
+ var RouterProvider = ({ children, routeList, context: initialContext = {}, preserveScroll = true }) => {
417
373
  const [location, setLocation] = useState({});
418
374
  const [context, setContext] = useState(initialContext);
419
375
  const [loaderState, setLoaderState] = useState({});
420
- useApplyCustomAnimation(animationDuration);
421
376
  const setScrollMap = usePreserveScroll({
422
377
  pathname: location.pathname,
423
378
  preserveScroll
@@ -428,13 +383,12 @@ var RouterProvider = ({ children, routeList, context: initialContext = {}, isAni
428
383
  setContext,
429
384
  setLoaderState
430
385
  });
431
- const { blockerState, updateLocation, updateBlockedRoute } = useHandleNavigation({
386
+ const { blockerState, updateLocation, updateBlockedRoute, nextRouteItem } = useHandleNavigation({
432
387
  setLocation,
433
388
  routeList,
434
389
  context,
435
390
  setContext,
436
391
  revalidateCache,
437
- isAnimated,
438
392
  setScrollMap,
439
393
  setLoaderState
440
394
  });
@@ -450,8 +404,8 @@ var RouterProvider = ({ children, routeList, context: initialContext = {}, isAni
450
404
  context,
451
405
  setContext,
452
406
  routeList,
453
- isLoading,
454
- isAnimated
407
+ nextRouteItem,
408
+ isLoading
455
409
  }), [
456
410
  blockerState,
457
411
  context,
@@ -462,7 +416,7 @@ var RouterProvider = ({ children, routeList, context: initialContext = {}, isAni
462
416
  routeList,
463
417
  updateBlockedRoute,
464
418
  updateLocation,
465
- isAnimated
419
+ nextRouteItem
466
420
  ]),
467
421
  children
468
422
  });
@@ -479,6 +433,43 @@ var useRouterActions = () => useServiceState(ActionsContext);
479
433
  var useRouterData = () => useServiceState(DataContext);
480
434
  var usePropsData = () => useServiceState(PropsContext);
481
435
  //#endregion
436
+ //#region hooks/useApplyCustomAnimation.ts
437
+ var useApplyCustomAnimation = (animationDuration) => {
438
+ useEffect(() => {
439
+ const style = document.createElement("style");
440
+ style.id = "spinner-style";
441
+ style.textContent = `
442
+ @keyframes cr-spin {
443
+ 0% { transform: rotate(0deg); }
444
+ 100% { transform: rotate(360deg); }
445
+ }
446
+
447
+ .cr-spinner {
448
+ position: fixed;
449
+ top: 5px;
450
+ left: 5px;
451
+ z-index: 9999;
452
+ width: 1rem;
453
+ height: 1rem;
454
+ border: 2px solid gray;
455
+ border-bottom-color: transparent;
456
+ border-radius: 50%;
457
+ animation: cr-spin 1s linear infinite;
458
+ }
459
+ `;
460
+ document.head.appendChild(style);
461
+ return () => style.remove();
462
+ }, []);
463
+ useEffect(() => {
464
+ if (!animationDuration) return;
465
+ const style = document.createElement("style");
466
+ style.id = "dynamic-view-transition-duration-style";
467
+ style.textContent = `::view-transition-group(page) { animation-duration: ${animationDuration}ms; }`;
468
+ document.head.appendChild(style);
469
+ return () => style.remove();
470
+ }, [animationDuration]);
471
+ };
472
+ //#endregion
482
473
  //#region context/RouterViewContext.ts
483
474
  var RouterViewContext = createContext({});
484
475
  //#endregion
@@ -501,37 +492,34 @@ var renderElement = (Component) => {
501
492
  //#endregion
502
493
  //#region components/Router.tsx
503
494
  var ALL_LOCATIONS = "*";
504
- var Router = ({ spinner = true }) => {
505
- const { location, isLoading } = useNavigationState();
506
- const { routeList, isAnimated } = usePropsData();
495
+ var Router = ({ isAnimated, animationDuration, spinner = true }) => {
496
+ const { location, isLoading, nextRouteItem } = useNavigationState();
497
+ const { routeList } = usePropsData();
507
498
  const { loaderState } = useRouterData();
499
+ useApplyCustomAnimation(animationDuration);
508
500
  const routeItem = useMemo(() => {
509
501
  if (!location.pathname) return void 0;
510
502
  return routeList.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, location.pathname));
511
503
  }, [location.pathname, routeList]);
512
- const pendingRoute = useMemo(() => {
513
- if (location.pathname) return void 0;
514
- return routeList.find((el) => comparePaths(el, window.location.pathname));
515
- }, [location.pathname, routeList]);
516
504
  const params = useMemo(() => {
517
505
  return getParamsObject({
518
- routeItem: routeItem || pendingRoute,
506
+ routeItem: routeItem || nextRouteItem,
519
507
  pathname: location.pathname || window.location.pathname
520
508
  });
521
509
  }, [
522
510
  location.pathname,
523
511
  routeItem,
524
- pendingRoute
512
+ nextRouteItem
525
513
  ]);
526
514
  const shouldErrorElementShown = useMemo(() => Boolean(loaderState[location.pathname]?.loaderError || loaderState[location.pathname]?.beforeLoadError), [loaderState, location.pathname]);
527
- if (!routeItem && !pendingRoute) return null;
528
- if (!routeItem && pendingRoute) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
515
+ if (!routeItem && !nextRouteItem) return null;
516
+ if (!routeItem && nextRouteItem) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
529
517
  params,
530
- children: renderElement(pendingRoute?.loaderFallback)
518
+ children: renderElement(nextRouteItem?.loaderFallback)
531
519
  });
532
520
  if (!isAnimated && !shouldErrorElementShown && isLoading) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
533
521
  params,
534
- children: renderElement(routeItem?.loaderFallback)
522
+ children: renderElement(nextRouteItem?.loaderFallback)
535
523
  });
536
524
  if (shouldErrorElementShown) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ViewProvider, {
537
525
  params,
@@ -3,5 +3,5 @@ import { type ActionsContextValue, type DataContextValue, type NavigationContext
3
3
  type ProviderProps = PropsContextValue & NavigationContextValue & ActionsContextValue & DataContextValue & {
4
4
  children: ReactNode;
5
5
  };
6
- export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, location, setLocation, prefetchLoader, loaderState, blockerState, routeList, isLoading, isAnimated, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, location, setLocation, prefetchLoader, loaderState, blockerState, routeList, isLoading, nextRouteItem, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export {};
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.4.2",
3
+ "version": "1.4.4",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {