/* 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);