/* 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/. */ import { classMap, html, ifDefined, when, } from "chrome://global/content/vendor/lit.all.mjs"; import { FxviewTabListBase, FxviewTabRowBase, } from "chrome://browser/content/firefoxview/fxview-tab-list.mjs"; export class SidebarTabList extends FxviewTabListBase { constructor() { super(); // Panel is open, assume we always want to react to updates. this.updatesPaused = false; this.multiSelect = true; this.selectedGuids = new Set(); this.shortcutsLocalization = new Localization( ["toolkit/global/textActions.ftl"], true ); } static queries = { ...FxviewTabListBase.queries, rowEls: { all: "sidebar-tab-row", }, }; /** * Only handle vertical navigation in sidebar. * * @param {KeyboardEvent} e */ handleFocusElementInRow(e) { // Handle vertical navigation. if ( (e.code == "ArrowUp" && this.activeIndex > 0) || (e.code == "ArrowDown" && this.activeIndex < this.rowEls.length - 1) ) { super.handleFocusElementInRow(e); } else if ( (e.code == "ArrowUp" && this.activeIndex == 0) || e.code === "ArrowLeft" ) { this.#focusParentHeader(e.target); } else if ( e.code == "ArrowDown" && this.activeIndex == this.rowEls.length - 1 ) { this.#focusNextHeader(e.target); } // Update or clear multi-selection (depending on whether shift key is used). if (this.multiSelect && (e.code === "ArrowUp" || e.code === "ArrowDown")) { this.#updateSelection(e); } // (Ctrl / Cmd) + A should select all rows. if ( e.getModifierState("Accel") && e.key.toUpperCase() === this.selectAllShortcut ) { e.preventDefault(); this.#selectAll(); } } #focusParentHeader(row) { let parentCard = row.getRootNode().host.closest("moz-card"); if (parentCard) { parentCard.summaryEl.focus(); } } #focusNextHeader(row) { let parentCard = row.getRootNode().host.closest("moz-card"); if ( this.sortOption == "datesite" && parentCard.classList.contains("last-card") ) { // If we're going down from the last site, then focus the next date. const dateCard = parentCard.parentElement; const nextDate = dateCard.nextElementSibling; nextDate?.summaryEl.focus(); } let nextCard = parentCard.nextElementSibling; if (nextCard && nextCard.localName == "moz-card") { nextCard.summaryEl.focus(); } } #updateSelection(event) { if (!event.shiftKey) { // Clear the selection when navigating without shift key. // Dispatch event so that other lists will also clear their selection. this.clearSelection(); this.dispatchEvent( new CustomEvent("clear-selection", { bubbles: true, composed: true, }) ); return; } // Select the current row. const row = event.target; const { guid, previousElementSibling: prevRow, nextElementSibling: nextRow, } = row; this.selectedGuids.add(guid); // Select the previous or next sibling, depending on which arrow key was used. if (event.code === "ArrowUp" && prevRow) { this.selectedGuids.add(prevRow.guid); } else if (event.code === "ArrowDown" && nextRow) { this.selectedGuids.add(nextRow.guid); } else { this.requestVirtualListUpdate(); } // Notify the host component. this.dispatchEvent( new CustomEvent("update-selection", { bubbles: true, composed: true, }) ); } clearSelection() { this.selectedGuids.clear(); this.requestVirtualListUpdate(); } get selectAllShortcut() { const [l10nMessage] = this.shortcutsLocalization.formatMessagesSync([ "text-action-select-all-shortcut", ]); const shortcutKey = l10nMessage.attributes[0].value; return shortcutKey; } #selectAll() { for (const { guid } of this.tabItems) { this.selectedGuids.add(guid); } this.requestVirtualListUpdate(); this.dispatchEvent( new CustomEvent("update-selection", { bubbles: true, composed: true, }) ); } itemTemplate = (tabItem, i) => { let tabIndex = -1; if ((this.searchQuery || this.sortOption == "lastvisited") && i == 0) { // Make the first row focusable if there is no header. tabIndex = 0; } else if (!this.searchQuery) { tabIndex = 0; } return html` e.currentTarget.primaryActionHandler(e)} > `; }; stylesheets() { return [ super.stylesheets(), html``, ]; } } customElements.define("sidebar-tab-list", SidebarTabList); export class SidebarTabRow extends FxviewTabRowBase { static properties = { containerObj: { type: Object }, guid: { type: String }, selected: { type: Boolean, reflect: true }, indicators: { type: Array }, }; /** * Fallback to the native implementation in sidebar. We want to focus the * entire row instead of delegating it to link or hover buttons. */ focus() { HTMLElement.prototype.focus.call(this); } #getContainerClasses() { let containerClasses = ["fxview-tab-row-container-indicator", "icon"]; if (this.containerObj) { let { icon, color } = this.containerObj; containerClasses.push(`identity-icon-${icon}`); containerClasses.push(`identity-color-${color}`); } return containerClasses; } #containerIndicatorTemplate() { let tabList = this.getRootNode().host; let tabsToCheck = tabList.tabItems; return html`${when( tabsToCheck.some(tab => tab.containerObj), () => html`` )}`; } secondaryButtonTemplate() { return html`${when( this.secondaryL10nId && this.secondaryActionClass, () => html`` )}`; } render() { return html` ${this.stylesheets()} ${when( this.containerObj, () => html` ` )} ${this.faviconTemplate()} ${this.titleTemplate()} ${this.secondaryButtonTemplate()} ${this.#containerIndicatorTemplate()} `; } } customElements.define("sidebar-tab-row", SidebarTabRow);