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);
|