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

A draggable value slider: a track, a fill up to the current value, a thumb, and an
optional label and value readout.

The value lives on the widget. Keys, the mouse and the scroll wheel all move it,
`:on_change` reports it, and `bind:` keeps it in step with a key of the app state.

## Component tag

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

    slider(opts)
    slider(value, opts)

The two-argument form puts `value` into `opts` under `:value`. `:value` and
`:on_change` go through the binding layer, so `bind: :some_key` reads the value from
that app-state key and writes each new one back.

## Options

  * `:value` - `t:number/0` the slider starts at, clamped into the range and snapped
    to `:step`. Default `:min`.
  * `:min` - `t:number/0` low end of the range. Default `0.0`.
  * `:max` - `t:number/0` high end of the range. Default `1.0`.
  * `:step` - `t:number/0` the value moves in. Default `nil`, a hundredth of the
    range — whole numbers when `:min` and `:max` are both integers. A range whose
    bounds and step are all integers keeps integer values; any other range works in
    floats.
  * `:bind` - app-state key atom for two-way binding of the value. Default: none.
    Without it a later `:value` prop does not reach the mounted widget, which owns
    whatever the user set it to.
  * `:label` - `t:String.t/0` drawn ahead of the track, or `nil`. Default `nil`.
  * `:show_value` - `t:boolean/0`, draw the value after the track. Default `true`.
    The readout reserves the width of the widest value in the range, so the track
    does not move as the value changes.
  * `:format` - `(number() -> String.t())` for the readout. Default `nil`, which
    writes the number with `:precision` decimals.
  * `:precision` - decimals in the readout. Default: as many as `:step` needs.
  * `:orientation` - `:horizontal | :vertical`. Default `:horizontal`. A vertical
    slider runs bottom to top, with the label on its first row and the readout on
    its last.
  * `:disabled` - `t:boolean/0`. Default `false`. A disabled slider draws muted and
    bubbles every key and click.
  * `:track_color` / `:fill_color` / `:thumb_color` - `{r, g, b}` overrides for the
    three parts. Default `nil`, which takes them from the theme.
  * `:renderer` - `:text` (default) draws characters; `:braille` draws the shape
    through `Drafter.Widget.Slider.Pixel`; a graphics protocol atom (`:pixel`,
    `:kitty`, `:iterm2`, `:sixel`, `:auto`) transmits a picture, falling back to
    braille where the terminal has no protocol. Unset, the mode the app was run with
    applies; `DRAFTER_MODE` overrides both.
  * `:class` - theme class atom or list of them. Default `[]`.
  * `:style` - inline style map merged over the theme. Default `%{}`.

`update/2` accepts every option above except `:class` and `:style`, which are
mount-only. Through the component tree `update_props_from_mount/3` narrows that
further, adding `:value` only when `:bind` is set and the bound value differs from
the widget's own.

## Key bindings

  * `→`, `↑` - one step up; `←`, `↓` - one step down
  * `PageUp`, `PageDown` - ten steps
  * `Home`, `End` - the ends of the range

A press or drag anywhere on the track moves the thumb there, and the scroll wheel
moves one step.

## Widget value

`Drafter.get_widget_value/1` returns the number, and `Drafter.set_widget_value/2`
writes one, clamped and snapped like any other.

## Usage

    slider(value: 0.5, label: "Gain", on_change: :set_gain)
    slider(min: 0, max: 11, step: 1, bind: :volume)
    slider(value: 0.546, precision: 3, renderer: :auto)

# `rgb`

```elixir
@type rgb() :: {0..255, 0..255, 0..255}
```

# `t`

```elixir
@type t() :: %Drafter.Widget.Slider{
  app_module: module() | nil,
  classes: [atom()],
  disabled: boolean(),
  dragging: boolean(),
  fill_color: rgb() | nil,
  focused: boolean(),
  format: (number() -&gt; String.t()) | nil,
  height: pos_integer() | nil,
  hovered: boolean(),
  label: String.t() | nil,
  max: number(),
  min: number(),
  on_change: (number() -&gt; term()) | nil,
  orientation: :horizontal | :vertical,
  precision: non_neg_integer() | nil,
  renderer: atom() | nil,
  show_value: boolean(),
  step: number() | nil,
  style: map(),
  thumb_color: rgb() | nil,
  track_color: rgb() | nil,
  value: number(),
  width: pos_integer() | nil
}
```

# `component_tag`

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

The component tag this widget registers under.

    iex> Drafter.Widget.Slider.component_tag()
    :slider

# `focused`

# `from_component_opts`

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

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

The positional argument is ignored. `:value` is the bound value for that key, so `bind: :key` reads it from
`opts[:__app_state__]`, and `:on_change` is the binding's writer. `:class` is
normalised into `:classes` and `:__app_module__` into `:app_module`.

    iex> props = Drafter.Widget.Slider.from_component_opts(nil, min: 0, max: 10, step: 1)
    iex> {props.value, props.min, props.max, props.step}
    {0, 0, 10, 1}

    iex> opts = [bind: :gain, __app_state__: %{gain: 0.75}]
    iex> Drafter.Widget.Slider.from_component_opts(nil, opts).value
    0.75

