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