react-compound-timer
Version:
React hooks for timers, countdowns, and stopwatches.
389 lines (382 loc) • 11.3 kB
JavaScript
var __defProp = Object.defineProperty;
var __defProps = Object.defineProperties;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __propIsEnum = Object.prototype.propertyIsEnumerable;
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
var __spreadValues = (a, b) => {
for (var prop in b || (b = {}))
if (__hasOwnProp.call(b, prop))
__defNormalProp(a, prop, b[prop]);
if (__getOwnPropSymbols)
for (var prop of __getOwnPropSymbols(b)) {
if (__propIsEnum.call(b, prop))
__defNormalProp(a, prop, b[prop]);
}
return a;
};
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
// src/index.ts
var src_exports = {};
__export(src_exports, {
createTimeModel: () => createTimeModel,
getTimeParts: () => getTimeParts,
useTimeModel: () => useTimeModel
});
module.exports = __toCommonJS(src_exports);
// src/helpers/getTimeParts.ts
function getTimeParts(time, lastUnit, roundUnit = "ms") {
const units = ["ms", "s", "m", "h", "d"];
const lastUnitIndex = units.findIndex((unit) => unit === lastUnit);
const roundUnitIndex = units.findIndex((unit) => unit === roundUnit);
if (roundUnitIndex > lastUnitIndex) {
throw new Error("roundUnitIndex must be less or equal than lastUnitIndex");
}
const dividers = [1e3, 60, 60, 24, 1];
const dividersAcc = [1, 1e3, 6e4, 36e5, 864e5];
const startValue = {
ms: 0,
s: 0,
m: 0,
h: 0,
d: 0
};
const output = units.reduce((obj, unit, index) => {
if (index > lastUnitIndex) {
obj[unit] = 0;
} else if (index === lastUnitIndex) {
obj[unit] = Math.floor(time / dividersAcc[index]);
} else {
obj[unit] = Math.floor(time / dividersAcc[index]) % dividers[index];
}
return obj;
}, startValue);
if (roundUnitIndex > 0) {
const unitToDecideOnRoundIndex = roundUnitIndex - 1;
const unitToDecideOnRound = units[unitToDecideOnRoundIndex];
const isRoundNeeded = output[unitToDecideOnRound] >= dividers[unitToDecideOnRoundIndex] / 2;
if (isRoundNeeded) {
output[roundUnit] = output[roundUnit] + 1;
}
for (let i = 0; i < roundUnitIndex; i++) {
output[units[i]] = 0;
}
}
return output;
}
// src/hook/useTimeModel.ts
var import_react = require("react");
var useTimeModel = (timer) => {
const [timerValue, setTimerValue] = (0, import_react.useState)(timer.value);
(0, import_react.useEffect)(() => {
setTimerValue(timer.value);
const onChange = (timerValue2) => {
setTimerValue(timerValue2);
};
timer.events = __spreadProps(__spreadValues({}, timer.events), {
onChange
});
return () => {
timer.events = __spreadProps(__spreadValues({}, timer.events), {
onChange: void 0
});
};
}, [timer]);
const value = (0, import_react.useMemo)(
() => ({
value: timerValue,
changeTime: timer.changeTime,
changeLastUnit: timer.changeLastUnit,
changeTimeToUpdate: timer.changeTimeToUpdate,
changeDirection: timer.changeDirection,
changeCheckpoints: timer.changeCheckpoints,
start: timer.start,
pause: timer.pause,
resume: timer.resume,
stop: timer.stop,
reset: timer.reset
}),
[timer, timerValue]
);
return value;
};
// src/helpers/now.ts
function now() {
return Date.now();
}
// src/models/TimeModelState.ts
var INITED = "INITED";
var PLAYING = "PLAYING";
var PAUSED = "PAUSED";
var STOPPED = "STOPPED";
var TimeModelState = class {
constructor(initialStatus = INITED, onStateChange = () => {
}) {
this.state = INITED;
this.state = initialStatus;
this.onStateChange = onStateChange;
}
setOnStateChange(onStateChange) {
this.onStateChange = onStateChange;
}
getState() {
return this.state;
}
setInited() {
if (this.state === INITED) {
return false;
}
this.state = INITED;
this.onChange();
return true;
}
isInited() {
return this.state === INITED;
}
setPlaying() {
if (this.state === PLAYING) {
return false;
}
this.state = PLAYING;
this.onChange();
return true;
}
isPlaying() {
return this.state === PLAYING;
}
setPaused() {
if (this.state !== PLAYING) {
return false;
}
this.state = PAUSED;
this.onChange();
return true;
}
isPaused() {
return this.state === PAUSED;
}
setStopped() {
if (this.state === INITED) {
return false;
}
this.state = STOPPED;
this.onChange();
return true;
}
isStopped() {
return this.state === STOPPED;
}
onChange() {
this.onStateChange({ state: this.state });
}
};
// src/models/TimeModel.ts
var TimeModel = class {
constructor(options, events = {}) {
/**
* Change options methods
**/
this.changeTime = (time) => {
this.internalTime = now();
this.options.initialTime = time;
this.time = this.options.initialTime;
if (this.events.onChange) {
this.events.onChange(this.getTimerValue(this.time));
}
};
this.changeLastUnit = (lastUnit) => {
if (this.innerState.isPlaying()) {
this.pause();
this.options.lastUnit = lastUnit;
this.resume(true);
} else {
this.options.lastUnit = lastUnit;
}
};
this.changeRoundUnit = (roundUnit) => {
if (this.innerState.isPlaying()) {
this.pause();
this.options.roundUnit = roundUnit;
this.resume(true);
} else {
this.options.roundUnit = roundUnit;
}
};
this.changeTimeToUpdate = (interval) => {
if (this.innerState.isPlaying()) {
this.pause();
this.options.timeToUpdate = interval;
this.resume();
} else {
this.options.timeToUpdate = interval;
}
};
this.changeDirection = (direction) => {
this.options.direction = direction;
};
this.changeCheckpoints = (checkpoints) => {
this.options.checkpoints = checkpoints;
};
/**
* Timer controls methods
**/
this.start = () => {
if (this.innerState.setPlaying()) {
this.setTimerInterval(true);
if (this.events.onStart) {
this.events.onStart();
}
}
};
this.resume = (callImmediately = false) => {
if (this.innerState.isPaused() && this.innerState.setPlaying()) {
this.setTimerInterval(callImmediately);
if (this.events.onResume) {
this.events.onResume();
}
}
};
this.pause = () => {
if (this.innerState.setPaused()) {
if (this.timerId) {
clearInterval(this.timerId);
}
if (this.events.onPause) {
this.events.onPause();
}
}
};
this.stop = () => {
if (this.innerState.setStopped()) {
if (this.timerId) {
clearInterval(this.timerId);
}
if (this.events.onStop) {
this.events.onStop();
}
}
};
this.reset = () => {
this.time = this.options.initialTime;
if (this.events.onChange) {
this.events.onChange(this.getTimerValue(this.time));
}
if (this.events.onReset) {
this.events.onReset();
}
};
/**
* Private methods
**/
this.setTimerInterval = (callImmediately = false) => {
if (this.timerId) {
clearInterval(this.timerId);
}
this.internalTime = now();
const repeatedFunc = () => {
const oldTime = this.time;
const updatedTime = this.computeTime();
if (this.events.onChange) {
this.events.onChange(this.getTimerValue(updatedTime));
}
this.options.checkpoints.map(({ time, callback }) => {
const checkForForward = time > oldTime && time <= updatedTime;
const checkForBackward = time < oldTime && time >= updatedTime;
const checkIntersection = this.options.direction === "backward" ? checkForBackward : checkForForward;
if (checkIntersection) {
callback();
}
});
};
if (callImmediately && this.events.onChange) {
this.events.onChange(this.getTimerValue(this.time));
}
this.timerId = window.setInterval(repeatedFunc, this.options.timeToUpdate);
};
this.getTimerValue = (time) => {
return __spreadProps(__spreadValues({}, getTimeParts(time, this.options.lastUnit, this.options.roundUnit)), {
state: this.innerState.getState()
});
};
this.computeTime = () => {
if (this.innerState.isPlaying()) {
const currentInternalTime = now();
const delta = Math.abs(currentInternalTime - this.internalTime);
switch (this.options.direction) {
case "forward":
this.time = this.time + delta;
this.internalTime = currentInternalTime;
return this.time;
case "backward": {
this.time = this.time - delta;
this.internalTime = currentInternalTime;
if (this.time < 0) {
this.stop();
return 0;
}
return this.time;
}
default:
return this.time;
}
}
return this.time < 0 ? 0 : this.time;
};
this.internalTime = now();
this.options = options;
this.events = events;
this.time = options.initialTime;
this.innerState = new TimeModelState("INITED", () => {
if (this.events.onChange) {
this.events.onChange(this.value);
}
});
this.timerId = null;
if (this.options.startImmediately) {
this.start();
}
}
get value() {
return this.getTimerValue(this.computeTime());
}
get currentOptions() {
return JSON.parse(JSON.stringify(this.options));
}
};
// src/instances/timeModel.ts
var defaultOptions = {
initialTime: 0,
direction: "forward",
timeToUpdate: 250,
startImmediately: true,
lastUnit: "d",
roundUnit: "ms",
checkpoints: []
};
var createTimeModel = (options = {}, events = {}) => {
const resultOptions = __spreadValues(__spreadValues({}, defaultOptions), options);
return new TimeModel(resultOptions, events);
};
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
createTimeModel,
getTimeParts,
useTimeModel
});
//# sourceMappingURL=react-compound-timer.cjs.map