rkgk/static/context-menu.js
2025-06-19 13:48:12 +02:00

115 lines
3.2 KiB
JavaScript

// - Create your context menu by instantiating ContextMenu and adding elements to it
// - Open your menu in response to a contextmenu event by using globalContextMenuSpace.openAtCursor(event, menu)
import { listen } from "rkgk/framework.js";
export class ContextMenu extends HTMLElement {
connectedCallback() {
this.classList.add("rkgk-panel");
this.closeController = new AbortController();
this.hasMouse = false;
this.addEventListener("mouseover", () => (this.hasMouse = true));
this.addEventListener("mouseleave", () => (this.hasMouse = false));
window.addEventListener(
"mousedown",
() => {
if (!this.hasMouse) {
this.close();
}
},
{ signal: this.closeController.signal },
);
window.addEventListener(
"keydown",
(event) => {
if (event.key == "Escape") {
this.close();
}
},
{ signal: this.closeController.signal },
);
}
disconnectedCallback() {
this.closeController.abort();
}
close() {
this.space.close(this);
}
addButton(label, options) {
let { classList, disabled, tooltip, noCloseOnClick } = options ?? {};
classList ??= [];
disabled ??= false;
tooltip ??= "";
noCloseOnClick ??= false;
let button = this.appendChild(document.createElement("button"));
button.classList.add(...classList);
button.innerText = label;
button.disabled = disabled;
button.title = tooltip;
button.addEventListener("click", () => {
if (!noCloseOnClick) this.close();
});
return button;
}
addHtmlP(text, options) {
let { classList } = options ?? {};
classList ??= [];
let p = this.appendChild(document.createElement("p"));
p.innerHTML = text;
p.classList.add(...classList);
return p;
}
addSeparator() {
this.appendChild(document.createElement("hr"));
}
}
customElements.define("rkgk-context-menu", ContextMenu);
export class ContextMenuSpace extends HTMLElement {
open(contextMenu) {
contextMenu.space = this;
this.appendChild(contextMenu);
}
close(contextMenu) {
this.removeChild(contextMenu);
}
openAtCursor(cursorEvent, contextMenu) {
this.open(contextMenu);
// NOTE: This assumes the context menu space is positioned at (0, 0) and spans the full page.
let areaWidth = this.clientWidth;
let areaHeight = this.clientHeight;
let menuWidth = contextMenu.clientWidth;
let menuHeight = contextMenu.clientHeight;
let x = cursorEvent.clientX;
let y = cursorEvent.clientY;
// If the menu would reach outside the screen bounds, snap it back in.
if (x + menuWidth > areaWidth) x -= menuWidth;
if (y + menuHeight > areaHeight) y -= menuHeight;
contextMenu.style.transform = `translate(${x}px, ${y}px)`;
}
}
customElements.define("rkgk-context-menu-space", ContextMenuSpace);
export const globalContextMenuSpace = document.querySelector("rkgk-context-menu-space");