@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/index.d.mts CHANGED
@@ -1,4 +1,4 @@
1
- import { a as LoaderArgs, c as RouteComponentProps, d as RouteDefinition, f as TypefulOpaqueRouteDefinition, i as ExtractRouteState, l as RouteComponentPropsOf, m as routeState, n as ExtractRouteId, o as OpaqueRouteDefinition, p as route, r as ExtractRouteParams, s as PathParams, t as ExtractRouteData, u as RouteComponentPropsWithData } from "./route-ClVnhrQD.mjs";
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; /** Data loader function for this route */
31
- loader?: (args: LoaderArgs<Record<string, string>>) => unknown; /** Component to render when this route matches */
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
- //#region src/core/RouterAdapter.d.ts
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
@@ -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","../src/core/RouterAdapter.ts"],"mappings":";;;;cAGM,6BAAA;;AAFwD;;;;;;;KAgBlD,uBAAA;EAAA,CACT,6BAAA,UA4Bc;EA1Bf,IAAA,WAwBI;EAtBJ,QAAA,GAAW,uBAAA;EAiCE;;;;;;EA1Bb,KAAA;EAMA;;;;;EAAA,eAAA,YASI;EAHJ,MAAA,IAAU,IAAA,EAAM,UAAA,CAAW,MAAA,+BAKrB;EAHN,SAAA,GACI,aAAA;IACE,IAAA;IACA,MAAA,GAAS,MAAA;IACT,KAAA;IACA,QAAA,IACE,KAAA,cAAmB,IAAA,2BAChB,OAAA;IACL,YAAA,IAAgB,KAAA,cAAmB,IAAA;IACnC,UAAA;IACA,IAAA;EAAA,KAEF,SAAA;AAAA;;;;KAmBM,YAAA;EAMV,oCAJA,KAAA,EAAO,uBAAA,EAIC;EAFR,MAAA,EAAQ,MAAA,kBAQsB;EAN9B,QAAA;AAAA;;AAcF;;KARY,oBAAA,GAAuB,YAAA;EAUH,6DAR9B,IAAA;AAAA;;;;KAMU,cAAA;EAUe,4DARzB,OAAA,WAAkB,YAAA,WAQO;EANzB,YAAA;AAAA;;;;KAMU,eAAA;EAYQ,uDAVlB,OAAA,YAUkB;EARlB,KAAA,YAUA;EARA,IAAA;AAAA;;AAmBF;;KAbY,QAAA;EACV,QAAA;EACA,MAAA;EACA,IAAA;AAAA;;;;AAqBF;;;;KAXY,kBAAA,IACV,KAAA,EAAO,aAAA,EACP,IAAA,EAAM,cAAA;;;;ACrGR;;;KD8GY,YAAA;;;KC9GA,WAAA;EACV,MAAA,EAAQ,eAAA;ED5BoC;;;;AAc9C;;;ECsBE,UAAA,GAAa,kBAAA;EDjBF;;;;;;ECwBX,QAAA,GAAW,YAAA;AAAA;AAAA,iBAYG,MAAA,CAAA;EACd,MAAA,EAAQ,WAAA;EACR,UAAA;EACA;AAAA,GACC,WAAA,GAAc,SAAA;;;;;;AD7D6C;iBEM9C,MAAA,CAAA,GAAU,SAAA;;;;;;iBCAV,WAAA,CAAA,IAAgB,EAAA,UAAY,OAAA,GAAU,eAAA;;;;;;iBCAtC,WAAA,CAAA,GAAe,QAAA;;;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;;;;ANFkD;EMO5D,WAAA;AAAA;;;ANSF;;;;;;;;;;;;;;;;;;;;;;;;;iBMqBgB,UAAA,CAAW,OAAA,EAAS,iBAAA;;;;;;ANrC0B;;;;;AAgB9D;;;;;;;;;;;;iBOSgB,cAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,kBAAA,CAAmB,CAAA;;;;;;APhC8B;;;;;AAgB9D;;;;;;;;;;;;;iBQUgB,aAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,iBAAA,CAAkB,CAAA;;;;;;ARjC+B;;;;;AAgB9D;;;;;;;;;;;;;;;;iBSagB,YAAA,WACJ,4BAAA,SAER,MAAA,oCAAA,CAIF,KAAA,EAAO,CAAA,GAAI,gBAAA,CAAiB,CAAA;;;;;;iBC/Bd,YAAA,CAAA;;;;;;AVL8C;KWSlD,aAAA;wBAEV,GAAA,EAAK,GAAA,EXTuC;EWW5C,GAAA,UXGiC;EWDjC,KAAA,WXEC;EWAD,IAAA;AAAA"}
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", callback, { signal: controller.signal });
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
- const results = executeLoaders(matched, currentEntry.id, request, event.signal);
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(to, _options) {
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 resetState = useCallback(() => {
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, {