@lowentry/utils 1.13.5 → 1.15.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/LeUtils.js CHANGED
@@ -4,7 +4,7 @@ import {ISSET, IS_OBJECT, IS_ARRAY, STRING, INT_LAX, FLOAT_LAX, INT_LAX_ANY, FLO
4
4
 
5
5
 
6
6
  /**
7
- * @param {LeUtils~TransactionalValue} transactionalValue
7
+ * @param {LeUtils_TransactionalValue} transactionalValue
8
8
  */
9
9
  const checkTransactionalValue = (transactionalValue) =>
10
10
  {
@@ -17,7 +17,7 @@ const checkTransactionalValue = (transactionalValue) =>
17
17
  };
18
18
 
19
19
  /**
20
- * @param {LeUtils~TransactionalValue} transactionalValue
20
+ * @param {LeUtils_TransactionalValue} transactionalValue
21
21
  * @param {string} changeId
22
22
  * @returns {{index:number, value:*}|null}
23
23
  */
@@ -75,7 +75,7 @@ export const LeUtils = {
75
75
 
76
76
  if((document.readyState === 'interactive') || (document.readyState === 'complete'))
77
77
  {
78
- return LeUtils.setTimeout(callback, 0);
78
+ return LeUtils.setTimeout(() => callback(), 0);
79
79
  }
80
80
  else
81
81
  {
@@ -435,17 +435,11 @@ export const LeUtils = {
435
435
  return result;
436
436
  },
437
437
 
438
- /**
439
- * @callback LeUtils~__findIndexValueCallback
440
- * @param {*} value
441
- * @param {*} index
442
- * @returns {boolean|undefined}
443
- */
444
438
  /**
445
439
  * Finds the first element in the given array or object that returns true from the callback, and returns an object with the index and value.
446
440
  *
447
441
  * @param {*[]|object|Function} elements
448
- * @param {LeUtils~__findIndexValueCallback} callback
442
+ * @param {(value:*, index:*) => boolean|void} callback
449
443
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
450
444
  * @returns {{index:*, value:*}|null}
451
445
  */
@@ -460,7 +454,7 @@ export const LeUtils = {
460
454
  result = {index, value};
461
455
  return false;
462
456
  }
463
- });
457
+ }, optionalSkipHasOwnPropertyCheck);
464
458
  return result;
465
459
  },
466
460
 
