@oscarpalmer/timer 0.27.1 → 0.29.0

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.
Files changed (67) hide show
  1. package/dist/constants.cjs +32 -4
  2. package/dist/constants.js +26 -9
  3. package/dist/delay.cjs +24 -0
  4. package/dist/delay.js +20 -0
  5. package/dist/get.cjs +15 -0
  6. package/dist/get.js +10 -0
  7. package/dist/global.cjs +17 -2
  8. package/dist/global.js +15 -1
  9. package/dist/index.cjs +16 -42
  10. package/dist/index.js +6 -48
  11. package/dist/is.cjs +11 -8
  12. package/dist/is.js +8 -12
  13. package/dist/models.cjs +5 -2
  14. package/dist/models.js +2 -3
  15. package/dist/node_modules/@oscarpalmer/atoms/dist/function.cjs +8 -0
  16. package/dist/node_modules/@oscarpalmer/atoms/dist/function.js +4 -0
  17. package/dist/repeat.cjs +30 -0
  18. package/dist/repeat.js +26 -0
  19. package/dist/timer.cjs +59 -71
  20. package/dist/timer.full.js +469 -0
  21. package/dist/timer.js +56 -72
  22. package/dist/wait.cjs +30 -0
  23. package/dist/wait.js +26 -0
  24. package/dist/when.cjs +59 -44
  25. package/dist/when.js +55 -44
  26. package/dist/work.cjs +72 -0
  27. package/dist/work.js +68 -0
  28. package/package.json +50 -9
  29. package/src/constants.ts +48 -6
  30. package/src/delay.ts +25 -0
  31. package/src/get.ts +12 -0
  32. package/src/global.ts +16 -1
  33. package/src/index.ts +5 -45
  34. package/src/is.ts +6 -6
  35. package/src/models.ts +55 -47
  36. package/src/repeat.ts +32 -0
  37. package/src/timer.ts +67 -151
  38. package/src/wait.ts +31 -0
  39. package/src/when.ts +49 -24
  40. package/src/work.ts +129 -0
  41. package/types/constants.d.cts +49 -74
  42. package/types/constants.d.ts +15 -6
  43. package/types/delay.d.cts +8 -0
  44. package/types/delay.d.ts +4 -0
  45. package/types/get.d.cts +7 -0
  46. package/types/get.d.ts +3 -0
  47. package/types/index.d.cts +89 -94
  48. package/types/index.d.ts +5 -6
  49. package/types/is.d.cts +50 -69
  50. package/types/models.d.cts +61 -63
  51. package/types/models.d.ts +43 -40
  52. package/types/repeat.d.cts +98 -0
  53. package/types/repeat.d.ts +7 -0
  54. package/types/timer.d.cts +35 -97
  55. package/types/timer.d.ts +19 -52
  56. package/types/wait.d.cts +83 -0
  57. package/types/wait.d.ts +8 -0
  58. package/types/when.d.cts +65 -69
  59. package/types/when.d.ts +18 -5
  60. package/types/work.d.cts +78 -0
  61. package/types/work.d.ts +3 -0
  62. package/dist/functions.cjs +0 -100
  63. package/dist/functions.js +0 -100
  64. package/dist/timer.iife.js +0 -429
  65. package/src/functions.ts +0 -158
  66. package/types/functions.d.cts +0 -114
  67. package/types/functions.d.ts +0 -5
