Corex.NumberInput (Corex v0.1.2)

View Source

Phoenix implementation of Zag.js Number Input.

Anatomy

Minimal

<.number_input class="number-input">
  <:label>Quantity</:label>
  <:decrement_trigger><.heroicon name="hero-chevron-down" class="icon" /></:decrement_trigger>
  <:increment_trigger><.heroicon name="hero-chevron-up" class="icon" /></:increment_trigger>
</.number_input>

Min, max, step

<.number_input
  class="number-input"
  min={0.0}
  max={100.0}
  step={5.0}
  value="10"
>
  <:label>Amount</:label>
  <:decrement_trigger><.heroicon name="hero-chevron-down" class="icon" /></:decrement_trigger>
  <:increment_trigger><.heroicon name="hero-chevron-up" class="icon" /></:increment_trigger>
</.number_input>

min, max, and step are floats. Use step for granularity: step={1} for whole-number steps (display omits .0), step={0.01} for two decimal places. The visible value is formatted on the server to match Zag after hydration.

Slots :decrement_trigger and :increment_trigger are required.

API

Requires a stable id on <.number_input>. For forms, use field; use the API for imperative updates and reading machine state.

FunctionActionReturns
set_value/2Set value (client)%Phoenix.LiveView.JS{}
set_value/3Set value (server)socket
clear_value/1Clear value (client)%Phoenix.LiveView.JS{}
clear_value/2Clear value (server)socket
increment/1Increment by step (client)%Phoenix.LiveView.JS{}
increment/2Increment by step (server)socket
decrement/1Decrement by step (client)%Phoenix.LiveView.JS{}
decrement/2Decrement by step (server)socket
set_to_min/1Set to min (client)%Phoenix.LiveView.JS{}
set_to_min/2Set to min (server)socket
set_to_max/1Set to max (client)%Phoenix.LiveView.JS{}
set_to_max/2Set to max (server)socket
focus/1Focus input (client)%Phoenix.LiveView.JS{}
focus/2Focus input (server)socket
state/1Read machine state (client)%Phoenix.LiveView.JS{}
state/2Read machine state (client, opts)%Phoenix.LiveView.JS{}
state/3Read machine state (server)socket

Machine state (state/2, state/3)

Replies with number_input_state_response (server) or number-input-state on the host (client). Payload includes Zag machine fields:

FieldTypeMeaning
focusedbooleanInput is focused
invalidbooleanInput is invalid
emptybooleanValue is empty
valuestringFormatted value
valueAsNumbernumberNumeric value

Events

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

Server events

EventWhenPayload
on_value_change="number_input_changed"Value changes%{"id" => id, "value" => string, "valueAsNumber" => number}

on_value_change

<.number_input
  class="number-input"
  on_value_change="number_input_changed"
>
  <:label>Quantity</:label>
  <:decrement_trigger><.heroicon name="hero-chevron-down" class="icon" /></:decrement_trigger>
  <:increment_trigger><.heroicon name="hero-chevron-up" class="icon" /></:increment_trigger>
</.number_input>
def handle_event("number_input_changed", %{"id" => _id, "value" => value}, socket) do
  {:noreply, assign(socket, :quantity, value)}
end

Client events

EventWhenevent.detail
on_value_change_client="number-input-changed"Value changesid, value, valueAsNumber

Patterns

Pass value for the initial number on mount. The machine owns updates after that unless you use set_value/2 or form field.

Form

Use field={f[:value]} inside <.form>. With a form field, increment and decrement stay local; the hidden input updates for submit.

For cross-cutting invalid styling and error presentation, see the Forms guide. Pass invalid={Corex.FormField.invalid?(@form[:value])} when you want alert borders after validation.

<.form for={@form} phx-change="validate">
  <.number_input field={@form[:value]} class="number-input">
    <:label>Quantity</:label>
    <:decrement_trigger><.heroicon name="hero-chevron-down" class="icon" /></:decrement_trigger>
    <:increment_trigger><.heroicon name="hero-chevron-up" class="icon" /></:increment_trigger>
    <:error :let={msg}>
      <.heroicon name="hero-exclamation-circle" class="icon" />
      {msg}
    </:error>
  </.number_input>
