react-router 6.0.0-beta.5 → 6.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,3 +1,13 @@
1
+ /**
2
+ * React Router v6.0.0
3
+ *
4
+ * Copyright (c) Remix Software Inc.
5
+ *
6
+ * This source code is licensed under the MIT license found in the
7
+ * LICENSE.md file in the root directory of this source tree.
8
+ *
9
+ * @license MIT
10
+ */
1
11
  import { createContext, useRef, useState, useLayoutEffect, createElement, useContext, useEffect, useMemo, useCallback, Children, isValidElement, Fragment } from 'react';
2
12
  import { createMemoryHistory, Action, parsePath } from 'history';
3
13
 
@@ -28,7 +38,20 @@ function warningOnce(key, cond, message) {
28
38
  alreadyWarned[key] = true;
29
39
  warning(false, message) ;
30
40
  }
31
- }
41
+ } ///////////////////////////////////////////////////////////////////////////////
42
+ // CONTEXT
43
+ ///////////////////////////////////////////////////////////////////////////////
44
+
45
+ /**
46
+ * A Navigator is a "location changer"; it's how you get to different locations.
47
+ *
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.
53
+ */
54
+
32
55
 
33
56
  const NavigationContext = /*#__PURE__*/createContext(null);
34
57
 
@@ -44,21 +67,21 @@ const LocationContext = /*#__PURE__*/createContext(null);
44
67
 
45
68
  const RouteContext = /*#__PURE__*/createContext({
46
69
  outlet: null,
47
- params: {},
48
- pathname: "/",
49
- route: null
70
+ matches: []
50
71
  });
51
72
 
52
73
  {
53
74
  RouteContext.displayName = "Route";
54
- }
75
+ } ///////////////////////////////////////////////////////////////////////////////
76
+ // COMPONENTS
77
+ ///////////////////////////////////////////////////////////////////////////////
78
+
79
+
55
80
  /**
56
81
  * A <Router> that stores all entries in memory.
57
82
  *
58
83
  * @see https://reactrouter.com/api/MemoryRouter
59
84
  */
