Corex.AngleSlider (Corex v0.2.0)

View Source

Circular angle control for Phoenix LiveView forms. Behavior follows Zag.js Angle Slider. WAI-ARIA circular angle control. Use angle_slider/1 with an optional label slot and marks.

Anatomy

Basic

<.angle_slider class="angle-slider">
  <:label>Angle</:label>
</.angle_slider>

With marks

<.angle_slider class="angle-slider" marker_values={[0, 90, 180, 270]}>
  <:label>Angle</:label>
</.angle_slider>

API

Requires a stable id on <.angle_slider>.

FunctionActionReturns
set_value/2Set angle in degrees (client)%Phoenix.LiveView.JS{}
set_value/3Set angle in degrees (server)socket
value/2Read angle (client)%Phoenix.LiveView.JS{}
value/3Read angle (server)socket

For value, use respond_to: :server | :client | :both. LiveView receives angle_slider_value_response; the DOM receives angle-slider-value.

Events

Pick an event name and pass it to on_* on <.angle_slider>.

Server events

EventWhenPayload
on_value_change="angle_changed"Value changes while dragging%{"id" => id, "value" => number, "valueAsDegree" => number}
on_value_change_end="angle_changed_end"User releases thumb%{"id" => id, "value" => number, "valueAsDegree" => number}

on_value_change

<.angle_slider
  class="angle-slider"
  on_value_change="angle_changed"
  marker_values={[0, 90, 180, 270]}
>
  <:label>Angle</:label>
</.angle_slider>
def handle_event("angle_changed", %{"value" => value}, socket) do
  {:noreply, assign(socket, :value, value)}
end

Client events

EventWhenevent.detail
on_value_change_client="angle-changed"Value changes while draggingid, value, valueAsDegree
on_value_change_end_client="angle-changed-end"User releases thumbid, value, valueAsDegree

Style

Use data attributes to target elements:

[data-scope="angle-slider"][data-part="root"] {}
[data-scope="angle-slider"][data-part="control"] {}
[data-scope="angle-slider"][data-part="thumb"] {}
[data-scope="angle-slider"][data-part="value-text"] {}
[data-scope="angle-slider"][data-part="marker-group"] {}
[data-scope="angle-slider"][data-part="marker"] {}

If you wish to use the default Corex styling, you can use the class angle-slider on the component. This requires the corex_design dependency and mix corex.design.build; import the component css file.

@import "../corex/corex.css";

Stack modifiers on the host (class on <.angle_slider>). Combine axes, for example angle-slider ui-accent ui-size-lg or angle-slider ui-info.

Axes: Semantic (ui-accent, ui-brand, ui-alert, ui-info, ui-success), Size (ui-size-smui-size-xl), Radius (ui-rounded-*). No variant axis. See the modifier guide.

Semantic modifiers set palette variables on the control and thumb handle. Selection paint always fills; there is no variant axis.

Semantic

Palette variables for control fill and thumb ink.

ModifierClasses
Defaultangle-slider
Accentangle-slider ui-accent
Brandangle-slider ui-brand
Alertangle-slider ui-alert
Infoangle-slider ui-info
Successangle-slider ui-success

Size

ModifierClasses
SMangle-slider ui-size-sm
MDangle-slider ui-size-md
LGangle-slider ui-size-lg
XLangle-slider ui-size-xl

Patterns

Async and skeleton

Use assign_async/3 with <.async_result> and show angle_slider_skeleton/1 while loading.

<.async_result :let={slider} assign={@slider}>
  <:loading>
    <.angle_slider_skeleton class="angle-slider" />
  </:loading>
  <:failed>Could not load.</:failed>
  <.angle_slider id="async-angle" class="angle-slider" value={slider.value} />
</.async_result>

Form

When using with Phoenix forms, set the form id in to_form/2 (for example to_form(changeset, as: :name, id: "my-form")) and use <.form for={@form}>.

For cross-cutting invalid styling and error presentation, see the Forms guide. With field={@form[:…]}, pass auto_invalid for alert borders from visible errors, or invalid={true} to force the alert state.

def angle_slider_form_page(conn, _params) do
  form =
    %MyApp.Form.AngleSliderForm{}
    |> MyApp.Form.AngleSliderForm.changeset(%{})
    |> Phoenix.Component.to_form(as: :angle_slider_form, id: "angle-slider-form")

  render(conn, :angle_slider_form_page, form: form)
end
<.form :let={f} for={@form} action={@action} method="post">
  <.angle_slider field={f[:angle]} class="angle-slider" marker_values={[0, 90, 180, 270]}>
    <:label>Angle</:label>
    <:error :let={msg}>
      <.heroicon name="hero-exclamation-circle" class="icon" />
      {msg}
    </:error>
  </.angle_slider>
  <button type="submit">Submit</button>
</.form>

Summary

Components

Renders a loading skeleton for the angle slider. No hook; static data-part markup for styling.

API

Set the angle from a control (phx-click). value is degrees (number).

Set the angle from handle_event. Accepts a number or a numeric string.

Read the current value from phx-click. Optional respond_to: :server (default), :client, or :both.

Read the value from handle_event. Same respond_to behavior as value/2.

