# `Drafter.App`
[🔗](https://github.com/jaman/drafter/blob/main/lib/drafter/app.ex#L1)

Declarative API for building a Drafter terminal application.

An application is a module that does `use Drafter.App` and implements
`c:mount/1`, `c:render/1`, and one or both `handle_event` callbacks. Drafter owns
the terminal, the event loop, layout, theming, and rendering; the module supplies
state and a component tree.

## Complete application

This module compiles and runs as written:

    defmodule Counter do
      use Drafter.App

      def mount(_props), do: %{count: 0}

      def render(state) do
        vertical([
          header("Counter"),
          label("Count: #{state.count}", style: %{bold: true}),
          horizontal(
            [
              button("Decrement", on_click: :decrement),
              button("Increment", on_click: :increment, variant: :primary)
            ],
            gap: 2
          ),
          footer()
        ])
      end

      keybinding :q, "quit" do
        {:stop, :normal}
      end

      def handle_event(:increment, _data, state), do: {:ok, %{state | count: state.count + 1}}
      def handle_event(:decrement, _data, state), do: {:ok, %{state | count: state.count - 1}}
      def handle_event(_name, _data, state), do: {:noreply, state}
    end

    Drafter.run(Counter)

## `use Drafter.App` options

  * `:css_path` - `String.t()`, path to a CSS file used for widget styling.
    Default `nil`.
  * `:styles` - `map()` of inline style overrides. Default `%{}`.
  * `:mouse_hover` - `boolean()`. `true` (default) puts the terminal into hover
    tracking mode for this app. `false` cuts mouse event volume for apps with no
    hover effects.
  * `:key_release` - `boolean()`, default `false`. `true` turns on the kitty
    keyboard protocol for this app's sessions where the terminal supports it:
    every key press is followed by `{:key_down, key, modifiers}`, every release
    arrives as `{:key_up, key, modifiers}`, and `{:key_release_support, true}` is
    delivered once the terminal confirms it will report releases — `false` from a
    terminal that speaks the protocol without them. The `{:key, ...}` events are
    unchanged.
    See `Drafter.Terminal.KittyKeyboard` for the key names.
  * `:cell_size` - `boolean()`, default `true`. Asks each session's terminal how
    many pixels a cell is; the answer arrives as `{:cell_size, {width, height}}` and
    `Drafter.Session.Context.cell_size/0` keeps it. Image widgets draw at that size,
    and widgets placing images at pixel offsets need it. `false` sends no query, and
    images are drawn at a fixed pixels-per-cell scale.
  * `:pixel_mouse` - `boolean()`, default `false`. `true` asks each session's
    terminal for its cell size and then for mouse reports in pixels (SGR-Pixels,
    DECSET 1016). Mouse payloads keep `x` and `y` in cells and, once the terminal
    confirms with `{:pixel_mouse, true}`, add `px: {x, y}`, the zero-based pixel
    under the pointer. A terminal that ignores the mode answers
    `{:pixel_mouse, false}` and its payloads carry no `:px`.
    `Drafter.Session.Context.pixel_mouse?/0` reports which. Local and ssh sessions.
  * `:controller` - `boolean()`, default `false`. `true` reads the game controllers
    attached to the machine the app runs on through the `gamepad` package, which
    the app adds to its own deps, and delivers their events as `{:controller, event}`
    — buttons, axes, connections — in the shape `Gamepad.Wire` describes. Local
    terminal sessions only; a client over ssh or telnet has its controller on its
    own machine. Without the package a note is printed and no events arrive.
  * `:frame_pacing` - `:animations` (default) or `:always`. `:always` draws at
    most once per `c:refresh_rate/0` interval whatever caused the change, so a
    burst of events or timer ticks becomes one frame; `:animations` paces only
    animation frames and draws every other change as it happens.
  * `:runtime` - the runtime backend: a module, or the shorthand `:callback`,
    `:reducer`, or `:shared`. Default `Drafter.Runtime.Callback`, the
    `mount`/`render`/`handle_event` style described here. See `Drafter.Runtime`.

No other key is read; an unknown one is silently ignored. Each of these is
exposed on the module as `__css_path__/0`, `__inline_styles__/0`,
`__mouse_hover__/0`, `__key_release__/0`, `__cell_size__/0`, `__pixel_mouse__/0`,
`__controller__/0`,
`__frame_pacing__/0`, and
`__runtime__/0`, which the loop calls and which are not overridable.

`use Drafter.App` imports this module, so every element constructor
(`vertical/2`, `label/2`, `button/2`, …) and the `keybinding/3` macro are
available unqualified. A separate `import Drafter.App` is redundant.

It also defines overridable defaults for `c:mount/1` (returns `%{}`),
`c:render/1` (returns `[]`), `c:on_ready/1`, `c:unmount/1`, and `keybindings/0`,
and appends catch-all clauses for `handle_event/2` (returns `{:noreply, state}`)
and `c:on_timer/2` (returns the state) after every clause the module defined,
each only when the module has no catch-all clause of its own for that function.
A module therefore never has to write those two catch-alls, and may write its own. `handle_event/3` gets
no catch-all: a module that defines any `handle_event/3` clause must define its
own final clause or an unmatched named callback raises `FunctionClauseError`.

## Callbacks

  * `c:mount/1` — `mount(props :: map()) :: state`. Builds the initial state from
    the map passed under `:props` to `Drafter.run/2` (`%{}` when absent). `state`
    is any term; a map is what the element constructors and `Drafter.Test` expect.
  * `c:render/1` — `render(state) :: element | [element]`. Returns the component
    tree: the tuples produced by the constructors in this module, the
    `{WidgetModule, props_map}` tuples produced by `Drafter.label/2` and friends,
    or a list of either. Called after every state change; it must be pure.
  * `handle_event/2` — `handle_event(event, state) :: result`. Raw input events:
    `{:key, key}`, `{:key, key, modifiers}`, `{:char, codepoint}`, `{:mouse, map}`,
    `{:resize, {width, height}}`, and the other members of `t:Drafter.Event.t/0`.
    Printable ASCII arrives as a named key — `q` is `{:key, :q}`, `/` is
    `{:key, :/}` — and only codepoints above ASCII 126 arrive as
    `{:char, codepoint}`. See `Drafter.Terminal.ANSI` for the full set of shapes,
    and "Where a key is handled" below for which keys reach this callback at all.
  * `handle_event/3` — `handle_event(name, data, state) :: result`. Named
    callbacks: `name` is the atom given to a widget as `on_click:`, `on_change:`,
    and so on; `data` is that widget's payload (`nil` for widgets with no
    payload). Also how `Drafter.send_app_event/2` and a popped screen's result
    arrive.

Optional lifecycle callbacks, each returning the new state — omit them and the
state passes through unchanged:

  * `c:on_ready/1` — after the first render, before the first event. The place to
    start timers with `Drafter.set_interval/2`.
  * `c:on_timer/2` — `on_timer(timer_id, state)`, once per firing of a timer
    registered with `Drafter.set_interval/2` or `Drafter.set_timeout/2`.
  * `c:on_message/2` — `on_message(msg, state)` for any process message delivered
    to the loop that Drafter does not itself handle (PubSub broadcasts, `send/2`).
  * `c:on_scroll_active/1` — on the first scroll event of a scroll gesture.
  * `c:on_scroll_idle/1` — 150 ms after the last scroll event of a gesture.
  * `c:update/2` — `update(props, state)`, when a parent re-renders this module
    with new props.
  * `c:unmount/1` — returns `:ok`; called once as the app stops.
  * `c:refresh_rate/0` — the frame interval: `"30fps"`, `"7.5fps"`, a millisecond
    integer, or `:unlimited`. Default `"30fps"`.

## Where a key is handled

`handle_event/2` — and therefore every `keybinding/3` clause, which is only a
`handle_event/2` clause — is not the first thing to see an input event. Three
things run ahead of it.

**`Ctrl+Q` stops the loop** before any callback runs, always. A
`keybinding {:q, [:ctrl]}` clause and a hand-written `{:key, :q, [:ctrl]}` clause
are both unreachable. `Ctrl+C` is *not* reserved and does reach the module.

**A non-empty screen stack takes every event.** After a
`{:push, ...}`, `{:replace, ...}` or `{:show_modal, ...}` the root application
module's `handle_event/2` is not called at all; input goes to the screen stack.
Its `keybinding/3` clauses are dormant until the last screen pops.

**The widget hierarchy runs before the app whenever a widget holds focus.** A tree
containing no focusable widget has no focus and never enters this path; a tree
containing focusable widgets focuses the first of them on the initial render. So:

  * *Nothing focused* — `handle_event/2` runs first, and `{:noreply, state}` then
    offers the event to the hierarchy.
  * *Something focused* — the hierarchy runs first. Only events it leaves
    unconsumed reach `handle_event/2`, and the hierarchy has already had its turn
    by then, so `{:noreply, state}` does not offer the event a second time.

