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

A boolean toggle widget that renders an `X` mark inside a box next to an optional label.

The checked state is toggled by pressing Space, Enter, or clicking the widget. The
`:on_change` callback receives the new boolean value after each toggle.

## Component tag

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

    checkbox(label, opts)

The positional argument becomes `:label`. The checked state goes through
the binding layer: passing `bind: :some_key` reads it from that app-state key
and writes the new value back on every toggle, and `:on_change` is built from
the same binding.

## Options

  * `:label` - `t:String.t/0` displayed to the right of the box. Default `""`.
    Supplied positionally through the `checkbox/2` element
  * `:checked` - `t:boolean/0` initial checked state. Default `false`
  * `:bind` - app-state key atom for two-way binding of the checked state.
    Default `nil`
  * `:on_change` - `(boolean() -> term())` called with the new value after each
    toggle. Default `nil`. An exception raised inside it is caught and ignored
  * `:style` - `t:map/0` of style overrides. Default `%{}`. An `:app_module` key
    inside this map selects the theme used for the box and label colours
  * `:focused` - `t:boolean/0` initial focus flag, read by `mount/1`. Default
    `false`
  * `:class` - accepted by the element and normalised into a `:classes` prop, but
    `mount/1` and `update/2` both ignore it, so it has no effect

`update/2` accepts `:label`, `:checked`, `:focused`, `:style` and `:on_change` and
silently drops every other key. Through the component tree only `:on_change` and,
when `:bind` is set, `:checked` are re-applied on a re-render — `:label` and
`:style` are effectively mount-only there.

## Widget value

`Drafter.get_widget_value/1` returns the checked `t:boolean/0`.

## Key bindings

`Enter` and `Space` with no modifiers toggle the checkbox, as does a mouse
release. The same keys with modifiers are ignored.

## Usage

    checkbox("Remember me", checked: false, on_change: fn checked -> IO.inspect(checked) end)

# `t`

```elixir
@type t() :: %Drafter.Widget.Checkbox{
  checked: boolean(),
  focused: boolean(),
  hovered: boolean(),
  label: String.t(),
  on_change: (boolean() -&gt; term()) | nil,
  style: map()
}
```

# `component_tag`

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

The registry tag for this widget.

    iex> Drafter.Widget.Checkbox.component_tag()
    :checkbox

# `focused`

# `from_component_opts`

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

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

`label` is the positional argument. The checked state comes from `:bind` read
against `opts[:__app_state__]`, falling back to `opts[:checked]` and then `false`,
and `:on_change` is the binding's writer. The emitted `:classes` key is not read
by `mount/1`.

    iex> props = Drafter.Widget.Checkbox.from_component_opts("Agree", checked: true)
    iex> {props.label, props.checked, props.style, props.classes}
    {"Agree", true, %{}, []}

# `handle_event`

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

Handles events directly instead of going through `Drafter.Widget.EventRouter`.

`:activate`, `{:key, :enter}`, `{:key, :" "}` and `{:mouse, %{type: :mouse_up}}`
flip `:checked` and call `:on_change` with the new value. `:hover` and `:unhover`
set and clear `:hovered`; `{:focus}` sets both `:focused` and `:hovered`, and
`{:blur}` clears both. Everything else, including a key event carrying modifiers,
returns `{:noreply, state}`.

    iex> cb = Drafter.Widget.Checkbox.mount(%{label: "Agree"})
    iex> {:ok, toggled} = Drafter.Widget.Checkbox.handle_event({:key, :" "}, cb)
    iex> toggled.checked
    true

    iex> cb = Drafter.Widget.Checkbox.mount(%{label: "Agree"})
    iex> {:ok, focused} = Drafter.Widget.Checkbox.handle_event({:focus}, cb)
    iex> {focused.focused, focused.hovered}
    {true, true}

    iex> cb = Drafter.Widget.Checkbox.mount(%{label: "Agree"})
    iex> Drafter.Widget.Checkbox.handle_event({:key, :enter, [:shift]}, cb) |> elem(0)
    :noreply

# `mount`

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

Builds the checkbox state from `props`.

`:hovered` always starts at `false`.

    iex> cb = Drafter.Widget.Checkbox.mount(%{label: "Remember me", checked: true})
    iex> {cb.label, cb.checked, cb.focused, cb.hovered}
    {"Remember me", true, false, false}

    iex> cb = Drafter.Widget.Checkbox.mount(%{})
    iex> {cb.label, cb.checked, cb.style, cb.on_change}
    {"", false, %{}, nil}

# `preferred_height`

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

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

# `render`

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

Draws the indicator and the label into `rect`.

Accepts either a `t:t/0` or a raw props map, which is mounted first. The indicator
takes the first three columns and the label the rest; a rect narrower than four
columns leaves no room for the label. Returns one strip per row of `rect.height`,
with only the first carrying content.

# `unmount`

# `update`

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

Folds fresh props into `state`.

Only `:label`, `:checked`, `:focused`, `:style` and `:on_change` are applied; any
other key in `props` is dropped without error.

    iex> cb = Drafter.Widget.Checkbox.mount(%{label: "Agree"})
    iex> updated = Drafter.Widget.Checkbox.update(%{checked: true, nonsense: 1}, cb)
    iex> {updated.checked, updated.label}
    {true, "Agree"}

# `update_props_from_mount`

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

Narrows the props a re-render feeds to `update/2`.

Always passes `:on_change` and `:classes`, and adds `:checked` only when `opts`
carries a `:bind`. `:label` and `:style` are deliberately left out, so a re-render
does not overwrite them.

---

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