fune/toolkit/content/widgets/search-textbox.js

243 lines
6.5 KiB
JavaScript

/* 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.
{
const HTML_NS = "http://www.w3.org/1999/xhtml";
class MozSearchTextbox extends MozXULElement {
constructor() {
super();
this.inputField = document.createElementNS(HTML_NS, "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) {
return (this.inputField[property] = val);
},
});
}
this.addEventListener("input", event => {
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;
});
this.addEventListener("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;
}
});
}
static get inheritedAttributes() {
return {
".textbox-input":
"value,maxlength,disabled,size,readonly,placeholder,tabindex,accesskey,mozactionhint,spellcheck",
".textbox-search-icon":
"src=image,label=searchbuttonlabel,searchbutton,disabled",
".textbox-search-clear": "disabled",
};
}
connectedCallback() {
if (this.delayConnectedCallback()) {
return;
}
this.textContent = "";
const textboxSign = document.createXULElement("image");
textboxSign.className = "textbox-search-sign";
const input = this.inputField;
input.className = "textbox-input";
input.setAttribute("mozactionhint", "search");
input.addEventListener("focus", () =>
this.setAttribute("focused", "true")
);
input.addEventListener("blur", () => this.removeAttribute("focused"));
const searchBtn = (this._searchButtonIcon = document.createXULElement(
"image"
));
searchBtn.className = "textbox-search-icon";
searchBtn.addEventListener("click", e => this._iconClick(e));
// TODO: Bug 1534799 - Convert string to Fluent and use manual DOM construction
let clearBtn = MozXULElement.parseXULToFragment(
`
<image class="textbox-search-clear" label="&searchTextBox.clear.label;"/>
`,
["chrome://global/locale/textcontext.dtd"]
);
clearBtn = this._searchClearIcon = clearBtn.querySelector(
".textbox-search-clear"
);
clearBtn.addEventListener("click", () => this._clearSearch());
const deck = (this._searchIcons = document.createXULElement("deck"));
deck.className = "textbox-search-icons";
deck.append(searchBtn, clearBtn);
this.append(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();
}
set timeout(val) {
this.setAttribute("timeout", val);
return val;
}
get timeout() {
return parseInt(this.getAttribute("timeout")) || 500;
}
set searchButton(val) {
if (val) {
this.setAttribute("searchbutton", "true");
this.removeAttribute("aria-autocomplete");
// Hack for the button to get the right accessible:
// If you update the 'onclick' event handler code within the
// _searchButtonIcon you also have to update the sha512 hash in the
// CSP of about:addons within extensions.xhtml.
this._searchButtonIcon.setAttribute("onclick", "true");
} else {
this.removeAttribute("searchbutton");
this._searchButtonIcon.removeAttribute("onclick");
this.setAttribute("aria-autocomplete", "list");
}
return val;
}
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);
}
return val;
}
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");
}
return val;
}
get disabled() {
return this.inputField.disabled;
}
_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);
}