// @ts-check import { $el } from "../../ui.js"; import { applyClasses, toggleElement } from "../utils.js"; import { prop } from "../../utils.js"; /** * @typedef {{ * icon?: string; * overIcon?: string; * iconSize?: number; * content?: string | HTMLElement; * tooltip?: string; * enabled?: boolean; * action?: (e: Event, btn: ComfyButton) => void, * classList?: import("../utils.js").ClassList, * visibilitySetting?: { id: string, showValue: any }, * app?: import("../../app.js").ComfyApp * }} ComfyButtonProps */ export class ComfyButton { #over = 0; #popupOpen = false; isOver = false; iconElement = $el("i.mdi"); contentElement = $el("span"); /** * @type {import("./popup.js").ComfyPopup} */ popup; /** * @param {ComfyButtonProps} opts */ constructor({ icon, overIcon, iconSize, content, tooltip, action, classList = "comfyui-button", visibilitySetting, app, enabled = true, }) { this.element = $el("button", { onmouseenter: () => { this.isOver = true; if(this.overIcon) { this.updateIcon(); } }, onmouseleave: () => { this.isOver = false; if(this.overIcon) { this.updateIcon(); } } }, [this.iconElement, this.contentElement]); this.icon = prop(this, "icon", icon, toggleElement(this.iconElement, { onShow: this.updateIcon })); this.overIcon = prop(this, "overIcon", overIcon, () => { if(this.isOver) { this.updateIcon(); } }); this.iconSize = prop(this, "iconSize", iconSize, this.updateIcon); this.content = prop( this, "content", content, toggleElement(this.contentElement, { onShow: (el, v) => { if (typeof v === "string") { el.textContent = v; } else { el.replaceChildren(v); } }, }) ); this.tooltip = prop(this, "tooltip", tooltip, (v) => { if (v) { this.element.title = v; } else { this.element.removeAttribute("title"); } }); this.classList = prop(this, "classList", classList, this.updateClasses); this.hidden = prop(this, "hidden", false, this.updateClasses); this.enabled = prop(this, "enabled", enabled, () => { this.updateClasses(); this.element.disabled = !this.enabled; }); this.action = prop(this, "action", action); this.element.addEventListener("click", (e) => { if (this.popup) { // we are either a touch device or triggered by click not hover if (!this.#over) { this.popup.toggle(); } } this.action?.(e, this); }); if (visibilitySetting?.id) { const settingUpdated = () => { this.hidden = app.ui.settings.getSettingValue(visibilitySetting.id) !== visibilitySetting.showValue; }; app.ui.settings.addEventListener(visibilitySetting.id + ".change", settingUpdated); settingUpdated(); } } updateIcon = () => (this.iconElement.className = `mdi mdi-${(this.isOver && this.overIcon) || this.icon}${this.iconSize ? " mdi-" + this.iconSize + "px" : ""}`); updateClasses = () => { const internalClasses = []; if (this.hidden) { internalClasses.push("hidden"); } if (!this.enabled) { internalClasses.push("disabled"); } if (this.popup) { if (this.#popupOpen) { internalClasses.push("popup-open"); } else { internalClasses.push("popup-closed"); } } applyClasses(this.element, this.classList, ...internalClasses); }; /** * * @param { import("./popup.js").ComfyPopup } popup * @param { "click" | "hover" } mode */ withPopup(popup, mode = "click") { this.popup = popup; if (mode === "hover") { for (const el of [this.element, this.popup.element]) { el.addEventListener("mouseenter", () => { this.popup.open = !!++this.#over; }); el.addEventListener("mouseleave", () => { this.popup.open = !!--this.#over; }); } } popup.addEventListener("change", () => { this.#popupOpen = popup.open; this.updateClasses(); }); return this; } }