// Panels overlay the canvas: revealing tools never resizes or replaces it. export class Menus { constructor(canOpen, ignoreOutsidePointer) { this.canOpen = canOpen; this.items = [ ["tools-toggle", "tools-panel"], ["settings-toggle", "inspector"], ].map(([button, panel]) => ({ button: document.getElementById(button), panel: document.getElementById(panel), })); for (const item of this.items) { item.button.addEventListener("pointerenter", (e) => { if (["pen", "mouse"].includes(e.pointerType) && e.buttons === 0) this.open(item); }); item.button.addEventListener("click", () => { if (this.current === item && this.pinned) this.close(); else this.open(item, true); }); item.button.addEventListener("keydown", (e) => { if (e.key === "ArrowDown") { e.preventDefault(); this.open(item, true, true); } }); for (const el of [item.button, item.panel]) { el.addEventListener("pointerenter", () => clearTimeout(this.timer)); el.addEventListener("pointerleave", () => { clearTimeout(this.timer); this.timer = setTimeout(() => { if (!this.pinned) this.close(false); }, 250); }); } item.panel.addEventListener("pointerdown", () => { this.pinned = true; clearTimeout(this.timer); }); item.panel.addEventListener("focusin", () => { this.pinned = true; clearTimeout(this.timer); }); item.panel.querySelector("[data-close]").onclick = () => this.close(); } document.addEventListener( "pointerdown", (e) => { if ( this.current && !ignoreOutsidePointer(e) && !this.current.panel.contains(e.target) && !this.current.button.contains(e.target) ) this.close(false); }, true, ); document.addEventListener( "keydown", (e) => { if ( e.key === "Escape" && this.current && !document.querySelector("dialog[open]") ) { e.preventDefault(); e.stopPropagation(); this.close(); } }, true, ); window.addEventListener("resize", () => this.close(false)); } open(item, pin = false, focus = false) { if (!this.canOpen() || document.querySelector("dialog[open]")) return; clearTimeout(this.timer); if (this.current !== item) { this.close(false); this.current = item; this.pinned = false; } this.pinned ||= pin; item.panel.hidden = false; const top = Math.max( 12, Math.min( item.button.getBoundingClientRect().top, window.innerHeight - item.panel.offsetHeight - 12, ), ); item.panel.style.top = `${top}px`; item.panel.style.maxHeight = `calc(100dvh - ${top + 12}px)`; item.button.setAttribute("aria-expanded", "true"); if (focus) item.panel.querySelector("button:not(:disabled),input,select")?.focus(); } close(restore = true) { clearTimeout(this.timer); if (!this.current) return; const item = this.current; this.current = null; this.pinned = false; item.panel.hidden = true; item.button.setAttribute("aria-expanded", "false"); if (restore) item.button.focus(); } }