@oscarpalmer/timer 0.17.0 → 0.19.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
@@ -1,6 +1,6 @@
1
1
  # Timer
2
2
 
3
- [![npm version](https://badge.fury.io/js/@oscarpalmer%2Ftimer.svg)](https://badge.fury.io/js/@oscarpalmer%2Ftimer)
3
+ [![npm](https://badge.fury.io/js/@oscarpalmer%2Ftimer.svg)](https://www.npmjs.com/package/@oscarpalmer/timer) [![Tests](https://github.com/oscarpalmer/timer/actions/workflows/test.yml/badge.svg)](https://github.com/oscarpalmer/timer/actions/workflows/test.yml)
4
4
 
5
5
  A better solution for timeout- and interval-based timers.
6
6
 
@@ -62,6 +62,16 @@ Both the nice helper methods and the class syntax create similar objects – `Wa
62
62
 
63
63
  ## Callbacks
64
64
 
65
+ Callbacks for waited timers do not receive any arguments, but callbacks for repeated ones do:
66
+
67
+ ```typescript
68
+ repeat(index => {
69
+ // 'index' is the current step
70
+ // starts at 0, goes up to a maximum of count - 1
71
+ // for this example: 0 → 9
72
+ }, 0, 10);
73
+ ```
74
+
65
75
  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:
66
76
 
67
77
  ```typescript
@@ -1,150 +1,162 @@
1
- var Timer = (() => {
2
- var __defProp = Object.defineProperty;
3
- var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
- var __getOwnPropNames = Object.getOwnPropertyNames;
5
- var __hasOwnProp = Object.prototype.hasOwnProperty;
6
- var __export = (target, all) => {
7
- for (var name in all)
8
- __defProp(target, name, { get: all[name], enumerable: true });
9
- };
10
- var __copyProps = (to, from, except, desc) => {
11
- if (from && typeof from === "object" || typeof from === "function") {
12
- for (let key of __getOwnPropNames(from))
13
- if (!__hasOwnProp.call(to, key) && key !== except)
14
- __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
- }
16
- return to;
17
- };
18
- var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
1
+ var Timer = (function (exports) {
2
+ 'use strict';
19
3
 
20
- // src/index.js
21
- var src_exports = {};
22
- __export(src_exports, {
23
- Repeated: () => Repeated,
24
- Waited: () => Waited,
25
- repeat: () => repeat,
26
- wait: () => wait
27
- });
28
- var milliseconds = Math.round(1e3 / 60);
29
- var request = requestAnimationFrame ?? function(callback) {
30
- return setTimeout?.(() => {
31
- callback(Date.now());
32
- }, milliseconds);
33
- };
34
- function run(timed) {
35
- timed.state.active = true;
36
- timed.state.finished = false;
37
- const isRepeated = timed instanceof Repeated;
38
- let index = 0;
39
- let start;
40
- function step(timestamp) {
41
- if (!timed.state.active) {
42
- return;
43
- }
44
- start ?? (start = timestamp);
45
- const elapsed = timestamp - start;
46
- const elapsedMinimum = elapsed - milliseconds;
47
- const elapsedMaximum = elapsed + milliseconds;
48
- if (elapsedMinimum < timed.configuration.time && timed.configuration.time < elapsedMaximum) {
49
- if (timed.state.active) {
50
- timed.callbacks.default(isRepeated ? index : void 0);
51
- }
52
- index += 1;
53
- if (isRepeated && index < timed.configuration.count) {
54
- start = void 0;
55
- } else {
56
- timed.state.finished = true;
57
- timed.stop();
58
- return;
59
- }
60
- }
61
- timed.state.frame = request(step);
62
- }
63
- timed.state.frame = request(step);
64
- }
65
- var Timed = class {
66
- get active() {
67
- return this.state.active;
68
- }
69
- get finished() {
70
- return !this.active && this.state.finished;
71
- }
72
- /**
73
- * @param {RepeatedCallback} callback
74
- * @param {number} time
75
- * @param {number} count
76
- * @param {AfterCallback|undefined} afterCallback
77
- */
78
- constructor(callback, time, count, afterCallback) {
79
- const isRepeated = this instanceof Repeated;
80
- const type = isRepeated ? "repeated" : "waited";
81
- if (typeof callback !== "function") {
82
- throw new TypeError(`A ${type} timer must have a callback function`);
83
- }
84
- if (typeof time !== "number" || time < 0) {
85
- throw new TypeError(
86
- `A ${type} timer must have a non-negative number as its time`
87
- );
88
- }
89
- if (isRepeated && (typeof count !== "number" || count < 2)) {
90
- throw new TypeError(
91
- "A repeated timer must have a number above 1 as its repeat count"
92
- );
93
- }
94
- if (isRepeated && afterCallback !== void 0 && typeof afterCallback !== "function") {
95
- throw new TypeError(
96
- "A repeated timer's after-callback must be a function"
97
- );
98
- }
99
- this.configuration = { count, time };
100
- this.callbacks = {
101
- after: afterCallback,
102
- default: callback
103
- };
104
- this.state = {
105
- active: false,
106
- finished: false,
107
- frame: null
108
- };
109
- }
110
- restart() {
111
- this.stop();
112
- run(this);
113
- return this;
114
- }
115
- start() {
116
- if (!this.state.active) {
117
- run(this);
118
- }
119
- return this;
120
- }
121
- stop() {
122
- this.state.active = false;
123
- if (this.state.frame === void 0) {
124
- return this;
125
- }
126
- (cancelAnimationFrame ?? clearTimeout)?.(this.state.frame);
127
- this.callbacks.after?.(this.finished);
128
- this.state.frame = void 0;
129
- return this;
130
- }
131
- };
132
- var Repeated = class extends Timed {
133
- };
134
- var Waited = class extends Timed {
135
- /**
136
- * @param {Function} callback
137
- * @param {number} time
138
- */
139
- constructor(callback, time) {
140
- super(callback, time, 1, null);
141
- }
142
- };
143
- function repeat(callback, time, count, afterCallback) {
144
- return new Repeated(callback, time, count, afterCallback).start();
145
- }
146
- function wait(callback, time) {
147
- return new Waited(callback, time).start();
148
- }
149
- return __toCommonJS(src_exports);
150
- })();
4
+ const callbacks = new WeakMap();
5
+ const configuration = new WeakMap();
6
+ const state = new WeakMap();
7
+ const milliseconds = Math.round(1000 / 60);
8
+ function run(timed) {
9
+ const timedConfiguration = configuration.get(timed);
10
+ const timedCallbacks = callbacks.get(timed);
11
+ const timedState = state.get(timed);
12
+ timedState.active = true;
13
+ timedState.finished = false;
14
+ const isRepeated = timed instanceof Repeated;
15
+ let index = 0;
16
+ let start;
17
+ function step(timestamp) {
18
+ if (!timedState.active) {
19
+ return;
20
+ }
21
+ start ?? (start = timestamp);
22
+ const elapsed = timestamp - start;
23
+ const elapsedMinimum = elapsed - milliseconds;
24
+ const elapsedMaximum = elapsed + milliseconds;
25
+ if (
26
+ elapsedMinimum < timedConfiguration.time &&
27
+ timedConfiguration.time < elapsedMaximum
28
+ ) {
29
+ if (timedState.active) {
30
+ timedCallbacks.default(isRepeated ? index : undefined);
31
+ }
32
+ index += 1;
33
+ if (isRepeated && index < timedConfiguration.count) {
34
+ start = undefined;
35
+ } else {
36
+ timedState.finished = true;
37
+ timed.stop();
38
+ return;
39
+ }
40
+ }
41
+ timedState.frame = globalThis.requestAnimationFrame(step);
42
+ }
43
+ timedState.frame = globalThis.requestAnimationFrame(step);
44
+ }
45
+ class Timed {
46
+ get active() {
47
+ return state.get(this)?.active ?? false;
48
+ }
49
+ get finished() {
50
+ return !this.active && (state.get(this)?.finished ?? false);
51
+ }
52
+ /**
53
+ * @param {Callback} callback
54
+ * @param {number} time
55
+ * @param {number} count
56
+ * @param {AfterCallback=} afterCallback
57
+ */
58
+ constructor(callback, time, count, afterCallback) {
59
+ const isRepeated = this instanceof Repeated;
60
+ const type = isRepeated ? 'repeated' : 'waited';
61
+ if (typeof callback !== 'function') {
62
+ throw new TypeError(`A ${type} timer must have a callback function`);
63
+ }
64
+ if (typeof time !== 'number' || time < 0) {
65
+ throw new TypeError(
66
+ `A ${type} timer must have a non-negative number as its time`,
67
+ );
68
+ }
69
+ if (isRepeated && (typeof count !== 'number' || count < 2)) {
70
+ throw new TypeError(
71
+ 'A repeated timer must have a number above 1 as its repeat count',
72
+ );
73
+ }
74
+ if (
75
+ isRepeated &&
76
+ afterCallback !== undefined &&
77
+ typeof afterCallback !== 'function'
78
+ ) {
79
+ throw new TypeError(
80
+ "A repeated timer's after-callback must be a function",
81
+ );
82
+ }
83
+ callbacks.set(this, {
84
+ after: afterCallback,
85
+ default: callback,
86
+ });
87
+ configuration.set(this, {count, time});
88
+ state.set(this, {
89
+ active: false,
90
+ finished: false,
91
+ });
92
+ }
93
+ restart() {
94
+ this.stop();
95
+ run(this);
96
+ return this;
97
+ }
98
+ start() {
99
+ if (!this.active) {
100
+ run(this);
101
+ }
102
+ return this;
103
+ }
104
+ stop() {
105
+ const timedCallbacks = callbacks.get(this);
106
+ const timedState = state.get(this);
107
+ timedState.active = false;
108
+ if (timedState.frame === undefined) {
109
+ return this;
110
+ }
111
+ globalThis.cancelAnimationFrame(timedState.frame);
112
+ timedCallbacks.after?.(this.finished);
113
+ timedState.frame = undefined;
114
+ return this;
115
+ }
116
+ }
117
+ /**
118
+ * A timer that waits and runs repeatedly
119
+ */
120
+ class Repeated extends Timed {}
121
+ /**
122
+ * A timer that waits and runs once
123
+ */
124
+ class Waited extends Timed {
125
+ /**
126
+ * Creates a new waited timer
127
+ * @param {() => void} callback
128
+ * @param {number} time
129
+ */
130
+ constructor(callback, time) {
131
+ super(callback, time, 1);
132
+ }
133
+ }
134
+ /**
135
+ * Creates and starts a new repeated timer
136
+ * @param {RepeatedCallback} callback
137
+ * @param {number} time
138
+ * @param {number} count
139
+ * @param {AfterCallback=} afterCallback
140
+ * @return {Repeated}
141
+ */
142
+ function repeat(callback, time, count, afterCallback) {
143
+ return new Repeated(callback, time, count, afterCallback).start();
144
+ }
145
+ /**
146
+ * Creates and starts a new waited timer
147
+ * @param {() => void} callback
148
+ * @param {number} time
149
+ * @return {Waited}
150
+ */
151
+ function wait(callback, time) {
152
+ return new Waited(callback, time).start();
153
+ }
154
+
155
+ exports.Repeated = Repeated;
156
+ exports.Waited = Waited;
157
+ exports.repeat = repeat;
158
+ exports.wait = wait;
159
+
160
+ return exports;
161
+
162
+ })({});
package/dist/timer.js CHANGED
@@ -1,128 +1,162 @@
1
- // src/index.js
2
- var milliseconds = Math.round(1e3 / 60);
3
- var request = requestAnimationFrame ?? function(callback) {
4
- return setTimeout?.(() => {
5
- callback(Date.now());
6
- }, milliseconds);
7
- };
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);
8
15
  function run(timed) {
9
- timed.state.active = true;
10
- timed.state.finished = false;
11
- const isRepeated = timed instanceof Repeated;
12
- let index = 0;
13
- let start;
14
- function step(timestamp) {
15
- if (!timed.state.active) {
16
- return;
17
- }
18
- start ?? (start = timestamp);
19
- const elapsed = timestamp - start;
20
- const elapsedMinimum = elapsed - milliseconds;
21
- const elapsedMaximum = elapsed + milliseconds;
22
- if (elapsedMinimum < timed.configuration.time && timed.configuration.time < elapsedMaximum) {
23
- if (timed.state.active) {
24
- timed.callbacks.default(isRepeated ? index : void 0);
25
- }
26
- index += 1;
27
- if (isRepeated && index < timed.configuration.count) {
28
- start = void 0;
29
- } else {
30
- timed.state.finished = true;
31
- timed.stop();
32
- return;
33
- }
34
- }
35
- timed.state.frame = request(step);
36
- }
37
- timed.state.frame = request(step);
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 = globalThis.requestAnimationFrame(step);
49
+ }
50
+ timedState.frame = globalThis.requestAnimationFrame(step);
38
51
  }
39
- var Timed = class {
40
- get active() {
41
- return this.state.active;
42
- }
43
- get finished() {
44
- return !this.active && this.state.finished;
45
- }
46
- /**
47
- * @param {RepeatedCallback} callback
48
- * @param {number} time
49
- * @param {number} count
50
- * @param {AfterCallback|undefined} afterCallback
51
- */
52
- constructor(callback, time, count, afterCallback) {
53
- const isRepeated = this instanceof Repeated;
54
- const type = isRepeated ? "repeated" : "waited";
55
- if (typeof callback !== "function") {
56
- throw new TypeError(`A ${type} timer must have a callback function`);
57
- }
58
- if (typeof time !== "number" || time < 0) {
59
- throw new TypeError(
60
- `A ${type} timer must have a non-negative number as its time`
61
- );
62
- }
63
- if (isRepeated && (typeof count !== "number" || count < 2)) {
64
- throw new TypeError(
65
- "A repeated timer must have a number above 1 as its repeat count"
66
- );
67
- }
68
- if (isRepeated && afterCallback !== void 0 && typeof afterCallback !== "function") {
69
- throw new TypeError(
70
- "A repeated timer's after-callback must be a function"
71
- );
72
- }
73
- this.configuration = { count, time };
74
- this.callbacks = {
75
- after: afterCallback,
76
- default: callback
77
- };
78
- this.state = {
79
- active: false,
80
- finished: false,
81
- frame: null
82
- };
83
- }
84
- restart() {
85
- this.stop();
86
- run(this);
87
- return this;
88
- }
89
- start() {
90
- if (!this.state.active) {
91
- run(this);
92
- }
93
- return this;
94
- }
95
- stop() {
96
- this.state.active = false;
97
- if (this.state.frame === void 0) {
98
- return this;
99
- }
100
- (cancelAnimationFrame ?? clearTimeout)?.(this.state.frame);
101
- this.callbacks.after?.(this.finished);
102
- this.state.frame = void 0;
103
- return this;
104
- }
105
- };
106
- var Repeated = class extends Timed {
107
- };
108
- var Waited = class extends Timed {
109
- /**
110
- * @param {Function} callback
111
- * @param {number} time
112
- */
113
- constructor(callback, time) {
114
- super(callback, time, 1, null);
115
- }
116
- };
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
+ });
99
+ }
100
+ restart() {
101
+ this.stop();
102
+ run(this);
103
+ return this;
104
+ }
105
+ start() {
106
+ if (!this.active) {
107
+ run(this);
108
+ }
109
+ return this;
110
+ }
111
+ stop() {
112
+ const timedCallbacks = callbacks.get(this);
113
+ const timedState = state.get(this);
114
+ timedState.active = false;
115
+ if (timedState.frame === undefined) {
116
+ return this;
117
+ }
118
+ globalThis.cancelAnimationFrame(timedState.frame);
119
+ timedCallbacks.after?.(this.finished);
120
+ timedState.frame = undefined;
121
+ return this;
122
+ }
123
+ }
124
+ /**
125
+ * A timer that waits and runs repeatedly
126
+ */
127
+ class Repeated extends Timed {}
128
+ /**
129
+ * A timer that waits and runs once
130
+ */
131
+ class Waited extends Timed {
132
+ /**
133
+ * Creates a new waited timer
134
+ * @param {() => void} callback
135
+ * @param {number} time
136
+ */
137
+ constructor(callback, time) {
138
+ super(callback, time, 1);
139
+ }
140
+ }
141
+ /**
142
+ * Creates and starts a new repeated timer
143
+ * @param {RepeatedCallback} callback
144
+ * @param {number} time
145
+ * @param {number} count
146
+ * @param {AfterCallback=} afterCallback
147
+ * @return {Repeated}
148
+ */
117
149
  function repeat(callback, time, count, afterCallback) {
118
- return new Repeated(callback, time, count, afterCallback).start();
150
+ return new Repeated(callback, time, count, afterCallback).start();
119
151
  }
