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/dist/router.d.ts +1 -0
- package/dist/router.d.ts.map +1 -1
- package/dist/router.js +1 -1
- package/dist/router.js.map +1 -1
- package/dist/schema.js +1 -1
- package/dist/schema.js.map +1 -1
- package/dist/utils-dom.d.ts +124 -0
- package/dist/utils-dom.d.ts.map +1 -0
- package/dist/utils-dom.js +1 -0
- package/dist/utils-dom.js.map +1 -0
- package/dist/utils-node.d.ts +14 -0
- package/dist/utils-node.d.ts.map +1 -0
- package/dist/utils-node.js +1 -0
- package/dist/utils-node.js.map +1 -0
- package/dist/utils.d.ts +6 -125
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +1 -1
- package/dist/utils.js.map +1 -1
- package/dist/uuid.d.ts +2 -2
- package/dist/uuid.d.ts.map +1 -1
- package/dist/uuid.js +1 -1
- package/dist/uuid.js.map +1 -1
- package/package.json +9 -3
- package/src/router.ts +3 -2
- package/src/utils-dom.ts +399 -0
- package/src/utils-node.ts +41 -0
- package/src/utils.ts +32 -442
- package/src/uuid.ts +10 -11
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
|
-
|
|
5
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
//====
|
|
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
|
-
|
|
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
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
|
|
512
|
-
|
|
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
|
-
|
|
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(
|
|
681
|
-
if(exp
|
|
682
|
-
|
|
683
|
-
|
|
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
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|