raiutils 9.0.6 → 9.1.0

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.
@@ -0,0 +1,38 @@
1
+ //https://github.com/Pecacheu/Utils.js; GNU GPL v3
2
+
3
+ import os from 'os';
4
+ import { utils as U } from './utils.js';
5
+ export type * from './utils.js';
6
+
7
+ namespace ext {
8
+ /** Get list of system IPs */
9
+ export function getIPs() {
10
+ const ip: string[]=[], fl=os.networkInterfaces();
11
+ for(let k in fl) fl[k]!.forEach(f => {
12
+ if(!f.internal && f.family == 'IPv4' && f.mac != '00:00:00:00:00:00' && f.address) ip.push(f.address);
13
+ });
14
+ return ip;
15
+ }
16
+
17
+ /** Get system info
18
+ @returns [sysOS, arch, cpuInfo] */
19
+ export function getOS() {
20
+ let sysOS, arch;
21
+ switch(os.platform()) {
22
+ case 'win32': sysOS="Windows"; break;
23
+ case 'darwin': sysOS="MacOS"; break;
24
+ case 'linux': sysOS="Linux"; break;
25
+ default: sysOS=os.platform();
26
+ }
27
+ switch(os.arch()) {
28
+ case 'ia32': arch="32-bit"; break;
29
+ case 'x64': arch="64-bit"; break;
30
+ case 'arm': arch="ARM"; break;
31
+ default: arch=os.arch();
32
+ }
33
+ return [sysOS, arch, os.cpus()[0]?.model||''];
34
+ }
35
+ }
36
+
37
+ export const utils = <typeof U & typeof ext>U;
38
+ export default utils;
package/src/utils.ts CHANGED
@@ -1,12 +1,10 @@
1
1
  //https://github.com/Pecacheu/Utils.js; GNU GPL v3
2
2
 
3
3
  //Node.js compat
4
- type P = [typeof window, typeof history, typeof DOMRect, typeof HTMLCollection,
5
- typeof Element, typeof NodeList, typeof addEventListener]
4
+ //@ts-ignore
5
+ type P = [typeof document, typeof HTMLCollection];
6
6
  //@ts-expect-error
7
- const IsNode=typeof window==='undefined', P:P=IsNode?
8
- [{}, {back:()=>{},forward:()=>{}}, class{}, class{}, class{}, class{}, ()=>{}]:
9
- [window, history, DOMRect, HTMLCollection, Element, NodeList, addEventListener];
7
+ const IsNode=typeof window==='undefined', P:P=IsNode?[{}, class{}]:[document, HTMLCollection];
10
8
 
11
9
  //-------------------------------------------- Types --------------------------------------------
12
10
 
@@ -39,19 +37,6 @@ export interface Array<T> {
39
37
  at(idx: number): T | undefined;
40
38
  }
41
39
 
