@oscarpalmer/timer 0.48.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/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