{"version":3,"file":"xkt-DHSc23ad.js","sources":["../../node_modules/@xeokit/xeokit-sdk/dist/xeokit-sdk.es.js"],"sourcesContent":["/** @private */\nclass Map$1 {\n\n constructor(items, baseId) {\n this.items = items || [];\n this._lastUniqueId = (baseId || 0) + 1;\n }\n\n /**\n * Usage:\n *\n * id = myMap.addItem(\"foo\") // ID internally generated\n * id = myMap.addItem(\"foo\", \"bar\") // ID is \"foo\"\n */\n addItem() {\n let item;\n if (arguments.length === 2) {\n const id = arguments[0];\n item = arguments[1];\n if (this.items[id]) { // Won't happen if given ID is string\n throw \"ID clash: '\" + id + \"'\";\n }\n this.items[id] = item;\n return id;\n\n } else {\n item = arguments[0] || {};\n while (true) {\n const findId = this._lastUniqueId++;\n if (!this.items[findId]) {\n this.items[findId] = item;\n return findId;\n }\n }\n }\n }\n\n removeItem(id) {\n const item = this.items[id];\n delete this.items[id];\n return item;\n }\n}\n\nconst idMap = new Map$1();\n\n/**\n * Internal data class that represents the state of a menu or a submenu.\n * @private\n */\nclass Menu {\n constructor(id) {\n this.id = id;\n this.parentItem = null; // Set to an Item when this Menu is a submenu\n this.groups = [];\n this.menuElement = null;\n this.shown = false;\n this.mouseOver = 0;\n }\n}\n\n/**\n * Internal data class that represents a group of Items in a Menu.\n * @private\n */\nclass Group {\n constructor() {\n this.items = [];\n }\n}\n\n/**\n * Internal data class that represents the state of a menu item.\n * @private\n */\nclass Item {\n constructor(id, getTitle, doAction, getEnabled, getShown) {\n this.id = id;\n this.getTitle = getTitle;\n this.doAction = doAction;\n this.getEnabled = getEnabled;\n this.getShown = getShown;\n this.itemElement = null;\n this.subMenu = null;\n this.enabled = true;\n }\n}\n\n/**\n * @desc A customizable HTML context menu.\n *\n * [](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)\n *\n * * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_TreeViewPlugin_Custom)]\n *\n * ## Overview\n *\n * * A pure JavaScript, lightweight context menu\n * * Dynamically configure menu items\n * * Dynamically enable or disable items\n * * Dynamically show or hide items\n * * Supports cascading sub-menus\n * * Configure custom style with CSS (see examples above)\n *\n * ## Usage\n *\n * In the example below we'll create a ````ContextMenu```` that pops up whenever we right-click on an {@link Entity} within\n * our {@link Scene}.\n *\n * First, we'll create the ````ContextMenu````, configuring it with a list of menu items.\n *\n * Each item has:\n *\n * * a ````title```` for the item,\n * * a ````doAction()```` callback to fire when the item's title is clicked,\n * * an optional ````getShown()```` callback that indicates if the item should shown in the menu or not, and\n * * an optional ````getEnabled()```` callback that indicates if the item should be shown enabled in the menu or not.\n *\n *
\n *\n * The ````getShown()```` and ````getEnabled()```` callbacks are invoked whenever the menu is shown.\n *\n * When an item's ````getShown()```` callback\n * returns ````true````, then the item is shown. When it returns ````false````, then the item is hidden. An item without\n * a ````getShown()```` callback is always shown.\n *\n * When an item's ````getEnabled()```` callback returns ````true````, then the item is enabled and clickable (as long as it's also shown). When it\n * returns ````false````, then the item is disabled and cannot be clicked. An item without a ````getEnabled()````\n * callback is always enabled and clickable.\n *\n * Note how the ````doAction()````, ````getShown()```` and ````getEnabled()```` callbacks accept a ````context````\n * 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,\n * we'll use the context object to provide the callbacks with the Entity that we right-clicked.\n *\n * We'll also initially enable the ````ContextMenu````.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_Canvas_Custom)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n *\n * enabled: true,\n *\n * items: [\n * [\n * {\n * title: \"Hide Object\",\n * getEnabled: (context) => {\n * return context.entity.visible; // Can't hide entity if already hidden\n * },\n * doAction: function (context) {\n * context.entity.visible = false;\n * }\n * }\n * ],\n * [\n * {\n * title: \"Select Object\",\n * getEnabled: (context) => {\n * return (!context.entity.selected); // Can't select an entity that's already selected\n * },\n * doAction: function (context) {\n * context.entity.selected = true;\n * }\n * }\n * ],\n * [\n * {\n * title: \"X-Ray Object\",\n * getEnabled: (context) => {\n * return (!context.entity.xrayed); // Can't X-ray an entity that's already X-rayed\n * },\n * doAction: (context) => {\n * context.entity.xrayed = true;\n * }\n * }\n * ]\n * ]\n * });\n * ````\n *\n * Next, we'll make the ````ContextMenu```` appear whenever we right-click on an Entity. Whenever we right-click\n * on the canvas, we'll attempt to pick the Entity at those mouse coordinates. If we succeed, we'll feed the\n * Entity into ````ContextMenu```` via the context object, then show the ````ContextMenu````.\n *\n * From there, each ````ContextMenu```` item's ````getEnabled()```` callback will be invoked (if provided), to determine if the item should\n * be enabled. If we click an item, its ````doAction()```` callback will be invoked with our context object.\n *\n * Remember that we must set the context on our ````ContextMenu```` before we show it, otherwise it will log an error to the console,\n * and ignore our attempt to show it.\n *\n * ````javascript*\n * viewer.scene.canvas.canvas.oncontextmenu = (e) => { // Right-clicked on the canvas\n *\n * if (!objectContextMenu.enabled) {\n * return;\n * }\n *\n * var hit = viewer.scene.pick({ // Try to pick an Entity at the coordinates\n * canvasPos: [e.pageX, e.pageY]\n * });\n *\n * if (hit) { // Picked an Entity\n *\n * objectContextMenu.context = { // Feed entity to ContextMenu\n * entity: hit.entity\n * };\n *\n * objectContextMenu.show(e.pageX, e.pageY); // Show the ContextMenu\n * }\n *\n * e.preventDefault();\n * });\n * ````\n *\n * Note how we only show the ````ContextMenu```` if it's enabled. We can use that mechanism to switch between multiple\n * ````ContextMenu```` instances depending on what we clicked.\n *\n * ## Dynamic Item Titles\n *\n * To make an item dynamically regenerate its title text whenever we show the ````ContextMenu````, provide its title with a\n * ````getTitle()```` callback. The callback will fire each time you show ````ContextMenu````, which will dynamically\n * set the item title text.\n *\n * In the example below, we'll create a simple ````ContextMenu```` that allows us to toggle the selection of an object\n * via its first item, which changes text depending on whether we are selecting or deselecting the object.\n *\n * [[Run an example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_dynamicItemTitles)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n *\n * enabled: true,\n *\n * items: [\n * [\n * {\n * getTitle: (context) => {\n * return (!context.entity.selected) ? \"Select\" : \"Undo Select\";\n * },\n * doAction: function (context) {\n * context.entity.selected = !context.entity.selected;\n * }\n * },\n * {\n * title: \"Clear Selection\",\n * getEnabled: function (context) {\n * return (context.viewer.scene.numSelectedObjects > 0);\n * },\n * doAction: function (context) {\n * context.viewer.scene.setObjectsSelected(context.viewer.scene.selectedObjectIds, false);\n * }\n * }\n * ]\n * ]\n * });\n * ````\n *\n * ## Sub-menus\n *\n * Each menu item can optionally have a sub-menu, which will appear when we hover over the item.\n *\n * In the example below, we'll create a much simpler ````ContextMenu```` that has only one item, called \"Effects\", which\n * will open a cascading sub-menu whenever we hover over that item.\n *\n * Note that our \"Effects\" item has no ````doAction```` callback, because an item with a sub-menu performs no\n * action of its own.\n *\n * [[Run this example](https://xeokit.github.io/xeokit-sdk/examples/index.html#ContextMenu_subMenus)]\n *\n * ````javascript\n * const canvasContextMenu = new ContextMenu({\n * items: [ // Top level items\n * [\n * {\n * getTitle: (context) => {\n * return \"Effects\";\n * },\n *\n * items: [ // Sub-menu\n * [\n * {\n * getTitle: (context) => {\n * return (!context.entity.visible) ? \"Show\" : \"Hide\";\n * },\n * doAction: function (context) {\n * context.entity.visible = !context.entity.visible;\n * }\n * },\n * {\n * getTitle: (context) => {\n * return (!context.entity.selected) ? \"Select\" : \"Undo Select\";\n * },\n * doAction: function (context) {\n * context.entity.selected = !context.entity.selected;\n * }\n * },\n * {\n * getTitle: (context) => {\n * return (!context.entity.highlighted) ? \"Highlight\" : \"Undo Highlight\";\n * },\n * doAction: function (context) {\n * context.entity.highlighted = !context.entity.highlighted;\n * }\n * }\n * ]\n * ]\n * }\n * ]\n * ]\n * });\n * ````\n */\nclass ContextMenu {\n\n /**\n * Creates a ````ContextMenu````.\n *\n * The ````ContextMenu```` will be initially hidden.\n *\n * @param {Object} [cfg] ````ContextMenu```` configuration.\n * @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.\n * @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}.\n * @param {Boolean} [cfg.enabled=true] Whether this ````ContextMenu```` is initially enabled. {@link ContextMenu#show} does nothing while this is ````false````.\n * @param {Boolean} [cfg.hideOnMouseDown=true] Whether this ````ContextMenu```` automatically hides whenever we mouse-down or tap anywhere in the page.\n * @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.\n */\n constructor(cfg = {}) {\n\n this._id = idMap.addItem();\n this._context = null;\n this._enabled = false; // True when the ContextMenu is enabled\n this._itemsCfg = []; // Items as given as configs\n this._rootMenu = null; // The root Menu in the tree\n this._menuList = []; // List of Menus\n this._menuMap = {}; // Menus mapped to their IDs\n this._itemList = []; // List of Items\n this._itemMap = {}; // Items mapped to their IDs\n this._shown = false; // True when the ContextMenu is visible\n this._nextId = 0;\n\n /**\n * Subscriptions to events fired at this ContextMenu.\n * @private\n */\n this._eventSubs = {};\n\n if (cfg.hideOnMouseDown !== false) {\n document.addEventListener(\"mousedown\", (event) => {\n if (!event.target.classList.contains(\"xeokit-context-menu-item\")) {\n this.hide();\n }\n });\n document.addEventListener(\"touchstart\", this._canvasTouchStartHandler = (event) => {\n if (!event.target.classList.contains(\"xeokit-context-menu-item\")) {\n this.hide();\n }\n });\n }\n\n if (cfg.items) {\n this.items = cfg.items;\n }\n\n this._hideOnAction = (cfg.hideOnAction !== false);\n\n this.context = cfg.context;\n this.enabled = cfg.enabled !== false;\n this.hide();\n }\n\n\n /**\n Subscribes to an event fired at this ````ContextMenu````.\n\n @param {String} event The event\n @param {Function} callback Callback fired on the event\n */\n on(event, callback) {\n let subs = this._eventSubs[event];\n if (!subs) {\n subs = [];\n this._eventSubs[event] = subs;\n }\n subs.push(callback);\n }\n\n /**\n Fires an event at this ````ContextMenu````.\n\n @param {String} event The event type name\n @param {Object} value The event parameters\n */\n fire(event, value) {\n const subs = this._eventSubs[event];\n if (subs) {\n for (let i = 0, len = subs.length; i < len; i++) {\n subs[i](value);\n }\n }\n }\n\n /**\n * Sets the ````ContextMenu```` items.\n *\n * These can be updated dynamically at any time.\n *\n * See class documentation for an example.\n *\n * @type {Object[]}\n */\n set items(itemsCfg) {\n this._clear();\n this._itemsCfg = itemsCfg || [];\n this._parseItems(itemsCfg);\n this._createUI();\n }\n\n /**\n * Gets the ````ContextMenu```` items.\n *\n * @type {Object[]}\n */\n get items() {\n return this._itemsCfg;\n }\n\n /**\n * Sets whether this ````ContextMenu```` is enabled.\n *\n * Hides the menu when disabling.\n *\n * @type {Boolean}\n */\n set enabled(enabled) {\n enabled = (!!enabled);\n if (enabled === this._enabled) {\n return;\n }\n this._enabled = enabled;\n if (!this._enabled) {\n this.hide();\n }\n }\n\n /**\n * Gets whether this ````ContextMenu```` is enabled.\n *\n * {@link ContextMenu#show} does nothing while this is ````false````.\n *\n * @type {Boolean}\n */\n get enabled() {\n return this._enabled;\n }\n\n /**\n * Sets the ````ContextMenu```` context.\n *\n * The context can be any object that you need to be provides to the callbacks configured on {@link ContextMenu#items}.\n *\n * This must be set before calling {@link ContextMenu#show}.\n *\n * @type {Object}\n */\n set context(context) {\n this._context = context;\n }\n\n /**\n * Gets the ````ContextMenu```` context.\n *\n * @type {Object}\n */\n get context() {\n return this._context;\n }\n\n /**\n * Shows this ````ContextMenu```` at the given page coordinates.\n *\n * Does nothing when {@link ContextMenu#enabled} is ````false````.\n *\n * Logs error to console and does nothing if {@link ContextMenu#context} has not been set.\n *\n * Fires a \"shown\" event when shown.\n *\n * @param {Number} pageX Page X-coordinate.\n * @param {Number} pageY Page Y-coordinate.\n */\n show(pageX, pageY) {\n if (!this._context) {\n console.error(\"ContextMenu cannot be shown without a context - set context first\");\n return;\n }\n if (!this._enabled) {\n return;\n }\n if (this._shown) {\n return;\n }\n this._hideAllMenus();\n this._updateItemsTitles();\n this._updateItemsEnabledStatus();\n this._showMenu(this._rootMenu.id, pageX, pageY);\n this._shown = true;\n this.fire(\"shown\", {});\n }\n\n /**\n * Gets whether this ````ContextMenu```` is currently shown or not.\n *\n * @returns {Boolean} Whether this ````ContextMenu```` is shown.\n */\n get shown() {\n return this._shown;\n }\n\n /**\n * Hides this ````ContextMenu````.\n *\n * Fires a \"hidden\" event when hidden.\n */\n hide() {\n if (!this._enabled) {\n return;\n }\n if (!this._shown) {\n return;\n }\n this._hideAllMenus();\n this._shown = false;\n this.fire(\"hidden\", {});\n }\n\n /**\n * Destroys this ````ContextMenu````.\n */\n destroy() {\n this._context = null;\n this._clear();\n if (this._id !== null) {\n idMap.removeItem(this._id);\n this._id = null;\n }\n }\n\n _clear() { // Destroys DOM elements, clears menu data\n for (let i = 0, len = this._menuList.length; i < len; i++) {\n const menu = this._menuList[i];\n const menuElement = menu.menuElement;\n menuElement.parentElement.removeChild(menuElement);\n }\n this._itemsCfg = [];\n this._rootMenu = null;\n this._menuList = [];\n this._menuMap = {};\n this._itemList = [];\n this._itemMap = {};\n }\n\n _parseItems(itemsCfg) { // Parses \"items\" config into menu data\n\n const visitItems = (itemsCfg) => {\n\n const menuId = this._getNextId();\n const menu = new Menu(menuId);\n\n for (let i = 0, len = itemsCfg.length; i < len; i++) {\n\n const itemsGroupCfg = itemsCfg[i];\n\n const group = new Group();\n\n menu.groups.push(group);\n\n for (let j = 0, lenj = itemsGroupCfg.length; j < lenj; j++) {\n\n const itemCfg = itemsGroupCfg[j];\n const subItemsCfg = itemCfg.items;\n const hasSubItems = (subItemsCfg && (subItemsCfg.length > 0));\n const itemId = this._getNextId();\n\n const getTitle = itemCfg.getTitle || (() => {\n return (itemCfg.title || \"\");\n });\n\n const doAction = itemCfg.doAction || itemCfg.callback || (() => {\n });\n\n const getEnabled = itemCfg.getEnabled || (() => {\n return true;\n });\n\n const getShown = itemCfg.getShown || (() => {\n return true;\n });\n\n const item = new Item(itemId, getTitle, doAction, getEnabled, getShown);\n\n item.parentMenu = menu;\n\n group.items.push(item);\n\n if (hasSubItems) {\n const subMenu = visitItems(subItemsCfg);\n item.subMenu = subMenu;\n subMenu.parentItem = item;\n }\n\n this._itemList.push(item);\n this._itemMap[item.id] = item;\n }\n }\n\n this._menuList.push(menu);\n this._menuMap[menu.id] = menu;\n\n return menu;\n };\n\n this._rootMenu = visitItems(itemsCfg);\n }\n\n _getNextId() { // Returns a unique ID\n return \"ContextMenu_\" + this._id + \"_\" + this._nextId++; // Start ID with alpha chars to make a valid DOM element selector\n }\n\n _createUI() { // Builds DOM elements for the entire menu tree\n\n const visitMenu = (menu) => {\n\n this._createMenuUI(menu);\n\n const groups = menu.groups;\n for (let i = 0, len = groups.length; i < len; i++) {\n const group = groups[i];\n const groupItems = group.items;\n for (let j = 0, lenj = groupItems.length; j < lenj; j++) {\n const item = groupItems[j];\n const subMenu = item.subMenu;\n if (subMenu) {\n visitMenu(subMenu);\n }\n }\n }\n };\n\n visitMenu(this._rootMenu);\n }\n\n _createMenuUI(menu) { // Builds DOM elements for a menu\n\n const groups = menu.groups;\n const html = [];\n\n html.push('