summaryrefslogtreecommitdiffstats
path: root/comm/mail/components/unifiedtoolbar/content/search-bar.mjs
blob: a450f7349f89a0589d7201cdb364cc2cac795df5 (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
120
121
/* 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/. */

/**
 * Search input with customizable search button and placeholder.
 * Attributes:
 * - label: Search field label for accessibility tree.
 * - disabled: When present, disable the search field and button.
 * Slots in template (#searchBarTemplate):
 * - placeholder: Content displayed as placeholder. When not provided, the value
 *   of the label attribute is shown as placeholder.
 * - button: Content displayed on the search button.
 *
 * @emits search: Event when a search should be executed. detail holds the
 *  search term.
 * @emits autocomplte: Auto complete update. detail holds the current search
 *  term.
 */
export class SearchBar extends HTMLElement {
  static get observedAttributes() {
    return ["label", "disabled"];
  }

  /**
   * Reference to the input field in the form.
   *
   * @type {?HTMLInputElement}
   */
  #input = null;

  /**
   * Reference to the search button in the form.
   *
   * @type {?HTMLButtonElement}
   */
  #button = null;

  #onSubmit = event => {
    event.preventDefault();
    if (!this.#input.value) {
      return;
    }

    const searchEvent = new CustomEvent("search", {
      detail: this.#input.value,
      cancelable: true,
    });
    if (this.dispatchEvent(searchEvent)) {
      this.reset();
    }
  };

  #onInput = () => {
    const autocompleteEvent = new CustomEvent("autocomplete", {
      detail: this.#input.value,
    });
    this.dispatchEvent(autocompleteEvent);
  };

  connectedCallback() {
    if (this.shadowRoot) {
      return;
    }

    const shadowRoot = this.attachShadow({ mode: "open" });

    const template = document
      .getElementById("searchBarTemplate")
      .content.cloneNode(true);
    this.#input = template.querySelector("input");
    this.#button = template.querySelector("button");

    template.querySelector("form").addEventListener("submit", this.#onSubmit, {
      passive: false,
    });

    this.#input.setAttribute("aria-label", this.getAttribute("label"));
    template.querySelector("slot[name=placeholder]").textContent =
      this.getAttribute("label");
    this.#input.addEventListener("input", this.#onInput);

    const styles = document.createElement("link");
    styles.setAttribute("rel", "stylesheet");
    styles.setAttribute(
      "href",
      "chrome://messenger/skin/shared/search-bar.css"
    );
    shadowRoot.append(styles, template);
  }

  attributeChangedCallback(attributeName, oldValue, newValue) {
    if (!this.#input) {
      return;
    }
    switch (attributeName) {
      case "label":
        this.#input.setAttribute("aria-label", newValue);
        this.shadowRoot.querySelector("slot[name=placeholder]").textContent =
          newValue;
        break;
      case "disabled": {
        const isDisabled = this.hasAttribute("disabled");
        this.#input.disabled = isDisabled;
        this.#button.disabled = isDisabled;
      }
    }
  }

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

  /**
   * Reset the search bar to its empty state.
   */
  reset() {
    this.#input.value = "";
  }
}
customElements.define("search-bar", SearchBar);