@funstack/router 0.0.9 → 1.0.0
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/{route-DRcgs0Pt.d.mts → bindRoute-BtT4qPKI.d.mts} +169 -12
- package/dist/bindRoute-BtT4qPKI.d.mts.map +1 -0
- package/dist/{route-p_gr5yPI.mjs → bindRoute-C7JBYje-.mjs} +11 -2
- package/dist/bindRoute-C7JBYje-.mjs.map +1 -0
- package/dist/docs/ApiHooksPage.tsx +0 -22
- package/dist/docs/ApiUtilitiesPage.tsx +62 -3
- package/dist/docs/ExamplesPage.tsx +3 -5
- package/dist/docs/FaqPage.tsx +84 -0
- package/dist/docs/GettingStartedPage.tsx +6 -3
- package/dist/docs/LearnActionsPage.tsx +228 -0
- package/dist/docs/LearnNavigationApiPage.tsx +1 -1
- package/dist/docs/LearnRouteDefinitionsPage.tsx +285 -0
- package/dist/docs/LearnRscPage.tsx +6 -0
- package/dist/docs/LearnSsgPage.tsx +3 -5
- package/dist/docs/index.md +3 -0
- package/dist/index.d.mts +17 -12
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +233 -159
- package/dist/index.mjs.map +1 -1
- package/dist/server.d.mts +2 -2
- package/dist/server.mjs +2 -2
- package/package.json +4 -5
- package/skills/funstack-router-knowledge/SKILL.md +1 -1
- package/dist/route-DRcgs0Pt.d.mts.map +0 -1
- package/dist/route-p_gr5yPI.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -1,28 +1,12 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
|
-
import { n as routeState, t as
|
|
4
|
-
import { createContext, useCallback, useContext, useEffect, useId, useMemo, useState, useSyncExternalStore, useTransition } from "react";
|
|
3
|
+
import { n as route, r as routeState, t as bindRoute } from "./bindRoute-C7JBYje-.mjs";
|
|
4
|
+
import { createContext, useCallback, useContext, useEffect, useEffectEvent, 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
|
/**
|
|
@@ -184,6 +168,29 @@ function matchPath(pattern, pathname, exact) {
|
|
|
184
168
|
};
|
|
185
169
|
}
|
|
186
170
|
|
|
171
|
+
//#endregion
|
|
172
|
+
//#region src/bypassInterception.ts
|
|
173
|
+
const bypassInterceptionSymbol = Symbol("bypassInterception");
|
|
174
|
+
/**
|
|
175
|
+
* Check if the given info is a bypass interception marker.
|
|
176
|
+
*/
|
|
177
|
+
function isBypassInterception(info) {
|
|
178
|
+
return info === bypassInterceptionSymbol;
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Perform a full page reload, bypassing the router's interception.
|
|
182
|
+
*/
|
|
183
|
+
function hardReload() {
|
|
184
|
+
navigation.reload({ info: bypassInterceptionSymbol });
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* Navigate to the given URL with a full page navigation,
|
|
188
|
+
* bypassing the router's interception.
|
|
189
|
+
*/
|
|
190
|
+
function hardNavigate(url) {
|
|
191
|
+
navigation.navigate(url, { info: bypassInterceptionSymbol });
|
|
192
|
+
}
|
|
193
|
+
|
|
187
194
|
//#endregion
|
|
188
195
|
//#region src/core/loaderCache.ts
|
|
189
196
|
/**
|
|
@@ -317,8 +324,16 @@ var NavigationAPIAdapter = class {
|
|
|
317
324
|
info: options?.info
|
|
318
325
|
}).finished;
|
|
319
326
|
}
|
|
320
|
-
setupInterception(
|
|
327
|
+
setupInterception(getRoutes, onNavigate, checkBlockers) {
|
|
321
328
|
const handleNavigate = (event) => {
|
|
329
|
+
if (isBypassInterception(event.info)) {
|
|
330
|
+
onNavigate?.(event, {
|
|
331
|
+
matches: [],
|
|
332
|
+
intercepting: false,
|
|
333
|
+
formData: event.formData
|
|
334
|
+
});
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
322
337
|
this.#currentNavigationInfo = event.info;
|
|
323
338
|
this.#cachedSnapshot = null;
|
|
324
339
|
if (checkBlockers?.()) {
|
|
@@ -334,7 +349,7 @@ var NavigationAPIAdapter = class {
|
|
|
334
349
|
return;
|
|
335
350
|
}
|
|
336
351
|
const url = new URL(event.destination.url);
|
|
337
|
-
const matched = matchRoutes(
|
|
352
|
+
const matched = matchRoutes(getRoutes(), url.pathname);
|
|
338
353
|
const isFormSubmission = event.formData !== null;
|
|
339
354
|
if (isFormSubmission && matched !== null) {
|
|
340
355
|
if (!matched.some((m) => m.route.action)) {
|
|
@@ -433,7 +448,7 @@ var StaticAdapter = class {
|
|
|
433
448
|
async navigateAsync(to, options) {
|
|
434
449
|
this.navigate(to, options);
|
|
435
450
|
}
|
|
436
|
-
setupInterception(
|
|
451
|
+
setupInterception(_getRoutes, _onNavigate, _checkBlockers) {}
|
|
437
452
|
getIdleAbortSignal() {
|
|
438
453
|
this.#idleController ??= new AbortController();
|
|
439
454
|
return this.#idleController.signal;
|
|
@@ -461,7 +476,7 @@ var NullAdapter = class {
|
|
|
461
476
|
async navigateAsync(to, options) {
|
|
462
477
|
this.navigate(to, options);
|
|
463
478
|
}
|
|
464
|
-
setupInterception(
|
|
479
|
+
setupInterception(_getRoutes, _onNavigate, _checkBlockers) {}
|
|
465
480
|
getIdleAbortSignal() {
|
|
466
481
|
this.#idleController ??= new AbortController();
|
|
467
482
|
return this.#idleController.signal;
|
|
@@ -491,121 +506,55 @@ function createAdapter(fallback) {
|
|
|
491
506
|
}
|
|
492
507
|
|
|
493
508
|
//#endregion
|
|
494
|
-
//#region src/Router.
|
|
509
|
+
//#region src/Router/ServerLocationSnapshot.ts
|
|
495
510
|
/**
|
|
496
|
-
* Special
|
|
511
|
+
* Special class returned as server snapshot during SSR/hydration.
|
|
497
512
|
*/
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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
|
-
]);
|
|
513
|
+
var ServerLocationSnapshot = class {
|
|
514
|
+
actualLocationEntry;
|
|
515
|
+
constructor(adapter) {
|
|
516
|
+
this.actualLocationEntry = adapter.getSnapshot();
|
|
517
|
+
}
|
|
518
|
+
};
|
|
519
|
+
function isServerSnapshot(value) {
|
|
520
|
+
return value instanceof ServerLocationSnapshot;
|
|
580
521
|
}
|
|
522
|
+
const noopSubscribe = () => () => {};
|
|
523
|
+
|
|
524
|
+
//#endregion
|
|
525
|
+
//#region src/context/RouteContext.ts
|
|
526
|
+
const RouteContext = createContext(null);
|
|
581
527
|
/**
|
|
582
|
-
*
|
|
528
|
+
* Find a route context by ID in the ancestor chain.
|
|
529
|
+
* Returns the matching context or null if not found.
|
|
583
530
|
*/
|
|
584
|
-
function
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
531
|
+
function findRouteContextById(context, id) {
|
|
532
|
+
let current = context;
|
|
533
|
+
while (current !== null) {
|
|
534
|
+
if (current.id === id) return current;
|
|
535
|
+
current = current.parent;
|
|
536
|
+
}
|
|
537
|
+
return null;
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
//#endregion
|
|
541
|
+
//#region src/Router/useRouteStateCallbacks.ts
|
|
542
|
+
function useRouteStateCallbacks(index, internalState, url, navigateAsync, updateCurrentEntryState) {
|
|
593
543
|
const setStateSync = useCallback((stateOrUpdater) => {
|
|
594
|
-
|
|
595
|
-
const currentStates = locationEntry.state?.__routeStates ?? [];
|
|
544
|
+
const currentStates = internalState?.__routeStates ?? [];
|
|
596
545
|
const currentRouteState = currentStates[index];
|
|
597
546
|
const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
|
|
598
547
|
const newStates = [...currentStates];
|
|
599
548
|
newStates[index] = newState;
|
|
600
549
|
updateCurrentEntryState({ __routeStates: newStates });
|
|
601
550
|
}, [
|
|
602
|
-
|
|
551
|
+
internalState,
|
|
603
552
|
index,
|
|
604
553
|
updateCurrentEntryState
|
|
605
554
|
]);
|
|
606
555
|
const setState = useCallback(async (stateOrUpdater) => {
|
|
607
|
-
if (
|
|
608
|
-
const currentStates =
|
|
556
|
+
if (url === null) return;
|
|
557
|
+
const currentStates = internalState?.__routeStates ?? [];
|
|
609
558
|
const currentRouteState = currentStates[index];
|
|
610
559
|
const newState = typeof stateOrUpdater === "function" ? stateOrUpdater(currentRouteState) : stateOrUpdater;
|
|
611
560
|
const newStates = [...currentStates];
|
|
@@ -615,44 +564,65 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
615
564
|
state: { __routeStates: newStates }
|
|
616
565
|
});
|
|
617
566
|
}, [
|
|
618
|
-
|
|
567
|
+
internalState,
|
|
619
568
|
index,
|
|
620
569
|
url,
|
|
621
570
|
navigateAsync
|
|
622
571
|
]);
|
|
623
572
|
const resetStateSync = useCallback(() => {
|
|
624
|
-
|
|
625
|
-
const newStates = [...locationEntry.state?.__routeStates ?? []];
|
|
573
|
+
const newStates = [...internalState?.__routeStates ?? []];
|
|
626
574
|
newStates[index] = void 0;
|
|
627
575
|
updateCurrentEntryState({ __routeStates: newStates });
|
|
628
576
|
}, [
|
|
629
|
-
|
|
577
|
+
internalState,
|
|
630
578
|
index,
|
|
631
579
|
updateCurrentEntryState
|
|
632
580
|
]);
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
581
|
+
return {
|
|
582
|
+
setState,
|
|
583
|
+
setStateSync,
|
|
584
|
+
resetState: useCallback(async () => {
|
|
585
|
+
if (url === null) return;
|
|
586
|
+
const newStates = [...internalState?.__routeStates ?? []];
|
|
587
|
+
newStates[index] = void 0;
|
|
588
|
+
await navigateAsync(url.href, {
|
|
589
|
+
replace: true,
|
|
590
|
+
state: { __routeStates: newStates }
|
|
591
|
+
});
|
|
592
|
+
}, [
|
|
593
|
+
internalState,
|
|
594
|
+
index,
|
|
595
|
+
url,
|
|
596
|
+
navigateAsync
|
|
597
|
+
]),
|
|
598
|
+
resetStateSync
|
|
599
|
+
};
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
//#endregion
|
|
603
|
+
//#region src/Router/RouteRenderer.tsx
|
|
604
|
+
/**
|
|
605
|
+
* Recursively render matched routes with proper context.
|
|
606
|
+
*/
|
|
607
|
+
function RouteRenderer({ matchedRoutes, index }) {
|
|
608
|
+
const parentRouteContext = useContext(RouteContext);
|
|
609
|
+
const routerContext = useContext(RouterContext);
|
|
610
|
+
if (!routerContext) throw new Error("RouteRenderer must be used within RouterContext");
|
|
611
|
+
const { locationState, locationInfo, url, isPending, navigateAsync, updateCurrentEntryState } = routerContext;
|
|
612
|
+
const match = matchedRoutes[index];
|
|
613
|
+
const { route, params, pathname, data } = match ?? {};
|
|
614
|
+
const internalState = locationState;
|
|
615
|
+
const routeState = internalState?.__routeStates?.[index];
|
|
616
|
+
const { setState, setStateSync, resetState, resetStateSync } = useRouteStateCallbacks(index, internalState, url, navigateAsync, updateCurrentEntryState);
|
|
617
|
+
const outlet = useMemo(() => index < matchedRoutes.length - 1 ? /* @__PURE__ */ jsx(RouteRenderer, {
|
|
648
618
|
matchedRoutes,
|
|
649
619
|
index: index + 1
|
|
650
|
-
}) : null;
|
|
651
|
-
const routeId = route
|
|
620
|
+
}) : null, [matchedRoutes, index]);
|
|
621
|
+
const routeId = route?.id;
|
|
652
622
|
const routeContextValue = useMemo(() => ({
|
|
653
623
|
id: routeId,
|
|
654
|
-
params,
|
|
655
|
-
matchedPath: pathname,
|
|
624
|
+
params: params ?? {},
|
|
625
|
+
matchedPath: pathname ?? "",
|
|
656
626
|
state: routeState,
|
|
657
627
|
data,
|
|
658
628
|
outlet,
|
|
@@ -666,6 +636,7 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
666
636
|
outlet,
|
|
667
637
|
parentRouteContext
|
|
668
638
|
]);
|
|
639
|
+
if (!match) return null;
|
|
669
640
|
const renderComponent = () => {
|
|
670
641
|
const componentOrElement = route.component;
|
|
671
642
|
if (componentOrElement == null) return outlet;
|
|
@@ -678,7 +649,7 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
678
649
|
resetState,
|
|
679
650
|
resetStateSync
|
|
680
651
|
};
|
|
681
|
-
const info =
|
|
652
|
+
const info = locationInfo;
|
|
682
653
|
if (route.loader) return /* @__PURE__ */ jsx(Component, {
|
|
683
654
|
data,
|
|
684
655
|
params,
|
|
@@ -699,6 +670,119 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
699
670
|
});
|
|
700
671
|
}
|
|
701
672
|
|
|
673
|
+
//#endregion
|
|
674
|
+
//#region src/Router/index.tsx
|
|
675
|
+
function Router({ routes: inputRoutes, onNavigate, fallback = "none", ssr }) {
|
|
676
|
+
const routes = internalRoutes(inputRoutes);
|
|
677
|
+
const adapter = useMemo(() => createAdapter(fallback), [fallback]);
|
|
678
|
+
const [blockerRegistry] = useState(() => createBlockerRegistry());
|
|
679
|
+
const getSnapshot = useCallback(() => adapter.getSnapshot(), [adapter]);
|
|
680
|
+
const serverSnapshotCacheRef = useRef(null);
|
|
681
|
+
const initialEntry = useSyncExternalStore(noopSubscribe, getSnapshot, useCallback(() => {
|
|
682
|
+
return serverSnapshotCacheRef.current ??= new ServerLocationSnapshot(adapter);
|
|
683
|
+
}, [adapter]));
|
|
684
|
+
const [isPending, startTransition] = useTransition();
|
|
685
|
+
const [locationEntryInternal, setLocationEntry] = useState(initialEntry);
|
|
686
|
+
const locationEntry = isServerSnapshot(locationEntryInternal) ? null : locationEntryInternal;
|
|
687
|
+
if (isServerSnapshot(locationEntryInternal) && !isServerSnapshot(initialEntry)) setLocationEntry(initialEntry);
|
|
688
|
+
useEffect(() => {
|
|
689
|
+
return adapter.subscribe((changeType) => {
|
|
690
|
+
if (changeType === "navigation") startTransition(() => {
|
|
691
|
+
setLocationEntry(adapter.getSnapshot());
|
|
692
|
+
});
|
|
693
|
+
else setLocationEntry(adapter.getSnapshot());
|
|
694
|
+
});
|
|
695
|
+
}, [adapter, startTransition]);
|
|
696
|
+
const getRoutes = useEffectEvent(() => routes);
|
|
697
|
+
const handleNavigate = useEffectEvent((...args) => onNavigate?.(...args));
|
|
698
|
+
useEffect(() => {
|
|
699
|
+
return adapter.setupInterception(getRoutes, handleNavigate, blockerRegistry.checkAll);
|
|
700
|
+
}, [adapter, blockerRegistry]);
|
|
701
|
+
const navigateAsync = useCallback((to, options) => {
|
|
702
|
+
return adapter.navigateAsync(to, options);
|
|
703
|
+
}, [adapter]);
|
|
704
|
+
const updateCurrentEntryState = useCallback((state) => {
|
|
705
|
+
adapter.updateCurrentEntryState(state);
|
|
706
|
+
}, [adapter]);
|
|
707
|
+
const url = useMemo(() => {
|
|
708
|
+
if (locationEntry) return locationEntry.url.toString();
|
|
709
|
+
if (ssr) {
|
|
710
|
+
const origin = typeof window !== "undefined" ? window.location.origin : "http://localhost";
|
|
711
|
+
return new URL(ssr.path, origin).toString();
|
|
712
|
+
}
|
|
713
|
+
return null;
|
|
714
|
+
}, [locationEntry, ssr]);
|
|
715
|
+
/**
|
|
716
|
+
* URL object. Non-null when client-side or during SSR with ssr.path provided.
|
|
717
|
+
* Null during SSR without ssr.path.
|
|
718
|
+
*/
|
|
719
|
+
const urlObject = useMemo(() => url ? new URL(url) : null, [url]);
|
|
720
|
+
/**
|
|
721
|
+
* Whether to run loaders.
|
|
722
|
+
* 1. Loaders are always run for rendering with URL available (client-side)
|
|
723
|
+
* 2. During SSR, loaders are only run if ssr.runLoaders is true and URL is available (ssr.path provided).
|
|
724
|
+
*/
|
|
725
|
+
const runLoaders = locationEntry !== null || !!ssr?.runLoaders && urlObject !== null;
|
|
726
|
+
/**
|
|
727
|
+
* Key of location. This is used as the cache key for loader data saved in navigation entry.
|
|
728
|
+
*/
|
|
729
|
+
const locationKey = locationEntry?.key ?? (isServerSnapshot(locationEntryInternal) ? locationEntryInternal.actualLocationEntry?.key : null) ?? "ssr";
|
|
730
|
+
const matchedRoutesWithData = useMemo(() => {
|
|
731
|
+
if (!runLoaders) {
|
|
732
|
+
const matched = matchRoutes(routes, urlObject?.pathname ?? null, { skipLoaders: true });
|
|
733
|
+
if (!matched) return null;
|
|
734
|
+
return matched.map((m) => ({
|
|
735
|
+
...m,
|
|
736
|
+
data: void 0
|
|
737
|
+
}));
|
|
738
|
+
}
|
|
739
|
+
if (urlObject === null) throw new Error("Invariant failure: loaders cannot run without URL.");
|
|
740
|
+
const matched = matchRoutes(routes, urlObject.pathname);
|
|
741
|
+
if (!matched) return null;
|
|
742
|
+
return executeLoaders(matched, locationKey, createLoaderRequest(urlObject), adapter.getIdleAbortSignal());
|
|
743
|
+
}, [
|
|
744
|
+
routes,
|
|
745
|
+
adapter,
|
|
746
|
+
urlObject,
|
|
747
|
+
runLoaders,
|
|
748
|
+
locationKey
|
|
749
|
+
]);
|
|
750
|
+
const locationState = locationEntry?.state;
|
|
751
|
+
const locationInfo = locationEntry?.info;
|
|
752
|
+
const routerContextValue = useMemo(() => ({
|
|
753
|
+
locationState,
|
|
754
|
+
locationInfo,
|
|
755
|
+
url: urlObject,
|
|
756
|
+
isPending,
|
|
757
|
+
navigateAsync,
|
|
758
|
+
updateCurrentEntryState
|
|
759
|
+
}), [
|
|
760
|
+
locationState,
|
|
761
|
+
locationInfo,
|
|
762
|
+
urlObject,
|
|
763
|
+
isPending,
|
|
764
|
+
navigateAsync,
|
|
765
|
+
updateCurrentEntryState
|
|
766
|
+
]);
|
|
767
|
+
return useMemo(() => {
|
|
768
|
+
const blockerContextValue = { registry: blockerRegistry };
|
|
769
|
+
return /* @__PURE__ */ jsx(BlockerContext.Provider, {
|
|
770
|
+
value: blockerContextValue,
|
|
771
|
+
children: /* @__PURE__ */ jsx(RouterContext.Provider, {
|
|
772
|
+
value: routerContextValue,
|
|
773
|
+
children: matchedRoutesWithData ? /* @__PURE__ */ jsx(RouteRenderer, {
|
|
774
|
+
matchedRoutes: matchedRoutesWithData,
|
|
775
|
+
index: 0
|
|
776
|
+
}) : null
|
|
777
|
+
})
|
|
778
|
+
});
|
|
779
|
+
}, [
|
|
780
|
+
routerContextValue,
|
|
781
|
+
matchedRoutesWithData,
|
|
782
|
+
blockerRegistry
|
|
783
|
+
]);
|
|
784
|
+
}
|
|
785
|
+
|
|
702
786
|
//#endregion
|
|
703
787
|
//#region src/Outlet.tsx
|
|
704
788
|
/**
|
|
@@ -711,17 +795,6 @@ function Outlet() {
|
|
|
711
795
|
return routeContext.outlet;
|
|
712
796
|
}
|
|
713
797
|
|
|
714
|
-
//#endregion
|
|
715
|
-
//#region src/hooks/useNavigate.ts
|
|
716
|
-
/**
|
|
717
|
-
* Returns a function for programmatic navigation.
|
|
718
|
-
*/
|
|
719
|
-
function useNavigate() {
|
|
720
|
-
const context = useContext(RouterContext);
|
|
721
|
-
if (!context) throw new Error("useNavigate must be used within a Router");
|
|
722
|
-
return context.navigate;
|
|
723
|
-
}
|
|
724
|
-
|
|
725
798
|
//#endregion
|
|
726
799
|
//#region src/hooks/useLocation.ts
|
|
727
800
|
/**
|
|
@@ -751,6 +824,7 @@ function useSearchParams() {
|
|
|
751
824
|
if (!context) throw new Error("useSearchParams must be used within a Router");
|
|
752
825
|
if (context.url === null) throw new Error("useSearchParams: URL is not available during SSR.");
|
|
753
826
|
const currentUrl = context.url;
|
|
827
|
+
const { navigateAsync } = context;
|
|
754
828
|
return [currentUrl.searchParams, useCallback((params) => {
|
|
755
829
|
const url = new URL(currentUrl);
|
|
756
830
|
let newParams;
|
|
@@ -760,8 +834,8 @@ function useSearchParams() {
|
|
|
760
834
|
} else if (params instanceof URLSearchParams) newParams = params;
|
|
761
835
|
else newParams = new URLSearchParams(params);
|
|
762
836
|
url.search = newParams.toString();
|
|
763
|
-
|
|
764
|
-
}, [currentUrl,
|
|
837
|
+
navigateAsync(url.pathname + url.search + url.hash, { replace: true });
|
|
838
|
+
}, [currentUrl, navigateAsync])];
|
|
765
839
|
}
|
|
766
840
|
|
|
767
841
|
//#endregion
|
|
@@ -928,5 +1002,5 @@ function useIsPending() {
|
|
|
928
1002
|
}
|
|
929
1003
|
|
|
930
1004
|
//#endregion
|
|
931
|
-
export { Outlet, Router, route, routeState, useBlocker, useIsPending, useLocation,
|
|
1005
|
+
export { Outlet, Router, bindRoute, hardNavigate, hardReload, route, routeState, useBlocker, useIsPending, useLocation, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
932
1006
|
//# sourceMappingURL=index.mjs.map
|