Manual UI Behaviors typeahead

typeahead

elements man ui/behaviors/typeahead

Jumps to the item whose text starts with what the user typed. Characters typed within timeout ms accumulate ("ap" finds Apple before Avocado), repeating one character cycles through the items that start with it, and matching ignores case and accents ("e" finds "Éclair"). rovingFocus (opens in a new tab) and activeDescendant (opens in a new tab) run it from their typeahead option; call it directly in a list that handles its own keys.

Signatures
function typeahead(timeout?: number): Typeahead; function isTypeaheadKey(e: KeyboardEvent): boolean; interface Typeahead { (key: string, items: HTMLElement[], current: HTMLElement | null): HTMLElement | null; active(): boolean; }
Argument Type Default What it does
timeout number 1000 ms after the last character before the search starts over.

The returned search takes the key, the items and the current item, and returns the item to move to, or null when nothing moves, which includes the current item staying the best match. Text is matched on data-text-value, else the item's trimmed text. Disabled items are the caller's to filter out. isTypeaheadKey(e) is true for one printable character with no modifier.

A Space continues a search in progress but never starts one, so Space keeps its own meaning (select, toggle) on a fresh press. active() is true while a search is running: check it after the call to know whether the key was taken, and prevent its default when it was.

In a template of your own

app/shared/templates/color-list/index.ehtml
import { typeahead, isTypeaheadKey, type Typeahead } from "@elements/ui/behaviors"; function onKeyDown(e: KeyboardEvent, search: Typeahead) { if (!isTypeaheadKey(e)) { return; } let list = e.currentTarget as HTMLElement; let items = Array.from(list.querySelectorAll<HTMLElement>("[role=option]")); let match = search(e.key, items, document.activeElement as HTMLElement | null); if (match) { match.focus(); } if (match || search.active()) { e.preventDefault(); } } <ColorList (colors: string[], private search: Typeahead = typeahead())> <ul role="listbox" onkeydown={(e) => onKeyDown(e, search)}> <li e:for={color of colors} role="option" tabindex="0">{color}</li> </ul> </ColorList>

Writes

Nothing.

Used by

DropdownMenu (opens in a new tab), ContextMenu (opens in a new tab), Menubar (opens in a new tab), Select (opens in a new tab), Tree (opens in a new tab), and every template with a typeahead option on roving focus or active descendant.