Corex.Timer (Corex v0.1.0)

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.

FunctionActionReturns
start/1Start timer (client)%Phoenix.LiveView.JS{}
start/2Start timer (server)socket
pause/1Pause timer (client)%Phoenix.LiveView.JS{}
pause/2Pause timer (server)socket
resume/1Resume timer (client)%Phoenix.LiveView.JS{}
resume/2Resume timer (server)socket
reset/1Reset timer (client)%Phoenix.LiveView.JS{}
reset/2Reset timer (server)socket
restart/1Restart timer (client)%Phoenix.LiveView.JS{}
restart/2Restart timer (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 timer_state_response (server) or timer-state on the host (client). Payload includes Zag machine fields:

FieldTypeMeaning
runningbooleanTimer is running
pausedbooleanTimer is paused
progressPercentnumberProgress toward target
timemap{days, hours, minutes, seconds, milliseconds}
formattedTimemapSame keys, string values

Events

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

Server events

EventWhenPayload
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}
end

Client events

EventWhenevent.detail
on_tick_client="timer-tick"Each tickid, formatted time fields
on_complete_client="timer-complete"Countdown completesid

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.

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.

Components

timer(assigns)

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 to false.
  • start_ms (:integer) - Initial elapsed or remaining milliseconds depending on mode. Defaults to 0.
  • target_ms (:integer) - Countdown stops at this millisecond value (often 0). Defaults to nil.
  • auto_start (:boolean) - Start the timer automatically on mount. Defaults to true.
  • interval (:integer) - Tick interval in milliseconds. Defaults to 1000.
  • on_tick (:string) - LiveView event for each tick; see module Events section. Defaults to nil.
  • on_tick_client (:string) - Browser CustomEvent name for each tick. Defaults to nil.
  • on_complete (:string) - LiveView event when countdown or count-up reaches target. Defaults to nil.
  • on_complete_client (:string) - Browser CustomEvent name when the run completes. Defaults to nil.
  • collapse_leading_zeros (:boolean) - When nil and countdown without fixed segments, leading zero units are hidden (minimum minutes and seconds visible). Defaults to nil.
  • segments (:list) - Fixed subset of [:days, :hours, :minutes, :seconds] in natural order; disables collapse when set. Defaults to nil.
  • translation (Corex.Timer.Translation) - Zag timer translations; supports area_label for the timer region aria-label. Defaults to nil.
  • dir (:string) - Text direction for styling; nil follows the document. Defaults to nil. Must be one of nil, "ltr", or "rtl".
  • orientation (:string) - Layout orientation for CSS. Defaults to "horizontal". Must be one of "horizontal", or "vertical".
  • Global attributes are accepted.

Slots

  • separator
  • day_label
  • hour_label
  • minute_label
  • second_label
  • start_trigger - Accepts attributes:
    • class (:string)
  • pause_trigger - Accepts attributes:
    • class (:string)
  • resume_trigger - Accepts attributes:
    • class (:string)
  • reset_trigger - Accepts attributes:
    • class (:string)

timer_skeleton(assigns)

Attributes

  • id (:string)
  • Global attributes are accepted.

API

pause(timer_id)

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(socket, timer_id)

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(timer_id)

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(socket, timer_id)

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(timer_id)

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(socket, timer_id)

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(timer_id)

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(socket, timer_id)

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(timer_id)

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(socket, timer_id)

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

state(timer_id)

Same as state/2 with default respond_to:.

state(timer_id, opts)

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

ReplyPayload
Servertimer_state_response%{"id" => id} plus running, paused, progressPercent, time, formattedTime
Clienttimer-state on the timer rootsame 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)}
end
document.getElementById("my-timer")?.addEventListener("timer-state", (e) => {
  console.log(e.detail.running, e.detail.paused);
});

state(socket, timer_id, opts)

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.

ReplyPayload
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