Corex. Timer
(Corex v0.1.1)
View Source
Phoenix implementation of 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));
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
Attributes
id(:string) - DOM id for the timer root; required for imperative timer API helpers.countdown(:boolean) - Count toward zero using target_ms when set. Defaults tofalse.start_ms(:integer) - Initial elapsed or remaining milliseconds depending on mode. Defaults to0.target_ms(:integer) - Countdown stops at this millisecond value (often 0). Defaults tonil.auto_start(:boolean) - Start the timer automatically on mount. Defaults totrue.interval(:integer) - Tick interval in milliseconds. Defaults to1000.on_tick(:string) - LiveView event for each tick; see module Events section. Defaults tonil.on_tick_client(:string) - Browser CustomEvent name for each tick. Defaults tonil.on_complete(:string) - LiveView event when countdown or count-up reaches target. Defaults tonil.on_complete_client(:string) - Browser CustomEvent name when the run completes. Defaults tonil.collapse_leading_zeros(:boolean) - When nil and countdown without fixed segments, leading zero units are hidden (minimum minutes and seconds visible). Defaults tonil.segments(:list) - Fixed subset of [:days, :hours, :minutes, :seconds] in natural order; disables collapse when set. Defaults tonil.translation(Corex.Timer.Translation) - Zag timer translations; supports area_label for the timer region aria-label. Defaults tonil.dir(:string) - Text direction for styling; nil follows the document. Defaults tonil. Must be one ofnil,"ltr", or"rtl".orientation(:string) - Layout orientation for CSS. Defaults to"horizontal". Must be one of"horizontal", or"vertical".- Global attributes are accepted.
Slots
separatorday_labelhour_labelminute_labelsecond_labelstart_trigger- Accepts attributes:class(:string)
pause_trigger- Accepts attributes:class(:string)
resume_trigger- Accepts attributes:class(:string)
reset_trigger- Accepts attributes:class(:string)
Attributes
id(:string)- Global attributes are accepted.
API
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>
Pause the timer from handle_event (timer_pause).
def handle_event("pause_timer", _params, socket) do
{:noreply, Corex.Timer.pause(socket, "my-timer")}
end
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>
Reset the timer from handle_event (timer_reset).
def handle_event("reset_timer", _params, socket) do
{:noreply, Corex.Timer.reset(socket, "my-timer")}
end
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>
Restart the timer from handle_event (timer_restart).
def handle_event("restart_timer", _params, socket) do
{:noreply, Corex.Timer.restart(socket, "my-timer")}
end
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>
Resume the timer from handle_event (timer_resume).
def handle_event("resume_timer", _params, socket) do
{:noreply, Corex.Timer.resume(socket, "my-timer")}
end
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 }));
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
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.
| 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);
});
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