</.form>

Style

Target parts with data-scope and data-part, or use Corex Design: import tokens and number-input.css, then set class="number-input" on <.number_input>.

[data-scope="number-input"][data-part="root"] {}
[data-scope="number-input"][data-part="control"] {}
[data-scope="number-input"][data-part="input"] {}
[data-scope="number-input"][data-part="trigger-group"] {}
[data-scope="number-input"][data-part="decrement-trigger"] {}
[data-scope="number-input"][data-part="increment-trigger"] {}
@import "../corex/main.css";
@import "../corex/tokens/themes/neo/light.css";
@import "../corex/components/number-input.css";

Stack modifiers on the host (class on <.number_input>).

Color

ModifierClasses
Defaultnumber-input
Accentnumber-input number-input--accent
Brandnumber-input number-input--brand
Alertnumber-input number-input--alert
Infonumber-input number-input--info
Successnumber-input number-input--success

Size

ModifierClasses
SMnumber-input number-input--sm
MDnumber-input number-input--md
LGnumber-input number-input--lg
XLnumber-input number-input--xl

Summary

API

Clear the value from phx-click. Dispatches corex:number-input:clear-value.

Clear the value from handle_event (number_input_clear_value).

Decrement from phx-click. Dispatches corex:number-input:decrement.

Decrement from handle_event (number_input_decrement).

Focus the value field from phx-click. Dispatches corex:number-input:focus.

Focus the value field from handle_event (number_input_focus).

Increment from phx-click. Dispatches corex:number-input:increment.

Increment from handle_event (number_input_increment).

Snap to maximum from phx-click. Dispatches corex:number-input:set-to-max.

Snap to maximum from handle_event (number_input_set_to_max).

Snap to minimum from phx-click. Dispatches corex:number-input:set-to-min.

Snap to minimum from handle_event (number_input_set_to_min).

Replace the formatted value from phx-click. Dispatches corex:number-input:set-value with a numeric value.

Replace the formatted value from handle_event (number_input_set_value).

Same as state/2 with default respond_to:.

Read machine state from phx-click. Dispatches corex:number-input:state. Optional respond_to: :server, :client, or :both.

Read machine state from handle_event (number_input_state). Same replies as state/2; server-only unless you also use state/2 for a DOM reply.

Components

number_input(assigns)

Attributes

  • id (:string)
  • value (:string) - Defaults to nil.
  • min (:float) - Defaults to nil.
  • max (:float) - Defaults to nil.
  • step (:float) - Defaults to 1.0.
  • disabled (:boolean) - Defaults to false.
  • read_only (:boolean) - Defaults to false.
  • invalid (:boolean) - Defaults to false.
  • required (:boolean) - Defaults to false.
  • allow_mouse_wheel (:boolean) - Defaults to false.
  • name (:string) - Defaults to nil.
  • form (:string) - Defaults to nil.
  • on_value_change (:string) - Defaults to nil.
  • on_value_change_client (:string) - Defaults to nil.
  • dir (:string) - Defaults to nil.Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Defaults to "vertical". Must be one of "horizontal", or "vertical".
  • translation (Corex.NumberInput.Translation) - Override translatable strings. Defaults to nil.
  • errors (:list) - List of error messages to display. Defaults to [].
  • field (Phoenix.HTML.FormField) - A form field struct, e.g. f[:age] or @form[:age].
  • Global attributes are accepted.

Slots

  • label - Accepts attributes:
    • class (:string)
  • decrement_trigger (required) - Accepts attributes:
    • class (:string)
  • increment_trigger (required) - Accepts attributes:
    • class (:string)
  • error - Accepts attributes:
    • class (:string)

API

clear_value(number_input_id)

Clear the value from phx-click. Dispatches corex:number-input:clear-value.

<.action phx-click={Corex.NumberInput.clear_value("my-num")}>Clear</.action>
<.number_input id="my-num" class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

clear_value(socket, number_input_id)

Clear the value from handle_event (number_input_clear_value).

def handle_event("clear", _, socket) do
  {:noreply, Corex.NumberInput.clear_value(socket, "my-num")}
end

decrement(number_input_id)

