UNPKG

drab

Version:

Interactivity for You

65 lines (64 loc) 2.4 kB
import { Content, Lifecycle, Trigger, } from "../base/index.js"; /** * Displays content when the `trigger` element is right clicked, or long pressed on mobile. */ export class ContextMenu extends Lifecycle(Trigger(Content())) { /** 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.content().style.left = `${value.x}px`; this.content().style.top = `${value.y}px`; } 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.content().style.position = "absolute"; this.content().setAttribute("data-open", ""); const offsetWidth = this.content().offsetWidth + 24; const offsetHeight = this.content().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 }; } hide() { this.content().removeAttribute("data-open"); } mount() { // mouse this.listener("contextmenu", (e) => { e.preventDefault(); this.show(e); }); this.safeListener("click", () => this.hide()); // touch this.listener("touchstart", (e) => { this.#touchTimer = setTimeout(() => { this.show(e); }, 800); }, { passive: true }); const resetTimer = () => clearTimeout(this.#touchTimer); this.listener("touchend", resetTimer, { passive: true }); this.listener("touchcancel", resetTimer, { passive: true }); // keyboard this.safeListener("keydown", (e) => { if (e.key === "Escape") this.hide(); }); } }