mirror of
https://github.com/react/react-native-devtools-frontend.git
synced 2026-10-09 05:08:28 +08:00
This adds all missing imports for `ls` to all files. After this change, all usages of `ls` will now properly be resolved to a LocalizedString rather than a normal string. R=jacktfranklin@chromium.org No-Presubmit: True Change-Id: Icf6d8c3b8bea1f9e66f4b6b58e4ae4c7f99e6f9c Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2629554 Commit-Queue: Tim van der Lippe <tvanderlippe@chromium.org> Auto-Submit: Tim van der Lippe <tvanderlippe@chromium.org> Reviewed-by: Jack Franklin <jacktfranklin@chromium.org>
559 lines
20 KiB
JavaScript
559 lines
20 KiB
JavaScript
/*
|
|
* Copyright (C) 2011 Google Inc. All rights reserved.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions are
|
|
* met:
|
|
*
|
|
* * Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* * Redistributions in binary form must reproduce the above
|
|
* copyright notice, this list of conditions and the following disclaimer
|
|
* in the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* * Neither the name of Google Inc. nor the names of its
|
|
* contributors may be used to endorse or promote products derived from
|
|
* this software without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
import * as Host from '../host/host.js';
|
|
import {ls} from '../platform/platform.js';
|
|
import * as ThemeSupport from '../theme_support/theme_support.js';
|
|
|
|
import * as ARIAUtils from './ARIAUtils.js';
|
|
import {AnchorBehavior, GlassPane, MarginBehavior, PointerEventsBehavior, SizeBehavior,} from './GlassPane.js'; // eslint-disable-line no-unused-vars
|
|
import {Icon} from './Icon.js';
|
|
import {createTextChild, ElementFocusRestorer} from './UIUtils.js';
|
|
|
|
export class SoftContextMenu {
|
|
/**
|
|
* @param {!Array.<!SoftContextMenuDescriptor>} items
|
|
* @param {function(number):void} itemSelectedCallback
|
|
* @param {!SoftContextMenu=} parentMenu
|
|
*/
|
|
constructor(items, itemSelectedCallback, parentMenu) {
|
|
this._items = items;
|
|
this._itemSelectedCallback = itemSelectedCallback;
|
|
this._parentMenu = parentMenu;
|
|
/** @type {?HTMLElement} */
|
|
this._highlightedMenuItemElement = null;
|
|
|
|
/**
|
|
* @type {!WeakMap<!HTMLElement, !ElementMenuDetails>}
|
|
*/
|
|
this.detailsForElementMap = new WeakMap();
|
|
}
|
|
|
|
/**
|
|
* @param {!Document} document
|
|
* @param {!AnchorBox} anchorBox
|
|
*/
|
|
show(document, anchorBox) {
|
|
if (!this._items.length) {
|
|
return;
|
|
}
|
|
|
|
this._document = document;
|
|
|
|
this._glassPane = new GlassPane();
|
|
this._glassPane.setPointerEventsBehavior(
|
|
this._parentMenu ? PointerEventsBehavior.PierceGlassPane : PointerEventsBehavior.BlockedByGlassPane);
|
|
this._glassPane.registerRequiredCSS('ui/softContextMenu.css', {enableLegacyPatching: true});
|
|
this._glassPane.setContentAnchorBox(anchorBox);
|
|
this._glassPane.setSizeBehavior(SizeBehavior.MeasureContent);
|
|
this._glassPane.setMarginBehavior(MarginBehavior.NoMargin);
|
|
this._glassPane.setAnchorBehavior(this._parentMenu ? AnchorBehavior.PreferRight : AnchorBehavior.PreferBottom);
|
|
|
|
this._contextMenuElement = this._glassPane.contentElement.createChild('div', 'soft-context-menu');
|
|
this._contextMenuElement.tabIndex = -1;
|
|
ARIAUtils.markAsMenu(this._contextMenuElement);
|
|
this._contextMenuElement.addEventListener('mouseup', e => e.consume(), false);
|
|
this._contextMenuElement.addEventListener('keydown', this._menuKeyDown.bind(this), false);
|
|
|
|
for (let i = 0; i < this._items.length; ++i) {
|
|
this._contextMenuElement.appendChild(this._createMenuItem(this._items[i]));
|
|
}
|
|
|
|
this._glassPane.show(document);
|
|
this._focusRestorer = new ElementFocusRestorer(this._contextMenuElement);
|
|
|
|
if (!this._parentMenu) {
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
this._hideOnUserGesture = event => {
|
|
// If a user clicks on any submenu, prevent the menu system from closing.
|
|
let subMenu = this._subMenu;
|
|
while (subMenu) {
|
|
if (subMenu._contextMenuElement === event.composedPath()[0]) {
|
|
return;
|
|
}
|
|
subMenu = subMenu._subMenu;
|
|
}
|
|
|
|
this.discard();
|
|
event.consume(true);
|
|
};
|
|
this._document.body.addEventListener('mousedown', this._hideOnUserGesture, false);
|
|
if (this._document.defaultView) {
|
|
this._document.defaultView.addEventListener('resize', this._hideOnUserGesture, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
discard() {
|
|
if (this._subMenu) {
|
|
this._subMenu.discard();
|
|
}
|
|
if (this._focusRestorer) {
|
|
this._focusRestorer.restore();
|
|
}
|
|
if (this._glassPane) {
|
|
this._glassPane.hide();
|
|
delete this._glassPane;
|
|
if (this._hideOnUserGesture) {
|
|
if (this._document) {
|
|
this._document.body.removeEventListener('mousedown', this._hideOnUserGesture, false);
|
|
if (this._document.defaultView) {
|
|
this._document.defaultView.removeEventListener('resize', this._hideOnUserGesture, false);
|
|
}
|
|
}
|
|
delete this._hideOnUserGesture;
|
|
}
|
|
}
|
|
if (this._parentMenu) {
|
|
delete this._parentMenu._subMenu;
|
|
if (this._parentMenu._activeSubMenuElement) {
|
|
ARIAUtils.setExpanded(this._parentMenu._activeSubMenuElement, false);
|
|
delete this._parentMenu._activeSubMenuElement;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {!SoftContextMenuDescriptor} item
|
|
*/
|
|
_createMenuItem(item) {
|
|
if (item.type === 'separator') {
|
|
return this._createSeparator();
|
|
}
|
|
|
|
if (item.type === 'subMenu') {
|
|
return this._createSubMenu(item);
|
|
}
|
|
|
|
const menuItemElement = /** @type {!HTMLElement} */ (document.createElement('div'));
|
|
menuItemElement.classList.add('soft-context-menu-item');
|
|
menuItemElement.tabIndex = -1;
|
|
ARIAUtils.markAsMenuItem(menuItemElement);
|
|
const checkMarkElement = Icon.create('smallicon-checkmark', 'checkmark');
|
|
menuItemElement.appendChild(checkMarkElement);
|
|
if (!item.checked) {
|
|
checkMarkElement.style.opacity = '0';
|
|
}
|
|
/** @type {!ElementMenuDetails} */
|
|
const detailsForElement = {
|
|
actionId: undefined,
|
|
isSeparator: undefined,
|
|
customElement: undefined,
|
|
subItems: undefined,
|
|
subMenuTimer: undefined,
|
|
};
|
|
|
|
if (item.element) {
|
|
const wrapper = menuItemElement.createChild('div', 'soft-context-menu-custom-item');
|
|
wrapper.appendChild(item.element);
|
|
detailsForElement.customElement = /** @type {!HTMLElement} */ (item.element);
|
|
this.detailsForElementMap.set(menuItemElement, detailsForElement);
|
|
return menuItemElement;
|
|
}
|
|
|
|
if (!item.enabled) {
|
|
menuItemElement.classList.add('soft-context-menu-disabled');
|
|
}
|
|
createTextChild(menuItemElement, item.label || '');
|
|
menuItemElement.createChild('span', 'soft-context-menu-shortcut').textContent = item.shortcut || '';
|
|
|
|
menuItemElement.addEventListener('mousedown', this._menuItemMouseDown.bind(this), false);
|
|
menuItemElement.addEventListener('mouseup', this._menuItemMouseUp.bind(this), false);
|
|
|
|
// Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation.
|
|
menuItemElement.addEventListener('mouseover', this._menuItemMouseOver.bind(this), false);
|
|
menuItemElement.addEventListener(
|
|
'mouseleave', /** @type {!EventListener} */ (this._menuItemMouseLeave.bind(this)), false);
|
|
|
|
detailsForElement.actionId = item.id;
|
|
|
|
let accessibleName = item.label || '';
|
|
|
|
if (item.type === 'checkbox') {
|
|
const checkedState = item.checked ? ls`checked` : ls`unchecked`;
|
|
if (item.shortcut) {
|
|
accessibleName = ls`${item.label}, ${item.shortcut}, ${checkedState}`;
|
|
} else {
|
|
accessibleName = ls`${item.label}, ${checkedState}`;
|
|
}
|
|
} else if (item.shortcut) {
|
|
accessibleName = ls`${item.label}, ${item.shortcut}`;
|
|
}
|
|
ARIAUtils.setAccessibleName(menuItemElement, accessibleName);
|
|
|
|
this.detailsForElementMap.set(menuItemElement, detailsForElement);
|
|
return menuItemElement;
|
|
}
|
|
|
|
/**
|
|
* @param {!SoftContextMenuDescriptor} item
|
|
*/
|
|
_createSubMenu(item) {
|
|
const menuItemElement = /** @type {!HTMLElement} */ (document.createElement('div'));
|
|
menuItemElement.classList.add('soft-context-menu-item');
|
|
menuItemElement.tabIndex = -1;
|
|
ARIAUtils.markAsMenuItemSubMenu(menuItemElement);
|
|
this.detailsForElementMap.set(menuItemElement, {
|
|
subItems: item.subItems,
|
|
actionId: undefined,
|
|
isSeparator: undefined,
|
|
customElement: undefined,
|
|
subMenuTimer: undefined,
|
|
});
|
|
|
|
// Occupy the same space on the left in all items.
|
|
const checkMarkElement = Icon.create('smallicon-checkmark', 'soft-context-menu-item-checkmark');
|
|
checkMarkElement.classList.add('checkmark');
|
|
menuItemElement.appendChild(checkMarkElement);
|
|
checkMarkElement.style.opacity = '0';
|
|
|
|
createTextChild(menuItemElement, item.label || '');
|
|
ARIAUtils.setExpanded(menuItemElement, false);
|
|
|
|
// TODO: Consider removing this branch and use the same icon on all platforms.
|
|
if (Host.Platform.isMac() && !ThemeSupport.ThemeSupport.instance().hasTheme()) {
|
|
const subMenuArrowElement = menuItemElement.createChild('span', 'soft-context-menu-item-submenu-arrow');
|
|
ARIAUtils.markAsHidden(subMenuArrowElement);
|
|
subMenuArrowElement.textContent = '\u25B6'; // BLACK RIGHT-POINTING TRIANGLE
|
|
} else {
|
|
const subMenuArrowElement = Icon.create('smallicon-triangle-right', 'soft-context-menu-item-submenu-arrow');
|
|
menuItemElement.appendChild(subMenuArrowElement);
|
|
}
|
|
|
|
menuItemElement.addEventListener('mousedown', this._menuItemMouseDown.bind(this), false);
|
|
menuItemElement.addEventListener('mouseup', this._menuItemMouseUp.bind(this), false);
|
|
|
|
// Manually manage hover highlight since :hover does not work in case of click-and-hold menu invocation.
|
|
menuItemElement.addEventListener('mouseover', this._menuItemMouseOver.bind(this), false);
|
|
menuItemElement.addEventListener(
|
|
'mouseleave', /** @type {!EventListener} */ (this._menuItemMouseLeave.bind(this)), false);
|
|
|
|
return menuItemElement;
|
|
}
|
|
|
|
_createSeparator() {
|
|
const separatorElement = /** @type {!HTMLElement} */ (document.createElement('div'));
|
|
separatorElement.classList.add('soft-context-menu-separator');
|
|
this.detailsForElementMap.set(separatorElement, {
|
|
subItems: undefined,
|
|
actionId: undefined,
|
|
isSeparator: true,
|
|
customElement: undefined,
|
|
subMenuTimer: undefined,
|
|
});
|
|
separatorElement.createChild('div', 'separator-line');
|
|
return separatorElement;
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_menuItemMouseDown(event) {
|
|
// Do not let separator's mouse down hit menu's handler - we need to receive mouse up!
|
|
event.consume(true);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_menuItemMouseUp(event) {
|
|
this._triggerAction(/** @type {!HTMLElement} */ (event.target), event);
|
|
event.consume();
|
|
}
|
|
|
|
/**
|
|
* @return {!SoftContextMenu}
|
|
*/
|
|
_root() {
|
|
let root = /** @type {!SoftContextMenu} */ (this);
|
|
while (root._parentMenu) {
|
|
root = root._parentMenu;
|
|
}
|
|
return root;
|
|
}
|
|
|
|
/**
|
|
* @param {!HTMLElement} menuItemElement
|
|
* @param {!Event} event
|
|
*/
|
|
_triggerAction(menuItemElement, event) {
|
|
const detailsForElement = this.detailsForElementMap.get(menuItemElement);
|
|
if (detailsForElement) {
|
|
if (!detailsForElement.subItems) {
|
|
this._root().discard();
|
|
event.consume(true);
|
|
if (typeof detailsForElement.actionId !== 'undefined') {
|
|
this._itemSelectedCallback(detailsForElement.actionId);
|
|
delete detailsForElement.actionId;
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
this._showSubMenu(menuItemElement);
|
|
event.consume();
|
|
}
|
|
|
|
/**
|
|
* @param {!HTMLElement} menuItemElement
|
|
*/
|
|
_showSubMenu(menuItemElement) {
|
|
const detailsForElement = this.detailsForElementMap.get(menuItemElement);
|
|
if (!detailsForElement) {
|
|
return;
|
|
}
|
|
if (detailsForElement.subMenuTimer) {
|
|
window.clearTimeout(detailsForElement.subMenuTimer);
|
|
delete detailsForElement.subMenuTimer;
|
|
}
|
|
if (this._subMenu || !this._document) {
|
|
return;
|
|
}
|
|
|
|
this._activeSubMenuElement = menuItemElement;
|
|
ARIAUtils.setExpanded(menuItemElement, true);
|
|
if (!detailsForElement.subItems) {
|
|
return;
|
|
}
|
|
this._subMenu = new SoftContextMenu(detailsForElement.subItems, this._itemSelectedCallback, this);
|
|
const anchorBox = menuItemElement.boxInWindow();
|
|
// Adjust for padding.
|
|
anchorBox.y -= 5;
|
|
anchorBox.x += 3;
|
|
anchorBox.width -= 6;
|
|
anchorBox.height += 10;
|
|
this._subMenu.show(this._document, anchorBox);
|
|
}
|
|
|
|
/**
|
|
* @param {!Event} event
|
|
*/
|
|
_menuItemMouseOver(event) {
|
|
this._highlightMenuItem(/** @type {!HTMLElement} */ (event.target), true);
|
|
}
|
|
|
|
/**
|
|
* @param {!MouseEvent} event
|
|
*/
|
|
_menuItemMouseLeave(event) {
|
|
if (!this._subMenu || !event.relatedTarget) {
|
|
this._highlightMenuItem(null, true);
|
|
return;
|
|
}
|
|
|
|
const relatedTarget = event.relatedTarget;
|
|
if (relatedTarget === this._contextMenuElement) {
|
|
this._highlightMenuItem(null, true);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* @param {?HTMLElement} menuItemElement
|
|
* @param {boolean} scheduleSubMenu
|
|
*/
|
|
_highlightMenuItem(menuItemElement, scheduleSubMenu) {
|
|
if (this._highlightedMenuItemElement === menuItemElement) {
|
|
return;
|
|
}
|
|
if (this._subMenu) {
|
|
this._subMenu.discard();
|
|
}
|
|
if (this._highlightedMenuItemElement) {
|
|
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
|
|
this._highlightedMenuItemElement.classList.remove('force-white-icons');
|
|
this._highlightedMenuItemElement.classList.remove('soft-context-menu-item-mouse-over');
|
|
if (detailsForElement && detailsForElement.subItems && detailsForElement.subMenuTimer) {
|
|
window.clearTimeout(detailsForElement.subMenuTimer);
|
|
delete detailsForElement.subMenuTimer;
|
|
}
|
|
}
|
|
|
|
this._highlightedMenuItemElement = menuItemElement;
|
|
if (this._highlightedMenuItemElement) {
|
|
if (ThemeSupport.ThemeSupport.instance().hasTheme() || Host.Platform.isMac()) {
|
|
this._highlightedMenuItemElement.classList.add('force-white-icons');
|
|
}
|
|
this._highlightedMenuItemElement.classList.add('soft-context-menu-item-mouse-over');
|
|
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
|
|
if (detailsForElement && detailsForElement.customElement) {
|
|
detailsForElement.customElement.focus();
|
|
} else {
|
|
this._highlightedMenuItemElement.focus();
|
|
}
|
|
if (scheduleSubMenu && detailsForElement && detailsForElement.subItems && !detailsForElement.subMenuTimer) {
|
|
detailsForElement.subMenuTimer =
|
|
window.setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement), 150);
|
|
}
|
|
}
|
|
}
|
|
|
|
_highlightPrevious() {
|
|
let menuItemElement = this._highlightedMenuItemElement ?
|
|
this._highlightedMenuItemElement.previousSibling :
|
|
this._contextMenuElement ? this._contextMenuElement.lastChild : null;
|
|
let menuItemDetails =
|
|
menuItemElement ? this.detailsForElementMap.get(/** @type {!HTMLElement} */ (menuItemElement)) : undefined;
|
|
while (menuItemElement && menuItemDetails &&
|
|
(menuItemDetails.isSeparator ||
|
|
/** @type {!HTMLElement} */ (menuItemElement).classList.contains('soft-context-menu-disabled'))) {
|
|
menuItemElement = menuItemElement.previousSibling;
|
|
menuItemDetails =
|
|
menuItemElement ? this.detailsForElementMap.get(/** @type {!HTMLElement} */ (menuItemElement)) : undefined;
|
|
}
|
|
if (menuItemElement) {
|
|
this._highlightMenuItem(/** @type {!HTMLElement} */ (menuItemElement), false);
|
|
}
|
|
}
|
|
|
|
_highlightNext() {
|
|
let menuItemElement = this._highlightedMenuItemElement ?
|
|
this._highlightedMenuItemElement.nextSibling :
|
|
this._contextMenuElement ? this._contextMenuElement.firstChild : null;
|
|
let menuItemDetails =
|
|
menuItemElement ? this.detailsForElementMap.get(/** @type {!HTMLElement} */ (menuItemElement)) : undefined;
|
|
while (menuItemElement &&
|
|
(menuItemDetails && menuItemDetails.isSeparator ||
|
|
/** @type {!HTMLElement} */ (menuItemElement).classList.contains('soft-context-menu-disabled'))) {
|
|
menuItemElement = menuItemElement.nextSibling;
|
|
menuItemDetails =
|
|
menuItemElement ? this.detailsForElementMap.get(/** @type {!HTMLElement} */ (menuItemElement)) : undefined;
|
|
}
|
|
if (menuItemElement) {
|
|
this._highlightMenuItem(/** @type {!HTMLElement} */ (menuItemElement), false);
|
|
}
|
|
}
|
|
|
|
/**
|
|
*
|
|
* @param {!Event} event
|
|
*/
|
|
_menuKeyDown(event) {
|
|
const keyboardEvent = /** @type {!KeyboardEvent} */ (event);
|
|
/**
|
|
* @this {!SoftContextMenu}
|
|
*/
|
|
function onEnterOrSpace() {
|
|
if (!this._highlightedMenuItemElement) {
|
|
return;
|
|
}
|
|
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
|
|
if (!detailsForElement || detailsForElement.customElement) {
|
|
// The custom element will handle the event, so return early and do not consume it.
|
|
return;
|
|
}
|
|
this._triggerAction(this._highlightedMenuItemElement, keyboardEvent);
|
|
if (detailsForElement.subItems && this._subMenu) {
|
|
this._subMenu._highlightNext();
|
|
}
|
|
keyboardEvent.consume(true);
|
|
}
|
|
|
|
switch (keyboardEvent.key) {
|
|
case 'ArrowUp':
|
|
this._highlightPrevious();
|
|
keyboardEvent.consume(true);
|
|
break;
|
|
case 'ArrowDown':
|
|
this._highlightNext();
|
|
keyboardEvent.consume(true);
|
|
break;
|
|
case 'ArrowLeft':
|
|
if (this._parentMenu) {
|
|
this._highlightMenuItem(null, false);
|
|
this.discard();
|
|
}
|
|
keyboardEvent.consume(true);
|
|
break;
|
|
case 'ArrowRight': {
|
|
if (!this._highlightedMenuItemElement) {
|
|
break;
|
|
}
|
|
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
|
|
if (detailsForElement && detailsForElement.subItems) {
|
|
this._showSubMenu(this._highlightedMenuItemElement);
|
|
if (this._subMenu) {
|
|
this._subMenu._highlightNext();
|
|
}
|
|
}
|
|
keyboardEvent.consume(true);
|
|
break;
|
|
}
|
|
case 'Escape':
|
|
this.discard();
|
|
keyboardEvent.consume(true);
|
|
break;
|
|
/**
|
|
* Important: we don't consume the event by default for `Enter` or `Space`
|
|
* key events, as if there's a custom sub menu we pass the event onto
|
|
* that.
|
|
*/
|
|
case 'Enter':
|
|
if (!(keyboardEvent.key === 'Enter')) {
|
|
return;
|
|
}
|
|
onEnterOrSpace.call(this);
|
|
break;
|
|
case ' ':
|
|
onEnterOrSpace.call(this);
|
|
break;
|
|
default:
|
|
keyboardEvent.consume(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** @typedef
|
|
{{
|
|
type: string,
|
|
id: (number|undefined),
|
|
label: (string|undefined),
|
|
enabled: (boolean|undefined),
|
|
checked: (boolean|undefined),
|
|
subItems: (!Array.<!SoftContextMenuDescriptor>|undefined),
|
|
element: (Element|undefined),
|
|
shortcut: (string|undefined),
|
|
}} */
|
|
// @ts-ignore typedef
|
|
export let SoftContextMenuDescriptor;
|
|
|
|
/** @typedef
|
|
{{
|
|
customElement: (HTMLElement|undefined),
|
|
isSeparator: (boolean|undefined),
|
|
subMenuTimer: (number|undefined),
|
|
subItems: (!Array.<!SoftContextMenuDescriptor>|undefined),
|
|
actionId: (number|undefined),
|
|
}} */
|
|
// @ts-ignore typedef
|
|
// eslint-disable-next-line no-unused-vars
|
|
let ElementMenuDetails;
|