right-click-menu.js 2.9 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
  1. (() => {
  2. class RightClickMenu {
  3. constructor(options = {}) {
  4. this.target = options.target || document.body;
  5. this.menu = options.menu || null;
  6. this.onAction = options.onAction || (() => {});
  7. this.onBeforeShow = options.onBeforeShow || null;
  8. this.lastRightClickTarget = null;
  9. this.handleContextMenu = this.handleContextMenu.bind(this);
  10. this.handleDocumentClick = this.handleDocumentClick.bind(this);
  11. this.handleKeydown = this.handleKeydown.bind(this);
  12. this.bindEvents();
  13. }
  14. bindEvents() {
  15. if (!this.target || !this.menu) return;
  16. this.target.addEventListener('contextmenu', this.handleContextMenu);
  17. document.addEventListener('click', this.handleDocumentClick);
  18. document.addEventListener('keydown', this.handleKeydown);
  19. this.menu.addEventListener('click', (e) => {
  20. const btn = e.target.closest('[data-action]');
  21. if (!btn) return;
  22. const action = btn.dataset.action;
  23. this.hide();
  24. this.onAction(action, e);
  25. });
  26. }
  27. handleContextMenu(e) {
  28. const editable = e.target.closest('input, textarea');
  29. if (editable) {
  30. return;
  31. }
  32. e.preventDefault();
  33. // 保存右键点击的目标元素
  34. this.lastRightClickTarget = e.target;
  35. // 如果有回调,在显示菜单前调用
  36. if (this.onBeforeShow) {
  37. this.onBeforeShow(e);
  38. }
  39. this.show(e.clientX, e.clientY);
  40. }
  41. handleDocumentClick(e) {
  42. // 点击重命名输入框时不隐藏菜单
  43. if (e.target.classList.contains('rename-input')) {
  44. return;
  45. }
  46. if (this.menu && !this.menu.contains(e.target)) {
  47. this.hide();
  48. }
  49. }
  50. handleKeydown(e) {
  51. if (e.key === 'Escape') {
  52. this.hide();
  53. }
  54. }
  55. show(x, y) {
  56. if (!this.menu) return;
  57. this.menu.classList.add('show');
  58. const { innerWidth, innerHeight } = window;
  59. const rect = this.menu.getBoundingClientRect();
  60. let left = x;
  61. let top = y;
  62. if (left + rect.width > innerWidth) {
  63. left = innerWidth - rect.width - 8;
  64. }
  65. if (top + rect.height > innerHeight) {
  66. top = innerHeight - rect.height - 8;
  67. }
  68. this.menu.style.left = `${left}px`;
  69. this.menu.style.top = `${top}px`;
  70. }
  71. hide() {
  72. if (this.menu) {
  73. this.menu.classList.remove('show');
  74. }
  75. }
  76. }
  77. window.RightClickMenu = RightClickMenu;
  78. })();