152
+ /**
153
+ * Creates and starts a new waited timer
154
+ * @param {() => void} callback
155
+ * @param {number} time
156
+ * @return {Waited}
157
+ */
120
158
  function wait(callback, time) {
121
- return new Waited(callback, time).start();
159
+ return new Waited(callback, time).start();
122
160
  }
123
- export {
124
- Repeated,
125
- Waited,
126
- repeat,
127
- wait
128
- };
161
+
162
+ export {Repeated, Waited, repeat, wait};
package/package.json CHANGED
@@ -6,12 +6,18 @@
6
6
  "browser": "dist/timer.iife.js",
7
7
  "description": "A better solution for timeout- and interval-based timers.",
8
8
  "devDependencies": {
9
- "esbuild": "^0.18",
10
- "xo": "^0.54"
9
+ "@happy-dom/global-registrator": "^11.0",
10
+ "@rollup/plugin-typescript": "^11.1",
11
+ "bun": "^1.0",
12
+ "prettier": "^3.0",
13
+ "rollup": "^3.29",
14
+ "typescript": "^5.2",
15
+ "xo": "^0.56"
11
16
  },
12
17
  "files": [
13
18
  "dist",
14
- "src"
19
+ "src",
20
+ "types"
15
21
  ],
16
22
  "jsdelivr": "dist/timer.iife.js",
