clear-react-router 1.4.6 → 1.4.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 CHANGED
@@ -45,7 +45,6 @@ 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
- | `preserveScroll` | `boolean` | `true` | Save and restore scroll position when navigating between pages |
49
48
  | `children` | `ReactNode` | required | App content (must include `<Router />`) |
50
49
 
51
50
  ```
@@ -71,6 +70,8 @@ Renders the current route's component. Must be placed inside `<RouterProvider>`.
71
70
  | `isAnimated` | `boolean \| undefined` | `false` | Enable smooth page fade transitions |
72
71
  | `animationDuration` | `number` | `optional` | Animation duration in milliseconds (browser default is used if not set) |
73
72
  | `spinner` | `boolean \| undefined` | `true` | Show a small spinner in the corner while loading data (only when `isAnimated` is enabled) |
73
+ | `preserveScroll` | `boolean \| undefined` | `true` | Save and restore scroll position when navigating between pages |
74
+ | `showFallbackIfAnimated` | `boolean \| undefined` | `false` | Show `loaderFallback` even when `isAnimated` is `true` (instead of spinner) |
74
75
 
75
76
  ```
76
77
  <RouterProvider routeList={routes} isAnimated>
@@ -3,6 +3,7 @@ type RouterProps = {
3
3
  animationDuration?: number;
4
4
  spinner?: boolean;
5
5
  preserveScroll?: boolean;
6
+ showFallbackIfAnimated?: boolean;
6
7
  };
7
- export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
8
+ export declare const Router: ({ isAnimated, animationDuration, spinner, preserveScroll, showFallbackIfAnimated, }: RouterProps) => import("react/jsx-runtime").JSX.Element | null;
8
9
  export {};
