UNPKG

drab

Version:

Interactivity for You

96 lines (78 loc) 2.33 kB
import { Content, type ContentAttributes, Lifecycle, Trigger, type TriggerAttributes, } from "../base/index.js"; export interface ContextMenuAttributes extends TriggerAttributes, ContentAttributes {} /** * 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: NodeJS.Timeout | undefined; constructor() { super(); } /** Sets the context menu's `style.left` and `style.top` position. */ set #coordinates(value: { x: number; y: number }) { this.content().style.left = `${value.x}px`; this.content().style.top = `${value.y}px`; } show(e: MouseEvent | TouchEvent) { // 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"); } override 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(); }); } }