Files
T
Claude 294e610eb1 fix: clean up gallery key/resize listeners on teardown; guard overlay.destroy()
- galleryView: hoist Ctrl/Meta key handlers to module scope and remove them (plus a new blur reset) in teardownGalleryView(); disconnect the ResizeObserver; clear the ctrl-active body class; reset header-reveal state so a re-initialised gallery starts clean.
- overlay: make destroy() idempotent and make updateOverlayBounds() a no-op afterwards, so a pending rAF/ResizeObserver callback can't re-observe elements after disconnect().
2026-11-10 09:32:57 +00:00

201 lines
8.6 KiB
JavaScript

import { store } from "../core/store.js";
import { findActiveSidebars, findPropertiesPanel } from "../comfy/adapter.js";
export class SidebarOverlay {
constructor(options = {}) {
this.onDestroy = options.onDestroy || (() => {});
this.onUserClose = options.onUserClose || (() => {});
this.cleanupFns = [];
this.observedElements = new Set();
// Elevate sidebars above overlay
document.body.classList.add("comfy-sidebar-overlay-active");
// Root container: visual backdrop (covers 100% flush from sidebar to sidebar, zero gap)
this.container = document.createElement("div");
this.container.className = `comfy-sidebar-comparison-overlay ${options.className || ""}`;
Object.assign(this.container.style, {
position: "fixed", top: "0", left: "0", width: "100vw", height: "100vh",
background: options.background || "color-mix(in srgb, var(--comfy-menu-bg, #121212) 95%, transparent)",
backdropFilter: "blur(8px)",
color: "var(--fg-color, #eee)",
display: "flex", flexDirection: "column", alignItems: "center",
justifyContent: "center", zIndex: "1000", boxSizing: "border-box",
overflow: "hidden", pointerEvents: "none", userSelect: "none"
});
// Interactive backdrop: insets 6px from each sidebar so the border zones pass clicks to the resizers
this.backdrop = document.createElement("div");
Object.assign(this.backdrop.style, {
position: "absolute", top: "0", bottom: "0", left: "6px", right: "6px",
pointerEvents: "auto", zIndex: "1"
});
this.backdrop.addEventListener("click", (e) => {
if (e.target === this.backdrop) {
this.onUserClose();
this.destroy();
}
});
this.container.appendChild(this.backdrop);
// Close button
this.closeBtn = document.createElement("span");
this.closeBtn.className = "pi pi-times";
this.closeBtn.title = "Close (Esc)";
Object.assign(this.closeBtn.style, {
position: "absolute", top: "16px", right: "24px", zIndex: "30",
cursor: "pointer", fontSize: "18px",
color: "var(--desc-color, #aaa)",
transition: "all 0.15s ease",
background: "var(--comfy-input-bg, rgba(20, 20, 20, 0.6))",
border: "1px solid var(--border-color, rgba(255, 255, 255, 0.1))",
borderRadius: "50%", padding: "6px", pointerEvents: "auto"
});
this.closeBtn.onmouseenter = () => {
this.closeBtn.style.color = "var(--fg-color, #fff)";
this.closeBtn.style.borderColor = "var(--fg-color, #fff)";
};
this.closeBtn.onmouseleave = () => {
this.closeBtn.style.color = "var(--desc-color, #aaa)";
this.closeBtn.style.borderColor = "var(--border-color, rgba(255, 255, 255, 0.1))";
};
this.closeBtn.onclick = () => {
this.onUserClose();
this.destroy();
};
this.container.appendChild(this.closeBtn);
// ResizeObserver to track live drag-resizing on all active sidebars
if (window.ResizeObserver) {
this.ro = new ResizeObserver(() => {
this.updateOverlayBounds();
});
this.cleanupFns.push(() => this.ro.disconnect());
}
// Sidebar boundary alignment
this.updateOverlayBounds = this.updateOverlayBounds.bind(this);
this.updateOverlayBounds();
window.addEventListener("resize", this.updateOverlayBounds);
this.cleanupFns.push(() => window.removeEventListener("resize", this.updateOverlayBounds));
// Live observer for opening/closing panels or layout changes
if (window.MutationObserver) {
let updateScheduled = false;
const mo = new MutationObserver((mutations) => {
// Skip mutations caused purely by the sticky header's scroll animation
const relevant = mutations.some((m) => {
const el = m.target.nodeType === 1 ? m.target : m.target.parentElement;
return !el?.closest?.(".comfy-sidebar-sticky-header-group");
});
if (!relevant) return;
if (!updateScheduled) {
updateScheduled = true;
requestAnimationFrame(() => {
this.updateOverlayBounds();
updateScheduled = false;
});
}
});
mo.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ["style", "class"] });
this.cleanupFns.push(() => mo.disconnect());
}
// Global Key Handler (Esc to close)
const onKeyDown = (e) => {
if (e.key === "Escape") {
e.preventDefault();
this.onUserClose();
this.destroy();
}
if (options.onKeyDown) options.onKeyDown(e);
};
document.addEventListener("keydown", onKeyDown);
this.cleanupFns.push(() => document.removeEventListener("keydown", onKeyDown));
document.body.appendChild(this.container);
}
observeElement(el) {
if (!el || !this.ro || this.observedElements.has(el)) return;
this.observedElements.add(el);
this.ro.observe(el);
}
updateOverlayBounds() {
// A pending rAF/ResizeObserver callback can fire after destroy(); don't re-attach observers
if (this.destroyed) return;
let leftOffset = 0;
let rightOffset = 0;
let topOffset = 0;
const winW = window.innerWidth;
const midX = winW / 2;
try {
// Dynamically detect any top header/tab bar docked to the top of the screen
const potentialTopBars = document.querySelectorAll("header, .comfyui-header, .comfyui-menu, .comfy-menu, [class*='tab-bar'], [class*='workflow-tab'], [class*='workspace-tab'], [class*='header']");
for (const bar of potentialTopBars) {
// Ignore our own sliding gallery/queue header (it animates via translateY)
if (bar.closest(".comfy-sidebar-sticky-header-group, .comfy-sidebar-header-root")) continue;
// Must be an element sitting at the top of the viewport
const r = bar.getBoundingClientRect();
if (r.top <= 2 && r.bottom > 20 && r.bottom < 120 && r.width > 200) {
topOffset = Math.max(topOffset, Math.round(r.bottom));
}
}
const { leftPanels, rightPanels } = findActiveSidebars();
[...leftPanels, ...rightPanels].forEach(p => this.observeElement(p));
for (const el of leftPanels) {
const rect = el.getBoundingClientRect();
if (rect.right > 0 && rect.right < midX) {
leftOffset = Math.max(leftOffset, Math.round(rect.right));
}
}
for (const el of rightPanels) {
const rect = el.getBoundingClientRect();
if (rect.left > midX && rect.left < winW) {
rightOffset = Math.max(rightOffset, Math.round(winW - rect.left));
}
}
} catch (e) {
console.error("Comfy Sidebar: Error finding active sidebars:", e);
}
// Safety clamp: offsets can never exceed half screen width
leftOffset = Math.min(midX - 50, Math.max(0, leftOffset));
rightOffset = Math.min(midX - 50, Math.max(0, rightOffset));
topOffset = Math.max(0, topOffset);
this.container.style.left = `${leftOffset}px`;
this.container.style.right = `${rightOffset}px`;
this.container.style.width = `calc(100vw - ${leftOffset + rightOffset}px)`;
this.container.style.top = `${topOffset}px`;
this.container.style.height = `calc(100vh - ${topOffset}px)`;
// Crisp 1px visible separator lines along active sidebar borders
this.container.style.borderLeft = leftOffset > 0 ? "1px solid var(--border-color, rgba(255, 255, 255, 0.22))" : "none";
this.container.style.borderRight = rightOffset > 0 ? "1px solid var(--border-color, rgba(255, 255, 255, 0.22))" : "none";
}
addCleanup(fn) {
this.cleanupFns.push(fn);
}
destroy() {
if (this.destroyed) return;
this.destroyed = true;
document.body.classList.remove("comfy-sidebar-overlay-active");
for (const fn of this.cleanupFns.splice(0)) {
try { fn(); } catch (e) {}
}
this.container.remove();
this.onDestroy();
}
}