summaryrefslogtreecommitdiffstats
path: root/browser/components/firefoxview/fxview-search-textbox.mjs
diff options
context:
space:
mode:
Diffstat (limited to 'browser/components/firefoxview/fxview-search-textbox.mjs')
-rw-r--r--browser/components/firefoxview/fxview-search-textbox.mjs143
1 files changed, 143 insertions, 0 deletions
diff --git a/browser/components/firefoxview/fxview-search-textbox.mjs b/browser/components/firefoxview/fxview-search-textbox.mjs
new file mode 100644
index 0000000000..1332f5f3f6
--- /dev/null
+++ b/browser/components/firefoxview/fxview-search-textbox.mjs
@@ -0,0 +1,143 @@
+/* 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/. */
+
+import { html, ifDefined } from "chrome://global/content/vendor/lit.all.mjs";
+import { MozLitElement } from "chrome://global/content/lit-utils.mjs";
+
+const lazy = {};
+ChromeUtils.defineESModuleGetters(lazy, {
+ DeferredTask: "resource://gre/modules/DeferredTask.sys.mjs",
+});
+
+const SEARCH_DEBOUNCE_RATE_MS = 500;
+const SEARCH_DEBOUNCE_TIMEOUT_MS = 1000;
+
+/**
+ * A search box that displays a search icon and is clearable. Updates to the
+ * search query trigger a `fxview-search-textbox-query` event with the current
+ * query value.
+ *
+ * There is no actual searching done here. That needs to be implemented by the
+ * `fxview-search-textbox-query` event handler. `searchTabList()` from
+ * `helpers.mjs` can be used as a starting point.
+ *
+ * @property {string} placeholder
+ * The placeholder text for the search box.
+ * @property {number} size
+ * The width (number of characters) of the search box.
+ * @property {string} pageName
+ * The hash for the page name that the search input is located on.
+ */
+export default class FxviewSearchTextbox extends MozLitElement {
+ static properties = {
+ placeholder: { type: String },
+ size: { type: Number },
+ pageName: { type: String },
+ };
+
+ static queries = {
+ clearButton: ".clear-icon",
+ input: "input",
+ };
+
+ #query = "";
+
+ constructor() {
+ super();
+ this.searchTask = new lazy.DeferredTask(
+ () => this.#dispatchQueryEvent(),
+ SEARCH_DEBOUNCE_RATE_MS,
+ SEARCH_DEBOUNCE_TIMEOUT_MS
+ );
+ }
+
+ disconnectedCallback() {
+ super.disconnectedCallback();
+ if (!this.searchTask?.isFinalized) {
+ this.searchTask?.finalize();
+ }
+ }
+
+ focus() {
+ this.input.focus();
+ }
+
+ blur() {
+ this.input.blur();
+ }
+
+ onInput(event) {
+ this.#query = event.target.value.trim();
+ event.preventDefault();
+ this.onSearch();
+ }
+
+ /**
+ * Handler for query updates from keyboard input, and textbox clears from 'X'
+ * button.
+ */
+ onSearch() {
+ this.searchTask?.arm();
+ this.requestUpdate();
+ }
+
+ clear(event) {
+ if (
+ event.type == "click" ||
+ (event.type == "keydown" && event.code == "Enter") ||
+ (event.type == "keydown" && event.code == "Space")
+ ) {
+ this.#query = "";
+ event.preventDefault();
+ this.onSearch();
+ }
+ }
+
+ #dispatchQueryEvent() {
+ window.scrollTo(0, 0);
+ this.dispatchEvent(
+ new CustomEvent("fxview-search-textbox-query", {
+ bubbles: true,
+ composed: true,
+ detail: { query: this.#query },
+ })
+ );
+
+ Services.telemetry.recordEvent(
+ "firefoxview_next",
+ "search_initiated",
+ "search",
+ null,
+ {
+ page: this.pageName,
+ }
+ );
+ }
+
+ render() {
+ return html`
+ <link rel="stylesheet" href="chrome://browser/content/firefoxview/fxview-search-textbox.css" />
+ <div class="search-container">
+ <div class="search-icon"></div>
+ <input
+ type="search"
+ .placeholder=${ifDefined(this.placeholder)}
+ .size=${ifDefined(this.size)}
+ .value=${this.#query}
+ @input=${this.onInput}
+ ></input>
+ <div
+ class="clear-icon"
+ role="button"
+ tabindex="0"
+ ?hidden=${!this.#query}
+ @click=${this.clear}
+ @keydown=${this.clear}
+ data-l10n-id="firefoxview-search-text-box-clear-button"
+ ></div>
+ </div>`;
+ }
+}
+
+customElements.define("fxview-search-textbox", FxviewSearchTextbox);