clear-react-router 1.7.3 → 1.7.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  # Clear Router
4
4
 
5
- A lightweight, type-safe routing library for React applications with nested routes, data loading, navigation blocking, prefetching, and route actions.
5
+ A lightweight, type-safe routing library for client side React applications with nested routes, data loading, navigation blocking, prefetching, and route actions.
6
6
 
7
7
  ## Why Clear Router?
8
8
 
@@ -418,6 +418,8 @@ You can also invalidate any registered route by passing its pathname:
418
418
  const invalidate = useInvalidate();
419
419
 
420
420
  await invalidate('/posts');
421
+ await invalidate('/posts/[id]');
422
+ await invalidate('/posts/[id]/comment/[id]');
421
423
  ```
422
424
 
423
425
  The route does not need to be currently active. Its cache will be marked as stale, and the next time it is visited, `beforeLoad` and `loader` will run again.
package/dist/cell.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ declare class Cell<T> {
2
+ private _value;
3
+ constructor(_value: T);
4
+ get value(): T;
5
+ set(action: T | ((prev: T) => T)): void;
6
+ }
7
+ export declare const loaderStateRef: Cell<import("./types/global").LoaderState>;
8
+ export declare const prevPathnameRef: Cell<string>;
9
+ export declare const timestampMap: Map<string, number>;
10
+ export {};
@@ -1,6 +1,9 @@
1
1
  import { RouterProps } from '../types/global';
2
2
  declare class RouterConfig {
3
+ routes: RouterProps['routes'];
3
4
  prefetch: RouterProps['prefetch'];
5
+ isAnimated: RouterProps['isAnimated'];
6
+ showFallbackOnAnimation: RouterProps['showFallbackOnAnimation'];
4
7
  hoverPrefetchDelay: number;
5
8
  configure(config: Partial<RouterConfig>): void;
6
9
  }
@@ -1,15 +1 @@
1
- import { RefObject } from 'react';
2
- import { LoaderState, Location, RevalidateCacheArgs, RouteItem } from '../types/global';
3
- type UseHandleNavigation = {
4
- routes: RouteItem[];
5
- revalidateCache(arg: RevalidateCacheArgs): Promise<unknown>;
6
- isCacheItemFresh(arg: {
7
- routeItem?: RouteItem;
8
- pathname: string;
9
- }): boolean;
10
- loaderStateRef: RefObject<LoaderState>;
11
- isAnimated: boolean;
12
- showFallbackOnAnimation: boolean;
13
- };
14
- export declare const useNavigation: ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef, isAnimated, showFallbackOnAnimation: showFallback, }: UseHandleNavigation) => (nextLocation: Location) => Promise<void>;
15
- export {};
1
+ export declare const useNavigation: () => void;
@@ -1,2 +1,2 @@
1
1
  import { RouterProps } from '../types/global';
2
- export declare const useSetRouterConfig: (routerProps: Omit<RouterProps, "routes">) => void;
2
+ export declare const useSetRouterConfig: (routerProps: RouterProps) => void;
package/dist/index.js CHANGED
@@ -37,28 +37,88 @@ var createState = (initialState) => {
37
37
  var emptyLoaderState = {};
38
38
  //#endregion
39
39
  //#region state/state.ts
40
- var useIsLoading = createState(false);
40
+ var isLoadingState = create(false);
41
+ var useIsLoading = () => useGlobalState(isLoadingState);
41
42
  var useBlockedRoute = createState({
42
43
  from: "",
43
44
  to: ""
44
45
  });
45
- var useLoaderFallback = createState(void 0);
46
- var useRouteItemData = createState({
46
+ var loaderFallbackState = create(void 0);
47
+ var useLoaderFallback = () => useGlobalState(loaderFallbackState);
48
+ var routeItemDataState = create({
47
49
  routeItem: void 0,
48
50
  location: {}
49
51
  });
50
- var useCurrentLoaderState = createState(emptyLoaderState);
51
- var useScrollMap = createState({});
52
- var useContextState = createState({});
53
- var useActionState = createState({});
52
+ var useRouteItemData = () => useGlobalState(routeItemDataState);
53
+ var currentLoaderState = create(emptyLoaderState);
54
+ var useCurrentLoaderState = () => useGlobalState(currentLoaderState);
55
+ var scrollMapState = create({});
56
+ var useScrollMap = () => useGlobalState(scrollMapState);
57
+ var contextState = create({});
58
+ var useContextState = () => useGlobalState(contextState);
54
59
  //#endregion
55
- //#region hooks/useLatest.ts
56
- var useLatest = (value) => {
57
- const ref = useRef(value);
58
- useEffect(() => {
59
- ref.current = value;
60
- }, [value]);
61
- return ref;
60
+ //#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
61
+ function _typeof(o) {
62
+ "@babel/helpers - typeof";
63
+ return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
64
+ return typeof o;
65
+ } : function(o) {
66
+ return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
67
+ }, _typeof(o);
68
+ }
69
+ //#endregion
70
+ //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
71
+ function toPrimitive(t, r) {
72
+ if ("object" != _typeof(t) || !t) return t;
73
+ var e = t[Symbol.toPrimitive];
74
+ if (void 0 !== e) {
75
+ var i = e.call(t, r || "default");
76
+ if ("object" != _typeof(i)) return i;
77
+ throw new TypeError("@@toPrimitive must return a primitive value.");
78
+ }
79
+ return ("string" === r ? String : Number)(t);
80
+ }
81
+ //#endregion
82
+ //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
83
+ function toPropertyKey(t) {
84
+ var i = toPrimitive(t, "string");
85
+ return "symbol" == _typeof(i) ? i : i + "";
86
+ }
87
+ //#endregion
88
+ //#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
89
+ function _defineProperty(e, r, t) {
90
+ return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
91
+ value: t,
92
+ enumerable: !0,
93
+ configurable: !0,
94
+ writable: !0
95
+ }) : e[r] = t, e;
96
+ }
97
+ //#endregion
98
+ //#region cell.ts
99
+ var Cell = class {
100
+ constructor(_value) {
101
+ _defineProperty(this, "_value", void 0);
102
+ this._value = _value;
103
+ }
104
+ get value() {
105
+ return this._value;
106
+ }
107
+ set(action) {
108
+ this._value = typeof action === "function" ? action(this._value) : action;
109
+ }
110
+ };
111
+ var loaderStateRef = new Cell(emptyLoaderState);
112
+ var prevPathnameRef = new Cell("");
113
+ var timestampMap = /* @__PURE__ */ new Map();
114
+ //#endregion
115
+ //#region utils/isCacheItemFresh.ts
116
+ var isCacheItemFresh = ({ routeItem, pathname }) => {
117
+ if (!routeItem) return true;
118
+ const currentCacheTimestamp = timestampMap.get(pathname);
119
+ if (!currentCacheTimestamp) return false;
120
+ if (!routeItem.staleTime) return true;
121
+ return Date.now() - currentCacheTimestamp < routeItem.staleTime;
62
122
  };
63
123
  //#endregion
64
124
  //#region ../../node_modules/react/cjs/react-jsx-runtime.production.js
@@ -149,278 +209,194 @@ var comparePaths = (el, pathname) => {
149
209
  return splitElementPath.every((item, index) => item === splitPathname[index + (index ? 1 : 0)]) && splitPathname.length === splitElementPath.length + paramsLength;
150
210
  };
151
211
  //#endregion
152
- //#region hooks/useLoader.ts
153
- var useLoader = (routes) => {
154
- const [, setLoaderState] = useCurrentLoaderState();
155
- const [routeItemData] = useRouteItemData();
156
- const [context, setContext] = useContextState();
157
- const loaderStateRef = useRef(emptyLoaderState);
158
- const latestPathname = useLatest(routeItemData.location.pathname);
159
- const timestampMapRef = useRef(/* @__PURE__ */ new Map());
160
- const loaderMapRef = useRef({});
161
- const loadingPromises = useRef(/* @__PURE__ */ new Map());
162
- const isCacheItemFresh = useCallback(({ routeItem, pathname }) => {
163
- if (!routeItem) return true;
164
- const currentCacheTimestamp = timestampMapRef.current.get(pathname);
165
- if (!currentCacheTimestamp) return false;
166
- if (!routeItem.staleTime) return true;
167
- return Date.now() - currentCacheTimestamp < routeItem.staleTime;
168
- }, []);
169
- const revalidateCache = useCallback(async ({ routeItem, pathname }) => {
170
- if (!routeItem?.loader) return;
171
- if (loadingPromises.current.has(pathname)) return loadingPromises.current.get(pathname);
172
- if (isCacheItemFresh({
173
- routeItem,
174
- pathname
175
- })) {
176
- loaderStateRef.current = loaderMapRef.current[pathname];
177
- return;
178
- }
179
- const promise = (async () => {
180
- if (!routeItem?.loader) return;
181
- try {
182
- const params = getParamsObject({
183
- params: routeItem.params,
184
- pathname
185
- });
186
- const result = await routeItem?.loader({
187
- params,
188
- context,
189
- setContext
190
- });
191
- timestampMapRef.current.set(pathname, Date.now());
192
- loaderStateRef.current = {
193
- ...loaderStateRef?.current,
194
- data: result,
195
- loaderError: null
196
- };
197
- loaderMapRef.current[pathname] = loaderStateRef.current;
198
- } catch (error) {
199
- loaderStateRef.current = {
200
- ...loaderStateRef?.current,
201
- data: null,
202
- loaderError: error
203
- };
204
- } finally {
205
- loadingPromises.current.delete(pathname);
206
- }
207
- })();
208
- loadingPromises.current.set(pathname, promise);
209
- return promise;
210
- }, [
211
- context,
212
- isCacheItemFresh,
213
- setContext
214
- ]);
212
+ //#region utils/getContext.ts
213
+ var getContext = () => {
215
214
  return {
216
- prefetchLoader: useCallback(async (pathname) => {
217
- const item = routes.find((el) => comparePaths(el, pathname));
218
- if (item) await revalidateCache({
219
- routeItem: item,
220
- pathname
221
- });
222
- }, [revalidateCache, routes]),
223
- revalidateCache,
224
- isCacheItemFresh,
225
- loaderStateRef,
226
- invalidate: useCallback(async (pathname = latestPathname.current) => {
227
- if (typeof pathname !== "string") return;
228
- const routeItem = routes.find((el) => comparePaths(el, pathname));
229
- const resultParams = getParamsObject({
230
- params: routeItem?.params,
215
+ context: contextState.getState(),
216
+ setContext: contextState.setState
217
+ };
218
+ };
219
+ //#endregion
220
+ //#region utils/revalidateCache.ts
221
+ var loaderMapRef = {};
222
+ var loadingPromises = /* @__PURE__ */ new Map();
223
+ var revalidateCache = ({ routeItem, pathname }) => {
224
+ if (!routeItem?.loader) return;
225
+ if (loadingPromises.has(pathname)) return loadingPromises.get(pathname);
226
+ if (isCacheItemFresh({
227
+ routeItem,
228
+ pathname
229
+ })) {
230
+ loaderStateRef.set(loaderMapRef[pathname]);
231
+ return;
232
+ }
233
+ const promise = (async () => {
234
+ if (!routeItem?.loader) return;
235
+ try {
236
+ const { context, setContext } = getContext();
237
+ const params = getParamsObject({
238
+ params: routeItem.params,
231
239
  pathname
232
240
  });
233
- timestampMapRef.current.delete(pathname);
234
- try {
235
- if (routeItem?.beforeLoad) await routeItem.beforeLoad({
236
- context,
237
- redirect: () => Promise.resolve(),
238
- params: resultParams,
239
- setContext
240
- });
241
- loaderStateRef.current = {
242
- ...loaderStateRef.current,
243
- beforeLoadError: null
244
- };
245
- } catch (error) {
246
- loaderStateRef.current = {
247
- ...loaderStateRef.current,
248
- beforeLoadError: error
249
- };
250
- }
251
- await revalidateCache({
252
- routeItem,
253
- pathname
241
+ const result = await routeItem?.loader({
242
+ params,
243
+ context,
244
+ setContext
254
245
  });
255
- if (pathname === latestPathname.current) setLoaderState(loaderStateRef.current);
256
- }, [
257
- context,
258
- loaderStateRef,
259
- revalidateCache,
260
- latestPathname,
261
- routes,
262
- setContext,
263
- setLoaderState
264
- ])
265
- };
246
+ timestampMap.set(pathname, Date.now());
247
+ loaderStateRef.set((prev) => ({
248
+ ...prev,
249
+ data: result,
250
+ loaderError: null
251
+ }));
252
+ loaderMapRef[pathname] = loaderStateRef.value;
253
+ } catch (error) {
254
+ loaderStateRef.set((prev) => ({
255
+ ...prev,
256
+ data: null,
257
+ loaderError: error
258
+ }));
259
+ } finally {
260
+ loadingPromises.delete(pathname);
261
+ }
262
+ })();
263
+ loadingPromises.set(pathname, promise);
264
+ return promise;
266
265
  };
267
266
  //#endregion
268
- //#region hooks/useNavigation.ts
269
- var ALL_LOCATIONS = "*";
270
- var useNavigation = ({ routes, revalidateCache, isCacheItemFresh, loaderStateRef, isAnimated, showFallbackOnAnimation: showFallback }) => {
271
- const [, setIsLoading] = useIsLoading();
272
- const [, setScrollMap] = useScrollMap();
273
- const [, setRouteItemData] = useRouteItemData();
274
- const [, setLoaderState] = useCurrentLoaderState();
275
- const [, setCurrentLoaderFallback] = useLoaderFallback();
276
- const [context, setContext] = useContextState();
277
- const [blockedRoute, setBlockedRoute] = useBlockedRoute();
278
- const prevPathname = useRef("");
279
- const navigationSeq = useRef(0);
280
- const navigation = useCallback((nextLocation, routeItem) => {
281
- setRouteItemData({
282
- routeItem,
283
- location: nextLocation
284
- });
285
- setLoaderState(loaderStateRef.current);
286
- setIsLoading(false);
287
- setCurrentLoaderFallback(void 0);
288
- prevPathname.current = nextLocation.pathname;
289
- const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
290
- if (fullPath === window.location.pathname + window.location.search) return;
291
- history.pushState(null, "", fullPath);
292
- }, [
293
- loaderStateRef,
294
- setCurrentLoaderFallback,
295
- setIsLoading,
296
- setLoaderState,
297
- setRouteItemData
298
- ]);
299
- const transitionedNavigation = useCallback((nextLocation, routeItem) => {
300
- if (!isAnimated || !prevPathname.current) {
301
- navigation(nextLocation, routeItem);
302
- return;
303
- }
267
+ //#region utils/navigationExecutor.ts
268
+ var navigationExecutor = (nextLocation, routeItem) => {
269
+ routeItemDataState.setState({
270
+ routeItem,
271
+ location: nextLocation
272
+ });
273
+ currentLoaderState.setState(loaderStateRef.value);
274
+ isLoadingState.setState(false);
275
+ loaderFallbackState.setState(void 0);
276
+ prevPathnameRef.set(nextLocation.pathname);
277
+ const fullPath = nextLocation.search ? `${nextLocation.pathname}${nextLocation.search}` : nextLocation.pathname;
278
+ if (fullPath === window.location.pathname + window.location.search) return;
279
+ history.pushState(null, "", fullPath);
280
+ };
281
+ //#endregion
282
+ //#region config/routerConfig.ts
283
+ var RouterConfig = class {
284
+ constructor() {
285
+ _defineProperty(this, "routes", []);
286
+ _defineProperty(this, "prefetch", "hover");
287
+ _defineProperty(this, "isAnimated", false);
288
+ _defineProperty(this, "showFallbackOnAnimation", false);
289
+ _defineProperty(this, "hoverPrefetchDelay", 150);
290
+ }
291
+ configure(config) {
292
+ Object.assign(this, config);
293
+ }
294
+ };
295
+ var routerConfig = new RouterConfig();
296
+ //#endregion
297
+ //#region utils/transitionedNavigation.ts
298
+ var transitionedNavigation = (nextLocation, routeItem) => {
299
+ const { isAnimated } = routerConfig;
300
+ if (!isAnimated || !prevPathnameRef.value) {
301
+ navigationExecutor(nextLocation, routeItem);
302
+ return;
303
+ }
304
+ try {
305
+ document.startViewTransition(() => navigationExecutor(nextLocation, routeItem));
306
+ } catch {
307
+ navigationExecutor(nextLocation, routeItem);
308
+ }
309
+ };
310
+ var findRoute = (pathname, includeAll) => {
311
+ if (includeAll) return routerConfig.routes.find((el) => el.path === "*" || comparePaths(el, pathname));
312
+ return routerConfig.routes.find((el) => el.path === "*" || comparePaths(el, pathname));
313
+ };
314
+ //#endregion
315
+ //#region runtime/navigate.ts
316
+ var navigationSeq = 0;
317
+ var navigate = async (nextLocation) => {
318
+ navigationSeq = navigationSeq + 1;
319
+ const seq = navigationSeq;
320
+ loaderStateRef.set(emptyLoaderState);
321
+ const { isAnimated, showFallbackOnAnimation: showFallback } = routerConfig;
322
+ const nextItem = findRoute(nextLocation.pathname, true);
323
+ const params = getParamsObject({
324
+ params: nextItem?.params,
325
+ pathname: nextLocation.pathname
326
+ });
327
+ const { context, setContext } = getContext();
328
+ if (nextItem?.beforeLoad) {
329
+ const redirect = async (location) => await navigate(typeof location === "string" ? { pathname: location } : location);
304
330
  try {
305
- document.startViewTransition(() => navigation(nextLocation, routeItem));
306
- } catch {
307
- navigation(nextLocation, routeItem);
308
- }
309
- }, [navigation, isAnimated]);
310
- const navigationHandler = useCallback(async (nextLocation) => {
311
- navigationSeq.current = navigationSeq.current + 1;
312
- const seq = navigationSeq.current;
313
- loaderStateRef.current = emptyLoaderState;
314
- const nextItem = routes.find((el) => el.path === ALL_LOCATIONS || comparePaths(el, nextLocation.pathname));
315
- const params = getParamsObject({
316
- params: nextItem?.params,
317
- pathname: nextLocation.pathname
318
- });
319
- if (nextItem?.beforeLoad) {
320
- const redirect = async (location) => await navigationHandler(typeof location === "string" ? { pathname: location } : location);
321
- try {
322
- await nextItem.beforeLoad({
323
- context,
324
- redirect,
325
- params,
326
- setContext
327
- });
328
- loaderStateRef.current = {
329
- ...loaderStateRef.current,
330
- beforeLoadError: null
331
- };
332
- } catch (error) {
333
- loaderStateRef.current = {
334
- ...loaderStateRef.current,
335
- beforeLoadError: error
336
- };
337
- return transitionedNavigation(nextLocation, nextItem);
338
- }
331
+ await nextItem.beforeLoad({
332
+ context,
333
+ redirect,
334
+ params,
335
+ setContext
336
+ });
337
+ loaderStateRef.set((prev) => ({
338
+ ...prev,
339
+ beforeLoadError: null
340
+ }));
341
+ } catch (error) {
342
+ loaderStateRef.set((prev) => ({
343
+ ...prev,
344
+ beforeLoadError: error
345
+ }));
346
+ return transitionedNavigation(nextLocation, nextItem);
339
347
  }
340
- if (seq !== navigationSeq.current) return;
341
- setScrollMap((prevState) => {
342
- const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
343
- if (!scrollPosition || prevState[prevPathname.current] === scrollPosition) return prevState;
344
- return {
345
- ...prevState,
346
- [prevPathname.current]: scrollPosition
347
- };
348
- });
349
- setCurrentLoaderFallback(isCacheItemFresh({
348
+ }
349
+ if (seq !== navigationSeq) return;
350
+ scrollMapState.setState((prevState) => {
351
+ const scrollPosition = document.scrollingElement?.scrollTop ?? 0;
352
+ if (!scrollPosition || prevState[prevPathnameRef.value] === scrollPosition) return prevState;
353
+ return {
354
+ ...prevState,
355
+ [prevPathnameRef.value]: scrollPosition
356
+ };
357
+ });
358
+ loaderFallbackState.setState(isCacheItemFresh({
359
+ routeItem: nextItem,
360
+ pathname: nextLocation.pathname
361
+ }) || isAnimated && !showFallback ? void 0 : nextItem?.loaderFallback);
362
+ if (nextItem?.loader) {
363
+ isLoadingState.setState(true);
364
+ await revalidateCache({
350
365
  routeItem: nextItem,
351
366
  pathname: nextLocation.pathname
352
- }) || isAnimated && !showFallback ? void 0 : nextItem?.loaderFallback);
353
- if (nextItem?.loader) {
354
- setIsLoading(true);
355
- await revalidateCache({
356
- routeItem: nextItem,
357
- pathname: nextLocation.pathname
358
- });
359
- }
360
- if (seq !== navigationSeq.current) return;
361
- transitionedNavigation(nextLocation, nextItem);
362
- if (nextItem?.afterLoad) await nextItem.afterLoad({
363
- context,
364
- params,
365
- setContext
366
367
  });
367
- }, [
368
+ }
369
+ if (seq !== navigationSeq) return;
370
+ transitionedNavigation(nextLocation, nextItem);
371
+ if (nextItem?.afterLoad) await nextItem.afterLoad({
368
372
  context,
369
- isAnimated,
370
- isCacheItemFresh,
371
- loaderStateRef,
372
- revalidateCache,
373
- routes,
374
- setContext,
375
- setCurrentLoaderFallback,
376
- setIsLoading,
377
- setScrollMap,
378
- showFallback,
379
- transitionedNavigation
380
- ]);
381
- const setNextLocationRef = useLatest(navigationHandler);
382
- const updateLocation = useCallback(async (nextLocation) => await setNextLocationRef.current(nextLocation), [setNextLocationRef]);
373
+ params,
374
+ setContext
375
+ });
376
+ };
377
+ //#endregion
378
+ //#region hooks/useNavigation.ts
379
+ var useNavigation = () => {
380
+ const [blockedRoute, setBlockedRoute] = useBlockedRoute();
383
381
  useEffect(() => {
384
382
  const handler = async (event) => {
385
383
  const newLocation = parseWindowLocation(event.target.location);
386
- if (prevPathname.current === blockedRoute.from) {
384
+ if (prevPathnameRef.value === blockedRoute.from) {
387
385
  setBlockedRoute({
388
- from: prevPathname.current,
386
+ from: prevPathnameRef.value,
389
387
  to: newLocation.pathname
390
388
  });
391
- history.pushState(null, "", prevPathname.current);
392
- } else setNextLocationRef.current(newLocation);
389
+ history.pushState(null, "", prevPathnameRef.value);
390
+ } else navigate(newLocation);
393
391
  };
394
392
  window.addEventListener("popstate", handler);
395
393
  return () => window.removeEventListener("popstate", handler);
396
- }, [
397
- blockedRoute.from,
398
- setBlockedRoute,
399
- setNextLocationRef
400
- ]);
394
+ }, [blockedRoute.from, setBlockedRoute]);
401
395
  useEffect(() => {
402
396
  const currentLocation = parseWindowLocation(window.location);
403
- setNextLocationRef.current(currentLocation);
404
- prevPathname.current = currentLocation.pathname;
405
- }, [setNextLocationRef]);
406
- return updateLocation;
407
- };
408
- //#endregion
409
- //#region hooks/useSetRuntime.ts
410
- var useSetRouterRuntime = ({ updateLocation, prefetchLoader, invalidate }) => {
411
- const [, setCallbackState] = useActionState();
412
- useEffect(() => {
413
- setCallbackState({
414
- updateLocation,
415
- prefetchLoader,
416
- invalidate
417
- });
418
- }, [
419
- invalidate,
420
- prefetchLoader,
421
- setCallbackState,
422
- updateLocation
423
- ]);
397
+ navigate(currentLocation);
398
+ prevPathnameRef.set(currentLocation.pathname);
399
+ }, []);
424
400
  };
425
401
  //#endregion
426
402
  //#region hooks/useApplyCustomAnimation.ts
@@ -483,56 +459,6 @@ var usePreserveScroll = (preserveScroll) => {
483
459
  ]);
484
460
  };
485
461
  //#endregion
486
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/typeof.js
487
- function _typeof(o) {
488
- "@babel/helpers - typeof";
489
- return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function(o) {
490
- return typeof o;
491
- } : function(o) {
492
- return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o;
493
- }, _typeof(o);
494
- }
495
- //#endregion
496
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPrimitive.js
497
- function toPrimitive(t, r) {
498
- if ("object" != _typeof(t) || !t) return t;
499
- var e = t[Symbol.toPrimitive];
500
- if (void 0 !== e) {
501
- var i = e.call(t, r || "default");
502
- if ("object" != _typeof(i)) return i;
503
- throw new TypeError("@@toPrimitive must return a primitive value.");
504
- }
505
- return ("string" === r ? String : Number)(t);
506
- }
507
- //#endregion
508
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/toPropertyKey.js
509
- function toPropertyKey(t) {
510
- var i = toPrimitive(t, "string");
511
- return "symbol" == _typeof(i) ? i : i + "";
512
- }
513
- //#endregion
514
- //#region \0@oxc-project+runtime@0.133.0/helpers/esm/defineProperty.js
515
- function _defineProperty(e, r, t) {
516
- return (r = toPropertyKey(r)) in e ? Object.defineProperty(e, r, {
517
- value: t,
518
- enumerable: !0,
519
- configurable: !0,
520
- writable: !0
521
- }) : e[r] = t, e;
522
- }
523
- //#endregion
524
- //#region config/routerConfig.ts
525
- var RouterConfig = class {
526
- constructor() {
527
- _defineProperty(this, "prefetch", "hover");
528
- _defineProperty(this, "hoverPrefetchDelay", 150);
529
- }
530
- configure(config) {
531
- Object.assign(this, config);
532
- }
533
- };
534
- var routerConfig = new RouterConfig();
535
- //#endregion
536
462
  //#region hooks/useSetRouterConfig.ts
537
463
  var useSetRouterConfig = (routerProps) => {
538
464
  useEffect(() => routerConfig.configure(routerProps), [routerProps]);
@@ -562,24 +488,13 @@ var Router = ({ routes, animationDuration, isAnimated = false, spinner = true, p
562
488
  const [currentLoaderFallback] = useLoaderFallback();
563
489
  const [routeItemData] = useRouteItemData();
564
490
  const [loaderState] = useCurrentLoaderState();
565
- const { prefetchLoader, revalidateCache, isCacheItemFresh, loaderStateRef, invalidate } = useLoader(routes);
566
- useSetRouterRuntime({
567
- updateLocation: useNavigation({
568
- routes,
569
- revalidateCache,
570
- isCacheItemFresh,
571
- loaderStateRef,
572
- isAnimated,
573
- showFallbackOnAnimation
574
- }),
575
- prefetchLoader,
576
- invalidate
577
- });
491
+ useNavigation();
578
492
  useSetRouterConfig({
493
+ routes,
579
494
  isAnimated,
580
- showFallbackOnAnimation,
581
495
  prefetch,
582
- hoverPrefetchDelay
496
+ hoverPrefetchDelay,
497
+ showFallbackOnAnimation
583
498
  });
584
499
  useApplyCustomAnimation(animationDuration);
585
500
  useSetInitialContext(initialContext);
@@ -598,19 +513,13 @@ var Router = ({ routes, animationDuration, isAnimated = false, spinner = true, p
598
513
  });
599
514
  };
600
515
  //#endregion
601
- //#region hooks/useRuntime.ts
602
- var useRuntime = () => {
603
- const [callbackState] = useActionState();
604
- const { updateLocation, prefetchLoader, invalidate } = callbackState;
605
- return useMemo(() => ({
606
- updateLocation,
607
- prefetchLoader,
608
- invalidate
609
- }), [
610
- updateLocation,
611
- prefetchLoader,
612
- invalidate
613
- ]);
516
+ //#region runtime/prefetch.ts
517
+ var prefetch = async (pathname) => {
518
+ const item = findRoute(pathname);
519
+ if (item) await revalidateCache({
520
+ routeItem: item,
521
+ pathname
522
+ });
614
523
  };
615
524
  //#endregion
616
525
  //#region hooks/useLocation.ts
@@ -619,14 +528,22 @@ var useLocation = () => {
619
528
  return routeItemData.location;
620
529
  };
621
530
  //#endregion
531
+ //#region hooks/useLatest.ts
532
+ var useLatest = (value) => {
533
+ const ref = useRef(value);
534
+ useEffect(() => {
535
+ ref.current = value;
536
+ }, [value]);
537
+ return ref;
538
+ };
539
+ //#endregion
622
540
  //#region hooks/useNavigate.ts
623
541
  var useNavigate = () => {
624
542
  const [blockedRoute, setBlockedRoute] = useBlockedRoute();
625
- const { updateLocation } = useRuntime();
626
543
  const locationRef = useLatest(useLocation());
627
544
  const blockedRouteRef = useLatest(blockedRoute);
628
545
  return useCallback(async (arg) => {
629
- if (!updateLocation) throw new Error("Router has not been initialized. Did you forget to render <Router />?");
546
+ if (!navigate) throw new Error("Router has not been initialized. Did you forget to render <Router />?");
630
547
  if (arg !== -1 && blockedRouteRef.current.from) {
631
548
  const to = typeof arg === "object" ? arg.pathname : arg;
632
549
  setBlockedRoute((prevState) => ({
@@ -637,68 +554,57 @@ var useNavigate = () => {
637
554
  }
638
555
  if (arg === -1) return history.go(arg);
639
556
  if (typeof arg === "string") {
640
- if (arg !== locationRef.current.pathname) await updateLocation({ pathname: arg });
641
- } else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await updateLocation(arg);
557
+ if (arg !== locationRef.current.pathname) await navigate({ pathname: arg });
558
+ } else if (JSON.stringify(arg) !== JSON.stringify(locationRef.current)) await navigate(arg);
642
559
  }, [
643
560
  blockedRouteRef,
644
561
  locationRef,
645
- setBlockedRoute,
646
- updateLocation
562
+ setBlockedRoute
647
563
  ]);
648
564
  };
649
565
  //#endregion
650
566
  //#region components/Link.tsx
651
567
  var Link = ({ children, to, prefetch: prefetchLink, hoverPrefetchDelay }) => {
652
568
  const { prefetch: configPrefetch, hoverPrefetchDelay: configPrefetchDelay } = routerConfig;
653
- const prefetch = prefetchLink || configPrefetch;
569
+ const prefetch$1 = prefetchLink || configPrefetch;
654
570
  const prefetchDelay = hoverPrefetchDelay ?? configPrefetchDelay;
655
- const { prefetchLoader } = useRuntime();
656
571
  const navigate = useNavigate();
657
572
  const timeout = useRef(0);
658
573
  const ref = useRef(null);
659
574
  const onMouseEnter = useCallback(() => {
660
- if (prefetch !== "hover" || !prefetchDelay) return;
575
+ if (prefetch$1 !== "hover" || !prefetchDelay) return;
661
576
  if (timeout.current) clearTimeout(timeout.current);
662
- timeout.current = window.setTimeout(() => prefetchLoader(to), prefetchDelay);
577
+ timeout.current = window.setTimeout(() => prefetch(to), prefetchDelay);
663
578
  }, [
664
- prefetch,
579
+ prefetch$1,
665
580
  prefetchDelay,
666
- prefetchLoader,
667
581
  to
668
582
  ]);
669
583
  const onMouseLeave = useCallback(() => {
670
- if (prefetch !== "hover" || !prefetchDelay) return;
584
+ if (prefetch$1 !== "hover" || !prefetchDelay) return;
671
585
  if (timeout.current) {
672
586
  clearTimeout(timeout.current);
673
587
  timeout.current = 0;
674
588
  }
675
- }, [prefetch, prefetchDelay]);
589
+ }, [prefetch$1, prefetchDelay]);
676
590
  useEffect(() => {
677
- if (prefetch !== "render") return;
591
+ if (prefetch$1 !== "render") return;
678
592
  (async () => {
679
- await prefetchLoader(to);
593
+ await prefetch(to);
680
594
  })();
681
- }, [
682
- prefetch,
683
- prefetchLoader,
684
- to
685
- ]);
595
+ }, [prefetch$1, to]);
686
596
  useEffect(() => {
687
- if (prefetch !== "viewport") return;
597
+ if (prefetch$1 !== "viewport") return;
688
598
  const element = ref.current;
689
599
  const observer = new IntersectionObserver(async () => {
690
- await prefetchLoader(to);
600
+ await prefetch(to);
691
601
  observer.disconnect();
692
602
  });
693
603
  if (element) observer.observe(element);
694
604
  return () => {
695
605
  if (element) observer.disconnect();
696
606
  };
697
- }, [
698
- prefetch,
699
- prefetchLoader,
700
- to
701
- ]);
607
+ }, [prefetch$1, to]);
702
608
  return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("a", {
703
609
  ref,
704
610
  style: { cursor: "pointer" },
@@ -718,12 +624,46 @@ var FormProvider = ({ children, isSubmitting }) => /* @__PURE__ */ (0, import_js
718
624
  children
719
625
  });
720
626
  //#endregion
721
- //#region hooks/useInvalidate.ts
722
- var useInvalidate = () => {
723
- const { invalidate } = useRuntime();
724
- return invalidate;
627
+ //#region runtime/invalidate.ts
628
+ var invalidate = async (path) => {
629
+ const routePathname = routeItemDataState.getState().location.pathname;
630
+ const pathname = path || routePathname;
631
+ const routeItem = findRoute(pathname);
632
+ const resultParams = getParamsObject({
633
+ params: routeItem?.params,
634
+ pathname
635
+ });
636
+ timestampMap.delete(pathname);
637
+ try {
638
+ if (routeItem?.beforeLoad) {
639
+ const { context, setContext } = getContext();
640
+ await routeItem.beforeLoad({
641
+ context,
642
+ redirect: () => Promise.resolve(),
643
+ params: resultParams,
644
+ setContext
645
+ });
646
+ }
647
+ loaderStateRef.set((prev) => ({
648
+ ...prev,
649
+ beforeLoadError: null
650
+ }));
651
+ } catch (error) {
652
+ loaderStateRef.set((prev) => ({
653
+ ...prev,
654
+ beforeLoadError: error
655
+ }));
656
+ }
657
+ await revalidateCache({
658
+ routeItem,
659
+ pathname
660
+ });
661
+ if (pathname === routePathname) currentLoaderState.setState(loaderStateRef.value);
725
662
  };
726
663
  //#endregion
664
+ //#region hooks/useInvalidate.ts
665
+ var useInvalidate = () => invalidate;
666
+ //#endregion
727
667
  //#region hooks/useParams.ts
728
668
  var useParams = () => {
729
669
  const [routeItemData] = useRouteItemData();
@@ -793,7 +733,6 @@ var useLoaderState = () => {
793
733
  //#region hooks/useBlocker.ts
794
734
  var useBlocker = (blockerFn) => {
795
735
  const [blockedRoute, setBlockedRoute] = useBlockedRoute();
796
- const { updateLocation } = useRuntime();
797
736
  const [routeItemData] = useRouteItemData();
798
737
  const { location: { pathname } } = routeItemData;
799
738
  const updateBlockedRoute = useCallback(({ type, payload = "" }) => setBlockedRoute((prevState) => {
@@ -806,13 +745,13 @@ var useBlocker = (blockerFn) => {
806
745
  ...prevState,
807
746
  to: ""
808
747
  };
809
- if (type === "process") updateLocation({ pathname: prevState.to });
748
+ if (type === "process") navigate({ pathname: prevState.to });
810
749
  if (!prevState.from && !prevState.to) return prevState;
811
750
  return {
812
751
  from: "",
813
752
  to: ""
814
753
  };
815
- }), [setBlockedRoute, updateLocation]);
754
+ }), [setBlockedRoute]);
816
755
  const blockerState = useMemo(() => {
817
756
  if (blockedRoute.from && blockedRoute.to) return "blocked";
818
757
  if (blockedRoute.from) return "charged";
@@ -0,0 +1 @@
1
+ export declare const invalidate: (path?: string) => Promise<void>;
@@ -0,0 +1,2 @@
1
+ import { Location } from '../types/global';
2
+ export declare const navigate: (nextLocation: Location) => Promise<void>;
@@ -0,0 +1 @@
1
+ export declare const prefetch: (pathname: string) => Promise<void>;
@@ -1,3 +1,11 @@
1
1
  type SetStateAction<T> = ((prevState: T) => T) | T;
2
+ type Listener<T> = (state: T, prevState: T) => void;
3
+ type Store<T> = {
4
+ subscribe: (listener: Listener<T>) => () => void;
5
+ getState: () => T;
6
+ setState: (action: SetStateAction<T>) => void;
7
+ };
8
+ export declare const create: <T>(initialState: T) => Store<T>;
9
+ export declare const useGlobalState: <T>({ subscribe, getState, setState }: Store<T>) => readonly [T, (action: SetStateAction<T>) => void];
2
10
  export declare const createState: <T>(initialState: T) => () => readonly [T, (action: SetStateAction<T>) => void];
3
11
  export {};
@@ -1,8 +1,8 @@
1
- import { LoaderState, Location, RouteItemData } from '../types/global';
2
- type Runtime = {
3
- updateLocation(route: Location): Promise<void>;
4
- prefetchLoader(arg: string): Promise<void>;
5
- invalidate(path?: string): Promise<void>;
1
+ import { LoaderState, RouteItemData } from '../types/global';
2
+ export declare const isLoadingState: {
3
+ subscribe: (listener: (state: boolean, prevState: boolean) => void) => () => void;
4
+ getState: () => boolean;
5
+ setState: (action: boolean | ((prevState: boolean) => boolean)) => void;
6
6
  };
7
7
  export declare const useIsLoading: () => readonly [boolean, (action: boolean | ((prevState: boolean) => boolean)) => void];
8
8
  export declare const useBlockedRoute: () => readonly [{
@@ -18,10 +18,33 @@ export declare const useBlockedRoute: () => readonly [{
18
18
  from: string;
19
19
  to: string;
20
20
  })) => void];
21
- export declare const useLoaderFallback: () => readonly [(import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined, (action: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | ((prevState: (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) => (import("react").ReactElement<unknown, string | import("react").JSXElementConstructor<any>> | (() => import("react").ReactElement)) | undefined) | undefined) => void];
21
+ export declare const loaderFallbackState: {
22
+ subscribe: (listener: (state: import("../types/global").RenderElement | undefined, prevState: import("../types/global").RenderElement | undefined) => void) => () => void;
23
+ getState: () => import("../types/global").RenderElement | undefined;
24
+ setState: (action: import("../types/global").RenderElement | ((prevState: import("../types/global").RenderElement | undefined) => import("../types/global").RenderElement | undefined) | undefined) => void;
25
+ };
26
+ export declare const useLoaderFallback: () => readonly [import("../types/global").RenderElement | undefined, (action: import("../types/global").RenderElement | ((prevState: import("../types/global").RenderElement | undefined) => import("../types/global").RenderElement | undefined) | undefined) => void];
27
+ export declare const routeItemDataState: {
28
+ subscribe: (listener: (state: RouteItemData, prevState: RouteItemData) => void) => () => void;
29
+ getState: () => RouteItemData;
30
+ setState: (action: RouteItemData | ((prevState: RouteItemData) => RouteItemData)) => void;
31
+ };
22
32
  export declare const useRouteItemData: () => readonly [RouteItemData, (action: RouteItemData | ((prevState: RouteItemData) => RouteItemData)) => void];
33
+ export declare const currentLoaderState: {
34
+ subscribe: (listener: (state: LoaderState, prevState: LoaderState) => void) => () => void;
35
+ getState: () => LoaderState;
36
+ setState: (action: LoaderState | ((prevState: LoaderState) => LoaderState)) => void;
37
+ };
23
38
  export declare const useCurrentLoaderState: () => readonly [LoaderState, (action: LoaderState | ((prevState: LoaderState) => LoaderState)) => void];
39
+ export declare const scrollMapState: {
40
+ subscribe: (listener: (state: Record<string, number>, prevState: Record<string, number>) => void) => () => void;
41
+ getState: () => Record<string, number>;
42
+ setState: (action: Record<string, number> | ((prevState: Record<string, number>) => Record<string, number>)) => void;
43
+ };
24
44
  export declare const useScrollMap: () => readonly [Record<string, number>, (action: Record<string, number> | ((prevState: Record<string, number>) => Record<string, number>)) => void];
45
+ export declare const contextState: {
46
+ subscribe: (listener: (state: Record<string, unknown>, prevState: Record<string, unknown>) => void) => () => void;
47
+ getState: () => Record<string, unknown>;
48
+ setState: (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void;
49
+ };
25
50
  export declare const useContextState: () => readonly [Record<string, unknown>, (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void];
26
- export declare const useActionState: () => readonly [Runtime, (action: Runtime | ((prevState: Runtime) => Runtime)) => void];
27
- export {};
@@ -2,18 +2,18 @@ import type { ComponentType, Dispatch, ReactElement, ReactNode, SetStateAction }
2
2
  export type LazyComponent = () => Promise<{
3
3
  default: ComponentType<unknown>;
4
4
  }>;
5
- type Element = (() => ReactElement) | ReactElement;
5
+ export type RenderElement = (() => ReactElement) | ReactElement;
6
6
  export type ClientRouteItem = {
7
7
  path: string;
8
- element: Element | LazyComponent;
8
+ element: RenderElement | LazyComponent;
9
9
  loader?(arg: {
10
10
  params: Record<string, string>;
11
11
  context: Record<string, unknown>;
12
12
  setContext: Dispatch<SetStateAction<Record<string, unknown>>>;
13
13
  }): Promise<unknown>;
14
- loaderFallback?: Element;
15
- errorElement?: Element;
16
- fallback?: Element;
14
+ loaderFallback?: RenderElement;
15
+ errorElement?: RenderElement;
16
+ fallback?: RenderElement;
17
17
  children?: ClientRouteItem[];
18
18
  staleTime?: number;
19
19
  beforeLoad?: (arg: {
@@ -35,7 +35,7 @@ export type ClientRouteItem = {
35
35
  }) => Record<string, (arg: FormData) => Promise<unknown> | Promise<void> | void | unknown>;
36
36
  };
37
37
  export type RouteItem = ClientRouteItem & {
38
- element: Element;
38
+ element: RenderElement;
39
39
  params?: {
40
40
  key: string;
41
41
  value: string;
@@ -71,8 +71,8 @@ export type RouterProps = {
71
71
  animationDuration?: number;
72
72
  spinner?: boolean;
73
73
  preserveScroll?: boolean;
74
- defaultLoaderFallback?: Element;
75
- defaultErrorElement?: Element;
74
+ defaultLoaderFallback?: RenderElement;
75
+ defaultErrorElement?: RenderElement;
76
76
  showFallbackOnAnimation?: boolean;
77
77
  prefetch?: 'hover' | 'render' | 'viewport' | 'none';
78
78
  hoverPrefetchDelay?: number;
@@ -81,4 +81,3 @@ export type RouterProps = {
81
81
  }>;
82
82
  context?: Record<string, unknown>;
83
83
  };
84
- export {};
@@ -0,0 +1,2 @@
1
+ export declare const ALL_LOCATIONS = "*";
2
+ export declare const findRoute: (pathname: string, includeAll?: boolean) => import("..").RouteItem | undefined;
@@ -0,0 +1,4 @@
1
+ export declare const getContext: () => {
2
+ context: Record<string, unknown>;
3
+ setContext: (action: Record<string, unknown> | ((prevState: Record<string, unknown>) => Record<string, unknown>)) => void;
4
+ };
@@ -0,0 +1,5 @@
1
+ import type { RouteItem } from '../types/global';
2
+ export declare const isCacheItemFresh: ({ routeItem, pathname }: {
3
+ routeItem?: RouteItem;
4
+ pathname: string;
5
+ }) => boolean;
@@ -0,0 +1,2 @@
1
+ import { Location, RouteItem } from '../types/global';
2
+ export declare const navigationExecutor: (nextLocation: Location, routeItem: RouteItem | undefined) => void;
@@ -1,2 +1,2 @@
1
- import { type ReactElement } from 'react';
2
- export declare const renderElement: (Component?: (() => ReactElement) | ReactElement) => import("react/jsx-runtime").JSX.Element | null;
1
+ import { RenderElement } from '../types/global';
2
+ export declare const renderElement: (Component?: RenderElement) => import("react/jsx-runtime").JSX.Element | null;
@@ -0,0 +1,3 @@
1
+ import type { RevalidateCacheArgs } from '../types/global';
2
+ declare const revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown> | undefined;
3
+ export { revalidateCache };
@@ -0,0 +1,2 @@
1
+ import { Location, RouteItem } from '../types/global';
2
+ export declare const transitionedNavigation: (nextLocation: Location, routeItem: RouteItem | undefined) => void;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "clear-react-router",
3
- "version": "1.7.3",
3
+ "version": "1.7.4",
4
4
  "description": "A lightweight, type-safe routing library for React applications",
5
5
  "author": "Andrew Bubnov",
6
6
  "scripts": {
@@ -1,11 +0,0 @@
1
- import type { LoaderState, RevalidateCacheArgs, RouteItem } from '../types/global';
2
- export declare const useLoader: (routes: RouteItem[]) => {
3
- prefetchLoader: (pathname: string) => Promise<void>;
4
- revalidateCache: ({ routeItem, pathname }: RevalidateCacheArgs) => Promise<unknown>;
5
- isCacheItemFresh: ({ routeItem, pathname }: {
6
- routeItem?: RouteItem;
7
- pathname: string;
8
- }) => boolean;
9
- loaderStateRef: import("react").RefObject<LoaderState>;
10
- invalidate: (pathname?: string) => Promise<void>;
11
- };
@@ -1,5 +0,0 @@
1
- export declare const useRuntime: () => {
2
- updateLocation: (route: import("..").Location) => Promise<void>;
3
- prefetchLoader: (arg: string) => Promise<void>;
4
- invalidate: (path?: string) => Promise<void>;
5
- };
@@ -1,7 +0,0 @@
1
- type UseSetRuntime = {
2
- updateLocation(arg: Location): Promise<void>;
3
- prefetchLoader(arg: string): Promise<void>;
4
- invalidate(arg?: string): Promise<void>;
5
- };
6
- export declare const useSetRouterRuntime: ({ updateLocation, prefetchLoader, invalidate }: UseSetRuntime) => void;
7
- export {};