@oscarpalmer/timer 0.18.0 → 0.20.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 CHANGED
@@ -8,8 +8,6 @@ A better solution for timeout- and interval-based timers.
8
8
 
9
9
  Timer is available on _npm_ as [`@oscarpalmer/timer`](https://www.npmjs.com/package/@oscarpalmer/timer) to be bundled with your awesome projects.
10
10
 
11
- If you don't need to bundle things, you can use the CDN-version and include a script in your proejcts right away, thanks to [jsDelivr](https://www.jsdelivr.com/package/npm/@oscarpalmer/timer) and [UNPKG](https://unpkg.com/@oscarpalmer/timer).
12
-
13
11
  ## Getting started
14
12
 
15
13
  This is fairly lightweight package, so hopefully you'll be up and running in seconds :blush:
@@ -21,55 +19,62 @@ The timers can be called with nice helper methods, which also auto-starts the ti
21
19
  ```typescript
22
20
  import {repeat, wait} from '@oscarpalmer/timer';
23
21
 
24
- let waited = wait(callback, time);
25
- let repeated = repeat(callback, time, count);
22
+ const waited = wait(waitedCallback);
23
+ const repeated = repeat(repeatedCallback, 10);
26
24
  ```
27
25
 
28
- Or they can be created using class syntax, but without being auto-started:
26
+ Or they can be created using the `new`-keyword, but without being auto-started:
29
27
 
30
28
  ```typescript
31
- import {Repeated, Waited} from '@oscarpalmer/timer';
29
+ import {Timer} from '@oscarpalmer/timer';
32
30
 
33
- waited = new Waited(callback, time);
34
- repeated = new Repeated(callback, time, count);
31
+ const waited = new Timer(waitedCallback);
32
+ const repeated = new Timer(repeatedCallback, 10);
35
33
  ```
36
34
 
37
- ### CDN & IIFE
35
+ ## Parameters
38
36
 
39
- If you're using Timer by including the file suffixed with `.iife.js` or one of the CDN-versions mentioned above – you won't have to import any of the classes or methods.
37
+ When creating a _Timer_, either with the new `new`-keyword or using the functions, you can configure the timer with a few parameters:
40
38
 
41
- Instead, just include a `script`-tag in your HTML linking to Timer and you can access Timer in other scripts, as below:
39
+ |Parameter|Description|
40
+ |--------:|:----------|
41
+ |`callback`|Callback function to be invoked for each run that are __required__ for all timers.<br>For more information on callbacks, please read [the callbacks section](#callbacks).|
42
+ |`count`|How many times the timer should run.<br>If no value is provided, it will default to `1` when using the `new`-keyword and the `wait`-method, but throws an error for the `repeat`-method.|
43
+ |`time`|How many milliseconds between each invokations of the provided callback.<br>Defaults to `0`, which is not really _0_ milliseconds, but close enough :wink:|
44
+ |`after`|A callback to run after the timer finishes, both when cancelled and completed.<br>If _count_ is greater than `1` and _after_ __is not__ `undefined`, a function is expected.|
42
45
 
43
- ```javascript
44
- // With auto-start
45
- var waited = Timer.wait(callback, time);
46
- var repeated = Timer.repeat(callback, time, count);
46
+ ## Methods and properties
47
47
 
48
- // With manual start
49
- waited = new Timer.Waited(callback, time);
50
- repeated = new Timer.Repeated(callback, time, count);
51
- ```
48
+ An instance of _Timer_ also has a few helpful methods and properties:
52
49
 
53
- ## Methods
50
+ |Name|Type|Description|
51
+ |---:|----|:----------|
52
+ |`active`|_Property_|A `boolean` value to check if the timer is running|
53
+ |`finished`|_Property_|A `boolean` value to check if the timer was able to finish|
54
+ |`start()`|_Method_|Starts the timer.<br>Necessary when creating a timer using the class syntax _(e.g. `new Waited...`)_, but helpful when the timer needs to be started at other times, as well.|
55
+ |`stop()`|_Method_|Stops the timer|
56
+ |`restart()`|_Method_|Restarts the timer|
54
57
 
55
- Both the nice helper methods and the class syntax create similar objects – `Waited` and `Repeated` – which share methods:
58
+ ## Callbacks
56
59
 
57
- |Method|Description|
58
- |-----:|:----------|
59
- |`start()`|Starts the timer: necessary when creating a timer using the class syntax _(e.g. `new Waited...`)_, but helpful when the timer needs to be started at other times, as well|
60
- |`stop()`|Stops the timer|
61
- |`restart()`|Restarts the timer|
60
+ Callbacks for both waited and repeated timers receive one parameter:
62
61
 
63
- ## Callbacks
62
+ ```typescript
63
+ function callback(index) {
64
+ // 'index' is the current step
65
+ // starts at 0, goes up to a maximum of count - 1
66
+ // for this example: 0 → 9
67
+ };
68
+ ```
64
69
 
65
- When you create a repeated timer, you can also provide a fourth parameter to act as a callback to run when the timer stops, as below:
70
+ When you create a repeated timer, you can also provide a callback to run when the timer stops, as below:
66
71
 
67
72
  ```typescript
68
73
  function after(finished: boolean) {
69
74
  // Let's do something fun!
70
75
  }
71
76
 
72
- repeat(() => {}, 0, 10, after);
77
+ repeat(() => {}, 10, after);
73
78
  ```
74
79
 
75
80
  The `finished`-parameter for the `after`-function can be used to determine if the timer was stopped manually, or if it was able to finish its work.
package/dist/timer.js CHANGED
@@ -1,170 +1,111 @@
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
- };
22
- function run(timed) {
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);
1
+ // src/index.ts
2
+ var run = function(timer) {
3
+ const { _configuration, _state } = timer;
4
+ _state.active = true;
5
+ _state.finished = false;
6
+ const isRepeated = _configuration.count > 1;
7
+ let index = 0;
8
+ let start;
9
+ function step(timestamp) {
10
+ if (!_state.active) {
11
+ return;
12
+ }
13
+ start ??= timestamp;
14
+ const elapsed = timestamp - start;
15
+ const elapsedMinimum = elapsed - milliseconds;
16
+ const elapsedMaximum = elapsed + milliseconds;
17
+ if (elapsedMinimum < _configuration.time && _configuration.time < elapsedMaximum) {
18
+ if (_state.active) {
19
+ _configuration.callbacks.default(index);
20
+ }
21
+ index += 1;
22
+ if (isRepeated && index < _configuration.count) {
23
+ start = undefined;
24
+ } else {
25
+ _state.finished = true;
26
+ timer.stop();
27
+ return;
28
+ }
29
+ }
30
+ _state.frame = requestAnimationFrame(step);
31
+ }
32
+ _state.frame = requestAnimationFrame(step);
33
+ };
34
+ function repeat(callback, count, afterOrTime, after) {
35
+ if (typeof count !== "number" || count < 2) {
36
+ throw new TypeError("A repeated timer must have a number greater than or equal to 2 as its run count");
37
+ }
38
+ const afterOrTimeIsFunction = typeof afterOrTime === "function";
39
+ return new Timer(callback, afterOrTimeIsFunction ? 0 : afterOrTime, count, afterOrTimeIsFunction ? afterOrTime : after).start();
58
40
  }
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
- */
157
- function repeat(callback, time, count, afterCallback) {
158
- return new Repeated(callback, time, count, afterCallback).start();
159
- }
160
- /**
161
- * Creates and starts a new waited timer
162
- * @param {() => void} callback
163
- * @param {number} time
164
- * @return {Waited}
165
- */
166
41
  function wait(callback, time) {
167
- return new Waited(callback, time).start();
42
+ return new Timer(callback, time).start();
168
43
  }
44
+ var milliseconds = Math.round(16.666666666666668);
169
45
 
170
- export {Repeated, Waited, repeat, wait};
46
+ class Timer {
47
+ get active() {
48
+ return this._state.active;
49
+ }
50
+ get finished() {
51
+ return this._state.finished;
52
+ }
53
+ constructor(callback, time, count, afterCallback) {
54
+ if (typeof callback !== "function") {
55
+ throw new TypeError("A timer must have a callback function");
56
+ }
57
+ const actualTime = typeof time === "number" ? time : 0;
58
+ if (actualTime < 0) {
59
+ throw new TypeError("A timer must have a non-negative number as its time");
60
+ }
61
+ const actualCount = typeof count === "number" ? count : 1;
62
+ if (actualCount < 1) {
63
+ throw new TypeError("A timer must have a number greater than or equal to 1 as its run count");
64
+ }
65
+ if (actualCount > 1 && afterCallback !== undefined && typeof afterCallback !== "function") {
66
+ throw new TypeError("A repeated timer's after-callback must be a function");
67
+ }
68
+ Object.defineProperty(this, "_configuration", {
69
+ value: {
70
+ callbacks: {
71
+ after: afterCallback,
72
+ default: callback
73
+ },
74
+ count: actualCount,
75
+ time: actualTime
76
+ }
77
+ });
78
+ Object.defineProperty(this, "_state", {
79
+ value: {
80
+ active: false,
81
+ finished: false
82
+ }
83
+ });
84
+ }
85
+ restart() {
86
+ this.stop();
87
+ run(this);
88
+ return this;
89
+ }
90
+ start() {
91
+ if (!this._state.active) {
92
+ run(this);
93
+ }
94
+ return this;
95
+ }
96
+ stop() {
97
+ this._state.active = false;
98
+ if (this._state.frame === undefined) {
99
+ return this;
100
+ }
101
+ cancelAnimationFrame(this._state.frame);
102
+ this._configuration.callbacks.after?.(this._state.finished);
103
+ this._state.frame = undefined;
104
+ return this;
105
+ }
106
+ }
107
+ export {
108
+ wait,
109
+ repeat,
110
+ Timer
111
+ };
package/package.json CHANGED
@@ -3,31 +3,17 @@
3
3
  "name": "Oscar Palmér",
4
4
  "url": "https://oscarpalmer.se"
5
5
  },
