# presentation/widgets/tab.widget.ts

> 70 lines of code and 15 definitions.

Tree: Site tree
Language: typescript
Layer: product
Canonical: https://banes-lab.com/anatomy/tree#file-presentation-widgets-tab-widget-ts
Source text: https://banes-lab.com/assets/sources/source.84138ff191e279fa78176f84075f1b729131dc6e071dbdfa8b0500ab0baea5a5.generated.txt

## Definitions

- `renderTabbedPage` (lexical_declaration, line 62, exported)
- `renderBody` (lexical_declaration, line 30)
- `selectTab` (lexical_declaration, line 50)
- `scrollToRequested` (lexical_declaration, line 21)
- `mountTabSwitching` (lexical_declaration, line 71, exported)
- `PAGE_ATTRIBUTE` (lexical_declaration, line 14)
- `activeTab` (lexical_declaration, line 16)
- `requested` (lexical_declaration, line 17)
- `target` (lexical_declaration, line 22)
- `tab` (lexical_declaration, line 31)
- `content` (lexical_declaration, line 35)
- `footer` (lexical_declaration, line 36)
- `page` (lexical_declaration, line 37)
- `path` (lexical_declaration, line 55)
- `root` (lexical_declaration, line 63, exported)

## Uses

- [core/buses/base.bus.ts](https://banes-lab.com/source/tree/core/buses/base.bus.ts.md)
- [domain/registries/page.registry.ts](https://banes-lab.com/source/tree/domain/registries/page.registry.ts.md)
- [presentation/components/tab.component.ts](https://banes-lab.com/source/tree/presentation/components/tab.component.ts.md)
- [presentation/renderers/page.renderer.ts](https://banes-lab.com/source/tree/presentation/renderers/page.renderer.ts.md)

## Used by

- [presentation/views/anatomy.view.ts](https://banes-lab.com/source/tree/presentation/views/anatomy.view.ts.md)
- [presentation/views/architecture.view.ts](https://banes-lab.com/source/tree/presentation/views/architecture.view.ts.md)
- [presentation/views/grammar.view.ts](https://banes-lab.com/source/tree/presentation/views/grammar.view.ts.md)
- [presentation/views/methodology.view.ts](https://banes-lab.com/source/tree/presentation/views/methodology.view.ts.md)
- [presentation/views/ontology.view.ts](https://banes-lab.com/source/tree/presentation/views/ontology.view.ts.md)

## Source

```typescript
import { applyHead, headOf } from "#presentation/renderers/page.renderer";
import { renderContent, renderFooterFor, renderTabHeader, renderTabs } from "#presentation/renderers/tab.renderer";
import { scrollToId, scroller } from "#presentation/components/tab.component";
import { tabFromPath, tabPath } from "#assets/link.assets";
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 target = window.location.hash.slice(ANCHOR_PREFIX.length);
    if (target.length > 0) {
        requestAnimationFrame(() => {
            scrollToId(target);
        });
    }
};

const renderBody = function renderBody(root: HTMLElement, definition: TabbedPage): void {
    const tab = activeTab(definition);
    if (tab === undefined) {
        return;
    }
    const content = renderContent(definition.layout, tab);
    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 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);
        }
    });
};
```
