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);
}
});
};