@funstack/router 0.0.9 → 0.0.10

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 CHANGED
@@ -89,7 +89,7 @@ type OnNavigateCallback = (event: NavigateEvent, info: OnNavigateInfo) => void;
89
89
  */
90
90
  type FallbackMode = "none" | "static";
91
91
  //#endregion
92
- //#region src/Router.d.ts
92
+ //#region src/Router/index.d.ts
93
93
  /**
94
94
  * SSR configuration for the router.
95
95
  */
@@ -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/hooks/useIsPending.ts"],"mappings":";;;;cAGM,6BAAA;;AAFoE;;;;;;;KAgB9D,uBAAA;EAAA,CACT,6BAAA,UAyB0B;EAvB3B,IAAA,WA4Be;EA1Bf,QAAA,GAAW,uBAAA;EAgCc;;;;;;EAzBzB,KAAA;EAPA;;;;;EAaA,eAAA,YAM2B;EAA3B,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAE3B;EAAA,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,wCAAA;EAE3B,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,SAAmB,OAAA;IACnB,cAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;AAmBN;KAAY,YAAA;sCAEV,KAAA,EAAO,uBAAA,EAAP;EAEA,MAAA,EAAQ,MAAA,kBAAR;EAEA,QAAA;AAAA;;;;KAcU,cAAA;EAMQ,4DAJlB,OAAA,WAAkB,YAAA,WAUR;EARV,YAAA;EAEA,QAAA,EAAU,QAAA;AAAA;;;;KAMA,eAAA;EAYA,uDAVV,OAAA;EAEA,KAAA,YASA;EAPA,IAAA;AAAA;;;AAmBF;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;;AAqBF;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;;ACvGR;;KDgHY,YAAA;;;;AAhJ8D;;KCgC9D,SAAA;ED9BkC;;AAc9C;;;;ECuBE,IAAA;EDC2B;;;;;;;;;;;ECW3B,UAAA;AAAA;AAAA,KAGU,WAAA;EACV,MAAA,EAAQ,eAAA;ED3BR;;;;;;;ECmCA,UAAA,GAAa,kBAAA;EDrBc;;;;;;EC4B3B,QAAA,GAAW,YAAA;EDtBL;;;;;;;;;;;;;;;AA4BR;;;;;ECeE,GAAA,GAAM,SAAA;AAAA;AAAA,iBAYQ,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA,QAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD5GyD;iBEM1D,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;KCJ1B,eAAA,IACH,MAAA,EACI,eAAA,GACA,MAAA,qBACE,IAAA,EAAM,eAAA,KAAoB,eAAA,GAAkB,MAAA;;;;iBAMpC,eAAA,CAAA,IAAoB,eAAA,EAAiB,eAAA;;;KCVzC,iBAAA;;;;ANF8D;EMOxE,WAAA;AAAA;;;ANSF;;;;;;;;;;;;;;;;;;;;;;;;;iBMqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;ANrCsC;;;;;AAgB1E;;;;;;;;;;;;iBOSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;APhC0C;;;;;AAgB1E;;;;;;;;;;;;;iBQUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ARjC2C;;;;;AAgB1E;;;;;;;;;;;;;;;;iBSagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router/index.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/hooks/useIsPending.ts"],"mappings":";;;;cAGM,6BAAA;;AAFoE;;;;;;;KAgB9D,uBAAA;EAAA,CACT,6BAAA,UAyB0B;EAvB3B,IAAA,WA4Be;EA1Bf,QAAA,GAAW,uBAAA;EAgCc;;;;;;EAzBzB,KAAA;EAPA;;;;;EAaA,eAAA,YAM2B;EAA3B,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAE3B;EAAA,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,wCAAA;EAE3B,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,SAAmB,OAAA;IACnB,cAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;AAmBN;KAAY,YAAA;sCAEV,KAAA,EAAO,uBAAA,EAAP;EAEA,MAAA,EAAQ,MAAA,kBAAR;EAEA,QAAA;AAAA;;;;KAcU,cAAA;EAMQ,4DAJlB,OAAA,WAAkB,YAAA,WAUR;EARV,YAAA;EAEA,QAAA,EAAU,QAAA;AAAA;;;;KAMA,eAAA;EAYA,uDAVV,OAAA;EAEA,KAAA,YASA;EAPA,IAAA;AAAA;;;AAmBF;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;;AAqBF;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;;ACjGR;;KD0GY,YAAA;;;;AAhJ8D;;KCsC9D,SAAA;EDpCkC;;AAc9C;;;;EC6BE,IAAA;EDL2B;;;;;;;;;;;ECiB3B,UAAA;AAAA;AAAA,KAGU,WAAA;EACV,MAAA,EAAQ,eAAA;EDjCR;;;;;;;ECyCA,UAAA,GAAa,kBAAA;ED3Bc;;;;;;ECkC3B,QAAA,GAAW,YAAA;ED5BL;;;;;;;;;;;;;;;AA4BR;;;;;ECqBE,GAAA,GAAM,SAAA;AAAA;AAAA,iBAGQ,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA,QAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;ADzGyD;iBEM1D,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;KCJ1B,eAAA,IACH,MAAA,EACI,eAAA,GACA,MAAA,qBACE,IAAA,EAAM,eAAA,KAAoB,eAAA,GAAkB,MAAA;;;;iBAMpC,eAAA,CAAA,IAAoB,eAAA,EAAiB,eAAA;;;KCVzC,iBAAA;;;;ANF8D;EMOxE,WAAA;AAAA;;;ANSF;;;;;;;;;;;;;;;;;;;;;;;;;iBMqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;ANrCsC;;;;;AAgB1E;;;;;;;;;;;;iBOSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;APhC0C;;;;;AAgB1E;;;;;;;;;;;;;iBQUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ARjC2C;;;;;AAgB1E;;;;;;;;;;;;;;;;iBSagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA"}
package/dist/index.mjs CHANGED
@@ -1,28 +1,12 @@
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, useTransition } from "react";
4
+ import { createContext, useCallback, useContext, useEffect, useId, useMemo, useRef, useState, useSyncExternalStore, useTransition } from "react";
5
5
  import { jsx } from "react/jsx-runtime";
6
6
 
7
7
  //#region src/context/RouterContext.ts
8
8
  const RouterContext = createContext(null);
9
9
 
10
- //#endregion
11
- //#region src/context/RouteContext.ts
12
- const RouteContext = createContext(null);
13
- /**
14
- * Find a route context by ID in the ancestor chain.
15
- * Returns the matching context or null if not found.
16
- */
17
- function findRouteContextById(context, id) {
18
- let current = context;
19
- while (current !== null) {
20
- if (current.id === id) return current;
21
- current = current.parent;
22
- }
23
- return null;
24
- }
25
-
26
10
  //#endregion