The hierarchy counts an event as consumed when a widget produced an action or when
focus moved. That makes the following keys reserved **while a widget is focused**:

  * `Tab` and `Shift+Tab` cycle focus and are consumed. The exception is a tree
    holding exactly one focusable widget: focus cannot move, so the key falls
    through to `handle_event/2`. A focused widget carrying both `focused` and
    `trap_focus` in its state — `Drafter.Widget.TextArea` with `trap_focus: true` —
    receives the key itself instead of moving focus.
  * `↑`, `↓`, `←`, `→` are offered to directional focus navigation first. They are
    consumed when a neighbouring widget takes focus, and otherwise fall through to
    the focused widget and then to `handle_event/2`.
  * Every other key is dispatched to the focused widget, so what is reserved
    depends on which widget that is. A focused `Drafter.Widget.Button` consumes
    `Enter` and `Space` and fires its `:on_click`. A focused
    `Drafter.Widget.TextInput` consumes every printable key, `Space` and the
    editing keys, but leaves `Enter` alone.

`Esc`, `PageUp`/`PageDown`, `Home`/`End`, `Ctrl+C` and the function keys are not
reserved by the framework and reach `handle_event/2` unless the focused widget
claims them.

A key the hierarchy consumed still reaches the module through `handle_event/3` when
the widget carried a callback: `Enter` on `button("Save", on_click: :save)` calls
`handle_event(:save, nil, state)`.

## Event results

Both `handle_event` callbacks return one of:

  * `{:ok, new_state}` — the app handled the event. `new_state` replaces the state
    and the app re-renders. The event is not offered to the widget hierarchy again.
  * `{:noreply, new_state}` — the app did not handle the event. `new_state`
    replaces the state, and the event is routed to the widget hierarchy — the
    focused widget first, then bubbling to its ancestors — **only if the hierarchy
    has not already been offered it**, which per "Where a key is handled" it has
    whenever a widget holds focus. This is what a catch-all clause returns;
    returning `{:ok, state}` from a catch-all swallows every event the hierarchy
    did not already claim.
  * `{:stop, reason}` — the loop terminates. `:normal` makes `Drafter.run/2`
    return `:ok`; any other reason makes it return `{:error, reason}`. Inside a
    nested session, control returns to the parent app instead of exiting.
  * `{:error, reason}` — the event is discarded and the state is left unchanged.
  * `{:show_modal, screen_module, props, opts}` — see `show_modal/3`.
  * `{:show_toast, message, opts}` — see `show_toast/2`.
  * `{:pop, result}` — see `pop_screen/1`.
  * `{:push, screen_module, props, opts}` and
    `{:replace, screen_module, props, opts}` — push a screen onto, or replace the
    top of, the screen stack.

Any other term returned from `handle_event/3` is offered to the handlers
registered with `Drafter.ActionRegistry`; see `Drafter.ActionHandler`. An
unrecognised term leaves the state unchanged. Any other term returned from
`handle_event/2` raises `FunctionClauseError` in the loop.

## Element options

Every constructor takes a trailing keyword list. These keys are read for all of
them:

  * `:id` - `atom()`, the widget id used by `Drafter.get_widget_value/1`,
    `Drafter.focus/1`, and `Drafter.Test`. Default `nil`, which generates
    `:"<LastModulePart>_<type>_<n>"`, e.g. `:Counter_label_1`.
  * `:key` - a stable identity for a widget whose position in the tree moves, used
    in place of the positional part of the generated id. Default `nil`. A `:key`
    produces a **`String.t()`** widget id of the form `"Counter_label#abc"`, not an
    atom, so pass that string to `Drafter.focus/1` and `Drafter.Test.query_one/2`.
    Ignored when `:id` is also given.
  * `:visible` - `boolean()`, default `true`. `false` omits the element and its
    children from the hierarchy entirely and its siblings close the gap. The
    generated ids the element would take stay reserved, so the widgets after it
    keep their ids, and their state, whether it is shown or not.
  * `:visibility` - `:hidden` allots the element its space and draws nothing in it.
    Default `nil`, which draws normally.
  * `:margin` - space outside the element: an integer for all four sides, a
    `{vertical, horizontal}` tuple, or a `{top, right, bottom, left}` tuple.
    Default `nil`, i.e. no margin. Any other shape is treated as no margin.
  * `:dock` - `:top`, `:bottom`, `:left`, or `:right`. Default `nil`. A docked
    element leaves the normal flow and takes the full span of that edge; the
    remaining space is shared by its undocked siblings. A `footer/1` element is
    docked to `:bottom` whether or not the option is given. Only a `vertical/2`
    container honours this — inside a `horizontal/2` a docked child is laid out
    like any other.

A layout container reads `:gap` (default `0`) and `:padding` from its own options,
and `:width`, `:height`, `:flex`, `:min_width`/`:max_width`, and
`:min_height`/`:max_height` from each child's options to divide its space.
`:width` and `:height` take a cell count, `{:percent, n}`, `{:fr, n}` for a share
of what is left after fixed siblings, or `:auto` for the child's own preferred
size. `:padding` takes the same shapes as `:margin`.

Remaining keys are the individual widget's props; each is documented on the widget
module under `Drafter.Widget`. Note that the element constructors here do not
forward every prop a widget's `mount/1` accepts — each constructor below names the
keys its own widget reads only when mounted directly.

# `direction`

```elixir
@type direction() :: :vertical | :horizontal
```

The direction a layout container arranges its children in.

# `element`

```elixir
@type element() ::
  {atom(), opts()}
  | {atom(), term(), opts()}
  | {atom(), term(), term(), opts()}
  | {module(), map()}
```

A node of a component tree.

Either a tag tuple built by one of the constructors in this module — the tag atom,
zero to two positional arguments, and the options keyword list last — or a
`{WidgetModule, props_map}` tuple as `Drafter.label/2` and friends build.

# `opts`

```elixir
@type opts() :: keyword()
```

The trailing keyword list every element constructor accepts.

# `props`

```elixir
@type props() :: map()
```

# `rect`

```elixir
@type rect() :: Drafter.Widget.rect()
```

# `screen_action`

```elixir
@type screen_action() ::
  {:show_modal, module(), props(), opts()}
  | {:show_toast, String.t(), opts()}
  | {:push, module(), props(), opts()}
  | {:replace, module(), props(), opts()}
  | {:pop, term()}
```

A screen-stack instruction both `handle_event` callbacks dispatch.

Note the four-element `{:push, ...}` and `{:replace, ...}`, which are what the loop
matches — not the tags `push_screen/3` and `replace_screen/3` build.

# `state`

```elixir
@type state() :: term()
```

# `handle_event`

```elixir
@callback handle_event(Drafter.Event.t(), state()) ::
  {:ok, state()}
  | {:error, term()}
  | {:noreply, state()}
  | {:stop, term()}
  | screen_action()
```

Handles a raw input event.

`event` is a `t:Drafter.Event.t/0` tuple — `{:key, key}`, `{:key, key, modifiers}`,
`{:char, codepoint}`, `{:mouse, map}`, `{:resize, {width, height}}`, and the rest.
Printable ASCII 32..126 arrives as a named key (`{:key, :q}`, `{:key, :" "}`); only
codepoints above 126 arrive as `{:char, codepoint}`.

This callback does **not** see every event. `Ctrl+Q` stops the loop first; a
non-empty screen stack takes the event instead; and whenever a
widget holds focus the widget hierarchy is offered the event before this callback
and anything it consumes never arrives. See "Where a key is handled" in
`Drafter.App`.

Return `{:noreply, state}` for events the app does not claim: the event is then
routed to the focused widget and its ancestors, unless the hierarchy was already
offered it. `{:ok, new_state}` consumes the event and re-renders, `{:stop, reason}`
ends the app, `{:error, reason}` discards the event, and a `t:screen_action/0`
manipulates the screen stack. Any other term raises `FunctionClauseError` in the
loop.

A catch-all clause returning `{:noreply, state}` is appended automatically, so a
module need only define the clauses it cares about.

# `handle_event`
*optional* 

```elixir
@callback handle_event(event_name :: term(), data :: term(), state()) :: term()
```

Handles a named callback.

`event_name` is whatever term a widget was given as `on_click:`, `on_change:`, and
so on, or the name passed to `Drafter.send_app_event/2`. It is normally an atom,
but any non-function term is passed through unchanged — `switch_group/2` delivers
the tuple `{:switch_group_changed, group_name, value}` this way. `data` is the
widget's payload, `nil` for widgets that carry none.

Returns `{:ok, new_state}` to accept the change and re-render, `{:noreply, new_state}`
to accept it without claiming the event, `{:stop, reason}` to end the app, or
`{:error, reason}` to discard it, or a `t:screen_action/0`. Any other term is offered
to the handlers registered with `Drafter.ActionRegistry`, and an unrecognised one
leaves the state unchanged — which is why the return type here is `term()` rather
than a closed union.

No catch-all clause is generated for this callback: a module that defines any
clause must also define a final one, or an unmatched name raises `FunctionClauseError`.

# `mount`

```elixir
@callback mount(props()) :: state()
```

Builds the application's initial state from its mount props.

`props` is the map passed under `:props` to `Drafter.run/2`, `Drafter.run_session/3`,
or `Drafter.Test.start_headless/3`, and is `%{}` when none were given. The returned
term is the state handed to every other callback.

# `on_message`
*optional* 

```elixir
@callback on_message(msg :: term(), state()) :: state()
```

