UNPKG

react-compound-timer

Version:

React hooks for timers, countdowns, and stopwatches.

389 lines (382 loc) 11.3 kB
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