/* 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() { 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); }