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 thebreadcrumb/2element. A plain string is used as both label and id. A label that is not a binary raisesFunctionClauseError:separator-String.t/0drawn between items. Default" › ":on_click- atom event name, or a function of arity 1 receiving the clicked item's id, or of arity 0. Defaultnil:active-boolean/0, highlight the last item as the current crumb. Defaulttrue:style-map/0of style overrides. Default%{}:class- theme class atom or list of them, reachingmount/1as:classes. Default[]:focused-boolean/0initial focus flag, read bymount/1only. Defaultfalse:app_module- module supplying a per-app theme, passed by the renderer as:__app_module__. Defaultnil
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)
Summary
Functions
The registry tag for this widget.
Turns the {:breadcrumb, items, opts} element into a props map for mount/1.
Callback implementation for Drafter.Widget.handle_event/2.
Moves the focus between crumbs and activates the focused one.
Selects the crumb whose label spans column x, counted from the left edge of the
widget.
Builds the breadcrumb state from props, normalising :items so that every entry
is a {label, id} tuple.
Always 1: the trail occupies a single row whatever the item count.
Draws the trail as a single strip padded to rect.width.
Callback implementation for Drafter.Widget.unmount/1.
Folds fresh props into state.
Returns mount_props unchanged, so a re-render passes every option through to
update/2.
Types
Functions
@spec component_tag() :: :breadcrumb
The registry tag for this widget.
iex> Drafter.Widget.Breadcrumb.component_tag()
:breadcrumb
@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}
Callback implementation for Drafter.Widget.handle_event/2.
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
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.
@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}
@spec preferred_height( term(), keyword() ) :: pos_integer()
Always 1: the trail occupies a single row whatever the item count.
@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/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.
Callback implementation for Drafter.Widget.unmount/1.
@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
@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.