react-router 6.2.1 → 6.3.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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * React Router v6.2.1
2
+ * React Router v6.3.0
3
3
  *
4
4
  * Copyright (c) Remix Software Inc.
5
5
  *
@@ -8,13 +8,34 @@
8
8
  *
9
9
  * @license MIT
10
10
  */
11
- import { createContext, useRef, useState, useLayoutEffect, createElement, useContext, useEffect, useMemo, useCallback, Children, isValidElement, Fragment } from 'react';
12
- import { createMemoryHistory, Action, parsePath } from 'history';
11
+ import { parsePath, createMemoryHistory, Action } from 'history';
12
+ export { Action as NavigationType, createPath, parsePath } from 'history';
13
+ import { createContext, useContext, useMemo, useRef, useEffect, useCallback, createElement, useState, useLayoutEffect, Children, isValidElement, Fragment } from 'react';
14
+
15
+ const NavigationContext = /*#__PURE__*/createContext(null);
16
+
17
+ {
18
+ NavigationContext.displayName = "Navigation";
19
+ }
20
+
21
+ const LocationContext = /*#__PURE__*/createContext(null);
22
+
23
+ {
24
+ LocationContext.displayName = "Location";
25
+ }
26
+
27
+ const RouteContext = /*#__PURE__*/createContext({
28
+ outlet: null,
29
+ matches: []
30
+ });
31
+
32
+ {
33
+ RouteContext.displayName = "Route";
34
+ }
13
35
 
14
36
  function invariant(cond, message) {
15
37
  if (!cond) throw new Error(message);
16
38
  }
17
-
18
39
  function warning(cond, message) {
19
40
  if (!cond) {
20
41
  // eslint-disable-next-line no-console
@@ -30,216 +51,353 @@ function warning(cond, message) {
30
51
  } catch (e) {}
31
52
  }
32
53
  }
33
-
34
54
  const alreadyWarned = {};
35
-
36
55
  function warningOnce(key, cond, message) {
37
56
  if (!cond && !alreadyWarned[key]) {
38
57
  alreadyWarned[key] = true;
39
58
  warning(false, message) ;
40
59
  }
41
- } ///////////////////////////////////////////////////////////////////////////////
42
- // CONTEXT
43
- ///////////////////////////////////////////////////////////////////////////////
60
+ }
44
61
 
