// Companion to the actor-typeahead web component, which keeps its state in a // closed shadow root. Two gaps are bridged here: // // - Enter: the component swallows every Enter keydown (preventDefault) so // that Enter can pick the highlighted suggestion. With no highlight that // leaves Enter doing nothing, breaking implicit form submission. This // mirrors the relevant part of the component's state: whether the user has // moved the highlight since last typing. // - Click: picking a suggestion with the pointer only copies the handle into // the input, so signing in would always need a second click on the button. // Clicking an account should just sign in as it; the button stays for the // typed-by-hand path. // // Kept free of DOM globals so it can be unit-tested in node; the entry points // import 'actor-typeahead' themselves to register the element. const NAV_KEYS = ['ArrowDown', 'ArrowUp', 'PageDown', 'PageUp'] export function wireTypeaheadSubmit( input: EventTarget & { value: string }, form: { requestSubmit(): void }, host?: EventTarget, ): void { let navigating = false let typed = input.value input.addEventListener('input', () => { navigating = false typed = input.value }) input.addEventListener('keydown', (evt) => { const key = (evt as KeyboardEvent).key if (NAV_KEYS.includes(key)) { navigating = true } else if (key === 'Escape') { navigating = false } else if (key === 'Enter') { console.debug('[substandard] typeahead enter:', navigating ? 'select suggestion' : 'submit') if (navigating) { // actor-typeahead consumes this Enter to fill in the suggestion. navigating = false } else { form.requestSubmit() } } }) // A pointer pick is visible out here as a pointerup retargeted to the host // element (clicks on the slotted input keep their own target), and the // component's shadow-root listener has already run by then, copying the // handle into the input without an 'input' event. A retargeted pointerup // that changed the value is therefore a picked account. Enter picks stay // two-step: their synthetic pointerup is not composed and never gets here. host?.addEventListener('pointerup', (evt) => { if (evt.target !== host) return if (!input.value || input.value === typed) return typed = input.value navigating = false console.debug('[substandard] typeahead click: sign in as', input.value) form.requestSubmit() }) }