Handles a process message delivered to the loop that Drafter does not itself
consume — PubSub broadcasts, `send/2` from a task, GenServer replies.

Returns the new state.

# `on_ready`
*optional* 

```elixir
@callback on_ready(state()) :: state()
```

Runs once after the first render, before the first event.

Returns the state to continue with. Timers started here with
`Drafter.set_interval/2` are registered before the loop begins receiving.

# `on_scroll_active`
*optional* 

```elixir
@callback on_scroll_active(state()) :: state()
```

Runs on the first scroll event of a scroll gesture. Returns the new state.

# `on_scroll_idle`
*optional* 

```elixir
@callback on_scroll_idle(state()) :: state()
```

Runs once a scroll gesture settles, 150 ms after its last scroll event.
Returns the new state.

# `on_timer`
*optional* 

```elixir
@callback on_timer(atom(), state()) :: state()
```

Runs once per firing of a registered timer.

The first argument is the `timer_id` given to `Drafter.set_interval/2` or
`Drafter.set_timeout/2`. Returns the new state.

# `refresh_rate`
*optional* 

```elixir
@callback refresh_rate() :: pos_integer() | String.t() | :unlimited
```

The application's frame interval.

Returns `"30fps"`-style strings, `"7.5fps"`, a positive millisecond integer, or
`:unlimited` to render without pacing. `"30fps"` is used when the callback is not
defined. Anything else raises `ArgumentError` when the loop starts.

# `render`

```elixir
@callback render(state()) :: term()
```

Returns the component tree for the current state.

An element tuple from a constructor in this module, a `{WidgetModule, props_map}`
tuple, or a list of either. Called after every state change, so it must be free of
side effects.

# `unmount`
*optional* 

```elixir
@callback unmount(state()) :: :ok
```

Releases resources as the application stops. Returns `:ok`.

# `update`
*optional* 

```elixir
@callback update(props(), state()) :: state()
```

Rebuilds the state when the module is re-rendered with new props.

`props` is the new prop map, `state` the state as it stands. Returns the state to
continue with.

# `__before_compile__`
*macro* 

Appends the catch-all `handle_event/2` and `on_timer/2` clauses after every clause
the module defined, and defines `keybindings/0` when `keybinding/3` was used.

# `box`

```elixir
@spec box([element()], opts()) :: {:box, [element()], opts()}
```

A bordered container. Returns `{:box, children, opts}`.

`children` is a list of child elements, laid out inside the rect left after the
border and padding are subtracted.

Options beyond the shared element keys, forwarded to `Drafter.Widget.Box`:

  * `:title` - `String.t()` embedded in the top border. Default `nil`.
  * `:border` - `:none`, `:single`, `:double`, `:rounded`, `:heavy`, `:dashed`, or
    `:ascii`. Default: the current character set's border style, falling back to
    `:rounded`. `:none` still costs no rows.
  * `:padding` - `non_neg_integer()` cells inside the border. Default: the
    character set's padding, falling back to `1` — not `0`.
  * `:style` - `map()` of style attributes for the box as a whole. Default `%{}`.
  * `:border_style` - `map()` of style attributes for the border characters, such as
    `%{fg: {r, g, b}}`. Default `%{}`.
  * `:title_style` - `map()` of style attributes for the title text. Default `%{}`.
  * `:content_style` - `map()` of style attributes for the interior fill, such as
    `%{bg: {r, g, b}}`. Default `%{}`.
  * `:highlight_focus` - `boolean()`, draw the border and title in the theme's primary
    colour while a widget inside the box has keyboard focus. Default `false`.

`:classes` is documented on `Drafter.Widget.Box` because its `mount/1` reads it, but
this element does not forward it.

    iex> box([label("Ready")], title: "Status", border: :double)
    {:box, [{:label, "Ready", []}], [title: "Status", border: :double]}

# `breadcrumb`

```elixir
@spec breadcrumb([String.t() | {String.t(), term()}], opts()) ::
  {:breadcrumb, [term()], opts()}
```

A path trail. Returns `{:breadcrumb, items, opts}`.

`items` is a list of crumbs, each either a `String.t()` used as both label and id
or a `{label, id}` tuple.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.Breadcrumb`:

  * `:separator` - `String.t()` between crumbs. Default `" › "`.
  * `:on_click` - atom event name or one-arity function receiving the crumb's id.
    Default `nil`.
  * `:active` - `boolean()`, whether crumbs respond to clicks. Default `true`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

A focused breadcrumb consumes `←`, `→`, `Enter` and `Space`.

    iex> breadcrumb(["home", {"Reports", :reports}], on_click: :navigate)
    {:breadcrumb, ["home", {"Reports", :reports}], [on_click: :navigate]}

# `button`

```elixir
@spec button(String.t(), opts()) :: {:button, String.t(), opts()}
```

A pressable button. Returns `{:button, text, opts}`.

`text` is the `String.t()` caption.

A focused button consumes `Enter` and `Space`, so neither key reaches the app's
`handle_event/2` or a `keybinding/3` clause while it holds focus. Activation
arrives as `handle_event(on_click_atom, nil, state)` instead.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Button`:

  * `:on_click` - the atom delivered to `handle_event/3` when the button is
    activated, or a zero-arity function. Default `nil`, and no callback fires.
    Forced to `nil` when `:disabled` is `true`.
  * `:variant` - `:default` (the default), `:primary`, and the other theme
    variants. `:type` is read as a fallback when `:variant` is absent.
  * `:disabled` - `boolean()`, default `false`. A disabled button takes no
    focus and fires no callback.
  * `:compact` - `boolean()`, default `false`. Drops the button's border rows.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms matched by CSS selectors. Default `[]`.

## Examples

    iex> button("Save", on_click: :save, variant: :primary)
    {:button, "Save", [on_click: :save, variant: :primary]}

# `calendar`

```elixir
@spec calendar(opts()) :: {:calendar, opts()}
```

A month calendar. Returns `{:calendar, opts}`.

See `Drafter.Widget.Calendar` for its options; the positional argument the widget
would take is not used by this element, so everything comes from `opts`. A focused
calendar consumes the four arrow keys, `Enter` and `Space`.

    iex> calendar(id: :when)
    {:calendar, [id: :when]}

# `card`

```elixir
@spec card([String.t()], opts()) :: {:card, [String.t()], opts()}
```

A titled panel. Returns `{:card, children, opts}`.

`children` must be a list of **strings**, one per content row. Each entry is run
through `to_string/1`, so passing element tuples — the natural reading of
"children" and what `box/2` and `vertical/2` take — raises
`Protocol.UndefinedError` for `String.Chars` when the tree renders. A card is a
text panel, not a layout container.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Card`:

  * `:title` - `String.t()` in the top border. Default `nil`.
  * `:border` - border style atom. Default: the current character set's border
    style, falling back to `:rounded`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:border_color` - border colour. Default `nil`.
  * `:background` - panel background colour. Default `nil`.
  * `:color` - content foreground colour. Default `nil`.
  * `:class` - an atom or list of atoms. Default `[]`; reaches the widget as
    `:classes`.

## Examples

    iex> card(["3 files changed"], title: "Summary")
    {:card, ["3 files changed"], [title: "Summary"]}

# `chart`

```elixir
@spec chart(term(), opts()) :: {:chart, term(), opts()}
```

A line, bar, or area chart. Returns `{:chart, data, opts}`.

`data` is the series to plot; any non-list is treated as absent and the `:data`
option is used instead. See `Drafter.Widget.Chart` for the accepted series shapes
and the full option list. The chart kind is `:chart_type` (default `:line`), not
`:type`. `:width` and `:height` default to the rect the parent allocated.

    iex> chart([1, 2, 3], chart_type: :bar)
    {:chart, [1, 2, 3], [chart_type: :bar]}

# `checkbox`

```elixir
@spec checkbox(String.t(), opts()) :: {:checkbox, String.t(), opts()}
```

A checkbox with a caption. Returns `{:checkbox, label, opts}`.

`label` is the `String.t()` shown beside the box. `Drafter.get_widget_value/1`
returns the checked `boolean()`. A focused checkbox consumes `Space`.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.Checkbox`:

  * `:checked` - `boolean()` initial state. Default `false`.
  * `:bind` - app-state key atom for two-way binding of the checked state.
    Default `nil`. Only with a `:bind` does a later `:checked` change reach the
    mounted widget.
  * `:on_change` - atom event name or one-arity function called with the new
    `boolean()`. Default `nil`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

## Examples

    iex> checkbox("Enable logging", checked: true, on_change: :toggle_logging)
    {:checkbox, "Enable logging", [checked: true, on_change: :toggle_logging]}

# `code_view`

```elixir
@spec code_view(opts()) :: {:code_view, opts()}
```

A syntax-highlighted source view. Returns `{:code_view, opts}`.

Called with a single keyword list, the source comes from the options. Called as
`code_view(content, opts)` with a `String.t()` first argument, the content is
prepended as `source: content`, so an explicit `:source` in `opts` is shadowed by
the positional argument. A non-list `opts` or a non-binary first argument raises
`FunctionClauseError`.

Highlighting requires `Drafter.run(app, syntax_highlighting: true)`; without it
the source renders unstyled. See `Drafter.Widget.CodeView` for the options.

A focused code view consumes the arrow keys, `Enter`, `Space`, `Escape`, `Home`,
`End`, `PageUp`, `PageDown`, `Backspace` and the printable keys, so almost nothing
reaches a `keybinding/3` clause while it has focus.

    iex> code_view(source: "IO.puts(:hi)", language: :elixir)
    {:code_view, [source: "IO.puts(:hi)", language: :elixir]}

    iex> code_view("IO.puts(:hi)", language: :elixir)
    {:code_view, [source: "IO.puts(:hi)", language: :elixir]}

# `code_view`

```elixir
@spec code_view(String.t(), opts()) :: {:code_view, opts()}
```

# `collapsible`

```elixir
@spec collapsible(String.t(), [element()], opts()) ::
  {:collapsible, String.t(), [element()], opts()}
