import { useCallback , useLayoutEffect } from 'react' ; import { useBasePath } from './path' ; import { isNode } from './node' ; import type { QueryParam } from './querystring' ; import { shouldCancelNavigation , addInterceptor , removeInterceptor , defaultPrompt , undoNavigation , } from './intercept' ; export interface NavigateWithReplace { ( url , replace ? ) : void ; } export interface NavigateWithQuery { ( url , query ? , replace ? ) : void ; } let lastPath = '' ; export function navigate ( url ) ; export function navigate ( url , replace ) ; export function navigate ( url , query ) ; export function navigate ( url , query , replace ) ; export function navigate ( url , queryOrReplace ? , replace ? ) ; export function navigate ( url , query , replace , state ) ; export function navigate ( url , replaceOrQuery ? , replace ? , state = null ) { if ( typeof url !== 'string' ) { throw new Error ( `"url" must be a string, was provided a(n) ${ typeof url }` ) ; } if ( Array . isArray ( replaceOrQuery ) ) { throw new Error ( '"replaceOrQuery" must be boolean, object, or URLSearchParams' ) ; } if ( shouldCancelNavigation ( ) ) return ; if ( replaceOrQuery !== null && typeof replaceOrQuery === 'object' ) { url += '?' + new URLSearchParams ( replaceOrQuery ) . toString ( ) ; } else if ( replace === undefined && replaceOrQuery !== undefined ) { replace = replaceOrQuery ?? undefined ; } else if ( replace === undefined && replaceOrQuery === undefined ) { replace = false ; } lastPath = url ; if ( isAbsolute ( url ) && ! isCurrentOrigin ( url ) ) { window . location . assign ( url ) ; return ; } if ( replace ) window . history . replaceState ( state , '' , url ) ; else window . history . pushState ( state , '' , url ) ; dispatchEvent ( new PopStateEvent ( 'popstate' ) ) ; } export function useNavigationPrompt ( predicate = true , prompt = defaultPrompt ) { if ( isNode ) return ; useLayoutEffect ( ( ) => { const onPopStateNavigation = ( ) => { if ( shouldCancelNavigation ( ) ) { undoNavigation ( lastPath ) ; } } ; window . addEventListener ( 'popstate' , onPopStateNavigation ) ; return ( ) => window . removeEventListener ( 'popstate' , onPopStateNavigation ) ; } , [ ] ) ; useLayoutEffect ( ( ) => { const handler = ( e ? ) : string | void => { if ( predicate ) { return e ? cancelNavigation ( e , prompt ) : prompt ; } } ; addInterceptor ( handler ) ; return ( ) => removeInterceptor ( handler ) ; } , [ predicate , prompt ] ) ; } function cancelNavigation ( event , prompt ) { event . preventDefault ( ) ; event . returnValue = prompt ; return prompt ; } export function useNavigate ( optBasePath = '' ) { const basePath = useBasePath ( ) ; const navigateWithBasePath = useCallback < NavigateWithReplace & NavigateWithQuery > ( ( url , replaceOrQuery ? , replace ? ) => { const base = optBasePath || basePath ; const href = url . startsWith ( '/' ) ? base + url : url ; navigate ( href , replaceOrQuery , replace ) ; } , [ basePath , optBasePath ] ) ; return navigateWithBasePath ; } function isAbsolute ( url ) { return /^(?:[a-z]+:)?\/\//i . test ( url ) ; } function isCurrentOrigin ( url ) { return window . location . origin === new URL ( url ) . origin ; }