Renders an animated spinner with an optional label.
The spinner frame is read from the monotonic clock at render time, one frame per
100 ms. An optional colour gradient cycles through the provided colours on its own
clock, independent of the spinner frame. While :running is false both the frame
and the gradient step are pinned to 0.
Send :start or :stop events to control animation at runtime.
Component tag
Tag :loading_indicator, built by Drafter.App as {:loading_indicator, opts}:
loading_indicator(opts)There is no positional argument; every prop comes from opts. The renderer stamps
the current monotonic time onto the props on each pass, which forces the widget to
re-render; the frame itself is read from the clock, not from the stamp.
Options
:text-String.t/0label shown after the spinner character. Default"Loading...".nilrenders the spinner alone:spinner_type-:default(default),:dots,:line,:points,:arrowor:bounce.:arrowand:bounceuse built-in four-frame sets; the rest come from the character set, with:defaultmapping to its:dotsframes,:dotsto its:brailleframes, and anything unrecognised to:dots:running-boolean/0, whether the spinner animates. Defaulttrue:gradient_colors- list of{r, g, b}tuples to cycle the spinner colour through. Defaultnil, leaving the spinner the computed theme colour. A one-colour list is used as a constant colour:gradient_speed- milliseconds per gradient step. Default50:style-map/0of style properties. Default%{}:class- theme class atom or list of them, reachingmount/1as:classes. Default[]:app_module- module supplying a per-app theme. Defaultnil
update/2 re-reads every option. Through the component tree a re-render passes
only a fresh :_render_timestamp, so every other option is effectively mount-only
there.
Widget value
Drafter.get_widget_value/1 returns the indicator's :text, because the value
extractor reads the :text field.
Usage
loading_indicator(text: "Fetching data...")
loading_indicator(spinner_type: :dots, gradient_colors: [{255, 0, 100}, {0, 100, 255}])
Summary
Functions
The registry tag for this widget.
Turns the {:loading_indicator, opts} element into a props map for mount/1.
The render cache key, the current monotonic millisecond.
Starts and stops the animation.
Builds the indicator state from props.
Always 1: the indicator occupies a single row.
Draws the spinner and label as a single strip.
Callback implementation for Drafter.Widget.unmount/1.
Folds fresh props into state, re-reading every option.
Narrows the props a re-render feeds to update/2 to a fresh
:_render_timestamp, so no other option changes after mount through the component
tree.
Types
@type spinner_type() :: :default | :dots | :line | :points | :arrow | :bounce
@type t() :: %Drafter.Widget.LoadingIndicator{ _render_timestamp: integer(), app_module: module() | nil, classes: [atom()], gradient_colors: [{0..255, 0..255, 0..255}] | nil, gradient_speed: pos_integer(), running: boolean(), spinner_type: spinner_type(), style: map(), text: String.t() | nil }
Functions
@spec component_tag() :: :loading_indicator
The registry tag for this widget.
iex> Drafter.Widget.LoadingIndicator.component_tag()
:loading_indicator
@spec from_component_opts( term(), keyword() ) :: Drafter.Widget.props()
Turns the {:loading_indicator, opts} element into a props map for mount/1.
The positional argument is ignored. :class is normalised into :classes and a
fresh :_render_timestamp is stamped from the monotonic clock.
iex> props = Drafter.Widget.LoadingIndicator.from_component_opts(nil, text: "Wait")
iex> {props.text, props.spinner_type, props.running, props.gradient_speed, props.classes}
{"Wait", :default, true, 50, []}
The render cache key, the current monotonic millisecond.
It never repeats, so the widget is redrawn on every frame and the animation keeps moving.
@spec handle_event(Drafter.Event.t() | :start | :stop, t() | Drafter.Widget.props()) :: {:ok, t()} | {:noreply, t()}
Starts and stops the animation.
:start sets :running and :stop clears it, both returning {:ok, state}.
Everything else returns {:noreply, state}.
iex> li = Drafter.Widget.LoadingIndicator.mount(%{})
iex> {:ok, stopped} = Drafter.Widget.LoadingIndicator.handle_event(:stop, li)
iex> stopped.running
false
iex> li = Drafter.Widget.LoadingIndicator.mount(%{})
iex> Drafter.Widget.LoadingIndicator.handle_event({:key, :enter}, li) |> elem(0)
:noreply
@spec mount(Drafter.Widget.props()) :: t()
Builds the indicator state from props.
:_render_timestamp defaults to the current monotonic millisecond.
iex> li = Drafter.Widget.LoadingIndicator.mount(%{text: "Fetching..."})
iex> {li.text, li.spinner_type, li.running, li.gradient_speed, li.gradient_colors}
{"Fetching...", :default, true, 50, nil}
iex> li = Drafter.Widget.LoadingIndicator.mount(%{})
iex> {li.text, li.style, li.classes}
{"Loading...", %{}, []}
@spec preferred_height( term(), keyword() ) :: pos_integer()
Always 1: the indicator occupies a single row.
@spec render(t() | Drafter.Widget.props(), Drafter.Widget.rect()) :: [ Drafter.Draw.Strip.t() ]
Draws the spinner and label as a single strip.
Accepts either a t/0 or a raw props map, which is mounted first. The strip is
" <frame> <text> " with :text and " <frame> " without it. rect is not
consulted, so the strip is neither cropped nor padded to it.
Callback implementation for Drafter.Widget.unmount/1.
@spec update(Drafter.Widget.props(), t()) :: t()
Folds fresh props into state, re-reading every option.
:_render_timestamp is taken from props or re-stamped from the monotonic clock.
iex> li = Drafter.Widget.LoadingIndicator.mount(%{})
iex> Drafter.Widget.LoadingIndicator.update(%{text: "Almost there"}, li).text
"Almost there"
@spec update_props_from_mount(Drafter.Widget.props(), t(), keyword()) :: Drafter.Widget.props()
Narrows the props a re-render feeds to update/2 to a fresh
:_render_timestamp, so no other option changes after mount through the component
tree.