diff options
Diffstat (limited to 'app')
| -rw-r--r-- | app/dom.js | 166 | ||||
| -rw-r--r-- | app/main.js | 71 |
2 files changed, 140 insertions, 97 deletions
@@ -1,3 +1,4 @@ +// dom.js import { House } from "models"; export class DomOptions { @@ -216,56 +217,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 * @returns {DocumentFragment} @@ -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(); + } +} diff --git a/app/main.js b/app/main.js index 3c9b637..c8a10a3 100644 --- a/app/main.js +++ b/app/main.js @@ -1,4 +1,5 @@ -import { Dom, DomOptions, Widgets } from "dom"; +// main.js +import { Dom, DomOptions, Modal, Widgets } from "dom"; import { ColorParameter, MapEl } from "map"; import { DataProvider, @@ -32,10 +33,8 @@ export class App { #stats; /** @type {HTMLElement} */ #controls; - /** @type {HTMLDialogElement|null} */ + /** @type {Modal|null} */ #modal = null; - /** @type {number | undefined} */ - #modalTimer = undefined; /** @type {boolean} */ #persistent = false; /** @type {string} */ @@ -80,7 +79,7 @@ export class App { onHouseClick: (houseId, persistent) => this.#showHouseModal(houseId, persistent), onHouseHover: (houseId, hide) => { if (hide) { - this.#hideModal(); + this.#modal?.hide(); } else { this.#showHouseModal(houseId, false); } @@ -409,11 +408,12 @@ export class App { this.#map.setModalPersistence(persistent); } - // Remove existing modal - this.#modal?.remove(); + // Hide existing modal + this.#modal?.hide(); - // Create new modal - this.#modal = Widgets.buildModal( + this.#modal = new Modal( + house, + persistent, { left: "auto", maxHeight: "80vh", @@ -423,47 +423,24 @@ export class App { transform: "translateY(-50%)", width: "90%", }, - - () => this.#hideModal(), + () => { + this.#modal = null; + this.#persistent = false; + if (this.#map) { + this.#map.setModalPersistence(false); + this.#map.clearModalTimer(); + } + }, + () => { + if (this.#map) { + this.#map.clearModalTimer(); + } + }, ); - Object.assign(this.#modal.style); - // Add hover grace period listeners - this.#modal.addEventListener("mouseenter", () => { - clearTimeout(this.#modalTimer); - if (this.#map) { - this.#map.clearModalTimer(); - } - }); - - this.#modal.addEventListener("mouseleave", () => { - if (!this.#persistent) { - this.#modalTimer = setTimeout(() => this.#hideModal(), 200); - } - }); - - this.#modal.appendChild(Widgets.buildModalContent(house)); - document.body.appendChild(this.#modal); - - if (persistent) { - this.#modal.showModal(); - } else { - this.#modal.show(); - } + document.body.appendChild(this.#modal.render()); + this.#modal.show(); } - - #hideModal() { - this.#modal?.close(); - this.#modal?.remove(); - this.#modal = null; - this.#persistent = false; - clearTimeout(this.#modalTimer); - if (this.#map) { - this.#map.setModalPersistence(false); - this.#map.clearModalTimer(); - } - } - /** * Load data and initialize application * @param {HTMLElement} loading |
