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

Renders a horizontal breadcrumb trail with keyboard and mouse navigation.

Items are displayed as a single line separated by a configurable separator
string. The last item is highlighted as the active/current crumb using the
theme's primary colour and bold weight. When items exceed the available
width, the leftmost crumbs are replaced with an ellipsis (`...`).

## Component tag

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

    breadcrumb(items, opts)

The positional argument becomes `:items`. `from_component_opts/2` wraps
`:on_click` with `Drafter.Widget.Callback`, so it may be given as an atom event
name.

## Options

  * `:items` - `[{String.t(), term()} | String.t()]`. Default `[]`. Supplied
    positionally through the `breadcrumb/2` element. A plain string is used as
    both label and id. A label that is not a binary raises `FunctionClauseError`
  * `:separator` - `t:String.t/0` drawn between items. Default `" › "`
  * `:on_click` - atom event name, or a function of arity 1 receiving the clicked
    item's id, or of arity 0. Default `nil`
  * `:active` - `t:boolean/0`, highlight the last item as the current crumb.
    Default `true`
  * `:style` - `t:map/0` of style overrides. Default `%{}`
  * `:class` - theme class atom or list of them, reaching `mount/1` as
    `:classes`. Default `[]`
  * `:focused` - `t:boolean/0` initial focus flag, read by `mount/1` only.
    Default `false`
  * `:app_module` - module supplying a per-app theme, passed by the renderer as
    `:__app_module__`. Default `nil`

`update/2` re-reads `:items`, `:separator`, `:on_click`, `:active`, `:style`,
`:classes`, `:app_module` and `:focused_index`, so all of those are live. It does
not re-read `:focused`, which is owned by the focus system after mount.

## Widget value

`Drafter.get_widget_value/1` is not implemented for this widget; the selected
crumb is reported through `:on_click` instead.

## Key bindings

Handled when focused: `:left` and `:right` move `:focused_index` within the item
range, `:enter` and `:" "` fire `:on_click` for the focused crumb. Every other key
bubbles. A mouse release inside a crumb's label selects that crumb.

## Usage

    breadcrumb([{"Home", :home}, {"Products", :products}, "Details"])
    breadcrumb([{"Home", :home}, {"Settings", :settings}], separator: " / ", on_click: :crumb_clicked)

# `item`

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

# `t`

```elixir
@type t() :: %Drafter.Widget.Breadcrumb{
  active: boolean(),
  app_module: module() | nil,
  classes: [atom()],
  focused: boolean(),
  focused_index: non_neg_integer(),
  items: [item()],
  on_click: (term() -&gt; any()) | (-&gt; any()) | nil,
  separator: String.t(),
  style: map()
}
```

# `component_tag`

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

The registry tag for this widget.

    iex> Drafter.Widget.Breadcrumb.component_tag()
    :breadcrumb

# `focused`

# `from_component_opts`

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

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

`items` is the positional argument. `:class` is normalised into `:classes`,
`:on_click` is wrapped by `Drafter.Widget.Callback.wrap_1/1`, and
`:__app_module__` becomes `:app_module`.

    iex> props = Drafter.Widget.Breadcrumb.from_component_opts(["Home"], separator: " / ")
    iex> {props.items, props.separator, props.on_click, props.classes, props.active}
    {["Home"], " / ", nil, [], true}

# `handle_event`

# `handle_key`

```elixir
@spec handle_key(Drafter.Widget.key(), t()) ::
  {:ok, t()} | {:ok, t(), [tuple()]} | {:bubble, t()}
```

Moves the focus between crumbs and activates the focused one.

`:left` and `:right` clamp `:focused_index` to `0..length(items) - 1` and return
`{:ok, state}`. `:enter` and `:" "` return `{:ok, state, actions}` where `actions`
holds an `{:app_callback, name, data}` tuple when `:on_click` produced one, and is
`[]` otherwise. Any other key returns `{:bubble, state}` unchanged.

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{items: ["a", "b", "c"]})
    iex> {:ok, moved} = Drafter.Widget.Breadcrumb.handle_key(:right, crumbs)
    iex> moved.focused_index
    1

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{items: ["a"]})
    iex> {:ok, same} = Drafter.Widget.Breadcrumb.handle_key(:left, crumbs)
    iex> same.focused_index
    0

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{items: ["a"]})
    iex> Drafter.Widget.Breadcrumb.handle_key(:escape, crumbs) |> elem(0)
    :bubble

# `handle_mouse_up`

```elixir
@spec handle_mouse_up(integer(), integer(), t()) :: {:ok, t()} | {:ok, t(), [tuple()]}
```

Selects the crumb whose label spans column `x`, counted from the left edge of the
widget.

Returns `{:ok, state, actions}` for a hit and `{:ok, state}` when `x` falls on a
separator or past the last crumb, so the event is consumed either way. The column
map is computed from the untruncated item list.

# `mount`

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

Builds the breadcrumb state from `props`, normalising `:items` so that every entry
is a `{label, id}` tuple.

`:focused_index` always starts at `0`, whatever `props` contains.

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{items: [{"Home", :home}, "Details"]})
    iex> crumbs.items
    [{"Home", :home}, {"Details", "Details"}]

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{})
    iex> {crumbs.items, crumbs.separator, crumbs.active, crumbs.focused_index}
    {[], " › ", true, 0}

# `preferred_height`

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

Always `1`: the trail occupies a single row whatever the item count.

# `render`

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

Draws the trail as a single strip padded to `rect.width`.

Accepts either a `t:t/0` or a raw props map, which is mounted first. When the
crumbs do not fit, the leftmost ones are dropped and replaced with `"..."`.
Always returns exactly one strip.

# `unmount`

# `update`

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

Folds fresh props into `state`.

Re-reads `:items`, `:separator`, `:on_click`, `:active`, `:style`, `:classes`,
`:app_module` and `:focused_index`; `:focused` is left alone. `:focused_index` is
clamped to the new item count.

    iex> crumbs = Drafter.Widget.Breadcrumb.mount(%{items: ["a", "b", "c"]})
    iex> {:ok, crumbs} = Drafter.Widget.Breadcrumb.handle_key(:right, crumbs)
    iex> Drafter.Widget.Breadcrumb.update(%{items: ["a"]}, crumbs).focused_index
    0

# `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*
