# `ExRatatui.Focus`
[🔗](https://github.com/mcass19/ex_ratatui/blob/v0.8.2/lib/ex_ratatui/focus.ex#L1)

Focus management for multi-panel apps.

`Focus` is a tiny state machine over an ordered ring of focusable
IDs. You declare the IDs up front, feed every key event through
`handle_key/2`, and pattern-match on `current/1` to decide which
widget receives the keystroke. `handle_key/2` consumes
Tab / Shift+Tab (or your overrides) and passes everything else
through unchanged.

There is no process, no macro, no protocol — just a struct you keep
in your reducer state or `ExRatatui.App` model.

## Caller pattern

    def handle_event(%Event.Key{} = key, state) do
      {focus, key} = Focus.handle_key(state.focus, key)
      state = %{state | focus: focus}

      case key do
        nil ->
          # consumed by Focus (Tab / Shift+Tab); nothing more to do
          state

        key ->
          case Focus.current(focus) do
            :search  -> update_search(state, key)
            :results -> update_results(state, key)
            :details -> update_details(state, key)
          end
      end
    end

## Styling the focused widget

`Focus` never touches widget structs. Use `focused?/2` to decide the
style yourself:

    border_style =
      if Focus.focused?(focus, :search),
        do: %Style{fg: :yellow},
        else: %Style{fg: :gray}

    %TextInput{
      state: search_state,
      block: %Block{borders: :all, border_style: border_style}
    }

## Custom keys

Pass `:next_keys` / `:prev_keys` to `new/2` as lists of
`%ExRatatui.Event.Key{}` structs. Only `:code` and `:modifiers`
matter — `:kind` is ignored, and `:modifiers` is compared as a set
(order-independent).

    Focus.new([:a, :b, :c],
      next_keys: [%Event.Key{code: "tab"}, %Event.Key{code: "right", modifiers: ["ctrl"]}],
      prev_keys: [%Event.Key{code: "left", modifiers: ["ctrl"]}]
    )

# `id`

```elixir
@type id() :: atom()
```

# `t`

```elixir
@type t() :: %ExRatatui.Focus{
  ids: [id(), ...],
  index: non_neg_integer(),
  next_keys: [ExRatatui.Event.Key.t()],
  prev_keys: [ExRatatui.Event.Key.t()]
}
```

# `current`

```elixir
@spec current(t()) :: id()
```

Returns the currently focused ID.

## Examples

    iex> ExRatatui.Focus.new([:a, :b, :c]) |> ExRatatui.Focus.current()
    :a

    iex> ExRatatui.Focus.new([:a, :b, :c], initial: :b) |> ExRatatui.Focus.current()
    :b

# `focus`

```elixir
@spec focus(t(), id()) :: t()
```

Jumps focus to a specific ID.

Raises `ArgumentError` if `id` is not in the ring.

# `focused?`

```elixir
@spec focused?(t(), id()) :: boolean()
```

Returns `true` when `id` is the currently focused ID.

## Examples

    iex> focus = ExRatatui.Focus.new([:a, :b, :c])
    iex> ExRatatui.Focus.focused?(focus, :a)
    true
    iex> ExRatatui.Focus.focused?(focus, :b)
    false

# `handle_key`

```elixir
@spec handle_key(t(), ExRatatui.Event.Key.t()) :: {t(), ExRatatui.Event.Key.t() | nil}
```

Routes a key event through the focus ring.

Returns `{focus, nil}` when the event matched a `:next_keys` or
`:prev_keys` entry (focus moved, event consumed). Returns
`{focus, event}` unchanged otherwise so the caller can forward it to
the currently focused widget.

Matching compares `:code` and `:modifiers` (as a set). `:kind` is
ignored.

# `new`

```elixir
@spec new(
  [id(), ...],
  keyword()
) :: t()
```

Builds a focus ring from an ordered list of IDs.

## Options

  * `:initial` — ID to start focused on (defaults to the first entry).
  * `:next_keys` — list of `%ExRatatui.Event.Key{}` that advance focus
    (defaults to Tab).
  * `:prev_keys` — list of `%ExRatatui.Event.Key{}` that retreat focus
    (defaults to Shift+Tab and `back_tab`).

Raises `ArgumentError` for an empty list, duplicate IDs, non-atom
entries, or an `:initial` that is not in `ids`.

# `next`

```elixir
@spec next(t()) :: t()
```

Advances focus to the next ID, wrapping from the last back to the first.

# `prev`

```elixir
@spec prev(t()) :: t()
```

Retreats focus to the previous ID, wrapping from the first back to the last.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
