# useTypeahead

## Usage

Adds APG type-to-focus search to a collection: printable keystrokes are buffered (resetting after a pause), and the first item whose label starts with the buffer is reported through `onMatch`. Pressing the same letter repeatedly cycles through the matches rather than filtering deeper. It moves nothing itself; pair it with the collection's own focus management, most often `useListFocus` or `useGridRovingFocus`.

`onKeyDown` takes a native `KeyboardEvent` and returns `true` when it consumed a printable character, so let it look at the event first and fall through to arrow-key navigation only when it returns `false`. Space counts as part of the query only when something is already typed, so Space still activates the focused item. `reset()` clears a pending buffer, for example when the collection closes.

```ts
import { useTypeahead } from '@astryx-vue/core'
```

```vue
<script setup lang="ts">
import { useTypeahead } from '@astryx-vue/core'
import { ref } from 'vue'

const items = ['Apple', 'Banana', 'Cherry']
const activeIndex = ref(-1)

const { onKeyDown } = useTypeahead({
  getItemLabels: () => items,
  getCurrentIndex: () => activeIndex.value,
  onMatch: (index) => {
    activeIndex.value = index
  },
})

function handleArrows(event: KeyboardEvent): void {
  const step = event.key === 'ArrowDown' ? 1 : event.key === 'ArrowUp' ? -1 : 0
  if (step === 0)
    return
  event.preventDefault()
  activeIndex.value = (activeIndex.value + step + items.length) % items.length
}
</script>

<template>
  <ul role="listbox" tabindex="0" @keydown="(event) => { if (!onKeyDown(event)) handleArrows(event) }">
    <li
      v-for="(item, index) in items"
      :key="item"
      role="option"
      :aria-selected="index === activeIndex"
      tabindex="-1"
    >
      {{ item }}
    </li>
  </ul>
</template>
```

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Wire `onMatch` to the focus manager you already have (`useListFocus().focusItem`) instead of moving focus yourself. |
| Do | Let it see the key event first and fall through to arrow-key navigation only when it returns `false`. |
| Do | Pass `getCurrentIndex` so repeated presses of one letter walk through matches instead of sticking on the first. |
| Don't | Use it on a text input; the field already receives the characters, and typeahead would fight the value. |

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `UseTypeaheadOptions` | — | Configuration object. |
| `options.getItemLabels` | `() => ReadonlyArray<string \| null \| undefined>` | — | Returns the item labels in DOM order. A null or empty entry marks a non-matchable slot and keeps indices aligned with the caller's items. |
| `options.onMatch` | `(index: number) => void` | — | Called with the index of the matched item so the caller can focus or select it; typically `useListFocus`'s `focusItem`. |
| `options.getCurrentIndex` | `() => number` | `() => -1` | The index to search from, usually the focused item, so repeated presses of one letter cycle through matches. A negative value means nothing is current. |
| `options.resetMs` | `number` | `750` | Milliseconds of inactivity after which the typed buffer resets. |
| `options.isDisabled` | `(index: number) => boolean` | — | Whether an index should be skipped, e.g. disabled items. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `onKeyDown` | `(event: KeyboardEvent) => boolean` | Keydown handler. Returns `true` when it consumed a printable character, so the caller can stop its own key handling. |
| `reset` | `() => void` | Clears the pending buffer, e.g. when the collection closes. |
