@khanacademy/wonder-blocks-timing 5.0.1 → 5.0.2
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/CHANGELOG.md +6 -0
- package/package.json +1 -1
- package/src/components/__tests__/action-scheduler-provider.test.tsx +0 -36
- package/src/components/__tests__/with-action-scheduler.test.tsx +0 -22
- package/src/components/action-scheduler-provider.ts +0 -35
- package/src/components/with-action-scheduler.tsx +0 -36
- package/src/hooks/__tests__/use-interval.test.ts +0 -520
- package/src/hooks/__tests__/use-timeout.test.ts +0 -489
- package/src/hooks/use-interval.ts +0 -101
- package/src/hooks/use-timeout.ts +0 -101
- package/src/index.ts +0 -17
- package/src/util/__tests__/action-scheduler.test.ts +0 -271
- package/src/util/__tests__/animation-frame.test.ts +0 -225
- package/src/util/__tests__/interval.test.ts +0 -234
- package/src/util/__tests__/timeout.test.ts +0 -224
- package/src/util/action-scheduler.ts +0 -89
- package/src/util/animation-frame.ts +0 -103
- package/src/util/interval.ts +0 -102
- package/src/util/policies.ts +0 -14
- package/src/util/timeout.ts +0 -108
- package/src/util/types.ts +0 -256
- package/src/util/types.typestest.tsx +0 -72
- package/tsconfig-build.json +0 -9
- package/tsconfig-build.tsbuildinfo +0 -1
|
@@ -1,489 +0,0 @@
|
|
|
1
|
-
import {renderHook, act} from "@testing-library/react-hooks";
|
|
2
|
-
import {SchedulePolicy, ClearPolicy, ActionPolicy} from "../../util/policies";
|
|
3
|
-
|
|
4
|
-
import {useTimeout} from "../use-timeout";
|
|
5
|
-
|
|
6
|
-
describe("useTimeout", () => {
|
|
7
|
-
beforeEach(() => {
|
|
8
|
-
jest.useFakeTimers();
|
|
9
|
-
});
|
|
10
|
-
|
|
11
|
-
afterEach(() => {
|
|
12
|
-
jest.restoreAllMocks();
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
it("throws if the action is not a function", () => {
|
|
16
|
-
// Arrange
|
|
17
|
-
|
|
18
|
-
// Act
|
|
19
|
-
const {result} = renderHook(() => useTimeout(null as any, 1000));
|
|
20
|
-
|
|
21
|
-
// Assert
|
|
22
|
-
expect(result.error).toEqual(Error("Action must be a function"));
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
it("throws if the period is less than 0", () => {
|
|
26
|
-
// Arrange
|
|
27
|
-
|
|
28
|
-
// Act
|
|
29
|
-
const {result} = renderHook(() => useTimeout(() => {}, -1));
|
|
30
|
-
|
|
31
|
-
// Assert
|
|
32
|
-
expect(result.error).toEqual(Error("Timeout period must be >= 0"));
|
|
33
|
-
});
|
|
34
|
-
|
|
35
|
-
it("should return an ITimeout", () => {
|
|
36
|
-
// Arrange
|
|
37
|
-
const {result} = renderHook(() => useTimeout(() => {}, 1000));
|
|
38
|
-
|
|
39
|
-
// Act
|
|
40
|
-
|
|
41
|
-
// Assert
|
|
42
|
-
expect(result.current).toEqual(
|
|
43
|
-
expect.objectContaining({
|
|
44
|
-
clear: expect.any(Function),
|
|
45
|
-
set: expect.any(Function),
|
|
46
|
-
isSet: expect.any(Boolean),
|
|
47
|
-
}),
|
|
48
|
-
);
|
|
49
|
-
});
|
|
50
|
-
|
|
51
|
-
it("should default to being immediately set", () => {
|
|
52
|
-
// Arrange
|
|
53
|
-
const {result} = renderHook(() => useTimeout(() => {}, 1000));
|
|
54
|
-
|
|
55
|
-
// Act
|
|
56
|
-
|
|
57
|
-
// Assert
|
|
58
|
-
expect(result.current.isSet).toBe(true);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
it("should call the action before unmounting", () => {
|
|
62
|
-
// Arrange
|
|
63
|
-
const action = jest.fn();
|
|
64
|
-
const {unmount} = renderHook(() =>
|
|
65
|
-
useTimeout(action, 1000, {
|
|
66
|
-
clearPolicy: ClearPolicy.Resolve,
|
|
67
|
-
}),
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
// Act
|
|
71
|
-
act(() => {
|
|
72
|
-
unmount();
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
// Assert
|
|
76
|
-
expect(action).toHaveBeenCalled();
|
|
77
|
-
});
|
|
78
|
-
|
|
79
|
-
it("should call the current action", () => {
|
|
80
|
-
// Arrange
|
|
81
|
-
const action1 = jest.fn();
|
|
82
|
-
const action2 = jest.fn();
|
|
83
|
-
const {rerender} = renderHook(
|
|
84
|
-
({action}: any) => useTimeout(action, 500),
|
|
85
|
-
{
|
|
86
|
-
initialProps: {action: action1},
|
|
87
|
-
},
|
|
88
|
-
);
|
|
89
|
-
|
|
90
|
-
// Act
|
|
91
|
-
rerender({action: action2});
|
|
92
|
-
jest.advanceTimersByTime(501);
|
|
93
|
-
|
|
94
|
-
// Assert
|
|
95
|
-
expect(action2).toHaveBeenCalledTimes(1);
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
it("should only call setTimeout once even if action changes", () => {
|
|
99
|
-
// Arrange
|
|
100
|
-
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
101
|
-
const action1 = jest.fn();
|
|
102
|
-
const action2 = jest.fn();
|
|
103
|
-
const {rerender} = renderHook(
|
|
104
|
-
({action}: any) => useTimeout(action, 500),
|
|
105
|
-
{
|
|
106
|
-
initialProps: {action: action1},
|
|
107
|
-
},
|
|
108
|
-
);
|
|
109
|
-
// Act
|
|
110
|
-
rerender({action: action2});
|
|
111
|
-
|
|
112
|
-
// Assert
|
|
113
|
-
expect(timeoutSpy).toHaveBeenCalledOnce();
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
it("should not reset the timeout if the action changes", () => {
|
|
117
|
-
// Arrange
|
|
118
|
-
const action1 = jest.fn();
|
|
119
|
-
const action2 = jest.fn();
|
|
120
|
-
const {rerender} = renderHook(
|
|
121
|
-
({action}: any) => useTimeout(action, 500),
|
|
122
|
-
{
|
|
123
|
-
initialProps: {action: action1},
|
|
124
|
-
},
|
|
125
|
-
);
|
|
126
|
-
|
|
127
|
-
// Act
|
|
128
|
-
jest.advanceTimersByTime(250);
|
|
129
|
-
rerender({action: action2});
|
|
130
|
-
jest.advanceTimersByTime(251);
|
|
131
|
-
|
|
132
|
-
// Assert
|
|
133
|
-
expect(action1).not.toHaveBeenCalled();
|
|
134
|
-
expect(action2).toHaveBeenCalledTimes(1);
|
|
135
|
-
});
|
|
136
|
-
|
|
137
|
-
it("should reset the timeout if the action changes and the action policy is Reset", () => {
|
|
138
|
-
// Arrange
|
|
139
|
-
const action1 = jest.fn();
|
|
140
|
-
const action2 = jest.fn();
|
|
141
|
-
const {rerender} = renderHook(
|
|
142
|
-
({action}: any) =>
|
|
143
|
-
useTimeout(action, 500, {actionPolicy: ActionPolicy.Reset}),
|
|
144
|
-
{
|
|
145
|
-
initialProps: {action: action1},
|
|
146
|
-
},
|
|
147
|
-
);
|
|
148
|
-
|
|
149
|
-
// Act
|
|
150
|
-
jest.advanceTimersByTime(250);
|
|
151
|
-
rerender({action: action2});
|
|
152
|
-
jest.advanceTimersByTime(251);
|
|
153
|
-
|
|
154
|
-
// Assert
|
|
155
|
-
expect(action1).not.toHaveBeenCalled();
|
|
156
|
-
expect(action2).not.toHaveBeenCalled();
|
|
157
|
-
});
|
|
158
|
-
|
|
159
|
-
it("should use the new timeout duration after changing it", () => {
|
|
160
|
-
// Arrange
|
|
161
|
-
const action = jest.fn();
|
|
162
|
-
const {rerender} = renderHook(
|
|
163
|
-
({timeoutMs}: any) => useTimeout(action, timeoutMs),
|
|
164
|
-
{
|
|
165
|
-
initialProps: {timeoutMs: 500},
|
|
166
|
-
},
|
|
167
|
-
);
|
|
168
|
-
rerender({timeoutMs: 1000});
|
|
169
|
-
|
|
170
|
-
// Act
|
|
171
|
-
jest.advanceTimersByTime(1501);
|
|
172
|
-
|
|
173
|
-
// Assert
|
|
174
|
-
expect(action).toHaveBeenCalledTimes(1);
|
|
175
|
-
});
|
|
176
|
-
|
|
177
|
-
it("should restart the timeout if intervalMs changes", () => {
|
|
178
|
-
// Arrange
|
|
179
|
-
const timeoutSpy = jest.spyOn(global, "setTimeout");
|
|
180
|
-
const {rerender} = renderHook(
|
|
181
|
-
({timeoutMs}: any) => useTimeout(() => {}, timeoutMs),
|
|
182
|
-
{
|
|
183
|
-
initialProps: {timeoutMs: 500},
|
|
184
|
-
},
|
|
185
|
-
);
|
|
186
|
-
|
|
187
|
-
// Act
|
|
188
|
-
rerender({timeoutMs: 1000});
|
|
189
|
-
|
|
190
|
-
// Assert
|
|
191
|
-
expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), 500);
|
|
192
|
-
expect(timeoutSpy).toHaveBeenCalledWith(expect.any(Function), 1000);
|
|
193
|
-
});
|
|
194
|
-
|
|
195
|
-
describe("SchedulePolicy.Immediately", () => {
|
|
196
|
-
it("should call the action after the timeout expires", () => {
|
|
197
|
-
// Arrange
|
|
198
|
-
const action = jest.fn();
|
|
199
|
-
renderHook(() => useTimeout(action, 1000));
|
|
200
|
-
|
|
201
|
-
// Act
|
|
202
|
-
act(() => {
|
|
203
|
-
jest.advanceTimersByTime(1000);
|
|
204
|
-
});
|
|
205
|
-
|
|
206
|
-
// Assert
|
|
207
|
-
expect(action).toHaveBeenCalled();
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("should update isSet to false after the timeout expires", () => {
|
|
211
|
-
// Arrange
|
|
212
|
-
const action = jest.fn();
|
|
213
|
-
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
214
|
-
|
|
215
|
-
// Act
|
|
216
|
-
act(() => {
|
|
217
|
-
jest.advanceTimersByTime(1001);
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
// Assert
|
|
221
|
-
expect(result.current.isSet).toBe(false);
|
|
222
|
-
});
|
|
223
|
-
|
|
224
|
-
it("should call the action again if 'set' is called after the action was called", () => {
|
|
225
|
-
// Arrange
|
|
226
|
-
const action = jest.fn();
|
|
227
|
-
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
228
|
-
|
|
229
|
-
// Act
|
|
230
|
-
act(() => {
|
|
231
|
-
jest.advanceTimersByTime(1001);
|
|
232
|
-
});
|
|
233
|
-
act(() => {
|
|
234
|
-
result.current.set();
|
|
235
|
-
});
|
|
236
|
-
act(() => {
|
|
237
|
-
jest.advanceTimersByTime(1001);
|
|
238
|
-
});
|
|
239
|
-
|
|
240
|
-
// Assert
|
|
241
|
-
expect(action).toHaveBeenCalledTimes(2);
|
|
242
|
-
});
|
|
243
|
-
|
|
244
|
-
it("should restart the timeout if timeoutMs gets updated", () => {
|
|
245
|
-
// Arrange
|
|
246
|
-
const action = jest.fn();
|
|
247
|
-
const {rerender} = renderHook(
|
|
248
|
-
({timeoutMs}: any) => useTimeout(action, timeoutMs),
|
|
249
|
-
{
|
|
250
|
-
initialProps: {timeoutMs: 1000},
|
|
251
|
-
},
|
|
252
|
-
);
|
|
253
|
-
|
|
254
|
-
// Act
|
|
255
|
-
act(() => {
|
|
256
|
-
jest.advanceTimersByTime(900);
|
|
257
|
-
});
|
|
258
|
-
rerender({timeoutMs: 500});
|
|
259
|
-
act(() => {
|
|
260
|
-
jest.advanceTimersByTime(100);
|
|
261
|
-
});
|
|
262
|
-
|
|
263
|
-
// Assert
|
|
264
|
-
expect(action).not.toHaveBeenCalled();
|
|
265
|
-
act((): void => jest.advanceTimersByTime(500));
|
|
266
|
-
expect(action).toHaveBeenCalled();
|
|
267
|
-
});
|
|
268
|
-
|
|
269
|
-
it("should should timeout after the new timeoutMs if it gets updated", () => {
|
|
270
|
-
// Arrange
|
|
271
|
-
const action = jest.fn();
|
|
272
|
-
const {rerender} = renderHook(
|
|
273
|
-
({timeoutMs}: any) => useTimeout(action, timeoutMs),
|
|
274
|
-
{
|
|
275
|
-
initialProps: {timeoutMs: 1000},
|
|
276
|
-
},
|
|
277
|
-
);
|
|
278
|
-
|
|
279
|
-
// Act
|
|
280
|
-
rerender({timeoutMs: 500});
|
|
281
|
-
act(() => {
|
|
282
|
-
jest.advanceTimersByTime(500);
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// Assert
|
|
286
|
-
expect(action).toHaveBeenCalled();
|
|
287
|
-
});
|
|
288
|
-
|
|
289
|
-
it("should call the new action after re-rendering with a new action", () => {
|
|
290
|
-
// Arrange
|
|
291
|
-
const action1 = jest.fn();
|
|
292
|
-
const action2 = jest.fn();
|
|
293
|
-
const {rerender} = renderHook(
|
|
294
|
-
({action}: any) => useTimeout(action, 1000),
|
|
295
|
-
{
|
|
296
|
-
initialProps: {action: action1},
|
|
297
|
-
},
|
|
298
|
-
);
|
|
299
|
-
|
|
300
|
-
// Act
|
|
301
|
-
rerender({action: action2});
|
|
302
|
-
act(() => {
|
|
303
|
-
jest.advanceTimersByTime(1000);
|
|
304
|
-
});
|
|
305
|
-
|
|
306
|
-
// Assert
|
|
307
|
-
expect(action2).toHaveBeenCalled();
|
|
308
|
-
});
|
|
309
|
-
|
|
310
|
-
it("should not call the original action after re-rendering with a new action", () => {
|
|
311
|
-
// Arrange
|
|
312
|
-
const action1 = jest.fn();
|
|
313
|
-
const action2 = jest.fn();
|
|
314
|
-
const {rerender} = renderHook(
|
|
315
|
-
({action}: any) => useTimeout(action, 1000),
|
|
316
|
-
{
|
|
317
|
-
initialProps: {action: action1},
|
|
318
|
-
},
|
|
319
|
-
);
|
|
320
|
-
|
|
321
|
-
// Act
|
|
322
|
-
rerender({action: action2});
|
|
323
|
-
act(() => {
|
|
324
|
-
jest.advanceTimersByTime(1000);
|
|
325
|
-
});
|
|
326
|
-
|
|
327
|
-
// Assert
|
|
328
|
-
expect(action1).not.toHaveBeenCalled();
|
|
329
|
-
});
|
|
330
|
-
|
|
331
|
-
it("should not call the action if the timeout is cleared", () => {
|
|
332
|
-
// Arrange
|
|
333
|
-
const action = jest.fn();
|
|
334
|
-
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
335
|
-
|
|
336
|
-
// Act
|
|
337
|
-
act(() => {
|
|
338
|
-
result.current.clear();
|
|
339
|
-
});
|
|
340
|
-
act(() => {
|
|
341
|
-
jest.advanceTimersByTime(1000);
|
|
342
|
-
});
|
|
343
|
-
|
|
344
|
-
// Assert
|
|
345
|
-
expect(action).not.toHaveBeenCalled();
|
|
346
|
-
});
|
|
347
|
-
|
|
348
|
-
it("should call the action when the timeout is cleared when passing ClearPolicy.Resolve to clear()", () => {
|
|
349
|
-
// Arrange
|
|
350
|
-
const action = jest.fn();
|
|
351
|
-
const {result} = renderHook(() => useTimeout(action, 1000));
|
|
352
|
-
|
|
353
|
-
// Act
|
|
354
|
-
act(() => {
|
|
355
|
-
result.current.clear(ClearPolicy.Resolve);
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
// Assert
|
|
359
|
-
expect(action).toHaveBeenCalled();
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
it("should call the action on unmount when using ClearPolicy.Resolve in options", () => {
|
|
363
|
-
// Arrange
|
|
364
|
-
const action = jest.fn();
|
|
365
|
-
const {unmount} = renderHook(() =>
|
|
366
|
-
useTimeout(action, 1000, {
|
|
367
|
-
clearPolicy: ClearPolicy.Resolve,
|
|
368
|
-
}),
|
|
369
|
-
);
|
|
370
|
-
|
|
371
|
-
// Act
|
|
372
|
-
unmount();
|
|
373
|
-
|
|
374
|
-
// Assert
|
|
375
|
-
expect(action).toHaveBeenCalled();
|
|
376
|
-
});
|
|
377
|
-
|
|
378
|
-
it("should not call the action on unmount when using the default options", () => {
|
|
379
|
-
// Arrange
|
|
380
|
-
const action = jest.fn();
|
|
381
|
-
const {unmount} = renderHook(() => useTimeout(action, 1000));
|
|
382
|
-
|
|
383
|
-
// Act
|
|
384
|
-
unmount();
|
|
385
|
-
|
|
386
|
-
// Assert
|
|
387
|
-
expect(action).not.toHaveBeenCalled();
|
|
388
|
-
});
|
|
389
|
-
});
|
|
390
|
-
|
|
391
|
-
describe("SchedulePolicy.OnDemand", () => {
|
|
392
|
-
it("should not set the timer on creation", () => {
|
|
393
|
-
// Arrange
|
|
394
|
-
const {result} = renderHook(() =>
|
|
395
|
-
useTimeout(() => {}, 1000, {
|
|
396
|
-
schedulePolicy: SchedulePolicy.OnDemand,
|
|
397
|
-
}),
|
|
398
|
-
);
|
|
399
|
-
|
|
400
|
-
// Act
|
|
401
|
-
|
|
402
|
-
// Assert
|
|
403
|
-
expect(result.current.isSet).toBe(false);
|
|
404
|
-
});
|
|
405
|
-
|
|
406
|
-
it("should not call action after timeoutMs if the timer hasn't been set", () => {
|
|
407
|
-
// Arrange
|
|
408
|
-
const action = jest.fn();
|
|
409
|
-
renderHook(() =>
|
|
410
|
-
useTimeout(action, 1000, {
|
|
411
|
-
schedulePolicy: SchedulePolicy.OnDemand,
|
|
412
|
-
}),
|
|
413
|
-
);
|
|
414
|
-
|
|
415
|
-
// Act
|
|
416
|
-
act(() => {
|
|
417
|
-
jest.advanceTimersByTime(1000);
|
|
418
|
-
});
|
|
419
|
-
|
|
420
|
-
// Assert
|
|
421
|
-
expect(action).not.toHaveBeenCalled();
|
|
422
|
-
});
|
|
423
|
-
|
|
424
|
-
it("should call action after timeoutMs if the timer has been set", () => {
|
|
425
|
-
// Arrange
|
|
426
|
-
const action = jest.fn();
|
|
427
|
-
const {result} = renderHook(() =>
|
|
428
|
-
useTimeout(action, 1000, {
|
|
429
|
-
schedulePolicy: SchedulePolicy.OnDemand,
|
|
430
|
-
}),
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
// Act
|
|
434
|
-
act(() => {
|
|
435
|
-
result.current.set();
|
|
436
|
-
});
|
|
437
|
-
act(() => {
|
|
438
|
-
jest.advanceTimersByTime(1000);
|
|
439
|
-
});
|
|
440
|
-
|
|
441
|
-
// Assert
|
|
442
|
-
expect(action).toHaveBeenCalled();
|
|
443
|
-
});
|
|
444
|
-
|
|
445
|
-
it("should reset the timer after calling set() again", () => {
|
|
446
|
-
// Arrange
|
|
447
|
-
const action = jest.fn();
|
|
448
|
-
const {result} = renderHook(() =>
|
|
449
|
-
useTimeout(action, 750, {
|
|
450
|
-
schedulePolicy: SchedulePolicy.OnDemand,
|
|
451
|
-
}),
|
|
452
|
-
);
|
|
453
|
-
|
|
454
|
-
// Act
|
|
455
|
-
act(() => {
|
|
456
|
-
result.current.set();
|
|
457
|
-
jest.advanceTimersByTime(501);
|
|
458
|
-
result.current.set();
|
|
459
|
-
jest.advanceTimersByTime(501);
|
|
460
|
-
});
|
|
461
|
-
|
|
462
|
-
// Assert
|
|
463
|
-
expect(action).not.toHaveBeenCalled();
|
|
464
|
-
});
|
|
465
|
-
|
|
466
|
-
it("should call the action after calling set() again", () => {
|
|
467
|
-
// Arrange
|
|
468
|
-
const action = jest.fn();
|
|
469
|
-
const {result} = renderHook(() =>
|
|
470
|
-
useTimeout(action, 500, {
|
|
471
|
-
schedulePolicy: SchedulePolicy.OnDemand,
|
|
472
|
-
}),
|
|
473
|
-
);
|
|
474
|
-
|
|
475
|
-
// Act
|
|
476
|
-
act(() => {
|
|
477
|
-
result.current.set();
|
|
478
|
-
jest.advanceTimersByTime(501);
|
|
479
|
-
});
|
|
480
|
-
act(() => {
|
|
481
|
-
result.current.set();
|
|
482
|
-
jest.advanceTimersByTime(501);
|
|
483
|
-
});
|
|
484
|
-
|
|
485
|
-
// Assert
|
|
486
|
-
expect(action).toHaveBeenCalledTimes(2);
|
|
487
|
-
});
|
|
488
|
-
});
|
|
489
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {useEffect, useMemo, useRef} from "react";
|
|
2
|
-
import {ClearPolicy, ActionPolicy} from "../util/policies";
|
|
3
|
-
|
|
4
|
-
import type {IInterval, HookOptions} from "../util/types";
|
|
5
|
-
|
|
6
|
-
import Interval from "../util/interval";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Hook providing access to a scheduled interval.
|
|
10
|
-
*
|
|
11
|
-
* @param action The action to be invoked each time the interval period has
|
|
12
|
-
* passed. By default, this will not cause the interval to restart if it
|
|
13
|
-
* changes. This makes it easier to use with inline lambda functions rather than
|
|
14
|
-
* requiring consumers to wrap their action in a `useCallback`. To change this
|
|
15
|
-
* behavior, see the `actionPolicy` option.
|
|
16
|
-
* @param intervalMs The interval period. If this changes, the interval will
|
|
17
|
-
* be reset per the `schedulePolicy` option.
|
|
18
|
-
* @param options Options for the hook.
|
|
19
|
-
* @param options.actionPolicy Determines how the action is handled when it
|
|
20
|
-
* changes. By default, the action is replaced but the interval is not reset,
|
|
21
|
-
* and the updated action will be invoked when the interval next fires.
|
|
22
|
-
* If you want to reset the interval when the action changes, use
|
|
23
|
-
* `ActionPolicy.Reset`.
|
|
24
|
-
* @param options.clearPolicy Determines how the interval is cleared when the
|
|
25
|
-
* component is unmounted or the interval is recreated. By default, the
|
|
26
|
-
* interval is cleared immediately. If you want to let the interval run to
|
|
27
|
-
* completion, use `ClearPolicy.Resolve`. This is NOT applied if the interval
|
|
28
|
-
* is cleared manually via the `clear()` method on the returned API.
|
|
29
|
-
* @param options.schedulePolicy Determines when the interval is scheduled.
|
|
30
|
-
* By default, the interval is scheduled immediately. If you want to delay
|
|
31
|
-
* scheduling the interval, use `SchedulePolicy.OnDemand`.
|
|
32
|
-
* @returns An `IInterval` API for interacting with the given interval. This
|
|
33
|
-
* API is a no-op if called when not mounted. This means that any calls prior
|
|
34
|
-
* to mounting or after unmounting will not have any effect.
|
|
35
|
-
*/
|
|
36
|
-
export function useInterval(
|
|
37
|
-
action: () => unknown,
|
|
38
|
-
intervalMs: number,
|
|
39
|
-
options: HookOptions = {},
|
|
40
|
-
): IInterval {
|
|
41
|
-
const {actionPolicy, clearPolicy, schedulePolicy} = options;
|
|
42
|
-
const actionProxyRef = useRef<() => unknown>(action);
|
|
43
|
-
const intervalRef = useRef<IInterval | null>(null);
|
|
44
|
-
|
|
45
|
-
// Since we are passing our proxy function to the interval instance,
|
|
46
|
-
// it's check that the action is a function will never fail. So, we have to
|
|
47
|
-
// do that check ourselves, and we do it here.
|
|
48
|
-
if (typeof action !== "function") {
|
|
49
|
-
throw new Error("Action must be a function");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// If we're rendered with an updated action, we want to update the ref
|
|
53
|
-
// so the existing interval gets the new action, and then reset the
|
|
54
|
-
// interval if our action policy calls for it.
|
|
55
|
-
if (action !== actionProxyRef.current) {
|
|
56
|
-
actionProxyRef.current = action;
|
|
57
|
-
if (actionPolicy === ActionPolicy.Reset) {
|
|
58
|
-
intervalRef.current?.set();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// This effect updates the interval when the intervalMs, clearPolicy,
|
|
63
|
-
// or schedulePolicy changes.
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
// Make a new interval.
|
|
66
|
-
intervalRef.current = new Interval(
|
|
67
|
-
() => {
|
|
68
|
-
actionProxyRef.current?.();
|
|
69
|
-
},
|
|
70
|
-
intervalMs,
|
|
71
|
-
schedulePolicy,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// Clear the interval when the effect is cleaned up, if necessary,
|
|
75
|
-
// making sure to use the clear policy.
|
|
76
|
-
return () => {
|
|
77
|
-
intervalRef.current?.clear(clearPolicy);
|
|
78
|
-
intervalRef.current = null;
|
|
79
|
-
};
|
|
80
|
-
}, [intervalMs, clearPolicy, schedulePolicy]);
|
|
81
|
-
|
|
82
|
-
// This is the API we expose to the consumer. We expose this rather than
|
|
83
|
-
// the interval instance itself so that the API we give back is stable
|
|
84
|
-
// even if the underlying interval instance changes.
|
|
85
|
-
const externalApi = useMemo(
|
|
86
|
-
() => ({
|
|
87
|
-
set: () => {
|
|
88
|
-
intervalRef.current?.set();
|
|
89
|
-
},
|
|
90
|
-
clear: (policy?: ClearPolicy) => {
|
|
91
|
-
intervalRef.current?.clear(policy);
|
|
92
|
-
},
|
|
93
|
-
get isSet() {
|
|
94
|
-
return intervalRef.current?.isSet ?? false;
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
[],
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
return externalApi;
|
|
101
|
-
}
|
package/src/hooks/use-timeout.ts
DELETED
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
import {useEffect, useMemo, useRef} from "react";
|
|
2
|
-
import {ClearPolicy, ActionPolicy} from "../util/policies";
|
|
3
|
-
|
|
4
|
-
import type {ITimeout, HookOptions} from "../util/types";
|
|
5
|
-
|
|
6
|
-
import Timeout from "../util/timeout";
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Hook providing access to a scheduled timeout.
|
|
10
|
-
*
|
|
11
|
-
* @param action The action to be invoked when the timeout period has
|
|
12
|
-
* passed. By default, this will not cause the timeout to restart if it changes.
|
|
13
|
-
* This makes it easier to use with inline lambda functions rather than
|
|
14
|
-
* requiring consumers to wrap their action in a `useCallback`. To change
|
|
15
|
-
* this behavior, see the `actionPolicy` option.
|
|
16
|
-
* @param timeoutMs The timeout period. If this changes, the timeout will
|
|
17
|
-
* be reset per the `schedulePolicy` option.
|
|
18
|
-
* @param options Options for the hook.
|
|
19
|
-
* @param options.actionPolicy Determines how the action is handled when it
|
|
20
|
-
* changes. By default, the action is replaced but the timeout is not reset,
|
|
21
|
-
* and the updated action will be invoked when the timeout next fires.
|
|
22
|
-
* If you want to reset the timeout when the action changes, use
|
|
23
|
-
* `ActionPolicy.Reset`.
|
|
24
|
-
* @param options.clearPolicy Determines how the timeout is cleared when the
|
|
25
|
-
* component is unmounted or the timeout is recreated. By default, the
|
|
26
|
-
* timeout is cleared immediately. If you want to let the timeout run to
|
|
27
|
-
* completion, use `ClearPolicy.Resolve`. This is NOT applied if the timeout
|
|
28
|
-
* is cleared manually via the `clear()` method on the returned API.
|
|
29
|
-
* @param options.schedulePolicy Determines when the timeout is scheduled.
|
|
30
|
-
* By default, the timeout is scheduled immediately. If you want to delay
|
|
31
|
-
* scheduling the timeout, use `SchedulePolicy.OnDemand`.
|
|
32
|
-
* @returns An `ITimeout` API for interacting with the given timeout. This
|
|
33
|
-
* API is a no-op if called when not mounted. This means that any calls prior
|
|
34
|
-
* to mounting or after unmounting will not have any effect.
|
|
35
|
-
*/
|
|
36
|
-
export function useTimeout(
|
|
37
|
-
action: () => unknown,
|
|
38
|
-
timeoutMs: number,
|
|
39
|
-
options: HookOptions = {},
|
|
40
|
-
): ITimeout {
|
|
41
|
-
const {actionPolicy, clearPolicy, schedulePolicy} = options;
|
|
42
|
-
const actionProxyRef = useRef<() => unknown>(action);
|
|
43
|
-
const timeoutRef = useRef<ITimeout | null>(null);
|
|
44
|
-
|
|
45
|
-
// Since we are passing our proxy function to the timeout instance,
|
|
46
|
-
// it's check that the action is a function will never fail. So, we have to
|
|
47
|
-
// do that check ourselves, and we do it here.
|
|
48
|
-
if (typeof action !== "function") {
|
|
49
|
-
throw new Error("Action must be a function");
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
// If we're rendered with an updated action, we want to update the ref
|
|
53
|
-
// so the existing timeout gets the new action, and then reset the
|
|
54
|
-
// timeout if our action policy calls for it.
|
|
55
|
-
if (action !== actionProxyRef.current) {
|
|
56
|
-
actionProxyRef.current = action;
|
|
57
|
-
if (actionPolicy === ActionPolicy.Reset) {
|
|
58
|
-
timeoutRef.current?.set();
|
|
59
|
-
}
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
// This effect updates the timeout when the timeoutMs, clearPolicy,
|
|
63
|
-
// or schedulePolicy changes.
|
|
64
|
-
useEffect(() => {
|
|
65
|
-
// Make a new timeout.
|
|
66
|
-
timeoutRef.current = new Timeout(
|
|
67
|
-
() => {
|
|
68
|
-
actionProxyRef.current?.();
|
|
69
|
-
},
|
|
70
|
-
timeoutMs,
|
|
71
|
-
schedulePolicy,
|
|
72
|
-
);
|
|
73
|
-
|
|
74
|
-
// Clear the interval when the effect is cleaned up, if necessary,
|
|
75
|
-
// making sure to use the clear policy.
|
|
76
|
-
return () => {
|
|
77
|
-
timeoutRef.current?.clear(clearPolicy);
|
|
78
|
-
timeoutRef.current = null;
|
|
79
|
-
};
|
|
80
|
-
}, [timeoutMs, clearPolicy, schedulePolicy]);
|
|
81
|
-
|
|
82
|
-
// This is the API we expose to the consumer. We expose this rather than
|
|
83
|
-
// the interval instance itself so that the API we give back is stable
|
|
84
|
-
// even if the underlying interval instance changes.
|
|
85
|
-
const externalApi = useMemo(
|
|
86
|
-
() => ({
|
|
87
|
-
set: () => {
|
|
88
|
-
timeoutRef.current?.set();
|
|
89
|
-
},
|
|
90
|
-
clear: (policy?: ClearPolicy) => {
|
|
91
|
-
timeoutRef.current?.clear(policy);
|
|
92
|
-
},
|
|
93
|
-
get isSet() {
|
|
94
|
-
return timeoutRef.current?.isSet ?? false;
|
|
95
|
-
},
|
|
96
|
-
}),
|
|
97
|
-
[],
|
|
98
|
-
);
|
|
99
|
-
|
|
100
|
-
return externalApi;
|
|
101
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
export type {
|
|
2
|
-
IAnimationFrame,
|
|
3
|
-
IInterval,
|
|
4
|
-
IScheduleActions,
|
|
5
|
-
ITimeout,
|
|
6
|
-
WithActionScheduler,
|
|
7
|
-
WithActionSchedulerProps,
|
|
8
|
-
WithoutActionScheduler,
|
|
9
|
-
HookOptions,
|
|
10
|
-
Options,
|
|
11
|
-
} from "./util/types";
|
|
12
|
-
|
|
13
|
-
export {SchedulePolicy, ClearPolicy, ActionPolicy} from "./util/policies";
|
|
14
|
-
export {default as ActionSchedulerProvider} from "./components/action-scheduler-provider";
|
|
15
|
-
export {default as withActionScheduler} from "./components/with-action-scheduler";
|
|
16
|
-
export {useInterval} from "./hooks/use-interval";
|
|
17
|
-
export {useTimeout} from "./hooks/use-timeout";
|