Components

angle_slider(assigns)

Attributes

  • id (:string) - The id of the angle slider. Defaults to nil.
  • field (Phoenix.HTML.FormField) - A form field struct retrieved from the form, for example: @form[:angle]. Automatically sets id, name, value, and errors from the form field. Defaults to nil.
  • name (:string) - Name for form submission. Defaults to nil.
  • invalid (:boolean) - Whether the control has validation errors. Defaults to nil.
  • auto_invalid (:boolean) - When true with field, set invalid from visible changeset errors (default false). Defaults to false.
  • disabled (:boolean) - Whether the control is disabled. Defaults to false.
  • read_only (:boolean) - Whether the slider is read-only. Defaults to false.
  • value (:float) - The initial value in degrees. Defaults to 0.0.
  • step (:float) - Step value. Defaults to 1.0.
  • dir (:string) - Direction. Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • value_text_as (:string) - Displayed value format: raw (api.value) or degree (api.valueAsDegree). Defaults to "degree". Must be one of "raw", or "degree".
  • compound (:boolean) - Enable compound mode. Use with :let={ctx} and sub-components to fully control structure. Defaults to false.
  • on_value_change (:string) - Server event when value changes during drag. Defaults to nil.
  • on_value_change_client (:string) - Client event when value changes during drag. Defaults to nil.
  • on_value_change_end (:string) - Server event when the user releases the thumb. Defaults to nil.
  • on_value_change_end_client (:string) - Client event when the user releases the thumb. Defaults to nil.
  • marker_values (:list) - List of angle values to show as markers (e.g. [0, 90, 180, 270]). Defaults to [].
  • errors (:list) - List of error messages to display. Defaults to [].
  • Global attributes are accepted.

Slots

  • inner_block
  • label - Accepts attributes:
    • class (:string)
  • value_text - Accepts attributes:
    • class (:string)
  • error - Accepts attributes:
    • class (:string)

angle_slider_skeleton(assigns)

Renders a loading skeleton for the angle slider. No hook; static data-part markup for styling.

Attributes

  • Global attributes are accepted.

Compounds

angle_slider_control(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

Slots

  • inner_block

angle_slider_hidden_input(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

angle_slider_label(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

Slots

  • inner_block (required)

angle_slider_marker(assigns)

Attributes

  • ctx (:map) (required)
  • value (:float) (required)
  • disabled (:boolean) - Defaults to false.
  • Global attributes are accepted.

angle_slider_marker_group(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

Slots

  • inner_block

angle_slider_root(assigns)

Attributes

  • ctx (:map) (required) - The context map yielded by the parent angle_slider via :let={ctx}.
  • Global attributes are accepted.

Slots

  • inner_block (required)

angle_slider_text(assigns)

Attributes

  • Global attributes are accepted.

Slots

  • inner_block (required)

angle_slider_thumb(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

angle_slider_value(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

Slots

  • inner_block (required)

angle_slider_value_text(assigns)

Attributes

  • ctx (:map) (required)
  • Global attributes are accepted.

Slots

  • value_text - Accepts attributes:
    • class (:string)

API

set_value(angle_slider_id, value)

@spec set_value(String.t(), number()) :: Phoenix.LiveView.JS.t()

Set the angle from a control (phx-click). value is degrees (number).

<.action phx-click={Corex.AngleSlider.set_value("my-angle-slider", 90.0)}>90°</.action>
<.angle_slider id="my-angle-slider" class="angle-slider" value={0.0} name="angle" />
document.getElementById("my-angle-slider")?.dispatchEvent(
  new CustomEvent("corex:angle-slider:set-value", {
    bubbles: false,
    detail: { value: 90 },
  })
);

set_value(socket, angle_slider_id, value)

Set the angle from handle_event. Accepts a number or a numeric string.

<.action phx-click="set_angle" phx-value-value="90">90°</.action>
<.angle_slider id="my-angle-slider" class="angle-slider" value={0.0} name="angle" />
def handle_event("set_angle", %{"value" => v}, socket) do
  {:noreply, Corex.AngleSlider.set_value(socket, "my-angle-slider", v)}
end

value(angle_slider_id, opts)

@spec value(
  String.t(),
  keyword()
) :: Phoenix.LiveView.JS.t()

Read the current value from phx-click. Optional respond_to: :server (default), :client, or :both.

<.action phx-click={Corex.AngleSlider.value("my-angle-slider", respond_to: :both)}>Read</.action>
<.angle_slider id="my-angle-slider" class="angle-slider" value={45.0} name="angle" />
document.getElementById("my-angle-slider")?.dispatchEvent(
  new CustomEvent("corex:angle-slider:value", {
    bubbles: false,
    detail: { respond_to: "both" },
  })
);

value(socket, angle_slider_id, opts \\ [])

Read the value from handle_event. Same respond_to behavior as value/2.

<.action phx-click="read_angle">Read</.action>
<.angle_slider id="my-angle-slider" class="angle-slider" value={45.0} name="angle" />
def handle_event("read_angle", _, socket) do
  {:noreply, Corex.AngleSlider.value(socket, "my-angle-slider", respond_to: :server)}
end