Revision control
Copy as Markdown
Other Tools
/* 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
import "./in-app-notification-button.mjs"; //eslint-disable-line import/no-unassigned-import
import "./in-app-notification-close-button.mjs"; //eslint-disable-line import/no-unassigned-import
const attrs = ["cta", "description", "heading", "url", "data-id", "type"];
/**
 * Container for in app notifications.
 * Template ID: #inAppNotificationContainerTemplate
 *
 * @tagname in-app-notification-container
 * @attribute {string} cta - Call to action label.
 * @attribute {string} description - Notification body text.
 * @attribute {string} heading - Notification title text.
 * @attribute {string} url - URL to open from the CTA button.
 * @attribute {string} data-id - ID of the notification.
 * @attribute {"donation"|"message"|"blog"|"security"} type - Type of the notification.
 */
class InAppNotificationContainer extends HTMLElement {
  static observedAttributes = attrs;
  connectedCallback() {
    const detached = !this.shadowRoot;
    let template;
    let styles;
    let shadowRoot;
    if (detached) {
      template = document
        .getElementById("inAppNotificationContainerTemplate")
        .content.cloneNode(true);
      styles = document.createElement("link");
      styles.rel = "stylesheet";
      shadowRoot = this.attachShadow({ mode: "open" });
      window.MozXULElement?.insertFTLIfNeeded(
        "messenger/inAppNotifications.ftl"
      );
    }
    // While this component can be gracefully disconnected and reconnected,
    // if this actually happens any changes that would affect translations
    // will not be reflected when the component is re-connected.
    document.l10n.connectRoot(this.shadowRoot);
    if (detached) {
      shadowRoot.append(styles, template);
    }
    for (const attr of attrs) {
      this.attributeChangedCallback(attr, "", this.getAttribute(attr));
    }
  }
  disconnectedCallback() {
    document.l10n.disconnectRoot(this.shadowRoot);
  }
  attributeChangedCallback(property, oldValue, newValue) {
    if (!this.shadowRoot) {
      return;
    }
    switch (property) {
      case "url":
        if (newValue) {
          this.shadowRoot.querySelector(
            '[is="in-app-notification-button"]'
          ).href = Services.urlFormatter.formatURL(newValue);
        } else {
          this.shadowRoot
            .querySelector('[is="in-app-notification-button"]')
            .removeAttribute("href");
        }
        break;
      case "cta": {
        const button = this.shadowRoot.querySelector(
          `[is="in-app-notification-button"]`
        );
        this.shadowRoot.querySelector(`.in-app-notification-cta`).textContent =
          newValue;
        button.hidden = !(
          newValue &&
          newValue !== "undefined" &&
          newValue !== "null"
        );
        button.title = newValue;
        break;
      }
      case "description":
      case "heading":
        this.shadowRoot.querySelector(
          `.in-app-notification-${property}`
        ).textContent = newValue;
        break;
      case "data-id":
        this.shadowRoot.querySelector(
          '[is="in-app-notification-button"]'
        ).dataset.id = newValue;
        this.shadowRoot.querySelector(
          '[is="in-app-notification-close-button"]'
        ).dataset.id = newValue;
        break;
      case "type": {
        const container = this.shadowRoot.querySelector(
          ".in-app-notification-container"
        );
        container.classList.remove(`in-app-notification-${oldValue}`);
        container.classList.add(`in-app-notification-${newValue}`);
        break;
      }
    }
  }
  /**
   * handles setting focus on the correct element when the notification is
   * focused
   */
  focus() {
    this.shadowRoot.querySelector(".in-app-notification-container").focus();
  }
}
customElements.define(
  "in-app-notification-container",
  InAppNotificationContainer
);