17
23
  "keywords": [
@@ -24,19 +30,39 @@
24
30
  "main": "dist/timer.iife.js",
25
31
  "module": "dist/timer.js",
26
32
  "name": "@oscarpalmer/timer",
33
+ "prettier": {
34
+ "arrowParens": "avoid",
35
+ "bracketSpacing": false,
36
+ "singleQuote": true,
37
+ "switchIndent": true,
38
+ "trailingComma": "all",
39
+ "useTabs": true
40
+ },
27
41
  "repository": {
28
42
  "type": "git",
29
43
  "url": "git+https://github.com/oscarpalmer/timer.git"
30
44
  },
31
45
  "scripts": {
32
- "build": "npm run build:esm; npm run build:iife; npm run types",
33
- "build:esm": "esbuild ./src/index.js --bundle --format=esm --outfile=./dist/timer.js --target=es2020",
34
- "build:iife": "esbuild ./src/index.js --bundle --format=iife --global-name=Timer --outfile=./dist/timer.iife.js --target=es2020",
35
- "types": "./node_modules/.bin/tsc ./src/index.js --allowJs --declaration --emitDeclarationOnly --removeComments",
36
- "xo": "./node_modules/.bin/xo ./src/*.js --env browser"
46
+ "build": "npm run build:esm && npm run build:iife",
47
+ "build:esm": "rollup -c",
48
+ "build:iife": "rollup -c --environment ROLLUP_FORMAT:iife",
49
+ "test": "bun test --coverage",
50
+ "types": "tsc ./src/index.ts --outdir ./types --declaration --emitDeclarationOnly",
51
+ "watch": "rollup -c -w",
52
+ "xo": "xo ./src/*.ts --env browser"
37
53
  },
38
54
  "type": "module",
39
55
  "types": "src/index.d.ts",
40
56
  "unpkg": "dist/timer.iife.js",
41
- "version": "0.17.0"
42
- }
57
+ "version": "0.19.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
+ }
package/src/index.ts ADDED
@@ -0,0 +1,230 @@
1
+ type Callbacks = {
2
+ after: AfterCallback | undefined;
3
+ default: (index?: number) => void;
4
+ };
5
+
6
+ type Configuration = {
7
+ count: number;
8
+ time: number;
9
+ };
10
+
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;
20
+
21
+ type State = {
22
+ active: boolean;
23
+ finished: boolean;
24
+ frame?: number;
25
+ };
26
+
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
+ const milliseconds = Math.round(1000 / 60);
32
+
33
+ function run(timed: Timed<never, never>): void {
34
+ const timedConfiguration = configuration.get(timed)!;
35
+ const timedCallbacks = callbacks.get(timed)!;
36
+ const timedState = state.get(timed)!;
37
+
38
+ timedState.active = true;
39
+ timedState.finished = false;
40
+
41
+ const isRepeated = timed instanceof Repeated;
42
+
43
+ let index = 0;
44
+
45
+ let start;
46
+
47
+ function step(timestamp: DOMHighResTimeStamp): void {
48
+ if (!timedState.active) {
49
+ return;
50
+ }
51
+
52
+ start ??= timestamp;
53
+
54
+ const elapsed = timestamp - start;
55
+
56
+ const elapsedMinimum = elapsed - milliseconds;
57
+ const elapsedMaximum = elapsed + milliseconds;
58
+
59
+ if (
60
+ elapsedMinimum < timedConfiguration.time &&
61
+ timedConfiguration.time < elapsedMaximum
62
+ ) {
63
+ if (timedState.active) {
64
+ timedCallbacks.default(isRepeated ? index : undefined);
65
+ }
66
+
67
+ index += 1;
68
+
69
+ if (isRepeated && index < timedConfiguration.count) {
70
+ start = undefined;
71
+ } else {
72
+ timedState.finished = true;
73
+
74
+ timed.stop();
75
+
76
+ return;
77
+ }
78
+ }
79
+
80
+ timedState.frame = globalThis.requestAnimationFrame(step);
81
+ }
82
+
83
+ timedState.frame = globalThis.requestAnimationFrame(step);
84
+ }
85
+
86
+ class Timed<Type, Callback> {
87
+ get active(): boolean {
88
+ return state.get(this as never)?.active ?? false;
89
+ }
90
+
91
+ get finished(): boolean {
92
+ return !this.active && (state.get(this as never)?.finished ?? false);
93
+ }
94
+
95
+ /**
96
+ * @param {Callback} callback
97
+ * @param {number} time
98
+ * @param {number} count
99
+ * @param {AfterCallback=} afterCallback
100
+ */
101
+ constructor(
102
+ callback: Callback,
103
+ time: number,
104
+ count: number,
105
+ afterCallback?: AfterCallback,
106
+ ) {
107
+ const isRepeated = this instanceof Repeated;
108
+
109
+ const type = isRepeated ? 'repeated' : 'waited';
110
+
111
+ if (typeof callback !== 'function') {
112
+ throw new TypeError(`A ${type} timer must have a callback function`);
113
+ }
114
+
115
+ if (typeof time !== 'number' || time < 0) {
116
+ throw new TypeError(
117
+ `A ${type} timer must have a non-negative number as its time`,
118
+ );
119
+ }
120
+
121
+ if (isRepeated && (typeof count !== 'number' || count < 2)) {
122
+ throw new TypeError(
123
+ 'A repeated timer must have a number above 1 as its repeat count',
124
+ );
125
+ }
126
+
127
+ if (
128
+ isRepeated &&
129
+ afterCallback !== undefined &&
130
+ typeof afterCallback !== 'function'
131
+ ) {
132
+ throw new TypeError(
133
+ "A repeated timer's after-callback must be a function",
134
+ );
135
+ }
136
+
137
+ callbacks.set(this as never, {
138
+ after: afterCallback,
139
+ default: callback as never,
140
+ });
141
+
142
+ configuration.set(this as never, {count, time});
143
+
144
+ state.set(this as never, {
145
+ active: false,
146
+ finished: false,
147
+ });
148
+ }
149
+
150
+ restart(): Type {
151
+ this.stop();
152
+
153
+ run(this as never);
154
+
155
+ return this as never;
156
+ }
157
+
158
+ start(): Type {
159
+ if (!this.active) {
160
+ run(this as never);
161
+ }
162
+
163
+ return this as never;
164
+ }
165
+
166
+ stop(): Type {
167
+ const timedCallbacks = callbacks.get(this as never)!;
168
+ const timedState = state.get(this as never)!;
169
+
170
+ timedState.active = false;
171
+
172
+ if (timedState.frame === undefined) {
173
+ return this as never;
174
+ }
175
+
176
+ globalThis.cancelAnimationFrame(timedState.frame);
177
+
178
+ timedCallbacks.after?.(this.finished);
179
+
180
+ timedState.frame = undefined;
181
+
182
+ return this as never;
183
+ }
184
+ }
185
+
186
+ /**
187
+ * A timer that waits and runs repeatedly
188
+ */
189
+ export class Repeated extends Timed<Repeated, RepeatedCallback> {}
190
+
191
+ /**
192
+ * A timer that waits and runs once
193
+ */
194
+ export class Waited extends Timed<Waited, () => void> {
195
+ /**
196
+ * Creates a new waited timer
197
+ * @param {() => void} callback
198
+ * @param {number} time
199
+ */
200
+ constructor(callback: () => void, time: number) {
201
+ super(callback, time, 1);
202
+ }
203
+ }
204
+
205
+ /**
206
+ * Creates and starts a new repeated timer
207
+ * @param {RepeatedCallback} callback
208
+ * @param {number} time
209
+ * @param {number} count
210
+ * @param {AfterCallback=} afterCallback
211
+ * @return {Repeated}
212
+ */
213
+ export function repeat(
214
+ callback: RepeatedCallback,
215
+ time: number,
216
+ count: number,
217
+ afterCallback?: AfterCallback,
218
+ ): Repeated {
219
+ return new Repeated(callback as never, time, count, afterCallback).start();
220
+ }
221
+
222
+ /**
223
+ * Creates and starts a new waited timer
224
+ * @param {() => void} callback
225
+ * @param {number} time
226
+ * @return {Waited}
227
+ */
228
+ export function wait(callback: () => void, time: number): Waited {
229
+ return new Waited(callback, time).start();
230
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * @param {boolean} finished Did the timer finish?
3
+ */
4
+ 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);
49
+ }
50
+
51
+ /**
52
+ * 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
+ */
59
+ export declare function repeat(
60
+ callback: RepeatedCallback,
61
+ time: number,
62
+ count: number,
63
+ afterCallback?: AfterCallback,
64
+ ): Repeated;
65
+
66
+ /**
67
+ * Creates and starts a new waited timer
68
+ * @param {() => void} callback
69
+ * @param {number} time
70
+ * @return {Waited}
71
+ */
72
+ export declare function wait(callback: () => void, time: number): Waited;
package/src/index.d.ts DELETED
@@ -1,31 +0,0 @@
1
- export function repeat(callback: RepeatedCallback, time: number, count: number, afterCallback: AfterCallback | undefined): Repeated;
2
- export function wait(callback: Function, time: number): Waited;
3
- export class Repeated extends Timed {
4
- }
5
- export class Waited extends Timed {
6
- constructor(callback: Function, time: number);
7
- }
8
- export type AfterCallback = (finished: boolean) => void;
9
- export type RepeatedCallback = (index: number) => void;
10
- declare class Timed {
11
- constructor(callback: RepeatedCallback, time: number, count: number, afterCallback: AfterCallback | undefined);
12
- get active(): boolean;
13
- get finished(): boolean;
14
- readonly configuration: {
15
- count: number;
16
- time: number;
17
- };
18
- readonly callbacks: {
19
- after: AfterCallback | undefined;
20
- default: RepeatedCallback;
21
- };
22
- readonly state: {
23
- active: boolean;
24
- finished: boolean;
25
- frame?: DOMHighResTimeStamp;
26
- };
27
- restart(): Timed;
28
- start(): Timed;
29
- stop(): Timed;
30
- }
31
- export {};
package/src/index.js DELETED
@@ -1,210 +0,0 @@
1
- /**
2
- * @callback AfterCallback
3
- * @param {boolean} finished
4
- * @returns {void}
5
- */
6
-
7
- /**
8
- * @callback RepeatedCallback
9
- * @param {number} index
10
- * @returns {void}
11
- */
12
-
13
- const milliseconds = Math.round(1000 / 60);
14
-
15
- const request =
16
- requestAnimationFrame ??
17
- function (callback) {
18
- return setTimeout?.(() => {
19
- callback(Date.now());
20
- }, milliseconds);
21
- };
22
-
23
- /**
24
- * @param {Timed} timed
25
- */
26
- function run(timed) {
27
- timed.state.active = true;
28
- timed.state.finished = false;
29
-
30
- const isRepeated = timed instanceof Repeated;
31
-
32
- let index = 0;
33
-
34
- let start;
35
-
36
- function step(timestamp) {
37
- if (!timed.state.active) {
38
- return;
39
- }
40
-
41
- start ??= timestamp;
42
-
43
- const elapsed = timestamp - start;
44
-
45
- const elapsedMinimum = elapsed - milliseconds;
46
- const elapsedMaximum = elapsed + milliseconds;
47
-
48
- if (
49
- elapsedMinimum < timed.configuration.time &&
50
- timed.configuration.time < elapsedMaximum
51
- ) {
52
- if (timed.state.active) {
53
- timed.callbacks.default(isRepeated ? index : undefined);
54
- }
55
-
56
- index += 1;
57
-
58
- if (isRepeated && index < timed.configuration.count) {
59
- start = undefined;
60
- } else {
61
- timed.state.finished = true;
62
-
63
- timed.stop();
64
-
65
- return;
66
- }
67
- }
68
-
69
- timed.state.frame = request(step);
70
- }
71
-
72
- timed.state.frame = request(step);
73
- }
74
-
75
- class Timed {
76
- get active() {
77
- return this.state.active;
78
- }
79
-
80
- get finished() {
81
- return !this.active && this.state.finished;
82
- }
83
-
84
- /**
85
- * @param {RepeatedCallback} callback
86
- * @param {number} time
87
- * @param {number} count
88
- * @param {AfterCallback|undefined} afterCallback
89
- */
90
- constructor(callback, time, count, afterCallback) {
91
- const isRepeated = this instanceof Repeated;
92
-
93
- const type = isRepeated ? 'repeated' : 'waited';
94
-
95
- if (typeof callback !== 'function') {
96
- throw new TypeError(`A ${type} timer must have a callback function`);
97
- }
98
-
99
- if (typeof time !== 'number' || time < 0) {
100
- throw new TypeError(
101
- `A ${type} timer must have a non-negative number as its time`,
102
- );
103
- }
104
-
105
- if (isRepeated && (typeof count !== 'number' || count < 2)) {
106
- throw new TypeError(
107
- 'A repeated timer must have a number above 1 as its repeat count',
108
- );
109
- }
110
-
111
- if (
112
- isRepeated &&
113
- afterCallback !== undefined &&
114
- typeof afterCallback !== 'function'
115
- ) {
116
- throw new TypeError(
117
- "A repeated timer's after-callback must be a function",
118
- );
119
- }
120
-
121
- /**
122
- * @readonly
123
- * @type {{count: number; time: number}}
124
- */
125
- this.configuration = {count, time};
126
-
127
- /**
128
- * @readonly
129
- * @type {{after: AfterCallback | undefined; default: RepeatedCallback}}
130
- */
131
- this.callbacks = {
132
- after: afterCallback,
133
- default: callback,
134
- };
135
-
136
- /**
137
- * @readonly
138
- * @type {{active: boolean; finished: boolean; frame?: DOMHighResTimeStamp}}
139
- */
140
- this.state = {
141
- active: false,
142
- finished: false,
143
- frame: null,
144
- };
145
- }
146
-
147
- restart() {
148
- this.stop();
149
-
150
- run(this);
151
-
152
- return this;
153
- }
154
-
155
- start() {
156
- if (!this.state.active) {
157
- run(this);
158
- }
159
-
160
- return this;
161
- }
162
-
163
- stop() {
164
- this.state.active = false;
165
-
166
- if (this.state.frame === undefined) {
167
- return this;
168
- }
169
-
170
- (cancelAnimationFrame ?? clearTimeout)?.(this.state.frame);
171
-
172
- this.callbacks.after?.(this.finished);
173
-
174
- this.state.frame = undefined;
175
-
176
- return this;
177
- }
178
- }
179
-
180
- export class Repeated extends Timed {}
181
-
182
- export class Waited extends Timed {
183
- /**
184
- * @param {Function} callback
185
- * @param {number} time
186
- */
187
- constructor(callback, time) {
188
- super(callback, time, 1, null);
189
- }
190
- }
191
-
192
- /**
193
- * @param {RepeatedCallback} callback
194
- * @param {number} time
195
- * @param {number} count
196
- * @param {AfterCallback|undefined} afterCallback
197
- * @return {Repeated} A repeated timer
198
- */
199
- export function repeat(callback, time, count, afterCallback) {
200
- return new Repeated(callback, time, count, afterCallback).start();
201
- }
202
-
203
- /**
204
- * @param {Function} callback
205
- * @param {number} time
206
- * @return {Waited} A waited timer
207
- */
208
- export function wait(callback, time) {
209
- return new Waited(callback, time).start();
210
- }