```

A titled section that folds away. Returns `{:collapsible, title, content, opts}`.

`title` is the `String.t()` on the header row; `content` is the list of elements
shown while expanded. `Drafter.get_widget_value/1` returns the expanded
`boolean()`, and a focused collapsible consumes `Enter` and `Space`.

Options beyond the shared element keys, forwarded to
`Drafter.Widget.Collapsible`:

  * `:expanded` - `boolean()` initial state. Default `false`.
  * `:on_toggle` - atom event name or one-arity function receiving the new
    `boolean()`. Default `nil`.
  * `:content_height` - `pos_integer()` rows reserved for the expanded content.
    Default `nil`, i.e. the content's own preferred height.
  * `:summary` - `String.t()` shown dimmed at the right end of the toggle row.
    Default `nil`.
  * `:rule` - `boolean()`, draw a dimmed line across the toggle row between the
    title and the summary. Default `false`.

Its widget id is not positional: absent an `:id` or `:key`, the generated id is
derived from a hash of `title`, so two collapsibles with the same title in one
tree collide.

    iex> collapsible("Advanced", [label("Verbose")], expanded: true)
    {:collapsible, "Advanced", [{:label, "Verbose", []}], [expanded: true]}

# `container`

```elixir
@spec container([element()], opts()) :: {:layout, direction(), [element()], opts()}
```

A layout container whose direction comes from its options.

Returns `{:layout, direction, children, opts}` where `direction` is the `:layout`
option, default `:vertical`. Otherwise identical to `vertical/2` and
`horizontal/2`, which are the direct forms.

  * `:layout` - `:vertical` (default) or `:horizontal`. Any other value is copied
    into the tuple unchanged and raises `FunctionClauseError` when the tree is
    rendered — the renderer has no clause for a third direction.

## Examples

    iex> container([label("a")], layout: :horizontal)
    {:layout, :horizontal, [{:label, "a", []}], [layout: :horizontal]}

    iex> container([label("a")])
    {:layout, :vertical, [{:label, "a", []}], []}

# `data_table`

```elixir
@spec data_table(opts()) :: {:data_table, opts()}
```

A scrollable table with selectable rows. Returns `{:data_table, opts}`.

Options are the widget's props; `Drafter.Widget.DataTable` documents all of them.
There is no positional argument: columns go in `:columns` and rows in `:data`,
**not** `:rows`. `:width` and `:height` default to the rect the parent allocated.
`Drafter.get_widget_value/1` returns the list of selected row indices.

A focused data table consumes `↑`, `↓`, `PageUp`, `PageDown`, `Home`, `End`,
`Enter` and `Space`, so none of those reach a `keybinding/3` clause.

    iex> data_table(id: :rows, columns: [{:name, "Name"}], data: [%{name: "a"}])
    {:data_table, [id: :rows, columns: [{:name, "Name"}], data: [%{name: "a"}]]}

# `digits`

```elixir
@spec digits(term(), opts()) :: {:digits, term(), opts()}
```

Large seven-segment style digits. Returns `{:digits, value, opts}`.

`value` is the text to draw; it is run through `to_string/1`, so an integer or
float works as well as a `String.t()`.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Digits`:

  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:align` - `:left` (default), `:center`, or `:right`.
  * `:size` - `:large` (default) and the other sizes the widget lists.
  * `:font` - glyph set name. Default `nil`, i.e. the built-in font.
  * `:renderer` - `:text` (default) or the image renderers.
  * `:color` - `{r, g, b}` for the lit segments. Default `{0, 150, 255}`.
  * `:bg_data`, `:bg_min`, `:bg_max` - background sparkline data and its range.
    Defaults `nil`, `0`, and `nil`.

## Examples

    iex> digits("12:04", align: :center)
    {:digits, "12:04", [align: :center]}

# `directory_tree`

```elixir
@spec directory_tree(opts()) :: {:directory_tree, opts()}
```

A browsable filesystem tree. Returns `{:directory_tree, opts}`.

See `Drafter.Widget.DirectoryTree` for its `:path` and other options. A focused
directory tree consumes the arrow keys, `Enter`, `Space`, `Escape`, `Home`, `End`,
`PageUp`, `PageDown`, `Backspace` and the printable keys it uses to type-to-find,
so almost nothing reaches a `keybinding/3` clause while it has focus.

    iex> directory_tree(id: :files, path: "/tmp")
    {:directory_tree, [id: :files, path: "/tmp"]}

# `dismiss_modal`

```elixir
@spec dismiss_modal() :: {:pop, :dismissed}
```

Closes the topmost modal. Returns `{:pop, :dismissed}`, so the revealed screen's
`handle_event/3` receives `:dismissed` as its data.

Identical to `pop_screen(:dismissed)`; it pops whatever is on top of the
screen stack, modal or not.

    iex> dismiss_modal()
    {:pop, :dismissed}

# `footer`

```elixir
@spec footer(opts()) :: {:footer, opts()}
```

The application's key-hint bar. Returns `{:footer, opts}`.

Takes options only — unlike `Drafter.footer/2`, there is no text argument.
`use Drafter.App` imports this module and not `Drafter`, so an unqualified
`footer(...)` in an application module is always this function; reaching
`Drafter.footer/2` needs the `Drafter.` prefix. Adding `import Drafter` alongside
`use Drafter.App` does not give you a choice — it makes `footer/1`, `vertical/1`
and every other name the two modules share ambiguous, and the module fails to
compile.

A footer element is docked to the bottom of its enclosing `vertical/2` container
whether or not `dock: :bottom` is given.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Footer`:

  * `:bindings` - a list of `{key_label, hint}` tuples to show. Default `nil`,
    which reads the active screen's `keybindings/0` — the app module's when no
    screen is on the screen stack — i.e. the hints registered by
    `keybinding/3`, and `[]` when the module defines none.
  * `:separator` - `String.t()` placed between entries. Default `" "`.
  * `:style` - `map()` of style attributes for the hint text. Default `nil`.
  * `:key_style` - `map()` of style attributes for the key labels. Default `nil`.

The hints are what `keybinding/3` registered, which is not the same as what the
keys do: a hint whose key the focused widget consumes is displayed anyway. See
"Where a key is handled" in `Drafter.App`.

    iex> footer()
    {:footer, []}

    iex> footer(bindings: [{"Q", "quit"}], separator: " | ")
    {:footer, [bindings: [{"Q", "quit"}], separator: " | "]}

# `gauge`

```elixir
@spec gauge(opts()) :: {:gauge, opts()}
```

A dial-style gauge. Returns `{:gauge, opts}`.

Options are the widget's props; see `Drafter.Widget.Gauge`.

    iex> gauge(value: 72, max: 100)
    {:gauge, [value: 72, max: 100]}

# `header`

```elixir
@spec header(String.t() | nil, opts()) :: {:header, String.t() | nil, opts()}
```

The application's title bar. Returns `{:header, title, opts}`.

`title` is the `String.t()` shown on the left, or `nil` to take the `:title`
option instead.

Options beyond the shared element keys:

  * `:title` - fallback title, used when the first argument is `nil`. Default `""`.
  * `:show_clock` - `boolean()`, default `false`. Draws a self-updating clock on
    the right, and starts the 1-second timer that keeps it current.
  * `:clock_format` - `:time` (default) or the other formats accepted by
    `Drafter.Widget.Header`.

Only the title reaches an already-mounted header, so changing `:show_clock` or
`:clock_format` on a re-render has no effect. Not focusable, so it reserves no
keys.

    iex> header("Counter")
    {:header, "Counter", []}

    iex> header(nil, title: "Counter", show_clock: false)
    {:header, nil, [title: "Counter", show_clock: false]}

# `horizontal`

```elixir
@spec horizontal([element()], opts()) :: {:layout, :horizontal, [element()], opts()}
```

Lays `children` out left to right. Returns `{:layout, :horizontal, children, opts}`
with `layout: :horizontal` added to `opts`.

`children` is a list of elements.

Options beyond the shared element keys:

  * `:gap` - `non_neg_integer()` columns between children. Default `0`.
  * `:padding` - space inside the container, in the same shapes as `:margin`.
    Default `nil`, i.e. none.
  * `:layout` - overwritten with `:horizontal`; passing it has no effect.

