type UiDom = { connectButton: HTMLButtonElement; preconnectNickname: HTMLInputElement; disconnectButton: HTMLButtonElement; focusGridButton: HTMLButtonElement; settingsButton: HTMLButtonElement; closeSettingsButton: HTMLButtonElement; audioInputSelect: HTMLSelectElement; audioOutputSelect: HTMLSelectElement; settingsModal: HTMLDivElement; canvas: HTMLCanvasElement; }; type UiBindingsDeps = { dom: UiDom; sanitizeName: (value: string) => string; nicknameStorageKey: string; updateConnectAvailability: () => void; connect: () => Promise; disconnect: () => void; openSettings: () => void; closeSettings: () => void; updateStatus: (message: string) => void; sfxUiBlip: () => void; setupLocalMedia: (audioDeviceId: string) => Promise; setPreferredInput: (id: string, name: string) => void; setPreferredOutput: (id: string, name: string) => void; updateDeviceSummary: () => void; setOutputDevice: (id: string) => Promise; persistOnUnload: () => void; }; export function setupUiHandlers(deps: UiBindingsDeps): void { window.addEventListener('pagehide', deps.persistOnUnload); window.addEventListener('beforeunload', deps.persistOnUnload); deps.dom.connectButton.addEventListener('click', () => { void deps.connect(); }); deps.dom.preconnectNickname.addEventListener('input', () => { deps.updateConnectAvailability(); }); deps.dom.preconnectNickname.addEventListener('change', () => { const clean = deps.sanitizeName(deps.dom.preconnectNickname.value); deps.dom.preconnectNickname.value = clean; if (clean) { localStorage.setItem(deps.nicknameStorageKey, clean); } else { localStorage.removeItem(deps.nicknameStorageKey); } deps.updateConnectAvailability(); }); deps.dom.preconnectNickname.addEventListener('keydown', (event) => { if (event.key === 'Enter' && !deps.dom.connectButton.disabled) { event.preventDefault(); void deps.connect(); } }); deps.dom.disconnectButton.addEventListener('click', () => { deps.disconnect(); }); deps.dom.focusGridButton.addEventListener('click', () => { deps.dom.canvas.focus(); deps.updateStatus('Chat Grid focused.'); deps.sfxUiBlip(); }); deps.dom.settingsButton.addEventListener('click', () => { deps.openSettings(); }); deps.dom.closeSettingsButton.addEventListener('click', () => { deps.closeSettings(); }); deps.dom.audioInputSelect.addEventListener('change', (event) => { const target = event.target as HTMLSelectElement; if (!target.value) return; deps.setPreferredInput(target.value, target.selectedOptions[0]?.text || ''); deps.updateDeviceSummary(); void deps.setupLocalMedia(target.value); }); deps.dom.audioOutputSelect.addEventListener('change', (event) => { const target = event.target as HTMLSelectElement; deps.setPreferredOutput(target.value, target.selectedOptions[0]?.text || ''); deps.updateDeviceSummary(); void deps.setOutputDevice(target.value); }); deps.dom.settingsModal.addEventListener('keydown', (event) => { if (event.key !== 'Tab') return; const focusable = Array.from(deps.dom.settingsModal.querySelectorAll('select, button')); if (focusable.length === 0) return; const first = focusable[0]; const last = focusable[focusable.length - 1]; if (event.shiftKey && document.activeElement === first) { last.focus(); event.preventDefault(); return; } if (!event.shiftKey && document.activeElement === last) { first.focus(); event.preventDefault(); } }); }