# useGridFocus

## Usage

Manages keyboard navigation within a 2D grid following the WAI-ARIA grid pattern. Supports arrow keys for cell-to-cell navigation, Home/End for row boundaries, Ctrl+Home/Ctrl+End for grid boundaries, and Page Up/Down for custom callbacks (e.g., month navigation in calendars). Boundary navigation callbacks allow cross-grid navigation.

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

The hook reads the cells out of the DOM, so `gridRef` and `handleKeyDown` go on the grid container. `handleFocus` goes on the container's `focusin` event: a `focus` event does not bubble in the DOM, so `@focus` on the container would never hear the cells.

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Use for calendar date grids: wire `onPageUp` / `onPageDown` to month navigation and `onNavigateBefore` / `onNavigateAfter` for cross-month arrow key navigation. |
| Do | Attach both `gridRef` and `handleKeyDown` to the grid container element. |
| Do | For roving-tabindex grids (e.g. Calendar), set `hasRovingTabIndex: true` and attach `handleFocus` to the container's `focusin` event; seed one focus target with `tabindex="0"` and the hook repairs and moves it. |
| Don't | Use for simple linear lists; prefer `useListFocus` for 1D navigation. |

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `UseGridFocusOptions` | — | Configuration object for grid focus behavior. Required. |
| `options.columns` | `number \| MaybeRefOrGetter<number>` | — | Number of columns in the grid. Used for up/down navigation (moves by this many cells). Required. |
| `options.cellSelector` | `string` | `'button:not([disabled]), [tabindex]:not([tabindex="-1"])'` | Selector for cells within the grid. Should match ALL cell positions in DOM order (including disabled/empty) so grid geometry is preserved. |
| `options.isCellFocusable` | `(cell: HTMLElement) => boolean` | every matched cell is focusable | Predicate for whether a matched cell can receive focus. Omit to treat every matched cell as focusable. |
| `options.getFocusTarget` | `(cell: HTMLElement) => HTMLElement \| null` | the cell itself | Resolves the element to focus for a cell, e.g. a button inside a `role="gridcell"` wrapper. |
| `options.onNavigateBefore` | `(column: number, offset: number) => void` | — | Callback when navigation would go before the first cell. Receives the current column index and the offset (1 for horizontal, columns for vertical). |
| `options.onNavigateAfter` | `(column: number, offset: number) => void` | — | Callback when navigation would go after the last cell. Receives the current column index and the offset. |
| `options.onPageUp` | `() => void` | — | Callback for Page Up (e.g., navigate to the previous month in calendars). Without it the key is left alone. |
| `options.onPageDown` | `() => void` | — | Callback for Page Down (e.g., navigate to the next month in calendars). Without it the key is left alone. |
| `options.hasRovingTabIndex` | `boolean \| MaybeRefOrGetter<boolean>` | `false` | Owns a single roving tab stop across the grid: one focusable cell (its resolved focus target) carries `tabindex="0"`, the rest `-1`. Stamped and repaired after mount and moved with arrow navigation. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `gridRef` | `Ref` | Ref to attach to the grid container element. |
| `handleKeyDown` | `(event: KeyboardEvent) => void` | Key down handler to attach to the grid container. |
| `handleFocus` | `(event: FocusEvent) => void` | Focus handler for the grid container, bound to `focusin`. Keeps the roving tab stop in sync when `hasRovingTabIndex` is enabled; a no-op otherwise, so it is always safe to attach. |
| `focusCell` | `(index: number) => void` | Focus a specific cell by index (clamped to the valid range). A cell that cannot take focus is left alone. |
| `focusFirst` | `() => void` | Focus the first focusable cell in the grid. |
| `focusLast` | `() => void` | Focus the last focusable cell in the grid. |
