import { createContext , useContext , useRef , useEffect , useCallback , useMemo , isValidElement , Fragment , Children , } from 'react' ; import { Action as NavigationType , parsePath } from 'history' ; import type { History , Location , Path , To } from 'history' ; /**
 * 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 <Router> 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 = Omit < History , 'action' | 'location' | 'back' | 'forward' | 'listen' | 'block' > ; interface NavigationContextObject { basename : string ; navigator : Navigator ; static : boolean ; pending : boolean ; } const NavigationContext = createContext < NavigationContextObject > ( null ! ) ; if ( ! FLAG_PRODUCTION ) { NavigationContext . displayName = 'Navigation' ; } interface LocationContextObject { location : Location ; navigationType : NavigationType ; } const LocationContext = createContext < LocationContextObject > ( null ! ) ; if ( ! FLAG_PRODUCTION ) { LocationContext . displayName = 'Location' ; } interface RouteContextObject { outlet : React . ReactElement | null ; matches : RouteMatch [ ] ; } const RouteContext = createContext < RouteContextObject > ( { outlet : null , matches : [ ] , } ) ; if ( ! FLAG_PRODUCTION ) { RouteContext . displayName = 'Route' ; } export interface OutletProps { } see https export function Outlet ( _props ) { return useOutlet ( ) ; } export interface RouteProps { caseSensitive ? : boolean ; children ? : React . ReactNode ; element ? : React . ReactElement | null ; index ? : boolean ; path ? : string ; } export interface PathRouteProps { caseSensitive ? : boolean ; children ? : React . ReactNode ; element ? : React . ReactElement | null ; index ? : false ; path : string ; } export interface LayoutRouteProps { children ? : React . ReactNode ; element ? : React . ReactElement | null ; } export interface IndexRouteProps { element ? : React . ReactElement | null ; index : true ; } see https export function Route ( _props ) { return null ; } export interface RouterProps { basename ? : string ; children ? : React . ReactNode ; location : Partial < Location > | string ; navigationType ? : NavigationType ; navigator : Navigator ; static ? : boolean ; pending ? : boolean ; } see https export function Router ( { basename : basenameProp = '/' , children = null , location : locationProp , navigationType = NavigationType . Pop , navigator , static : staticProp = false , pending = false , } ) { let basename = normalizePathname ( basenameProp ) ; let navigationContext = useMemo ( ( ) => ( { basename , navigator , static : staticProp , pending } ) , [ basename , navigator , staticProp , pending ] ) ; if ( typeof locationProp === 'string' ) { locationProp = parsePath ( locationProp ) ; } let { pathname = '/' , search = '' , hash = '' , state = null , key = 'default' } = locationProp ; let location = useMemo ( ( ) => { let trailingPathname = stripBasename ( pathname , basename ) ; if ( trailingPathname == null ) { return null ; } return { pathname : trailingPathname , search , hash , state , key , } ; } , [ basename , pathname , search , hash , state , key ] ) ; if ( location == null ) { return null ; } return ( < NavigationContext . Provider value = { navigationContext } > < LocationContext . Provider children = { children } value = { { location , navigationType } } /> < /NavigationContext.Provider>); } export interface RoutesProps { children ? : React . ReactNode ; location ? : Partial < Location > | string ; } see https export function Routes ( { children , location } ) { return useRoutes ( createRoutesFromChildren ( children ) , location ) ; } see https export function useHref ( to ) { let { basename , navigator } = 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 } ) ; } see https export function useInRouterContext ( ) { return useContext ( LocationContext ) != null ; } see https export function useLocation ( ) { return useContext ( LocationContext ) . location ; } see https export function useNavigationType ( ) { return useContext ( LocationContext ) . navigationType ; } /**
 * Returns true if the router is pending a location update.
 */ export function useLocationPending ( ) { return useContext ( NavigationContext ) . pending ; } see https export function useMatch < ParamKey extends string = string > ( pattern ) { return matchPath ( pattern , useLocation ( ) . pathname ) ; } /**
 * The interface for the navigate() function returned from useNavigate().
 */ export interface NavigateFunction { ( to , options ? ) : void ; ( delta ) : void ; } export interface NavigateOptions { replace ? : boolean ; state ? : any ; } see https export function useNavigate ( ) { let { basename , navigator } = useContext ( NavigationContext ) ; let { matches } = useContext ( RouteContext ) ; let { pathname : locationPathname } = useLocation ( ) ; let routePathnamesJson = JSON . stringify ( matches . map ( ( match ) => match . pathnameBase ) ) ; let activeRef = useRef ( false ) ; useEffect ( ( ) => { activeRef . current = true ; } ) ; let navigate = useCallback ( ( to , options = { } ) => { 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 ; } see https export function useOutlet ( ) { return useContext ( RouteContext ) . outlet ; } see https export function useParams < Key extends string = string > ( ) { let { matches } = useContext ( RouteContext ) ; let routeMatch = matches [ matches . length - 1 ] ; return routeMatch ? ( routeMatch . params as any ) : { } ; } see https export function useResolvedPath ( to ) { let { matches } = useContext ( RouteContext ) ; let { pathname : locationPathname } = useLocation ( ) ; let routePathnamesJson = JSON . stringify ( matches . map ( ( match ) => match . pathnameBase ) ) ; return useMemo ( ( ) => resolveTo ( to , JSON . parse ( routePathnamesJson ) , locationPathname ) , [ to , routePathnamesJson , locationPathname ] ) ; } see https export function useRoutes ( routes , locationArg ? ) { let { matches : parentMatches } = useContext ( RouteContext ) ; let routeMatch = parentMatches [ parentMatches . length - 1 ] ; let parentParams = routeMatch ? routeMatch . params : { } ; let parentPathnameBase = routeMatch ? routeMatch . pathnameBase : '/' ; let locationFromContext = useLocation ( ) ; let location ; if ( locationArg ) { let parsedLocationArg = typeof locationArg === 'string' ? parsePath ( locationArg ) : locationArg ; location = parsedLocationArg ; } else { location = locationFromContext ; } let pathname = location . pathname || '/' ; let remainingPathname = parentPathnameBase === '/' ? pathname : pathname . slice ( parentPathnameBase . length ) || '/' ; let matches = matchRoutes ( routes , { pathname : remainingPathname } ) ; 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 ) ; } see https export function createRoutesFromChildren ( children ) { let routes = [ ] ; Children . forEach ( children , ( element ) => { if ( ! isValidElement ( element ) ) { return ; } if ( element . type === Fragment ) { routes . push . apply ( routes , createRoutesFromChildren ( element . props . children ) ) ; return ; } let route = { 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 < Key extends string = string > = { 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 ; } see https export function generatePath ( path , params = { } ) { return path . replace ( /:(\w+)/g , ( _ , key ) => params [ key ] ! ) . replace ( /\/*\*$/ , ( _ ) => ( params [ '*' ] == null ? '' : params [ '*' ] . replace ( /^\/*/ , '/' ) ) ) ; } /**
 * A RouteMatch contains info about how a route matched a URL.
 */ export interface RouteMatch < ParamKey extends string = string > { /**
     * The names and values of dynamic parameters in the URL.
     */ params : Params < ParamKey > ; /**
     * 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 ; } see https export function matchRoutes ( routes , locationArg , basename = '/' ) { 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 ] , routes , pathname ) ; } return matches ; } interface RouteMeta { relativePath : string ; caseSensitive : boolean ; childrenIndex : number ; } interface RouteBranch { path : string ; score : number ; routesMeta : RouteMeta [ ] ; } function flattenRoutes ( routes , branches = [ ] , parentsMeta = [ ] , parentPath = '' ) { routes . forEach ( ( route , index ) => { let meta = { relativePath : route . path || '' , caseSensitive : route . caseSensitive === true , childrenIndex : index , } ; if ( meta . relativePath . startsWith ( '/' ) ) { meta . relativePath = meta . relativePath . slice ( parentPath . length ) ; } let path = joinPaths ( [ parentPath , meta . relativePath ] ) ; let routesMeta = parentsMeta . concat ( meta ) ; if ( route . children && route . children . length > 0 ) { flattenRoutes ( route . children , branches , routesMeta , path ) ; } if ( route . path == null && ! route . index ) { return ; } branches . push ( { path , score : computeScore ( path , route . index ) , routesMeta } ) ; } ) ; return branches ; } function rankRouteBranches ( branches ) { branches . sort ( ( a , b ) => a . score !== b . score ? b . score - a . score : 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 ) => s === '*' ; function computeScore ( path , index ) { 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 , b ) { let siblings = a . length === b . length && a . slice ( 0 , - 1 ) . every ( ( n , i ) => n === b [ i ] ) ; return siblings ? a [ a . length - 1 ] - b [ b . length - 1 ] : 0 ; } function matchRouteBranch < ParamKey extends string = string > ( branch , routesArg , pathname ) { let routes = routesArg ; let { routesMeta } = branch ; let matchedParams = { } ; let matchedPathname = '/' ; let matches = [ ] ; 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 = routes [ meta . childrenIndex ] ; matches . push ( { params : matchedParams , pathname : joinPaths ( [ matchedPathname , match . pathname ] ) , pathnameBase : joinPaths ( [ matchedPathname , match . pathnameBase ] ) , route , } ) ; if ( match . pathnameBase !== '/' ) { matchedPathname = joinPaths ( [ matchedPathname , match . pathnameBase ] ) ; } routes = route . children ! ; } return matches ; } /**
 * Renders the result of `matchRoutes()` into a React element.
 */ export function renderMatches ( matches ) { return _renderMatches ( matches ) ; } function _renderMatches ( matches , parentMatches = [ ] ) { if ( matches == null ) return null ; return matches . reduceRight ( ( outlet , match , index ) => { return ( < RouteContext . Provider children = { match . route . element !== undefined ? match . route . element : < Outlet />} value={{ outlet , matches : parentMatches . concat ( matches . slice ( 0 , index + 1 ) ) , } } />); } , 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 : string ; /**
     * 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 < ParamKey extends string = string > { /**
     * The names and values of dynamic parameters in the URL.
     */ params : Params < ParamKey > ; /**
     * 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 < T > = { - readonly [ P in keyof T ] : T [ P ] ; } ; see https export function matchPath < ParamKey extends string = string > ( pattern , pathname ) { 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 = paramNames . reduce < Mutable < Params > > ( ( memo , paramName , index ) => { 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 , caseSensitive = false , end = true ) { let paramNames = [ ] ; let regexpSource = '^' + path . replace ( /\/*\*?$/ , '' ) . replace ( /^\/*/ , '/' ) . replace ( /[\\.*+^$?{}|()[\]]/g , '\\$&' ) . replace ( /:(\w+)/g , ( _ , paramName ) => { paramNames . push ( paramName ) ; return '([^\\/]+)' ; } ) ; if ( path . endsWith ( '*' ) ) { paramNames . push ( '*' ) ; regexpSource += path === '*' || path === '/*' ? '(.*)$' : '(?:\\/(.+)|\\/*)$' ; } else { regexpSource += end ? '\\/*$' : '(?:\\b|$)' ; } let matcher = new RegExp ( regexpSource , caseSensitive ? undefined : 'i' ) ; return [ matcher , paramNames ] ; } function safelyDecodeURIComponent ( value , paramName ) { try { return decodeURIComponent ( value ) ; } catch ( error ) { return value ; } } see https export function resolvePath ( to , fromPathname = '/' ) { 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 , fromPathname ) { let segments = fromPathname . replace ( /\/+$/ , '' ) . split ( '/' ) ; let relativeSegments = relativePath . split ( '/' ) ; relativeSegments . forEach ( ( segment ) => { if ( segment === '..' ) { if ( segments . length > 1 ) segments . pop ( ) ; } else if ( segment !== '.' ) { segments . push ( segment ) ; } } ) ; return segments . length > 1 ? segments . join ( '/' ) : '/' ; } function resolveTo ( toArg , routePathnames , locationPathname ) { let to = typeof toArg === 'string' ? parsePath ( toArg ) : toArg ; let toPathname = toArg === '' || to . pathname === '' ? '/' : to . pathname ; let from ; if ( toPathname == null ) { from = locationPathname ; } else { let routePathnameIndex = routePathnames . length - 1 ; if ( toPathname . startsWith ( '..' ) ) { let toSegments = toPathname . split ( '/' ) ; while ( toSegments [ 0 ] === '..' ) { toSegments . shift ( ) ; routePathnameIndex -= 1 ; } to . pathname = toSegments . join ( '/' ) ; } from = routePathnameIndex >= 0 ? routePathnames [ routePathnameIndex ] : '/' ; } let path = resolvePath ( to , from ) ; if ( toPathname && toPathname !== '/' && toPathname . endsWith ( '/' ) && ! path . pathname . endsWith ( '/' ) ) { path . pathname += '/' ; } return path ; } function getToPathname ( to ) { return to === '' || ( to as Path ) . pathname === '' ? '/' : typeof to === 'string' ? parsePath ( to ) . pathname : to . pathname ; } function stripBasename ( pathname , basename ) { if ( basename === '/' ) return pathname ; if ( ! pathname . toLowerCase ( ) . startsWith ( basename . toLowerCase ( ) ) ) { return null ; } let nextChar = pathname . charAt ( basename . length ) ; if ( nextChar && nextChar !== '/' ) { return null ; } return pathname . slice ( basename . length ) || '/' ; } const joinPaths = ( paths ) : string => paths . join ( '/' ) . replace ( /\/\/+/g , '/' ) ; const normalizePathname = ( pathname ) : string => pathname . replace ( /\/+$/ , '' ) . replace ( /^\/*/ , '/' ) ; const normalizeSearch = ( search ) : string => ! search || search === '?' ? '' : search . startsWith ( '?' ) ? search : '?' + search ; const normalizeHash = ( hash ) : string => ( ! hash || hash === '#' ? '' : hash . startsWith ( '#' ) ? hash : '#' + hash ) ;