@funstack/router 0.0.7-alpha.0 → 0.0.8
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/docs/ApiComponentsPage.tsx +32 -0
- package/dist/docs/ApiHooksPage.tsx +8 -3
- package/dist/docs/ApiTypesPage.tsx +56 -8
- package/dist/docs/ApiUtilitiesPage.tsx +26 -8
- package/dist/docs/GettingStartedPage.tsx +16 -1
- package/dist/docs/LearnNestedRoutesPage.tsx +2 -4
- package/dist/docs/LearnRscPage.tsx +16 -3
- package/dist/docs/LearnSsrPage.tsx +99 -20
- package/dist/docs/LearnTransitionsPage.tsx +80 -6
- package/dist/docs/LearnTypeSafetyPage.tsx +12 -2
- package/dist/docs/index.md +2 -2
- package/dist/index.d.mts +28 -25
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +86 -22
- package/dist/index.mjs.map +1 -1
- package/dist/{route-ClVnhrQD.d.mts → route-DRcgs0Pt.d.mts} +61 -6
- package/dist/route-DRcgs0Pt.d.mts.map +1 -0
- package/dist/route-p_gr5yPI.mjs.map +1 -1
- package/dist/server.d.mts +1 -1
- package/package.json +4 -4
- package/dist/route-ClVnhrQD.d.mts.map +0 -1
package/dist/index.d.mts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as ExtractRouteState, c as PathParams, d as RouteComponentPropsWithData, f as RouteDefinition, h as routeState, i as ExtractRouteParams, l as RouteComponentProps, m as route, n as ExtractRouteData, o as LoaderArgs, p as TypefulOpaqueRouteDefinition, r as ExtractRouteId, s as OpaqueRouteDefinition, t as ActionArgs, u as RouteComponentPropsOf } from "./route-DRcgs0Pt.mjs";
|
|
2
2
|
import { ComponentType, ReactNode } from "react";
|
|
3
3
|
|
|
4
4
|
//#region src/types.d.ts
|
|
@@ -27,15 +27,17 @@ type InternalRouteDefinition = {
|
|
|
27
27
|
* - true (default): Parent does not match if no child matches
|
|
28
28
|
* - false: Parent can match alone if it has a component, outlet will be null
|
|
29
29
|
*/
|
|
30
|
-
requireChildren?: boolean; /**
|
|
31
|
-
|
|
30
|
+
requireChildren?: boolean; /** Action function for handling form submissions (POST navigations) */
|
|
31
|
+
action?: (args: ActionArgs<Record<string, string>>) => unknown; /** Data loader function for this route */
|
|
32
|
+
loader?: (args: LoaderArgs<Record<string, string>, unknown>) => unknown; /** Component to render when this route matches */
|
|
32
33
|
component?: ComponentType<{
|
|
33
34
|
data?: unknown;
|
|
34
35
|
params?: Record<string, string>;
|
|
35
36
|
state?: unknown;
|
|
36
37
|
setState?: (state: unknown | ((prev: unknown) => unknown)) => Promise<void>;
|
|
37
38
|
setStateSync?: (state: unknown | ((prev: unknown) => unknown)) => void;
|
|
38
|
-
resetState?: () => void
|
|
39
|
+
resetState?: () => Promise<void>;
|
|
40
|
+
resetStateSync?: () => void;
|
|
39
41
|
info?: unknown;
|
|
40
42
|
}> | ReactNode;
|
|
41
43
|
};
|
|
@@ -47,18 +49,13 @@ type MatchedRoute = {
|
|
|
47
49
|
params: Record<string, string>; /** The matched pathname segment */
|
|
48
50
|
pathname: string;
|
|
49
51
|
};
|
|
50
|
-
/**
|
|
51
|
-
* A matched route with loader data.
|
|
52
|
-
*/
|
|
53
|
-
type MatchedRouteWithData = MatchedRoute & {
|
|
54
|
-
/** Data returned from the loader (undefined if no loader) */data: unknown | undefined;
|
|
55
|
-
};
|
|
56
52
|
/**
|
|
57
53
|
* Information passed to onNavigate callback.
|
|
58
54
|
*/
|
|
59
55
|
type OnNavigateInfo = {
|
|
60
56
|
/** Array of matched routes, or null if no routes matched */matches: readonly MatchedRoute[] | null; /** Whether the router will intercept this navigation (before user's preventDefault() call) */
|
|
61
|
-
intercepting: boolean;
|
|
57
|
+
intercepting: boolean; /** FormData from the NavigateEvent, or null for non-POST navigations */
|
|
58
|
+
formData: FormData | null;
|
|
62
59
|
};
|
|
63
60
|
/**
|
|
64
61
|
* Options for navigation.
|
|
@@ -110,11 +107,29 @@ type RouterProps = {
|
|
|
110
107
|
* - `"static"`: Render matched routes without navigation capabilities (MPA behavior)
|
|
111
108
|
*/
|
|
112
109
|
fallback?: FallbackMode;
|
|
110
|
+
/**
|
|
111
|
+
* Pathname to use for route matching during SSR.
|
|
112
|
+
*
|
|
113
|
+
* By default, during SSR only pathless routes match. When this prop is provided,
|
|
114
|
+
* the router uses this pathname to match path-based routes during SSR as well.
|
|
115
|
+
* Loaders are not executed during SSR regardless of this setting.
|
|
116
|
+
*
|
|
117
|
+
* This prop is only used when the location entry is not available (during SSR
|
|
118
|
+
* or hydration). Once the client hydrates, the real URL from the Navigation API
|
|
119
|
+
* takes over.
|
|
120
|
+
*
|
|
121
|
+
* @example
|
|
122
|
+
* ```tsx
|
|
123
|
+
* <Router routes={routes} ssrPathname="/about" />
|
|
124
|
+
* ```
|
|
125
|
+
*/
|
|
126
|
+
ssrPathname?: string;
|
|
113
127
|
};
|
|
114
128
|
declare function Router({
|
|
115
129
|
routes: inputRoutes,
|
|
116
130
|
onNavigate,
|
|
117
|
-
fallback
|
|
131
|
+
fallback,
|
|
132
|
+
ssrPathname
|
|
118
133
|
}: RouterProps): ReactNode;
|
|
119
134
|
//#endregion
|
|
120
135
|
//#region src/Outlet.d.ts
|
|
@@ -260,17 +275,5 @@ declare function useRouteData<T extends TypefulOpaqueRouteDefinition<string, Rec
|
|
|
260
275
|
*/
|
|
261
276
|
declare function useIsPending(): boolean;
|
|
262
277
|
//#endregion
|
|
263
|
-
|
|
264
|
-
/**
|
|
265
|
-
* Represents the current location state.
|
|
266
|
-
* Abstracts NavigationHistoryEntry for static mode compatibility.
|
|
267
|
-
*/
|
|
268
|
-
type LocationEntry = {
|
|
269
|
-
/** The current URL */url: URL; /** Unique key for this entry (used for loader caching) */
|
|
270
|
-
key: string; /** State associated with this entry */
|
|
271
|
-
state: unknown; /** Ephemeral info from current navigation (undefined if not from navigation event) */
|
|
272
|
-
info: unknown;
|
|
273
|
-
};
|
|
274
|
-
//#endregion
|
|
275
|
-
export { type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type LocationEntry, type MatchedRoute, type MatchedRouteWithData, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useIsPending, useLocation, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
278
|
+
export { type ActionArgs, type ExtractRouteData, type ExtractRouteId, type ExtractRouteParams, type ExtractRouteState, type FallbackMode, type LoaderArgs, type Location, type MatchedRoute, type NavigateOptions, type OnNavigateCallback, type OnNavigateInfo, type OpaqueRouteDefinition, Outlet, type PathParams, type RouteComponentProps, type RouteComponentPropsOf, type RouteComponentPropsWithData, type RouteDefinition, Router, type RouterProps, type TypefulOpaqueRouteDefinition, type UseBlockerOptions, route, routeState, useBlocker, useIsPending, useLocation, useNavigate, useRouteData, useRouteParams, useRouteState, useSearchParams };
|
|
276
279
|
//# sourceMappingURL=index.d.mts.map
|
package/dist/index.d.mts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.mts","names":[],"sources":["../src/types.ts","../src/Router.tsx","../src/Outlet.tsx","../src/hooks/useNavigate.ts","../src/hooks/useLocation.ts","../src/hooks/useSearchParams.ts","../src/hooks/useBlocker.ts","../src/hooks/useRouteParams.ts","../src/hooks/useRouteState.ts","../src/hooks/useRouteData.ts","../src/hooks/useIsPending.ts"
|
|
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;;;;;AC1GR;;KDmHY,YAAA;;;KCnHA,WAAA;EACV,MAAA,EAAQ,eAAA;ED5BoC;;;;AAc9C;;;ECsBE,UAAA,GAAa,kBAAA;EDjBF;;;;;;ECwBX,QAAA,GAAW,YAAA;EDQc;;;;;;;;;;;;;;;;ECSzB,WAAA;AAAA;AAAA,iBAYc,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA,QAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD/EyD;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
|
@@ -65,9 +65,9 @@ function internalRoutes(routes) {
|
|
|
65
65
|
* Match a pathname against a route tree, returning the matched route stack.
|
|
66
66
|
* Returns null if no match is found.
|
|
67
67
|
*/
|
|
68
|
-
function matchRoutes(routes, pathname) {
|
|
68
|
+
function matchRoutes(routes, pathname, options) {
|
|
69
69
|
for (const route of routes) {
|
|
70
|
-
const matched = matchRoute(route, pathname);
|
|
70
|
+
const matched = matchRoute(route, pathname, options);
|
|
71
71
|
if (matched) return matched;
|
|
72
72
|
}
|
|
73
73
|
return null;
|
|
@@ -75,10 +75,11 @@ function matchRoutes(routes, pathname) {
|
|
|
75
75
|
/**
|
|
76
76
|
* Match a single route and its children recursively.
|
|
77
77
|
*/
|
|
78
|
-
function matchRoute(route, pathname) {
|
|
78
|
+
function matchRoute(route, pathname, options) {
|
|
79
79
|
const hasChildren = Boolean(route.children?.length);
|
|
80
|
+
const skipLoaders = options?.skipLoaders ?? false;
|
|
81
|
+
if ((pathname === null || skipLoaders) && route.loader) return null;
|
|
80
82
|
if (route.path === void 0) {
|
|
81
|
-
if (pathname === null && route.loader) return null;
|
|
82
83
|
const result = {
|
|
83
84
|
route,
|
|
84
85
|
params: {},
|
|
@@ -86,11 +87,11 @@ function matchRoute(route, pathname) {
|
|
|
86
87
|
};
|
|
87
88
|
if (hasChildren) {
|
|
88
89
|
for (const child of route.children) {
|
|
89
|
-
const childMatch = matchRoute(child, pathname);
|
|
90
|
+
const childMatch = matchRoute(child, pathname, options);
|
|
90
91
|
if (childMatch) return [result, ...childMatch];
|
|
91
92
|
}
|
|
92
93
|
if (route.component && route.requireChildren === false) return [result];
|
|
93
|
-
if (pathname === null && route.component) return [result];
|
|
94
|
+
if ((pathname === null || skipLoaders) && route.component) return [result];
|
|
94
95
|
return null;
|
|
95
96
|
}
|
|
96
97
|
return [result];
|
|
@@ -109,7 +110,7 @@ function matchRoute(route, pathname) {
|
|
|
109
110
|
if (!remainingPathname.startsWith("/")) remainingPathname = "/" + remainingPathname;
|
|
110
111
|
if (remainingPathname === "") remainingPathname = "/";
|
|
111
112
|
for (const child of route.children) {
|
|
112
|
-
const childMatch = matchRoute(child, remainingPathname);
|
|
113
|
+
const childMatch = matchRoute(child, remainingPathname, options);
|
|
113
114
|
if (childMatch) return [result, ...childMatch.map((m) => ({
|
|
114
115
|
...m,
|
|
115
116
|
params: {
|
|
@@ -119,6 +120,7 @@ function matchRoute(route, pathname) {
|
|
|
119
120
|
}))];
|
|
120
121
|
}
|
|
121
122
|
if (route.component && route.requireChildren === false) return [result];
|
|
123
|
+
if (skipLoaders && route.component) return [result];
|
|
122
124
|
return null;
|
|
123
125
|
}
|
|
124
126
|
return [result];
|
|
@@ -178,16 +180,26 @@ function createLoaderRequest(url) {
|
|
|
178
180
|
return new Request(url.href, { method: "GET" });
|
|
179
181
|
}
|
|
180
182
|
/**
|
|
183
|
+
* Create a Request object for action args (POST with FormData body).
|
|
184
|
+
*/
|
|
185
|
+
function createActionRequest(url, formData) {
|
|
186
|
+
return new Request(url.href, {
|
|
187
|
+
method: "POST",
|
|
188
|
+
body: formData
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
181
192
|
* Execute loaders for matched routes and return routes with data.
|
|
182
193
|
* Results are cached by navigation entry id to prevent duplicate execution.
|
|
183
194
|
*/
|
|
184
|
-
function executeLoaders(matchedRoutes, entryId, request, signal) {
|
|
195
|
+
function executeLoaders(matchedRoutes, entryId, request, signal, actionResult) {
|
|
185
196
|
return matchedRoutes.map((match, index) => {
|
|
186
197
|
const { route, params } = match;
|
|
187
198
|
const data = getOrCreateLoaderResult(entryId, index, route, {
|
|
188
199
|
params,
|
|
189
200
|
request,
|
|
190
|
-
signal
|
|
201
|
+
signal,
|
|
202
|
+
actionResult
|
|
191
203
|
});
|
|
192
204
|
return {
|
|
193
205
|
...match,
|
|
@@ -235,7 +247,9 @@ var NavigationAPIAdapter = class {
|
|
|
235
247
|
}
|
|
236
248
|
subscribe(callback) {
|
|
237
249
|
const controller = new AbortController();
|
|
238
|
-
navigation.addEventListener("currententrychange",
|
|
250
|
+
navigation.addEventListener("currententrychange", (event) => {
|
|
251
|
+
callback(event.navigationType === null ? "state" : "navigation");
|
|
252
|
+
}, { signal: controller.signal });
|
|
239
253
|
this.#subscribeToDisposeEvents(controller.signal);
|
|
240
254
|
navigation.addEventListener("currententrychange", () => this.#subscribeToDisposeEvents(controller.signal), { signal: controller.signal });
|
|
241
255
|
return () => {
|
|
@@ -286,17 +300,30 @@ var NavigationAPIAdapter = class {
|
|
|
286
300
|
if (!event.canIntercept) {
|
|
287
301
|
onNavigate?.(event, {
|
|
288
302
|
matches: [],
|
|
289
|
-
intercepting: false
|
|
303
|
+
intercepting: false,
|
|
304
|
+
formData: event.formData
|
|
290
305
|
});
|
|
291
306
|
return;
|
|
292
307
|
}
|
|
293
308
|
const url = new URL(event.destination.url);
|
|
294
309
|
const matched = matchRoutes(routes, url.pathname);
|
|
310
|
+
const isFormSubmission = event.formData !== null;
|
|
311
|
+
if (isFormSubmission && matched !== null) {
|
|
312
|
+
if (!matched.some((m) => m.route.action)) {
|
|
313
|
+
onNavigate?.(event, {
|
|
314
|
+
matches: matched,
|
|
315
|
+
intercepting: false,
|
|
316
|
+
formData: event.formData
|
|
317
|
+
});
|
|
318
|
+
return;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
295
321
|
const willIntercept = matched !== null && !event.hashChange && event.downloadRequest === null;
|
|
296
322
|
if (onNavigate) {
|
|
297
323
|
onNavigate(event, {
|
|
298
324
|
matches: matched,
|
|
299
|
-
intercepting: willIntercept
|
|
325
|
+
intercepting: willIntercept,
|
|
326
|
+
formData: event.formData
|
|
300
327
|
});
|
|
301
328
|
if (event.defaultPrevented) return;
|
|
302
329
|
}
|
|
@@ -306,10 +333,23 @@ var NavigationAPIAdapter = class {
|
|
|
306
333
|
idleController = null;
|
|
307
334
|
}
|
|
308
335
|
event.intercept({ handler: async () => {
|
|
309
|
-
const request = createLoaderRequest(url);
|
|
310
336
|
const currentEntry = navigation.currentEntry;
|
|
311
337
|
if (!currentEntry) throw new Error("Navigation currentEntry is null during navigation interception");
|
|
312
|
-
|
|
338
|
+
let actionResult = void 0;
|
|
339
|
+
if (isFormSubmission) {
|
|
340
|
+
const actionRoute = findActionRoute(matched);
|
|
341
|
+
if (actionRoute) {
|
|
342
|
+
const actionRequest = createActionRequest(url, event.formData);
|
|
343
|
+
actionResult = await actionRoute.route.action({
|
|
344
|
+
params: actionRoute.params,
|
|
345
|
+
request: actionRequest,
|
|
346
|
+
signal: event.signal
|
|
347
|
+
});
|
|
348
|
+
}
|
|
349
|
+
clearLoaderCacheForEntry(currentEntry.id);
|
|
350
|
+
}
|
|
351
|
+
const request = createLoaderRequest(url);
|
|
352
|
+
const results = executeLoaders(matched, currentEntry.id, request, event.signal, actionResult);
|
|
313
353
|
await Promise.all(results.map((r) => r.data));
|
|
314
354
|
} });
|
|
315
355
|
};
|
|
@@ -328,6 +368,13 @@ var NavigationAPIAdapter = class {
|
|
|
328
368
|
navigation.updateCurrentEntry({ state });
|
|
329
369
|
}
|
|
330
370
|
};
|
|
371
|
+
/**
|
|
372
|
+
* Find the deepest matched route that has an action defined.
|
|
373
|
+
* Iterates from deepest to shallowest.
|
|
374
|
+
*/
|
|
375
|
+
function findActionRoute(matched) {
|
|
376
|
+
for (let i = matched.length - 1; i >= 0; i--) if (matched[i].route.action) return matched[i];
|
|
377
|
+
}
|
|
331
378
|
|
|
332
379
|
//#endregion
|
|
333
380
|
//#region src/core/StaticAdapter.ts
|
|
@@ -352,7 +399,7 @@ var StaticAdapter = class {
|
|
|
352
399
|
subscribe(_callback) {
|
|
353
400
|
return () => {};
|
|
354
401
|
}
|
|
355
|
-
navigate(
|
|
402
|
+
navigate(_to, _options) {
|
|
356
403
|
console.warn("FUNSTACK Router: navigate() called in static fallback mode. Navigation API is not available in this browser. Links will cause full page loads.");
|
|
357
404
|
}
|
|
358
405
|
async navigateAsync(to, options) {
|
|
@@ -423,7 +470,7 @@ function createAdapter(fallback) {
|
|
|
423
470
|
const serverSnapshotSymbol = Symbol();
|
|
424
471
|
const noopSubscribe = () => () => {};
|
|
425
472
|
const getServerSnapshot = () => serverSnapshotSymbol;
|
|
426
|
-
function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
473
|
+
function Router({ routes: inputRoutes, onNavigate, fallback = "none", ssrPathname }) {
|
|
427
474
|
const routes = internalRoutes(inputRoutes);
|
|
428
475
|
const adapter = useMemo(() => createAdapter(fallback), [fallback]);
|
|
429
476
|
const [blockerRegistry] = useState(() => createBlockerRegistry());
|
|
@@ -433,10 +480,11 @@ function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
|
433
480
|
const locationEntry = locationEntryInternal === serverSnapshotSymbol ? null : locationEntryInternal;
|
|
434
481
|
if (locationEntryInternal === serverSnapshotSymbol && initialEntry !== serverSnapshotSymbol) setLocationEntry(initialEntry);
|
|
435
482
|
useEffect(() => {
|
|
436
|
-
return adapter.subscribe(() => {
|
|
437
|
-
startTransition(() => {
|
|
483
|
+
return adapter.subscribe((changeType) => {
|
|
484
|
+
if (changeType === "navigation") startTransition(() => {
|
|
438
485
|
setLocationEntry(adapter.getSnapshot());
|
|
439
486
|
});
|
|
487
|
+
else setLocationEntry(adapter.getSnapshot());
|
|
440
488
|
});
|
|
441
489
|
}, [adapter, startTransition]);
|
|
442
490
|
useEffect(() => {
|
|
@@ -459,7 +507,7 @@ function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
|
459
507
|
return useMemo(() => {
|
|
460
508
|
const matchedRoutesWithData = (() => {
|
|
461
509
|
if (locationEntry === null) {
|
|
462
|
-
const matched = matchRoutes(routes, null);
|
|
510
|
+
const matched = matchRoutes(routes, ssrPathname ?? null, { skipLoaders: true });
|
|
463
511
|
if (!matched) return null;
|
|
464
512
|
return matched.map((m) => ({
|
|
465
513
|
...m,
|
|
@@ -498,7 +546,8 @@ function Router({ routes: inputRoutes, onNavigate, fallback = "none" }) {
|
|
|
498
546
|
locationEntry,
|
|
499
547
|
routes,
|
|
500
548
|
adapter,
|
|
501
|
-
blockerRegistry
|
|
549
|
+
blockerRegistry,
|
|
550
|
+
ssrPathname
|
|
502
551
|
]);
|
|
503
552
|
}
|
|
504
553
|
/**
|
|
@@ -543,7 +592,7 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
543
592
|
url,
|
|
544
593
|
navigateAsync
|
|
545
594
|
]);
|
|
546
|
-
const
|
|
595
|
+
const resetStateSync = useCallback(() => {
|
|
547
596
|
if (locationEntry === null) return;
|
|
548
597
|
const newStates = [...locationEntry.state?.__routeStates ?? []];
|
|
549
598
|
newStates[index] = void 0;
|
|
@@ -553,6 +602,20 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
553
602
|
index,
|
|
554
603
|
updateCurrentEntryState
|
|
555
604
|
]);
|
|
605
|
+
const resetState = useCallback(async () => {
|
|
606
|
+
if (locationEntry === null || url === null) return;
|
|
607
|
+
const newStates = [...locationEntry.state?.__routeStates ?? []];
|
|
608
|
+
newStates[index] = void 0;
|
|
609
|
+
await navigateAsync(url.href, {
|
|
610
|
+
replace: true,
|
|
611
|
+
state: { __routeStates: newStates }
|
|
612
|
+
});
|
|
613
|
+
}, [
|
|
614
|
+
locationEntry?.state,
|
|
615
|
+
index,
|
|
616
|
+
url,
|
|
617
|
+
navigateAsync
|
|
618
|
+
]);
|
|
556
619
|
const outlet = index < matchedRoutes.length - 1 ? /* @__PURE__ */ jsx(RouteRenderer, {
|
|
557
620
|
matchedRoutes,
|
|
558
621
|
index: index + 1
|
|
@@ -584,7 +647,8 @@ function RouteRenderer({ matchedRoutes, index }) {
|
|
|
584
647
|
state: routeState,
|
|
585
648
|
setState,
|
|
586
649
|
setStateSync,
|
|
587
|
-
resetState
|
|
650
|
+
resetState,
|
|
651
|
+
resetStateSync
|
|
588
652
|
};
|
|
589
653
|
const info = locationEntry?.info;
|
|
590
654
|
if (route.loader) return /* @__PURE__ */ jsx(Component, {
|