# useKeyboardHint

## 使用方法

一组靠方向键移动的控件，第一次被键盘点进来时，在当前项旁边短暂提示一句「用方向键移动」。这是给看得见键盘用户的：告诉他方向键在这个组里能走。提示里的方向键用 `Kbd` 画，用 `fixed` 定位跟在当前项旁边，所以不会被滚动容器裁掉，并且是 `aria-hidden`。按过方向键、超时、或者焦点离开这个组，它都会自己关掉；关掉之后这个实例就不再提示。自己搭 roving-tabindex 控件时才会直接用到这个 hook；`Toolbar`、`TabList`、`SegmentedControl` 不会替你挂。

hook 返回的 `Hint` 组件要放在这一组容器里面，作为最后一个子元素。

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

## 建议

| 指引 | 实践 |
| --- | --- |
| 推荐 | 返回的 `onFocus` 和 `onKeyDown` 要和你自己的处理函数叠加，不要替换：先调 `onKeyDown`（它只负责收起提示，不会拦截），再调你的导航处理。 |
| 推荐 | `Hint` 渲染成这一组容器的最后一个子元素；它是 `fixed` 定位且 `aria-hidden`，所以不影响布局，也不进无障碍树。 |
| 推荐 | `orientation` 要和控件真正响应的方向一致，提示才会画对图标。 |
| 避免 | 用在单个控件，或者没有 roving-tabindex 导航的控件上；只有「方向键在一组里移动焦点」时这个提示才有意义。 |

## 示例

### useKeyboardHint — 方向键提示

自己搭的 roving-tabindex 工具栏，第一次被键盘聚焦时通过 useKeyboardHint 短暂提示方向键，让看得见的用户知道组里可以用方向键移动。

```vue
<script setup lang="ts">
import type { ComponentPublicInstance } from 'vue'
import { useKeyboardHint, useListFocus } from '@astryx-vue/core'
import { Button } from '@astryx-vue/core/Button'
import { Text } from '@astryx-vue/core/Text'

const actions = ['Bold', 'Italic', 'Underline', 'Strikethrough']

const list = useListFocus({
  itemSelector: '[data-hint-item]',
  orientation: 'horizontal',
  hasRovingTabIndex: true,
})
const hint = useKeyboardHint({ orientation: 'horizontal' })

// 模板引用写进 hook 的 listRef，listRef 只由 hook 持有。
function bindList(el: Element | ComponentPublicInstance | null): void {
  list.listRef.value = el instanceof HTMLElement ? el : null
}

function onFocus(event: FocusEvent): void {
  hint.onFocus(event)
}

function onBlur(event: FocusEvent): void {
  hint.onBlur(event)
}

function onKeyDown(event: KeyboardEvent): void {
  hint.onKeyDown(event)
  list.handleKeyDown(event)
}
</script>

<template>
  <div class="hint-preview">
    <Text type="supporting" color="secondary">
      {{ 'Tab into the group, then press the arrow keys.' }}
    </Text>
    <!-- eslint-disable-next-line vue-a11y/no-static-element-interactions -- role="toolbar" 的焦点停在内部按钮上，容器只接冒泡上来的焦点与方向键。 -->
    <div
      :ref="bindList"
      class="hint-preview__group"
      role="toolbar"
      aria-label="Text formatting"
      @focusin="onFocus"
      @focusout="onBlur"
      @keydown="onKeyDown"
    >
      <Button
        v-for="action in actions"
        :key="action"
        :label="action"
        data-hint-item
        size="sm"
        variant="ghost"
      />
      <component :is="hint.Hint" />
    </div>
  </div>
</template>

<style scoped>
.hint-preview {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
}

.hint-preview__group {
  display: flex;
  align-items: center;
  gap: 2px;
  padding: 4px;
  border: 1px solid var(--astryx-color-border);
  border-radius: var(--astryx-radius-container, 0.75rem);
  background: var(--astryx-color-background-surface);
}
</style>
```

## 参数

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `options` | `UseKeyboardHintOptions` | `{}` | 提示的配置对象，可以不传。 |
| `options.orientation` | `'horizontal' \| 'vertical' \| 'both'` | `'horizontal'` | 这一组控件用哪个方向键导航，决定提示里画哪几个箭头（横向 ← →，纵向 ↑ ↓，双向四个都画）。 |
| `options.dismissAfterMs` | `number` | `3000` | 出现之后过多少毫秒自己消失。 |
| `options.isEnabled` | `boolean` | `true` | 是否启用提示。某处不想提示时（例如控件被禁用或只读）设为 false。 |

## 返回值

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `Hint` | `Component` | 提示元素本身，渲染在这一组容器的最后，例如 `<component :is="hint.Hint" />`。它自己用 `fixed` 定位、用 `Kbd` 画方向键、自己管显示与隐藏。无条件渲染即可。 |
| `visible` | `Ref<boolean>` | 提示当前是否显示。`Hint` 读的就是它；自己画提示时也可以用。 |
| `anchor` | `Ref` | 提示跟在哪个元素旁边，也就是当前聚焦的那一项。第一次出现之前是 `null`。 |
| `orientation` | `ComputedRef` | 补过默认值的方向。 |
| `onFocus` | `(event: FocusEvent) => void` | 绑在容器的 `focusin` 上（`@focusin`），因为 focus 不冒泡。第一次从组外靠键盘（`:focus-visible`）进入时显示提示。 |
| `onBlur` | `(event: FocusEvent) => void` | 绑在容器的 `focusout` 上（`@focusout`）。焦点彻底离开这一组时藏起提示，并算作这次提示已经用完，这个实例不会再提示；焦点在组内移动时只把提示挪到新的当前项。 |
| `onKeyDown` | `(event: KeyboardEvent) => void` | 绑在容器的 `keydown` 上。第一次按方向键时收起提示（用户已经发现这个交互了）。它不会 preventDefault，也不会停止传播。 |
