/** * Search Bar Component * Search input with mic and search icons on the right * Includes speech recognition functionality */ import { micIcon } from "../icons/mic.js"; import { searchIcon } from "../icons/search.js"; class SearchBar extends HTMLElement { constructor() { super(); this.attachShadow({ mode: "open" }); this.recognition = null; this.isListening = false; } connectedCallback() { this.render(); this.addEventListeners(); this.initSpeechRecognition(); } disconnectedCallback() { if (this.recognition) { this.recognition.stop(); } } initSpeechRecognition() { // Check for browser support const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition; if (!SpeechRecognition) { console.warn("Speech recognition is not supported in this browser"); const micButton = this.shadowRoot.querySelector(".mic-button"); if (micButton) { micButton.style.display = "none"; } return; } this.recognition = new SpeechRecognition(); this.recognition.continuous = false; this.recognition.interimResults = true; this.recognition.lang = "nl-NL"; this.recognition.onstart = () => { this.isListening = true; this.updateMicButtonState(); }; this.recognition.onend = () => { this.isListening = false; this.updateMicButtonState(); }; this.recognition.onresult = (event) => { const input = this.shadowRoot.querySelector(".search-input"); const transcript = Array.from(event.results) .map((result) => result[0].transcript) .join(""); input.value = transcript; // Dispatch input event for live updates this.dispatchEvent( new CustomEvent("search-input", { detail: { query: transcript }, bubbles: true, composed: true, }) ); // If final result, also dispatch search event if (event.results[0].isFinal) { this.dispatchEvent( new CustomEvent("search", { detail: { query: transcript }, bubbles: true, composed: true, }) ); } }; this.recognition.onerror = (event) => { console.error("Speech recognition error:", event.error); this.isListening = false; this.updateMicButtonState(); }; } updateMicButtonState() { const micButton = this.shadowRoot.querySelector(".mic-button"); if (micButton) { micButton.classList.toggle("listening", this.isListening); micButton.setAttribute("aria-pressed", this.isListening.toString()); } } toggleSpeechRecognition() { if (!this.recognition) return; if (this.isListening) { this.recognition.stop(); } else { this.recognition.start(); } } addEventListeners() { const input = this.shadowRoot.querySelector(".search-input"); const form = this.shadowRoot.querySelector(".search-form"); const micButton = this.shadowRoot.querySelector(".mic-button"); const searchButton = this.shadowRoot.querySelector(".search-button"); form.addEventListener("submit", (e) => { e.preventDefault(); this.dispatchEvent( new CustomEvent("search", { detail: { query: input.value }, bubbles: true, composed: true, }) ); }); input.addEventListener("input", (e) => { this.dispatchEvent( new CustomEvent("search-input", { detail: { query: e.target.value }, bubbles: true, composed: true, }) ); }); micButton.addEventListener("click", () => { this.toggleSpeechRecognition(); }); searchButton.addEventListener("click", () => { this.dispatchEvent( new CustomEvent("search", { detail: { query: input.value }, bubbles: true, composed: true, }) ); }); } render() { const iconColor = "#951D51"; this.shadowRoot.innerHTML = `
`; } } customElements.define("search-bar", SearchBar);