6
- "browser": "dist/timer.iife.js",
7
6
  "description": "A better solution for timeout- and interval-based timers.",
8
7
  "devDependencies": {
9
- "@happy-dom/global-registrator": "^11.0",
10
- "@rollup/plugin-typescript": "^11.1",
8
+ "@biomejs/biome": "^1.4",
9
+ "@happy-dom/global-registrator": "^12.10",
11
10
  "bun": "^1.0",
12
- "prettier": "^3.0",
13
- "rollup": "^3.29",
14
- "typescript": "^5.2",
15
- "xo": "^0.56"
11
+ "typescript": "^5.3"
16
12
  },
17
- "files": [
18
- "dist",
19
- "src",
20
- "types"
21
- ],
22
- "jsdelivr": "dist/timer.iife.js",
23
- "keywords": [
24
- "timer",
25
- "setTimeout",
26
- "setInterval",
27
- "requestAnimationFrame"
28
- ],
13
+ "files": ["dist", "src", "types"],
14
+ "keywords": ["timer", "setTimeout", "setInterval", "requestAnimationFrame"],
29
15
  "license": "MIT",
30
- "main": "dist/timer.iife.js",
16
+ "main": "dist/timer.js",
31
17
  "module": "dist/timer.js",
32
18
  "name": "@oscarpalmer/timer",
