/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ export default class MenuButton extends HTMLElement { connectedCallback() { if (this.shadowRoot) { return; } let MenuButtonTemplate = document.querySelector("#menu-button-template"); let shadowRoot = this.attachShadow({ mode: "open" }); document.l10n.connectRoot(shadowRoot); shadowRoot.appendChild(MenuButtonTemplate.content.cloneNode(true)); for (let menuitem of this.shadowRoot.querySelectorAll( ".menuitem-button[data-supported-platforms]" )) { let supportedPlatforms = menuitem.dataset.supportedPlatforms .split(",") .map(platform => platform.trim()); if (supportedPlatforms.includes(navigator.platform)) { menuitem.hidden = false; } } this._menu = this.shadowRoot.querySelector(".menu"); this._menuButton = this.shadowRoot.querySelector(".menu-button"); this._menuButton.addEventListener("click", this); document.addEventListener("keydown", this, true); } handleEvent(event) { switch (event.type) { case "blur": { if (event.explicitOriginalTarget) { let node = event.explicitOriginalTarget; if (node.nodeType == Node.TEXT_NODE) { node = node.parentElement; } if (node.closest(".menu") == this._menu) { // Only hide the menu if focus has left the menu-button. return; } } this._hideMenu(); break; } case "click": { // Skip the catch-all event listener if it was the menu-button // that was clicked on. if ( event.currentTarget == document.documentElement && event.target == this && event.originalTarget == this._menuButton ) { return; } if (event.originalTarget == this._menuButton) { this._toggleMenu(); if (!this._menu.hidden) { this._menuButton.focus(); } return; } let classList = event.originalTarget.classList; if (classList.contains("menuitem-button")) { let eventName = event.originalTarget.dataset.eventName; const linkTrackingSource = "Elipsis_Menu"; document.dispatchEvent( new CustomEvent(eventName, { bubbles: true, detail: linkTrackingSource, }) ); // Bug 1645365: Only hide the menu when the buttons are clicked // So that the menu isn't closed when non-buttons (e.g. separators, paddings) are clicked this._hideMenu(); } // Explicitly close menu at the catch-all click event (i.e. a click outside of the menu) if ( !this._menu.contains(event.originalTarget) && !this._menuButton.contains(event.originalTarget) ) { this._hideMenu(); } break; } case "keydown": { this._handleKeyDown(event); } } } _handleKeyDown(event) { if (event.key == "Enter" && event.originalTarget == this._menuButton) { event.preventDefault(); this._toggleMenu(); this._focusSuccessor(true); } else if (event.key == "Escape" && !this._menu.hidden) { this._hideMenu(); this._menuButton.focus(); } else if ( (event.key == "ArrowDown" || event.key == "ArrowUp") && !this._menu.hidden ) { event.preventDefault(); this._focusSuccessor(event.key == "ArrowDown"); } } _focusSuccessor(next = true) { let items = this._menu.querySelectorAll(".menuitem-button:not([hidden])"); let firstItem = items[0]; let lastItem = items[items.length - 1]; let activeItem = this.shadowRoot.activeElement; let activeItemIndex = [...items].indexOf(activeItem); let successor = null; if (next) { if (!activeItem || activeItem === lastItem) { successor = firstItem; } else { successor = items[activeItemIndex + 1]; } } else if (activeItem === this._menuButton || activeItem === firstItem) { successor = lastItem; } else { successor = items[activeItemIndex - 1]; } if (this._menu.hidden) { this._showMenu(); } if (successor.disabled) { if (next) { successor = items[activeItemIndex + 2]; } else { successor = items[activeItemIndex - 2]; } } window.AboutLoginsUtils.setFocus(successor); } _hideMenu() { this._menu.hidden = true; this.removeEventListener("blur", this); document.documentElement.removeEventListener("click", this, true); } _showMenu() { this._menu.querySelector(".menuitem-import-file").hidden = !window.AboutLoginsUtils.fileImportEnabled; this._menu.hidden = false; // Event listeners to close the menu this.addEventListener("blur", this); document.documentElement.addEventListener("click", this, true); } /** * Toggles the visibility of the menu. */ _toggleMenu() { let wasHidden = this._menu.hidden; if (wasHidden) { this._showMenu(); } else { this._hideMenu(); } } } customElements.define("menu-button", MenuButton);