Drafter.Widget.LoadingIndicator (drafter v0.3.2)

Copy Markdown View Source

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/0 label shown after the spinner character. Default "Loading...". nil renders the spinner alone
  • :spinner_type - :default (default), :dots, :line, :points, :arrow or :bounce. :arrow and :bounce use built-in four-frame sets; the rest come from the character set, with :default mapping to its :dots frames, :dots to its :braille frames, and anything unrecognised to :dots
  • :running - boolean/0, whether the spinner animates. Default true
  • :gradient_colors - list of {r, g, b} tuples to cycle the spinner colour through. Default nil, leaving the spinner the computed theme colour. A one-colour list is used as a constant colour
  • :gradient_speed - milliseconds per gradient step. Default 50
  • :style - map/0 of style properties. Default %{}
  • :class - theme class atom or list of them, reaching mount/1 as :classes. Default []
  • :app_module - module supplying a per-app theme. Default nil

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

spinner_type()

@type spinner_type() :: :default | :dots | :line | :points | :arrow | :bounce

t()

@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

component_tag()

@spec component_tag() :: :loading_indicator

The registry tag for this widget.

iex> Drafter.Widget.LoadingIndicator.component_tag()
:loading_indicator

focused(state)

from_component_opts(args, opts)

@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, []}

get_render_key(state)

@spec get_render_key(t()) :: integer()

The render cache key, the current monotonic millisecond.

It never repeats, so the widget is redrawn on every frame and the animation keeps moving.

handle_event(event, state)

@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

mount(props)

@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...", %{}, []}

preferred_height(args, opts)

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

Always 1: the indicator occupies a single row.

render(state, rect)

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.

unmount(state)

Callback implementation for Drafter.Widget.unmount/1.

update(props, state)

@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"

update_props_from_mount(mount_props, existing_state, opts)

@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.