fune/browser/components/firefoxview/card-container.mjs
Anna Yeddi 72d920332c Bug 1884986 - Ensure the control on an empty card on Firefox View would announce the group info. r=fxview-reviewers,Jamie
Since the wrapper of the `<details>` element cannot access its labeling element (included in the slot), we could move the grouping attributes to the `<card-container>` element itself. This would also allow us to remove an additional `<section>` container and reduce the number of nested groups exposed to the Accessibility API, including the case where another `<details>` element is being added for the inner card.

This patch also removes the extra `group` container for an empty card to reduce the verbocity of the announcement and the nesting complexity, while keeping the grouping programmatically indicated (just not duplicated in the case of an empty card where the additional card template is added).

Differential Revision: https://phabricator.services.mozilla.com/D206261
2024-04-16 23:57:18 +00:00

203 lines
6.3 KiB
JavaScript

/* 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 { MozLitElement } from "chrome://global/content/lit-utils.mjs";
/**
* A collapsible card container to be used throughout Firefox View
*
* @property {string} sectionLabel - The aria-label used for the section landmark if the header is hidden with hideHeader
* @property {boolean} hideHeader - Optional property given if the card container should not display a header
* @property {boolean} isEmptyState - Optional property given if the card is used within an empty state
* @property {boolean} isInnerCard - Optional property given if the card a nested card within another card and given a border rather than box-shadow
* @property {boolean} preserveCollapseState - Whether or not the expanded/collapsed state should persist
* @property {string} shortPageName - Page name that the 'View all' link will navigate to and the preserveCollapseState pref will use
* @property {boolean} showViewAll - True if you need to display a 'View all' header link to navigate
* @property {boolean} toggleDisabled - Optional property given if the card container should not be collapsible
* @property {boolean} removeBlockEndMargin - True if you need to remove the block end margin on the card container
*/
class CardContainer extends MozLitElement {
constructor() {
super();
this.initiallyExpanded = true;
this.isExpanded = false;
this.visible = false;
}
static properties = {
sectionLabel: { type: String },
hideHeader: { type: Boolean },
isExpanded: { type: Boolean },
isEmptyState: { type: Boolean },
isInnerCard: { type: Boolean },
preserveCollapseState: { type: Boolean },
shortPageName: { type: String },
showViewAll: { type: Boolean },
toggleDisabled: { type: Boolean },
removeBlockEndMargin: { type: Boolean },
visible: { type: Boolean },
};
static queries = {
detailsEl: "details",
mainSlot: "slot[name=main]",
summaryEl: "summary",
viewAllLink: ".view-all-link",
};
get detailsExpanded() {
return this.detailsEl.hasAttribute("open");
}
get detailsOpenPrefValue() {
const prefName = this.shortPageName
? `browser.tabs.firefox-view.ui-state.${this.shortPageName}.open`
: null;
if (prefName && Services.prefs.prefHasUserValue(prefName)) {
return Services.prefs.getBoolPref(prefName);
}
return null;
}
connectedCallback() {
super.connectedCallback();
this.isExpanded = this.detailsOpenPrefValue ?? this.initiallyExpanded;
}
onToggleContainer() {
if (this.isExpanded == this.detailsExpanded) {
return;
}
this.isExpanded = this.detailsExpanded;
this.updateTabLists();
if (!this.shortPageName) {
return;
}
if (this.preserveCollapseState) {
const prefName = this.shortPageName
? `browser.tabs.firefox-view.ui-state.${this.shortPageName}.open`
: null;
Services.prefs.setBoolPref(prefName, this.isExpanded);
}
// Record telemetry
Services.telemetry.recordEvent(
"firefoxview_next",
this.isExpanded ? "card_expanded" : "card_collapsed",
"card_container",
null,
{
data_type: this.shortPageName,
}
);
}
viewAllClicked() {
this.dispatchEvent(
new CustomEvent("card-container-view-all", {
bubbles: true,
composed: true,
})
);
}
willUpdate(changes) {
if (changes.has("visible")) {
this.updateTabLists();
}
}
updateTabLists() {
let tabLists = this.querySelectorAll("fxview-tab-list, opentabs-tab-list");
if (tabLists) {
tabLists.forEach(tabList => {
tabList.updatesPaused = !this.visible || !this.isExpanded;
});
}
}
render() {
return html`
<link
rel="stylesheet"
href="chrome://browser/content/firefoxview/card-container.css"
/>
${when(
this.toggleDisabled,
() => html`<div
class=${classMap({
"card-container": true,
inner: this.isInnerCard,
"empty-state": this.isEmptyState && !this.isInnerCard,
})}
>
<span
id="header"
class="card-container-header"
?hidden=${ifDefined(this.hideHeader)}
toggleDisabled
?withViewAll=${this.showViewAll}
>
<slot name="header"></slot>
<slot name="secondary-header"></slot>
</span>
<a
href="about:firefoxview#${this.shortPageName}"
@click=${this.viewAllClicked}
class="view-all-link"
data-l10n-id="firefoxview-view-all-link"
?hidden=${!this.showViewAll}
></a>
<slot name="main"></slot>
<slot name="footer" class="card-container-footer"></slot>
</div>`,
() => html`<details
class=${classMap({
"card-container": true,
inner: this.isInnerCard,
"empty-state": this.isEmptyState && !this.isInnerCard,
})}
?open=${this.isExpanded}
?isOpenTabsView=${this.removeBlockEndMargin}
@toggle=${this.onToggleContainer}
role=${this.isInnerCard ? "presentation" : "group"}
>
<summary
class="card-container-header"
?hidden=${ifDefined(this.hideHeader)}
?withViewAll=${this.showViewAll}
>
<span
class="icon chevron-icon"
role="presentation"
data-l10n-id="firefoxview-collapse-button-${this.isExpanded
? "hide"
: "show"}"
></span>
<slot name="header"></slot>
</summary>
<a
href="about:firefoxview#${this.shortPageName}"
@click=${this.viewAllClicked}
class="view-all-link"
data-l10n-id="firefoxview-view-all-link"
?hidden=${!this.showViewAll}
></a>
<slot name="main"></slot>
<slot name="footer" class="card-container-footer"></slot>
</details>`
)}
`;
}
}
customElements.define("card-container", CardContainer);