60
-
61
-
62
85
  function MemoryRouter({
63
86
  basename,
64
87
  children,
@@ -83,11 +106,12 @@ function MemoryRouter({
83
106
  return /*#__PURE__*/createElement(Router, {
84
107
  basename: basename,
85
108
  children: children,
86
- action: state.action,
87
109
  location: state.location,
110
+ navigationType: state.action,
88
111
  navigator: history
89
112
  });
90
113
  }
114
+
91
115
  /**
92
116
  * Changes the current location.
93
117
  *
@@ -97,7 +121,6 @@ function MemoryRouter({
97
121
  *
98
122
  * @see https://reactrouter.com/api/Navigate
99
123
  */
100
-
101
124
  function Navigate({
102
125
  to,
103
126
  replace,
@@ -116,24 +139,25 @@ function Navigate({
116
139
  });
117
140
  return null;
118
141
  }
142
+
119
143
  /**
120
144
  * Renders the child route's element, if there is one.
121
145
  *
122
146
  * @see https://reactrouter.com/api/Outlet
123
147
  */
124
-
125
148
  function Outlet(_props) {
126
149
  return useOutlet();
127
150
  }
151
+
128
152
  /**
129
153
  * Declares an element that should be rendered at a certain URL path.
130
154
  *
131
155
  * @see https://reactrouter.com/api/Route
132
156
  */
133
-
134
157
  function Route(_props) {
135
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>.`) ;
136
159
  }
160
+
137
161
  /**
138
162
  * Provides location context for the rest of the app.
139
163
  *
@@ -143,12 +167,11 @@ function Route(_props) {
143
167
  *
144
168
  * @see https://reactrouter.com/api/Router
145
169
  */
146
-
147
170
  function Router({
148
- action = Action.Pop,
149
171
  basename: basenameProp = "/",
150
172
  children = null,
151
173
  location: locationProp,
174
+ navigationType = Action.Pop,
152
175
  navigator,
153
176
  static: staticProp = false
154
177
  }) {
@@ -197,18 +220,18 @@ function Router({
197
220
  }, /*#__PURE__*/createElement(LocationContext.Provider, {
198
221
  children: children,
199
222
  value: {
200
- action,
201
- location
223
+ location,
224
+ navigationType
202
225
  }
203
226
  }));
204
227
  }
228
+
205
229
  /**
206
230
  * A container for a nested tree of <Route> elements that renders the branch
207
231
  * that best matches the current location.
208
232
  *
209
233
  * @see https://reactrouter.com/api/Routes
210
234
  */
211
-
212
235
  function Routes({
213
236
  children,
214
237
  location
@@ -218,39 +241,6 @@ function Routes({
218
241
  // HOOKS
219
242
  ///////////////////////////////////////////////////////////////////////////////
220
243
 
221
- /**
222
- * Blocks all navigation attempts. This is useful for preventing the page from
223
- * changing until some condition is met, like saving form data.
224
- *
225
- * @see https://reactrouter.com/api/useBlocker
226
- */
227
-
228
- function useBlocker(blocker, when = true) {
229
- !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
230
- // router loaded. We can help them understand how to avoid that.
231
- `useBlocker() may be used only in the context of a <Router> component.`) : void 0;
232
- let {
233
- navigator
234
- } = useContext(NavigationContext);
235
- useEffect(() => {
236
- if (!when) return;
237
- let unblock = navigator.block(tx => {
238
- let autoUnblockingTx = { ...tx,
239
-
240
- retry() {
241
- // Automatically unblock the transition so it can play all the way
242
- // through before retrying it. TODO: Figure out how to re-enable
243
- // this block if the transition is cancelled for some reason.
244
- unblock();
245
- tx.retry();
246
- }
247
-
248
- };
249
- blocker(autoUnblockingTx);
250
- });
251
- return unblock;
252
- }, [navigator, blocker, when]);
253
- }
254
244
  /**
255
245
  * Returns the full href for the given "to" value. This is useful for building
256
246
  * custom links that are also accessible and preserve right-click behavior.
@@ -266,15 +256,24 @@ function useHref(to) {
266
256
  basename,
267
257
  navigator
268
258
  } = useContext(NavigationContext);
269
- let path = useResolvedPath(to);
259
+ let {
260
+ hash,
261
+ pathname,
262
+ search
263
+ } = useResolvedPath(to);
264
+ let joinedPathname = pathname;
270
265
 
271
266
  if (basename !== "/") {
272
267
  let toPathname = getToPathname(to);
273
268
  let endsWithSlash = toPathname != null && toPathname.endsWith("/");
274
- path.pathname = path.pathname === "/" ? basename + (endsWithSlash ? "/" : "") : joinPaths([basename, path.pathname]);
269
+ joinedPathname = pathname === "/" ? basename + (endsWithSlash ? "/" : "") : joinPaths([basename, pathname]);
275
270
  }
276
271
 
277
- return navigator.createHref(path);
272
+ return navigator.createHref({
273
+ pathname: joinedPathname,
274
+ search,
275
+ hash
276
+ });
278
277
  }
279
278
  /**
280
279
  * Returns true if this component is a descendant of a <Router>.
@@ -302,6 +301,16 @@ function useLocation() {
302
301
  `useLocation() may be used only in the context of a <Router> component.`) : void 0;
303
302
  return useContext(LocationContext).location;
304
303
  }
304
+ /**
305
+ * Returns the current navigation action which describes how the router came to
306
+ * the current location, either by a pop, push, or replace on the history stack.
307
+ *
308
+ * @see https://reactrouter.com/api/useNavigationType
309
+ */
310
+
311
+ function useNavigationType() {
312
+ return useContext(LocationContext).navigationType;
313
+ }
305
314
  /**
306
315
  * Returns true if the URL for the given "to" value matches the current URL.
307
316
  * This is useful for components that need to know "active" state, e.g.
@@ -316,13 +325,16 @@ function useMatch(pattern) {
316
325
  `useMatch() may be used only in the context of a <Router> component.`) : void 0;
317
326
  return matchPath(pattern, useLocation().pathname);
318
327
  }
328
+ /**
329
+ * The interface for the navigate() function returned from useNavigate().
330
+ */
331
+
319
332
  /**
320
333
  * Returns an imperative method for changing the location. Used by <Link>s, but
321
334
  * may also be used by other elements to change the location.
322
335
  *
323
336
  * @see https://reactrouter.com/api/useNavigate
324
337
  */
325
-
326
338
  function useNavigate() {
327
339
  !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
328
340
  // router loaded. We can help them understand how to avoid that.
@@ -332,11 +344,12 @@ function useNavigate() {
332
344
  navigator
333
345
  } = useContext(NavigationContext);
334
346
  let {
335
- pathname: routePathname
347
+ matches
336
348
  } = useContext(RouteContext);
337
349
  let {
338
350
  pathname: locationPathname
339
351
  } = useLocation();
352
+ let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
340
353
  let activeRef = useRef(false);
341
354
  useEffect(() => {
342
355
  activeRef.current = true;
@@ -350,14 +363,14 @@ function useNavigate() {
350
363
  return;
351
364
  }
352
365
 
353
- let path = resolveTo(to, routePathname, locationPathname);
366
+ let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname);
354
367
 
355
368
  if (basename !== "/") {
356
369
  path.pathname = joinPaths([basename, path.pathname]);
357
370
  }
358
371
 
359
372
  (!!options.replace ? navigator.replace : navigator.push)(path, options.state);
360
- }, [basename, navigator, routePathname, locationPathname]);
373
+ }, [basename, navigator, routePathnamesJson, locationPathname]);
361
374
  return navigate;
362
375
  }
363
376
  /**
@@ -378,7 +391,11 @@ function useOutlet() {
378
391
  */
379
392
 
380
393
  function useParams() {
381
- return useContext(RouteContext).params;
394
+ let {
395
+ matches
396
+ } = useContext(RouteContext);
397
+ let routeMatch = matches[matches.length - 1];
398
+ return routeMatch ? routeMatch.params : {};
382
399
  }
383
400
  /**
384
401
  * Resolves the pathname of the given `to` value against the current location.
@@ -387,13 +404,14 @@ function useParams() {
387
404
  */
388
405
 
389
406
  function useResolvedPath(to) {
407
+ let {
408
+ matches
409
+ } = useContext(RouteContext);
390
410
  let {
391
411
  pathname: locationPathname
392
412
  } = useLocation();
393
- let {
394
- pathname: routePathname
395
- } = useContext(RouteContext);
396
- return useMemo(() => resolveTo(to, routePathname, locationPathname), [to, routePathname, locationPathname]);
413
+ let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
414
+ return useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname), [to, routePathnamesJson, locationPathname]);
397
415
  }
398
416
  /**
399
417
  * Returns the element of the route that matched the current location, prepared
@@ -409,10 +427,13 @@ function useRoutes(routes, locationArg) {
409
427
  // router loaded. We can help them understand how to avoid that.
410
428
  `useRoutes() may be used only in the context of a <Router> component.`) : void 0;
411
429
  let {
412
- params: parentParams,
413
- pathname: parentPathname,
414
- route: parentRoute
430
+ matches: parentMatches
415
431
  } = useContext(RouteContext);
432
+ let routeMatch = parentMatches[parentMatches.length - 1];
433
+ let parentParams = routeMatch ? routeMatch.params : {};
434
+ let parentPathname = routeMatch ? routeMatch.pathname : "/";
435
+ let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : "/";
436
+ let parentRoute = routeMatch && routeMatch.route;
416
437
 
417
438
  {
418
439
  // You won't get a warning about 2 different <Routes> under a <Route>
@@ -440,21 +461,32 @@ function useRoutes(routes, locationArg) {
440
461
  }
441
462
 
442
463
  let locationFromContext = useLocation();
443
- let location = locationArg ? typeof locationArg === "string" ? parsePath(locationArg) : locationArg : locationFromContext;
464
+ let location;
465
+
466
+ if (locationArg) {
467
+ let parsedLocationArg = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
468
+ !(parentPathnameBase === "/" || parsedLocationArg.pathname?.startsWith(parentPathnameBase)) ? invariant(false, `When overriding the location using \`<Routes location>\` or \`useRoutes(routes, location)\`, ` + `the location pathname must begin with the portion of the URL pathname that was ` + `matched by all parent routes. The current pathname base is "${parentPathnameBase}" ` + `but pathname "${parsedLocationArg.pathname}" was given in the \`location\` prop.`) : void 0;
469
+ location = parsedLocationArg;
470
+ } else {
471
+ location = locationFromContext;
472
+ }
473
+
444
474
  let pathname = location.pathname || "/";
445
- let parentPathnameStart = getPathnameStart(parentPathname, parentParams);
446
- let remainingPathname = parentPathnameStart === "/" ? pathname : pathname.slice(parentPathnameStart.length);
475
+ let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/";
447
476
  let matches = matchRoutes(routes, {
448
477
  pathname: remainingPathname
449
478
  });
450
479
 
451
480
  {
452
481
  warning(parentRoute || matches != null, `No routes matched location "${location.pathname}${location.search}${location.hash}" `) ;
482
+ warning(matches == null || matches[matches.length - 1].route.element !== undefined, `Matched leaf route at location "${location.pathname}${location.search}${location.hash}" does not have an element. ` + `This means it will render an <Outlet /> with a null value by default resulting in an "empty" page.`) ;
453
483
  }
454
484
 
455
- return renderMatches(matches && matches.map(match => Object.assign({}, match, {
456
- pathname: joinPaths([parentPathnameStart, match.pathname])
457
- })));
485
+ return _renderMatches(matches && matches.map(match => Object.assign({}, match, {
486
+ params: Object.assign({}, parentParams, match.params),
487
+ pathname: joinPaths([parentPathnameBase, match.pathname]),
488
+ pathnameBase: match.pathnameBase === "/" ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase])
489
+ })), parentMatches);
458
490
  } ///////////////////////////////////////////////////////////////////////////////
