@lowentry/react-redux 0.2.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/LeRed.js ADDED
@@ -0,0 +1,977 @@
1
+ import * as FastDeepEqualReact from 'fast-deep-equal/react';
2
+ import * as RTK from '@reduxjs/toolkit';
3
+ import * as React from 'react';
4
+ import * as ReactDOM from 'react-dom';
5
+ import * as ReactRedux from 'react-redux';
6
+ import * as ReduxSaga from 'redux-saga';
7
+ import * as ReduxSagaEffects from 'redux-saga/effects';
8
+ import {ISSET, ARRAY, STRING} from './LeTypes.js';
9
+ import {LeUtils} from './LeUtils.js';
10
+
11
+ export const LeRed = (() =>
12
+ {
13
+ let LeRed = {
14
+ // for editor auto-complete >>
15
+ createTheme: () =>
16
+ {
17
+ },
18
+ useDispatch: () =>
19
+ {
20
+ },
21
+ useDrag: () =>
22
+ {
23
+ },
24
+ useDrop: () =>
25
+ {
26
+ },
27
+ useDragLayer:() =>
28
+ {
29
+ },
30
+ effects: {},
31
+ // for editor auto-complete <<
32
+ };
33
+ LeRed = {};
34
+
35
+ try
36
+ {
37
+ const set = (value, key, ignoreOverrides = false) =>
38
+ {
39
+ const keyFirstChar = key.charAt(0);
40
+ if(keyFirstChar === keyFirstChar.toLowerCase() && (keyFirstChar !== keyFirstChar.toUpperCase()))
41
+ {
42
+ if((key === 'default') || (key === 'version'))
43
+ {
44
+ return;
45
+ }
46
+ if((key === 'set') || (key === 'setAll'))
47
+ {
48
+ console.error('tried to override LeRed["' + key + '"], which isn\'t allowed, to:');
49
+ console.error(value);
50
+ return;
51
+ }
52
+ if((ignoreOverrides !== true) && (key in LeRed))
53
+ {
54
+ console.warn('LeRed["' + key + '"] was overwritten, from:');
55
+ console.warn(LeRed[key]);
56
+ console.warn('to:');
57
+ console.warn(value);
58
+ }
59
+ LeRed[key] = value;
60
+ }
61
+ };
62
+ const setAll = (obj, ignoreOverrides = false, optionalSkipHasOwnPropertyCheck = true) =>
63
+ {
64
+ LeUtils.each(obj, (value, key) =>
65
+ {
66
+ set(value, key, ignoreOverrides);
67
+ }, optionalSkipHasOwnPropertyCheck);
68
+ };
69
+
70
+ LeRed.set = (value, key) => set(value, key, true);
71
+ LeRed.setAll = (obj, optionalSkipHasOwnPropertyCheck = true) => setAll(obj, true, optionalSkipHasOwnPropertyCheck);
72
+
73
+ setAll(ReactDOM);
74
+ setAll(ReduxSaga);
75
+ setAll({effects:ReduxSagaEffects});
76
+ setAll(RTK);
77
+ setAll(React);
78
+ setAll(ReactRedux);
79
+
80
+ LeRed.effects.delayFrames = function* (frames = 1)
81
+ {
82
+ yield LeRed.effects.call(() =>
83
+ {
84
+ return new Promise((resolve, reject) =>
85
+ {
86
+ try
87
+ {
88
+ LeUtils.setAnimationFrameTimeout(resolve, frames);
89
+ }
90
+ catch(e)
91
+ {
92
+ reject(e);
93
+ }
94
+ });
95
+ });
96
+ };
97
+
98
+ LeRed.effects.interval = function* (callback, intervalMs)
99
+ {
100
+ let channel = LeRed.eventChannel((emitter) =>
101
+ {
102
+ const interval = setInterval(() =>
103
+ {
104
+ emitter({});
105
+ }, intervalMs);
106
+ return () =>
107
+ {
108
+ clearInterval(interval);
109
+ };
110
+ });
111
+
112
+ const stop = () =>
113
+ {
114
+ try
115
+ {
116
+ if(channel !== null)
117
+ {
118
+ channel.close();
119
+ channel = null;
120
+ }
121
+ }
122
+ catch(e)
123
+ {
124
+ console.error(e);
125
+ }
126
+ };
127
+
128
+ while(channel !== null)
129
+ {
130
+ try
131
+ {
132
+ yield LeRed.effects.take(channel);
133
+ yield callback(stop);
134
+ }
135
+ catch(e)
136
+ {
137
+ console.error(e);
138
+ }
139
+ finally
140
+ {
141
+ try
142
+ {
143
+ if(yield LeRed.effects.cancelled())
144
+ {
145
+ channel.close();
146
+ channel = null;
147
+ }
148
+ }
149
+ catch(e)
150
+ {
151
+ console.error(e);
152
+ }
153
+ }
154
+ }
155
+ };
156
+ }
157
+ catch(e)
158
+ {
159
+ console.error(e);
160
+ }
161
+
162
+
163
+ const fixEqualsComparator = (equalsComparator, errorMessage) =>
164
+ {
165
+ if(ISSET(equalsComparator))
166
+ {
167
+ if(typeof equalsComparator !== 'function')
168
+ {
169
+ console.error(errorMessage);
170
+ console.error(equalsComparator);
171
+ return FastDeepEqualReact;
172
+ }
173
+ return equalsComparator;
174
+ }
175
+ return FastDeepEqualReact;
176
+ };
177
+
178
+ const useCompareMemoize = (value, equalsComparator) =>
179
+ {
180
+ const ref = React.useRef();
181
+ if(!equalsComparator(value, ref.current))
182
+ {
183
+ ref.current = value;
184
+ }
185
+ return ref.current;
186
+ };
187
+
188
+
189
+ LeRed.Root = LeRed.memo(({store, children}) =>
190
+ {
191
+ return React.createElement(ReactRedux.Provider, {store}, children);
192
+ });
193
+
194
+ LeRed.createRootElement = (elementClass, storeData) =>
195
+ {
196
+ if(ISSET(storeData))
197
+ {
198
+ storeData = LeRed.configureStore(storeData);
199
+ return React.createElement(ReactRedux.Provider, {store:storeData}, React.createElement(elementClass));
200
+ }
201
+ return React.createElement(elementClass);
202
+ };
203
+
204
+ LeRed.createElement = (elementClass, props = null, ...children) =>
205
+ {
206
+ return React.createElement(elementClass, props, ...children);
207
+ };
208
+
209
+ LeRed.configureStore = (storeData) =>
210
+ {
211
+ if(storeData.__lowentry_store__ === true)
212
+ {
213
+ return storeData;
214
+ }
215
+ if(ISSET(storeData.slices))
216
+ {
217
+ storeData.reducer = storeData.slices;
218
+ delete storeData.slices;
219
+ }
220
+ let sagaListeners = [];
221
+ if(ISSET(storeData.reducer))
222
+ {
223
+ let slices = storeData.reducer;
224
+ if(typeof slices === 'object')
225
+ {
226
+ if(slices.name || slices.__lowentry_unfinished_slice)
227
+ {
228
+ slices = [slices];
229
+ }
230
+ else
231
+ {
232
+ let slicesArray = [];
233
+ LeUtils.each(slices, (slice, index) =>
234
+ {
235
+ if(!slice.name)
236
+ {
237
+ slice.name = index;
238
+ }
239
+ slicesArray.push(slice);
240
+ });
241
+ slices = slicesArray;
242
+ }
243
+ }
244
+ slices = ARRAY(slices);
245
+
246
+ let initialState = {};
247
+ let reducerArrays = {};
248
+ LeUtils.each(slices, (slice, index) =>
249
+ {
250
+ if(!slice.name)
251
+ {
252
+ slice.name = 'slice_' + index;
253
+ }
254
+ if(slice.__lowentry_unfinished_slice)
255
+ {
256
+ delete slice.__lowentry_unfinished_slice;
257
+ slice = LeRed.createSlice(slice);
258
+ }
259
+ initialState[slice.name] = ((typeof slice.state === 'function') ? slice.state() : slice.state);
260
+ LeUtils.each(slice.reducers, (reducer, reducerName) =>
261
+ {
262
+ const fullReducerName = reducerName.startsWith('lowentrystore/') ? reducerName.substring('lowentrystore/'.length) : (slice.name + '/' + reducerName);
263
+ if(typeof reducerArrays[fullReducerName] === 'undefined')
264
+ {
265
+ reducerArrays[fullReducerName] = [];
266
+ }
267
+ reducerArrays[fullReducerName].push(reducer);
268
+ });
269
+ LeUtils.each(slice.sagaListeners, (sagaListener, reducerName) =>
270
+ {
271
+ LeUtils.each(LeUtils.flattenArray(sagaListener), (listener) =>
272
+ {
273
+ try
274
+ {
275
+ sagaListeners.push(listener());
276
+ }
277
+ catch(e)
278
+ {
279
+ console.error('an error was thrown by your saga code, in slice "' + slice.name + '", action "' + reducerName + '":');
280
+ console.error(e);
281
+ }
282
+ });
283
+ });
284
+ });
285
+
286
+ let reducers = {};
287
+ LeUtils.each(reducerArrays, (reducerArray, reducerName) =>
288
+ {
289
+ reducerArray = LeUtils.flattenArray(reducerArray);
290
+ if(reducerArray.length <= 0)
291
+ {
292
+ reducers[reducerName] = reducerArray[0];
293
+ }
294
+ else
295
+ {
296
+ reducers[reducerName] = (...args) =>
297
+ {
298
+ LeUtils.each(reducerArray, (reducer) =>
299
+ {
300
+ try
301
+ {
302
+ reducer(...args);
303
+ }
304
+ catch(e)
305
+ {
306
+ console.error(e);
307
+ }
308
+ });
309
+ };
310
+ }
311
+ });
312
+
313
+ storeData.reducer = RTK.createSlice({
314
+ name: 'lowentrystore',
315
+ initialState:initialState,
316
+ reducers: reducers,
317
+ }).reducer;
318
+ }
319
+ if(ISSET(storeData.state))
320
+ {
321
+ storeData.preloadedState = storeData.state;
322
+ delete storeData.state;
323
+ }
324
+ if(ISSET(storeData.preloadedState))
325
+ {
326
+ storeData.preloadedState = {reducer:storeData.preloadedState};
327
+ }
328
+
329
+ let middleware = ARRAY(storeData.middleware);
330
+ let sagaMiddleware = null;
331
+ if(sagaListeners.length > 0)
332
+ {
333
+ sagaMiddleware = ReduxSaga.default();
334
+ middleware.push(sagaMiddleware);
335
+ }
336
+ storeData.middleware = (getDefaultMiddleware) => getDefaultMiddleware().concat(...middleware);
337
+
338
+ let store = RTK.configureStore(storeData);
339
+ store.__lowentry_store__ = true;
340
+ store.state = () => store.getState().reducer;
341
+
342
+ const dispatch = store.dispatch;
343
+ // noinspection JSValidateTypes
344
+ store.dispatch = (action) =>
345
+ {
346
+ action.__lowentry_dispatch__ = true;
347
+ if(STRING(action.type).startsWith('lowentrystore/lowentryaction/'))
348
+ {
349
+ action.__lowentry_dispatch_result__ = [];
350
+ }
351
+ else
352
+ {
353
+ delete action.__lowentry_dispatch_result__;
354
+ }
355
+ dispatch(action);
356
+ const result = action.__lowentry_dispatch_result__;
357
+ delete action.__lowentry_dispatch_result__;
358
+ delete action.__lowentry_dispatch__;
359
+ return result;
360
+ };
361
+
362
+ if(sagaMiddleware !== null)
363
+ {
364
+ sagaMiddleware.run(function* ()
365
+ {
366
+ yield ReduxSagaEffects.all(sagaListeners);
367
+ });
368
+ }
369
+ return store;
370
+ };
371
+
372
+ LeRed.createAction = (id) =>
373
+ {
374
+ return RTK.createAction('lowentrystore/lowentryaction/' + id);
375
+ };
376
+
377
+ LeRed.createSelector = (selectorsGenerator) =>
378
+ {
379
+ return function(stateOfSlice)
380
+ {
381
+ const state = this;
382
+ const selectors = selectorsGenerator.apply(state, [stateOfSlice]);
383
+ let selectorArgs = [];
384
+
385
+ for(let i = 0; i < selectors.length - 1; i++)
386
+ {
387
+ let selectorsEntry = selectors[i];
388
+ if(typeof selectorsEntry === 'function')
389
+ {
390
+ selectorsEntry = selectorsEntry.apply(state, [state]);
391
+ }
392
+ selectorArgs.push(selectorsEntry);
393
+ }
394
+
395
+ let finalSelector = selectors[selectors.length - 1];
396
+ if(typeof finalSelector === 'function')
397
+ {
398
+ finalSelector = finalSelector.apply(state, selectorArgs);
399
+ }
400
+ return finalSelector;
401
+ };
402
+ };
403
+
404
+ LeRed.createCachedSelector = (selectorsGenerator, equalsComparator) =>
405
+ {
406
+ equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.createCachedSelector() was given an invalid comparator:');
407
+ if(equalsComparator === false)
408
+ {
409
+ return;
410
+ }
411
+ let previousSelectorArgs = null;
412
+ let previousFinalSelectorResult = null;
413
+ return function(stateOfSlice)
414
+ {
415
+ const state = this;
416
+ const selectors = selectorsGenerator.apply(state, [stateOfSlice]);
417
+ let selectorArgs = [];
418
+
419
+ for(let i = 0; i < selectors.length - 1; i++)
420
+ {
421
+ let selectorsEntry = selectors[i];
422
+ if(typeof selectorsEntry === 'function')
423
+ {
424
+ selectorsEntry = selectorsEntry.apply(state, [state]);
425
+ }
426
+ selectorArgs.push(selectorsEntry);
427
+ }
428
+
429
+ let finalSelector = selectors[selectors.length - 1];
430
+ if(typeof finalSelector === 'function')
431
+ {
432
+ if(equalsComparator(previousSelectorArgs, selectorArgs))
433
+ {
434
+ finalSelector = previousFinalSelectorResult;
435
+ }
436
+ else
437
+ {
438
+ finalSelector = finalSelector.apply(state, selectorArgs);
439
+ previousSelectorArgs = selectorArgs;
440
+ previousFinalSelectorResult = finalSelector;
441
+ }
442
+ }
443
+ return finalSelector;
444
+ };
445
+ };
446
+
447
+ LeRed.createSlice = (slice) =>
448
+ {
449
+ if(Array.isArray(slice))
450
+ {
451
+ const e = new Error('the given slice is an array (instead of an object)');
452
+ console.error('an error was thrown by your LeRed.createSlice(...) code:');
453
+ console.error(e);
454
+ throw e;
455
+ }
456
+ if(slice.name)
457
+ {
458
+ let actions = {};
459
+ let reducers = {};
460
+ let sagas = {};
461
+ let sagaListeners = {};
462
+ LeUtils.each(slice.actions, (reducer, reducerNames) =>
463
+ {
464
+ LeUtils.each(reducerNames.split(','), (reducerName) =>
465
+ {
466
+ if(reducerName.length <= 0)
467
+ {
468
+ return;
469
+ }
470
+ const reducerAction = RTK.createAction((reducerName.startsWith('lowentrystore/') ? '' : ('lowentrystore/' + slice.name + '/')) + reducerName);
471
+ actions[reducerName] = reducerAction;
472
+ LeUtils.each(LeUtils.flattenArray(reducer), (reducer) =>
473
+ {
474
+ if(LeUtils.isGeneratorFunction(reducer))
475
+ {
476
+ const sagaListener = function* ()
477
+ {
478
+ yield ReduxSagaEffects.takeEvery(reducerAction, function* (action)
479
+ {
480
+ let promiseResolve = null;
481
+ let promiseReject = null;
482
+ try
483
+ {
484
+ if(action.__lowentry_dispatch__ === true)
485
+ {
486
+ const promise = new Promise((resolve, reject) =>
487
+ {
488
+ promiseResolve = resolve;
489
+ promiseReject = reject;
490
+ });
491
+ if(Array.isArray(action.__lowentry_dispatch_result__))
492
+ {
493
+ if(typeof promise !== 'undefined')
494
+ {
495
+ action.__lowentry_dispatch_result__.push(promise);
496
+ }
497
+ }
498
+ else
499
+ {
500
+ action.__lowentry_dispatch_result__ = promise;
501
+ }
502
+ }
503
+
504
+ const result = yield reducer.apply(slice, [action.payload]);
505
+ if(promiseResolve !== null)
506
+ {
507
+ promiseResolve(result);
508
+ }
509
+ }
510
+ catch(e)
511
+ {
512
+ console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", action "' + reducerName + '":');
513
+ console.error(e);
514
+ if(promiseReject !== null)
515
+ {
516
+ try
517
+ {
518
+ promiseReject(e);
519
+ }
520
+ catch(e2)
521
+ {
522
+ console.error(e2);
523
+ }
524
+ }
525
+ }
526
+ });
527
+ };
528
+
529
+ if(ISSET(sagas[reducerName]))
530
+ {
531
+ sagas[reducerName].push(reducer);
532
+ }
533
+ else
534
+ {
535
+ sagas[reducerName] = [reducer];
536
+ }
537
+
538
+ if(ISSET(sagaListeners[reducerName]))
539
+ {
540
+ sagaListeners[reducerName].push(sagaListener);
541
+ }
542
+ else
543
+ {
544
+ sagaListeners[reducerName] = [sagaListener];
545
+ }
546
+ }
547
+ else
548
+ {
549
+ const reducerFunction = (state, action) =>
550
+ {
551
+ try
552
+ {
553
+ const result = reducer.apply(state, [state[slice.name], action.payload]);
554
+ if(action.__lowentry_dispatch__ === true)
555
+ {
556
+ if(Array.isArray(action.__lowentry_dispatch_result__))
557
+ {
558
+ if(typeof result !== 'undefined')
559
+ {
560
+ action.__lowentry_dispatch_result__.push(result);
561
+ }
562
+ }
563
+ else
564
+ {
565
+ action.__lowentry_dispatch_result__ = result;
566
+ }
567
+ }
568
+ }
569
+ catch(e)
570
+ {
571
+ console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", action "' + reducerName + '":');
572
+ console.error(e);
573
+ }
574
+ };
575
+
576
+ if(ISSET(reducers[reducerName]))
577
+ {
578
+ reducers[reducerName].push(reducerFunction);
579
+ }
580
+ else
581
+ {
582
+ reducers[reducerName] = [reducerFunction];
583
+ }
584
+ }
585
+ });
586
+ });
587
+ });
588
+ slice.actions = actions;
589
+ slice.reducers = reducers;
590
+ slice.sagas = sagas;
591
+ slice.sagaListeners = sagaListeners;
592
+
593
+ let selectors = {};
594
+ LeUtils.each(slice.selectors, (selector, selectorName) =>
595
+ {
596
+ selectors[selectorName] = (state) =>
597
+ {
598
+ try
599
+ {
600
+ return selector.apply(state, [state[slice.name]]);
601
+ }
602
+ catch(e)
603
+ {
604
+ console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", selector "' + selectorName + '":');
605
+ console.error(e);
606
+ throw e;
607
+ }
608
+ };
609
+ });
610
+ slice.selectors = selectors;
611
+
612
+ let getters = {};
613
+ LeUtils.each(slice.getters, (getter, getterName) =>
614
+ {
615
+ getters[getterName] = (...params) =>
616
+ {
617
+ try
618
+ {
619
+ const selector = getter.apply(window, [params]);
620
+ return (state) =>
621
+ {
622
+ try
623
+ {
624
+ return selector.apply(state, [state[slice.name]]);
625
+ }
626
+ catch(e)
627
+ {
628
+ console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", getter "' + getterName + '":');
629
+ console.error(e);
630
+ throw e;
631
+ }
632
+ };
633
+ }
634
+ catch(e)
635
+ {
636
+ console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", getter "' + getterName + '":');
637
+ console.error(e);
638
+ throw e;
639
+ }
640
+ };
641
+ });
642
+ slice.getters = getters;
643
+ return slice;
644
+ }
645
+ else
646
+ {
647
+ slice.__lowentry_unfinished_slice = true;
648
+ return slice;
649
+ }
650
+ };
651
+
652
+ LeRed.createFastSlice = (slice) =>
653
+ {
654
+ if(Array.isArray(slice))
655
+ {
656
+ const e = new Error('the given slice is an array (instead of an object)');
657
+ console.error('an error was thrown by your LeRed.createFastSlice(...) code:');
658
+ console.error(e);
659
+ throw e;
660
+ }
661
+
662
+ let actions = {};
663
+ LeUtils.each(slice.actions, (reducer, reducerName) =>
664
+ {
665
+ actions[reducerName] = (...params) => reducer.apply(slice, [slice.state, ...params]);
666
+ });
667
+ slice.actions = actions;
668
+
669
+ let selectors = {};
670
+ LeUtils.each(slice.selectors, (selector, selectorName) =>
671
+ {
672
+ selectors[selectorName] = () => selector.apply(slice, [slice.state]);
673
+ });
674
+ slice.selectors = new Proxy(selectors, {
675
+ get:(target, key) => (key in target) ? target[key]() : undefined,
676
+ });
677
+
678
+ let getters = {};
679
+ LeUtils.each(slice.getters, (selector, selectorName) =>
680
+ {
681
+ getters[selectorName] = (...params) => selector.apply(slice, [slice.state, ...params]);
682
+ });
683
+ slice.getters = getters;
684
+ return slice;
685
+ };
686
+
687
+ LeRed.current = (obj) =>
688
+ {
689
+ try
690
+ {
691
+ return RTK.current(obj);
692
+ }
693
+ catch(e)
694
+ {
695
+ }
696
+ return obj;
697
+ };
698
+
699
+ LeRed.useConfigureStore = (storeData) =>
700
+ {
701
+ return React.useMemo(() => LeRed.configureStore(storeData), [storeData]);
702
+ };
703
+
704
+ LeRed.useSelector = (selector, equalsComparator) =>
705
+ {
706
+ if(typeof selector !== 'function')
707
+ {
708
+ console.error('LeRed.useSelector() was given an invalid selector:');
709
+ console.error(selector);
710
+ selector = () =>
711
+ {
712
+ };
713
+ }
714
+ equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useSelector() was given an invalid comparator:');
715
+ return ReactRedux.useSelector(selector, equalsComparator);
716
+ };
717
+
718
+ LeRed.useEffect = (callable, comparingValues, equalsComparator) =>
719
+ {
720
+ equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useEffect() was given an invalid comparator:');
721
+ comparingValues = ARRAY(comparingValues);
722
+ // eslint-disable-next-line react-hooks/rules-of-hooks
723
+ comparingValues = comparingValues.map(value => useCompareMemoize(value, equalsComparator));
724
+ // eslint-disable-next-line react-hooks/exhaustive-deps
725
+ return React.useEffect(callable, comparingValues);
726
+ };
727
+
728
+ LeRed.useEffectInterval = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
729
+ {
730
+ return LeRed.useEffect(() => LeUtils.setInterval(callable, intervalMs, fireImmediately).remove, [comparingValues, equalsComparator]);
731
+ };
732
+
733
+ LeRed.useEffectAnimationFrameInterval = (callable, comparingValues, intervalFrames, fireImmediately, equalsComparator) =>
734
+ {
735
+ return LeRed.useEffect(() => LeUtils.setAnimationFrameInterval(callable, intervalFrames, fireImmediately).remove, [comparingValues, equalsComparator]);
736
+ };
737
+
738
+ LeRed.useEffectGenerator = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
739
+ {
740
+ return LeRed.useEffect(() =>
741
+ {
742
+ let stop = false;
743
+
744
+ (async () =>
745
+ {
746
+ for(const promise of callable())
747
+ {
748
+ if(stop)
749
+ {
750
+ return;
751
+ }
752
+ await promise;
753
+ if(stop)
754
+ {
755
+ return;
756
+ }
757
+ }
758
+ })();
759
+
760
+ return () =>
761
+ {
762
+ stop = true;
763
+ };
764
+ }, [comparingValues, equalsComparator]);
765
+ };
766
+
767
+ LeRed.useEffectGeneratorLoop = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
768
+ {
769
+ return LeRed.useEffect(() =>
770
+ {
771
+ let stop = false;
772
+
773
+ (async () =>
774
+ {
775
+ while(!stop)
776
+ {
777
+ for(const promise of callable())
778
+ {
779
+ if(stop)
780
+ {
781
+ return;
782
+ }
783
+ await promise;
784
+ if(stop)
785
+ {
786
+ return;
787
+ }
788
+ }
789
+ }
790
+ })();
791
+
792
+ return () =>
793
+ {
794
+ stop = true;
795
+ };
796
+ }, [comparingValues, equalsComparator]);
797
+ };
798
+
799
+ LeRed.useEffectShutdown = (callable, comparingValues, equalsComparator) =>
800
+ {
801
+ return LeRed.useEffect(() =>
802
+ {
803
+ let stop = false;
804
+
805
+ let end;
806
+
807
+ const run = () =>
808
+ {
809
+ if(stop)
810
+ {
811
+ return;
812
+ }
813
+ callable();
814
+ end();
815
+ };
816
+
817
+ end = () =>
818
+ {
819
+ stop = true;
820
+ window?.removeEventListener('beforeunload', run);
821
+ };
822
+
823
+ window?.addEventListener('beforeunload', run);
824
+
825
+ return run;
826
+ }, [comparingValues, equalsComparator]);
827
+ };
828
+
829
+ LeRed.memo = (component, equalsComparator) =>
830
+ {
831
+ equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.memo() was given an invalid comparator:');
832
+ return React.memo(component, equalsComparator);
833
+ };
834
+
835
+ LeRed.useMemo = (callable, comparingValues, equalsComparator) =>
836
+ {
837
+ equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useMemo() was given an invalid comparator:');
838
+ comparingValues = ARRAY(comparingValues);
839
+ // eslint-disable-next-line react-hooks/rules-of-hooks
840
+ comparingValues = comparingValues.map(value => useCompareMemoize(value, equalsComparator));
841
+ // eslint-disable-next-line react-hooks/exhaustive-deps
842
+ return React.useMemo(callable, comparingValues);
843
+ };
844
+
845
+ LeRed.usePrevious = (value, initialValue) =>
846
+ {
847
+ const ref = LeRed.useRef(initialValue);
848
+ LeRed.useEffect(() =>
849
+ {
850
+ ref.current = value;
851
+ }, [value]);
852
+ return ref.current;
853
+ };
854
+
855
+ LeRed.useFont = (font) =>
856
+ {
857
+ font = '12px ' + STRING(font).trim();
858
+ const [hasFont, setHasFont] = LeRed.useState(false);
859
+ LeRed.useEffect(() =>
860
+ {
861
+ if(!hasFont)
862
+ {
863
+ if(!ISSET(document?.fonts?.check))
864
+ {
865
+ setHasFont(true);
866
+ return;
867
+ }
868
+
869
+ const handler = setInterval(() =>
870
+ {
871
+ try
872
+ {
873
+ if(document.fonts.check(font))
874
+ {
875
+ clearInterval(handler);
876
+ setHasFont(true);
877
+ }
878
+ }
879
+ catch(e)
880
+ {
881
+ console.error(e);
882
+ clearInterval(handler);
883
+ setHasFont(true);
884
+ }
885
+ }, 30);
886
+ }
887
+ }, [hasFont]);
888
+ return hasFont;
889
+ };
890
+
891
+ /**
892
+ * Adds a <script> tag to the <head> of the document.
893
+ * Only for development and testing purposes.
894
+ *
895
+ * @param {string} url The URL of the js file to include.
896
+ * @param {object} props Additional props of the <script> tag.
897
+ */
898
+ LeRed.useScript = (url, props = {}) =>
899
+ {
900
+ return LeRed.useEffect(() =>
901
+ {
902
+ const script = document.createElement('script');
903
+ script.type = 'text/javascript';
904
+ script.src = url;
905
+
906
+ LeUtils.each(props, (value, key) =>
907
+ {
908
+ script.setAttribute(key, value);
909
+ });
910
+
911
+ document.head.appendChild(script);
912
+ return () => document.head.removeChild(script);
913
+ }, [url, props]);
914
+ };
915
+
916
+ LeRed.mergeRefs = (...refs) =>
917
+ {
918
+ refs = LeUtils.flattenArray(refs);
919
+ if(!refs)
920
+ {
921
+ return null;
922
+ }
923
+
924
+ let newRefs = [];
925
+ LeUtils.each(refs, (ref) =>
926
+ {
927
+ if(ref)
928
+ {
929
+ newRefs.push(ref);
930
+ }
931
+ });
932
+ refs = newRefs;
933
+
934
+ if(refs.length <= 0)
935
+ {
936
+ return null;
937
+ }
938
+ if(refs.length === 1)
939
+ {
940
+ return refs[0];
941
+ }
942
+ return (inst) =>
943
+ {
944
+ LeUtils.each(refs, (ref) =>
945
+ {
946
+ try
947
+ {
948
+ if(typeof ref === 'function')
949
+ {
950
+ ref(inst);
951
+ }
952
+ else if(ref)
953
+ {
954
+ ref.current = inst;
955
+ }
956
+ }
957
+ catch(e)
958
+ {
959
+ console.error(e);
960
+ }
961
+ });
962
+ };
963
+ };
964
+
965
+ if(typeof Proxy === 'undefined')
966
+ {
967
+ return LeRed;
968
+ }
969
+
970
+ return new Proxy(LeRed, {
971
+ set:(target, key, value) =>
972
+ {
973
+ LeRed.set(key, value);
974
+ return true;
975
+ },
976
+ });
977
+ })();