Rewild Your Web
web browser dweb
at main 116 lines 2.7 kB view raw
1// SPDX-License-Identifier: AGPL-3.0-or-later 2 3import { MenuBase, css, html } from "./menu_base.js"; 4 5export class ContextMenu extends MenuBase { 6 static properties = { 7 ...MenuBase.properties, 8 items: { type: Array }, 9 x: { type: Number }, 10 y: { type: Number }, 11 controlId: { type: String }, 12 }; 13 14 constructor() { 15 super(); 16 this.items = []; 17 this.x = 0; 18 this.y = 0; 19 this.controlId = ""; 20 this.handleKeyDown = this.handleKeyDown.bind(this); 21 } 22 23 connectedCallback() { 24 super.connectedCallback(); 25 } 26 27 disconnectedCallback() { 28 super.disconnectedCallback(); 29 this.removeEventListeners(); 30 } 31 32 updated(changedProperties) { 33 if (changedProperties.has("open")) { 34 if (this.open) { 35 // Add keyboard listener when menu opens 36 document.addEventListener("keydown", this.handleKeyDown); 37 } else { 38 this.removeEventListeners(); 39 } 40 } 41 } 42 43 removeEventListeners() { 44 document.removeEventListener("keydown", this.handleKeyDown); 45 } 46 47 handleKeyDown(e) { 48 if (e.key === "Escape") { 49 this.cancel(); 50 } 51 } 52 53 handleBackdropClick(e) { 54 // Only cancel if the click was directly on the backdrop, not on the menu 55 if (e.target.classList.contains("backdrop")) { 56 this.cancel(); 57 } 58 } 59 60 cancel() { 61 this.dispatchEvent( 62 new CustomEvent("menu-cancel", { 63 bubbles: true, 64 composed: true, 65 detail: { controlId: this.controlId }, 66 }), 67 ); 68 this.close(); 69 } 70 71 handleItemClick(item) { 72 if (item.disabled) { 73 return; 74 } 75 this.dispatchEvent( 76 new CustomEvent("menu-action", { 77 bubbles: true, 78 composed: true, 79 detail: { action: item.id, controlId: this.controlId }, 80 }), 81 ); 82 this.close(); 83 } 84 85 static styles = css` 86 @import url(//system.localhost:8888/context_menu.css); 87 `; 88 89 render() { 90 return html` 91 <div class="backdrop" @click=${this.handleBackdropClick}></div> 92 <div class="menu" style="left: ${this.x}px; top: ${this.y}px;"> 93 ${this.items.map( 94 (item) => html` 95 <div 96 class="menu-item ${item.disabled ? "disabled" : ""}" 97 @click=${() => this.handleItemClick(item)} 98 > 99 <span class="icon-slot"> 100 ${item.icon 101 ? html`<lucide-icon name="${item.icon}"></lucide-icon>` 102 : ""} 103 </span> 104 <span>${item.label}</span> 105 ${item.checked 106 ? html`<lucide-icon name="check"></lucide-icon>` 107 : ""} 108 </div> 109 `, 110 )} 111 </div> 112 `; 113 } 114} 115 116customElements.define("context-menu", ContextMenu);