# `Drafter.Widget.Footer`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/widget/footer.ex#L1)

Renders a single-row key-binding bar, typically anchored to the bottom of a screen.

Bindings are `{key_label, description}` tuples displayed as styled `[key] action`
pairs separated by a configurable separator string. When no `:bindings` list is
provided the widget calls `keybindings/0` on the currently active screen module
automatically.

Each binding is clickable: hovering highlights the binding, and clicking dispatches
the associated key event as if the user pressed that key.

## Component tag

Tag `:footer`, built by `Drafter.App` as `{:footer, opts}`:

    footer(opts)

There is no positional argument; every prop comes from `opts`. `:app_module`
is supplied by the renderer.

## Options

  * `:bindings` - list of `{key, description}` tuples. Default `nil`, in which
    case the widget calls `keybindings/0` on the active screen module, falling
    back to `:app_module`, and then to `[]` when neither exports it
  * `:separator` - `t:String.t/0` placed between binding pairs. Default `" "`
  * `:style` - style map applied to description text. Default `nil`, which uses
    the computed `:footer` theme style
  * `:key_style` - style map applied to key label text. Default `nil`, which uses
    the computed `:footer` `:key` part style
  * `:app_module` - module used for theme resolution and as the fallback source of
    `keybindings/0`, passed by the renderer as `:__app_module__`. Default `nil`

`update/2` re-reads every option, and passing `bindings: nil` explicitly restores
the "ask the active screen" behaviour. All of them are live-updatable through the
component tree.

## Widget value

`Drafter.get_widget_value/1` is not implemented for this widget and returns `nil`.

## Events

The footer is not focusable and handles no keys. A hover moves the highlight to
the binding under the pointer, and a mouse release on a binding sends the
corresponding key event through `Drafter.Event.Manager`. A label of one byte
becomes a `{:char, codepoint}` event, a label in the built-in table becomes its
`{:key, atom}` event, a label with `+` becomes `{:key, key, modifiers}`, and any
other label is downcased and converted to an atom.

## Usage

    footer(bindings: [{"q", "Quit"}, {"Tab", "Focus next"}, {"Enter", "Select"}])
    footer()

# `binding`

```elixir
@type binding() :: {String.t(), String.t()}
```

# `t`

```elixir
@type t() :: %Drafter.Widget.Footer{
  app_module: module() | nil,
  bindings: [binding()] | nil,
  hovered_index: non_neg_integer() | nil,
  key_style: map() | nil,
  separator: String.t(),
  style: map() | nil
}
```

# `component_tag`

```elixir
@spec component_tag() :: :footer
```

The registry tag for this widget.

    iex> Drafter.Widget.Footer.component_tag()
    :footer

# `focused`

# `from_component_opts`

```elixir
@spec from_component_opts(
  term(),
  keyword()
) :: Drafter.Widget.props()
```

Turns the `{:footer, opts}` element into a props map for `mount/1`.

The positional argument is ignored. `:__app_module__` becomes `:app_module`.

    iex> Drafter.Widget.Footer.from_component_opts(nil, bindings: [{"q", "Quit"}])
    %{bindings: [{"q", "Quit"}], separator: " ", style: nil, key_style: nil, app_module: nil}

# `handle_event`

# `handle_hover`

```elixir
@spec handle_hover(integer(), integer(), t()) :: {:ok, t()} | {:noreply, t()}
```

Moves the highlight to the binding spanning column `x`, counted from the left edge
of the widget.

Returns `{:ok, state}` with the new `:hovered_index`, which is `nil` when `x`
falls on a separator or past the last binding, or `{:noreply, state}` when the
index has not changed.

# `handle_mouse_up`

```elixir
@spec handle_mouse_up(integer(), integer(), t()) :: {:ok, t()} | {:noreply, t()}
```

Dispatches the key event for the binding under column `x`.

On a hit the event is sent through `Drafter.Event.Manager.send_event/1`, exactly
as if the key had been pressed, and `{:ok, state}` is returned. A release on a
separator or past the last binding returns `{:noreply, state}`.

# `mount`

```elixir
@spec mount(Drafter.Widget.props()) :: t()
```

Builds the footer state from `props`. `:hovered_index` always starts at `nil`.

    iex> f = Drafter.Widget.Footer.mount(%{bindings: [{"q", "Quit"}]})
    iex> {f.bindings, f.separator, f.style, f.key_style, f.hovered_index}
    {[{"q", "Quit"}], " ", nil, nil, nil}

# `preferred_height`

```elixir
@spec preferred_height(
  term(),
  keyword()
) :: pos_integer()
```

Always `1`: the footer occupies a single row.

# `render`

```elixir
@spec render(t(), Drafter.Widget.rect()) :: [Drafter.Draw.Strip.t()]
```

Draws the binding bar as a single strip padded or cropped to `rect.width`.

Each binding takes `" key "` followed by `" description"`, with `:separator`
between pairs and none after the last. The hovered binding is drawn with
`reverse: true`. `rect.height` is not consulted.

# `unmount`

# `update`

```elixir
@spec update(Drafter.Widget.props(), t()) :: t()
```

Folds fresh props into `state`, re-reading `:bindings`, `:style`, `:key_style`,
`:separator` and `:app_module`. `:hovered_index` is left alone.

`:bindings` is taken whenever the key is present, so `bindings: nil` restores the
active-screen lookup rather than being ignored.

    iex> f = Drafter.Widget.Footer.mount(%{bindings: [{"q", "Quit"}]})
    iex> Drafter.Widget.Footer.update(%{bindings: nil}, f).bindings
    nil

# `update_props_from_mount`

```elixir
@spec update_props_from_mount(Drafter.Widget.props(), t(), keyword()) ::
  Drafter.Widget.props()
```

Returns `mount_props` unchanged, so a re-render passes every option through to
`update/2`.

---

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