react-router 8.3.0 → 8.4.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.
Files changed (176) hide show
  1. package/CHANGELOG.md +46 -2
  2. package/dist/development/dom-export.js +1 -1
  3. package/dist/development/index-react-server-client.js +1 -1
  4. package/dist/development/index-react-server.d.ts +50 -48
  5. package/dist/development/index-react-server.js +96 -43
  6. package/dist/development/index.d.ts +2 -2
  7. package/dist/development/index.js +3 -3
  8. package/dist/development/lib/actions.js +10 -5
  9. package/dist/development/lib/components.js +50 -10
  10. package/dist/development/lib/context.d.ts +14 -5
  11. package/dist/development/lib/context.js +11 -3
  12. package/dist/development/lib/dom/dom.js +2 -2
  13. package/dist/development/lib/dom/lib.js +33 -36
  14. package/dist/development/lib/dom/server.d.ts +6 -1
  15. package/dist/development/lib/dom/server.js +40 -14
  16. package/dist/development/lib/dom/ssr/components.js +16 -24
  17. package/dist/development/lib/dom/ssr/data.js +1 -1
  18. package/dist/development/lib/dom/ssr/entry.d.ts +4 -0
  19. package/dist/development/lib/dom/ssr/errorBoundaries.js +1 -1
  20. package/dist/development/lib/dom/ssr/fallback.js +1 -1
  21. package/dist/development/lib/dom/ssr/fog-of-war.js +34 -12
  22. package/dist/development/lib/dom/ssr/hydration.d.ts +3 -1
  23. package/dist/development/lib/dom/ssr/hydration.js +6 -4
  24. package/dist/development/lib/dom/ssr/invariant.js +1 -1
  25. package/dist/development/lib/dom/ssr/links.js +1 -1
  26. package/dist/development/lib/dom/ssr/markup.js +5 -2
  27. package/dist/development/lib/dom/ssr/routeModules.js +1 -1
  28. package/dist/development/lib/dom/ssr/routes-test-stub.js +1 -1
  29. package/dist/development/lib/dom/ssr/routes.js +1 -1
  30. package/dist/development/lib/dom/ssr/server.js +2 -2
  31. package/dist/development/lib/dom/ssr/single-fetch.js +13 -22
  32. package/dist/development/lib/dom-export/dom-router-provider.js +1 -1
  33. package/dist/development/lib/dom-export/hydrated-router.js +2 -1
  34. package/dist/development/lib/errors.js +1 -1
  35. package/dist/development/lib/hooks.js +54 -46
  36. package/dist/development/lib/href.js +1 -1
  37. package/dist/development/lib/router/history.js +2 -2
  38. package/dist/development/lib/router/instrumentation.js +1 -1
  39. package/dist/development/lib/router/matcher-route-pattern.d.ts +2 -0
  40. package/dist/development/lib/router/matcher-route-pattern.js +216 -0
  41. package/dist/development/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  42. package/dist/development/lib/router/matcher-route-pattern.preload.js +36 -0
  43. package/dist/development/lib/router/matcher.d.ts +2 -0
  44. package/dist/development/lib/router/matcher.js +29 -0
  45. package/dist/development/lib/router/navigation.js +45 -0
  46. package/dist/development/lib/router/router.d.ts +21 -7
  47. package/dist/development/lib/router/router.js +111 -82
  48. package/dist/development/lib/router/url.js +1 -1
  49. package/dist/development/lib/router/utils.d.ts +6 -1
  50. package/dist/development/lib/router/utils.js +15 -9
  51. package/dist/development/lib/rsc/browser.js +5 -1
  52. package/dist/development/lib/rsc/errorBoundaries.js +1 -1
  53. package/dist/development/lib/rsc/html-stream/browser.js +1 -1
  54. package/dist/development/lib/rsc/html-stream/server.js +3 -5
  55. package/dist/development/lib/rsc/route-modules.js +1 -1
  56. package/dist/development/lib/rsc/server.rsc.d.ts +7 -0
  57. package/dist/development/lib/rsc/server.ssr.js +26 -22
  58. package/dist/development/lib/server-runtime/cookies.js +2 -2
  59. package/dist/development/lib/server-runtime/crypto.js +2 -2
  60. package/dist/development/lib/server-runtime/data.js +1 -1
  61. package/dist/development/lib/server-runtime/dev.js +2 -2
  62. package/dist/development/lib/server-runtime/entry.js +1 -1
  63. package/dist/development/lib/server-runtime/errors.js +1 -1
  64. package/dist/development/lib/server-runtime/headers.js +1 -1
  65. package/dist/development/lib/server-runtime/invariant.js +1 -1
  66. package/dist/development/lib/server-runtime/mode.js +1 -1
  67. package/dist/development/lib/server-runtime/routeMatching.js +3 -4
  68. package/dist/development/lib/server-runtime/routes.js +1 -1
  69. package/dist/development/lib/server-runtime/server.js +8 -7
  70. package/dist/development/lib/server-runtime/serverHandoff.js +1 -1
  71. package/dist/development/lib/server-runtime/sessions/cookieStorage.js +1 -1
  72. package/dist/development/lib/server-runtime/sessions/memoryStorage.js +1 -1
  73. package/dist/development/lib/server-runtime/sessions.js +1 -1
  74. package/dist/development/lib/server-runtime/single-fetch.js +2 -2
  75. package/dist/development/lib/server-runtime/urls.js +1 -1
  76. package/dist/development/lib/server-runtime/warnings.js +1 -1
  77. package/dist/development/lib/types/internal.js +1 -1
  78. package/dist/development/route-pattern.d.ts +3 -0
  79. package/dist/development/route-pattern.js +12 -0
  80. package/dist/development/vendor/turbo-stream-v2/flatten.js +1 -1
  81. package/dist/development/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  82. package/dist/development/vendor/turbo-stream-v2/unflatten.js +1 -1
  83. package/dist/development/vendor/turbo-stream-v2/utils.js +1 -1
  84. package/dist/production/dom-export.js +1 -1
  85. package/dist/production/index-react-server-client.js +1 -1
  86. package/dist/production/index-react-server.d.ts +50 -48
  87. package/dist/production/index-react-server.js +96 -43
  88. package/dist/production/index.d.ts +2 -2
  89. package/dist/production/index.js +3 -3
  90. package/dist/production/lib/actions.js +10 -5
  91. package/dist/production/lib/components.js +50 -10
  92. package/dist/production/lib/context.d.ts +14 -5
  93. package/dist/production/lib/context.js +11 -3
  94. package/dist/production/lib/dom/dom.js +2 -2
  95. package/dist/production/lib/dom/lib.js +33 -36
  96. package/dist/production/lib/dom/server.d.ts +6 -1
  97. package/dist/production/lib/dom/server.js +40 -14
  98. package/dist/production/lib/dom/ssr/components.js +16 -24
  99. package/dist/production/lib/dom/ssr/data.js +1 -1
  100. package/dist/production/lib/dom/ssr/entry.d.ts +4 -0
  101. package/dist/production/lib/dom/ssr/errorBoundaries.js +1 -1
  102. package/dist/production/lib/dom/ssr/fallback.js +1 -1
  103. package/dist/production/lib/dom/ssr/fog-of-war.js +34 -12
  104. package/dist/production/lib/dom/ssr/hydration.d.ts +3 -1
  105. package/dist/production/lib/dom/ssr/hydration.js +6 -4
  106. package/dist/production/lib/dom/ssr/invariant.js +1 -1
  107. package/dist/production/lib/dom/ssr/links.js +1 -1
  108. package/dist/production/lib/dom/ssr/markup.js +5 -2
  109. package/dist/production/lib/dom/ssr/routeModules.js +1 -1
  110. package/dist/production/lib/dom/ssr/routes-test-stub.js +1 -1
  111. package/dist/production/lib/dom/ssr/routes.js +1 -1
  112. package/dist/production/lib/dom/ssr/server.js +2 -2
  113. package/dist/production/lib/dom/ssr/single-fetch.js +13 -22
  114. package/dist/production/lib/dom-export/dom-router-provider.js +1 -1
  115. package/dist/production/lib/dom-export/hydrated-router.js +2 -1
  116. package/dist/production/lib/errors.js +1 -1
  117. package/dist/production/lib/hooks.js +54 -46
  118. package/dist/production/lib/href.js +1 -1
  119. package/dist/production/lib/router/history.js +2 -2
  120. package/dist/production/lib/router/instrumentation.js +1 -1
  121. package/dist/production/lib/router/matcher-route-pattern.d.ts +2 -0
  122. package/dist/production/lib/router/matcher-route-pattern.js +216 -0
  123. package/dist/production/lib/router/matcher-route-pattern.preload.d.ts +20 -0
  124. package/dist/production/lib/router/matcher-route-pattern.preload.js +36 -0
  125. package/dist/production/lib/router/matcher.d.ts +2 -0
  126. package/dist/production/lib/router/matcher.js +29 -0
  127. package/dist/production/lib/router/navigation.js +45 -0
  128. package/dist/production/lib/router/router.d.ts +21 -7
  129. package/dist/production/lib/router/router.js +111 -82
  130. package/dist/production/lib/router/url.js +1 -1
  131. package/dist/production/lib/router/utils.d.ts +6 -1
  132. package/dist/production/lib/router/utils.js +15 -9
  133. package/dist/production/lib/rsc/browser.js +5 -1
  134. package/dist/production/lib/rsc/errorBoundaries.js +1 -1
  135. package/dist/production/lib/rsc/html-stream/browser.js +1 -1
  136. package/dist/production/lib/rsc/html-stream/server.js +3 -5
  137. package/dist/production/lib/rsc/route-modules.js +1 -1
  138. package/dist/production/lib/rsc/server.rsc.d.ts +7 -0
  139. package/dist/production/lib/rsc/server.ssr.js +26 -22
  140. package/dist/production/lib/server-runtime/cookies.js +2 -2
  141. package/dist/production/lib/server-runtime/crypto.js +2 -2
  142. package/dist/production/lib/server-runtime/data.js +1 -1
  143. package/dist/production/lib/server-runtime/dev.js +2 -2
  144. package/dist/production/lib/server-runtime/entry.js +1 -1
  145. package/dist/production/lib/server-runtime/errors.js +1 -1
  146. package/dist/production/lib/server-runtime/headers.js +1 -1
  147. package/dist/production/lib/server-runtime/invariant.js +1 -1
  148. package/dist/production/lib/server-runtime/mode.js +1 -1
  149. package/dist/production/lib/server-runtime/routeMatching.js +3 -4
  150. package/dist/production/lib/server-runtime/routes.js +1 -1
  151. package/dist/production/lib/server-runtime/server.js +8 -7
  152. package/dist/production/lib/server-runtime/serverHandoff.js +1 -1
  153. package/dist/production/lib/server-runtime/sessions/cookieStorage.js +1 -1
  154. package/dist/production/lib/server-runtime/sessions/memoryStorage.js +1 -1
  155. package/dist/production/lib/server-runtime/sessions.js +1 -1
  156. package/dist/production/lib/server-runtime/single-fetch.js +2 -2
  157. package/dist/production/lib/server-runtime/urls.js +1 -1
  158. package/dist/production/lib/server-runtime/warnings.js +1 -1
  159. package/dist/production/lib/types/internal.js +1 -1
  160. package/dist/production/route-pattern.d.ts +3 -0
  161. package/dist/production/route-pattern.js +12 -0
  162. package/dist/production/vendor/turbo-stream-v2/flatten.js +1 -1
  163. package/dist/production/vendor/turbo-stream-v2/turbo-stream.js +4 -4
  164. package/dist/production/vendor/turbo-stream-v2/unflatten.js +1 -1
  165. package/dist/production/vendor/turbo-stream-v2/utils.js +1 -1
  166. package/docs/how-to/fetchers.md +6 -1
  167. package/docs/how-to/middleware.md +3 -1
  168. package/docs/how-to/optimize-revalidation.md +178 -5
  169. package/docs/how-to/react-server-components.md +26 -3
  170. package/docs/how-to/resource-routes.md +3 -0
  171. package/docs/start/data/actions.md +2 -1
  172. package/docs/start/data/route-object.md +21 -6
  173. package/docs/start/framework/actions.md +4 -2
  174. package/docs/start/framework/route-module.md +32 -5
  175. package/docs/upgrading/future.md +68 -0
  176. package/package.json +11 -1
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.0
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -10,8 +10,11 @@
10
10
  */