45
62
  /**
46
- * A Navigator is a "location changer"; it's how you get to different locations.
63
+ * Returns a path with params interpolated.
47
64
  *
48
- * Every history instance conforms to the Navigator interface, but the
49
- * distinction is useful primarily when it comes to the low-level <Router> API
50
- * where both the location and a navigator must be provided separately in order
51
- * to avoid "tearing" that may occur in a suspense-enabled app if the action
52
- * and/or location were to be read directly from the history instance.
65
+ * @see https://reactrouter.com/docs/en/v6/api#generatepath
66
+ */
67
+ function generatePath(path, params = {}) {
68
+ return path.replace(/:(\w+)/g, (_, key) => {
69
+ !(params[key] != null) ? invariant(false, `Missing ":${key}" param`) : void 0;
70
+ return params[key];
71
+ }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
72
+ }
73
+ /**
74
+ * A RouteMatch contains info about how a route matched a URL.
53
75
  */
54
76
 
77
+ /**
78
+ * Matches the given routes to a location and returns the match data.
79
+ *
80
+ * @see https://reactrouter.com/docs/en/v6/api#matchroutes
81
+ */
82
+ function matchRoutes(routes, locationArg, basename = "/") {
83
+ let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
84
+ let pathname = stripBasename(location.pathname || "/", basename);
55
85
 
56
- const NavigationContext = /*#__PURE__*/createContext(null);
86
+ if (pathname == null) {
87
+ return null;
88
+ }
57
89
 
58
- {
59
- NavigationContext.displayName = "Navigation";
60
- }
90
+ let branches = flattenRoutes(routes);
91
+ rankRouteBranches(branches);
92
+ let matches = null;
61
93
 
62
- const LocationContext = /*#__PURE__*/createContext(null);
94
+ for (let i = 0; matches == null && i < branches.length; ++i) {
95
+ matches = matchRouteBranch(branches[i], pathname);
96
+ }
63
97
 
64
- {
65
- LocationContext.displayName = "Location";
98
+ return matches;
66
99
  }
67
100
 
68
- const RouteContext = /*#__PURE__*/createContext({
69
- outlet: null,
70
- matches: []
71
- });
101
+ function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
102
+ routes.forEach((route, index) => {
103
+ let meta = {
104
+ relativePath: route.path || "",
105
+ caseSensitive: route.caseSensitive === true,
106
+ childrenIndex: index,
107
+ route
108
+ };
72
109
 
73
- {
74
- RouteContext.displayName = "Route";
75
- } ///////////////////////////////////////////////////////////////////////////////
76
- // COMPONENTS
77
- ///////////////////////////////////////////////////////////////////////////////
110
+ if (meta.relativePath.startsWith("/")) {
111
+ !meta.relativePath.startsWith(parentPath) ? invariant(false, `Absolute route path "${meta.relativePath}" nested under path ` + `"${parentPath}" is not valid. An absolute child route path ` + `must start with the combined path of all its parent routes.`) : void 0;
112
+ meta.relativePath = meta.relativePath.slice(parentPath.length);
113
+ }
78
114
 
115
+ let path = joinPaths([parentPath, meta.relativePath]);
116
+ let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
117
+ // route tree depth-first and child routes appear before their parents in
118
+ // the "flattened" version.
79
119
 
80
- /**
81
- * A <Router> that stores all entries in memory.
82
- *
83
- * @see https://reactrouter.com/docs/en/v6/api#memoryrouter
84
- */
85
- function MemoryRouter({
86
- basename,
87
- children,
88
- initialEntries,
89
- initialIndex
90
- }) {
91
- let historyRef = useRef();
120
+ if (route.children && route.children.length > 0) {
121
+ !(route.index !== true) ? invariant(false, `Index routes must not have child routes. Please remove ` + `all child routes from route path "${path}".`) : void 0;
122
+ flattenRoutes(route.children, branches, routesMeta, path);
123
+ } // Routes without a path shouldn't ever match by themselves unless they are
124
+ // index routes, so don't add them to the list of possible branches.
92
125
 
93
- if (historyRef.current == null) {
94
- historyRef.current = createMemoryHistory({
95
- initialEntries,
96
- initialIndex
97
- });
98
- }
99
126
 
100
- let history = historyRef.current;
101
- let [state, setState] = useState({
102
- action: history.action,
103
- location: history.location
104
- });
105
- useLayoutEffect(() => history.listen(setState), [history]);
106
- return /*#__PURE__*/createElement(Router, {
107
- basename: basename,
108
- children: children,
109
- location: state.location,
110
- navigationType: state.action,
111
- navigator: history
112
- });
113
- }
127
+ if (route.path == null && !route.index) {
128
+ return;
129
+ }
114
130
 
115
- /**
116
- * Changes the current location.
117
- *
118
- * Note: This API is mostly useful in React.Component subclasses that are not
119
- * able to use hooks. In functional components, we recommend you use the
120
- * `useNavigate` hook instead.
121
- *
122
- * @see https://reactrouter.com/docs/en/v6/api#navigate
123
- */
124
- function Navigate({
125
- to,
126
- replace,
127
- state
128
- }) {
129
- !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of
130
- // the router loaded. We can help them understand how to avoid that.
131
- `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
132
- warning(!useContext(NavigationContext).static, `<Navigate> must not be used on the initial render in a <StaticRouter>. ` + `This is a no-op, but you should modify your code so the <Navigate> is ` + `only ever rendered in response to some user interaction or state change.`) ;
133
- let navigate = useNavigate();
134
- useEffect(() => {
135
- navigate(to, {
136
- replace,
137
- state
131
+ branches.push({
132
+ path,
133
+ score: computeScore(path, route.index),
134
+ routesMeta
138
135
  });
139
136
  });
140
- return null;
141
- }
142
-
143
- /**
144
- * Renders the child route's element, if there is one.
145
- *
146
- * @see https://reactrouter.com/docs/en/v6/api#outlet
147
- */
148
- function Outlet(props) {
149
- return useOutlet(props.context);
137
+ return branches;
150
138
  }
151
139
 
152
- /**
153
- * Declares an element that should be rendered at a certain URL path.
154
- *
155
- * @see https://reactrouter.com/docs/en/v6/api#route
156
- */
157
- function Route(_props) {
158
- invariant(false, `A <Route> is only ever to be used as the child of <Routes> element, ` + `never rendered directly. Please wrap your <Route> in a <Routes>.`) ;
140
+ function rankRouteBranches(branches) {
141
+ branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
142
+ : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
159
143
  }
160
144
 
161
- /**
162
- * Provides location context for the rest of the app.
163
- *
164
- * Note: You usually won't render a <Router> directly. Instead, you'll render a
165
- * router that is more specific to your environment such as a <BrowserRouter>
166
- * in web browsers or a <StaticRouter> for server rendering.
167
- *
168
- * @see https://reactrouter.com/docs/en/v6/api#router
169
- */
170
- function Router({
171
- basename: basenameProp = "/",
172
- children = null,
173
- location: locationProp,
174
- navigationType = Action.Pop,
175
- navigator,
176
- static: staticProp = false
177
- }) {
178
- !!useInRouterContext() ? invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0;
179
- let basename = normalizePathname(basenameProp);
180
- let navigationContext = useMemo(() => ({
181
- basename,
182
- navigator,
183
- static: staticProp
184
- }), [basename, navigator, staticProp]);
185
-
186
- if (typeof locationProp === "string") {
187
- locationProp = parsePath(locationProp);
188
- }
145
+ const paramRe = /^:\w+$/;
146
+ const dynamicSegmentValue = 3;
147
+ const indexRouteValue = 2;
148
+ const emptySegmentValue = 1;
149
+ const staticSegmentValue = 10;
150
+ const splatPenalty = -2;
189
151
 
190
- let {
191
- pathname = "/",
192
- search = "",
193
- hash = "",
194
- state = null,
195
- key = "default"
196
- } = locationProp;
197
- let location = useMemo(() => {
198
- let trailingPathname = stripBasename(pathname, basename);
152
+ const isSplat = s => s === "*";
199
153
 
200
- if (trailingPathname == null) {
201
- return null;
202
- }
154
+ function computeScore(path, index) {
155
+ let segments = path.split("/");
156
+ let initialScore = segments.length;
203
157
 
204
- return {
205
- pathname: trailingPathname,
206
- search,
207
- hash,
208
- state,
209
- key
210
- };
211
- }, [basename, pathname, search, hash, state, key]);
212
- warning(location != null, `<Router basename="${basename}"> is not able to match the URL ` + `"${pathname}${search}${hash}" because it does not start with the ` + `basename, so the <Router> won't render anything.`) ;
158
+ if (segments.some(isSplat)) {
159
+ initialScore += splatPenalty;
160
+ }
213
161
 
214
- if (location == null) {
215
- return null;
162
+ if (index) {
163
+ initialScore += indexRouteValue;
216
164
  }
217
165
 
218
- return /*#__PURE__*/createElement(NavigationContext.Provider, {
219
- value: navigationContext
220
- }, /*#__PURE__*/createElement(LocationContext.Provider, {
221
- children: children,
222
- value: {
223
- location,
224
- navigationType
225
- }
226
- }));
166
+ return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
227
167
  }
228
168
 
229
- /**
230
- * A container for a nested tree of <Route> elements that renders the branch
231
- * that best matches the current location.
232
- *
233
- * @see https://reactrouter.com/docs/en/v6/api#routes
169
+ function compareIndexes(a, b) {
170
+ let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
171
+ return siblings ? // If two routes are siblings, we should try to match the earlier sibling
172
+ // first. This allows people to have fine-grained control over the matching
173
+ // behavior by simply putting routes with identical paths in the order they
174
+ // want them tried.
175
+ a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
176
+ // so they sort equally.
177
+ 0;
178
+ }
179
+
180
+ function matchRouteBranch(branch, pathname) {
181
+ let {
182
+ routesMeta
183
+ } = branch;
184
+ let matchedParams = {};
185
+ let matchedPathname = "/";
186
+ let matches = [];
187
+
188
+ for (let i = 0; i < routesMeta.length; ++i) {
189
+ let meta = routesMeta[i];
190
+ let end = i === routesMeta.length - 1;
191
+ let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
192
+ let match = matchPath({
193
+ path: meta.relativePath,
194
+ caseSensitive: meta.caseSensitive,
195
+ end
196
+ }, remainingPathname);
197
+ if (!match) return null;
198
+ Object.assign(matchedParams, match.params);
199
+ let route = meta.route;
200
+ matches.push({
201
+ params: matchedParams,
202
+ pathname: joinPaths([matchedPathname, match.pathname]),
203
+ pathnameBase: normalizePathname(joinPaths([matchedPathname, match.pathnameBase])),
204
+ route
205
+ });
206
+
207
+ if (match.pathnameBase !== "/") {
208
+ matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
209
+ }
210
+ }
211
+
212
+ return matches;
213
+ }
214
+ /**
215
+ * A PathPattern is used to match on some portion of a URL pathname.
234
216
  */
235
- function Routes({
236
- children,
237
- location
238
- }) {
239
- return useRoutes(createRoutesFromChildren(children), location);
240
- } ///////////////////////////////////////////////////////////////////////////////
241
- // HOOKS
242
- ///////////////////////////////////////////////////////////////////////////////
217
+
218
+
219
+ /**
220
+ * Performs pattern matching on a URL pathname and returns information about
221
+ * the match.
222
+ *
223
+ * @see https://reactrouter.com/docs/en/v6/api#matchpath
224
+ */
225
+ function matchPath(pattern, pathname) {
226
+ if (typeof pattern === "string") {
227
+ pattern = {
228
+ path: pattern,
229
+ caseSensitive: false,
230
+ end: true
231
+ };
232
+ }
233
+
234
+ let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
235
+ let match = pathname.match(matcher);
236
+ if (!match) return null;
237
+ let matchedPathname = match[0];
238
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
239
+ let captureGroups = match.slice(1);
240
+ let params = paramNames.reduce((memo, paramName, index) => {
241
+ // We need to compute the pathnameBase here using the raw splat value
242
+ // instead of using params["*"] later because it will be decoded then
243
+ if (paramName === "*") {
244
+ let splatValue = captureGroups[index] || "";
245
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
246
+ }
247
+
248
+ memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
249
+ return memo;
250
+ }, {});
251
+ return {
252
+ params,
253
+ pathname: matchedPathname,
254
+ pathnameBase,
255
+ pattern
256
+ };
257
+ }
258
+
259
+ function compilePath(path, caseSensitive = false, end = true) {
260
+ warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), `Route path "${path}" will be treated as if it were ` + `"${path.replace(/\*$/, "/*")}" because the \`*\` character must ` + `always follow a \`/\` in the pattern. To get rid of this warning, ` + `please change the route path to "${path.replace(/\*$/, "/*")}".`) ;
261
+ let paramNames = [];
262
+ let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
263
+ .replace(/^\/*/, "/") // Make sure it has a leading /
264
+ .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
265
+ .replace(/:(\w+)/g, (_, paramName) => {
266
+ paramNames.push(paramName);
267
+ return "([^\\/]+)";
268
+ });
269
+
270
+ if (path.endsWith("*")) {
271
+ paramNames.push("*");
272
+ regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
273
+ : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
274
+ } else {
275
+ regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
276
+ : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
277
+ // parent routes to matching only their own words and nothing more, e.g. parent
278
+ // route "/home" should not match "/home2".
279
+ // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities,
280
+ // but do not consume the character in the matched path so they can match against
281
+ // nested paths.
282
+ "(?:(?=[.~-]|%[0-9A-F]{2})|\\b|\\/|$)";
283
+ }
284
+
285
+ let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
286
+ return [matcher, paramNames];
287
+ }
288
+
289
+ function safelyDecodeURIComponent(value, paramName) {
290
+ try {
291
+ return decodeURIComponent(value);
292
+ } catch (error) {
293
+ warning(false, `The value for the URL param "${paramName}" will not be decoded because` + ` the string "${value}" is a malformed URL segment. This is probably` + ` due to a bad percent encoding (${error}).`) ;
294
+ return value;
295
+ }
296
+ }
297
+ /**
298
+ * Returns a resolved path object relative to the given pathname.
299
+ *
300
+ * @see https://reactrouter.com/docs/en/v6/api#resolvepath
301
+ */
302
+
303
+
304
+ function resolvePath(to, fromPathname = "/") {
305
+ let {
306
+ pathname: toPathname,
307
+ search = "",
308
+ hash = ""
309
+ } = typeof to === "string" ? parsePath(to) : to;
310
+ let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
311
+ return {
312
+ pathname,
313
+ search: normalizeSearch(search),
314
+ hash: normalizeHash(hash)
315
+ };
316
+ }
317
+
318
+ function resolvePathname(relativePath, fromPathname) {
319
+ let segments = fromPathname.replace(/\/+$/, "").split("/");
320
+ let relativeSegments = relativePath.split("/");
321
+ relativeSegments.forEach(segment => {
322
+ if (segment === "..") {
323
+ // Keep the root "" segment so the pathname starts at /
324
+ if (segments.length > 1) segments.pop();
325
+ } else if (segment !== ".") {
326
+ segments.push(segment);
327
+ }
328
+ });
329
+ return segments.length > 1 ? segments.join("/") : "/";
330
+ }
331
+
332
+ function resolveTo(toArg, routePathnames, locationPathname) {
333
+ let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
334
+ let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
335
+ // route context. This is explained in `Note on `<Link to>` values` in our
336
+ // migration guide from v5 as a means of disambiguation between `to` values
337
+ // that begin with `/` and those that do not. However, this is problematic for
338
+ // `to` values that do not provide a pathname. `to` can simply be a search or
339
+ // hash string, in which case we should assume that the navigation is relative
340
+ // to the current location's pathname and *not* the route pathname.
341
+
342
+ let from;
343
+
344
+ if (toPathname == null) {
345
+ from = locationPathname;
346
+ } else {
347
+ let routePathnameIndex = routePathnames.length - 1;
348
+
349
+ if (toPathname.startsWith("..")) {
350
+ let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
351
+ // URL segment". This is a key difference from how <a href> works and a
352
+ // major reason we call this a "to" value instead of a "href".
353
+
354
+ while (toSegments[0] === "..") {
355
+ toSegments.shift();
356
+ routePathnameIndex -= 1;
357
+ }
358
+
359
+ to.pathname = toSegments.join("/");
360
+ } // If there are more ".." segments than parent routes, resolve relative to
361
+ // the root / URL.
362
+
363
+
364
+ from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
365
+ }
366
+
367
+ let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
368
+
369
+ if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
370
+ path.pathname += "/";
371
+ }
372
+
373
+ return path;
374
+ }
375
+ function getToPathname(to) {
376
+ // Empty strings should be treated the same as / paths
377
+ return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
378
+ }
379
+ function stripBasename(pathname, basename) {
380
+ if (basename === "/") return pathname;
381
+
382
+ if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
383
+ return null;
384
+ }
385
+
386
+ let nextChar = pathname.charAt(basename.length);
387
+
388
+ if (nextChar && nextChar !== "/") {
389
+ // pathname does not start with basename/
390
+ return null;
391
+ }
392
+
393
+ return pathname.slice(basename.length) || "/";
394
+ }
395
+ const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
396
+ const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
397
+
398
+ const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
399
+
400
+ const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash;
243
401
 
244
402
  /**
245
403
  * Returns the full href for the given "to" value. This is useful for building
@@ -301,13 +459,13 @@ function useLocation() {
301
459
  `useLocation() may be used only in the context of a <Router> component.`) : void 0;
302
460
  return useContext(LocationContext).location;
303
461
  }
304
-
305
462
  /**
306
463
  * Returns the current navigation action which describes how the router came to
307
464
  * the current location, either by a pop, push, or replace on the history stack.
308
465
  *
309
466
  * @see https://reactrouter.com/docs/en/v6/api#usenavigationtype
310
467
  */
468
+
311
469
  function useNavigationType() {
312
470
  return useContext(LocationContext).navigationType;
313
471
  }
@@ -508,414 +666,230 @@ function useRoutes(routes, locationArg) {
508
666
  pathname: joinPaths([parentPathnameBase, match.pathname]),
509
667
  pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
510
668
  })), parentMatches);
