UNPKG

drab

Version:

Interactivity for You

66 lines (65 loc) 2.43 kB
import { Base } from "../base/index.js"; /** * Displays content when the `trigger` element is right clicked, or long pressed on mobile. */ export class ContextMenu extends Base { /** Tracks the long press duration on mobile. */ #touchTimer; constructor() { super(); } /** Sets the context menu's `style.left` and `style.top` position. */ set #coordinates(value) { this.getContent().style.left = `${value.x}px`; this.getContent().style.top = `${value.y}px`; } async show(e) { // find coordinates of the click const scrollY = window.scrollY; const scrollX = window.scrollX; const clientX = e instanceof MouseEvent ? e.clientX : (e.touches[0]?.clientX ?? 0); const clientY = e instanceof MouseEvent ? e.clientY : (e.touches[0]?.clientY ?? 0); let x = clientX + scrollX; let y = clientY + scrollY; this.getContent().style.position = "absolute"; this.getContent().setAttribute("data-open", ""); const offsetWidth = this.getContent().offsetWidth + 24; const offsetHeight = this.getContent().offsetHeight + 6; const innerWidth = window.innerWidth; const innerHeight = window.innerHeight; // ensure menu is within view if (x + offsetWidth > scrollX + innerWidth) { x = scrollX + innerWidth - offsetWidth; } if (y + offsetHeight > scrollY + innerHeight) { y = scrollY + innerHeight - offsetHeight; } this.#coordinates = { x, y }; } async hide() { this.getContent().removeAttribute("data-open"); } mount() { // mouse this.triggerListener((e) => { e.preventDefault(); this.show(e); }, "contextmenu"); this.safeListener("click", () => this.hide()); // touch this.triggerListener((e) => { this.#touchTimer = setTimeout(() => { this.show(e); }, 800); }, "touchstart", { passive: true }); const resetTimer = () => clearTimeout(this.#touchTimer); this.triggerListener(resetTimer, "touchend", { passive: true }); this.triggerListener(resetTimer, "touchcancel", { passive: true }); // keyboard this.safeListener("keydown", (e) => { if (e.key === "Escape") { this.hide(); } }); } }