import { useState , useCallback , useRef , useContext , useLayoutEffect } from 'react' ; import { BasePathContext , PathContext } from './context' ; import { useMountedLayout } from './hooks' ; import { getSsrPath , isNode } from './node' ; import { shouldCancelNavigation } from './intercept' ; import { isFunction } from './typeChecks' ; export interface LocationChangeSetFn { ( path ) : void ; } export interface LocationChangeOptionParams { inheritBasePath ? : boolean ; basePath ? : string ; isActive ? : boolean | ( ( ) => boolean ) ; onInitial ? : boolean ; } export function usePath ( basePath ? ) { const contextPath = useContext ( PathContext ) ; const contextBasePath = useBasePath ( ) ; basePath = basePath || contextBasePath ; const [ path , setPath ] = useState ( getFormattedPath ( basePath ) ) ; useLocationChange ( ( newPath ) => setPath ( newPath ) , { basePath , inheritBasePath : ! basePath , } ) ; return contextPath || path ; } export function useBasePath ( ) { return useContext ( BasePathContext ) ; } export function useFullPath ( ) { const [ path , setPath ] = useState < string | null > ( getCurrentPath ( ) ) ; useLocationChange ( setPath , { inheritBasePath : false } ) ; return path || '/' ; } export function useHash ( { stripHash = true } = { } ) { const [ hash , setHash ] = useState ( window . location . hash ) ; const handleHash = useCallback ( ( ) => { if ( window . location . hash === hash ) return ; setHash ( window . location . hash ) ; } , [ setHash , hash ] ) ; useLayoutEffect ( ( ) => { window . addEventListener ( 'hashchange' , handleHash , false ) ; return ( ) => window . removeEventListener ( 'hashchange' , handleHash ) ; } , [ handleHash ] ) ; useLocationChange ( handleHash ) ; return stripHash ? hash . substring ( 1 ) : hash ; } export function getCurrentPath ( ) { return isNode ? getSsrPath ( ) : window . location . pathname || '/' ; } export function getCurrentHash ( ) { if ( isNode ) { const path = getSsrPath ( ) ; const hashIndex = path . indexOf ( '#' ) ; return path . substring ( hashIndex ) ; } return window . location . hash ; } export function useLocationChange ( setFn , { inheritBasePath = true , basePath = '' , isActive , onInitial = false , } = { } ) { if ( isNode ) return ; const routerBasePath = useBasePath ( ) ; if ( inheritBasePath && routerBasePath ) basePath = routerBasePath ; const setRef = useRef < LocationChangeSetFn > ( setFn ) ; useLayoutEffect ( ( ) => { setRef . current = setFn ; } ) ; const onPopState = useCallback ( ( ) => { if ( isActive !== undefined && ! isPredicateActive ( isActive ) ) return ; if ( shouldCancelNavigation ( ) ) return ; setRef . current ( getFormattedPath ( basePath ) ) ; } , [ isActive , basePath ] ) ; useLayoutEffect ( ( ) => { window . addEventListener ( 'popstate' , onPopState ) ; return ( ) => window . removeEventListener ( 'popstate' , onPopState ) ; } , [ onPopState ] ) ; useMountedLayout ( ( ) => { if ( isActive !== undefined && ! isPredicateActive ( isActive ) ) return ; setRef . current ( getFormattedPath ( basePath ) ) ; } , [ basePath , isActive ] , { onInitial } ) ; } param { string } basePath return { string | null } export function getFormattedPath ( basePath ) { const path = getCurrentPath ( ) ; const baseMissing = basePath && ! isPathInBase ( basePath , path ) ; if ( path === null || baseMissing ) return null ; return decodeURIComponent ( ! basePath ? path : path . replace ( basePathMatcher ( basePath ) , '' ) || '/' ) ; } function isPredicateActive ( predicate ) { return isFunction ( predicate ) ? predicate ( ) : predicate ; } function basePathMatcher ( basePath ) { return new RegExp ( '^' + basePath , 'i' ) ; } function isPathInBase ( basePath , path ) { return ! ! ( basePath && path && path . toLowerCase ( ) . startsWith ( basePath . toLowerCase ( ) ) ) ; }