src/extras/ContextMenu/ContextMenu.js
- import { Map } from "../../viewer/scene/utils/Map.js";
-
- const idMap = new Map();
-
- /**
- * Internal data class that represents the state of a menu or a submenu.
- * @private
- */
- class Menu {
- constructor(id) {
- this.id = id;
- this.parentItem = null; // Set to an Item when this Menu is a submenu
- this.groups = [];
- this.menuElement = null;
- this.shown = false;
- this.mouseOver = 0;
- }
- }
-
- /**
- * Internal data class that represents a group of Items in a Menu.
- * @private
- */
- class Group {
- constructor() {
- this.items = [];
- }
- }
-
- /**
- * Internal data class that represents the state of a menu item.
- * @private
- */
- class Item {
- constructor(id, getTitle, doAction, getEnabled, getShown) {
- this.id = id;
- this.getTitle = getTitle;
- this.doAction = doAction;
- this.getEnabled = getEnabled;
- this.getShown = getShown;
- this.itemElement = null;
- this.subMenu = null;
- this.enabled = true;
- }
- }
-
- /**
- * @desc A customizable HTML context menu.
- *
- * [<img src="http://xeokit.io/img/docs/ContextMenu/ContextMenu.gif">](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)
- *
- * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)]
- *
- * ## Overview
- *
- * * A pure JavaScript, lightweight context menu
- * * Dynamically configure menu items
- * * Dynamically enable or disable items
- * * Dynamically show or hide items
- * * Supports cascading sub-menus
- * * Configure custom style with CSS (see examples above)
- *
- * ## Usage
- *
- * In the example below we'll create a ````ContextMenu```` that pops up whenever we right-click on an {@link Entity} within
- * our {@link Scene}.
- *
- * First, we'll create the ````ContextMenu````, configuring it with a list of menu items.
- *
- * Each item has:
- *
- * * a ````title```` for the item,
- * * a ````doAction()```` callback to fire when the item's title is clicked,
- * * an optional ````getShown()```` callback that indicates if the item should shown in the menu or not, and
- * * an optional ````getEnabled()```` callback that indicates if the item should be shown enabled in the menu or not.
- *
- * <br>
- *
- * The ````getShown()```` and ````getEnabled()```` callbacks are invoked whenever the menu is shown.
- *
- * When an item's ````getShown()```` callback
- * returns ````true````, then the item is shown. When it returns ````false````, then the item is hidden. An item without
- * a ````getShown()```` callback is always shown.
- *
- * When an item's ````getEnabled()```` callback returns ````true````, then the item is enabled and clickable (as long as it's also shown). When it
- * returns ````false````, then the item is disabled and cannot be clicked. An item without a ````getEnabled()````
- * callback is always enabled and clickable.
- *
- * Note how the ````doAction()````, ````getShown()```` and ````getEnabled()```` callbacks accept a ````context````
- * object. That must be set on the ````ContextMenu```` before we're able to we show it. The context object can be anything. In this example,
- * we'll use the context object to provide the callbacks with the Entity that we right-clicked.
- *
- * We'll also initially enable the ````ContextMenu````.
- *
- * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_Custom)]
- *
- * ````javascript
- * const canvasContextMenu = new ContextMenu({
- *
- * enabled: true,
- *
- * items: [
- * [
- * {
- * title: "Hide Object",
- * getEnabled: (context) => {
- * return context.entity.visible; // Can't hide entity if already hidden
- * },
- * doAction: function (context) {
- * context.entity.visible = false;
- * }
- * }
- * ],
- * [
- * {
- * title: "Select Object",
- * getEnabled: (context) => {
- * return (!context.entity.selected); // Can't select an entity that's already selected
- * },
- * doAction: function (context) {
- * context.entity.selected = true;
- * }
- * }
- * ],
- * [
- * {
- * title: "X-Ray Object",
- * getEnabled: (context) => {
- * return (!context.entity.xrayed); // Can't X-ray an entity that's already X-rayed
- * },
- * doAction: (context) => {
- * context.entity.xrayed = true;
- * }
- * }
- * ]
- * ]
- * });
- * ````
- *
- * Next, we'll make the ````ContextMenu```` appear whenever we right-click on an Entity. Whenever we right-click
- * on the canvas, we'll attempt to pick the Entity at those mouse coordinates. If we succeed, we'll feed the
- * Entity into ````ContextMenu```` via the context object, then show the ````ContextMenu````.
- *
- * From there, each ````ContextMenu```` item's ````getEnabled()```` callback will be invoked (if provided), to determine if the item should
- * be enabled. If we click an item, its ````doAction()```` callback will be invoked with our context object.
- *
- * Remember that we must set the context on our ````ContextMenu```` before we show it, otherwise it will log an error to the console,
- * and ignore our attempt to show it.
- *
- * ````javascript*
- * viewer.scene.canvas.canvas.oncontextmenu = (e) => { // Right-clicked on the canvas
- *
- * if (!objectContextMenu.enabled) {
- * return;
- * }
- *
- * var hit = viewer.scene.pick({ // Try to pick an Entity at the coordinates
- * canvasPos: [e.pageX, e.pageY]
- * });
- *
- * if (hit) { // Picked an Entity
- *
- * objectContextMenu.context = { // Feed entity to ContextMenu
- * entity: hit.entity
- * };
- *
- * objectContextMenu.show(e.pageX, e.pageY); // Show the ContextMenu
- * }
- *
- * e.preventDefault();
- * });
- * ````
- *
- * Note how we only show the ````ContextMenu```` if it's enabled. We can use that mechanism to switch between multiple
- * ````ContextMenu```` instances depending on what we clicked.
- *
- * ## Dynamic Item Titles
- *
- * To make an item dynamically regenerate its title text whenever we show the ````ContextMenu````, provide its title with a
- * ````getTitle()```` callback. The callback will fire each time you show ````ContextMenu````, which will dynamically
- * set the item title text.
- *
- * In the example below, we'll create a simple ````ContextMenu```` that allows us to toggle the selection of an object
- * via its first item, which changes text depending on whether we are selecting or deselecting the object.
- *
- * [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_dynamicItemTitles)]
- *
- * ````javascript
- * const canvasContextMenu = new ContextMenu({
- *
- * enabled: true,
- *
- * items: [
- * [
- * {
- * getTitle: (context) => {
- * return (!context.entity.selected) ? "Select" : "Undo Select";
- * },
- * doAction: function (context) {
- * context.entity.selected = !context.entity.selected;
- * }
- * },
- * {
- * title: "Clear Selection",
- * getEnabled: function (context) {
- * return (context.viewer.scene.numSelectedObjects > 0);
- * },
- * doAction: function (context) {
- * context.viewer.scene.setObjectsSelected(context.viewer.scene.selectedObjectIds, false);
- * }
- * }
- * ]
- * ]
- * });
- * ````
- *
- * ## Sub-menus
- *
- * Each menu item can optionally have a sub-menu, which will appear when we hover over the item.
- *
- * In the example below, we'll create a much simpler ````ContextMenu```` that has only one item, called "Effects", which
- * will open a cascading sub-menu whenever we hover over that item.
- *
- * Note that our "Effects" item has no ````doAction```` callback, because an item with a sub-menu performs no
- * action of its own.
- *
- * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_subMenus)]
- *
- * ````javascript
- * const canvasContextMenu = new ContextMenu({
- * items: [ // Top level items
- * [
- * {
- * getTitle: (context) => {
- * return "Effects";
- * },
- *
- * items: [ // Sub-menu
- * [
- * {
- * getTitle: (context) => {
- * return (!context.entity.visible) ? "Show" : "Hide";
- * },
- * doAction: function (context) {
- * context.entity.visible = !context.entity.visible;
- * }
- * },
- * {
- * getTitle: (context) => {
- * return (!context.entity.selected) ? "Select" : "Undo Select";
- * },
- * doAction: function (context) {
- * context.entity.selected = !context.entity.selected;
- * }
- * },
- * {
- * getTitle: (context) => {
- * return (!context.entity.highlighted) ? "Highlight" : "Undo Highlight";
- * },
- * doAction: function (context) {
- * context.entity.highlighted = !context.entity.highlighted;
- * }
- * }
- * ]
- * ]
- * }
- * ]
- * ]
- * });
- * ````
- */
- class ContextMenu {
-
- /**
- * Creates a ````ContextMenu````.
- *
- * The ````ContextMenu```` will be initially hidden.
- *
- * @param {Object} [cfg] ````ContextMenu```` configuration.
- * @param {Object} [cfg.items] The context menu items. These can also be dynamically set on {@link ContextMenu#items}. See the class documentation for an example.
- * @param {Object} [cfg.context] The context, which is passed into the item callbacks. This can also be dynamically set on {@link ContextMenu#context}. This must be set before calling {@link ContextMenu#show}.
- * @param {Boolean} [cfg.enabled=true] Whether this ````ContextMenu```` is initially enabled. {@link ContextMenu#show} does nothing while this is ````false````.
- * @param {Boolean} [cfg.hideOnMouseDown=true] Whether this ````ContextMenu```` automatically hides whenever we mouse-down or tap anywhere in the page.
- * @param {Boolean} [cfg.hideOnAction=true] Whether this ````ContextMenu```` automatically hides after we select a menu item. Se false if we want the menu to remain shown and show any updates to its item titles, after we've selected an item.
- */
- constructor(cfg = {}) {
-
- this._id = idMap.addItem();
- this._context = null;
- this._enabled = false; // True when the ContextMenu is enabled
- this._itemsCfg = []; // Items as given as configs
- this._rootMenu = null; // The root Menu in the tree
- this._menuList = []; // List of Menus
- this._menuMap = {}; // Menus mapped to their IDs
- this._itemList = []; // List of Items
- this._itemMap = {}; // Items mapped to their IDs
- this._shown = false; // True when the ContextMenu is visible
- this._nextId = 0;
-
- /**
- * Subscriptions to events fired at this ContextMenu.
- * @private
- */
- this._eventSubs = {};
-
- if (cfg.hideOnMouseDown !== false) {
- document.addEventListener("mousedown", (event) => {
- if (!event.target.classList.contains("xeokit-context-menu-item")) {
- this.hide();
- }
- });
- document.addEventListener("touchstart", this._canvasTouchStartHandler = (event) => {
- if (!event.target.classList.contains("xeokit-context-menu-item")) {
- this.hide();
- }
- });
- }
-
- if (cfg.items) {
- this.items = cfg.items;
- }
-
- this._hideOnAction = (cfg.hideOnAction !== false);
-
- this.context = cfg.context;
- this.enabled = cfg.enabled !== false;
- this.hide();
- }
-
-
- /**
- Subscribes to an event fired at this ````ContextMenu````.
-
- @param {String} event The event
- @param {Function} callback Callback fired on the event
- */
- on(event, callback) {
- let subs = this._eventSubs[event];
- if (!subs) {
- subs = [];
- this._eventSubs[event] = subs;
- }
- subs.push(callback);
- }
-
- /**
- Fires an event at this ````ContextMenu````.
-
- @param {String} event The event type name
- @param {Object} value The event parameters
- */
- fire(event, value) {
- const subs = this._eventSubs[event];
- if (subs) {
- for (let i = 0, len = subs.length; i < len; i++) {
- subs[i](value);
- }
- }
- }
-
- /**
- * Sets the ````ContextMenu```` items.
- *
- * These can be updated dynamically at any time.
- *
- * See class documentation for an example.
- *
- * @type {Object[]}
- */
- set items(itemsCfg) {
- this._clear();
- this._itemsCfg = itemsCfg || [];
- this._parseItems(itemsCfg);
- this._createUI();
- }
-
- /**
- * Gets the ````ContextMenu```` items.
- *
- * @type {Object[]}
- */
- get items() {
- return this._itemsCfg;
- }
-
- /**
- * Sets whether this ````ContextMenu```` is enabled.
- *
- * Hides the menu when disabling.
- *
- * @type {Boolean}
- */
- set enabled(enabled) {
- enabled = (!!enabled);
- if (enabled === this._enabled) {
- return;
- }
- this._enabled = enabled;
- if (!this._enabled) {
- this.hide();
- }
- }
-
- /**
- * Gets whether this ````ContextMenu```` is enabled.
- *
- * {@link ContextMenu#show} does nothing while this is ````false````.
- *
- * @type {Boolean}
- */
- get enabled() {
- return this._enabled;
- }
-
- /**
- * Sets the ````ContextMenu```` context.
- *
- * The context can be any object that you need to be provides to the callbacks configured on {@link ContextMenu#items}.
- *
- * This must be set before calling {@link ContextMenu#show}.
- *
- * @type {Object}
- */
- set context(context) {
- this._context = context;
- }
-
- /**
- * Gets the ````ContextMenu```` context.
- *
- * @type {Object}
- */
- get context() {
- return this._context;
- }
-
- /**
- * Shows this ````ContextMenu```` at the given page coordinates.
- *
- * Does nothing when {@link ContextMenu#enabled} is ````false````.
- *
- * Logs error to console and does nothing if {@link ContextMenu#context} has not been set.
- *
- * Fires a "shown" event when shown.
- *
- * @param {Number} pageX Page X-coordinate.
- * @param {Number} pageY Page Y-coordinate.
- */
- show(pageX, pageY) {
- if (!this._context) {
- console.error("ContextMenu cannot be shown without a context - set context first");
- return;
- }
- if (!this._enabled) {
- return;
- }
- if (this._shown) {
- return;
- }
- this._hideAllMenus();
- this._updateItemsTitles();
- this._updateItemsEnabledStatus();
- this._showMenu(this._rootMenu.id, pageX, pageY);
- this._updateSubMenuInfo();
- this._shown = true;
- this.fire("shown", {});
- }
-
- /**
- * Gets whether this ````ContextMenu```` is currently shown or not.
- *
- * @returns {Boolean} Whether this ````ContextMenu```` is shown.
- */
- get shown() {
- return this._shown;
- }
-
- /**
- * Hides this ````ContextMenu````.
- *
- * Fires a "hidden" event when hidden.
- */
- hide() {
- if (!this._enabled) {
- return;
- }
- if (!this._shown) {
- return;
- }
- this._hideAllMenus();
- this._shown = false;
- this.fire("hidden", {});
- }
-
- /**
- * Destroys this ````ContextMenu````.
- */
- destroy() {
- this._context = null;
- this._clear();
- if (this._id !== null) {
- idMap.removeItem(this._id);
- this._id = null;
- }
- }
-
- _clear() { // Destroys DOM elements, clears menu data
- for (let i = 0, len = this._menuList.length; i < len; i++) {
- const menu = this._menuList[i];
- const menuElement = menu.menuElement;
- menuElement.parentElement.removeChild(menuElement);
- }
- this._itemsCfg = [];
- this._rootMenu = null;
- this._menuList = [];
- this._menuMap = {};
- this._itemList = [];
- this._itemMap = {};
- }
-
- _parseItems(itemsCfg) { // Parses "items" config into menu data
-
- const visitItems = (itemsCfg) => {
-
- const menuId = this._getNextId();
- const menu = new Menu(menuId);
-
- for (let i = 0, len = itemsCfg.length; i < len; i++) {
-
- const itemsGroupCfg = itemsCfg[i];
-
- const group = new Group();
-
- menu.groups.push(group);
-
- for (let j = 0, lenj = itemsGroupCfg.length; j < lenj; j++) {
-
- const itemCfg = itemsGroupCfg[j];
- const subItemsCfg = itemCfg.items;
- const hasSubItems = (subItemsCfg && (subItemsCfg.length > 0));
- const itemId = this._getNextId();
-
- const getTitle = itemCfg.getTitle || (() => {
- return (itemCfg.title || "");
- });
-
- const doAction = itemCfg.doAction || itemCfg.callback || (() => {
- });
-
- const getEnabled = itemCfg.getEnabled || (() => {
- return true;
- });
-
- const getShown = itemCfg.getShown || (() => {
- return true;
- });
-
- const item = new Item(itemId, getTitle, doAction, getEnabled, getShown);
-
- item.parentMenu = menu;
-
- group.items.push(item);
-
- if (hasSubItems) {
- const subMenu = visitItems(subItemsCfg);
- item.subMenu = subMenu;
- subMenu.parentItem = item;
- }
-
- this._itemList.push(item);
- this._itemMap[item.id] = item;
- }
- }
-
- this._menuList.push(menu);
- this._menuMap[menu.id] = menu;
-
- return menu;
- };
-
- this._rootMenu = visitItems(itemsCfg);
- }
-
- _getNextId() { // Returns a unique ID
- return "ContextMenu_" + this._id + "_" + this._nextId++; // Start ID with alpha chars to make a valid DOM element selector
- }
-
- _createUI() { // Builds DOM elements for the entire menu tree
-
- const visitMenu = (menu) => {
-
- this._createMenuUI(menu);
-
- const groups = menu.groups;
- for (let i = 0, len = groups.length; i < len; i++) {
- const group = groups[i];
- const groupItems = group.items;
- for (let j = 0, lenj = groupItems.length; j < lenj; j++) {
- const item = groupItems[j];
- const subMenu = item.subMenu;
- if (subMenu) {
- visitMenu(subMenu);
- }
- }
- }
- };
-
- visitMenu(this._rootMenu);
- }
-
- _createMenuUI(menu) { // Builds DOM elements for a menu
-
- const groups = menu.groups;
- const html = [];
-
- html.push('<div class="xeokit-context-menu ' + menu.id + '" style="z-index:300000; position: absolute;">');
-
- html.push('<ul>');
-
- if (groups) {
-
- for (let i = 0, len = groups.length; i < len; i++) {
-
- const group = groups[i];
- const groupIdx = i;
- const groupLen = len;
- const groupItems = group.items;
-
- if (groupItems) {
-
- for (let j = 0, lenj = groupItems.length; j < lenj; j++) {
-
- const item = groupItems[j];
- const itemSubMenu = item.subMenu;
- const actionTitle = item.title || "";
-
- if (itemSubMenu) {
-
- html.push(
- '<li id="' + item.id + '" class="xeokit-context-menu-item xeokit-context-menu-submenu">' +
- actionTitle +
- '</li>');
- if (!((groupIdx === groupLen - 1) || (j < lenj - 1))) {
- html.push(
- '<li id="' + item.id + '" class="xeokit-context-menu-item-separator"></li>'
- );
- }
-
- } else {
-
- html.push(
- '<li id="' + item.id + '" class="xeokit-context-menu-item">' +
- actionTitle +
- '</li>');
- if (!((groupIdx === groupLen - 1) || (j < lenj - 1))) {
- html.push(
- '<li id="' + item.id + '" class="xeokit-context-menu-item-separator"></li>'
- );
- }
- }
- }
- }
- }
- }
-
- html.push('</ul>');
- html.push('</div>');
-
- const htmlString = html.join("");
-
- document.body.insertAdjacentHTML('beforeend', htmlString);
-
- const menuElement = document.querySelector("." + menu.id);
-
- menu.menuElement = menuElement;
-
- menuElement.style["border-radius"] = 4 + "px";
- menuElement.style.display = 'none';
- menuElement.style["z-index"] = 300000;
- menuElement.style.background = "white";
- menuElement.style.border = "1px solid black";
- menuElement.style["box-shadow"] = "0 4px 5px 0 gray";
- menuElement.oncontextmenu = (e) => {
- e.preventDefault();
- };
-
- // Bind event handlers
-
- const self = this;
-
- let lastSubMenu = null;
-
- if (groups) {
-
- for (let i = 0, len = groups.length; i < len; i++) {
-
- const group = groups[i];
- const groupItems = group.items;
-
- if (groupItems) {
-
- for (let j = 0, lenj = groupItems.length; j < lenj; j++) {
-
- const item = groupItems[j];
- const itemSubMenu = item.subMenu;
-
- item.itemElement = document.getElementById(item.id);
-
- if (!item.itemElement) {
- console.error("ContextMenu item element not found: " + item.id);
- continue;
- }
-
- item.itemElement.addEventListener("mouseenter", (event) => {
- event.preventDefault();
-
- const subMenu = item.subMenu;
- if (!subMenu) {
- if (lastSubMenu) {
- self._hideMenu(lastSubMenu.id);
- lastSubMenu = null;
- }
- return;
- }
- if (lastSubMenu && (lastSubMenu.id !== subMenu.id)) {
- self._hideMenu(lastSubMenu.id);
- lastSubMenu = null;
- }
-
- if (item.enabled === false) {
- return;
- }
-
- const itemElement = item.itemElement;
- const subMenuElement = subMenu.menuElement;
-
- const itemRect = itemElement.getBoundingClientRect();
- const menuRect = subMenuElement.getBoundingClientRect();
-
- const subMenuWidth = 200; // TODO
- const showOnLeft = ((itemRect.right + subMenuWidth) > window.innerWidth);
-
- if (showOnLeft) {
- self._showMenu(subMenu.id, itemRect.left - subMenuWidth, itemRect.top - 1);
- } else {
- self._showMenu(subMenu.id, itemRect.right - 5, itemRect.top - 1);
- }
-
- lastSubMenu = subMenu;
- });
-
- if (!itemSubMenu) {
-
- // Item without sub-menu
- // clicking item fires the item's action callback
-
- item.itemElement.addEventListener("click", (event) => {
- event.preventDefault();
- if (!self._context) {
- return;
- }
- if (item.enabled === false) {
- return;
- }
- if (item.doAction) {
- item.doAction(self._context);
- }
- if (this._hideOnAction) {
- self.hide();
- } else {
- self._updateItemsTitles();
- self._updateItemsEnabledStatus();
- }
- });
- item.itemElement.addEventListener("mouseup", (event) => {
- if (event.which !== 3) {
- return;
- }
- event.preventDefault();
- if (!self._context) {
- return;
- }
- if (item.enabled === false) {
- return;
- }
- if (item.doAction) {
- item.doAction(self._context);
- }
- if (this._hideOnAction) {
- self.hide();
- } else {
- self._updateItemsTitles();
- self._updateItemsEnabledStatus();
- }
- });
- item.itemElement.addEventListener("mouseenter", (event) => {
- event.preventDefault();
- if (item.enabled === false) {
- return;
- }
- if (item.doHover) {
- item.doHover(self._context);
- }
- });
-
- }
- }
- }
- }
- }
- }
-
- _updateItemsTitles() { // Dynamically updates the title of each Item to the result of Item#getTitle()
- if (!this._context) {
- return;
- }
- for (let i = 0, len = this._itemList.length; i < len; i++) {
- const item = this._itemList[i];
- const itemElement = item.itemElement;
- if (!itemElement) {
- continue;
- }
- const getShown = item.getShown;
- if (!getShown || !getShown(this._context)) {
- continue;
- }
- const title = item.getTitle(this._context);
- if (item.subMenu) {
- itemElement.innerText = title;
- } else {
- itemElement.innerText = title;
- }
- }
- }
-
- _updateItemsEnabledStatus() { // Enables or disables each Item, depending on the result of Item#getEnabled()
- if (!this._context) {
- return;
- }
- for (let i = 0, len = this._itemList.length; i < len; i++) {
- const item = this._itemList[i];
- const itemElement = item.itemElement;
- if (!itemElement) {
- continue;
- }
- const getEnabled = item.getEnabled;
- if (!getEnabled) {
- continue;
- }
- const getShown = item.getShown;
- if (!getShown) {
- continue;
- }
- const shown = getShown(this._context);
- item.shown = shown;
- if (!shown) {
- itemElement.style.display = "none";
- continue;
- } else {
- itemElement.style.display = "";
-
- }
- const enabled = getEnabled(this._context);
- item.enabled = enabled;
- if (!enabled) {
- itemElement.classList.add("disabled");
- } else {
- itemElement.classList.remove("disabled");
- }
- }
- }
-
- _updateSubMenuInfo() {
- if (!this._context) return;
- let itemElement, itemRect, subMenuElement, initialStyles, showOnLeft, subMenuWidth;
- this._itemList.forEach((item) => {
- if (item.subMenu) {
- itemElement = item.itemElement;
- itemRect = itemElement.getBoundingClientRect();
- subMenuElement = item.subMenu.menuElement;
- initialStyles = {
- visibility: subMenuElement.style.visibility,
- display: subMenuElement.style.display,
- }
- subMenuElement.style.display = "block";
- subMenuElement.style.visibility = "hidden";
- subMenuWidth = item.subMenu.menuElement.getBoundingClientRect().width;
- subMenuElement.style.visibility = initialStyles.visibility;
- subMenuElement.style.display = initialStyles.display;
- showOnLeft = ((itemRect.right + subMenuWidth) > window.innerWidth);
- itemElement.setAttribute("data-submenuposition", showOnLeft ? "left" : "right");
- }
- })
- }
-
- _showMenu(menuId, pageX, pageY) { // Shows the given menu, at the specified page coordinates
- const menu = this._menuMap[menuId];
- if (!menu) {
- console.error("Menu not found: " + menuId);
- return;
- }
- if (menu.shown) {
- return;
- }
- const menuElement = menu.menuElement;
- if (menuElement) {
- this._showMenuElement(menuElement, pageX, pageY);
- menu.shown = true;
- }
- }
-
- _hideMenu(menuId) { // Hides the given menu
- const menu = this._menuMap[menuId];
- if (!menu) {
- console.error("Menu not found: " + menuId);
- return;
- }
- if (!menu.shown) {
- return;
- }
- const menuElement = menu.menuElement;
- if (menuElement) {
- this._hideMenuElement(menuElement);
- menu.shown = false;
- }
- }
-
- _hideAllMenus() {
- for (let i = 0, len = this._menuList.length; i < len; i++) {
- const menu = this._menuList[i];
- this._hideMenu(menu.id);
- }
- }
-
- _showMenuElement(menuElement, pageX, pageY) { // Shows the given menu element, at the specified page coordinates
- menuElement.style.display = 'block';
- const menuHeight = menuElement.offsetHeight;
- const menuWidth = menuElement.offsetWidth;
- if ((pageY + menuHeight) > window.innerHeight) {
- pageY = window.innerHeight - menuHeight;
- }
- if ((pageX + menuWidth) > window.innerWidth) {
- pageX = window.innerWidth - menuWidth;
- }
- menuElement.style.left = pageX + 'px';
- menuElement.style.top = pageY + 'px';
- }
-
- _hideMenuElement(menuElement) {
- menuElement.style.display = 'none';
- }
- }
-
- export { ContextMenu };