33
19
  "prettier": {
@@ -43,26 +29,13 @@
43
29
  "url": "git+https://github.com/oscarpalmer/timer.git"
44
30
  },
45
31
  "scripts": {
46
- "build": "npm run build:esm && npm run build:iife",
47
- "build:esm": "rollup -c",
48
- "build:iife": "rollup -c --environment ROLLUP_FORMAT:iife",
32
+ "build": "bun build ./src/index.ts --outfile ./dist/timer.js",
49
33
  "test": "bun test --coverage",
50
- "types": "tsc ./src/index.ts --outdir ./types --declaration --emitDeclarationOnly",
34
+ "types": "bunx tsc -p ./tsconfig.json",
51
35
  "watch": "rollup -c -w",
52
36
  "xo": "xo ./src/*.ts --env browser"
53
37
  },
54
38
  "type": "module",
55
39
  "types": "src/index.d.ts",
56
- "unpkg": "dist/timer.iife.js",
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
- }
40
+ "version": "0.20.0"
41
+ }
package/src/index.ts CHANGED
@@ -1,59 +1,41 @@
1
+ export type AfterCallback = (finished: boolean) => void;
2
+
1
3
  type Callbacks = {
2
4
  after: AfterCallback | undefined;
3
- default: (index?: number) => void;
5
+ default: IndexedCallback;
4
6
  };
