1.目的

今回 LWC で ContextMenu コンポーネントを作成しようと思います、
基本的な考え方は右クリックをすると、カーソルの座標を取得し、
その座標を使って、ContextMenu の位置を設定します。

2.ソース構成

lwc
  ├─contextMenu
  └─contextMenuContainer
  • contextMenu
    image.png
div {
display: block;
}

.contextmenu {
background-color: #fff;
background-clip: padding-box;
border: 1px solid rgba(0, 0, 0, 0.15);
border-radius: 0.25rem;
color: #373a3c;
margin: 2px 0 0;
min-width: 10em;
outline: none;
padding: 2px 0;
/_ pointer-events: none; _/
text-align: left;
transition: opacity 250ms ease !important;
z-index: 9999;
position: fixed;
opacity: 1;
}

.contextmenu-item {
background: 0 0;
border: 0;
color: #373a3c;
cursor: pointer;
font-weight: 400;
line-height: 1.5;
padding: 3px 20px;
text-align: inherit;
white-space: nowrap;
}

.contextmenu-item:active {
outline: none;
}

.contextmenu-item:hover {
background-color: rgb(78, 107, 235);
color: #fff;
}

<template>
  <template if:true="{_showMenu}">
    <div
      role="menu"
      tabindex="-1"
      class="contextmenu"
      style="{position}"
      oncontextmenu="{contextMenuHadler}"
    >
      <template for:each="{_menuItem}" for:item="item">
        <div
          key="{item.id}"
          data-id="{item.id}"
          data-name="{item.name}"
          data-value="{item.value}"
          onclick="{clickHandler}"
          class="contextmenu-item"
          role="menuitem"
          tabindex="-1"
          aria-disabled="false"
        >
          {item.name}
        </div>
      </template>
    </div>
  </template>
</template>
import { LightningElement, api, track } from "lwc";

export default class MenuComponent extends LightningElement {
  @track _positionX;
  @track _positionY;
  @track _showMenu;
  @track _menuItem;
  @api name;

  /**
   * スタイル取得
   */
  get position() {
    const { _positionX, _positionY } = this;
    if (_positionX && _positionY) {
      // return `position:absolute;z-index: 999; top: ${_positionY}px; left:${_positionX}px`;
      return `top: ${_positionY}px; left:${_positionX}px`;
    } else {
      this.closeMenu();
      return "";
    }
  }

  /**
   * 座標Xを取得
   */
  @api
  get positionX() {
    return this._positionX;
  }
  /**
   * 座標Xを設定
   */
  set positionX(val) {
    this._positionX = val;
  }

  /**
   * 座標Yを取得
   */
  @api
  get positionY() {
    return this._positionY;
  }

  /**
   * 座標Yを設定
   */
  set positionY(val) {
    this._positionY = val;
  }

  /**
   * メニューアイテムを取得
   */
  @api
  get menuItem() {
    return this._menuItem;
  }

  /**
   * メニューアイテムを設定
   */
  set menuItem(val) {
    this._menuItem = val || [];
  }

  /**
   * メニューアイテム押下
   * @param {*} event
   */
  clickHandler(event) {
    event.preventDefault();
    event.stopPropagation();
    let target = event.target;
    let id = target.dataset.id;
    let name = target.dataset.name;
    let value = target.dataset.value;
    this.closeMenu();
    const clickEvent = new CustomEvent("rightclick", {
      detail: {
        id,
        name,
        value,
      },
    });
    this.dispatchEvent(clickEvent);
  }

  @api
  closeMenu() {
    this._showMenu = false;
  }

  @api
  openMenu() {
    this._showMenu = true;
  }

  /**
   *
   * @param {*} event
   * @returns
   */
  contextMenuHadler(event) {
    event.preventDefault();
    return false;
  }
}
  • contextMenuContainer
    image.png
<template>
  <c-context-menu
    position-x="{positionX}"
    position-y="{positionY}"
    menu-item="{menuItem}"
    onrightclick="{contextMenuClickHandler}"
  >
  </c-context-menu>
  <div
    class="slds-card"
    style="width:100%;height:300px;"
    onmousedown="{rightClick}"
    oncontextmenu="{contextMenuHadler}"
  ></div>
</template>
import { LightningElement, track } from "lwc";

export default class ContextMenuContainer extends LightningElement {
  @track positionX;
  @track positionY;

  //メニューアイテム設定
  @track menuItem = [
    {
      id: 0,
      name: "メニュー1",
      value: "menu1",
    },
    {
      id: 1,
      name: "メニュー2",
      value: "menu2",
    },
  ];

  /**
   * 右クリック
   * @param {*} event
   */
  rightClick(event) {
    if (event.which == 3) {
      this.positionX = event.clientX;
      this.positionY = event.clientY;
      //右クリックメニュー表示
      this.template.querySelector("c-context-menu").openMenu();
    } else {
      //右クリックメニュー閉じる
      this.template.querySelector("c-context-menu").closeMenu();
    }
  }

  /**
   *
   * @param {*} event
   * @returns
   */
  contextMenuHadler(event) {
    event.preventDefault();
    return false;
  }

  /**
   * 右クリックメニューハンドラー
   * @param {*} event
   */
  contextMenuClickHandler(event) {
    let result = event.detail;
    const { id, name, value } = result;
    console.log(id, name, value);
  }
}

Salesforce 側動作確認

  • Salesforce 側 Lightning コンポーネントタブを作成
    image.png

  • タブを開く
    右クリックすると、ContextMenu が表示します
    image.png