export class VoiceSelector { constructor(voiceService) { this.voiceService = voiceService; this.elements = { voiceSearch: document.getElementById('voice-search'), voiceDropdown: document.getElementById('voice-dropdown'), voiceOptions: document.getElementById('voice-options'), selectedVoices: document.getElementById('selected-voices') }; this.setupEventListeners(); } setupEventListeners() { // Voice search focus this.elements.voiceSearch.addEventListener('focus', () => { this.elements.voiceDropdown.classList.add('show'); }); // Voice search this.elements.voiceSearch.addEventListener('input', (e) => { const filteredVoices = this.voiceService.filterVoices(e.target.value); this.renderVoiceOptions(filteredVoices); }); // Voice selection - handle clicks on the entire voice option this.elements.voiceOptions.addEventListener('mousedown', (e) => { e.preventDefault(); // Prevent blur on search input const voiceOption = e.target.closest('.voice-option'); if (!voiceOption) return; const voice = voiceOption.dataset.voice; if (!voice) return; const isSelected = voiceOption.classList.contains('selected'); if (!isSelected) { this.voiceService.addVoice(voice); } else { this.voiceService.removeVoice(voice); } voiceOption.classList.toggle('selected'); this.updateSelectedVoicesDisplay(); // Keep focus on search input requestAnimationFrame(() => { this.elements.voiceSearch.focus(); }); }); // Weight adjustment this.elements.selectedVoices.addEventListener('input', (e) => { if (e.target.type === 'number') { const voice = e.target.dataset.voice; let weight = parseFloat(e.target.value); // Ensure weight is between 0.1 and 10 weight = Math.max(0.1, Math.min(10, weight)); e.target.value = weight; this.voiceService.updateWeight(voice, weight); } }); // Remove selected voice this.elements.selectedVoices.addEventListener('click', (e) => { if (e.target.classList.contains('remove-voice')) { e.preventDefault(); e.stopPropagation(); const voice = e.target.dataset.voice; this.voiceService.removeVoice(voice); this.updateVoiceOptionState(voice, false); this.updateSelectedVoicesDisplay(); } }); // Handle clicks outside to close dropdown document.addEventListener('mousedown', (e) => { // Don't handle clicks in selected voices area if (this.elements.selectedVoices.contains(e.target)) { return; } // Don't close if clicking in search or dropdown if (this.elements.voiceSearch.contains(e.target) || this.elements.voiceDropdown.contains(e.target)) { return; } this.elements.voiceDropdown.classList.remove('show'); this.elements.voiceSearch.blur(); }); this.elements.voiceSearch.addEventListener('blur', () => { if (!this.elements.voiceSearch.value) { this.updateSearchPlaceholder(); } }); } renderVoiceOptions(voices) { this.elements.voiceOptions.innerHTML = voices .map(voice => `