@@ -468,7 +462,7 @@ export const LeUtils = {
468
462
  * Finds the first element in the given array or object that returns true from the callback, and returns the index.
469
463
  *
470
464
  * @param {*[]|object|Function} elements
471
- * @param {LeUtils~__findIndexValueCallback} callback
465
+ * @param {(value:*, index:*) => boolean|void} callback
472
466
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
473
467
  * @returns {*|null}
474
468
  */
@@ -479,7 +473,7 @@ export const LeUtils = {
479
473
  * Finds the first element in the given array or object that returns true from the callback, and returns the value.
480
474
  *
481
475
  * @param {*[]|object|Function} elements
482
- * @param {LeUtils~__findIndexValueCallback} callback
476
+ * @param {(value:*, index:*) => boolean|void} callback
483
477
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
484
478
  * @returns {*|null}
485
479
  */
@@ -487,50 +481,222 @@ export const LeUtils = {
487
481
  (elements, callback, optionalSkipHasOwnPropertyCheck = false) => LeUtils.findIndexValue(elements, callback, optionalSkipHasOwnPropertyCheck)?.value ?? null,
488
482
 
489
483
  /**
490
- * @callback LeUtils~__eachCallback
491
- * @param {*} value
484
+ * Returns the value at the given index in the given elements.
485
+ *
486
+ * @param {*} elements
492
487
  * @param {*} index
493
- * @returns {boolean|undefined}
488
+ * @param {boolean} [optionalSkipHasOwnPropertyCheck]
489
+ * @returns {*}
494
490
  */
491
+ getValueAtIndex:
492
+ (elements, index, optionalSkipHasOwnPropertyCheck = false) =>
493
+ {
494
+ if((elements === null) || (typeof elements === 'undefined'))
495
+ {
496
+ return undefined;
497
+ }
498
+ if(Array.isArray(elements))
499
+ {
500
+ return elements[index];
501
+ }
502
+ if((typeof elements === 'object') && (elements?.constructor === Object))
503
+ {
504
+ if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
505
+ {
506
+ return elements[index];
507
+ }
508
+ return undefined;
509
+ }
510
+ if(elements instanceof Map)
511
+ {
512
+ return elements.get(index);
513
+ }
514
+ if(elements instanceof Set)
515
+ {
516
+ return index;
517
+ }
518
+ if(ArrayBuffer.isView(elements) && !(elements instanceof DataView))
519
+ {
520
+ return elements[index];
521
+ }
522
+ if(typeof elements === 'string')
523
+ {
524
+ return elements.charAt(index);
525
+ }
526
+ if(typeof elements?.[Symbol.iterator] === 'function')
527
+ {
528
+ let i = 0;
529
+ for(const value of elements)
530
+ {
531
+ if(i === index)
532
+ {
533
+ return value;
534
+ }
535
+ i++;
536
+ }
537
+ return undefined;
538
+ }
539
+ if(typeof elements?.forEach === 'function')
540
+ {
541
+ let result = undefined;
542
+ let shouldContinue = true;
543
+ elements.forEach((value, i) =>
544
+ {
545
+ if(shouldContinue)
546
+ {
547
+ if(i === index)
548
+ {
549
+ result = value;
550
+ shouldContinue = false;
551
+ }
552
+ }
553
+ });
554
+ return result;
555
+ }
556
+ if((typeof elements === 'object') || (typeof elements === 'function'))
557
+ {
558
+ if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
559
+ {
560
+ return elements[index];
561
+ }
562
+ return undefined;
563
+ }
564
+ return undefined;
565
+ },
566
+
495
567
  /**
496
- * Loops through each element in the given array or object, and calls the callback for each element.
568
+ * Checks if the given elements can be iterated over using LeUtils.each().
497
569
  *
498
- * @param {*[]|object|Function} elements
499
- * @param {LeUtils~__eachCallback} callback
570
+ * @param {*} elements
571
+ * @returns {boolean}
572
+ */
573
+ supportsEach:
574
+ (elements) =>
575
+ {
576
+ if((elements === null) || (typeof elements === 'undefined'))
577
+ {
578
+ return false;
579
+ }
580
+ return !!(
581
+ (Array.isArray(elements))
582
+ || ((typeof elements === 'object') && (elements?.constructor === Object))
583
+ || (typeof elements === 'string')
584
+ || (typeof elements?.[Symbol.iterator] === 'function')
585
+ || (typeof elements?.forEach === 'function')
586
+ || ((typeof elements === 'object') || (typeof elements === 'function'))
587
+ );
588
+ },
589
+
590
+ /**
591
+ * Returns an iterator that iterates over each element in the given array or object, yielding an array with the value and the index/key.
592
+ *
593
+ * @param {*} elements
500
594
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
501
- * @returns {*[]|object|Function}
595
+ * @returns {Generator<*, void, *>}
502
596
  */
503
- each:
504
- (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
597
+ eachIterator:
598
+ function* (elements, optionalSkipHasOwnPropertyCheck = false)
505
599
  {
506
- if((elements !== null) && (typeof elements !== 'undefined'))
600
+ if((elements === null) || (typeof elements === 'undefined'))
601
+ {
602
+ return;
603
+ }
604
+ if(Array.isArray(elements))
605
+ {
606
+ for(let i = 0; i < elements.length; i++)
607
+ {
608
+ yield [elements[i], i];
609
+ }
610
+ return;
611
+ }
612
+ if((typeof elements === 'object') && (elements?.constructor === Object))
507
613
  {
508
- if(Array.isArray(elements))
614
+ for(const i in elements)
509
615
  {
510
- for(let index = 0; index < elements.length; index++)
616
+ if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, i))
511
617
  {
512
- if(callback.call(elements[index], elements[index], index) === false)
513
- {
514
- break;
515
- }
618
+ yield [elements[i], i];
516
619
  }
517
620
  }
518
- else if((typeof elements === 'object') || (typeof elements === 'function'))
621
+ return;
622
+ }
623
+ if(elements instanceof Map)
624
+ {
625
+ for(const [i, value] of elements)
626
+ {
627
+ yield [value, i];
628
+ }
629
+ return;
630
+ }
631
+ if(elements instanceof Set)
632
+ {
633
+ for(const value of elements)
634
+ {
635
+ yield [value, value];
636
+ }
637
+ return;
638
+ }
639
+ if(typeof elements === 'string')
640
+ {
641
+ for(let i = 0; i < elements.length; i++)
642
+ {
643
+ yield [elements.charAt(i), i];
644
+ }
645
+ return;
646
+ }
647
+ if(typeof elements?.[Symbol.iterator] === 'function')
648
+ {
649
+ let i = 0;
650
+ for(const value of elements)
519
651
  {
520
- for(let index in elements)
652
+ yield [value, i];
653
+ i++;
654
+ }
655
+ return;
656
+ }
657
+ if(typeof elements?.forEach === 'function')
658
+ {
659
+ const buffer = [];
660
+ elements.forEach((value, i) =>
661
+ {
662
+ buffer.push([value, i]);
663
+ });
664
+ for(const entry of buffer)
665
+ {
666
+ yield entry;
667
+ }
668
+ return;
669
+ }
670
+ if((typeof elements === 'object') || (typeof elements === 'function'))
671
+ {
672
+ for(const i in elements)
673
+ {
674
+ if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, i))
521
675
  {
522
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
523
- {
524
- if(callback.call(elements[index], elements[index], index) === false)
525
- {
526
- break;
527
- }
528
- }
676
+ yield [elements[i], i];
529
677
  }
530
678
  }
531
- else
679
+ return;
680
+ }
681
+ console.warn('Executed LeUtils.eachIterator() on an invalid type: [' + (typeof elements) + ']', elements);
682
+ },
683
+
684
+ /**
685
+ * Loops through each element in the given array or object, and calls the callback for each element.
686
+ *
687
+ * @param {*} elements
688
+ * @param {(value:*, index?:*) => boolean|void} callback
689
+ * @param {boolean} [optionalSkipHasOwnPropertyCheck]
690
+ * @returns {*}
691
+ */
692
+ each:
693
+ (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
694
+ {
695
+ for(const [value, key] of LeUtils.eachIterator(elements, optionalSkipHasOwnPropertyCheck))
696
+ {
697
+ if(callback.call(value, value, key) === false)
532
698
  {
533
- console.warn('Executed LeUtils.each() on an invalid type: [' + (typeof elements) + ']', elements);
699
+ break;
534
700
  }
535
701
  }
536
702
  return elements;
@@ -539,54 +705,55 @@ export const LeUtils = {
539
705
  /**
540
706
  * Like LeUtils.each(), except that it expects an async callback.
541
707
  *
542
- * @param {*[]|object|function} elements
543
- * @param {LeUtils~__eachCallback} asyncCallback
708
+ * @param {*} elements
709
+ * @param {(value:*, index?:*) => Promise<boolean|undefined>} asyncCallback
544
710
  * @param {number} [optionalParallelCount]
545
711
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
546
- * @returns {*[]|object|function}
712
+ * @returns {Promise<*>}
547
713
  */
548
714
  eachAsync:
549
715
  (() =>
550
716
  {
717
+ /**
718
+ * Instead of waiting for every promise individually, this function will queue up multiple promises at once, then wait for any of them to finish, before adding more, until it has looped through all elements.
719
+ * Then, at the end, it will wait for the remaining promises to finish.
720
+ */
551
721
  const eachAsyncParallel = async (elements, asyncCallback, optionalParallelCount, optionalSkipHasOwnPropertyCheck) =>
552
722
  {
553
- let promises = [];
723
+ const runningPromises = new Set();
554
724
  let doBreak = false;
555
- await LeUtils.eachAsync(elements, async (element, index) =>
725
+ await LeUtils.eachAsync(elements, async (value, index) =>// loop through each element
556
726
  {
557
- while(promises.length > optionalParallelCount)
727
+ if(doBreak)
558
728
  {
559
- let newPromises = [];
560
- LeUtils.each(promises, (promise) =>
561
- {
562
- if(!promise.__lowentry_utils__promise_is_done__)
563
- {
564
- newPromises.push(promise);
565
- }
566
- });
567
- promises = newPromises;
568
- if(promises.length > optionalParallelCount)
569
- {
570
- await Promise.any(promises);
571
- }
729
+ return false;
572
730
  }
573
731
 
574
- if(doBreak)
732
+ // if no spot is available, wait for one to finish
733
+ while(runningPromises.size >= optionalParallelCount)
575
734
  {
576
- return false;
735
+ await Promise.race(runningPromises);
736
+ if(doBreak)
737
+ {
738
+ return false;
739
+ }
577
740
  }
578
741
 
742
+ // process this element, by creating a promise, and adding it to the queue
579
743
  const promise = (async () =>
580
744
  {
581
- if((await asyncCallback.call(element, element, index)) === false)
745
+ if((await asyncCallback.call(value, value, index)) === false)
582
746
  {
583
747
  doBreak = true;
584
748
  }
585
- promise.__lowentry_utils__promise_is_done__ = true;
586
749
  })();
587
- promises.push(promise);
588
- }, optionalSkipHasOwnPropertyCheck);
589
- await Promise.all(promises);
750
+ runningPromises.add(promise);
751
+ promise.finally(() =>
752
+ {
753
+ runningPromises.delete(promise);
754
+ });
755
+ }, 1, optionalSkipHasOwnPropertyCheck);
756
+ await Promise.all(runningPromises);
590
757
  return elements;
591
758
  };
592
759
 
@@ -600,166 +767,163 @@ export const LeUtils = {
600
767
  return await eachAsyncParallel(elements, asyncCallback, parallelCount, optionalSkipHasOwnPropertyCheck);
601
768
  }
602
769
 
603
- if(Array.isArray(elements))
604
- {
605
- for(let index = 0; index < elements.length; index++)
606
- {
607
- if((await asyncCallback.call(elements[index], elements[index], index)) === false)
608
- {
609
- break;
610
- }
611
- }
612
- }
613
- else if((typeof elements === 'object') || (typeof elements === 'function'))
770
+ for(const [value, key] of LeUtils.eachIterator(elements, optionalSkipHasOwnPropertyCheck))
614
771
  {
615
- for(let index in elements)
772
+ if((await asyncCallback.call(value, value, key)) === false)
616
773
  {
617
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
618
- {
619
- if((await asyncCallback.call(elements[index], elements[index], index)) === false)
620
- {
621
- break;
622
- }
623
- }
774
+ break;
624
775
  }
625
776
  }
626
- else
627
- {
628
- console.warn('Executed LeUtils.eachAsync() on an invalid type: [' + (typeof elements) + ']', elements);
629
- }
630
777
  }
631
778
  return elements;
632
779
  };
633
780
  })(),
634
781
 
635
782
  /**
636
- * @callback LeUtils~__filterCallback
637
- * @param {*} value
638
- * @param {*} index
639
- * @returns {boolean|undefined}
783
+ * Returns an empty simplified collection (array, object, or Map), based on the given elements.
784
+ *
785
+ * Usage:
786
+ *
787
+ * ```js
788
+ * const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
789
+ * ```
790
+ *
791
+ * @param {*} elements
792
+ * @returns {[boolean, *[]|object|Map, (value:*,index:*)=>void]}
640
793
  */
