About

TeamSystem Hospitality
import * as React from “react”; import type { Hash, History, InitialEntry, Location, MemoryHistory, Path, Pathname, Search, To, } from “history”; import { Action as NavigationType, createMemoryHistory, parsePath, createPath, } from “history”; export { parsePath, createPath, NavigationType }; export type { Hash, Location, Path, Pathname, Search, To }; function invariant(cond: any, message: string): asserts cond { if (!cond) throw new Error(message); } function warning(cond: any, message: string): void { if (!cond) { // eslint-disable-next-line no-console if (typeof console !== “undefined”) console.warn(message); try { // Welcome to debugging React Router! // // This error is thrown as a convenience so you can more easily // find the source for a warning that appears in the console by // enabling “pause on exceptions” in your JavaScript debugger. throw new Error(message); // eslint-disable-next-line no-empty } catch (e) {} } } const alreadyWarned: Record = {}; function warningOnce(key: string, cond: boolean, message: string) { if (!cond && !alreadyWarned[key]) { alreadyWarned[key] = true; warning(false, message); } } /////////////////////////////////////////////////////////////////////////////// // CONTEXT /////////////////////////////////////////////////////////////////////////////// /** * A Navigator is a “location changer”; it’s how you get to different locations. * * Every history instance conforms to the Navigator interface, but the * distinction is useful primarily when it comes to the low-level API * where both the location and a navigator must be provided separately in order * to avoid “tearing” that may occur in a suspense-enabled app if the action * and/or location were to be read directly from the history instance. */ export type Navigator = Pick; interface NavigationContextObject { basename: string; navigator: Navigator; static: boolean; } const NavigationContext = React.createContext(null!); if (__DEV__) { NavigationContext.displayName = “Navigation”; } interface LocationContextObject { location: Location; navigationType: NavigationType; } const LocationContext = React.createContext(null!); if (__DEV__) { LocationContext.displayName = “Location”; } interface RouteContextObject { outlet: React.ReactElement | null; matches: RouteMatch[]; } const RouteContext = React.createContext({ outlet: null, matches: [], }); if (__DEV__) { RouteContext.displayName = “Route”; } /////////////////////////////////////////////////////////////////////////////// // COMPONENTS /////////////////////////////////////////////////////////////////////////////// export interface MemoryRouterProps { basename?: string; children?: React.ReactNode; initialEntries?: InitialEntry[]; initialIndex?: number; } /** * A that stores all entries in memory. * * @see https://reactrouter.com/docs/en/v6/api#memoryrouter */ export function MemoryRouter({ basename, children, initialEntries, initialIndex, }: MemoryRouterProps): React.ReactElement { let historyRef = React.useRef(); if (historyRef.current == null) { historyRef.current = createMemoryHistory({ initialEntries, initialIndex }); } let history = historyRef.current; let [state, setState] = React.useState({ action: history.action, location: history.location, }); React.useLayoutEffect(() => history.listen(setState), [history]); return ( ); } export interface NavigateProps { to: To; replace?: boolean; state?: any; } /** * Changes the current location. * * Note: This API is mostly useful in React.Component subclasses that are not * able to use hooks. In functional components, we recommend you use the * `useNavigate` hook instead. * * @see https://reactrouter.com/docs/en/v6/api#navigate */ export function Navigate({ to, replace, state }: NavigateProps): null { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of // the router loaded. We can help them understand how to avoid that. ` may be used only in the context of a component.` ); warning( !React.useContext(NavigationContext).static, ` must not be used on the initial render in a . ` + `This is a no-op, but you should modify your code so the is ` + `only ever rendered in response to some user interaction or state change.` ); let navigate = useNavigate(); React.useEffect(() => { navigate(to, { replace, state }); }); return null; } export interface OutletProps { context?: unknown; } /** * Renders the child route’s element, if there is one. * * @see https://reactrouter.com/docs/en/v6/api#outlet */ export function Outlet(props: OutletProps): React.ReactElement | null { return useOutlet(props.context); } export interface RouteProps { caseSensitive?: boolean; children?: React.ReactNode; element?: React.ReactNode | null; index?: boolean; path?: string; } export interface PathRouteProps { caseSensitive?: boolean; children?: React.ReactNode; element?: React.ReactNode | null; index?: false; path: string; } export interface LayoutRouteProps { children?: React.ReactNode; element?: React.ReactNode | null; } export interface IndexRouteProps { element?: React.ReactNode | null; index: true; } /** * Declares an element that should be rendered at a certain URL path. * * @see https://reactrouter.com/docs/en/v6/api#route */ export function Route( _props: PathRouteProps | LayoutRouteProps | IndexRouteProps ): React.ReactElement | null { invariant( false, `A is only ever to be used as the child of element, ` + `never rendered directly. Please wrap your in a .` ); } export interface RouterProps { basename?: string; children?: React.ReactNode; location: Partial | string; navigationType?: NavigationType; navigator: Navigator; static?: boolean; } /** * Provides location context for the rest of the app. * * Note: You usually won’t render a directly. Instead, you’ll render a * router that is more specific to your environment such as a * in web browsers or a for server rendering. * * @see https://reactrouter.com/docs/en/v6/api#router */ export function Router({ basename: basenameProp = “/”, children = null, location: locationProp, navigationType = NavigationType.Pop, navigator, static: staticProp = false, }: RouterProps): React.ReactElement | null { invariant( !useInRouterContext(), `You cannot render a inside another .` + ` You should never have more than one in your app.` ); let basename = normalizePathname(basenameProp); let navigationContext = React.useMemo( () => ({ basename, navigator, static: staticProp }), [basename, navigator, staticProp] ); if (typeof locationProp === “string”) { locationProp = parsePath(locationProp); } let { pathname = “/”, search = “”, hash = “”, state = null, key = “default”, } = locationProp; let location = React.useMemo(() => { let trailingPathname = stripBasename(pathname, basename); if (trailingPathname == null) { return null; } return { pathname: trailingPathname, search, hash, state, key, }; }, [basename, pathname, search, hash, state, key]); warning( location != null, ` is not able to match the URL ` + `”${pathname}${search}${hash}” because it does not start with the ` + `basename, so the won’t render anything.` ); if (location == null) { return null; } return ( ); } export interface RoutesProps { children?: React.ReactNode; location?: Partial | string; } /** * A container for a nested tree of elements that renders the branch * that best matches the current location. * * @see https://reactrouter.com/docs/en/v6/api#routes */ export function Routes({ children, location, }: RoutesProps): React.ReactElement | null { return useRoutes(createRoutesFromChildren(children), location); } /////////////////////////////////////////////////////////////////////////////// // HOOKS /////////////////////////////////////////////////////////////////////////////// /** * Returns the full href for the given “to” value. This is useful for building * custom links that are also accessible and preserve right-click behavior. * * @see https://reactrouter.com/docs/en/v6/api#usehref */ export function useHref(to: To): string { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useHref() may be used only in the context of a component.` ); let { basename, navigator } = React.useContext(NavigationContext); let { hash, pathname, search } = useResolvedPath(to); let joinedPathname = pathname; if (basename !== “/”) { let toPathname = getToPathname(to); let endsWithSlash = toPathname != null && toPathname.endsWith(“/”); joinedPathname = pathname === “/” ? basename + (endsWithSlash ? “/” : “”) : joinPaths([basename, pathname]); } return navigator.createHref({ pathname: joinedPathname, search, hash }); } /** * Returns true if this component is a descendant of a . * * @see https://reactrouter.com/docs/en/v6/api#useinroutercontext */ export function useInRouterContext(): boolean { return React.useContext(LocationContext) != null; } /** * Returns the current location object, which represents the current URL in web * browsers. * * Note: If you’re using this it may mean you’re doing some of your own * “routing” in your app, and we’d like to know what your use case is. We may * be able to provide something higher-level to better suit your needs. * * @see https://reactrouter.com/docs/en/v6/api#uselocation */ export function useLocation(): Location { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useLocation() may be used only in the context of a component.` ); return React.useContext(LocationContext).location; } type ParamParseFailed = { failed: true }; type ParamParseSegment = // Check here if there exists a forward slash in the string. Segment extends `${infer LeftSegment}/${infer RightSegment}` ? // If there is a forward slash, then attempt to parse each side of the // forward slash. ParamParseSegment extends infer LeftResult ? ParamParseSegment extends infer RightResult ? LeftResult extends string ? // If the left side is successfully parsed as a param, then check if // the right side can be successfully parsed as well. If both sides // can be parsed, then the result is a union of the two sides // (read: “foo” | “bar”). RightResult extends string ? LeftResult | RightResult : LeftResult : // If the left side is not successfully parsed as a param, then check // if only the right side can be successfully parse as a param. If it // can, then the result is just right, else it’s a failure. RightResult extends string ? RightResult : ParamParseFailed : ParamParseFailed : // If the left side didn’t parse into a param, then just check the right // side. ParamParseSegment extends infer RightResult ? RightResult extends string ? RightResult : ParamParseFailed : ParamParseFailed : // If there’s no forward slash, then check if this segment starts with a // colon. If it does, then this is a dynamic segment, so the result is // just the remainder of the string. Otherwise, it’s a failure. Segment extends `:${infer Remaining}` ? Remaining : ParamParseFailed; // Attempt to parse the given string segment. If it fails, then just return the // plain string type as a default fallback. Otherwise return the union of the // parsed string literals that were referenced as dynamic segments in the route. type ParamParseKey = ParamParseSegment extends string ? ParamParseSegment : string; /** * Returns the current navigation action which describes how the router came to * the current location, either by a pop, push, or replace on the history stack. * * @see https://reactrouter.com/docs/en/v6/api#usenavigationtype */ export function useNavigationType(): NavigationType { return React.useContext(LocationContext).navigationType; } /** * Returns true if the URL for the given “to” value matches the current URL. * This is useful for components that need to know “active” state, e.g. * . * * @see https://reactrouter.com/docs/en/v6/api#usematch */ export function useMatch< ParamKey extends ParamParseKey, Path extends string >(pattern: PathPattern | Path): PathMatch | null { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useMatch() may be used only in the context of a component.` ); let { pathname } = useLocation(); return React.useMemo( () => matchPath(pattern, pathname), [pathname, pattern] ); } /** * The interface for the navigate() function returned from useNavigate(). */ export interface NavigateFunction { (to: To, options?: NavigateOptions): void; (delta: number): void; } export interface NavigateOptions { replace?: boolean; state?: any; } /** * Returns an imperative method for changing the location. Used by s, but * may also be used by other elements to change the location. * * @see https://reactrouter.com/docs/en/v6/api#usenavigate */ export function useNavigate(): NavigateFunction { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useNavigate() may be used only in the context of a component.` ); let { basename, navigator } = React.useContext(NavigationContext); let { matches } = React.useContext(RouteContext); let { pathname: locationPathname } = useLocation(); let routePathnamesJson = JSON.stringify( matches.map((match) => match.pathnameBase) ); let activeRef = React.useRef(false); React.useEffect(() => { activeRef.current = true; }); let navigate: NavigateFunction = React.useCallback( (to: To | number, options: NavigateOptions = {}) => { warning( activeRef.current, `You should call navigate() in a React.useEffect(), not when ` + `your component is first rendered.` ); if (!activeRef.current) return; if (typeof to === “number”) { navigator.go(to); return; } let path = resolveTo( to, JSON.parse(routePathnamesJson), locationPathname ); if (basename !== “/”) { path.pathname = joinPaths([basename, path.pathname]); } (!!options.replace ? navigator.replace : navigator.push)( path, options.state ); }, [basename, navigator, routePathnamesJson, locationPathname] ); return navigate; } const OutletContext = React.createContext(null); /** * Returns the context (if provided) for the child route at this level of the route * hierarchy. * @see https://reactrouter.com/docs/en/v6/api#useoutletcontext */ export function useOutletContext(): Context { return React.useContext(OutletContext) as Context; } /** * Returns the element for the child route at this level of the route * hierarchy. Used internally by to render child routes. * * @see https://reactrouter.com/docs/en/v6/api#useoutlet */ export function useOutlet(context?: unknown): React.ReactElement | null { let outlet = React.useContext(RouteContext).outlet; if (outlet) { return ( {outlet} ); } return outlet; } /** * Returns an object of key/value pairs of the dynamic params from the current * URL that were matched by the route path. * * @see https://reactrouter.com/docs/en/v6/api#useparams */ export function useParams< ParamsOrKey extends string | Record = string >(): Readonly< [ParamsOrKey] extends [string] ? Params : Partial > { let { matches } = React.useContext(RouteContext); let routeMatch = matches[matches.length – 1]; return routeMatch ? (routeMatch.params as any) : {}; } /** * Resolves the pathname of the given `to` value against the current location. * * @see https://reactrouter.com/docs/en/v6/api#useresolvedpath */ export function useResolvedPath(to: To): Path { let { matches } = React.useContext(RouteContext); let { pathname: locationPathname } = useLocation(); let routePathnamesJson = JSON.stringify( matches.map((match) => match.pathnameBase) ); return React.useMemo( () => resolveTo(to, JSON.parse(routePathnamesJson), locationPathname), [to, routePathnamesJson, locationPathname] ); } /** * Returns the element of the route that matched the current location, prepared * with the correct context to render the remainder of the route tree. Route * elements in the tree must render an to render their child route’s * element. * * @see https://reactrouter.com/docs/en/v6/api#useroutes */ export function useRoutes( routes: RouteObject[], locationArg?: Partial | string ): React.ReactElement | null { invariant( useInRouterContext(), // TODO: This error is probably because they somehow have 2 versions of the // router loaded. We can help them understand how to avoid that. `useRoutes() may be used only in the context of a component.` ); let { matches: parentMatches } = React.useContext(RouteContext); let routeMatch = parentMatches[parentMatches.length – 1]; let parentParams = routeMatch ? routeMatch.params : {}; let parentPathname = routeMatch ? routeMatch.pathname : “/”; let parentPathnameBase = routeMatch ? routeMatch.pathnameBase : “/”; let parentRoute = routeMatch && routeMatch.route; if (__DEV__) { // You won’t get a warning about 2 different under a // without a trailing *, but this is a best-effort warning anyway since we // cannot even give the warning unless they land at the parent route. // // Example: // // // {/* This route path MUST end with /* because otherwise // it will never match /blog/post/123 */} // } /> // } /> // // // function Blog() { // return ( // // } /> // // ); // } let parentPath = (parentRoute && parentRoute.path) || “”; warningOnce( parentPathname, !parentRoute || parentPath.endsWith(“*”), `You rendered descendant (or called \`useRoutes()\`) at ` + `”${parentPathname}” (under ) 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 to .` ); } let locationFromContext = useLocation(); let location; if (locationArg) { let parsedLocationArg = typeof locationArg === “string” ? parsePath(locationArg) : locationArg; invariant( parentPathnameBase === “/” || parsedLocationArg.pathname?.startsWith(parentPathnameBase), `When overriding the location using \`\` 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.` ); location = parsedLocationArg; } else { location = locationFromContext; } let pathname = location.pathname || “/”; let remainingPathname = parentPathnameBase === “/” ? pathname : pathname.slice(parentPathnameBase.length) || “/”; let matches = matchRoutes(routes, { pathname: remainingPathname }); if (__DEV__) { warning( parentRoute || matches != null, `No routes matched location “${location.pathname}${location.search}${location.hash}” ` ); 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 with a null value by default resulting in an “empty” page.` ); } return _renderMatches( matches && matches.map((match) => Object.assign({}, match, { params: Object.assign({}, parentParams, match.params), pathname: joinPaths([parentPathnameBase, match.pathname]), pathnameBase: match.pathnameBase === “/” ? parentPathnameBase : joinPaths([parentPathnameBase, match.pathnameBase]), }) ), parentMatches ); } /////////////////////////////////////////////////////////////////////////////// // UTILS /////////////////////////////////////////////////////////////////////////////// /** * Creates a route config from a React “children” object, which is usually * either a `` element or an array of them. Used internally by * `` to create a route config from its children. * * @see https://reactrouter.com/docs/en/v6/api#createroutesfromchildren */ export function createRoutesFromChildren( children: React.ReactNode ): RouteObject[] { let routes: RouteObject[] = []; React.Children.forEach(children, (element) => { if (!React.isValidElement(element)) { // Ignore non-elements. This allows people to more easily inline // conditionals in their route config. return; } if (element.type === React.Fragment) { // Transparently support React.Fragment and its children. routes.push.apply( routes, createRoutesFromChildren(element.props.children) ); return; } invariant( element.type === Route, `[${ typeof element.type === “string” ? element.type : element.type.name }] is not a component. All component children of must be a or ` ); let route: RouteObject = { caseSensitive: element.props.caseSensitive, element: element.props.element, index: element.props.index, path: element.props.path, }; if (element.props.children) { route.children = createRoutesFromChildren(element.props.children); } routes.push(route); }); return routes; } /** * The parameters that were parsed from the URL path. */ export type Params = { readonly [key in Key]: string | undefined; }; /** * A route object represents a logical route, with (optionally) its child * routes organized in a tree-like structure. */ export interface RouteObject { caseSensitive?: boolean; children?: RouteObject[]; element?: React.ReactNode; index?: boolean; path?: string; } /** * Returns a path with params interpolated. * * @see https://reactrouter.com/docs/en/v6/api#generatepath */ export function generatePath(path: string, params: Params = {}): string { return path .replace(/:(\w+)/g, (_, key) => { invariant(params[key] != null, `Missing “:${key}” param`); return params[key]!; }) .replace(/\/*\*$/, (_) => params[“*”] == null ? “” : params[“*”].replace(/^\/*/, “/”) ); } /** * A RouteMatch contains info about how a route matched a URL. */ export interface RouteMatch { /** * The names and values of dynamic parameters in the URL. */ params: Params; /** * The portion of the URL pathname that was matched. */ pathname: string; /** * The portion of the URL pathname that was matched before child routes. */ pathnameBase: string; /** * The route object that was used to match. */ route: RouteObject; } /** * Matches the given routes to a location and returns the match data. * * @see https://reactrouter.com/docs/en/v6/api#matchroutes */ export function matchRoutes( routes: RouteObject[], locationArg: Partial | string, basename = “/” ): RouteMatch[] | null { let location = typeof locationArg === “string” ? parsePath(locationArg) : locationArg; let pathname = stripBasename(location.pathname || “/”, basename); if (pathname == null) { return null; } let branches = flattenRoutes(routes); rankRouteBranches(branches); let matches = null; for (let i = 0; matches == null && i < branches.length; ++i) { matches = matchRouteBranch(branches[i], pathname); } return matches; } interface RouteMeta { relativePath: string; caseSensitive: boolean; childrenIndex: number; route: RouteObject; } interface RouteBranch { path: string; score: number; routesMeta: RouteMeta[]; } function flattenRoutes( routes: RouteObject[], branches: RouteBranch[] = [], parentsMeta: RouteMeta[] = [], parentPath = "" ): RouteBranch[] { routes.forEach((route, index) => { let meta: RouteMeta = { relativePath: route.path || “”, caseSensitive: route.caseSensitive === true, childrenIndex: index, route, }; if (meta.relativePath.startsWith(“/”)) { invariant( meta.relativePath.startsWith(parentPath), `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.` ); meta.relativePath = meta.relativePath.slice(parentPath.length); } let path = joinPaths([parentPath, meta.relativePath]); let routesMeta = parentsMeta.concat(meta); // Add the children before adding this route to the array so we traverse the // route tree depth-first and child routes appear before their parents in // the “flattened” version. if (route.children && route.children.length > 0) { invariant( route.index !== true, `Index routes must not have child routes. Please remove ` + `all child routes from route path “${path}”.` ); flattenRoutes(route.children, branches, routesMeta, path); } // Routes without a path shouldn’t ever match by themselves unless they are // index routes, so don’t add them to the list of possible branches. if (route.path == null && !route.index) { return; } branches.push({ path, score: computeScore(path, route.index), routesMeta }); }); return branches; } function rankRouteBranches(branches: RouteBranch[]): void { branches.sort((a, b) => a.score !== b.score ? b.score – a.score // Higher score first : compareIndexes( a.routesMeta.map((meta) => meta.childrenIndex), b.routesMeta.map((meta) => meta.childrenIndex) ) ); } const paramRe = /^:\w+$/; const dynamicSegmentValue = 3; const indexRouteValue = 2; const emptySegmentValue = 1; const staticSegmentValue = 10; const splatPenalty = -2; const isSplat = (s: string) => s === “*”; function computeScore(path: string, index: boolean | undefined): number { let segments = path.split(“/”); let initialScore = segments.length; if (segments.some(isSplat)) { initialScore += splatPenalty; } if (index) { initialScore += indexRouteValue; } return segments .filter((s) => !isSplat(s)) .reduce( (score, segment) => score + (paramRe.test(segment) ? dynamicSegmentValue : segment === “” ? emptySegmentValue : staticSegmentValue), initialScore ); } function compareIndexes(a: number[], b: number[]): number { let siblings = a.length === b.length && a.slice(0, -1).every((n, i) => n === b[i]); return siblings ? // If two routes are siblings, we should try to match the earlier sibling // first. This allows people to have fine-grained control over the matching // behavior by simply putting routes with identical paths in the order they // want them tried. a[a.length – 1] – b[b.length – 1] : // Otherwise, it doesn’t really make sense to rank non-siblings by index, // so they sort equally. 0; } function matchRouteBranch( branch: RouteBranch, pathname: string ): RouteMatch[] | null { let { routesMeta } = branch; let matchedParams = {}; let matchedPathname = “/”; let matches: RouteMatch[] = []; for (let i = 0; i < routesMeta.length; ++i) { let meta = routesMeta[i]; let end = i === routesMeta.length - 1; let remainingPathname = matchedPathname === "/" ? pathname : pathname.slice(matchedPathname.length) || "/"; let match = matchPath( { path: meta.relativePath, caseSensitive: meta.caseSensitive, end }, remainingPathname ); if (!match) return null; Object.assign(matchedParams, match.params); let route = meta.route; matches.push({ params: matchedParams, pathname: joinPaths([matchedPathname, match.pathname]), pathnameBase: normalizePathname( joinPaths([matchedPathname, match.pathnameBase]) ), route, }); if (match.pathnameBase !== "/") { matchedPathname = joinPaths([matchedPathname, match.pathnameBase]); } } return matches; } /** * Renders the result of `matchRoutes()` into a React element. */ export function renderMatches( matches: RouteMatch[] | null ): React.ReactElement | null { return _renderMatches(matches); } function _renderMatches( matches: RouteMatch[] | null, parentMatches: RouteMatch[] = [] ): React.ReactElement | null { if (matches == null) return null; return matches.reduceRight((outlet, match, index) => { return ( ); }, null as React.ReactElement | null); } /** * A PathPattern is used to match on some portion of a URL pathname. */ export interface PathPattern { /** * A string to match against a URL pathname. May contain `:id`-style segments * to indicate placeholders for dynamic parameters. May also end with `/*` to * indicate matching the rest of the URL pathname. */ path: Path; /** * Should be `true` if the static portions of the `path` should be matched in * the same case. */ caseSensitive?: boolean; /** * Should be `true` if this pattern should match the entire URL pathname. */ end?: boolean; } /** * A PathMatch contains info about how a PathPattern matched on a URL pathname. */ export interface PathMatch { /** * The names and values of dynamic parameters in the URL. */ params: Params; /** * The portion of the URL pathname that was matched. */ pathname: string; /** * The portion of the URL pathname that was matched before child routes. */ pathnameBase: string; /** * The pattern that was used to match. */ pattern: PathPattern; } type Mutable = { -readonly [P in keyof T]: T[P]; }; /** * Performs pattern matching on a URL pathname and returns information about * the match. * * @see https://reactrouter.com/docs/en/v6/api#matchpath */ export function matchPath< ParamKey extends ParamParseKey, Path extends string >( pattern: PathPattern | Path, pathname: string ): PathMatch | null { if (typeof pattern === “string”) { pattern = { path: pattern, caseSensitive: false, end: true }; } let [matcher, paramNames] = compilePath( pattern.path, pattern.caseSensitive, pattern.end ); let match = pathname.match(matcher); if (!match) return null; let matchedPathname = match[0]; let pathnameBase = matchedPathname.replace(/(.)\/+$/, “$1”); let captureGroups = match.slice(1); let params: Params = paramNames.reduce>( (memo, paramName, index) => { // We need to compute the pathnameBase here using the raw splat value // instead of using params[“*”] later because it will be decoded then if (paramName === “*”) { let splatValue = captureGroups[index] || “”; pathnameBase = matchedPathname .slice(0, matchedPathname.length – splatValue.length) .replace(/(.)\/+$/, “$1”); } memo[paramName] = safelyDecodeURIComponent( captureGroups[index] || “”, paramName ); return memo; }, {} ); return { params, pathname: matchedPathname, pathnameBase, pattern, }; } function compilePath( path: string, caseSensitive = false, end = true ): [RegExp, string[]] { 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(/\*$/, “/*”)}”.` ); let paramNames: string[] = []; let regexpSource = “^” + path .replace(/\/*\*?$/, “”) // Ignore trailing / and /*, we’ll handle it below .replace(/^\/*/, “/”) // Make sure it has a leading / .replace(/[\\.*+^$?{}|()[\]]/g, “\\$&”) // Escape special regex chars .replace(/:(\w+)/g, (_: string, paramName: string) => { paramNames.push(paramName); return “([^\\/]+)”; }); if (path.endsWith(“*”)) { paramNames.push(“*”); regexpSource += path === “*” || path === “/*” ? “(.*)$” // Already matched the initial /, just match the rest : “(?:\\/(.+)|\\/*)$”; // Don’t include the / in params[“*”] } else { regexpSource += end ? “\\/*$” // When matching to the end, ignore trailing slashes : // Otherwise, match a word boundary or a proceeding /. The word boundary restricts // parent routes to matching only their own words and nothing more, e.g. parent // route “/home” should not match “/home2”. // Additionally, allow paths starting with `.`, `-`, `~`, and url-encoded entities, // but do not consume the character in the matched path so they can match against // nested paths. “(?:(?=[.~-]|%[0-9A-F]{2})|\\b|\\/|$)”; } let matcher = new RegExp(regexpSource, caseSensitive ? undefined : “i”); return [matcher, paramNames]; } function safelyDecodeURIComponent(value: string, paramName: string) { try { return decodeURIComponent(value); } catch (error) { 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}).` ); return value; } } /** * Returns a resolved path object relative to the given pathname. * * @see https://reactrouter.com/docs/en/v6/api#resolvepath */ export function resolvePath(to: To, fromPathname = “/”): Path { let { pathname: toPathname, search = “”, hash = “”, } = typeof to === “string” ? parsePath(to) : to; let pathname = toPathname ? toPathname.startsWith(“/”) ? toPathname : resolvePathname(toPathname, fromPathname) : fromPathname; return { pathname, search: normalizeSearch(search), hash: normalizeHash(hash), }; } function resolvePathname(relativePath: string, fromPathname: string): string { let segments = fromPathname.replace(/\/+$/, “”).split(“/”); let relativeSegments = relativePath.split(“/”); relativeSegments.forEach((segment) => { if (segment === “..”) { // Keep the root “” segment so the pathname starts at / if (segments.length > 1) segments.pop(); } else if (segment !== “.”) { segments.push(segment); } }); return segments.length > 1 ? segments.join(“/”) : “/”; } function resolveTo( toArg: To, routePathnames: string[], locationPathname: string ): Path { let to = typeof toArg === “string” ? parsePath(toArg) : toArg; let toPathname = toArg === “” || to.pathname === “” ? “/” : to.pathname; // If a pathname is explicitly provided in `to`, it should be relative to the // route context. This is explained in `Note on `` values` in our // migration guide from v5 as a means of disambiguation between `to` values // that begin with `/` and those that do not. However, this is problematic for // `to` values that do not provide a pathname. `to` can simply be a search or // hash string, in which case we should assume that the navigation is relative // to the current location’s pathname and *not* the route pathname. let from: string; if (toPathname == null) { from = locationPathname; } else { let routePathnameIndex = routePathnames.length – 1; if (toPathname.startsWith(“..”)) { let toSegments = toPathname.split(“/”); // Each leading .. segment means “go up one route” instead of “go up one // URL segment”. This is a key difference from how works and a // major reason we call this a “to” value instead of a “href”. while (toSegments[0] === “..”) { toSegments.shift(); routePathnameIndex -= 1; } to.pathname = toSegments.join(“/”); } // If there are more “..” segments than parent routes, resolve relative to // the root / URL. from = routePathnameIndex >= 0 ? routePathnames[routePathnameIndex] : “/”; } let path = resolvePath(to, from); // Ensure the pathname has a trailing slash if the original to value had one. if ( toPathname && toPathname !== “/” && toPathname.endsWith(“/”) && !path.pathname.endsWith(“/”) ) { path.pathname += “/”; } return path; } function getToPathname(to: To): string | undefined { // Empty strings should be treated the same as / paths return to === “” || (to as Path).pathname === “” ? “/” : typeof to === “string” ? parsePath(to).pathname : to.pathname; } function stripBasename(pathname: string, basename: string): string | null { if (basename === “/”) return pathname; if (!pathname.toLowerCase().startsWith(basename.toLowerCase())) { return null; } let nextChar = pathname.charAt(basename.length); if (nextChar && nextChar !== “/”) { // pathname does not start with basename/ return null; } return pathname.slice(basename.length) || “/”; } const joinPaths = (paths: string[]): string => paths.join(“/”).replace(/\/\/+/g, “/”); const normalizePathname = (pathname: string): string => pathname.replace(/\/+$/, “”).replace(/^\/*/, “/”); const normalizeSearch = (search: string): string => !search || search === “?” ? “” : search.startsWith(“?”) ? search : “?” + search; const normalizeHash = (hash: string): string => !hash || hash === “#” ? “” : hash.startsWith(“#”) ? hash : “#” + hash; /////////////////////////////////////////////////////////////////////////////// // DANGER! PLEASE READ ME! // We provide these exports as an escape hatch in the event that you need any // routing data that we don’t provide an explicit API for. With that said, we // want to cover your use case if we can, so if you feel the need to use these // we want to hear from you. Let us know what you’re building and we’ll do our // best to make sure we can support you! // // We consider these exports an implementation detail and do not guarantee // against any breaking changes, regardless of the semver release. Use with // extreme caution and only if you understand the consequences. Godspeed. /////////////////////////////////////////////////////////////////////////////// /** @internal */ export { NavigationContext as UNSAFE_NavigationContext, LocationContext as UNSAFE_LocationContext, RouteContext as UNSAFE_RouteContext, };