@oscarpalmer/timer 0.47.0 → 0.49.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/src/models.ts CHANGED
@@ -1,3 +1,5 @@
1
+ // #region Types
2
+
1
3
  /**
2
4
  * Options for a repeating timer
3
5
  */
@@ -32,7 +34,7 @@ export type Timer = {
32
34
 
33
35
  /**
34
36
  * Is the timer destroyed?
35
- *
37
+ *
36
38
  * @deprecated Timers take care of their own cleanup; this always returns `false`
37
39
  */
38
40
  get destroyed(): boolean;
@@ -54,7 +56,7 @@ export type Timer = {
54
56
 
55
57
  /**
56
58
  * Destroy the timer
57
- *
59
+ *
58
60
  * @deprecated Timers take care of their own cleanup
59
61
  */
60
62
  destroy(): void;
@@ -133,7 +135,7 @@ export type When = {
133
135
 
134
136
  /**
135
137
  * Is the timer destroyed?
136
- *
138
+ *
137
139
  * @deprecated Timers take care of their own cleanup; this always returns `false`
138
140
  */
139
141
  get destroyed(): boolean;
@@ -155,7 +157,7 @@ export type When = {
155
157
 
156
158
  /**
157
159
  * Destroys the timer _(and stops it,if it was running)_
158
- *
160
+ *
159
161
  * @deprecated Timers take care of their own cleanup
160
162
  */
161
163
  destroy(): void;
@@ -198,6 +200,7 @@ export type WhenOptions = {
198
200
  };
199
201
 
200
202
  export type WhenState = {
203
+ name: TimerName;
201
204
  promise: Promise<void>;
202
205
  rejecter?: () => void;
203
206
  resolver?: () => void;
@@ -219,3 +222,5 @@ export type WorkHandlerTimer = {
219
222
  };
220
223
 
221
224
  export type WorkHandlerType = 'continue' | 'pause' | 'restart' | 'start' | 'stop';
225
+
226
+ // #endregion
package/src/repeat.ts CHANGED
@@ -4,6 +4,8 @@ import './global';
4
4
  import {type RepeatOptions, type Timer, TimerTrace} from './models';
5
5
  import {createTimer} from './timer';
6
6
 
7
+ // #region Functions
8
+
7
9
  /**
8
10
  * Create a repeating timer
9
11
  *
@@ -28,3 +30,5 @@ export function repeat(callback: (index: number) => void, options?: Partial<Repe
28
30
  true,
29
31
  );
30
32
  }
33
+
34
+ // #endregion
package/src/timer.ts CHANGED
@@ -1,65 +1,124 @@
1
- import {noop} from '@oscarpalmer/atoms/function';
2
- import {WORK_CONTINUE, WORK_PAUSE, WORK_RESTART, WORK_START, WORK_STOP} from './constants';
1
+ import {SYMBOL, WORK_CONTINUE, WORK_PAUSE, WORK_RESTART, WORK_START, WORK_STOP} from './constants';
3
2
  import type {Timer, TimerName, TimerOptions, TimerState} from './models';
4
3
  import {work} from './work';
5
4
 
6
- export function createTimer(
5
+ // #region Types
6
+
7
+ type InternalTimer = {
8
+ [SYMBOL]: TimerState;
9
+ } & Timer;
10
+
11
+ // #endregion
12
+
13
+ // #region Instances
14
+
15
+ function Timer(
16
+ this: any,
7
17
  name: TimerName,
8
- pick: Pick<TimerState, 'callback' | 'trace'>,
18
+ state: Pick<TimerState, 'callback' | 'trace'>,
9
19
  options: TimerOptions,
10
20
  start: boolean,
11
- ): Timer {
12
- const state: TimerState = {
13
- ...pick,
14
- name,
15
- options,
16
- active: false,
17
- destroyed: false,
18
- elapsed: 0,
19
- frame: undefined,
20
- index: 0,
21
- paused: false,
22
- timer: undefined as never,
23
- total: 0,
24
- };
25
-
26
- const instance = {
27
- continue: () => work(WORK_CONTINUE, state),
28
- destroy: noop,
29
- pause: () => work(WORK_PAUSE, state),
30
- restart: () => work(WORK_RESTART, state),
31
- start: () => work(WORK_START, state),
32
- stop: () => work(WORK_STOP, state),
33
- };
34
-
35
- Object.defineProperties(instance, {
36
- $timer: {
37
- enumerable: false,
38
- value: name,
39
- },
40
- active: {
41
- enumerable: true,
42
- get: () => state.active && !state.paused,
21
+ ) {
22
+ Object.defineProperty(this, SYMBOL, {
23
+ value: {
24
+ ...state,
25
+ name,
26
+ options,
27
+ active: false,
28
+ destroyed: false,
29
+ elapsed: 0,
30
+ frame: undefined,
31
+ index: 0,
32
+ paused: false,
33
+ timer: undefined as never,
34
+ total: 0,
43
35
  },
44
- destroyed: {
45
- enumerable: true,
46
- value: false,
36
+ });
37
+
38
+ if (start) {
39
+ startTimer.call(this);
40
+ }
41
+ }
42
+
43
+ Object.defineProperties(Timer.prototype, {
44
+ active: {
45
+ enumerable: true,
46
+ get() {
47
+ return isActiveTimer.call(this);
47
48
  },
48
- paused: {
49
- enumerable: true,
50
- get: () => state.paused,
49
+ },
50
+ continue: {
51
+ value: continueTimer,
52
+ },
53
+ pause: {
54
+ value: pauseTimer,
55
+ },
56
+ paused: {
57
+ enumerable: true,
58
+ get() {
59
+ return isPausedTimer.call(this);
51
60
  },
52
- trace: {
53
- enumerable: true,
54
- get: () => ((globalThis._oscarpalmer_timer_debug ?? false) ? state.trace : undefined),
61
+ },
62
+ restart: {
63
+ value: restartTimer,
64
+ },
65
+ start: {
66
+ value: startTimer,
67
+ },
68
+ stop: {
69
+ value: stopTimer,
70
+ },
71
+ trace: {
72
+ get() {
73
+ return getTimerTrace.call(this);
55
74
  },
56
- });
75
+ },
76
+ });
57
77
 
58
- state.timer = Object.freeze(instance) as Timer;
78
+ // #endregion
59
79
 
60
- if (start) {
61
- state.timer.start();
62
- }
80
+ // #region Functions
81
+
82
+ function continueTimer(this: InternalTimer): Timer {
83
+ return work(WORK_CONTINUE, this[SYMBOL]);
84
+ }
85
+
86
+ export function createTimer(
87
+ name: TimerName,
88
+ state: Pick<TimerState, 'callback' | 'trace'>,
89
+ options: TimerOptions,
90
+ start: boolean,
91
+ ): Timer {
92
+ // @ts-expect-error All good, no worries :-)
93
+ return new Timer(name, state, options, start);
94
+ }
95
+
96
+ function getTimerTrace(this: InternalTimer): string | undefined {
97
+ return (globalThis._oscarpalmer_timer_debug ?? false) ? this[SYMBOL].trace : undefined;
98
+ }
99
+
100
+ function isActiveTimer(this: InternalTimer): boolean {
101
+ return this[SYMBOL].active && !this[SYMBOL].paused;
102
+ }
63
103
 
64
- return state.timer;
104
+ function isPausedTimer(this: InternalTimer): boolean {
105
+ return this[SYMBOL].paused;
65
106
  }
107
+
108
+ function pauseTimer(this: InternalTimer): Timer {
109
+ return work(WORK_PAUSE, this[SYMBOL]);
110
+ }
111
+
112
+ function restartTimer(this: InternalTimer): Timer {
113
+ return work(WORK_RESTART, this[SYMBOL]);
114
+ }
115
+
116
+ function startTimer(this: InternalTimer): Timer {
117
+ return work(WORK_START, this[SYMBOL]);
118
+ }
119
+
120
+ function stopTimer(this: InternalTimer): Timer {
121
+ return work(WORK_STOP, this[SYMBOL]);
122
+ }
123
+
124
+ // #endregion
package/src/wait.ts CHANGED
@@ -4,6 +4,8 @@ import './global';
4
4
  import {type Timer, TimerTrace} from './models';
5
5
  import {createTimer} from './timer';
6
6
 
7
+ // #region Functions
8
+
7
9
  /**
8
10
  * Create a waiting timer
9
11
  *
@@ -28,3 +30,5 @@ export function wait(callback: () => void, time?: number): Timer {
28
30
  true,
29
31
  );
30
32
  }
33
+
34
+ // #endregion
package/src/when.ts CHANGED
@@ -1,7 +1,13 @@
1
- import {noop} from '@oscarpalmer/atoms/function';
2
- import {MESSAGE_STARTED, TYPE_WHEN, WORK_CONTINUE, WORK_PAUSE, WORK_STOP} from './constants';
3
- import {getValidNumber, getValidTimeout} from './misc';
1
+ import {
2
+ MESSAGE_STARTED,
3
+ SYMBOL,
4
+ TYPE_WHEN,
5
+ WORK_CONTINUE,
6
+ WORK_PAUSE,
7
+ WORK_STOP,
8
+ } from './constants';
4
9
  import './global';
10
+ import {getValidNumber, getValidTimeout} from './misc';
5
11
  import {
6
12
  TimerTrace,
7
13
  type When,
@@ -11,6 +17,110 @@ import {
11
17
  } from './models';
12
18
  import {createTimer} from './timer';
13
19
 
20
+ // #region Types
21
+
22
+ type InternalWhen = {
23
+ [SYMBOL]: WhenState;
24
+ } & When;
25
+
26
+ // #endregion
27
+
28
+ // #region Instances
29
+
30
+ function When(this: any, condition: () => boolean, options: WhenOptions) {
31
+ const state: WhenState = {
32
+ name: TYPE_WHEN,
33
+ promise: undefined as never,
34
+ result: false,
35
+ started: false,
36
+ timer: undefined as never,
37
+ };
38
+
39
+ Object.defineProperty(this, SYMBOL, {
40
+ value: state,
41
+ });
42
+
43
+ state.promise = new Promise<void>((resolve, reject) => {
44
+ state.resolver = resolve;
45
+ state.rejecter = reject;
46
+ });
47
+
48
+ state.timer = createTimer(
49
+ TYPE_WHEN,
50
+ {
51
+ callback: () => onCallback(condition, state),
52
+ trace: new TimerTrace().stack,
53
+ },
54
+ {
55
+ ...options,
56
+ onAfter: () => onAfter(state),
57
+ onError: () => state.rejecter?.(),
58
+ },
59
+ false,
60
+ );
61
+ }
62
+
63
+ Object.defineProperties(When.prototype, {
64
+ active: {
65
+ get() {
66
+ return isActiveWhen.call(this);
67
+ },
68
+ },
69
+ continue: {
70
+ value: continueWhen,
71
+ },
72
+ pause: {
73
+ value: pauseWhen,
74
+ },
75
+ paused: {
76
+ get() {
77
+ return isPausedWhen.call(this);
78
+ },
79
+ },
80
+ start: {
81
+ value: startWhen,
82
+ },
83
+ stop: {
84
+ value: stopWhen,
85
+ },
86
+ trace: {
87
+ get() {
88
+ return getWhenTrace.call(this);
89
+ },
90
+ },
91
+ });
92
+
93
+ // #endregion
94
+
95
+ // #region Functions
96
+
97
+ function continueWhen(this: InternalWhen): When {
98
+ return onWhen(WORK_CONTINUE, this, this[SYMBOL]);
99
+ }
100
+
101
+ function getWhenOptions(input: unknown): WhenOptions {
102
+ const options =
103
+ typeof input === 'object' && input !== null ? (input as Partial<WhenOptions>) : {};
104
+
105
+ return {
106
+ count: getValidNumber(options?.count),
107
+ interval: getValidNumber(options?.interval),
108
+ timeout: getValidTimeout(options?.timeout),
109
+ };
110
+ }
111
+
112
+ function getWhenTrace(this: InternalWhen): string | undefined {
113
+ return (globalThis._oscarpalmer_timer_debug ?? false) ? this[SYMBOL].timer?.trace : undefined;
114
+ }
115
+
116
+ function isActiveWhen(this: InternalWhen): boolean {
117
+ return this[SYMBOL].timer.active;
118
+ }
119
+
120
+ function isPausedWhen(this: InternalWhen): boolean {
121
+ return this[SYMBOL].timer.paused;
122
+ }
123
+
14
124
  function onAfter(state: WhenState): void {
15
125
  if (state.result) {
16
126
  state.resolver?.();
@@ -37,7 +147,13 @@ function onWhen(type: WorkHandlerType, instance: When, state: WhenState): When {
37
147
  return instance;
38
148
  }
39
149
 
40
- function startWhen(state: WhenState, resolve?: (() => void) | null): Promise<void> {
150
+ function pauseWhen(this: InternalWhen): When {
151
+ return onWhen(WORK_PAUSE, this, this[SYMBOL]);
152
+ }
153
+
154
+ function startWhen(this: InternalWhen, resolve?: (() => void) | null): Promise<void> {
155
+ const state = this[SYMBOL];
156
+
41
157
  if (state.started) {
42
158
  throw new Error(MESSAGE_STARTED);
43
159
  }
@@ -49,6 +165,10 @@ function startWhen(state: WhenState, resolve?: (() => void) | null): Promise<voi
49
165
  return state.promise.then(resolve);
50
166
  }
51
167
 
168
+ function stopWhen(this: InternalWhen): When {
169
+ return onWhen(WORK_STOP, this, this[SYMBOL]);
170
+ }
171
+
52
172
  /**
53
173
  * Create a conditional timer
54
174
  * @param condition Condition to check
@@ -56,66 +176,8 @@ function startWhen(state: WhenState, resolve?: (() => void) | null): Promise<voi
56
176
  * @returns Timer instance
57
177
  */
58
178
  export function when(condition: () => boolean, options?: Partial<WhenOptions>): When {
59
- const state: WhenState = {
60
- promise: undefined as never,
61
- result: false,
62
- started: false,
63
- timer: undefined as never,
64
- };
65
-
66
- let instance: When;
67
-
68
- state.promise = new Promise<void>((resolve, reject) => {
69
- state.resolver = resolve;
70
- state.rejecter = reject;
71
- });
72
-
73
- state.timer = createTimer(
74
- TYPE_WHEN,
75
- {
76
- callback: () => onCallback(condition, state),
77
- trace: new TimerTrace().stack,
78
- },
79
- {
80
- onAfter: () => onAfter(state),
81
- onError: () => state.rejecter?.(),
82
- count: getValidNumber(options?.count),
83
- interval: getValidNumber(options?.interval),
84
- timeout: getValidTimeout(options?.timeout),
85
- },
86
- false,
87
- );
88
-
89
- instance = {
90
- continue: () => onWhen(WORK_CONTINUE, instance, state),
91
- destroy: noop,
92
- pause: () => onWhen(WORK_PAUSE, instance, state),
93
- start: (resolve: never) => startWhen(state, resolve),
94
- stop: () => onWhen(WORK_STOP, instance, state),
95
- } as When;
96
-
97
- Object.defineProperties(instance, {
98
- $timer: {
99
- enumerable: false,
100
- value: TYPE_WHEN,
101
- },
102
- active: {
103
- enumerable: true,
104
- get: () => state.timer.active,
105
- },
106
- destroyed: {
107
- enumerable: true,
108
- value: false,
109
- },
110
- paused: {
111
- enumerable: true,
112
- get: () => state.timer.paused,
113
- },
114
- trace: {
115
- enumerable: true,
116
- get: () => ((globalThis._oscarpalmer_timer_debug ?? false) ? state.timer?.trace : undefined),
117
- },
118
- });
119
-
120
- return Object.freeze(instance) as When;
179
+ // @ts-expect-error All good, no worries :-)
180
+ return new When(condition, getWhenOptions(options));
121
181
  }
182
+
183
+ // #endregion
package/src/work.ts CHANGED
@@ -7,13 +7,15 @@ import {
7
7
  WORK_START,
8
8
  WORK_STOP,
9
9
  } from './constants';
10
- import {updateStates} from './misc';
10
+ import {startTimer, stopTimer, updateStates} from './misc';
11
11
  import type {Timer, TimerState, WorkHandlerType} from './models';
12
12
 
13
+ // #region Functions
14
+
13
15
  function finish(state: TimerState, success: boolean): void {
14
16
  updateStates(state);
15
17
 
16
- cancelAnimationFrame(state.frame as never);
18
+ stopTimer(state.frame as never);
17
19
 
18
20
  state.active = false;
19
21
  state.elapsed = 0;
@@ -34,14 +36,16 @@ function ignore(type: WorkHandlerType, state: TimerState): boolean {
34
36
  return state.active && (type === WORK_CONTINUE || type === WORK_START);
35
37
  }
36
38
 
37
- function run(state: TimerState): (now: DOMHighResTimeStamp) => void {
38
- let last: DOMHighResTimeStamp | undefined;
39
+ function run(state: TimerState): () => void {
40
+ let last: number | undefined;
39
41
 
40
- return function step(now: DOMHighResTimeStamp): void {
42
+ return function step(): void {
41
43
  if (!state.active) {
42
44
  return;
43
45
  }
44
46
 
47
+ const now = performance.now();
48
+
45
49
  last ??= now;
46
50
 
47
51
  const difference = now - last;
@@ -77,7 +81,7 @@ function run(state: TimerState): (now: DOMHighResTimeStamp) => void {
77
81
  }
78
82
  }
79
83
 
80
- state.frame = requestAnimationFrame(step);
84
+ state.frame = startTimer(step);
81
85
  };
82
86
  }
83
87
 
@@ -92,7 +96,7 @@ function setState(type: WorkHandlerType, state: TimerState): void {
92
96
  export function stop(state: TimerState): Timer {
93
97
  updateStates(state);
94
98
 
95
- cancelAnimationFrame(state.frame as never);
99
+ stopTimer(state.frame as never);
96
100
 
97
101
  state.options.onAfter?.(false);
98
102
 
@@ -115,7 +119,7 @@ export function work(type: WorkHandlerType, state: TimerState, hide?: boolean):
115
119
  }
116
120
 
117
121
  if (type === WORK_PAUSE || type === WORK_RESTART) {
118
- cancelAnimationFrame(state.frame as never);
122
+ stopTimer(state.frame as never);
119
123
 
120
124
  state.frame = undefined;
121
125
  }
@@ -131,7 +135,9 @@ export function work(type: WorkHandlerType, state: TimerState, hide?: boolean):
131
135
 
132
136
  const runner = run(state);
133
137
 
134
- state.frame = requestAnimationFrame(runner);
138
+ state.frame = startTimer(runner);
135
139
 
136
140
  return state.timer;
137
141
  }
142
+
143
+ // #endregion