794
+ getEmptySimplifiedCollection:
795
+ (elements) =>
796
+ {
797
+ if((elements === null) || (typeof elements === 'undefined'))
798
+ {
799
+ return [false, [], (value, index) =>
800
+ {
801
+ }];
802
+ }
803
+
804
+ let collection = null;
805
+ let add = null;
806
+ if(Array.isArray(elements))
807
+ {
808
+ collection = [];
809
+ add = (value, index) =>
810
+ {
811
+ collection.push(value);
812
+ };
813
+ }
814
+ else if((typeof elements === 'object') && (elements?.constructor === Object))
815
+ {
816
+ collection = {};
817
+ add = (value, index) =>
818
+ {
819
+ collection[index] = value;
820
+ };
821
+ }
822
+ else if(elements instanceof Map)
823
+ {
824
+ collection = new Map();
825
+ add = (value, index) =>
826
+ {
827
+ collection.set(index, value);
828
+ };
829
+ }
830
+ else if((typeof elements === 'string') || (typeof elements?.[Symbol.iterator] === 'function') || (typeof elements?.forEach === 'function'))
831
+ {
832
+ collection = [];
833
+ add = (value, index) =>
834
+ {
835
+ collection.push(value);
836
+ };
837
+ }
838
+ else if((typeof elements === 'object') || (typeof elements === 'function'))
839
+ {
840
+ collection = {};
841
+ add = (value, index) =>
842
+ {
843
+ collection[index] = value;
844
+ };
845
+ }
846
+ else
847
+ {
848
+ console.warn('Executed LeUtils.getEmptySimplifiedCollection() on an invalid type: [' + (typeof elements) + ']', elements);
849
+ return [false, [], (value, index) =>
850
+ {
851
+ }];
852
+ }
853
+ return [true, collection, add];
854
+ },
855
+
641
856
  /**
642
- * Loops through the given elements, and returns a new array or object, with only the elements that returned true (or a value equals to true) from the callback.
857
+ * Loops through the given elements, and returns a new collection, with only the elements that returned true (or a value equals to true) from the callback.
643
858
  * If no callback is given, it will return all elements that are of a true value (for example, values that are: not null, not undefined, not false, not 0, not an empty string, not an empty array, not an empty object).
644
859
  *
645
- * @param {*[]|object|Function} elements
646
- * @param {LeUtils~__filterCallback} [callback]
860
+ * @param {*} elements
861
+ * @param {(value:*, index:*) => boolean|undefined} [callback]
647
862
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
648
- * @returns {*[]|object|Function}
863
+ * @returns {*}
649
864
  */
650
865
  filter:
651
866
  (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
652
867
  {
653
- if((elements !== null) && (typeof elements !== 'undefined'))
868
+ const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
869
+ if(!success)
654
870
  {
655
- if(Array.isArray(elements))
656
- {
657
- let result = [];
658
- for(let index = 0; index < elements.length; index++)
659
- {
660
- if((!callback && elements[index]) || (callback && callback.call(elements[index], elements[index], index)))
661
- {
662
- result.push(elements[index]);
663
- }
664
- }
665
- return result;
666
- }
667
- else if((typeof elements === 'object') || (typeof elements === 'function'))
871
+ return elements;
872
+ }
873
+
874
+ LeUtils.each(elements, (value, index) =>
875
+ {
876
+ if(!callback)
668
877
  {
669
- let result = {};
670
- for(let index in elements)
878
+ if(value)
671
879
  {
672
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
673
- {
674
- if((!callback && elements[index]) || (callback && callback.call(elements[index], elements[index], index)))
675
- {
676
- result[index] = elements[index];
677
- }
678
- }
880
+ add(value, index);
679
881
  }
680
- return result;
681
882
  }
682
- else
883
+ else if(callback.call(value, value, index))
683
884
  {
684
- console.warn('Executed LeUtils.filter() on an invalid type: [' + (typeof elements) + ']', elements);
885
+ add(value, index);
685
886
  }
686
- }
687
- return elements;
887
+ }, optionalSkipHasOwnPropertyCheck);
888
+ return collection;
688
889
  },
689
890
 
690
891
  /**
691
- * @callback LeUtils~__mapCallback
692
- * @param {*} value
693
- * @param {*} index
694
- * @returns {*}
695
- */
696
- /**
697
- * Loops through the given elements, and returns a new array or object, with the elements that were returned from the callback.
892
+ * Loops through the given elements, and returns a new collection, with the elements that were returned from the callback.
698
893
  *
699
- * @param {*[]|object|Function} elements
700
- * @param {LeUtils~__mapCallback} [callback]
894
+ * @param {*} elements
895
+ * @param {(value:*, index:*) => *} [callback]
701
896
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
702
- * @returns {*[]|object|Function}
897
+ * @returns {*}
703
898
  */
704
899
  map:
705
900
  (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
706
901
  {
707
- if((elements !== null) && (typeof elements !== 'undefined'))
902
+ const [success, collection, add] = LeUtils.getEmptySimplifiedCollection(elements);
903
+ if(!success)
708
904
  {
709
- if(Array.isArray(elements))
710
- {
711
- let result = [];
712
- for(let index = 0; index < elements.length; index++)
713
- {
714
- if(!callback)
715
- {
716
- result[index] = elements[index];
717
- }
718
- else
719
- {
720
- result[index] = callback.call(elements[index], elements[index], index);
721
- }
722
- }
723
- return result;
724
- }
725
- else if((typeof elements === 'object') || (typeof elements === 'function'))
905
+ return elements;
906
+ }
907
+
908
+ LeUtils.each(elements, (value, index) =>
909
+ {
910
+ if(!callback)
726
911
  {
727
- let result = {};
728
- for(let index in elements)
729
- {
730
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
731
- {
732
- if(!callback)
733
- {
734
- result[index] = elements[index];
735
- }
736
- else
737
- {
738
- result[index] = callback.call(elements[index], elements[index], index);
739
- }
740
- }
741
- }
742
- return result;
912
+ add(value, index);
743
913
  }
744
914
  else
745
915
  {
746
- console.warn('Executed LeUtils.map() on an invalid type: [' + (typeof elements) + ']', elements);
916
+ add(callback.call(value, value, index), index);
747
917
  }
748
- }
749
- return elements;
918
+ }, optionalSkipHasOwnPropertyCheck);
919
+ return collection;
750
920
  },
751
921
 
752
- /**
753
- * @callback LeUtils~__mapToArrayCallback
754
- * @param {*} value
755
- * @param {*} index
756
- * @returns {*}
757
- */
758
922
  /**
759
923
  * Loops through the given elements, and returns a new array, with the elements that were returned from the callback. Always returns an array.
760
924
  *
761
- * @param {*[]|object|Function} elements
762
- * @param {LeUtils~__mapToArrayCallback} [callback]
925
+ * @param {*} elements
926
+ * @param {(value:*, index:*) => *} [callback]
763
927
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
764
928
  * @returns {*[]}
765
929
  */
@@ -767,59 +931,26 @@ export const LeUtils = {
767
931
  (elements, callback, optionalSkipHasOwnPropertyCheck = false) =>
768
932
  {
769
933
  let result = [];
770
- if((elements !== null) && (typeof elements !== 'undefined'))
934
+ LeUtils.each(elements, (value, index) =>
771
935
  {
772
- if(Array.isArray(elements))
773
- {
774
- for(let index = 0; index < elements.length; index++)
775
- {
776
- if(!callback)
777
- {
778
- result.push(elements[index]);
779
- }
780
- else
781
- {
782
- result.push(callback.call(elements[index], elements[index], index));
783
- }
784
- }
785
- }
786
- else if((typeof elements === 'object') || (typeof elements === 'function'))
936
+ if(!callback)
787
937
  {
788
- for(let index in elements)
789
- {
790
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
791
- {
792
- if(!callback)
793
- {
794
- result.push(elements[index]);
795
- }
796
- else
797
- {
798
- result.push(callback.call(elements[index], elements[index], index));
799
- }
800
- }
801
- }
938
+ result.push(value);
802
939
  }
803
940
  else
804
941
  {
805
- console.warn('Executed LeUtils.mapToArray() on an invalid type: [' + (typeof elements) + ']', elements);
942
+ result.push(callback.call(value, value, index));
806
943
  }
807
- }
944
+ }, optionalSkipHasOwnPropertyCheck);
808
945
  return result;
809
946
  },
810
947
 
811
- /**
812
- * @callback LeUtils~__mapToArraySortedCallback
813
- * @param {*} value
814
- * @param {*} index
815
- * @returns {*}
816
- */
817
948
  /**
818
949
  * Loops through the given elements, and returns a new array, with the elements that were returned from the callback. The elements will be sorted by the result from the given comparator. Always returns an array.
819
950
  *
820
- * @param {*[]|object|Function} elements
821
- * @param {LeUtils~__sortKeysComparatorCallback} comparator
822
- * @param {LeUtils~__mapToArraySortedCallback} [callback]
951
+ * @param {*} elements
952
+ * @param {(valueA:*, valueB:*) => number} comparator
953
+ * @param {(value:*, index:*) => *} [callback]
823
954
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
824
955
  * @returns {*[]}
825
956
  */