@@ -0,0 +1,469 @@
1
+ /**
2
+ * A set of all active timers
3
+ */
4
+ const activeTimers = new Set();
5
+ /**
6
+ * A set of types that allow work to begin
7
+ */
8
+ const beginTypes = new Set(['continue', 'start']);
9
+ /**
10
+ * Message to show when a when-timer is destroyed
11
+ */
12
+ const destroyedMessage = 'Timer has already been destroyed';
13
+ /**
14
+ * A set of types that allow work to end
15
+ */
16
+ const endTypes = new Set(['pause', 'stop']);
17
+ /**
18
+ * A set of types that allow work to end or restart
19
+ */
20
+ const endOrRestartTypes = new Set([
21
+ 'pause',
22
+ 'restart',
23
+ 'stop',
24
+ ]);
25
+ /**
26
+ * A set of timers that were paused due to the document being hidden
27
+ */
28
+ const hiddenTimers = new Set();
29
+ const pauseTypes = new Set(['continue', 'pause']);
30
+ /**
31
+ * Message to show when a when-timer is started
32
+ */
33
+ const startedMessage = 'Timer has already been started';
34
+ //
35
+ const values = [];
36
+ let last;
37
+ /**
38
+ * A calculated average of the refresh rate of the display
39
+ */
40
+ let milliseconds;
41
+ function step(now) {
42
+ if (values.length === 10) {
43
+ milliseconds = Math.floor(values.slice(2, -2).reduce((first, second) => first + second) / 6);
44
+ last = undefined;
45
+ values.length = 0;
46
+ }
47
+ else {
48
+ last ??= now;
49
+ const difference = now - last;
50
+ if (difference > 0) {
51
+ values.push(difference);
52
+ }
53
+ last = now;
54
+ requestAnimationFrame(step);
55
+ }
56
+ }
57
+ requestAnimationFrame(step);
58
+
59
+ if (globalThis._oscarpalmer_timers == null) {
60
+ Object.defineProperty(globalThis, '_oscarpalmer_timers', {
61
+ get() {
62
+ return globalThis._oscarpalmer_timer_debug ? [...activeTimers] : [];
63
+ },
64
+ });
65
+ }
66
+ document.addEventListener('visibilitychange', () => {
67
+ if (document.hidden) {
68
+ for (const timer of activeTimers) {
69
+ hiddenTimers.add(timer);
70
+ timer.pause();
71
+ }
72
+ }
73
+ else {
74
+ for (const timer of hiddenTimers) {
75
+ timer.continue();
76
+ }
77
+ hiddenTimers.clear();
78
+ }
79
+ });
80
+
81
+ function noop() {
82
+ }
83
+
84
+ function getCallback(value) {
85
+ return typeof value === 'function' ? value : noop;
86
+ }
87
+ function getValidNumber(value, defaultValue) {
88
+ return typeof value === 'number' && value > (defaultValue ?? 0)
89
+ ? value
90
+ : defaultValue ?? 0;
91
+ }
92
+
93
+ /**
94
+ * Create a delayed promise that resolves after a certain amount of time _(in milliseconds; defaults to screen refresh rate)_
95
+ */
96
+ function delay(time) {
97
+ return new Promise(resolve => {
98
+ const interval = getValidNumber(time, milliseconds);
99
+ let start;
100
+ function step(now) {
101
+ start ??= now;
102
+ if (interval === milliseconds || now - start >= interval - 5) {
103
+ resolve();
104
+ }
105
+ else {
106
+ requestAnimationFrame(step);
107
+ }
108
+ }
109
+ requestAnimationFrame(step);
110
+ });
111
+ }
112
+
113
+ function is(names, value) {
114
+ return names.includes(value?.$timer);
115
+ }
116
+ /**
117
+ * Is the value a repeating timer?
118
+ */
119
+ function isRepeated(value) {
120
+ return is(['repeat'], value);
121
+ }
122
+ /**
123
+ * Is the value a timer?
124
+ */
125
+ function isTimer(value) {
126
+ return is(['repeat', 'wait'], value);
127
+ }
128
+ /**
129
+ * Is the value a waiting timer?
130
+ */
131
+ function isWaited(value) {
132
+ return is(['wait'], value);
133
+ }
134
+ /**
135
+ * Is the value a conditional timer?
136
+ */
137
+ function isWhen(value) {
138
+ return is(['when'], value) && typeof value.then === 'function';
139
+ }
140
+
141
+ class TimerTrace extends Error {
142
+ constructor() {
143
+ super();
144
+ this.name = 'TimerTrace';
145
+ }
146
+ }
147
+
148
+ class Timer {
149
+ worker;
150
+ options;
151
+ state;
152
+ /**
153
+ * Is the timer active?
154
+ */
155
+ get active() {
156
+ return this.state.active;
157
+ }
158
+ /**
159
+ * Is the timer destroyed?
160
+ */
161
+ get destroyed() {
162
+ return this.state.destroyed;
163
+ }
164
+ /**
165
+ * Is the timer paused?
166
+ */
167
+ get paused() {
168
+ return this.state.paused;
169
+ }
170
+ /**
171
+ * Get the timer's origin _(if debugging is enabled)_
172
+ */
173
+ get trace() {
174
+ return globalThis._oscarpalmer_timer_debug ?? false
175
+ ? this.state.trace
176
+ : undefined;
177
+ }
178
+ constructor(type, worker, state, options, start) {
179
+ this.worker = worker;
180
+ this.options = options;
181
+ this.$timer = type;
182
+ this.state = {
183
+ ...state,
184
+ active: false,
185
+ destroyed: false,
186
+ elapsed: 0,
187
+ frame: undefined,
188
+ index: 0,
189
+ paused: false,
190
+ total: 0,
191
+ };
192
+ if (start) {
193
+ this.start();
194
+ }
195
+ }
196
+ /**
197
+ * Continue running the timer _(if it's paused)_
198
+ */
199
+ continue() {
200
+ return this.#work('continue');
201
+ }
202
+ /**
203
+ * Destroy the timer
204
+ */
205
+ destroy() {
206
+ this.state.destroyed = true;
207
+ this.#work('stop');
208
+ }
209
+ /**
210
+ * Pause the timer _(if it's running)_
211
+ */
212
+ pause() {
213
+ return this.#work('pause');
214
+ }
215
+ /**
216
+ * Restart the timer _(or start it, if it's not running)_
217
+ */
218
+ restart() {
219
+ return this.#work('restart');
220
+ }
221
+ /**
222
+ * Start the timer _(if it's not running)_
223
+ */
224
+ start() {
225
+ return this.#work('start');
226
+ }
227
+ /**
228
+ * Stop the timer _(if it's running)_
229
+ */
230
+ stop() {
231
+ return this.#work('stop');
232
+ }
233
+ #work(type) {
234
+ return this.worker(type, {
235
+ instance: this,
236
+ type: this.$timer,
237
+ }, this.state, this.options);
238
+ }
239
+ }
240
+
241
+ function finish(timer, state, options, success) {
242
+ activeTimers.delete(timer.instance);
243
+ state.active = false;
244
+ state.elapsed = 0;
245
+ state.frame = undefined;
246
+ if (timer.type === 'wait') {
247
+ state.callback();
248
+ }
249
+ else {
250
+ options.onAfter?.(success);
251
+ }
252
+ }
253
+ function work(type, timer, state, options) {
254
+ if ((state.destroyed && type !== 'stop') ||
255
+ (state.active ? beginTypes.has(type) : endTypes.has(type))) {
256
+ return timer.instance;
257
+ }
258
+ const pausable = pauseTypes.has(type);
259
+ state.elapsed = pausable ? state.elapsed : 0;
260
+ state.index = pausable ? state.index : 0;
261
+ state.total = pausable ? state.total : 0;
262
+ if (endOrRestartTypes.has(type)) {
263
+ activeTimers.delete(timer.instance);
264
+ cancelAnimationFrame(state.frame);
265
+ if (type === 'stop') {
266
+ options.onAfter?.(false);
267
+ }
268
+ state.active = false;
269
+ state.frame = undefined;
270
+ state.paused = type === 'pause';
271
+ return type === 'restart'
272
+ ? work('start', timer, state, options)
273
+ : timer.instance;
274
+ }
275
+ state.active = true;
276
+ state.paused = false;
277
+ let start;
278
+ function step(now) {
279
+ if (!state.active) {
280
+ return;
281
+ }
282
+ start ??= now;
283
+ const difference = now - start;
284
+ state.elapsed += difference;
285
+ state.total += difference;
286
+ if (options.timeout > 0 && state.total >= options.timeout) {
287
+ options.onError?.();
288
+ finish(timer, state, options, false);
289
+ return;
290
+ }
291
+ if (options.interval === milliseconds ||
292
+ state.elapsed >= options.interval - 5) {
293
+ if (options.count > -1) {
294
+ state.callback(state.index);
295
+ }
296
+ start = now;
297
+ state.elapsed = 0;
298
+ state.index += 1;
299
+ if (options.count === -1 ||
300
+ (options.count > 0 && state.index >= options.count)) {
301
+ finish(timer, state, options, true);
302
+ return;
303
+ }
304
+ }
305
+ state.frame = requestAnimationFrame(step);
306
+ }
307
+ activeTimers.add(timer.instance);
308
+ state.frame = requestAnimationFrame(step);
309
+ return timer.instance;
310
+ }
311
+
312
+ /**
313
+ * Create a repeating timer
314
+ */
315
+ function repeat(callback, options) {
316
+ return new Timer('repeat', work, {
317
+ callback: getCallback(callback),
318
+ trace: new TimerTrace().stack,
319
+ }, {
320
+ onAfter: getCallback(options?.onAfter),
321
+ onError: undefined,
322
+ count: getValidNumber(options?.count),
323
+ interval: getValidNumber(options?.interval, milliseconds),
324
+ timeout: 0,
325
+ }, true);
326
+ }
327
+
328
+ /**
329
+ * Create a waiting timer
330
+ * @param callback Callback to run when the timer has finished
331
+ * @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
332
+ */
333
+ function wait(callback, time) {
334
+ return new Timer('wait', work, {
335
+ callback: getCallback(callback),
336
+ trace: new TimerTrace().stack,
337
+ }, {
338
+ onAfter: undefined,
339
+ onError: undefined,
340
+ count: -1,
341
+ interval: getValidNumber(time, milliseconds),
342
+ timeout: 0,
343
+ }, true);
344
+ }
345
+
346
+ class When {
347
+ $timer = 'when';
348
+ state;
349
+ /**
350
+ * Is the timer active?
351
+ */
352
+ get active() {
353
+ return this.state.timer?.active ?? false;
354
+ }
355
+ /**
356
+ * Is the timer destroyed?
357
+ */
358
+ get destroyed() {
359
+ return this.state.timer == null;
360
+ }
361
+ /**
362
+ * Is the timer paused?
363
+ */
364
+ get paused() {
365
+ return this.state.timer?.paused ?? false;
366
+ }
367
+ /**
368
+ * Get the timer's origin _(if debugging is enabled)_
369
+ */
370
+ get trace() {
371
+ return globalThis._oscarpalmer_timer_debug ?? false
372
+ ? this.state.timer?.trace
373
+ : undefined;
374
+ }
375
+ constructor(state) {
376
+ this.state = state;
377
+ }
378
+ /**
379
+ * Continues the timer _(if it was paused)_
380
+ */
381
+ continue() {
382
+ this.state.timer?.continue();
383
+ return this;
384
+ }
385
+ /**
386
+ * Destroys the timer _(and stops it,if it was running)_
387
+ */
388
+ destroy() {
389
+ this.state.timer?.destroy();
390
+ this.state.promise = undefined;
391
+ this.state.resolver = noop;
392
+ this.state.rejecter = noop;
393
+ this.state.timer = undefined;
394
+ }
395
+ /**
396
+ * Pauses the timer _(if it was running)_
397
+ */
398
+ pause() {
399
+ this.state.timer?.pause();
400
+ return this;
401
+ }
402
+ /**
403
+ * Stops the timer _(if it was running)_
404
+ */
405
+ stop() {
406
+ this.state.timer?.stop();
407
+ return this;
408
+ }
409
+ /**
410
+ * Starts the timer and returns a promise that resolves when the condition is met
411
+ */
412
+ // biome-ignore lint/suspicious/noThenProperty: returning a promise-like object, so it's ok ;)
413
+ then(resolve, reject) {
414
+ if (this.state.timer == null || this.state?.started) {
415
+ throw new Error(this.state.timer == null ? destroyedMessage : startedMessage);
416
+ }
417
+ this.state.started = true;
418
+ this.state.timer.start();
419
+ return this.state.promise.then(resolve ?? noop, reject ?? noop);
420
+ }
421
+ }
422
+ /**
423
+ * Create a conditional timer
424
+ */
425
+ function when(condition, options) {
426
+ let called = false;
427
+ let result = false;
428
+ const state = {
429
+ started: false,
430
+ timer: new Timer('when', work, {
431
+ callback() {
432
+ if (condition()) {
433
+ result = true;
434
+ state.timer.stop();
435
+ }
436
+ },
437
+ trace: new TimerTrace().stack,
438
+ }, {
439
+ onAfter() {
440
+ if (!(state.timer?.paused ?? false) && !called) {
441
+ called = true;
442
+ if (result) {
443
+ state.resolver?.();
444
+ }
445
+ else {
446
+ state.rejecter?.();
447
+ }
448
+ instance.destroy();
449
+ }
450
+ },
451
+ onError() {
452
+ state.rejecter?.();
453
+ instance.destroy();
454
+ },
455
+ count: getValidNumber(options?.count),
456
+ interval: getValidNumber(options?.interval, milliseconds),
457
+ timeout: getValidNumber(options?.timeout),
458
+ }, false),
459
+ };
460
+ const promise = new Promise((resolve, reject) => {
461
+ state.resolver = resolve;
462
+ state.rejecter = reject;
463
+ });
464
+ state.promise = promise;
465
+ const instance = new When(state);
466
+ return instance;
467
+ }
468
+
469
+ export { delay, isRepeated, isTimer, isWaited, isWhen, repeat, wait, when };
package/dist/timer.js CHANGED
@@ -1,111 +1,95 @@
1
- import { milliseconds } from "./constants.js";
2
- import { work, getOptions } from "./functions.js";
3
- import { TimerTrace } from "./models.js";
4
- class BasicTimer {
5
- constructor(type, state) {
1
+ class Timer {
2
+ constructor(type, worker, state, options, start) {
3
+ this.worker = worker;
4
+ this.options = options;
6
5
  this.$timer = type;
7
- this.state = state;
6
+ this.state = {
7
+ ...state,
8
+ active: false,
9
+ destroyed: false,
10
+ elapsed: 0,
11
+ frame: void 0,
12
+ index: 0,
13
+ paused: false,
14
+ total: 0
15
+ };
16
+ if (start) {
17
+ this.start();
18
+ }
8
19
  }
9
- }
10
- class Timer extends BasicTimer {
20
+ state;
21
+ /**
22
+ * Is the timer active?
23
+ */
11
24
  get active() {
12
25
  return this.state.active;
13
26
  }
27
+ /**
28
+ * Is the timer destroyed?
29
+ */
14
30
  get destroyed() {
15
31
  return this.state.destroyed;
16
32
  }
33
+ /**
34
+ * Is the timer paused?
35
+ */
17
36
  get paused() {
18
37
  return this.state.paused;
19
38
  }
39
+ /**
40
+ * Get the timer's origin _(if debugging is enabled)_
41
+ */
20
42
  get trace() {
21
- return globalThis._oscarpalmer_timer_debug ? this.state.trace : void 0;
22
- }
23
- constructor(type, state, options) {
24
- super(type, state);
25
- this.options = options;
43
+ return globalThis._oscarpalmer_timer_debug ?? false ? this.state.trace : void 0;
26
44
  }
27
45
  /**
28
- * Continues the timer _(if it was paused)_
46
+ * Continue running the timer _(if it's paused)_
29
47
  */
30
48
  continue() {
31
- return work("continue", this, this.state, this.options);
49
+ return this.#work("continue");
32
50
  }
33
51
  /**
34
- * Destroys the timer _(after stopping it, if it was running)_
52
+ * Destroy the timer
35
53
  */
36
54
  destroy() {
37
- if (!this.state.destroyed) {
38
- this.state.destroyed = true;
39
- this.stop();
40
- this.options.afterCallback = void 0;
41
- this.options.errorCallback = void 0;
42
- this.state.callback = void 0;
43
- this.state.trace = void 0;
44
- }
55
+ this.state.destroyed = true;
56
+ this.#work("stop");
45
57
  }
46
58
  /**
47
- * Pauses the timer _(if it was running)_
59
+ * Pause the timer _(if it's running)_
48
60
  */
49
61
  pause() {
50
- return work("pause", this, this.state, this.options);
62
+ return this.#work("pause");
51
63
  }
52
64
  /**
53
- * Restarts the timer _(if it was running)_
65
+ * Restart the timer _(or start it, if it's not running)_
54
66
  */
55
67
  restart() {
56
- return work("restart", this, this.state, this.options);
68
+ return this.#work("restart");
57
69
  }
58
70
  /**
59
- * Starts the timer _(if it was stopped)_
71
+ * Start the timer _(if it's not running)_
60
72
  */
61
73
  start() {
62
- return work("start", this, this.state, this.options);
74
+ return this.#work("start");
63
75
  }
64
76
  /**
65
- * Stops the timer _(if it was running)_
77
+ * Stop the timer _(if it's running)_
66
78
  */
67
79
  stop() {
68
- return work("stop", this, this.state, this.options);
80
+ return this.#work("stop");
69
81
  }
70
- }
71
- function repeat(callback, options) {
72
- return timer("repeat", callback, options ?? {}, true);
73
- }
74
- function timer(type, callback, partial, start) {
75
- const isRepeated = type === "repeat";
76
- const options = getOptions(partial, isRepeated);
77
- const instance = new Timer(
78
- type,
79
- {
80
- callback,
81
- isRepeated,
82
- active: false,
83
- destroyed: false,
84
- minimum: options.interval - options.interval % milliseconds / 2,
85
- paused: false,
86
- trace: new TimerTrace().stack
87
- },
88
- options
89
- );
90
- if (start) {
91
- instance.start();
82
+ #work(type) {
83
+ return this.worker(
84
+ type,
85
+ {
86
+ instance: this,
87
+ type: this.$timer
88
+ },
89
+ this.state,
90
+ this.options
91
+ );
92
92
  }
93
- return instance;
94
- }
95
- function wait(callback, options) {
96
- return timer(
97
- "wait",
98
- callback,
99
- options == null || typeof options === "number" ? {
100
- interval: options
101
- } : options,
102
- true
103
- );
104
93
  }
105
- export {
106
- BasicTimer,
107
- Timer,
108
- repeat,
109
- timer,
110
- wait
111
- };
94
+
95
+ export { Timer };
package/dist/wait.cjs ADDED
@@ -0,0 +1,30 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const constants = require('./constants.cjs');
6
+ const get = require('./get.cjs');
7
+ const models = require('./models.cjs');
8
+ const timer = require('./timer.cjs');
9
+ const work = require('./work.cjs');
10
+
11
+ function wait(callback, time) {
12
+ return new timer.Timer(
13
+ "wait",
14
+ work.work,
15
+ {
16
+ callback: get.getCallback(callback),
17
+ trace: new models.TimerTrace().stack
18
+ },
19
+ {
20
+ onAfter: void 0,
21
+ onError: void 0,
22
+ count: -1,
23
+ interval: get.getValidNumber(time, constants.milliseconds),
24
+ timeout: 0
25
+ },
26
+ true
27
+ );
28
+ }
29
+
30
+ exports.wait = wait;
package/dist/wait.js ADDED
@@ -0,0 +1,26 @@
1
+ import { milliseconds } from './constants.js';
2
+ import { getCallback, getValidNumber } from './get.js';
3
+ import { TimerTrace } from './models.js';
4
+ import { Timer } from './timer.js';
5
+ import { work } from './work.js';
6
+
7
+ function wait(callback, time) {
8
+ return new Timer(
9
+ "wait",
10
+ work,
11
+ {
12
+ callback: getCallback(callback),
13
+ trace: new TimerTrace().stack
14
+ },
15
+ {
16
+ onAfter: void 0,
17
+ onError: void 0,
18
+ count: -1,
19
+ interval: getValidNumber(time, milliseconds),
20
+ timeout: 0
21
+ },
22
+ true
23
+ );
24
+ }
25
+
26
+ export { wait };