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

Draws a titled border around a region; children render inside the border.

The box itself renders only the frame and the title. Children are laid out by
the component renderer into the rect remaining after the border and padding
are subtracted, so a box one row tall has no room for content.

A `:title` is embedded in the top border. Border style `:none` still occupies
no rows, meaning the content rect is inset by padding only.

## Component tag

This module has no `component_tag/0` and is not reached through the widget
registry. `Drafter.App` builds it as the element `{:box, children, opts}`:

    box(children, opts)

`children` is a list of child element tuples. `opts` is a keyword list read
by the renderer, which passes `:title`, `:border`, `:padding` and `:style`
through to `mount/1`.

## Options

  * `:title` — string embedded in the top border; `nil` for none (default `nil`)
  * `:border` — border style atom, one of `:none`, `:single`, `:double`,
    `:rounded`, `:heavy`, `:dashed`, `:ascii`. Defaults to the current
    character set's border style, falling back to `:rounded`
  * `:padding` — inner padding in columns and rows (default `0` when mounting
    this module directly; the `box/2` element defaults it to the character
    set's padding, falling back to `1`)
  * `:style` — map of style overrides for the widget as a whole
  * `:border_style` — map of style overrides for the border characters
  * `:title_style` — map of style overrides for the title text
  * `:content_style` — map of style overrides for the interior fill
  * `:classes` — list of theme class atoms

`:border_style`, `:title_style`, `:content_style` and `:classes` are read by
`mount/1` only; the `{:box, children, opts}` element does not forward them.

Every option is live-updatable: `update/2` merges the props map into the state.

## Usage

    box([label("Ready")], title: "Status", border: :double, padding: 1)

# `border`

```elixir
@type border() :: :none | :single | :double | :rounded | :heavy | :dashed | :ascii
```

# `t`

```elixir
@type t() :: %Drafter.Widget.Box{
  app_module: module() | nil,
  border: border(),
  border_style: map(),
  classes: [atom()],
  content_style: map(),
  padding: non_neg_integer(),
  style: map(),
  title: String.t() | nil,
  title_style: map()
}
```

# `handle_event`

```elixir
@spec handle_event(Drafter.Event.t(), t()) :: {:noreply, t()}
```

Ignores every event and returns `{:noreply, state}`. The box is not focusable and
never consumes input; children handle their own events.

# `mount`

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

Builds the box state from `props`.

An unknown `:border` value is kept on the state as given and falls back to the
`:rounded` characters at render time.

    iex> box = Drafter.Widget.Box.mount(%{title: "Status", border: :double, padding: 2})
    iex> {box.title, box.border, box.padding}
    {"Status", :double, 2}

    iex> box = Drafter.Widget.Box.mount(%{})
    iex> {box.title, box.padding, box.classes, box.style}
    {nil, 0, [], %{}}

# `render`

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

Draws the frame, the title and the blank interior of the box into `rect`.

Accepts either a `t:t/0` or a raw props map, which is mounted first. Emits, in
order, the top border row (when `:border` is not `:none`), `:padding` blank rows,
the interior rows, `:padding` blank rows again, and the bottom border row. At
least one interior row is always emitted, so a box shorter than its own chrome
returns more strips than `rect.height`.

# `update`

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

Merges `props` into `state`, so every option is live-updatable.

Keys absent from `props` keep their current value.

    iex> state = Drafter.Widget.Box.mount(%{title: "One"})
    iex> Drafter.Widget.Box.update(%{title: "Two", padding: 3}, state).title
    "Two"

---

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