feat(web): add typeahead keyboard selection to Select (Alpha) - #870
feat(web): add typeahead keyboard selection to Select (Alpha)#870adrienzheng-cb wants to merge 6 commits into
Conversation
🟡 Heimdall Review Status
🟡
|
| Code Owner | Status | Calculation | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|
| ui-systems-eng-team |
🟡
0/1
|
Denominator calculation
|
50ba4a4 to
64c2e69
Compare
The web Select (Alpha) previously only matched the first letter of an option and only when opening the listbox. Add native <select>-style typeahead: a multi-character search buffer that resets ~500ms after the last keystroke, repeated-key cycling through options that share a first letter, and matching that works both when the listbox is open and closed. Buffer/matching logic is extracted into a pure typeahead module with focused unit tests. Refs CDS-2505 Co-authored-by: Cursor <cursoragent@cursor.com>
Unify the closed- and open-state key guards behind a shared isTypeaheadKeyEvent helper and reduce comments to intent-only. Co-authored-by: Cursor <cursoragent@cursor.com>
64c2e69 to
2fa6852
Compare
Co-authored-by: Cursor <cursoragent@cursor.com>
…peahead-keyboard-selection
…peahead-keyboard-selection
cb-ekuersch
left a comment
There was a problem hiding this comment.
There is a lot going on here - i think it may help to review together. We should potentailly consider the possibility of abstracting this entire typeahead behavior into a single hook that can be called in the component render instead of inlining all this complex react logc in an already compex/long component render body
| type TypeaheadKeyEvent = Pick<KeyboardEvent, 'key' | 'ctrlKey' | 'metaKey' | 'altKey'>; | ||
|
|
||
| // Bare printable key, no modifier (shared by closed and open paths). | ||
| export function isTypeaheadKeyEvent(event: TypeaheadKeyEvent): boolean { |
There was a problem hiding this comment.
nit: why not make this KeyboardEvent?
|
|
||
| useEffect( | ||
| () => () => { | ||
| if (typeaheadResetTimeoutRef.current) clearTimeout(typeaheadResetTimeoutRef.current); |
There was a problem hiding this comment.
we should clear this in the cleanup of the effect that creates the timeout
There was a problem hiding this comment.
i see that the timeout is crated in a user interaction so i dont think this effect just to do cleanup in even necessary
| ); | ||
|
|
||
| useEffect(() => { | ||
| if (!open || !pendingTypeAheadKeyRef.current) return; |
There was a problem hiding this comment.
can we label what these different effects are for? Maybe there is value in giving them names and pulling them into custom hooks just for oganization's sake (e.g. useFocusInPortaledDropdown etc.)
Co-authored-by: Cursor <cursoragent@cursor.com>
What changed? Why?
Adds native
<select>-style typeahead keyboard selection to the webSelect (Alpha):<select>semantics.keydownlistener so keystrokes are handled consistently even after focus moves into the portaled dropdown.typeahead.tsmodule (isPrintableTypeaheadKey,normalizeOptionText,getTypeaheadMatchIndex) with focused unit tests.The previous implementation only matched the first letter of an option and only at open time, so users could not type multiple characters or cycle between same-prefixed options.
Testing
How has it been tested?
Testing instructions
Focus a web
Select (Alpha)and type characters: with the listbox closed, typing opens it and focuses the first match; with it open, continue typing to refine the prefix match, or repeat a single letter to cycle through same-prefixed options. Seetypeahead.test.tsfor the pure matching logic and the addedSelect.test.tsxcases for integration behavior.Linear: https://linear.app/coinbase/issue/CDS-2505
Change management
type=routine
risk=low
impact=sev5
automerge=false
Made with Cursor