presentation/widgets/overlay.widget.ts
presentation/widgets/overlay.widget.ts is a file in Bane's Lab Site. 88 lines of code and 14 definitions.
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();
}
};
};