raiutils 9.0.6 → 9.1.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/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,12 +72,10 @@ 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.1";
112
79
 
113
80
  /** Whether the environment is Node.js or Browser */
114
81
  export const isNode = IsNode;
@@ -243,7 +210,9 @@ async function eachAsync(this: any[], fn: (itm: any, idx: number, len: number) =
243
210
  this instanceof HTMLCollection?this[i].remove():this.splice(i,1); --i,--l;
244
211
  } else if(r[n]!=null) return r[n];
245
212
  }
246
- [Array, HTMLCollection, NodeList].forEach(p => {proto(p,'each',each), proto(p,'eachAsync',eachAsync)});
213
+
214
+ proto(Array, 'each', each);
215
+ proto(Array, 'eachAsync', eachAsync);
247
216
 
248
217
  //==== Numbers ====
249
218
 
@@ -422,108 +391,30 @@ if(!('at' in Array.prototype)) proto(Array, 'at', function(this: any[], idx: num
422
391
  if(i>=0 && i<l) return this[i];
423
392
  });
424
393
 
425
- //==== Other Extensions ====
394
+ //==== Utility ====
426
395
 
427
396
  proto(Function, 'wrap', function(this: any, ...args: any[]) {
428
397
  const f=this; return function() {return f.apply(arguments, args)}
429
398
  }, false, true);
430
399
 
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
- }
400
+ const R_ES = /\S/;
498
401
 
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;
402
+ /** Check if string, array, or object is empty.
403
+ Returns false for all other types */
404
+ export function isBlank(s: any) {
405
+ if(s == null) return true;
406
+ if(typeof s === 'string') return !R_ES.test(s);
407
+ if(typeof s === 'object') {
408
+ if(typeof s.length === 'number') return s.length === 0;
409
+ return Object.keys(s).length === 0;
509
410
  }
411
+ return false;
412
+ }
510
413
 
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
- }
414
+ /** setTimeout but async */
415
+ export const delay = (ms: number): Promise<void> => new Promise(r => setTimeout(r,ms));
519
416
 
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
- }
417
+ //-------------------------------------------- DOM Model --------------------------------------------
527
418
 
528
419
  export interface UserAgentInfo {
529
420
  os?: string;
@@ -568,107 +459,6 @@ export function deviceInfo(ua?: string) {
568
459
  return d;
569
460
  }
570
461
 
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
462
  //==== Cookies! (Yum) ====
673
463
 
674
464
  /** Set a cookie
@@ -677,12 +467,14 @@ export function remCSS(sel: string) {
677
467
  @param secure Only allow in HTTPS context */
678
468
  export function setCookie(key: string, val?: string, exp?: Date | number, secure?: boolean) {
679
469
  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;
470
+ if(val) {
471
+ if(exp != null) {
472
+ if(exp === -1) exp=new Date(9e14);
473
+ if(exp instanceof Date) c+=';expires='+exp.toUTCString();
474
+ else c+=';max-age='+exp;
475
+ }
476
+ if(secure) c+=';secure';
684
477
  }
685
- if(secure) c+=';secure';
686
478
  if(!IsNode) document.cookie = c;
687
479
  return c;
688
480
  }
@@ -692,7 +484,7 @@ export function setCookie(key: string, val?: string, exp?: Date | number, secure
692
484
  export function getCookie(key: string, ckStr?: string) {
693
485
  if(ckStr == null) ckStr=document.cookie;
694
486
  key=encodeURIComponent(key)+'=';
695
- let l=ckStr.split('; '), c: string;
487
+ let l=ckStr!.split('; '), c: string;
696
488
  for(c of l) if(c.startsWith(key))
697
489
  return decodeURIComponent(c.slice(key.length));
698
490
  }
@@ -759,123 +551,6 @@ export function toQuery(data: QueryMap, sep='&') {
759
551
  return q.join(sep);
760
552
  }
761
553
 
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
554
  //==== Dates ====
880
555
 
881
556
  export const months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
@@ -924,89 +599,4 @@ export function suffix(n: number) {
924
599
  if(j==3 && k!=13) return n+"rd";
925
600
  return n+"th";
926
601
  }
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;
602
+ }
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