# useTypeahead

## 使用方法

给一组选项加上 APG 里的「打字跳转」：打出来的字符先攒在缓冲区里，停一会儿就清空；只要某一项的标签以这段字符开头，就把它的下标交给 `onMatch`。同一个字母一直按，会在匹配上的几项之间轮着走，而不是把条件收得更窄。它自己不动焦点，要跟你已有的焦点管理配合，通常就是 `useListFocus` 或 `useGridRovingFocus`。

`onKeyDown` 收的是原生 `KeyboardEvent`，吃掉一个可打印字符时返回 `true`。所以先让它看一眼事件，它返回 `false` 时再交给方向键。空格只有在缓冲区里已经有字的时候才算查询的一部分，所以空格照样能激活当前项。`reset()` 清掉还没超时的缓冲区，比如这组选项关掉的时候。

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

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

const items = ['苹果', '香蕉', '樱桃']
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>
```

## 建议

| 指引 | 做法 |
| --- | --- |
| Do | `onMatch` 接到你已有的焦点管理上（比如 `useListFocus().focusItem`），不要自己挪焦点。 |
| Do | 让按键事件先过它，它返回 `false` 时再交给方向键。 |
| Do | 传 `getCurrentIndex`，同一个字母才是在匹配项之间走，而不是一直卡在第一项。 |
| Don't | 把它用在文本框上。字符本来就会进输入框，再来一套打字跳转只会跟输入的值打架。 |

## 参数

| 参数 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `options` | `UseTypeaheadOptions` | — | 配置对象。 |
| `options.getItemLabels` | `() => ReadonlyArray<string \| null \| undefined>` | — | 按 DOM 顺序返回各项的标签。某一项是 null 或空字符串，表示这个位置不参与匹配，下标仍然跟调用方的项对得上。 |
| `options.onMatch` | `(index: number) => void` | — | 匹配到之后把下标告诉你，你可以聚焦或者选中它；通常接 `useListFocus` 的 `focusItem`。 |
| `options.getCurrentIndex` | `() => number` | `() => -1` | 从哪一项开始找，一般是当前聚焦的那项。同一个字母一直按，就会在匹配项之间轮着走。负的数表示当前没有项。 |
| `options.resetMs` | `number` | `750` | 多久没有新按键就清空缓冲区，单位毫秒。 |
| `options.isDisabled` | `(index: number) => boolean` | — | 这个下标要不要跳过，比如禁用项。 |

## 返回值

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `onKeyDown` | `(event: KeyboardEvent) => boolean` | 按键处理函数。吃掉一个可打印字符时返回 `true`，调用方就不用再自己处理这个键。 |
| `reset` | `() => void` | 清掉还没超时的缓冲区，比如这组选项关掉的时候。 |
