import { createElement, nextFrame, getHTML } from "./util"; export interface ModalOptions { id?: string; url: string; size?: string; close?: boolean; } export class ModalPresenter { private modals: Record = {}; constructor() { document.addEventListener("turbo:load", this.prepareBlind); } async open(options: ModalOptions): Promise { const id = (options.id = options.id ?? "default-modal"); (this.modals[id] = new Modal({ presenter: this, id })).open(options); this.updateBlindStatus(); } async close({ id }: { id?: string } = {}): Promise { let promise: Promise | null = null; if (id) { promise = this.modals[id]?.close(); delete this.modals[id]; } else { promise = Promise.all(Object.values(this.modals).map((e) => e.close())); this.modals = {}; } this.updateBlindStatus(); await promise; } private updateBlindStatus(): void { const open = Object.keys(this.modals).length > 0; document.body.classList.toggle("modal-open", open); } private prepareBlind: () => void = () => { createElement(document.body, "modal-blind"); }; } export class Modal { private readonly root: HTMLDivElement; private readonly frame: HTMLDivElement; private readonly closeButton: HTMLDivElement; constructor({ presenter, id }: { presenter: ModalPresenter; id: string }) { this.root = createElement(document.body, "modal"); const content = createElement(this.root, "modal__content"); this.closeButton = createElement(content, "modal__close"); this.closeButton.addEventListener("click", () => { presenter.close({ id }); }); this.frame = createElement(content, "modal__frame"); } async open({ size, url, close }: ModalOptions): Promise { await nextFrame(); this.closeButton.style.display = close ?? true ? "block" : "none"; this.root.classList.add("modal--open"); this.root.classList.add("modal--loading"); this.root.classList.toggle("modal--small", size === "small"); this.frame.innerHTML = await getHTML(url); } async close(): Promise { this.root.classList.remove("modal--open"); } }