(() => { class RightClickMenu { constructor(options = {}) { this.target = options.target || document.body; this.menu = options.menu || null; this.onAction = options.onAction || (() => {}); this.onBeforeShow = options.onBeforeShow || null; this.lastRightClickTarget = null; this.handleContextMenu = this.handleContextMenu.bind(this); this.handleDocumentClick = this.handleDocumentClick.bind(this); this.handleKeydown = this.handleKeydown.bind(this); this.bindEvents(); } bindEvents() { if (!this.target || !this.menu) return; this.target.addEventListener('contextmenu', this.handleContextMenu); document.addEventListener('click', this.handleDocumentClick); document.addEventListener('keydown', this.handleKeydown); this.menu.addEventListener('click', (e) => { const btn = e.target.closest('[data-action]'); if (!btn) return; const action = btn.dataset.action; this.hide(); this.onAction(action, e); }); } handleContextMenu(e) { const editable = e.target.closest('input, textarea'); if (editable) { return; } e.preventDefault(); // 保存右键点击的目标元素 this.lastRightClickTarget = e.target; // 如果有回调,在显示菜单前调用 if (this.onBeforeShow) { this.onBeforeShow(e); } this.show(e.clientX, e.clientY); } handleDocumentClick(e) { // 点击重命名输入框时不隐藏菜单 if (e.target.classList.contains('rename-input')) { return; } if (this.menu && !this.menu.contains(e.target)) { this.hide(); } } handleKeydown(e) { if (e.key === 'Escape') { this.hide(); } } show(x, y) { if (!this.menu) return; this.menu.classList.add('show'); const { innerWidth, innerHeight } = window; const rect = this.menu.getBoundingClientRect(); let left = x; let top = y; if (left + rect.width > innerWidth) { left = innerWidth - rect.width - 8; } if (top + rect.height > innerHeight) { top = innerHeight - rect.height - 8; } this.menu.style.left = `${left}px`; this.menu.style.top = `${top}px`; } hide() { if (this.menu) { this.menu.classList.remove('show'); } } } window.RightClickMenu = RightClickMenu; })();