Decrement from phx-click. Dispatches corex:number-input:decrement.

<.action phx-click={Corex.NumberInput.decrement("my-num")}>Decrement</.action>
<.number_input id="my-num" class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

decrement(socket, number_input_id)

Decrement from handle_event (number_input_decrement).

def handle_event("dec", _, socket) do
  {:noreply, Corex.NumberInput.decrement(socket, "my-num")}
end

focus(number_input_id)

Focus the value field from phx-click. Dispatches corex:number-input:focus.

<.action phx-click={Corex.NumberInput.focus("my-num")}>Focus</.action>
<.number_input id="my-num" class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

focus(socket, number_input_id)

Focus the value field from handle_event (number_input_focus).

def handle_event("focus", _, socket) do
  {:noreply, Corex.NumberInput.focus(socket, "my-num")}
end

increment(number_input_id)

Increment from phx-click. Dispatches corex:number-input:increment.

<.action phx-click={Corex.NumberInput.increment("my-num")}>Increment</.action>
<.number_input id="my-num" class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

increment(socket, number_input_id)

Increment from handle_event (number_input_increment).

def handle_event("inc", _, socket) do
  {:noreply, Corex.NumberInput.increment(socket, "my-num")}
end

set_to_max(number_input_id)

Snap to maximum from phx-click. Dispatches corex:number-input:set-to-max.

<.action phx-click={Corex.NumberInput.set_to_max("my-num")}>Max</.action>
<.number_input id="my-num" max={100} class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

set_to_max(socket, number_input_id)

Snap to maximum from handle_event (number_input_set_to_max).

def handle_event("max", _, socket) do
  {:noreply, Corex.NumberInput.set_to_max(socket, "my-num")}
end

set_to_min(number_input_id)

Snap to minimum from phx-click. Dispatches corex:number-input:set-to-min.

<.action phx-click={Corex.NumberInput.set_to_min("my-num")}>Min</.action>
<.number_input id="my-num" min={0} class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>

set_to_min(socket, number_input_id)

Snap to minimum from handle_event (number_input_set_to_min).

def handle_event("min", _, socket) do
  {:noreply, Corex.NumberInput.set_to_min(socket, "my-num")}
end

set_value(number_input_id, value)

Replace the formatted value from phx-click. Dispatches corex:number-input:set-value with a numeric value.

<.action phx-click={Corex.NumberInput.set_value("my-num", 42)}>42</.action>
<.number_input id="my-num" value={40} step={1} class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>
document.getElementById("my-num")?.dispatchEvent(
  new CustomEvent("corex:number-input:set-value", {
    bubbles: false,
    detail: { value: 42 },
  })
);

set_value(socket, number_input_id, value)

Replace the formatted value from handle_event (number_input_set_value).

def handle_event("set_answer", _, socket) do
  {:noreply, Corex.NumberInput.set_value(socket, "my-num", 7)}
end

state(number_input_id)

Same as state/2 with default respond_to:.

state(number_input_id, opts)

Read machine state from phx-click. Dispatches corex:number-input:state. Optional respond_to: :server, :client, or :both.

ReplyPayload
Servernumber_input_state_response%{"id" => id, "focused" => bool, "invalid" => bool, "empty" => bool, "value" => str, "valueAsNumber" => num}
Clientnumber-input-state on the input rootsame fields in detail
<.action phx-click={Corex.NumberInput.state("my-num")}>Snapshot</.action>
<.number_input id="my-num" class="number-input">
  <:increment_trigger><span>+</span></:increment_trigger>
  <:decrement_trigger><span>-</span></:decrement_trigger>
</.number_input>
def handle_event("number_input_state_response", %{"id" => _, "valueAsNumber" => n}, socket) do
  {:noreply, assign(socket, :n, n)}
end

state(socket, number_input_id, opts \\ [])

Read machine state from handle_event (number_input_state). Same replies as state/2; server-only unless you also use state/2 for a DOM reply.

ReplyPayload
number_input_state_response%{"id" => id} plus focused/invalid/value fields
def handle_event("snapshot", _, socket) do
  {:noreply, Corex.NumberInput.state(socket, "my-num", respond_to: :server)}
end