@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
text/typescript
/// <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);
}
}