Each child's own `:width` and `:flex` options decide how the row is divided. When
no child carries either, the width is split evenly.

    iex> horizontal([button("No", on_click: :no)], gap: 2)
    {:layout, :horizontal, [{:button, "No", [on_click: :no]}],
     [layout: :horizontal, gap: 2]}

# `keybinding`
*macro* 

Defines a `handle_event/2` clause for a key, and registers it for display in the
footer.

`key_spec` is either a bare key atom (`:q`, `:enter`, `:f1`) or a
`{key, modifiers}` tuple whose modifiers are drawn from `:ctrl`, `:shift`, and
`:alt` (`{:s, [:ctrl]}`). `hint` is the `String.t()` shown beside the key label in
`footer/1` and returned from the module's generated `keybindings/0` as
`{"Ctrl+S", "save"}`.

The block is the body of the clause. The current state is bound to `state`, and
the block must return one of the `handle_event/2` results.

    keybinding :q, "quit" do
      {:stop, :normal}
    end

    keybinding {:s, [:ctrl]}, "save" do
      {:ok, %{state | saved: true}}
    end

A bare key atom generates a clause matching `{:key, key}` exactly, so it does not
fire when a modifier is held; a `{key, modifiers}` spec generates a clause matching
`{:key, key, modifiers}` with that modifier list exactly. A codepoint above ASCII
126 arrives as `{:char, codepoint}` and needs a hand-written `handle_event/2` clause.

## Clause order

The generated clause is placed where the macro call appears, in source order among
the module's own `handle_event/2` clauses and ahead of the catch-all
`__before_compile__/1` appends. A `keybinding/3` written after a hand-written
catch-all `handle_event(_event, state)` is unreachable and the compiler warns.

## Which keys can be bound

A `keybinding/3` clause is a `handle_event/2` clause, so it is subject to
everything under "Where a key is handled" in `Drafter.App`. It is not a global
hotkey and it does not pre-empt the widget hierarchy. In particular:

  * `{:q, [:ctrl]}` never fires. `Ctrl+Q` stops the loop before any callback runs.
  * No keybinding on the root application module fires while the
    screen stack is non-empty. Bind the key on the screen module
    that is on top instead.
  * With a widget focused — which is the normal case, since the first focusable
    widget in the tree is focused on the initial render — the hierarchy sees the
    key first and a key it consumes never reaches the clause. `Enter` and `Space`
    are consumed by a focused `Drafter.Widget.Button`; the printable keys, `Space`
    and the editing keys are consumed by a focused `Drafter.Widget.TextInput`;
    `Tab` and `Shift+Tab` are consumed whenever the tree has two or more focusable
    widgets; the arrow keys are consumed whenever directional navigation finds a
    neighbour to move focus to.
  * With no focusable widget in the tree, nothing is reserved except `Ctrl+Q`, and
    every binding — `Tab`, `Enter`, `:" "` included — fires.

The bindings that behave the way their footer hint implies regardless of what is
focused are the letters, punctuation and function keys no focused widget claims —
`:q`, `:k`, `:f1` and the like. A modifier combination is not automatically safe:
a focused `Drafter.Widget.TextInput` claims `Ctrl+A`, `Ctrl+←` and `Ctrl+→` among
others, and each widget's own moduledoc lists what it takes.

# `label`

```elixir
@spec label(String.t() | [{String.t(), map()}], opts()) ::
  {:label, String.t() | [{String.t(), map()}], opts()}
```

A line of text. Returns `{:label, text, opts}`.

`text` is a `String.t()`; embedded newlines wrap onto further rows.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Label`:

  * `:style` - `map()` of style attributes, e.g. `%{bold: true, fg: :cyan}`.
    Default `%{}`.
  * `:align` - `:left` (default), `:center`, or `:right`.
  * `:variant` - theme variant atom. Default `:default`.
  * `:class` - an atom or list of atoms matched by CSS selectors. Default `[]`;
    reaches the widget as `:classes`.

## Examples

    iex> label("Ready")
    {:label, "Ready", []}

    iex> label("Count: 3", style: %{bold: true}, align: :center)
    {:label, "Count: 3", [style: %{bold: true}, align: :center]}

`text` may instead be a list of `{text, style}` runs, drawn side by side on one line as
one widget, each run in its own style over the label's:

    iex> label([{"ok ", %{fg: :green}}, {"3 failed", %{fg: :red}}])
    {:label, [{"ok ", %{fg: :green}}, {"3 failed", %{fg: :red}}], []}

# `link`

```elixir
@spec link(String.t() | nil, String.t() | opts()) :: {:link, String.t() | nil, opts()}
```

A clickable hyperlink. Returns `{:link, text, opts}`.

`text` is the caption, or `nil` to take the `:text` option instead. The second
argument is either the URL as a `String.t()`, which is wrapped into `[url: url]`
and discards every other option, or a keyword list. Anything else — an atom, a
map, an integer — raises `FunctionClauseError`.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Link`:

  * `:url` - the address opened when the link is activated. Default `nil`, and
    activating it does nothing.
  * `:text` - fallback caption, used when `text` is `nil`. Default `nil`.
  * `:tooltip` - `String.t()` shown on hover. Default `nil`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

A focused link consumes `Enter`, which shells out to `open`, `xdg-open`, or
`start` for the platform.

    iex> link("Docs", "https://hexdocs.pm/drafter")
    {:link, "Docs", [url: "https://hexdocs.pm/drafter"]}

    iex> link("Docs", url: "https://hexdocs.pm/drafter", tooltip: "Hex")
    {:link, "Docs", [url: "https://hexdocs.pm/drafter", tooltip: "Hex"]}

# `loading_indicator`

```elixir
@spec loading_indicator(opts()) :: {:loading_indicator, opts()}
```

An animated spinner. Returns `{:loading_indicator, opts}`.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.LoadingIndicator`:

  * `:text` - caption beside the spinner. Default `"Loading..."`.
  * `:spinner_type` - `:default` (default), `:dots`, `:line`, or `:points`.
  * `:running` - `boolean()`. Default `true`.
  * `:gradient_colors` - list of `{r, g, b}` cycled through the caption.
    Default `nil`, i.e. a plain caption.
  * `:gradient_speed` - milliseconds per gradient step. Default `50`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

A re-render only refreshes the animation timestamp, so changing any of these
options after the first mount has no effect.

    iex> loading_indicator(text: "Fetching…", spinner_type: :line)
    {:loading_indicator, [text: "Fetching…", spinner_type: :line]}

# `log`

```elixir
@spec log(opts()) :: {:log, opts()}
```

A scrolling line log. Returns `{:log, opts}`.

See `Drafter.Widget.Log` for its options. Lines are appended with
`Drafter.push_data/2` when the element carries both `:buffer` and `:refresh`;
those two keys are lifted out of `opts` and used to open the widget's data
channel, and `:image_throttle` is lifted the same way.

Not focusable, so it reserves no keys.

    iex> log(id: :output, max_lines: 500)
    {:log, [id: :output, max_lines: 500]}

# `markdown`

```elixir
@spec markdown(String.t() | nil, opts()) :: {:markdown, String.t() | nil, opts()}
```

Rendered Markdown. Returns `{:markdown, content, opts}`.

`content` is the Markdown source as a `String.t()`, or `nil` to take the `:content`
option instead.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.Markdown`:

  * `:content` - fallback source, used when `content` is `nil`. Default `""`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:padding` - `non_neg_integer()` cells of inset. Default `1`.

Only `:content` reaches an already-mounted widget, so changing `:style` or
`:padding` on a re-render has no effect. Not focusable, so it reserves no keys.

    iex> markdown("# Title", padding: 0)
    {:markdown, "# Title", [padding: 0]}

# `masked_input`

```elixir
@spec masked_input(opts()) :: {:masked_input, opts()}
```

A text field that renders its content as mask characters.

Returns `{:masked_input, opts}`. `opts` is required and must be a keyword list —
there is no zero-arity form, so `masked_input()` is an `UndefinedFunctionError`
and any non-list argument a `FunctionClauseError`. See `Drafter.Widget.MaskedInput`
for its options.

A focused masked input consumes `←`, `→` and `Enter`.

    iex> masked_input(id: :pin, mask: "****-****")
    {:masked_input, [id: :pin, mask: "****-****"]}

# `meter`

```elixir
@spec meter(opts()) :: {:meter, opts()}
```

A labelled value bar. Returns `{:meter, opts}`.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Meter`:

  * `:value` - the reading, `0.0`..`1.0`. Default `0.0`.
  * `:label` - `String.t()` caption. Default `nil`.
  * `:orientation` - `:horizontal` (default) or `:vertical`.
  * `:thresholds` - the widget's colour bands. Default: its own threshold list.
  * `:show_value` - `boolean()`. Default `true`.
  * `:show_label` - `boolean()`. Default `true`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

`:style`, `:class` and the app module are read at mount time only; the rest are
live-updatable. `:height` is ignored for a horizontal meter.

    iex> meter(value: 0.82, label: "CPU")
    {:meter, [value: 0.82, label: "CPU"]}

# `option_list`

```elixir
@spec option_list([term()], opts()) :: {:option_list, [term()], opts()}
```

A single-selection list. Returns `{:option_list, items, opts}`.

