TypeScriptify ui/SoftContextMenu.js

Bug: 1011811
Change-Id: Ib670d2715bae6213f1047eba401c2e15a2119a0e
Reviewed-on: https://chromium-review.googlesource.com/c/devtools/devtools-frontend/+/2550061
Commit-Queue: Jack Franklin <jacktfranklin@chromium.org>
Reviewed-by: Tim van der Lippe <tvanderlippe@chromium.org>
This commit is contained in:
Jack Franklin
2020-11-20 10:54:26 +00:00
committed by Commit Bot
parent 2477097c78
commit 67d6b84d9a
+141 -55
View File
@@ -28,9 +28,6 @@
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
// @ts-nocheck
// TODO(crbug.com/1011811): Enable TypeScript compiler checks
import * as Host from '../host/host.js';
import * as ThemeSupport from '../theme_support/theme_support.js';
@@ -54,6 +51,11 @@ export class SoftContextMenu {
this._parentMenu = parentMenu;
/** @type {?HTMLElement} */
this._highlightedMenuItemElement = null;
/**
* @type {!WeakMap<!HTMLElement, !ElementMenuDetails>}
*/
this.detailsForElementMap = new WeakMap();
}
/**
@@ -154,7 +156,7 @@ export class SoftContextMenu {
return this._createSubMenu(item);
}
const menuItemElement = document.createElement('div');
const menuItemElement = /** @type {!HTMLElement} */ (document.createElement('div'));
menuItemElement.classList.add('soft-context-menu-item');
menuItemElement.tabIndex = -1;
ARIAUtils.markAsMenuItem(menuItemElement);
@@ -163,11 +165,19 @@ export class SoftContextMenu {
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);
menuItemElement._customElement = item.element;
detailsForElement.customElement = /** @type {!HTMLElement} */ (item.element);
return menuItemElement;
}
@@ -175,7 +185,7 @@ export class SoftContextMenu {
menuItemElement.classList.add('soft-context-menu-disabled');
}
createTextChild(menuItemElement, item.label || '');
menuItemElement.createChild('span', 'soft-context-menu-shortcut').textContent = item.shortcut;
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);
@@ -185,7 +195,7 @@ export class SoftContextMenu {
menuItemElement.addEventListener(
'mouseleave', /** @type {!EventListener} */ (this._menuItemMouseLeave.bind(this)), false);
menuItemElement._actionId = item.id;
detailsForElement.actionId = item.id;
let accessibleName = item.label || '';
@@ -201,6 +211,7 @@ export class SoftContextMenu {
}
ARIAUtils.setAccessibleName(menuItemElement, accessibleName);
this.detailsForElementMap.set(menuItemElement, detailsForElement);
return menuItemElement;
}
@@ -208,11 +219,17 @@ export class SoftContextMenu {
* @param {!SoftContextMenuDescriptor} item
*/
_createSubMenu(item) {
const menuItemElement = document.createElement('div');
const menuItemElement = /** @type {!HTMLElement} */ (document.createElement('div'));
menuItemElement.classList.add('soft-context-menu-item');
menuItemElement._subItems = item.subItems;
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');
@@ -245,9 +262,15 @@ export class SoftContextMenu {
}
_createSeparator() {
const separatorElement = document.createElement('div');
const separatorElement = /** @type {!HTMLElement} */ (document.createElement('div'));
separatorElement.classList.add('soft-context-menu-separator');
separatorElement._isSeparator = true;
this.detailsForElementMap.set(separatorElement, {
subItems: undefined,
actionId: undefined,
isSeparator: true,
customElement: undefined,
subMenuTimer: undefined,
});
separatorElement.createChild('div', 'separator-line');
return separatorElement;
}
@@ -284,14 +307,17 @@ export class SoftContextMenu {
* @param {!Event} event
*/
_triggerAction(menuItemElement, event) {
if (!menuItemElement._subItems) {
this._root().discard();
event.consume(true);
if (typeof menuItemElement._actionId !== 'undefined') {
this._itemSelectedCallback(menuItemElement._actionId);
delete menuItemElement._actionId;
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;
}
return;
}
this._showSubMenu(menuItemElement);
@@ -302,9 +328,13 @@ export class SoftContextMenu {
* @param {!HTMLElement} menuItemElement
*/
_showSubMenu(menuItemElement) {
if (menuItemElement._subMenuTimer) {
clearTimeout(menuItemElement._subMenuTimer);
delete menuItemElement._subMenuTimer;
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;
@@ -312,7 +342,10 @@ export class SoftContextMenu {
this._activeSubMenuElement = menuItemElement;
ARIAUtils.setExpanded(menuItemElement, true);
this._subMenu = new SoftContextMenu(menuItemElement._subItems, this._itemSelectedCallback, this);
if (!detailsForElement.subItems) {
return;
}
this._subMenu = new SoftContextMenu(detailsForElement.subItems, this._itemSelectedCallback, this);
const anchorBox = menuItemElement.boxInWindow();
// Adjust for padding.
anchorBox.y -= 5;
@@ -357,58 +390,101 @@ export class SoftContextMenu {
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 (this._highlightedMenuItemElement._subItems && this._highlightedMenuItemElement._subMenuTimer) {
clearTimeout(this._highlightedMenuItemElement._subMenuTimer);
delete this._highlightedMenuItemElement._subMenuTimer;
if (!detailsForElement) {
return;
}
if (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');
if (this._highlightedMenuItemElement._customElement) {
this._highlightedMenuItemElement._customElement.focus();
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
if (!detailsForElement) {
return;
}
if (detailsForElement.customElement) {
detailsForElement.customElement.focus();
} else {
this._highlightedMenuItemElement.focus();
}
if (scheduleSubMenu && this._highlightedMenuItemElement._subItems &&
!this._highlightedMenuItemElement._subMenuTimer) {
this._highlightedMenuItemElement._subMenuTimer =
setTimeout(this._showSubMenu.bind(this, this._highlightedMenuItemElement), 150);
if (scheduleSubMenu && 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.lastChild;
while (menuItemElement &&
(menuItemElement._isSeparator || menuItemElement.classList.contains('soft-context-menu-disabled'))) {
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(menuItemElement, false);
this._highlightMenuItem(/** @type {!HTMLElement} */ (menuItemElement), false);
}
}
_highlightNext() {
let menuItemElement = this._highlightedMenuItemElement ? this._highlightedMenuItemElement.nextSibling :
this._contextMenuElement.firstChild;
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 &&
(menuItemElement._isSeparator || menuItemElement.classList.contains('soft-context-menu-disabled'))) {
(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(menuItemElement, false);
this._highlightMenuItem(/** @type {!HTMLElement} */ (menuItemElement), false);
}
}
/**
*
* @param {!Event} event
*/
_menuKeyDown(event) {
switch (event.key) {
const keyboardEvent = /** @type {!KeyboardEvent} */ (event);
/**
* @this {!SoftContextMenu}
*/
function onEnterOrSpace() {
if (!this._highlightedMenuItemElement) {
return;
}
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
if (!detailsForElement || detailsForElement.customElement) {
return;
}
this._triggerAction(this._highlightedMenuItemElement, keyboardEvent);
if (detailsForElement.subItems && this._subMenu) {
this._subMenu._highlightNext();
}
}
switch (keyboardEvent.key) {
case 'ArrowUp':
this._highlightPrevious();
break;
@@ -421,34 +497,32 @@ export class SoftContextMenu {
this.discard();
}
break;
case 'ArrowRight':
case 'ArrowRight': {
if (!this._highlightedMenuItemElement) {
break;
}
if (this._highlightedMenuItemElement._subItems) {
const detailsForElement = this.detailsForElementMap.get(this._highlightedMenuItemElement);
if (detailsForElement && detailsForElement.subItems) {
this._showSubMenu(this._highlightedMenuItemElement);
this._subMenu._highlightNext();
if (this._subMenu) {
this._subMenu._highlightNext();
}
}
break;
}
case 'Escape':
this.discard();
break;
case 'Enter':
if (!isEnterKey(event)) {
if (!isEnterKey(keyboardEvent)) {
return;
}
// Fall through
case ' ': // Space
if (!this._highlightedMenuItemElement || this._highlightedMenuItemElement._customElement) {
return;
}
this._triggerAction(this._highlightedMenuItemElement, event);
if (this._highlightedMenuItemElement._subItems && this._subMenu) {
this._subMenu._highlightNext();
}
onEnterOrSpace.call(this);
break;
case ' ':
onEnterOrSpace.call(this);
}
event.consume(true);
keyboardEvent.consume(true);
}
}
@@ -465,3 +539,15 @@ export class SoftContextMenu {
}} */
// @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;