@@ -1,26 +1,21 @@
1
- import { Dispatch, SetStateAction } from 'react';
2
- import { BlockerState, LoaderState, Location, NextItemData, RouteItem, UpdateBlockedRouteProps } from '../types/global';
3
- export type PropsContextValue = {
4
- routeList: RouteItem[];
5
- };
1
+ import { BlockerState, Location, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
6
2
  export type NavigationContextValue = {
7
- location: Location;
8
3
  blockerState: BlockerState;
9
4
  isLoading: boolean;
10
- nextItemData: NextItemData;
5
+ routeItemData: RouteItemData;
6
+ currentLoaderFallback: RouteItem['loaderFallback'];
11
7
  };
12
8
  export type ActionsContextValue = {
13
- setLocation: Dispatch<SetStateAction<Location>>;
9
+ setSearch(arg: string): void;
14
10
  updateLocation(route: Location): Promise<void>;
15
11
  updateBlockedRoute(arg: UpdateBlockedRouteProps): void;
16
12
  prefetchLoader(arg: string): Promise<void>;
17
13
  setContext(arg: object): void;
14
+ restoreScroll(): void;
18
15
  };
19
16
  export type DataContextValue = {
20
- loaderState: LoaderState;
21
17
  context: Record<string, unknown>;
22
18
  };
23
- export declare const PropsContext: import("react").Context<PropsContextValue>;
24
19
  export declare const ActionsContext: import("react").Context<ActionsContextValue>;
25
20
  export declare const DataContext: import("react").Context<DataContextValue>;
26
21
  export declare const NavigationContext: import("react").Context<NavigationContextValue>;
@@ -1,17 +1,19 @@
1
1
  import { type Dispatch, type SetStateAction } from 'react';
2
- import { BlockerState, LoaderState, Location, NextItemData, RevalidateCacheArgs, RouteItem, UpdateBlockedRouteProps } from '../types/global';
2
+ import { BlockerState, Location, RevalidateCacheArgs, RouteItem, RouteItemData, UpdateBlockedRouteProps } from '../types/global';
3
3
  type UseHandleNavigation = {
4
4
  routeList: RouteItem[];
5
- setLocation: (arg: Location) => void;
6
5
  context: Record<string, unknown>;
7
6
  revalidateCache(arg: RevalidateCacheArgs): Promise<void>;
8
7
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
9
- setLoaderState: Dispatch<SetStateAction<LoaderState>>;
8
+ setIsLoading: Dispatch<SetStateAction<boolean>>;
10
9
  };
11
- export declare const useHandleNavigation: ({ setLocation, routeList, context, revalidateCache, setContext, setLoaderState, }: UseHandleNavigation) => {
10
+ export declare const useHandleNavigation: ({ routeList, context, revalidateCache, setContext, setIsLoading, }: UseHandleNavigation) => {
12
11
  blockerState: BlockerState;
13
12
  updateLocation: (nextLocation: Location) => Promise<void>;
14
13
  updateBlockedRoute: ({ type, payload }: UpdateBlockedRouteProps) => void;
15
- nextItemData: NextItemData;
14
+ routeItemData: RouteItemData;
15
+ setSearch: (search: string) => void;
16
+ restoreScroll: () => void;
17
+ currentLoaderFallback: import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement) | undefined;
16
18
  };
17
19
  export {};
@@ -1,14 +1,14 @@
1
1
  import { type Dispatch, type SetStateAction } from 'react';
2
- import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
2
+ import type { RevalidateCacheArgs, RouteItem } from '../types/global';
3
3
  type UseLoaderParams = {
4
4
  routeList: RouteItem[];
5
5
  context: Record<string, unknown>;
6
6
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
7
- setLoaderState: Dispatch<SetStateAction<LoaderState>>;
8
7
  };
9
- export declare const useLoader: ({ routeList, context, setContext, setLoaderState }: UseLoaderParams) => {
8
+ export declare const useLoader: ({ routeList, context, setContext }: UseLoaderParams) => {
10
9
  prefetchLoader: (pathname: string) => Promise<void>;
11
- revalidateCache: ({ routeItem, isCurrentRoute, pathname }: RevalidateCacheArgs) => Promise<void>;
10
+ revalidateCache: ({ routeItem, loaderState, pathname }: RevalidateCacheArgs) => Promise<void>;
12
11
  isLoading: boolean;
12
+ setIsLoading: Dispatch<SetStateAction<boolean>>;
13
13
  };
14
14
  export {};
@@ -1,7 +1 @@
1
- type UsePreserveScrollParams = {
2
- pathname: string;
3
- preserveScroll: boolean;
4
- nextPathname?: string;
5
- };
6
- export declare const usePreserveScroll: ({ pathname, preserveScroll, nextPathname }: UsePreserveScrollParams) => void;
7
- export {};
1
+ export declare const usePreserveScroll: (preserveScroll: boolean) => void;
@@ -1,4 +1,3 @@
1
1
  export declare const useNavigationState: () => import("../context/RouterProviderContext").NavigationContextValue;
2
2
  export declare const useRouterActions: () => import("../context/RouterProviderContext").ActionsContextValue;
3
3
  export declare const useRouterData: () => import("../context/RouterProviderContext").DataContextValue;
4
- export declare const usePropsData: () => import("../context/RouterProviderContext").PropsContextValue;
package/dist/index.js CHANGED
@@ -3,7 +3,6 @@ import { Suspense, createContext, lazy, useCallback, useContext, useEffect, useM
3
3
  var __commonJSMin = (cb, mod) => () => (mod || (cb((mod = { exports: {} }).exports, mod), cb = null), mod.exports);
4
4
  //#endregion
5
5
  //#region context/RouterProviderContext.ts
6
- var PropsContext = createContext({});
7
6
  var ActionsContext = createContext({});
8
7
  var DataContext = createContext({});
9
8
  var NavigationContext = createContext({});
@@ -19,7 +18,7 @@ var NavigationContext = createContext({});
19
18
  * LICENSE file in the root directory of this source tree.
20
19
  */
21
20
  var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((exports) => {
22
- var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element");
21
+ var REACT_ELEMENT_TYPE = Symbol.for("react.transitional.element"), REACT_FRAGMENT_TYPE = Symbol.for("react.fragment");
23
22
  function jsxProd(type, config, maybeKey) {
24
23
  var key = null;
25
24
  void 0 !== maybeKey && (key = "" + maybeKey);
@@ -37,6 +36,7 @@ var require_react_jsx_runtime_production = /* @__PURE__ */ __commonJSMin(((expor
37
36
  props: maybeKey
38
37
  };
39
38
  }
39
+ exports.Fragment = REACT_FRAGMENT_TYPE;
40
40
  exports.jsx = jsxProd;
41
41
  exports.jsxs = jsxProd;
42
42
  }));
@@ -45,31 +45,26 @@ 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, nextItemData }) => {
49
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PropsContext.Provider, {
50
- value: { routeList },
51
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
52
- value: {
53
- setLocation,
54
- updateLocation,
55
- updateBlockedRoute,
56
- prefetchLoader,
57
- setContext
58
- },
59
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataContext.Provider, {
48
+ var Provider = ({ children, setContext, context, updateBlockedRoute, updateLocation, setSearch, prefetchLoader, blockerState, isLoading, routeItemData, restoreScroll, currentLoaderFallback }) => {
49
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ActionsContext.Provider, {
50
+ value: {
51
+ setSearch,
52
+ updateLocation,
53
+ updateBlockedRoute,
54
+ prefetchLoader,
55
+ setContext,
56
+ restoreScroll
57
+ },
58
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(DataContext.Provider, {
59
+ value: { context },
60
+ children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavigationContext.Provider, {
60
61
  value: {
61
- context,
62
- loaderState
62
+ blockerState,
63
+ isLoading,
64
+ routeItemData,
65
+ currentLoaderFallback
63
66
  },
64
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(NavigationContext.Provider, {
65
- value: {
66
- blockerState,
67
- location,
68
- isLoading,
69
- nextItemData
70
- },
71
- children
72
- })
67
+ children
73
68
  })
74
69
  })
75
70
  });
@@ -136,75 +131,103 @@ var comparePaths = (el, pathname) => {
136
131
  };
137
132
  //#endregion
138
133
  //#region hooks/useHandleNavigation.ts
139
- var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, setContext, setLoaderState }) => {
134
+ var ALL_LOCATIONS = "*";
135
+ var useHandleNavigation = ({ routeList, context, revalidateCache, setContext, setIsLoading }) => {
140
136
  const [blockedRoute, setBlockedRoute] = useState({
141
137
  from: "",
142
138
  to: ""
143
139
  });
144
- const [nextItemData, setNextItemData] = useState({});
140
+ const [routeItemData, setRouteItemData] = useState({
141
+ location: {},
142
+ routeItem: void 0,
143
+ loaderState: {}
144
+ });
145
+ const [scrollMap, setScrollMap] = useState({});
146
+ const [currentLoaderFallback, setCurrentLoaderFallback] = useState();
147
+ const loaderState = useRef({});
145
148
  const prevPathname = useRef("");
146
149
  const navigationSeq = useRef(0);
147
- const navigation = useCallback((nextLocation) => {
148
- setLocation(nextLocation);
150
+ const scrollMapRef = useLatest(scrollMap);
151
+ const setSearch = useCallback((search) => setRouteItemData((prevState) => ({
152
+ ...prevState,
153
+ location: {
154
+ ...prevState.location,
155
+ search
156
+ }
157
+ })), []);
158
+ const restoreScroll = useCallback(() => {
159
+ if (!prevPathname.current || !scrollMapRef.current[prevPathname.current]) return;
160
+ requestAnimationFrame(() => {
161
+ window.scrollTo({
162
+ top: scrollMapRef.current[prevPathname.current],
163
+ behavior: "smooth"
164
+ });
165
+ });
166
+ }, [scrollMapRef]);
167
+ const navigation = useCallback((nextLocation, routeItem) => {
168
+ setRouteItemData({
169
+ location: nextLocation,
170
+ routeItem,
171
+ loaderState: loaderState.current
172
+ });
173
+ setIsLoading(false);
149
174
  prevPathname.current = nextLocation.pathname;
150
175
  const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
151
176
  if (fullPath === window.location.pathname + window.location.search) return;
152
177
  history.pushState(null, "", fullPath);
153
- }, [setLocation]);
154
- const transitionedNavigation = useCallback((nextLocation) => {
178
+ }, [setIsLoading]);
179
+ const transitionedNavigation = useCallback((nextLocation, routeItem) => {
180
+ setScrollMap((prevState) => {
181
+ const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
182
+ if (!scrollPosition || prevState[prevPathname.current] === scrollPosition) return prevState;
183
+ return {
184
+ ...prevState,
185
+ [prevPathname.current]: scrollPosition
186
+ };
187
+ });
155
188
  try {
156
- document.startViewTransition(() => navigation(nextLocation));
189
+ document.startViewTransition(() => navigation(nextLocation, routeItem));
157
190
  } catch {
158
- navigation(nextLocation);
191
+ navigation(nextLocation, routeItem);
159
192
  }
160
193
  }, [navigation]);
161
194
  const navigationHandler = useCallback(async (nextLocation) => {
162
195
  navigationSeq.current = navigationSeq.current + 1;
163
196
  const seq = navigationSeq.current;
164
- const nextItem = routeList.find((el) => comparePaths(el, nextLocation.pathname));
165
- setNextItemData({
166
- loaderFallback: nextItem?.loaderFallback,
167
- params: nextItem?.params,
168
- pathname: nextLocation.pathname
169
- });
197
+ loaderState.current = {};
198
+ const nextItem = routeList.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, nextLocation.pathname));
170
199
  const params = getParamsObject({
171
200
  params: nextItem?.params,
172
201
  pathname: nextLocation.pathname
173
202
  });
174
203
  if (nextItem?.beforeLoad) try {
175
- const redirect = async (location) => typeof location === "string" ? await navigationHandler({ pathname: location }) : await navigationHandler(location);
204
+ const redirect = async (location) => await navigationHandler(typeof location === "string" ? { pathname: location } : location);
176
205
  await nextItem.beforeLoad({
177
206
  context,
178
207
  redirect,
179
208
  params,
180
209
  setContext
181
210
  });
182
- setLoaderState((prevState) => ({
183
- ...prevState,
184
- [nextLocation.pathname]: {
185
- ...prevState[nextLocation.pathname],
186
- beforeLoadError: null
187
- }
188
- }));
211
+ loaderState.current = {
212
+ ...loaderState.current,
213
+ beforeLoadError: null
214
+ };
189
215
  } catch (error) {
190
- setLoaderState((prevState) => ({
191
- ...prevState,
192
- [nextLocation.pathname]: {
193
- ...prevState[nextLocation.pathname],
194
- beforeLoadError: error
195
- }
196
- }));
197
- transitionedNavigation(nextLocation);
198
- return;
216
+ loaderState.current = {
217
+ ...loaderState.current,
218
+ beforeLoadError: error
219
+ };
220
+ return transitionedNavigation(nextLocation, nextItem);
199
221
  }
200
222
  if (seq !== navigationSeq.current) return;
223
+ setCurrentLoaderFallback(nextItem?.loaderFallback);
201
224
  await revalidateCache({
202
225
  routeItem: nextItem,
203
- isCurrentRoute: true,
226
+ loaderState,
204
227
  pathname: nextLocation.pathname
205
228
  });
206
229
  if (seq !== navigationSeq.current) return;
207
- transitionedNavigation(nextLocation);
230
+ transitionedNavigation(nextLocation, nextItem);
208
231
  if (nextItem?.afterLoad) await nextItem.afterLoad({
209
232
  context,
210
233
  params,
@@ -215,8 +238,7 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, s
215
238
  revalidateCache,
216
239
  routeList,
217
240
  transitionedNavigation,
218
- setContext,
219
- setLoaderState
241
+ setContext
220
242
  ]);
221
243
  const setNextLocationRef = useLatest(navigationHandler);
222
244
  const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
@@ -270,14 +292,18 @@ var useHandleNavigation = ({ setLocation, routeList, context, revalidateCache, s
270
292
  }, [blockedRoute]),
271
293
  updateLocation,
272
294
  updateBlockedRoute,
273
- nextItemData
295
+ routeItemData,
296
+ setSearch,
297
+ restoreScroll,
298
+ currentLoaderFallback
274
299
  };
275
300
  };
276
301
  //#endregion
277
302
  //#region hooks/useLoader.ts
278
- var useLoader = ({ routeList, context, setContext, setLoaderState }) => {
303
+ var useLoader = ({ routeList, context, setContext }) => {
279
304
  const [isLoading, setIsLoading] = useState(false);
280
305
  const cacheTimestampsRef = useRef({});
306
+ const loaderCacheRef = useRef({});
281
307
  const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
282
308
  if (!routeItem) return true;
283
309
  const currentCacheTimestamp = cacheTimestampsRef.current[pathname];
@@ -285,13 +311,19 @@ var useLoader = ({ routeList, context, setContext, setLoaderState }) => {
285
311
  if (!routeItem.staleTime) return true;
286
312
  return Date.now() - currentCacheTimestamp < routeItem.staleTime;
287
313
  }, []);
288
- const revalidateCache = useCallback(async ({ routeItem, isCurrentRoute, pathname }) => {
289
- if (!routeItem?.loader) return;
314
+ const revalidateCache = useCallback(async ({ routeItem, loaderState, pathname }) => {
315
+ if (!routeItem?.loader) {
316
+ if (loaderState) loaderState.current = {};
317
+ return;
318
+ }
290
319
  if (isCacheItemFresh({
291
320
  routeItem,
292
321
  pathname
293
- })) return;
294
- if (isCurrentRoute) setIsLoading(true);
322
+ })) {
323
+ if (loaderState) loaderState.current = loaderCacheRef.current[pathname];
324
+ return;
325
+ }
326
+ if (loaderState) setIsLoading(true);
295
327
  try {
296
328
  const params = getParamsObject({
297
329
  params: routeItem.params,
@@ -306,31 +338,27 @@ var useLoader = ({ routeList, context, setContext, setLoaderState }) => {
306
338
  ...cacheTimestampsRef.current,
307
339
  [pathname]: Date.now()
308
340
  };
309
- if (isCurrentRoute) setLoaderState((prevState) => ({
310
- ...prevState,
311
- [pathname]: {
312
- ...prevState[pathname],
313
- data: result,
314
- loaderError: null
315
- }
316
- }));
341
+ loaderCacheRef.current[pathname] = {
342
+ data: result,
343
+ loaderError: null,
344
+ beforeLoadError: null
345
+ };
346
+ if (loaderState) loaderState.current = {
347
+ ...loaderState?.current,
348
+ data: result,
349
+ loaderError: null
350
+ };
317
351
  } catch (error) {
318
- if (isCurrentRoute) setLoaderState((prevState) => ({
319
- ...prevState,
320
- [pathname]: {
321
- ...prevState[pathname],
322
- data: null,
323
- loaderError: error
324
- }
325
- }));
326
- } finally {
327
- setTimeout(() => setIsLoading(false), 10);
352
+ if (loaderState) loaderState.current = {
353
+ ...loaderState?.current,
354
+ data: null,
355
+ loaderError: error
356
+ };
328
357
  }
329
358
  }, [
330
359
  context,
331
360
  isCacheItemFresh,
332
- setContext,
333
- setLoaderState
361
+ setContext
334
362
  ]);
335
363
  return {
336
364
  prefetchLoader: useCallback(async (pathname) => {
@@ -341,54 +369,52 @@ var useLoader = ({ routeList, context, setContext, setLoaderState }) => {
341
369
  });
342
370
  }, [revalidateCache, routeList]),
343
371
  revalidateCache,
344
- isLoading
372
+ isLoading,
373
+ setIsLoading
345
374
  };
346
375
  };
347
376
  //#endregion
348
377
  //#region components/RouterProvider.tsx
349
378
  var RouterProvider = ({ children, routeList, context: initialContext = {} }) => {
350
- const [location, setLocation] = useState({});
351
379
  const [context, setContext] = useState(initialContext);
352
- const [loaderState, setLoaderState] = useState({});
353
- const { prefetchLoader, revalidateCache, isLoading } = useLoader({
380
+ const { prefetchLoader, revalidateCache, isLoading, setIsLoading } = useLoader({
354
381
  routeList,
355
382
  context,
356
- setContext,
357
- setLoaderState
383
+ setContext
358
384
  });
359
- const { blockerState, updateLocation, updateBlockedRoute, nextItemData } = useHandleNavigation({
360
- setLocation,
385
+ const { blockerState, updateLocation, updateBlockedRoute, routeItemData, setSearch, restoreScroll, currentLoaderFallback } = useHandleNavigation({
361
386
  routeList,
362
387
  context,
363
388
  setContext,
364
389
  revalidateCache,
365
- setLoaderState
390
+ setIsLoading
366
391
  });
367
392
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Provider, {
368
393
  ...useMemo(() => ({
369
- location,
370
- setLocation,
394
+ setSearch,
371
395
  updateLocation,
372
- loaderState,
373
396
  prefetchLoader,
374
397
  updateBlockedRoute,
375
398
  blockerState,
376
399
  context,
377
400
  setContext,
378
401
  routeList,
379
- nextItemData,
402
+ routeItemData,
403
+ restoreScroll,
404
+ currentLoaderFallback,
380
405
  isLoading
381
406
  }), [
382
407
  blockerState,
383
408
  context,
384
409
  isLoading,
385
- loaderState,
386
- location,
387
410
  prefetchLoader,
411
+ routeItemData,
388
412
  routeList,
413
+ setSearch,
389
414
  updateBlockedRoute,
390
415
  updateLocation,
391
- nextItemData
416
+ currentLoaderFallback,
417
+ restoreScroll
392
418
  ]),
393
419
  children
394
420
  });
@@ -403,7 +429,6 @@ var useServiceState = (reactContext) => {
403
429
  var useNavigationState = () => useServiceState(NavigationContext);
404
430
  var useRouterActions = () => useServiceState(ActionsContext);
405
431
  var useRouterData = () => useServiceState(DataContext);
406
- var usePropsData = () => useServiceState(PropsContext);
407
432
  //#endregion
408
433
  //#region hooks/useApplyCustomAnimation.ts
409
434
  var useApplyCustomAnimation = (animationDuration) => {
@@ -443,46 +468,18 @@ var useApplyCustomAnimation = (animationDuration) => {
443
468
  };
444
469
  //#endregion
445
470
  //#region hooks/usePreserveScroll.ts
446
- var usePreserveScroll = ({ pathname, preserveScroll, nextPathname }) => {
447
- const [scrollMap, setScrollMap] = useState({});
448
- const prevPathname = useRef("");
449
- useEffect(() => {
450
- setScrollMap((prevState) => {
451
- const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
452
- if (!scrollPosition || prevState[prevPathname.current] === scrollPosition) return prevState;
453
- return {
454
- ...prevState,
455
- [prevPathname.current]: scrollPosition
456
- };
457
- });
458
- prevPathname.current = pathname;
459
- }, [pathname, nextPathname]);
471
+ var usePreserveScroll = (preserveScroll) => {
472
+ const { restoreScroll } = useRouterActions();
473
+ const { routeItemData: { location: { pathname } } } = useNavigationState();
460
474
  useEffect(() => {
461
- if (!preserveScroll || !pathname || !scrollMap[pathname]) return;
462
- requestAnimationFrame(() => {
463
- window.scrollTo({
464
- top: scrollMap[pathname],
465
- behavior: "smooth"
466
- });
467
- });
475
+ if (preserveScroll) restoreScroll();
468
476
  }, [
469
- pathname,
470
- scrollMap,
471
- preserveScroll
477
+ preserveScroll,
478
+ restoreScroll,
479
+ pathname
472
480
  ]);
473
481
  };
474
482
  //#endregion
475
- //#region context/RouterViewContext.ts
476
- var RouterViewContext = createContext({});
477
- //#endregion
478
- //#region provider/ViewProvider.tsx
479
- var ViewProvider = ({ children, params }) => {
480
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(RouterViewContext.Provider, {
481
- value: params,
482
- children
483
- });
484
- };
485
- //#endregion
486
483
  //#region components/Spinner.tsx
487
484
  var Spinner = () => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: "cr-spinner" });
488
485
  //#endregion
@@ -493,55 +490,27 @@ var renderElement = (Component) => {
493
490
  };
494
491
  //#endregion
495
492
  //#region components/Router.tsx
496
- var ALL_LOCATIONS = "*";
497
- var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true }) => {
498
- const { location, isLoading, nextItemData } = useNavigationState();
499
- const { routeList } = usePropsData();
500
- const { loaderState } = useRouterData();
501
- usePreserveScroll({
502
- nextPathname: nextItemData.pathname,
503
- pathname: location.pathname,
504
- preserveScroll
505
- });
493
+ var Router = ({ isAnimated, animationDuration, spinner = true, preserveScroll = true, showFallbackIfAnimated = false }) => {
494
+ const { isLoading, routeItemData: { routeItem, loaderState }, currentLoaderFallback } = useNavigationState();
495
+ usePreserveScroll(preserveScroll);
506
496
  useApplyCustomAnimation(animationDuration);
507
- const routeItem = useMemo(() => {
508
- if (!location.pathname) return void 0;
509
- return routeList.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, location.pathname));
510
- }, [location.pathname, routeList]);
511
- const params = useMemo(() => getParamsObject({
512
- params: nextItemData.params,
513
- pathname: nextItemData.pathname || location.pathname
514
- }), [
515
- location.pathname,
516
- nextItemData.params,
517
- nextItemData.pathname
518
- ]);
519
- const shouldErrorElementShown = useMemo(() => Boolean(loaderState[location.pathname]?.loaderError || loaderState[location.pathname]?.beforeLoadError), [loaderState, location.pathname]);
520
- if (!routeItem && !nextItemData.loaderFallback) return null;
521
- if (!routeItem && nextItemData.loaderFallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
522
- params,
523
- children: renderElement(nextItemData.loaderFallback)
524
- });
525
- if (!isAnimated && !shouldErrorElementShown && isLoading && nextItemData.loaderFallback) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(ViewProvider, {
526
- params,
527
- children: renderElement(nextItemData.loaderFallback)
528
- });
529
- if (shouldErrorElementShown) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ViewProvider, {
530
- params,
531
- children: [renderElement(routeItem?.errorElement), spinner && isAnimated && isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
532
- });
533
- return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", {
497
+ const showErrorElement = useMemo(() => Boolean(loaderState.loaderError || loaderState.beforeLoadError), [loaderState]);
498
+ const showSpinner = spinner && isAnimated && isLoading;
499
+ const loadingContent = !showErrorElement && isLoading;
500
+ if ((showFallbackIfAnimated || !isAnimated) && loadingContent) return renderElement(currentLoaderFallback);
501
+ if (!showFallbackIfAnimated && isAnimated && loadingContent) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {});
502
+ if (!routeItem) return null;
503
+ if (showErrorElement) return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(import_jsx_runtime.Fragment, { children: [renderElement(routeItem.errorElement), showSpinner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})] });
504
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", {
534
505
  style: { viewTransitionName: "page" },
535
- children: /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(ViewProvider, {
536
- params,
537
- children: [renderElement(routeItem?.element) || null, spinner && isAnimated && isLoading && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
538
- })
506
+ children: [renderElement(routeItem.element) || null, showSpinner && /* @__PURE__ */ (0, import_jsx_runtime.jsx)(Spinner, {})]
539
507
  });
540
508
  };
541
509
  //#endregion
542
510
  //#region hooks/useLocation.ts
543
511
  var useLocation = () => {
544
- return useNavigationState().location;
512
+ const { routeItemData: { location } } = useNavigationState();
513
+ return location;
545
514
  };
546
515
  //#endregion
547
516
  //#region hooks/useNavigate.ts
@@ -588,19 +557,24 @@ var Link = ({ children, to, prefetch = true, prefetchDelay = STANDARD_DELAY }) =
588
557
  //#endregion
589
558
  //#region hooks/useParams.ts
590
559
  var useParams = () => {
591
- return useContext(RouterViewContext);
560
+ const { routeItemData: { routeItem } } = useNavigationState();
561
+ if (!routeItem) return void 0;
562
+ return getParamsObject({
563
+ params: routeItem?.params,
564
+ pathname: location.pathname
565
+ });
592
566
  };
593
567
  //#endregion
594
568
  //#region hooks/useLoaderState.ts
595
569
  var useLoaderState = () => {
596
- const { pathname } = useLocation();
597
- const { loaderState } = useRouterData();
598
- return loaderState[pathname];
570
+ const { routeItemData: { loaderState } } = useNavigationState();
571
+ return loaderState;
599
572
  };
600
573
  //#endregion
601
574
  //#region hooks/useBlocker.ts
602
575
  var useBlocker = (blockerFn) => {
603
- const { location: { pathname }, blockerState } = useNavigationState();
576
+ const { blockerState } = useNavigationState();
577
+ const { routeItemData: { location: { pathname } } } = useNavigationState();
604
578
  const { updateBlockedRoute } = useRouterActions();
605
579
  const shouldBlock = blockerFn();
606
580
  useEffect(() => updateBlockedRoute(shouldBlock ? {
@@ -643,8 +617,8 @@ var useRouterContext = () => {
643
617
  //#endregion
644
618
  //#region hooks/useSearchParams.ts
645
619
  var useSearchParams = () => {
646
- const location = useLocation();
647
- const { setLocation } = useRouterActions();
620
+ const { routeItemData: { location } } = useNavigationState();
621
+ const { setSearch } = useRouterActions();
648
622
  const locationRef = useLatest(location);
649
623
  const searchString = useMemo(() => location.search ? location.search.replace("?", "") : location.pathname.split("?")?.[1] ?? "", [location.pathname, location.search]);
650
624
  const searchParams = useMemo(() => new URLSearchParams(searchString), [searchString]);
@@ -656,12 +630,9 @@ var useSearchParams = () => {
656
630
  const newSearch = params.toString();
657
631
  const { pathname } = locationRef.current;
658
632
  const search = newSearch ? `?${newSearch}` : "";
659
- setLocation((prevState) => ({
660
- ...prevState,
661
- search
662
- }));
633
+ setSearch(search);
663
634
  history.replaceState(null, "", pathname + search);
664
- }, [locationRef, setLocation]);
635
+ }, [locationRef, setSearch]);
665
636
  return {
666
637
  searchParams,
667
638
  getSearchParams,
@@ -1,7 +1,7 @@
1
1
  import { type ReactNode } from 'react';
2
- import { type ActionsContextValue, type DataContextValue, type NavigationContextValue, PropsContextValue } from '../context/RouterProviderContext';
3
- type ProviderProps = PropsContextValue & NavigationContextValue & ActionsContextValue & DataContextValue & {
2
+ import { type ActionsContextValue, type DataContextValue, type NavigationContextValue } from '../context/RouterProviderContext';
3
+ type ProviderProps = 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, nextItemData, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
6
+ export declare const Provider: ({ children, setContext, context, updateBlockedRoute, updateLocation, setSearch, prefetchLoader, blockerState, isLoading, routeItemData, restoreScroll, currentLoaderFallback, }: ProviderProps) => import("react/jsx-runtime").JSX.Element;
7
7
  export {};
@@ -1,4 +1,4 @@
1
- import type { ComponentType, Dispatch, ReactElement, SetStateAction } from 'react';
1
+ import type { ComponentType, Dispatch, ReactElement, RefObject, SetStateAction } from 'react';
2
2
  export type LazyComponent = () => Promise<{
3
3
  default: ComponentType<unknown>;
4
4
  }>;
@@ -48,19 +48,19 @@ export type UpdateBlockedRouteProps = {
48
48
  export type RevalidateCacheArgs = {
49
49
  pathname: string;
50
50
  routeItem?: RouteItem;
51
- isCurrentRoute?: boolean;
51
+ loaderState?: RefObject<LoaderState>;
52
52
  };
53
- export type LoaderState = Record<string, {
53
+ export type LoaderState = {
54
54
  data: unknown;
55
55
  loaderError: Error | null;
56
56
  beforeLoadError: Error | null;
57
- }>;
57
+ };
58
58
  export type Adapter<T> = {
59
59
  parse: (params: string[]) => T;
60
60
  serialize?: (params: T) => string | string[];
61
61
  };
62
- export type NextItemData = {
63
- loaderFallback: RouteItem['loaderFallback'];
64
- params: RouteItem['params'];
65
- pathname: string;
62
+ export type RouteItemData = {
63
+ location: Location;
64
+ routeItem: RouteItem | undefined;
65
+ loaderState: LoaderState;
66
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.4.6",
3
+ "version": "1.4.7",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -1 +0,0 @@
1
- export declare const RouterViewContext: import("react").Context<Record<string, string>>;
@@ -1,7 +0,0 @@
1
- import { type ReactNode } from 'react';
2
- type ViewProviderProps = {
3
- params: Record<string, string>;
4
- children: ReactNode;
5
- };
6
- export declare const ViewProvider: ({ children, params }: ViewProviderProps) => import("react/jsx-runtime").JSX.Element;
7
- export {};