@oscarpalmer/timer 0.48.0 → 0.50.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.mjs CHANGED
@@ -1,10 +1,9 @@
1
- import { WORK_CONTINUE, WORK_PAUSE, WORK_RESTART, WORK_START, WORK_STOP } from "./constants.mjs";
1
+ import { SYMBOL, WORK_CONTINUE, WORK_PAUSE, WORK_RESTART, WORK_START, WORK_STOP } from "./constants.mjs";
2
2
  import { work } from "./work.mjs";
3
- import { noop } from "@oscarpalmer/atoms/function";
4
3
  //#region src/timer.ts
5
- function createTimer(name, pick, options, start) {
6
- const state = {
7
- ...pick,
4
+ function Timer(name, state, options, start) {
5
+ this[SYMBOL] = {
6
+ ...state,
8
7
  name,
9
8
  options,
10
9
  active: false,
@@ -16,39 +15,53 @@ function createTimer(name, pick, options, start) {
16
15
  timer: void 0,
17
16
  total: 0
18
17
  };
19
- const instance = {
20
- continue: () => work(WORK_CONTINUE, state),
21
- destroy: noop,
22
- pause: () => work(WORK_PAUSE, state),
23
- restart: () => work(WORK_RESTART, state),
24
- start: () => work(WORK_START, state),
25
- stop: () => work(WORK_STOP, state)
26
- };
27
- Object.defineProperties(instance, {
28
- $timer: {
29
- enumerable: false,
30
- value: name
31
- },
32
- active: {
33
- enumerable: true,
34
- get: () => state.active && !state.paused
35
- },
36
- destroyed: {
37
- enumerable: true,
38
- value: false
39
- },
40
- paused: {
41
- enumerable: true,
42
- get: () => state.paused
43
- },
44
- trace: {
45
- enumerable: true,
46
- get: () => globalThis._oscarpalmer_timer_debug ?? false ? state.trace : void 0
47
- }
48
- });
49
- state.timer = Object.freeze(instance);
50
- if (start) state.timer.start();
51
- return state.timer;
18
+ if (start) startTimer.call(this);
19
+ }
20
+ Timer.prototype.continue = continueTimer;
21
+ Timer.prototype.pause = pauseTimer;
22
+ Timer.prototype.restart = restartTimer;
23
+ Timer.prototype.start = startTimer;
24
+ Timer.prototype.stop = stopTimer;
25
+ Object.defineProperties(Timer.prototype, {
26
+ active: {
27
+ enumerable: true,
28
+ get: getTimerActive
29
+ },
30
+ paused: {
31
+ enumerable: true,
32
+ get: getTimerPaused
33
+ },
34
+ trace: {
35
+ enumerable: true,
36
+ get: getTimerTrace
37
+ }
38
+ });
39
+ function continueTimer() {
40
+ return work(WORK_CONTINUE, this[SYMBOL]);
41
+ }
42
+ function createTimer(name, state, options, start) {
43
+ return new Timer(name, state, options, start);
44
+ }
45
+ function getTimerActive() {
46
+ return this[SYMBOL].active && !this[SYMBOL].paused;
47
+ }
48
+ function getTimerPaused() {
49
+ return this[SYMBOL].paused;
50
+ }
51
+ function getTimerTrace() {
52
+ return globalThis._oscarpalmer_timer_debug ?? false ? this[SYMBOL].trace : void 0;
53
+ }
54
+ function pauseTimer() {
55
+ return work(WORK_PAUSE, this[SYMBOL]);
56
+ }
57
+ function restartTimer() {
58
+ return work(WORK_RESTART, this[SYMBOL]);
59
+ }
60
+ function startTimer() {
61
+ return work(WORK_START, this[SYMBOL]);
62
+ }
63
+ function stopTimer() {
64
+ return work(WORK_STOP, this[SYMBOL]);
52
65
  }
53
66
  //#endregion
54
67
  export { createTimer };
package/dist/wait.d.mts CHANGED
@@ -8,6 +8,5 @@ import "./global.mjs";
8
8
  * @param time How long to wait for _(in milliseconds; defaults to screen refresh rate)_
9
9
  * @returns Waiting timer
10
10
  */
11
- declare function wait(callback: () => void, time?: number): Timer;
12
- //#endregion
13
- export { wait };
11
+ export declare function wait(callback: () => void, time?: number): Timer;
12
+ //#endregion
package/dist/when.d.mts CHANGED
@@ -1,12 +1,12 @@
1
1
  import { When, WhenOptions } from "./models.mjs";
2
2
  import "./global.mjs";
3
3
  //#region src/when.d.ts
4
+ declare function When(this: any, condition: () => boolean, options: WhenOptions): void;
4
5
  /**
5
6
  * Create a conditional timer
6
7
  * @param condition Condition to check
7
8
  * @param options Timer options
8
9
  * @returns Timer instance
9
10
  */
10
- declare function when(condition: () => boolean, options?: Partial<WhenOptions>): When;
11
- //#endregion
12
- export { when };
11
+ export declare function when(condition: () => boolean, options?: Partial<WhenOptions>): When;
12
+ //#endregion
package/dist/when.mjs CHANGED
@@ -1,10 +1,60 @@
1
- import { MESSAGE_STARTED, TYPE_WHEN, WORK_CONTINUE, WORK_PAUSE, WORK_STOP } from "./constants.mjs";
1
+ import { MESSAGE_STARTED, SYMBOL, TYPE_WHEN, WORK_CONTINUE, WORK_PAUSE, WORK_STOP } from "./constants.mjs";
2
2
  import { getValidNumber, getValidTimeout } from "./misc.mjs";
3
3
  import "./global.mjs";
4
4
  import { TimerTrace } from "./models.mjs";
5
5
  import { createTimer } from "./timer.mjs";
6
- import { noop } from "@oscarpalmer/atoms/function";
7
6
  //#region src/when.ts
7
+ function When(condition, options) {
8
+ const state = {
9
+ name: TYPE_WHEN,
10
+ promise: void 0,
11
+ result: false,
12
+ started: false,
13
+ timer: void 0
14
+ };
15
+ this[SYMBOL] = state;
16
+ state.promise = new Promise((resolve, reject) => {
17
+ state.resolver = resolve;
18
+ state.rejecter = reject;
19
+ });
20
+ state.timer = createTimer(TYPE_WHEN, {
21
+ callback: () => onCallback(condition, state),
22
+ trace: new TimerTrace().stack
23
+ }, {
24
+ ...options,
25
+ onAfter: () => onAfter(state),
26
+ onError: () => state.rejecter?.()
27
+ }, false);
28
+ }
29
+ When.prototype.continue = continueWhen;
30
+ When.prototype.pause = pauseWhen;
31
+ When.prototype.start = startWhen;
32
+ When.prototype.stop = stopWhen;
33
+ Object.defineProperties(When.prototype, {
34
+ active: { get: getWhenActive },
35
+ paused: { get: getWhenPaused },
36
+ trace: { get: getWhenTrace }
37
+ });
38
+ function continueWhen() {
39
+ return onWhen(WORK_CONTINUE, this, this[SYMBOL]);
40
+ }
41
+ function getWhenOptions(input) {
42
+ const options = typeof input === "object" && input !== null ? input : {};
43
+ return {
44
+ count: getValidNumber(options?.count),
45
+ interval: getValidNumber(options?.interval),
46
+ timeout: getValidTimeout(options?.timeout)
47
+ };
48
+ }
49
+ function getWhenActive() {
50
+ return this[SYMBOL].timer.active;
51
+ }
52
+ function getWhenPaused() {
53
+ return this[SYMBOL].timer.paused;
54
+ }
55
+ function getWhenTrace() {
56
+ return globalThis._oscarpalmer_timer_debug ?? false ? this[SYMBOL].timer?.trace : void 0;
57
+ }
8
58
  function onAfter(state) {
9
59
  if (state.result) state.resolver?.();
10
60
  else state.rejecter?.();
@@ -23,12 +73,19 @@ function onWhen(type, instance, state) {
23
73
  state.timer?.[type]?.();
24
74
  return instance;
25
75
  }
26
- function startWhen(state, resolve) {
76
+ function pauseWhen() {
77
+ return onWhen(WORK_PAUSE, this, this[SYMBOL]);
78
+ }
79
+ function startWhen(resolve) {
80
+ const state = this[SYMBOL];
27
81
  if (state.started) throw new Error(MESSAGE_STARTED);
28
82
  state.started = true;
29
83
  state.timer.start();
30
84
  return state.promise.then(resolve);
31
85
  }
86
+ function stopWhen() {
87
+ return onWhen(WORK_STOP, this, this[SYMBOL]);
88
+ }
32
89
  /**
33
90
  * Create a conditional timer
34
91
  * @param condition Condition to check
@@ -36,57 +93,7 @@ function startWhen(state, resolve) {
36
93
  * @returns Timer instance
37
94
  */
38
95
  function when(condition, options) {
39
- const state = {
40
- promise: void 0,
41
- result: false,
42
- started: false,
43
- timer: void 0
44
- };
45
- let instance;
46
- state.promise = new Promise((resolve, reject) => {
47
- state.resolver = resolve;
48
- state.rejecter = reject;
49
- });
50
- state.timer = createTimer(TYPE_WHEN, {
51
- callback: () => onCallback(condition, state),
52
- trace: new TimerTrace().stack
53
- }, {
54
- onAfter: () => onAfter(state),
55
- onError: () => state.rejecter?.(),
56
- count: getValidNumber(options?.count),
57
- interval: getValidNumber(options?.interval),
58
- timeout: getValidTimeout(options?.timeout)
59
- }, false);
60
- instance = {
61
- continue: () => onWhen(WORK_CONTINUE, instance, state),
62
- destroy: noop,
63
- pause: () => onWhen(WORK_PAUSE, instance, state),
64
- start: (resolve) => startWhen(state, resolve),
65
- stop: () => onWhen(WORK_STOP, instance, state)
66
- };
67
- Object.defineProperties(instance, {
68
- $timer: {
69
- enumerable: false,
70
- value: TYPE_WHEN
71
- },
72
- active: {
73
- enumerable: true,
74
- get: () => state.timer.active
75
- },
76
- destroyed: {
77
- enumerable: true,
78
- value: false
79
- },
80
- paused: {
81
- enumerable: true,
82
- get: () => state.timer.paused
83
- },
84
- trace: {
85
- enumerable: true,
86
- get: () => globalThis._oscarpalmer_timer_debug ?? false ? state.timer?.trace : void 0
87
- }
88
- });
89
- return Object.freeze(instance);
96
+ return new When(condition, getWhenOptions(options));
90
97
  }
91
98
  //#endregion
92
99
  export { when };
package/dist/work.d.mts CHANGED
@@ -1,6 +1,5 @@
1
1
  import { Timer, TimerState, WorkHandlerType } from "./models.mjs";
2
2
  //#region src/work.d.ts
3
- declare function stop(state: TimerState): Timer;
4
- declare function work(type: WorkHandlerType, state: TimerState, hide?: boolean): Timer;
5
- //#endregion
6
- export { stop, work };
3
+ export declare function stop(state: TimerState): Timer;
4
+ export declare function work(type: WorkHandlerType, state: TimerState, hide?: boolean): Timer;
5
+ //#endregion
package/dist/work.mjs CHANGED
@@ -1,9 +1,9 @@
1
1
  import { WORK_PAUSE } from "./constants.mjs";
2
- import { updateStates } from "./misc.mjs";
2
+ import { startTimer, stopTimer, updateStates } from "./misc.mjs";
3
3
  //#region src/work.ts
4
4
  function finish(state, success) {
5
5
  updateStates(state);
6
- cancelAnimationFrame(state.frame);
6
+ stopTimer(state.frame);
7
7
  state.active = false;
8
8
  state.elapsed = 0;
9
9
  state.frame = void 0;
@@ -16,8 +16,9 @@ function ignore(type, state) {
16
16
  }
17
17
  function run(state) {
18
18
  let last;
19
- return function step(now) {
19
+ return function step() {
20
20
  if (!state.active) return;
21
+ const now = performance.now();
21
22
  last ??= now;
22
23
  const difference = now - last;
23
24
  state.elapsed += difference;
@@ -37,7 +38,7 @@ function run(state) {
37
38
  return;
38
39
  }
39
40
  }
40
- state.frame = requestAnimationFrame(step);
41
+ state.frame = startTimer(step);
41
42
  };
42
43
  }
43
44
  function setState(type, state) {
@@ -48,7 +49,7 @@ function setState(type, state) {
48
49
  }
49
50
  function stop(state) {
50
51
  updateStates(state);
51
- cancelAnimationFrame(state.frame);
52
+ stopTimer(state.frame);
52
53
  state.options.onAfter?.(false);
53
54
  state.active = false;
54
55
  state.frame = void 0;
@@ -60,7 +61,7 @@ function work(type, state, hide) {
60
61
  setState(type, state);
61
62
  if (type === "stop") return stop(state);
62
63
  if (type === "pause" || type === "restart") {
63
- cancelAnimationFrame(state.frame);
64
+ stopTimer(state.frame);
64
65
  state.frame = void 0;
65
66
  }
66
67
  state.active = true;
@@ -68,7 +69,7 @@ function work(type, state, hide) {
68
69
  updateStates(state, state.paused ? hide : false);
69
70
  if (state.paused) return state.timer;
70
71
  const runner = run(state);
71
- state.frame = requestAnimationFrame(runner);
72
+ state.frame = startTimer(runner);
72
73
  return state.timer;
73
74
  }
74
75
  //#endregion
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oscarpalmer/timer",
3
- "version": "0.48.0",
3
+ "version": "0.50.0",
4
4
  "description": "A better solution for timeout- and interval-based timers.",
5
5
  "keywords": [
6
6
  "requestAnimationFrame",
@@ -60,13 +60,13 @@
60
60
  "watch": "npx vite build --watch"
61
61
  },
62
62
  "dependencies": {
63
- "@oscarpalmer/atoms": "^0.199"
63
+ "@oscarpalmer/atoms": "^0.204"
64
64
  },
65
65
  "devDependencies": {
66
- "@oxlint/plugins": "^1.81",
67
- "@types/node": "^26.4",
66
+ "@oxlint/plugins": "^1.83",
67
+ "@types/node": "^26.5",
68
68
  "@vitest/coverage-istanbul": "^4.1",
69
- "jsdom": "^30",
69
+ "jsdom": "^30.1",
70
70
  "tsdown": "^0.23",
71
71
  "typescript": "^6",
72
72
  "vite": "npm:@voidzero-dev/vite-plus-core@latest",
package/src/constants.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import type {TimerName, TimerStates, WorkHandlerType} from './models';
2
2
 
3
+ // #region Variables
4
+
3
5
  /**
4
6
  * Buffer value to use when evaluating if a specific time is within a certain range
5
7
  */
@@ -19,6 +21,8 @@ export const STATES: TimerStates = {
19
21
  hidden: new Set(),
20
22
  };
21
23
 
24
+ export const SYMBOL = Symbol('timer');
25
+
22
26
  export const TYPE_REPEAT: TimerName = 'repeat';
23
27
 
24
28
  export const TYPE_WAIT: TimerName = 'wait';
@@ -34,3 +38,5 @@ export const WORK_RESTART: WorkHandlerType = 'restart';
34
38
  export const WORK_START: WorkHandlerType = 'start';
35
39
 
36
40
  export const WORK_STOP: WorkHandlerType = 'stop';
41
+
42
+ // #endregion
package/src/global.ts CHANGED
@@ -2,6 +2,8 @@ import {GLOBAL_NAME, STATES} from './constants';
2
2
  import {onVisibilityChange} from './misc';
3
3
  import type {Timer} from './models';
4
4
 
5
+ // #region Types
6
+
5
7
  declare global {
6
8
  var _oscarpalmer_timer_debug: boolean | undefined;
7
9
  /**
@@ -10,6 +12,10 @@ declare global {
10
12
  var _oscarpalmer_timers: Timer[] | undefined;
11
13
  }
12
14
 
15
+ // #endregion
16
+
17
+ // #region Initialization
18
+
13
19
  /* istanbul ignore next */
14
20
  if (!(GLOBAL_NAME in globalThis)) {
15
21
  Object.defineProperty(globalThis, GLOBAL_NAME, {
@@ -21,4 +27,9 @@ if (!(GLOBAL_NAME in globalThis)) {
21
27
  });
22
28
  }
23
29
 
24
- document.addEventListener('visibilitychange', onVisibilityChange);
30
+ /* istanbul ignore next */
31
+ if ('document' in globalThis) {
32
+ document.addEventListener('visibilitychange', onVisibilityChange);
33
+ }
34
+
35
+ // #endregion
package/src/is.ts CHANGED
@@ -1,47 +1,55 @@
1
- import type {PlainObject} from '@oscarpalmer/atoms/models';
2
- import {TYPE_REPEAT, TYPE_WAIT, TYPE_WHEN} from './constants';
1
+ import {SYMBOL, TYPE_REPEAT, TYPE_WAIT, TYPE_WHEN} from './constants';
3
2
  import type {Timer, When} from './models';
4
3
 
5
- function is(names: string[], value: unknown) {
6
- return names.includes((value as PlainObject)?.$timer as string);
4
+ // #region Functions
5
+
6
+ function isInstance(names: string[], value: unknown) {
7
+ return (
8
+ typeof value === 'object' &&
9
+ value !== null &&
10
+ SYMBOL in value &&
11
+ names.includes((value as any)[SYMBOL].name as string)
12
+ );
7
13
  }
8
14
 
9
15
  /**
10
16
  * Is the value a repeating timer?
11
- *
17
+ *
12
18
  * @param value Value to check
13
19
  * @returns `true` if the value is a repeating timer, otherwise `false`
14
20
  */
15
21
  export function isRepeated(value: unknown): value is Timer {
16
- return is([TYPE_REPEAT], value);
22
+ return isInstance([TYPE_REPEAT], value);
17
23
  }
18
24
 
19
25
  /**
20
26
  * Is the value a timer?
21
- *
27
+ *
22
28
  * @param value Value to check
23
29
  * @returns `true` if the value is a timer, otherwise `false`
24
30
  */
25
31
  export function isTimer(value: unknown): value is Timer {
26
- return is([TYPE_REPEAT, TYPE_WAIT], value);
32
+ return isInstance([TYPE_REPEAT, TYPE_WAIT], value);
27
33
  }
28
34
 
29
35
  /**
30
36
  * Is the value a waiting timer?
31
- *
37
+ *
32
38
  * @param value Value to check
33
39
  * @returns `true` if the value is a waiting timer, otherwise `false`
34
40
  */
35
41
  export function isWaited(value: unknown): value is Timer {
36
- return is([TYPE_WAIT], value);
42
+ return isInstance([TYPE_WAIT], value);
37
43
  }
38
44
 
39
45
  /**
40
46
  * Is the value a conditional timer?
41
- *
47
+ *
42
48
  * @param value Value to check
43
49
  * @returns `true` if the value is a conditional timer, otherwise `false`
44
50
  */
45
51
  export function isWhen(value: unknown): value is When {
46
- return is([TYPE_WHEN], value) && typeof (value as When).start === 'function';
52
+ return isInstance([TYPE_WHEN], value) && typeof (value as When).start === 'function';
47
53
  }
54
+
55
+ // #endregion
package/src/misc.ts CHANGED
@@ -4,6 +4,8 @@ import {DEFAULT_TIMEOUT, STATES, WORK_CONTINUE, WORK_PAUSE} from './constants';
4
4
  import type {TimerState} from './models';
5
5
  import {work} from './work';
6
6
 
7
+ // #region Functions
8
+
7
9
  export function getCallback(value: unknown): GenericCallback {
8
10
  return typeof value === 'function' ? (value as GenericCallback) : noop;
9
11
  }
@@ -53,3 +55,16 @@ export function updateStates(state: TimerState, hide?: boolean): void {
53
55
  STATES.active.add(state);
54
56
  }
55
57
  }
58
+
59
+ // #endregion
60
+
61
+ // #region Variables
62
+
63
+ /* istanbul ignore next */
64
+ export const startTimer =
65
+ 'requestAnimationFrame' in globalThis ? requestAnimationFrame : setTimeout;
66
+
67
+ /* istanbul ignore next */
68
+ export const stopTimer = 'cancelAnimationFrame' in globalThis ? cancelAnimationFrame : clearTimeout;
69
+
70
+ // #endregion
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