# `handle_custom_event`

```elixir
@spec handle_custom_event(Drafter.Event.t(), t()) :: {:ok, t()} | {:bubble, t()}
```

Tracks `:hover` and `:unhover`; every other event bubbles.

# `handle_drag`

```elixir
@spec handle_drag(integer(), integer(), t()) :: {:ok, t()} | {:bubble, t()}
```

Moves the thumb to the pointer while a button is held.

# `handle_event`

# `handle_key`

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

Moves the value by one step for `→` and `↑`, down one for `←` and `↓`, ten for the
page keys, and to the ends of the range for `Home` and `End`.

A key that does not move the value still returns `{:ok, state}`; every other key,
and every key at all while `:disabled`, bubbles.

    iex> state = Drafter.Widget.Slider.mount(%{value: 0.5, step: 0.1})
    iex> {:ok, moved} = Drafter.Widget.Slider.handle_key(:right, state)
    iex> moved.value
    0.6

    iex> state = Drafter.Widget.Slider.mount(%{value: 0.5})
    iex> {:ok, moved} = Drafter.Widget.Slider.handle_key(:home, state)
    iex> moved.value
    0.0

# `handle_mouse_up`

```elixir
@spec handle_mouse_up(integer(), integer(), t()) :: {:ok, t()} | {:bubble, t()}
```

Moves the thumb to the pointer and ends the drag gesture.

# `handle_press`

```elixir
@spec handle_press(integer(), integer(), t()) :: {:ok, t()} | {:bubble, t()}
```

Moves the thumb to the pointer and starts a drag gesture, so later motion keeps
tracking even once the pointer leaves the widget.

`x` and `y` are widget-relative cells.

# `handle_scroll`

```elixir
@spec handle_scroll(Drafter.Widget.scroll_direction(), t()) ::
  {:ok, t()} | {:bubble, t()}
```

Moves the value one step per wheel notch.

# `image_active?`

```elixir
@spec image_active?(t()) :: boolean()
```

Whether this slider is drawing a transmitted image rather than characters.

Only a `:pixel` mode on a terminal with a graphics protocol draws one; a `:text` or
`:braille` slider costs nothing on the image path.

    iex> Drafter.Widget.Slider.image_active?(Drafter.Widget.Slider.mount(%{renderer: :text}))
    false

# `mount`

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

Builds the widget state from `props`.

The value is clamped into `:min`..`:max` and snapped to `:step`, so a slider can
never mount off its own scale.

    iex> state = Drafter.Widget.Slider.mount(%{})
    iex> {state.value, state.min, state.max, state.orientation}
    {0.0, 0.0, 1.0, :horizontal}

    iex> Drafter.Widget.Slider.mount(%{value: 0.37, step: 0.25}).value
    0.25

    iex> Drafter.Widget.Slider.mount(%{value: 42, min: 0, max: 10, step: 1}).value
    10

# `on_rect_change`

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

Records the rect the layout gave the widget, so a click can be turned into a value.

    iex> state = Drafter.Widget.Slider.mount(%{})
    iex> sized = Drafter.Widget.Slider.on_rect_change(%{x: 0, y: 0, width: 40, height: 1}, state)
    iex> {sized.width, sized.height}
    {40, 1}

# `preferred_height`

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

The number of rows the element asks for: `:height`, defaulting to `8` for a vertical
slider and `1` for a horizontal one.

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

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

# `render`

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

Draws the slider into `rect`, returning exactly `rect.height` strips.

`state` may be a plain props map, in which case it is passed through `mount/1`
first. The renderer decides the track: a `:text` slider draws characters, a
`:braille` one the braille shape, and a `:pixel` one leaves the track blank for the
picture the widget server transmits. The label and the readout are characters in
every mode. A rect with no width draws nothing.

# `unmount`

# `update`

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

Folds fresh props into `state`, keeping the current value for any key that is
absent.

The value is re-snapped against whichever range the props leave behind, so moving
`:min`, `:max` or `:step` never leaves the thumb off its scale.

    iex> state = Drafter.Widget.Slider.mount(%{value: 90, min: 0, max: 100})
    iex> Drafter.Widget.Slider.update(%{max: 50}, state).value
    50

# `update_props_from_mount`

```elixir
@spec update_props_from_mount(Drafter.Widget.props(), t() | map(), keyword()) ::
  Drafter.Widget.props()
```

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

`:value` is added only when `opts` carries `:bind` and the bound value differs from
the widget's own, so an unbound slider keeps whatever the user dragged it to.
`:class` and `:style` are dropped, making them mount-only.

---

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