@@ -828,31 +959,26 @@ export const LeUtils = {
828
959
  {
829
960
  const keys = LeUtils.sortKeys(elements, comparator, optionalSkipHasOwnPropertyCheck);
830
961
  let result = [];
831
- for(let i = 0; i < keys.length; i++)
962
+ for(const key of keys)
832
963
  {
964
+ const value = LeUtils.getValueAtIndex(elements, key, optionalSkipHasOwnPropertyCheck);
833
965
  if(!callback)
834
966
  {
835
- result.push(elements[keys[i]]);
967
+ result.push(value);
836
968
  }
837
969
  else
838
970
  {
839
- result.push(callback.call(elements[keys[i]], elements[keys[i]], keys[i]));
971
+ result.push(callback.call(value, value, key));
840
972
  }
841
973
  }
842
974
  return result;
843
975
  },
844
976
 
845
- /**
846
- * @callback LeUtils~__sortKeysComparatorCallback
847
- * @param {*} elementA
848
- * @param {*} elementB
849
- * @returns {number}
850
- */
851
977
  /**
852
978
  * Loops through the given elements, and returns a new array, with the keys from the given elements, sorted by the result from the given comparator. Always returns an array.
853
979
  *
854
- * @param {*[]|object|Function} elements
855
- * @param {LeUtils~__sortKeysComparatorCallback} comparator
980
+ * @param {*} elements
981
+ * @param {(valueA:*, valueB:*) => number} comparator
856
982
  * @param {boolean} [optionalSkipHasOwnPropertyCheck]
857
983
  * @returns {*[]}
858
984
  */
@@ -860,31 +986,12 @@ export const LeUtils = {
860
986
  (elements, comparator, optionalSkipHasOwnPropertyCheck = false) =>
861
987
  {
862
988
  let keys = [];
863
- if((elements !== null) && (typeof elements !== 'undefined'))
989
+ LeUtils.each(elements, (value, index) =>
864
990
  {
865
- if(Array.isArray(elements))
866
- {
867
- for(let index = 0; index < elements.length; index++)
868
- {
869
- keys.push(index);
870
- }
871
- }
872
- else if((typeof elements === 'object') || (typeof elements === 'function'))
873
- {
874
- for(let index in elements)
875
- {
876
- if((optionalSkipHasOwnPropertyCheck === true) || Object.prototype.hasOwnProperty.call(elements, index))
877
- {
878
- keys.push(index);
879
- }
880
- }
881
- }
882
- else
883
- {
884
- console.warn('Executed LeUtils.sortKeys() on an invalid type: [' + (typeof elements) + ']', elements);
885
- }
886
- }
887
- keys.sort((a, b) => comparator(elements[a], elements[b]));
991
+ keys.push(index);
992
+ }, optionalSkipHasOwnPropertyCheck);
993
+
994
+ keys.sort((a, b) => comparator(LeUtils.getValueAtIndex(elements, a, optionalSkipHasOwnPropertyCheck), LeUtils.getValueAtIndex(elements, b, optionalSkipHasOwnPropertyCheck)));
888
995
  return keys;
889
996
  },
890
997
 
@@ -927,6 +1034,46 @@ export const LeUtils = {
927
1034
  };
928
1035
  })(),
929
1036
 
1037
+ /**
1038
+ * Turns the given value(s) into a 1 dimensional array.
1039
+ *
1040
+ * Compared to LeUtils.flattenArray(), this function also supports objects, Maps, Sets, and other iterable objects.
1041
+ *
1042
+ * @param {*} elements
1043
+ * @param {boolean} [optionalSkipHasOwnPropertyCheck]
1044
+ * @returns {*[]}
1045
+ */
1046
+ flattenToArray:
1047
+ (() =>
1048
+ {
1049
+ const flattenToArrayRecursive = (result, elements, optionalSkipHasOwnPropertyCheck) =>
1050
+ {
1051
+ if(!LeUtils.supportsEach(elements))
1052
+ {
1053
+ result.push(elements);
1054
+ return;
1055
+ }
1056
+ LeUtils.each(elements, entry =>
1057
+ {
1058
+ flattenToArrayRecursive(result, entry, optionalSkipHasOwnPropertyCheck);
1059
+ }, optionalSkipHasOwnPropertyCheck);
1060
+ };
1061
+
1062
+ return (elements, optionalSkipHasOwnPropertyCheck = false) =>
1063
+ {
1064
+ if(!LeUtils.supportsEach(elements))
1065
+ {
1066
+ return [elements];
1067
+ }
1068
+ let result = [];
1069
+ LeUtils.each(elements, entry =>
1070
+ {
1071
+ flattenToArrayRecursive(result, entry, optionalSkipHasOwnPropertyCheck);
1072
+ }, optionalSkipHasOwnPropertyCheck);
1073
+ return result;
1074
+ };
1075
+ })(),
1076
+
930
1077
  /**
931
1078
  * Compares two values. Primarily used for sorting.
932
1079
  *
@@ -957,13 +1104,26 @@ export const LeUtils = {
957
1104
  compareNumericStrings:
958
1105
  (a, b) =>
959
1106
  {
960
- a = STRING(a).trim();
961
- b = STRING(b).trim();
962
- if(a.length === b.length)
1107
+ const aParts = STRING(a).split('.');
1108
+ const bParts = STRING(b).split('.');
1109
+ for(let i = 0; i < Math.min(aParts.length, bParts.length); i++)
1110
+ {
1111
+ a = aParts[i].trim();
1112
+ b = bParts[i].trim();
1113
+ if(a.length !== b.length)
1114
+ {
1115
+ return (a.length < b.length) ? -1 : 1;
1116
+ }
1117
+ if(a !== b)
1118
+ {
1119
+ return (a < b) ? -1 : 1;
1120
+ }
1121
+ }
1122
+ if(aParts.length !== bParts.length)
963
1123
  {
964
- return (a < b) ? -1 : ((a > b) ? 1 : 0);
1124
+ return (aParts.length < bParts.length) ? -1 : 1;
965
1125
  }
966
- return (a.length < b.length) ? -1 : 1;
1126
+ return 0;
967
1127
  },
968
1128
 
969
1129
  /**
@@ -1043,9 +1203,9 @@ export const LeUtils = {
1043
1203
  {
1044
1204
  }.constructor;
1045
1205
 
1046
- const PossibleGeneratorFunctionNames = Array.from(new Set(['GeneratorFunction', 'AsyncFunction', 'AsyncGeneratorFunction', GeneratorFunction.name, GeneratorFunction.displayName, AsyncGeneratorFunction.name, AsyncGeneratorFunction.displayName])).filter((element) =>
1206
+ const PossibleGeneratorFunctionNames = Array.from(new Set(['GeneratorFunction', 'AsyncFunction', 'AsyncGeneratorFunction', GeneratorFunction.name, AsyncGeneratorFunction.name])).filter((element) =>
1047
1207
  {
1048
- return (element && (element !== RegularFunction.name) && (element !== RegularFunction.displayName));
1208
+ return (element && (element !== RegularFunction.name));
1049
1209
  });
1050
1210
 
1051
1211
  return (func) =>
@@ -1059,20 +1219,17 @@ export const LeUtils = {
1059
1219
  {
1060
1220
  return false;
1061
1221
  }
1222
+ // noinspection JSUnresolvedVariable
1062
1223
  return ((constructor.name && PossibleGeneratorFunctionNames.includes(constructor.name)) || (constructor.displayName && PossibleGeneratorFunctionNames.includes(constructor.displayName)));
1063
1224
  };
1064
1225
  })(),
1065
1226
 
1066
- /**
1067
- * @callback LeUtils~__setTimeoutCallback
1068
- * @param {number} deltaTime
1069
- */
1070
1227
  /**
1071
1228
  * Executes the callback after the given number of milliseconds. Passes the elapsed time in seconds to the callback.
1072
1229
  *
1073
1230
  * To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setTimeout((deltaTime)=>{}, 1000); timeoutHandler.remove();")
1074
1231
  *
1075
- * @param {LeUtils~__setTimeoutCallback} callback ([number] deltaTime)
1232
+ * @param {(deltaTime:number) => *} callback
1076
1233
  * @param {number} ms
1077
1234
  * @returns {{remove:Function}}
1078
1235
  */
