@funstack/router 0.0.4 → 0.0.5
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/dist/index.d.mts +14 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +84 -26
- package/dist/index.mjs.map +1 -1
- package/dist/{route-gXPaU-cv.d.mts → route-Bc8BUlhv.d.mts} +3 -1
- package/dist/route-Bc8BUlhv.d.mts.map +1 -0
- package/dist/route-p_gr5yPI.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/package.json +4 -4
- package/dist/route-gXPaU-cv.d.mts.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, f as TypefulOpaqueRouteDefinition, i as ExtractRouteState, l as RouteComponentPropsOf, m as routeState, n as ExtractRouteId, o as OpaqueRouteDefinition, p as route, r as ExtractRouteParams, s as PathParams, t as ExtractRouteData, u as RouteComponentPropsWithData } from "./route-
|
|
1
|
+
import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, f as TypefulOpaqueRouteDefinition, i as ExtractRouteState, l as RouteComponentPropsOf, m as routeState, n as ExtractRouteId, o as OpaqueRouteDefinition, p as route, r as ExtractRouteParams, s as PathParams, t as ExtractRouteData, u as RouteComponentPropsWithData } from "./route-Bc8BUlhv.mjs";
|
|
2
2
|
import { ComponentType, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
@@ -136,6 +136,12 @@ declare function useNavigate(): (to: string, options?: NavigateOptions) => void;
|
|
|
136
136
|
*/
|
|
137
137
|
declare function useLocation(): Location;
|
|
138
138
|
//#endregion
|
|
139
|
+
//#region src/hooks/useLocationSSR.d.ts
|
|
140
|
+
/**
|
|
141
|
+
* Returns the current location object, or `null` when the URL is not available (e.g. during SSR).
|
|
142
|
+
*/
|
|
143
|
+
declare function useLocationSSR(): Location | null;
|
|
144
|
+
//#endregion
|
|
139
145
|
//#region src/hooks/useSearchParams.d.ts
|
|
140
146
|
type SetSearchParams = (params: URLSearchParams | Record<string, string> | ((prev: URLSearchParams) => URLSearchParams | Record<string, string>)) => void;
|
|
141
147
|
/**
|
|
@@ -254,6 +260,12 @@ declare function useRouteState<T extends TypefulOpaqueRouteDefinition<string, Re
|
|
|
254
260
|
*/
|
|
255
261
|
declare function useRouteData<T extends TypefulOpaqueRouteDefinition<string, Record<string, string>, unknown, unknown>>(route: T): ExtractRouteData<T>;
|
|
256
262
|
//#endregion
|
|
263
|
+
//#region src/hooks/useIsPending.d.ts
|
|
264
|
+
/**
|
|
265
|
+
* Returns whether a navigation transition is currently pending.
|
|
266
|
+
*/
|
|
267
|
+
declare function useIsPending(): boolean;
|
|
268
|
+
//#endregion
|
|
257
269
|
//#region src/core/RouterAdapter.d.ts
|
|
258
270
|
/**
|
|
259
271
|
* Represents the current location state.
|
|
@@ -266,5 +278,5 @@ type LocationEntry = {
|
|
|
266
278
|
info: unknown;
|
|
267
279
|
};
|
|
268
280
|
//#endregion
|
|
269
|
-
export { type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type LocationEntry, type MatchedRoute, type MatchedRouteWithData, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useLocation, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
281
|
+
export { type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type LocationEntry, type MatchedRoute, type MatchedRouteWithData, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useIsPending, useLocation, useLocationSSR, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
270
282
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/core/RouterAdapter.ts"],"mappings":";;;;cAGM,6BAAA;;AAFwD;;;;;;;KAgBlD,uBAAA;EAAA,CACT,6BAAA,UA4Bc;EA1Bf,IAAA,WAwBI;EAtBJ,QAAA,GAAW,uBAAA;EAiCE;;;;;;EA1Bb,KAAA;EAMA;;;;;EAAA,eAAA,YASI;EAHJ,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAKrB;EAHN,SAAA,GACI,aAAA;IACE,IAAA;IACA,MAAA,GAAS,MAAA;IACT,KAAA;IACA,QAAA,IACE,KAAA,cAAmB,IAAA,2BAChB,OAAA;IACL,YAAA,IAAgB,KAAA,cAAmB,IAAA;IACnC,UAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;;KAmBM,YAAA;EAMV,oCAJA,KAAA,EAAO,uBAAA,EAIC;EAFR,MAAA,EAAQ,MAAA,kBAQsB;EAN9B,QAAA;AAAA;;AAcF;;KARY,oBAAA,GAAuB,YAAA;EAUH,6DAR9B,IAAA;AAAA;;;;KAMU,cAAA;EAUe,4DARzB,OAAA,WAAkB,YAAA,WAQO;EANzB,YAAA;AAAA;;;;KAMU,eAAA;EAYQ,uDAVlB,OAAA,YAUkB;EARlB,KAAA,YAUA;EARA,IAAA;AAAA;;AAmBF;;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;AAqBF;;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;
|
|
1
|
+
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useLocationSSR.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/hooks/useIsPending.ts","../src/core/RouterAdapter.ts"],"mappings":";;;;cAGM,6BAAA;;AAFwD;;;;;;;KAgBlD,uBAAA;EAAA,CACT,6BAAA,UA4Bc;EA1Bf,IAAA,WAwBI;EAtBJ,QAAA,GAAW,uBAAA;EAiCE;;;;;;EA1Bb,KAAA;EAMA;;;;;EAAA,eAAA,YASI;EAHJ,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAKrB;EAHN,SAAA,GACI,aAAA;IACE,IAAA;IACA,MAAA,GAAS,MAAA;IACT,KAAA;IACA,QAAA,IACE,KAAA,cAAmB,IAAA,2BAChB,OAAA;IACL,YAAA,IAAgB,KAAA,cAAmB,IAAA;IACnC,UAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;;KAmBM,YAAA;EAMV,oCAJA,KAAA,EAAO,uBAAA,EAIC;EAFR,MAAA,EAAQ,MAAA,kBAQsB;EAN9B,QAAA;AAAA;;AAcF;;KARY,oBAAA,GAAuB,YAAA;EAUH,6DAR9B,IAAA;AAAA;;;;KAMU,cAAA;EAUe,4DARzB,OAAA,WAAkB,YAAA,WAQO;EANzB,YAAA;AAAA;;;;KAMU,eAAA;EAYQ,uDAVlB,OAAA,YAUkB;EARlB,KAAA,YAUA;EARA,IAAA;AAAA;;AAmBF;;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;AAqBF;;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;AClGR;;;KD2GY,YAAA;;;KC3GA,WAAA;EACV,MAAA,EAAQ,eAAA;ED/BoC;;;;AAc9C;;;ECyBE,UAAA,GAAa,kBAAA;EDpBF;;;;;;EC2BX,QAAA,GAAW,YAAA;AAAA;AAAA,iBASG,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD7D6C;iBEM9C,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;;;;iBCAf,cAAA,CAAA,GAAkB,QAAA;;;KCJ7B,eAAA,IACH,MAAA,EACI,eAAA,GACA,MAAA,qBACE,IAAA,EAAM,eAAA,KAAoB,eAAA,GAAkB,MAAA;;;;iBAMpC,eAAA,CAAA,IAAoB,eAAA,EAAiB,eAAA;;;KCVzC,iBAAA;;;;APFkD;EOO5D,WAAA;AAAA;;;APSF;;;;;;;;;;;;;;;;;;;;;;;;;iBOqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;APrC0B;;;;;AAgB9D;;;;;;;;;;;;iBQSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;ARhC8B;;;;;AAgB9D;;;;;;;;;;;;;iBSUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ATjC+B;;;;;AAgB9D;;;;;;;;;;;;;;;;iBUagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA;;;;;;AXL8C;KYSlD,aAAA;wBAEV,GAAA,EAAK,GAAA,EZTuC;EYW5C,GAAA,UZGiC;EYDjC,KAAA,WZEC;EYAD,IAAA;AAAA"}
|
package/dist/index.mjs
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
import { n as routeState, t as route } from "./route-p_gr5yPI.mjs";
|
|
4
|
-
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState, useSyncExternalStore } from "react";
|
|
4
|
+
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState, useSyncExternalStore, useTransition } from "react";
|
|
5
5
|
import { jsx } from "react/jsx-runtime";
|
|
6
6
|
|
|
7
7
|
//#region src/context/RouterContext.ts
|
|
@@ -78,6 +78,7 @@ function matchRoutes(routes, pathname) {
|
|
|
78
78
|
function matchRoute(route, pathname) {
|
|
79
79
|
const hasChildren = Boolean(route.children?.length);
|
|
80
80
|
if (route.path === void 0) {
|
|
81
|
+
if (pathname === null && route.loader) return null;
|
|
81
82
|
const result = {
|
|
82
83
|
route,
|
|
83
84
|
params: {},
|
|
@@ -89,10 +90,12 @@ function matchRoute(route, pathname) {
|
|
|
89
90
|
if (childMatch) return [result, ...childMatch];
|
|
90
91
|
}
|
|
91
92
|
if (route.component && route.requireChildren === false) return [result];
|
|
93
|
+
if (pathname === null && route.component) return [result];
|
|
92
94
|
return null;
|
|
93
95
|
}
|
|
94
96
|
return [result];
|
|
95
97
|
}
|
|
98
|
+
if (pathname === null) return null;
|
|
96
99
|
const isExact = route.exact ?? !hasChildren;
|
|
97
100
|
const { matched, params, consumedPathname } = matchPath(route.path, pathname, isExact);
|
|
98
101
|
if (!matched) return null;
|
|
@@ -230,9 +233,6 @@ var NavigationAPIAdapter = class {
|
|
|
230
233
|
};
|
|
231
234
|
return this.#cachedSnapshot;
|
|
232
235
|
}
|
|
233
|
-
getServerSnapshot() {
|
|
234
|
-
return null;
|
|
235
|
-
}
|
|
236
236
|
subscribe(callback) {
|
|
237
237
|
const controller = new AbortController();
|
|
238
238
|
navigation.addEventListener("currententrychange", callback, { signal: controller.signal });
|
|
@@ -349,9 +349,6 @@ var StaticAdapter = class {
|
|
|
349
349
|
};
|
|
350
350
|
return this.#cachedSnapshot;
|
|
351
351
|
}
|
|
352
|
-
getServerSnapshot() {
|
|
353
|
-
return null;
|
|
354
|
-
}
|
|
355
352
|
subscribe(_callback) {
|
|
356
353
|
return () => {};
|
|
357
354
|
}
|
|
@@ -380,9 +377,6 @@ var NullAdapter = class {
|
|
|
380
377
|
getSnapshot() {
|
|
381
378
|
return null;
|
|
382
379
|
}
|
|
383
|
-
getServerSnapshot() {
|
|
384
|
-
return null;
|
|
385
|
-
}
|
|
386
380
|
subscribe(_callback) {
|
|
387
381
|
return () => {};
|
|
388
382
|
}
|
|
@@ -423,11 +417,28 @@ function createAdapter(fallback) {
|
|
|
423
417
|
|
|
424
418
|
//#endregion
|
|
425
419
|
//#region src/Router.tsx
|
|
420
|
+
const noopSubscribe = () => () => {};
|
|
421
|
+
const getServerSnapshot = () => null;
|
|
422
|
+
/**
|
|
423
|
+
* Initial value of locationEntry.
|
|
424
|
+
* This value means to use the `initialEntry` from `useSyncExternalStore` instead.
|
|
425
|
+
*/
|
|
426
|
+
const entryInitValue = Symbol();
|
|
426
427
|
function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
427
428
|
const routes = internalRoutes(inputRoutes);
|
|
428
429
|
const adapter = useMemo(() => createAdapter(fallback), [fallback]);
|
|
429
430
|
const [blockerRegistry] = useState(() => createBlockerRegistry());
|
|
430
|
-
const
|
|
431
|
+
const initialEntry = useSyncExternalStore(noopSubscribe, useCallback(() => adapter.getSnapshot(), [adapter]), getServerSnapshot);
|
|
432
|
+
const [isPending, startTransition] = useTransition();
|
|
433
|
+
const [locationEntryInternal, setLocationEntry] = useState(entryInitValue);
|
|
434
|
+
const locationEntry = locationEntryInternal === entryInitValue ? initialEntry : locationEntryInternal;
|
|
435
|
+
useEffect(() => {
|
|
436
|
+
return adapter.subscribe(() => {
|
|
437
|
+
startTransition(() => {
|
|
438
|
+
setLocationEntry(adapter.getSnapshot());
|
|
439
|
+
});
|
|
440
|
+
});
|
|
441
|
+
}, [adapter, startTransition]);
|
|
431
442
|
useEffect(() => {
|
|
432
443
|
return adapter.setupInterception(routes, onNavigate, blockerRegistry.checkAll);
|
|
433
444
|
}, [
|
|
@@ -446,16 +457,24 @@ function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
|
446
457
|
adapter.updateCurrentEntryState(state);
|
|
447
458
|
}, [adapter]);
|
|
448
459
|
return useMemo(() => {
|
|
449
|
-
if (locationEntry === null) return null;
|
|
450
|
-
const { url, key } = locationEntry;
|
|
451
460
|
const matchedRoutesWithData = (() => {
|
|
461
|
+
if (locationEntry === null) {
|
|
462
|
+
const matched = matchRoutes(routes, null);
|
|
463
|
+
if (!matched) return null;
|
|
464
|
+
return matched.map((m) => ({
|
|
465
|
+
...m,
|
|
466
|
+
data: void 0
|
|
467
|
+
}));
|
|
468
|
+
}
|
|
469
|
+
const { url, key } = locationEntry;
|
|
452
470
|
const matched = matchRoutes(routes, url.pathname);
|
|
453
471
|
if (!matched) return null;
|
|
454
472
|
return executeLoaders(matched, key, createLoaderRequest(url), adapter.getIdleAbortSignal());
|
|
455
473
|
})();
|
|
456
474
|
const routerContextValue = {
|
|
457
475
|
locationEntry,
|
|
458
|
-
url,
|
|
476
|
+
url: locationEntry?.url ?? null,
|
|
477
|
+
isPending,
|
|
459
478
|
navigate,
|
|
460
479
|
navigateAsync,
|
|
461
480
|
updateCurrentEntryState
|
|
@@ -475,6 +494,7 @@ function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
|
475
494
|
navigate,
|
|
476
495
|
navigateAsync,
|
|
477
496
|
updateCurrentEntryState,
|
|
497
|
+
isPending,
|
|
478
498
|
locationEntry,
|
|
479
499
|
routes,
|
|
480
500
|
adapter,
|
|
@@ -491,9 +511,10 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
491
511
|
const { route, params, pathname, data } = match;
|
|
492
512
|
const routerContext = useContext(RouterContext);
|
|
493
513
|
if (!routerContext) throw new Error("RouteRenderer must be used within RouterContext");
|
|
494
|
-
const { locationEntry, url, navigateAsync, updateCurrentEntryState } = routerContext;
|
|
495
|
-
const routeState = locationEntry
|
|
514
|
+
const { locationEntry, url, isPending, navigateAsync, updateCurrentEntryState } = routerContext;
|
|
515
|
+
const routeState = (locationEntry?.state)?.__routeStates?.[index];
|
|
496
516
|
const setStateSync = useCallback((stateOrUpdater) => {
|
|
517
|
+
if (locationEntry === null) return;
|
|
497
518
|
const currentStates = locationEntry.state?.__routeStates ?? [];
|
|
498
519
|
const currentRouteState = currentStates[index];
|
|
499
520
|
const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
|
|
@@ -501,11 +522,12 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
501
522
|
newStates[index] = newState;
|
|
502
523
|
updateCurrentEntryState({ __routeStates: newStates });
|
|
503
524
|
}, [
|
|
504
|
-
locationEntry
|
|
525
|
+
locationEntry?.state,
|
|
505
526
|
index,
|
|
506
527
|
updateCurrentEntryState
|
|
507
528
|
]);
|
|
508
529
|
const setState = useCallback(async (stateOrUpdater) => {
|
|
530
|
+
if (locationEntry === null || url === null) return;
|
|
509
531
|
const currentStates = locationEntry.state?.__routeStates ?? [];
|
|
510
532
|
const currentRouteState = currentStates[index];
|
|
511
533
|
const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
|
|
@@ -516,17 +538,18 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
516
538
|
state: { __routeStates: newStates }
|
|
517
539
|
});
|
|
518
540
|
}, [
|
|
519
|
-
locationEntry
|
|
541
|
+
locationEntry?.state,
|
|
520
542
|
index,
|
|
521
543
|
url,
|
|
522
544
|
navigateAsync
|
|
523
545
|
]);
|
|
524
546
|
const resetState = useCallback(() => {
|
|
547
|
+
if (locationEntry === null) return;
|
|
525
548
|
const newStates = [...locationEntry.state?.__routeStates ?? []];
|
|
526
549
|
newStates[index] = void 0;
|
|
527
550
|
updateCurrentEntryState({ __routeStates: newStates });
|
|
528
551
|
}, [
|
|
529
|
-
locationEntry
|
|
552
|
+
locationEntry?.state,
|
|
530
553
|
index,
|
|
531
554
|
updateCurrentEntryState
|
|
532
555
|
]);
|
|
@@ -563,17 +586,19 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
563
586
|
setStateSync,
|
|
564
587
|
resetState
|
|
565
588
|
};
|
|
566
|
-
const
|
|
589
|
+
const info = locationEntry?.info;
|
|
567
590
|
if (route.loader) return /* @__PURE__ */ jsx(Component, {
|
|
568
591
|
data,
|
|
569
592
|
params,
|
|
570
593
|
...stateProps,
|
|
571
|
-
info
|
|
594
|
+
info,
|
|
595
|
+
isPending
|
|
572
596
|
});
|
|
573
597
|
return /* @__PURE__ */ jsx(Component, {
|
|
574
598
|
params,
|
|
575
599
|
...stateProps,
|
|
576
|
-
info
|
|
600
|
+
info,
|
|
601
|
+
isPending
|
|
577
602
|
});
|
|
578
603
|
};
|
|
579
604
|
return /* @__PURE__ */ jsx(RouteContext.Provider, {
|
|
@@ -614,6 +639,7 @@ function useLocation() {
|
|
|
614
639
|
const context = useContext(RouterContext);
|
|
615
640
|
if (!context) throw new Error("useLocation must be used within a Router");
|
|
616
641
|
const { url } = context;
|
|
642
|
+
if (url === null) throw new Error("useLocation: URL is not available during SSR.");
|
|
617
643
|
return useMemo(() => {
|
|
618
644
|
return {
|
|
619
645
|
pathname: url.pathname,
|
|
@@ -623,6 +649,25 @@ function useLocation() {
|
|
|
623
649
|
}, [url]);
|
|
624
650
|
}
|
|
625
651
|
|
|
652
|
+
//#endregion
|
|
653
|
+
//#region src/hooks/useLocationSSR.ts
|
|
654
|
+
/**
|
|
655
|
+
* Returns the current location object, or `null` when the URL is not available (e.g. during SSR).
|
|
656
|
+
*/
|
|
657
|
+
function useLocationSSR() {
|
|
658
|
+
const context = useContext(RouterContext);
|
|
659
|
+
if (!context) throw new Error("useLocationSSR must be used within a Router");
|
|
660
|
+
const { url } = context;
|
|
661
|
+
return useMemo(() => {
|
|
662
|
+
if (url === null) return null;
|
|
663
|
+
return {
|
|
664
|
+
pathname: url.pathname,
|
|
665
|
+
search: url.search,
|
|
666
|
+
hash: url.hash
|
|
667
|
+
};
|
|
668
|
+
}, [url]);
|
|
669
|
+
}
|
|
670
|
+
|
|
626
671
|
//#endregion
|
|
627
672
|
//#region src/hooks/useSearchParams.ts
|
|
628
673
|
/**
|
|
@@ -631,8 +676,10 @@ function useLocation() {
|
|
|
631
676
|
function useSearchParams() {
|
|
632
677
|
const context = useContext(RouterContext);
|
|
633
678
|
if (!context) throw new Error("useSearchParams must be used within a Router");
|
|
634
|
-
|
|
635
|
-
|
|
679
|
+
if (context.url === null) throw new Error("useSearchParams: URL is not available during SSR.");
|
|
680
|
+
const currentUrl = context.url;
|
|
681
|
+
return [currentUrl.searchParams, useCallback((params) => {
|
|
682
|
+
const url = new URL(currentUrl);
|
|
636
683
|
let newParams;
|
|
637
684
|
if (typeof params === "function") {
|
|
638
685
|
const result = params(new URLSearchParams(url.search));
|
|
@@ -641,7 +688,7 @@ function useSearchParams() {
|
|
|
641
688
|
else newParams = new URLSearchParams(params);
|
|
642
689
|
url.search = newParams.toString();
|
|
643
690
|
context.navigate(url.pathname + url.search + url.hash, { replace: true });
|
|
644
|
-
}, [context])];
|
|
691
|
+
}, [currentUrl, context.navigate])];
|
|
645
692
|
}
|
|
646
693
|
|
|
647
694
|
//#endregion
|
|
@@ -797,5 +844,16 @@ function useRouteData(route) {
|
|
|
797
844
|
}
|
|
798
845
|
|
|
799
846
|
//#endregion
|
|
800
|
-
|
|
847
|
+
//#region src/hooks/useIsPending.ts
|
|
848
|
+
/**
|
|
849
|
+
* Returns whether a navigation transition is currently pending.
|
|
850
|
+
*/
|
|
851
|
+
function useIsPending() {
|
|
852
|
+
const context = useContext(RouterContext);
|
|
853
|
+
if (!context) throw new Error("useIsPending must be used within a Router");
|
|
854
|
+
return context.isPending;
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
//#endregion
|
|
858
|
+
export { Outlet, Router, route, routeState, useBlocker, useIsPending, useLocation, useLocationSSR, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
801
859
|
//# sourceMappingURL=index.mjs.map
|
package/dist/index.mjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.mjs","names":["#cachedEntryId","#cachedSnapshot","#currentNavigationInfo","#subscribeToDisposeEvents","#subscribedEntryIds","#cachedSnapshot","#idleController","#idleController"],"sources":["../src/context/RouterContext.ts","../src/context/RouteContext.ts","../src/context/BlockerContext.ts","../src/types.ts","../src/core/matchRoutes.ts","../src/core/loaderCache.ts","../src/core/NavigationAPIAdapter.ts","../src/core/StaticAdapter.ts","../src/core/NullAdapter.ts","../src/core/createAdapter.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteContext.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type { NavigateOptions } from \"../types.js\";\nimport type { LocationEntry } from \"../core/RouterAdapter.js\";\n\nexport type RouterContextValue = {\n /** Current location entry */\n locationEntry: LocationEntry;\n /** Current URL */\n url: URL;\n /** Navigate to a new URL */\n navigate: (to: string, options?: NavigateOptions) => void;\n /** Navigate to a new URL and wait for completion */\n navigateAsync: (to: string, options?: NavigateOptions) => Promise<void>;\n /** Update current entry's state without navigation */\n updateCurrentEntryState: (state: unknown) => void;\n};\n\nexport const RouterContext = createContext<RouterContextValue | null>(null);\n","import { createContext, type ReactNode } from \"react\";\n\nexport type RouteContextValue = {\n /** Route identifier (if provided in route definition) */\n id: string | undefined;\n /** Matched route parameters extracted from URL */\n params: Record<string, string>;\n /** The matched path pattern */\n matchedPath: string;\n /** Navigation state for this route */\n state: unknown;\n /** Data from loader (if route has loader) */\n data: unknown;\n /** Child route element to render via Outlet */\n outlet: ReactNode;\n /** Parent route context (for nested routes) */\n parent: RouteContextValue | null;\n};\n\nexport const RouteContext = createContext<RouteContextValue | null>(null);\n\n/**\n * Find a route context by ID in the ancestor chain.\n * Returns the matching context or null if not found.\n */\nexport function findRouteContextById(\n context: RouteContextValue | null,\n id: string,\n): RouteContextValue | null {\n let current = context;\n while (current !== null) {\n if (current.id === id) return current;\n current = current.parent;\n }\n return null;\n}\n","import { createContext } from \"react\";\n\nexport type BlockerId = string;\n\nexport type BlockerRegistry = {\n /** Register a blocker, returns unregister function */\n register: (id: BlockerId, shouldBlock: () => boolean) => () => void;\n /** Check all blockers - returns true if any blocks */\n checkAll: () => boolean;\n};\n\nexport type BlockerContextValue = {\n registry: BlockerRegistry;\n};\n\n/**\n * Create a new blocker registry.\n * The registry manages registered blockers and provides a way to check if any blocker is active.\n */\nexport function createBlockerRegistry(): BlockerRegistry {\n const blockers = new Map<BlockerId, () => boolean>();\n\n return {\n register(id: BlockerId, shouldBlock: () => boolean): () => void {\n blockers.set(id, shouldBlock);\n return () => {\n blockers.delete(id);\n };\n },\n\n checkAll(): boolean {\n for (const shouldBlock of blockers.values()) {\n if (shouldBlock()) {\n return true;\n }\n }\n return false;\n },\n };\n}\n\nexport const BlockerContext = createContext<BlockerContextValue | null>(null);\n","import type { ComponentType, ReactNode } from \"react\";\nimport type { LoaderArgs, RouteDefinition } from \"./route.js\";\n\nconst InternalRouteDefinitionSymbol = Symbol();\n\n/**\n * Internal structure for storing per-route state in NavigationHistoryEntry.\n * Each route in the matched stack gets its own state slot indexed by match position.\n */\nexport type InternalRouteState = {\n __routeStates: (unknown | undefined)[];\n};\n\n/**\n * Route definition for the router.\n * When a loader is defined, the component receives the loader result as a `data` prop.\n */\nexport type InternalRouteDefinition = {\n [InternalRouteDefinitionSymbol]: never;\n /** Path pattern to match (e.g., \"users/:id\"). If omitted, the route is pathless (always matches, consumes nothing). */\n path?: string;\n /** Child routes for nested routing */\n children?: InternalRouteDefinition[];\n /**\n * Whether this route requires an exact match.\n * - true: Only matches exact pathname\n * - false: Matches as prefix\n * - undefined: Default (exact for leaf, prefix for parent)\n */\n exact?: boolean;\n /**\n * Whether this route requires a child to match when it has children.\n * - true (default): Parent does not match if no child matches\n * - false: Parent can match alone if it has a component, outlet will be null\n */\n requireChildren?: boolean;\n\n // Note: `loader` and `component` may both exist or both not exist.\n // Also, `unknown`s may actually be more specific types. They are guaranteed\n // to be the same type by the `route` helper function.\n /** Data loader function for this route */\n loader?: (args: LoaderArgs<Record<string, string>>) => unknown;\n /** Component to render when this route matches */\n component?:\n | ComponentType<{\n data?: unknown;\n params?: Record<string, string>;\n state?: unknown;\n setState?: (\n state: unknown | ((prev: unknown) => unknown),\n ) => Promise<void>;\n setStateSync?: (state: unknown | ((prev: unknown) => unknown)) => void;\n resetState?: () => void;\n info?: unknown;\n }>\n | ReactNode;\n};\n\n/**\n * Converts user-defined routes to internal route definitions.\n * This function is used internally by the Router.\n *\n * Actually, this function just performs a type assertion since\n * both RouteDefinition and InternalRouteDefinition have the same runtime shape.\n */\nexport function internalRoutes(\n routes: RouteDefinition[],\n): InternalRouteDefinition[] {\n return routes as InternalRouteDefinition[];\n}\n\n/**\n * A matched route with its parameters.\n */\nexport type MatchedRoute = {\n /** The original route definition */\n route: InternalRouteDefinition;\n /** Extracted path parameters */\n params: Record<string, string>;\n /** The matched pathname segment */\n pathname: string;\n};\n\n/**\n * A matched route with loader data.\n */\nexport type MatchedRouteWithData = MatchedRoute & {\n /** Data returned from the loader (undefined if no loader) */\n data: unknown | undefined;\n};\n\n/**\n * Information passed to onNavigate callback.\n */\nexport type OnNavigateInfo = {\n /** Array of matched routes, or null if no routes matched */\n matches: readonly MatchedRoute[] | null;\n /** Whether the router will intercept this navigation (before user's preventDefault() call) */\n intercepting: boolean;\n};\n\n/**\n * Options for navigation.\n */\nexport type NavigateOptions = {\n /** Replace current history entry instead of pushing */\n replace?: boolean;\n /** State to associate with the navigation */\n state?: unknown;\n /** Ephemeral info for this navigation only (not persisted in history) */\n info?: unknown;\n};\n\n/**\n * Location object representing current URL state.\n */\nexport type Location = {\n pathname: string;\n search: string;\n hash: string;\n};\n\n/**\n * Callback invoked before navigation is intercepted.\n * Call `event.preventDefault()` to prevent the router from handling this navigation.\n *\n * @param event - The NavigateEvent from the Navigation API\n * @param info - Information about the navigation including matched routes and whether it will be intercepted\n */\nexport type OnNavigateCallback = (\n event: NavigateEvent,\n info: OnNavigateInfo,\n) => void;\n\n/**\n * Fallback mode when Navigation API is unavailable.\n *\n * - `\"none\"` (default): Render nothing when Navigation API is unavailable\n * - `\"static\"`: Render matched routes without navigation capabilities (MPA behavior)\n */\nexport type FallbackMode =\n | \"none\" // Default: render nothing when Navigation API unavailable\n | \"static\"; // Render matched routes without navigation capabilities\n","import type { InternalRouteDefinition, MatchedRoute } from \"../types.js\";\n\n/**\n * Match a pathname against a route tree, returning the matched route stack.\n * Returns null if no match is found.\n */\nexport function matchRoutes(\n routes: InternalRouteDefinition[],\n pathname: string,\n): MatchedRoute[] | null {\n for (const route of routes) {\n const matched = matchRoute(route, pathname);\n if (matched) {\n return matched;\n }\n }\n return null;\n}\n\n/**\n * Match a single route and its children recursively.\n */\nfunction matchRoute(\n route: InternalRouteDefinition,\n pathname: string,\n): MatchedRoute[] | null {\n const hasChildren = Boolean(route.children?.length);\n\n // Handle pathless routes - always match, consume nothing\n if (route.path === undefined) {\n const result: MatchedRoute = {\n route,\n params: {},\n pathname: \"\",\n };\n\n if (hasChildren) {\n for (const child of route.children!) {\n const childMatch = matchRoute(child, pathname);\n if (childMatch) {\n return [result, ...childMatch];\n }\n }\n // No children matched - only valid if requireChildren is false and route has a component\n if (route.component && route.requireChildren === false) {\n return [result];\n }\n return null;\n }\n\n return [result];\n }\n\n const isExact = route.exact ?? !hasChildren;\n\n const { matched, params, consumedPathname } = matchPath(\n route.path,\n pathname,\n isExact,\n );\n\n if (!matched) {\n return null;\n }\n\n const result: MatchedRoute = {\n route,\n params,\n pathname: consumedPathname,\n };\n\n // If this route has children, try to match them\n if (hasChildren) {\n // Calculate remaining pathname, ensuring it starts with /\n let remainingPathname = pathname.slice(consumedPathname.length);\n if (!remainingPathname.startsWith(\"/\")) {\n remainingPathname = \"/\" + remainingPathname;\n }\n if (remainingPathname === \"\") {\n remainingPathname = \"/\";\n }\n\n for (const child of route.children!) {\n const childMatch = matchRoute(child, remainingPathname);\n if (childMatch) {\n // Merge params from parent into children\n return [\n result,\n ...childMatch.map((m) => ({\n ...m,\n params: { ...params, ...m.params },\n })),\n ];\n }\n }\n\n // If no children matched - only valid if requireChildren is false and route has a component\n if (route.component && route.requireChildren === false) {\n return [result];\n }\n\n return null;\n }\n\n return [result];\n}\n\n/**\n * Match a path pattern against a pathname.\n */\nfunction matchPath(\n pattern: string,\n pathname: string,\n exact: boolean,\n): {\n matched: boolean;\n params: Record<string, string>;\n consumedPathname: string;\n} {\n // Normalize pattern\n const normalizedPattern = pattern.startsWith(\"/\") ? pattern : `/${pattern}`;\n\n // Build URLPattern\n let urlPatternPath: string;\n if (exact) {\n urlPatternPath = normalizedPattern;\n } else if (normalizedPattern === \"/\") {\n // Special case: root path as prefix matches anything\n urlPatternPath = \"/*\";\n } else {\n // For other prefix matches, add optional wildcard suffix\n urlPatternPath = `${normalizedPattern}{/*}?`;\n }\n\n const urlPattern = new URLPattern({ pathname: urlPatternPath });\n\n const match = urlPattern.exec({ pathname });\n if (!match) {\n return { matched: false, params: {}, consumedPathname: \"\" };\n }\n\n // Extract params (excluding the wildcard group \"0\")\n const params: Record<string, string> = {};\n for (const [key, value] of Object.entries(match.pathname.groups)) {\n if (value !== undefined && key !== \"0\") {\n params[key] = value;\n }\n }\n\n // Calculate consumed pathname\n let consumedPathname: string;\n if (exact) {\n consumedPathname = pathname;\n } else if (normalizedPattern === \"/\") {\n // Root pattern consumes just \"/\"\n consumedPathname = \"/\";\n } else {\n // For prefix matches, calculate based on pattern segments\n const patternSegments = normalizedPattern.split(\"/\").filter(Boolean);\n const pathnameSegments = pathname.split(\"/\").filter(Boolean);\n consumedPathname =\n \"/\" + pathnameSegments.slice(0, patternSegments.length).join(\"/\");\n }\n\n return { matched: true, params, consumedPathname };\n}\n","import type { LoaderArgs } from \"../route.js\";\nimport type {\n MatchedRoute,\n MatchedRouteWithData,\n InternalRouteDefinition,\n} from \"../types.js\";\n\n/**\n * Cache for loader results.\n * Key format: `${entryId}:${matchIndex}`\n */\nconst loaderCache = new Map<string, unknown>();\n\n/**\n * Get or create a loader result from cache.\n * If the result is not cached, executes the loader and caches the result.\n */\nfunction getOrCreateLoaderResult(\n entryId: string,\n matchIndex: number,\n route: InternalRouteDefinition,\n args: LoaderArgs<Record<string, string>>,\n): unknown | undefined {\n if (!route.loader) {\n return undefined;\n }\n\n const cacheKey = `${entryId}:${matchIndex}`;\n\n if (!loaderCache.has(cacheKey)) {\n loaderCache.set(cacheKey, route.loader(args));\n }\n\n return loaderCache.get(cacheKey);\n}\n\n/**\n * Create a Request object for loader args.\n */\nexport function createLoaderRequest(url: URL): Request {\n return new Request(url.href, {\n method: \"GET\",\n });\n}\n\n/**\n * Execute loaders for matched routes and return routes with data.\n * Results are cached by navigation entry id to prevent duplicate execution.\n */\nexport function executeLoaders(\n matchedRoutes: MatchedRoute[],\n entryId: string,\n request: Request,\n signal: AbortSignal,\n): MatchedRouteWithData[] {\n return matchedRoutes.map((match, index) => {\n const { route, params } = match;\n const args: LoaderArgs<Record<string, string>> = {\n params,\n request,\n signal,\n };\n const data = getOrCreateLoaderResult(entryId, index, route, args);\n\n return { ...match, data };\n });\n}\n\n/**\n * Clear the loader cache.\n * Mainly used for testing.\n */\nexport function clearLoaderCache(): void {\n loaderCache.clear();\n}\n\n/**\n * Clear loader cache entries for a specific navigation entry.\n * Called when a NavigationHistoryEntry is disposed (removed from history stack).\n */\nexport function clearLoaderCacheForEntry(entryId: string): void {\n const prefix = `${entryId}:`;\n for (const key of loaderCache.keys()) {\n if (key.startsWith(prefix)) {\n loaderCache.delete(key);\n }\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\nimport { matchRoutes } from \"./matchRoutes.js\";\nimport {\n executeLoaders,\n createLoaderRequest,\n clearLoaderCacheForEntry,\n} from \"./loaderCache.js\";\n\n/**\n * Fallback AbortController for data loading initialized outside of a navigation event.\n * Aborted when the next navigation occurs.\n *\n * To save resources, this controller is created only when needed.\n */\nlet idleController: AbortController | null = null;\n\n/**\n * Reset navigation state. Used for testing.\n */\nexport function resetNavigationState(): void {\n idleController?.abort();\n idleController = null;\n}\n\n/**\n * Adapter that uses the Navigation API for full SPA functionality.\n */\nexport class NavigationAPIAdapter implements RouterAdapter {\n // Cache the snapshot to ensure referential stability for useSyncExternalStore\n #cachedSnapshot: LocationEntry | null = null;\n #cachedEntryId: string | null = null;\n // Ephemeral info from the current navigation event (not persisted in history)\n #currentNavigationInfo: unknown = undefined;\n\n getSnapshot(): LocationEntry | null {\n const entry = navigation.currentEntry;\n if (!entry?.url) {\n return null;\n }\n\n // Return cached snapshot if entry hasn't changed\n if (this.#cachedEntryId === entry.id && this.#cachedSnapshot) {\n return this.#cachedSnapshot;\n }\n\n // Create new snapshot and cache it\n this.#cachedEntryId = entry.id;\n this.#cachedSnapshot = {\n url: new URL(entry.url),\n key: entry.id,\n state: entry.getState(),\n info: this.#currentNavigationInfo,\n };\n return this.#cachedSnapshot;\n }\n\n getServerSnapshot(): LocationEntry | null {\n return null;\n }\n\n subscribe(callback: () => void): () => void {\n const controller = new AbortController();\n navigation.addEventListener(\"currententrychange\", callback, {\n signal: controller.signal,\n });\n\n // Subscribe to dispose events on all existing entries\n this.#subscribeToDisposeEvents(controller.signal);\n\n // When current entry changes, subscribe to any new entries' dispose events\n navigation.addEventListener(\n \"currententrychange\",\n () => this.#subscribeToDisposeEvents(controller.signal),\n { signal: controller.signal },\n );\n\n return () => {\n controller.abort();\n };\n }\n\n /**\n * Track which entries we've subscribed to dispose events for.\n */\n #subscribedEntryIds = new Set<string>();\n\n /**\n * Subscribe to dispose events on all navigation entries.\n * When an entry is disposed, its cached loader results are cleared.\n */\n #subscribeToDisposeEvents(signal: AbortSignal): void {\n for (const entry of navigation.entries()) {\n if (this.#subscribedEntryIds.has(entry.id)) {\n continue;\n }\n this.#subscribedEntryIds.add(entry.id);\n\n const entryId = entry.id;\n entry.addEventListener(\n \"dispose\",\n () => {\n clearLoaderCacheForEntry(entryId);\n this.#subscribedEntryIds.delete(entryId);\n },\n { signal },\n );\n }\n }\n\n navigate(to: string, options?: NavigateOptions): void {\n navigation.navigate(to, {\n history: options?.replace ? \"replace\" : \"push\",\n state: options?.state,\n info: options?.info,\n });\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n const result = navigation.navigate(to, {\n history: options?.replace ? \"replace\" : \"push\",\n state: options?.state,\n info: options?.info,\n });\n await result.finished;\n }\n\n setupInterception(\n routes: InternalRouteDefinition[],\n onNavigate?: OnNavigateCallback,\n checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n const handleNavigate = (event: NavigateEvent) => {\n // Capture ephemeral info from the navigate event\n // This info is only available during this navigation and resets on the next one\n this.#currentNavigationInfo = event.info;\n // Invalidate cached snapshot to pick up new info\n this.#cachedSnapshot = null;\n\n // Check blockers first - if any blocker returns true, prevent navigation\n if (checkBlockers?.()) {\n event.preventDefault();\n return;\n }\n\n // Only intercept same-origin navigations\n if (!event.canIntercept) {\n onNavigate?.(event, { matches: [], intercepting: false });\n return;\n }\n\n // Check if the URL matches any of our routes\n const url = new URL(event.destination.url);\n const matched = matchRoutes(routes, url.pathname);\n\n // Compute whether we will intercept this navigation (before user's preventDefault)\n const willIntercept =\n matched !== null && !event.hashChange && event.downloadRequest === null;\n\n // Call onNavigate callback if provided (regardless of route match)\n if (onNavigate) {\n onNavigate(event, { matches: matched, intercepting: willIntercept });\n if (event.defaultPrevented) {\n return; // Do not intercept, allow browser default\n }\n }\n\n if (!willIntercept) {\n return;\n }\n\n // Route match, so intercept\n\n // Abort initial load's loaders if this is the first navigation\n if (idleController) {\n idleController.abort();\n idleController = null;\n }\n\n event.intercept({\n handler: async () => {\n const request = createLoaderRequest(url);\n\n // Note: in response to `currententrychange` event, <Router> should already\n // have dispatched data loaders and the results should be cached.\n // Here we run executeLoader to retrieve cached results.\n const currentEntry = navigation.currentEntry;\n if (!currentEntry) {\n throw new Error(\n \"Navigation currentEntry is null during navigation interception\",\n );\n }\n\n const results = executeLoaders(\n matched,\n currentEntry.id,\n request,\n event.signal,\n );\n\n // Delay navigation until async loaders complete\n await Promise.all(results.map((r) => r.data));\n },\n });\n };\n\n const controller = new AbortController();\n navigation.addEventListener(\"navigate\", handleNavigate, {\n signal: controller.signal,\n });\n return () => {\n controller.abort();\n };\n }\n\n getIdleAbortSignal(): AbortSignal {\n idleController ??= new AbortController();\n return idleController.signal;\n }\n\n updateCurrentEntryState(state: unknown): void {\n // Invalidate cached snapshot BEFORE updating, so the subscriber gets fresh state\n this.#cachedSnapshot = null;\n navigation.updateCurrentEntry({ state });\n // Note: updateCurrentEntry fires currententrychange, so subscribers are notified automatically\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\n\n/**\n * Static adapter for fallback mode when Navigation API is unavailable.\n * Provides read-only location access with no SPA navigation capabilities.\n * Links will cause full page loads (MPA behavior).\n */\nexport class StaticAdapter implements RouterAdapter {\n #cachedSnapshot: LocationEntry | null = null;\n #idleController: AbortController | null = null;\n\n getSnapshot(): LocationEntry | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n // Cache the snapshot - it never changes in static mode\n if (!this.#cachedSnapshot) {\n this.#cachedSnapshot = {\n url: new URL(window.location.href),\n key: \"__static__\",\n state: undefined,\n info: undefined,\n };\n }\n return this.#cachedSnapshot;\n }\n\n getServerSnapshot(): LocationEntry | null {\n return null;\n }\n\n subscribe(_callback: () => void): () => void {\n // Static mode never fires location change events\n return () => {};\n }\n\n navigate(to: string, _options?: NavigateOptions): void {\n console.warn(\n \"FUNSTACK Router: navigate() called in static fallback mode. \" +\n \"Navigation API is not available in this browser. \" +\n \"Links will cause full page loads.\",\n );\n // Note: We intentionally do NOT do window.location.href = to\n // as that would mask bugs where developers expect SPA behavior.\n // If needed in the future, we could add a \"static-reload\" mode.\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n this.navigate(to, options);\n }\n\n setupInterception(\n _routes: InternalRouteDefinition[],\n _onNavigate?: OnNavigateCallback,\n _checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n // No interception in static mode - links cause full page loads\n return undefined;\n }\n\n getIdleAbortSignal(): AbortSignal {\n this.#idleController ??= new AbortController();\n return this.#idleController.signal;\n }\n\n updateCurrentEntryState(_state: unknown): void {\n // No-op in static mode - state updates require Navigation API\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\n\n/**\n * Null adapter for when Navigation API is unavailable and no fallback is configured.\n * All methods are no-ops that return safe default values.\n */\nexport class NullAdapter implements RouterAdapter {\n #idleController: AbortController | null = null;\n\n getSnapshot(): LocationEntry | null {\n return null;\n }\n\n getServerSnapshot(): LocationEntry | null {\n return null;\n }\n\n subscribe(_callback: () => void): () => void {\n return () => {};\n }\n\n navigate(_to: string, _options?: NavigateOptions): void {\n console.warn(\n \"FUNSTACK Router: navigate() called but no adapter is available. \" +\n \"Navigation API is not available in this browser and no fallback mode is configured.\",\n );\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n this.navigate(to, options);\n }\n\n setupInterception(\n _routes: InternalRouteDefinition[],\n _onNavigate?: OnNavigateCallback,\n _checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n return undefined;\n }\n\n getIdleAbortSignal(): AbortSignal {\n this.#idleController ??= new AbortController();\n return this.#idleController.signal;\n }\n\n updateCurrentEntryState(_state: unknown): void {\n // No-op: NullAdapter doesn't support state updates\n }\n}\n","import type { RouterAdapter } from \"./RouterAdapter.js\";\nimport { NavigationAPIAdapter } from \"./NavigationAPIAdapter.js\";\nimport { StaticAdapter } from \"./StaticAdapter.js\";\nimport { NullAdapter } from \"./NullAdapter.js\";\nimport type { FallbackMode } from \"../types.js\";\n\n/**\n * Check if Navigation API is available.\n */\nfunction hasNavigation(): boolean {\n return typeof window !== \"undefined\" && \"navigation\" in window;\n}\n\n/**\n * Create the appropriate router adapter based on browser capabilities\n * and the specified fallback mode.\n *\n * @param fallback - The fallback mode to use when Navigation API is unavailable\n * @returns A RouterAdapter instance\n */\nexport function createAdapter(fallback: FallbackMode): RouterAdapter {\n // Try Navigation API first\n if (hasNavigation()) {\n return new NavigationAPIAdapter();\n }\n\n // Fall back to static mode if enabled\n if (fallback === \"static\") {\n return new StaticAdapter();\n }\n\n // No adapter available (fallback=\"none\" or default)\n return new NullAdapter();\n}\n","import {\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n} from \"react\";\nimport { RouterContext } from \"./context/RouterContext.js\";\nimport { RouteContext } from \"./context/RouteContext.js\";\nimport {\n BlockerContext,\n createBlockerRegistry,\n} from \"./context/BlockerContext.js\";\nimport {\n type NavigateOptions,\n type MatchedRouteWithData,\n type OnNavigateCallback,\n type FallbackMode,\n type InternalRouteState,\n internalRoutes,\n} from \"./types.js\";\nimport { matchRoutes } from \"./core/matchRoutes.js\";\nimport { createAdapter } from \"./core/createAdapter.js\";\nimport { executeLoaders, createLoaderRequest } from \"./core/loaderCache.js\";\nimport type { RouteDefinition } from \"./route.js\";\n\nexport type RouterProps = {\n routes: RouteDefinition[];\n /**\n * Callback invoked before navigation is intercepted.\n * Call `event.preventDefault()` to prevent the router from handling this navigation.\n *\n * @param event - The NavigateEvent from the Navigation API\n * @param info - Information about the navigation including matched routes and whether it will be intercepted\n */\n onNavigate?: OnNavigateCallback;\n /**\n * Fallback mode when Navigation API is unavailable.\n *\n * - `\"none\"` (default): Render nothing when Navigation API is unavailable\n * - `\"static\"`: Render matched routes without navigation capabilities (MPA behavior)\n */\n fallback?: FallbackMode;\n};\n\nexport function Router({\n routes: inputRoutes,\n onNavigate,\n fallback = \"none\",\n}: RouterProps): ReactNode {\n const routes = internalRoutes(inputRoutes);\n\n // Create adapter once based on browser capabilities and fallback setting\n const adapter = useMemo(() => createAdapter(fallback), [fallback]);\n\n // Create blocker registry once\n const [blockerRegistry] = useState(() => createBlockerRegistry());\n\n // Subscribe to location changes via adapter\n const locationEntry = useSyncExternalStore(\n useCallback((callback) => adapter.subscribe(callback), [adapter]),\n () => adapter.getSnapshot(),\n () => adapter.getServerSnapshot(),\n );\n\n // Set up navigation interception via adapter\n useEffect(() => {\n return adapter.setupInterception(\n routes,\n onNavigate,\n blockerRegistry.checkAll,\n );\n }, [adapter, routes, onNavigate, blockerRegistry]);\n\n // Navigate function from adapter\n const navigate = useCallback(\n (to: string, options?: NavigateOptions) => {\n adapter.navigate(to, options);\n },\n [adapter],\n );\n\n // Navigate function that returns a Promise\n const navigateAsync = useCallback(\n (to: string, options?: NavigateOptions) => {\n return adapter.navigateAsync(to, options);\n },\n [adapter],\n );\n\n // Update current entry's state without navigation\n const updateCurrentEntryState = useCallback(\n (state: unknown) => {\n adapter.updateCurrentEntryState(state);\n },\n [adapter],\n );\n\n return useMemo(() => {\n if (locationEntry === null) {\n // This happens either when Navigation API is unavailable (and no fallback),\n // or the current document is not fully active.\n return null;\n }\n\n const { url, key } = locationEntry;\n\n // Match current URL against routes and execute loaders\n const matchedRoutesWithData = (() => {\n const matched = matchRoutes(routes, url.pathname);\n if (!matched) return null;\n\n // Execute loaders (results are cached by location entry key)\n const request = createLoaderRequest(url);\n const signal = adapter.getIdleAbortSignal();\n return executeLoaders(matched, key, request, signal);\n })();\n\n const routerContextValue = {\n locationEntry,\n url,\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n };\n\n const blockerContextValue = { registry: blockerRegistry };\n\n return (\n <BlockerContext.Provider value={blockerContextValue}>\n <RouterContext.Provider value={routerContextValue}>\n {matchedRoutesWithData ? (\n <RouteRenderer matchedRoutes={matchedRoutesWithData} index={0} />\n ) : null}\n </RouterContext.Provider>\n </BlockerContext.Provider>\n );\n }, [\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n locationEntry,\n routes,\n adapter,\n blockerRegistry,\n ]);\n}\n\ntype RouteRendererProps = {\n matchedRoutes: MatchedRouteWithData[];\n index: number;\n};\n\n/**\n * Recursively render matched routes with proper context.\n */\nfunction RouteRenderer({\n matchedRoutes,\n index,\n}: RouteRendererProps): ReactNode {\n // Get parent route context (null for root route)\n const parentRouteContext = useContext(RouteContext);\n\n const match = matchedRoutes[index];\n if (!match) return null;\n\n const { route, params, pathname, data } = match;\n\n const routerContext = useContext(RouterContext);\n if (!routerContext) {\n throw new Error(\"RouteRenderer must be used within RouterContext\");\n }\n const { locationEntry, url, navigateAsync, updateCurrentEntryState } =\n routerContext;\n\n // Extract this route's state from internal structure\n const internalState = locationEntry.state as InternalRouteState | undefined;\n const routeState = internalState?.__routeStates?.[index];\n\n // Create stable setStateSync callback for this route's slice (synchronous via updateCurrentEntry)\n const setStateSync = useCallback(\n (stateOrUpdater: unknown | ((prev: unknown) => unknown)) => {\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)\n ?.__routeStates ?? [];\n const currentRouteState = currentStates[index];\n\n const newState =\n typeof stateOrUpdater === \"function\"\n ? (stateOrUpdater as (prev: unknown) => unknown)(currentRouteState)\n : stateOrUpdater;\n\n const newStates = [...currentStates];\n newStates[index] = newState;\n updateCurrentEntryState({ __routeStates: newStates });\n },\n [locationEntry.state, index, updateCurrentEntryState],\n );\n\n // Create stable setState callback for this route's slice (async via replace navigation)\n const setState = useCallback(\n async (\n stateOrUpdater: unknown | ((prev: unknown) => unknown),\n ): Promise<void> => {\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)\n ?.__routeStates ?? [];\n const currentRouteState = currentStates[index];\n\n const newState =\n typeof stateOrUpdater === \"function\"\n ? (stateOrUpdater as (prev: unknown) => unknown)(currentRouteState)\n : stateOrUpdater;\n\n const newStates = [...currentStates];\n newStates[index] = newState;\n\n await navigateAsync(url.href, {\n replace: true,\n state: { __routeStates: newStates },\n });\n },\n [locationEntry.state, index, url, navigateAsync],\n );\n\n // Create stable resetState callback\n const resetState = useCallback(() => {\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)?.__routeStates ??\n [];\n const newStates = [...currentStates];\n newStates[index] = undefined;\n updateCurrentEntryState({ __routeStates: newStates });\n }, [locationEntry.state, index, updateCurrentEntryState]);\n\n // Create outlet for child routes\n const outlet =\n index < matchedRoutes.length - 1 ? (\n <RouteRenderer matchedRoutes={matchedRoutes} index={index + 1} />\n ) : null;\n\n // Extract id from route definition (if available)\n const routeId = (route as { id?: string }).id;\n\n const routeContextValue = useMemo(\n () => ({\n id: routeId,\n params,\n matchedPath: pathname,\n state: routeState,\n data,\n outlet,\n parent: parentRouteContext,\n }),\n [routeId, params, pathname, routeState, data, outlet, parentRouteContext],\n );\n\n // Render component with or without data prop based on loader presence\n // Always pass params, state, setState, resetState, and info props to components\n const renderComponent = () => {\n const componentOrElement = route.component;\n\n if (componentOrElement == null) return outlet;\n\n // Check if it's a component reference (function) or a ReactNode (JSX element)\n if (typeof componentOrElement !== \"function\") {\n // ReactNode (JSX element, string, number, etc.): render as-is without router props\n return componentOrElement;\n }\n\n // Component reference: inject router props (existing behavior)\n const Component = componentOrElement;\n\n const stateProps = {\n state: routeState,\n setState,\n setStateSync,\n resetState,\n };\n\n // Ephemeral info from the current navigation\n const { info } = locationEntry;\n\n // When loader exists, data is defined and component expects data prop\n // When loader doesn't exist, data is undefined and component doesn't expect data prop\n // TypeScript can't narrow this union, so we use runtime check with type assertion\n if (route.loader) {\n const ComponentWithData = Component as React.ComponentType<{\n data: unknown;\n params: Record<string, string>;\n state: unknown;\n setState: (s: unknown | ((prev: unknown) => unknown)) => Promise<void>;\n setStateSync: (s: unknown | ((prev: unknown) => unknown)) => void;\n resetState: () => void;\n info: unknown;\n }>;\n return (\n <ComponentWithData\n data={data}\n params={params}\n {...stateProps}\n info={info}\n />\n );\n }\n const ComponentWithoutData = Component as React.ComponentType<{\n params: Record<string, string>;\n state: unknown;\n setState: (s: unknown | ((prev: unknown) => unknown)) => Promise<void>;\n setStateSync: (s: unknown | ((prev: unknown) => unknown)) => void;\n resetState: () => void;\n info: unknown;\n }>;\n return <ComponentWithoutData params={params} {...stateProps} info={info} />;\n };\n\n return (\n <RouteContext.Provider value={routeContextValue}>\n {renderComponent()}\n </RouteContext.Provider>\n );\n}\n","import { type ReactNode, useContext } from \"react\";\nimport { RouteContext } from \"./context/RouteContext.js\";\n\n/**\n * Renders the matched child route.\n * Used in layout components to specify where child routes should render.\n */\nexport function Outlet(): ReactNode {\n const routeContext = useContext(RouteContext);\n\n if (!routeContext) {\n return null;\n }\n\n return routeContext.outlet;\n}\n","import { useContext } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport type { NavigateOptions } from \"../types.js\";\n\n/**\n * Returns a function for programmatic navigation.\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useNavigate must be used within a Router\");\n }\n\n return context.navigate;\n}\n","import { useContext, useMemo } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport type { Location } from \"../types.js\";\n\n/**\n * Returns the current location object.\n */\nexport function useLocation(): Location {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useLocation must be used within a Router\");\n }\n\n const { url } = context;\n\n return useMemo(() => {\n return {\n pathname: url.pathname,\n search: url.search,\n hash: url.hash,\n };\n }, [url]);\n}\n","import { useCallback, useContext, useMemo } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\n\ntype SetSearchParams = (\n params:\n | URLSearchParams\n | Record<string, string>\n | ((prev: URLSearchParams) => URLSearchParams | Record<string, string>),\n) => void;\n\n/**\n * Returns and allows manipulation of URL search parameters.\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useSearchParams must be used within a Router\");\n }\n\n const searchParams = context.url.searchParams;\n\n const setSearchParams = useCallback<SetSearchParams>(\n (params) => {\n const url = new URL(context.url);\n\n let newParams: URLSearchParams;\n if (typeof params === \"function\") {\n const result = params(new URLSearchParams(url.search));\n newParams =\n result instanceof URLSearchParams\n ? result\n : new URLSearchParams(result);\n } else if (params instanceof URLSearchParams) {\n newParams = params;\n } else {\n newParams = new URLSearchParams(params);\n }\n\n url.search = newParams.toString();\n context.navigate(url.pathname + url.search + url.hash, { replace: true });\n },\n [context],\n );\n\n return [searchParams, setSearchParams];\n}\n","import { useContext, useEffect, useId } from \"react\";\nimport { BlockerContext } from \"../context/BlockerContext.js\";\n\nexport type UseBlockerOptions = {\n /**\n * Function that returns true if navigation should be blocked.\n * Can call `confirm()` inside to show a confirmation dialog.\n */\n shouldBlock: () => boolean;\n};\n\n/**\n * Hook to block navigation away from the current route.\n *\n * This is useful for scenarios like unsaved form data, ongoing file uploads,\n * or any state that would be lost on navigation.\n *\n * @example\n * ```tsx\n * function EditForm() {\n * const [isDirty, setIsDirty] = useState(false);\n *\n * useBlocker({\n * shouldBlock: () => {\n * if (isDirty) {\n * return !confirm(\"You have unsaved changes. Leave anyway?\");\n * }\n * return false;\n * },\n * });\n *\n * return <form>...</form>;\n * }\n * ```\n *\n * Note: This hook only handles SPA navigations (links, programmatic navigation).\n * For hard navigations (tab close, refresh), handle `beforeunload` separately.\n */\nexport function useBlocker(options: UseBlockerOptions): void {\n const context = useContext(BlockerContext);\n\n if (!context) {\n throw new Error(\"useBlocker must be used within a Router\");\n }\n\n const { shouldBlock } = options;\n const blockerId = useId();\n const { registry } = context;\n\n // Register blocker on mount, unregister on unmount\n // Re-registers when shouldBlock function changes\n useEffect(() => {\n return registry.register(blockerId, shouldBlock);\n }, [blockerId, shouldBlock, registry]);\n}\n","import { useContext } from \"react\";\nimport {\n RouteContext,\n findRouteContextById,\n type RouteContextValue,\n} from \"../context/RouteContext.js\";\n\n/**\n * Internal hook that returns the RouteContextValue for the given route.\n * If the route has an ID, it searches the ancestor chain for a matching route.\n * If no ID is provided, it returns the current (nearest) route context.\n *\n * @param hookName - Name of the calling hook (for error messages)\n * @param routeId - Optional route ID to search for in the ancestor chain\n * @returns The matching RouteContextValue\n * @throws If called outside a route component or if the route ID is not found\n * @internal\n */\nexport function useRouteContext(\n hookName: string,\n routeId: string | undefined,\n): RouteContextValue {\n const context = useContext(RouteContext);\n if (!context) {\n throw new Error(`${hookName} must be used within a route component`);\n }\n\n // If no expected ID, use current context (backwards compatible)\n if (routeId === undefined) {\n return context;\n }\n\n // Look for matching route in ancestor chain\n const matchedContext = findRouteContextById(context, routeId);\n if (!matchedContext) {\n throw new Error(\n `${hookName}: Route ID \"${routeId}\" not found in current route hierarchy. ` +\n `Current route is \"${context.id ?? \"(no id)\"}\"`,\n );\n }\n\n return matchedContext;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteParams,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed route parameters for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * const userRoute = route({\n * id: \"user\",\n * path: \"/users/:userId\",\n * component: UserPage,\n * });\n *\n * function UserPage() {\n * const params = useRouteParams(userRoute);\n * // params is typed as { userId: string }\n * return <div>User ID: {params.userId}</div>;\n * }\n * ```\n */\nexport function useRouteParams<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteParams<T> {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteParams\", routeId);\n return context.params as ExtractRouteParams<T>;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteState,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed navigation state for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * type ScrollState = { scrollPos: number };\n * const scrollRoute = routeState<ScrollState>()({\n * id: \"scroll\",\n * path: \"/scroll\",\n * component: ScrollPage,\n * });\n *\n * function ScrollPage() {\n * const state = useRouteState(scrollRoute);\n * // state is typed as ScrollState | undefined\n * return <div>Scroll position: {state?.scrollPos ?? 0}</div>;\n * }\n * ```\n */\nexport function useRouteState<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteState<T> | undefined {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteState\", routeId);\n return context.state as ExtractRouteState<T> | undefined;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteData,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed loader data for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * const userRoute = route({\n * id: \"user\",\n * path: \"/users/:userId\",\n * loader: async ({ params }) => {\n * const res = await fetch(`/api/users/${params.userId}`);\n * return res.json() as Promise<{ name: string; age: number }>;\n * },\n * component: UserPage,\n * });\n *\n * function UserPage() {\n * const data = useRouteData(userRoute);\n * // data is typed as Promise<{ name: string; age: number }>\n * return <div>User: {data.name}</div>;\n * }\n * ```\n */\nexport function useRouteData<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteData<T> {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteData\", routeId);\n return context.data as ExtractRouteData<T>;\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.mjs","names":["#cachedEntryId","#cachedSnapshot","#currentNavigationInfo","#subscribeToDisposeEvents","#subscribedEntryIds","#cachedSnapshot","#idleController","#idleController"],"sources":["../src/context/RouterContext.ts","../src/context/RouteContext.ts","../src/context/BlockerContext.ts","../src/types.ts","../src/core/matchRoutes.ts","../src/core/loaderCache.ts","../src/core/NavigationAPIAdapter.ts","../src/core/StaticAdapter.ts","../src/core/NullAdapter.ts","../src/core/createAdapter.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useLocationSSR.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteContext.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/hooks/useIsPending.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type { NavigateOptions } from \"../types.js\";\nimport type { LocationEntry } from \"../core/RouterAdapter.js\";\n\nexport type RouterContextValue = {\n /** Current location entry (null during SSR) */\n locationEntry: LocationEntry | null;\n /** Current URL (null during SSR) */\n url: URL | null;\n /** Whether a navigation transition is pending */\n isPending: boolean;\n /** Navigate to a new URL */\n navigate: (to: string, options?: NavigateOptions) => void;\n /** Navigate to a new URL and wait for completion */\n navigateAsync: (to: string, options?: NavigateOptions) => Promise<void>;\n /** Update current entry's state without navigation */\n updateCurrentEntryState: (state: unknown) => void;\n};\n\nexport const RouterContext = createContext<RouterContextValue | null>(null);\n","import { createContext, type ReactNode } from \"react\";\n\nexport type RouteContextValue = {\n /** Route identifier (if provided in route definition) */\n id: string | undefined;\n /** Matched route parameters extracted from URL */\n params: Record<string, string>;\n /** The matched path pattern */\n matchedPath: string;\n /** Navigation state for this route */\n state: unknown;\n /** Data from loader (if route has loader) */\n data: unknown;\n /** Child route element to render via Outlet */\n outlet: ReactNode;\n /** Parent route context (for nested routes) */\n parent: RouteContextValue | null;\n};\n\nexport const RouteContext = createContext<RouteContextValue | null>(null);\n\n/**\n * Find a route context by ID in the ancestor chain.\n * Returns the matching context or null if not found.\n */\nexport function findRouteContextById(\n context: RouteContextValue | null,\n id: string,\n): RouteContextValue | null {\n let current = context;\n while (current !== null) {\n if (current.id === id) return current;\n current = current.parent;\n }\n return null;\n}\n","import { createContext } from \"react\";\n\nexport type BlockerId = string;\n\nexport type BlockerRegistry = {\n /** Register a blocker, returns unregister function */\n register: (id: BlockerId, shouldBlock: () => boolean) => () => void;\n /** Check all blockers - returns true if any blocks */\n checkAll: () => boolean;\n};\n\nexport type BlockerContextValue = {\n registry: BlockerRegistry;\n};\n\n/**\n * Create a new blocker registry.\n * The registry manages registered blockers and provides a way to check if any blocker is active.\n */\nexport function createBlockerRegistry(): BlockerRegistry {\n const blockers = new Map<BlockerId, () => boolean>();\n\n return {\n register(id: BlockerId, shouldBlock: () => boolean): () => void {\n blockers.set(id, shouldBlock);\n return () => {\n blockers.delete(id);\n };\n },\n\n checkAll(): boolean {\n for (const shouldBlock of blockers.values()) {\n if (shouldBlock()) {\n return true;\n }\n }\n return false;\n },\n };\n}\n\nexport const BlockerContext = createContext<BlockerContextValue | null>(null);\n","import type { ComponentType, ReactNode } from \"react\";\nimport type { LoaderArgs, RouteDefinition } from \"./route.js\";\n\nconst InternalRouteDefinitionSymbol = Symbol();\n\n/**\n * Internal structure for storing per-route state in NavigationHistoryEntry.\n * Each route in the matched stack gets its own state slot indexed by match position.\n */\nexport type InternalRouteState = {\n __routeStates: (unknown | undefined)[];\n};\n\n/**\n * Route definition for the router.\n * When a loader is defined, the component receives the loader result as a `data` prop.\n */\nexport type InternalRouteDefinition = {\n [InternalRouteDefinitionSymbol]: never;\n /** Path pattern to match (e.g., \"users/:id\"). If omitted, the route is pathless (always matches, consumes nothing). */\n path?: string;\n /** Child routes for nested routing */\n children?: InternalRouteDefinition[];\n /**\n * Whether this route requires an exact match.\n * - true: Only matches exact pathname\n * - false: Matches as prefix\n * - undefined: Default (exact for leaf, prefix for parent)\n */\n exact?: boolean;\n /**\n * Whether this route requires a child to match when it has children.\n * - true (default): Parent does not match if no child matches\n * - false: Parent can match alone if it has a component, outlet will be null\n */\n requireChildren?: boolean;\n\n // Note: `loader` and `component` may both exist or both not exist.\n // Also, `unknown`s may actually be more specific types. They are guaranteed\n // to be the same type by the `route` helper function.\n /** Data loader function for this route */\n loader?: (args: LoaderArgs<Record<string, string>>) => unknown;\n /** Component to render when this route matches */\n component?:\n | ComponentType<{\n data?: unknown;\n params?: Record<string, string>;\n state?: unknown;\n setState?: (\n state: unknown | ((prev: unknown) => unknown),\n ) => Promise<void>;\n setStateSync?: (state: unknown | ((prev: unknown) => unknown)) => void;\n resetState?: () => void;\n info?: unknown;\n }>\n | ReactNode;\n};\n\n/**\n * Converts user-defined routes to internal route definitions.\n * This function is used internally by the Router.\n *\n * Actually, this function just performs a type assertion since\n * both RouteDefinition and InternalRouteDefinition have the same runtime shape.\n */\nexport function internalRoutes(\n routes: RouteDefinition[],\n): InternalRouteDefinition[] {\n return routes as InternalRouteDefinition[];\n}\n\n/**\n * A matched route with its parameters.\n */\nexport type MatchedRoute = {\n /** The original route definition */\n route: InternalRouteDefinition;\n /** Extracted path parameters */\n params: Record<string, string>;\n /** The matched pathname segment */\n pathname: string;\n};\n\n/**\n * A matched route with loader data.\n */\nexport type MatchedRouteWithData = MatchedRoute & {\n /** Data returned from the loader (undefined if no loader) */\n data: unknown | undefined;\n};\n\n/**\n * Information passed to onNavigate callback.\n */\nexport type OnNavigateInfo = {\n /** Array of matched routes, or null if no routes matched */\n matches: readonly MatchedRoute[] | null;\n /** Whether the router will intercept this navigation (before user's preventDefault() call) */\n intercepting: boolean;\n};\n\n/**\n * Options for navigation.\n */\nexport type NavigateOptions = {\n /** Replace current history entry instead of pushing */\n replace?: boolean;\n /** State to associate with the navigation */\n state?: unknown;\n /** Ephemeral info for this navigation only (not persisted in history) */\n info?: unknown;\n};\n\n/**\n * Location object representing current URL state.\n */\nexport type Location = {\n pathname: string;\n search: string;\n hash: string;\n};\n\n/**\n * Callback invoked before navigation is intercepted.\n * Call `event.preventDefault()` to prevent the router from handling this navigation.\n *\n * @param event - The NavigateEvent from the Navigation API\n * @param info - Information about the navigation including matched routes and whether it will be intercepted\n */\nexport type OnNavigateCallback = (\n event: NavigateEvent,\n info: OnNavigateInfo,\n) => void;\n\n/**\n * Fallback mode when Navigation API is unavailable.\n *\n * - `\"none\"` (default): Render nothing when Navigation API is unavailable\n * - `\"static\"`: Render matched routes without navigation capabilities (MPA behavior)\n */\nexport type FallbackMode =\n | \"none\" // Default: render nothing when Navigation API unavailable\n | \"static\"; // Render matched routes without navigation capabilities\n","import type { InternalRouteDefinition, MatchedRoute } from \"../types.js\";\n\n/**\n * Match a pathname against a route tree, returning the matched route stack.\n * Returns null if no match is found.\n */\nexport function matchRoutes(\n routes: InternalRouteDefinition[],\n pathname: string | null,\n): MatchedRoute[] | null {\n for (const route of routes) {\n const matched = matchRoute(route, pathname);\n if (matched) {\n return matched;\n }\n }\n return null;\n}\n\n/**\n * Match a single route and its children recursively.\n */\nfunction matchRoute(\n route: InternalRouteDefinition,\n pathname: string | null,\n): MatchedRoute[] | null {\n const hasChildren = Boolean(route.children?.length);\n\n // Handle pathless routes - always match, consume nothing\n if (route.path === undefined) {\n // Pathless routes with loaders can't render during SSR (no request context)\n if (pathname === null && route.loader) {\n return null;\n }\n const result: MatchedRoute = {\n route,\n params: {},\n pathname: \"\",\n };\n\n if (hasChildren) {\n for (const child of route.children!) {\n const childMatch = matchRoute(child, pathname);\n if (childMatch) {\n return [result, ...childMatch];\n }\n }\n // No children matched - only valid if requireChildren is false and route has a component\n if (route.component && route.requireChildren === false) {\n return [result];\n }\n // When pathname is null, pathless route with component matches alone (SSR shell)\n if (pathname === null && route.component) {\n return [result];\n }\n return null;\n }\n\n return [result];\n }\n\n // Path-based routes cannot match when pathname is null\n if (pathname === null) {\n return null;\n }\n\n const isExact = route.exact ?? !hasChildren;\n\n const { matched, params, consumedPathname } = matchPath(\n route.path,\n pathname,\n isExact,\n );\n\n if (!matched) {\n return null;\n }\n\n const result: MatchedRoute = {\n route,\n params,\n pathname: consumedPathname,\n };\n\n // If this route has children, try to match them\n if (hasChildren) {\n // Calculate remaining pathname, ensuring it starts with /\n let remainingPathname = pathname.slice(consumedPathname.length);\n if (!remainingPathname.startsWith(\"/\")) {\n remainingPathname = \"/\" + remainingPathname;\n }\n if (remainingPathname === \"\") {\n remainingPathname = \"/\";\n }\n\n for (const child of route.children!) {\n const childMatch = matchRoute(child, remainingPathname);\n if (childMatch) {\n // Merge params from parent into children\n return [\n result,\n ...childMatch.map((m) => ({\n ...m,\n params: { ...params, ...m.params },\n })),\n ];\n }\n }\n\n // If no children matched - only valid if requireChildren is false and route has a component\n if (route.component && route.requireChildren === false) {\n return [result];\n }\n\n return null;\n }\n\n return [result];\n}\n\n/**\n * Match a path pattern against a pathname.\n */\nfunction matchPath(\n pattern: string,\n pathname: string,\n exact: boolean,\n): {\n matched: boolean;\n params: Record<string, string>;\n consumedPathname: string;\n} {\n // Normalize pattern\n const normalizedPattern = pattern.startsWith(\"/\") ? pattern : `/${pattern}`;\n\n // Build URLPattern\n let urlPatternPath: string;\n if (exact) {\n urlPatternPath = normalizedPattern;\n } else if (normalizedPattern === \"/\") {\n // Special case: root path as prefix matches anything\n urlPatternPath = \"/*\";\n } else {\n // For other prefix matches, add optional wildcard suffix\n urlPatternPath = `${normalizedPattern}{/*}?`;\n }\n\n const urlPattern = new URLPattern({ pathname: urlPatternPath });\n\n const match = urlPattern.exec({ pathname });\n if (!match) {\n return { matched: false, params: {}, consumedPathname: \"\" };\n }\n\n // Extract params (excluding the wildcard group \"0\")\n const params: Record<string, string> = {};\n for (const [key, value] of Object.entries(match.pathname.groups)) {\n if (value !== undefined && key !== \"0\") {\n params[key] = value;\n }\n }\n\n // Calculate consumed pathname\n let consumedPathname: string;\n if (exact) {\n consumedPathname = pathname;\n } else if (normalizedPattern === \"/\") {\n // Root pattern consumes just \"/\"\n consumedPathname = \"/\";\n } else {\n // For prefix matches, calculate based on pattern segments\n const patternSegments = normalizedPattern.split(\"/\").filter(Boolean);\n const pathnameSegments = pathname.split(\"/\").filter(Boolean);\n consumedPathname =\n \"/\" + pathnameSegments.slice(0, patternSegments.length).join(\"/\");\n }\n\n return { matched: true, params, consumedPathname };\n}\n","import type { LoaderArgs } from \"../route.js\";\nimport type {\n MatchedRoute,\n MatchedRouteWithData,\n InternalRouteDefinition,\n} from \"../types.js\";\n\n/**\n * Cache for loader results.\n * Key format: `${entryId}:${matchIndex}`\n */\nconst loaderCache = new Map<string, unknown>();\n\n/**\n * Get or create a loader result from cache.\n * If the result is not cached, executes the loader and caches the result.\n */\nfunction getOrCreateLoaderResult(\n entryId: string,\n matchIndex: number,\n route: InternalRouteDefinition,\n args: LoaderArgs<Record<string, string>>,\n): unknown | undefined {\n if (!route.loader) {\n return undefined;\n }\n\n const cacheKey = `${entryId}:${matchIndex}`;\n\n if (!loaderCache.has(cacheKey)) {\n loaderCache.set(cacheKey, route.loader(args));\n }\n\n return loaderCache.get(cacheKey);\n}\n\n/**\n * Create a Request object for loader args.\n */\nexport function createLoaderRequest(url: URL): Request {\n return new Request(url.href, {\n method: \"GET\",\n });\n}\n\n/**\n * Execute loaders for matched routes and return routes with data.\n * Results are cached by navigation entry id to prevent duplicate execution.\n */\nexport function executeLoaders(\n matchedRoutes: MatchedRoute[],\n entryId: string,\n request: Request,\n signal: AbortSignal,\n): MatchedRouteWithData[] {\n return matchedRoutes.map((match, index) => {\n const { route, params } = match;\n const args: LoaderArgs<Record<string, string>> = {\n params,\n request,\n signal,\n };\n const data = getOrCreateLoaderResult(entryId, index, route, args);\n\n return { ...match, data };\n });\n}\n\n/**\n * Clear the loader cache.\n * Mainly used for testing.\n */\nexport function clearLoaderCache(): void {\n loaderCache.clear();\n}\n\n/**\n * Clear loader cache entries for a specific navigation entry.\n * Called when a NavigationHistoryEntry is disposed (removed from history stack).\n */\nexport function clearLoaderCacheForEntry(entryId: string): void {\n const prefix = `${entryId}:`;\n for (const key of loaderCache.keys()) {\n if (key.startsWith(prefix)) {\n loaderCache.delete(key);\n }\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\nimport { matchRoutes } from \"./matchRoutes.js\";\nimport {\n executeLoaders,\n createLoaderRequest,\n clearLoaderCacheForEntry,\n} from \"./loaderCache.js\";\n\n/**\n * Fallback AbortController for data loading initialized outside of a navigation event.\n * Aborted when the next navigation occurs.\n *\n * To save resources, this controller is created only when needed.\n */\nlet idleController: AbortController | null = null;\n\n/**\n * Reset navigation state. Used for testing.\n */\nexport function resetNavigationState(): void {\n idleController?.abort();\n idleController = null;\n}\n\n/**\n * Adapter that uses the Navigation API for full SPA functionality.\n */\nexport class NavigationAPIAdapter implements RouterAdapter {\n // Cache the snapshot to ensure referential stability for useSyncExternalStore\n #cachedSnapshot: LocationEntry | null = null;\n #cachedEntryId: string | null = null;\n // Ephemeral info from the current navigation event (not persisted in history)\n #currentNavigationInfo: unknown = undefined;\n\n getSnapshot(): LocationEntry | null {\n const entry = navigation.currentEntry;\n if (!entry?.url) {\n return null;\n }\n\n // Return cached snapshot if entry hasn't changed\n if (this.#cachedEntryId === entry.id && this.#cachedSnapshot) {\n return this.#cachedSnapshot;\n }\n\n // Create new snapshot and cache it\n this.#cachedEntryId = entry.id;\n this.#cachedSnapshot = {\n url: new URL(entry.url),\n key: entry.id,\n state: entry.getState(),\n info: this.#currentNavigationInfo,\n };\n return this.#cachedSnapshot;\n }\n\n subscribe(callback: () => void): () => void {\n const controller = new AbortController();\n navigation.addEventListener(\"currententrychange\", callback, {\n signal: controller.signal,\n });\n\n // Subscribe to dispose events on all existing entries\n this.#subscribeToDisposeEvents(controller.signal);\n\n // When current entry changes, subscribe to any new entries' dispose events\n navigation.addEventListener(\n \"currententrychange\",\n () => this.#subscribeToDisposeEvents(controller.signal),\n { signal: controller.signal },\n );\n\n return () => {\n controller.abort();\n };\n }\n\n /**\n * Track which entries we've subscribed to dispose events for.\n */\n #subscribedEntryIds = new Set<string>();\n\n /**\n * Subscribe to dispose events on all navigation entries.\n * When an entry is disposed, its cached loader results are cleared.\n */\n #subscribeToDisposeEvents(signal: AbortSignal): void {\n for (const entry of navigation.entries()) {\n if (this.#subscribedEntryIds.has(entry.id)) {\n continue;\n }\n this.#subscribedEntryIds.add(entry.id);\n\n const entryId = entry.id;\n entry.addEventListener(\n \"dispose\",\n () => {\n clearLoaderCacheForEntry(entryId);\n this.#subscribedEntryIds.delete(entryId);\n },\n { signal },\n );\n }\n }\n\n navigate(to: string, options?: NavigateOptions): void {\n navigation.navigate(to, {\n history: options?.replace ? \"replace\" : \"push\",\n state: options?.state,\n info: options?.info,\n });\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n const result = navigation.navigate(to, {\n history: options?.replace ? \"replace\" : \"push\",\n state: options?.state,\n info: options?.info,\n });\n await result.finished;\n }\n\n setupInterception(\n routes: InternalRouteDefinition[],\n onNavigate?: OnNavigateCallback,\n checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n const handleNavigate = (event: NavigateEvent) => {\n // Capture ephemeral info from the navigate event\n // This info is only available during this navigation and resets on the next one\n this.#currentNavigationInfo = event.info;\n // Invalidate cached snapshot to pick up new info\n this.#cachedSnapshot = null;\n\n // Check blockers first - if any blocker returns true, prevent navigation\n if (checkBlockers?.()) {\n event.preventDefault();\n return;\n }\n\n // Only intercept same-origin navigations\n if (!event.canIntercept) {\n onNavigate?.(event, { matches: [], intercepting: false });\n return;\n }\n\n // Check if the URL matches any of our routes\n const url = new URL(event.destination.url);\n const matched = matchRoutes(routes, url.pathname);\n\n // Compute whether we will intercept this navigation (before user's preventDefault)\n const willIntercept =\n matched !== null && !event.hashChange && event.downloadRequest === null;\n\n // Call onNavigate callback if provided (regardless of route match)\n if (onNavigate) {\n onNavigate(event, { matches: matched, intercepting: willIntercept });\n if (event.defaultPrevented) {\n return; // Do not intercept, allow browser default\n }\n }\n\n if (!willIntercept) {\n return;\n }\n\n // Route match, so intercept\n\n // Abort initial load's loaders if this is the first navigation\n if (idleController) {\n idleController.abort();\n idleController = null;\n }\n\n event.intercept({\n handler: async () => {\n const request = createLoaderRequest(url);\n\n // Note: in response to `currententrychange` event, <Router> should already\n // have dispatched data loaders and the results should be cached.\n // Here we run executeLoader to retrieve cached results.\n const currentEntry = navigation.currentEntry;\n if (!currentEntry) {\n throw new Error(\n \"Navigation currentEntry is null during navigation interception\",\n );\n }\n\n const results = executeLoaders(\n matched,\n currentEntry.id,\n request,\n event.signal,\n );\n\n // Delay navigation until async loaders complete\n await Promise.all(results.map((r) => r.data));\n },\n });\n };\n\n const controller = new AbortController();\n navigation.addEventListener(\"navigate\", handleNavigate, {\n signal: controller.signal,\n });\n return () => {\n controller.abort();\n };\n }\n\n getIdleAbortSignal(): AbortSignal {\n idleController ??= new AbortController();\n return idleController.signal;\n }\n\n updateCurrentEntryState(state: unknown): void {\n // Invalidate cached snapshot BEFORE updating, so the subscriber gets fresh state\n this.#cachedSnapshot = null;\n navigation.updateCurrentEntry({ state });\n // Note: updateCurrentEntry fires currententrychange, so subscribers are notified automatically\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\n\n/**\n * Static adapter for fallback mode when Navigation API is unavailable.\n * Provides read-only location access with no SPA navigation capabilities.\n * Links will cause full page loads (MPA behavior).\n */\nexport class StaticAdapter implements RouterAdapter {\n #cachedSnapshot: LocationEntry | null = null;\n #idleController: AbortController | null = null;\n\n getSnapshot(): LocationEntry | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n // Cache the snapshot - it never changes in static mode\n if (!this.#cachedSnapshot) {\n this.#cachedSnapshot = {\n url: new URL(window.location.href),\n key: \"__static__\",\n state: undefined,\n info: undefined,\n };\n }\n return this.#cachedSnapshot;\n }\n\n subscribe(_callback: () => void): () => void {\n // Static mode never fires location change events\n return () => {};\n }\n\n navigate(to: string, _options?: NavigateOptions): void {\n console.warn(\n \"FUNSTACK Router: navigate() called in static fallback mode. \" +\n \"Navigation API is not available in this browser. \" +\n \"Links will cause full page loads.\",\n );\n // Note: We intentionally do NOT do window.location.href = to\n // as that would mask bugs where developers expect SPA behavior.\n // If needed in the future, we could add a \"static-reload\" mode.\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n this.navigate(to, options);\n }\n\n setupInterception(\n _routes: InternalRouteDefinition[],\n _onNavigate?: OnNavigateCallback,\n _checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n // No interception in static mode - links cause full page loads\n return undefined;\n }\n\n getIdleAbortSignal(): AbortSignal {\n this.#idleController ??= new AbortController();\n return this.#idleController.signal;\n }\n\n updateCurrentEntryState(_state: unknown): void {\n // No-op in static mode - state updates require Navigation API\n }\n}\n","import type { RouterAdapter, LocationEntry } from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\n\n/**\n * Null adapter for when Navigation API is unavailable and no fallback is configured.\n * All methods are no-ops that return safe default values.\n */\nexport class NullAdapter implements RouterAdapter {\n #idleController: AbortController | null = null;\n\n getSnapshot(): LocationEntry | null {\n return null;\n }\n\n subscribe(_callback: () => void): () => void {\n return () => {};\n }\n\n navigate(_to: string, _options?: NavigateOptions): void {\n console.warn(\n \"FUNSTACK Router: navigate() called but no adapter is available. \" +\n \"Navigation API is not available in this browser and no fallback mode is configured.\",\n );\n }\n\n async navigateAsync(to: string, options?: NavigateOptions): Promise<void> {\n this.navigate(to, options);\n }\n\n setupInterception(\n _routes: InternalRouteDefinition[],\n _onNavigate?: OnNavigateCallback,\n _checkBlockers?: () => boolean,\n ): (() => void) | undefined {\n return undefined;\n }\n\n getIdleAbortSignal(): AbortSignal {\n this.#idleController ??= new AbortController();\n return this.#idleController.signal;\n }\n\n updateCurrentEntryState(_state: unknown): void {\n // No-op: NullAdapter doesn't support state updates\n }\n}\n","import type { RouterAdapter } from \"./RouterAdapter.js\";\nimport { NavigationAPIAdapter } from \"./NavigationAPIAdapter.js\";\nimport { StaticAdapter } from \"./StaticAdapter.js\";\nimport { NullAdapter } from \"./NullAdapter.js\";\nimport type { FallbackMode } from \"../types.js\";\n\n/**\n * Check if Navigation API is available.\n */\nfunction hasNavigation(): boolean {\n return typeof window !== \"undefined\" && \"navigation\" in window;\n}\n\n/**\n * Create the appropriate router adapter based on browser capabilities\n * and the specified fallback mode.\n *\n * @param fallback - The fallback mode to use when Navigation API is unavailable\n * @returns A RouterAdapter instance\n */\nexport function createAdapter(fallback: FallbackMode): RouterAdapter {\n // Try Navigation API first\n if (hasNavigation()) {\n return new NavigationAPIAdapter();\n }\n\n // Fall back to static mode if enabled\n if (fallback === \"static\") {\n return new StaticAdapter();\n }\n\n // No adapter available (fallback=\"none\" or default)\n return new NullAdapter();\n}\n","import {\n type ReactNode,\n useCallback,\n useContext,\n useEffect,\n useMemo,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\nimport type { LocationEntry } from \"./core/RouterAdapter.js\";\nimport { RouterContext } from \"./context/RouterContext.js\";\nimport { RouteContext } from \"./context/RouteContext.js\";\nimport {\n BlockerContext,\n createBlockerRegistry,\n} from \"./context/BlockerContext.js\";\nimport {\n type NavigateOptions,\n type MatchedRouteWithData,\n type OnNavigateCallback,\n type FallbackMode,\n type InternalRouteState,\n internalRoutes,\n} from \"./types.js\";\nimport { matchRoutes } from \"./core/matchRoutes.js\";\nimport { createAdapter } from \"./core/createAdapter.js\";\nimport { executeLoaders, createLoaderRequest } from \"./core/loaderCache.js\";\nimport type { RouteDefinition } from \"./route.js\";\n\nconst noopSubscribe = () => () => {};\nconst getServerSnapshot = () => null;\n\nexport type RouterProps = {\n routes: RouteDefinition[];\n /**\n * Callback invoked before navigation is intercepted.\n * Call `event.preventDefault()` to prevent the router from handling this navigation.\n *\n * @param event - The NavigateEvent from the Navigation API\n * @param info - Information about the navigation including matched routes and whether it will be intercepted\n */\n onNavigate?: OnNavigateCallback;\n /**\n * Fallback mode when Navigation API is unavailable.\n *\n * - `\"none\"` (default): Render nothing when Navigation API is unavailable\n * - `\"static\"`: Render matched routes without navigation capabilities (MPA behavior)\n */\n fallback?: FallbackMode;\n};\n\n/**\n * Initial value of locationEntry.\n * This value means to use the `initialEntry` from `useSyncExternalStore` instead.\n */\nconst entryInitValue = Symbol();\n\nexport function Router({\n routes: inputRoutes,\n onNavigate,\n fallback = \"none\",\n}: RouterProps): ReactNode {\n const routes = internalRoutes(inputRoutes);\n\n // Create adapter once based on browser capabilities and fallback setting\n const adapter = useMemo(() => createAdapter(fallback), [fallback]);\n\n // Create blocker registry once\n const [blockerRegistry] = useState(() => createBlockerRegistry());\n\n // Hydration-aware initial value: null during SSR/hydration, real on client-only mount\n const getSnapshot = useCallback(() => adapter.getSnapshot(), [adapter]);\n const initialEntry = useSyncExternalStore(\n noopSubscribe,\n getSnapshot,\n getServerSnapshot,\n );\n\n const [isPending, startTransition] = useTransition();\n const [locationEntryInternal, setLocationEntry] = useState<\n LocationEntry | null | typeof entryInitValue\n >(entryInitValue);\n const locationEntry =\n locationEntryInternal === entryInitValue\n ? initialEntry\n : locationEntryInternal;\n\n // Subscribe to navigation changes (wrapped in transition)\n useEffect(() => {\n return adapter.subscribe(() => {\n startTransition(() => {\n setLocationEntry(adapter.getSnapshot());\n });\n });\n }, [adapter, startTransition]);\n\n // Set up navigation interception via adapter\n useEffect(() => {\n return adapter.setupInterception(\n routes,\n onNavigate,\n blockerRegistry.checkAll,\n );\n }, [adapter, routes, onNavigate, blockerRegistry]);\n\n // Navigate function from adapter\n const navigate = useCallback(\n (to: string, options?: NavigateOptions) => {\n adapter.navigate(to, options);\n },\n [adapter],\n );\n\n // Navigate function that returns a Promise\n const navigateAsync = useCallback(\n (to: string, options?: NavigateOptions) => {\n return adapter.navigateAsync(to, options);\n },\n [adapter],\n );\n\n // Update current entry's state without navigation\n const updateCurrentEntryState = useCallback(\n (state: unknown) => {\n adapter.updateCurrentEntryState(state);\n },\n [adapter],\n );\n\n return useMemo(() => {\n // Match routes and execute loaders\n const matchedRoutesWithData = (() => {\n if (locationEntry === null) {\n // SSR/hydration: match only pathless routes, skip loaders\n const matched = matchRoutes(routes, null);\n if (!matched) return null;\n return matched.map((m) => ({ ...m, data: undefined }));\n }\n\n const { url, key } = locationEntry;\n const matched = matchRoutes(routes, url.pathname);\n if (!matched) return null;\n\n // Execute loaders (results are cached by location entry key)\n const request = createLoaderRequest(url);\n const signal = adapter.getIdleAbortSignal();\n return executeLoaders(matched, key, request, signal);\n })();\n\n const routerContextValue = {\n locationEntry: locationEntry,\n url: locationEntry?.url ?? null,\n isPending,\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n };\n\n const blockerContextValue = { registry: blockerRegistry };\n\n return (\n <BlockerContext.Provider value={blockerContextValue}>\n <RouterContext.Provider value={routerContextValue}>\n {matchedRoutesWithData ? (\n <RouteRenderer matchedRoutes={matchedRoutesWithData} index={0} />\n ) : null}\n </RouterContext.Provider>\n </BlockerContext.Provider>\n );\n }, [\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n isPending,\n locationEntry,\n routes,\n adapter,\n blockerRegistry,\n ]);\n}\n\ntype RouteRendererProps = {\n matchedRoutes: MatchedRouteWithData[];\n index: number;\n};\n\n/**\n * Recursively render matched routes with proper context.\n */\nfunction RouteRenderer({\n matchedRoutes,\n index,\n}: RouteRendererProps): ReactNode {\n // Get parent route context (null for root route)\n const parentRouteContext = useContext(RouteContext);\n\n const match = matchedRoutes[index];\n if (!match) return null;\n\n const { route, params, pathname, data } = match;\n\n const routerContext = useContext(RouterContext);\n if (!routerContext) {\n throw new Error(\"RouteRenderer must be used within RouterContext\");\n }\n const {\n locationEntry,\n url,\n isPending,\n navigateAsync,\n updateCurrentEntryState,\n } = routerContext;\n\n // Extract this route's state from internal structure\n const internalState = locationEntry?.state as InternalRouteState | undefined;\n const routeState = internalState?.__routeStates?.[index];\n\n // Create stable setStateSync callback for this route's slice (synchronous via updateCurrentEntry)\n const setStateSync = useCallback(\n (stateOrUpdater: unknown | ((prev: unknown) => unknown)) => {\n if (locationEntry === null) return;\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)\n ?.__routeStates ?? [];\n const currentRouteState = currentStates[index];\n\n const newState =\n typeof stateOrUpdater === \"function\"\n ? (stateOrUpdater as (prev: unknown) => unknown)(currentRouteState)\n : stateOrUpdater;\n\n const newStates = [...currentStates];\n newStates[index] = newState;\n updateCurrentEntryState({ __routeStates: newStates });\n },\n [locationEntry?.state, index, updateCurrentEntryState],\n );\n\n // Create stable setState callback for this route's slice (async via replace navigation)\n const setState = useCallback(\n async (\n stateOrUpdater: unknown | ((prev: unknown) => unknown),\n ): Promise<void> => {\n if (locationEntry === null || url === null) return;\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)\n ?.__routeStates ?? [];\n const currentRouteState = currentStates[index];\n\n const newState =\n typeof stateOrUpdater === \"function\"\n ? (stateOrUpdater as (prev: unknown) => unknown)(currentRouteState)\n : stateOrUpdater;\n\n const newStates = [...currentStates];\n newStates[index] = newState;\n\n await navigateAsync(url.href, {\n replace: true,\n state: { __routeStates: newStates },\n });\n },\n [locationEntry?.state, index, url, navigateAsync],\n );\n\n // Create stable resetState callback\n const resetState = useCallback(() => {\n if (locationEntry === null) return;\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)?.__routeStates ??\n [];\n const newStates = [...currentStates];\n newStates[index] = undefined;\n updateCurrentEntryState({ __routeStates: newStates });\n }, [locationEntry?.state, index, updateCurrentEntryState]);\n\n // Create outlet for child routes\n const outlet =\n index < matchedRoutes.length - 1 ? (\n <RouteRenderer matchedRoutes={matchedRoutes} index={index + 1} />\n ) : null;\n\n // Extract id from route definition (if available)\n const routeId = (route as { id?: string }).id;\n\n const routeContextValue = useMemo(\n () => ({\n id: routeId,\n params,\n matchedPath: pathname,\n state: routeState,\n data,\n outlet,\n parent: parentRouteContext,\n }),\n [routeId, params, pathname, routeState, data, outlet, parentRouteContext],\n );\n\n // Render component with or without data prop based on loader presence\n // Always pass params, state, setState, resetState, and info props to components\n const renderComponent = () => {\n const componentOrElement = route.component;\n\n if (componentOrElement == null) return outlet;\n\n // Check if it's a component reference (function) or a ReactNode (JSX element)\n if (typeof componentOrElement !== \"function\") {\n // ReactNode (JSX element, string, number, etc.): render as-is without router props\n return componentOrElement;\n }\n\n // Component reference: inject router props (existing behavior)\n const Component = componentOrElement;\n\n const stateProps = {\n state: routeState,\n setState,\n setStateSync,\n resetState,\n };\n\n // Ephemeral info from the current navigation\n const info = locationEntry?.info;\n\n // When loader exists, data is defined and component expects data prop\n // When loader doesn't exist, data is undefined and component doesn't expect data prop\n // TypeScript can't narrow this union, so we use runtime check with type assertion\n if (route.loader) {\n const ComponentWithData = Component as React.ComponentType<{\n data: unknown;\n params: Record<string, string>;\n state: unknown;\n setState: (s: unknown | ((prev: unknown) => unknown)) => Promise<void>;\n setStateSync: (s: unknown | ((prev: unknown) => unknown)) => void;\n resetState: () => void;\n info: unknown;\n isPending: boolean;\n }>;\n return (\n <ComponentWithData\n data={data}\n params={params}\n {...stateProps}\n info={info}\n isPending={isPending}\n />\n );\n }\n const ComponentWithoutData = Component as React.ComponentType<{\n params: Record<string, string>;\n state: unknown;\n setState: (s: unknown | ((prev: unknown) => unknown)) => Promise<void>;\n setStateSync: (s: unknown | ((prev: unknown) => unknown)) => void;\n resetState: () => void;\n info: unknown;\n isPending: boolean;\n }>;\n return (\n <ComponentWithoutData\n params={params}\n {...stateProps}\n info={info}\n isPending={isPending}\n />\n );\n };\n\n return (\n <RouteContext.Provider value={routeContextValue}>\n {renderComponent()}\n </RouteContext.Provider>\n );\n}\n","import { type ReactNode, useContext } from \"react\";\nimport { RouteContext } from \"./context/RouteContext.js\";\n\n/**\n * Renders the matched child route.\n * Used in layout components to specify where child routes should render.\n */\nexport function Outlet(): ReactNode {\n const routeContext = useContext(RouteContext);\n\n if (!routeContext) {\n return null;\n }\n\n return routeContext.outlet;\n}\n","import { useContext } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport type { NavigateOptions } from \"../types.js\";\n\n/**\n * Returns a function for programmatic navigation.\n */\nexport function useNavigate(): (to: string, options?: NavigateOptions) => void {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useNavigate must be used within a Router\");\n }\n\n return context.navigate;\n}\n","import { useContext, useMemo } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport type { Location } from \"../types.js\";\n\n/**\n * Returns the current location object.\n */\nexport function useLocation(): Location {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useLocation must be used within a Router\");\n }\n\n const { url } = context;\n\n if (url === null) {\n throw new Error(\"useLocation: URL is not available during SSR.\");\n }\n\n return useMemo(() => {\n return {\n pathname: url.pathname,\n search: url.search,\n hash: url.hash,\n };\n }, [url]);\n}\n","import { useContext, useMemo } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport type { Location } from \"../types.js\";\n\n/**\n * Returns the current location object, or `null` when the URL is not available (e.g. during SSR).\n */\nexport function useLocationSSR(): Location | null {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useLocationSSR must be used within a Router\");\n }\n\n const { url } = context;\n\n return useMemo(() => {\n if (url === null) return null;\n return {\n pathname: url.pathname,\n search: url.search,\n hash: url.hash,\n };\n }, [url]);\n}\n","import { useCallback, useContext } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\n\ntype SetSearchParams = (\n params:\n | URLSearchParams\n | Record<string, string>\n | ((prev: URLSearchParams) => URLSearchParams | Record<string, string>),\n) => void;\n\n/**\n * Returns and allows manipulation of URL search parameters.\n */\nexport function useSearchParams(): [URLSearchParams, SetSearchParams] {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useSearchParams must be used within a Router\");\n }\n\n if (context.url === null) {\n throw new Error(\"useSearchParams: URL is not available during SSR.\");\n }\n\n const currentUrl = context.url;\n const searchParams = currentUrl.searchParams;\n\n const setSearchParams = useCallback<SetSearchParams>(\n (params) => {\n const url = new URL(currentUrl);\n\n let newParams: URLSearchParams;\n if (typeof params === \"function\") {\n const result = params(new URLSearchParams(url.search));\n newParams =\n result instanceof URLSearchParams\n ? result\n : new URLSearchParams(result);\n } else if (params instanceof URLSearchParams) {\n newParams = params;\n } else {\n newParams = new URLSearchParams(params);\n }\n\n url.search = newParams.toString();\n context.navigate(url.pathname + url.search + url.hash, { replace: true });\n },\n [currentUrl, context.navigate],\n );\n\n return [searchParams, setSearchParams];\n}\n","import { useContext, useEffect, useId } from \"react\";\nimport { BlockerContext } from \"../context/BlockerContext.js\";\n\nexport type UseBlockerOptions = {\n /**\n * Function that returns true if navigation should be blocked.\n * Can call `confirm()` inside to show a confirmation dialog.\n */\n shouldBlock: () => boolean;\n};\n\n/**\n * Hook to block navigation away from the current route.\n *\n * This is useful for scenarios like unsaved form data, ongoing file uploads,\n * or any state that would be lost on navigation.\n *\n * @example\n * ```tsx\n * function EditForm() {\n * const [isDirty, setIsDirty] = useState(false);\n *\n * useBlocker({\n * shouldBlock: () => {\n * if (isDirty) {\n * return !confirm(\"You have unsaved changes. Leave anyway?\");\n * }\n * return false;\n * },\n * });\n *\n * return <form>...</form>;\n * }\n * ```\n *\n * Note: This hook only handles SPA navigations (links, programmatic navigation).\n * For hard navigations (tab close, refresh), handle `beforeunload` separately.\n */\nexport function useBlocker(options: UseBlockerOptions): void {\n const context = useContext(BlockerContext);\n\n if (!context) {\n throw new Error(\"useBlocker must be used within a Router\");\n }\n\n const { shouldBlock } = options;\n const blockerId = useId();\n const { registry } = context;\n\n // Register blocker on mount, unregister on unmount\n // Re-registers when shouldBlock function changes\n useEffect(() => {\n return registry.register(blockerId, shouldBlock);\n }, [blockerId, shouldBlock, registry]);\n}\n","import { useContext } from \"react\";\nimport {\n RouteContext,\n findRouteContextById,\n type RouteContextValue,\n} from \"../context/RouteContext.js\";\n\n/**\n * Internal hook that returns the RouteContextValue for the given route.\n * If the route has an ID, it searches the ancestor chain for a matching route.\n * If no ID is provided, it returns the current (nearest) route context.\n *\n * @param hookName - Name of the calling hook (for error messages)\n * @param routeId - Optional route ID to search for in the ancestor chain\n * @returns The matching RouteContextValue\n * @throws If called outside a route component or if the route ID is not found\n * @internal\n */\nexport function useRouteContext(\n hookName: string,\n routeId: string | undefined,\n): RouteContextValue {\n const context = useContext(RouteContext);\n if (!context) {\n throw new Error(`${hookName} must be used within a route component`);\n }\n\n // If no expected ID, use current context (backwards compatible)\n if (routeId === undefined) {\n return context;\n }\n\n // Look for matching route in ancestor chain\n const matchedContext = findRouteContextById(context, routeId);\n if (!matchedContext) {\n throw new Error(\n `${hookName}: Route ID \"${routeId}\" not found in current route hierarchy. ` +\n `Current route is \"${context.id ?? \"(no id)\"}\"`,\n );\n }\n\n return matchedContext;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteParams,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed route parameters for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * const userRoute = route({\n * id: \"user\",\n * path: \"/users/:userId\",\n * component: UserPage,\n * });\n *\n * function UserPage() {\n * const params = useRouteParams(userRoute);\n * // params is typed as { userId: string }\n * return <div>User ID: {params.userId}</div>;\n * }\n * ```\n */\nexport function useRouteParams<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteParams<T> {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteParams\", routeId);\n return context.params as ExtractRouteParams<T>;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteState,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed navigation state for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * type ScrollState = { scrollPos: number };\n * const scrollRoute = routeState<ScrollState>()({\n * id: \"scroll\",\n * path: \"/scroll\",\n * component: ScrollPage,\n * });\n *\n * function ScrollPage() {\n * const state = useRouteState(scrollRoute);\n * // state is typed as ScrollState | undefined\n * return <div>Scroll position: {state?.scrollPos ?? 0}</div>;\n * }\n * ```\n */\nexport function useRouteState<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteState<T> | undefined {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteState\", routeId);\n return context.state as ExtractRouteState<T> | undefined;\n}\n","import type {\n TypefulOpaqueRouteDefinition,\n ExtractRouteData,\n} from \"../route.js\";\nimport { useRouteContext } from \"./useRouteContext.js\";\n\n/**\n * Returns typed loader data for the given route definition.\n * Throws an error if called outside a matching route or if the route ID is not found\n * in the current route hierarchy.\n *\n * @example\n * ```typescript\n * const userRoute = route({\n * id: \"user\",\n * path: \"/users/:userId\",\n * loader: async ({ params }) => {\n * const res = await fetch(`/api/users/${params.userId}`);\n * return res.json() as Promise<{ name: string; age: number }>;\n * },\n * component: UserPage,\n * });\n *\n * function UserPage() {\n * const data = useRouteData(userRoute);\n * // data is typed as Promise<{ name: string; age: number }>\n * return <div>User: {data.name}</div>;\n * }\n * ```\n */\nexport function useRouteData<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n>(route: T): ExtractRouteData<T> {\n const routeId = (route as { id?: string }).id;\n const context = useRouteContext(\"useRouteData\", routeId);\n return context.data as ExtractRouteData<T>;\n}\n","import { useContext } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\n\n/**\n * Returns whether a navigation transition is currently pending.\n */\nexport function useIsPending(): boolean {\n const context = useContext(RouterContext);\n\n if (!context) {\n throw new Error(\"useIsPending must be used within a Router\");\n }\n\n return context.isPending;\n}\n"],"mappings":"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"}
|
|
@@ -37,6 +37,8 @@ interface RouteComponentProps<TParams extends Record<string, string>, TState = u
|
|
|
37
37
|
resetState: () => void;
|
|
38
38
|
/** Ephemeral navigation info (only available during navigation, not persisted) */
|
|
39
39
|
info: unknown;
|
|
40
|
+
/** Whether a navigation transition is pending */
|
|
41
|
+
isPending: boolean;
|
|
40
42
|
}
|
|
41
43
|
/**
|
|
42
44
|
* Props for route components with loader.
|
|
@@ -232,4 +234,4 @@ declare function routeState<TState>(): {
|
|
|
232
234
|
};
|
|
233
235
|
//#endregion
|
|
234
236
|
export { LoaderArgs as a, RouteComponentProps as c, RouteDefinition as d, TypefulOpaqueRouteDefinition as f, ExtractRouteState as i, RouteComponentPropsOf as l, routeState as m, ExtractRouteId as n, OpaqueRouteDefinition as o, route as p, ExtractRouteParams as r, PathParams as s, ExtractRouteData as t, RouteComponentPropsWithData as u };
|
|
235
|
-
//# sourceMappingURL=route-
|
|
237
|
+
//# sourceMappingURL=route-Bc8BUlhv.d.mts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"route-Bc8BUlhv.d.mts","names":[],"sources":["../src/route.ts"],"mappings":";;;cAEM,qBAAA;;AAFgD;;;KAQjD,aAAA,qBACH,CAAA,oDACI,KAAA,GAAQ,aAAA,KAAkB,IAAA,MAC1B,CAAA,sCACE,KAAA;;AAV8B;;;KAiB1B,UAAA,sBAAgC,aAAA,CAAc,CAAA,qBACtD,MAAA,0BACQ,aAAA,CAAc,CAAA;;;;KAKd,UAAA,gBAA0B,MAAA;EAlBnB,gCAoBjB,MAAA,EAAQ,MAAA,EAnBqB;EAqB7B,OAAA,EAAS,OAAA,EApBL;EAsBJ,MAAA,EAAQ,WAAA;AAAA;;;;;UAOO,mBAAA,iBACC,MAAA;EArBN;EAyBV,MAAA,EAAQ,OAAA;EAzBY;EA2BpB,KAAA,EAAO,MAAA;EA3BmC;EA6B1C,QAAA,GACE,KAAA,EAAO,MAAA,KAAW,IAAA,EAAM,MAAA,iBAAuB,MAAA,MAC5C,OAAA;EA7BmB;EA+BxB,YAAA,GACE,KAAA,EAAO,MAAA,KAAW,IAAA,EAAM,MAAA,iBAAuB,MAAA;EAhC1B;EAmCvB,UAAA;EArCqB;EAuCrB,IAAA;EAvCwD;EAyCxD,SAAA;AAAA;;;;;UAOe,2BAAA,iBACC,MAAA,qDAGR,mBAAA,CAAoB,OAAA,EAAS,MAAA;EA7CjB;EA+CpB,IAAA,EAAM,KAAA;AAAA;;;;UAMS,qBAAA;EAAA,CACd,qBAAA;EACD,IAAA;EACA,QAAA,GAAW,eAAA;EACX,KAAA;EACA,eAAA;AAAA;;;;;UAOe,4BAAA,mCAEA,MAAA;EAAA,CAId,qBAAA;IACC,EAAA,EAAI,EAAA;IACJ,MAAA,EAAQ,MAAA;IACR,KAAA,EAAO,KAAA;IACP,IAAA,EAAM,IAAA;EAAA;EAER,IAAA;EACA,QAAA,GAAW,eAAA;EACX,KAAA;EACA,eAAA;AAAA;;KAIU,cAAA,MACV,CAAA,SAAU,4BAAA,uDAMN,EAAA;;KAIM,kBAAA,MACV,CAAA,SAAU,4BAAA,uDAMN,MAAA;;KAIM,iBAAA,MACV,CAAA,SAAU,4BAAA,uDAMN,KAAA;;KAIM,gBAAA,MACV,CAAA,SAAU,4BAAA,uDAMN,IAAA;;KAIM,qBAAA,WACA,4BAAA,SAER,MAAA,uCAKF,CAAA,SAAU,4BAAA,qDAMN,IAAA,qBACE,mBAAA,CAAoB,MAAA,EAAQ,KAAA,IAC5B,2BAAA,CAA4B,MAAA,EAAQ,IAAA,EAAM,KAAA;;;;KAMtC,eAAA,GACR,qBAAA,GACA,4BAAA,SAEE,MAAA;EAKA,IAAA;EACA,SAAA,GAAY,aAAA,WAAwB,SAAA;EACpC,QAAA,GAAW,eAAA;EACX,KAAA;EACA,eAAA;AAAA;;;;;;;KASD,eAAA;EAMH,EAAA,GAAK,GAAA;EACL,IAAA,EAAM,KAAA;EACN,MAAA,GAAS,IAAA,EAAM,UAAA,CAAW,UAAA,CAAW,KAAA,OAAY,KAAA;EACjD,SAAA,EACI,aAAA,CACE,2BAAA,CAA4B,UAAA,CAAW,KAAA,GAAQ,KAAA,EAAO,MAAA,KAExD,SAAA;EACJ,QAAA,GAAW,eAAA;EACX,KAAA;EACA,eAAA;AAAA;;;;;;;KASG,kBAAA;EAKH,EAAA,GAAK,GAAA;EACL,IAAA,EAAM,KAAA;EACN,SAAA,GACI,aAAA,CAAc,mBAAA,CAAoB,UAAA,CAAW,KAAA,GAAQ,MAAA,KACrD,SAAA;EACJ,QAAA,GAAW,eAAA;EACX,KAAA;EACA,eAAA;AAAA;;AA5JF;;;KAmKK,uBAAA;EAKH,EAAA,GAAK,GAAA;EACL,IAAA;EACA,MAAA,GAAS,IAAA,EAAM,UAAA,CAAW,MAAA,qBAA2B,KAAA;EACrD,SAAA,EACI,aAAA,CACE,2BAAA,CAA4B,MAAA,iBAAuB,KAAA,EAAO,MAAA,KAE5D,SAAA;EACJ,QAAA,GAAW,eAAA;EACX,eAAA;AAAA;;AArKF;;;KA4KK,0BAAA;EAIH,EAAA,GAAK,GAAA;EACL,IAAA;EACA,SAAA,GACI,aAAA,CAAc,mBAAA,CAAoB,MAAA,iBAAuB,MAAA,KACzD,SAAA;EACJ,QAAA,GAAW,eAAA;EACX,eAAA;AAAA;;;;;;;;;;;;;;;;;;;;;;;;AAnKF;;;;;iBAmMgB,KAAA,2BAAA,CACd,UAAA,EAAY,uBAAA,CAAwB,KAAA,aAAkB,GAAA;EAAS,EAAA,EAAI,GAAA;AAAA,IAClE,4BAAA,CAA6B,GAAA,EAAK,MAAA,4BAAkC,KAAA;AAAA,iBAEvD,KAAA,oBAAA,CACd,UAAA,EAAY,0BAAA,YAAsC,GAAA;EAAS,EAAA,EAAI,GAAA;AAAA,IAC9D,4BAAA,CACD,GAAA,EACA,MAAA;AAAA,iBAKc,KAAA,OAAA,CACd,UAAA,EAAY,uBAAA,CAAwB,KAAA,eACnC,qBAAA;AAAA,iBAEa,KAAA,CACd,UAAA,EAAY,0BAAA,cACX,qBAAA;AAAA,iBAEa,KAAA,uDAAA,CACd,UAAA,EAAY,eAAA,CAAgB,KAAA,EAAO,KAAA,aAAkB,GAAA;EAAS,EAAA,EAAI,GAAA;AAAA,IACjE,4BAAA,CAA6B,GAAA,EAAK,UAAA,CAAW,KAAA,cAAmB,KAAA;AAAA,iBAEnD,KAAA,gDAAA,CACd,UAAA,EAAY,kBAAA,CAAmB,KAAA,aAAkB,GAAA;EAAS,EAAA,EAAI,GAAA;AAAA,IAC7D,4BAAA,CAA6B,GAAA,EAAK,UAAA,CAAW,KAAA;AAAA,iBAEhC,KAAA,mCAAA,CACd,UAAA,EAAY,eAAA,CAAgB,KAAA,EAAO,KAAA,eAClC,qBAAA;AAAA,iBAEa,KAAA,4BAAA,CACd,UAAA,EAAY,kBAAA,CAAmB,KAAA,eAC9B,qBAAA;;;;;;;;AAhNH;;;;;;;;;;;;;;AAWA;;;iBAgPgB,UAAA,QAAA,CAAA;EAAA,4BAGZ,UAAA,EAAY,uBAAA,CAAwB,KAAA,EAAO,MAAA,EAAQ,GAAA;IAAS,EAAA,EAAI,GAAA;EAAA,IAC/D,4BAAA,CAA6B,GAAA,EAAK,MAAA,iBAAuB,MAAA,EAAQ,KAAA;EAAA,qBAGlE,UAAA,EAAY,0BAAA,CAA2B,MAAA,EAAQ,GAAA;IAAS,EAAA,EAAI,GAAA;EAAA,IAC3D,4BAAA,CACD,GAAA,EACA,MAAA,iBACA,MAAA;EAAA,QAKA,UAAA,EAAY,uBAAA,CAAwB,KAAA,EAAO,MAAA,IAC1C,qBAAA;EAAA,CAEF,UAAA,EAAY,0BAAA,CAA2B,MAAA,IAAU,qBAAA;EAAA,kDAGhD,UAAA,EAAY,eAAA,CAAgB,KAAA,EAAO,KAAA,EAAO,MAAA,EAAQ,GAAA;IAAS,EAAA,EAAI,GAAA;EAAA,IAC9D,4BAAA,CAA6B,GAAA,EAAK,UAAA,CAAW,KAAA,GAAQ,MAAA,EAAQ,KAAA;EAAA,2CAG9D,UAAA,EAAY,kBAAA,CAAmB,KAAA,EAAO,MAAA,EAAQ,GAAA;IAAS,EAAA,EAAI,GAAA;EAAA,IAC1D,4BAAA,CAA6B,GAAA,EAAK,UAAA,CAAW,KAAA,GAAQ,MAAA;EAAA,8BAGtD,UAAA,EAAY,eAAA,CAAgB,KAAA,EAAO,KAAA,EAAO,MAAA,IACzC,qBAAA;EAAA,uBAGD,UAAA,EAAY,kBAAA,CAAmB,KAAA,EAAO,MAAA,IACrC,qBAAA;AAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"route-p_gr5yPI.mjs","names":[],"sources":["../src/route.ts"],"sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\nconst routeDefinitionSymbol = Symbol();\n\n/**\n * Extracts parameter names from a path pattern.\n * E.g., \"/users/:id/posts/:postId\" -> \"id\" | \"postId\"\n */\ntype ExtractParams<T extends string> =\n T extends `${string}:${infer Param}/${infer Rest}`\n ? Param | ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * Creates a params object type from a path pattern.\n * E.g., \"/users/:id\" -> { id: string }\n */\nexport type PathParams<T extends string> = [ExtractParams<T>] extends [never]\n ? Record<string, never>\n : { [K in ExtractParams<T>]: string };\n\n/**\n * Arguments passed to loader functions.\n */\nexport type LoaderArgs<Params extends Record<string, string>> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with URL and headers */\n request: Request;\n /** AbortSignal for cancellation on navigation */\n signal: AbortSignal;\n};\n\n/**\n * Props for route components without loader.\n * Includes navigation state management props.\n */\nexport interface RouteComponentProps<\n TParams extends Record<string, string>,\n TState = undefined,\n> {\n /** Extracted path parameters */\n params: TParams;\n /** Current navigation state for this route (undefined on first visit) */\n state: TState | undefined;\n /** Update navigation state for this route asynchronously via replace navigation */\n setState: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => Promise<void>;\n /** Update navigation state for this route synchronously via updateCurrentEntry */\n setStateSync: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => void;\n /** Reset navigation state to undefined */\n resetState: () => void;\n /** Ephemeral navigation info (only available during navigation, not persisted) */\n info: unknown;\n}\n\n/**\n * Props for route components with loader.\n * Includes data from loader and navigation state management props.\n */\nexport interface RouteComponentPropsWithData<\n TParams extends Record<string, string>,\n TData,\n TState = undefined,\n> extends RouteComponentProps<TParams, TState> {\n /** Data returned from the loader */\n data: TData;\n}\n\n/**\n * Route definition created by the `route` helper function.\n */\nexport interface OpaqueRouteDefinition {\n [routeDefinitionSymbol]: unknown;\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Type-carrying route definition created by the `route` helper function when an `id` is provided.\n * This type carries type information for params, state, and data, enabling type-safe hooks in the future.\n */\nexport interface TypefulOpaqueRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [routeDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/** Extract the Id type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteId<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : never;\n\n/** Extract the Params type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteParams<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : never;\n\n/** Extract the State type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteState<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : never;\n\n/** Extract the Data type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteData<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : never;\n\n/** Extract the component props type from a TypefulOpaqueRouteDefinition */\nexport type RouteComponentPropsOf<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : never;\n\n/**\n * Any route definition defined by user.\n */\nexport type RouteDefinition =\n | OpaqueRouteDefinition\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | {\n path?: string;\n component?: ComponentType<object> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n };\n\n/**\n * Route definition with loader - infers TData from loader return type.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithLoader<\n TPath extends string,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition without loader.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithoutLoader<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition with loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithLoader<\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<Record<string, never>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition without loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithoutLoader<\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?:\n | ComponentType<RouteComponentProps<Record<string, never>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Helper function for creating type-safe route definitions.\n *\n * When a loader is provided, TypeScript infers the return type and ensures\n * the component accepts a `data` prop of that type. Components always receive\n * a `params` prop with types inferred from the path pattern.\n *\n * For routes with navigation state, use `routeState<TState>()({ ... })` instead.\n *\n * @example\n * ```typescript\n * // Route with async loader\n * route({\n * path: \"users/:userId\",\n * loader: async ({ params, signal }) => {\n * const res = await fetch(`/api/users/${params.userId}`, { signal });\n * return res.json() as Promise<User>;\n * },\n * component: UserDetail, // Must accept { data: Promise<User>, params: { userId: string }, state, setState, resetState }\n * });\n *\n * // Route without loader\n * route({\n * path: \"about\",\n * component: AboutPage, // Must accept { params: {}, state, setState, resetState }\n * });\n * ```\n */\n// Pathless overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string>(\n definition: PathlessRouteWithoutLoader<undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n undefined,\n undefined\n>;\n// Pathless overload with loader (no id)\nexport function route<TData>(\n definition: PathlessRouteWithLoader<TData, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload without loader (no id)\nexport function route(\n definition: PathlessRouteWithoutLoader<undefined>,\n): OpaqueRouteDefinition;\n// Overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with loader (no id)\nexport function route<const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload without loader (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Implementation\nexport function route<TId extends string, const TPath extends string, TData>(\n definition:\n | (PathlessRouteWithLoader<TData, undefined, TId> & { id: TId })\n | (PathlessRouteWithoutLoader<undefined, TId> & { id: TId })\n | PathlessRouteWithLoader<TData, undefined>\n | PathlessRouteWithoutLoader<undefined>\n | (RouteWithLoader<TPath, TData, undefined, TId> & { id: TId })\n | (RouteWithoutLoader<TPath, undefined, TId> & { id: TId })\n | RouteWithLoader<TPath, TData, undefined>\n | RouteWithoutLoader<TPath, undefined>,\n):\n | TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>\n | TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>\n | OpaqueRouteDefinition {\n return definition as unknown as OpaqueRouteDefinition;\n}\n\n/**\n * Helper function for creating type-safe route definitions with navigation state.\n *\n * Use this curried function when your route component needs to manage navigation state.\n * The state is tied to the navigation history entry and persists across back/forward navigation.\n *\n * @example\n * ```typescript\n * // Route with navigation state\n * type MyState = { scrollPosition: number };\n * routeState<MyState>()({\n * path: \"users/:userId\",\n * component: UserPage, // Receives { params, state, setState, resetState }\n * });\n *\n * // Route with both loader and navigation state\n * type FilterState = { filter: string };\n * routeState<FilterState>()({\n * path: \"products\",\n * loader: async () => fetchProducts(),\n * component: ProductList, // Receives { data, params, state, setState, resetState }\n * });\n * ```\n */\nexport function routeState<TState>(): {\n // Pathless overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string>(\n definition: PathlessRouteWithoutLoader<TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n TState,\n undefined\n >;\n // Pathless overload with loader (no id)\n <TData>(\n definition: PathlessRouteWithLoader<TData, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload without loader (no id)\n (definition: PathlessRouteWithoutLoader<TState>): OpaqueRouteDefinition;\n // Overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with loader (no id)\n <TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload without loader (no id)\n <TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState>,\n ): OpaqueRouteDefinition;\n} {\n return ((definition: object) => {\n return definition as unknown as OpaqueRouteDefinition;\n }) as never;\n}\n"],"mappings":";AAoVA,SAAgB,MACd,YAYwB;AACxB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,aAoCd;AACA,UAAS,eAAuB;AAC9B,SAAO"}
|
|
1
|
+
{"version":3,"file":"route-p_gr5yPI.mjs","names":[],"sources":["../src/route.ts"],"sourcesContent":["import type { ComponentType, ReactNode } from \"react\";\n\nconst routeDefinitionSymbol = Symbol();\n\n/**\n * Extracts parameter names from a path pattern.\n * E.g., \"/users/:id/posts/:postId\" -> \"id\" | \"postId\"\n */\ntype ExtractParams<T extends string> =\n T extends `${string}:${infer Param}/${infer Rest}`\n ? Param | ExtractParams<`/${Rest}`>\n : T extends `${string}:${infer Param}`\n ? Param\n : never;\n\n/**\n * Creates a params object type from a path pattern.\n * E.g., \"/users/:id\" -> { id: string }\n */\nexport type PathParams<T extends string> = [ExtractParams<T>] extends [never]\n ? Record<string, never>\n : { [K in ExtractParams<T>]: string };\n\n/**\n * Arguments passed to loader functions.\n */\nexport type LoaderArgs<Params extends Record<string, string>> = {\n /** Extracted path parameters */\n params: Params;\n /** Request object with URL and headers */\n request: Request;\n /** AbortSignal for cancellation on navigation */\n signal: AbortSignal;\n};\n\n/**\n * Props for route components without loader.\n * Includes navigation state management props.\n */\nexport interface RouteComponentProps<\n TParams extends Record<string, string>,\n TState = undefined,\n> {\n /** Extracted path parameters */\n params: TParams;\n /** Current navigation state for this route (undefined on first visit) */\n state: TState | undefined;\n /** Update navigation state for this route asynchronously via replace navigation */\n setState: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => Promise<void>;\n /** Update navigation state for this route synchronously via updateCurrentEntry */\n setStateSync: (\n state: TState | ((prev: TState | undefined) => TState),\n ) => void;\n /** Reset navigation state to undefined */\n resetState: () => void;\n /** Ephemeral navigation info (only available during navigation, not persisted) */\n info: unknown;\n /** Whether a navigation transition is pending */\n isPending: boolean;\n}\n\n/**\n * Props for route components with loader.\n * Includes data from loader and navigation state management props.\n */\nexport interface RouteComponentPropsWithData<\n TParams extends Record<string, string>,\n TData,\n TState = undefined,\n> extends RouteComponentProps<TParams, TState> {\n /** Data returned from the loader */\n data: TData;\n}\n\n/**\n * Route definition created by the `route` helper function.\n */\nexport interface OpaqueRouteDefinition {\n [routeDefinitionSymbol]: unknown;\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/**\n * Type-carrying route definition created by the `route` helper function when an `id` is provided.\n * This type carries type information for params, state, and data, enabling type-safe hooks in the future.\n */\nexport interface TypefulOpaqueRouteDefinition<\n Id extends string,\n Params extends Record<string, string>,\n State,\n Data,\n> {\n [routeDefinitionSymbol]: {\n id: Id;\n params: Params;\n state: State;\n data: Data;\n };\n path?: string;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n}\n\n/** Extract the Id type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteId<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer Id,\n infer _Params,\n infer _State,\n infer _Data\n >\n ? Id\n : never;\n\n/** Extract the Params type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteParams<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer _State,\n infer _Data\n >\n ? Params\n : never;\n\n/** Extract the State type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteState<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer State,\n infer _Data\n >\n ? State\n : never;\n\n/** Extract the Data type from a TypefulOpaqueRouteDefinition */\nexport type ExtractRouteData<T> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer _Params,\n infer _State,\n infer Data\n >\n ? Data\n : never;\n\n/** Extract the component props type from a TypefulOpaqueRouteDefinition */\nexport type RouteComponentPropsOf<\n T extends TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >,\n> =\n T extends TypefulOpaqueRouteDefinition<\n infer _Id,\n infer Params,\n infer State,\n infer Data\n >\n ? Data extends undefined\n ? RouteComponentProps<Params, State>\n : RouteComponentPropsWithData<Params, Data, State>\n : never;\n\n/**\n * Any route definition defined by user.\n */\nexport type RouteDefinition =\n | OpaqueRouteDefinition\n | TypefulOpaqueRouteDefinition<\n string,\n Record<string, string>,\n unknown,\n unknown\n >\n | {\n path?: string;\n component?: ComponentType<object> | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n };\n\n/**\n * Route definition with loader - infers TData from loader return type.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithLoader<\n TPath extends string,\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n loader: (args: LoaderArgs<PathParams<TPath>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<PathParams<TPath>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Route definition without loader.\n * TPath is used to infer params type from the path pattern.\n * TState is the type of navigation state for this route.\n * TId is the optional route identifier for type-safe route references.\n */\ntype RouteWithoutLoader<\n TPath extends string,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path: TPath;\n component?:\n | ComponentType<RouteComponentProps<PathParams<TPath>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n exact?: boolean;\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition with loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithLoader<\n TData,\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n loader: (args: LoaderArgs<Record<string, never>>) => TData;\n component:\n | ComponentType<\n RouteComponentPropsWithData<Record<string, never>, TData, TState>\n >\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Pathless route definition without loader.\n * Pathless routes always match and don't consume any pathname.\n */\ntype PathlessRouteWithoutLoader<\n TState,\n TId extends string | undefined = undefined,\n> = {\n id?: TId;\n path?: undefined;\n component?:\n | ComponentType<RouteComponentProps<Record<string, never>, TState>>\n | ReactNode;\n children?: RouteDefinition[];\n requireChildren?: boolean;\n};\n\n/**\n * Helper function for creating type-safe route definitions.\n *\n * When a loader is provided, TypeScript infers the return type and ensures\n * the component accepts a `data` prop of that type. Components always receive\n * a `params` prop with types inferred from the path pattern.\n *\n * For routes with navigation state, use `routeState<TState>()({ ... })` instead.\n *\n * @example\n * ```typescript\n * // Route with async loader\n * route({\n * path: \"users/:userId\",\n * loader: async ({ params, signal }) => {\n * const res = await fetch(`/api/users/${params.userId}`, { signal });\n * return res.json() as Promise<User>;\n * },\n * component: UserDetail, // Must accept { data: Promise<User>, params: { userId: string }, state, setState, resetState }\n * });\n *\n * // Route without loader\n * route({\n * path: \"about\",\n * component: AboutPage, // Must accept { params: {}, state, setState, resetState }\n * });\n * ```\n */\n// Pathless overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>;\n// Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string>(\n definition: PathlessRouteWithoutLoader<undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n undefined,\n undefined\n>;\n// Pathless overload with loader (no id)\nexport function route<TData>(\n definition: PathlessRouteWithLoader<TData, undefined>,\n): OpaqueRouteDefinition;\n// Pathless overload without loader (no id)\nexport function route(\n definition: PathlessRouteWithoutLoader<undefined>,\n): OpaqueRouteDefinition;\n// Overload with id + loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>;\n// Overload with id + no loader → TypefulOpaqueRouteDefinition\nexport function route<TId extends string, const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined, TId> & { id: TId },\n): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, undefined>;\n// Overload with loader (no id)\nexport function route<const TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, undefined>,\n): OpaqueRouteDefinition;\n// Overload without loader (no id)\nexport function route<const TPath extends string>(\n definition: RouteWithoutLoader<TPath, undefined>,\n): OpaqueRouteDefinition;\n// Implementation\nexport function route<TId extends string, const TPath extends string, TData>(\n definition:\n | (PathlessRouteWithLoader<TData, undefined, TId> & { id: TId })\n | (PathlessRouteWithoutLoader<undefined, TId> & { id: TId })\n | PathlessRouteWithLoader<TData, undefined>\n | PathlessRouteWithoutLoader<undefined>\n | (RouteWithLoader<TPath, TData, undefined, TId> & { id: TId })\n | (RouteWithoutLoader<TPath, undefined, TId> & { id: TId })\n | RouteWithLoader<TPath, TData, undefined>\n | RouteWithoutLoader<TPath, undefined>,\n):\n | TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, undefined, TData>\n | TypefulOpaqueRouteDefinition<TId, Record<string, never>, undefined, TData>\n | OpaqueRouteDefinition {\n return definition as unknown as OpaqueRouteDefinition;\n}\n\n/**\n * Helper function for creating type-safe route definitions with navigation state.\n *\n * Use this curried function when your route component needs to manage navigation state.\n * The state is tied to the navigation history entry and persists across back/forward navigation.\n *\n * @example\n * ```typescript\n * // Route with navigation state\n * type MyState = { scrollPosition: number };\n * routeState<MyState>()({\n * path: \"users/:userId\",\n * component: UserPage, // Receives { params, state, setState, resetState }\n * });\n *\n * // Route with both loader and navigation state\n * type FilterState = { filter: string };\n * routeState<FilterState>()({\n * path: \"products\",\n * loader: async () => fetchProducts(),\n * component: ProductList, // Receives { data, params, state, setState, resetState }\n * });\n * ```\n */\nexport function routeState<TState>(): {\n // Pathless overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TData>(\n definition: PathlessRouteWithLoader<TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, Record<string, never>, TState, TData>;\n // Pathless overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string>(\n definition: PathlessRouteWithoutLoader<TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<\n TId,\n Record<string, never>,\n TState,\n undefined\n >;\n // Pathless overload with loader (no id)\n <TData>(\n definition: PathlessRouteWithLoader<TData, TState>,\n ): OpaqueRouteDefinition;\n // Pathless overload without loader (no id)\n (definition: PathlessRouteWithoutLoader<TState>): OpaqueRouteDefinition;\n // Overload with id + loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, TData>;\n // Overload with id + no loader → TypefulOpaqueRouteDefinition\n <TId extends string, TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState, TId> & { id: TId },\n ): TypefulOpaqueRouteDefinition<TId, PathParams<TPath>, TState, undefined>;\n // Overload with loader (no id)\n <TPath extends string, TData>(\n definition: RouteWithLoader<TPath, TData, TState>,\n ): OpaqueRouteDefinition;\n // Overload without loader (no id)\n <TPath extends string>(\n definition: RouteWithoutLoader<TPath, TState>,\n ): OpaqueRouteDefinition;\n} {\n return ((definition: object) => {\n return definition as unknown as OpaqueRouteDefinition;\n }) as never;\n}\n"],"mappings":";AAsVA,SAAgB,MACd,YAYwB;AACxB,QAAO;;;;;;;;;;;;;;;;;;;;;;;;;;AA2BT,SAAgB,aAoCd;AACA,UAAS,eAAuB;AAC9B,SAAO"}
|
package/dist/server.d.mts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, m as routeState, p as route, s as PathParams, u as RouteComponentPropsWithData } from "./route-
|
|
1
|
+
import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, m as routeState, p as route, s as PathParams, u as RouteComponentPropsWithData } from "./route-Bc8BUlhv.mjs";
|
|
2
2
|
export { type LoaderArgs, type PathParams, type RouteComponentProps, type RouteComponentPropsWithData, type RouteDefinition, route, routeState };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@funstack/router",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.5",
|
|
4
4
|
"description": "A modern React router based on the Navigation API",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -34,9 +34,9 @@
|
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@testing-library/jest-dom": "^6.9.1",
|
|
36
36
|
"@testing-library/react": "^16.3.2",
|
|
37
|
-
"@types/react": "^19.2.
|
|
38
|
-
"jsdom": "^
|
|
39
|
-
"react": "^19.
|
|
37
|
+
"@types/react": "^19.2.11",
|
|
38
|
+
"jsdom": "^28.0.0",
|
|
39
|
+
"react": "^19.2.4",
|
|
40
40
|
"tsdown": "^0.20.1",
|
|
41
41
|
"typescript": "^5.7.0",
|
|
42
42
|
"urlpattern-polyfill": "^10.1.0",
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"route-gXPaU-cv.d.mts","names":[],"sources":["../src/route.ts"],"mappings":"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"}
|