@nativerent/js-utils 1.0.0 → 1.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/index.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { Primitive } from "./types";
1
+ import { FlattenableObject, Primitive, SimpleObject } from "./types";
2
2
 
3
3
  export function debounce(fn: Function, delay: number): () => void {
4
4
  let timeout: ReturnType<typeof setTimeout>;
@@ -118,22 +118,75 @@ export function getObjectKeys(object: object): string[] {
118
118
 
119
119
  /**
120
120
  * Works with primitive objects, see JSDoc @param
121
- *
122
- * @param object {[key: string]: SimpleObject | { [key: string]: SimpleObject }}
123
121
  */
124
- export function objectToQueryString(object: object): string {
122
+ export function objectToQueryString(object: {
123
+ [key: string]: SimpleObject | { [key: string]: SimpleObject };
124
+ }): string {
125
125
  return Object.entries(object)
126
126
  .map(([k, v]) => {
127
127
  if (Array.isArray(v)) {
128
128
  return v.map((item) => `${k}[]=${encodeURIComponent(item)}`).join("&");
129
129
  } else if (isObject(v)) {
130
- v = JSON.stringify(v);
130
+ return `${k}=${encodeURIComponent(JSON.stringify(v))}`;
131
131
  }
132
132
  return `${k}=${encodeURIComponent(v || "")}`;
133
133
  })
134
134
  .join("&");
135
135
  }
136
136
 
137
+ export function countObjectInnerLength(object: {
138
+ [key: string | number]: any;
139
+ }) {
140
+ const obj = Object.values(object);
141
+
142
+ return obj.length === 1
143
+ ? obj[0].length
144
+ : obj.reduce((acc, cur) => {
145
+ if (Array.isArray(acc)) {
146
+ acc = acc.length;
147
+ }
148
+ if (cur) {
149
+ acc += cur.length;
150
+ }
151
+ return acc;
152
+ });
153
+ }
154
+
155
+ /**
156
+ * Check if the element is in viewport
157
+ */
158
+ export const isElementVisible = (
159
+ element: HTMLElement,
160
+ minVisiblePercent = 50,
161
+ ) => {
162
+ const elementCoords = element.getBoundingClientRect();
163
+ const elementHeight = elementCoords.height;
164
+
165
+ if (!elementHeight) {
166
+ return false;
167
+ }
168
+
169
+ const elementTop = elementCoords.top;
170
+ const elementBottom = elementCoords.bottom;
171
+ const viewPortHeight = window.innerHeight;
172
+
173
+ const elementVisibleHeight =
174
+ elementTop > 0
175
+ ? // the element is in or below viewport
176
+ viewPortHeight - elementTop
177
+ : // the element is still in or above viewport
178
+ elementBottom;
179
+
180
+ return (elementVisibleHeight / elementHeight) * 100 >= minVisiblePercent;
181
+ };
182
+
183
+ /**
184
+ * Find a DOM element where an ad unit should be rendered to
185
+ */
186
+ export function getHtmlElement(id: string) {
187
+ return document.getElementById(id);
188
+ }
189
+
137
190
  export function decodeSafeURL(url: string): string {
138
191
  try {
139
192
  return decodeURI(url);
@@ -340,6 +393,18 @@ export function createStyleElement(css: string | null) {
340
393
  return element;
341
394
  }
342
395
 
396
+ /**
397
+ * Create a <link> element
398
+ */
399
+ export function createLinkElement(href: string) {
400
+ const element = createHtmlElement("link") as HTMLLinkElement;
401
+
402
+ element.rel = "stylesheet";
403
+ element.href = href;
404
+
405
+ return element;
406
+ }
407
+
343
408
  /**
344
409
  * Create svg elements
345
410
  */
@@ -360,3 +425,475 @@ export function createSvgElement(
360
425
 
361
426
  return element;
362
427
  }
428
+
429
+ /**
430
+ * Append a new script element to the DOM head
431
+ */
432
+ export function insertJs(js: string, inline = false) {
433
+ const element = createScriptElement(js, inline);
434
+
435
+ document.head.appendChild(element);
436
+ }
437
+
438
+ /**
439
+ * Append a new style element to the DOM head
440
+ */
441
+ export const insertCss = (
442
+ css: string,
443
+ className?: string,
444
+ external = false,
445
+ ) => {
446
+ const element = external ? createLinkElement(css) : createStyleElement(css);
447
+
448
+ if (className) {
449
+ element.className = className;
450
+ }
451
+
452
+ document.head.appendChild(element);
453
+ };
454
+
455
+ /**
456
+ * Get screen sizes
457
+ */
458
+ export function getScreenSize(): { width: number; height: number } {
459
+ let width, height;
460
+
461
+ if (window.screen) {
462
+ width = screen.width;
463
+ height = screen.height;
464
+ } else {
465
+ width = window.innerWidth;
466
+ height = window.innerHeight;
467
+ }
468
+
469
+ return {
470
+ width: width,
471
+ height: height,
472
+ };
473
+ }
474
+
475
+ /**
476
+ * Get a random integer between min and max
477
+ */
478
+ export function getRandomInt(min = 0, max = 4294967295) {
479
+ min = Math.ceil(min);
480
+ max = Math.floor(max);
481
+
482
+ return Math.floor(Math.random() * (max - min)) + min;
483
+ }
484
+
485
+ /**
486
+ * Create an image element to use as a tracking pixel
487
+ */
488
+ export function createPixelElement(src: string) {
489
+ const image = document.createElement("img");
490
+
491
+ image.src = src;
492
+ image.width = 1;
493
+ image.height = 1;
494
+ image.style.position = "absolute";
495
+ image.style.left = "-99999px";
496
+
497
+ return image;
498
+ }
499
+
500
+ /**
501
+ * Implementation of Java's String.hashCode() method
502
+ */
503
+ export function hashCode(str: string) {
504
+ let hash = 0,
505
+ chr;
506
+
507
+ for (let i = 0; i < str.length; i++) {
508
+ chr = str.charCodeAt(i);
509
+ hash = (hash << 5) - hash + chr;
510
+ hash |= 0; // convert to 32bit integer
511
+ }
512
+
513
+ return hash >>> 0;
514
+ }
515
+
516
+ /**
517
+ * Executes a callback as soon as DOM is interactive
518
+ */
519
+ export function onDOMReady(callback: Function, ...args: any[]) {
520
+ if (
521
+ document.readyState === "interactive" ||
522
+ document.readyState === "complete"
523
+ ) {
524
+ callback(...args);
525
+ } else {
526
+ document.addEventListener("DOMContentLoaded", () => callback(...args));
527
+ }
528
+ }
529
+
530
+ /**
531
+ * Calculate the percentage of two values
532
+ *
533
+ * @param a
534
+ * @param b
535
+ * @return number
536
+ */
537
+ export function toPercent(a: number, b: number): number {
538
+ return b > 0 ? (a / b) * 100 : 0;
539
+ }
540
+
541
+ /**
542
+ * Make a camelCase string
543
+ *
544
+ * @param string
545
+ * @return string
546
+ */
547
+ export function toCamelCase(string: string): string {
548
+ return string
549
+ .split(new RegExp(/[-_.]/))
550
+ .reduce(
551
+ (a: string, b: string) => a + b.charAt(0).toUpperCase() + b.slice(1),
552
+ );
553
+ }
554
+
555
+ /**
556
+ * Make a snake_case string
557
+ *
558
+ * @param string
559
+ * @return string
560
+ */
561
+ export function toSnakeCase(string: string): string {
562
+ return toCamelCase(string)
563
+ .replace(/(^[A-Z])?([A-Z])/gm, "$1_$2")
564
+ .toLowerCase();
565
+ }
566
+
567
+ /**
568
+ * Transform the first letter of the given string to the upper case
569
+ *
570
+ * @param string
571
+ * @return string
572
+ */
573
+ export function capitalize(string: string): string {
574
+ return string.charAt(0).toUpperCase() + string.slice(1);
575
+ }
576
+
577
+ /**
578
+ * Registry independent string sorting
579
+ *
580
+ * @param first
581
+ * @param second
582
+ */
583
+ export function sortByAlphabet(first: string, second: string): number {
584
+ return first.toLowerCase() > second.toLowerCase() ? 1 : -1;
585
+ }
586
+
587
+ /**
588
+ * @param length
589
+ * @return string of max length 100
590
+ */
591
+ export function getRandomStr(length?: number): string {
592
+ length = length ? (length > 100 ? 100 : length) : 10;
593
+
594
+ let str = Math.random().toString(36).substring(2);
595
+
596
+ while (str.length < length) {
597
+ str += Math.random().toString(36).substring(2);
598
+ }
599
+
600
+ const result = str.slice(-length);
601
+ return isNaN(Number(result)) ? result : getRandomStr(length);
602
+ }
603
+
604
+ export function getRandomItem(items: any[]): any {
605
+ return items[Math.floor(Math.random() * items.length)];
606
+ }
607
+
608
+ /**
609
+ * Summarize elements of the given array
610
+ *
611
+ * @param values
612
+ * @return number | string - a summary of all the numbers in the array or an empty string if
613
+ * there was only NaNs
614
+ */
615
+ export function sumArray(values: Array<any>): number | string {
616
+ if (values.every((value: any) => isNaN(value))) {
617
+ return "";
618
+ } else {
619
+ return values.reduce((sum: any, curr: any) => {
620
+ const value = Number(curr);
621
+
622
+ if (!isNaN(value)) {
623
+ return sum + value;
624
+ } else {
625
+ return sum;
626
+ }
627
+ }, 0);
628
+ }
629
+ }
630
+
631
+ /**
632
+ * Returns an array of the object keys which values are not present in filter
633
+ *
634
+ * @param obj
635
+ * @param values
636
+ */
637
+ export function filterObjectKeysByValues(
638
+ obj: { [key: string]: any },
639
+ values: Array<string | number | boolean | null | undefined>,
640
+ ): string[] {
641
+ values = Array.isArray(values) ? values : Array(values);
642
+
643
+ const keys = [];
644
+
645
+ for (const key in obj) {
646
+ if (!values.includes(obj[key])) {
647
+ keys.push(key);
648
+ }
649
+ }
650
+
651
+ return keys;
652
+ }
653
+
654
+ export function getFromObject(object: object, key: string): any {
655
+ return key.split(".").reduce((a: any, b: any) => {
656
+ return a[b];
657
+ }, object);
658
+ }
659
+
660
+ export function fireBlurEvent(
661
+ element: string | Element | null,
662
+ delay: number = 0,
663
+ ): void {
664
+ fireEvent("blur", element, delay);
665
+ }
666
+
667
+ export function fireInputEvent(
668
+ element: string | Element | null,
669
+ delay: number = 0,
670
+ ): void {
671
+ fireEvent("input", element, delay);
672
+ }
673
+
674
+ export function fireEvent(
675
+ eventName: string,
676
+ element: string | Element | null,
677
+ delay: number = 0,
678
+ ): void {
679
+ setTimeout(() => {
680
+ if (isString(element)) {
681
+ element = document.querySelector(element);
682
+ }
683
+
684
+ if (!isNullOrUndef(element)) {
685
+ element.dispatchEvent(
686
+ new Event(eventName, {
687
+ bubbles: true,
688
+ }),
689
+ );
690
+ }
691
+ }, delay);
692
+ }
693
+
694
+ export function hasObjectChanged(
695
+ object: { [key: string]: any },
696
+ values: any[],
697
+ ): boolean {
698
+ let hasChanged = false;
699
+
700
+ for (const key in object) {
701
+ let oldVal = object[key];
702
+ let [, newVal] = values.find(([newKey]) => key === newKey);
703
+
704
+ if (isObject(oldVal)) {
705
+ if (!isObject(newVal)) {
706
+ hasChanged = true;
707
+ } else {
708
+ hasChanged = hasObjectChanged(oldVal, Object.entries(newVal));
709
+ }
710
+ } else if (Array.isArray(newVal) || Array.isArray(oldVal)) {
711
+ newVal = Array.isArray(newVal) ? newVal : [];
712
+ oldVal = Array.isArray(oldVal) ? oldVal : [];
713
+
714
+ hasChanged = arrayDiff(newVal, oldVal).length > 0;
715
+ } else {
716
+ hasChanged = areDiff(newVal, oldVal);
717
+ }
718
+
719
+ if (hasChanged) {
720
+ break;
721
+ }
722
+ }
723
+
724
+ return hasChanged;
725
+ }
726
+
727
+ export function roundBigNum(number: number): number {
728
+ const digitsNum = Math.trunc(number).toString().length;
729
+
730
+ let roundTo = 0;
731
+ switch (true) {
732
+ case digitsNum > 2 && digitsNum < 5:
733
+ roundTo = 100;
734
+ break;
735
+ case digitsNum >= 5:
736
+ roundTo = 10000;
737
+ break;
738
+ }
739
+
740
+ return roundTo ? Math.round(number / roundTo) * roundTo : number;
741
+ }
742
+
743
+ export function extractNumbers(value: any): string {
744
+ if (!isStr(value) && !isNum(value)) {
745
+ return "";
746
+ }
747
+ return value.toString().replace(/[^0-9]/g, "");
748
+ }
749
+
750
+ export function removeSpaces(value: any): string {
751
+ if (!isStr(value) && !isNum(value)) {
752
+ return "";
753
+ }
754
+ return value.toString().replace(/\s/g, "");
755
+ }
756
+
757
+ export function roundUp(num: number, precision: number): number {
758
+ const multiplier = Number("1".padEnd(precision + 1, "0"));
759
+ return Math.ceil(num * multiplier) / multiplier;
760
+ }
761
+
762
+ export function roundDown(num: number, precision: number): number {
763
+ const multiplier = Number("1".padEnd(precision + 1, "0"));
764
+ return Math.floor(num * multiplier) / multiplier;
765
+ }
766
+
767
+ export function areDiff(
768
+ val1?: Primitive | null,
769
+ val2?: Primitive | null,
770
+ strict?: boolean,
771
+ ): boolean {
772
+ if (strict) {
773
+ return val1 !== val2;
774
+ }
775
+
776
+ // at least one value is not empty
777
+ if (Boolean(val1) || Boolean(val2)) {
778
+ return val1 != val2;
779
+ }
780
+
781
+ // both empty, but not equally empty!
782
+ // each one may be one of: empty string, null or undefined
783
+ return false;
784
+ }
785
+
786
+ export function flattenObject(obj: FlattenableObject, prefix = "") {
787
+ return Object.keys(obj).reduce((acc, k) => {
788
+ const pre = prefix.length ? prefix + "." : "";
789
+ if (isObject(obj[k]) || Array.isArray(obj[k])) {
790
+ Object.assign(acc, flattenObject(obj[k], pre + k));
791
+ } else {
792
+ acc[pre + k] = obj[k];
793
+ }
794
+ return acc;
795
+ }, {} as FlattenableObject);
796
+ }
797
+
798
+ export function flattenObjectAsArray(obj: FlattenableObject) {
799
+ const result: FlattenableObject = {};
800
+ const flat = flattenObject(obj);
801
+ Object.keys(flat).forEach((key) => {
802
+ const newKey = key.replaceAll(/\.([^.]+)/g, "[$1]");
803
+ result[newKey] = flat[key];
804
+ });
805
+
806
+ return result;
807
+ }
808
+
809
+ export function parseObjectPathStr(pathStr: string): string[] {
810
+ const pathParts = pathStr.replace(/\[/g, ".").replace(/\]/g, "").split(".");
811
+
812
+ if (pathParts.length > 1 && pathParts[1].includes("[")) {
813
+ return [pathParts[0], ...parseObjectPathStr(pathParts[1])];
814
+ }
815
+
816
+ return pathParts;
817
+ }
818
+
819
+ export function formatNumber(
820
+ num: number | string,
821
+ fractionDigits?: number,
822
+ ): string {
823
+ fractionDigits = isNullOrUndef(fractionDigits) ? 2 : fractionDigits;
824
+ return new Intl.NumberFormat("ru-RU", {
825
+ style: "decimal",
826
+ minimumFractionDigits: fractionDigits,
827
+ maximumFractionDigits: fractionDigits,
828
+ })
829
+ .format(Number(num))
830
+ .replace(",", ".")
831
+ .replace(/\u00A0/g, " "); // charCode 160, White-space
832
+ }
833
+
834
+ export function formatNumberWithSign(
835
+ num: number | string,
836
+ fractionDigits?: number,
837
+ ): string {
838
+ return formatWithSign(num, formatNumber(num, fractionDigits));
839
+ }
840
+
841
+ export function formatPercent(num: number | string): string {
842
+ return formatNumber(num) + "%";
843
+ }
844
+
845
+ export function formatWithSign(
846
+ num: number | string,
847
+ numString?: string,
848
+ ): string {
849
+ numString = numString ? numString : num.toString();
850
+ return (Number(num) < 0 ? "" : "+") + numString;
851
+ }
852
+
853
+ export function autoSizeText(
854
+ el: HTMLElement,
855
+ height: number,
856
+ minFontSize: number,
857
+ maxFontSize: number = 50,
858
+ ) {
859
+ let attempts = 30;
860
+
861
+ const resizeText = () => {
862
+ if (getTextHeight() === 0) {
863
+ return;
864
+ }
865
+ while (attempts && getTextHeight() > height) {
866
+ attempts--;
867
+ reduceText();
868
+ }
869
+ while (attempts && getTextHeight() < height) {
870
+ attempts--;
871
+ enlargeText();
872
+ }
873
+ };
874
+
875
+ const reduceText = () => {
876
+ const fontSize = getNumericStyleProp("fontSize", el);
877
+ const newFontSize = fontSize - 1;
878
+ if (fontSize > 1 && newFontSize >= minFontSize) {
879
+ el.style.fontSize = `${newFontSize}px`;
880
+ }
881
+ };
882
+
883
+ const enlargeText = () => {
884
+ const fontSize = getNumericStyleProp("fontSize", el);
885
+ const newFontSize = fontSize + 1;
886
+ if (newFontSize <= maxFontSize) {
887
+ el.style.fontSize = `${newFontSize}px`;
888
+ }
889
+ };
890
+
891
+ const getTextHeight = () =>
892
+ Math.floor(
893
+ el.scrollHeight -
894
+ getNumericStyleProp("paddingTop", el) -
895
+ getNumericStyleProp("paddingBottom", el),
896
+ );
897
+
898
+ resizeText();
899
+ }
package/src/types.d.ts CHANGED
@@ -1 +1,7 @@
1
- export type Primitive = string | boolean | number;
1
+ export type FlattenableObject = { [key: string]: any };
2
+
3
+ export type Primitive = boolean | number | string;
4
+
5
+ export type SimpleObject = {
6
+ [key: string]: Primitive | Primitive[] | null | undefined;
7
+ };