@lowentry/react-redux 1.15.4 → 2.0.1

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/src/LeRed.jsx DELETED
@@ -1,1512 +0,0 @@
1
- import * as RTK from '@reduxjs/toolkit';
2
- import * as React from 'react';
3
- import * as ReactDOM from 'react-dom';
4
- import * as ReactRedux from 'react-redux';
5
- import * as ReduxSaga from 'redux-saga';
6
- import * as ReduxSagaEffects from 'redux-saga/effects';
7
- import {LeUtils, ISSET, ARRAY, STRING, INT_LAX_ANY, IS_OBJECT, IS_ARRAY} from '@lowentry/utils';
8
-
9
-
10
- export const LeRed = (() =>
11
- {
12
- /** @type {Object} */
13
- const LeRed = {};
14
-
15
-
16
- try
17
- {
18
- const set = (key, value, ignoreOverrides = false) =>
19
- {
20
- const keyFirstChar = key.charAt(0);
21
- if(keyFirstChar === keyFirstChar.toLowerCase() && (keyFirstChar !== keyFirstChar.toUpperCase()))
22
- {
23
- if((key === 'default') || (key === 'version'))
24
- {
25
- return;
26
- }
27
- if((key === 'set') || (key === 'setAll'))
28
- {
29
- console.error('tried to override LeRed["' + key + '"], which isn\'t allowed, to:');
30
- console.error(value);
31
- return;
32
- }
33
- if((ignoreOverrides !== true) && (key in LeRed))
34
- {
35
- console.warn('LeRed["' + key + '"] was overwritten, from:');
36
- console.warn(LeRed[key]);
37
- console.warn('to:');
38
- console.warn(value);
39
- }
40
- LeRed[key] = value;
41
- }
42
- };
43
- const setAll = (obj, ignoreOverrides = false, optionalSkipHasOwnPropertyCheck = true) =>
44
- {
45
- LeUtils.each(obj, (value, key) =>
46
- {
47
- set(key, value, ignoreOverrides);
48
- }, optionalSkipHasOwnPropertyCheck);
49
- };
50
-
51
- LeRed.set = (key, value) => set(key, value, true);
52
- LeRed.setAll = (obj, optionalSkipHasOwnPropertyCheck = true) => setAll(obj, true, optionalSkipHasOwnPropertyCheck);
53
-
54
- setAll(ReactDOM);
55
- setAll(ReduxSaga);
56
- setAll({effects:{...ReduxSagaEffects}});
57
- setAll(RTK);
58
- setAll(React);
59
- setAll(ReactRedux);
60
-
61
- LeRed.effects.delayFrames = function* (frames = 1)
62
- {
63
- yield LeRed.effects.call(() =>
64
- {
65
- return new Promise((resolve, reject) =>
66
- {
67
- try
68
- {
69
- LeUtils.setAnimationFrameTimeout(resolve, frames);
70
- }
71
- catch(e)
72
- {
73
- reject(e);
74
- }
75
- });
76
- });
77
- };
78
-
79
- LeRed.effects.interval = function* (callback, intervalMs)
80
- {
81
- let channel = LeRed.eventChannel((emitter) =>
82
- {
83
- const interval = setInterval(() =>
84
- {
85
- emitter({});
86
- }, intervalMs);
87
- return () =>
88
- {
89
- clearInterval(interval);
90
- };
91
- });
92
-
93
- const stop = () =>
94
- {
95
- try
96
- {
97
- if(channel !== null)
98
- {
99
- channel.close();
100
- channel = null;
101
- }
102
- }
103
- catch(e)
104
- {
105
- console.error(e);
106
- }
107
- };
108
-
109
- while(channel !== null)
110
- {
111
- try
112
- {
113
- yield LeRed.effects.take(channel);
114
- yield callback(stop);
115
- }
116
- catch(e)
117
- {
118
- console.error(e);
119
- }
120
- finally
121
- {
122
- try
123
- {
124
- if(yield LeRed.effects.cancelled())
125
- {
126
- channel.close();
127
- channel = null;
128
- }
129
- }
130
- catch(e)
131
- {
132
- console.error(e);
133
- }
134
- }
135
- }
136
- };
137
- }
138
- catch(e)
139
- {
140
- console.error(e);
141
- }
142
-
143
-
144
- const fixEqualsComparator = (equalsComparator, errorMessage) =>
145
- {
146
- if(ISSET(equalsComparator))
147
- {
148
- if(typeof equalsComparator !== 'function')
149
- {
150
- console.error(errorMessage);
151
- console.error(equalsComparator);
152
- return LeUtils.equals;
153
- }
154
- return equalsComparator;
155
- }
156
- return LeUtils.equals;
157
- };
158
-
159
- const useCompareMemoize = (value, equalsComparator) =>
160
- {
161
- const ref = React.useRef();
162
- if(!equalsComparator(value, ref.current))
163
- {
164
- ref.current = value;
165
- }
166
- return ref.current;
167
- };
168
-
169
-
170
- LeRed.configureStore = (storeData) =>
171
- {
172
- if(storeData.__lowentry_store__ === true)
173
- {
174
- return storeData;
175
- }
176
- if(ISSET(storeData.slices))
177
- {
178
- storeData.reducer = storeData.slices;
179
- delete storeData.slices;
180
- }
181
- let sagaListeners = [];
182
- if(ISSET(storeData.reducer))
183
- {
184
- let slices = storeData.reducer;
185
- if(typeof slices === 'object')
186
- {
187
- if(slices.name || slices.__lowentry_unfinished_slice)
188
- {
189
- slices = [slices];
190
- }
191
- else
192
- {
193
- let slicesArray = [];
194
- LeUtils.each(slices, (slice, index) =>
195
- {
196
- if(!slice.name)
197
- {
198
- slice.name = index;
199
- }
200
- slicesArray.push(slice);
201
- });
202
- slices = slicesArray;
203
- }
204
- }
205
- slices = ARRAY(slices);
206
-
207
- let initialState = {};
208
- let reducerArrays = {};
209
- LeUtils.each(slices, (slice, index) =>
210
- {
211
- if(!slice.name)
212
- {
213
- slice.name = 'slice_' + index;
214
- }
215
- if(slice.__lowentry_unfinished_slice)
216
- {
217
- delete slice.__lowentry_unfinished_slice;
218
- slice = LeRed.createSlice(slice);
219
- }
220
- initialState[slice.name] = ((typeof slice.state === 'function') ? slice.state() : slice.state);
221
- LeUtils.each(slice.reducers, (reducer, reducerName) =>
222
- {
223
- const fullReducerName = reducerName.startsWith('lowentrystore/') ? reducerName.substring('lowentrystore/'.length) : (slice.name + '/' + reducerName);
224
- if(typeof reducerArrays[fullReducerName] === 'undefined')
225
- {
226
- reducerArrays[fullReducerName] = [];
227
- }
228
- reducerArrays[fullReducerName].push(reducer);
229
- });
230
- LeUtils.each(slice.sagaListeners, (sagaListener, reducerName) =>
231
- {
232
- LeUtils.each(LeUtils.flattenArray(sagaListener), (listener) =>
233
- {
234
- try
235
- {
236
- sagaListeners.push(listener());
237
- }
238
- catch(e)
239
- {
240
- console.error('an error was thrown by your saga code, in slice "' + slice.name + '", action "' + reducerName + '":');
241
- console.error(e);
242
- }
243
- });
244
- });
245
- });
246
-
247
- /** @type {Object|*} */
248
- let reducers = {};
249
- LeUtils.each(reducerArrays, (reducerArray, reducerName) =>
250
- {
251
- reducerArray = LeUtils.flattenArray(reducerArray);
252
- if(reducerArray.length <= 0)
253
- {
254
- reducers[reducerName] = reducerArray[0];
255
- }
256
- else
257
- {
258
- reducers[reducerName] = (...args) =>
259
- {
260
- LeUtils.each(reducerArray, (reducer) =>
261
- {
262
- try
263
- {
264
- reducer(...args);
265
- }
266
- catch(e)
267
- {
268
- console.error(e);
269
- }
270
- });
271
- };
272
- }
273
- });
274
-
275
- storeData.reducer = RTK.createSlice({
276
- name: 'lowentrystore',
277
- initialState:initialState,
278
- reducers: reducers,
279
- }).reducer;
280
- }
281
- if(ISSET(storeData.state))
282
- {
283
- storeData.preloadedState = storeData.state;
284
- delete storeData.state;
285
- }
286
- if(ISSET(storeData.preloadedState))
287
- {
288
- storeData.preloadedState = {reducer:storeData.preloadedState};
289
- }
290
-
291
- let middleware = ARRAY(storeData.middleware);
292
- let sagaMiddleware = null;
293
- if(sagaListeners.length > 0)
294
- {
295
- sagaMiddleware = ReduxSaga.default();
296
- middleware.push(sagaMiddleware);
297
- }
298
- storeData.middleware = (getDefaultMiddleware) => getDefaultMiddleware().concat(...middleware);
299
-
300
- /** @type {RTK.EnhancedStore|*} */
301
- let store = RTK.configureStore(storeData);
302
- store.__lowentry_store__ = true;
303
- store.state = () => store.getState().reducer;
304
-
305
- const dispatch = store.dispatch;
306
- // noinspection JSValidateTypes
307
- store.dispatch = (action) =>
308
- {
309
- action.__lowentry_dispatch__ = true;
310
- if(STRING(action.type).startsWith('lowentrystore/lowentryaction/'))
311
- {
312
- action.__lowentry_dispatch_result__ = [];
313
- }
314
- else
315
- {
316
- delete action.__lowentry_dispatch_result__;
317
- }
318
- dispatch(action);
319
- const result = action.__lowentry_dispatch_result__;
320
- delete action.__lowentry_dispatch_result__;
321
- delete action.__lowentry_dispatch__;
322
- return result;
323
- };
324
-
325
- if(sagaMiddleware !== null)
326
- {
327
- sagaMiddleware.run(function* ()
328
- {
329
- yield ReduxSagaEffects.all(sagaListeners);
330
- });
331
- }
332
- return store;
333
- };
334
-
335
- LeRed.createAction = (id) =>
336
- {
337
- return RTK.createAction('lowentrystore/lowentryaction/' + id);
338
- };
339
-
340
- LeRed.createSelector = (selectorsGenerator) =>
341
- {
342
- return function(stateOfSlice)
343
- {
344
- const state = this;
345
- const selectors = selectorsGenerator.apply(state, [stateOfSlice]);
346
- let selectorArgs = [];
347
-
348
- for(let i = 0; i < selectors.length - 1; i++)
349
- {
350
- let selectorsEntry = selectors[i];
351
- if(typeof selectorsEntry === 'function')
352
- {
353
- selectorsEntry = selectorsEntry.apply(state, [state]);
354
- }
355
- selectorArgs.push(selectorsEntry);
356
- }
357
-
358
- let finalSelector = selectors[selectors.length - 1];
359
- if(typeof finalSelector === 'function')
360
- {
361
- finalSelector = finalSelector.apply(state, selectorArgs);
362
- }
363
- return finalSelector;
364
- };
365
- };
366
-
367
- LeRed.createCachedSelector = (selectorsGenerator, equalsComparator) =>
368
- {
369
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.createCachedSelector() was given an invalid comparator:');
370
- if(equalsComparator === false)
371
- {
372
- return;
373
- }
374
- let previousSelectorArgs = null;
375
- let previousFinalSelectorResult = null;
376
- return function(stateOfSlice)
377
- {
378
- const state = this;
379
- const selectors = selectorsGenerator.apply(state, [stateOfSlice]);
380
- let selectorArgs = [];
381
-
382
- for(let i = 0; i < selectors.length - 1; i++)
383
- {
384
- let selectorsEntry = selectors[i];
385
- if(typeof selectorsEntry === 'function')
386
- {
387
- selectorsEntry = selectorsEntry.apply(state, [state]);
388
- }
389
- selectorArgs.push(selectorsEntry);
390
- }
391
-
392
- let finalSelector = selectors[selectors.length - 1];
393
- if(typeof finalSelector === 'function')
394
- {
395
- if(equalsComparator(previousSelectorArgs, selectorArgs))
396
- {
397
- finalSelector = previousFinalSelectorResult;
398
- }
399
- else
400
- {
401
- finalSelector = finalSelector.apply(state, selectorArgs);
402
- previousSelectorArgs = selectorArgs;
403
- previousFinalSelectorResult = finalSelector;
404
- }
405
- }
406
- return finalSelector;
407
- };
408
- };
409
-
410
- LeRed.createSlice = (slice) =>
411
- {
412
- if(Array.isArray(slice))
413
- {
414
- const e = new Error('the given slice is an array (instead of an object)');
415
- console.error('an error was thrown by your LeRed.createSlice(...) code:');
416
- console.error(e);
417
- throw e;
418
- }
419
- if(slice.name)
420
- {
421
- let actions = {};
422
- let reducers = {};
423
- let sagas = {};
424
- let sagaListeners = {};
425
- LeUtils.each(slice.actions, (reducer, reducerNames) =>
426
- {
427
- LeUtils.each(reducerNames.split(','), (reducerName) =>
428
- {
429
- if(reducerName.length <= 0)
430
- {
431
- return;
432
- }
433
- const reducerAction = RTK.createAction((reducerName.startsWith('lowentrystore/') ? '' : ('lowentrystore/' + slice.name + '/')) + reducerName);
434
- actions[reducerName] = reducerAction;
435
- LeUtils.each(LeUtils.flattenArray(reducer), (reducer) =>
436
- {
437
- if(LeUtils.isGeneratorFunction(reducer))
438
- {
439
- const sagaListener = function* ()
440
- {
441
- yield ReduxSagaEffects.takeEvery(reducerAction, function* (/** @type {RTK.Action|*} */ action)
442
- {
443
- /** @type {((value:*)=>void)|null|*} */
444
- let promiseResolve = null;
445
- /** @type {((reason:*)=>void)|null|*} */
446
- let promiseReject = null;
447
- try
448
- {
449
- if(action.__lowentry_dispatch__ === true)
450
- {
451
- const promise = new Promise((resolve, reject) =>
452
- {
453
- promiseResolve = resolve;
454
- promiseReject = reject;
455
- });
456
- if(Array.isArray(action.__lowentry_dispatch_result__))
457
- {
458
- if(typeof promise !== 'undefined')
459
- {
460
- action.__lowentry_dispatch_result__.push(promise);
461
- }
462
- }
463
- else
464
- {
465
- action.__lowentry_dispatch_result__ = promise;
466
- }
467
- }
468
-
469
- const result = yield reducer.apply(slice, [action.payload]);
470
- if(promiseResolve !== null)
471
- {
472
- promiseResolve(result);
473
- }
474
- }
475
- catch(e)
476
- {
477
- console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", action "' + reducerName + '":');
478
- console.error(e);
479
- if(promiseReject !== null)
480
- {
481
- try
482
- {
483
- promiseReject(e);
484
- }
485
- catch(e2)
486
- {
487
- console.error(e2);
488
- }
489
- }
490
- }
491
- });
492
- };
493
-
494
- if(ISSET(sagas[reducerName]))
495
- {
496
- sagas[reducerName].push(reducer);
497
- }
498
- else
499
- {
500
- sagas[reducerName] = [reducer];
501
- }
502
-
503
- if(ISSET(sagaListeners[reducerName]))
504
- {
505
- sagaListeners[reducerName].push(sagaListener);
506
- }
507
- else
508
- {
509
- sagaListeners[reducerName] = [sagaListener];
510
- }
511
- }
512
- else
513
- {
514
- const reducerFunction = (state, action) =>
515
- {
516
- try
517
- {
518
- const result = reducer.apply(state, [state[slice.name], action.payload]);
519
- if(action.__lowentry_dispatch__ === true)
520
- {
521
- if(Array.isArray(action.__lowentry_dispatch_result__))
522
- {
523
- if(typeof result !== 'undefined')
524
- {
525
- action.__lowentry_dispatch_result__.push(result);
526
- }
527
- }
528
- else
529
- {
530
- action.__lowentry_dispatch_result__ = result;
531
- }
532
- }
533
- }
534
- catch(e)
535
- {
536
- console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", action "' + reducerName + '":');
537
- console.error(e);
538
- }
539
- };
540
-
541
- if(ISSET(reducers[reducerName]))
542
- {
543
- reducers[reducerName].push(reducerFunction);
544
- }
545
- else
546
- {
547
- reducers[reducerName] = [reducerFunction];
548
- }
549
- }
550
- });
551
- });
552
- });
553
- slice.actions = actions;
554
- slice.reducers = reducers;
555
- slice.sagas = sagas;
556
- slice.sagaListeners = sagaListeners;
557
-
558
- let selectors = {};
559
- LeUtils.each(slice.selectors, (selector, selectorName) =>
560
- {
561
- selectors[selectorName] = (state) =>
562
- {
563
- try
564
- {
565
- return selector.apply(state, [state[slice.name]]);
566
- }
567
- catch(e)
568
- {
569
- console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", selector "' + selectorName + '":');
570
- console.error(e);
571
- throw e;
572
- }
573
- };
574
- });
575
- slice.selectors = selectors;
576
-
577
- let getters = {};
578
- LeUtils.each(slice.getters, (getter, getterName) =>
579
- {
580
- getters[getterName] = (...params) =>
581
- {
582
- try
583
- {
584
- const selector = getter(...params);
585
- return (state) =>
586
- {
587
- try
588
- {
589
- return selector.apply(state, [state[slice.name]]);
590
- }
591
- catch(e)
592
- {
593
- console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", getter "' + getterName + '":');
594
- console.error(e);
595
- throw e;
596
- }
597
- };
598
- }
599
- catch(e)
600
- {
601
- console.error('an error was thrown by your LeRed.createSlice(...) code, by slice "' + slice.name + '", getter "' + getterName + '":');
602
- console.error(e);
603
- throw e;
604
- }
605
- };
606
- });
607
- slice.getters = getters;
608
- return slice;
609
- }
610
- else
611
- {
612
- slice.__lowentry_unfinished_slice = true;
613
- return slice;
614
- }
615
- };
616
-
617
- LeRed.createFastSlice = (slice) =>
618
- {
619
- if(Array.isArray(slice))
620
- {
621
- const e = new Error('the given slice is an array (instead of an object)');
622
- console.error('an error was thrown by your LeRed.createFastSlice(...) code:');
623
- console.error(e);
624
- throw e;
625
- }
626
-
627
- let actions = {};
628
- LeUtils.each(slice.actions, (reducer, reducerName) =>
629
- {
630
- actions[reducerName] = (...params) => reducer.apply(slice, [slice.state, ...params]);
631
- });
632
- slice.actions = actions;
633
-
634
- let selectors = {};
635
- LeUtils.each(slice.selectors, (selector, selectorName) =>
636
- {
637
- selectors[selectorName] = () => selector.apply(slice, [slice.state]);
638
- });
639
- slice.selectors = new Proxy(selectors, {
640
- get:(target, key) => (key in target) ? target[key]() : undefined,
641
- });
642
-
643
- let getters = {};
644
- LeUtils.each(slice.getters, (selector, selectorName) =>
645
- {
646
- getters[selectorName] = (...params) => selector.apply(slice, [slice.state, ...params]);
647
- });
648
- slice.getters = getters;
649
- return slice;
650
- };
651
-
652
- LeRed.current = (obj) =>
653
- {
654
- try
655
- {
656
- return RTK.current(obj);
657
- }
658
- catch(e)
659
- {
660
- }
661
- return obj;
662
- };
663
-
664
- LeRed.useConfigureStore = (storeData) =>
665
- {
666
- return LeRed.useMemo(() => LeRed.configureStore(storeData), [storeData]);
667
- };
668
-
669
- LeRed.useSelector = (selector, equalsComparator) =>
670
- {
671
- if(typeof selector !== 'function')
672
- {
673
- console.error('LeRed.useSelector() was given an invalid selector:');
674
- console.error(selector);
675
- selector = () =>
676
- {
677
- };
678
- }
679
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useSelector() was given an invalid comparator:');
680
- return ReactRedux.useSelector(selector, equalsComparator);
681
- };
682
-
683
- LeRed.useEffect = (callable, comparingValues, equalsComparator) =>
684
- {
685
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useEffect() was given an invalid comparator:');
686
- comparingValues = ARRAY(comparingValues);
687
- // eslint-disable-next-line react-hooks/rules-of-hooks
688
- comparingValues = comparingValues.map(value => useCompareMemoize(value, equalsComparator));
689
- // eslint-disable-next-line react-hooks/exhaustive-deps
690
- return React.useEffect(callable, comparingValues);
691
- };
692
-
693
- LeRed.useEffectInterval = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
694
- {
695
- return LeRed.useEffect(() => LeUtils.setInterval(callable, intervalMs, fireImmediately).remove, [comparingValues, equalsComparator]);
696
- };
697
-
698
- LeRed.useEffectAnimationFrameInterval = (callable, comparingValues, intervalFrames, fireImmediately, equalsComparator) =>
699
- {
700
- return LeRed.useEffect(() => LeUtils.setAnimationFrameInterval(callable, intervalFrames, fireImmediately).remove, [comparingValues, equalsComparator]);
701
- };
702
-
703
- LeRed.useEffectGenerator = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
704
- {
705
- return LeRed.useEffect(() =>
706
- {
707
- let stop = false;
708
-
709
- (async () =>
710
- {
711
- for(const promise of callable())
712
- {
713
- if(stop)
714
- {
715
- return;
716
- }
717
- await promise;
718
- if(stop)
719
- {
720
- return;
721
- }
722
- }
723
- })();
724
-
725
- return () =>
726
- {
727
- stop = true;
728
- };
729
- }, [comparingValues, equalsComparator]);
730
- };
731
-
732
- LeRed.useEffectGeneratorLoop = (callable, comparingValues, intervalMs, fireImmediately, equalsComparator) =>
733
- {
734
- return LeRed.useEffect(() =>
735
- {
736
- let stop = false;
737
-
738
- (async () =>
739
- {
740
- while(!stop)
741
- {
742
- for(const promise of callable())
743
- {
744
- if(stop)
745
- {
746
- return;
747
- }
748
- await promise;
749
- if(stop)
750
- {
751
- return;
752
- }
753
- }
754
- }
755
- })();
756
-
757
- return () =>
758
- {
759
- stop = true;
760
- };
761
- }, [comparingValues, equalsComparator]);
762
- };
763
-
764
- LeRed.useEffectShutdown = (callable, comparingValues, equalsComparator) =>
765
- {
766
- return LeRed.useEffect(() =>
767
- {
768
- const run = () =>
769
- {
770
- callable();
771
- };
772
-
773
- globalThis?.addEventListener?.('beforeunload', run, {capture:true});
774
- return () =>
775
- {
776
- globalThis?.removeEventListener?.('beforeunload', run, {capture:true});
777
- run();
778
- };
779
- }, [comparingValues, equalsComparator]);
780
- };
781
-
782
- LeRed.useEffectPageFocusLost = (callable, comparingValues, equalsComparator) =>
783
- {
784
- const events = ['pagehide', 'freeze', 'blur', 'visibilitychange'];
785
- return LeRed.useEffect(() =>
786
- {
787
- const run = () =>
788
- {
789
- if((globalThis?.document?.visibilityState !== 'hidden') && globalThis?.document?.hasFocus?.())
790
- {
791
- return;
792
- }
793
- callable();
794
- };
795
-
796
- events.forEach(type =>
797
- {
798
- globalThis?.addEventListener?.(type, run, {capture:true});
799
- });
800
- return () =>
801
- {
802
- events.forEach(type =>
803
- {
804
- globalThis?.removeEventListener?.(type, run, {capture:true});
805
- });
806
- };
807
- }, [comparingValues, equalsComparator]);
808
- };
809
-
810
- LeRed.memo = (component, equalsComparator) =>
811
- {
812
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.memo() was given an invalid comparator:');
813
- return React.memo(component, equalsComparator);
814
- };
815
-
816
- LeRed.useMemo = (callable, comparingValues, equalsComparator) =>
817
- {
818
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useMemo() was given an invalid comparator:');
819
- comparingValues = ARRAY(comparingValues);
820
- // eslint-disable-next-line react-hooks/rules-of-hooks
821
- comparingValues = comparingValues.map(value => useCompareMemoize(value, equalsComparator));
822
- // eslint-disable-next-line react-hooks/exhaustive-deps
823
- return React.useMemo(callable, comparingValues);
824
- };
825
-
826
- LeRed.useCallback = (callable, comparingValues, equalsComparator) =>
827
- {
828
- equalsComparator = fixEqualsComparator(equalsComparator, 'LeRed.useCallback() was given an invalid comparator:');
829
- comparingValues = ARRAY(comparingValues);
830
- // eslint-disable-next-line react-hooks/rules-of-hooks
831
- comparingValues = comparingValues.map(value => useCompareMemoize(value, equalsComparator));
832
- // eslint-disable-next-line react-hooks/exhaustive-deps
833
- return React.useCallback(callable, comparingValues);
834
- };
835
-
836
- LeRed.usePrevious = (value, initialValue) =>
837
- {
838
- const ref = LeRed.useRef(initialValue);
839
- LeRed.useEffect(() =>
840
- {
841
- ref.current = value;
842
- }, [value]);
843
- return ref.current;
844
- };
845
-
846
- LeRed.useFont = (font) =>
847
- {
848
- font = '12px ' + STRING(font).trim();
849
- const [hasFont, setHasFont] = LeRed.useState(false);
850
- LeRed.useEffect(() =>
851
- {
852
- if(!hasFont)
853
- {
854
- if(!globalThis?.document?.fonts?.check)
855
- {
856
- setHasFont(true);
857
- return;
858
- }
859
-
860
- const handler = setInterval(() =>
861
- {
862
- try
863
- {
864
- if(globalThis.document.fonts.check(font))
865
- {
866
- clearInterval(handler);
867
- setHasFont(true);
868
- }
869
- }
870
- catch(e)
871
- {
872
- console.error(e);
873
- clearInterval(handler);
874
- setHasFont(true);
875
- }
876
- }, 30);
877
- }
878
- }, [hasFont]);
879
- return hasFont;
880
- };
881
-
882
- /**
883
- * Adds a <script> tag to the <head> of the document.
884
- * Only for development and testing purposes.
885
- *
886
- * @param {string} url The URL of the js file to include.
887
- * @param {Object} props Additional props of the <script> tag.
888
- */
889
- LeRed.useScript = (url, props = {}) =>
890
- {
891
- return LeRed.useEffect(() =>
892
- {
893
- if(!globalThis?.document?.createElement || !globalThis?.document?.head?.appendChild || !globalThis?.document?.head?.removeChild)
894
- {
895
- return;
896
- }
897
-
898
- const script = globalThis.document.createElement('script');
899
- script.type = 'text/javascript';
900
- script.src = url;
901
-
902
- LeUtils.each(props, (value, key) =>
903
- {
904
- script.setAttribute(key, value);
905
- });
906
-
907
- globalThis.document.head.appendChild(script);
908
- return () => globalThis.document.head.removeChild(script);
909
- }, [url, props]);
910
- };
911
-
912
- LeRed.mergeRefs = (...refs) =>
913
- {
914
- refs = LeUtils.flattenArray(refs);
915
- if(!refs)
916
- {
917
- return null;
918
- }
919
-
920
- let newRefs = [];
921
- LeUtils.each(refs, (ref) =>
922
- {
923
- if(ref)
924
- {
925
- newRefs.push(ref);
926
- }
927
- });
928
- refs = newRefs;
929
-
930
- if(refs.length <= 0)
931
- {
932
- return null;
933
- }
934
- if(refs.length === 1)
935
- {
936
- return refs[0];
937
- }
938
- return (inst) =>
939
- {
940
- LeUtils.each(refs, (ref) =>
941
- {
942
- try
943
- {
944
- if(typeof ref === 'function')
945
- {
946
- ref(inst);
947
- }
948
- else if(ref)
949
- {
950
- ref.current = inst;
951
- }
952
- }
953
- catch(e)
954
- {
955
- console.error(e);
956
- }
957
- });
958
- };
959
- };
960
-
961
- LeRed.useTriggerable = (event) =>
962
- {
963
- const [[value, uniqueId], setValue] = LeRed.useState([null, LeUtils.uniqueId()]);
964
-
965
- LeRed.useEffect(() =>
966
- {
967
- if(!globalThis?.addEventListener || !globalThis?.removeEventListener)
968
- {
969
- return;
970
- }
971
-
972
- const callback = (e) =>
973
- {
974
- setValue([e?.detail, LeUtils.uniqueId()]);
975
- };
976
-
977
- const eventName = 'lowentrytriggerable_' + event;
978
- globalThis.addEventListener(eventName, callback);
979
- return () => globalThis.removeEventListener(eventName, callback);
980
- }, [event]);
981
-
982
- return value;
983
- };
984
-
985
- LeRed.trigger = (event, value) =>
986
- {
987
- if(!globalThis?.dispatchEvent || !globalThis?.CustomEvent)
988
- {
989
- return;
990
- }
991
- const eventName = 'lowentrytriggerable_' + event;
992
- globalThis.dispatchEvent(new globalThis.CustomEvent(eventName, {detail:value}));
993
- };
994
-
995
- /**
996
- * A useState() hook that automatically resets to the defaultValue after the given duration.
997
- *
998
- * Example:
999
- *
1000
- * ```js
1001
- * const [value, setValue] = LeRed.useTempState(true, 2000);
1002
- * // somewhere in your code:
1003
- * setValue(false); // value is now false, after 2 seconds it will be reset to true
1004
- * ```
1005
- *
1006
- * Repeated calls cause the timer to reset, meaning each set value will always remain for the full given duration.
1007
- */
1008
- LeRed.useTempState = (defaultValue, duration) =>
1009
- {
1010
- const [value, setValue] = LeRed.useState(defaultValue);
1011
- const timeoutHandle = LeRed.useRef(null);
1012
-
1013
- return [value, (newValue) =>
1014
- {
1015
- if(timeoutHandle.current)
1016
- {
1017
- clearTimeout(timeoutHandle.current);
1018
- }
1019
- setValue(newValue);
1020
- timeoutHandle.current = setTimeout(() =>
1021
- {
1022
- timeoutHandle.current = null;
1023
- setValue(defaultValue);
1024
- }, duration);
1025
- }];
1026
- };
1027
-
1028
- /**
1029
- * Allows you to listen to the browser history events (forwards, backwards) and execute a callback on those events.
1030
- *
1031
- * You pass 2 functions to it (the callbacks), and it also provides 2 functions (for manually going forwards and backwards).
1032
- *
1033
- * Usage:
1034
- *
1035
- * ```js
1036
- * const [goForwards, goBackwards] = LeRed.useHistory(() => console.log('has gone forwards'), () => console.log('has gone backwards'));
1037
- * ```
1038
- */
1039
- LeRed.useHistory = (() =>
1040
- {
1041
- let historyStateListeners = [];
1042
-
1043
- globalThis?.addEventListener?.('popstate', () =>
1044
- {
1045
- historyStateListeners.pop()?.callback();
1046
- });
1047
-
1048
- const addListener = (callback) =>
1049
- {
1050
- const id = LeUtils.uniqueId();
1051
- historyStateListeners.push({id, callback});
1052
- return id;
1053
- };
1054
-
1055
- const removeListener = (id) =>
1056
- {
1057
- if(!id)
1058
- {
1059
- return;
1060
- }
1061
- historyStateListeners = historyStateListeners.filter(listener => (listener.id !== id));
1062
- };
1063
-
1064
- return (onForward, onBack) =>
1065
- {
1066
- const remaining = LeRed.useRef(0);
1067
- const id = LeRed.useRef(null);
1068
-
1069
- const goBack = LeRed.useCallback(() =>
1070
- {
1071
- if(remaining.current <= 0)
1072
- {
1073
- return;
1074
- }
1075
- remaining.current--;
1076
- if(remaining.current === 0)
1077
- {
1078
- if(id.current)
1079
- {
1080
- removeListener(id.current);
1081
- }
1082
- id.current = null;
1083
- }
1084
- onBack();
1085
- }, [onBack]);
1086
-
1087
- return [
1088
- () => /** do **/
1089
- {
1090
- LeRed.navigate('#');
1091
- remaining.current++;
1092
- if(remaining.current === 1)
1093
- {
1094
- if(id.current)
1095
- {
1096
- removeListener(id.current);
1097
- }
1098
- id.current = addListener(goBack);
1099
- }
1100
- onForward();
1101
- },
1102
-
1103
- () => /** undo **/
1104
- {
1105
- if(remaining.current > 0)
1106
- {
1107
- LeRed.navigate(-1);
1108
- }
1109
- },
1110
- ];
1111
- };
1112
- })();
1113
-
1114
- /**
1115
- * Similar to {@link LeRed.useHistory}, but this is specifically for toggling a boolean state between true and false. For example, for a modal, which you'd like to be closed when the user goes back in history.
1116
- *
1117
- * Example:
1118
- *
1119
- * ```js
1120
- * const [isModalOpen, openModal, closeModal] = LeRed.useHistoryState(false); // you'd open it programmatically using openModal(), afterwards, if the user goes back in history, it will close again
1121
- * ```
1122
- *
1123
- * or, if you'd like it to be true by default:
1124
- *
1125
- * ```js
1126
- * const [isModalOpen, openModal, closeModal] = LeRed.useHistoryState(true); // you'd close it programmatically using closeModal(), afterwards, if the user goes back in history, it will open again
1127
- * ```
1128
- */
1129
- LeRed.useHistoryState = (initialState) =>
1130
- {
1131
- const [state, setState] = LeRed.useState(!!initialState);
1132
- const [forwards, backwards] = LeRed.useHistory(() => setState(!initialState), () => setState(!!initialState));
1133
- if(!!initialState)
1134
- {
1135
- return [state, backwards, forwards];
1136
- }
1137
- return [state, forwards, backwards];
1138
- };
1139
-
1140
- /**
1141
- * Allows you to listen to the hash of the URL (window.location.hash).
1142
- *
1143
- * The hash can be useful than the query, as changing the hash will not cause the page to reload. Plus there's a good way to listen to hash changes, using the `hashchange` event, which is lacking for the query.
1144
- *
1145
- * Example:
1146
- *
1147
- * ```js
1148
- * const [hashParams, hashString] = LeRed.useUrlHashParams();
1149
- * ```
1150
- */
1151
- LeRed.useUrlHashParams = (() =>
1152
- {
1153
- const getHashString = () => (globalThis?.location?.hash?.trim()?.replace(/^#/, '') ?? '');
1154
- const parseHashParams = (hashString) => Object.fromEntries(new URLSearchParams(hashString));
1155
-
1156
- /**
1157
- * @returns {[hashParams:URLSearchParams, hashString:string]}
1158
- */
1159
- return () =>
1160
- {
1161
- const [hashString, setHashString] = LeRed.useState(getHashString);
1162
- const hashParams = LeRed.useMemo(() => parseHashParams(hashString), [hashString]);
1163
-
1164
- LeRed.useEffect(() =>
1165
- {
1166
- const onUrlChanged = () =>
1167
- {
1168
- setHashString(getHashString());
1169
- };
1170
-
1171
- globalThis?.addEventListener?.('hashchange', onUrlChanged);
1172
- return () =>
1173
- {
1174
- globalThis?.removeEventListener?.('hashchange', onUrlChanged);
1175
- };
1176
- }, []);
1177
-
1178
- return [hashParams, hashString];
1179
- };
1180
- })();
1181
-
1182
- /**
1183
- * Allows you to easily create an <pre><img></pre> url and onError handler that will automatically retry loading the image if it fails.
1184
- */
1185
- LeRed.useRetryingImageUrl = (url, options) =>
1186
- {
1187
- url = STRING(url);
1188
- const urlHasQ = url.includes('?');
1189
-
1190
- const [imageUrl, setImageUrl] = LeRed.useState(url);
1191
- const retries = LeRed.useRef(0);
1192
- const timeout = LeRed.useRef({remove:() => undefined});
1193
-
1194
- LeRed.useEffect(() =>
1195
- {
1196
- timeout.current.remove();
1197
- retries.current = 0;
1198
- setImageUrl(url);
1199
- }, [url]);
1200
-
1201
- const onImageLoadError = LeRed.useCallback(() =>
1202
- {
1203
- if(retries.current < INT_LAX_ANY(options?.retries, 30))
1204
- {
1205
- const defaultDelay = 100 + (50 * retries.current);
1206
- timeout.current.remove();
1207
- timeout.current = LeUtils.setTimeout(() =>
1208
- {
1209
- setImageUrl(url + (urlHasQ ? '&' : '?') + (options?.queryParam || 'lowentryretryingimgversion') + '=' + (retries.current++));
1210
- }, (typeof options?.delay === 'function') ? INT_LAX_ANY(options?.delay(retries.current), defaultDelay) : (INT_LAX_ANY(options?.delay, defaultDelay)));
1211
- }
1212
- }, [url, options]);
1213
-
1214
- const onImageLoadErrorIgnored = LeRed.useCallback(() =>
1215
- {
1216
- }, []);
1217
-
1218
- if(!url)
1219
- {
1220
- return [url, onImageLoadErrorIgnored];
1221
- }
1222
- return [imageUrl, onImageLoadError];
1223
- };
1224
-
1225
- /**
1226
- * Allows you to easily convert promises to react hooks.
1227
- *
1228
- * The given callable should return promises. The returned promises can be an array, an object, or even a single promise. The returned data of this hook will match the promises it has operated on.
1229
- *
1230
- * The given comparingValues can be anything, this is used to detect whether the given promises have changed or not, and so whether new promises have to be generated and executed again.
1231
- */
1232
- LeRed.usePromises = (callable, comparingValues) =>
1233
- {
1234
- const comparingValuesClone = LeUtils.clone(comparingValues);
1235
- const comparingValuesRef = LeRed.useRef(comparingValuesClone);
1236
- const latestComparingValuesRef = LeRed.useRef();
1237
- latestComparingValuesRef.current = comparingValuesClone;
1238
-
1239
- const [data, setData] = LeRed.useState(null);
1240
- const [loading, setLoading] = LeRed.useState(true);
1241
- const [error, setError] = LeRed.useState(null);
1242
-
1243
- LeRed.useEffect(() =>
1244
- {
1245
- setLoading(true);
1246
- setData(null);
1247
- setError(null);
1248
-
1249
- try
1250
- {
1251
- const promises = callable();
1252
-
1253
- const promisesIsObject = IS_OBJECT(promises) && (typeof promises?.then !== 'function');
1254
- const promisesIsArray = IS_ARRAY(promises);
1255
-
1256
- let promisesKeyed = [];
1257
- if(promisesIsObject || promisesIsArray)
1258
- {
1259
- LeUtils.each(promises, (promise, key) =>
1260
- {
1261
- promisesKeyed.push({promise, key});
1262
- });
1263
- }
1264
- else
1265
- {
1266
- promisesKeyed.push({promise:promises, key:undefined});
1267
- }
1268
-
1269
- let wrappedPromises = [];
1270
- LeUtils.each(promisesKeyed, ({promise, key}) =>
1271
- {
1272
- wrappedPromises.push(promise
1273
- .then(async result => ({result, key})));
1274
- });
1275
-
1276
- Promise.all(wrappedPromises)
1277
- .then(resultObjects =>
1278
- {
1279
- if(promisesIsObject)
1280
- {
1281
- let results = {};
1282
- LeUtils.each(resultObjects, ({result, key}) =>
1283
- {
1284
- results[key] = result;
1285
- });
1286
- return results;
1287
- }
1288
- else if(promisesIsArray)
1289
- {
1290
- let results = [];
1291
- LeUtils.each(resultObjects, ({result, key}) =>
1292
- {
1293
- results[key] = result;
1294
- });
1295
- return results;
1296
- }
1297
- return resultObjects.pop()?.result;
1298
- })
1299
- .then(results =>
1300
- {
1301
- if(!LeUtils.equals(latestComparingValuesRef.current, comparingValuesClone))
1302
- {
1303
- // canceled
1304
- return;
1305
- }
1306
- comparingValuesRef.current = comparingValuesClone;
1307
- setLoading(false);
1308
- setData(results);
1309
- setError(null);
1310
- })
1311
- .catch(error =>
1312
- {
1313
- if(!LeUtils.equals(latestComparingValuesRef.current, comparingValuesClone))
1314
- {
1315
- // canceled
1316
- return;
1317
- }
1318
- comparingValuesRef.current = comparingValuesClone;
1319
- setLoading(false);
1320
- setData(null);
1321
- setError(LeUtils.purgeErrorMessage(error));
1322
- });
1323
-
1324
- return () =>
1325
- {
1326
- LeUtils.each(wrappedPromises, promise =>
1327
- {
1328
- try
1329
- {
1330
- promise?.cancel?.();
1331
- }
1332
- catch(e)
1333
- {
1334
- console.error('Failed to cancel the given promise:', e);
1335
- }
1336
-
1337
- try
1338
- {
1339
- promise?.remove?.();
1340
- }
1341
- catch(e)
1342
- {
1343
- console.error('Failed to remove the given promise:', e);
1344
- }
1345
- });
1346
- };
1347
- }
1348
- catch(error)
1349
- {
1350
- LeUtils.setAnimationFrameTimeout(() =>
1351
- {
1352
- if(!LeUtils.equals(latestComparingValuesRef.current, comparingValuesClone))
1353
- {
1354
- // canceled
1355
- return;
1356
- }
1357
- comparingValuesRef.current = comparingValuesClone;
1358
- setLoading(false);
1359
- setData(null);
1360
- setError(LeUtils.purgeErrorMessage(error));
1361
- });
1362
- }
1363
- }, [comparingValuesClone]);
1364
-
1365
- if(!LeUtils.equals(comparingValuesRef.current, comparingValuesClone))
1366
- {
1367
- return [null, true, null];
1368
- }
1369
- return [data, loading, error];
1370
- };
1371
-
1372
- /**
1373
- * Allows you to easily obtain external data.
1374
- */
1375
- LeRed.useExternal = (url, options, responseFunction) =>
1376
- {
1377
- return LeRed.usePromises(() =>
1378
- {
1379
- const createFetch = (urlString) => LeUtils.fetch(STRING(urlString), {retries:3, ...(options ?? {})})
1380
- .then(async response =>
1381
- {
1382
- const data = await responseFunction(response);
1383
- if(typeof options?.verify === 'function')
1384
- {
1385
- await options.verify(data, response);
1386
- }
1387
- return data;
1388
- });
1389
-
1390
- if(IS_OBJECT(url))
1391
- {
1392
- let promises = {};
1393
- LeUtils.each(url, (urlString, key) =>
1394
- {
1395
- promises[key] = createFetch(urlString);
1396
- });
1397
- return promises;
1398
- }
1399
- if(IS_ARRAY(url))
1400
- {
1401
- let promises = [];
1402
- LeUtils.each(url, urlString =>
1403
- {
1404
- promises.push(createFetch(urlString));
1405
- });
1406
- return promises;
1407
- }
1408
- return createFetch(url);
1409
- }, [url, options, responseFunction]);
1410
- };
1411
-
1412
- /**
1413
- * Allows you to easily obtain external JSON data.
1414
- */
1415
- LeRed.useExternalJson = (url, options) =>
1416
- {
1417
- const responseFunction = LeRed.useCallback(response => response.json(), []);
1418
- return LeRed.useExternal(url, options, responseFunction);
1419
- };
1420
-
1421
- /**
1422
- * Allows you to easily obtain external Blob data.
1423
- */
1424
- LeRed.useExternalBlob = (url, options) =>
1425
- {
1426
- const responseFunction = LeRed.useCallback(response => response.blob(), []);
1427
- return LeRed.useExternal(url, options, responseFunction);
1428
- };
1429
-
1430
- /**
1431
- * Allows you to easily obtain external ArrayBuffer data.
1432
- */
1433
- LeRed.useExternalArrayBuffer = (url, options) =>
1434
- {
1435
- const responseFunction = LeRed.useCallback(response => response.arrayBuffer(), []);
1436
- return LeRed.useExternal(url, options, responseFunction);
1437
- };
1438
-
1439
- /**
1440
- * Allows you to easily obtain external string data.
1441
- */
1442
- LeRed.useExternalString = (url, options) =>
1443
- {
1444
- const responseFunction = LeRed.useCallback(response => response.text(), []);
1445
- return LeRed.useExternal(url, options, responseFunction);
1446
- };
1447
-
1448
- /**
1449
- * Allows you to easily obtain external form data.
1450
- */
1451
- LeRed.useExternalFormData = (url, options) =>
1452
- {
1453
- const responseFunction = LeRed.useCallback(response => response.formData(), []);
1454
- return LeRed.useExternal(url, options, responseFunction);
1455
- };
1456
-
1457
-
1458
- LeRed.Root = LeRed.memo(({store, children, ...other}) =>
1459
- {
1460
- if(ISSET(store))
1461
- {
1462
- store = LeRed.configureStore(store);
1463
- return (<ReactRedux.Provider store={store} children={children} {...other}/>);
1464
- }
1465
- return children;
1466
- });
1467
-
1468
- LeRed.PreloadComponent = (load) =>
1469
- {
1470
- if(typeof window !== 'undefined')
1471
- {
1472
- const promise = load(); // in the browser, start loading already, before it's being rendered in React
1473
- return () => promise;
1474
- }
1475
- return load;
1476
- };
1477
-
1478
- LeRed.LoadComponent = LeRed.memo(({loading, load, ...other}) =>
1479
- {
1480
- const [Component, setComponent] = LeRed.useState(loading ?? null);
1481
-
1482
- LeRed.useEffect(() =>
1483
- {
1484
- (async () =>
1485
- {
1486
- const LoadedComponent = (typeof load === 'function') ? await load() : await load;
1487
- if(!LoadedComponent)
1488
- {
1489
- setComponent(null);
1490
- return;
1491
- }
1492
- setComponent(<LoadedComponent {...other}/>);
1493
- })();
1494
- }, []);
1495
-
1496
- return Component;
1497
- });
1498
-
1499
-
1500
- if(typeof Proxy === 'undefined')
1501
- {
1502
- return LeRed;
1503
- }
1504
-
1505
- return new Proxy(LeRed, {
1506
- set:(target, key, value) =>
1507
- {
1508
- LeRed.set(key, value);
1509
- return true;
1510
- },
1511
- });
1512
- })();