459
491
  // UTILS
460
492
  ///////////////////////////////////////////////////////////////////////////////
@@ -483,10 +515,10 @@ function createRoutesFromChildren(children) {
483
515
  }
484
516
 
485
517
  let route = {
486
- path: element.props.path,
487
518
  caseSensitive: element.props.caseSensitive,
519
+ element: element.props.element,
488
520
  index: element.props.index,
489
- element: element.props.element
521
+ path: element.props.path
490
522
  };
491
523
 
492
524
  if (element.props.children) {
@@ -497,24 +529,30 @@ function createRoutesFromChildren(children) {
497
529
  });
498
530
  return routes;
499
531
  }
532
+ /**
533
+ * The parameters that were parsed from the URL path.
534
+ */
535
+
500
536
  /**
501
537
  * Returns a path with params interpolated.
502
538
  *
503
539
  * @see https://reactrouter.com/api/generatePath
504
540
  */
505
-
506
541
  function generatePath(path, params = {}) {
507
542
  return path.replace(/:(\w+)/g, (_, key) => {
508
543
  !(params[key] != null) ? invariant(false, `Missing ":${key}" param`) : void 0;
509
544
  return params[key];
510
545
  }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
511
546
  }
547
+ /**
548
+ * A RouteMatch contains info about how a route matched a URL.
549
+ */
550
+
512
551
  /**
513
552
  * Matches the given routes to a location and returns the match data.
514
553
  *
515
554
  * @see https://reactrouter.com/api/matchRoutes
516
555
  */
517
-
518
556
  function matchRoutes(routes, locationArg, basename = "/") {
519
557
  let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
520
558
  let pathname = stripBasename(location.pathname || "/", basename);
@@ -555,11 +593,17 @@ function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "")
555
593
  if (route.children && route.children.length > 0) {
556
594
  !(route.index !== true) ? invariant(false, `Index routes must not have child routes. Please remove ` + `all child routes from route path "${path}".`) : void 0;
557
595
  flattenRoutes(route.children, branches, routesMeta, path);
596
+ } // Routes without a path shouldn't ever match by themselves unless they are
597
+ // index routes, so don't add them to the list of possible branches.
598
+
599
+
600
+ if (route.path == null && !route.index) {
601
+ return;
558
602
  }
559
603
 
560
604
  branches.push({
561
605
  path,
562
- score: computeScore(path),
606
+ score: computeScore(path, route.index),
563
607
  routesMeta
564
608
  });
565
609
  });
