@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/src/index.ts
CHANGED
|
@@ -1,229 +1,37 @@
|
|
|
1
|
-
|
|
1
|
+
import {noop} from '@oscarpalmer/atoms/function';
|
|
2
|
+
import {activeTimers, hiddenTimers} from './constants';
|
|
3
|
+
import './global';
|
|
4
|
+
import {wait} from './timer';
|
|
2
5
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
type IndexedCallback = (index: number) => void;
|
|
15
|
-
|
|
16
|
-
type State = {
|
|
17
|
-
active: boolean;
|
|
18
|
-
finished: boolean;
|
|
19
|
-
frame?: number;
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
const milliseconds = Math.round(1000 / 60);
|
|
23
|
-
|
|
24
|
-
function run(timer: Timer): void {
|
|
25
|
-
// @ts-expect-error Keep private status, but allow access
|
|
26
|
-
const {_configuration, _state} = timer;
|
|
27
|
-
|
|
28
|
-
_state.active = true;
|
|
29
|
-
_state.finished = false;
|
|
30
|
-
|
|
31
|
-
const isRepeated = _configuration.count > 1;
|
|
32
|
-
|
|
33
|
-
let index = 0;
|
|
34
|
-
|
|
35
|
-
let start;
|
|
36
|
-
|
|
37
|
-
function step(timestamp: DOMHighResTimeStamp): void {
|
|
38
|
-
if (!_state.active) {
|
|
39
|
-
return;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
start ??= timestamp;
|
|
43
|
-
|
|
44
|
-
const elapsed = timestamp - start;
|
|
45
|
-
|
|
46
|
-
const elapsedMinimum = elapsed - milliseconds;
|
|
47
|
-
const elapsedMaximum = elapsed + milliseconds;
|
|
48
|
-
|
|
49
|
-
if (
|
|
50
|
-
elapsedMinimum < _configuration.time &&
|
|
51
|
-
_configuration.time < elapsedMaximum
|
|
52
|
-
) {
|
|
53
|
-
if (_state.active) {
|
|
54
|
-
_configuration.callbacks.default(index);
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
index += 1;
|
|
58
|
-
|
|
59
|
-
if (isRepeated && index < _configuration.count) {
|
|
60
|
-
start = undefined;
|
|
61
|
-
} else {
|
|
62
|
-
_state.finished = true;
|
|
63
|
-
|
|
64
|
-
timer.stop();
|
|
65
|
-
|
|
66
|
-
return;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
_state.frame = requestAnimationFrame(step);
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
_state.frame = requestAnimationFrame(step);
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
export class Timer {
|
|
77
|
-
private declare readonly _configuration: Configuration;
|
|
78
|
-
private declare readonly _state: State;
|
|
79
|
-
|
|
80
|
-
get active(): boolean {
|
|
81
|
-
return this._state.active;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
get finished(): boolean {
|
|
85
|
-
return this._state.finished;
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
/**
|
|
89
|
-
* @param {Callback} callback
|
|
90
|
-
* @param {number} time
|
|
91
|
-
* @param {number} count
|
|
92
|
-
* @param {AfterCallback=} afterCallback
|
|
93
|
-
*/
|
|
94
|
-
constructor(
|
|
95
|
-
callback: IndexedCallback,
|
|
96
|
-
time?: number,
|
|
97
|
-
count?: number,
|
|
98
|
-
afterCallback?: AfterCallback,
|
|
99
|
-
) {
|
|
100
|
-
if (typeof callback !== 'function') {
|
|
101
|
-
throw new TypeError('A timer must have a callback function');
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const actualTime = typeof time === 'number' ? time : 0;
|
|
105
|
-
|
|
106
|
-
if (actualTime < 0) {
|
|
107
|
-
throw new TypeError(
|
|
108
|
-
'A timer must have a non-negative number as its time',
|
|
109
|
-
);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
const actualCount = typeof count === 'number' ? count : 1;
|
|
113
|
-
|
|
114
|
-
if (actualCount < 1) {
|
|
115
|
-
throw new TypeError(
|
|
116
|
-
'A timer must have a number greater than or equal to 1 as its run count',
|
|
117
|
-
);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (
|
|
121
|
-
actualCount > 1 &&
|
|
122
|
-
afterCallback !== undefined &&
|
|
123
|
-
typeof afterCallback !== 'function'
|
|
124
|
-
) {
|
|
125
|
-
throw new TypeError(
|
|
126
|
-
"A repeated timer's after-callback must be a function",
|
|
127
|
-
);
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
Object.defineProperty(this, '_configuration', {
|
|
131
|
-
value: {
|
|
132
|
-
callbacks: {
|
|
133
|
-
after: afterCallback,
|
|
134
|
-
default: callback,
|
|
135
|
-
},
|
|
136
|
-
count: actualCount,
|
|
137
|
-
time: actualTime,
|
|
138
|
-
},
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
Object.defineProperty(this, '_state', {
|
|
142
|
-
value: {
|
|
143
|
-
active: false,
|
|
144
|
-
finished: false,
|
|
145
|
-
},
|
|
6
|
+
/**
|
|
7
|
+
* Creates a delayed promise that resolves after a certain amount of time _(or rejects when timed out)_
|
|
8
|
+
*/
|
|
9
|
+
export function delay(time: number, timeout?: number): Promise<void> {
|
|
10
|
+
return new Promise((resolve, reject) => {
|
|
11
|
+
wait(resolve ?? noop, {
|
|
12
|
+
timeout,
|
|
13
|
+
errorCallback: reject ?? noop,
|
|
14
|
+
interval: time,
|
|
146
15
|
});
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
restart(): Timer {
|
|
150
|
-
this.stop();
|
|
151
|
-
|
|
152
|
-
run(this);
|
|
153
|
-
|
|
154
|
-
return this;
|
|
155
|
-
}
|
|
16
|
+
});
|
|
17
|
+
}
|
|
156
18
|
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
19
|
+
document.addEventListener('visibilitychange', () => {
|
|
20
|
+
if (document.hidden) {
|
|
21
|
+
for (const timer of activeTimers) {
|
|
22
|
+
hiddenTimers.add(timer);
|
|
23
|
+
timer.pause();
|
|
160
24
|
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
stop(): Timer {
|
|
166
|
-
this._state.active = false;
|
|
167
|
-
|
|
168
|
-
if (this._state.frame === undefined) {
|
|
169
|
-
return this;
|
|
25
|
+
} else {
|
|
26
|
+
for (const timer of hiddenTimers) {
|
|
27
|
+
timer.continue();
|
|
170
28
|
}
|
|
171
29
|
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
this._configuration.callbacks.after?.(this._state.finished);
|
|
175
|
-
|
|
176
|
-
this._state.frame = undefined;
|
|
177
|
-
|
|
178
|
-
return this;
|
|
179
|
-
}
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/**
|
|
183
|
-
* Creates and starts a new repeated timer
|
|
184
|
-
*/
|
|
185
|
-
export function repeat(callback: IndexedCallback, count: number): Timer;
|
|
186
|
-
export function repeat(
|
|
187
|
-
callback: IndexedCallback,
|
|
188
|
-
count: number,
|
|
189
|
-
afterCallback: AfterCallback,
|
|
190
|
-
): Timer;
|
|
191
|
-
export function repeat(
|
|
192
|
-
callback: IndexedCallback,
|
|
193
|
-
count: number,
|
|
194
|
-
time: number,
|
|
195
|
-
): Timer;
|
|
196
|
-
export function repeat(
|
|
197
|
-
callback: IndexedCallback,
|
|
198
|
-
count: number,
|
|
199
|
-
time: number,
|
|
200
|
-
afterCallback: AfterCallback,
|
|
201
|
-
): Timer;
|
|
202
|
-
export function repeat(
|
|
203
|
-
callback: IndexedCallback,
|
|
204
|
-
count: number,
|
|
205
|
-
afterOrTime?: number | AfterCallback,
|
|
206
|
-
after?: AfterCallback,
|
|
207
|
-
): Timer {
|
|
208
|
-
if (typeof count !== 'number' || count < 2) {
|
|
209
|
-
throw new TypeError(
|
|
210
|
-
'A repeated timer must have a number greater than or equal to 2 as its run count',
|
|
211
|
-
);
|
|
30
|
+
hiddenTimers.clear();
|
|
212
31
|
}
|
|
32
|
+
});
|
|
213
33
|
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
callback,
|
|
218
|
-
afterOrTimeIsFunction ? 0 : afterOrTime,
|
|
219
|
-
count,
|
|
220
|
-
afterOrTimeIsFunction ? afterOrTime : after,
|
|
221
|
-
).start();
|
|
222
|
-
}
|
|
34
|
+
export {isRepeated, isTimer, isWaited, isWhen} from './is';
|
|
35
|
+
export {repeat, wait, type Timer} from './timer';
|
|
36
|
+
export {when, type When} from './when';
|
|
223
37
|
|
|
224
|
-
/**
|
|
225
|
-
* Creates and starts a new waited timer
|
|
226
|
-
*/
|
|
227
|
-
export function wait(callback: IndexedCallback, time?: number): Timer {
|
|
228
|
-
return new Timer(callback, time).start();
|
|
229
|
-
}
|
package/src/is.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type {PlainObject} from '@oscarpalmer/atoms/models';
|
|
2
|
+
import type {Timer} from './timer';
|
|
3
|
+
import type {When} from './when';
|
|
4
|
+
|
|
5
|
+
function is(pattern: RegExp, value: unknown) {
|
|
6
|
+
return pattern.test((value as PlainObject)?.$timer as string);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Is the value a repeating timer?
|
|
11
|
+
*/
|
|
12
|
+
export function isRepeated(value: unknown): value is Timer {
|
|
13
|
+
return is(/^repeat$/, value);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Is the value a timer?
|
|
18
|
+
*/
|
|
19
|
+
export function isTimer(value: unknown): value is Timer {
|
|
20
|
+
return is(/^repeat|wait$/, value);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Is the value a waiting timer?
|
|
25
|
+
*/
|
|
26
|
+
export function isWaited(value: unknown): value is Timer {
|
|
27
|
+
return is(/^wait$/, value);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Is the value a conditional timer?
|
|
32
|
+
*/
|
|
33
|
+
export function isWhen(value: unknown): value is When {
|
|
34
|
+
return is(/^when$/, value) && typeof (value as When).then === 'function';
|
|
35
|
+
}
|
package/src/models.ts
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type {Timer} from './timer';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Callback that runs after the timer has finished (or is stopped)
|
|
5
|
+
* - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
|
|
6
|
+
*/
|
|
7
|
+
export type AfterCallback = (finished: boolean) => void;
|
|
8
|
+
|
|
9
|
+
export type AnyCallback = (() => void) | IndexedCallback;
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* Callback that runs for each iteration of the timer
|
|
13
|
+
*/
|
|
14
|
+
export type IndexedCallback = (index: number) => void;
|
|
15
|
+
|
|
16
|
+
export type BaseOptions = {
|
|
17
|
+
/**
|
|
18
|
+
* Interval between each callback
|
|
19
|
+
*/
|
|
20
|
+
interval: number;
|
|
21
|
+
/**
|
|
22
|
+
* Maximum amount of time the timer may run for
|
|
23
|
+
*/
|
|
24
|
+
timeout: number;
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
export type OptionsWithCount = {
|
|
28
|
+
/**
|
|
29
|
+
* How many times the timer should repeat
|
|
30
|
+
*/
|
|
31
|
+
count: number;
|
|
32
|
+
} & BaseOptions;
|
|
33
|
+
|
|
34
|
+
export type OptionsWithError = {
|
|
35
|
+
/**
|
|
36
|
+
* Callback to run when an error occurs _(usually a timeout)_
|
|
37
|
+
*/
|
|
38
|
+
errorCallback?: () => void;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
export type RepeatOptions = {
|
|
42
|
+
/**
|
|
43
|
+
* Callback to run after the timer has finished (or is stopped)
|
|
44
|
+
* - `finished` is `true` if the timer was allowed to finish, and `false` if it was stopped
|
|
45
|
+
*/
|
|
46
|
+
afterCallback?: AfterCallback;
|
|
47
|
+
} & OptionsWithCount &
|
|
48
|
+
OptionsWithError;
|
|
49
|
+
|
|
50
|
+
export type TimerOptions = {} & RepeatOptions;
|
|
51
|
+
|
|
52
|
+
export type TimerState = {
|
|
53
|
+
active: boolean;
|
|
54
|
+
callback: AnyCallback;
|
|
55
|
+
count?: number;
|
|
56
|
+
elapsed?: number;
|
|
57
|
+
frame?: number;
|
|
58
|
+
index?: number;
|
|
59
|
+
isRepeated: boolean;
|
|
60
|
+
minimum: number;
|
|
61
|
+
paused: boolean;
|
|
62
|
+
trace: unknown;
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
export type WaitOptions = {} & BaseOptions & OptionsWithError;
|
|
66
|
+
|
|
67
|
+
export type WhenOptions = {} & OptionsWithCount;
|
|
68
|
+
|
|
69
|
+
export type WhenState = {
|
|
70
|
+
promise: Promise<void>;
|
|
71
|
+
rejecter?: () => void;
|
|
72
|
+
resolver?: () => void;
|
|
73
|
+
timer: Timer;
|
|
74
|
+
};
|
|
75
|
+
|
|
76
|
+
export type WorkType = 'continue' | 'pause' | 'restart' | 'start' | 'stop';
|
package/src/timer.ts
ADDED
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import {milliseconds} from './constants';
|
|
2
|
+
import {getOptions, work} from './functions';
|
|
3
|
+
import type {
|
|
4
|
+
AnyCallback,
|
|
5
|
+
IndexedCallback,
|
|
6
|
+
RepeatOptions,
|
|
7
|
+
TimerOptions,
|
|
8
|
+
TimerState,
|
|
9
|
+
WaitOptions,
|
|
10
|
+
} from './models';
|
|
11
|
+
|
|
12
|
+
export abstract class BasicTimer<State> {
|
|
13
|
+
protected declare readonly $timer: string;
|
|
14
|
+
protected declare readonly state: State;
|
|
15
|
+
|
|
16
|
+
constructor(type: 'repeat' | 'wait' | 'when', state: State) {
|
|
17
|
+
this.$timer = type;
|
|
18
|
+
this.state = state;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Is the timer running?
|
|
23
|
+
*/
|
|
24
|
+
abstract readonly active: boolean;
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Is the timer paused?
|
|
28
|
+
*/
|
|
29
|
+
abstract readonly paused: boolean;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A timer that can be started, stopped, and restarted as neeeded
|
|
34
|
+
*/
|
|
35
|
+
export class Timer extends BasicTimer<TimerState> {
|
|
36
|
+
private declare readonly options: TimerOptions;
|
|
37
|
+
|
|
38
|
+
get active() {
|
|
39
|
+
return this.state.active;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
get paused() {
|
|
43
|
+
return this.state.paused;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Gets the traced location of the timer
|
|
48
|
+
*/
|
|
49
|
+
get trace() {
|
|
50
|
+
return globalThis._oscarpalmer_timer_debug ? this.state.trace : undefined;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
constructor(
|
|
54
|
+
type: 'repeat' | 'wait',
|
|
55
|
+
state: TimerState,
|
|
56
|
+
options: TimerOptions,
|
|
57
|
+
) {
|
|
58
|
+
super(type, state);
|
|
59
|
+
|
|
60
|
+
this.options = options;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Continues the timer _(if it was paused)_
|
|
65
|
+
*/
|
|
66
|
+
continue(): Timer {
|
|
67
|
+
return work('continue', this, this.state, this.options);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Pauses the timer _(if it was running)_
|
|
72
|
+
*/
|
|
73
|
+
pause(): Timer {
|
|
74
|
+
return work('pause', this, this.state, this.options);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Restarts the timer _(if it was running)_
|
|
79
|
+
*/
|
|
80
|
+
restart(): Timer {
|
|
81
|
+
return work('restart', this, this.state, this.options);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Starts the timer _(if it was stopped)_
|
|
86
|
+
*/
|
|
87
|
+
start(): Timer {
|
|
88
|
+
return work('start', this, this.state, this.options);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Stops the timer _(if it was running)_
|
|
93
|
+
*/
|
|
94
|
+
stop(): Timer {
|
|
95
|
+
return work('stop', this, this.state, this.options);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
class TimerTrace extends Error {
|
|
100
|
+
constructor() {
|
|
101
|
+
super();
|
|
102
|
+
|
|
103
|
+
this.name = 'TimerTrace';
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Creates a timer which:
|
|
109
|
+
* - calls a callback after a certain amount of time...
|
|
110
|
+
* - ... and repeats it a certain amount of times
|
|
111
|
+
* ---
|
|
112
|
+
* - `options.count` defaults to `Infinity`
|
|
113
|
+
* - `options.interval` defaults to `1000/60` _(1 frame)_
|
|
114
|
+
* - `options.timeout` defaults to `Infinity`
|
|
115
|
+
*/
|
|
116
|
+
export function repeat(
|
|
117
|
+
callback: IndexedCallback,
|
|
118
|
+
options?: Partial<RepeatOptions>,
|
|
119
|
+
): Timer {
|
|
120
|
+
return timer('repeat', callback, options ?? {}, true);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export function timer(
|
|
124
|
+
type: 'repeat' | 'wait',
|
|
125
|
+
callback: AnyCallback,
|
|
126
|
+
partial: Partial<TimerOptions>,
|
|
127
|
+
start: boolean,
|
|
128
|
+
): Timer {
|
|
129
|
+
const isRepeated = type === 'repeat';
|
|
130
|
+
const options = getOptions(partial, isRepeated);
|
|
131
|
+
|
|
132
|
+
const instance = new Timer(
|
|
133
|
+
type,
|
|
134
|
+
{
|
|
135
|
+
callback,
|
|
136
|
+
isRepeated,
|
|
137
|
+
active: false,
|
|
138
|
+
minimum: options.interval - (options.interval % milliseconds) / 2,
|
|
139
|
+
paused: false,
|
|
140
|
+
trace: new TimerTrace(),
|
|
141
|
+
},
|
|
142
|
+
options,
|
|
143
|
+
);
|
|
144
|
+
|
|
145
|
+
if (start) {
|
|
146
|
+
instance.start();
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return instance;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
/**
|
|
153
|
+
* Creates a timer which calls a callback after a certain amount of time
|
|
154
|
+
*/
|
|
155
|
+
export function wait(callback: () => void): Timer;
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Creates a timer which calls a callback after a certain amount of time
|
|
159
|
+
*/
|
|
160
|
+
export function wait(callback: () => void, time: number): Timer;
|
|
161
|
+
|
|
162
|
+
/**
|
|
163
|
+
* Creates a timer which calls a callback after a certain amount of time
|
|
164
|
+
* - `options.interval` defaults to `1000/60` _(1 frame)_
|
|
165
|
+
* - `options.timeout` defaults to `30_000` _(30 seconds)_
|
|
166
|
+
*/
|
|
167
|
+
export function wait(
|
|
168
|
+
callback: () => void,
|
|
169
|
+
options: Partial<WaitOptions>,
|
|
170
|
+
): Timer;
|
|
171
|
+
|
|
172
|
+
export function wait(
|
|
173
|
+
callback: () => void,
|
|
174
|
+
options?: number | Partial<WaitOptions>,
|
|
175
|
+
): Timer {
|
|
176
|
+
return timer(
|
|
177
|
+
'wait',
|
|
178
|
+
callback,
|
|
179
|
+
options == null || typeof options === 'number'
|
|
180
|
+
? {
|
|
181
|
+
interval: options,
|
|
182
|
+
}
|
|
183
|
+
: options,
|
|
184
|
+
true,
|
|
185
|
+
);
|
|
186
|
+
}
|
package/src/when.ts
ADDED
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import {noop} from '@oscarpalmer/atoms/function';
|
|
2
|
+
import type {WhenOptions, WhenState} from './models';
|
|
3
|
+
import {BasicTimer, timer} from './timer';
|
|
4
|
+
|
|
5
|
+
export class When extends BasicTimer<WhenState> {
|
|
6
|
+
get active() {
|
|
7
|
+
return this.state.timer.active;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
get paused() {
|
|
11
|
+
return this.state.timer.paused;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
constructor(state: WhenState) {
|
|
15
|
+
super('when', state);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Continues the timer _(if it was paused)_
|
|
20
|
+
*/
|
|
21
|
+
continue(): When {
|
|
22
|
+
this.state.timer.continue();
|
|
23
|
+
|
|
24
|
+
return this;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Pauses the timer _(if it was running)_
|
|
29
|
+
*/
|
|
30
|
+
pause(): When {
|
|
31
|
+
this.state.timer.pause();
|
|
32
|
+
|
|
33
|
+
return this;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Stops the timer _(if it was running)_
|
|
38
|
+
*/
|
|
39
|
+
stop(): When {
|
|
40
|
+
if (this.state.timer.active) {
|
|
41
|
+
this.state.timer.stop();
|
|
42
|
+
|
|
43
|
+
this.state.rejecter?.();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return this;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* Starts the timer and returns a promise that resolves when the condition is met
|
|
51
|
+
*/
|
|
52
|
+
|
|
53
|
+
// biome-ignore lint/suspicious/noThenProperty: returning a promise-like object, so it's ok ;)
|
|
54
|
+
then(
|
|
55
|
+
resolve?: (() => void) | null,
|
|
56
|
+
reject?: (() => void) | null,
|
|
57
|
+
): Promise<void> {
|
|
58
|
+
this.state.timer.start();
|
|
59
|
+
|
|
60
|
+
return this.state.promise.then(resolve ?? noop, reject ?? noop);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* - Creates a promise that resolves when a condition is met
|
|
66
|
+
* - If the condition is never met in a timely manner, the promise will reject
|
|
67
|
+
*/
|
|
68
|
+
export function when(
|
|
69
|
+
condition: () => boolean,
|
|
70
|
+
options?: Partial<WhenOptions>,
|
|
71
|
+
): When {
|
|
72
|
+
const repeated = timer(
|
|
73
|
+
'repeat',
|
|
74
|
+
() => {
|
|
75
|
+
if (condition()) {
|
|
76
|
+
repeated.stop();
|
|
77
|
+
|
|
78
|
+
state.resolver?.();
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
afterCallback() {
|
|
83
|
+
if (!repeated.paused) {
|
|
84
|
+
if (condition()) {
|
|
85
|
+
state.resolver?.();
|
|
86
|
+
} else {
|
|
87
|
+
state.rejecter?.();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
errorCallback() {
|
|
92
|
+
state.rejecter?.();
|
|
93
|
+
},
|
|
94
|
+
count: options?.count,
|
|
95
|
+
interval: options?.interval,
|
|
96
|
+
timeout: options?.timeout,
|
|
97
|
+
},
|
|
98
|
+
false,
|
|
99
|
+
);
|
|
100
|
+
|
|
101
|
+
const state: WhenState = {} as never;
|
|
102
|
+
|
|
103
|
+
state.promise = new Promise((resolve, reject) => {
|
|
104
|
+
state.resolver = resolve;
|
|
105
|
+
state.rejecter = reject;
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
state.timer = repeated;
|
|
109
|
+
|
|
110
|
+
return new When(state);
|
|
111
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Timer } from './timer';
|
|
2
|
+
/**
|
|
3
|
+
* A set of all active timers
|
|
4
|
+
*/
|
|
5
|
+
export declare const activeTimers: Set<Timer>;
|
|
6
|
+
/**
|
|
7
|
+
* A set of timers that were paused due to the document being hidden
|
|
8
|
+
*/
|
|
9
|
+
export declare const hiddenTimers: Set<Timer>;
|
|
10
|
+
/**
|
|
11
|
+
* Milliseconds in a frame, probably ;-)
|
|
12
|
+
*/
|
|
13
|
+
export declare const milliseconds: number;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { TimerOptions, TimerState, WorkType } from './models';
|
|
2
|
+
import type { Timer } from './timer';
|
|
3
|
+
export declare function getOptions(options: Partial<TimerOptions>, isRepeated: boolean): TimerOptions;
|
|
4
|
+
export declare function getValueOrDefault(value: unknown, defaultValue: number, minimum?: number): number;
|
|
5
|
+
export declare function work(type: WorkType, timer: Timer, state: TimerState, options: TimerOptions): Timer;
|