@oscarpalmer/timer 0.20.0 → 0.21.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.
- package/dist/timer.js +257 -76
- package/dist/timer.mjs +40 -0
- package/package.json +29 -20
- package/src/constants.ts +16 -0
- package/src/functions.ts +151 -0
- package/src/global.ts +14 -0
- package/src/index.ts +28 -220
- package/src/is.ts +35 -0
- package/src/models.ts +76 -0
- package/src/timer.ts +186 -0
- package/src/when.ts +111 -0
- package/types/constants.d.ts +13 -0
- package/types/functions.d.ts +5 -0
- package/types/global.d.ts +5 -0
- package/types/index.d.cts +184 -0
- package/types/index.d.ts +6 -28
- package/types/is.d.ts +18 -0
- package/types/models.d.ts +62 -0
- package/types/timer.d.ts +72 -0
- package/types/when.d.ts +28 -0
package/dist/timer.js
CHANGED
|
@@ -1,111 +1,292 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
// node_modules/@oscarpalmer/atoms/dist/js/function.mjs
|
|
2
|
+
function noop() {
|
|
3
|
+
}
|
|
4
|
+
|
|
5
|
+
// src/constants.ts
|
|
6
|
+
var activeTimers = new Set;
|
|
7
|
+
var hiddenTimers = new Set;
|
|
8
|
+
var milliseconds = 1000 / 60;
|
|
9
|
+
|
|
10
|
+
// src/global.ts
|
|
11
|
+
if (globalThis._oscarpalmer_timers == null) {
|
|
12
|
+
Object.defineProperty(globalThis, "_oscarpalmer_timers", {
|
|
13
|
+
get() {
|
|
14
|
+
return globalThis._oscarpalmer_timer_debug ? [...activeTimers] : [];
|
|
15
|
+
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// src/functions.ts
|
|
20
|
+
function getOptions(options, isRepeated) {
|
|
21
|
+
return {
|
|
22
|
+
afterCallback: options.afterCallback,
|
|
23
|
+
count: getValueOrDefault(options.count, isRepeated ? Number.POSITIVE_INFINITY : 1),
|
|
24
|
+
errorCallback: options.errorCallback,
|
|
25
|
+
interval: getValueOrDefault(options.interval, milliseconds, milliseconds),
|
|
26
|
+
timeout: getValueOrDefault(options.timeout, isRepeated ? Number.POSITIVE_INFINITY : 30000)
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
function getValueOrDefault(value, defaultValue, minimum) {
|
|
30
|
+
return typeof value === "number" && value > (minimum ?? 0) ? value : defaultValue;
|
|
31
|
+
}
|
|
32
|
+
function work(type, timer, state, options) {
|
|
33
|
+
if (["continue", "start"].includes(type) && state.active || ["pause", "stop"].includes(type) && !state.active) {
|
|
34
|
+
return timer;
|
|
35
|
+
}
|
|
36
|
+
const { count, interval, timeout } = options;
|
|
37
|
+
const { isRepeated, minimum } = state;
|
|
38
|
+
if (["pause", "restart", "stop"].includes(type)) {
|
|
39
|
+
const isStop = type === "stop";
|
|
40
|
+
activeTimers.delete(timer);
|
|
41
|
+
cancelAnimationFrame(state.frame);
|
|
42
|
+
if (isStop) {
|
|
43
|
+
options.afterCallback?.(false);
|
|
44
|
+
}
|
|
45
|
+
state.active = false;
|
|
46
|
+
state.frame = undefined;
|
|
47
|
+
state.paused = !isStop;
|
|
48
|
+
if (isStop) {
|
|
49
|
+
state.elapsed = undefined;
|
|
50
|
+
state.index = undefined;
|
|
51
|
+
}
|
|
52
|
+
return type === "restart" ? work("start", timer, state, options) : timer;
|
|
53
|
+
}
|
|
54
|
+
state.active = true;
|
|
55
|
+
state.paused = false;
|
|
56
|
+
const elapsed = type === "continue" ? +(state.elapsed ?? 0) : 0;
|
|
57
|
+
let index = type === "continue" ? +(state.index ?? 0) : 0;
|
|
58
|
+
state.elapsed = elapsed;
|
|
59
|
+
state.index = index;
|
|
60
|
+
const total = (count === Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY : (count - index) * (interval > 0 ? interval : milliseconds)) - elapsed;
|
|
61
|
+
let current;
|
|
8
62
|
let start;
|
|
63
|
+
function finish(finished, error) {
|
|
64
|
+
activeTimers.delete(timer);
|
|
65
|
+
state.active = false;
|
|
66
|
+
state.elapsed = undefined;
|
|
67
|
+
state.frame = undefined;
|
|
68
|
+
state.index = undefined;
|
|
69
|
+
if (error) {
|
|
70
|
+
options.errorCallback?.();
|
|
71
|
+
}
|
|
72
|
+
options.afterCallback?.(finished);
|
|
73
|
+
}
|
|
9
74
|
function step(timestamp) {
|
|
10
|
-
if (!
|
|
75
|
+
if (!state.active) {
|
|
11
76
|
return;
|
|
12
77
|
}
|
|
78
|
+
current ??= timestamp;
|
|
13
79
|
start ??= timestamp;
|
|
14
|
-
const
|
|
15
|
-
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
80
|
+
const time = timestamp - current;
|
|
81
|
+
state.elapsed = elapsed + (current - start);
|
|
82
|
+
const finished = time - elapsed >= total;
|
|
83
|
+
if (timestamp - start >= timeout - elapsed) {
|
|
84
|
+
finish(finished, !finished);
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
if (finished || time >= minimum) {
|
|
88
|
+
if (state.active) {
|
|
89
|
+
state.callback(isRepeated ? index : undefined);
|
|
20
90
|
}
|
|
21
91
|
index += 1;
|
|
22
|
-
|
|
23
|
-
|
|
92
|
+
state.index = index;
|
|
93
|
+
if (!finished && index < count) {
|
|
94
|
+
current = null;
|
|
24
95
|
} else {
|
|
25
|
-
|
|
26
|
-
timer.stop();
|
|
96
|
+
finish(true, false);
|
|
27
97
|
return;
|
|
28
98
|
}
|
|
29
99
|
}
|
|
30
|
-
|
|
100
|
+
state.frame = requestAnimationFrame(step);
|
|
31
101
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
102
|
+
activeTimers.add(timer);
|
|
103
|
+
state.frame = requestAnimationFrame(step);
|
|
104
|
+
return timer;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
// src/timer.ts
|
|
108
|
+
function repeat(callback, options) {
|
|
109
|
+
return timer("repeat", callback, options ?? {}, true);
|
|
110
|
+
}
|
|
111
|
+
function timer(type, callback, partial, start) {
|
|
112
|
+
const isRepeated = type === "repeat";
|
|
113
|
+
const options = getOptions(partial, isRepeated);
|
|
114
|
+
const instance = new Timer(type, {
|
|
115
|
+
callback,
|
|
116
|
+
isRepeated,
|
|
117
|
+
active: false,
|
|
118
|
+
minimum: options.interval - options.interval % milliseconds / 2,
|
|
119
|
+
paused: false,
|
|
120
|
+
trace: new TimerTrace
|
|
121
|
+
}, options);
|
|
122
|
+
if (start) {
|
|
123
|
+
instance.start();
|
|
37
124
|
}
|
|
38
|
-
|
|
39
|
-
return new Timer(callback, afterOrTimeIsFunction ? 0 : afterOrTime, count, afterOrTimeIsFunction ? afterOrTime : after).start();
|
|
125
|
+
return instance;
|
|
40
126
|
}
|
|
41
|
-
function wait(callback,
|
|
42
|
-
return
|
|
127
|
+
function wait(callback, options) {
|
|
128
|
+
return timer("wait", callback, options == null || typeof options === "number" ? {
|
|
129
|
+
interval: options
|
|
130
|
+
} : options, true);
|
|
43
131
|
}
|
|
44
|
-
var milliseconds = Math.round(16.666666666666668);
|
|
45
132
|
|
|
46
|
-
class
|
|
133
|
+
class BasicTimer {
|
|
134
|
+
constructor(type, state) {
|
|
135
|
+
this.$timer = type;
|
|
136
|
+
this.state = state;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
class Timer extends BasicTimer {
|
|
47
141
|
get active() {
|
|
48
|
-
return this.
|
|
142
|
+
return this.state.active;
|
|
49
143
|
}
|
|
50
|
-
get
|
|
51
|
-
return this.
|
|
144
|
+
get paused() {
|
|
145
|
+
return this.state.paused;
|
|
52
146
|
}
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
if (actualCount > 1 && afterCallback !== undefined && typeof afterCallback !== "function") {
|
|
66
|
-
throw new TypeError("A repeated timer's after-callback must be a function");
|
|
67
|
-
}
|
|
68
|
-
Object.defineProperty(this, "_configuration", {
|
|
69
|
-
value: {
|
|
70
|
-
callbacks: {
|
|
71
|
-
after: afterCallback,
|
|
72
|
-
default: callback
|
|
73
|
-
},
|
|
74
|
-
count: actualCount,
|
|
75
|
-
time: actualTime
|
|
76
|
-
}
|
|
77
|
-
});
|
|
78
|
-
Object.defineProperty(this, "_state", {
|
|
79
|
-
value: {
|
|
80
|
-
active: false,
|
|
81
|
-
finished: false
|
|
82
|
-
}
|
|
83
|
-
});
|
|
147
|
+
get trace() {
|
|
148
|
+
return globalThis._oscarpalmer_timer_debug ? this.state.trace : undefined;
|
|
149
|
+
}
|
|
150
|
+
constructor(type, state, options) {
|
|
151
|
+
super(type, state);
|
|
152
|
+
this.options = options;
|
|
153
|
+
}
|
|
154
|
+
continue() {
|
|
155
|
+
return work("continue", this, this.state, this.options);
|
|
156
|
+
}
|
|
157
|
+
pause() {
|
|
158
|
+
return work("pause", this, this.state, this.options);
|
|
84
159
|
}
|
|
85
160
|
restart() {
|
|
86
|
-
this.
|
|
87
|
-
run(this);
|
|
88
|
-
return this;
|
|
161
|
+
return work("restart", this, this.state, this.options);
|
|
89
162
|
}
|
|
90
163
|
start() {
|
|
91
|
-
|
|
92
|
-
|
|
164
|
+
return work("start", this, this.state, this.options);
|
|
165
|
+
}
|
|
166
|
+
stop() {
|
|
167
|
+
return work("stop", this, this.state, this.options);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
class TimerTrace extends Error {
|
|
172
|
+
constructor() {
|
|
173
|
+
super();
|
|
174
|
+
this.name = "TimerTrace";
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// src/index.ts
|
|
179
|
+
function delay(time, timeout) {
|
|
180
|
+
return new Promise((resolve, reject) => {
|
|
181
|
+
wait(resolve ?? noop, {
|
|
182
|
+
timeout,
|
|
183
|
+
errorCallback: reject ?? noop,
|
|
184
|
+
interval: time
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// src/is.ts
|
|
190
|
+
function is(pattern, value) {
|
|
191
|
+
return pattern.test(value?.$timer);
|
|
192
|
+
}
|
|
193
|
+
function isRepeated(value) {
|
|
194
|
+
return is(/^repeat$/, value);
|
|
195
|
+
}
|
|
196
|
+
function isTimer(value) {
|
|
197
|
+
return is(/^repeat|wait$/, value);
|
|
198
|
+
}
|
|
199
|
+
function isWaited(value) {
|
|
200
|
+
return is(/^wait$/, value);
|
|
201
|
+
}
|
|
202
|
+
function isWhen(value) {
|
|
203
|
+
return is(/^when$/, value) && typeof value.then === "function";
|
|
204
|
+
}
|
|
205
|
+
// src/when.ts
|
|
206
|
+
function when(condition, options) {
|
|
207
|
+
const repeated = timer("repeat", () => {
|
|
208
|
+
if (condition()) {
|
|
209
|
+
repeated.stop();
|
|
210
|
+
state.resolver?.();
|
|
93
211
|
}
|
|
212
|
+
}, {
|
|
213
|
+
afterCallback() {
|
|
214
|
+
if (!repeated.paused) {
|
|
215
|
+
if (condition()) {
|
|
216
|
+
state.resolver?.();
|
|
217
|
+
} else {
|
|
218
|
+
state.rejecter?.();
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
},
|
|
222
|
+
errorCallback() {
|
|
223
|
+
state.rejecter?.();
|
|
224
|
+
},
|
|
225
|
+
count: options?.count,
|
|
226
|
+
interval: options?.interval,
|
|
227
|
+
timeout: options?.timeout
|
|
228
|
+
}, false);
|
|
229
|
+
const state = {};
|
|
230
|
+
state.promise = new Promise((resolve, reject) => {
|
|
231
|
+
state.resolver = resolve;
|
|
232
|
+
state.rejecter = reject;
|
|
233
|
+
});
|
|
234
|
+
state.timer = repeated;
|
|
235
|
+
return new When(state);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
class When extends BasicTimer {
|
|
239
|
+
get active() {
|
|
240
|
+
return this.state.timer.active;
|
|
241
|
+
}
|
|
242
|
+
get paused() {
|
|
243
|
+
return this.state.timer.paused;
|
|
244
|
+
}
|
|
245
|
+
constructor(state) {
|
|
246
|
+
super("when", state);
|
|
247
|
+
}
|
|
248
|
+
continue() {
|
|
249
|
+
this.state.timer.continue();
|
|
250
|
+
return this;
|
|
251
|
+
}
|
|
252
|
+
pause() {
|
|
253
|
+
this.state.timer.pause();
|
|
94
254
|
return this;
|
|
95
255
|
}
|
|
96
256
|
stop() {
|
|
97
|
-
this.
|
|
98
|
-
|
|
99
|
-
|
|
257
|
+
if (this.state.timer.active) {
|
|
258
|
+
this.state.timer.stop();
|
|
259
|
+
this.state.rejecter?.();
|
|
100
260
|
}
|
|
101
|
-
cancelAnimationFrame(this._state.frame);
|
|
102
|
-
this._configuration.callbacks.after?.(this._state.finished);
|
|
103
|
-
this._state.frame = undefined;
|
|
104
261
|
return this;
|
|
105
262
|
}
|
|
263
|
+
then(resolve, reject) {
|
|
264
|
+
this.state.timer.start();
|
|
265
|
+
return this.state.promise.then(resolve ?? noop, reject ?? noop);
|
|
266
|
+
}
|
|
106
267
|
}
|
|
268
|
+
|
|
269
|
+
// src/index.ts
|
|
270
|
+
document.addEventListener("visibilitychange", () => {
|
|
271
|
+
if (document.hidden) {
|
|
272
|
+
for (const timer4 of activeTimers) {
|
|
273
|
+
hiddenTimers.add(timer4);
|
|
274
|
+
timer4.pause();
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
for (const timer4 of hiddenTimers) {
|
|
278
|
+
timer4.continue();
|
|
279
|
+
}
|
|
280
|
+
hiddenTimers.clear();
|
|
281
|
+
}
|
|
282
|
+
});
|
|
107
283
|
export {
|
|
284
|
+
when,
|
|
108
285
|
wait,
|
|
109
286
|
repeat,
|
|
110
|
-
|
|
287
|
+
isWhen,
|
|
288
|
+
isWaited,
|
|
289
|
+
isTimer,
|
|
290
|
+
isRepeated,
|
|
291
|
+
delay
|
|
111
292
|
};
|
package/dist/timer.mjs
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
// src/index.ts
|
|
2
|
+
import {noop} from "@oscarpalmer/atoms/function";
|
|
3
|
+
import {activeTimers, hiddenTimers} from "./constants";
|
|
4
|
+
import"./global";
|
|
5
|
+
import {wait} from "./timer";
|
|
6
|
+
function delay(time, timeout) {
|
|
7
|
+
return new Promise((resolve, reject) => {
|
|
8
|
+
wait(resolve ?? noop, {
|
|
9
|
+
timeout,
|
|
10
|
+
errorCallback: reject ?? noop,
|
|
11
|
+
interval: time
|
|
12
|
+
});
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
import {isRepeated, isTimer, isWaited, isWhen} from "./is";
|
|
16
|
+
import {repeat, wait as wait2} from "./timer";
|
|
17
|
+
import {when} from "./when";
|
|
18
|
+
document.addEventListener("visibilitychange", () => {
|
|
19
|
+
if (document.hidden) {
|
|
20
|
+
for (const timer2 of activeTimers) {
|
|
21
|
+
hiddenTimers.add(timer2);
|
|
22
|
+
timer2.pause();
|
|
23
|
+
}
|
|
24
|
+
} else {
|
|
25
|
+
for (const timer2 of hiddenTimers) {
|
|
26
|
+
timer2.continue();
|
|
27
|
+
}
|
|
28
|
+
hiddenTimers.clear();
|
|
29
|
+
}
|
|
30
|
+
});
|
|
31
|
+
export {
|
|
32
|
+
when,
|
|
33
|
+
wait2 as wait,
|
|
34
|
+
repeat,
|
|
35
|
+
isWhen,
|
|
36
|
+
isWaited,
|
|
37
|
+
isTimer,
|
|
38
|
+
isRepeated,
|
|
39
|
+
delay
|
|
40
|
+
};
|
package/package.json
CHANGED
|
@@ -3,39 +3,48 @@
|
|
|
3
3
|
"name": "Oscar Palmér",
|
|
4
4
|
"url": "https://oscarpalmer.se"
|
|
5
5
|
},
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"@oscarpalmer/atoms": "0.69.0"
|
|
8
|
+
},
|
|
6
9
|
"description": "A better solution for timeout- and interval-based timers.",
|
|
7
10
|
"devDependencies": {
|
|
8
|
-
"@biomejs/biome": "^1.
|
|
9
|
-
"@happy-dom/global-registrator": "^12.
|
|
10
|
-
"bun": "^1.
|
|
11
|
-
"
|
|
11
|
+
"@biomejs/biome": "^1.8.3",
|
|
12
|
+
"@happy-dom/global-registrator": "^14.12.3",
|
|
13
|
+
"@types/bun": "^1.1.6",
|
|
14
|
+
"bun": "^1.1.21",
|
|
15
|
+
"dts-bundle-generator": "9.5.1",
|
|
16
|
+
"typescript": "^5.5.4"
|
|
17
|
+
},
|
|
18
|
+
"exports": {
|
|
19
|
+
".": {
|
|
20
|
+
"types": "./types/index.d.cts",
|
|
21
|
+
"bun": "./src/index.ts",
|
|
22
|
+
"import": "./dist/timer.mjs",
|
|
23
|
+
"require": "./dist/timer.js"
|
|
24
|
+
}
|
|
12
25
|
},
|
|
13
26
|
"files": ["dist", "src", "types"],
|
|
14
27
|
"keywords": ["timer", "setTimeout", "setInterval", "requestAnimationFrame"],
|
|
15
28
|
"license": "MIT",
|
|
16
29
|
"main": "dist/timer.js",
|
|
17
|
-
"module": "dist/timer.
|
|
30
|
+
"module": "dist/timer.mjs",
|
|
18
31
|
"name": "@oscarpalmer/timer",
|
|
19
|
-
"prettier": {
|
|
20
|
-
"arrowParens": "avoid",
|
|
21
|
-
"bracketSpacing": false,
|
|
22
|
-
"singleQuote": true,
|
|
23
|
-
"switchIndent": true,
|
|
24
|
-
"trailingComma": "all",
|
|
25
|
-
"useTabs": true
|
|
26
|
-
},
|
|
27
32
|
"repository": {
|
|
28
33
|
"type": "git",
|
|
29
34
|
"url": "git+https://github.com/oscarpalmer/timer.git"
|
|
30
35
|
},
|
|
31
36
|
"scripts": {
|
|
32
|
-
"build": "bun build
|
|
33
|
-
"
|
|
34
|
-
"
|
|
35
|
-
"
|
|
36
|
-
"
|
|
37
|
+
"build": "bun run clean && bun run build:cjs && bun run build:esm && bun run types",
|
|
38
|
+
"build:cjs": "bun build ./src/index.ts --outfile ./dist/timer.js",
|
|
39
|
+
"build:esm": "bun build ./src/index.ts --external '*' --outfile ./dist/timer.mjs",
|
|
40
|
+
"clean": "rm -rf ./dist && rm -rf ./types && rm -f ./tsconfig.tsbuildinfo",
|
|
41
|
+
"test": "bun test",
|
|
42
|
+
"types": "bun run types:cjs && bun run types:esm",
|
|
43
|
+
"types:cjs": "bunx dts-bundle-generator --out-file ./types/index.d.cts --external-inlines '@oscarpalmer/atoms' --no-check --silent ./src/index.ts",
|
|
44
|
+
"types:esm": "bunx tsc -p ./tsconfig.json",
|
|
45
|
+
"watch": "bun build ./src/index.ts --outfile ./dist/timer.js --watch"
|
|
37
46
|
},
|
|
38
47
|
"type": "module",
|
|
39
|
-
"types": "
|
|
40
|
-
"version": "0.
|
|
48
|
+
"types": "types/index.d.cts",
|
|
49
|
+
"version": "0.21.0"
|
|
41
50
|
}
|
package/src/constants.ts
ADDED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {Timer} from './timer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* A set of all active timers
|
|
5
|
+
*/
|
|
6
|
+
export const activeTimers = new Set<Timer>();
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* A set of timers that were paused due to the document being hidden
|
|
10
|
+
*/
|
|
11
|
+
export const hiddenTimers = new Set<Timer>();
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Milliseconds in a frame, probably ;-)
|
|
15
|
+
*/
|
|
16
|
+
export const milliseconds = 1_000 / 60;
|
package/src/functions.ts
ADDED
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
import {activeTimers, milliseconds} from './constants';
|
|
2
|
+
import type {TimerOptions, TimerState, WorkType} from './models';
|
|
3
|
+
import type {Timer} from './timer';
|
|
4
|
+
|
|
5
|
+
export function getOptions(
|
|
6
|
+
options: Partial<TimerOptions>,
|
|
7
|
+
isRepeated: boolean,
|
|
8
|
+
): TimerOptions {
|
|
9
|
+
return {
|
|
10
|
+
afterCallback: options.afterCallback,
|
|
11
|
+
count: getValueOrDefault(
|
|
12
|
+
options.count,
|
|
13
|
+
isRepeated ? Number.POSITIVE_INFINITY : 1,
|
|
14
|
+
),
|
|
15
|
+
errorCallback: options.errorCallback,
|
|
16
|
+
interval: getValueOrDefault(options.interval, milliseconds, milliseconds),
|
|
17
|
+
timeout: getValueOrDefault(
|
|
18
|
+
options.timeout,
|
|
19
|
+
isRepeated ? Number.POSITIVE_INFINITY : 30_000,
|
|
20
|
+
),
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function getValueOrDefault(
|
|
25
|
+
value: unknown,
|
|
26
|
+
defaultValue: number,
|
|
27
|
+
minimum?: number,
|
|
28
|
+
): number {
|
|
29
|
+
return typeof value === 'number' && value > (minimum ?? 0)
|
|
30
|
+
? value
|
|
31
|
+
: defaultValue;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function work(
|
|
35
|
+
type: WorkType,
|
|
36
|
+
timer: Timer,
|
|
37
|
+
state: TimerState,
|
|
38
|
+
options: TimerOptions,
|
|
39
|
+
): Timer {
|
|
40
|
+
if (
|
|
41
|
+
(['continue', 'start'].includes(type) && state.active) ||
|
|
42
|
+
(['pause', 'stop'].includes(type) && !state.active)
|
|
43
|
+
) {
|
|
44
|
+
return timer;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const {count, interval, timeout} = options;
|
|
48
|
+
const {isRepeated, minimum} = state;
|
|
49
|
+
|
|
50
|
+
if (['pause', 'restart', 'stop'].includes(type)) {
|
|
51
|
+
const isStop = type === 'stop';
|
|
52
|
+
|
|
53
|
+
activeTimers.delete(timer);
|
|
54
|
+
|
|
55
|
+
cancelAnimationFrame(state.frame as never);
|
|
56
|
+
|
|
57
|
+
if (isStop) {
|
|
58
|
+
options.afterCallback?.(false);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
state.active = false;
|
|
62
|
+
state.frame = undefined;
|
|
63
|
+
state.paused = !isStop;
|
|
64
|
+
|
|
65
|
+
if (isStop) {
|
|
66
|
+
state.elapsed = undefined;
|
|
67
|
+
state.index = undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return type === 'restart' ? work('start', timer, state, options) : timer;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
state.active = true;
|
|
74
|
+
state.paused = false;
|
|
75
|
+
|
|
76
|
+
const elapsed = type === 'continue' ? +(state.elapsed ?? 0) : 0;
|
|
77
|
+
|
|
78
|
+
let index = type === 'continue' ? +(state.index ?? 0) : 0;
|
|
79
|
+
|
|
80
|
+
state.elapsed = elapsed;
|
|
81
|
+
state.index = index;
|
|
82
|
+
|
|
83
|
+
const total =
|
|
84
|
+
(count === Number.POSITIVE_INFINITY
|
|
85
|
+
? Number.POSITIVE_INFINITY
|
|
86
|
+
: (count - index) * (interval > 0 ? interval : milliseconds)) - elapsed;
|
|
87
|
+
|
|
88
|
+
let current: DOMHighResTimeStamp | null;
|
|
89
|
+
let start: DOMHighResTimeStamp | null;
|
|
90
|
+
|
|
91
|
+
function finish(finished: boolean, error: boolean) {
|
|
92
|
+
activeTimers.delete(timer);
|
|
93
|
+
|
|
94
|
+
state.active = false;
|
|
95
|
+
state.elapsed = undefined;
|
|
96
|
+
state.frame = undefined;
|
|
97
|
+
state.index = undefined;
|
|
98
|
+
|
|
99
|
+
if (error) {
|
|
100
|
+
options.errorCallback?.();
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
options.afterCallback?.(finished);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function step(timestamp: DOMHighResTimeStamp): void {
|
|
107
|
+
if (!state.active) {
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
current ??= timestamp;
|
|
112
|
+
start ??= timestamp;
|
|
113
|
+
|
|
114
|
+
const time = timestamp - current;
|
|
115
|
+
|
|
116
|
+
state.elapsed = elapsed + (current - start);
|
|
117
|
+
|
|
118
|
+
const finished = time - elapsed >= total;
|
|
119
|
+
|
|
120
|
+
if (timestamp - start >= timeout - elapsed) {
|
|
121
|
+
finish(finished, !finished);
|
|
122
|
+
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (finished || time >= minimum) {
|
|
127
|
+
if (state.active) {
|
|
128
|
+
state.callback((isRepeated ? index : undefined) as never);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
index += 1;
|
|
132
|
+
|
|
133
|
+
state.index = index;
|
|
134
|
+
|
|
135
|
+
if (!finished && index < count) {
|
|
136
|
+
current = null;
|
|
137
|
+
} else {
|
|
138
|
+
finish(true, false);
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
state.frame = requestAnimationFrame(step);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
activeTimers.add(timer);
|
|
147
|
+
|
|
148
|
+
state.frame = requestAnimationFrame(step);
|
|
149
|
+
|
|
150
|
+
return timer;
|
|
151
|
+
}
|
package/src/global.ts
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {activeTimers} from './constants';
|
|
2
|
+
|
|
3
|
+
declare global {
|
|
4
|
+
var _oscarpalmer_timer_debug: boolean | undefined;
|
|
5
|
+
var _oscarpalmer_timers: Timer[] | undefined;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
if (globalThis._oscarpalmer_timers == null) {
|
|
9
|
+
Object.defineProperty(globalThis, '_oscarpalmer_timers', {
|
|
10
|
+
get() {
|
|
11
|
+
return globalThis._oscarpalmer_timer_debug ? [...activeTimers] : [];
|
|
12
|
+
},
|
|
13
|
+
});
|
|
14
|
+
}
|