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
endAPI
See API.
LiveView
The API targets one angle slider via its DOM id (the same id you pass to angle_slider/1).
set_value/2andset_value/3value/2andvalue/3
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))}
endFrom 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-value | value - number in degrees |
corex:angle-slider:value | optional respond_to: "server", "client", or "both" |
Responses to the DOM (listen on the hook root element):
angle-slider-value-detailwithid,value,valueAsDegree, anddragging
Events
See Events.
Server (LiveView)
When phx-hook="AngleSlider" is active, Zag maps drag and value updates to your LiveView:
on_value_change-pushEvent/3with 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- browserCustomEventtype is the string you set.event.detailmatches 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
Attributes
id(:string) - The id of the angle slider.value(:float) - The value or controlled value in degrees. Defaults to0.0.controlled(:boolean) - Whether the value is controlled. Defaults tofalse.step(:float) - Step value. Defaults to1.0.disabled(:boolean) - Whether the slider is disabled. Defaults tofalse.read_only(:boolean) - Whether the slider is read-only. Defaults tofalse.invalid(:boolean) - Whether the slider is invalid. Defaults tofalse.name(:string) - Name for form submission. Defaults tonil.dir(:string) - Direction. Defaults tonil. Must be one ofnil,"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 tofalse.on_value_change(:string) - Server event when value changes (uncontrolled). Defaults tonil.on_value_change_client(:string) - Client event when value changes (uncontrolled). Defaults tonil.on_value_change_end(:string) - Server event when value change ends (controlled). Defaults tonil.on_value_change_end_client(:string) - Client event when value change ends (controlled). Defaults tonil.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_blocklabel- Accepts attributes:class(:string)
value_text- Accepts attributes:class(:string)
error- Accepts attributes:class(:string)
Renders a loading skeleton for the angle slider. No hook; static data-part markup for styling.
Attributes
- Global attributes are accepted.
Compounds
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)value(:float) (required)disabled(:boolean) - Defaults tofalse.- Global attributes are accepted.
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block
Attributes
ctx(:map) (required) - The context map yielded by the parent angle_slider via :let={ctx}.- Global attributes are accepted.
Slots
inner_block(required)
Attributes
- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
inner_block(required)
Attributes
ctx(:map) (required)- Global attributes are accepted.
Slots
value_text- Accepts attributes:class(:string)
API
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>
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
Requests the angle slider value from the browser. Returns a Phoenix.LiveView.JS command.
Options:
:respond_to-:server(default, LiveViewangle_slider_value_responseonly),:both(also dispatchesangle-slider-value), or:client(DOMangle-slider-valueonly). When:serverand 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>
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