`items` is the list of options; see `Drafter.Widget.OptionList` for their shape
and for the widget's options. `Drafter.get_widget_value/1` returns the selected
option's id.

A focused option list consumes `↓`, `Home`, `End`, `PageUp`, `PageDown`, `Enter`
and `Space`, and `↑` except on the first entry, where it bubbles so focus can
leave upwards.

    iex> option_list([%{id: :a, label: "Alpha"}], on_select: :chose)
    {:option_list, [%{id: :a, label: "Alpha"}], [on_select: :chose]}

# `pie_chart`

```elixir
@spec pie_chart(term(), opts()) :: {:pie_chart, term(), opts()}
```

A pie chart. Returns `{:pie_chart, data, opts}`.

`data` is the list of slices, each `{label, value}` or `{label, value, {r, g, b}}`.
An empty list or a keyword list is treated as absent and the `:data` option is used
instead. See `Drafter.Widget.PieChart` for the rest of its options; the ones with
non-obvious defaults are `:show_legend` (`true`), `:show_percentages` (`true`), and
`:renderer` (`:text`).

    iex> pie_chart([{"Elixir", 3}, {"Erlang", 1}], show_legend: false)
    {:pie_chart, [{"Elixir", 3}, {"Erlang", 1}], [show_legend: false]}

# `placeholder`

```elixir
@spec placeholder(opts()) :: {:placeholder, opts()}
```

A labelled block that fills its space, for laying out a screen before its content
exists. Returns `{:placeholder, opts}`.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.Placeholder`:

  * `:label` - the caption. Default: the `:text` option, itself defaulting to
    `"Placeholder"`.
  * `:text` - fallback caption when `:label` is absent. Default `"Placeholder"`.
  * `:padding` - `non_neg_integer()` cells of inset. Default `2`.
  * `:align` - `:center` (default), `:left`, or `:right`.
  * `:border` - `boolean()`. Default `false`.
  * `:style` - `map()` of style attributes. Default `%{}`.

Only the caption reaches an already-mounted widget; `:padding`, `:align`,
`:border` and `:style` are read at mount time.

    iex> placeholder(label: "Chart goes here", border: true)
    {:placeholder, [label: "Chart goes here", border: true]}

# `pop_screen`

```elixir
@spec pop_screen(term()) :: {:pop, term()}
```

Pops the top screen off the screen stack.

Returns `{:pop, result}`, which both `handle_event/2` and `handle_event/3`
dispatch. `result` (default `nil`) is delivered to the revealed screen's
`handle_event/3`.

    iex> pop_screen()
    {:pop, nil}

    iex> pop_screen({:selected, 3})
    {:pop, {:selected, 3}}

# `pretty`

```elixir
@spec pretty(term(), opts()) :: {:pretty, term(), opts()}
```

An inspected Elixir term, formatted and syntax-coloured.

Returns `{:pretty, data, opts}`. `data` is any term.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Pretty`:

  * `:expand` - `boolean()`, print one field per line. Default `false`.
  * `:syntax_highlighting` - `boolean()`. Default `true`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

Only `:data` reaches an already-mounted widget, so changing any of the others on a
re-render has no effect. Not focusable, so it reserves no keys.

    iex> pretty(%{status: :ok}, expand: true)
    {:pretty, %{status: :ok}, [expand: true]}

# `progress_bar`

```elixir
@spec progress_bar(opts()) :: {:progress_bar, opts()}
```

A horizontal progress bar. Returns `{:progress_bar, opts}`.

Options are the widget's props; see `Drafter.Widget.ProgressBar`. Not focusable,
so it reserves no keys. `:width` and `:height` fall back to the rect the parent
allocated.

    iex> progress_bar(value: 0.4)
    {:progress_bar, [value: 0.4]}

# `push_screen`

```elixir
@spec push_screen(module(), props(), opts()) ::
  {:push_screen, module(), props(), opts()}
```

Returns `{:push_screen, screen_module, props, opts}`.

`screen_module` is another `Drafter.App` module, `props` the map handed to its
`mount/1` (default `%{}`), `opts` the screen options (default `[]`).

The application loop and `Drafter.ActionHandler` dispatch on `{:push, module,
props, opts}`, not on this tag: returning this tuple from `handle_event/2` raises
`FunctionClauseError` in the loop, and returning it from `handle_event/3` leaves
the state unchanged with no screen pushed. Return `{:push, screen_module, props,
opts}` to push a screen.

    iex> push_screen(MyApp.Detail, %{id: 7})
    {:push_screen, MyApp.Detail, %{id: 7}, []}

# `radio_set`

```elixir
@spec radio_set([term()], opts()) :: {:radio_set, [term()], opts()}
```

A group of mutually exclusive radio buttons. Returns `{:radio_set, options, opts}`.

`options` is the list of choices; see `Drafter.Widget.RadioSet` for their shape and
for the widget's options. `Drafter.get_widget_value/1` returns the selected
option's id. A `:width` given here is ignored: the set is drawn to the rect the
parent allocated.

A focused radio set consumes `↑`, `↓`, `Enter` and `Space` — except `↑` on the
first entry, which bubbles so focus can leave upwards.

    iex> radio_set([%{id: :a, label: "A"}], on_change: :picked)
    {:radio_set, [%{id: :a, label: "A"}], [on_change: :picked]}

# `replace_screen`

```elixir
@spec replace_screen(module(), props(), opts()) :: {:replace, module(), props()}
```

Returns `{:replace, screen_module, props}`.

`screen_module` is another `Drafter.App` module and `props` the map handed to its
`mount/1` (default `%{}`). The third argument is accepted and discarded.

The application loop and `Drafter.ActionHandler` dispatch on the four-element
`{:replace, module, props, opts}`, not on this three-element tuple: returning this
from `handle_event/2` raises `FunctionClauseError` in the loop, and returning it
from `handle_event/3` leaves the state unchanged with no screen replaced. Return
`{:replace, screen_module, props, opts}` to replace the top screen.

    iex> replace_screen(MyApp.Detail, %{id: 7}, title: "ignored")
    {:replace, MyApp.Detail, %{id: 7}}

# `rich_log`

```elixir
@spec rich_log(opts()) :: {:rich_log, opts()}
```

A scrolling log that keeps per-line styling. Returns `{:rich_log, opts}`.

See `Drafter.Widget.RichLog` for its options. As with `log/1`, `:buffer`,
`:refresh` and `:image_throttle` open a `Drafter.push_data/2` channel. Not
focusable, so it reserves no keys.

    iex> rich_log(id: :events)
    {:rich_log, [id: :events]}

# `rule`

```elixir
@spec rule(opts()) :: {:rule, opts()}
```

A divider line. Returns `{:rule, opts}`.

Options beyond the shared element keys, all forwarded to `Drafter.Widget.Rule`:

  * `:orientation` - `:horizontal` (default) or `:vertical`.
  * `:title` - `String.t()` set into the line. Default `nil`.
  * `:title_align` - `:center` (default), `:left`, or `:right`.
  * `:line_style` - `:solid` (default) and the other styles the widget lists.
  * `:style` - `map()` of style attributes. Default `%{}`.

Every option is live-updatable through the component tree.

    iex> rule(title: "Details")
    {:rule, [title: "Details"]}

# `screen_layout`

```elixir
@spec screen_layout(keyword()) :: {:layout, :vertical, [element()], []}
```

Assembles a header/content/footer screen into one vertical layout.

`parts` is a keyword list read for `:header`, `:content`, and `:footer`; each
defaults to `nil` and an omitted part leaves no gap. Every other key is ignored.
`:content` may be a single element or a list of them. When a `:footer` is present
the content is wrapped in a `vertical(content, flex: 1)` so it absorbs the leftover
rows; without a footer a list of content elements is spliced in directly.

Returns a `vertical/2` element whose own options are always `[]`, so `:gap`,
`:padding` and the shared element keys cannot be set through this function — build
the `vertical/2` by hand when you need them.

    iex> screen_layout(header: header("Files"), content: [tree(id: :files)], footer: footer())
    {:layout, :vertical,
     [
       {:header, "Files", []},
       {:layout, :vertical, [{:tree, [id: :files]}], [flex: 1]},
       {:footer, []}
     ], []}

    iex> screen_layout(content: label("body"))
    {:layout, :vertical, [{:label, "body", []}], []}

# `scrollable`

```elixir
@spec scrollable([element()], opts()) :: {:scrollable, [element()], opts()}
```

A viewport that scrolls its children. Returns `{:scrollable, children, opts}`.

`children` is a list of elements, whose summed preferred heights become the
scrollable content height. One column on the right is reserved for the vertical
scrollbar.

Options beyond the shared element keys — these five are the only ones this element
reads, and the rest of `Drafter.Widget.ScrollableContainer`'s options are not
forwarded:

  * `:focusable` - `boolean()`. Default `true`. `false` keeps the container out of
    the Tab order, and the keys below then reach whatever else has focus.
  * `:click_to_scroll` - `boolean()`, jump the viewport to a clicked position.
    Default `false`.
  * `:show_vertical_scrollbar` - `:auto` (default), `:always`, or `:never`.
  * `:show_horizontal_scrollbar` - `:never` (default), `:auto`, or `:always`.
  * `:scrollbar` - `:block` (default, the skin's solid thumb) or `:thin` (a line
    thumb on a dim line track).
  * `:height` - `pos_integer()` used as the element's preferred height in a
    parent's layout. Default: the summed heights of `children`.