27
11
  //#region src/context/BlockerContext.ts
28
12
  /**
@@ -491,121 +475,55 @@ function createAdapter(fallback) {
491
475
  }
492
476
 
493
477
  //#endregion
494
- //#region src/Router.tsx
478
+ //#region src/Router/ServerLocationSnapshot.ts
495
479
  /**
496
- * Special value returned as server snapshot during SSR/hydration.
480
+ * Special class returned as server snapshot during SSR/hydration.
497
481
  */
498
- const serverSnapshotSymbol = Symbol();
499
- const noopSubscribe = () => () => {};
500
- const getServerSnapshot = () => serverSnapshotSymbol;
501
- function Router({ routes: inputRoutes, onNavigate, fallback = "none", ssr }) {
502
- const routes = internalRoutes(inputRoutes);
503
- const adapter = useMemo(() => createAdapter(fallback), [fallback]);
504
- const [blockerRegistry] = useState(() => createBlockerRegistry());
505
- const initialEntry = useSyncExternalStore(noopSubscribe, useCallback(() => adapter.getSnapshot(), [adapter]), getServerSnapshot);
506
- const [isPending, startTransition] = useTransition();
507
- const [locationEntryInternal, setLocationEntry] = useState(initialEntry);
508
- const locationEntry = locationEntryInternal === serverSnapshotSymbol ? null : locationEntryInternal;
509
- if (locationEntryInternal === serverSnapshotSymbol && initialEntry !== serverSnapshotSymbol) setLocationEntry(initialEntry);
510
- useEffect(() => {
511
- return adapter.subscribe((changeType) => {
512
- if (changeType === "navigation") startTransition(() => {
513
- setLocationEntry(adapter.getSnapshot());
514
- });
515
- else setLocationEntry(adapter.getSnapshot());
516
- });
517
- }, [adapter, startTransition]);
518
- useEffect(() => {
519
- return adapter.setupInterception(routes, onNavigate, blockerRegistry.checkAll);
520
- }, [
521
- adapter,
522
- routes,
523
- onNavigate,
524
- blockerRegistry
525
- ]);
526
- const navigate = useCallback((to, options) => {
527
- adapter.navigate(to, options);
528
- }, [adapter]);
529
- const navigateAsync = useCallback((to, options) => {
530
- return adapter.navigateAsync(to, options);
531
- }, [adapter]);
532
- const updateCurrentEntryState = useCallback((state) => {
533
- adapter.updateCurrentEntryState(state);
534
- }, [adapter]);
535
- return useMemo(() => {
536
- const matchedRoutesWithData = (() => {
537
- if (locationEntry === null && !ssr?.runLoaders) {
538
- const matched = matchRoutes(routes, ssr?.path ?? null, { skipLoaders: true });
539
- if (!matched) return null;
540
- return matched.map((m) => ({
541
- ...m,
542
- data: void 0
543
- }));
544
- }
545
- const url = locationEntry ? locationEntry.url : new URL(ssr.path, "http://localhost");
546
- const matched = matchRoutes(routes, url.pathname);
547
- if (!matched) return null;
548
- return executeLoaders(matched, locationEntry?.key ?? "ssr", createLoaderRequest(url), locationEntry ? adapter.getIdleAbortSignal() : new AbortController().signal);
549
- })();
550
- const routerContextValue = {
551
- locationEntry,
552
- url: locationEntry?.url ?? (ssr ? new URL(ssr.path, "http://localhost") : null),
553
- isPending,
554
- navigate,
555
- navigateAsync,
556
- updateCurrentEntryState
557
- };
558
- const blockerContextValue = { registry: blockerRegistry };
559
- return /* @__PURE__ */ jsx(BlockerContext.Provider, {
560
- value: blockerContextValue,
561
- children: /* @__PURE__ */ jsx(RouterContext.Provider, {
562
- value: routerContextValue,
563
- children: matchedRoutesWithData ? /* @__PURE__ */ jsx(RouteRenderer, {
564
- matchedRoutes: matchedRoutesWithData,
565
- index: 0
566
- }) : null
567
- })
568
- });
569
- }, [
570
- navigate,
571
- navigateAsync,
572
- updateCurrentEntryState,
573
- isPending,
574
- locationEntry,
575
- routes,
576
- adapter,
577
- blockerRegistry,
578
- ssr
579
- ]);
482
+ var ServerLocationSnapshot = class {
483
+ actualLocationEntry;
484
+ constructor(adapter) {
485
+ this.actualLocationEntry = adapter.getSnapshot();
486
+ }
487
+ };
488
+ function isServerSnapshot(value) {
489
+ return value instanceof ServerLocationSnapshot;
580
490
  }
491
+ const noopSubscribe = () => () => {};
492
+
493
+ //#endregion
494
+ //#region src/context/RouteContext.ts
495
+ const RouteContext = createContext(null);
581
496
  /**
582
- * Recursively render matched routes with proper context.
497
+ * Find a route context by ID in the ancestor chain.
498
+ * Returns the matching context or null if not found.
583
499
  */
584
- function RouteRenderer({ matchedRoutes, index }) {
585
- const parentRouteContext = useContext(RouteContext);
586
- const match = matchedRoutes[index];
587
- if (!match) return null;
588
- const { route, params, pathname, data } = match;
589
- const routerContext = useContext(RouterContext);
590
- if (!routerContext) throw new Error("RouteRenderer must be used within RouterContext");
591
- const { locationEntry, url, isPending, navigateAsync, updateCurrentEntryState } = routerContext;
592
- const routeState = (locationEntry?.state)?.__routeStates?.[index];
500
+ function findRouteContextById(context, id) {
501
+ let current = context;
502
+ while (current !== null) {
503
+ if (current.id === id) return current;
504
+ current = current.parent;
505
+ }
506
+ return null;
507
+ }
508
+
509
+ //#endregion
510
+ //#region src/Router/useRouteStateCallbacks.ts
511
+ function useRouteStateCallbacks(index, internalState, url, navigateAsync, updateCurrentEntryState) {
593
512
  const setStateSync = useCallback((stateOrUpdater) => {
594
- if (locationEntry === null) return;
595
- const currentStates = locationEntry.state?.__routeStates ?? [];
513
+ const currentStates = internalState?.__routeStates ?? [];
596
514
  const currentRouteState = currentStates[index];
597
515
  const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
598
516
  const newStates = [...currentStates];
599
517
  newStates[index] = newState;
600
518
  updateCurrentEntryState({ __routeStates: newStates });
601
519
  }, [
602
- locationEntry?.state,
520
+ internalState,
603
521
  index,
604
522
  updateCurrentEntryState
605
523
  ]);
606
524
  const setState = useCallback(async (stateOrUpdater) => {
607
- if (locationEntry === null || url === null) return;
608
- const currentStates = locationEntry.state?.__routeStates ?? [];
525
+ if (url === null) return;
526
+ const currentStates = internalState?.__routeStates ?? [];
609
527
  const currentRouteState = currentStates[index];
610
528
  const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
611
529
  const newStates = [...currentStates];
@@ -615,39 +533,61 @@ function RouteRenderer({ matchedRoutes, index }) {
615
533
  state: { __routeStates: newStates }
616
534
  });
617
535
  }, [
618
- locationEntry?.state,
536
+ internalState,
619
537
  index,
620
538
  url,
621
539
  navigateAsync
622
540
  ]);
623
541
  const resetStateSync = useCallback(() => {
624
- if (locationEntry === null) return;
625
- const newStates = [...locationEntry.state?.__routeStates ?? []];
542
+ const newStates = [...internalState?.__routeStates ?? []];
626
543
  newStates[index] = void 0;
627
544
  updateCurrentEntryState({ __routeStates: newStates });
628
545
  }, [
629
- locationEntry?.state,
546
+ internalState,
630
547
  index,
631
548
  updateCurrentEntryState
632
549
  ]);
633
- const resetState = useCallback(async () => {
634
- if (locationEntry === null || url === null) return;
635
- const newStates = [...locationEntry.state?.__routeStates ?? []];
636
- newStates[index] = void 0;
637
- await navigateAsync(url.href, {
638
- replace: true,
639
- state: { __routeStates: newStates }
640
- });
641
- }, [
642
- locationEntry?.state,
643
- index,
644
- url,
645
- navigateAsync
646
- ]);
647
- const outlet = index < matchedRoutes.length - 1 ? /* @__PURE__ */ jsx(RouteRenderer, {
550
+ return {
551
+ setState,
552
+ setStateSync,
553
+ resetState: useCallback(async () => {
554
+ if (url === null) return;
555
+ const newStates = [...internalState?.__routeStates ?? []];
556
+ newStates[index] = void 0;
557
+ await navigateAsync(url.href, {
558
+ replace: true,
559
+ state: { __routeStates: newStates }
560
+ });
561
+ }, [
562
+ internalState,
563
+ index,
564
+ url,
565
+ navigateAsync
566
+ ]),
567
+ resetStateSync
568
+ };
569
+ }
570
+
571
+ //#endregion
572
+ //#region src/Router/RouteRenderer.tsx
573
+ /**
574
+ * Recursively render matched routes with proper context.
575
+ */
576
+ function RouteRenderer({ matchedRoutes, index }) {
577
+ const parentRouteContext = useContext(RouteContext);
578
+ const match = matchedRoutes[index];
579
+ if (!match) return null;
580
+ const { route, params, pathname, data } = match;
581
+ const routerContext = useContext(RouterContext);
582
+ if (!routerContext) throw new Error("RouteRenderer must be used within RouterContext");
583
+ const { locationState, locationInfo, url, isPending, navigateAsync, updateCurrentEntryState } = routerContext;
584
+ const internalState = locationState;
585
+ const routeState = internalState?.__routeStates?.[index];
586
+ const { setState, setStateSync, resetState, resetStateSync } = useRouteStateCallbacks(index, internalState, url, navigateAsync, updateCurrentEntryState);
587
+ const outlet = useMemo(() => index < matchedRoutes.length - 1 ? /* @__PURE__ */ jsx(RouteRenderer, {
648
588
  matchedRoutes,
649
589
  index: index + 1
650
- }) : null;
590
+ }) : null, [matchedRoutes, index]);
651
591
  const routeId = route.id;
652
592
  const routeContextValue = useMemo(() => ({
653
593
  id: routeId,
@@ -678,7 +618,7 @@ function RouteRenderer({ matchedRoutes, index }) {
678
618
  resetState,
679
619
  resetStateSync
680
620
  };
681
- const info = locationEntry?.info;
621
+ const info = locationInfo;
682
622
  if (route.loader) return /* @__PURE__ */ jsx(Component, {
683
623
  data,
684
624
  params,
@@ -699,6 +639,127 @@ function RouteRenderer({ matchedRoutes, index }) {
699
639
  });
700
640
  }
701
641
 
642
+ //#endregion
643
+ //#region src/Router/index.tsx
644
+ function Router({ routes: inputRoutes, onNavigate, fallback = "none", ssr }) {
645
+ const routes = internalRoutes(inputRoutes);
646
+ const adapter = useMemo(() => createAdapter(fallback), [fallback]);
647
+ const [blockerRegistry] = useState(() => createBlockerRegistry());
648
+ const getSnapshot = useCallback(() => adapter.getSnapshot(), [adapter]);
649
+ const serverSnapshotCacheRef = useRef(null);
650
+ const initialEntry = useSyncExternalStore(noopSubscribe, getSnapshot, useCallback(() => {
651
+ return serverSnapshotCacheRef.current ??= new ServerLocationSnapshot(adapter);
652
+ }, [adapter]));
653
+ const [isPending, startTransition] = useTransition();
654
+ const [locationEntryInternal, setLocationEntry] = useState(initialEntry);
655
+ const locationEntry = isServerSnapshot(locationEntryInternal) ? null : locationEntryInternal;
656
+ if (isServerSnapshot(locationEntryInternal) && !isServerSnapshot(initialEntry)) setLocationEntry(initialEntry);
657
+ useEffect(() => {
658
+ return adapter.subscribe((changeType) => {
659
+ if (changeType === "navigation") startTransition(() => {
660
+ setLocationEntry(adapter.getSnapshot());
661
+ });
662
+ else setLocationEntry(adapter.getSnapshot());
663
+ });
664
+ }, [adapter, startTransition]);
665
+ useEffect(() => {
666
+ return adapter.setupInterception(routes, onNavigate, blockerRegistry.checkAll);
667
+ }, [
668
+ adapter,
669
+ routes,
670
+ onNavigate,
671
+ blockerRegistry
672
+ ]);
673
+ const navigate = useCallback((to, options) => {
674
+ adapter.navigate(to, options);
675
+ }, [adapter]);
676
+ const navigateAsync = useCallback((to, options) => {
677
+ return adapter.navigateAsync(to, options);
678
+ }, [adapter]);
679
+ const updateCurrentEntryState = useCallback((state) => {
680
+ adapter.updateCurrentEntryState(state);
681
+ }, [adapter]);
682
+ const url = useMemo(() => {
683
+ if (locationEntry) return locationEntry.url.toString();
684
+ if (ssr) {
685
+ const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
686
+ return new URL(ssr.path, origin).toString();
687
+ }
688
+ return null;
689
+ }, [locationEntry, ssr]);
690
+ /**
691
+ * URL object. Non-null when client-side or during SSR with ssr.path provided.
692
+ * Null during SSR without ssr.path.
693
+ */
694
+ const urlObject = useMemo(() => url ? new URL(url) : null, [url]);
695
+ /**
696
+ * Whether to run loaders.
697
+ * 1. Loaders are always run for rendering with URL available (client-side)
698
+ * 2. During SSR, loaders are only run if ssr.runLoaders is true and URL is available (ssr.path provided).
699
+ */
700
+ const runLoaders = locationEntry !== null || !!ssr?.runLoaders && urlObject !== null;
701
+ /**
702
+ * Key of location. This is used as the cache key for loader data saved in navigation entry.
703
+ */
704
+ const locationKey = locationEntry?.key ?? (isServerSnapshot(locationEntryInternal) ? locationEntryInternal.actualLocationEntry?.key : null) ?? "ssr";
705
+ const matchedRoutesWithData = useMemo(() => {
706
+ if (!runLoaders) {
707
+ const matched = matchRoutes(routes, urlObject?.pathname ?? null, { skipLoaders: true });
708
+ if (!matched) return null;
709
+ return matched.map((m) => ({
710
+ ...m,
711
+ data: void 0
712
+ }));
713
+ }
714
+ if (urlObject === null) throw new Error("Invariant failure: loaders cannot run without URL.");
715
+ const matched = matchRoutes(routes, urlObject.pathname);
716
+ if (!matched) return null;
717
+ return executeLoaders(matched, locationKey, createLoaderRequest(urlObject), adapter.getIdleAbortSignal());
718
+ }, [
719
+ routes,
720
+ adapter,
721
+ urlObject,
722
+ runLoaders,
723
+ locationKey
724
+ ]);
725
+ const locationState = locationEntry?.state;
726
+ const locationInfo = locationEntry?.info;
727
+ const routerContextValue = useMemo(() => ({
728
+ locationState,
729
+ locationInfo,
730
+ url: urlObject,
731
+ isPending,
732
+ navigate,
733
+ navigateAsync,
734
+ updateCurrentEntryState
735
+ }), [
736
+ locationState,
737
+ locationInfo,
738
+ urlObject,
739
+ isPending,
740
+ navigate,
741
+ navigateAsync,
742
+ updateCurrentEntryState
743
+ ]);
744
+ return useMemo(() => {
745
+ const blockerContextValue = { registry: blockerRegistry };
746
+ return /* @__PURE__ */ jsx(BlockerContext.Provider, {
747
+ value: blockerContextValue,
748
+ children: /* @__PURE__ */ jsx(RouterContext.Provider, {
749
+ value: routerContextValue,
750
+ children: matchedRoutesWithData ? /* @__PURE__ */ jsx(RouteRenderer, {
751
+ matchedRoutes: matchedRoutesWithData,
752
+ index: 0
753
+ }) : null
754
+ })
755
+ });
756
+ }, [
757
+ routerContextValue,
758
+ matchedRoutesWithData,
759
+ blockerRegistry
760
+ ]);
761
+ }
762
+
702
763
  //#endregion
703
764
  //#region src/Outlet.tsx
704
765
  /**
@@ -751,6 +812,7 @@ function useSearchParams() {
751
812
  if (!context) throw new Error("useSearchParams must be used within a Router");
752
813
  if (context.url === null) throw new Error("useSearchParams: URL is not available during SSR.");
753
814
  const currentUrl = context.url;
815
+ const { navigate } = context;
754
816
  return [currentUrl.searchParams, useCallback((params) => {
755
817
  const url = new URL(currentUrl);
756
818
  let newParams;
@@ -760,8 +822,8 @@ function useSearchParams() {
760
822
  } else if (params instanceof URLSearchParams) newParams = params;
761
823
  else newParams = new URLSearchParams(params);
762
824
  url.search = newParams.toString();
763
- context.navigate(url.pathname + url.search + url.hash, { replace: true });
764
- }, [currentUrl, context.navigate])];
825
+ navigate(url.pathname + url.search + url.hash, { replace: true });
826
+ }, [currentUrl, navigate])];
765
827
  }
766
828
 
767
829
  //#endregion
@@ -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","../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 { ActionArgs, 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 /** Action function for handling form submissions (POST navigations) */\n action?: (args: ActionArgs<Record<string, string>>) => unknown;\n /** Data loader function for this route */\n loader?: (args: LoaderArgs<Record<string, string>, unknown>) => 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?: () => Promise<void>;\n resetStateSync?: () => 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 /** FormData from the NavigateEvent, or null for non-POST navigations */\n formData: FormData | null;\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\nconst SKIPPED = Symbol(\"skipped\");\ntype MatchRouteInternalResult = MatchedRoute[] | typeof SKIPPED | null;\n\nexport type MatchRoutesOptions = {\n /**\n * When true, routes with loaders are skipped during matching.\n * Used during SSR where loaders cannot be executed.\n */\n skipLoaders?: boolean;\n};\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 options?: MatchRoutesOptions,\n): MatchedRoute[] | null {\n for (const route of routes) {\n const matched = matchRoute(route, pathname, options);\n if (matched === SKIPPED) return null;\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 options?: MatchRoutesOptions,\n): MatchRouteInternalResult {\n const hasChildren = Boolean(route.children?.length);\n const skipLoaders = options?.skipLoaders ?? false;\n\n // Routes with loaders can't render during SSR (no request context)\n if ((pathname === null || skipLoaders) && route.loader) {\n if (skipLoaders && pathname !== null) {\n // This route can't render (loader skipped), but check if it would match.\n // If it would, return SKIPPED to prevent fallback routes from matching.\n if (route.path === undefined) {\n return SKIPPED; // pathless always matches\n }\n const isExact = route.exact ?? !hasChildren;\n const { matched } = matchPath(route.path, pathname, isExact);\n if (matched) return SKIPPED;\n }\n return null;\n }\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 let anySkipped = false;\n for (const child of route.children!) {\n const childMatch = matchRoute(child, pathname, options);\n if (childMatch === SKIPPED) {\n anySkipped = true;\n break;\n }\n if (childMatch) {\n return [result, ...childMatch];\n }\n }\n if (anySkipped) {\n if (route.component) return [result]; // render as shell\n return SKIPPED; // propagate\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 // During SSR, pathless route with component matches alone (SSR shell)\n if ((pathname === null || skipLoaders) && 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 let anyChildSkipped = false;\n for (const child of route.children!) {\n const childMatch = matchRoute(child, remainingPathname, options);\n if (childMatch === SKIPPED) {\n anyChildSkipped = true;\n break;\n }\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 (anyChildSkipped) {\n if (route.component) return [result]; // render as shell\n return SKIPPED; // propagate\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 // During SSR, path-based route with component matches alone (SSR shell)\n if (skipLoaders && route.component) {\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>, unknown>,\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 * Create a Request object for action args (POST with FormData body).\n */\nexport function createActionRequest(url: URL, formData: FormData): Request {\n return new Request(url.href, {\n method: \"POST\",\n body: formData,\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 actionResult?: unknown,\n): MatchedRouteWithData[] {\n return matchedRoutes.map((match, index) => {\n const { route, params } = match;\n const args: LoaderArgs<Record<string, string>, unknown> = {\n params,\n request,\n signal,\n actionResult,\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 {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n MatchedRoute,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\nimport { matchRoutes } from \"./matchRoutes.js\";\nimport {\n executeLoaders,\n createLoaderRequest,\n createActionRequest,\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: (changeType: EntryChangeType) => void): () => void {\n const controller = new AbortController();\n navigation.addEventListener(\n \"currententrychange\",\n (event) => {\n // NavigationCurrentEntryChangeEvent.navigationType is null\n // when the change was caused by updateCurrentEntry()\n const changeType: EntryChangeType =\n (event as NavigationCurrentEntryChangeEvent).navigationType === null\n ? \"state\"\n : \"navigation\";\n callback(changeType);\n },\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, {\n matches: [],\n intercepting: false,\n formData: event.formData,\n });\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 const isFormSubmission = event.formData !== null;\n\n // For POST form submissions, don't intercept if no matched route has an action\n if (isFormSubmission && matched !== null) {\n const hasAction = matched.some((m) => m.route.action);\n if (!hasAction) {\n // Don't intercept — let browser submit the form normally\n onNavigate?.(event, {\n matches: matched,\n intercepting: false,\n formData: event.formData,\n });\n return;\n }\n }\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, {\n matches: matched,\n intercepting: willIntercept,\n formData: event.formData,\n });\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 currentEntry = navigation.currentEntry;\n if (!currentEntry) {\n throw new Error(\n \"Navigation currentEntry is null during navigation interception\",\n );\n }\n\n let actionResult: unknown = undefined;\n\n if (isFormSubmission) {\n // Find the deepest matched route with an action\n const actionRoute = findActionRoute(matched);\n if (actionRoute) {\n const actionRequest = createActionRequest(url, event.formData!);\n actionResult = await actionRoute.route.action!({\n params: actionRoute.params,\n request: actionRequest,\n signal: event.signal,\n });\n }\n // Revalidate loaders after action — clear cache so loaders re-execute\n clearLoaderCacheForEntry(currentEntry.id);\n }\n\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 // For form submissions, cache was cleared above so loaders re-execute with actionResult.\n const results = executeLoaders(\n matched,\n currentEntry.id,\n request,\n event.signal,\n actionResult,\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\n/**\n * Find the deepest matched route that has an action defined.\n * Iterates from deepest to shallowest.\n */\nfunction findActionRoute(matched: MatchedRoute[]): MatchedRoute | undefined {\n for (let i = matched.length - 1; i >= 0; i--) {\n if (matched[i].route.action) {\n return matched[i];\n }\n }\n return undefined;\n}\n","import type {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} 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: (changeType: EntryChangeType) => 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 {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} 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: (changeType: EntryChangeType) => 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\n/**\n * SSR configuration for the router.\n */\nexport type SSRConfig = {\n /**\n * Pathname to use for route matching during SSR.\n *\n * The router uses this pathname to match path-based routes during SSR.\n * Route params are extracted normally.\n */\n path: string;\n /**\n * Whether to run loaders during SSR.\n *\n * - When `false` or omitted, routes with loaders are skipped during SSR\n * and the parent route renders as a shell.\n * - When `true`, routes with loaders are matched and their loaders are\n * executed during SSR. The loader results are passed to components as\n * the `data` prop, so server-rendered HTML includes loader content.\n *\n * @default false\n */\n runLoaders?: boolean;\n};\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 * SSR configuration for the router.\n *\n * By default (no `ssr` prop), during SSR only pathless routes match.\n * When provided, the router uses the given pathname to match path-based\n * routes during SSR as well.\n *\n * This prop is only used when the location entry is not available (during SSR\n * or hydration). Once the client hydrates, the real URL from the Navigation API\n * takes over.\n *\n * @example\n * ```tsx\n * // SSG: match path-based routes, skip loaders\n * <Router routes={routes} ssr={{ path: \"/about\" }} />\n *\n * // SSR with loaders: match path-based routes including those with loaders\n * <Router routes={routes} ssr={{ path: \"/about\", runLoaders: true }} />\n * ```\n */\n ssr?: SSRConfig;\n};\n\n/**\n * Special value returned as server snapshot during SSR/hydration.\n */\nconst serverSnapshotSymbol = Symbol();\n\nconst noopSubscribe = () => () => {};\nconst getServerSnapshot = (): typeof serverSnapshotSymbol =>\n serverSnapshotSymbol;\n\nexport function Router({\n routes: inputRoutes,\n onNavigate,\n fallback = \"none\",\n ssr,\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 LocationEntry | null | typeof serverSnapshotSymbol\n >(noopSubscribe, getSnapshot, getServerSnapshot);\n\n const [isPending, startTransition] = useTransition();\n const [locationEntryInternal, setLocationEntry] = useState<\n LocationEntry | null | typeof serverSnapshotSymbol\n >(initialEntry);\n const locationEntry =\n locationEntryInternal === serverSnapshotSymbol\n ? null\n : locationEntryInternal;\n\n if (\n locationEntryInternal === serverSnapshotSymbol &&\n initialEntry !== serverSnapshotSymbol\n ) {\n // On second hydrated render on client, sync state with real snapshot\n // Rendering flow on hydration:\n // 1. Hydrated Render 1: initialEntry = serverSnapshotSymbol, locationEntryInternal = serverSnapshotSymbol\n // 2. Render 1 is committed\n // 3. Hydrated Render 2: initialEntry = client snapshot, locationEntryInternal = serverSnapshotSymbol\n // 4. This `if` block runs, updating state to client snapshot\n // 5. Hydrated Render 2 (retry): initialEntry = client snapshot, locationEntryInternal = client snapshot\n // 6. Render 2 is committed\n setLocationEntry(initialEntry);\n }\n\n // Subscribe to navigation changes (conditionally wrapped in transition)\n useEffect(() => {\n return adapter.subscribe((changeType) => {\n if (changeType === \"navigation\") {\n startTransition(() => {\n setLocationEntry(adapter.getSnapshot());\n });\n } else {\n // State-only update: apply synchronously, no transition\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 && !ssr?.runLoaders) {\n // SSR/hydration without loader execution: match routes, data is undefined.\n // Routes with loaders are skipped (skipLoaders: true).\n const matched = matchRoutes(routes, ssr?.path ?? null, {\n skipLoaders: true,\n });\n if (!matched) return null;\n return matched.map((m) => ({ ...m, data: undefined }));\n }\n\n // Unified path: SSR with loaders or client-side.\n // Both cases match routes normally and execute loaders.\n const url = locationEntry\n ? locationEntry.url\n : new URL(ssr!.path, \"http://localhost\");\n const matched = matchRoutes(routes, url.pathname);\n if (!matched) return null;\n\n const entryKey = locationEntry?.key ?? \"ssr\";\n const request = createLoaderRequest(url);\n const signal = locationEntry\n ? adapter.getIdleAbortSignal()\n : new AbortController().signal;\n return executeLoaders(matched, entryKey, request, signal);\n })();\n\n const routerContextValue = {\n locationEntry: locationEntry,\n url:\n locationEntry?.url ??\n (ssr ? new URL(ssr.path, \"http://localhost\") : 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 ssr,\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 resetStateSync callback (synchronous via updateCurrentEntry)\n const resetStateSync = 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 stable resetState callback (async via replace navigation)\n const resetState = useCallback(async (): Promise<void> => {\n if (locationEntry === null || url === null) return;\n const currentStates =\n (locationEntry.state as InternalRouteState | undefined)?.__routeStates ??\n [];\n const newStates = [...currentStates];\n newStates[index] = undefined;\n\n await navigateAsync(url.href, {\n replace: true,\n state: { __routeStates: newStates },\n });\n }, [locationEntry?.state, index, url, navigateAsync]);\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 resetStateSync,\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: () => Promise<void>;\n resetStateSync: () => 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: () => Promise<void>;\n resetStateSync: () => 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 { 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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"}
1
+ {"version":3,"file":"index.mjs","names":["#cachedEntryId","#cachedSnapshot","#currentNavigationInfo","#subscribeToDisposeEvents","#subscribedEntryIds","#cachedSnapshot","#idleController","#idleController"],"sources":["../src/context/RouterContext.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/ServerLocationSnapshot.ts","../src/context/RouteContext.ts","../src/Router/useRouteStateCallbacks.ts","../src/Router/RouteRenderer.tsx","../src/Router/index.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","../src/hooks/useIsPending.ts"],"sourcesContent":["import { createContext } from \"react\";\nimport type { NavigateOptions } from \"../types.js\";\n\nexport type RouterContextValue = {\n /**\n * Current location state associated with the current location entry.\n * Initially undefined. Also, it is undefined during SSR.\n */\n locationState: unknown;\n /**\n * Current location info associated with the current location entry.\n * This is undefined during SSR.\n */\n locationInfo: unknown;\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 } 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 { ActionArgs, 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 /** Action function for handling form submissions (POST navigations) */\n action?: (args: ActionArgs<Record<string, string>>) => unknown;\n /** Data loader function for this route */\n loader?: (args: LoaderArgs<Record<string, string>, unknown>) => 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?: () => Promise<void>;\n resetStateSync?: () => 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 /** FormData from the NavigateEvent, or null for non-POST navigations */\n formData: FormData | null;\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\nconst SKIPPED = Symbol(\"skipped\");\ntype MatchRouteInternalResult = MatchedRoute[] | typeof SKIPPED | null;\n\nexport type MatchRoutesOptions = {\n /**\n * When true, routes with loaders are skipped during matching.\n * Used during SSR where loaders cannot be executed.\n */\n skipLoaders?: boolean;\n};\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 options?: MatchRoutesOptions,\n): MatchedRoute[] | null {\n for (const route of routes) {\n const matched = matchRoute(route, pathname, options);\n if (matched === SKIPPED) return null;\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 options?: MatchRoutesOptions,\n): MatchRouteInternalResult {\n const hasChildren = Boolean(route.children?.length);\n const skipLoaders = options?.skipLoaders ?? false;\n\n // Routes with loaders can't render during SSR (no request context)\n if ((pathname === null || skipLoaders) && route.loader) {\n if (skipLoaders && pathname !== null) {\n // This route can't render (loader skipped), but check if it would match.\n // If it would, return SKIPPED to prevent fallback routes from matching.\n if (route.path === undefined) {\n return SKIPPED; // pathless always matches\n }\n const isExact = route.exact ?? !hasChildren;\n const { matched } = matchPath(route.path, pathname, isExact);\n if (matched) return SKIPPED;\n }\n return null;\n }\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 let anySkipped = false;\n for (const child of route.children!) {\n const childMatch = matchRoute(child, pathname, options);\n if (childMatch === SKIPPED) {\n anySkipped = true;\n break;\n }\n if (childMatch) {\n return [result, ...childMatch];\n }\n }\n if (anySkipped) {\n if (route.component) return [result]; // render as shell\n return SKIPPED; // propagate\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 // During SSR, pathless route with component matches alone (SSR shell)\n if ((pathname === null || skipLoaders) && 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 let anyChildSkipped = false;\n for (const child of route.children!) {\n const childMatch = matchRoute(child, remainingPathname, options);\n if (childMatch === SKIPPED) {\n anyChildSkipped = true;\n break;\n }\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 (anyChildSkipped) {\n if (route.component) return [result]; // render as shell\n return SKIPPED; // propagate\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 // During SSR, path-based route with component matches alone (SSR shell)\n if (skipLoaders && route.component) {\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>, unknown>,\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 * Create a Request object for action args (POST with FormData body).\n */\nexport function createActionRequest(url: URL, formData: FormData): Request {\n return new Request(url.href, {\n method: \"POST\",\n body: formData,\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 actionResult?: unknown,\n): MatchedRouteWithData[] {\n return matchedRoutes.map((match, index) => {\n const { route, params } = match;\n const args: LoaderArgs<Record<string, string>, unknown> = {\n params,\n request,\n signal,\n actionResult,\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 {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} from \"./RouterAdapter.js\";\nimport type {\n InternalRouteDefinition,\n MatchedRoute,\n NavigateOptions,\n OnNavigateCallback,\n} from \"../types.js\";\nimport { matchRoutes } from \"./matchRoutes.js\";\nimport {\n executeLoaders,\n createLoaderRequest,\n createActionRequest,\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: (changeType: EntryChangeType) => void): () => void {\n const controller = new AbortController();\n navigation.addEventListener(\n \"currententrychange\",\n (event) => {\n // NavigationCurrentEntryChangeEvent.navigationType is null\n // when the change was caused by updateCurrentEntry()\n const changeType: EntryChangeType =\n (event as NavigationCurrentEntryChangeEvent).navigationType === null\n ? \"state\"\n : \"navigation\";\n callback(changeType);\n },\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, {\n matches: [],\n intercepting: false,\n formData: event.formData,\n });\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 const isFormSubmission = event.formData !== null;\n\n // For POST form submissions, don't intercept if no matched route has an action\n if (isFormSubmission && matched !== null) {\n const hasAction = matched.some((m) => m.route.action);\n if (!hasAction) {\n // Don't intercept — let browser submit the form normally\n onNavigate?.(event, {\n matches: matched,\n intercepting: false,\n formData: event.formData,\n });\n return;\n }\n }\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, {\n matches: matched,\n intercepting: willIntercept,\n formData: event.formData,\n });\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 currentEntry = navigation.currentEntry;\n if (!currentEntry) {\n throw new Error(\n \"Navigation currentEntry is null during navigation interception\",\n );\n }\n\n let actionResult: unknown = undefined;\n\n if (isFormSubmission) {\n // Find the deepest matched route with an action\n const actionRoute = findActionRoute(matched);\n if (actionRoute) {\n const actionRequest = createActionRequest(url, event.formData!);\n actionResult = await actionRoute.route.action!({\n params: actionRoute.params,\n request: actionRequest,\n signal: event.signal,\n });\n }\n // Revalidate loaders after action — clear cache so loaders re-execute\n clearLoaderCacheForEntry(currentEntry.id);\n }\n\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 // For form submissions, cache was cleared above so loaders re-execute with actionResult.\n const results = executeLoaders(\n matched,\n currentEntry.id,\n request,\n event.signal,\n actionResult,\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\n/**\n * Find the deepest matched route that has an action defined.\n * Iterates from deepest to shallowest.\n */\nfunction findActionRoute(matched: MatchedRoute[]): MatchedRoute | undefined {\n for (let i = matched.length - 1; i >= 0; i--) {\n if (matched[i].route.action) {\n return matched[i];\n }\n }\n return undefined;\n}\n","import type {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} 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: (changeType: EntryChangeType) => 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 {\n RouterAdapter,\n LocationEntry,\n EntryChangeType,\n} 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: (changeType: EntryChangeType) => 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 type { LocationEntry, RouterAdapter } from \"../core/RouterAdapter.js\";\n\n/**\n * Special class returned as server snapshot during SSR/hydration.\n */\nexport class ServerLocationSnapshot {\n actualLocationEntry: LocationEntry | null;\n constructor(adapter: RouterAdapter) {\n this.actualLocationEntry = adapter.getSnapshot();\n }\n}\n\nexport function isServerSnapshot(\n value: unknown,\n): value is ServerLocationSnapshot {\n return value instanceof ServerLocationSnapshot;\n}\n\nexport const noopSubscribe = () => () => {};\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 { useCallback } from \"react\";\nimport type { InternalRouteState, NavigateOptions } from \"../types.js\";\n\ntype RouteStateCallbacks = {\n setState: (\n stateOrUpdater: unknown | ((prev: unknown) => unknown),\n ) => Promise<void>;\n setStateSync: (\n stateOrUpdater: unknown | ((prev: unknown) => unknown),\n ) => void;\n resetState: () => Promise<void>;\n resetStateSync: () => void;\n};\n\nexport function useRouteStateCallbacks(\n index: number,\n internalState: InternalRouteState | undefined,\n url: URL | null,\n navigateAsync: (to: string, options?: NavigateOptions) => Promise<void>,\n updateCurrentEntryState: (state: unknown) => void,\n): RouteStateCallbacks {\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 = internalState?.__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 [internalState, 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 (url === null) return;\n const currentStates = internalState?.__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 [internalState, index, url, navigateAsync],\n );\n\n // Create stable resetStateSync callback (synchronous via updateCurrentEntry)\n const resetStateSync = useCallback(() => {\n const currentStates = internalState?.__routeStates ?? [];\n const newStates = [...currentStates];\n newStates[index] = undefined;\n updateCurrentEntryState({ __routeStates: newStates });\n }, [internalState, index, updateCurrentEntryState]);\n\n // Create stable resetState callback (async via replace navigation)\n const resetState = useCallback(async (): Promise<void> => {\n if (url === null) return;\n const currentStates = internalState?.__routeStates ?? [];\n const newStates = [...currentStates];\n newStates[index] = undefined;\n\n await navigateAsync(url.href, {\n replace: true,\n state: { __routeStates: newStates },\n });\n }, [internalState, index, url, navigateAsync]);\n\n return { setState, setStateSync, resetState, resetStateSync };\n}\n","import { type ReactNode, useContext, useMemo } from \"react\";\nimport { RouterContext } from \"../context/RouterContext.js\";\nimport { RouteContext } from \"../context/RouteContext.js\";\nimport type { MatchedRouteWithData, InternalRouteState } from \"../types.js\";\nimport { useRouteStateCallbacks } from \"./useRouteStateCallbacks.js\";\n\nexport type RouteRendererProps = {\n matchedRoutes: MatchedRouteWithData[];\n index: number;\n};\n\n/**\n * Recursively render matched routes with proper context.\n */\nexport function 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 locationState,\n locationInfo,\n url,\n isPending,\n navigateAsync,\n updateCurrentEntryState,\n } = routerContext;\n\n // Extract this route's state from internal structure\n const internalState = locationState as InternalRouteState | undefined;\n const routeState = internalState?.__routeStates?.[index];\n\n const { setState, setStateSync, resetState, resetStateSync } =\n useRouteStateCallbacks(\n index,\n internalState,\n url,\n navigateAsync,\n updateCurrentEntryState,\n );\n\n // Create outlet for child routes\n const outlet = useMemo(\n () =>\n index < matchedRoutes.length - 1 ? (\n <RouteRenderer matchedRoutes={matchedRoutes} index={index + 1} />\n ) : null,\n [matchedRoutes, index],\n );\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 resetStateSync,\n };\n\n // Ephemeral info from the current navigation\n const info = locationInfo;\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: () => Promise<void>;\n resetStateSync: () => 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: () => Promise<void>;\n resetStateSync: () => 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 {\n type ReactNode,\n useCallback,\n useEffect,\n useMemo,\n useRef,\n useState,\n useSyncExternalStore,\n useTransition,\n} from \"react\";\nimport type { LocationEntry } from \"../core/RouterAdapter.js\";\nimport {\n RouterContext,\n type RouterContextValue,\n} from \"../context/RouterContext.js\";\nimport {\n BlockerContext,\n createBlockerRegistry,\n} from \"../context/BlockerContext.js\";\nimport {\n type NavigateOptions,\n type OnNavigateCallback,\n type FallbackMode,\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\";\nimport {\n ServerLocationSnapshot,\n isServerSnapshot,\n noopSubscribe,\n} from \"./ServerLocationSnapshot.js\";\nimport { RouteRenderer } from \"./RouteRenderer.js\";\n\n/**\n * SSR configuration for the router.\n */\nexport type SSRConfig = {\n /**\n * Pathname to use for route matching during SSR.\n *\n * The router uses this pathname to match path-based routes during SSR.\n * Route params are extracted normally.\n */\n path: string;\n /**\n * Whether to run loaders during SSR.\n *\n * - When `false` or omitted, routes with loaders are skipped during SSR\n * and the parent route renders as a shell.\n * - When `true`, routes with loaders are matched and their loaders are\n * executed during SSR. The loader results are passed to components as\n * the `data` prop, so server-rendered HTML includes loader content.\n *\n * @default false\n */\n runLoaders?: boolean;\n};\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 * SSR configuration for the router.\n *\n * By default (no `ssr` prop), during SSR only pathless routes match.\n * When provided, the router uses the given pathname to match path-based\n * routes during SSR as well.\n *\n * This prop is only used when the location entry is not available (during SSR\n * or hydration). Once the client hydrates, the real URL from the Navigation API\n * takes over.\n *\n * @example\n * ```tsx\n * // SSG: match path-based routes, skip loaders\n * <Router routes={routes} ssr={{ path: \"/about\" }} />\n *\n * // SSR with loaders: match path-based routes including those with loaders\n * <Router routes={routes} ssr={{ path: \"/about\", runLoaders: true }} />\n * ```\n */\n ssr?: SSRConfig;\n};\n\nexport function Router({\n routes: inputRoutes,\n onNavigate,\n fallback = \"none\",\n ssr,\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 serverSnapshotCacheRef = useRef<ServerLocationSnapshot | null>(null);\n const getServerSnapshot = useCallback(() => {\n // During SSR/hydration, return special server snapshot object\n // that captures the real snapshot at the time of first render.\n // This allows us to detect hydration and sync to real snapshot on client.\n return (serverSnapshotCacheRef.current ??= new ServerLocationSnapshot(\n adapter,\n ));\n }, [adapter]);\n const initialEntry = useSyncExternalStore<\n LocationEntry | null | ServerLocationSnapshot\n >(noopSubscribe, getSnapshot, getServerSnapshot);\n\n const [isPending, startTransition] = useTransition();\n const [locationEntryInternal, setLocationEntry] = useState<\n LocationEntry | null | ServerLocationSnapshot\n >(initialEntry);\n const locationEntry = isServerSnapshot(locationEntryInternal)\n ? null\n : locationEntryInternal;\n\n if (\n isServerSnapshot(locationEntryInternal) &&\n !isServerSnapshot(initialEntry)\n ) {\n // On second hydrated render on client, sync state with real snapshot\n // Rendering flow on hydration:\n // 1. Hydrated Render 1: initialEntry = serverSnapshotSymbol, locationEntryInternal = serverSnapshotSymbol\n // 2. Render 1 is committed\n // 3. Hydrated Render 2: initialEntry = client snapshot, locationEntryInternal = serverSnapshotSymbol\n // 4. This `if` block runs, updating state to client snapshot\n // 5. Hydrated Render 2 (retry): initialEntry = client snapshot, locationEntryInternal = client snapshot\n // 6. Render 2 is committed\n setLocationEntry(initialEntry);\n }\n\n // Subscribe to navigation changes (conditionally wrapped in transition)\n useEffect(() => {\n return adapter.subscribe((changeType) => {\n if (changeType === \"navigation\") {\n startTransition(() => {\n setLocationEntry(adapter.getSnapshot());\n });\n } else {\n // State-only update: apply synchronously, no transition\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 const url = useMemo(() => {\n if (locationEntry) {\n return locationEntry.url.toString();\n }\n if (ssr) {\n const origin =\n typeof window !== \"undefined\"\n ? window.location.origin\n : \"http://localhost\";\n return new URL(ssr.path, origin).toString();\n }\n return null;\n }, [locationEntry, ssr]);\n /**\n * URL object. Non-null when client-side or during SSR with ssr.path provided.\n * Null during SSR without ssr.path.\n */\n const urlObject = useMemo(() => (url ? new URL(url) : null), [url]);\n\n /**\n * Whether to run loaders.\n * 1. Loaders are always run for rendering with URL available (client-side)\n * 2. During SSR, loaders are only run if ssr.runLoaders is true and URL is available (ssr.path provided).\n */\n const runLoaders =\n locationEntry !== null || (!!ssr?.runLoaders && urlObject !== null);\n\n /**\n * Key of location. This is used as the cache key for loader data saved in navigation entry.\n */\n const locationKey =\n locationEntry?.key ??\n (isServerSnapshot(locationEntryInternal)\n ? locationEntryInternal.actualLocationEntry?.key\n : null) ??\n \"ssr\";\n\n // Match routes and execute loaders\n const matchedRoutesWithData = useMemo(() => {\n if (!runLoaders) {\n // SSR/hydration without loader execution: match routes, data is undefined.\n // Routes with loaders are skipped (skipLoaders: true).\n const matched = matchRoutes(routes, urlObject?.pathname ?? null, {\n skipLoaders: true,\n });\n if (!matched) return null;\n return matched.map((m) => ({ ...m, data: undefined }));\n }\n\n if (urlObject === null) {\n throw new Error(\"Invariant failure: loaders cannot run without URL.\");\n }\n\n // Unified path: SSR with loaders or client-side.\n // Both cases match routes normally and execute loaders.\n const matched = matchRoutes(routes, urlObject.pathname);\n if (!matched) return null;\n\n const entryKey = locationKey;\n const request = createLoaderRequest(urlObject);\n const signal = adapter.getIdleAbortSignal();\n return executeLoaders(matched, entryKey, request, signal);\n }, [routes, adapter, urlObject, runLoaders, locationKey]);\n\n const locationState = locationEntry?.state;\n const locationInfo = locationEntry?.info;\n const routerContextValue: RouterContextValue = useMemo(\n () => ({\n locationState,\n locationInfo,\n url: urlObject,\n isPending,\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n }),\n [\n locationState,\n locationInfo,\n urlObject,\n isPending,\n navigate,\n navigateAsync,\n updateCurrentEntryState,\n ],\n );\n\n return useMemo(() => {\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 }, [routerContextValue, matchedRoutesWithData, blockerRegistry]);\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 { 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 { navigate } = context;\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 navigate(url.pathname + url.search + url.hash, { replace: true });\n },\n [currentUrl, 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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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@funstack/router",
3
- "version": "0.0.9",
3
+ "version": "0.0.10",
4
4
  "description": "A modern React router based on the Navigation API",
5
5
  "repository": {
6
6
  "type": "git",
@@ -47,7 +47,6 @@
47
47
  "react": "^19.2.4",
48
48
  "tsdown": "^0.20.3",
49
49
  "typescript": "^5.7.0",
50
- "urlpattern-polyfill": "^10.1.0",
51
50
  "vitest": "^4.0.18"
52
51
  },
53
52
  "scripts": {