@@ -1091,6 +1248,7 @@ export const LeUtils = {
1091
1248
  ms = FLOAT_LAX(ms);
1092
1249
 
1093
1250
  let lastTime = performance.now();
1251
+ /** @type {number|null} */
1094
1252
  let handler = window.setTimeout(() =>
1095
1253
  {
1096
1254
  const currentTime = performance.now();
@@ -1118,16 +1276,12 @@ export const LeUtils = {
1118
1276
  };
1119
1277
  },
1120
1278
 
1121
- /**
1122
- * @callback LeUtils~__setIntervalCallback
1123
- * @param {number} deltaTime
1124
- */
1125
1279
  /**
1126
1280
  * Executes the callback every given number of milliseconds. Passes the time difference in seconds between the last frame and now to it.
1127
1281
  *
1128
1282
  * To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setInterval((deltaTime)=>{}, 1000); intervalHandler.remove();")
1129
1283
  *
1130
- * @param {LeUtils~__setIntervalCallback} callback ([number] deltaTime)
1284
+ * @param {(deltaTime:number) => *} callback
1131
1285
  * @param {number} [intervalMs]
1132
1286
  * @param {boolean} [fireImmediately]
1133
1287
  * @returns {{remove:Function}}
@@ -1159,6 +1313,7 @@ export const LeUtils = {
1159
1313
  }
1160
1314
 
1161
1315
  let lastTime = performance.now();
1316
+ /** @type {number|null} */
1162
1317
  let handler = window.setInterval(() =>
1163
1318
  {
1164
1319
  const currentTime = performance.now();
@@ -1186,16 +1341,12 @@ export const LeUtils = {
1186
1341
  };
1187
1342
  },
1188
1343
 
1189
- /**
1190
- * @callback LeUtils~__setAnimationFrameTimeoutCallback
1191
- * @param {number} deltaTime
1192
- */
1193
1344
  /**
1194
1345
  * Executes the callback after the given number of frames. Passes the elapsed time in seconds to the callback.
1195
1346
  *
1196
1347
  * To cancel the timeout, call remove() on the result of this function (example: "const timeoutHandler = LeUtils.setAnimationFrameTimeout((deltaTime){}, 5); timeoutHandler.remove();")
1197
1348
  *
1198
- * @param {LeUtils~__setAnimationFrameTimeoutCallback} callback ([number] deltaTime)
1349
+ * @param {(deltaTime:number) => *} callback
1199
1350
  * @param {number} [frames]
1200
1351
  * @returns {{remove:Function}}
1201
1352
  */
@@ -1256,16 +1407,12 @@ export const LeUtils = {
1256
1407
  };
1257
1408
  },
1258
1409
 
1259
- /**
1260
- * @callback LeUtils~__setAnimationFrameIntervalCallback
1261
- * @param {number} deltaTime
1262
- */
1263
1410
  /**
1264
1411
  * Executes the callback every given number of frames. Passes the time difference in seconds between the last frame and now to it.
1265
1412
  *
1266
1413
  * To remove the interval, call remove() on the result of this function (example: "const intervalHandler = LeUtils.setAnimationFrameInterval((deltaTime)=>{}, 5); intervalHandler.remove();")
1267
1414
  *
1268
- * @param {LeUtils~__setAnimationFrameIntervalCallback} callback ([number] deltaTime)
1415
+ * @param {(deltaTime:number) => *} callback
1269
1416
  * @param {number} [intervalFrames]
1270
1417
  * @param {boolean} [fireImmediately]
1271
1418
  * @returns {{remove:Function}}
@@ -1346,7 +1493,7 @@ export const LeUtils = {
1346
1493
  * Returns a promise, which will be resolved after the given number of milliseconds.
1347
1494
  *
1348
1495
  * @param {number} ms
1349
- * @returns {Promise}
1496
+ * @returns {Promise<number>}
1350
1497
  */
1351
1498
  promiseTimeout:
1352
1499
  (ms) =>
@@ -1354,7 +1501,7 @@ export const LeUtils = {
1354
1501
  ms = FLOAT_LAX(ms);
1355
1502
  if(ms <= 0)
1356
1503
  {
1357
- return new Promise(resolve => resolve(undefined));
1504
+ return new Promise(resolve => resolve(0));
1358
1505
  }
1359
1506
  return new Promise(resolve => LeUtils.setTimeout(resolve, ms));
1360
1507
  },
@@ -1363,7 +1510,7 @@ export const LeUtils = {
1363
1510
  * Returns a promise, which will be resolved after the given number of frames.
1364
1511
  *
1365
1512
  * @param {number} frames
1366
- * @returns {Promise}
1513
+ * @returns {Promise<number>}
1367
1514
  */
1368
1515
  promiseAnimationFrameTimeout:
1369
1516
  (frames) =>
@@ -1371,7 +1518,7 @@ export const LeUtils = {
1371
1518
  frames = INT_LAX(frames);
1372
1519
  if(frames <= 0)
1373
1520
  {
1374
- return new Promise(resolve => resolve(undefined));
1521
+ return new Promise(resolve => resolve(0));
1375
1522
  }
1376
1523
  return new Promise(resolve => LeUtils.setAnimationFrameTimeout(resolve, frames));
1377
1524
  },
@@ -1380,7 +1527,7 @@ export const LeUtils = {
1380
1527
  * Allows you to do a fetch, with built-in retry and abort functionality.
1381
1528
  *
1382
1529
  * @param {string} url
1383
- * @param {Object} [options]
1530
+ * @param {{retries?:number|null, delay?:number|((attempt:number)=>number)|null}|object|null} [options]
1384
1531
  * @returns {{then:Function, catch:Function, finally:Function, remove:Function, isRemoved:Function}}
1385
1532
  */
1386
1533
  fetch:
@@ -1466,6 +1613,68 @@ export const LeUtils = {
1466
1613
  return result;
1467
1614
  },
1468
1615
 
1616
+ /**
1617
+ * Allows you to do a fetch, with built-in retry functionality. Caches on the requested URL, so that the same URL will not be fetched multiple times.
1618
+ *
1619
+ * @param {string} url
1620
+ * @param {{retries?:number|null, delay?:number|((attempt:number)=>number)|null, [verify]:((data:*,response:*)=>void)|null}|null} [options]
1621
+ * @param {((response:*)=>*)|null} [responseFunction] A function that will be called with the response object, and should return the data to be cached.
1622
+ * @returns {Promise<*>}
1623
+ */
1624
+ cachedFetch:
1625
+ (() =>
1626
+ {
1627
+ const cache = new Map();
1628
+ return async (url, options, responseFunction) =>
1629
+ {
1630
+ if(cache.has(url))
1631
+ {
1632
+ const result = cache.get(url);
1633
+ if(result.data)
1634
+ {
1635
+ return result.data;
1636
+ }
1637
+ if(result.promise)
1638
+ {
1639
+ return await result.promise;
1640
+ }
1641
+ if(result.error)
1642
+ {
1643
+ throw result.error;
1644
+ }
1645
+ console.warn('Failed to use the cachedFetch cache, for URL: ', url, ', it is in an unexpected state: ', result);
1646
+ return null;
1647
+ }
1648
+
1649
+ const promise = LeUtils.fetch(url, options)
1650
+ .then(async response =>
1651
+ {
1652
+ const data = responseFunction ? (await responseFunction(response)) : response;
1653
+ if(typeof options?.verify === 'function')
1654
+ {
1655
+ await options.verify(data, response);
1656
+ }
1657
+ return data;
1658
+ })
1659
+ .then(data =>
1660
+ {
1661
+ cache.set(url, {data});
1662
+ return data;
1663
+ })
1664
+ .catch(error =>
1665
+ {
1666
+ cache.set(url, {error});
1667
+ console.error('Failed to fetch: ', error);
1668
+ throw error;
1669
+ });
1670
+ if(!cache.has(url))
1671
+ {
1672
+ cache.set(url, {promise});
1673
+ }
1674
+ return await promise;
1675
+ };
1676
+ })(),
1677
+
1469
1678
  /**
1470
1679
  * Returns true if the user is on a smartphone device (mobile).
1471
1680
  * Will return false if the user is on a tablet or on a desktop.
@@ -1732,7 +1941,7 @@ export const LeUtils = {
1732
1941
  * - foo-bar
1733
1942
  * - foo_bar
1734
1943
  *
1735
- * @param {string} names
1944
+ * @param {string[]} names
1736
1945
  * @returns {string[]}
1737
1946
  */
1738
1947
  generateNamePermutations:
@@ -1790,7 +1999,7 @@ export const LeUtils = {
1790
1999
  }
1791
2000
  if(c < '9')
1792
2001
  {
1793
- c++;
2002
+ c = String.fromCharCode(c.charCodeAt(0) + 1);
1794
2003
  string = string.substring(0, i) + c + string.substring(i + 1);// string[i] = (char + 1);
1795
2004
  break;
1796
2005
  }
@@ -1812,7 +2021,7 @@ export const LeUtils = {
1812
2021
  (() =>
1813
2022
  {
1814
2023
  let previousUniqueIdsTime = null;
1815
- let previousUniqueIds = {};
2024
+ let previousUniqueIds = new Map();
1816
2025
 
1817
2026
  const numberToBytes = (number) =>
1818
2027
  {
@@ -1896,12 +2105,13 @@ export const LeUtils = {
1896
2105
  if(previousUniqueIdsTime !== result.time)
1897
2106
  {
1898
2107
  previousUniqueIdsTime = result.time;
1899
- previousUniqueIds = {[result.id]:true};
2108
+ previousUniqueIds.clear();
2109
+ previousUniqueIds.set(result.id, true);
1900
2110
  return result.id;
1901
2111
  }
1902
- else if(previousUniqueIds[result.id] !== true)
2112
+ else if(previousUniqueIds.get(result.id) !== true)
1903
2113
  {
1904
- previousUniqueIds[result.id] = true;
2114
+ previousUniqueIds.set(result.id, true);
1905
2115
  return result.id;
1906
2116
  }
1907
2117
  }
@@ -1931,7 +2141,7 @@ export const LeUtils = {
1931
2141
  return bytes;
1932
2142
  };
1933
2143
 
1934
- return (now = null) =>
2144
+ return (/** @type {number|null|undefined} */ now = null) =>
1935
2145
  {
1936
2146
  if(ISSET(now))
1937
2147
  {
@@ -1988,7 +2198,7 @@ export const LeUtils = {
1988
2198
  {
1989
2199
  part = FLOAT_LAX(part);
1990
2200
  total = FLOAT_LAX(total);
1991
- if(total <= 0)
2201
+ if(total === 0)
1992
2202
  {
1993
2203
  return 100;
1994
2204
  }
@@ -2015,22 +2225,18 @@ export const LeUtils = {
2015
2225
  const ctx = canvas.getContext('2d');
2016
2226
  const width = Math.floor(image.width);
2017
2227
  const height = Math.floor(image.height);
2018
- if((width <= 0) || (height <= 0))
2019
- {
2020
- canvas.width = 1;
2021
- canvas.height = 1;
2022
- }
2023
- else
2228
+ if(!ctx || (width <= 0) || (height <= 0))
2024
2229
  {
2025
- canvas.width = width;
2026
- canvas.height = height;
2027
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
2230
+ return new Uint8ClampedArray();
2028
2231
  }
2232
+ canvas.width = width;
2233
+ canvas.height = height;
2234
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
2029
2235
  return ctx.getImageData(0, 0, canvas.width, canvas.height).data;
2030
2236
  }
2031
2237
  finally
2032
2238
  {
2033
- canvas.parentNode.removeChild(canvas);
2239
+ canvas?.parentNode?.removeChild(canvas);
2034
2240
  }
2035
2241
  },
2036
2242
 
@@ -2055,17 +2261,13 @@ export const LeUtils = {
2055
2261
  const ctx = canvas.getContext('2d');
2056
2262
  const width = Math.floor(image.width);
2057
2263
  const height = Math.floor(image.height);
2058
- if((width <= 0) || (height <= 0))
2264
+ if(!ctx || (width <= 0) || (height <= 0))
2059
2265
  {
2060
- canvas.width = 1;
2061
- canvas.height = 1;
2062
- }
2063
- else
2064
- {
2065
- canvas.width = width;
2066
- canvas.height = height;
2067
- ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
2266
+ return LeUtils.getEmptyImageSrc();
2068
2267
  }
2268
+ canvas.width = width;
2269
+ canvas.height = height;
2270
+ ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
2069
2271
  ctx.globalCompositeOperation = 'source-in';
2070
2272
  ctx.fillStyle = color;
2071
2273
  ctx.fillRect(0, 0, canvas.width, canvas.height);
@@ -2073,7 +2275,7 @@ export const LeUtils = {
2073
2275
  }
2074
2276
  finally
2075
2277
  {
2076
- canvas.parentNode.removeChild(canvas);
2278
+ canvas?.parentNode?.removeChild(canvas);
2077
2279
  }
2078
2280
  },
2079
2281
 
@@ -2102,13 +2304,18 @@ export const LeUtils = {
2102
2304
  hexToRgb:
2103
2305
  (hexstring) =>
2104
2306
  {
2307
+ const initialHexstring = hexstring;
2105
2308
  hexstring = hexstring.replace(/[^0-9A-F]/gi, '');
2106
2309
  const hasAlpha = ((hexstring.length === 4) || (hexstring.length === 8));
2107
2310
  while(hexstring.length < 6)
2108
2311
  {
2109
2312
  hexstring = hexstring.replace(/(.)/g, '$1$1');
2110
2313
  }
2111
- const result = hexstring.match(/\w{2}/g).map((a) => parseInt(a, 16));
2314
+ const result = hexstring.match(/\w{2}/g)?.map((a) => parseInt(a, 16));
2315
+ if(!result || (result.length < 3))
2316
+ {
2317
+ throw new Error('Invalid hex color: "' + hexstring + '" (was given "' + initialHexstring + '")');
2318
+ }
2112
2319
  return [
2113
2320
  result[0],
2114
2321
  result[1],
@@ -2131,12 +2338,10 @@ export const LeUtils = {
2131
2338
  const b = rgb[2] / 255;
2132
2339
  const max = Math.max(r, g, b);
2133
2340
  const min = Math.min(r, g, b);
2134
- let h, s, l = (max + min) / 2;
2135
- if(max === min)
2136
- {
2137
- h = s = 0;
2138
- }
2139
- else
2341
+ let h = 0;
2342
+ let s = 0;
2343
+ let l = (max + min) / 2;
2344
+ if(max !== min)
2140
2345
  {
2141
2346
  const d = max - min;
2142
2347
  s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
@@ -2195,12 +2400,10 @@ export const LeUtils = {
2195
2400
  const h = hsl[0];
2196
2401
  const s = hsl[1];
2197
2402
  const l = hsl[2];
2198
- let r, g, b;
2199
- if(s === 0)
2200
- {
2201
- r = g = b = l;
2202
- }
2203
- else
2403
+ let r = 1;
2404
+ let g = 1;
2405
+ let b = 1;
2406
+ if(s !== 0)
2204
2407
  {
2205
2408
  const q = (l < 0.5) ? (l * (1 + s)) : (l + s - (l * s));
2206
2409
  const p = (2 * l) - q;
@@ -2241,13 +2444,13 @@ export const LeUtils = {
2241
2444
  *
2242
2445
  * Returns a number:
2243
2446
  *
2244
- * <pre>
2245
- * < 1.0 is not perceptible by human eyes
2447
+ * ```js
2448
+ * < 1 is not perceptible by human eyes
2246
2449
  * 1-2 is perceptible through close observation
2247
2450
  * 2-10 is perceptible at a glance
2248
2451
  * 11-49 is more similar than opposite
2249
2452
  * 100 is exactly the opposite color
2250
- * </pre>
2453
+ * ```
2251
2454
  *
2252
2455
  * @param {number[]} rgbA
2253
2456
  * @param {number[]} rgbB
@@ -2266,13 +2469,13 @@ export const LeUtils = {
2266
2469
  *
2267
2470
  * Returns a number:
2268
2471
  *
2269
- * <pre>
2270
- * < 1.0 is not perceptible by human eyes
2472
+ * ```js
2473
+ * < 1 is not perceptible by human eyes
2271
2474
  * 1-2 is perceptible through close observation
2272
2475
  * 2-10 is perceptible at a glance
2273
2476
  * 11-49 is more similar than opposite
2274
2477
  * 100 is exactly the opposite color
2275
- * </pre>
2478
+ * ```
2276
2479
  *
2277
2480
  * @param {number[]} labA
2278
2481
  * @param {number[]} labB
@@ -2304,16 +2507,16 @@ export const LeUtils = {
2304
2507
  *
2305
2508
  * Usage:
2306
2509
  *
2307
- * <pre>
2510
+ * ```js
2308
2511
  * LeUtils.getRgbOfGradient({
2309
2512
  * 0: [255, 0, 0],
2310
2513
  * 33: [255, 255, 0],
2311
2514
  * 66: [0, 255, 0],
2312
2515
  * 100:[0, 255, 255],
2313
2516
  * }, 45.1234);
2314
- * </pre>
2517
+ * ```
2315
2518
  *
2316
- * @param {{[percentage]: number[]}} gradient
2519
+ * @param {{percentage?:number[]}} gradient
2317
2520
  * @param {number} percentage
2318
2521
  * @returns {number[]}
2319
2522
  */
@@ -2341,12 +2544,14 @@ export const LeUtils = {
2341
2544
  });
2342
2545
  if(closest === null)
2343
2546
  {
2344
- return null;
2547
+ return [0, 0, 0];
2345
2548
  }
2346
2549
  closest = closest[0];
2347
2550
 
2348
- let higher = 99999;
2349
- let lower = -99999;
2551
+ const HIGHER = 99999;
2552
+ const LOWER = -99999;
2553
+ let higher = HIGHER;
2554
+ let lower = LOWER;
2350
2555
  LeUtils.each(gradient, (color, percent) =>
2351
2556
  {
2352
2557
  percent = INT_LAX(percent);
@@ -2365,20 +2570,12 @@ export const LeUtils = {
2365
2570
  }
2366
2571
  }
2367
2572
  });
2368
- if(higher === 99999)
2369
- {
2370
- higher = null;
2371
- }
2372
- if(lower === -99999)
2373
- {
2374
- lower = null;
2375
- }
2376
2573
 
2377
- if(((higher === null) && (lower === null)) || (higher === lower))
2574
+ if(((higher === HIGHER) && (lower === LOWER)) || (higher === lower))
2378
2575
  {
2379
2576
  return gradient[closest];
2380
2577
  }
2381
- else if((higher !== null) && (lower !== null))
2578
+ else if((higher !== HIGHER) && (lower !== LOWER))
2382
2579
  {
2383
2580
  const higherDifference = Math.abs(higher - percentage);
2384
2581
  const lowerDifference = Math.abs(percentage - lower);
@@ -2391,7 +2588,7 @@ export const LeUtils = {
2391
2588
  lower = closest;
2392
2589
  }
2393
2590
  }
2394
- else if(lower === null)
2591
+ else if(lower === LOWER)
2395
2592
  {
2396
2593
  lower = closest;
2397
2594
  }
@@ -2512,7 +2709,12 @@ export const LeUtils = {
2512
2709
  hexToBase64:
2513
2710
  (hexstring) =>
2514
2711
  {
2515
- return LeUtils.btoa(hexstring.replace(/[^0-9A-F]/gi, '').match(/\w{2}/g).map((a) => String.fromCharCode(parseInt(a, 16))).join(''));
2712
+ const hexResult = hexstring.replace(/[^0-9A-F]/gi, '').match(/\w{2}/g)?.map((a) => String.fromCharCode(parseInt(a, 16)))?.join('');
2713
+ if(!hexResult)
2714
+ {
2715
+ throw new Error('Invalid hex string: "' + hexstring + '"');
2716
+ }
2717
+ return LeUtils.btoa(hexResult);
2516
2718
  },
2517
2719
 
2518
2720
  /**
@@ -2537,7 +2739,7 @@ export const LeUtils = {
2537
2739
  /**
2538
2740
  * Converts bytes into a base64 string.
2539
2741
  *
2540
- * @param {ArrayLike<number>|ArrayBufferLike} arraybuffer
2742
+ * @param {ArrayLike<number>|ArrayBuffer} arraybuffer
2541
2743
  * @returns {string}
2542
2744
  */
2543
2745
  bytesToBase64:
@@ -2594,11 +2796,7 @@ export const LeUtils = {
2594
2796
  }
2595
2797
  try
2596
2798
  {
2597
- result = JSON.parse(result);
2598
- if(typeof result['-'] !== 'undefined')
2599
- {
2600
- return result['-'];
2601
- }
2799
+ return JSON.parse(result)?.['-'];
2602
2800
  }
2603
2801
  catch(e)
2604
2802
  {
@@ -2707,8 +2905,8 @@ export const LeUtils = {
2707
2905
  * This way, you can have values that aren't the same be treated as if they are. This can be used to deal with issues such as floating point errors for example.
2708
2906
  *
2709
2907
  * @param {*[]} elements
2710
- * @param {Function} comparator
2711
- * @returns {{getElements: (function(): *[]), getComparator: (function(): Function), size: (function(): number), isEmpty: (function(): boolean), contains: (function(*): boolean), first: (function(): *|undefined), last: (function(): *|undefined), pollFirst: (function(): *|undefined), pollLast: (function(): *|undefined), add: function(*), addAll: function(*[]|object), getEqualValue: (function(*): (*)), getEqualValueOrAdd: (function(*): (*))}}
2908
+ * @param {(valueA:*, valueB:*) => number} comparator
2909
+ * @returns {{getElements:(()=>*[]), getComparator:(()=>((valueA:*,valueB:*)=>number)), size:(()=>number), isEmpty:(()=>boolean), contains:((value:*)=>boolean), first:(()=>*|undefined), last:(()=>*|undefined), pollFirst:(()=>*|undefined), pollLast:(()=>*|undefined), add:((value:*)=>void), addAll:((values:*)=>void), getEqualValue:((value:*)=>*), getEqualValueOrAdd:((value:*)=>*)}}
2712
2910
  */
2713
2911
  createTreeSet:
2714
2912
  (elements, comparator) =>
@@ -2760,7 +2958,7 @@ export const LeUtils = {
2760
2958
  /**
2761
2959
  * Returns the comparator of the set.
2762
2960
  *
2763
- * @returns {Function}
2961
+ * @returns {(valueA:*, valueB:*) => number}
2764
2962
  */
2765
2963
  getComparator:
2766
2964
  () => comparator,
@@ -2841,7 +3039,7 @@ export const LeUtils = {
2841
3039
  /**
2842
3040
  * Adds all the given values to the set. Will only do so if no equal value already exists.
2843
3041
  *
2844
- * @param {*[]|object} values
3042
+ * @param {*} values
2845
3043
  */
2846
3044
  addAll:
2847
3045
  (values) =>
@@ -2888,7 +3086,7 @@ export const LeUtils = {
2888
3086
  },
2889
3087
 
2890
3088
  /**
2891
- * @typedef {Object} LeUtils~TransactionalValue
3089
+ * @typedef {Object} LeUtils_TransactionalValue
2892
3090
  * @property {*} value
2893
3091
  * @property {{id:string, value:*}[]} changes
2894
3092
  */
@@ -2901,7 +3099,7 @@ export const LeUtils = {
2901
3099
  * This allows you to make multiple unconfirmed changes, and confirm or cancel each of them individually at any time.
2902
3100
  *
2903
3101
  * @param {*} [value]
2904
- * @returns {LeUtils~TransactionalValue}
3102
+ * @returns {LeUtils_TransactionalValue}
2905
3103
  */
2906
3104
  createTransactionalValue:
2907
3105
  (value) =>
@@ -2916,7 +3114,7 @@ export const LeUtils = {
2916
3114
  /**
2917
3115
  * Returns true if the given value is a valid TransactionalValue, returns false if it isn't.
2918
3116
  *
2919
- * @param {LeUtils~TransactionalValue} transactionalValue
3117
+ * @param {LeUtils_TransactionalValue} transactionalValue
2920
3118
  * @returns {boolean}
2921
3119
  */
2922
3120
  isTransactionalValueValid:
@@ -2928,7 +3126,7 @@ export const LeUtils = {
2928
3126
  /**
2929
3127
  * Returns true if the given value is a TransactionalValue, false otherwise.
2930
3128
  *
2931
- * @param {LeUtils~TransactionalValue} transactionalValue
3129
+ * @param {LeUtils_TransactionalValue} transactionalValue
2932
3130
  * @returns {string}
2933
3131
  */
2934
3132
  transactionalValueToString:
@@ -2953,7 +3151,7 @@ export const LeUtils = {
2953
3151
  /**
2954
3152
  * Sets the committed value of the given TransactionalValue to the given value. Clears out the previously uncommitted changes.
2955
3153
  *
2956
- * @param {LeUtils~TransactionalValue} transactionalValue
3154
+ * @param {LeUtils_TransactionalValue} transactionalValue
2957
3155
  * @param {*} value
2958
3156
  */
2959
3157
  transactionSetAndCommit:
@@ -2972,7 +3170,7 @@ export const LeUtils = {
2972
3170
  * Sets the value of the given TransactionalValue to the given value, without yet committing it, meaning it can be committed or cancelled later.
2973
3171
  * It returns the ID of the change, which can be used to commit or cancel the change later.
2974
3172
  *
2975
- * @param {LeUtils~TransactionalValue} transactionalValue
3173
+ * @param {LeUtils_TransactionalValue} transactionalValue
2976
3174
  * @param {*} value
2977
3175
  * @returns {string}
2978
3176
  */
@@ -2993,7 +3191,7 @@ export const LeUtils = {
2993
3191
  * Commits the change with the given ID, making it the new committed value.
2994
3192
  * Returns true if the change was found and committed, returns false if it was already overwritten by a newer committed change.
2995
3193
  *
2996
- * @param {LeUtils~TransactionalValue} transactionalValue
3194
+ * @param {LeUtils_TransactionalValue} transactionalValue
2997
3195
  * @param {string} changeId
2998
3196
  * @returns {boolean}
2999
3197
  */
@@ -3015,7 +3213,7 @@ export const LeUtils = {
3015
3213
  * Cancels the change with the given ID, removing it from the uncommitted changes.
3016
3214
  * Returns true if the change was found and removed, returns false if it was already overwritten by a newer committed change.
3017
3215
  *
3018
- * @param {LeUtils~TransactionalValue} transactionalValue
3216
+ * @param {LeUtils_TransactionalValue} transactionalValue
3019
3217
  * @param {string} changeId
3020
3218
  * @returns {boolean}
3021
3219
  */
@@ -3036,7 +3234,7 @@ export const LeUtils = {
3036
3234
  * Returns true if the change was found, meaning it can still make a difference to the final committed value of this TransactionalValue.
3037
3235
  * Returns false if it was already overwritten by a newer committed change, meaning that this change can no longer make a difference to the final committed value of this TransactionalValue.
3038
3236
  *
3039
- * @param {LeUtils~TransactionalValue} transactionalValue
3237
+ * @param {LeUtils_TransactionalValue} transactionalValue
3040
3238
  * @param {string} changeId
3041
3239
  * @returns {boolean}
3042
3240
  */
@@ -3050,7 +3248,7 @@ export const LeUtils = {
3050
3248
  /**
3051
3249
  * Returns the committed value of the given TransactionalValue.
3052
3250
  *
3053
- * @param {LeUtils~TransactionalValue} transactionalValue
3251
+ * @param {LeUtils_TransactionalValue} transactionalValue
3054
3252
  * @returns {*}
3055
3253
  */
3056
3254
  transactionGetCommittedValue:
@@ -3063,7 +3261,7 @@ export const LeUtils = {
3063
3261
  /**
3064
3262
  * Returns the value (including any uncommitted changes made to it) of the given TransactionalValue.
3065
3263
  *
3066
- * @param {LeUtils~TransactionalValue} transactionalValue
3264
+ * @param {LeUtils_TransactionalValue} transactionalValue
3067
3265
  * @returns {*}
3068
3266
  */
3069
3267
  transactionGetValue:
@@ -3129,7 +3327,7 @@ export const LeUtils = {
3129
3327
  * ```
3130
3328
  *
3131
3329
  * @param {string} name
3132
- * @returns {{worker: Worker, sendMessage: function(Object, {timeout: number|undefined}|undefined): Promise<Object>}}
3330
+ * @returns {{worker:Worker|null, sendMessage:(data:Object,options:{timeout:number|undefined}|undefined)=>Promise<Object>}}
3133
3331
  */
3134
3332
  createWorkerThread:
3135
3333
  (name) =>
@@ -3138,7 +3336,7 @@ export const LeUtils = {
3138
3336
  {
3139
3337
  return {
3140
3338
  worker: null,
3141
- sendMessage:new Promise((resolve, reject) =>
3339
+ sendMessage:(data, options) => new Promise((resolve, reject) =>
3142
3340
  {
3143
3341
  reject('Workers are not supported in this environment');
3144
3342
  }),
@@ -3146,16 +3344,16 @@ export const LeUtils = {
3146
3344
  }
3147
3345
 
3148
3346
  const worker = new Worker('/workers/' + name + '.worker.js');
3149
- let listeners = {};
3347
+ let listeners = new Map();
3150
3348
 
3151
3349
  const addListener = (id, callback) =>
3152
3350
  {
3153
- listeners[id] = callback;
3351
+ listeners.set(id, callback);
3154
3352
  };
3155
3353
 
3156
3354
  const removeListener = (id) =>
3157
3355
  {
3158
- delete listeners[id];
3356
+ listeners.delete(id);
3159
3357
  };
3160
3358
 
3161
3359
  const sendMessage = (data, options) =>
@@ -3186,7 +3384,7 @@ export const LeUtils = {
3186
3384
  const data = message.data;
3187
3385
  if(data?.id)
3188
3386
  {
3189
- const callback = listeners[data.id];
3387
+ const callback = listeners.get(data.id);
3190
3388
  if(callback)
3191
3389
  {
3192
3390
  removeListener(data.id);
@@ -3205,20 +3403,20 @@ export const LeUtils = {
3205
3403
  *
3206
3404
  * @param {string} workerName
3207
3405
  * @param {Object} data
3208
- * @param {{timeout: number|undefined}} [options]
3406
+ * @param {{timeout:number|undefined}} [options]
3209
3407
  * @returns {Promise<Object>}
3210
3408
  */
3211
3409
  sendWorkerMessage:
3212
3410
  (() =>
3213
3411
  {
3214
- const workers = {};
3412
+ const workers = new Map();
3215
3413
  return (workerName, data, options) =>
3216
3414
  {
3217
- if(!workers[workerName])
3415
+ if(!workers.has(workerName))
3218
3416
  {
3219
- workers[workerName] = LeUtils.createWorkerThread(workerName);
3417
+ workers.set(workerName, LeUtils.createWorkerThread(workerName));
3220
3418
  }
3221
- return workers[workerName].sendMessage(data, options);
3419
+ return workers.get(workerName).sendMessage(data, options);
3222
3420
  };
3223
3421
  })(),
3224
3422
 
@@ -3251,7 +3449,7 @@ export const LeUtils = {
3251
3449
  return () => true;
3252
3450
  }
3253
3451
  const inputTypes = ['text', 'search', 'email', 'number', 'password', 'tel', 'time', 'url', 'week', 'month', 'date', 'datetime-local'];
3254
- return () => !((document?.activeElement?.tagName?.toLowerCase() === 'input') && inputTypes.includes(document?.activeElement?.type?.toLowerCase()));
3452
+ return () => !((document?.activeElement?.tagName?.toLowerCase() === 'input') && inputTypes.includes(document?.activeElement?.getAttribute('type')?.toLowerCase() ?? ''));
3255
3453
  })(),
3256
3454
 
3257
3455
  /**