package.dist.index.mjs Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of timer Show documentation
Show all versions of timer Show documentation
Core logic for the timer widget implemented as a state machine
The newest version!
import { createAnatomy } from '@zag-js/anatomy';
import { createScope } from '@zag-js/dom-query';
import { createMachine } from '@zag-js/core';
import { createSplitProps, compact, isObject } from '@zag-js/utils';
import { createProps } from '@zag-js/types';
// src/timer.anatomy.ts
var anatomy = createAnatomy("timer").parts(
"root",
"area",
"control",
"item",
"itemValue",
"itemLabel",
"actionTrigger",
"separator"
);
var parts = anatomy.build();
var dom = createScope({
getRootId: (ctx) => ctx.ids?.root ?? `timer:${ctx.id}:root`,
getAreaId: (ctx) => ctx.ids?.area ?? `timer:${ctx.id}:area`,
getAreaEl: (ctx) => dom.getById(ctx, dom.getAreaId(ctx))
});
// src/timer.connect.ts
var validActions = /* @__PURE__ */ new Set(["start", "pause", "resume", "reset"]);
function connect(state, send, normalize) {
const running = state.matches("running");
const paused = state.matches("paused");
const time = state.context.time;
const formattedTime = state.context.formattedTime;
const progressPercent = state.context.progressPercent;
return {
running,
paused,
time,
formattedTime,
progressPercent,
start() {
send("START");
},
pause() {
send("PAUSE");
},
resume() {
send("RESUME");
},
reset() {
send("RESET");
},
restart() {
send("RESTART");
},
getRootProps() {
return normalize.element({
id: dom.getRootId(state.context),
...parts.root.attrs
});
},
getAreaProps() {
return normalize.element({
role: "timer",
id: dom.getAreaId(state.context),
"aria-label": `${time.days} days ${formattedTime.hours}:${formattedTime.minutes}:${formattedTime.seconds}`,
"aria-atomic": true,
...parts.area.attrs
});
},
getControlProps() {
return normalize.element({
...parts.control.attrs
});
},
getItemProps(props2) {
const value = time[props2.type];
return normalize.element({
...parts.item.attrs,
"data-type": props2.type,
style: {
"--value": value
}
});
},
getItemLabelProps(props2) {
return normalize.element({
...parts.itemLabel.attrs,
"data-type": props2.type
});
},
getItemValueProps(props2) {
return normalize.element({
...parts.itemValue.attrs,
"data-type": props2.type
});
},
getSeparatorProps() {
return normalize.element({
"aria-hidden": true,
...parts.separator.attrs
});
},
getActionTriggerProps(props2) {
if (!validActions.has(props2.action)) {
throw new Error(
`[zag-js] Invalid action: ${props2.action}. Must be one of: ${Array.from(validActions).join(", ")}`
);
}
return normalize.button({
...parts.actionTrigger.attrs,
hidden: (() => {
switch (props2.action) {
case "start":
return running || paused;
case "pause":
return !running;
case "reset":
return !running && !paused;
case "resume":
return !paused;
default:
return;
}
})(),
type: "button",
onClick(event) {
if (event.defaultPrevented) return;
send(props2.action.toUpperCase());
}
});
}
};
}
function machine(userContext) {
const ctx = compact(userContext);
return createMachine(
{
id: "timer",
initial: ctx.autoStart ? "running" : "idle",
context: {
interval: 250,
...ctx,
currentMs: ctx.startMs ?? 0
},
on: {
RESTART: {
target: "running",
actions: "resetTime"
}
},
computed: {
time: (ctx2) => msToTime(ctx2.currentMs),
formattedTime: (ctx2) => formatTime(ctx2.time),
progressPercent: (ctx2) => {
const targetMs = ctx2.targetMs;
if (targetMs == null) return 0;
return toPercent(ctx2.currentMs, ctx2.startMs ?? 0, targetMs);
}
},
states: {
idle: {
on: {
START: "running",
RESET: { actions: "resetTime" }
}
},
running: {
every: {
TICK_INTERVAL: ["sendTickEvent"]
},
on: {
PAUSE: "paused",
TICK: [
{
target: "idle",
guard: "hasReachedTarget",
actions: ["invokeOnComplete"]
},
{
actions: ["updateTime", "invokeOnTick"]
}
],
RESET: { actions: "resetTime" }
}
},
paused: {
on: {
RESUME: "running",
RESET: {
target: "idle",
actions: "resetTime"
}
}
}
}
},
{
delays: {
TICK_INTERVAL: (ctx2) => ctx2.interval
},
actions: {
updateTime(ctx2) {
const sign = ctx2.countdown ? -1 : 1;
ctx2.currentMs = ctx2.currentMs + sign * ctx2.interval;
},
sendTickEvent(_ctx, _evt, { send }) {
send({ type: "TICK" });
},
resetTime(ctx2) {
ctx2.currentMs = ctx2.startMs ?? 0;
},
invokeOnTick(ctx2) {
ctx2.onTick?.({
value: ctx2.currentMs,
time: ctx2.time,
formattedTime: ctx2.formattedTime
});
},
invokeOnComplete(ctx2) {
ctx2.onComplete?.();
}
},
guards: {
hasReachedTarget: (ctx2) => {
let targetMs = ctx2.targetMs;
if (targetMs == null && ctx2.countdown) targetMs = 0;
if (targetMs == null) return false;
return ctx2.currentMs === targetMs;
}
}
}
);
}
function msToTime(ms) {
const milliseconds = ms % 1e3;
const seconds = Math.floor(ms / 1e3) % 60;
const minutes = Math.floor(ms / (1e3 * 60)) % 60;
const hours = Math.floor(ms / (1e3 * 60 * 60)) % 24;
const days = Math.floor(ms / (1e3 * 60 * 60 * 24));
return {
days,
hours,
minutes,
seconds,
milliseconds
};
}
function toPercent(value, minValue, maxValue) {
return (value - minValue) / (maxValue - minValue);
}
function padStart(num, size = 2) {
return num.toString().padStart(size, "0");
}
function formatTime(time) {
const { days, hours, minutes, seconds } = time;
return {
days: padStart(days),
hours: padStart(hours),
minutes: padStart(minutes),
seconds: padStart(seconds),
milliseconds: time.milliseconds.toString()
};
}
var segments = /* @__PURE__ */ new Set(["days", "hours", "minutes", "seconds"]);
function isTimeSegment(date) {
return isObject(date) && Object.keys(date).some((key) => segments.has(key));
}
function parse(date) {
if (typeof date === "string") {
return new Date(date).getTime();
}
if (isTimeSegment(date)) {
const { days = 0, hours = 0, minutes = 0, seconds = 0, milliseconds = 0 } = date;
const value = (days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds) * 1e3;
return value + milliseconds;
}
throw new Error("Invalid date");
}
var props = createProps()([
"autoStart",
"countdown",
"getRootNode",
"id",
"ids",
"interval",
"onComplete",
"onTick",
"startMs",
"targetMs"
]);
var splitProps = createSplitProps(props);
export { anatomy, connect, machine, parse, props, splitProps };
© 2015 - 2025 Weber Informatics LLC | Privacy Policy