5
7
 
6
8
  type Configuration = {
9
+ callbacks: Callbacks;
7
10
  count: number;
8
11
  time: number;
9
12
  };
10
13
 
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;
14
+ type IndexedCallback = (index: number) => void;
20
15
 
21
16
  type State = {
22
17
  active: boolean;
23
18
  finished: boolean;
24
- frame?: DOMHighResTimeStamp;
19
+ frame?: number;
25
20
  };
26
21
 
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
22
  const milliseconds = Math.round(1000 / 60);
32
23
 
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)!;
24
+ function run(timer: Timer): void {
25
+ // @ts-expect-error Keep private status, but allow access
26
+ const {_configuration, _state} = timer;
45
27
 
46
- timedState.active = true;
47
- timedState.finished = false;
28
+ _state.active = true;
29
+ _state.finished = false;
48
30
 
49
- const isRepeated = timed instanceof Repeated;
31
+ const isRepeated = _configuration.count > 1;
50
32
 
51
33
  let index = 0;
52
34
 
53
35
  let start;
54
36
 
55
- function step(timestamp: DOMHighResTimeStamp) {
56
- if (!timedState.active) {
37
+ function step(timestamp: DOMHighResTimeStamp): void {
38
+ if (!_state.active) {
57
39
  return;
58
40
  }
59
41
 
@@ -65,39 +47,42 @@ function run(timed: Timed<never, never>): void {
65
47
  const elapsedMaximum = elapsed + milliseconds;
66
48
 
67
49
  if (
68
- elapsedMinimum < timedConfiguration.time &&
69
- timedConfiguration.time < elapsedMaximum
50
+ elapsedMinimum < _configuration.time &&
51
+ _configuration.time < elapsedMaximum
70
52
  ) {
71
- if (timedState.active) {
72
- timedCallbacks.default(isRepeated ? index : undefined);
53
+ if (_state.active) {
54
+ _configuration.callbacks.default(index);
73
55
  }
74
56
 
75
57
  index += 1;
76
58
 
77
- if (isRepeated && index < timedConfiguration.count) {
59
+ if (isRepeated && index < _configuration.count) {
78
60
  start = undefined;
79
61
  } else {
80
- timedState.finished = true;
62
+ _state.finished = true;
81
63
 
82
- timed.stop();
64
+ timer.stop();
83
65
 
84
66
  return;
85
67
  }
86
68
  }
87
69
 
88
- timedState.frame = request(step) as never;
70
+ _state.frame = requestAnimationFrame(step);
89
71
  }
90
72
 
91
- timedState.frame = request(step) as never;
73
+ _state.frame = requestAnimationFrame(step);
92
74
  }
93
75
 
94
- class Timed<Type, Callback> {
76
+ export class Timer {
77
+ private declare readonly _configuration: Configuration;
78
+ private declare readonly _state: State;
79
+
95
80
  get active(): boolean {
96
- return state.get(this as never)?.active ?? false;
81
+ return this._state.active;
97
82
  }
98
83
 
99
84
  get finished(): boolean {
100
- return !this.active && (state.get(this as never)?.finished ?? false);
85
+ return this._state.finished;
101
86
  }
102
87
 
103
88
  /**
@@ -107,33 +92,33 @@ class Timed<Type, Callback> {
107
92
  * @param {AfterCallback=} afterCallback
108
93
  */
109
94
  constructor(
110
- callback: () => void | ((index: number) => void),
111
- time: number,
112
- count: number,
95
+ callback: IndexedCallback,
96
+ time?: number,
97
+ count?: number,
113
98
  afterCallback?: AfterCallback,
114
99
  ) {
115
- const isRepeated = this instanceof Repeated;
116
-
117
- const type = isRepeated ? 'repeated' : 'waited';
118
-
119
100
  if (typeof callback !== 'function') {
120
- throw new TypeError(`A ${type} timer must have a callback function`);
101
+ throw new TypeError('A timer must have a callback function');
121
102
  }
122
103
 
123
- if (typeof time !== 'number' || time < 0) {
104
+ const actualTime = typeof time === 'number' ? time : 0;
105
+
106
+ if (actualTime < 0) {
124
107
  throw new TypeError(
125
- `A ${type} timer must have a non-negative number as its time`,
108
+ 'A timer must have a non-negative number as its time',
126
109
  );
127
110
  }
128
111
 
129
- if (isRepeated && (typeof count !== 'number' || count < 2)) {
112
+ const actualCount = typeof count === 'number' ? count : 1;
113
+
114
+ if (actualCount < 1) {
130
115
  throw new TypeError(
131
- 'A repeated timer must have a number above 1 as its repeat count',
116
+ 'A timer must have a number greater than or equal to 1 as its run count',
132
117
  );
133
118
  }
134
119
 
135
120
  if (
136
- isRepeated &&
121
+ actualCount > 1 &&
137
122
  afterCallback !== undefined &&
138
123
  typeof afterCallback !== 'function'
139
124
  ) {
@@ -142,98 +127,103 @@ class Timed<Type, Callback> {
142
127
  );
143
128
  }
144
129
 
145
- callbacks.set(this as never, {
146
- after: afterCallback,
147
- default: callback,
130
+ Object.defineProperty(this, '_configuration', {
131
+ value: {
132
+ callbacks: {
133
+ after: afterCallback,
134
+ default: callback,
135
+ },
136
+ count: actualCount,
137
+ time: actualTime,
138
+ },
148
139
  });
149
140
 
150
- configuration.set(this as never, {count, time});
151
-
152
- state.set(this as never, {
153
- active: false,
154
- finished: false,
155
- frame: undefined,
141
+ Object.defineProperty(this, '_state', {
142
+ value: {
143
+ active: false,
144
+ finished: false,
145
+ },
156
146
  });
157
147
  }
158
148
 
159
- restart(): Type {
149
+ restart(): Timer {
160
150
  this.stop();
161
151
 
162
- run(this as never);
152
+ run(this);
163
153
 
164
- return this as never;
154
+ return this;
165
155
  }
166
156
 
167
- start(): Type {
168
- if (!this.active) {
169
- run(this as never);
157
+ start(): Timer {
158
+ if (!this._state.active) {
159
+ run(this);
170
160
  }
171
161
 
172
- return this as never;
162
+ return this;
173
163
  }
174
164
 
175
- stop(): Type {
176
- const timedCallbacks = callbacks.get(this as never)!;
177
- const timedState = state.get(this as never)!;
178
-
179
- timedState.active = false;
165
+ stop(): Timer {
166
+ this._state.active = false;
180
167
 
181
- if (timedState.frame === undefined) {
182
- return this as never;
168
+ if (this._state.frame === undefined) {
169
+ return this;
183
170
  }
184
171
 
185
- (cancelAnimationFrame ?? clearTimeout)?.(timedState.frame);
186
-
187
- timedCallbacks.after?.(this.finished);
188
-
189
- timedState.frame = undefined;
172
+ cancelAnimationFrame(this._state.frame);
190
173
 
191
- return this as never;
192
- }
193
- }
174
+ this._configuration.callbacks.after?.(this._state.finished);
194
175
 
195
- /**
196
- * A timer that waits and runs repeatedly
197
- */
198
- export class Repeated extends Timed<Repeated, RepeatedCallback> {}
176
+ this._state.frame = undefined;
199
177
 
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);
178
+ return this;
211
179
  }
212
180
  }
213
181
 
214
182
  /**
215
183
  * 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
184
  */
185
+ export function repeat(callback: IndexedCallback, count: number): Timer;
222
186
  export function repeat(
223
- callback: RepeatedCallback,
187
+ callback: IndexedCallback,
188
+ count: number,
189
+ afterCallback: AfterCallback,
190
+ ): Timer;
191
+ export function repeat(
192
+ callback: IndexedCallback,
193
+ count: number,
224
194
  time: number,
195
+ ): Timer;
196
+ export function repeat(
197
+ callback: IndexedCallback,
225
198
  count: number,
226
- afterCallback?: AfterCallback,
227
- ): Repeated {
228
- return new Repeated(callback as never, time, count, afterCallback).start();
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
+ );
212
+ }
213
+
214
+ const afterOrTimeIsFunction = typeof afterOrTime === 'function';
215
+
216
+ return new Timer(
217
+ callback,
218
+ afterOrTimeIsFunction ? 0 : afterOrTime,
219
+ count,
220
+ afterOrTimeIsFunction ? afterOrTime : after,
221
+ ).start();
229
222
  }
230
223
 
231
224
  /**
232
225
  * Creates and starts a new waited timer
233
- * @param {() => void} callback
234
- * @param {number} time
235
- * @return {Waited}
236
226
  */
237
- export function wait(callback: () => void, time: number): Waited {
238
- return new Waited(callback, time).start();
227
+ export function wait(callback: IndexedCallback, time?: number): Timer {
228
+ return new Timer(callback, time).start();
239
229
  }
package/types/index.d.ts CHANGED
@@ -1,72 +1,30 @@
1
- /**
2
- * @param {boolean} finished Did the timer finish?
3
- */
4
1
  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);
2
+ type IndexedCallback = (index: number) => void;
3
+ export declare class Timer {
4
+ private readonly _configuration;
5
+ private readonly _state;
6
+ get active(): boolean;
7
+ get finished(): boolean;
8
+ /**
9
+ * @param {Callback} callback
10
+ * @param {number} time
11
+ * @param {number} count
12
+ * @param {AfterCallback=} afterCallback
13
+ */
14
+ constructor(callback: IndexedCallback, time?: number, count?: number, afterCallback?: AfterCallback);
15
+ restart(): Timer;
16
+ start(): Timer;
17
+ stop(): Timer;
49
18
  }
50
-
51
19
  /**
52
20
  * 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
21
  */
59
- export declare function repeat(
60
- callback: RepeatedCallback,
61
- time: number,
62
- count: number,
63
- afterCallback?: AfterCallback,
64
- ): Repeated;
65
-
22
+ export declare function repeat(callback: IndexedCallback, count: number): Timer;
23
+ export declare function repeat(callback: IndexedCallback, count: number, afterCallback: AfterCallback): Timer;
24
+ export declare function repeat(callback: IndexedCallback, count: number, time: number): Timer;
25
+ export declare function repeat(callback: IndexedCallback, count: number, time: number, afterCallback: AfterCallback): Timer;
66
26
  /**
67
27
  * Creates and starts a new waited timer
68
- * @param {() => void} callback
69
- * @param {number} time
70
- * @return {Waited}
71
28
  */
72
- export declare function wait(callback: () => void, time: number): Waited;
29
+ export declare function wait(callback: IndexedCallback, time?: number): Timer;
30
+ export {};
@@ -1,169 +0,0 @@
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
- })({});