@oscarpalmer/timer 0.16.0 → 0.18.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/README.md +1 -1
- package/dist/timer.iife.js +169 -1
- package/dist/timer.js +164 -122
- package/package.json +37 -23
- package/src/index.ts +239 -0
- package/types/index.d.ts +72 -0
- package/dist/timer.cjs +0 -148
- package/src/index.d.ts +0 -31
- package/src/index.js +0 -209
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# Timer
|
|
2
2
|
|
|
3
|
-
[](https://www.npmjs.com/package/@oscarpalmer/timer) [](https://github.com/oscarpalmer/timer/actions/workflows/test.yml)
|
|
4
4
|
|
|
5
5
|
A better solution for timeout- and interval-based timers.
|
|
6
6
|
|
package/dist/timer.iife.js
CHANGED
|
@@ -1 +1,169 @@
|
|
|
1
|
-
var Timer
|
|
1
|
+
var Timer = (function (exports) {
|
|
2
|
+
('use strict');
|
|
3
|
+
|
|
4
|
+
const callbacks = new WeakMap();
|
|
5
|
+
const configuration = new WeakMap();
|
|
6
|
+
const state = new WeakMap();
|
|
7
|
+
const milliseconds = Math.round(1000 / 60);
|
|
8
|
+
const request =
|
|
9
|
+
requestAnimationFrame ??
|
|
10
|
+
function (callback) {
|
|
11
|
+
return setTimeout?.(() => {
|
|
12
|
+
callback(Date.now());
|
|
13
|
+
}, milliseconds);
|
|
14
|
+
};
|
|
15
|
+
function run(timed) {
|
|
16
|
+
const timedConfiguration = configuration.get(timed);
|
|
17
|
+
const timedCallbacks = callbacks.get(timed);
|
|
18
|
+
const timedState = state.get(timed);
|
|
19
|
+
timedState.active = true;
|
|
20
|
+
timedState.finished = false;
|
|
21
|
+
const isRepeated = timed instanceof Repeated;
|
|
22
|
+
let index = 0;
|
|
23
|
+
let start;
|
|
24
|
+
function step(timestamp) {
|
|
25
|
+
if (!timedState.active) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
start ?? (start = timestamp);
|
|
29
|
+
const elapsed = timestamp - start;
|
|
30
|
+
const elapsedMinimum = elapsed - milliseconds;
|
|
31
|
+
const elapsedMaximum = elapsed + milliseconds;
|
|
32
|
+
if (
|
|
33
|
+
elapsedMinimum < timedConfiguration.time &&
|
|
34
|
+
timedConfiguration.time < elapsedMaximum
|
|
35
|
+
) {
|
|
36
|
+
if (timedState.active) {
|
|
37
|
+
timedCallbacks.default(isRepeated ? index : undefined);
|
|
38
|
+
}
|
|
39
|
+
index += 1;
|
|
40
|
+
if (isRepeated && index < timedConfiguration.count) {
|
|
41
|
+
start = undefined;
|
|
42
|
+
} else {
|
|
43
|
+
timedState.finished = true;
|
|
44
|
+
timed.stop();
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
timedState.frame = request(step);
|
|
49
|
+
}
|
|
50
|
+
timedState.frame = request(step);
|
|
51
|
+
}
|
|
52
|
+
class Timed {
|
|
53
|
+
get active() {
|
|
54
|
+
return state.get(this)?.active ?? false;
|
|
55
|
+
}
|
|
56
|
+
get finished() {
|
|
57
|
+
return !this.active && (state.get(this)?.finished ?? false);
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* @param {Callback} callback
|
|
61
|
+
* @param {number} time
|
|
62
|
+
* @param {number} count
|
|
63
|
+
* @param {AfterCallback=} afterCallback
|
|
64
|
+
*/
|
|
65
|
+
constructor(callback, time, count, afterCallback) {
|
|
66
|
+
const isRepeated = this instanceof Repeated;
|
|
67
|
+
const type = isRepeated ? 'repeated' : 'waited';
|
|
68
|
+
if (typeof callback !== 'function') {
|
|
69
|
+
throw new TypeError(`A ${type} timer must have a callback function`);
|
|
70
|
+
}
|
|
71
|
+
if (typeof time !== 'number' || time < 0) {
|
|
72
|
+
throw new TypeError(
|
|
73
|
+
`A ${type} timer must have a non-negative number as its time`,
|
|
74
|
+
);
|
|
75
|
+
}
|
|
76
|
+
if (isRepeated && (typeof count !== 'number' || count < 2)) {
|
|
77
|
+
throw new TypeError(
|
|
78
|
+
'A repeated timer must have a number above 1 as its repeat count',
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (
|
|
82
|
+
isRepeated &&
|
|
83
|
+
afterCallback !== undefined &&
|
|
84
|
+
typeof afterCallback !== 'function'
|
|
85
|
+
) {
|
|
86
|
+
throw new TypeError(
|
|
87
|
+
"A repeated timer's after-callback must be a function",
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
callbacks.set(this, {
|
|
91
|
+
after: afterCallback,
|
|
92
|
+
default: callback,
|
|
93
|
+
});
|
|
94
|
+
configuration.set(this, {count, time});
|
|
95
|
+
state.set(this, {
|
|
96
|
+
active: false,
|
|
97
|
+
finished: false,
|
|
98
|
+
frame: undefined,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
restart() {
|
|
102
|
+
this.stop();
|
|
103
|
+
run(this);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
start() {
|
|
107
|
+
if (!this.active) {
|
|
108
|
+
run(this);
|
|
109
|
+
}
|
|
110
|
+
return this;
|
|
111
|
+
}
|
|
112
|
+
stop() {
|
|
113
|
+
const timedCallbacks = callbacks.get(this);
|
|
114
|
+
const timedState = state.get(this);
|
|
115
|
+
timedState.active = false;
|
|
116
|
+
if (timedState.frame === undefined) {
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
(cancelAnimationFrame ?? clearTimeout)?.(timedState.frame);
|
|
120
|
+
timedCallbacks.after?.(this.finished);
|
|
121
|
+
timedState.frame = undefined;
|
|
122
|
+
return this;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* A timer that waits and runs repeatedly
|
|
127
|
+
*/
|
|
128
|
+
class Repeated extends Timed {}
|
|
129
|
+
/**
|
|
130
|
+
* A timer that waits and runs once
|
|
131
|
+
*/
|
|
132
|
+
class Waited extends Timed {
|
|
133
|
+
/**
|
|
134
|
+
* Creates a new waited timer
|
|
135
|
+
* @param {() => void} callback
|
|
136
|
+
* @param {number} time
|
|
137
|
+
*/
|
|
138
|
+
constructor(callback, time) {
|
|
139
|
+
super(callback, time, 1);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Creates and starts a new repeated timer
|
|
144
|
+
* @param {RepeatedCallback} callback
|
|
145
|
+
* @param {number} time
|
|
146
|
+
* @param {number} count
|
|
147
|
+
* @param {AfterCallback=} afterCallback
|
|
148
|
+
* @return {Repeated}
|
|
149
|
+
*/
|
|
150
|
+
function repeat(callback, time, count, afterCallback) {
|
|
151
|
+
return new Repeated(callback, time, count, afterCallback).start();
|
|
152
|
+
}
|
|
153
|
+
/**
|
|
154
|
+
* Creates and starts a new waited timer
|
|
155
|
+
* @param {() => void} callback
|
|
156
|
+
* @param {number} time
|
|
157
|
+
* @return {Waited}
|
|
158
|
+
*/
|
|
159
|
+
function wait(callback, time) {
|
|
160
|
+
return new Waited(callback, time).start();
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
exports.Repeated = Repeated;
|
|
164
|
+
exports.Waited = Waited;
|
|
165
|
+
exports.repeat = repeat;
|
|
166
|
+
exports.wait = wait;
|
|
167
|
+
|
|
168
|
+
return exports;
|
|
169
|
+
})({});
|
package/dist/timer.js
CHANGED
|
@@ -1,128 +1,170 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
/**
|
|
2
|
+
* @callback AfterCallback
|
|
3
|
+
* @param {boolean} finished Did the timer finish?
|
|
4
|
+
* @returns {void}
|
|
5
|
+
*/
|
|
6
|
+
/**
|
|
7
|
+
* @callback RepeatedCallback
|
|
8
|
+
* @param {number} index The index of the current iteration
|
|
9
|
+
* @returns {void}
|
|
10
|
+
*/
|
|
11
|
+
const callbacks = new WeakMap();
|
|
12
|
+
const configuration = new WeakMap();
|
|
13
|
+
const state = new WeakMap();
|
|
14
|
+
const milliseconds = Math.round(1000 / 60);
|
|
15
|
+
const request =
|
|
16
|
+
requestAnimationFrame ??
|
|
17
|
+
function (callback) {
|
|
18
|
+
return setTimeout?.(() => {
|
|
19
|
+
callback(Date.now());
|
|
20
|
+
}, milliseconds);
|
|
21
|
+
};
|
|
8
22
|
function run(timed) {
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
23
|
+
const timedConfiguration = configuration.get(timed);
|
|
24
|
+
const timedCallbacks = callbacks.get(timed);
|
|
25
|
+
const timedState = state.get(timed);
|
|
26
|
+
timedState.active = true;
|
|
27
|
+
timedState.finished = false;
|
|
28
|
+
const isRepeated = timed instanceof Repeated;
|
|
29
|
+
let index = 0;
|
|
30
|
+
let start;
|
|
31
|
+
function step(timestamp) {
|
|
32
|
+
if (!timedState.active) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
start ?? (start = timestamp);
|
|
36
|
+
const elapsed = timestamp - start;
|
|
37
|
+
const elapsedMinimum = elapsed - milliseconds;
|
|
38
|
+
const elapsedMaximum = elapsed + milliseconds;
|
|
39
|
+
if (
|
|
40
|
+
elapsedMinimum < timedConfiguration.time &&
|
|
41
|
+
timedConfiguration.time < elapsedMaximum
|
|
42
|
+
) {
|
|
43
|
+
if (timedState.active) {
|
|
44
|
+
timedCallbacks.default(isRepeated ? index : undefined);
|
|
45
|
+
}
|
|
46
|
+
index += 1;
|
|
47
|
+
if (isRepeated && index < timedConfiguration.count) {
|
|
48
|
+
start = undefined;
|
|
49
|
+
} else {
|
|
50
|
+
timedState.finished = true;
|
|
51
|
+
timed.stop();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
timedState.frame = request(step);
|
|
56
|
+
}
|
|
57
|
+
timedState.frame = request(step);
|
|
38
58
|
}
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
59
|
+
class Timed {
|
|
60
|
+
get active() {
|
|
61
|
+
return state.get(this)?.active ?? false;
|
|
62
|
+
}
|
|
63
|
+
get finished() {
|
|
64
|
+
return !this.active && (state.get(this)?.finished ?? false);
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* @param {Callback} callback
|
|
68
|
+
* @param {number} time
|
|
69
|
+
* @param {number} count
|
|
70
|
+
* @param {AfterCallback=} afterCallback
|
|
71
|
+
*/
|
|
72
|
+
constructor(callback, time, count, afterCallback) {
|
|
73
|
+
const isRepeated = this instanceof Repeated;
|
|
74
|
+
const type = isRepeated ? 'repeated' : 'waited';
|
|
75
|
+
if (typeof callback !== 'function') {
|
|
76
|
+
throw new TypeError(`A ${type} timer must have a callback function`);
|
|
77
|
+
}
|
|
78
|
+
if (typeof time !== 'number' || time < 0) {
|
|
79
|
+
throw new TypeError(
|
|
80
|
+
`A ${type} timer must have a non-negative number as its time`,
|
|
81
|
+
);
|
|
82
|
+
}
|
|
83
|
+
if (isRepeated && (typeof count !== 'number' || count < 2)) {
|
|
84
|
+
throw new TypeError(
|
|
85
|
+
'A repeated timer must have a number above 1 as its repeat count',
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
if (
|
|
89
|
+
isRepeated &&
|
|
90
|
+
afterCallback !== undefined &&
|
|
91
|
+
typeof afterCallback !== 'function'
|
|
92
|
+
) {
|
|
93
|
+
throw new TypeError(
|
|
94
|
+
"A repeated timer's after-callback must be a function",
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
callbacks.set(this, {
|
|
98
|
+
after: afterCallback,
|
|
99
|
+
default: callback,
|
|
100
|
+
});
|
|
101
|
+
configuration.set(this, {count, time});
|
|
102
|
+
state.set(this, {
|
|
103
|
+
active: false,
|
|
104
|
+
finished: false,
|
|
105
|
+
frame: undefined,
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
restart() {
|
|
109
|
+
this.stop();
|
|
110
|
+
run(this);
|
|
111
|
+
return this;
|
|
112
|
+
}
|
|
113
|
+
start() {
|
|
114
|
+
if (!this.active) {
|
|
115
|
+
run(this);
|
|
116
|
+
}
|
|
117
|
+
return this;
|
|
118
|
+
}
|
|
119
|
+
stop() {
|
|
120
|
+
const timedCallbacks = callbacks.get(this);
|
|
121
|
+
const timedState = state.get(this);
|
|
122
|
+
timedState.active = false;
|
|
123
|
+
if (timedState.frame === undefined) {
|
|
124
|
+
return this;
|
|
125
|
+
}
|
|
126
|
+
(cancelAnimationFrame ?? clearTimeout)?.(timedState.frame);
|
|
127
|
+
timedCallbacks.after?.(this.finished);
|
|
128
|
+
timedState.frame = undefined;
|
|
129
|
+
return this;
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
/**
|
|
133
|
+
* A timer that waits and runs repeatedly
|
|
134
|
+
*/
|
|
135
|
+
class Repeated extends Timed {}
|
|
136
|
+
/**
|
|
137
|
+
* A timer that waits and runs once
|
|
138
|
+
*/
|
|
139
|
+
class Waited extends Timed {
|
|
140
|
+
/**
|
|
141
|
+
* Creates a new waited timer
|
|
142
|
+
* @param {() => void} callback
|
|
143
|
+
* @param {number} time
|
|
144
|
+
*/
|
|
145
|
+
constructor(callback, time) {
|
|
146
|
+
super(callback, time, 1);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Creates and starts a new repeated timer
|
|
151
|
+
* @param {RepeatedCallback} callback
|
|
152
|
+
* @param {number} time
|
|
153
|
+
* @param {number} count
|
|
154
|
+
* @param {AfterCallback=} afterCallback
|
|
155
|
+
* @return {Repeated}
|
|
156
|
+
*/
|
|
117
157
|
function repeat(callback, time, count, afterCallback) {
|
|
118
|
-
|
|
158
|
+
return new Repeated(callback, time, count, afterCallback).start();
|
|
119
159
|
}
|
|
160
|
+
/**
|
|
161
|
+
* Creates and starts a new waited timer
|
|
162
|
+
* @param {() => void} callback
|
|
163
|
+
* @param {number} time
|
|
164
|
+
* @return {Waited}
|
|
165
|
+
*/
|
|
120
166
|
function wait(callback, time) {
|
|
121
|
-
|
|
167
|
+
return new Waited(callback, time).start();
|
|
122
168
|
}
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
Waited,
|
|
126
|
-
repeat,
|
|
127
|
-
wait
|
|
128
|
-
};
|
|
169
|
+
|
|
170
|
+
export {Repeated, Waited, repeat, wait};
|
package/package.json
CHANGED
|
@@ -6,23 +6,18 @@
|
|
|
6
6
|
"browser": "dist/timer.iife.js",
|
|
7
7
|
"description": "A better solution for timeout- and interval-based timers.",
|
|
8
8
|
"devDependencies": {
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
"
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
"module": "./dist/timer.js",
|
|
17
|
-
"import": "./dist/timer.js",
|
|
18
|
-
"require": "./dist/timer.cjs",
|
|
19
|
-
"default": "./dist/timer.js"
|
|
20
|
-
},
|
|
21
|
-
"./package.json": "./package.json"
|
|
9
|
+
"@happy-dom/global-registrator": "^11.0",
|
|
10
|
+
"@rollup/plugin-typescript": "^11.1",
|
|
11
|
+
"bun": "^1.0",
|
|
12
|
+
"prettier": "^3.0",
|
|
13
|
+
"rollup": "^3.29",
|
|
14
|
+
"typescript": "^5.2",
|
|
15
|
+
"xo": "^0.56"
|
|
22
16
|
},
|
|
23
17
|
"files": [
|
|
24
18
|
"dist",
|
|
25
|
-
"src"
|
|
19
|
+
"src",
|
|
20
|
+
"types"
|
|
26
21
|
],
|
|
27
22
|
"jsdelivr": "dist/timer.iife.js",
|
|
28
23
|
"keywords": [
|
|
@@ -32,23 +27,42 @@
|
|
|
32
27
|
"requestAnimationFrame"
|
|
33
28
|
],
|
|
34
29
|
"license": "MIT",
|
|
35
|
-
"main": "dist/timer.
|
|
30
|
+
"main": "dist/timer.iife.js",
|
|
36
31
|
"module": "dist/timer.js",
|
|
37
32
|
"name": "@oscarpalmer/timer",
|
|
33
|
+
"prettier": {
|
|
34
|
+
"arrowParens": "avoid",
|
|
35
|
+
"bracketSpacing": false,
|
|
36
|
+
"singleQuote": true,
|
|
37
|
+
"switchIndent": true,
|
|
38
|
+
"trailingComma": "all",
|
|
39
|
+
"useTabs": true
|
|
40
|
+
},
|
|
38
41
|
"repository": {
|
|
39
42
|
"type": "git",
|
|
40
43
|
"url": "git+https://github.com/oscarpalmer/timer.git"
|
|
41
44
|
},
|
|
42
45
|
"scripts": {
|
|
43
|
-
"build": "npm run build:
|
|
44
|
-
"build:
|
|
45
|
-
"build:
|
|
46
|
-
"
|
|
47
|
-
"types": "
|
|
48
|
-
"
|
|
46
|
+
"build": "npm run build:esm && npm run build:iife",
|
|
47
|
+
"build:esm": "rollup -c",
|
|
48
|
+
"build:iife": "rollup -c --environment ROLLUP_FORMAT:iife",
|
|
49
|
+
"test": "bun test --coverage",
|
|
50
|
+
"types": "tsc ./src/index.ts --outdir ./types --declaration --emitDeclarationOnly",
|
|
51
|
+
"watch": "rollup -c -w",
|
|
52
|
+
"xo": "xo ./src/*.ts --env browser"
|
|
49
53
|
},
|
|
50
54
|
"type": "module",
|
|
51
55
|
"types": "src/index.d.ts",
|
|
52
56
|
"unpkg": "dist/timer.iife.js",
|
|
53
|
-
"version": "0.
|
|
54
|
-
|
|
57
|
+
"version": "0.18.0",
|
|
58
|
+
"xo": {
|
|
59
|
+
"envs": [
|
|
60
|
+
"browser"
|
|
61
|
+
],
|
|
62
|
+
"prettier": true,
|
|
63
|
+
"rules": {
|
|
64
|
+
"import/extensions": "off",
|
|
65
|
+
"import/no-cycle": "off"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
type Callbacks = {
|
|
2
|
+
after: AfterCallback | undefined;
|
|
3
|
+
default: (index?: number) => void;
|
|
4
|
+
};
|
|
5
|
+
|
|
6
|
+
type Configuration = {
|
|
7
|
+
count: number;
|
|
8
|
+
time: number;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* @param {boolean} finished Did the timer finish?
|
|
13
|
+
*/
|
|
14
|
+
export type AfterCallback = (finished: boolean) => void;
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* @param {number} index The index of the current iteration
|
|
18
|
+
*/
|
|
19
|
+
export type RepeatedCallback = (index: number) => void;
|
|
20
|
+
|
|
21
|
+
type State = {
|
|
22
|
+
active: boolean;
|
|
23
|
+
finished: boolean;
|
|
24
|
+
frame?: DOMHighResTimeStamp;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
const callbacks = new WeakMap<Timed<never, never>, Callbacks>();
|
|
28
|
+
const configuration = new WeakMap<Timed<never, never>, Configuration>();
|
|
29
|
+
const state = new WeakMap<Timed<never, never>, State>();
|
|
30
|
+
|
|
31
|
+
const milliseconds = Math.round(1000 / 60);
|
|
32
|
+
|
|
33
|
+
const request =
|
|
34
|
+
globalThis.requestAnimationFrame ??
|
|
35
|
+
function (callback) {
|
|
36
|
+
return setTimeout?.(() => {
|
|
37
|
+
callback(Date.now());
|
|
38
|
+
}, milliseconds);
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
function run(timed: Timed<never, never>): void {
|
|
42
|
+
const timedConfiguration = configuration.get(timed)!;
|
|
43
|
+
const timedCallbacks = callbacks.get(timed)!;
|
|
44
|
+
const timedState = state.get(timed)!;
|
|
45
|
+
|
|
46
|
+
timedState.active = true;
|
|
47
|
+
timedState.finished = false;
|
|
48
|
+
|
|
49
|
+
const isRepeated = timed instanceof Repeated;
|
|
50
|
+
|
|
51
|
+
let index = 0;
|
|
52
|
+
|
|
53
|
+
let start;
|
|
54
|
+
|
|
55
|
+
function step(timestamp: DOMHighResTimeStamp) {
|
|
56
|
+
if (!timedState.active) {
|
|
57
|
+
return;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
start ??= timestamp;
|
|
61
|
+
|
|
62
|
+
const elapsed = timestamp - start;
|
|
63
|
+
|
|
64
|
+
const elapsedMinimum = elapsed - milliseconds;
|
|
65
|
+
const elapsedMaximum = elapsed + milliseconds;
|
|
66
|
+
|
|
67
|
+
if (
|
|
68
|
+
elapsedMinimum < timedConfiguration.time &&
|
|
69
|
+
timedConfiguration.time < elapsedMaximum
|
|
70
|
+
) {
|
|
71
|
+
if (timedState.active) {
|
|
72
|
+
timedCallbacks.default(isRepeated ? index : undefined);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
index += 1;
|
|
76
|
+
|
|
77
|
+
if (isRepeated && index < timedConfiguration.count) {
|
|
78
|
+
start = undefined;
|
|
79
|
+
} else {
|
|
80
|
+
timedState.finished = true;
|
|
81
|
+
|
|
82
|
+
timed.stop();
|
|
83
|
+
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
timedState.frame = request(step) as never;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
timedState.frame = request(step) as never;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
class Timed<Type, Callback> {
|
|
95
|
+
get active(): boolean {
|
|
96
|
+
return state.get(this as never)?.active ?? false;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
get finished(): boolean {
|
|
100
|
+
return !this.active && (state.get(this as never)?.finished ?? false);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* @param {Callback} callback
|
|
105
|
+
* @param {number} time
|
|
106
|
+
* @param {number} count
|
|
107
|
+
* @param {AfterCallback=} afterCallback
|
|
108
|
+
*/
|
|
109
|
+
constructor(
|
|
110
|
+
callback: () => void | ((index: number) => void),
|
|
111
|
+
time: number,
|
|
112
|
+
count: number,
|
|
113
|
+
afterCallback?: AfterCallback,
|
|
114
|
+
) {
|
|
115
|
+
const isRepeated = this instanceof Repeated;
|
|
116
|
+
|
|
117
|
+
const type = isRepeated ? 'repeated' : 'waited';
|
|
118
|
+
|
|
119
|
+
if (typeof callback !== 'function') {
|
|
120
|
+
throw new TypeError(`A ${type} timer must have a callback function`);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof time !== 'number' || time < 0) {
|
|
124
|
+
throw new TypeError(
|
|
125
|
+
`A ${type} timer must have a non-negative number as its time`,
|
|
126
|
+
);
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
if (isRepeated && (typeof count !== 'number' || count < 2)) {
|
|
130
|
+
throw new TypeError(
|
|
131
|
+
'A repeated timer must have a number above 1 as its repeat count',
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
if (
|
|
136
|
+
isRepeated &&
|
|
137
|
+
afterCallback !== undefined &&
|
|
138
|
+
typeof afterCallback !== 'function'
|
|
139
|
+
) {
|
|
140
|
+
throw new TypeError(
|
|
141
|
+
"A repeated timer's after-callback must be a function",
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
callbacks.set(this as never, {
|
|
146
|
+
after: afterCallback,
|
|
147
|
+
default: callback,
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
configuration.set(this as never, {count, time});
|
|
151
|
+
|
|
152
|
+
state.set(this as never, {
|
|
153
|
+
active: false,
|
|
154
|
+
finished: false,
|
|
155
|
+
frame: undefined,
|
|
156
|
+
});
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
restart(): Type {
|
|
160
|
+
this.stop();
|
|
161
|
+
|
|
162
|
+
run(this as never);
|
|
163
|
+
|
|
164
|
+
return this as never;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
start(): Type {
|
|
168
|
+
if (!this.active) {
|
|
169
|
+
run(this as never);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
return this as never;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
stop(): Type {
|
|
176
|
+
const timedCallbacks = callbacks.get(this as never)!;
|
|
177
|
+
const timedState = state.get(this as never)!;
|
|
178
|
+
|
|
179
|
+
timedState.active = false;
|
|
180
|
+
|
|
181
|
+
if (timedState.frame === undefined) {
|
|
182
|
+
return this as never;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
(cancelAnimationFrame ?? clearTimeout)?.(timedState.frame);
|
|
186
|
+
|
|
187
|
+
timedCallbacks.after?.(this.finished);
|
|
188
|
+
|
|
189
|
+
timedState.frame = undefined;
|
|
190
|
+
|
|
191
|
+
return this as never;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
/**
|
|
196
|
+
* A timer that waits and runs repeatedly
|
|
197
|
+
*/
|
|
198
|
+
export class Repeated extends Timed<Repeated, RepeatedCallback> {}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* A timer that waits and runs once
|
|
202
|
+
*/
|
|
203
|
+
export class Waited extends Timed<Waited, () => void> {
|
|
204
|
+
/**
|
|
205
|
+
* Creates a new waited timer
|
|
206
|
+
* @param {() => void} callback
|
|
207
|
+
* @param {number} time
|
|
208
|
+
*/
|
|
209
|
+
constructor(callback: () => void, time: number) {
|
|
210
|
+
super(callback, time, 1);
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
/**
|
|
215
|
+
* Creates and starts a new repeated timer
|
|
216
|
+
* @param {RepeatedCallback} callback
|
|
217
|
+
* @param {number} time
|
|
218
|
+
* @param {number} count
|
|
219
|
+
* @param {AfterCallback=} afterCallback
|
|
220
|
+
* @return {Repeated}
|
|
221
|
+
*/
|
|
222
|
+
export function repeat(
|
|
223
|
+
callback: RepeatedCallback,
|
|
224
|
+
time: number,
|
|
225
|
+
count: number,
|
|
226
|
+
afterCallback?: AfterCallback,
|
|
227
|
+
): Repeated {
|
|
228
|
+
return new Repeated(callback as never, time, count, afterCallback).start();
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
/**
|
|
232
|
+
* Creates and starts a new waited timer
|
|
233
|
+
* @param {() => void} callback
|
|
234
|
+
* @param {number} time
|
|
235
|
+
* @return {Waited}
|
|
236
|
+
*/
|
|
237
|
+
export function wait(callback: () => void, time: number): Waited {
|
|
238
|
+
return new Waited(callback, time).start();
|
|
239
|
+
}
|
package/types/index.d.ts
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @param {boolean} finished Did the timer finish?
|
|
3
|
+
*/
|
|
4
|
+
export type AfterCallback = (finished: boolean) => void;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {number} index The index of the current iteration
|
|
8
|
+
*/
|
|
9
|
+
export type RepeatedCallback = (index: number) => void;
|
|
10
|
+
|
|
11
|
+
declare class Timed<Type, Callback> {
|
|
12
|
+
get active(): boolean;
|
|
13
|
+
get finished(): boolean;
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {Callback} callback
|
|
17
|
+
* @param {number} time
|
|
18
|
+
* @param {number} count
|
|
19
|
+
* @param {AfterCallback=} afterCallback
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
constructor(
|
|
23
|
+
callback: () => void | ((index: number) => void),
|
|
24
|
+
time: number,
|
|
25
|
+
count: number,
|
|
26
|
+
afterCallback?: AfterCallback,
|
|
27
|
+
);
|
|
28
|
+
|
|
29
|
+
restart(): Type;
|
|
30
|
+
start(): Type;
|
|
31
|
+
stop(): Type;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* A timer that waits and runs repeatedly
|
|
36
|
+
*/
|
|
37
|
+
export declare class Repeated extends Timed<Repeated, RepeatedCallback> {}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A timer that waits and runs once
|
|
41
|
+
*/
|
|
42
|
+
export declare class Waited extends Timed<Waited, () => void> {
|
|
43
|
+
/**
|
|
44
|
+
* Creates a new waited timer
|
|
45
|
+
* @param {() => void} callback
|
|
46
|
+
* @param {number} time
|
|
47
|
+
*/
|
|
48
|
+
constructor(callback: () => void, time: number);
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Creates and starts a new repeated timer
|
|
53
|
+
* @param {RepeatedCallback} callback
|
|
54
|
+
* @param {number} time
|
|
55
|
+
* @param {number} count
|
|
56
|
+
* @param {AfterCallback=} afterCallback
|
|
57
|
+
* @return {Repeated}
|
|
58
|
+
*/
|
|
59
|
+
export declare function repeat(
|
|
60
|
+
callback: RepeatedCallback,
|
|
61
|
+
time: number,
|
|
62
|
+
count: number,
|
|
63
|
+
afterCallback?: AfterCallback,
|
|
64
|
+
): Repeated;
|
|
65
|
+
|
|
66
|
+
/**
|
|
67
|
+
* Creates and starts a new waited timer
|
|
68
|
+
* @param {() => void} callback
|
|
69
|
+
* @param {number} time
|
|
70
|
+
* @return {Waited}
|
|
71
|
+
*/
|
|
72
|
+
export declare function wait(callback: () => void, time: number): Waited;
|
package/dist/timer.cjs
DELETED
|
@@ -1,148 +0,0 @@
|
|
|
1
|
-
var __defProp = Object.defineProperty;
|
|
2
|
-
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
3
|
-
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
4
|
-
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
5
|
-
var __export = (target, all) => {
|
|
6
|
-
for (var name in all)
|
|
7
|
-
__defProp(target, name, { get: all[name], enumerable: true });
|
|
8
|
-
};
|
|
9
|
-
var __copyProps = (to, from, except, desc) => {
|
|
10
|
-
if (from && typeof from === "object" || typeof from === "function") {
|
|
11
|
-
for (let key of __getOwnPropNames(from))
|
|
12
|
-
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
13
|
-
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
14
|
-
}
|
|
15
|
-
return to;
|
|
16
|
-
};
|
|
17
|
-
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
18
|
-
|
|
19
|
-
// src/index.js
|
|
20
|
-
var src_exports = {};
|
|
21
|
-
__export(src_exports, {
|
|
22
|
-
Repeated: () => Repeated,
|
|
23
|
-
Waited: () => Waited,
|
|
24
|
-
repeat: () => repeat,
|
|
25
|
-
wait: () => wait
|
|
26
|
-
});
|
|
27
|
-
module.exports = __toCommonJS(src_exports);
|
|
28
|
-
var milliseconds = Math.round(1e3 / 60);
|
|
29
|
-
var request = globalThis.requestAnimationFrame ?? function(callback) {
|
|
30
|
-
return setTimeout?.(() => {
|
|
31
|
-
callback(Date.now());
|
|
32
|
-
}, milliseconds);
|
|
33
|
-
};
|
|
34
|
-
function run(timed) {
|
|
35
|
-
timed.state.active = true;
|
|
36
|
-
timed.state.finished = false;
|
|
37
|
-
const isRepeated = timed instanceof Repeated;
|
|
38
|
-
let index = 0;
|
|
39
|
-
let start;
|
|
40
|
-
function step(timestamp) {
|
|
41
|
-
if (!timed.state.active) {
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
start ?? (start = timestamp);
|
|
45
|
-
const elapsed = timestamp - start;
|
|
46
|
-
const elapsedMinimum = elapsed - milliseconds;
|
|
47
|
-
const elapsedMaximum = elapsed + milliseconds;
|
|
48
|
-
if (elapsedMinimum < timed.configuration.time && timed.configuration.time < elapsedMaximum) {
|
|
49
|
-
if (timed.state.active) {
|
|
50
|
-
timed.callbacks.default(isRepeated ? index : void 0);
|
|
51
|
-
}
|
|
52
|
-
index += 1;
|
|
53
|
-
if (isRepeated && index < timed.configuration.count) {
|
|
54
|
-
start = void 0;
|
|
55
|
-
} else {
|
|
56
|
-
timed.state.finished = true;
|
|
57
|
-
timed.stop();
|
|
58
|
-
return;
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
timed.state.frame = request(step);
|
|
62
|
-
}
|
|
63
|
-
timed.state.frame = request(step);
|
|
64
|
-
}
|
|
65
|
-
var Timed = class {
|
|
66
|
-
get active() {
|
|
67
|
-
return this.state.active;
|
|
68
|
-
}
|
|
69
|
-
get finished() {
|
|
70
|
-
return !this.active && this.state.finished;
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* @param {RepeatedCallback} callback
|
|
74
|
-
* @param {number} time
|
|
75
|
-
* @param {number} count
|
|
76
|
-
* @param {AfterCallback|undefined} afterCallback
|
|
77
|
-
*/
|
|
78
|
-
constructor(callback, time, count, afterCallback) {
|
|
79
|
-
const isRepeated = this instanceof Repeated;
|
|
80
|
-
const type = isRepeated ? "repeated" : "waited";
|
|
81
|
-
if (typeof callback !== "function") {
|
|
82
|
-
throw new TypeError(`A ${type} timer must have a callback function`);
|
|
83
|
-
}
|
|
84
|
-
if (typeof time !== "number" || time < 0) {
|
|
85
|
-
throw new TypeError(
|
|
86
|
-
`A ${type} timer must have a non-negative number as its time`
|
|
87
|
-
);
|
|
88
|
-
}
|
|
89
|
-
if (isRepeated && (typeof count !== "number" || count < 2)) {
|
|
90
|
-
throw new TypeError(
|
|
91
|
-
"A repeated timer must have a number above 1 as its repeat count"
|
|
92
|
-
);
|
|
93
|
-
}
|
|
94
|
-
if (isRepeated && afterCallback !== void 0 && typeof afterCallback !== "function") {
|
|
95
|
-
throw new TypeError(
|
|
96
|
-
"A repeated timer's after-callback must be a function"
|
|
97
|
-
);
|
|
98
|
-
}
|
|
99
|
-
this.configuration = { count, time };
|
|
100
|
-
this.callbacks = {
|
|
101
|
-
after: afterCallback,
|
|
102
|
-
default: callback
|
|
103
|
-
};
|
|
104
|
-
this.state = {
|
|
105
|
-
active: false,
|
|
106
|
-
finished: false,
|
|
107
|
-
frame: null
|
|
108
|
-
};
|
|
109
|
-
}
|
|
110
|
-
restart() {
|
|
111
|
-
this.stop();
|
|
112
|
-
run(this);
|
|
113
|
-
return this;
|
|
114
|
-
}
|
|
115
|
-
start() {
|
|
116
|
-
if (!this.state.active) {
|
|
117
|
-
run(this);
|
|
118
|
-
}
|
|
119
|
-
return this;
|
|
120
|
-
}
|
|
121
|
-
stop() {
|
|
122
|
-
this.state.active = false;
|
|
123
|
-
if (this.state.frame === void 0) {
|
|
124
|
-
return this;
|
|
125
|
-
}
|
|
126
|
-
(globalThis.cancelAnimationFrame ?? clearTimeout)?.(this.state.frame);
|
|
127
|
-
this.callbacks.after?.(this.finished);
|
|
128
|
-
this.state.frame = void 0;
|
|
129
|
-
return this;
|
|
130
|
-
}
|
|
131
|
-
};
|
|
132
|
-
var Repeated = class extends Timed {
|
|
133
|
-
};
|
|
134
|
-
var Waited = class extends Timed {
|
|
135
|
-
/**
|
|
136
|
-
* @param {Function} callback
|
|
137
|
-
* @param {number} time
|
|
138
|
-
*/
|
|
139
|
-
constructor(callback, time) {
|
|
140
|
-
super(callback, time, 1, null);
|
|
141
|
-
}
|
|
142
|
-
};
|
|
143
|
-
function repeat(callback, time, count, afterCallback) {
|
|
144
|
-
return new Repeated(callback, time, count, afterCallback).start();
|
|
145
|
-
}
|
|
146
|
-
function wait(callback, time) {
|
|
147
|
-
return new Waited(callback, time).start();
|
|
148
|
-
}
|
package/src/index.d.ts
DELETED
|
@@ -1,31 +0,0 @@
|
|
|
1
|
-
export function repeat(callback: RepeatedCallback, time: number, count: number, afterCallback: AfterCallback | undefined): Repeated;
|
|
2
|
-
export function wait(callback: Function, time: number): Waited;
|
|
3
|
-
export class Repeated extends Timed {
|
|
4
|
-
}
|
|
5
|
-
export class Waited extends Timed {
|
|
6
|
-
constructor(callback: Function, time: number);
|
|
7
|
-
}
|
|
8
|
-
export type AfterCallback = (finished: boolean) => void;
|
|
9
|
-
export type RepeatedCallback = (index: number) => void;
|
|
10
|
-
declare class Timed {
|
|
11
|
-
constructor(callback: RepeatedCallback, time: number, count: number, afterCallback: AfterCallback | undefined);
|
|
12
|
-
get active(): boolean;
|
|
13
|
-
get finished(): boolean;
|
|
14
|
-
readonly configuration: {
|
|
15
|
-
count: number;
|
|
16
|
-
time: number;
|
|
17
|
-
};
|
|
18
|
-
readonly callbacks: {
|
|
19
|
-
after: AfterCallback | undefined;
|
|
20
|
-
default: RepeatedCallback;
|
|
21
|
-
};
|
|
22
|
-
readonly state: {
|
|
23
|
-
active: boolean;
|
|
24
|
-
finished: boolean;
|
|
25
|
-
frame?: DOMHighResTimeStamp;
|
|
26
|
-
};
|
|
27
|
-
restart(): Timed;
|
|
28
|
-
start(): Timed;
|
|
29
|
-
stop(): Timed;
|
|
30
|
-
}
|
|
31
|
-
export {};
|
package/src/index.js
DELETED
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* @callback AfterCallback
|
|
3
|
-
* @param {boolean} finished
|
|
4
|
-
* @returns {void}
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
/**
|
|
8
|
-
* @callback RepeatedCallback
|
|
9
|
-
* @param {number} index
|
|
10
|
-
* @returns {void}
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
const milliseconds = Math.round(1000 / 60);
|
|
14
|
-
|
|
15
|
-
const request = globalThis.requestAnimationFrame
|
|
16
|
-
?? function (callback) {
|
|
17
|
-
return setTimeout?.(() => {
|
|
18
|
-
callback(Date.now());
|
|
19
|
-
}, milliseconds);
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
/**
|
|
23
|
-
* @param {Timed} timed
|
|
24
|
-
*/
|
|
25
|
-
function run(timed) {
|
|
26
|
-
timed.state.active = true;
|
|
27
|
-
timed.state.finished = false;
|
|
28
|
-
|
|
29
|
-
const isRepeated = timed instanceof Repeated;
|
|
30
|
-
|
|
31
|
-
let index = 0;
|
|
32
|
-
|
|
33
|
-
let start;
|
|
34
|
-
|
|
35
|
-
function step(timestamp) {
|
|
36
|
-
if (!timed.state.active) {
|
|
37
|
-
return;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
start ??= timestamp;
|
|
41
|
-
|
|
42
|
-
const elapsed = timestamp - start;
|
|
43
|
-
|
|
44
|
-
const elapsedMinimum = elapsed - milliseconds;
|
|
45
|
-
const elapsedMaximum = elapsed + milliseconds;
|
|
46
|
-
|
|
47
|
-
if (
|
|
48
|
-
elapsedMinimum < timed.configuration.time
|
|
49
|
-
&& timed.configuration.time < elapsedMaximum
|
|
50
|
-
) {
|
|
51
|
-
if (timed.state.active) {
|
|
52
|
-
timed.callbacks.default(isRepeated ? index : undefined);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
index += 1;
|
|
56
|
-
|
|
57
|
-
if (isRepeated && index < timed.configuration.count) {
|
|
58
|
-
start = undefined;
|
|
59
|
-
} else {
|
|
60
|
-
timed.state.finished = true;
|
|
61
|
-
|
|
62
|
-
timed.stop();
|
|
63
|
-
|
|
64
|
-
return;
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
timed.state.frame = request(step);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
timed.state.frame = request(step);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
class Timed {
|
|
75
|
-
get active() {
|
|
76
|
-
return this.state.active;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get finished() {
|
|
80
|
-
return !this.active && this.state.finished;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* @param {RepeatedCallback} callback
|
|
85
|
-
* @param {number} time
|
|
86
|
-
* @param {number} count
|
|
87
|
-
* @param {AfterCallback|undefined} afterCallback
|
|
88
|
-
*/
|
|
89
|
-
constructor(callback, time, count, afterCallback) {
|
|
90
|
-
const isRepeated = this instanceof Repeated;
|
|
91
|
-
|
|
92
|
-
const type = isRepeated ? 'repeated' : 'waited';
|
|
93
|
-
|
|
94
|
-
if (typeof callback !== 'function') {
|
|
95
|
-
throw new TypeError(`A ${type} timer must have a callback function`);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
if (typeof time !== 'number' || time < 0) {
|
|
99
|
-
throw new TypeError(
|
|
100
|
-
`A ${type} timer must have a non-negative number as its time`,
|
|
101
|
-
);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
if (isRepeated && (typeof count !== 'number' || count < 2)) {
|
|
105
|
-
throw new TypeError(
|
|
106
|
-
'A repeated timer must have a number above 1 as its repeat count',
|
|
107
|
-
);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
if (
|
|
111
|
-
isRepeated
|
|
112
|
-
&& afterCallback !== undefined
|
|
113
|
-
&& typeof afterCallback !== 'function'
|
|
114
|
-
) {
|
|
115
|
-
throw new TypeError(
|
|
116
|
-
'A repeated timer\'s after-callback must be a function',
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
/**
|
|
121
|
-
* @readonly
|
|
122
|
-
* @type {{count: number; time: number}}
|
|
123
|
-
*/
|
|
124
|
-
this.configuration = {count, time};
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* @readonly
|
|
128
|
-
* @type {{after: AfterCallback | undefined; default: RepeatedCallback}}
|
|
129
|
-
*/
|
|
130
|
-
this.callbacks = {
|
|
131
|
-
after: afterCallback,
|
|
132
|
-
default: callback,
|
|
133
|
-
};
|
|
134
|
-
|
|
135
|
-
/**
|
|
136
|
-
* @readonly
|
|
137
|
-
* @type {{active: boolean; finished: boolean; frame?: DOMHighResTimeStamp}}
|
|
138
|
-
*/
|
|
139
|
-
this.state = {
|
|
140
|
-
active: false,
|
|
141
|
-
finished: false,
|
|
142
|
-
frame: null,
|
|
143
|
-
};
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
restart() {
|
|
147
|
-
this.stop();
|
|
148
|
-
|
|
149
|
-
run(this);
|
|
150
|
-
|
|
151
|
-
return this;
|
|
152
|
-
}
|
|
153
|
-
|
|
154
|
-
start() {
|
|
155
|
-
if (!this.state.active) {
|
|
156
|
-
run(this);
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
return this;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
stop() {
|
|
163
|
-
this.state.active = false;
|
|
164
|
-
|
|
165
|
-
if (this.state.frame === undefined) {
|
|
166
|
-
return this;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
(globalThis.cancelAnimationFrame ?? clearTimeout)?.(this.state.frame);
|
|
170
|
-
|
|
171
|
-
this.callbacks.after?.(this.finished);
|
|
172
|
-
|
|
173
|
-
this.state.frame = undefined;
|
|
174
|
-
|
|
175
|
-
return this;
|
|
176
|
-
}
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
export class Repeated extends Timed {}
|
|
180
|
-
|
|
181
|
-
export class Waited extends Timed {
|
|
182
|
-
/**
|
|
183
|
-
* @param {Function} callback
|
|
184
|
-
* @param {number} time
|
|
185
|
-
*/
|
|
186
|
-
constructor(callback, time) {
|
|
187
|
-
super(callback, time, 1, null);
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
/**
|
|
192
|
-
* @param {RepeatedCallback} callback
|
|
193
|
-
* @param {number} time
|
|
194
|
-
* @param {number} count
|
|
195
|
-
* @param {AfterCallback|undefined} afterCallback
|
|
196
|
-
* @return {Repeated} A repeated timer
|
|
197
|
-
*/
|
|
198
|
-
export function repeat(callback, time, count, afterCallback) {
|
|
199
|
-
return new Repeated(callback, time, count, afterCallback).start();
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
/**
|
|
203
|
-
* @param {Function} callback
|
|
204
|
-
* @param {number} time
|
|
205
|
-
* @return {Waited} A waited timer
|
|
206
|
-
*/
|
|
207
|
-
export function wait(callback, time) {
|
|
208
|
-
return new Waited(callback, time).start();
|
|
209
|
-
}
|