tiny-essentials 1.21.0 → 1.21.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,312 @@
1
+ /**
2
+ * @typedef {Object} TickResult
3
+ * @property {number} prevValue - Infinite value before applying decay.
4
+ * @property {number} removedTotal - Total amount removed this tick.
5
+ * @property {number} removedPercent - Percentage of max removed this tick.
6
+ * @property {number} currentPercent - Current percentage relative to max.
7
+ * @property {number} remainingValue - Current clamped value (≥ 0).
8
+ * @property {number} infiniteRemaining - Current infinite value (can be negative).
9
+ */
10
+ /**
11
+ * Represents a decay factor applied to the need bar.
12
+ *
13
+ * - `amount`: base value reduced per tick.
14
+ * - `multiplier`: multiplier applied to the amount.
15
+ *
16
+ * @typedef {Object} BarFactor
17
+ * @property {number} amount - Base reduction value per tick.
18
+ * @property {number} multiplier - Multiplier applied to the amount.
19
+ */
20
+ /**
21
+ * Represents the serialized state of a TinyNeedBar instance.
22
+ *
23
+ * This object is typically produced by {@link TinyNeedBar#toJSON} and
24
+ * can be used to recreate an instance via {@link TinyNeedBar.fromJSON}.
25
+ *
26
+ * @typedef {Object} SerializedData
27
+ * @property {number} maxValue - Maximum value of the bar at the moment of serialization.
28
+ * @property {number} currentValue - Current clamped value (never below 0).
29
+ * @property {number} infiniteValue - Infinite value (can go negative).
30
+ * @property {Record<string, BarFactor>} factors - Active decay factors indexed by their keys.
31
+ */
32
+ /**
33
+ * A utility class to simulate a "need bar" system.
34
+ *
35
+ * The bar decreases over time according to defined factors (each with an amount and multiplier).
36
+ * - The **main factor** controls the base decay per tick.
37
+ * - Additional factors can be added dynamically.
38
+ *
39
+ * The system tracks two values:
40
+ * - `currentValue` → cannot go below zero.
41
+ * - `infiniteValue` → can decrease infinitely into negative numbers.
42
+ */
43
+ class TinyNeedBar {
44
+ /**
45
+ * Stores all factors that influence decay.
46
+ * Each entry contains an amount and a multiplier.
47
+ * @type {Map<string, BarFactor>}
48
+ */
49
+ #factors = new Map();
50
+ /** Maximum value of the bar. @type {number} */
51
+ #maxValue;
52
+ /** Current clamped value of the bar (never below 0). @type {number} */
53
+ #currentValue;
54
+ /** Current "infinite" value of the bar (can go negative). @type {number} */
55
+ #infiniteValue;
56
+ /**
57
+ * Returns a snapshot of all currently active factors.
58
+ * Each factor is returned as a plain object to prevent direct mutation of the internal map.
59
+ *
60
+ * @returns {Record<string, BarFactor>} A record of all factors indexed by their key.
61
+ */
62
+ get factors() {
63
+ /** @type {Record<string, BarFactor>} */
64
+ const factors = {};
65
+ for (let [name, factor] of this.#factors.entries()) {
66
+ factors[name] = { ...factor };
67
+ }
68
+ return factors;
69
+ }
70
+ /**
71
+ * Returns the current percentage of the bar relative to the maximum value.
72
+ *
73
+ * @returns {number} Percentage from `0` to `100`.
74
+ */
75
+ get currentPercent() {
76
+ return (this.#currentValue / this.#maxValue) * 100;
77
+ }
78
+ /**
79
+ * Updates the maximum possible value of the bar.
80
+ * Ensures `currentValue` never exceeds the new maximum.
81
+ *
82
+ * @param {number} value - New maximum value.
83
+ */
84
+ set maxValue(value) {
85
+ if (typeof value !== 'number' || Number.isNaN(value) || value <= 0)
86
+ throw new TypeError('maxValue must be a positive number.');
87
+ this.#maxValue = value;
88
+ this.#currentValue = Math.min(this.#currentValue, value);
89
+ }
90
+ /**
91
+ * Returns the maximum possible value of the bar.
92
+ *
93
+ * @returns {number} The maximum value.
94
+ */
95
+ get maxValue() {
96
+ return this.#maxValue;
97
+ }
98
+ /**
99
+ * Returns the current clamped value of the bar.
100
+ * This value will never be below `0`.
101
+ *
102
+ * @returns {number} Current value (≥ 0).
103
+ */
104
+ get currentValue() {
105
+ return this.#currentValue;
106
+ }
107
+ /**
108
+ * Updates the infinite value of the bar.
109
+ * Automatically recalculates the `currentValue` (never below 0).
110
+ *
111
+ * @param {number} value - New infinite value.
112
+ */
113
+ set infiniteValue(value) {
114
+ if (typeof value !== 'number' || Number.isNaN(value))
115
+ throw new TypeError('infiniteValue must be a number.');
116
+ this.#infiniteValue = value;
117
+ this.#currentValue = Math.max(0, value);
118
+ this.#currentValue = Math.min(this.#currentValue, this.#maxValue);
119
+ }
120
+ /**
121
+ * Returns the current infinite value of the bar.
122
+ * Unlike `currentValue`, this one can go below zero.
123
+ *
124
+ * @returns {number} Current infinite value.
125
+ */
126
+ get infiniteValue() {
127
+ return this.#infiniteValue;
128
+ }
129
+ /**
130
+ * Creates a new need bar instance.
131
+ *
132
+ * @param {number} [maxValue=100] - Maximum value of the bar.
133
+ * @param {number} [baseDecay=1] - Base amount reduced each tick.
134
+ * @param {number} [baseDecayMulti=1] - Multiplier applied to the base decay.
135
+ */
136
+ constructor(maxValue = 100, baseDecay = 1, baseDecayMulti = 1) {
137
+ if (typeof maxValue !== 'number' || Number.isNaN(maxValue) || maxValue <= 0)
138
+ throw new TypeError('maxValue must be a positive number.');
139
+ this.#maxValue = maxValue;
140
+ this.setFactor('main', baseDecay, baseDecayMulti);
141
+ this.#currentValue = maxValue;
142
+ this.#infiniteValue = maxValue;
143
+ }
144
+ /**
145
+ * Retrieves a specific factor by its key.
146
+ *
147
+ * @param {string} key - The unique key of the factor.
148
+ * @returns {BarFactor} The requested factor object.
149
+ * @throws {Error} If the factor does not exist.
150
+ */
151
+ getFactor(key) {
152
+ if (typeof key !== 'string' || !key)
153
+ throw new TypeError('Key must be a non-empty string.');
154
+ const result = this.#factors.get(key);
155
+ if (!result)
156
+ throw new Error(`Factor with key "${key}" not found.`);
157
+ return { ...result };
158
+ }
159
+ /**
160
+ * Checks if a specific factor exists by key.
161
+ *
162
+ * @param {string} key - The factor key to check.
163
+ * @returns {boolean} `true` if the factor exists, otherwise `false`.
164
+ */
165
+ hasFactor(key) {
166
+ if (typeof key !== 'string' || !key)
167
+ throw new TypeError('Key must be a non-empty string.');
168
+ return this.#factors.has(key);
169
+ }
170
+ /**
171
+ * Defines or updates a decay factor.
172
+ *
173
+ * @param {string} key - Unique identifier for the factor.
174
+ * @param {number} amount - Amount reduced per tick.
175
+ * @param {number} [multiplier=1] - Multiplier applied to the amount.
176
+ */
177
+ setFactor(key, amount, multiplier = 1) {
178
+ if (typeof key !== 'string' || !key)
179
+ throw new TypeError('Key must be a non-empty string.');
180
+ if (typeof amount !== 'number' || Number.isNaN(amount))
181
+ throw new TypeError('Amount must be a valid number.');
182
+ if (typeof multiplier !== 'number' || Number.isNaN(multiplier))
183
+ throw new TypeError('Multiplier must be a valid number.');
184
+ this.#factors.set(key, { amount, multiplier });
185
+ }
186
+ /**
187
+ * Removes a decay factor by its key.
188
+ *
189
+ * @param {string} key - The factor key to remove.
190
+ * @returns {boolean} Returns `true` if the factor existed and was successfully removed,
191
+ * or `false` if the factor did not exist.
192
+ */
193
+ removeFactor(key) {
194
+ if (typeof key !== 'string' || !key)
195
+ throw new TypeError('Key must be a non-empty string.');
196
+ return this.#factors.delete(key);
197
+ }
198
+ /**
199
+ * Applies a total decay value to the bar and updates both current and infinite values.
200
+ * This is a private helper used by the public tick methods.
201
+ *
202
+ * @param {number} removedTotal - The total amount to remove from the bar during this tick.
203
+ * @returns {TickResult} An object containing detailed information about the tick.
204
+ */
205
+ #tick(removedTotal) {
206
+ const prevValue = this.#infiniteValue;
207
+ this.#infiniteValue -= removedTotal;
208
+ this.#currentValue = Math.max(0, this.#currentValue - removedTotal);
209
+ const removedPercent = (removedTotal / this.#maxValue) * 100;
210
+ return {
211
+ prevValue,
212
+ removedTotal,
213
+ removedPercent,
214
+ currentPercent: this.currentPercent,
215
+ remainingValue: this.#currentValue,
216
+ infiniteRemaining: this.#infiniteValue,
217
+ };
218
+ }
219
+ /**
220
+ * Executes one tick of decay, applying all active factors.
221
+ *
222
+ * @returns {TickResult}
223
+ */
224
+ tick() {
225
+ let removedTotal = 0;
226
+ for (let [_, factor] of this.#factors.entries()) {
227
+ removedTotal += factor.amount * factor.multiplier;
228
+ }
229
+ return this.#tick(removedTotal);
230
+ }
231
+ /**
232
+ * Executes one tick of decay using a temporary factor.
233
+ *
234
+ * @param {BarFactor} tempFactor - Temporary factor to apply only in this tick.
235
+ * @returns {TickResult}
236
+ */
237
+ tickWithTempFactor(tempFactor) {
238
+ if (typeof tempFactor !== 'object' || tempFactor === null)
239
+ throw new TypeError('You must provide a valid factor object.');
240
+ if (typeof tempFactor.amount !== 'number' || Number.isNaN(tempFactor.amount))
241
+ throw new TypeError('Temp factor "amount" must be a valid number.');
242
+ if ('multiplier' in tempFactor &&
243
+ (typeof tempFactor.multiplier !== 'number' || Number.isNaN(tempFactor.multiplier)))
244
+ throw new TypeError('Temp factor "multiplier" must be a valid number if provided.');
245
+ let removedTotal = 0;
246
+ for (let [_, factor] of this.#factors.entries()) {
247
+ removedTotal += factor.amount * factor.multiplier;
248
+ }
249
+ if (tempFactor)
250
+ removedTotal += tempFactor.amount * (tempFactor.multiplier ?? 1);
251
+ return this.#tick(removedTotal);
252
+ }
253
+ /**
254
+ * Executes one tick using only the specified factor.
255
+ *
256
+ * @param {BarFactor} factor - The single factor to apply.
257
+ * @returns {TickResult}
258
+ */
259
+ tickSingleFactor(factor) {
260
+ if (typeof factor !== 'object' || factor === null)
261
+ throw new TypeError('You must provide a valid factor object.');
262
+ if (typeof factor.amount !== 'number' || Number.isNaN(factor.amount))
263
+ throw new TypeError('Temp factor "amount" must be a valid number.');
264
+ if ('multiplier' in factor &&
265
+ (typeof factor.multiplier !== 'number' || Number.isNaN(factor.multiplier)))
266
+ throw new TypeError('Temp factor "multiplier" must be a valid number if provided.');
267
+ if (!factor)
268
+ throw new Error('You must provide a factor to apply.');
269
+ const removedTotal = factor.amount * (factor.multiplier ?? 1);
270
+ return this.#tick(removedTotal);
271
+ }
272
+ /**
273
+ * Serializes the current state of the need bar.
274
+ * @returns {SerializedData}
275
+ */
276
+ toJSON() {
277
+ return {
278
+ maxValue: this.#maxValue,
279
+ currentValue: this.#currentValue,
280
+ infiniteValue: this.#infiniteValue,
281
+ factors: this.factors,
282
+ };
283
+ }
284
+ /**
285
+ * Restores a need bar from a serialized object.
286
+ * @param {SerializedData} data
287
+ * @returns {TinyNeedBar}
288
+ */
289
+ static fromJSON(data) {
290
+ const bar = new TinyNeedBar(data.maxValue, 0, 0);
291
+ bar.infiniteValue = data.infiniteValue;
292
+ bar.#factors.clear();
293
+ for (const [key, factor] of Object.entries(data.factors)) {
294
+ bar.setFactor(key, factor.amount, factor.multiplier);
295
+ }
296
+ return bar;
297
+ }
298
+ /**
299
+ * Creates a deep clone of this need bar.
300
+ * @returns {TinyNeedBar}
301
+ */
302
+ clone() {
303
+ return TinyNeedBar.fromJSON(this.toJSON());
304
+ }
305
+ /**
306
+ * Clear the factors map, clearing all factor data.
307
+ */
308
+ clearFactors() {
309
+ this.#factors.clear();
310
+ }
311
+ }
312
+ export default TinyNeedBar;
@@ -0,0 +1,91 @@
1
+ 'use strict';
2
+
3
+ /**
4
+ * TinySimpleDice
5
+ *
6
+ * A lightweight, flexible dice rolling utility for generating random numbers.
7
+ * You can configure the dice to allow zero, set a maximum value, and even roll
8
+ * values suitable for indexing arrays or Sets.
9
+ */
10
+ class TinySimpleDice {
11
+ /**
12
+ * Rolls a dice specifically for choosing an array or Set index.
13
+ * @param {any[]|Set<any>} arr - The array or Set to get a random index from.
14
+ * @returns {number} - Valid index for the array or Set.
15
+ * @throws {TypeError} If the input is not an array or Set.
16
+ */
17
+ static rollArrayIndex(arr) {
18
+ const isArray = Array.isArray(arr);
19
+ const isSet = arr instanceof Set;
20
+ if (!isArray && !isSet) throw new TypeError('rollArrayIndex expects an array or Set.');
21
+ return Math.floor(Math.random() * (isArray ? arr.length : arr.size));
22
+ }
23
+
24
+ /** @type {number} */
25
+ #maxValue;
26
+ /** @type {boolean} */
27
+ #allowZero;
28
+
29
+ /**
30
+ * Maximum value the dice can roll.
31
+ * @type {number}
32
+ */
33
+ get maxValue() {
34
+ return this.#maxValue;
35
+ }
36
+
37
+ /**
38
+ * Set the maximum value the dice can roll.
39
+ * @param {number} value - New maximum value (must be a non-negative integer)
40
+ * @throws {TypeError} If value is not a non-negative integer
41
+ */
42
+ set maxValue(value) {
43
+ if (!Number.isInteger(value) || value < 0)
44
+ throw new TypeError('maxValue must be an integer greater than -1.');
45
+ this.#maxValue = value;
46
+ }
47
+
48
+ /**
49
+ * Whether 0 is allowed as a result.
50
+ * @type {boolean}
51
+ */
52
+ get allowZero() {
53
+ return this.#allowZero;
54
+ }
55
+
56
+ /**
57
+ * Set whether 0 is allowed as a result.
58
+ * @param {boolean} value - true to allow 0, false to disallow
59
+ * @throws {TypeError} If value is not a boolean
60
+ */
61
+ set allowZero(value) {
62
+ if (typeof value !== 'boolean') throw new TypeError('allowZero must be a boolean.');
63
+ this.#allowZero = value;
64
+ }
65
+
66
+ /**
67
+ * Creates a new TinySimpleDice instance.
68
+ * @param {Object} options - Configuration options for the dice.
69
+ * @param {number} options.maxValue - Maximum value the dice can roll.
70
+ * @param {boolean} [options.allowZero=true] - Whether 0 is allowed as a result.
71
+ * @throws {TypeError} If maxValue is not a non-negative integer or allowZero is not boolean.
72
+ */
73
+ constructor({ maxValue, allowZero = true }) {
74
+ if (typeof allowZero !== 'boolean') throw new TypeError('allowZero must be an boolean.');
75
+ if (!Number.isInteger(maxValue) || maxValue < 0)
76
+ throw new TypeError('maxValue must be an integer greater than -1.');
77
+ this.#maxValue = maxValue;
78
+ this.#allowZero = allowZero;
79
+ }
80
+
81
+ /**
82
+ * Rolls the dice according to the configuration.
83
+ * @returns {number} - Random number according to the dice configuration.
84
+ */
85
+ roll() {
86
+ const min = this.#allowZero ? 0 : 1;
87
+ return Math.floor(Math.random() * (this.#maxValue - min + 1)) + min;
88
+ }
89
+ }
90
+
91
+ module.exports = TinySimpleDice;
@@ -0,0 +1,57 @@
1
+ export default TinySimpleDice;
2
+ /**
3
+ * TinySimpleDice
4
+ *
5
+ * A lightweight, flexible dice rolling utility for generating random numbers.
6
+ * You can configure the dice to allow zero, set a maximum value, and even roll
7
+ * values suitable for indexing arrays or Sets.
8
+ */
9
+ declare class TinySimpleDice {
10
+ /**
11
+ * Rolls a dice specifically for choosing an array or Set index.
12
+ * @param {any[]|Set<any>} arr - The array or Set to get a random index from.
13
+ * @returns {number} - Valid index for the array or Set.
14
+ * @throws {TypeError} If the input is not an array or Set.
15
+ */
16
+ static rollArrayIndex(arr: any[] | Set<any>): number;
17
+ /**
18
+ * Creates a new TinySimpleDice instance.
19
+ * @param {Object} options - Configuration options for the dice.
20
+ * @param {number} options.maxValue - Maximum value the dice can roll.
21
+ * @param {boolean} [options.allowZero=true] - Whether 0 is allowed as a result.
22
+ * @throws {TypeError} If maxValue is not a non-negative integer or allowZero is not boolean.
23
+ */
24
+ constructor({ maxValue, allowZero }: {
25
+ maxValue: number;
26
+ allowZero?: boolean | undefined;
27
+ });
28
+ /**
29
+ * Set the maximum value the dice can roll.
30
+ * @param {number} value - New maximum value (must be a non-negative integer)
31
+ * @throws {TypeError} If value is not a non-negative integer
32
+ */
33
+ set maxValue(value: number);
34
+ /**
35
+ * Maximum value the dice can roll.
36
+ * @type {number}
37
+ */
38
+ get maxValue(): number;
39
+ /**
40
+ * Set whether 0 is allowed as a result.
41
+ * @param {boolean} value - true to allow 0, false to disallow
42
+ * @throws {TypeError} If value is not a boolean
43
+ */
44
+ set allowZero(value: boolean);
45
+ /**
46
+ * Whether 0 is allowed as a result.
47
+ * @type {boolean}
48
+ */
49
+ get allowZero(): boolean;
50
+ /**
51
+ * Rolls the dice according to the configuration.
52
+ * @returns {number} - Random number according to the dice configuration.
53
+ */
54
+ roll(): number;
55
+ #private;
56
+ }
57
+ //# sourceMappingURL=TinySimpleDice.d.mts.map
@@ -0,0 +1,84 @@
1
+ /**
2
+ * TinySimpleDice
3
+ *
4
+ * A lightweight, flexible dice rolling utility for generating random numbers.
5
+ * You can configure the dice to allow zero, set a maximum value, and even roll
6
+ * values suitable for indexing arrays or Sets.
7
+ */
8
+ class TinySimpleDice {
9
+ /**
10
+ * Rolls a dice specifically for choosing an array or Set index.
11
+ * @param {any[]|Set<any>} arr - The array or Set to get a random index from.
12
+ * @returns {number} - Valid index for the array or Set.
13
+ * @throws {TypeError} If the input is not an array or Set.
14
+ */
15
+ static rollArrayIndex(arr) {
16
+ const isArray = Array.isArray(arr);
17
+ const isSet = arr instanceof Set;
18
+ if (!isArray && !isSet)
19
+ throw new TypeError('rollArrayIndex expects an array or Set.');
20
+ return Math.floor(Math.random() * (isArray ? arr.length : arr.size));
21
+ }
22
+ /** @type {number} */
23
+ #maxValue;
24
+ /** @type {boolean} */
25
+ #allowZero;
26
+ /**
27
+ * Maximum value the dice can roll.
28
+ * @type {number}
29
+ */
30
+ get maxValue() {
31
+ return this.#maxValue;
32
+ }
33
+ /**
34
+ * Set the maximum value the dice can roll.
35
+ * @param {number} value - New maximum value (must be a non-negative integer)
36
+ * @throws {TypeError} If value is not a non-negative integer
37
+ */
38
+ set maxValue(value) {
39
+ if (!Number.isInteger(value) || value < 0)
40
+ throw new TypeError('maxValue must be an integer greater than -1.');
41
+ this.#maxValue = value;
42
+ }
43
+ /**
44
+ * Whether 0 is allowed as a result.
45
+ * @type {boolean}
46
+ */
47
+ get allowZero() {
48
+ return this.#allowZero;
49
+ }
50
+ /**
51
+ * Set whether 0 is allowed as a result.
52
+ * @param {boolean} value - true to allow 0, false to disallow
53
+ * @throws {TypeError} If value is not a boolean
54
+ */
55
+ set allowZero(value) {
56
+ if (typeof value !== 'boolean')
57
+ throw new TypeError('allowZero must be a boolean.');
58
+ this.#allowZero = value;
59
+ }
60
+ /**
61
+ * Creates a new TinySimpleDice instance.
62
+ * @param {Object} options - Configuration options for the dice.
63
+ * @param {number} options.maxValue - Maximum value the dice can roll.
64
+ * @param {boolean} [options.allowZero=true] - Whether 0 is allowed as a result.
65
+ * @throws {TypeError} If maxValue is not a non-negative integer or allowZero is not boolean.
66
+ */
67
+ constructor({ maxValue, allowZero = true }) {
68
+ if (typeof allowZero !== 'boolean')
69
+ throw new TypeError('allowZero must be an boolean.');
70
+ if (!Number.isInteger(maxValue) || maxValue < 0)
71
+ throw new TypeError('maxValue must be an integer greater than -1.');
72
+ this.#maxValue = maxValue;
73
+ this.#allowZero = allowZero;
74
+ }
75
+ /**
76
+ * Rolls the dice according to the configuration.
77
+ * @returns {number} - Random number according to the dice configuration.
78
+ */
79
+ roll() {
80
+ const min = this.#allowZero ? 0 : 1;
81
+ return Math.floor(Math.random() * (this.#maxValue - min + 1)) + min;
82
+ }
83
+ }
84
+ export default TinySimpleDice;
package/docs/v1/README.md CHANGED
@@ -55,6 +55,8 @@ This folder contains the core scripts we have worked on so far. Each file is a m
55
55
  - 📦 **[TinyInventory](./libs/TinyInventory.md)** — A robust inventory management system with stack handling, slot management, special equipment slots, serialization, cloning, and item registry support.
56
56
  - 🤝 **[TinyInventoryTrader](./libs/TinyInventoryTrader.md)** — A trading helper for safely transferring items between two inventories with support for strict mode, slot targeting, and batch operations.
57
57
  - 🌐 **[TinyI18](./libs/TinyI18.md)** — A flexible i18n manager supporting local and file modes, regex-based keys, function-based entries, string interpolation, and safe helper functions for advanced rendering.
58
+ - 🎮 **[TinyNeedBar](./libs/TinyNeedBar.md)** — A versatile "need bar" system for simulating decay over time with multiple configurable factors, serialization, cloning, and full control over clamped and infinite values.
59
+ - 🎲 **[TinySimpleDice](./libs/TinySimpleDice.md)** — A lightweight and flexible dice rolling utility with configurable maximum values, zero allowance, and array/Set index rolling support.
58
60
 
59
61
  ### 3. **`fileManager/`**
60
62
  * 📁 **[Main](./fileManager/main.md)** — A Node.js file/directory utility module with support for JSON, backups, renaming, size analysis, and more.
@@ -1,12 +1,12 @@
1
1
  # ⏰ clock.mjs
2
2
 
3
- A versatile time utility module for JavaScript that helps you calculate and format time durations with style. Whether you're building countdowns, timers, or just need a nice "HH:MM:SS" string, this module has your back!
3
+ A versatile time utility module for JavaScript that helps you calculate and format time durations with style. Whether you're building countdowns, timers, or just need a nice "HH\:MM\:SS" string, this module has your back!
4
4
 
5
5
  ## ✨ Features
6
6
 
7
7
  - 🔢 Calculate time differences in various units
8
8
  - 🧭 Format durations into clean, readable timer strings
9
- - 🧱 Support for years, months, days, hours, minutes, and seconds
9
+ - 🧱 Support for years, months, days, hours, minutes, seconds **and milliseconds**
10
10
  - 🎛️ Customizable output with template-based formatting
11
11
  - 🛠️ Built-in presets for classic timer formats
12
12
  - 🪶 Zero dependencies, lightweight and modular (ESM-ready!)
@@ -19,11 +19,11 @@ A versatile time utility module for JavaScript that helps you calculate and form
19
19
 
20
20
  Calculates how much time remains (or has passed) between now and a given time.
21
21
 
22
- | Param | Type | Description |
23
- |---------------|----------|-----------------------------------------------------------------------------|
24
- | `timeData` | `Date` | The target time |
25
- | `durationType`| `string` | Format to return (`asMilliseconds`, `asSeconds`, `asMinutes`, etc.) |
26
- | `now` | `Date` | Optional custom "now". Defaults to current time. |
22
+ | Param | Type | Description |
23
+ | -------------- | -------- | ------------------------------------------------------------------- |
24
+ | `timeData` | `Date` | The target time |
25
+ | `durationType` | `string` | Format to return (`asMilliseconds`, `asSeconds`, `asMinutes`, etc.) |
26
+ | `now` | `Date` | Optional custom "now". Defaults to current time. |
27
27
 
28
28
  **Returns:** `number|null` — The duration in the chosen unit.
29
29
 
@@ -33,11 +33,11 @@ Calculates how much time remains (or has passed) between now and a given time.
33
33
 
34
34
  Turns seconds into a custom timer string with fine-grained control.
35
35
 
36
- | Param | Type | Description |
37
- |-------------|----------|----------------------------------------------------------------------------------------------|
36
+ | Param | Type | Description |
37
+ | -------------- | -------- | ------------------------------------------------------------------------------------------- |
38
38
  | `totalSeconds` | `number` | The duration to format (in seconds) |
39
- | `level` | `string` | Highest level to show: `'seconds'`, `'minutes'`, `'hours'`, `'days'`, `'months'`, `'years'` |
40
- | `format` | `string` | Output string template. Supports placeholders like `{days}`, `{hours}`, `{time}`, `{total}` |
39
+ | `level` | `string` | Highest level to show: `'seconds'`, `'minutes'`, `'hours'`, `'days'`, `'months'`, `'years'` |
40
+ | `format` | `string` | Output string template. Supports placeholders like `{days}`, `{hours}`, `{time}`, `{total}` |
41
41
 
42
42
  **Returns:** `string` — A formatted string with padded units.
43
43
 
@@ -63,6 +63,33 @@ formatDayTimer(190000); // "2d 04:46:40"
63
63
 
64
64
  ---
65
65
 
66
+ ### 🔹 `breakdownDuration(totalMs, level = 'milliseconds')`
67
+
68
+ Breaks down a duration (in **milliseconds**) into its full components, returning an object instead of a string.
69
+ This is useful when you want numeric values to build your own custom formats.
70
+
71
+ | Param | Type | Description |
72
+ | --------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
73
+ | `totalMs` | `number` | The duration to format (in milliseconds) |
74
+ | `level` | `string` | Highest level to break down: `'milliseconds'`, `'seconds'`, `'minutes'`, `'hours'`, `'days'`, `'months'`, `'years'` |
75
+
76
+ **Returns:** `object` — An object with numeric values for all included units:
77
+
78
+ ```json
79
+ {
80
+ "years": 0,
81
+ "months": 0,
82
+ "days": 11,
83
+ "hours": 10,
84
+ "minutes": 20,
85
+ "seconds": 54,
86
+ "milliseconds": 321,
87
+ "total": 987654321
88
+ }
89
+ ```
90
+
91
+ ---
92
+
66
93
  ## 🧪 Examples
67
94
 
68
95
  ```js
@@ -74,4 +101,17 @@ console.log(formatDayTimer(172800 + 3661)); // "2d 01:01:01"
74
101
 
75
102
  const custom = formatCustomTimer(3600 * 26 + 61, 'days', '{days}d {hours}h {minutes}m {seconds}s');
76
103
  console.log(custom); // "1d 2h 1m 1s"
104
+
105
+ const breakdown = breakdownDuration(987654321, 'years');
106
+ console.log(breakdown);
107
+ // {
108
+ // years: 0,
109
+ // months: 0,
110
+ // days: 11,
111
+ // hours: 10,
112
+ // minutes: 20,
113
+ // seconds: 54,
114
+ // milliseconds: 321,
115
+ // total: 987654321
116
+ // }
77
117
  ```