@khanacademy/wonder-blocks-timing 1.2.3 → 2.0.3
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/es/index.js +151 -48
- package/dist/index.js +309 -159
- package/docs.md +28 -18
- package/package.json +4 -4
- package/src/components/__tests__/action-scheduler-provider.test.js +3 -3
- package/src/components/action-scheduler-provider.js +2 -2
- package/src/components/with-action-scheduler.js +8 -4
- package/src/hooks/__tests__/use-timeout.test.js +336 -0
- package/src/hooks/use-timeout.js +70 -0
- package/src/hooks/use-timeout.stories.mdx +152 -0
- package/src/index.js +2 -0
- package/src/util/__tests__/action-scheduler.test.js +103 -88
- package/src/util/__tests__/animation-frame.test.js +29 -20
- package/src/util/__tests__/interval.test.js +51 -25
- package/src/util/__tests__/timeout.test.js +35 -20
- package/src/util/action-scheduler.js +41 -16
- package/src/util/animation-frame.js +26 -13
- package/src/util/interval.js +19 -13
- package/src/util/policies.js +10 -0
- package/src/util/timeout.js +23 -13
- package/src/util/types.js +26 -26
package/dist/es/index.js
CHANGED
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import _extends from '@babel/runtime/helpers/extends';
|
|
2
|
-
import { Component, forwardRef, createElement } from 'react';
|
|
2
|
+
import { Component, forwardRef, createElement, useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
|
|
4
|
+
const SchedulePolicy = {
|
|
5
|
+
Immediately: "schedule-immediately",
|
|
6
|
+
OnDemand: "schedule-on-demand"
|
|
7
|
+
};
|
|
8
|
+
const ClearPolicy = {
|
|
9
|
+
Resolve: "resolve-on-clear",
|
|
10
|
+
Cancel: "cancel-on-clear"
|
|
11
|
+
};
|
|
3
12
|
|
|
4
13
|
/**
|
|
5
14
|
* Encapsulates everything associated with calling setTimeout/clearTimeout, and
|
|
@@ -18,12 +27,13 @@ class Timeout {
|
|
|
18
27
|
* @param {() => mixed} action The action to be invoked when the timeout
|
|
19
28
|
* period has passed.
|
|
20
29
|
* @param {number} timeoutMs The timeout period.
|
|
21
|
-
* @param {
|
|
22
|
-
*
|
|
23
|
-
*
|
|
30
|
+
* @param {SchedulePolicy} [schedulePolicy] When SchedulePolicy.Immediately,
|
|
31
|
+
* the timer is set immediately on instantiation; otherwise, `set` must be
|
|
32
|
+
* called to set the timeout.
|
|
33
|
+
* Defaults to `SchedulePolicy.Immediately`.
|
|
24
34
|
* @memberof Timeout
|
|
25
35
|
*/
|
|
26
|
-
constructor(action, timeoutMs,
|
|
36
|
+
constructor(action, timeoutMs, schedulePolicy = SchedulePolicy.Immediately) {
|
|
27
37
|
if (typeof action !== "function") {
|
|
28
38
|
throw new Error("Action must be a function");
|
|
29
39
|
}
|
|
@@ -35,7 +45,7 @@ class Timeout {
|
|
|
35
45
|
this._action = action;
|
|
36
46
|
this._timeoutMs = timeoutMs;
|
|
37
47
|
|
|
38
|
-
if (
|
|
48
|
+
if (schedulePolicy === SchedulePolicy.Immediately) {
|
|
39
49
|
this.set();
|
|
40
50
|
}
|
|
41
51
|
}
|
|
@@ -64,10 +74,10 @@ class Timeout {
|
|
|
64
74
|
|
|
65
75
|
set() {
|
|
66
76
|
if (this.isSet) {
|
|
67
|
-
this.clear(
|
|
77
|
+
this.clear(ClearPolicy.Cancel);
|
|
68
78
|
}
|
|
69
79
|
|
|
70
|
-
this._timeoutId = setTimeout(() => this.clear(
|
|
80
|
+
this._timeoutId = setTimeout(() => this.clear(ClearPolicy.Resolve), this._timeoutMs);
|
|
71
81
|
}
|
|
72
82
|
/**
|
|
73
83
|
* Clear the set timeout.
|
|
@@ -75,16 +85,17 @@ class Timeout {
|
|
|
75
85
|
* If the timeout is pending, this cancels that pending timeout without
|
|
76
86
|
* invoking the action. If no timeout is pending, this does nothing.
|
|
77
87
|
*
|
|
78
|
-
* @param {
|
|
79
|
-
* the
|
|
80
|
-
*
|
|
88
|
+
* @param {ClearPolicy} [policy] When ClearPolicy.Resolve, if the request
|
|
89
|
+
* was set when called, the request action is invoked after cancelling
|
|
90
|
+
* the request; otherwise, the pending action is cancelled.
|
|
91
|
+
* Defaults to `ClearPolicy.Cancel`.
|
|
81
92
|
*
|
|
82
93
|
* @returns {void}
|
|
83
94
|
* @memberof Timeout
|
|
84
95
|
*/
|
|
85
96
|
|
|
86
97
|
|
|
87
|
-
clear(
|
|
98
|
+
clear(policy = ClearPolicy.Cancel) {
|
|
88
99
|
const timeoutId = this._timeoutId;
|
|
89
100
|
this._timeoutId = null;
|
|
90
101
|
|
|
@@ -94,7 +105,7 @@ class Timeout {
|
|
|
94
105
|
|
|
95
106
|
clearTimeout(timeoutId);
|
|
96
107
|
|
|
97
|
-
if (
|
|
108
|
+
if (policy === ClearPolicy.Resolve) {
|
|
98
109
|
this._action();
|
|
99
110
|
}
|
|
100
111
|
}
|
|
@@ -118,13 +129,13 @@ class Interval {
|
|
|
118
129
|
* @param {() => mixed} action The action to be invoked each time the
|
|
119
130
|
* interval period has passed.
|
|
120
131
|
* @param {number} intervalMs The interval period.
|
|
121
|
-
* @param {
|
|
122
|
-
* immediately on
|
|
123
|
-
*
|
|
124
|
-
* Defaults to `
|
|
132
|
+
* @param {SchedulePolicy} [schedulePolicy] When SchedulePolicy.Immediately,
|
|
133
|
+
* the interval is set immediately on instantiation; otherwise, `set` must be
|
|
134
|
+
* called to set the interval.
|
|
135
|
+
* Defaults to `SchedulePolicy.Immediately`.
|
|
125
136
|
* @memberof Interval
|
|
126
137
|
*/
|
|
127
|
-
constructor(action, intervalMs,
|
|
138
|
+
constructor(action, intervalMs, schedulePolicy = SchedulePolicy.Immediately) {
|
|
128
139
|
if (typeof action !== "function") {
|
|
129
140
|
throw new Error("Action must be a function");
|
|
130
141
|
}
|
|
@@ -136,7 +147,7 @@ class Interval {
|
|
|
136
147
|
this._action = action;
|
|
137
148
|
this._intervalMs = intervalMs;
|
|
138
149
|
|
|
139
|
-
if (
|
|
150
|
+
if (schedulePolicy === SchedulePolicy.Immediately) {
|
|
140
151
|
this.set();
|
|
141
152
|
}
|
|
142
153
|
}
|
|
@@ -163,7 +174,7 @@ class Interval {
|
|
|
163
174
|
|
|
164
175
|
set() {
|
|
165
176
|
if (this.isSet) {
|
|
166
|
-
this.clear(
|
|
177
|
+
this.clear(ClearPolicy.Cancel);
|
|
167
178
|
}
|
|
168
179
|
|
|
169
180
|
this._intervalId = setInterval(() => this._action(), this._intervalMs);
|
|
@@ -174,16 +185,17 @@ class Interval {
|
|
|
174
185
|
* If the interval is active, this cancels that interval. If no interval is
|
|
175
186
|
* pending, this does nothing.
|
|
176
187
|
*
|
|
177
|
-
* @param {
|
|
178
|
-
* called, the
|
|
179
|
-
*
|
|
188
|
+
* @param {ClearPolicy} [policy] When ClearPolicy.Resolve, if the request
|
|
189
|
+
* was set when called, the request action is invoked after cancelling
|
|
190
|
+
* the request; otherwise, the pending action is cancelled.
|
|
191
|
+
* Defaults to `ClearPolicy.Cancel`.
|
|
180
192
|
*
|
|
181
193
|
* @returns {void}
|
|
182
194
|
* @memberof Interval
|
|
183
195
|
*/
|
|
184
196
|
|
|
185
197
|
|
|
186
|
-
clear(
|
|
198
|
+
clear(policy = ClearPolicy.Cancel) {
|
|
187
199
|
const intervalId = this._intervalId;
|
|
188
200
|
this._intervalId = null;
|
|
189
201
|
|
|
@@ -193,7 +205,7 @@ class Interval {
|
|
|
193
205
|
|
|
194
206
|
clearInterval(intervalId);
|
|
195
207
|
|
|
196
|
-
if (
|
|
208
|
+
if (policy === ClearPolicy.Resolve) {
|
|
197
209
|
this._action();
|
|
198
210
|
}
|
|
199
211
|
}
|
|
@@ -215,19 +227,20 @@ class AnimationFrame {
|
|
|
215
227
|
* The request is not made until set is called.
|
|
216
228
|
*
|
|
217
229
|
* @param {DOMHighResTimeStamp => mixed} action The action to be invoked.
|
|
218
|
-
* @param {
|
|
219
|
-
*
|
|
220
|
-
*
|
|
230
|
+
* @param {SchedulePolicy} [schedulePolicy] When SchedulePolicy.Immediately,
|
|
231
|
+
* the interval is set immediately on instantiation; otherwise, `set` must be
|
|
232
|
+
* called to set the interval.
|
|
233
|
+
* Defaults to `SchedulePolicy.Immediately`.
|
|
221
234
|
* @memberof AnimationFrame
|
|
222
235
|
*/
|
|
223
|
-
constructor(action,
|
|
236
|
+
constructor(action, schedulePolicy = SchedulePolicy.Immediately) {
|
|
224
237
|
if (typeof action !== "function") {
|
|
225
238
|
throw new Error("Action must be a function");
|
|
226
239
|
}
|
|
227
240
|
|
|
228
241
|
this._action = action;
|
|
229
242
|
|
|
230
|
-
if (
|
|
243
|
+
if (schedulePolicy === SchedulePolicy.Immediately) {
|
|
231
244
|
this.set();
|
|
232
245
|
}
|
|
233
246
|
}
|
|
@@ -256,10 +269,10 @@ class AnimationFrame {
|
|
|
256
269
|
|
|
257
270
|
set() {
|
|
258
271
|
if (this.isSet) {
|
|
259
|
-
this.clear(
|
|
272
|
+
this.clear(ClearPolicy.Cancel);
|
|
260
273
|
}
|
|
261
274
|
|
|
262
|
-
this._animationFrameId = requestAnimationFrame(time => this.clear(
|
|
275
|
+
this._animationFrameId = requestAnimationFrame(time => this.clear(ClearPolicy.Resolve, time));
|
|
263
276
|
}
|
|
264
277
|
/**
|
|
265
278
|
* Clear the pending request.
|
|
@@ -267,16 +280,20 @@ class AnimationFrame {
|
|
|
267
280
|
* If the request is pending, this cancels that pending request without
|
|
268
281
|
* invoking the action. If no request is pending, this does nothing.
|
|
269
282
|
*
|
|
270
|
-
* @param {
|
|
271
|
-
* called, the request action is invoked after cancelling
|
|
272
|
-
*
|
|
283
|
+
* @param {ClearPolicy} [policy] When ClearPolicy.Resolve, if the request
|
|
284
|
+
* was set when called, the request action is invoked after cancelling
|
|
285
|
+
* the request; otherwise, the pending action is cancelled.
|
|
286
|
+
* Defaults to `ClearPolicy.Cancel`.
|
|
287
|
+
* @param {DOMHighResTimeStamp} [time] Timestamp to pass to the action when
|
|
288
|
+
* ClearPolicy.Resolve is specified. Ignored when ClearPolicy.Cancel is
|
|
289
|
+
* specified.
|
|
273
290
|
*
|
|
274
291
|
* @returns {void}
|
|
275
292
|
* @memberof AnimationFrame
|
|
276
293
|
*/
|
|
277
294
|
|
|
278
295
|
|
|
279
|
-
clear(
|
|
296
|
+
clear(policy = ClearPolicy.Cancel, time) {
|
|
280
297
|
const animationFrameId = this._animationFrameId;
|
|
281
298
|
this._animationFrameId = null;
|
|
282
299
|
|
|
@@ -286,7 +303,7 @@ class AnimationFrame {
|
|
|
286
303
|
|
|
287
304
|
cancelAnimationFrame(animationFrameId);
|
|
288
305
|
|
|
289
|
-
if (
|
|
306
|
+
if (policy === ClearPolicy.Resolve) {
|
|
290
307
|
this._action(time || performance.now());
|
|
291
308
|
}
|
|
292
309
|
}
|
|
@@ -301,29 +318,42 @@ class AnimationFrame {
|
|
|
301
318
|
*/
|
|
302
319
|
class ActionScheduler {
|
|
303
320
|
constructor() {
|
|
321
|
+
this._disabled = false;
|
|
304
322
|
this._registeredActions = [];
|
|
305
323
|
}
|
|
306
324
|
|
|
307
|
-
timeout(action, period,
|
|
308
|
-
|
|
325
|
+
timeout(action, period, options) {
|
|
326
|
+
if (this._disabled) {
|
|
327
|
+
return ActionScheduler.NoopAction;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const timeout = new Timeout(action, period, options == null ? void 0 : options.schedulePolicy);
|
|
309
331
|
|
|
310
|
-
this._registeredActions.push(() => timeout.clear(
|
|
332
|
+
this._registeredActions.push(() => timeout.clear(options == null ? void 0 : options.clearPolicy));
|
|
311
333
|
|
|
312
334
|
return timeout;
|
|
313
335
|
}
|
|
314
336
|
|
|
315
|
-
interval(action, period,
|
|
316
|
-
|
|
337
|
+
interval(action, period, options) {
|
|
338
|
+
if (this._disabled) {
|
|
339
|
+
return ActionScheduler.NoopAction;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const interval = new Interval(action, period, options == null ? void 0 : options.schedulePolicy);
|
|
317
343
|
|
|
318
|
-
this._registeredActions.push(() => interval.clear(
|
|
344
|
+
this._registeredActions.push(() => interval.clear(options == null ? void 0 : options.clearPolicy));
|
|
319
345
|
|
|
320
346
|
return interval;
|
|
321
347
|
}
|
|
322
348
|
|
|
323
|
-
animationFrame(action,
|
|
324
|
-
|
|
349
|
+
animationFrame(action, options) {
|
|
350
|
+
if (this._disabled) {
|
|
351
|
+
return ActionScheduler.NoopAction;
|
|
352
|
+
}
|
|
325
353
|
|
|
326
|
-
|
|
354
|
+
const animationFrame = new AnimationFrame(action, options == null ? void 0 : options.schedulePolicy);
|
|
355
|
+
|
|
356
|
+
this._registeredActions.push(() => animationFrame.clear(options == null ? void 0 : options.clearPolicy));
|
|
327
357
|
|
|
328
358
|
return animationFrame;
|
|
329
359
|
}
|
|
@@ -333,8 +363,27 @@ class ActionScheduler {
|
|
|
333
363
|
this._registeredActions = [];
|
|
334
364
|
registered.forEach(clearFn => clearFn());
|
|
335
365
|
}
|
|
366
|
+
/**
|
|
367
|
+
* Prevents this scheduler from creating any additional actions.
|
|
368
|
+
* This also clears any pending actions.
|
|
369
|
+
*/
|
|
370
|
+
|
|
371
|
+
|
|
372
|
+
disable() {
|
|
373
|
+
this._disabled = true;
|
|
374
|
+
this.clearAll();
|
|
375
|
+
}
|
|
336
376
|
|
|
337
377
|
}
|
|
378
|
+
ActionScheduler.NoopAction = {
|
|
379
|
+
set: () => {},
|
|
380
|
+
|
|
381
|
+
get isSet() {
|
|
382
|
+
return false;
|
|
383
|
+
},
|
|
384
|
+
|
|
385
|
+
clear: () => {}
|
|
386
|
+
};
|
|
338
387
|
|
|
339
388
|
/**
|
|
340
389
|
* A provider component that passes our action scheduling API to its children
|
|
@@ -353,7 +402,7 @@ class ActionSchedulerProvider extends Component {
|
|
|
353
402
|
}
|
|
354
403
|
|
|
355
404
|
componentWillUnmount() {
|
|
356
|
-
this._actionScheduler.
|
|
405
|
+
this._actionScheduler.disable();
|
|
357
406
|
}
|
|
358
407
|
|
|
359
408
|
render() {
|
|
@@ -381,4 +430,58 @@ function withActionScheduler(WrappedComponent) {
|
|
|
381
430
|
}))));
|
|
382
431
|
}
|
|
383
432
|
|
|
384
|
-
|
|
433
|
+
function useTimeout(action, timeoutMs, options) {
|
|
434
|
+
var _options$schedulePoli;
|
|
435
|
+
|
|
436
|
+
const schedulePolicy = (_options$schedulePoli = options == null ? void 0 : options.schedulePolicy) != null ? _options$schedulePoli : SchedulePolicy.Immediately;
|
|
437
|
+
const [isSet, setIsSet] = useState(schedulePolicy === SchedulePolicy.Immediately);
|
|
438
|
+
const actionRef = useRef(action);
|
|
439
|
+
const mountedRef = useRef(false);
|
|
440
|
+
useEffect(() => {
|
|
441
|
+
mountedRef.current = true;
|
|
442
|
+
return () => {
|
|
443
|
+
mountedRef.current = false;
|
|
444
|
+
};
|
|
445
|
+
}, []);
|
|
446
|
+
useEffect(() => {
|
|
447
|
+
actionRef.current = action;
|
|
448
|
+
}, [action]);
|
|
449
|
+
const clear = useCallback(policy => {
|
|
450
|
+
if ((policy != null ? policy : options == null ? void 0 : options.clearPolicy) === ClearPolicy.Resolve) {
|
|
451
|
+
actionRef.current();
|
|
452
|
+
} // This will cause the useEffect below to re-run
|
|
453
|
+
|
|
454
|
+
|
|
455
|
+
setIsSet(false);
|
|
456
|
+
}, [options == null ? void 0 : options.clearPolicy]);
|
|
457
|
+
const set = useCallback(() => {
|
|
458
|
+
if (isSet) {
|
|
459
|
+
clear();
|
|
460
|
+
} // This will cause the useEffect below to re-run
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
setIsSet(true);
|
|
464
|
+
}, [clear, isSet]);
|
|
465
|
+
useEffect(() => {
|
|
466
|
+
if (isSet && mountedRef.current) {
|
|
467
|
+
const timeout = window.setTimeout(() => {
|
|
468
|
+
actionRef.current();
|
|
469
|
+
setIsSet(false);
|
|
470
|
+
}, timeoutMs);
|
|
471
|
+
return () => {
|
|
472
|
+
window.clearTimeout(timeout);
|
|
473
|
+
|
|
474
|
+
if (!mountedRef.current) {
|
|
475
|
+
clear();
|
|
476
|
+
}
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
}, [clear, isSet, timeoutMs]);
|
|
480
|
+
return {
|
|
481
|
+
isSet,
|
|
482
|
+
set,
|
|
483
|
+
clear
|
|
484
|
+
};
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
export { ClearPolicy, SchedulePolicy, useTimeout, withActionScheduler };
|