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

Renders a horizontal progress bar with optional percentage, value, and ETA display.

Supports both a determinate mode (showing progress toward a known maximum) and
an indeterminate mode that animates a sliding block when the total is unknown.

## Component tag

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

    progress_bar(opts)

There is no positional argument; every prop comes from `opts`.

## Options

  * `:progress` - `t:number/0` current progress. Default `0.0`. The filled
    fraction is `progress / max_value`, clamped into `0.0..1.0`.
  * `:max_value` - `t:number/0` value representing 100%. Default `100.0`. A
    `max_value` of `0` or less renders as 0%.
  * `:show_percentage` - `t:boolean/0`, append the rounded percentage. Default
    `true`.
  * `:show_eta` - `t:boolean/0`, append an estimated time remaining. Default
    `true`. Shows `"..."` until at least one second of wall clock has passed
    since mount and progress is above zero, then `"12s"`, `"3m 4s"` or
    `"1h 2m"`, and `"∞"` when the computed rate is not positive.
  * `:indeterminate` - `t:boolean/0`. Default `false`. Animates a sliding block
    whose position advances by one on each `update/2` and ignores `:progress`,
    `:show_percentage` and `:show_eta`.
  * `:label` - `t:String.t/0` or `nil`. Default `nil`. Held on the state and
    never drawn.
  * `:show_value` - `t:boolean/0`. Default `false`. Held on the state and never
    drawn; the status text is built from `:show_percentage` and `:show_eta` only.
  * `:width` - `t:pos_integer/0`. Default `50` when mounting directly, and the
    width of `opts[:__rect__]` through the element. Held on the state and never
    read by `render/2`, which uses the rect it is given.
  * `:height` - `t:pos_integer/0`. Default `1` when mounting directly, and the
    height of `opts[:__rect__]` through the element. Held on the state and never
    read by `render/2`.
  * `:pulse` - read by `from_component_opts/2` with default `false` and dropped
    by `mount/1`; the state has no such field.
  * `:class` - theme class atom or list of them, normalised into `:classes` by
    `from_component_opts/2` with default `[]` and dropped by `mount/1`.

`update/2` accepts `:progress`, `:max_value`, `:label`, `:show_percentage`,
`:show_value`, `:show_eta`, `:width`, `:height` and `:indeterminate`, and
refreshes the animation clock on every call. Through the component tree,
`update_props_from_mount/3` narrows that to `:progress`, `:max_value`, `:label`,
`:show_percentage`, `:show_value`, `:indeterminate` and `:classes` — `:show_eta`,
`:width` and `:height` are mount-only.

## Usage

    progress_bar(progress: 42.0, max_value: 100.0)
    progress_bar(progress: 7, max_value: 20, show_percentage: false, show_value: true)
    progress_bar(indeterminate: true)

# `t`

```elixir
@type t() :: %Drafter.Widget.ProgressBar{
  height: pos_integer(),
  indeterminate: boolean(),
  label: String.t() | nil,
  last_progress: number(),
  last_update_time: integer(),
  max_value: number(),
  progress: number(),
  show_eta: boolean(),
  show_percentage: boolean(),
  show_value: boolean(),
  spin_position: non_neg_integer(),
  start_time: integer(),
  width: pos_integer()
}
```

# `apply_data_buffer`

```elixir
@spec apply_data_buffer(t(), Drafter.RingBuffer.t(), Drafter.Widget.rect()) :: t()
```

Sets `:progress` from the newest entry of a `Drafter.RingBuffer`.

Returns `state` unchanged when the buffer is empty. The rect is ignored.

# `component_tag`

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

The component tag this widget registers under.

    iex> Drafter.Widget.ProgressBar.component_tag()
    :progress_bar

# `focused`

# `from_component_opts`

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

Builds the props map for a `{:progress_bar, opts}` element.

The positional argument is ignored. `:width` and `:height` fall back to the width
and height of `opts[:__rect__]`, itself defaulting to `%{width: 50, height: 1}`.
`:class` is normalised into `:classes`. The result also carries `:pulse` and
`:classes`, which `mount/1` drops.

    iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, progress: 3, max_value: 6)
    iex> {props.progress, props.max_value, props.width, props.height}
    {3, 6, 50, 1}

    iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, __rect__: %{width: 80, height: 2})
    iex> {props.width, props.height}
    {80, 2}

# `handle_event`

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

Ignores every event and returns `{:noreply, state}`. The bar is not focusable.

# `mount`

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

Builds the widget state from `props`.

Every option listed in the module doc is read here with the default stated there.
`:start_time` and `:last_update_time` are set to the current monotonic
millisecond, which is what the ETA is measured against, and `:spin_position`
starts at `0`.

    iex> state = Drafter.Widget.ProgressBar.mount(%{})
    iex> {state.progress, state.max_value, state.indeterminate, state.spin_position}
    {0.0, 100.0, false, 0}

    iex> state = Drafter.Widget.ProgressBar.mount(%{progress: 7, max_value: 20})
    iex> {state.progress, state.last_progress, state.show_percentage, state.show_eta}
    {7, 7, true, true}

# `preferred_height`

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

The number of rows the element asks for: `1`, or `8` when `opts[:orientation]` is
`:vertical`.

`:orientation` is not otherwise an option of this widget — `render/2` always
draws horizontally — so a progress bar built from the component tree asks for one
row.

    iex> Drafter.Widget.ProgressBar.preferred_height(nil, [])
    1

    iex> Drafter.Widget.ProgressBar.preferred_height(nil, orientation: :vertical)
    8

# `render`

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

Draws the bar into `rect`.

`state` may be a plain props map, in which case it is passed through `mount/1`
first. The bar always fills `rect.width`; the status text is drawn at the right
and the track takes what is left. Returns `rect.height` strips, the first the bar
and the rest blank.

# `unmount`

# `update`

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

Replaces the state fields named in `props`, keeping the current value for any key
that is absent.

Also refreshes `:last_update_time` and `:last_progress`, and advances
`:spin_position` by one modulo `40` while the bar is indeterminate.

    iex> state = Drafter.Widget.ProgressBar.mount(%{indeterminate: true})
    iex> Drafter.Widget.ProgressBar.update(%{}, state).spin_position
    1

    iex> state = Drafter.Widget.ProgressBar.mount(%{progress: 1})
    iex> updated = Drafter.Widget.ProgressBar.update(%{progress: 40}, state)
    iex> {updated.progress, updated.last_progress, updated.spin_position}
    {40, 40, 0}

# `update_props_from_mount`

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

Narrows a re-render to the props that may change after mount.

Returns `:progress`, `:max_value`, `:label`, `:show_percentage`, `:show_value`,
`:indeterminate` and `:classes`. `:show_eta`, `:width` and `:height` are dropped,
so they are mount-only through the component tree.

    iex> props = Drafter.Widget.ProgressBar.from_component_opts(nil, progress: 3)
    iex> Drafter.Widget.ProgressBar.update_props_from_mount(props, %{}, []) |> Map.keys() |> Enum.sort()
    [:classes, :indeterminate, :label, :max_value, :progress, :show_percentage, :show_value]

---

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