Corex. Timer
(Corex v0.2.0)
View Source
Timer for Phoenix LiveView. Behavior follows Zag.js Timer.
Countdown leading-zero collapse is on by default when countdown is true. Override with collapse_leading_zeros={false} or fixed segments.
Anatomy
Minimal
<.timer start_ms={60_000} class="timer" />With triggers
<.timer start_ms={60_000} class="timer">
<:start_trigger><.heroicon name="hero-play" /></:start_trigger>
<:pause_trigger><.heroicon name="hero-pause" /></:pause_trigger>
<:resume_trigger><.heroicon name="hero-play" /></:resume_trigger>
<:reset_trigger><.heroicon name="hero-arrow-path" /></:reset_trigger>
</.timer>Countdown
<.timer countdown start_ms={60_000} target_ms={0} class="timer">
<:start_trigger><.heroicon name="hero-play" /></:start_trigger>
<:pause_trigger><.heroicon name="hero-pause" /></:pause_trigger>
<:resume_trigger><.heroicon name="hero-play" /></:resume_trigger>
<:reset_trigger><.heroicon name="hero-arrow-path" /></:reset_trigger>
</.timer>Interval tick
<.timer start_ms={60_000} interval={2000} auto_start class="timer">
<:start_trigger><.heroicon name="hero-play" /></:start_trigger>
<:pause_trigger><.heroicon name="hero-pause" /></:pause_trigger>
<:resume_trigger><.heroicon name="hero-play" /></:resume_trigger>
<:reset_trigger><.heroicon name="hero-arrow-path" /></:reset_trigger>
</.timer>API
Requires a stable id on <.timer>. Use trigger slots and auto_start for built-in controls; use the API for imperative control from LiveView or the client.
| Function | Action | Returns |
|---|---|---|
start/1 | Start timer (client) | %Phoenix.LiveView.JS{} |
start/2 | Start timer (server) | socket |
pause/1 | Pause timer (client) | %Phoenix.LiveView.JS{} |
pause/2 | Pause timer (server) | socket |
resume/1 | Resume timer (client) | %Phoenix.LiveView.JS{} |
resume/2 | Resume timer (server) | socket |
reset/1 | Reset timer (client) | %Phoenix.LiveView.JS{} |
reset/2 | Reset timer (server) | socket |
restart/1 | Restart timer (client) | %Phoenix.LiveView.JS{} |
restart/2 | Restart timer (server) | socket |
state/1 | Read machine state (client) | %Phoenix.LiveView.JS{} |
state/2 | Read machine state (client, opts) | %Phoenix.LiveView.JS{} |
state/3 | Read machine state (server) | socket |
Machine state (state/2, state/3)
Replies with timer_state_response (server) or timer-state on the host (client). Payload includes Zag machine fields:
| Field | Type | Meaning |
|---|---|---|
running | boolean | Timer is running |
paused | boolean | Timer is paused |
progressPercent | number | Progress toward target |
time | map | {days, hours, minutes, seconds, milliseconds} |
formattedTime | map | Same keys, string values |
Events
Pick an event name and pass it to on_* on <.timer>.
Server events
| Event | When | Payload |
|---|---|---|
on_tick="timer_tick" | Each tick | %{"id" => id, "formattedTime" => string, ...} |
on_complete="timer_complete" | Countdown reaches target | %{"id" => id} |
on_tick
<.timer
countdown
start_ms={3_600_000}
target_ms={0}
class="timer"
on_tick="timer_tick"
on_complete="timer_complete"
>
<:start_trigger><.heroicon name="hero-play" /></:start_trigger>
<:pause_trigger><.heroicon name="hero-pause" /></:pause_trigger>
<:resume_trigger><.heroicon name="hero-play" /></:resume_trigger>
<:reset_trigger><.heroicon name="hero-arrow-path" /></:reset_trigger>
</.timer>def handle_event("timer_tick", %{"id" => id} = params, socket) do
{:noreply, assign(socket, :last_tick, Map.get(params, "formattedTime"))}
end
def handle_event("timer_complete", %{"id" => _id}, socket) do
{:noreply, socket}
endClient events
| Event | When | event.detail |
|---|---|---|
on_tick_client="timer-tick" | Each tick | id, formatted time fields |
on_complete_client="timer-complete" | Countdown completes | id |
on_tick_client
<.timer
id="timer-events-client"
countdown
start_ms={3_600_000}
target_ms={0}
class="timer"
on_tick_client="timer-tick"
on_complete_client="timer-complete"
>
<:start_trigger><.heroicon name="hero-play" /></:start_trigger>
<:pause_trigger><.heroicon name="hero-pause" /></:pause_trigger>
<:resume_trigger><.heroicon name="hero-play" /></:resume_trigger>
<:reset_trigger><.heroicon name="hero-arrow-path" /></:reset_trigger>
</.timer>const el = document.getElementById("timer-events-client");
el?.addEventListener("timer-tick", (e) => console.log(e.detail));
el?.addEventListener("timer-complete", (e) => console.log(e.detail));Style
Import timer.css and stack modifiers on the host (class on <.timer>). Use Tailwind max-w-* on the root for width caps.
@import "../corex/corex.css";Axes: Semantic (ui-accent, ui-brand, ui-alert, ui-info, ui-success), Size (ui-size-sm … ui-size-xl), Radius (ui-rounded-*). No variant axis. See the modifier guide.
Semantic modifiers set palette variables on action triggers.
Semantic
| Modifier | Classes |
|---|---|
| Default | timer |
| Accent | timer ui-accent |
| Brand | timer ui-brand |
| Alert | timer ui-alert |
| Info | timer ui-info |
| Success | timer ui-success |
Size
| Modifier | Classes |
|---|---|
| SM | timer ui-size-sm |
| MD | timer ui-size-md |
| LG | timer ui-size-lg |
| XL | timer ui-size-xl |
Summary
API
Pause the timer from phx-click. Dispatches corex:timer:pause.
Pause the timer from handle_event (timer_pause).
Reset the timer from phx-click. Dispatches corex:timer:reset.
Reset the timer from handle_event (timer_reset).
Restart the timer from phx-click. Dispatches corex:timer:restart.
Restart the timer from handle_event (timer_restart).
Resume the timer from phx-click. Dispatches corex:timer:resume.
Resume the timer from handle_event (timer_resume).
Start the timer from phx-click. Dispatches corex:timer:start on the timer root (id matches the DOM host).
Start the timer from handle_event via push_event/3 (timer_start).
Same as state/2 with default respond_to:.
Read machine state from phx-click. Dispatches corex:timer:state. Optional respond_to: :server, :client, or :both.
Components
API
@spec pause(String.t()) :: Phoenix.LiveView.JS.t()
Pause the timer from phx-click. Dispatches corex:timer:pause.
<.action phx-click={Corex.Timer.pause("my-timer")}>Pause</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>
@spec pause(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Pause the timer from handle_event (timer_pause).
def handle_event("pause_timer", _params, socket) do
{:noreply, Corex.Timer.pause(socket, "my-timer")}
end
@spec reset(String.t()) :: Phoenix.LiveView.JS.t()
Reset the timer from phx-click. Dispatches corex:timer:reset.
<.action phx-click={Corex.Timer.reset("my-timer")}>Reset</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>
@spec reset(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Reset the timer from handle_event (timer_reset).
def handle_event("reset_timer", _params, socket) do
{:noreply, Corex.Timer.reset(socket, "my-timer")}
end
@spec restart(String.t()) :: Phoenix.LiveView.JS.t()
Restart the timer from phx-click. Dispatches corex:timer:restart.
<.action phx-click={Corex.Timer.restart("my-timer")}>Restart</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>
@spec restart(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Restart the timer from handle_event (timer_restart).
def handle_event("restart_timer", _params, socket) do
{:noreply, Corex.Timer.restart(socket, "my-timer")}
end
@spec resume(String.t()) :: Phoenix.LiveView.JS.t()
Resume the timer from phx-click. Dispatches corex:timer:resume.
<.action phx-click={Corex.Timer.resume("my-timer")}>Resume</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>
@spec resume(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Resume the timer from handle_event (timer_resume).
def handle_event("resume_timer", _params, socket) do
{:noreply, Corex.Timer.resume(socket, "my-timer")}
end
@spec start(String.t()) :: Phoenix.LiveView.JS.t()
Start the timer from phx-click. Dispatches corex:timer:start on the timer root (id matches the DOM host).
<.action phx-click={Corex.Timer.start("my-timer")}>Start</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>document.getElementById("my-timer")?.dispatchEvent(new CustomEvent("corex:timer:start", { bubbles: false }));
@spec start(Phoenix.LiveView.Socket.t(), String.t()) :: Phoenix.LiveView.Socket.t()
Start the timer from handle_event via push_event/3 (timer_start).
def handle_event("start_timer", _params, socket) do
{:noreply, Corex.Timer.start(socket, "my-timer")}
end
@spec state(String.t()) :: Phoenix.LiveView.JS.t()
Same as state/2 with default respond_to:.
@spec state( String.t(), keyword() ) :: Phoenix.LiveView.JS.t()
Read machine state from phx-click. Dispatches corex:timer:state. Optional respond_to: :server, :client, or :both.
| Reply | Payload | |
|---|---|---|
| Server | timer_state_response | %{"id" => id} plus running, paused, progressPercent, time, formattedTime |
| Client | timer-state on the timer root | same fields in detail |
<.action phx-click={Corex.Timer.state("my-timer")}>State</.action>
<.timer id="my-timer" start_ms={60_000} class="timer"></.timer>def handle_event("timer_state_response", %{"id" => _, "running" => running}, socket) do
{:noreply, assign(socket, :running, running)}
enddocument.getElementById("my-timer")?.addEventListener("timer-state", (e) => {
console.log(e.detail.running, e.detail.paused);
});
@spec state(Phoenix.LiveView.Socket.t(), String.t(), keyword()) :: Phoenix.LiveView.Socket.t()
Read machine state from handle_event (timer_state). Same replies as state/2; server-side only unless you also use state/2 for a client DOM reply.
| Reply | Payload |
|---|---|
timer_state_response | %{"id" => id} plus running, paused, progressPercent, time, formattedTime |
def handle_event("read_state", _params, socket) do
{:noreply, Corex.Timer.state(socket, "my-timer")}
end
def handle_event("timer_state_response", %{"id" => _, "running" => running}, socket) do
{:noreply, assign(socket, :running, running)}
end