summaryrefslogtreecommitdiffstats
path: root/toolkit/content/widgets/search-textbox.js
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/content/widgets/search-textbox.js')
-rw-r--r--toolkit/content/widgets/search-textbox.js263
1 files changed, 263 insertions, 0 deletions
diff --git a/toolkit/content/widgets/search-textbox.js b/toolkit/content/widgets/search-textbox.js
new file mode 100644
index 0000000000..e2be28c05f
--- /dev/null
+++ b/toolkit/content/widgets/search-textbox.js
@@ -0,0 +1,263 @@
+/* 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/. */
+
+"use strict";
+
+// This is loaded into all XUL windows. Wrap in a block to prevent
+// leaking to window scope.
+{
+ class MozSearchTextbox extends MozXULElement {
+ constructor() {
+ super();
+
+ MozXULElement.insertFTLIfNeeded("toolkit/global/textActions.ftl");
+
+ this.inputField = document.createElement("input");
+
+ const METHODS = [
+ "focus",
+ "blur",
+ "select",
+ "setUserInput",
+ "setSelectionRange",
+ ];
+ for (const method of METHODS) {
+ this[method] = (...args) => this.inputField[method](...args);
+ }
+
+ const READ_WRITE_PROPERTIES = [
+ "defaultValue",
+ "placeholder",
+ "readOnly",
+ "size",
+ "selectionStart",
+ "selectionEnd",
+ ];
+ for (const property of READ_WRITE_PROPERTIES) {
+ Object.defineProperty(this, property, {
+ enumerable: true,
+ get() {
+ return this.inputField[property];
+ },
+ set(val) {
+ this.inputField[property] = val;
+ },
+ });
+ }
+
+ this.attachShadow({ mode: "open" });
+ this.addEventListener("input", this);
+ this.addEventListener("keypress", this);
+ this.addEventListener("mousedown", this);
+ }
+
+ static get inheritedAttributes() {
+ return {
+ input:
+ "value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,inputmode,spellcheck",
+ ".textbox-search-icon": "label=searchbuttonlabel,disabled",
+ ".textbox-search-clear": "disabled",
+ };
+ }
+
+ connectedCallback() {
+ if (this.delayConnectedCallback() || this.connected) {
+ return;
+ }
+
+ document.l10n.connectRoot(this.shadowRoot);
+
+ this.connected = true;
+ this.textContent = "";
+
+ const stylesheet = document.createElement("link");
+ stylesheet.rel = "stylesheet";
+ stylesheet.href = "chrome://global/skin/search-textbox.css";
+
+ const textboxSign = document.createXULElement("image");
+ textboxSign.className = "textbox-search-sign";
+ textboxSign.part = "search-sign";
+
+ const input = this.inputField;
+ input.setAttribute("inputmode", "search");
+ input.autocomplete = "off"; // not applicable in XUL docs and confuses aria.
+ input.addEventListener("focus", this);
+ input.addEventListener("blur", this);
+
+ const searchBtn = (this._searchButtonIcon = document.createXULElement(
+ "image"
+ ));
+ searchBtn.className = "textbox-search-icon";
+ searchBtn.addEventListener("click", e => this._iconClick(e));
+
+ const clearBtn = document.createXULElement("image");
+ clearBtn.className = "textbox-search-clear";
+ clearBtn.part = "clear-icon";
+ clearBtn.setAttribute("role", "button");
+ document.l10n.setAttributes(
+ clearBtn,
+ "text-action-search-text-box-clear"
+ );
+ clearBtn.addEventListener("click", () => this._clearSearch());
+
+ const deck = (this._searchIcons = document.createXULElement("deck"));
+ deck.className = "textbox-search-icons";
+ deck.append(searchBtn, clearBtn);
+ this.shadowRoot.append(stylesheet, textboxSign, input, deck);
+
+ this._timer = null;
+
+ // Ensure the button state is up to date:
+ // eslint-disable-next-line no-self-assign
+ this.searchButton = this.searchButton;
+
+ this.initializeAttributeInheritance();
+ }
+
+ disconnectedCallback() {
+ document.l10n.disconnectRoot(this.shadowRoot);
+ }
+
+ set timeout(val) {
+ this.setAttribute("timeout", val);
+ }
+
+ get timeout() {
+ return parseInt(this.getAttribute("timeout")) || 500;
+ }
+
+ set searchButton(val) {
+ if (val) {
+ this.setAttribute("searchbutton", "true");
+ this.inputField.removeAttribute("aria-autocomplete");
+ this._searchButtonIcon.setAttribute("role", "button");
+ } else {
+ this.removeAttribute("searchbutton");
+ this.inputField.setAttribute("aria-autocomplete", "list");
+ this._searchButtonIcon.setAttribute("role", "none");
+ }
+ }
+
+ get searchButton() {
+ return this.getAttribute("searchbutton") == "true";
+ }
+
+ set value(val) {
+ this.inputField.value = val;
+
+ if (val) {
+ this._searchIcons.selectedIndex = this.searchButton ? 0 : 1;
+ } else {
+ this._searchIcons.selectedIndex = 0;
+ }
+
+ if (this._timer) {
+ clearTimeout(this._timer);
+ }
+ }
+
+ get value() {
+ return this.inputField.value;
+ }
+
+ get editor() {
+ return this.inputField.editor;
+ }
+
+ set disabled(val) {
+ this.inputField.disabled = val;
+ if (val) {
+ this.setAttribute("disabled", "true");
+ } else {
+ this.removeAttribute("disabled");
+ }
+ }
+
+ get disabled() {
+ return this.inputField.disabled;
+ }
+
+ on_blur() {
+ this.removeAttribute("focused");
+ }
+
+ on_focus() {
+ this.setAttribute("focused", "true");
+ }
+
+ on_input() {
+ if (this.searchButton) {
+ this._searchIcons.selectedIndex = 0;
+ return;
+ }
+ if (this._timer) {
+ clearTimeout(this._timer);
+ }
+ this._timer =
+ this.timeout && setTimeout(this._fireCommand, this.timeout, this);
+ this._searchIcons.selectedIndex = this.value ? 1 : 0;
+ }
+
+ on_keypress(event) {
+ switch (event.keyCode) {
+ case KeyEvent.DOM_VK_ESCAPE:
+ if (this._clearSearch()) {
+ event.preventDefault();
+ event.stopPropagation();
+ }
+ break;
+ case KeyEvent.DOM_VK_RETURN:
+ this._enterSearch();
+ event.preventDefault();
+ event.stopPropagation();
+ break;
+ }
+ }
+
+ on_mousedown(event) {
+ if (!this.hasAttribute("focused")) {
+ this.setSelectionRange(0, 0);
+ this.focus();
+ }
+ }
+
+ _fireCommand(me) {
+ if (me._timer) {
+ clearTimeout(me._timer);
+ }
+ me._timer = null;
+ me.doCommand();
+ }
+
+ _iconClick() {
+ if (this.searchButton) {
+ this._enterSearch();
+ } else {
+ this.focus();
+ }
+ }
+
+ _enterSearch() {
+ if (this.disabled) {
+ return;
+ }
+ if (this.searchButton && this.value && !this.readOnly) {
+ this._searchIcons.selectedIndex = 1;
+ }
+ this._fireCommand(this);
+ }
+
+ _clearSearch() {
+ if (!this.disabled && !this.readOnly && this.value) {
+ this.value = "";
+ this._fireCommand(this);
+ this._searchIcons.selectedIndex = 0;
+ return true;
+ }
+ return false;
+ }
+ }
+
+ customElements.define("search-textbox", MozSearchTextbox);
+}