summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/unifiedtoolbar/content/unified-toolbar-tab.mjs
blob: 134aec6cf1b9d5d7b184e545317b175bcdde9af9 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
/* 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/. */

/**
 * Template ID: unifiedToolbarTabTemplate
 * Attributes:
 * - selected: If the tab is active.
 * - aria-controls: The ID of the tab pane this controls.
 * Events:
 * - tabswitch: When the active tab is changed.
 */
class UnifiedToolbarTab extends HTMLElement {
  /**
   * @type {?HTMLButtonElement}
   */
  #tab = null;

  connectedCallback() {
    if (this.shadowRoot) {
      return;
    }
    this.setAttribute("role", "presentation");
    const shadowRoot = this.attachShadow({ mode: "open" });

    const template = document
      .getElementById("unifiedToolbarTabTemplate")
      .content.cloneNode(true);
    this.#tab = template.querySelector("button");
    this.#tab.tabIndex = this.hasAttribute("selected") ? 0 : -1;
    if (this.hasAttribute("selected")) {
      this.#tab.setAttribute("aria-selected", "true");
    }
    this.#tab.setAttribute("aria-controls", this.getAttribute("aria-controls"));
    this.removeAttribute("aria-controls");

    const styles = document.createElement("link");
    styles.setAttribute("rel", "stylesheet");
    styles.setAttribute(
      "href",
      "chrome://messenger/skin/shared/unifiedToolbarTab.css"
    );

    shadowRoot.append(styles, template);

    this.#tab.addEventListener("click", () => {
      this.select();
    });
    this.#tab.addEventListener("keydown", this.#handleKey);
  }

  #handleKey = event => {
    const rightIsForward = document.dir === "ltr";
    const rightSibling =
      (rightIsForward ? "next" : "previous") + "ElementSibling";
    const leftSibling =
      (rightIsForward ? "previous" : "next") + "ElementSibling";
    switch (event.key) {
      case "ArrowLeft":
        this[leftSibling]?.focus();
        break;
      case "ArrowRight":
        this[rightSibling]?.focus();
        break;
      case "Home":
        this.parentNode.firstElementChild?.focus();
        break;
      case "End":
        this.parentNode.lastElementChild?.focus();
        break;
      default:
        return;
    }

    event.stopPropagation();
    event.preventDefault();
  };

  #toggleTabPane(visible) {
    this.pane.hidden = !visible;
  }

  /**
   * Select this tab. Deselects the previously selected tab and shows the tab
   * pane for this tab.
   */
  select() {
    this.parentElement
      .querySelector("unified-toolbar-tab[selected]")
      ?.unselect();
    this.#tab.setAttribute("aria-selected", "true");
    this.#tab.tabIndex = 0;
    this.setAttribute("selected", true);
    this.#toggleTabPane(true);
    const tabSwitchEvent = new Event("tabswitch", {
      bubbles: true,
    });
    this.dispatchEvent(tabSwitchEvent);
  }

  /**
   * Remove the selection for this tab and hide the associated tab pane.
   */
  unselect() {
    this.#tab.removeAttribute("aria-selected");
    this.#tab.tabIndex = -1;
    this.removeAttribute("selected");
    this.#toggleTabPane(false);
  }

  focus() {
    this.#tab.focus();
  }

  get pane() {
    return document.getElementById(this.#tab.getAttribute("aria-controls"));
  }
}
customElements.define("unified-toolbar-tab", UnifiedToolbarTab);