42
- export interface HTMLCollection {
43
- each: <R>(fn: (itm: Element, idx: number, len: number) => R | "!",
44
- st?: number, en?: number) => (R | undefined);
45
- eachAsync: <R>(fn: (itm: Element, idx: number, len: number) => R | "!",
46
- st?: number, en?: number, pe?: boolean) => Promise<R | undefined>;
47
- }
48
- export interface NodeList {
49
- each: <R>(fn: (itm: Node, idx: number, len: number) => R | "!",
50
- st?: number, en?: number) => (R | undefined);
51
- eachAsync: <R>(fn: (itm: Node, idx: number, len: number) => R | "!",
52
- st?: number, en?: number, pe?: boolean) => Promise<R | undefined>;
53
- }
54
-
55
40
  export interface Function {
56
41
  /** Wrap a function so that it always has a preset argument list when called.
57
42
  In the called function, `this` is set to the caller's arguments, granting access to both */
@@ -68,11 +53,6 @@ export interface RegExpConstructor {
68
53
  escape(s: string): string;
69
54
  }
70
55
 
71
- export interface TouchList {
72
- /** Get touch by id, if it exists */
73
- get(id: number): Touch | undefined;
74
- }
75
-
76
56
  interface Base64Opts {
77
57
  alphabet?: 'base64'|'base64url';
78
58
  omitPadding?: boolean;
@@ -83,17 +63,6 @@ export interface Uint8ArrayConstructor {
83
63
  export interface Uint8Array {
84
64
  toBase64(opts: Base64Opts): string;
85
65
  }
86
-
87
- export interface Element {
88
- /** Get an element's index in its parent. Returns -1 if the element has no parent */
89
- index: number;
90
- /** Insert child at index */
91
- insertChildAt(el: Element, i: number): void;
92
- /** Get element bounding rect as UtilRect object */
93
- boundingRect: utils.UtilRect;
94
- /** Get element inner rect (excluding border and padding) as UtilRect object */
95
- innerRect: utils.UtilRect;
96
- }
97
66
  }
98
67
 
99
68
  export interface AnyMap {[k: string]: any};
@@ -103,16 +72,17 @@ export interface QueryMap {[k: string]: true | string | string[]};
103
72
  //-------------------------------------------- Extensions --------------------------------------------
104
73
 
105
74
  export namespace utils {
106
-
107
- const [window, history, DOMRect, HTMLCollection,
108
- Element, NodeList, addEventListener] = P;
75
+ const [document, HTMLCollection] = P;
109
76
 
110
77
  /** Current library version */
111
- export const VER = "v9.0.6";
78
+ export const VER = "v9.1.0";
112
79
 
113
80
  /** Whether the environment is Node.js or Browser */
114
81
  export const isNode = IsNode;
115
82
 
83
+ /** Import modules only in Node.js, otherwise return empty list */
84
+ export const importNode = async (...mods: string[]) => (IsNode ? Promise.all(mods.map(i => import(i))) : []) as any[];
85
+
116
86
  //==== Objects ====
117
87
 
118
88
  /** Add getter and/or setter for `name` to `obj` */
@@ -243,7 +213,9 @@ async function eachAsync(this: any[], fn: (itm: any, idx: number, len: number) =
243
213
  this instanceof HTMLCollection?this[i].remove():this.splice(i,1); --i,--l;
244
214
  } else if(r[n]!=null) return r[n];
245
215
  }
246
- [Array, HTMLCollection, NodeList].forEach(p => {proto(p,'each',each), proto(p,'eachAsync',eachAsync)});
216
+
217
+ proto(Array, 'each', each);
218
+ proto(Array, 'eachAsync', eachAsync);
247
219
 
248
220
  //==== Numbers ====
249
221
 
@@ -422,108 +394,30 @@ if(!('at' in Array.prototype)) proto(Array, 'at', function(this: any[], idx: num
422
394
  if(i>=0 && i<l) return this[i];
423
395
  });
424
396
 
425
- //==== Other Extensions ====
397
+ //==== Utility ====
426
398
 
427
399
  proto(Function, 'wrap', function(this: any, ...args: any[]) {
428
400
  const f=this; return function() {return f.apply(arguments, args)}
429
401
  }, false, true);
430
402
 
431
- if(window.TouchList) proto(TouchList, 'get', function(this: any, id: number) {
432
- for(const t of this) if(t.identifier === id) return t;
433
- });
434
-
435
- define(Element.prototype, 'index', function(this: any) {
436
- const p=this.parentElement; if(!p) return -1;
437
- return Array.prototype.indexOf.call(p.children, this);
438
- });
439
-
440
-
441
- proto(Element, 'insertChildAt', function(this: any, el: Element, i: number) {
442
- if(i<0) i=0; if(i >= this.children.length) this.appendChild(el);
443
- else this.insertBefore(el, this.children[i]);
444
- });
445
-
446
- /** Get element bounding rect as UtilRect object */
447
- export const boundingRect = (e: Element) => new UtilRect(e.getBoundingClientRect());
448
-
449
- /** Get element inner rect (excluding border and padding) as UtilRect object */
450
- export function innerRect(e: Element) {
451
- let r=e.getBoundingClientRect(), s=getComputedStyle(e);
452
- return new UtilRect(r.top+parseFloat(s.paddingTop)+parseFloat(s.borderTopWidth),
453
- r.bottom-parseFloat(s.paddingBottom)-parseFloat(s.borderBottomWidth),
454
- r.left+parseFloat(s.paddingLeft)+parseFloat(s.borderLeftWidth),
455
- r.right-parseFloat(s.paddingRight)-parseFloat(s.borderRightWidth));
456
- };
457
-
458
- define(Element.prototype, 'boundingRect', function(this: any) {return boundingRect(this)});
459
- define(Element.prototype, 'innerRect', function(this: any) {return innerRect(this)});
460
-
461
- //-------------------------------------------- DOM Model --------------------------------------------
462
-
463
- //==== General ====
464
-
465
- /** Better class for bounding boxes */
466
- export class UtilRect {
467
- x!: number; left!: number;
468
- y!: number; top!: number;
469
- x2!: number; right!: number;
470
- y2!: number; bottom!: number;
471
- w!: number; width!: number;
472
- h!: number; height!: number;
473
- centerX!: number; centerY!: number;
474
-
475
- constructor(t: number | DOMRect | UtilRect, b?: number, l?: number, r?: number) {
476
- const f=Number.isFinite; let tt=0,bb=0,ll=0,rr=0;
477
- define(this,'x', ()=>ll, v=>{f(v)?(rr+=v-ll,ll=v):0});
478
- define(this,'y', ()=>tt, v=>{f(v)?(bb+=v-tt,tt=v):0});
479
- define(this,'top', ()=>tt, v=>{tt=f(v)?v:0});
480
- define(this,['bottom','y2'],()=>bb, v=>{bb=f(v)?v:0});
481
- define(this,'left', ()=>ll, v=>{ll=f(v)?v:0});
482
- define(this,['right','x2'], ()=>rr, v=>{rr=f(v)?v:0});
483
- define(this,['width','w'], ()=>rr-ll, v=>{rr=v>=0?ll+v:0});
484
- define(this,['height','h'], ()=>bb-tt, v=>{bb=v>=0?tt+v:0});
485
- define(this,'centerX', ()=>ll/2+rr/2);
486
- define(this,'centerY', ()=>tt/2+bb/2);
487
- if(t instanceof DOMRect || t instanceof UtilRect)
488
- tt=t.top, bb=t.bottom, ll=t.left, rr=t.right;
489
- else tt=t, bb=b!, ll=l!, rr=r!;
490
- }
491
-
492
- /** Check if rect contains point, other rect, or Element */
493
- contains(x: number | UtilRect | Element, y?: number): boolean {
494
- if(x instanceof Element) return this.contains(x.boundingRect);
495
- if(x instanceof UtilRect) return x.x >= this.x && x.x2 <= this.x2 && x.y >= this.y && x.y2 <= this.y2;
496
- return x >= this.x && x <= this.x2 && y! >= this.y && y! <= this.y2;
497
- }
403
+ const R_ES = /\S/;
498
404
 
499
- /** Check if rect overlaps rect or Element */
500
- overlaps(r: UtilRect | Element): boolean {
501
- if(r instanceof Element) return this.overlaps(r.boundingRect);
502
- if(!(r instanceof UtilRect)) return false;
503
- let x: any, y: any;
504
- if(r.x2-r.x >= this.x2-this.x) x = this.x >= r.x && this.x <= r.x2 || this.x2 >= r.x && this.x2 <= r.x2;
505
- else x = r.x >= this.x && r.x <= this.x2 || r.x2 >= this.x && r.x2 <= this.x2;
506
- if(r.y2-r.y >= this.y2-this.y) y = this.y >= r.y && this.y <= r.y2 || this.y2 >= r.y && this.y2 <= r.y2;
507
- else y = r.y >= this.y && r.y <= this.y2 || r.y2 >= this.y && r.y2 <= this.y2;
508
- return x&&y;
405
+ /** Check if string, array, or object is empty.
406
+ Returns false for all other types */
407
+ export function isBlank(s: any) {
408
+ if(s == null) return true;
409
+ if(typeof s === 'string') return !R_ES.test(s);
410
+ if(typeof s === 'object') {
411
+ if(typeof s.length === 'number') return s.length === 0;
412
+ return Object.keys(s).length === 0;
509
413
  }
414
+ return false;
415
+ }
510
416
 
511
- /** Get distance from this rect to point, other rect, or Element */
512
- dist(x: number | UtilRect | Element, y?: number): number {
513
- if(x instanceof Element) return this.dist(x.boundingRect);
514
- const n = x instanceof UtilRect;
515
- y = Math.abs((n?(x as UtilRect).centerY:y as number)-this.centerY),
516
- x = Math.abs((n?(x as UtilRect).centerX:x as number)-this.centerX);
517
- return Math.sqrt(x*x+y*y);
518
- }
417
+ /** setTimeout but async */
418
+ export const delay = (ms: number): Promise<void> => new Promise(r => setTimeout(r,ms));
519
419
 
520
- /** Expand (or contract if negative) a UtilRect by num of pixels. Useful for using UtilRect objects as element hitboxes
521
- @returns self for chaining */
522
- expand(by: number) {
523
- this.top -= by, this.left -= by, this.bottom += by, this.right += by;
524
- return this;
525
- }
526
- }
420
+ //-------------------------------------------- DOM Model --------------------------------------------
527
421
 
528
422
  export interface UserAgentInfo {
529
423
  os?: string;
@@ -568,107 +462,6 @@ export function deviceInfo(ua?: string) {
568
462
  return d;
569
463
  }
570
464
 
571
- export const device = IsNode ? null : deviceInfo();
572
- export const mobile = device?.mobile;
573
-
574
- const R_CTR = /translate(?:x|y)?\(.+?\)/gi;
575
-
576
- /** Center element with JS
577
-
578
- Modes:
579
- - `trans`: Uses transform: translate. Responsive, no container
580
- - Default: New flexbox method
581
- @param only `x` for only x axis centering, `y` for only y axis, null for both */
582
- export function center(el: HTMLElement, only?: "x" | "y", mode?: "trans") {
583
- const os = el.style;
584
- if(mode == 'trans') {
585
- if(!os.position) os.position='absolute';
586
- let tr = os.transform.replace(R_CTR,'').trim();
587
- if(!only || only === 'x') os.left='50%', tr+=' translateX(-50%)';
588
- if(!only || only === 'y') os.top='50%', tr+=' translateY(-50%)';
589
- os.transform=tr;
590
- } else {
591
- const cont = mkDiv(el.parentNode, null, {display:'flex', top:0, left:0}), cs = cont.style;
592
- cont.appendChild(el);
593
- if(!only || only === 'x') cs.justifyContent='center', cs.width='100%';
594
- if(!only || only === 'y') cs.alignItems='center',
595
- cs.height='100%', cs.position='absolute';
596
- }
597
- }
598
-
599
- //==== Navigation ====
600
-
601
- /** Called when a virtual navigation event occurs, including on page load */
602
- export let onNav: (state: any) => void;
603
-
604
- /** Generate a virtual navigation event, updating the URL bar
605
- @param state Optional data given to `onNav` whenever the user returns to this history entry */
606
- export function go(url: string | URL, state?: any) {
607
- history.pushState(state, '', url), doNav(state);
608
- }
609
-
610
- addEventListener('popstate', e => doNav(e.state));
611
- addEventListener('load', () => setTimeout(() => doNav(history.state),1));
612
- function doNav(s: any) {if(onNav) onNav.call(null,s)}
613
-
614
- //==== DOM Creation ====
615
-
616
- /** Create elements with ease! Just remember **PCSI** - Parent, class, style, innerHTML */
617
- export function mkEl<K extends keyof HTMLElementTagNameMap>(tag: K, parent?: Node | null, cls?: string | null,
618
- style?: CSSStyleDeclaration | AnyMap | null, inner?: string | null): HTMLElementTagNameMap[K] {
619
- const e = document.createElement(tag);
620
- if(cls != null) e.className = cls;
621
- if(inner != null) e.innerHTML = inner;
622
- if(style && typeof style === 'object') for(const k in style) {
623
- if(k in e.style) (e.style as any)[k] = (style as any)[k];
624
- else e.style.setProperty(k, (style as any)[k]);
625
- }
626
- if(parent != null) parent.appendChild(e);
627
- return e;
628
- }
629
-
630
- /** Shorthand for `mkEl` with div tag */
631
- export const mkDiv = (parent?: Node | null, cls?: string | null, style?: CSSStyleDeclaration | AnyMap | null,
632
- inner?: string | null) => mkEl('div', parent, cls, style, inner);
633
-
634
- /** Add text node to the DOM */
635
- export const addText = (parent: Node, text: string) => parent.appendChild(document.createTextNode(text));
636
-
637
- //==== CSS ====
638
-
639
- let TWCanvas: HTMLCanvasElement;
640
-
641
- /** Get predicted width of text w/ given CSS font style */
642
- export function textWidth(txt: string, font: string) {
643
- const c = TWCanvas||(TWCanvas=mkEl('canvas')), ctx = c.getContext('2d')!;
644
- ctx.font = font; return ctx.measureText(txt).width;
645
- }
646
-
647
- const R_SK = /[A-Z]/g, R_SR=(s: string) => '-'+s.toLowerCase();
648
- function defSty() {
649
- for(const s of document.styleSheets as any) try {s.cssRules; return s} catch(e) {}
650
- //let ns=mkEl('style',document.head); addText(ns,''); return ns.sheet!;
651
- return mkEl('style', document.head).sheet;
652
- }
653
-
654
- /** Create a CSS rule and append it to the current document
655
- @param sel CSS selector, eg. `.class` or `#id` */
656
- export function addCSS(sel: string, style: CSSStyleDeclaration | AnyMap, sheet?: CSSStyleSheet) {
657
- if(!sheet) sheet=defSty(); let k,s=[];
658
- for(k in style) s.push(`${k.replace(R_SK, R_SR)}:${(style as AnyMap)[k]}`);
659
- sheet!.insertRule(`${sel}{${s.join(';')}}`);
660
- }
661
-
662
- /** Remove a CSS selector from **all** stylesheets */
663
- export function remCSS(sel: string) {
664
- let s,rl;
665
- for(s of document.styleSheets as any) {
666
- try {rl=s.cssRules} catch(e) {continue}
667
- for(let i=0,l=rl.length; i<l; ++i) if(rl[i] instanceof CSSStyleRule
668
- && rl[i].selectorText===sel) s.deleteRule(i);
669
- }
670
- }
671
-
672
465
  //==== Cookies! (Yum) ====
673
466
 
674
467
  /** Set a cookie
@@ -677,12 +470,14 @@ export function remCSS(sel: string) {
677
470
  @param secure Only allow in HTTPS context */
678
471
  export function setCookie(key: string, val?: string, exp?: Date | number, secure?: boolean) {
679
472
  let c=`${encodeURIComponent(key)}=${val==null?'':encodeURIComponent(val)};path=/`;
680
- if(exp != null) {
681
- if(exp === -1) exp=new Date(9e14);
682
- if(exp instanceof Date) c+=';expires='+exp.toUTCString();
683
- else c+=';max-age='+exp;
473
+ if(val) {
474
+ if(exp != null) {
475
+ if(exp === -1) exp=new Date(9e14);
476
+ if(exp instanceof Date) c+=';expires='+exp.toUTCString();
477
+ else c+=';max-age='+exp;
478
+ }
479
+ if(secure) c+=';secure';
684
480
  }
685
- if(secure) c+=';secure';
686
481
  if(!IsNode) document.cookie = c;
687
482
  return c;
688
483
  }
@@ -692,7 +487,7 @@ export function setCookie(key: string, val?: string, exp?: Date | number, secure
692
487
  export function getCookie(key: string, ckStr?: string) {
693
488
  if(ckStr == null) ckStr=document.cookie;
694
489
  key=encodeURIComponent(key)+'=';
695
- let l=ckStr.split('; '), c: string;
490
+ let l=ckStr!.split('; '), c: string;
696
491
  for(c of l) if(c.startsWith(key))
697
492
  return decodeURIComponent(c.slice(key.length));
698
493
  }
@@ -759,123 +554,6 @@ export function toQuery(data: QueryMap, sep='&') {
759
554
  return q.join(sep);
760
555
  }
761
556
 
762
- //==== Inputs ====
763
-
764
- const R_NFZ=/\.0*$/;
765
-
766
- export interface NumField extends HTMLInputElement {
767
- num: number;
768
- ns: string | null;
769
- set: (num: number | string) => void;
770
- setRange: (min?: number, max?: number, decMax?: number) => void;
771
- onnuminput?: (this: GlobalEventHandlers, ev?: Event) => any;
772
- }
773
-
774
- /** Turns your boring <input> into a mobile-friendly number entry field with max/min & negative support!
775
-
776
- Tips:
777
- - Use `field.onnuminput` in place of oninput, get number value with field.num
778
- - On mobile, use star key for decimal point and pound key for negative
779
- - You can set `field.nStep` in order to change the up/down arrow step size
780
- - Use `field.setRange` to change min, max, and decMax
781
-
782
- @param min Min value, default min safe int
783
- @param max Max value, default max safe int
784
- @param decMax Max decimal precision (eg. 3 is 0.001), default 0
785
- @param sym If a symbol (eg. '$') is given, uses currency mode */
786
- export function numField(field: HTMLInputElement, min?: number, max?: number, decMax?: number, sym?: string) {
787
- const f = field as NumField, RM = RegExp(`[,${sym?RegExp.escape(sym):''}]`, 'g');
788
- f.type = (mobile||decMax||sym)?'tel':'number';
789
- f.setAttribute('pattern', "\\d*");
790
- //@ts-expect-error
791
- if(!f.step) f.step = 1;
792
- f.addEventListener('keydown', e => {
793
- if(e.ctrlKey) return;
794
- let k=e.key, kn=k.length===1&&Number.isFinite(Number(k)),
795
- ns=f.ns, len=ns!.length, dec=ns!.indexOf('.');
796
-
797
- if(k==='Tab' || k==='Enter') return;
798
- else if(kn) {if(dec===-1 || len-dec < decMax!+1) ns+=k} //Number
799
- else if(k==='.' || k==='*') {if(decMax && dec==-1
800
- && f.num!=max && (min!>=0 || f.num!=min)) { //Decimal
801
- if(!len && min!>0) ns=Math.floor(min!)+'.';
802
- else ns+='.';
803
- }} else if(k==='Backspace' || k==='Delete') { //Backspace
804
- if(min!>0 && f.num===min && ns!.endsWith('.')) ns='';
805
- else ns=ns!.slice(0,-1);
806
- } else if(k==='-' || k==='#') {if(min!<0 && !len) ns='-'} //Negative
807
- else if(k==='ArrowUp') ns=null, f.set(f.num+Number(f.step)); //Up
808
- else if(k==='ArrowDown') ns=null, f.set(f.num-Number(f.step)); //Down
809
-
810
- if(ns !== null && ns !== f.ns) {
811
- len=ns.length, dec=ns.indexOf('.');
812
- let neg=ns==='-'||ns==='-.', s=neg?'0':ns+(ns.endsWith('.')?'0':''),
813
- nr=Number(s), n=bounds(nr, min, max);
814
- if(!kn || !ns || f.num !== n || (dec!==-1 && len-dec < decMax!+1)) {
815
- f.ns=ns, f.num=n;
816
- f.value = sym ? neg?sym+'-0.00':formatCost(n,sym):
817
- (ns[0]==='-'?'-':'')+Math.floor(Math.abs(n))
818
- +(dec!==-1?ns.slice(dec)+(R_NFZ.test(ns)?'0':''):'');
819
- if(f.onnuminput) f.onnuminput.call(f,e);
820
- }
821
- }
822
- e.preventDefault();
823
- });
824
- function numRng(n: any) {
825
- if(typeof n==='string') n=n.replace(RM,'');
826
- n=bounds(Number(n)||0, min, max);
827
- return decMax?Number(n.toFixed(decMax)):Math.round(n);
828
- }
829
- f.set=n => {
830
- f.num = numRng(n);
831
- f.ns = f.num.toString();
832
- f.value = sym?formatCost(f.num,sym):f.ns;
833
- f.ns=f.ns.replace(/^(-?)0+/,'$1');
834
- if(f.onnuminput) f.onnuminput.call(f);
835
- }
836
- f.setRange=(nMin, nMax, nDecMax) => {
837
- min=nMin==null ? Number.MIN_SAFE_INTEGER : nMin;
838
- max=nMax==null ? Number.MAX_SAFE_INTEGER : nMax;
839
- decMax=nDecMax==null ? sym?2:0 : nDecMax;
840
- if(numRng(f.num) !== f.num) f.set(f.num);
841
- }
842
- f.addEventListener('input', () => f.set(f.value));
843
- f.addEventListener('paste', e => {f.set(e.clipboardData!.getData('text')); e.preventDefault()});
844
- f.setRange(min, max, decMax);
845
- return f;
846
- }
847
-
848
- export interface TextArea extends HTMLTextAreaElement {
849
- set: (val: string) => void;
850
- }
851
-
852
- //By Rick Kukiela @ StackOverflow
853
- /** Auto-resizing textarea, dynamically scales lineHeight based on input.
854
- Use `el.set(value)` to set value & update size */
855
- export function autosize(el: HTMLTextAreaElement, maxRows=5, minRows=1) {
856
- const e = el as TextArea;
857
- e.set = v => {e.value=v,cb()};
858
- let s=e.style;
859
- s.maxHeight=s.resize='none', s.minHeight='0', s.height='auto';
860
- e.setAttribute('rows', minRows as any);
861
- function cb() {
862
- if(e.scrollHeight===0) return setTimeout(cb,1); //Still loading
863
- e.setAttribute('rows', 1 as any);
864
- //Override style
865
- let cs=getComputedStyle(e);
866
- s.setProperty('overflow', 'hidden', 'important');
867
- s.width=e.innerRect.w+'px', s.boxSizing='content-box', s.borderWidth=s.paddingInline='0';
868
- //Calc scroll height
869
- let pad=parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom),
870
- lh=cs.lineHeight==='normal' ? parseFloat(cs.height) : parseFloat(cs.lineHeight),
871
- rows=Math.round((Math.round(e.scrollHeight) - pad)/lh);
872
- //Undo overrides & apply
873
- s.overflow=s.width=s.boxSizing=s.borderWidth=s.paddingInline='';
874
- e.setAttribute('rows', bounds(rows, minRows, maxRows) as any);
875
- }
876
- e.addEventListener('input', cb);
877
- }
878
-
879
557
  //==== Dates ====
880
558
 
881
559
  export const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
@@ -924,89 +602,4 @@ export function suffix(n: number) {
924
602
  if(j==3 && k!=13) return n+"rd";
925
603
  return n+"th";
926
604
  }
927
-
928
- /** Set `datetime-local` or `date` input from JS Date object or string, adjusting for local timezone */
929
- export function setDateTime(el: HTMLInputElement, date: Date | string | number) {
930
- if(!(date instanceof Date)) date=new Date(date);
931
- el.value = new Date(date.getTime() - date.getTimezoneOffset()*60000).
932
- toISOString().slice(0, el.type==='date'?10:19);
933
- }
934
-
935
- /** Get value of `datetime-local` or `date` input as JS Date */
936
- export const getDateTime = (el: HTMLInputElement) => new Date(el.value+(el.type==='date'?'T00:00':''));
937
-
938
- //==== Utility ====
939
-
940
- const R_ES = /\S/;
941
-
942
- /** Check if string, array, or object is empty.
943
- Returns false for all other types */
944
- export function isBlank(s: any) {
945
- if(s == null) return true;
946
- if(typeof s === 'string') return !R_ES.test(s);
947
- if(typeof s === 'object') {
948
- if(typeof s.length === 'number') return s.length === 0;
949
- return Object.keys(s).length === 0;
950
- }
951
- return false;
952
- }
953
-
954
- /** Trigger browser download of file. If `data` is a string or URL,
955
- it is treated as a URL. Otherwise, it is downloaded as Blob data */
956
- export async function download(data: string | URL | Blob | ArrayBuffer, name?: string) {
957
- const a = mkEl('a');
958
- if(typeof data === 'string' || data instanceof URL) {
959
- a.href = data.toString();
960
- a.download = name || a.href.split('/').at(-1)!;
961
- a.click();
962
- } else {
963
- if(!(data instanceof Blob)) data = new Blob([data]);
964
- const u = URL.createObjectURL(data);
965
- a.href=u, a.download=name||'file', a.click();
966
- URL.revokeObjectURL(u);
967
- }
968
- }
969
-
970
- /** setTimeout but async */
971
- export const delay = (ms: number): Promise<void> => new Promise(r => setTimeout(r,ms));
972
-
973
- //-------------------------------------------- NodeJS --------------------------------------------
974
-
975
- let os: typeof import('os');
976
- async function importNode() {
977
- if(os) return;
978
- os = await import('os');
979
- }
980
-
981
- /** Get list of system IPs */
982
- export async function getIPs() {
983
- await importNode();
984
- const ip: string[]=[], fl=os.networkInterfaces();
985
- for(let k in fl) fl[k]!.forEach(f => {
986
- if(!f.internal && f.family == 'IPv4' && f.mac != '00:00:00:00:00:00' && f.address) ip.push(f.address);
987
- });
988
- return ip;
989
- }
990
-
991
- /** Get system info
992
- @returns [sysOS, arch, cpuInfo] */
993
- export async function getOS() {
994
- await importNode();
995
- let sysOS, arch;
996
- switch(os.platform()) {
997
- case 'win32': sysOS="Windows"; break;
998
- case 'darwin': sysOS="MacOS"; break;
999
- case 'linux': sysOS="Linux"; break;
1000
- default: sysOS=os.platform();
1001
- }
1002
- switch(os.arch()) {
1003
- case 'ia32': arch="32-bit"; break;
1004
- case 'x64': arch="64-bit"; break;
1005
- case 'arm': arch="ARM"; break;
1006
- default: arch=os.arch();
1007
- }
1008
- return [sysOS, arch, os.cpus()[0]?.model||''];
1009
- }
1010
- }
1011
-
1012
- export default utils;
605
+ }
package/src/uuid.ts CHANGED
@@ -3,16 +3,15 @@
3
3
  import { Buffer } from 'buffer';
4
4
  import utils from 'raiutils';
5
5
 
6
- const [os, fs, cRand] = utils.isNode ? [
7
- await import('os'),
8
- await import('fs/promises'),
9
- (await import('util')).promisify((await import('crypto')).randomBytes)
10
- ] : [];
6
+ declare type os = typeof import('os');
7
+ declare type fs = typeof import('fs/promises');
8
+ interface Long {unsigned: boolean; toString(r: number): string}
9
+
10
+ const [os, fs, U, C] = await utils.importNode('os', 'fs/promises', 'util', 'crypto');
11
+ const cRand = utils.isNode ? U.promisify(C.randomBytes) : null;
11
12
 
12
13
  let Long: any;
13
- //@ts-expect-error
14
- try {Long = (await import('mongodb')).Long} catch(e) {}
15
- interface Long {unsigned: boolean; toString(r: number): string}
14
+ try {Long = (await utils.importNode('mongodb'))[0].Long} catch(e) {}
16
15
 
17
16
  const ID_FN = import.meta.dirname+'/uuid';
18
17
  let Cnt: number, CLT: number, LT: number,
@@ -21,7 +20,7 @@ LD: number, UT: NodeJS.Timeout | boolean;
21
20
  //64-bit UUID Format
22
21
  //<U8 Uptime><U8 Magic><U8 CryptoRand><U8 Counter><U32 Date>
23
22
 
24
- function swapHex(h: string) {return h.match(/.{2}/g)!.reverse().join('')}
23
+ const swapHex = (h: string) => h.match(/.{2}/g)!.reverse().join('');
25
24
 
26
25
  async function loadId() {
27
26
  if(!fs) return Cnt = utils.rand(0,255);
@@ -74,8 +73,8 @@ export default class UUID {
74
73
  }
75
74
 
76
75
  /** Async `crypto.randomBytes` with browser fallback */
77
- static randBytes = async (size: number) => cRand ? cRand(size)
78
- : crypto.getRandomValues(Buffer.allocUnsafe(size));
76
+ static randBytes = async (size: number) => (cRand ? cRand(size)
77
+ : crypto.getRandomValues(Buffer.allocUnsafe(size))) as Promise<Buffer>;
79
78
 
80
79
  /** Generate new random UUID
81
80
  @param date Optional Date or Unix ms timestamp; default is current time