511
- } ///////////////////////////////////////////////////////////////////////////////
512
- // UTILS
513
- ///////////////////////////////////////////////////////////////////////////////
669
+ }
670
+ function _renderMatches(matches, parentMatches = []) {
671
+ if (matches == null) return null;
672
+ return matches.reduceRight((outlet, match, index) => {
673
+ return /*#__PURE__*/createElement(RouteContext.Provider, {
674
+ children: match.route.element !== undefined ? match.route.element : outlet,
675
+ value: {
676
+ outlet,
677
+ matches: parentMatches.concat(matches.slice(0, index + 1))
678
+ }
679
+ });
680
+ }, null);
681
+ }
514
682
 
515
683
  /**
516
- * Creates a route config from a React "children" object, which is usually
517
- * either a `<Route>` element or an array of them. Used internally by
518
- * `<Routes>` to create a route config from its children.
684
+ * A <Router> that stores all entries in memory.
519
685
  *
520
- * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren
686
+ * @see https://reactrouter.com/docs/en/v6/api#memoryrouter
521
687
  */
688
+ function MemoryRouter({
689
+ basename,
690
+ children,
691
+ initialEntries,
692
+ initialIndex
693
+ }) {
694
+ let historyRef = useRef();
522
695
 
523
- function createRoutesFromChildren(children) {
524
- let routes = [];
525
- Children.forEach(children, element => {
526
- if (! /*#__PURE__*/isValidElement(element)) {
527
- // Ignore non-elements. This allows people to more easily inline
528
- // conditionals in their route config.
529
- return;
530
- }
531
-
532
- if (element.type === Fragment) {
533
- // Transparently support React.Fragment and its children.
534
- routes.push.apply(routes, createRoutesFromChildren(element.props.children));
535
- return;
536
- }
537
-
538
- !(element.type === Route) ? invariant(false, `[${typeof element.type === "string" ? element.type : element.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`) : void 0;
539
- let route = {
540
- caseSensitive: element.props.caseSensitive,
541
- element: element.props.element,
542
- index: element.props.index,
543
- path: element.props.path
544
- };
545
-
546
- if (element.props.children) {
547
- route.children = createRoutesFromChildren(element.props.children);
548
- }
696
+ if (historyRef.current == null) {
697
+ historyRef.current = createMemoryHistory({
698
+ initialEntries,
699
+ initialIndex
700
+ });
701
+ }
549
702
 
550
- routes.push(route);
703
+ let history = historyRef.current;
704
+ let [state, setState] = useState({
705
+ action: history.action,
706
+ location: history.location
707
+ });
708
+ useLayoutEffect(() => history.listen(setState), [history]);
709
+ return /*#__PURE__*/createElement(Router, {
710
+ basename: basename,
711
+ children: children,
712
+ location: state.location,
713
+ navigationType: state.action,
714
+ navigator: history
551
715
  });
552
- return routes;
553
716
  }
717
+
554
718
  /**
555
- * The parameters that were parsed from the URL path.
719
+ * Changes the current location.
720
+ *
721
+ * Note: This API is mostly useful in React.Component subclasses that are not
722
+ * able to use hooks. In functional components, we recommend you use the
723
+ * `useNavigate` hook instead.
724
+ *
725
+ * @see https://reactrouter.com/docs/en/v6/api#navigate
556
726
  */
727
+ function Navigate({
728
+ to,
729
+ replace,
730
+ state
731
+ }) {
732
+ !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of
733
+ // the router loaded. We can help them understand how to avoid that.
734
+ `<Navigate> may be used only in the context of a <Router> component.`) : void 0;
735
+ warning(!useContext(NavigationContext).static, `<Navigate> must not be used on the initial render in a <StaticRouter>. ` + `This is a no-op, but you should modify your code so the <Navigate> is ` + `only ever rendered in response to some user interaction or state change.`) ;
736
+ let navigate = useNavigate();
737
+ useEffect(() => {
738
+ navigate(to, {
739
+ replace,
740
+ state
741
+ });
742
+ });
743
+ return null;
744
+ }
557
745
 
558
746
  /**
559
- * Returns a path with params interpolated.
747
+ * Renders the child route's element, if there is one.
560
748
  *
561
- * @see https://reactrouter.com/docs/en/v6/api#generatepath
749
+ * @see https://reactrouter.com/docs/en/v6/api#outlet
562
750
  */
563
- function generatePath(path, params = {}) {
564
- return path.replace(/:(\w+)/g, (_, key) => {
565
- !(params[key] != null) ? invariant(false, `Missing ":${key}" param`) : void 0;
566
- return params[key];
567
- }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
751
+ function Outlet(props) {
752
+ return useOutlet(props.context);
568
753
  }
754
+
569
755
  /**
570
- * A RouteMatch contains info about how a route matched a URL.
756
+ * Declares an element that should be rendered at a certain URL path.
757
+ *
758
+ * @see https://reactrouter.com/docs/en/v6/api#route
571
759
  */
760
+ function Route(_props) {
761
+ invariant(false, `A <Route> is only ever to be used as the child of <Routes> element, ` + `never rendered directly. Please wrap your <Route> in a <Routes>.`) ;
762
+ }
572
763
 
573
764
  /**
574
- * Matches the given routes to a location and returns the match data.
765
+ * Provides location context for the rest of the app.
575
766
  *
576
- * @see https://reactrouter.com/docs/en/v6/api#matchroutes
767
+ * Note: You usually won't render a <Router> directly. Instead, you'll render a
768
+ * router that is more specific to your environment such as a <BrowserRouter>
769
+ * in web browsers or a <StaticRouter> for server rendering.
770
+ *
771
+ * @see https://reactrouter.com/docs/en/v6/api#router
577
772
  */
578
- function matchRoutes(routes, locationArg, basename = "/") {
579
- let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
580
- let pathname = stripBasename(location.pathname || "/", basename);
581
-
582
- if (pathname == null) {
583
- return null;
584
- }
585
-
586
- let branches = flattenRoutes(routes);
587
- rankRouteBranches(branches);
588
- let matches = null;
773
+ function Router({
774
+ basename: basenameProp = "/",
775
+ children = null,
776
+ location: locationProp,
777
+ navigationType = Action.Pop,
778
+ navigator,
779
+ static: staticProp = false
780
+ }) {
781
+ !!useInRouterContext() ? invariant(false, `You cannot render a <Router> inside another <Router>.` + ` You should never have more than one in your app.`) : void 0;
782
+ let basename = normalizePathname(basenameProp);
783
+ let navigationContext = useMemo(() => ({
784
+ basename,
785
+ navigator,
786
+ static: staticProp
787
+ }), [basename, navigator, staticProp]);
589
788
 
590
- for (let i = 0; matches == null && i < branches.length; ++i) {
591
- matches = matchRouteBranch(branches[i], pathname);
789
+ if (typeof locationProp === "string") {
790
+ locationProp = parsePath(locationProp);
592
791
  }
593
792
 
594
- return matches;
595
- }
596
-
597
- function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
598
- routes.forEach((route, index) => {
599
- let meta = {
600
- relativePath: route.path || "",
601
- caseSensitive: route.caseSensitive === true,
602
- childrenIndex: index,
603
- route
604
- };
605
-
606
- if (meta.relativePath.startsWith("/")) {
607
- !meta.relativePath.startsWith(parentPath) ? invariant(false, `Absolute route path "${meta.relativePath}" nested under path ` + `"${parentPath}" is not valid. An absolute child route path ` + `must start with the combined path of all its parent routes.`) : void 0;
608
- meta.relativePath = meta.relativePath.slice(parentPath.length);
609
- }
610
-
611
- let path = joinPaths([parentPath, meta.relativePath]);
612
- let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
613
- // route tree depth-first and child routes appear before their parents in
614
- // the "flattened" version.
615
-
616
- if (route.children && route.children.length > 0) {
617
- !(route.index !== true) ? invariant(false, `Index routes must not have child routes. Please remove ` + `all child routes from route path "${path}".`) : void 0;
618
- flattenRoutes(route.children, branches, routesMeta, path);
619
- } // Routes without a path shouldn't ever match by themselves unless they are
620
- // index routes, so don't add them to the list of possible branches.
621
-
793
+ let {
794
+ pathname = "/",
795
+ search = "",
796
+ hash = "",
797
+ state = null,
798
+ key = "default"
799
+ } = locationProp;
800
+ let location = useMemo(() => {
801
+ let trailingPathname = stripBasename(pathname, basename);
622
802
 
623
- if (route.path == null && !route.index) {
624
- return;
803
+ if (trailingPathname == null) {
804
+ return null;
625
805
  }
626
806
 
627
- branches.push({
628
- path,
629
- score: computeScore(path, route.index),
630
- routesMeta
631
- });
632
- });
633
- return branches;
634
- }
635
-
636
- function rankRouteBranches(branches) {
637
- branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
638
- : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
639
- }
640
-
641
- const paramRe = /^:\w+$/;
642
- const dynamicSegmentValue = 3;
643
- const indexRouteValue = 2;
644
- const emptySegmentValue = 1;
645
- const staticSegmentValue = 10;
646
- const splatPenalty = -2;
647
-
648
- const isSplat = s => s === "*";
649
-
650
- function computeScore(path, index) {
651
- let segments = path.split("/");
652
- let initialScore = segments.length;
653
-
654
- if (segments.some(isSplat)) {
655
- initialScore += splatPenalty;
656
- }
807
+ return {
808
+ pathname: trailingPathname,
809
+ search,
810
+ hash,
811
+ state,
812
+ key
813
+ };
814
+ }, [basename, pathname, search, hash, state, key]);
815
+ warning(location != null, `<Router basename="${basename}"> is not able to match the URL ` + `"${pathname}${search}${hash}" because it does not start with the ` + `basename, so the <Router> won't render anything.`) ;
657
816
 
658
- if (index) {
659
- initialScore += indexRouteValue;
817
+ if (location == null) {
818
+ return null;
660
819
  }
661
820
 
662
- return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
663
- }
664
-
665
- function compareIndexes(a, b) {
666
- let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
667
- return siblings ? // If two routes are siblings, we should try to match the earlier sibling
668
- // first. This allows people to have fine-grained control over the matching
669
- // behavior by simply putting routes with identical paths in the order they
670
- // want them tried.
671
- a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
672
- // so they sort equally.
673
- 0;
674
- }
675
-
676
- function matchRouteBranch(branch, pathname) {
677
- let {
678
- routesMeta
679
- } = branch;
680
- let matchedParams = {};
681
- let matchedPathname = "/";
682
- let matches = [];
683
-
684
- for (let i = 0; i < routesMeta.length; ++i) {
685
- let meta = routesMeta[i];
686
- let end = i === routesMeta.length - 1;
687
- let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
688
- let match = matchPath({
689
- path: meta.relativePath,
690
- caseSensitive: meta.caseSensitive,
691
- end
692
- }, remainingPathname);
693
- if (!match) return null;
694
- Object.assign(matchedParams, match.params);
695
- let route = meta.route;
696
- matches.push({
697
- params: matchedParams,
698
- pathname: joinPaths([matchedPathname, match.pathname]),
699
- pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
700
- route
701
- });
702
-
703
- if (match.pathnameBase !== "/") {
704
- matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
821
+ return /*#__PURE__*/createElement(NavigationContext.Provider, {
822
+ value: navigationContext
823
+ }, /*#__PURE__*/createElement(LocationContext.Provider, {
824
+ children: children,
825
+ value: {
826
+ location,
827
+ navigationType
705
828
  }
706
- }
707
-
708
- return matches;
709
- }
710
- /**
711
- * Renders the result of `matchRoutes()` into a React element.
712
- */
713
-
714
-
715
- function renderMatches(matches) {
716
- return _renderMatches(matches);
829
+ }));
717
830
  }
718
831
 
719
- function _renderMatches(matches, parentMatches = []) {
720
- if (matches == null) return null;
721
- return matches.reduceRight((outlet, match, index) => {
722
- return /*#__PURE__*/createElement(RouteContext.Provider, {
723
- children: match.route.element !== undefined ? match.route.element : /*#__PURE__*/createElement(Outlet, null),
724
- value: {
725
- outlet,
726
- matches: parentMatches.concat(matches.slice(0, index + 1))
727
- }
728
- });
729
- }, null);
730
- }
731
832
  /**
732
- * A PathPattern is used to match on some portion of a URL pathname.
833
+ * A container for a nested tree of <Route> elements that renders the branch
834
+ * that best matches the current location.
835
+ *
836
+ * @see https://reactrouter.com/docs/en/v6/api#routes
733
837
  */
734
-
838
+ function Routes({
839
+ children,
840
+ location
841
+ }) {
842
+ return useRoutes(createRoutesFromChildren(children), location);
843
+ } ///////////////////////////////////////////////////////////////////////////////
844
+ // UTILS
845
+ ///////////////////////////////////////////////////////////////////////////////
735
846
 
736
847
  /**
737
- * Performs pattern matching on a URL pathname and returns information about
738
- * the match.
848
+ * Creates a route config from a React "children" object, which is usually
849
+ * either a `<Route>` element or an array of them. Used internally by
850
+ * `<Routes>` to create a route config from its children.
739
851
  *
740
- * @see https://reactrouter.com/docs/en/v6/api#matchpath
852
+ * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren
741
853
  */
742
- function matchPath(pattern, pathname) {
743
- if (typeof pattern === "string") {
744
- pattern = {
745
- path: pattern,
746
- caseSensitive: false,
747
- end: true
748
- };
749
- }
750
854
 
751
- let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
752
- let match = pathname.match(matcher);
753
- if (!match) return null;
754
- let matchedPathname = match[0];
755
- let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
756
- let captureGroups = match.slice(1);
757
- let params = paramNames.reduce((memo, paramName, index) => {
758
- // We need to compute the pathnameBase here using the raw splat value
759
- // instead of using params["*"] later because it will be decoded then
760
- if (paramName === "*") {
761
- let splatValue = captureGroups[index] || "";
762
- pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
855
+ function createRoutesFromChildren(children) {
856
+ let routes = [];
857
+ Children.forEach(children, element => {
858
+ if (! /*#__PURE__*/isValidElement(element)) {
859
+ // Ignore non-elements. This allows people to more easily inline
860
+ // conditionals in their route config.
861
+ return;
763
862
  }
764
863
 
765
- memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
766
- return memo;
767
- }, {});
768
- return {
769
- params,
770
- pathname: matchedPathname,
771
- pathnameBase,
772
- pattern
773
- };
774
- }
775
-
776
- function compilePath(path, caseSensitive = false, end = true) {
777
- warning(path === "*" || !path.endsWith("*") || path.endsWith("/*"), `Route path "${path}" will be treated as if it were ` + `"${path.replace(/\*$/, "/*")}" because the \`*\` character must ` + `always follow a \`/\` in the pattern. To get rid of this warning, ` + `please change the route path to "${path.replace(/\*$/, "/*")}".`) ;
778
- let paramNames = [];
779
- let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
780
- .replace(/^\/*/, "/") // Make sure it has a leading /
781
- .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
782
- .replace(/:(\w+)/g, (_, paramName) => {
783
- paramNames.push(paramName);
784
- return "([^\\/]+)";
785
- });
864
+ if (element.type === Fragment) {
865
+ // Transparently support React.Fragment and its children.
866
+ routes.push.apply(routes, createRoutesFromChildren(element.props.children));
867
+ return;
868
+ }
786
869
 
787
- if (path.endsWith("*")) {
788
- paramNames.push("*");
789
- regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
790
- : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
791
- } else {
792
- regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
793
- : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts
794
- // parent routes to matching only their own words and nothing more, e.g. parent
795
- // route "/home" should not match "/home2".
796
- "(?:\\b|\\/|$)";
797
- }
870
+ !(element.type === Route) ? invariant(false, `[${typeof element.type === "string" ? element.type : element.type.name}] is not a <Route> component. All component children of <Routes> must be a <Route> or <React.Fragment>`) : void 0;
871
+ let route = {
872
+ caseSensitive: element.props.caseSensitive,
873
+ element: element.props.element,
874
+ index: element.props.index,
875
+ path: element.props.path
876
+ };
798
877
 
799
- let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
800
- return [matcher, paramNames];
801
- }
878
+ if (element.props.children) {
879
+ route.children = createRoutesFromChildren(element.props.children);
880
+ }
802
881
 
803
- function safelyDecodeURIComponent(value, paramName) {
804
- try {
805
- return decodeURIComponent(value);
806
- } catch (error) {
807
- warning(false, `The value for the URL param "${paramName}" will not be decoded because` + ` the string "${value}" is a malformed URL segment. This is probably` + ` due to a bad percent encoding (${error}).`) ;
808
- return value;
809
- }
882
+ routes.push(route);
883
+ });
884
+ return routes;
810
885
  }
811
886
  /**
812
- * Returns a resolved path object relative to the given pathname.
813
- *
814
- * @see https://reactrouter.com/docs/en/v6/api#resolvepath
887
+ * Renders the result of `matchRoutes()` into a React element.
815
888
  */
816
889
 
817
-
818
- function resolvePath(to, fromPathname = "/") {
819
- let {
820
- pathname: toPathname,
821
- search = "",
822
- hash = ""
823
- } = typeof to === "string" ? parsePath(to) : to;
824
- let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
825
- return {
826
- pathname,
827
- search: normalizeSearch(search),
828
- hash: normalizeHash(hash)
829
- };
830
- }
831
-
832
- function resolvePathname(relativePath, fromPathname) {
833
- let segments = fromPathname.replace(/\/+$/, "").split("/");
834
- let relativeSegments = relativePath.split("/");
835
- relativeSegments.forEach(segment => {
836
- if (segment === "..") {
837
- // Keep the root "" segment so the pathname starts at /
838
- if (segments.length > 1) segments.pop();
839
- } else if (segment !== ".") {
840
- segments.push(segment);
841
- }
842
- });
843
- return segments.length > 1 ? segments.join("/") : "/";
844
- }
845
-
846
- function resolveTo(toArg, routePathnames, locationPathname) {
847
- let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
848
- let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
849
- // route context. This is explained in `Note on `<Link to>` values` in our
850
- // migration guide from v5 as a means of disambiguation between `to` values
851
- // that begin with `/` and those that do not. However, this is problematic for
852
- // `to` values that do not provide a pathname. `to` can simply be a search or
853
- // hash string, in which case we should assume that the navigation is relative
854
- // to the current location's pathname and *not* the route pathname.
855
-
856
- let from;
857
-
858
- if (toPathname == null) {
859
- from = locationPathname;
860
- } else {
861
- let routePathnameIndex = routePathnames.length - 1;
862
-
863
- if (toPathname.startsWith("..")) {
864
- let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
865
- // URL segment". This is a key difference from how <a href> works and a
866
- // major reason we call this a "to" value instead of a "href".
867
-
868
- while (toSegments[0] === "..") {
869
- toSegments.shift();
870
- routePathnameIndex -= 1;
871
- }
872
-
873
- to.pathname = toSegments.join("/");
874
- } // If there are more ".." segments than parent routes, resolve relative to
875
- // the root / URL.
876
-
877
-
878
- from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
879
- }
880
-
881
- let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
882
-
883
- if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
884
- path.pathname += "/";
885
- }
886
-
887
- return path;
888
- }
889
-
890
- function getToPathname(to) {
891
- // Empty strings should be treated the same as / paths
892
- return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
893
- }
894
-
895
- function stripBasename(pathname, basename) {
896
- if (basename === "/") return pathname;
897
-
898
- if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
899
- return null;
900
- }
901
-
902
- let nextChar = pathname.charAt(basename.length);
903
-
904
- if (nextChar && nextChar !== "/") {
905
- // pathname does not start with basename/
906
- return null;
907
- }
908
-
909
- return pathname.slice(basename.length) || "/";
890
+ function renderMatches(matches) {
891
+ return _renderMatches(matches);
910
892
  }
911
893
 
912
- const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
913
-
914
- const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
915
-
916
- const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
917
-
918
- const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash; ///////////////////////////////////////////////////////////////////////////////
919
-
920
894
  export { MemoryRouter, Navigate, Outlet, Route, Router, Routes, LocationContext as UNSAFE_LocationContext, NavigationContext as UNSAFE_NavigationContext, RouteContext as UNSAFE_RouteContext, createRoutesFromChildren, generatePath, matchPath, matchRoutes, renderMatches, resolvePath, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useNavigationType, useOutlet, useOutletContext, useParams, useResolvedPath, useRoutes };
921
895
  //# sourceMappingURL=react-router.development.js.map