From 67d6b84d9a1ec244ff2b6407dc972230c70d5f10 Mon Sep 17 00:00:00 2001 From: Jack Franklin Date: Fri, 20 Nov 2020 10:21:49 +0000 Subject: [PATCH] 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 Reviewed-by: Tim van der Lippe --- front_end/ui/SoftContextMenu.js | 196 +++++++++++++++++++++++--------- 1 file changed, 141 insertions(+), 55 deletions(-) diff --git a/front_end/ui/SoftContextMenu.js b/front_end/ui/SoftContextMenu.js index f048dc0be1..1e4c7757cc 100644 --- a/front_end/ui/SoftContextMenu.js +++ b/front_end/ui/SoftContextMenu.js @@ -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} + */ + 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.|undefined), + actionId: (number|undefined), +}} */ +// @ts-ignore typedef +// eslint-disable-next-line no-unused-vars +let ElementMenuDetails;