# useGridFocus

## 使用方法

按 WAI-ARIA 的 grid 模式管二维网格里的键盘导航。方向键一格一格走，Home/End 走到当前行的首尾，Ctrl+Home/Ctrl+End 走到整张网格的首尾，Page Up/Down 交给调用方（日历里就是翻月）。走到边界时可以把事件交出去，让另一张网格接住焦点，例如翻到上个月后继续用方向键。

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

这个函数从 DOM 里读格子，所以 `gridRef` 和 `handleKeyDown` 挂在网格容器上。`handleFocus` 挂在容器的 `focusin` 上：DOM 里 `focus` 不冒泡，挂在容器的 `@focus` 上永远听不到格子的焦点。

## 建议

| 指引 | 做法 |
| --- | --- |
| 推荐 | 用在日历这种日期网格上：`onPageUp` / `onPageDown` 接翻月，`onNavigateBefore` / `onNavigateAfter` 接跨月继续用方向键。 |
| 推荐 | `gridRef` 和 `handleKeyDown` 都挂在网格容器上。 |
| 推荐 | 用 roving tabindex 的网格（例如日历）打开 `hasRovingTabIndex: true`，把 `handleFocus` 挂在容器的 `focusin` 上；先给一个目标元素写 `tabindex="0"`，函数会自己修正并移动它。 |
| 不要 | 用它做普通的一维列表；一维导航用 `useListFocus`。 |

## 参数

| 名称 | 类型 | 默认值 | 说明 |
| --- | --- | --- | --- |
| `options` | `UseGridFocusOptions` | — | 必填。网格焦点行为的配置对象。 |
| `options.columns` | `number \| MaybeRefOrGetter<number>` | — | 必填。网格列数。上下方向键按这个数跨格。 |
| `options.cellSelector` | `string` | `'button:not([disabled]), [tabindex]:not([tabindex="-1"])'` | 选格子的选择器。要按 DOM 顺序选中 所有 位置，禁用和空的也算，这样行列几何才对得上。 |
| `options.isCellFocusable` | `(cell: HTMLElement) => boolean` | 每个选中的格子都能接焦点 | 判断某个格子能不能接焦点。不传就当成都能接。 |
| `options.getFocusTarget` | `(cell: HTMLElement) => HTMLElement \| null` | 格子本身 | 从一个格子算出真正要聚焦的元素，例如 `role="gridcell"` 里包着的按钮。 |
| `options.onNavigateBefore` | `(column: number, offset: number) => void` | — | 焦点要走到第一个格子之前时调用。拿到当前列序号和步长（横向是 1，纵向是列数）。 |
| `options.onNavigateAfter` | `(column: number, offset: number) => void` | — | 焦点要走到最后一个格子之后时调用。参数同上。 |
| `options.onPageUp` | `() => void` | — | 按 Page Up 时调用（例如日历翻到上个月）。不传就不拦这个键。 |
| `options.onPageDown` | `() => void` | — | 按 Page Down 时调用（例如日历翻到下个月）。不传就不拦这个键。 |
| `options.hasRovingTabIndex` | `boolean \| MaybeRefOrGetter<boolean>` | `false` | 整张网格只留一个 Tab 落脚点：当前格子的目标元素 `tabindex="0"`，其余 `-1`。挂载后补上，方向键移动时跟着走。 |

## 返回值

| 名称 | 类型 | 说明 |
| --- | --- | --- |
| `gridRef` | `Ref` | 挂到网格容器上的模板 ref。 |
| `handleKeyDown` | `(event: KeyboardEvent) => void` | 挂到网格容器的 `keydown` 上。 |
| `handleFocus` | `(event: FocusEvent) => void` | 挂到网格容器的 `focusin` 上。开着 `hasRovingTabIndex` 时保持 Tab 落脚点同步，没开就什么都不做，挂上也无害。 |
| `focusCell` | `(index: number) => void` | 按序号聚焦某个格子，序号会夹到合法范围里。接不了焦点的格子会跳过。 |
| `focusFirst` | `() => void` | 聚焦第一个能接焦点的格子。 |
| `focusLast` | `() => void` | 聚焦最后一个能接焦点的格子。 |
