From 81cb28a961520dd8ff29af6aee67814804e07263 Mon Sep 17 00:00:00 2001 From: Petri Hienonen Date: Sat, 8 Nov 2025 23:06:17 +0200 Subject: Separate modal --- app/dom.js | 166 ++++++++++++++++++++++++++++++++++++++++++------------------- 1 file changed, 116 insertions(+), 50 deletions(-) (limited to 'app/dom.js') diff --git a/app/dom.js b/app/dom.js index cf69e95..50768da 100644 --- a/app/dom.js +++ b/app/dom.js @@ -1,3 +1,4 @@ +// dom.js import { House } from "models"; export class DomOptions { @@ -215,56 +216,6 @@ export class Dom { } export class Widgets { - /** - * Build a modal dialog - * @param {object} style - * @param {() => void} onClose - * @returns {HTMLDialogElement} - */ - static buildModal(style, onClose) { - const modal = document.createElement("dialog"); - Object.assign( - modal.style, - { - background: "white", - border: "none", - borderRadius: "8px", - boxShadow: "0 4px 20px rgba(0,0,0,0.2)", - maxHeight: "80vh", - maxWidth: "600px", - overflowY: "auto", - padding: "20px", - position: "fixed", - top: "50%", - transform: "translateY(-50%)", - width: "90%", - zIndex: "1000", - }, - style, - ); - - const closeBtn = Dom.button( - "x", - onClose, - new DomOptions({ - styles: { - background: "none", - border: "none", - color: "#666", - cursor: "pointer", - fontSize: "24px", - position: "absolute", - right: "10px", - top: "10px", - }, - }), - ); - - modal.appendChild(closeBtn); - modal.addEventListener("close", onClose); - return modal; - } - /** * Build modal content for a house * @param {House} house @@ -552,3 +503,118 @@ export class Widgets { ); } } + +export class Modal { + /** @type {HTMLDialogElement} */ + #dialog; + /** @type {AbortController} */ + #abortController; + /** @type {number | undefined} */ + #timer; + /** @type {boolean} */ + #persistent; + /** @type {House} */ + #house; + /** @type {() => void} */ + #onHide; + /** @type {() => void} */ + #onClearMapTimer; + + /** + * @param {House} house + * @param {boolean} persistent + * @param {object} positionStyles + * @param {() => void} onHide + * @param {() => void} onClearMapTimer + */ + constructor(house, persistent, positionStyles, onHide, onClearMapTimer) { + this.#house = house; + this.#persistent = persistent; + this.#onHide = onHide; + this.#onClearMapTimer = onClearMapTimer; + this.#abortController = new AbortController(); + this.#dialog = document.createElement("dialog"); + + Object.assign( + this.#dialog.style, + { + background: "white", + border: "none", + borderRadius: "8px", + boxShadow: "0 4px 20px rgba(0,0,0,0.2)", + maxHeight: "80vh", + maxWidth: "600px", + overflowY: "auto", + padding: "20px", + position: "fixed", + top: "50%", + transform: "translateY(-50%)", + width: "90%", + zIndex: "1000", + }, + positionStyles, + ); + + const closeBtn = Dom.button( + "x", + () => this.hide(), + new DomOptions({ + styles: { + background: "none", + border: "none", + color: "#666", + cursor: "pointer", + fontSize: "24px", + position: "absolute", + right: "10px", + top: "10px", + }, + }), + ); + + this.#dialog.appendChild(closeBtn); + this.#dialog.appendChild(Widgets.buildModalContent(house)); + + // Add event listeners with AbortController + this.#dialog.addEventListener("close", () => this.hide(), { + signal: this.#abortController.signal, + }); + this.#dialog.addEventListener( + "mouseenter", + () => { + clearTimeout(this.#timer); + this.#onClearMapTimer(); + }, + { signal: this.#abortController.signal }, + ); + this.#dialog.addEventListener( + "mouseleave", + () => { + if (!this.#persistent) { + this.#timer = setTimeout(() => this.hide(), 200); + } + }, + { signal: this.#abortController.signal }, + ); + } + + render() { + return this.#dialog; + } + + show() { + if (this.#persistent) { + this.#dialog.showModal(); + } else { + this.#dialog.show(); + } + } + + hide() { + clearTimeout(this.#timer); + this.#dialog.close(); + this.#dialog.remove(); + this.#abortController.abort(); + this.#onHide(); + } +} -- cgit v1.2.3-70-g09d2