import { match , MatchResult , MatchResultParams } from './match' ; export { match , MatchResult , MatchResultParams } ; namespace dom { /** Serverside safe document.location */ const dloc = typeof document !== 'undefined' ? document . location : { hash : '' } ; export let html5Base = null ; export function html5ModeEnabled ( ) { return html5Base !== null ; } export function readLocation ( ) { if ( html5Base == null ) { const hash = ( dloc . hash === '' || dloc . hash === '#' ) ? '/' : dloc . hash . substring ( 1 ) ; return hash ; } else { return window . location . pathname . substr ( html5Base . length ) ; } } /**
     * Used to track the last value set.
     * if it does not change we ignore events
     */ export let oldLocation = readLocation ( ) ; export function setLocation ( location , replace ) { if ( readLocation ( ) === location ) return ; if ( typeof history !== 'undefined' && history . pushState ) { if ( replace ) { history . replaceState ( { } , document . title , location ) ; } else { history . pushState ( { } , document . title , location ) ; } /**
             * Just calling history.pushState() or history.replaceState() won't trigger a popstate event
             */ fire ( ) ; } else { dloc . hash = location ; } oldLocation = readLocation ( ) ; } /** Current listeners */ export type ChangeEvent = { oldLocation : string ; newLocation : string ; } ; type Listener = { ( evt ) : void ; } ; let listeners = [ ] ; const fire = ( ) => { const newLocation = readLocation ( ) ; if ( oldLocation === newLocation ) return ; listeners . forEach ( l => l ( { oldLocation , newLocation } ) ) ; oldLocation = newLocation ; } ; if ( typeof window !== 'undefined' ) { window . addEventListener ( 'hashchange' , fire , false ) ; window . addEventListener ( 'popstate' , fire ) ; } export function listen ( cb ) { listeners . push ( cb ) ; return ( ) => { listeners = listeners . filter ( l => l !== cb ) ; } ; } } export interface RouteChangeEvent { oldPath : string ; newPath : string ; } export interface RouteEnterEvent extends RouteChangeEvent { params : MatchResultParams ; } /**
 * We support sync and async operations in the same API
 */ export type SyncOrAsyncResult < T > = T | Promise < T > ; export type RouteBeforeEnterResult = SyncOrAsyncResult < void | null | undefined | { redirect : string ; replace ? : boolean ; } > ; export type RouteEnterResult = void ; export type RouteBeforeLeaveResult = SyncOrAsyncResult < void | null | undefined | boolean | { redirect : string ; replace ? : boolean ; } > ; export interface RouteConfig { /**
     * The pattern to match against
     */ $ : string ; /**
     * Called before entering a route. This is your chance to redirect if you want.
     **/ beforeEnter ? : ( evt : RouteEnterEvent ) => RouteBeforeEnterResult ; /**
     * Called on entering a route.
     **/ enter ? : ( evt : RouteEnterEvent ) => RouteEnterResult ; /**
     * On route leave,
     * you can redirect to elsewhere if you want or just return false to prevent leaving
     **/ beforeLeave ? : ( evt : RouteChangeEvent ) => RouteBeforeLeaveResult ; } export class Router { constructor ( public routes ) { dom . listen ( this . trigger ) ; } /**
     * Runs through the config and triggers an routes that matches the current path
     */ init ( ) { return this . trigger ( { oldLocation : '' , newLocation : dom . readLocation ( ) } ) ; } /**
     * Enables pure html5 routing.
     * NOTE:
     * - Server must support returning the same page on route triggers.
     * - Your browser targets support pushState: https://caniuse.com/#search=pushstate
     */ enableHtml5Routing ( baseUrl = '' ) { dom . html5Base = baseUrl ; dom . oldLocation = dom . readLocation ( ) ; return this ; } private trigger = async ( { oldLocation , newLocation } ) => { const oldPath = oldLocation ; const newPath = newLocation ; /** leaving */ for ( const config of this . routes ) { const pattern = config . $ ; if ( match ( { pattern , path : oldPath } ) ) { if ( config . beforeLeave ) { const result = await config . beforeLeave ( { oldPath , newPath } ) ; if ( result == null ) { } else if ( typeof result === 'boolean' ) { if ( result === false ) { navigate ( oldLocation , true ) ; return ; } } else if ( result . redirect ) { navigate ( result . redirect , result . replace ) ; return ; } } } } /** entering */ for ( const config of this . routes ) { const pattern = config . $ ; const enterMatch = match ( { pattern , path : newPath } ) ; if ( enterMatch ) { if ( enterMatch . remainingPath ) { continue ; } const params = enterMatch . params ; /** beforeEnter */ if ( config . beforeEnter ) { const result = await config . beforeEnter ( { oldPath , newPath , params } ) ; if ( result == null ) { } else if ( result . redirect ) { navigate ( result . redirect , result . replace ) ; return ; } } /** enter */ if ( config . enter ) { const result = await config . enter ( { oldPath , newPath , params } ) ; return ; } } } } ; } /**
 * Navigates to the given path
 */ export function navigate ( path , replace ? ) { dom . html5ModeEnabled ( ) ? dom . setLocation ( `${ dom . html5Base }${ path }` , ! ! replace ) : dom . setLocation ( `#${ path }` , ! ! replace ) ; } /**
 * Gives you a link that when triggered, navigates to the given path
 */ export function link ( path ) { return dom . html5ModeEnabled ( ) ? `${ dom . html5Base ! }${ path }` : `./#${ path }` ; } /**
 * Returns true if a modifier key is down.
 */ const isModifiedEvent = ( event ) : boolean => Boolean ( event . metaKey || event . altKey || event . ctrlKey || event . shiftKey ) ; /**
 * Suppresses browser default `click` behaviour on link
 */ export const html5LinkOnClick = ( { event , replace = false } ) => { const linkElement = event . target as HTMLLinkElement ; if ( ! event . defaultPrevented && event . button === 0 && ! ( linkElement ) . target && ! isModifiedEvent ( event ) ) { event . preventDefault ( ) ; const location = linkElement . href ; dom . setLocation ( location , ! ! replace ) ; } } ;