presentation/widgets/tab.widget.ts

presentation/widgets/tab.widget.ts is a file in Bane's Lab Site. 83 lines of code and 18 definitions.

import { applyHead, headOf } from "#presentation/renderers/page.renderer";
import { nodeFromPath, tabFromPath, tabPath } from "#core/assets/link.assets";
import { renderContent, renderFooterFor, renderTabHeader, renderTabs } from "#presentation/renderers/tab.renderer";
import { scrollToId, scroller } from "#presentation/components/tab.component";
import { ANCHOR_PREFIX } from "#configuration/constants/document.constants";
import type { Disposer } from "#types/base.types";
import { TAB_SELECTED } from "#core/ids/tab.ids";
import type { Tab } from "#types/document.types";
import type { TabbedPage } from "#types/tab.types";
import { createElement } from "#core/factories/element.factory";
import { loadedPage } from "#domain/registries/page.registry";
import { subscribeEvent } from "#core/buses/base.bus";

const PAGE_ATTRIBUTE = "data-tab-page";

const activeTab = function activeTab(definition: TabbedPage): Tab | undefined {
    const requested = tabFromPath(window.location.pathname);
    return definition.tabs.find((tab) => tab.id === requested) ?? definition.tabs[0];
};

const scrollToRequested = function scrollToRequested(): void {
    const hash = window.location.hash.slice(ANCHOR_PREFIX.length);
    const target = hash.length > 0 ? hash : (nodeFromPath(window.location.pathname) ?? "");
    if (target.length > 0) {
        requestAnimationFrame(() => {
            scrollToId(target);
        });
    }
};

const renderTab = function renderTab(root: HTMLElement, definition: TabbedPage, tab: Tab): void {
    const content = renderContent(definition.layout, tab, definition.page);
    const footer = renderFooterFor(definition.layout, tab);
    const page = createElement("div", {
        children: [
            renderTabHeader(definition.meta),
            renderTabs(definition.page, definition.tabs, tab.id),
            content,
            ...(footer === null ? [] : [footer]),
        ],
        className: "tab-page-content",
    });
    root.replaceChildren(page);
    scrollToRequested();
};

const renderBody = function renderBody(root: HTMLElement, definition: TabbedPage): void {
    const tab = activeTab(definition);
    if (tab === undefined) {
        return;
    }
    renderTab(root, definition, tab);
    const { loadTab } = definition;
    if (tab.sections.length > 0 || loadTab === undefined) {
        return;
    }
    void loadTab(tab.id).then((loaded) => {
        if (loaded !== undefined && activeTab(definition)?.id === tab.id) {
            renderTab(root, definition, loaded);
        }
    });
};

const selectTab = function selectTab(definition: TabbedPage, tab: string): void {
    const root = document.querySelector<HTMLElement>(`[${PAGE_ATTRIBUTE}="${definition.page}"]`);
    if (root === null || !definition.tabs.some((candidate) => candidate.id === tab)) {
        return;
    }
    const path = tabPath(definition.page, definition.tabs, tab);
    window.history.pushState({}, "", path);
    applyHead(document, headOf(definition.page, loadedPage(definition.page), path));
    scroller()?.scrollTo(0, 0);
    renderBody(root, definition);
};

export const renderTabbedPage = function renderTabbedPage(definition: TabbedPage): Element {
    const root = createElement("div", {
        attributes: { [PAGE_ATTRIBUTE]: definition.page },
        className: `tab-page ${definition.tone}`,
    });
    renderBody(root, definition);
    return root;
};

export const mountTabSwitching = function mountTabSwitching(definition: TabbedPage): Disposer {
    return subscribeEvent(TAB_SELECTED, (event) => {
        if (event.page === definition.page) {
            selectTab(definition, event.tab);
        }
    });
};