UNPKG

@wroud/navigation

Version:

A flexible, pattern-matching navigation system for JavaScript applications with built-in routing, browser integration, and navigation state management

89 lines (78 loc) 2.63 kB
/// <reference lib="dom" /> /// <reference lib="dom.iterable" /> import type { INavigation } from "../INavigation.js"; import type { IRouteState } from "../IRouteState.js"; import { NavigationType } from "../NavigationListener.js"; export class BrowserNavigation { private ignoreNextPopState: boolean; constructor(private readonly navigation: INavigation) { this.ignoreNextPopState = false; this.popStateHandler = this.popStateHandler.bind(this); this.hashChangeHandler = this.hashChangeHandler.bind(this); this.handleNavigation = this.handleNavigation.bind(this); } async registerRoutes(): Promise<void> { this.addBrowserNavigation(); await this.restoreNavigation(); } private addBrowserNavigation() { window.addEventListener("popstate", this.popStateHandler); window.addEventListener("hashchange", this.hashChangeHandler); this.navigation.addListener(this.handleNavigation); } private async restoreNavigation() { await this.popStateHandler(); } private handleNavigation( type: NavigationType, from: IRouteState | null, to: IRouteState | null, ) { switch (type) { case NavigationType.Navigate: { const targetUrl = this.navigation.router.matcher?.stateToUrl(to); const currentUrl = window.location.pathname + window.location.search; if (targetUrl != null && targetUrl === currentUrl) { window.history.replaceState(to, "", targetUrl); } else { window.history.pushState(to, "", targetUrl); } break; } case NavigationType.Replace: window.history.replaceState( to, "", this.navigation.router.matcher?.stateToUrl(to), ); break; case NavigationType.Back: this.ignoreNextPopState = true; window.history.back(); break; case NavigationType.Forward: this.ignoreNextPopState = true; window.history.forward(); break; } } private async popStateHandler() { if (this.ignoreNextPopState) { this.ignoreNextPopState = false; return; } const state = this.navigation.router.matcher?.urlToState( decodeURIComponent(window.location.pathname) + window.location.search, ); if (state) { await this.navigation.navigate(state); } } private hashChangeHandler() {} dispose(): void | Promise<void> { window.removeEventListener("popstate", this.popStateHandler); window.removeEventListener("hashchange", this.hashChangeHandler); this.navigation.removeListener(this.handleNavigation); } }