# presentation/widgets/overlay.widget.ts

> 88 lines of code and 14 definitions.

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

## Definitions

- `reveal` (lexical_declaration, line 44)
- `teardown` (lexical_declaration, line 52)
- `createDialog` (lexical_declaration, line 15)
- `createBackdrop` (lexical_declaration, line 28)
- `onEscape` (lexical_declaration, line 38)
- `open` (lexical_declaration, line 59)
- `close` (lexical_declaration, line 68)
- `mountOverlay` (lexical_declaration, line 75, exported)
- `header` (lexical_declaration, line 16)
- `body` (lexical_declaration, line 20)
- `backdrop` (lexical_declaration, line 29)
- `overlay` (lexical_declaration, line 60)
- `current` (lexical_declaration, line 76, exported)
- `disposers` (lexical_declaration, line 77, exported)

## Uses

- [core/buses/base.bus.ts](https://banes-lab.com/source/tree/core/buses/base.bus.ts.md)
- [core/factories/element.factory.ts](https://banes-lab.com/source/tree/core/factories/element.factory.ts.md)
- [presentation/widgets/home.widget.ts](https://banes-lab.com/source/tree/presentation/widgets/home.widget.ts.md)

## Source

```typescript
import {
    CLOSING_CLASS,
    ESCAPE_KEY,
    OVERLAY_CLOSE_MS,
    SCROLL_LOCK_CLASS,
} from "#configuration/constants/overlay.constants";
import { OVERLAY_DISMISSED, OVERLAY_REQUESTED } from "#core/ids/overlay.ids";
import { emitEvent, subscribeEvent } from "#core/buses/base.bus";
import type { Disposer } from "#types/base.types";
import { OPEN_CLASS } from "#configuration/constants/element.constants";
import type { OverlayRequestedEvent } from "#types/event.types";
import { createCloseButton } from "#presentation/components/overlay.component";
import { createElement } from "#core/factories/element.factory";

const createDialog = function createDialog(event: OverlayRequestedEvent): HTMLElement {
    const header = createElement("div", {
        children: [createElement("span", { className: "overlay-title", text: event.title }), createCloseButton()],
        className: "overlay-header",
    });
    const body = createElement("div", { children: [event.content], className: "overlay-body" });
    return createElement("div", {
        attributes: { "aria-label": event.title, "aria-modal": "true", role: "dialog" },
        children: [header, body],
        className: "overlay-dialog",
    });
};

const createBackdrop = function createBackdrop(dialog: HTMLElement): HTMLElement {
    const backdrop = createElement("div", { children: [dialog], className: "overlay-backdrop" });
    backdrop.addEventListener("click", (click) => {
        if (click.target === backdrop) {
            emitEvent({ name: OVERLAY_DISMISSED });
        }
    });
    return backdrop;
};

const onEscape = function onEscape(key: KeyboardEvent): void {
    if (key.key === ESCAPE_KEY) {
        emitEvent({ name: OVERLAY_DISMISSED });
    }
};

const reveal = function reveal(overlay: HTMLElement): void {
    requestAnimationFrame(() => {
        requestAnimationFrame(() => {
            overlay.classList.add(OPEN_CLASS);
        });
    });
};

const teardown = function teardown(overlay: HTMLElement): void {
    setTimeout(() => {
        overlay.remove();
        document.body.classList.remove(SCROLL_LOCK_CLASS);
    }, OVERLAY_CLOSE_MS);
};

const open = function open(event: OverlayRequestedEvent): HTMLElement {
    const overlay = createElement("div", { children: [createBackdrop(createDialog(event))], className: "overlay" });
    document.body.classList.add(SCROLL_LOCK_CLASS);
    document.body.append(overlay);
    document.addEventListener("keydown", onEscape);
    reveal(overlay);
    return overlay;
};

const close = function close(overlay: HTMLElement): void {
    overlay.classList.remove(OPEN_CLASS);
    overlay.classList.add(CLOSING_CLASS);
    document.removeEventListener("keydown", onEscape);
    teardown(overlay);
};

export const mountOverlay = function mountOverlay(): Disposer {
    let current: HTMLElement | null = null;
    const disposers = [
        subscribeEvent(OVERLAY_REQUESTED, (event) => {
            if (current !== null) {
                close(current);
            }
            current = open(event);
        }),
        subscribeEvent(OVERLAY_DISMISSED, () => {
            if (current !== null) {
                close(current);
                current = null;
            }
        }),
    ];
    return () => {
        for (const dispose of disposers) {
            dispose();
        }
    };
};
```