@@ -572,14 +616,15 @@ function rankRouteBranches(branches) {
572
616
  }
573
617
 
574
618
  const paramRe = /^:\w+$/;
575
- const dynamicSegmentValue = 2;
619
+ const dynamicSegmentValue = 3;
620
+ const indexRouteValue = 2;
576
621
  const emptySegmentValue = 1;
577
622
  const staticSegmentValue = 10;
578
623
  const splatPenalty = -2;
579
624
 
580
625
  const isSplat = s => s === "*";
581
626
 
582
- function computeScore(path) {
627
+ function computeScore(path, index) {
583
628
  let segments = path.split("/");
584
629
  let initialScore = segments.length;
585
630
 
@@ -587,6 +632,10 @@ function computeScore(path) {
587
632
  initialScore += splatPenalty;
588
633
  }
589
634
 
635
+ if (index) {
636
+ initialScore += indexRouteValue;
637
+ }
638
+
590
639
  return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
591
640
  }
592
641
 
@@ -601,7 +650,8 @@ function compareIndexes(a, b) {
601
650
  0;
602
651
  }
603
652
 
604
- function matchRouteBranch(branch, routesArg, pathname) {
653
+ function matchRouteBranch(branch, // TODO: attach original route object inside routesMeta so we don't need this arg
654
+ routesArg, pathname) {
605
655
  let routes = routesArg;
606
656
  let {
607
657
  routesMeta
@@ -612,27 +662,25 @@ function matchRouteBranch(branch, routesArg, pathname) {
612
662
 
613
663
  for (let i = 0; i < routesMeta.length; ++i) {
614
664
  let meta = routesMeta[i];
615
- let trailingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
665
+ let end = i === routesMeta.length - 1;
666
+ let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
616
667
  let match = matchPath({
617
668
  path: meta.relativePath,
618
669
  caseSensitive: meta.caseSensitive,
619
- end: i === routesMeta.length - 1
620
- }, trailingPathname);
670
+ end
671
+ }, remainingPathname);
621
672
  if (!match) return null;
622
673
  Object.assign(matchedParams, match.params);
623
674
  let route = routes[meta.childrenIndex];
624
675
  matches.push({
625
676
  params: matchedParams,
626
- pathname: match.pathname === "/" ? matchedPathname : joinPaths([matchedPathname, match.pathname]),
677
+ pathname: joinPaths([matchedPathname, match.pathname]),
678
+ pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
627
679
  route
628
680
  });
629
- let pathnameStart = getPathnameStart(match.pathname, match.params);
630
681
 
631
- if (pathnameStart !== "/") {
632
- // Add only the portion of the match.pathname that comes before the * to
633
- // the matchedPathname. This allows child routes to match against the
634
- // portion of the pathname that was matched by the *.
635
- matchedPathname = joinPaths([matchedPathname, pathnameStart]);
682
+ if (match.pathnameBase !== "/") {
683
+ matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
636
684
  }
637
685
 
638
686
  routes = route.children;
@@ -646,26 +694,32 @@ function matchRouteBranch(branch, routesArg, pathname) {
646
694
 
647
695
 
648
696
  function renderMatches(matches) {
697
+ return _renderMatches(matches);
698
+ }
699
+
700
+ function _renderMatches(matches, parentMatches = []) {
649
701
  if (matches == null) return null;
650
- return matches.reduceRight((outlet, match) => {
702
+ return matches.reduceRight((outlet, match, index) => {
651
703
  return /*#__PURE__*/createElement(RouteContext.Provider, {
652
- children: match.route.element || /*#__PURE__*/createElement(Outlet, null),
704
+ children: match.route.element !== undefined ? match.route.element : /*#__PURE__*/createElement(Outlet, null),
653
705
  value: {
654
706
  outlet,
655
- params: match.params,
656
- pathname: match.pathname,
657
- route: match.route
707
+ matches: parentMatches.concat(matches.slice(0, index + 1))
658
708
  }
659
709
  });
660
710
  }, null);
661
711
  }
712
+ /**
713
+ * A PathPattern is used to match on some portion of a URL pathname.
714
+ */
715
+
716
+
662
717
  /**
663
718
  * Performs pattern matching on a URL pathname and returns information about
664
719
  * the match.
665
720
  *
666
721
  * @see https://reactrouter.com/api/matchPath
667
722
  */
668
-
669
723
  function matchPath(pattern, pathname) {
670
724
  if (typeof pattern === "string") {
671
725
  pattern = {
@@ -679,48 +733,52 @@ function matchPath(pattern, pathname) {
679
733
  let match = pathname.match(matcher);
680
734
  if (!match) return null;
681
735
  let matchedPathname = match[0];
682
- let values = match.slice(1);
736
+ let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
737
+ let captureGroups = match.slice(1);
683
738
  let params = paramNames.reduce((memo, paramName, index) => {
684
- memo[paramName] = safelyDecodeURIComponent(values[index] || "", paramName);
739
+ // We need to compute the pathnameBase here using the raw splat value
740
+ // instead of using params["*"] later because it will be decoded then
741
+ if (paramName === "*") {
742
+ let splatValue = captureGroups[index] || "";
743
+ pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
744
+ }
745
+
746
+ memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
685
747
  return memo;
686
748
  }, {});
687
749
  return {
688
750
  params,
689
751
  pathname: matchedPathname,
752
+ pathnameBase,
690
753
  pattern
691
754
  };
692
755
  }
693
756
 
694
757
  function compilePath(path, caseSensitive = false, end = true) {
695
- let keys = [];
696
- let source = "^" + path.replace(/\/?\*?$/, "") // Ignore trailing / and /*, we'll handle it below
758
+ 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(/\*$/, "/*")}".`) ;
759
+ let paramNames = [];
760
+ let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
697
761
  .replace(/^\/*/, "/") // Make sure it has a leading /
698
762
  .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
699
- .replace(/:(\w+)/g, (_, key) => {
700
- keys.push(key);
763
+ .replace(/:(\w+)/g, (_, paramName) => {
764
+ paramNames.push(paramName);
701
765
  return "([^\\/]+)";
702
766
  });
703
767
 
704
768
  if (path.endsWith("*")) {
705
- if (path.endsWith("/*")) {
706
- source += "(?:\\/(.+)|\\/?)$"; // Don't include the / in params['*']
707
- } else {
708
- source += "(.*)$";
709
- }
710
-
711
- keys.push("*");
712
- } else if (end) {
713
- // When matching to the end, ignore trailing slashes.
714
- source += "\\/?$";
769
+ paramNames.push("*");
770
+ regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
771
+ : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
715
772
  } else {
716
- // If not matching to the end (as parent routes do), at least match a word
717
- // boundary. This restricts a parent route to matching only its own words
718
- // and nothing more, e.g. parent route "/home" should not match "/home2".
719
- source += "(?:\\b|$)";
773
+ regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
774
+ : // Otherwise, at least match a word boundary. This restricts parent
775
+ // routes to matching only their own words and nothing more, e.g. parent
776
+ // route "/home" should not match "/home2".
777
+ "(?:\\b|$)";
720
778
  }
721
779
 
722
- let matcher = new RegExp(source, caseSensitive ? undefined : "i");
723
- return [matcher, keys];
780
+ let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
781
+ return [matcher, paramNames];
724
782
  }
725
783
 
726
784
  function safelyDecodeURIComponent(value, paramName) {
@@ -766,17 +824,48 @@ function resolvePathname(relativePath, fromPathname) {
766
824
  return segments.length > 1 ? segments.join("/") : "/";
767
825
  }
768
826
 
769
- function resolveTo(to, routePathname, locationPathname) {
770
- return resolvePath(to, // If a pathname is explicitly provided in `to`, it should be
771
- // relative to the route context. This is explained in `Note on
772
- // `<Link to>` values` in our migration guide from v5 as a means of
773
- // disambiguation between `to` values that begin with `/` and those
774
- // that do not. However, this is problematic for `to` values that do
775
- // not provide a pathname. `to` can simply be a search or hash
776
- // string, in which case we should assume that the navigation is
777
- // relative to the current location's pathname and *not* the
778
- // route pathname.
779
- getToPathname(to) == null ? locationPathname : routePathname);
827
+ function resolveTo(toArg, routePathnames, locationPathname) {
828
+ let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
829
+ let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
830
+ // route context. This is explained in `Note on `<Link to>` values` in our
831
+ // migration guide from v5 as a means of disambiguation between `to` values
832
+ // that begin with `/` and those that do not. However, this is problematic for
833
+ // `to` values that do not provide a pathname. `to` can simply be a search or
834
+ // hash string, in which case we should assume that the navigation is relative
835
+ // to the current location's pathname and *not* the route pathname.
836
+
837
+ let from;
838
+
839
+ if (toPathname == null) {
840
+ from = locationPathname;
841
+ } else {
842
+ let routePathnameIndex = routePathnames.length - 1;
843
+
844
+ if (toPathname.startsWith("..")) {
845
+ let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
846
+ // URL segment". This is a key difference from how <a href> works and a
847
+ // major reason we call this a "to" value instead of a "href".
848
+
849
+ while (toSegments[0] === "..") {
850
+ toSegments.shift();
851
+ routePathnameIndex -= 1;
852
+ }
853
+
854
+ to.pathname = toSegments.join("/");
855
+ } // If there are more ".." segments than parent routes, resolve relative to
856
+ // the root / URL.
857
+
858
+
859
+ from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
860
+ }
861
+
862
+ let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
863
+
864
+ if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
865
+ path.pathname += "/";
866
+ }
867
+
868
+ return path;
780
869
  }
781
870
 
782
871
  function getToPathname(to) {
@@ -784,16 +873,6 @@ function getToPathname(to) {
784
873
  return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
785
874
  }
786
875
 
787
- function getPathnameStart(pathname, params) {
788
- let splat = params["*"];
789
- if (!splat) return pathname;
790
- let pathnameStart = pathname.slice(0, -splat.length);
791
- if (splat.startsWith("/")) return pathnameStart;
792
- let index = pathnameStart.lastIndexOf("/");
793
- if (index > 0) return pathnameStart.slice(0, index);
794
- return "/";
795
- }
796
-
797
876
  function stripBasename(pathname, basename) {
798
877
  if (basename === "/") return pathname;
799
878
 
@@ -819,5 +898,5 @@ const normalizeSearch = search => !search || search === "?" ? "" : search.starts
819
898
 
820
899
  const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash; ///////////////////////////////////////////////////////////////////////////////
821
900
 
822
- 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, useBlocker, useHref, useInRouterContext, useLocation, useMatch, useNavigate, useOutlet, useParams, useResolvedPath, useRoutes };
901
+ 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, useParams, useResolvedPath, useRoutes };
823
902
  //# sourceMappingURL=react-router.development.js.map