204 lines
5.7 KiB
JavaScript
204 lines
5.7 KiB
JavaScript
/* -*- indent-tabs-mode: nil; js-indent-level: 2 -*-
|
|
* 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 { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
|
|
import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
|
|
|
|
const MIN_SHOW_MORE_HEIGHT = 200;
|
|
/**
|
|
* A card container to be used in the shopping sidebar. There are three card types.
|
|
* The default type where no type attribute is required and the card will have no extra functionality.
|
|
* The "accordion" type will initially not show any content. The card will contain a arrow to expand the
|
|
* card so all of the content is visible.
|
|
*
|
|
* @property {string} label - The label text that will be used for the card header
|
|
* @property {string} type - (optional) The type of card. No type specified
|
|
* will be the default card. The other available types are "accordion" and "show-more".
|
|
*/
|
|
class ShoppingCard extends MozLitElement {
|
|
static properties = {
|
|
label: { type: String },
|
|
type: { type: String },
|
|
_isExpanded: { type: Boolean },
|
|
};
|
|
|
|
static get queries() {
|
|
return {
|
|
detailsEl: "#shopping-details",
|
|
contentEl: "#content",
|
|
};
|
|
}
|
|
|
|
labelTemplate() {
|
|
if (this.label) {
|
|
if (this.type === "accordion") {
|
|
return html`
|
|
<div id="label-wrapper">
|
|
<h2 id="header">${this.label}</h2>
|
|
<button
|
|
tabindex="-1"
|
|
class="icon chevron-icon ghost-button"
|
|
aria-labelledby="header"
|
|
@click=${this.handleChevronButtonClick}
|
|
></button>
|
|
</div>
|
|
`;
|
|
}
|
|
return html`
|
|
<div id="label-wrapper">
|
|
<h2 id="header">${this.label}</h2>
|
|
<slot name="rating"></slot>
|
|
</div>
|
|
`;
|
|
}
|
|
return "";
|
|
}
|
|
|
|
cardTemplate() {
|
|
if (this.type === "accordion") {
|
|
return html`
|
|
<details id="shopping-details" @toggle=${this.onCardToggle}>
|
|
<summary>${this.labelTemplate()}</summary>
|
|
<div id="content"><slot name="content"></slot></div>
|
|
</details>
|
|
`;
|
|
} else if (this.type === "show-more") {
|
|
return html`
|
|
${this.labelTemplate()}
|
|
<article
|
|
id="content"
|
|
class="show-more"
|
|
aria-describedby="content"
|
|
expanded="false"
|
|
>
|
|
<slot name="content"></slot>
|
|
|
|
<footer>
|
|
<button
|
|
aria-controls="content"
|
|
class="small-button shopping-button"
|
|
data-l10n-id="shopping-show-more-button"
|
|
@click=${this.handleShowMoreButtonClick}
|
|
></button>
|
|
</footer>
|
|
</article>
|
|
`;
|
|
}
|
|
return html`
|
|
${this.labelTemplate()}
|
|
<div id="content" aria-describedby="content">
|
|
<slot name="content"></slot>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
onCardToggle() {
|
|
const action = this.detailsEl.open ? "expanded" : "collapsed";
|
|
let l10nId = this.getAttribute("data-l10n-id");
|
|
switch (l10nId) {
|
|
case "shopping-settings-label":
|
|
Glean.shopping.surfaceSettingsExpandClicked.record({ action });
|
|
break;
|
|
case "shopping-analysis-explainer-label":
|
|
Glean.shopping.surfaceShowQualityExplainerClicked.record({
|
|
action,
|
|
});
|
|
break;
|
|
}
|
|
}
|
|
|
|
handleShowMoreButtonClick(e) {
|
|
this._isExpanded = !this._isExpanded;
|
|
// toggle show more/show less text
|
|
e.target.setAttribute(
|
|
"data-l10n-id",
|
|
this._isExpanded
|
|
? "shopping-show-less-button"
|
|
: "shopping-show-more-button"
|
|
);
|
|
// toggle content expanded attribute
|
|
this.contentEl.attributes.expanded.value = this._isExpanded;
|
|
|
|
let action = this._isExpanded ? "expanded" : "collapsed";
|
|
Glean.shopping.surfaceShowMoreReviewsButtonClicked.record({
|
|
action,
|
|
});
|
|
}
|
|
|
|
enableShowMoreButton() {
|
|
this._isExpanded = false;
|
|
this.toggleAttribute("showMoreButtonDisabled", false);
|
|
this.contentEl.attributes.expanded.value = false;
|
|
}
|
|
|
|
disableShowMoreButton() {
|
|
this._isExpanded = true;
|
|
this.toggleAttribute("showMoreButtonDisabled", true);
|
|
this.contentEl.attributes.expanded.value = true;
|
|
}
|
|
|
|
handleChevronButtonClick() {
|
|
this.detailsEl.open = !this.detailsEl.open;
|
|
}
|
|
|
|
firstUpdated() {
|
|
if (this.type !== "show-more") {
|
|
return;
|
|
}
|
|
|
|
let contentSlot = this.shadowRoot.querySelector("slot[name='content']");
|
|
let contentSlotEls = contentSlot.assignedElements();
|
|
if (!contentSlotEls.length) {
|
|
return;
|
|
}
|
|
|
|
let slottedDiv = contentSlotEls[0];
|
|
|
|
this.handleContentSlotResize = this.handleContentSlotResize.bind(this);
|
|
this.contentResizeObserver = new ResizeObserver(
|
|
this.handleContentSlotResize
|
|
);
|
|
this.contentResizeObserver.observe(slottedDiv);
|
|
}
|
|
|
|
disconnectedCallback() {
|
|
this.contentResizeObserver?.disconnect();
|
|
}
|
|
|
|
handleContentSlotResize(entries) {
|
|
for (let entry of entries) {
|
|
if (entry.contentRect.height === 0) {
|
|
return;
|
|
}
|
|
|
|
if (entry.contentRect.height < MIN_SHOW_MORE_HEIGHT) {
|
|
this.disableShowMoreButton();
|
|
} else if (this.hasAttribute("showMoreButtonDisabled")) {
|
|
this.enableShowMoreButton();
|
|
}
|
|
}
|
|
}
|
|
|
|
render() {
|
|
return html`
|
|
<link
|
|
rel="stylesheet"
|
|
href="chrome://browser/content/shopping/shopping-card.css"
|
|
/>
|
|
<link
|
|
rel="stylesheet"
|
|
href="chrome://browser/content/shopping/shopping-page.css"
|
|
/>
|
|
<article
|
|
class="shopping-card"
|
|
aria-labelledby="header"
|
|
aria-label=${ifDefined(this.label)}
|
|
>
|
|
${this.cardTemplate()}
|
|
</article>
|
|
`;
|
|
}
|
|
}
|
|
customElements.define("shopping-card", ShoppingCard);
|