Corex.AngleSlider (Corex v0.1.0-beta.5)

View Source

Phoenix implementation of Zag.js Angle Slider.

WAI-ARIA circular angle control. Use angle_slider/1 with an optional label slot, marks, and controlled or uncontrolled mode.

Anatomy

Basic

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

With marks

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

Controlled

In controlled mode, use on_value_change and on_value_change_client so the thumb moves during drag. Use on_value_change_end and on_value_change_end_client if you only need to react when the user releases.

defmodule MyAppWeb.AngleSliderLive do
  use MyAppWeb, :live_view

  def mount(_params, _session, socket) do
    {:ok, assign(socket, :value, 0)}
  end

  def handle_event("angle_changed", %{"value" => value}, socket) do
    {:noreply, assign(socket, :value, value)}
  end

  def render(assigns) do
    ~H"""
    <.angle_slider
      id="angle"
      controlled
      value={@value}
      on_value_change="angle_changed"
      marker_values={[0, 90, 180, 270]}
      class="angle-slider">
      <:label>Angle</:label>
    </.angle_slider>
    """
  end
end

API

See API.

LiveView

The API targets one angle slider via its DOM id (the same id you pass to angle_slider/1).

For value, use respond_to: :server | :client | :both to control whether the response is pushed to LiveView, dispatched as a DOM event, or both.

<.action phx-click={Corex.AngleSlider.set_value("my-angle-slider", 90)}>Set 90°</.action>
<.action phx-click={Corex.AngleSlider.value("my-angle-slider")}>Read value</.action>
<.action phx-click={Corex.AngleSlider.value("my-angle-slider", respond_to: :client)}>
  Read value (client only)
</.action>
def handle_event("set_angle", %{"value" => value}, socket) do
  {:noreply, Corex.AngleSlider.set_value(socket, "my-angle-slider", String.to_float(value))}
end

From LiveView, Corex.AngleSlider.set_value/3 and value/3 use push_event/3 to the hook; optional respond_to: :server | :client | :both controls where the answer goes for value/3.

Responses to LiveView (push_event from the hook; handle in handle_event/3):

  • angle_slider_value_response - %{"id" => ..., "value" => number, "valueAsDegree" => number, "dragging" => boolean}

Client (DOM)

From the client, dispatch CustomEvents on the hook root (the element with id, e.g. #my-angle-slider):

Dispatch (type)detail
corex:angle-slider:set-valuevalue - number in degrees
corex:angle-slider:valueoptional respond_to: "server", "client", or "both"

Responses to the DOM (listen on the hook root element):

  • angle-slider-value - detail with id, value, valueAsDegree, and dragging

Events

See Events.

Server (LiveView)

When phx-hook="AngleSlider" is active, Zag maps drag and value updates to your LiveView:

  • on_value_change - pushEvent/3 with the name you set. Params include %{"id" => dom_id, "value" => number, "valueAsDegree" => number}.
  • on_value_change_end - same for the release event when you only care about the final value.

Client (CustomEvent / DOM)

  • on_value_change_client - browser CustomEvent type is the string you set. event.detail matches the same shape (camelCase in JS; bubbles).
  • on_value_change_end_client - same pattern for the release event.

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 to install Mix.Tasks.Corex.Design first and import the component css file.

@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/angle-slider.css";

You can then use modifiers

<.angle_slider class="angle-slider angle-slider--accent angle-slider--lg" value={0} />

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 id={@form.id} on <.form>.

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} id={@form.id} 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

Sets the angle slider value from client-side. Returns a Phoenix.LiveView.JS command.

Sets the angle slider value from server-side. Pushes a LiveView event.

Requests the angle slider value from the browser. Returns a Phoenix.LiveView.JS command.

Requests the angle slider value from the client. Pushes a LiveView event handled by the hook.

Components

angle_slider(assigns)

Attributes

  • id (:string) - The id of the angle slider.
  • value (:float) - The value or controlled value in degrees. Defaults to 0.0.
  • controlled (:boolean) - Whether the value is controlled. Defaults to false.
  • step (:float) - Step value. Defaults to 1.0.
  • disabled (:boolean) - Whether the slider is disabled. Defaults to false.
  • read_only (:boolean) - Whether the slider is read-only. Defaults to false.
  • invalid (:boolean) - Whether the slider is invalid. Defaults to false.
  • name (:string) - Name for form submission. Defaults to nil.
  • dir (:string) - Direction. Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "vertical". 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 (uncontrolled). Defaults to nil.
  • on_value_change_client (:string) - Client event when value changes (uncontrolled). Defaults to nil.
  • on_value_change_end (:string) - Server event when value change ends (controlled). Defaults to nil.
  • on_value_change_end_client (:string) - Client event when value change ends (controlled). 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 [].
  • 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.
  • 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)

Sets the angle slider value from client-side. Returns a Phoenix.LiveView.JS command.

Examples

<button phx-click={Corex.AngleSlider.set_value("my-angle-slider", 90)}>
  Set to 90°
</button>

set_value(socket, angle_slider_id, value)

Sets the angle slider value from server-side. Pushes a LiveView event.

Examples

def handle_event("set_angle", %{"value" => value}, socket) do
  angle = String.to_float(value)
  {:noreply, Corex.AngleSlider.set_value(socket, "my-angle-slider", angle)}
end

value(angle_slider_id)

Requests the angle slider value from the browser. Returns a Phoenix.LiveView.JS command.

Options:

  • :respond_to - :server (default, LiveView angle_slider_value_response only), :both (also dispatches angle-slider-value), or :client (DOM angle-slider-value only). When :server and the LiveView is not connected, nothing is pushed.

The hook pushes angle_slider_value_response when :respond_to is :both or :server, and dispatches angle-slider-value on the element when :respond_to is :both or :client.

Examples

<.action phx-click={Corex.AngleSlider.value("my-angle-slider")} class="button button--sm">
  Value
</.action>
<.action phx-click={Corex.AngleSlider.value("my-angle-slider", respond_to: :client)} class="button button--sm">
  Value (client only)
</.action>

```javascript
const el = document.getElementById("my-angle-slider");
el?.addEventListener("angle-slider-value", (e) => console.log(e.detail));
```

<.action
  phx-click={JS.dispatch("corex:angle-slider:value",
    to: "#my-angle-slider",
    detail: %{respond_to: "client"},
    bubbles: false
  )}
  class="button button--sm"
>
  Value (JS.dispatch, client only)
</.action>

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

Requests the angle slider value from the client. Pushes a LiveView event handled by the hook.

See value/2 for :respond_to. The hook pushes angle_slider_value_response and/or dispatches angle-slider-value accordingly.

Examples

def handle_event("angle_slider_value_response", %{"id" => id, "value" => value}, socket) do
  {:noreply, assign(socket, :angle, {id, value})}
end

Functions

value(angle_slider_id, opts)