11
11
  import { PROTOCOL_RELATIVE_URL_REGEX, normalizeProtocolRelativeUrl } from "./url.js";
12
12
  import { createBrowserURLImpl, createLocation, createPath, invariant, parsePath, warning } from "./history.js";
13
- import { ErrorResponseImpl, RouterContextProvider, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createDataFunctionUrl, flattenAndRankRoutes, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, matchRoutesImpl, prependBasename, removeDoubleSlashes, resolveTo, stripBasename } from "./utils.js";
13
+ import { ErrorResponseImpl, RouterContextProvider, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createDataFunctionUrl, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, prependBasename, removeDoubleSlashes, resolveTo, stripBasename } from "./utils.js";
14
14
  import { consumeInstrumentationClientResultMetaReceiver, getRouteInstrumentationUpdates, instrumentClientSideRouter } from "./instrumentation.js";
15
+ import { V6RegExMatcher } from "./matcher.js";
16
+ import { getRoutePatternMatcher } from "./matcher-route-pattern.preload.js";
17
+ import { validateNavigationTarget } from "./navigation.js";
15
18
  //#region lib/router/router.ts
16
19
  const validMutationMethodsArr = [
17
20
  "POST",
@@ -60,18 +63,26 @@ const IDLE_BLOCKER = {
60
63
  };
61
64
  const TRANSITIONS_STORAGE_KEY = "remix-router-transitions";
62
65
  const ResetLoaderDataSymbol = Symbol("ResetLoaderData");
66
+ function createDataRouteMatcher(future, basename) {
67
+ if (future.unstable_routePatternMatching) {
68
+ let RoutePatternMatcher = getRoutePatternMatcher();
69
+ invariant(RoutePatternMatcher, "You must call unstable_preloadRoutePattern() from \"react-router/route-pattern\" before enabling future.unstable_routePatternMatching.");
70
+ return new RoutePatternMatcher(basename);
71
+ }
72
+ return new V6RegExMatcher(basename);
73
+ }
63
74
  /**
64
75
  * Encapsulates the stable and in-flight route trees together with their
65
76
  * pre-computed branch caches so the structures always stay in sync.
66
77
  */
67
78
  var DataRoutes = class {
68
79
  #routes;
69
- #branches;
70
80
  #hmrRoutes;
71
- #hmrBranches;
72
- constructor(routes) {
81
+ #matcher;
82
+ constructor(routes, matcher) {
73
83
  this.#routes = routes;
74
- this.#branches = flattenAndRankRoutes(routes);
84
+ this.#matcher = matcher;
85
+ this.#matcher.update(routes);
75
86
  }
76
87
  /** The stable route tree */
77
88
  get stableRoutes() {
@@ -81,30 +92,25 @@ var DataRoutes = class {
81
92
  get activeRoutes() {
82
93
  return this.#hmrRoutes ?? this.#routes;
83
94
  }
84
- /** Pre-computed branches */
85
- get branches() {
86
- return this.#hmrBranches ?? this.#branches;
87
- }
88
95
  get hasHMRRoutes() {
89
96
  return this.#hmrRoutes != null;
90
97
  }
91
98
  /** Replace the stable route tree and recompute its branches */
92
99
  setRoutes(routes) {
93
100
  this.#routes = routes;
94
- this.#branches = flattenAndRankRoutes(routes);
101
+ if (!this.#hmrRoutes) this.#matcher.update(routes);
95
102
  }
96
103
  /** Set a new in-flight route tree and recompute its branches */
97
104
  setHmrRoutes(routes) {
98
105
  this.#hmrRoutes = routes;
99
- this.#hmrBranches = flattenAndRankRoutes(routes);
106
+ this.#matcher.update(routes);
100
107
  }
101
108
  /** Commit in-flight routes/branches to the stable slot and clear in-flight */
102
109
  commitHmrRoutes() {
103
110
  if (this.#hmrRoutes) {
104
111
  this.#routes = this.#hmrRoutes;
105
- this.#branches = this.#hmrBranches;
106
112
  this.#hmrRoutes = void 0;
107
- this.#hmrBranches = void 0;
113
+ this.#matcher.update(this.#routes);
108
114
  }
109
115
  }
110
116
  };
@@ -127,12 +133,13 @@ function createRouter(init) {
127
133
  };
128
134
  };
129
135
  }
130
- let manifest = {};
131
- let dataRoutes = new DataRoutes(convertRoutesToDataRoutes(init.routes, mapRouteProperties, void 0, manifest));
136
+ let future = { ...init.future };
132
137
  let basename = init.basename || "/";
133
138
  if (!basename.startsWith("/")) basename = `/${basename}`;
139
+ let dataRouteMatcher = createDataRouteMatcher(future, basename);
140
+ let manifest = {};
141
+ let dataRoutes = new DataRoutes(convertRoutesToDataRoutes(init.routes, mapRouteProperties, void 0, manifest), dataRouteMatcher);
134
142
  let dataStrategyImpl = init.dataStrategy || defaultDataStrategyWithMiddleware;
135
- let future = { ...init.future };
136
143
  let unlistenHistory = null;
137
144
  let subscribers = /* @__PURE__ */ new Set();
138
145
  let bufferedInitialStateUpdate = null;
@@ -140,7 +147,7 @@ function createRouter(init) {
140
147
  let getScrollRestorationKey = null;
141
148
  let getScrollPosition = null;
142
149
  let initialScrollRestored = init.hydrationData != null;
143
- let initialMatches = matchRoutesImpl(dataRoutes.activeRoutes, init.history.location, basename, false, dataRoutes.branches);
150
+ let initialMatches = dataRouteMatcher.match(init.history.location);
144
151
  let initialMatchesIsFOW = false;
145
152
  let initialErrors = null;
146
153
  let initialized;
@@ -154,13 +161,13 @@ function createRouter(init) {
154
161
  initialErrors = { [route.id]: error };
155
162
  } else {
156
163
  if (initialMatches && !init.hydrationData) {
157
- if (checkFogOfWar(initialMatches, dataRoutes.activeRoutes, init.history.location.pathname).active) initialMatches = null;
164
+ if (checkFogOfWar(initialMatches, init.history.location.pathname).active) initialMatches = null;
158
165
  }
159
166
  if (!initialMatches) {
160
167
  initialized = false;
161
168
  renderFallback = !initialized;
162
169
  initialMatches = [];
163
- let fogOfWar = checkFogOfWar(null, dataRoutes.activeRoutes, init.history.location.pathname);
170
+ let fogOfWar = checkFogOfWar(null, init.history.location.pathname);
164
171
  if (fogOfWar.active && fogOfWar.matches) {
165
172
  initialMatchesIsFOW = true;
166
173
  initialMatches = fogOfWar.matches;
@@ -351,7 +358,7 @@ function createRouter(init) {
351
358
  if (isUninterruptedRevalidation) {} else if (pendingAction === "POP") {} else if (pendingAction === "PUSH") init.history.push(location, location.state);
352
359
  else if (pendingAction === "REPLACE") init.history.replace(location, location.state);
353
360
  let viewTransitionOpts;
354
- if (pendingAction === "POP") {
361
+ if (pendingAction === "POP" && !isUninterruptedRevalidation && location !== state.location) {
355
362
  let priorPaths = appliedViewTransitions.get(state.location.pathname);
356
363
  if (priorPaths && priorPaths.has(location.pathname)) viewTransitionOpts = {
357
364
  currentLocation: state.location,
@@ -412,21 +419,27 @@ function createRouter(init) {
412
419
  let instrumentationNavigateMetaReceiver = consumeInstrumentationClientResultMetaReceiver(router);
413
420
  let { path, submission, error } = normalizeNavigateOptions(false, normalizeTo(state.location, state.matches, basename, to, opts?.fromRouteId, opts?.relative), opts);
414
421
  let maskPath;
415
- if (opts?.mask) maskPath = {
416
- pathname: "",
417
- search: "",
418
- hash: "",
419
- ...typeof opts.mask === "string" ? parsePath(opts.mask) : {
422
+ if (opts?.mask) {
423
+ let partialPath = typeof opts.mask === "string" ? parsePath(opts.mask) : {
420
424
  ...state.location.mask,
421
425
  ...opts.mask
422
- }
423
- };
426
+ };
427
+ maskPath = {
428
+ pathname: partialPath.pathname ?? "",
429
+ search: partialPath.search ?? "",
430
+ hash: partialPath.hash ?? ""
431
+ };
432
+ if (PROTOCOL_RELATIVE_URL_REGEX.test(maskPath.pathname)) throw new Error("External navigation is not allowed");
433
+ else if (maskPath.pathname.startsWith("\\")) maskPath.pathname = maskPath.pathname.replace(/^\\+/, "/");
434
+ validateNavigationTarget(typeof opts.mask === "string" ? opts.mask : createPath(opts.mask), createPath(maskPath), init.history.createURL("/"), "reject");
435
+ }
424
436
  let currentLocation = state.location;
425
437
  let nextLocation = createLocation(currentLocation, path, opts && opts.state, void 0, maskPath);
426
438
  nextLocation = {
427
439
  ...nextLocation,
428
440
  ...init.history.encodeLocation(nextLocation)
429
441
  };
442
+ validateNavigationTarget(to == null ? init.history.createHref(state.location) : typeof to === "string" ? to : createPath(to), init.history.createHref(nextLocation.mask || nextLocation), init.history.createURL("/"), "reject");
430
443
  let userReplace = opts && opts.replace != null ? opts.replace : void 0;
431
444
  let historyAction = "PUSH";
432
445
  if (userReplace === true) historyAction = "REPLACE";
@@ -494,14 +507,13 @@ function createRouter(init) {
494
507
  saveScrollPosition(state.location, state.matches);
495
508
  pendingPreventScrollReset = (opts && opts.preventScrollReset) === true;
496
509
  pendingViewTransitionEnabled = (opts && opts.enableViewTransition) === true;
497
- let routesToUse = dataRoutes.activeRoutes;
498
- let matches = opts?.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ? state.matches : matchRoutesImpl(routesToUse, location, basename, false, dataRoutes.branches);
510
+ let matches = opts?.initialHydration && state.matches && state.matches.length > 0 && !initialMatchesIsFOW ? state.matches : dataRouteMatcher.match(location);
499
511
  let flushSync = (opts && opts.flushSync) === true;
500
512
  if (matches && state.initialized && !isRevalidationRequired && isHashChangeOnly(state.location, location) && !(opts && opts.submission && isMutationMethod(opts.submission.formMethod))) {
501
513
  completeNavigation(location, { matches }, { flushSync });
502
514
  return;
503
515
  }
504
- let fogOfWar = checkFogOfWar(matches, routesToUse, location.pathname);
516
+ let fogOfWar = checkFogOfWar(matches, location.pathname);
505
517
  if (fogOfWar.active && fogOfWar.matches) matches = fogOfWar.matches;
506
518
  if (opts?.instrumentationNavigateMetaReceiver) {
507
519
  let meta = getInstrumentationNavigateMeta(init.history, location, matches);
@@ -687,8 +699,7 @@ function createRouter(init) {
687
699
  };
688
700
  } else matches = discoverResult.matches;
689
701
  }
690
- let routesToUse = dataRoutes.activeRoutes;
691
- let { dsMatches, revalidatingFetchers } = getMatchesToLoad(request, scopedContext, mapRouteProperties, manifest, init.history, state, matches, activeSubmission, location, initialHydration ? [] : hydrationRouteProperties, initialHydration === true, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, init.patchRoutesOnNavigation != null, dataRoutes.branches, pendingActionResult, callSiteDefaultShouldRevalidate);
702
+ let { dsMatches, revalidatingFetchers } = getMatchesToLoad(request, scopedContext, mapRouteProperties, manifest, init.history, state, matches, activeSubmission, location, initialHydration ? [] : hydrationRouteProperties, initialHydration === true, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, dataRouteMatcher, pendingActionResult, callSiteDefaultShouldRevalidate);
692
703
  pendingNavigationLoadId = ++incrementingLoadId;
693
704
  if (!init.dataStrategy && !dsMatches.some((m) => m.shouldLoad) && !dsMatches.some((m) => m.route.middleware && m.route.middleware.length > 0) && revalidatingFetchers.length === 0) {
694
705
  let workingFetchers = new Map(state.fetchers);
@@ -767,10 +778,9 @@ function createRouter(init) {
767
778
  abortFetcher(key);
768
779
  let flushSync = (opts && opts.flushSync) === true;
769
780
  let instrumentationResultMetaReceiver = consumeInstrumentationClientResultMetaReceiver(router);
770
- let routesToUse = dataRoutes.activeRoutes;
771
781
  let normalizedPath = normalizeTo(state.location, state.matches, basename, href, routeId, opts?.relative);
772
- let matches = matchRoutesImpl(routesToUse, normalizedPath, basename, false, dataRoutes.branches);
773
- let fogOfWar = checkFogOfWar(matches, routesToUse, normalizedPath);
782
+ let matches = dataRouteMatcher.match(normalizedPath);
783
+ let fogOfWar = checkFogOfWar(matches, normalizedPath);
774
784
  if (fogOfWar.active && fogOfWar.matches) matches = fogOfWar.matches;
775
785
  if (instrumentationResultMetaReceiver) instrumentationResultMetaReceiver(getInstrumentationNavigateMeta(init.history, normalizedPath, matches));
776
786
  if (!matches) {
@@ -788,11 +798,13 @@ function createRouter(init) {
788
798
  await handleFetcherAction(key, routeId, path, matches, scopedContext, fogOfWar.active, flushSync, preventScrollReset, submission, opts && opts.defaultShouldRevalidate);
789
799
  return;
790
800
  }
791
- fetchLoadMatches.set(key, {
801
+ let loadMatch = {
792
802
  routeId,
793
- path
794
- });
795
- await handleFetcherLoader(key, routeId, path, matches, scopedContext, fogOfWar.active, flushSync, preventScrollReset, submission);
803
+ path,
804
+ isDiscovering: fogOfWar.active
805
+ };
806
+ fetchLoadMatches.set(key, loadMatch);
807
+ await handleFetcherLoader(key, loadMatch, matches, scopedContext, flushSync, preventScrollReset, submission);
796
808
  }
797
809
  async function handleFetcherAction(key, routeId, path, requestMatches, scopedContext, isFogOfWar, flushSync, preventScrollReset, submission, callSiteDefaultShouldRevalidate) {
798
810
  interruptActiveLoads();
@@ -862,12 +874,11 @@ function createRouter(init) {
862
874
  }
863
875
  let nextLocation = state.navigation.location || state.location;
864
876
  let revalidationRequest = createClientSideRequest(init.history, nextLocation, abortController.signal);
865
- let routesToUse = dataRoutes.activeRoutes;
866
- let matches = state.navigation.state !== "idle" ? matchRoutesImpl(routesToUse, state.navigation.location, basename, false, dataRoutes.branches) : state.matches;
877
+ let matches = state.navigation.state !== "idle" ? dataRouteMatcher.match(state.navigation.location) : state.matches;
867
878
  invariant(matches, "Didn't find any matches after fetcher action");
868
879
  let loadId = ++incrementingLoadId;
869
880
  fetchReloadIds.set(key, loadId);
870
- let { dsMatches, revalidatingFetchers } = getMatchesToLoad(revalidationRequest, scopedContext, mapRouteProperties, manifest, init.history, state, matches, submission, nextLocation, hydrationRouteProperties, false, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, init.patchRoutesOnNavigation != null, dataRoutes.branches, [match.route.id, actionResult], callSiteDefaultShouldRevalidate);
881
+ let { dsMatches, revalidatingFetchers } = getMatchesToLoad(revalidationRequest, scopedContext, mapRouteProperties, manifest, init.history, state, matches, submission, nextLocation, hydrationRouteProperties, false, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, dataRouteMatcher, [match.route.id, actionResult], callSiteDefaultShouldRevalidate);
871
882
  let loadFetcher = getLoadingFetcher(submission, actionResult.data);
872
883
  let workingFetchers = new Map(state.fetchers);
873
884
  workingFetchers.set(key, loadFetcher);
@@ -883,7 +894,10 @@ function createRouter(init) {
883
894
  let abortPendingFetchRevalidations = () => revalidatingFetchers.forEach((rf) => abortFetcher(rf.key));
884
895
  abortController.signal.addEventListener("abort", abortPendingFetchRevalidations);
885
896
  let { loaderResults, fetcherResults } = await callLoadersAndMaybeResolveData(dsMatches, revalidatingFetchers, revalidationRequest, nextLocation, scopedContext);
886
- if (abortController.signal.aborted) return;
897
+ if (abortController.signal.aborted) {
898
+ if (fetchReloadIds.get(key) === loadId) fetchReloadIds.delete(key);
899
+ return;
900
+ }
887
901
  abortController.signal.removeEventListener("abort", abortPendingFetchRevalidations);
888
902
  fetchReloadIds.delete(key);
889
903
  fetchControllers.delete(key);
@@ -931,12 +945,13 @@ function createRouter(init) {
931
945
  isRevalidationRequired = false;
932
946
  }
933
947
  }
934
- async function handleFetcherLoader(key, routeId, path, matches, scopedContext, isFogOfWar, flushSync, preventScrollReset, submission) {
948
+ async function handleFetcherLoader(key, loadMatch, matches, scopedContext, flushSync, preventScrollReset, submission) {
949
+ let { routeId, path } = loadMatch;
935
950
  let existingFetcher = state.fetchers.get(key);
936
951
  updateFetcherState(key, getLoadingFetcher(submission, existingFetcher ? existingFetcher.data : void 0), { flushSync });
937
952
  let abortController = new AbortController();
938
953
  let fetchRequest = createClientSideRequest(init.history, path, abortController.signal);
939
- if (isFogOfWar) {
954
+ if (loadMatch.isDiscovering) {
940
955
  let discoverResult = await discoverRoutes(matches, new URL(fetchRequest.url).pathname, fetchRequest.signal, key);
941
956
  if (discoverResult.type === "aborted") return;
942
957
  else if (discoverResult.type === "error") {
@@ -945,7 +960,10 @@ function createRouter(init) {
945
960
  } else if (!discoverResult.matches) {
946
961
  setFetcherError(key, routeId, getInternalRouterError(404, { pathname: path }), { flushSync });
947
962
  return;
948
- } else matches = discoverResult.matches;
963
+ } else {
964
+ matches = discoverResult.matches;
965
+ loadMatch.isDiscovering = false;
966
+ }
949
967
  }
950
968
  let match = getTargetMatch(matches, path);
951
969
  fetchControllers.set(key, abortController);
@@ -1005,7 +1023,10 @@ function createRouter(init) {
1005
1023
  if (redirect.response.headers.has("X-Remix-Revalidate")) isRevalidationRequired = true;
1006
1024
  let location = redirect.response.headers.get("Location");
1007
1025
  invariant(location, "Expected a Location header on the redirect Response");
1008
- location = normalizeRedirectLocation(location, new URL(request.url), basename, init.history);
1026
+ let originalLocation = location;
1027
+ let currentUrl = new URL(request.url);
1028
+ location = normalizeRedirectLocation(location, currentUrl, basename, init.history);
1029
+ validateNavigationTarget(originalLocation, location, currentUrl, "allow-explicit");
1009
1030
  let redirectLocation = createLocation(state.location, location, { _isRedirect: true });
1010
1031
  if (isBrowser) {
1011
1032
  let isDocumentReload = false;
@@ -1251,16 +1272,15 @@ function createRouter(init) {
1251
1272
  }
1252
1273
  return null;
1253
1274
  }
1254
- function checkFogOfWar(matches, routesToUse, pathname) {
1275
+ function checkFogOfWar(matches, pathname) {
1255
1276
  if (init.patchRoutesOnNavigation) {
1256
- let activeBranches = dataRoutes.branches;
1257
1277
  if (!matches) return {
1258
1278
  active: true,
1259
- matches: matchRoutesImpl(routesToUse, pathname, basename, true, activeBranches) || []
1279
+ matches: dataRouteMatcher.match(pathname, true) || []
1260
1280
  };
1261
1281
  else if (Object.keys(matches[0].params).length > 0) return {
1262
1282
  active: true,
1263
- matches: matchRoutesImpl(routesToUse, pathname, basename, true, activeBranches)
1283
+ matches: dataRouteMatcher.match(pathname, true)
1264
1284
  };
1265
1285
  }
1266
1286
  return {
@@ -1295,21 +1315,20 @@ function createRouter(init) {
1295
1315
  };
1296
1316
  }
1297
1317
  if (signal.aborted) return { type: "aborted" };
1298
- let activeBranches = dataRoutes.branches;
1299
- let newMatches = matchRoutesImpl(dataRoutes.activeRoutes, pathname, basename, false, activeBranches);
1318
+ let newMatches = dataRouteMatcher.match(pathname);
1300
1319
  let newPartialMatches = null;
1301
1320
  if (newMatches) if (Object.keys(newMatches[0].params).length === 0) return {
1302
1321
  type: "success",
1303
1322
  matches: newMatches
1304
1323
  };
1305
1324
  else {
1306
- newPartialMatches = matchRoutesImpl(dataRoutes.activeRoutes, pathname, basename, true, activeBranches);
1325
+ newPartialMatches = dataRouteMatcher.match(pathname, true);
1307
1326
  if (!(newPartialMatches && partialMatches.length < newPartialMatches.length && compareMatches(partialMatches, newPartialMatches.slice(0, partialMatches.length)))) return {
1308
1327
  type: "success",
1309
1328
  matches: newMatches
1310
1329
  };
1311
1330
  }
1312
- if (!newPartialMatches) newPartialMatches = matchRoutesImpl(dataRoutes.activeRoutes, pathname, basename, true, activeBranches);
1331
+ if (!newPartialMatches) newPartialMatches = dataRouteMatcher.match(pathname, true);
1313
1332
  if (!newPartialMatches || compareMatches(partialMatches, newPartialMatches)) return {
1314
1333
  type: "success",
1315
1334
  matches: null
@@ -1341,8 +1360,8 @@ function createRouter(init) {
1341
1360
  get routes() {
1342
1361
  return dataRoutes.stableRoutes;
1343
1362
  },
1344
- get branches() {
1345
- return dataRoutes.branches;
1363
+ match(locationArg) {
1364
+ return dataRouteMatcher.match(locationArg);
1346
1365
  },
1347
1366
  get manifest() {
1348
1367
  return manifest;
@@ -1357,6 +1376,7 @@ function createRouter(init) {
1357
1376
  fetch,
1358
1377
  revalidate,
1359
1378
  createHref: (to) => init.history.createHref(to),
1379
+ createURL: (to) => init.history.createURL(to),
1360
1380
  encodeLocation: (to) => init.history.encodeLocation(to),
1361
1381
  getFetcher,
1362
1382
  resetFetcher,
@@ -1410,7 +1430,7 @@ function createStaticHandler(routes, opts) {
1410
1430
  let basename = (opts ? opts.basename : null) || "/";
1411
1431
  let _mapRouteProperties = opts?.mapRouteProperties;
1412
1432
  let mapRouteProperties = _mapRouteProperties ? _mapRouteProperties : () => ({});
1413
- ({ ...opts?.future });
1433
+ let dataRouteMatcher = createDataRouteMatcher({ ...opts?.future }, basename);
1414
1434
  if (opts?.instrumentations) {
1415
1435
  let instrumentations = opts.instrumentations;
1416
1436
  mapRouteProperties = (route) => {
@@ -1421,7 +1441,8 @@ function createStaticHandler(routes, opts) {
1421
1441
  };
1422
1442
  }
1423
1443
  let dataRoutes = convertRoutesToDataRoutes(routes, mapRouteProperties, void 0, manifest);
1424
- let routeBranches = flattenAndRankRoutes(dataRoutes);
1444
+ dataRouteMatcher.update(dataRoutes);
1445
+ let match = (locationArg) => dataRouteMatcher.match(locationArg);
1425
1446
  /**
1426
1447
  * The query() method is intended for document requests, in which we want to
1427
1448
  * call an optional action and potentially multiple loaders for all nested
@@ -1452,7 +1473,7 @@ function createStaticHandler(routes, opts) {
1452
1473
  let normalizePathImpl = normalizePath || defaultNormalizePath;
1453
1474
  let method = request.method;
1454
1475
  let location = createLocation("", normalizePathImpl(request), null, "default");
1455
- let matches = matchRoutesImpl(dataRoutes, location, basename, false, routeBranches);
1476
+ let matches = dataRouteMatcher.match(location);
1456
1477
  requestContext = requestContext != null ? requestContext : new RouterContextProvider();
1457
1478
  if (!isValidMethod(method) && method !== "HEAD") {
1458
1479
  let error = getInternalRouterError(405, { method });
@@ -1466,7 +1487,8 @@ function createStaticHandler(routes, opts) {
1466
1487
  errors: { [route.id]: error },
1467
1488
  statusCode: error.status,
1468
1489
  loaderHeaders: {},
1469
- actionHeaders: {}
1490
+ actionHeaders: {},
1491
+ _match: match
1470
1492
  };
1471
1493
  return generateMiddlewareResponse ? generateMiddlewareResponse(() => Promise.resolve(staticContext)) : staticContext;
1472
1494
  } else if (!matches) {
@@ -1481,7 +1503,8 @@ function createStaticHandler(routes, opts) {
1481
1503
  errors: { [route.id]: error },
1482
1504
  statusCode: error.status,
1483
1505
  loaderHeaders: {},
1484
- actionHeaders: {}
1506
+ actionHeaders: {},
1507
+ _match: match
1485
1508
  };
1486
1509
  return generateMiddlewareResponse ? generateMiddlewareResponse(() => Promise.resolve(staticContext)) : staticContext;
1487
1510
  }
@@ -1504,7 +1527,8 @@ function createStaticHandler(routes, opts) {
1504
1527
  renderedStaticContext = {
1505
1528
  location,
1506
1529
  basename,
1507
- ...result
1530
+ ...result,
1531
+ _match: match
1508
1532
  };
1509
1533
  return renderedStaticContext;
1510
1534
  });
@@ -1530,7 +1554,8 @@ function createStaticHandler(routes, opts) {
1530
1554
  errors: { [skipLoaderErrorBubbling ? routeId : findNearestBoundary(matches, matches.find((m) => m.route.id === routeId || m.route.loader)?.route.id || routeId).route.id]: error },
1531
1555
  statusCode: isRouteErrorResponse(error) ? error.status : 500,
1532
1556
  actionHeaders: {},
1533
- loaderHeaders: {}
1557
+ loaderHeaders: {},
1558
+ _match: match
1534
1559
  };
1535
1560
  return generateMiddlewareResponse(() => Promise.resolve(staticContext));
1536
1561
  }
@@ -1547,7 +1572,8 @@ function createStaticHandler(routes, opts) {
1547
1572
  return {
1548
1573
  location,
1549
1574
  basename,
1550
- ...result
1575
+ ...result,
1576
+ _match: match
1551
1577
  };
1552
1578
  }
1553
1579
  /**
@@ -1580,7 +1606,7 @@ function createStaticHandler(routes, opts) {
1580
1606
  let normalizePathImpl = normalizePath || defaultNormalizePath;
1581
1607
  let method = request.method;
1582
1608
  let location = createLocation("", normalizePathImpl(request), null, "default");
1583
- let matches = matchRoutesImpl(dataRoutes, location, basename, false, routeBranches);
1609
+ let matches = dataRouteMatcher.match(location);
1584
1610
  requestContext = requestContext != null ? requestContext : new RouterContextProvider();
1585
1611
  if (!isValidMethod(method) && method !== "HEAD" && method !== "OPTIONS") throw getInternalRouterError(405, { method });
1586
1612
  else if (!matches) throw getInternalRouterError(404, { pathname: location.pathname });
@@ -1761,7 +1787,7 @@ function createStaticHandler(routes, opts) {
1761
1787
  }
1762
1788
  return {
1763
1789
  dataRoutes,
1764
- _internalRouteBranches: routeBranches,
1790
+ match,
1765
1791
  query,
1766
1792
  queryRoute
1767
1793
  };
@@ -1876,7 +1902,7 @@ function normalizeNavigateOptions(isFetcher, path, opts) {
1876
1902
  text: void 0
1877
1903
  }
1878
1904
  };
1879
- } catch (e) {
1905
+ } catch {
1880
1906
  return getInvalidBodyError();
1881
1907
  }
1882
1908
  }
@@ -1899,7 +1925,7 @@ function normalizeNavigateOptions(isFetcher, path, opts) {
1899
1925
  } else try {
1900
1926
  searchParams = new URLSearchParams(opts.body);
1901
1927
  formData = convertSearchParamsToFormData(searchParams);
1902
- } catch (e) {
1928
+ } catch {
1903
1929
  return getInvalidBodyError();
1904
1930
  }
1905
1931
  let submission = {
@@ -1922,7 +1948,7 @@ function normalizeNavigateOptions(isFetcher, path, opts) {
1922
1948
  submission
1923
1949
  };
1924
1950
  }
1925
- function getMatchesToLoad(request, scopedContext, mapRouteProperties, manifest, history, state, matches, submission, location, lazyRoutePropertiesToSkip, initialHydration, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, routesToUse, basename, hasPatchRoutesOnNavigation, branches, pendingActionResult, callSiteDefaultShouldRevalidate) {
1951
+ function getMatchesToLoad(request, scopedContext, mapRouteProperties, manifest, history, state, matches, submission, location, lazyRoutePropertiesToSkip, initialHydration, isRevalidationRequired, cancelledFetcherLoads, fetchersQueuedForDeletion, fetchLoadMatches, fetchRedirectIds, dataRouteMatcher, pendingActionResult, callSiteDefaultShouldRevalidate) {
1926
1952
  let actionResult = pendingActionResult ? isErrorResult(pendingActionResult[1]) ? pendingActionResult[1].error : pendingActionResult[1].data : void 0;
1927
1953
  let currentUrl = history.createURL(state.location);
1928
1954
  let nextUrl = history.createURL(location);
@@ -1972,12 +1998,11 @@ function getMatchesToLoad(request, scopedContext, mapRouteProperties, manifest,
1972
1998
  });
1973
1999
  let revalidatingFetchers = [];
1974
2000
  fetchLoadMatches.forEach((f, key) => {
1975
- if (initialHydration || !matches.some((m) => m.route.id === f.routeId) || fetchersQueuedForDeletion.has(key)) return;
2001
+ if (initialHydration || !matches.some((m) => m.route.id === f.routeId) || fetchersQueuedForDeletion.has(key) || f.isDiscovering) return;
1976
2002
  let fetcher = state.fetchers.get(key);
1977
2003
  let isMidInitialLoad = fetcher && fetcher.state !== "idle" && fetcher.data === void 0;
1978
- let fetcherMatches = matchRoutesImpl(routesToUse, f.path, basename ?? "/", false, branches);
2004
+ let fetcherMatches = dataRouteMatcher.match(f.path);
1979
2005
  if (!fetcherMatches) {
1980
- if (hasPatchRoutesOnNavigation && isMidInitialLoad) return;
1981
2006
  revalidatingFetchers.push({
1982
2007
  key,
1983
2008
  routeId: f.routeId,
@@ -2415,7 +2440,7 @@ async function callDataStrategyImpl(dataStrategyImpl, request, path, matches, fe
2415
2440
  });
2416
2441
  try {
2417
2442
  await Promise.all(matches.flatMap((m) => [m._lazyPromises?.handler, m._lazyPromises?.route]));
2418
- } catch (e) {}
2443
+ } catch {}
2419
2444
  return results;
2420
2445
  }
2421
2446
  async function callLoaderOrAction({ request, path, pattern, match, lazyHandlerPromise, lazyRoutePromise, handlerOverride, scopedContext }) {
@@ -2598,7 +2623,7 @@ function normalizeRedirectLocation(location, currentUrl, basename, historyInstan
2598
2623
  }
2599
2624
  try {
2600
2625
  if (hasInvalidProtocol(historyInstance.createURL(location).toString())) throw new Error("Invalid redirect location");
2601
- } catch (e) {}
2626
+ } catch {}
2602
2627
  return location;
2603
2628
  }
2604
2629
  function createClientSideRequest(history, location, signal, submission) {
@@ -2703,12 +2728,16 @@ function mergeLoaderData(loaderData, newLoaderData, matches, errors) {
2703
2728
  merged[k] = v;
2704
2729
  return merged;
2705
2730
  }, {});
2731
+ let preservedCount = 0;
2706
2732
  for (let match of matches) {
2707
2733
  let id = match.route.id;
2708
- if (!newLoaderData.hasOwnProperty(id) && loaderData.hasOwnProperty(id) && match.route.loader) mergedLoaderData[id] = loaderData[id];
2734
+ if (!newLoaderData.hasOwnProperty(id) && loaderData.hasOwnProperty(id) && match.route.loader) {
2735
+ mergedLoaderData[id] = loaderData[id];
2736
+ preservedCount++;
2737
+ }
2709
2738
  if (errors && errors.hasOwnProperty(id)) break;
2710
2739
  }
2711
- return mergedLoaderData;
2740
+ return Object.keys(newLoaderData).length === 0 && preservedCount === Object.keys(loaderData).length ? loaderData : mergedLoaderData;
2712
2741
  }
2713
2742
  function getActionDataForCommit(pendingActionResult) {
2714
2743
  if (!pendingActionResult) return {};
@@ -2948,7 +2977,7 @@ function restoreAppliedTransitions(_window, transitions) {
2948
2977
  let json = JSON.parse(sessionPositions);
2949
2978
  for (let [k, v] of Object.entries(json || {})) if (v && Array.isArray(v)) transitions.set(k, new Set(v || []));
2950
2979
  }
2951
- } catch (e) {}
2980
+ } catch {}
2952
2981
  }
2953
2982
  function persistAppliedTransitions(_window, transitions) {
2954
2983
  if (transitions.size > 0) {
@@ -2969,13 +2998,13 @@ function createDeferred() {
2969
2998
  res(val);
2970
2999
  try {
2971
3000
  await promise;
2972
- } catch (e) {}
3001
+ } catch {}
2973
3002
  };
2974
3003
  reject = async (error) => {
2975
3004
  rej(error);
2976
3005
  try {
2977
3006
  await promise;
2978
- } catch (e) {}
3007
+ } catch {}
2979
3008
  };
2980
3009
  });
2981
3010
  return {
@@ -2985,4 +3014,4 @@ function createDeferred() {
2985
3014
  };
2986
3015
  }
2987
3016
  //#endregion
2988
- export { IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, createRouter, createStaticHandler, getStaticContextFromError, hasInvalidProtocol, isDataWithResponseInit, isMutationMethod, isRedirectResponse, isRedirectStatusCode, isResponse };
3017
+ export { IDLE_BLOCKER, IDLE_FETCHER, IDLE_NAVIGATION, createDataRouteMatcher, createRouter, createStaticHandler, getStaticContextFromError, hasInvalidProtocol, isDataWithResponseInit, isMutationMethod, isRedirectResponse, isRedirectStatusCode, isResponse };
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.0
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -430,7 +430,7 @@ interface MapRoutePropertiesFunction {
430
430
  * onto the route. Either they're meaningful to the router, or they'll get
431
431
  * ignored.
432
432
  */
433
- type UnsupportedLazyRouteObjectKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children";
433
+ type UnsupportedLazyRouteObjectKey = "lazy" | "caseSensitive" | "path" | "id" | "index" | "children" | "unstable_validateParams";
434
434
  /**
435
435
  * Keys we cannot change from within a lazy() function. We spread all other keys
436
436
  * onto the route. Either they're meaningful to the router, or they'll get
@@ -488,6 +488,11 @@ type BaseRouteObject = {
488
488
  * See [`shouldRevalidate`](../../start/data/route-object#shouldRevalidate).
489
489
  */
490
490
  shouldRevalidate?: ShouldRevalidateFunction;
491
+ /**
492
+ * A map of route param names to regular expressions used to validate params
493
+ * after a route-pattern match.
494
+ */
495
+ unstable_validateParams?: Record<string, RegExp>;
491
496
  /**
492
497
  * The route handle.
493
498
  */
@@ -1,5 +1,5 @@
1
1
  /**
2
- * react-router v8.3.0
2
+ * react-router v8.4.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -135,7 +135,8 @@ const unsupportedLazyRouteObjectKeys = new Set([
135
135
  "path",
136
136
  "id",
137
137
  "index",
138
- "children"
138
+ "children",
139
+ "unstable_validateParams"
139
140
  ]);
140
141
  function isUnsupportedLazyRouteObjectKey(key) {
141
142
  return unsupportedLazyRouteObjectKeys.has(key);
@@ -147,7 +148,8 @@ const unsupportedLazyRouteFunctionKeys = new Set([
147
148
  "id",
148
149
  "index",
149
150
  "middleware",
150
- "children"
151
+ "children",
152
+ "unstable_validateParams"
151
153
  ]);
152
154
  function isUnsupportedLazyRouteFunctionKey(key) {
153
155
  return unsupportedLazyRouteFunctionKeys.has(key);
@@ -489,13 +491,13 @@ function matchPathImpl(pattern, pathname, matcher, compiledParams) {
489
491
  let match = pathname.match(matcher);
490
492
  if (!match) return null;
491
493
  let matchedPathname = match[0];
492
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
494
+ let pathnameBase = removeTrailingSlash(matchedPathname, 1);
493
495
  let captureGroups = match.slice(1);
494
496
  return {
495
497
  params: compiledParams.reduce((memo, { paramName, isOptional }, index) => {
496
498
  if (paramName === "*") {
497
499
  let splatValue = captureGroups[index] || "";
498
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
500
+ pathnameBase = removeTrailingSlash(matchedPathname.slice(0, matchedPathname.length - splatValue.length), 1);
499
501
  }
500
502
  const value = captureGroups[index];
501
503
  if (isOptional && !value) memo[paramName] = void 0;
@@ -564,7 +566,7 @@ function resolvePath(to, fromPathname = "/") {
564
566
  let pathname;
565
567
  if (toPathname) {
566
568
  toPathname = removeDoubleSlashes(toPathname);
567
- if (toPathname.startsWith("/")) pathname = resolvePathname(toPathname.substring(1), "/");
569
+ if (toPathname.startsWith("/") || toPathname.startsWith("\\")) pathname = resolvePathname(toPathname.substring(1), "/");
568
570
  else pathname = resolvePathname(toPathname, fromPathname);
569
571
  } else pathname = fromPathname;
570
572
  return {
@@ -625,7 +627,11 @@ function resolveTo(toArg, routePathnames, locationPathname, isPathRelative = fal
625
627
  }
626
628
  const removeDoubleSlashes = (path) => path.replace(/[\\/]{2,}/g, "/");
627
629
  const joinPaths = (paths) => removeDoubleSlashes(paths.join("/"));
628
- const removeTrailingSlash = (path) => path.replace(/\/+$/, "");
630
+ function removeTrailingSlash(path, minLength = 0) {
631
+ let end = path.length;
632
+ while (end > minLength && path.charCodeAt(end - 1) === 47) end--;
633
+ return end === path.length ? path : path.slice(0, end);
634
+ }
629
635
  const normalizePathname = (pathname) => removeTrailingSlash(pathname).replace(/^\/*/, "/");
630
636
  const normalizeSearch = (search) => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
631
637
  const normalizeHash = (hash) => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
@@ -868,7 +874,7 @@ function parseToInfo(_to, basename) {
868
874
  let path = stripBasename(targetUrl.pathname, basename);
869
875
  if (targetUrl.origin === currentUrl.origin && path != null) to = path + targetUrl.search + targetUrl.hash;
870
876
  else isExternal = true;
871
- } catch (e) {
877
+ } catch {
872
878
  warning(false, `<Link to="${to}"> contains an invalid URL which will probably break when clicked - please update to a valid URL path.`);
873
879
  }
874
880
  return {
@@ -878,4 +884,4 @@ function parseToInfo(_to, basename) {
878
884
  };
879
885
  }
880
886
  //#endregion
881
- export { ErrorResponseImpl, RouterContextProvider, SUPPORTED_ERROR_TYPES, compilePath, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createContext, createDataFunctionUrl, data, decodePath, defaultMapRouteProperties, encodePathParam, flattenAndRankRoutes, generatePath, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isBrowser, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, joinPaths, matchPath, matchRoutes, matchRoutesImpl, parseToInfo, prependBasename, redirect, redirectDocument, removeDoubleSlashes, removeTrailingSlash, replace, resolvePath, resolveTo, stripBasename };
887
+ export { ErrorResponseImpl, RouterContextProvider, SUPPORTED_ERROR_TYPES, compilePath, convertRouteMatchToUiMatch, convertRoutesToDataRoutes, createContext, createDataFunctionUrl, data, decodePath, defaultMapRouteProperties, encodePathParam, explodeOptionalSegments, flattenAndRankRoutes, generatePath, getPathContributingMatches, getResolveToMatches, getRoutePattern, isAbsoluteUrl, isBrowser, isRouteErrorResponse, isUnsupportedLazyRouteFunctionKey, isUnsupportedLazyRouteObjectKey, joinPaths, matchPath, matchRoutes, matchRoutesImpl, normalizePathname, parseToInfo, prependBasename, redirect, redirectDocument, removeDoubleSlashes, removeTrailingSlash, replace, resolvePath, resolveTo, stripBasename };