From 6bf0a5cb5034a7e684dcc3500e841785237ce2dd Mon Sep 17 00:00:00 2001 From: Daniel Baumann Date: Sun, 7 Apr 2024 19:32:43 +0200 Subject: Adding upstream version 1:115.7.0. Signed-off-by: Daniel Baumann --- .../aboutlogins/content/components/menu-button.mjs | 183 +++++++++++++++++++++ 1 file changed, 183 insertions(+) create mode 100644 browser/components/aboutlogins/content/components/menu-button.mjs (limited to 'browser/components/aboutlogins/content/components/menu-button.mjs') diff --git a/browser/components/aboutlogins/content/components/menu-button.mjs b/browser/components/aboutlogins/content/components/menu-button.mjs new file mode 100644 index 0000000000..bb69b711c9 --- /dev/null +++ b/browser/components/aboutlogins/content/components/menu-button.mjs @@ -0,0 +1,183 @@ +/* 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); -- cgit v1.2.3