UNPKG

31.7 kBJavaScriptView Raw
1/**
2 * React Router v6.0.2
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 */
11import { createContext, useRef, useState, useLayoutEffect, createElement, useContext, useEffect, useMemo, useCallback, Children, isValidElement, Fragment } from 'react';
12import { createMemoryHistory, Action, parsePath } from 'history';
13
14function invariant(cond, message) {
15 if (!cond) throw new Error(message);
16}
17
18function warning(cond, message) {
19 if (!cond) {
20 // eslint-disable-next-line no-console
21 if (typeof console !== "undefined") console.warn(message);
22
23 try {
24 // Welcome to debugging React Router!
25 //
26 // This error is thrown as a convenience so you can more easily
27 // find the source for a warning that appears in the console by
28 // enabling "pause on exceptions" in your JavaScript debugger.
29 throw new Error(message); // eslint-disable-next-line no-empty
30 } catch (e) {}
31 }
32}
33
34const alreadyWarned = {};
35
36function warningOnce(key, cond, message) {
37 if (!cond && !alreadyWarned[key]) {
38 alreadyWarned[key] = true;
39 warning(false, message) ;
40 }
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
55
56const NavigationContext = /*#__PURE__*/createContext(null);
57
58{
59 NavigationContext.displayName = "Navigation";
60}
61
62const LocationContext = /*#__PURE__*/createContext(null);
63
64{
65 LocationContext.displayName = "Location";
66}
67
68const RouteContext = /*#__PURE__*/createContext({
69 outlet: null,
70 matches: []
71});
72
73{
74 RouteContext.displayName = "Route";
75} ///////////////////////////////////////////////////////////////////////////////
76// COMPONENTS
77///////////////////////////////////////////////////////////////////////////////
78
79
80/**
81 * A <Router> that stores all entries in memory.
82 *
83 * @see https://reactrouter.com/docs/en/v6/api#memoryrouter
84 */
85function MemoryRouter({
86 basename,
87 children,
88 initialEntries,
89 initialIndex
90}) {
91 let historyRef = useRef();
92
93 if (historyRef.current == null) {
94 historyRef.current = createMemoryHistory({
95 initialEntries,
96 initialIndex
97 });
98 }
99
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}
114
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 */
124function 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
138 });
139 });
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 */
148function Outlet(_props) {
149 return useOutlet();
150}
151
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 */
157function 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>.`) ;
159}
160
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 */
170function 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 }
189
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);
199
200 if (trailingPathname == null) {
201 return null;
202 }
203
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.`) ;
213
214 if (location == null) {
215 return null;
216 }
217
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 }));
227}
228
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
234 */
235function Routes({
236 children,
237 location
238}) {
239 return useRoutes(createRoutesFromChildren(children), location);
240} ///////////////////////////////////////////////////////////////////////////////
241// HOOKS
242///////////////////////////////////////////////////////////////////////////////
243
244/**
245 * Returns the full href for the given "to" value. This is useful for building
246 * custom links that are also accessible and preserve right-click behavior.
247 *
248 * @see https://reactrouter.com/docs/en/v6/api#usehref
249 */
250
251function useHref(to) {
252 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
253 // router loaded. We can help them understand how to avoid that.
254 `useHref() may be used only in the context of a <Router> component.`) : void 0;
255 let {
256 basename,
257 navigator
258 } = useContext(NavigationContext);
259 let {
260 hash,
261 pathname,
262 search
263 } = useResolvedPath(to);
264 let joinedPathname = pathname;
265
266 if (basename !== "/") {
267 let toPathname = getToPathname(to);
268 let endsWithSlash = toPathname != null && toPathname.endsWith("/");
269 joinedPathname = pathname === "/" ? basename + (endsWithSlash ? "/" : "") : joinPaths([basename, pathname]);
270 }
271
272 return navigator.createHref({
273 pathname: joinedPathname,
274 search,
275 hash
276 });
277}
278/**
279 * Returns true if this component is a descendant of a <Router>.
280 *
281 * @see https://reactrouter.com/docs/en/v6/api#useinroutercontext
282 */
283
284function useInRouterContext() {
285 return useContext(LocationContext) != null;
286}
287/**
288 * Returns the current location object, which represents the current URL in web
289 * browsers.
290 *
291 * Note: If you're using this it may mean you're doing some of your own
292 * "routing" in your app, and we'd like to know what your use case is. We may
293 * be able to provide something higher-level to better suit your needs.
294 *
295 * @see https://reactrouter.com/docs/en/v6/api#uselocation
296 */
297
298function useLocation() {
299 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
300 // router loaded. We can help them understand how to avoid that.
301 `useLocation() may be used only in the context of a <Router> component.`) : void 0;
302 return useContext(LocationContext).location;
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/docs/en/v6/api#usenavigationtype
309 */
310
311function useNavigationType() {
312 return useContext(LocationContext).navigationType;
313}
314/**
315 * Returns true if the URL for the given "to" value matches the current URL.
316 * This is useful for components that need to know "active" state, e.g.
317 * <NavLink>.
318 *
319 * @see https://reactrouter.com/docs/en/v6/api#usematch
320 */
321
322function useMatch(pattern) {
323 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
324 // router loaded. We can help them understand how to avoid that.
325 `useMatch() may be used only in the context of a <Router> component.`) : void 0;
326 return matchPath(pattern, useLocation().pathname);
327}
328/**
329 * The interface for the navigate() function returned from useNavigate().
330 */
331
332/**
333 * Returns an imperative method for changing the location. Used by <Link>s, but
334 * may also be used by other elements to change the location.
335 *
336 * @see https://reactrouter.com/docs/en/v6/api#usenavigate
337 */
338function useNavigate() {
339 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
340 // router loaded. We can help them understand how to avoid that.
341 `useNavigate() may be used only in the context of a <Router> component.`) : void 0;
342 let {
343 basename,
344 navigator
345 } = useContext(NavigationContext);
346 let {
347 matches
348 } = useContext(RouteContext);
349 let {
350 pathname: locationPathname
351 } = useLocation();
352 let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
353 let activeRef = useRef(false);
354 useEffect(() => {
355 activeRef.current = true;
356 });
357 let navigate = useCallback((to, options = {}) => {
358 warning(activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.`) ;
359 if (!activeRef.current) return;
360
361 if (typeof to === "number") {
362 navigator.go(to);
363 return;
364 }
365
366 let path = resolveTo(to, JSON.parse(routePathnamesJson), locationPathname);
367
368 if (basename !== "/") {
369 path.pathname = joinPaths([basename, path.pathname]);
370 }
371
372 (!!options.replace ? navigator.replace : navigator.push)(path, options.state);
373 }, [basename, navigator, routePathnamesJson, locationPathname]);
374 return navigate;
375}
376/**
377 * Returns the element for the child route at this level of the route
378 * hierarchy. Used internally by <Outlet> to render child routes.
379 *
380 * @see https://reactrouter.com/docs/en/v6/api#useoutlet
381 */
382
383function useOutlet() {
384 return useContext(RouteContext).outlet;
385}
386/**
387 * Returns an object of key/value pairs of the dynamic params from the current
388 * URL that were matched by the route path.
389 *
390 * @see https://reactrouter.com/docs/en/v6/api#useparams
391 */
392
393function useParams() {
394 let {
395 matches
396 } = useContext(RouteContext);
397 let routeMatch = matches[matches.length - 1];
398 return routeMatch ? routeMatch.params : {};
399}
400/**
401 * Resolves the pathname of the given `to` value against the current location.
402 *
403 * @see https://reactrouter.com/docs/en/v6/api#useresolvedpath
404 */
405
406function useResolvedPath(to) {
407 let {
408 matches
409 } = useContext(RouteContext);
410 let {
411 pathname: locationPathname
412 } = useLocation();
413 let routePathnamesJson = JSON.stringify(matches.map(match => match.pathnameBase));
414 return useMemo(() => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname), [to, routePathnamesJson, locationPathname]);
415}
416/**
417 * Returns the element of the route that matched the current location, prepared
418 * with the correct context to render the remainder of the route tree. Route
419 * elements in the tree must render an <Outlet> to render their child route's
420 * element.
421 *
422 * @see https://reactrouter.com/docs/en/v6/api#useroutes
423 */
424
425function useRoutes(routes, locationArg) {
426 !useInRouterContext() ? invariant(false, // TODO: This error is probably because they somehow have 2 versions of the
427 // router loaded. We can help them understand how to avoid that.
428 `useRoutes() may be used only in the context of a <Router> component.`) : void 0;
429 let {
430 matches: parentMatches
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;
437
438 {
439 // You won't get a warning about 2 different <Routes> under a <Route>
440 // without a trailing *, but this is a best-effort warning anyway since we
441 // cannot even give the warning unless they land at the parent route.
442 //
443 // Example:
444 //
445 // <Routes>
446 // {/* This route path MUST end with /* because otherwise
447 // it will never match /blog/post/123 */}
448 // <Route path="blog" element={<Blog />} />
449 // <Route path="blog/feed" element={<BlogFeed />} />
450 // </Routes>
451 //
452 // function Blog() {
453 // return (
454 // <Routes>
455 // <Route path="post/:id" element={<Post />} />
456 // </Routes>
457 // );
458 // }
459 let parentPath = parentRoute && parentRoute.path || "";
460 warningOnce(parentPathname, !parentRoute || parentPath.endsWith("*"), `You rendered descendant <Routes> (or called \`useRoutes()\`) at ` + `"${parentPathname}" (under <Route path="${parentPath}">) but the ` + `parent route path has no trailing "*". This means if you navigate ` + `deeper, the parent won't match anymore and therefore the child ` + `routes will never render.\n\n` + `Please change the parent <Route path="${parentPath}"> to <Route ` + `path="${parentPath}/*">.`);
461 }
462
463 let locationFromContext = useLocation();
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
474 let pathname = location.pathname || "/";
475 let remainingPathname = parentPathnameBase === "/" ? pathname : pathname.slice(parentPathnameBase.length) || "/";
476 let matches = matchRoutes(routes, {
477 pathname: remainingPathname
478 });
479
480 {
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.`) ;
483 }
484
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);
490} ///////////////////////////////////////////////////////////////////////////////
491// UTILS
492///////////////////////////////////////////////////////////////////////////////
493
494/**
495 * Creates a route config from a React "children" object, which is usually
496 * either a `<Route>` element or an array of them. Used internally by
497 * `<Routes>` to create a route config from its children.
498 *
499 * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren
500 */
501
502function createRoutesFromChildren(children) {
503 let routes = [];
504 Children.forEach(children, element => {
505 if (! /*#__PURE__*/isValidElement(element)) {
506 // Ignore non-elements. This allows people to more easily inline
507 // conditionals in their route config.
508 return;
509 }
510
511 if (element.type === Fragment) {
512 // Transparently support React.Fragment and its children.
513 routes.push.apply(routes, createRoutesFromChildren(element.props.children));
514 return;
515 }
516
517 !(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;
518 let route = {
519 caseSensitive: element.props.caseSensitive,
520 element: element.props.element,
521 index: element.props.index,
522 path: element.props.path
523 };
524
525 if (element.props.children) {
526 route.children = createRoutesFromChildren(element.props.children);
527 }
528
529 routes.push(route);
530 });
531 return routes;
532}
533/**
534 * The parameters that were parsed from the URL path.
535 */
536
537/**
538 * Returns a path with params interpolated.
539 *
540 * @see https://reactrouter.com/docs/en/v6/api#generatepath
541 */
542function generatePath(path, params = {}) {
543 return path.replace(/:(\w+)/g, (_, key) => {
544 !(params[key] != null) ? invariant(false, `Missing ":${key}" param`) : void 0;
545 return params[key];
546 }).replace(/\/*\*$/, _ => params["*"] == null ? "" : params["*"].replace(/^\/*/, "/"));
547}
548/**
549 * A RouteMatch contains info about how a route matched a URL.
550 */
551
552/**
553 * Matches the given routes to a location and returns the match data.
554 *
555 * @see https://reactrouter.com/docs/en/v6/api#matchroutes
556 */
557function matchRoutes(routes, locationArg, basename = "/") {
558 let location = typeof locationArg === "string" ? parsePath(locationArg) : locationArg;
559 let pathname = stripBasename(location.pathname || "/", basename);
560
561 if (pathname == null) {
562 return null;
563 }
564
565 let branches = flattenRoutes(routes);
566 rankRouteBranches(branches);
567 let matches = null;
568
569 for (let i = 0; matches == null && i < branches.length; ++i) {
570 matches = matchRouteBranch(branches[i], routes, pathname);
571 }
572
573 return matches;
574}
575
576function flattenRoutes(routes, branches = [], parentsMeta = [], parentPath = "") {
577 routes.forEach((route, index) => {
578 let meta = {
579 relativePath: route.path || "",
580 caseSensitive: route.caseSensitive === true,
581 childrenIndex: index
582 };
583
584 if (meta.relativePath.startsWith("/")) {
585 !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;
586 meta.relativePath = meta.relativePath.slice(parentPath.length);
587 }
588
589 let path = joinPaths([parentPath, meta.relativePath]);
590 let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the
591 // route tree depth-first and child routes appear before their parents in
592 // the "flattened" version.
593
594 if (route.children && route.children.length > 0) {
595 !(route.index !== true) ? invariant(false, `Index routes must not have child routes. Please remove ` + `all child routes from route path "${path}".`) : void 0;
596 flattenRoutes(route.children, branches, routesMeta, path);
597 } // Routes without a path shouldn't ever match by themselves unless they are
598 // index routes, so don't add them to the list of possible branches.
599
600
601 if (route.path == null && !route.index) {
602 return;
603 }
604
605 branches.push({
606 path,
607 score: computeScore(path, route.index),
608 routesMeta
609 });
610 });
611 return branches;
612}
613
614function rankRouteBranches(branches) {
615 branches.sort((a, b) => a.score !== b.score ? b.score - a.score // Higher score first
616 : compareIndexes(a.routesMeta.map(meta => meta.childrenIndex), b.routesMeta.map(meta => meta.childrenIndex)));
617}
618
619const paramRe = /^:\w+$/;
620const dynamicSegmentValue = 3;
621const indexRouteValue = 2;
622const emptySegmentValue = 1;
623const staticSegmentValue = 10;
624const splatPenalty = -2;
625
626const isSplat = s => s === "*";
627
628function computeScore(path, index) {
629 let segments = path.split("/");
630 let initialScore = segments.length;
631
632 if (segments.some(isSplat)) {
633 initialScore += splatPenalty;
634 }
635
636 if (index) {
637 initialScore += indexRouteValue;
638 }
639
640 return segments.filter(s => !isSplat(s)).reduce((score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === "" ? emptySegmentValue : staticSegmentValue), initialScore);
641}
642
643function compareIndexes(a, b) {
644 let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]);
645 return siblings ? // If two routes are siblings, we should try to match the earlier sibling
646 // first. This allows people to have fine-grained control over the matching
647 // behavior by simply putting routes with identical paths in the order they
648 // want them tried.
649 a[a.length - 1] - b[b.length - 1] : // Otherwise, it doesn't really make sense to rank non-siblings by index,
650 // so they sort equally.
651 0;
652}
653
654function matchRouteBranch(branch, // TODO: attach original route object inside routesMeta so we don't need this arg
655routesArg, pathname) {
656 let routes = routesArg;
657 let {
658 routesMeta
659 } = branch;
660 let matchedParams = {};
661 let matchedPathname = "/";
662 let matches = [];
663
664 for (let i = 0; i < routesMeta.length; ++i) {
665 let meta = routesMeta[i];
666 let end = i === routesMeta.length - 1;
667 let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/";
668 let match = matchPath({
669 path: meta.relativePath,
670 caseSensitive: meta.caseSensitive,
671 end
672 }, remainingPathname);
673 if (!match) return null;
674 Object.assign(matchedParams, match.params);
675 let route = routes[meta.childrenIndex];
676 matches.push({
677 params: matchedParams,
678 pathname: joinPaths([matchedPathname, match.pathname]),
679 pathnameBase: joinPaths([matchedPathname, match.pathnameBase]),
680 route
681 });
682
683 if (match.pathnameBase !== "/") {
684 matchedPathname = joinPaths([matchedPathname, match.pathnameBase]);
685 }
686
687 routes = route.children;
688 }
689
690 return matches;
691}
692/**
693 * Renders the result of `matchRoutes()` into a React element.
694 */
695
696
697function renderMatches(matches) {
698 return _renderMatches(matches);
699}
700
701function _renderMatches(matches, parentMatches = []) {
702 if (matches == null) return null;
703 return matches.reduceRight((outlet, match, index) => {
704 return /*#__PURE__*/createElement(RouteContext.Provider, {
705 children: match.route.element !== undefined ? match.route.element : /*#__PURE__*/createElement(Outlet, null),
706 value: {
707 outlet,
708 matches: parentMatches.concat(matches.slice(0, index + 1))
709 }
710 });
711 }, null);
712}
713/**
714 * A PathPattern is used to match on some portion of a URL pathname.
715 */
716
717
718/**
719 * Performs pattern matching on a URL pathname and returns information about
720 * the match.
721 *
722 * @see https://reactrouter.com/docs/en/v6/api#matchpath
723 */
724function matchPath(pattern, pathname) {
725 if (typeof pattern === "string") {
726 pattern = {
727 path: pattern,
728 caseSensitive: false,
729 end: true
730 };
731 }
732
733 let [matcher, paramNames] = compilePath(pattern.path, pattern.caseSensitive, pattern.end);
734 let match = pathname.match(matcher);
735 if (!match) return null;
736 let matchedPathname = match[0];
737 let pathnameBase = matchedPathname.replace(/(.)\/+$/, "$1");
738 let captureGroups = match.slice(1);
739 let params = paramNames.reduce((memo, paramName, index) => {
740 // We need to compute the pathnameBase here using the raw splat value
741 // instead of using params["*"] later because it will be decoded then
742 if (paramName === "*") {
743 let splatValue = captureGroups[index] || "";
744 pathnameBase = matchedPathname.slice(0, matchedPathname.length - splatValue.length).replace(/(.)\/+$/, "$1");
745 }
746
747 memo[paramName] = safelyDecodeURIComponent(captureGroups[index] || "", paramName);
748 return memo;
749 }, {});
750 return {
751 params,
752 pathname: matchedPathname,
753 pathnameBase,
754 pattern
755 };
756}
757
758function compilePath(path, caseSensitive = false, end = true) {
759 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(/\*$/, "/*")}".`) ;
760 let paramNames = [];
761 let regexpSource = "^" + path.replace(/\/*\*?$/, "") // Ignore trailing / and /*, we'll handle it below
762 .replace(/^\/*/, "/") // Make sure it has a leading /
763 .replace(/[\\.*+^$?{}|()[\]]/g, "\\$&") // Escape special regex chars
764 .replace(/:(\w+)/g, (_, paramName) => {
765 paramNames.push(paramName);
766 return "([^\\/]+)";
767 });
768
769 if (path.endsWith("*")) {
770 paramNames.push("*");
771 regexpSource += path === "*" || path === "/*" ? "(.*)$" // Already matched the initial /, just match the rest
772 : "(?:\\/(.+)|\\/*)$"; // Don't include the / in params["*"]
773 } else {
774 regexpSource += end ? "\\/*$" // When matching to the end, ignore trailing slashes
775 : // Otherwise, at least match a word boundary. This restricts parent
776 // routes to matching only their own words and nothing more, e.g. parent
777 // route "/home" should not match "/home2".
778 "(?:\\b|$)";
779 }
780
781 let matcher = new RegExp(regexpSource, caseSensitive ? undefined : "i");
782 return [matcher, paramNames];
783}
784
785function safelyDecodeURIComponent(value, paramName) {
786 try {
787 return decodeURIComponent(value);
788 } catch (error) {
789 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}).`) ;
790 return value;
791 }
792}
793/**
794 * Returns a resolved path object relative to the given pathname.
795 *
796 * @see https://reactrouter.com/docs/en/v6/api#resolvepath
797 */
798
799
800function resolvePath(to, fromPathname = "/") {
801 let {
802 pathname: toPathname,
803 search = "",
804 hash = ""
805 } = typeof to === "string" ? parsePath(to) : to;
806 let pathname = toPathname ? toPathname.startsWith("/") ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname;
807 return {
808 pathname,
809 search: normalizeSearch(search),
810 hash: normalizeHash(hash)
811 };
812}
813
814function resolvePathname(relativePath, fromPathname) {
815 let segments = fromPathname.replace(/\/+$/, "").split("/");
816 let relativeSegments = relativePath.split("/");
817 relativeSegments.forEach(segment => {
818 if (segment === "..") {
819 // Keep the root "" segment so the pathname starts at /
820 if (segments.length > 1) segments.pop();
821 } else if (segment !== ".") {
822 segments.push(segment);
823 }
824 });
825 return segments.length > 1 ? segments.join("/") : "/";
826}
827
828function resolveTo(toArg, routePathnames, locationPathname) {
829 let to = typeof toArg === "string" ? parsePath(toArg) : toArg;
830 let toPathname = toArg === "" || to.pathname === "" ? "/" : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the
831 // route context. This is explained in `Note on `<Link to>` values` in our
832 // migration guide from v5 as a means of disambiguation between `to` values
833 // that begin with `/` and those that do not. However, this is problematic for
834 // `to` values that do not provide a pathname. `to` can simply be a search or
835 // hash string, in which case we should assume that the navigation is relative
836 // to the current location's pathname and *not* the route pathname.
837
838 let from;
839
840 if (toPathname == null) {
841 from = locationPathname;
842 } else {
843 let routePathnameIndex = routePathnames.length - 1;
844
845 if (toPathname.startsWith("..")) {
846 let toSegments = toPathname.split("/"); // Each leading .. segment means "go up one route" instead of "go up one
847 // URL segment". This is a key difference from how <a href> works and a
848 // major reason we call this a "to" value instead of a "href".
849
850 while (toSegments[0] === "..") {
851 toSegments.shift();
852 routePathnameIndex -= 1;
853 }
854
855 to.pathname = toSegments.join("/");
856 } // If there are more ".." segments than parent routes, resolve relative to
857 // the root / URL.
858
859
860 from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : "/";
861 }
862
863 let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one.
864
865 if (toPathname && toPathname !== "/" && toPathname.endsWith("/") && !path.pathname.endsWith("/")) {
866 path.pathname += "/";
867 }
868
869 return path;
870}
871
872function getToPathname(to) {
873 // Empty strings should be treated the same as / paths
874 return to === "" || to.pathname === "" ? "/" : typeof to === "string" ? parsePath(to).pathname : to.pathname;
875}
876
877function stripBasename(pathname, basename) {
878 if (basename === "/") return pathname;
879
880 if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) {
881 return null;
882 }
883
884 let nextChar = pathname.charAt(basename.length);
885
886 if (nextChar && nextChar !== "/") {
887 // pathname does not start with basename/
888 return null;
889 }
890
891 return pathname.slice(basename.length) || "/";
892}
893
894const joinPaths = paths => paths.join("/").replace(/\/\/+/g, "/");
895
896const normalizePathname = pathname => pathname.replace(/\/+$/, "").replace(/^\/*/, "/");
897
898const normalizeSearch = search => !search || search === "?" ? "" : search.startsWith("?") ? search : "?" + search;
899
900const normalizeHash = hash => !hash || hash === "#" ? "" : hash.startsWith("#") ? hash : "#" + hash; ///////////////////////////////////////////////////////////////////////////////
901
902export { 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 };
903//# sourceMappingURL=react-router.development.js.map