# useTreeFocus

## Usage

Manages roving-tabindex focus and the WAI-ARIA tree keyboard model. ArrowUp/ArrowDown/Home/End roam linearly over the visible treeitems (skipping disabled ones), while ArrowRight/ArrowLeft carry tree semantics (expand/collapse, move to first-child/parent). Enter/Space activate, and printable characters trigger typeahead.

Turn on `hasRovingTabIndex` to let the hook own the single tab stop. Leave it off to keep the tab stop yourself: `onActiveChange` then tells you which item the hook moved focus to.

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

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

const { treeRef, handleKeyDown, handleFocus } = useTreeFocus({
  hasRovingTabIndex: true,
  onToggleExpand: id => toggleNode(id),
})
</script>

<template>
  <div ref="treeRef" role="tree" tabindex="-1" @keydown="handleKeyDown" @focus="handleFocus">
    <!-- treeitems -->
  </div>
</template>
```

## Best practices

| Guidance | Practices |
| --- | --- |
| Do | Use for hierarchical tree widgets: wire `onToggleExpand` to your expansion state, and either turn on `hasRovingTabIndex` or move your own tab stop from `onActiveChange`. |
| Do | Attach both `treeRef` and `handleKeyDown` to the `role="tree"` container element (`ref="treeRef"` and `@keydown="handleKeyDown"`). |
| Do | Attach `handleFocus` to the container's `@focus` when the hook owns the tab stop: that is how it is repaired after a click. |
| Don't | Use for linear lists (prefer `useListFocus`) or 2D grids (prefer `useGridRovingFocus`); those traversals differ from a tree. |

## Parameters

| Name | Type | Default | Description |
| --- | --- | --- | --- |
| `options` | `UseTreeFocusOptions` | — | Configuration object for tree focus behavior. Required; every field inside it is optional. |
| `options.itemSelector` | `string` | `'[role="treeitem"]'` | Selector for the visible treeitems within the tree, in DOM order. |
| `options.isItemDisabled` | `(item: HTMLElement) => boolean` | — | Predicate for whether a treeitem is disabled and must be skipped during navigation. Defaults to reading `data-tree-disabled` and `aria-disabled`. |
| `options.getLevel` | `(item: HTMLElement) => number` | — | Reads the 1-based nesting level of a treeitem. Defaults to the `aria-level` attribute. |
| `options.onToggleExpand` | `(id: string) => void` | — | Called to expand or collapse the treeitem with the given id: ArrowRight on a collapsed parent, ArrowLeft on an expanded parent, and Enter or Space on a parent without its own action. The id is the item's `data-tree-id`, or its `id` attribute when that is absent. |
| `options.onActivate` | `(item: HTMLElement, id: string \| undefined) => boolean \| void` | — | Called when Enter or Space activates a treeitem. Return `true` when handled; return `false` or nothing to let the hook fall back to toggling expansion. |
| `options.onActiveChange` | `(id: string \| undefined) => void` | — | Notified when the hook moves focus to a treeitem. Consumers use this to move a single roving tab stop when the hook does not own it, and to keep their own active state in step. |
| `options.hasRovingTabIndex` | `boolean` | `false` | When true, the hook owns a single roving tab stop across the visible treeitems: it stamps `tabindex` 0/-1, repairs the stop on mount, and moves it with navigation. An existing `tabindex="0"` seed is preserved on mount. Attach the returned `handleFocus` to keep the stop in sync after clicks. |
| `options.typeahead` | `boolean` | `true` | Whether typeahead is enabled: a printable character jumps to the next item whose text starts with the typed characters. |

## Returns

| Name | Type | Description |
| --- | --- | --- |
| `treeRef` | `Ref` | Ref to attach to the tree container element (`role="tree"`). |
| `handleKeyDown` | `(event: KeyboardEvent) => void` | Key down handler to attach to the tree container. |
| `handleFocus` | `(event?: FocusEvent) => void` | Focus handler to attach to the container's `@focus`. Keeps the roving tab stop in sync when `hasRovingTabIndex` is on, and does nothing otherwise, so it is always safe to attach. |
| `focusFirst` | `() => void` | Focus the first enabled visible treeitem. |
| `focusLast` | `() => void` | Focus the last enabled visible treeitem. |
