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-dom.ts
ADDED
|
@@ -0,0 +1,399 @@
|
|
|
1
|
+
//https://github.com/Pecacheu/Utils.js; GNU GPL v3
|
|
2
|
+
|
|
3
|
+
import { utils as U } from './utils.js';
|
|
4
|
+
import type * as UT from './utils.js';
|
|
5
|
+
export type * from './utils.js';
|
|
6
|
+
|
|
7
|
+
//-------------------------------------------- Types --------------------------------------------
|
|
8
|
+
|
|
9
|
+
declare global {
|
|
10
|
+
export interface HTMLCollection {
|
|
11
|
+
each: <R>(fn: (itm: Element, idx: number, len: number) => R | "!",
|
|
12
|
+
st?: number, en?: number) => (R | undefined);
|
|
13
|
+
eachAsync: <R>(fn: (itm: Element, idx: number, len: number) => R | "!",
|
|
14
|
+
st?: number, en?: number, pe?: boolean) => Promise<R | undefined>;
|
|
15
|
+
}
|
|
16
|
+
export interface NodeList {
|
|
17
|
+
each: <R>(fn: (itm: Node, idx: number, len: number) => R | "!",
|
|
18
|
+
st?: number, en?: number) => (R | undefined);
|
|
19
|
+
eachAsync: <R>(fn: (itm: Node, idx: number, len: number) => R | "!",
|
|
20
|
+
st?: number, en?: number, pe?: boolean) => Promise<R | undefined>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface TouchList {
|
|
24
|
+
/** Get touch by id, if it exists */
|
|
25
|
+
get(id: number): Touch | undefined;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface Element {
|
|
29
|
+
/** Get an element's index in its parent. Returns -1 if the element has no parent */
|
|
30
|
+
index: number;
|
|
31
|
+
/** Insert child at index */
|
|
32
|
+
insertChildAt(el: Element, i: number): void;
|
|
33
|
+
/** Get element bounding rect as UtilRect object */
|
|
34
|
+
boundingRect: ext.UtilRect;
|
|
35
|
+
/** Get element inner rect (excluding border and padding) as UtilRect object */
|
|
36
|
+
innerRect: ext.UtilRect;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface NumField extends HTMLInputElement {
|
|
41
|
+
num: number;
|
|
42
|
+
ns: string | null;
|
|
43
|
+
set: (num: number | string) => void;
|
|
44
|
+
setRange: (min?: number, max?: number, decMax?: number) => void;
|
|
45
|
+
onnuminput?: (this: GlobalEventHandlers, ev?: Event) => any;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export interface TextArea extends HTMLTextAreaElement {
|
|
49
|
+
set: (val: string) => void;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
//-------------------------------------------- Extensions --------------------------------------------
|
|
53
|
+
|
|
54
|
+
namespace ext {
|
|
55
|
+
if(window.TouchList) U.proto(TouchList, 'get', function(this: any, id: number) {
|
|
56
|
+
for(const t of this) if(t.identifier === id) return t;
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
U.define(Element.prototype, 'index', function(this: any) {
|
|
60
|
+
const p=this.parentElement; if(!p) return -1;
|
|
61
|
+
return Array.prototype.indexOf.call(p.children, this);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
U.proto(Element, 'insertChildAt', function(this: any, el: Element, i: number) {
|
|
66
|
+
if(i<0) i=0; if(i >= this.children.length) this.appendChild(el);
|
|
67
|
+
else this.insertBefore(el, this.children[i]);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
/** Get element bounding rect as UtilRect object */
|
|
71
|
+
export const boundingRect = (e: Element) => new UtilRect(e.getBoundingClientRect());
|
|
72
|
+
|
|
73
|
+
/** Get element inner rect (excluding border and padding) as UtilRect object */
|
|
74
|
+
export function innerRect(e: Element) {
|
|
75
|
+
let r=e.getBoundingClientRect(), s=getComputedStyle(e);
|
|
76
|
+
return new UtilRect(r.top+parseFloat(s.paddingTop)+parseFloat(s.borderTopWidth),
|
|
77
|
+
r.bottom-parseFloat(s.paddingBottom)-parseFloat(s.borderBottomWidth),
|
|
78
|
+
r.left+parseFloat(s.paddingLeft)+parseFloat(s.borderLeftWidth),
|
|
79
|
+
r.right-parseFloat(s.paddingRight)-parseFloat(s.borderRightWidth));
|
|
80
|
+
};
|
|
81
|
+
|
|
82
|
+
U.define(Element.prototype, 'boundingRect', function(this: any) {return boundingRect(this)});
|
|
83
|
+
U.define(Element.prototype, 'innerRect', function(this: any) {return innerRect(this)});
|
|
84
|
+
|
|
85
|
+
export const device = U.deviceInfo(), mobile = device.mobile;
|
|
86
|
+
|
|
87
|
+
//-------------------------------------------- DOM Model --------------------------------------------
|
|
88
|
+
|
|
89
|
+
//==== General ====
|
|
90
|
+
|
|
91
|
+
/** Better class for bounding boxes */
|
|
92
|
+
export class UtilRect {
|
|
93
|
+
x!: number; left!: number;
|
|
94
|
+
y!: number; top!: number;
|
|
95
|
+
x2!: number; right!: number;
|
|
96
|
+
y2!: number; bottom!: number;
|
|
97
|
+
w!: number; width!: number;
|
|
98
|
+
h!: number; height!: number;
|
|
99
|
+
centerX!: number; centerY!: number;
|
|
100
|
+
|
|
101
|
+
constructor(t: number | DOMRect | UtilRect, b?: number, l?: number, r?: number) {
|
|
102
|
+
const f=Number.isFinite; let tt=0,bb=0,ll=0,rr=0;
|
|
103
|
+
U.define(this,'x', ()=>ll, v=>{f(v)?(rr+=v-ll,ll=v):0});
|
|
104
|
+
U.define(this,'y', ()=>tt, v=>{f(v)?(bb+=v-tt,tt=v):0});
|
|
105
|
+
U.define(this,'top', ()=>tt, v=>{tt=f(v)?v:0});
|
|
106
|
+
U.define(this,['bottom','y2'],()=>bb, v=>{bb=f(v)?v:0});
|
|
107
|
+
U.define(this,'left', ()=>ll, v=>{ll=f(v)?v:0});
|
|
108
|
+
U.define(this,['right','x2'], ()=>rr, v=>{rr=f(v)?v:0});
|
|
109
|
+
U.define(this,['width','w'], ()=>rr-ll, v=>{rr=v>=0?ll+v:0});
|
|
110
|
+
U.define(this,['height','h'], ()=>bb-tt, v=>{bb=v>=0?tt+v:0});
|
|
111
|
+
U.define(this,'centerX', ()=>ll/2+rr/2);
|
|
112
|
+
U.define(this,'centerY', ()=>tt/2+bb/2);
|
|
113
|
+
if(t instanceof DOMRect || t instanceof UtilRect)
|
|
114
|
+
tt=t.top, bb=t.bottom, ll=t.left, rr=t.right;
|
|
115
|
+
else tt=t, bb=b!, ll=l!, rr=r!;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** Check if rect contains point, other rect, or Element */
|
|
119
|
+
contains(x: number | UtilRect | Element, y?: number): boolean {
|
|
120
|
+
if(x instanceof Element) return this.contains(x.boundingRect);
|
|
121
|
+
if(x instanceof UtilRect) return x.x >= this.x && x.x2 <= this.x2 && x.y >= this.y && x.y2 <= this.y2;
|
|
122
|
+
return x >= this.x && x <= this.x2 && y! >= this.y && y! <= this.y2;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/** Check if rect overlaps rect or Element */
|
|
126
|
+
overlaps(r: UtilRect | Element): boolean {
|
|
127
|
+
if(r instanceof Element) return this.overlaps(r.boundingRect);
|
|
128
|
+
if(!(r instanceof UtilRect)) return false;
|
|
129
|
+
let x: any, y: any;
|
|
130
|
+
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;
|
|
131
|
+
else x = r.x >= this.x && r.x <= this.x2 || r.x2 >= this.x && r.x2 <= this.x2;
|
|
132
|
+
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;
|
|
133
|
+
else y = r.y >= this.y && r.y <= this.y2 || r.y2 >= this.y && r.y2 <= this.y2;
|
|
134
|
+
return x&&y;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/** Get distance from this rect to point, other rect, or Element */
|
|
138
|
+
dist(x: number | UtilRect | Element, y?: number): number {
|
|
139
|
+
if(x instanceof Element) return this.dist(x.boundingRect);
|
|
140
|
+
const n = x instanceof UtilRect;
|
|
141
|
+
y = Math.abs((n?(x as UtilRect).centerY:y as number)-this.centerY),
|
|
142
|
+
x = Math.abs((n?(x as UtilRect).centerX:x as number)-this.centerX);
|
|
143
|
+
return Math.sqrt(x*x+y*y);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Expand (or contract if negative) a UtilRect by num of pixels. Useful for using UtilRect objects as element hitboxes
|
|
147
|
+
@returns self for chaining */
|
|
148
|
+
expand(by: number) {
|
|
149
|
+
this.top -= by, this.left -= by, this.bottom += by, this.right += by;
|
|
150
|
+
return this;
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const R_CTR = /translate(?:x|y)?\(.+?\)/gi;
|
|
155
|
+
|
|
156
|
+
/** Center element with JS
|
|
157
|
+
|
|
158
|
+
Modes:
|
|
159
|
+
- `trans`: Uses transform: translate. Responsive, no container
|
|
160
|
+
- Default: New flexbox method
|
|
161
|
+
@param only `x` for only x axis centering, `y` for only y axis, null for both */
|
|
162
|
+
export function center(el: HTMLElement, only?: "x" | "y", mode?: "trans") {
|
|
163
|
+
const os = el.style;
|
|
164
|
+
if(mode == 'trans') {
|
|
165
|
+
if(!os.position) os.position='absolute';
|
|
166
|
+
let tr = os.transform.replace(R_CTR,'').trim();
|
|
167
|
+
if(!only || only === 'x') os.left='50%', tr+=' translateX(-50%)';
|
|
168
|
+
if(!only || only === 'y') os.top='50%', tr+=' translateY(-50%)';
|
|
169
|
+
os.transform=tr;
|
|
170
|
+
} else {
|
|
171
|
+
const cont = mkDiv(el.parentNode, null, {display:'flex', top:0, left:0}), cs = cont.style;
|
|
172
|
+
cont.appendChild(el);
|
|
173
|
+
if(!only || only === 'x') cs.justifyContent='center', cs.width='100%';
|
|
174
|
+
if(!only || only === 'y') cs.alignItems='center',
|
|
175
|
+
cs.height='100%', cs.position='absolute';
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
//==== Arrays ====
|
|
180
|
+
|
|
181
|
+
[HTMLCollection, NodeList].forEach(p => {
|
|
182
|
+
U.proto(p, 'each', Array.prototype.each);
|
|
183
|
+
U.proto(p, 'eachAsync', Array.prototype.eachAsync);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
//==== Navigation ====
|
|
187
|
+
|
|
188
|
+
/** Called when a virtual navigation event occurs, including on page load */
|
|
189
|
+
export let onNav: (state: any) => void;
|
|
190
|
+
|
|
191
|
+
/** Generate a virtual navigation event, updating the URL bar
|
|
192
|
+
@param state Optional data given to `onNav` whenever the user returns to this history entry */
|
|
193
|
+
export function go(url: string | URL, state?: any) {
|
|
194
|
+
history.pushState(state, '', url), doNav(state);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
addEventListener('popstate', e => doNav(e.state));
|
|
198
|
+
addEventListener('load', () => setTimeout(() => doNav(history.state),1));
|
|
199
|
+
function doNav(s: any) {if(onNav) onNav.call(null,s)}
|
|
200
|
+
|
|
201
|
+
//==== DOM Creation ====
|
|
202
|
+
|
|
203
|
+
/** Create elements with ease! Just remember **PCSI** - Parent, class, style, innerHTML */
|
|
204
|
+
export function mkEl<K extends keyof HTMLElementTagNameMap>(tag: K, parent?: Node | null, cls?: string | null,
|
|
205
|
+
style?: CSSStyleDeclaration | UT.AnyMap | null, inner?: string | null): HTMLElementTagNameMap[K] {
|
|
206
|
+
const e = document.createElement(tag);
|
|
207
|
+
if(cls != null) e.className = cls;
|
|
208
|
+
if(inner != null) e.innerHTML = inner;
|
|
209
|
+
if(style && typeof style === 'object') for(const k in style) {
|
|
210
|
+
if(k in e.style) (e.style as any)[k] = (style as any)[k];
|
|
211
|
+
else e.style.setProperty(k, (style as any)[k]);
|
|
212
|
+
}
|
|
213
|
+
if(parent != null) parent.appendChild(e);
|
|
214
|
+
return e;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
/** Shorthand for `mkEl` with div tag */
|
|
218
|
+
export const mkDiv = (parent?: Node | null, cls?: string | null, style?: CSSStyleDeclaration | UT.AnyMap | null,
|
|
219
|
+
inner?: string | null) => mkEl('div', parent, cls, style, inner);
|
|
220
|
+
|
|
221
|
+
/** Add text node to the DOM */
|
|
222
|
+
export const addText = (parent: Node, text: string) => parent.appendChild(document.createTextNode(text));
|
|
223
|
+
|
|
224
|
+
//==== CSS ====
|
|
225
|
+
|
|
226
|
+
let TWCanvas: HTMLCanvasElement;
|
|
227
|
+
|
|
228
|
+
/** Get predicted width of text w/ given CSS font style */
|
|
229
|
+
export function textWidth(txt: string, font: string) {
|
|
230
|
+
const c = TWCanvas||(TWCanvas=mkEl('canvas')), ctx = c.getContext('2d')!;
|
|
231
|
+
ctx.font = font; return ctx.measureText(txt).width;
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const R_SK = /[A-Z]/g, R_SR=(s: string) => '-'+s.toLowerCase();
|
|
235
|
+
function defSty() {
|
|
236
|
+
for(const s of document.styleSheets as any) try {s.cssRules; return s} catch(e) {}
|
|
237
|
+
//let ns=mkEl('style',document.head); addText(ns,''); return ns.sheet!;
|
|
238
|
+
return mkEl('style', document.head).sheet;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/** Create a CSS rule and append it to the current document
|
|
242
|
+
@param sel CSS selector, eg. `.class` or `#id` */
|
|
243
|
+
export function addCSS(sel: string, style: CSSStyleDeclaration | UT.AnyMap, sheet?: CSSStyleSheet) {
|
|
244
|
+
if(!sheet) sheet=defSty(); let k,s=[];
|
|
245
|
+
for(k in style) s.push(`${k.replace(R_SK, R_SR)}:${(style as UT.AnyMap)[k]}`);
|
|
246
|
+
sheet!.insertRule(`${sel}{${s.join(';')}}`);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/** Remove a CSS selector from **all** stylesheets */
|
|
250
|
+
export function remCSS(sel: string) {
|
|
251
|
+
let s,rl;
|
|
252
|
+
for(s of document.styleSheets as any) {
|
|
253
|
+
try {rl=s.cssRules} catch(e) {continue}
|
|
254
|
+
for(let i=0,l=rl.length; i<l; ++i) if(rl[i] instanceof CSSStyleRule
|
|
255
|
+
&& rl[i].selectorText===sel) s.deleteRule(i);
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
//==== Inputs ====
|
|
260
|
+
|
|
261
|
+
const R_NFZ=/\.0*$/;
|
|
262
|
+
|
|
263
|
+
/** Turns your boring <input> into a mobile-friendly number entry field with max/min & negative support!
|
|
264
|
+
|
|
265
|
+
Tips:
|
|
266
|
+
- Use `field.onnuminput` in place of oninput, get number value with field.num
|
|
267
|
+
- On mobile, use star key for decimal point and pound key for negative
|
|
268
|
+
- You can set `field.nStep` in order to change the up/down arrow step size
|
|
269
|
+
- Use `field.setRange` to change min, max, and decMax
|
|
270
|
+
|
|
271
|
+
@param min Min value, default min safe int
|
|
272
|
+
@param max Max value, default max safe int
|
|
273
|
+
@param decMax Max decimal precision (eg. 3 is 0.001), default 0
|
|
274
|
+
@param sym If a symbol (eg. '$') is given, uses currency mode */
|
|
275
|
+
export function numField(field: HTMLInputElement, min?: number, max?: number, decMax?: number, sym?: string) {
|
|
276
|
+
const f = field as NumField, RM = RegExp(`[,${sym?RegExp.escape(sym):''}]`, 'g');
|
|
277
|
+
f.type = (mobile||decMax||sym)?'tel':'number';
|
|
278
|
+
f.setAttribute('pattern', "\\d*");
|
|
279
|
+
//@ts-expect-error
|
|
280
|
+
if(!f.step) f.step = 1;
|
|
281
|
+
f.addEventListener('keydown', e => {
|
|
282
|
+
if(e.ctrlKey) return;
|
|
283
|
+
let k=e.key, kn=k.length===1&&Number.isFinite(Number(k)),
|
|
284
|
+
ns=f.ns, len=ns!.length, dec=ns!.indexOf('.');
|
|
285
|
+
|
|
286
|
+
if(k==='Tab' || k==='Enter') return;
|
|
287
|
+
else if(kn) {if(dec===-1 || len-dec < decMax!+1) ns+=k} //Number
|
|
288
|
+
else if(k==='.' || k==='*') {if(decMax && dec==-1
|
|
289
|
+
&& f.num!=max && (min!>=0 || f.num!=min)) { //Decimal
|
|
290
|
+
if(!len && min!>0) ns=Math.floor(min!)+'.';
|
|
291
|
+
else ns+='.';
|
|
292
|
+
}} else if(k==='Backspace' || k==='Delete') { //Backspace
|
|
293
|
+
if(min!>0 && f.num===min && ns!.endsWith('.')) ns='';
|
|
294
|
+
else ns=ns!.slice(0,-1);
|
|
295
|
+
} else if(k==='-' || k==='#') {if(min!<0 && !len) ns='-'} //Negative
|
|
296
|
+
else if(k==='ArrowUp') ns=null, f.set(f.num+Number(f.step)); //Up
|
|
297
|
+
else if(k==='ArrowDown') ns=null, f.set(f.num-Number(f.step)); //Down
|
|
298
|
+
|
|
299
|
+
if(ns !== null && ns !== f.ns) {
|
|
300
|
+
len=ns.length, dec=ns.indexOf('.');
|
|
301
|
+
let neg=ns==='-'||ns==='-.', s=neg?'0':ns+(ns.endsWith('.')?'0':''),
|
|
302
|
+
nr=Number(s), n=U.bounds(nr, min, max);
|
|
303
|
+
if(!kn || !ns || f.num !== n || (dec!==-1 && len-dec < decMax!+1)) {
|
|
304
|
+
f.ns=ns, f.num=n;
|
|
305
|
+
f.value = sym ? neg?sym+'-0.00':U.formatCost(n,sym):
|
|
306
|
+
(ns[0]==='-'?'-':'')+Math.floor(Math.abs(n))
|
|
307
|
+
+(dec!==-1?ns.slice(dec)+(R_NFZ.test(ns)?'0':''):'');
|
|
308
|
+
if(f.onnuminput) f.onnuminput.call(f,e);
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
e.preventDefault();
|
|
312
|
+
});
|
|
313
|
+
function numRng(n: any) {
|
|
314
|
+
if(typeof n==='string') n=n.replace(RM,'');
|
|
315
|
+
n=U.bounds(Number(n)||0, min, max);
|
|
316
|
+
return decMax?Number(n.toFixed(decMax)):Math.round(n);
|
|
317
|
+
}
|
|
318
|
+
f.set=n => {
|
|
319
|
+
f.num = numRng(n);
|
|
320
|
+
f.ns = f.num.toString();
|
|
321
|
+
f.value = sym?U.formatCost(f.num,sym):f.ns;
|
|
322
|
+
f.ns=f.ns.replace(/^(-?)0+/,'$1');
|
|
323
|
+
if(f.onnuminput) f.onnuminput.call(f);
|
|
324
|
+
}
|
|
325
|
+
f.setRange=(nMin, nMax, nDecMax) => {
|
|
326
|
+
min=nMin==null ? Number.MIN_SAFE_INTEGER : nMin;
|
|
327
|
+
max=nMax==null ? Number.MAX_SAFE_INTEGER : nMax;
|
|
328
|
+
decMax=nDecMax==null ? sym?2:0 : nDecMax;
|
|
329
|
+
if(numRng(f.num) !== f.num) f.set(f.num);
|
|
330
|
+
}
|
|
331
|
+
f.addEventListener('input', () => f.set(f.value));
|
|
332
|
+
f.addEventListener('paste', e => {f.set(e.clipboardData!.getData('text')); e.preventDefault()});
|
|
333
|
+
f.setRange(min, max, decMax);
|
|
334
|
+
return f;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
//By Rick Kukiela @ StackOverflow
|
|
338
|
+
/** Auto-resizing textarea, dynamically scales lineHeight based on input.
|
|
339
|
+
Use `el.set(value)` to set value & update size */
|
|
340
|
+
export function autosize(el: HTMLTextAreaElement, maxRows=5, minRows=1) {
|
|
341
|
+
const e = el as TextArea;
|
|
342
|
+
e.set = v => {e.value=v,cb()};
|
|
343
|
+
let s=e.style;
|
|
344
|
+
s.maxHeight=s.resize='none', s.minHeight='0', s.height='auto';
|
|
345
|
+
e.setAttribute('rows', minRows as any);
|
|
346
|
+
function cb() {
|
|
347
|
+
if(e.scrollHeight===0) return setTimeout(cb,1); //Still loading
|
|
348
|
+
e.setAttribute('rows', 1 as any);
|
|
349
|
+
//Override style
|
|
350
|
+
let cs=getComputedStyle(e);
|
|
351
|
+
s.setProperty('overflow', 'hidden', 'important');
|
|
352
|
+
s.width=e.innerRect.w+'px', s.boxSizing='content-box', s.borderWidth=s.paddingInline='0';
|
|
353
|
+
//Calc scroll height
|
|
354
|
+
let pad=parseFloat(cs.paddingTop) + parseFloat(cs.paddingBottom),
|
|
355
|
+
lh=cs.lineHeight==='normal' ? parseFloat(cs.height) : parseFloat(cs.lineHeight),
|
|
356
|
+
rows=Math.round((Math.round(e.scrollHeight) - pad)/lh);
|
|
357
|
+
//Undo overrides & apply
|
|
358
|
+
s.overflow=s.width=s.boxSizing=s.borderWidth=s.paddingInline='';
|
|
359
|
+
e.setAttribute('rows', U.bounds(rows, minRows, maxRows) as any);
|
|
360
|
+
}
|
|
361
|
+
e.addEventListener('input', cb);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
//==== Dates ====
|
|
365
|
+
|
|
366
|
+
/** Set `datetime-local` or `date` input from JS Date object or string, adjusting for local timezone */
|
|
367
|
+
export function setDateTime(el: HTMLInputElement, date: Date | string | number) {
|
|
368
|
+
if(!(date instanceof Date)) date=new Date(date);
|
|
369
|
+
el.value = new Date(date.getTime() - date.getTimezoneOffset()*60000).
|
|
370
|
+
toISOString().slice(0, el.type==='date'?10:19);
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
/** Get value of `datetime-local` or `date` input as JS Date */
|
|
374
|
+
export const getDateTime = (el: HTMLInputElement) => new Date(el.value+(el.type==='date'?'T00:00':''));
|
|
375
|
+
|
|
376
|
+
//==== Utility ====
|
|
377
|
+
|
|
378
|
+
/** Trigger browser download of file. If `data` is a string or URL,
|
|
379
|
+
it is treated as a URL. Otherwise, it is downloaded as Blob data */
|
|
380
|
+
export async function download(data: string | URL | Blob | ArrayBuffer, name?: string) {
|
|
381
|
+
const a = mkEl('a');
|
|
382
|
+
if(typeof data === 'string' || data instanceof URL) {
|
|
383
|
+
a.href = data.toString();
|
|
384
|
+
a.download = name || a.href.split('/').at(-1)!;
|
|
385
|
+
a.click();
|
|
386
|
+
} else {
|
|
387
|
+
if(!(data instanceof Blob)) data = new Blob([data]);
|
|
388
|
+
const u = URL.createObjectURL(data);
|
|
389
|
+
a.href=u, a.download=name||'file', a.click();
|
|
390
|
+
URL.revokeObjectURL(u);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
/** Import modules only in Node.js, otherwise return empty list */
|
|
395
|
+
export const importNode = async (..._: string[]) => [] as any[];
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
export const utils = <typeof U & typeof ext>U;
|
|
399
|
+
export default utils;
|
|
@@ -0,0 +1,41 @@
|
|
|
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
|
+
/** Import modules only in Node.js, otherwise return empty list */
|
|
9
|
+
export const importNode = async (...mods: string[]) => Promise.all(mods.map(i => import(i)));
|
|
10
|
+
|
|
11
|
+
/** Get list of system IPs */
|
|
12
|
+
export function getIPs() {
|
|
13
|
+
const ip: string[]=[], fl=os.networkInterfaces();
|
|
14
|
+
for(let k in fl) fl[k]!.forEach(f => {
|
|
15
|
+
if(!f.internal && f.family == 'IPv4' && f.mac != '00:00:00:00:00:00' && f.address) ip.push(f.address);
|
|
16
|
+
});
|
|
17
|
+
return ip;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/** Get system info
|
|
21
|
+
@returns [sysOS, arch, cpuInfo] */
|
|
22
|
+
export function getOS() {
|
|
23
|
+
let sysOS, arch;
|
|
24
|
+
switch(os.platform()) {
|
|
25
|
+
case 'win32': sysOS="Windows"; break;
|
|
26
|
+
case 'darwin': sysOS="MacOS"; break;
|
|
27
|
+
case 'linux': sysOS="Linux"; break;
|
|
28
|
+
default: sysOS=os.platform();
|
|
29
|
+
}
|
|
30
|
+
switch(os.arch()) {
|
|
31
|
+
case 'ia32': arch="32-bit"; break;
|
|
32
|
+
case 'x64': arch="64-bit"; break;
|
|
33
|
+
case 'arm': arch="ARM"; break;
|
|
34
|
+
default: arch=os.arch();
|
|
35
|
+
}
|
|
36
|
+
return [sysOS, arch, os.cpus()[0]?.model||''];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export const utils = <typeof U & typeof ext>U;
|
|
41
|
+
export default utils;
|