A focused scrollable consumes `↑`, `↓`, `Home`, `End`, `PageUp` and `PageDown`.

    iex> scrollable([label("row 1")], show_vertical_scrollbar: :always)
    {:scrollable, [{:label, "row 1", []}], [show_vertical_scrollbar: :always]}

# `selection_list`

```elixir
@spec selection_list([term()], opts()) :: {:selection_list, [term()], opts()}
```

A list whose entries can be checked. Returns `{:selection_list, options, opts}`.

`options` is the list of choices; see `Drafter.Widget.SelectionList` for their
shape and the widget's options. `Drafter.get_widget_value/1` returns the list of
selected option ids. A focused selection list consumes `↑`, `↓`, `Home`, `End`,
`Enter` and `Space`.

    iex> selection_list([%{id: :a, label: "A"}], on_change: :selected)
    {:selection_list, [%{id: :a, label: "A"}], [on_change: :selected]}

# `show_modal`

```elixir
@spec show_modal(module(), props(), opts()) ::
  {:show_modal, module(), props(), opts()}
```

Opens `screen_module` as a modal over the current screen.

Returns `{:show_modal, screen_module, props, opts}`, which both `handle_event/2`
and `handle_event/3` dispatch. `props` (default `%{}`) is the map handed to the
modal's `mount/1`; `opts` (default `[]`) is passed to
`Drafter.App.show_modal/3` and carries the modal's `:title`, `:width`,
`:height`, and `:border`.

    iex> show_modal(ConfirmModal, %{}, title: "Confirm", width: 45, height: 10)
    {:show_modal, ConfirmModal, %{}, [title: "Confirm", width: 45, height: 10]}

# `show_panel`

```elixir
@spec show_panel(module(), props(), opts()) ::
  {:push_screen, module(), props(), opts()}
```

Returns `{:push_screen, screen_module, props, [{:type, :panel} | opts]}`.

A `:type` already in `opts` is overwritten with `:panel`. Carries the same
unmatched tag as `push_screen/3` and has the same failure modes.

    iex> show_panel(MyApp.Sidebar, %{}, width: 30)
    {:push_screen, MyApp.Sidebar, %{}, [type: :panel, width: 30]}

# `show_popover`

```elixir
@spec show_popover(module(), props(), opts()) ::
  {:push_screen, module(), props(), opts()}
```

Returns `{:push_screen, screen_module, props, [{:type, :popover} | opts]}`.

A `:type` already in `opts` is overwritten with `:popover`. Carries the same
unmatched tag as `push_screen/3` and has the same failure modes: the loop raises
`FunctionClauseError` on it from `handle_event/2`, and ignores it from
`handle_event/3`.

    iex> show_popover(MyApp.Menu)
    {:push_screen, MyApp.Menu, %{}, [type: :popover]}

# `show_toast`

```elixir
@spec show_toast(String.t(), opts()) :: {:show_toast, String.t(), opts()}
```

Shows a transient message over the current screen.

Returns `{:show_toast, message, opts}`, which both `handle_event/2` and
`handle_event/3` dispatch. `message` is a `String.t()`; `opts` (default `[]`) is
passed to `Drafter.App.show_toast/2`, which documents the keys it reads.

    iex> show_toast("Saved", duration: 2000)
    {:show_toast, "Saved", [duration: 2000]}

# `sidebar`

```elixir
@spec sidebar([element()], [element()], opts()) ::
  {:layout, :horizontal, [element()], opts()}
```

A fixed-width column beside a column that takes the rest of the width.

`left_children` and `right_children` are lists of elements. `opts` is passed on to
the enclosing `horizontal/2` unchanged — including `:sidebar_width` itself, which
the layout ignores — and is additionally read for:

  * `:sidebar_width` - `pos_integer()` columns for the left column. Default `20`.

The left column becomes `vertical(left_children, width: sidebar_width)` and the
right `vertical(right_children, flex: 1)`. Neither column receives any of `opts`.

    iex> sidebar([label("nav")], [label("body")], sidebar_width: 12)
    {:layout, :horizontal,
     [
       {:layout, :vertical, [{:label, "nav", []}], [width: 12]},
       {:layout, :vertical, [{:label, "body", []}], [flex: 1]}
     ], [layout: :horizontal, sidebar_width: 12]}

# `slider`

```elixir
@spec slider(opts()) :: {:slider, opts()}
```

A draggable value slider. Returns `{:slider, opts}`.

`Drafter.get_widget_value/1` returns the `number()` it sits at. A focused slider
moves on `←`/`↓` and `→`/`↑`, ten steps on `PageUp`/`PageDown`, and to the ends of
the range on `Home`/`End`; a press or drag anywhere on the track moves the thumb
there.

Options beyond the shared element keys, forwarded to `Drafter.Widget.Slider`:

  * `:value` - `number()` to start at, clamped and snapped. Default: `:min`.
  * `:min` / `:max` - ends of the range. Default `0.0` and `1.0`.
  * `:step` - `number()` the value moves in. Default `nil`, a hundredth of the
    range, or whole numbers when `:min` and `:max` are both integers.
  * `:bind` - app-state key atom for two-way binding. Default `nil`. Only with a
    `:bind` does a later `:value` change reach the mounted widget.
  * `:label` - `String.t()` drawn ahead of the track. Default `nil`.
  * `:show_value` - draw the readout after the track. Default `true`.
  * `:format` - `(number() -> String.t())` for the readout. Default `nil`.
  * `:precision` - decimals in the readout. Default: as many as `:step` needs.
  * `:orientation` - `:horizontal` (default) or `:vertical`.
  * `:disabled` - `boolean()`. Default `false`.
  * `:on_change` - atom event name or one-arity function receiving the new number.
  * `:track_color` / `:fill_color` / `:thumb_color` - `{r, g, b}` overrides.
  * `:renderer` - `:text` (default), `:braille`, or a graphics protocol atom, which
    draws the track through `french_curve`.

## Examples

    iex> slider(value: 0.5, label: "Gain", on_change: :set_gain)
    {:slider, [value: 0.5, label: "Gain", on_change: :set_gain]}

# `slider`

```elixir
@spec slider(number(), opts()) :: {:slider, opts()}
```

Returns `{:slider, [{:value, value} | opts]}`.

    iex> slider(0.25, label: "Mix")
    {:slider, [value: 0.25, label: "Mix"]}

# `sparkline`

```elixir
@spec sparkline([number()], opts()) :: {:sparkline, [number()], opts()}
```

A single-row trend line. Returns `{:sparkline, data, opts}`.

`data` is a list of numbers. A keyword list, or an empty list, is treated as
absent and the `:data` option is used instead.

Options beyond the shared element keys, all forwarded to
`Drafter.Widget.Sparkline`:

  * `:data` - fallback series. Default `[]`.
  * `:min_value` / `:max_value` - fix the vertical range. Defaults `nil`, i.e.
    taken from the data.
  * `:color` - `{r, g, b}` for the whole line. Default `nil`.
  * `:min_color` / `:max_color` - endpoints of a value-based gradient.
    Defaults `nil`.
  * `:summary` - `boolean()`, appends min/max/last. Default `false`.
  * `:orientation` - `:vertical` (default) or `:horizontal`.
  * `:style` - `map()` of style attributes. Default `%{}`.
  * `:class` - an atom or list of atoms. Default `[]`.

## Examples

    iex> sparkline([1, 4, 2, 8], summary: true)
    {:sparkline, [1, 4, 2, 8], [summary: true]}

# `split_pane`

```elixir
@spec split_pane([element()], opts()) :: {:split_pane, [element()], opts()}
```

Two panes separated by a draggable divider.

Returns `{:split_pane, children, opts}`. `children` is the list of pane elements;
only the **first two** are laid out — a third and beyond are dropped.

Options beyond the shared element keys:

  * `:orientation` - `:horizontal` (default) puts the panes side by side with a
    vertical divider; `:vertical` stacks them.
  * `:ratio` - `float()` in `0.0`..`1.0`, the divider's starting position.
    Default `0.5`. It is a mount-time value only: a later `:ratio` change does not
    reach the already-created divider.
  * `:show_handle` - `boolean()`, draw the grip glyph. Default `true`. Mount-time
    only.
  * `:resize_mode` - `:quick` (default) or the other modes
    `Drafter.Widget.SplitPaneDivider` accepts. Mount-time only.
  * `:id` - names the divider `:"<id>_divider"` rather than the generated
    `:"<Module>_split_divider_<n>"`.

See `Drafter.Widget.SplitPaneDivider` for the divider itself.

    iex> split_pane([label("left"), label("right")], ratio: 0.3)
    {:split_pane, [{:label, "left", []}, {:label, "right", []}], [ratio: 0.3]}

# `static`

```elixir
@spec static(term(), opts()) :: {:static, term(), opts()}
```

Pre-rendered content that is drawn as given. Returns `{:static, content, opts}`.

