import { useSyncExternalStore } from '@wordpress/element'; import { formatRoute, parseRoute, type Route, type RouteParams, type View, } from './route'; const HASH_CHANGE_EVENT = 'hashchange'; const EMPTY_ROUTE_HASH = '#/'; export type ParamPatch = Readonly< Record< string, string | undefined > >; function subscribeToHash( onHashChange: () => void ): () => void { window.addEventListener( HASH_CHANGE_EVENT, onHashChange ); return () => { window.removeEventListener( HASH_CHANGE_EVENT, onHashChange ); }; } function currentHash(): string { return window.location.hash; } let lastHash: string | null = null; let lastRoute: Route = parseRoute( '' ); function currentRoute(): Route { const hash = currentHash(); if ( hash !== lastHash ) { lastHash = hash; lastRoute = parseRoute( hash ); } return lastRoute; } export function useRoute(): Route { useSyncExternalStore( subscribeToHash, currentHash ); return currentRoute(); } function isCurrentHash( hash: string ): boolean { const shownHash = currentHash() === '' ? EMPTY_ROUTE_HASH : currentHash(); return hash === shownHash; } function pushHash( hash: string ): void { if ( ! isCurrentHash( hash ) ) { window.location.hash = hash; } } function replaceHash( hash: string ): void { if ( isCurrentHash( hash ) ) { return; } const { pathname, search } = window.location; window.history.replaceState( null, '', pathname + search + hash ); window.dispatchEvent( new HashChangeEvent( HASH_CHANGE_EVENT ) ); } export function navigate( view: View, params: RouteParams = {} ): void { pushHash( formatRoute( view, params ) ); window.scrollTo( 0, 0 ); } function patchParams( params: RouteParams, patch: ParamPatch ): RouteParams { const patchedParams: Record< string, string > = {}; for ( const [ name, paramValue ] of Object.entries( params ) ) { if ( paramValue !== undefined && ! ( name in patch ) ) { patchedParams[ name ] = paramValue; } } for ( const [ name, paramValue ] of Object.entries( patch ) ) { if ( paramValue !== undefined && paramValue !== '' ) { patchedParams[ name ] = paramValue; } } return patchedParams; } function patchedHash( patch: ParamPatch ): string { const route = currentRoute(); return formatRoute( route.view, patchParams( route.params, patch ) ); } export function setParams( patch: ParamPatch ): void { pushHash( patchedHash( patch ) ); } export function replaceParams( patch: ParamPatch ): void { replaceHash( patchedHash( patch ) ); }