// @ts-check import { prop } from "../../utils.js"; import { $el } from "../../ui.js"; import { applyClasses } from "../utils.js"; export class ComfyPopup extends EventTarget { element = $el("div.comfyui-popup"); /** * @param {{ * target: HTMLElement, * container?: HTMLElement, * classList?: import("../utils.js").ClassList, * ignoreTarget?: boolean, * closeOnEscape?: boolean, * position?: "absolute" | "relative", * horizontal?: "left" | "right" * }} param0 * @param {...HTMLElement} children */ constructor( { target, container = document.body, classList = "", ignoreTarget = true, closeOnEscape = true, position = "absolute", horizontal = "left", }, ...children ) { super(); this.target = target; this.ignoreTarget = ignoreTarget; this.container = container; this.position = position; this.closeOnEscape = closeOnEscape; this.horizontal = horizontal; container.append(this.element); this.children = prop(this, "children", children, () => { this.element.replaceChildren(...this.children); this.update(); }); this.classList = prop(this, "classList", classList, () => applyClasses(this.element, this.classList, "comfyui-popup", horizontal)); this.open = prop(this, "open", false, (v, o) => { if (v === o) return; if (v) { this.#show(); } else { this.#hide(); } }); } toggle() { this.open = !this.open; } #hide() { this.element.classList.remove("open"); window.removeEventListener("resize", this.update); window.removeEventListener("click", this.#clickHandler, { capture: true }); window.removeEventListener("keydown", this.#escHandler, { capture: true }); this.dispatchEvent(new CustomEvent("close")); this.dispatchEvent(new CustomEvent("change")); } #show() { this.element.classList.add("open"); this.update(); window.addEventListener("resize", this.update); window.addEventListener("click", this.#clickHandler, { capture: true }); if (this.closeOnEscape) { window.addEventListener("keydown", this.#escHandler, { capture: true }); } this.dispatchEvent(new CustomEvent("open")); this.dispatchEvent(new CustomEvent("change")); } #escHandler = (e) => { if (e.key === "Escape") { this.open = false; e.preventDefault(); e.stopImmediatePropagation(); } }; #clickHandler = (e) => { /** @type {any} */ const target = e.target; if (!this.element.contains(target) && this.ignoreTarget && !this.target.contains(target)) { this.open = false; } }; update = () => { const rect = this.target.getBoundingClientRect(); this.element.style.setProperty("--bottom", "unset"); if (this.position === "absolute") { if (this.horizontal === "left") { this.element.style.setProperty("--left", rect.left + "px"); } else { this.element.style.setProperty("--left", rect.right - this.element.clientWidth + "px"); } this.element.style.setProperty("--top", rect.bottom + "px"); this.element.style.setProperty("--limit", rect.bottom + "px"); } else { this.element.style.setProperty("--left", 0 + "px"); this.element.style.setProperty("--top", rect.height + "px"); this.element.style.setProperty("--limit", rect.height + "px"); } const thisRect = this.element.getBoundingClientRect(); if (thisRect.height < 30) { // Move up instead this.element.style.setProperty("--top", "unset"); this.element.style.setProperty("--bottom", rect.height + 5 + "px"); this.element.style.setProperty("--limit", rect.height + 5 + "px"); } }; }