`content` is the text to draw. It becomes a `Drafter.Widget.Label` whose style is
the theme's foreground and background merged with `:style`.

  * `:style` - `map()` of style attributes merged over the theme's. Default `%{}`.

Every other option is ignored, **including `:id` and `:key`**: a static element's
widget id is always `:static_<n>` from its position in the tree, so it cannot be
reached by `Drafter.get_widget_value/1` or `Drafter.focus/1` under a name you
chose. Use `label/2` when you need an id.

    iex> static("v1.4.0", style: %{dim: true})
    {:static, "v1.4.0", [style: %{dim: true}]}

# `switch`

```elixir
@spec switch(opts()) :: {:switch, opts()}
```

An on/off switch. Returns `{:switch, opts}`.

`Drafter.get_widget_value/1` returns a `boolean()`. A focused switch acts on
`Enter` and `Space` to toggle and on `←`/`→` to force off/on; a key that leaves the
state as it was falls through to `handle_event/2`.

Options beyond the shared element keys, forwarded to `Drafter.Widget.Switch`:

  * `:enabled` - `boolean()` initial state. Default `false`. **This is the key
    the widget reads**, not `:value`.
  * `:bind` - app-state key atom for two-way binding. Default `nil`. Only with a
    `:bind` does a later `:enabled` change reach the mounted widget.
  * `:label` - `String.t()` shown to the right of the track. Default `nil`.
  * `:on_change` - atom event name or one-arity function receiving the new
    `boolean()`. Default `nil`.
  * `:size` - `:normal` (default), `:small`, or `:compact`.
  * `:width` / `:height` - column and row counts. Default: the width and height of
    the rect the parent allocated.

`:on_color` and `:off_color` are read by the widget's `mount/1` only and are not
forwarded by this element.

    iex> switch(enabled: true, label: "Dark mode", on_change: :toggle_theme)
    {:switch, [enabled: true, label: "Dark mode", on_change: :toggle_theme]}

# `switch`

```elixir
@spec switch(term(), opts()) :: {:switch, opts()}
```

Returns `{:switch, [{:value, value} | opts]}`.

`value` is stored under `:value`, which `Drafter.Widget.Switch` does not read — the
widget's on/off state comes from `:enabled`. Use this form only for
`switch_group/2`, which reads `:value` itself to build each switch's callback
payload; use `switch/1` with `enabled:` to set the state of a standalone switch.

    iex> switch("compact", label: "Compact")
    {:switch, [value: "compact", label: "Compact"]}

# `switch_group`

```elixir
@spec switch_group(term(), [opts()]) :: {:layout, :horizontal, [element()], opts()}
```

A two-column block of switches that all report to one callback.

`group_name` is any term; `switches` is a list of keyword lists, each the options
for one `switch/1`. Each switch's `:on_change` is **overwritten** with
`{:switch_group_changed, group_name, value}`, where `value` is that switch's
`:value` option or, absent that, its `:label`. Its `:label` is re-set from its own
`:label`, and every other option is passed through.

That tuple reaches the module as `handle_event({:switch_group_changed, group_name,
value}, enabled?, state)` — `handle_event/3` with a tuple rather than an atom as its
first argument, and the switch's new `boolean()` as `data`.

Returns the `horizontal/2` element holding two `vertical/2` columns of `width: 30`
with `gap: 2` between them. The first `div(length(switches) + 1, 2)` entries fill
the left column, so an odd count puts the extra switch on the left.

    iex> switch_group(:sizes, [[label: "Small", value: :s], [label: "Large"]])
    {:layout, :horizontal,
     [
       {:layout, :vertical,
        [{:switch, [value: :s, label: "Small",
           on_change: {:switch_group_changed, :sizes, :s}]}], [width: 30]},
       {:layout, :vertical,
        [{:switch, [label: "Large",
           on_change: {:switch_group_changed, :sizes, "Large"}]}], [width: 30]}
     ], [layout: :horizontal, gap: 2]}

# `tabbed_content`

```elixir
@spec tabbed_content([term()], opts()) :: {:tabbed_content, [term()], opts()}
```

A tab bar over switchable panes. Returns `{:tabbed_content, tabs, opts}`.

`tabs` is the list of tabs — a string label, a `{label, content}` tuple, or a map
with `:id`, `:label` and `:content`. `Drafter.get_widget_value/1` returns the
active tab index. See `Drafter.Widget.TabbedContent` for the widget's options;
the non-obvious defaults are `:active_tab` (`0`), `:title_align` (`:left`), and
`:width` (the available rect width).

`:on_item_select` is read by the widget's `mount/1` only and is not forwarded by
this element.

A focused tabbed content consumes `→`, `↑`, `↓` and `Enter`, and `←` except on the
first tab, where it falls through. It explicitly declines `Tab` so focus can still
move on.

    iex> tabbed_content([%{id: :a, label: "A", content: ["x"]}], on_tab_change: :tab)
    {:tabbed_content, [%{id: :a, label: "A", content: ["x"]}], [on_tab_change: :tab]}

# `text_area`

```elixir
@spec text_area(opts()) :: {:text_area, opts()}
```

A multi-line text editor. Returns `{:text_area, opts}`.

`Drafter.get_widget_value/1` returns the current text as a `String.t()`.
`Drafter.Widget.TextArea` documents every option; the frequently used ones are
`:value` (default `""`), `:show_line_numbers` (default `false`), `:read_only`
(default `false`), `:language` (default `nil`), `:trap_focus` (default `false`),
`:tab_behavior` (`:focus` by default), and `:tab_size` (default `2`).

`:placeholder_style`, `:focused_style`, `:selection_style`, `:line_number_style`
and `:max_checkpoints` are read by the widget's `mount/1` only — this element does
not forward them.

`:width` and `:height` are taken from the rect the parent allocated rather than
from the widget's own defaults. With `trap_focus: true` the editor receives `Tab`
itself instead of `Tab` moving focus.

    iex> text_area(id: :body, show_line_numbers: true, language: :elixir)
    {:text_area, [id: :body, show_line_numbers: true, language: :elixir]}

# `text_input`

```elixir
@spec text_input(opts()) :: {:text_input, opts()}
```

A single-line text field. Returns `{:text_input, opts}`.

`Drafter.get_widget_value/1` returns the current text as a `String.t()`.
`Drafter.Widget.TextInput` documents every option and every key it binds; the
frequently used ones are `:value` (default `""`), `:placeholder` (default `""`),
`:on_change`, `:on_submit`, `:disabled` (default `false`), `:readonly` (default
`false`), `:password` (default `false`), and `:type` (`:text` by default).

`:max_length` and `:width` are read by the widget's `mount/1` only — this element
does not forward them, so setting either here has no effect.

While this widget has focus it consumes every printable key, `Space`, `←`, `→`,
`Home`, `End`, `Backspace` and `Ctrl+A`, so none of those reach `handle_event/2` or
a `keybinding/3` clause. `↑`, `↓`, `Escape`, `PageUp` and `PageDown` pass through,
and so does `Enter` unless `:on_submit` is set.

    iex> text_input(id: :name, placeholder: "Your name")
    {:text_input, [id: :name, placeholder: "Your name"]}

# `theme_selector`

```elixir
@spec theme_selector(opts()) :: {:theme_selector, opts()}
```

A list of the installed themes that switches theme on selection.

Returns `{:theme_selector, opts}`. **Every option is accepted and ignored**,
including `:id` and `:key`: the widget is a `Drafter.Widget.OptionList` built from
`Drafter.ThemeManager`'s registered themes, its id is always `:theme_selector_<n>`
from its position in the tree, and it takes focus the first time it renders.
Highlighting or selecting an entry applies that theme immediately.

    iex> theme_selector()
    {:theme_selector, []}

# `tree`

```elixir
@spec tree(opts()) :: {:tree, opts()}
```

An expandable tree of nodes. Returns `{:tree, opts}`.

Options are the widget's props; see `Drafter.Widget.Tree`, whose "Key bindings"
section lists what a focused tree acts on — the arrow keys, `Enter`, `Space`, `+`,
`-`, `*` and `/` among them. A key it acts on does not reach `handle_event/2` or a
`keybinding/3` clause; one that leaves the tree unchanged, such as `←` on an
already-collapsed node, falls through. `Drafter.get_widget_value/1` returns the
list of selected node ids.

    iex> tree(id: :files, nodes: [])
    {:tree, [id: :files, nodes: []]}

# `vertical`

```elixir
@spec vertical([element()], opts()) :: {:layout, :vertical, [element()], opts()}
```

Stacks `children` top to bottom. Returns `{:layout, :vertical, children, opts}`.

`children` is a list of elements. `opts` is stored verbatim; unlike `horizontal/2`
no `:layout` key is added.

Options beyond the shared element keys:

  * `:gap` - `non_neg_integer()` rows between children. Default `0`.
  * `:padding` - space inside the container, in the same shapes as `:margin`.
    Default `nil`, i.e. none.
  * `:width` - the column width given to every child. Default `nil`, which uses
    the container's own width.

Each child's `:height` and `:flex` divide the column.

    iex> vertical([header("Title"), footer()])
    {:layout, :vertical, [{:header, "Title", []}, {:footer, []}], []}

---

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