runtime/coordinators/route.coordinator.ts

runtime/coordinators/route.coordinator.ts is a file in Bane's Lab Site. 74 lines of code and 13 definitions.

import { ROUTE_CHANGED, ROUTE_REQUESTED } from "#core/ids/route.ids";
import { applyHead, headOf, holdsHead } from "#presentation/renderers/page.renderer";
import { emitEvent, subscribeEvent } from "#core/buses/base.bus";
import { holdScroll, isReload } from "#core/observers/route.observer";
import { loadPage, loadedPage } from "#domain/registries/page.registry";
import { nodeFromPath, pageFromPath, pagePath } from "#core/assets/link.assets";
import { readScroll, writeScroll } from "#core/stores/route.store";
import type { Disposer } from "#types/base.types";
import { HOME_PAGE } from "#core/ids/page.ids";
import { renderPage } from "#presentation/renderers/page.shell.renderer";

let latest = 0;

const idle: Disposer = () => {};

const render = async function render(main: HTMLElement, page: string, rewriteHead = true): Promise<void> {
    latest += 1;
    const ticket = latest;
    await loadPage(page);
    if (ticket !== latest) {
        return;
    }
    main.replaceChildren(...renderPage(page));
    if (rewriteHead && nodeFromPath(window.location.pathname) === null) {
        applyHead(document, headOf(page, loadedPage(page), window.location.pathname));
    }
    main.scrollTo(0, 0);
    emitEvent({ name: ROUTE_CHANGED, page });
};

const restoreOnReload = function restoreOnReload(main: HTMLElement): Disposer {
    const top = isReload() ? readScroll(window.location.pathname) : null;
    if (top === null) {
        return idle;
    }
    let release = idle;
    const unsubscribe = subscribeEvent(ROUTE_CHANGED, () => {
        unsubscribe();
        requestAnimationFrame(() => {
            release = holdScroll(main, top);
        });
    });
    return () => {
        unsubscribe();
        release();
    };
};

const navigate = function navigate(main: HTMLElement, page: string, path: string): void {
    if (window.location.pathname + window.location.search + window.location.hash !== path) {
        window.history.pushState({ page }, "", path);
    }
    void render(main, page);
};

export const startRouting = function startRouting(main: HTMLElement): Disposer {
    const onPopState = function onPopState(): void {
        void render(main, pageFromPath(window.location.pathname, HOME_PAGE));
    };
    const onPageHide = function onPageHide(): void {
        writeScroll(window.location.pathname, main.scrollTop);
    };
    window.addEventListener("popstate", onPopState);
    window.addEventListener("pagehide", onPageHide);
    const unsubscribe = subscribeEvent(ROUTE_REQUESTED, (event) => {
        navigate(main, event.page, event.path ?? pagePath(event.page));
    });
    const restore = restoreOnReload(main);
    void render(
        main,
        pageFromPath(window.location.pathname, HOME_PAGE),
        !holdsHead(document, window.location.pathname),
    );
    return () => {
        window.removeEventListener("popstate", onPopState);
        window.removeEventListener("pagehide", onPageHide);
        unsubscribe();
        restore();
    };
};