aboutsummaryrefslogtreecommitdiffstats
path: root/app
diff options
context:
space:
mode:
Diffstat (limited to 'app')
-rw-r--r--app/dom.js166
-rw-r--r--app/main.js71
2 files changed, 140 insertions, 97 deletions
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 {
@@ -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