@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 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 {boolean} [autoSchedule] When true, the timer is set immediately on
22
- * instanstiation; otherwise, `set` must be called to set the timeout.
23
- * Defaults to `true`.
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, autoSchedule) {
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 (autoSchedule || autoSchedule == null) {
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(false);
77
+ this.clear(ClearPolicy.Cancel);
68
78
  }
69
79
 
70
- this._timeoutId = setTimeout(() => this.clear(true), this._timeoutMs);
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 {boolean} [resolve] When true, if the timeout was set when called,
79
- * the timeout action is invoked after cancelling the timeout. Defaults to
80
- * false.
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(resolve) {
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 (resolve) {
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 {boolean} [autoSchedule] When true, the interval is activated
122
- * immediately on instanstiation; otherwise, `set` must be called to
123
- * activate the interval.
124
- * Defaults to `true`.
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, autoSchedule) {
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 (autoSchedule || autoSchedule == null) {
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(false);
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 {boolean} [resolve] When true, if the interval was active when
178
- * called, the interval action is invoked after cancellation. Defaults to
179
- * false.
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(resolve) {
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 (resolve) {
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 {boolean} [autoSchedule] When true, the request is made immediately on
219
- * instanstiation; otherwise, `set` must be called to make the request.
220
- * Defaults to `true`.
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, autoSchedule) {
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 (autoSchedule || autoSchedule == null) {
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(false);
272
+ this.clear(ClearPolicy.Cancel);
260
273
  }
261
274
 
262
- this._animationFrameId = requestAnimationFrame(time => this.clear(true, time));
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 {boolean} [resolve] When true, if the request was pending when
271
- * called, the request action is invoked after cancelling the timeout.
272
- * Defaults to false.
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(resolve, time) {
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 (resolve) {
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, autoSchedule, resolveOnClear) {
308
- const timeout = new Timeout(action, period, autoSchedule);
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(resolveOnClear));
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, autoSchedule, resolveOnClear) {
316
- const interval = new Interval(action, period, autoSchedule);
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(resolveOnClear));
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, autoSchedule, resolveOnClear) {
324
- const animationFrame = new AnimationFrame(action, autoSchedule);
349
+ animationFrame(action, options) {
350
+ if (this._disabled) {
351
+ return ActionScheduler.NoopAction;
352
+ }
325
353
 
326
- this._registeredActions.push(() => animationFrame.clear(resolveOnClear));
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.clearAll();
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
- export { withActionScheduler };
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 };