raiutils 8.6.6

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/LICENSE.md ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Ray/Pecacheu
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # Utils.js
2
+ ###### If you, like many developers, prefer native JavaScript to jQuery, but can't live without those one or two essential features, then you need Utils.js!
3
+
4
+ Note: For production code, the minified version *utils.min.js* should be used. There's also *utilsES5.js*. It's been converted to the older ES5 standard, so should be more compatible with older browsers. Expect IE. Literally no one cares about IE :(
5
+
6
+ *More detailed usage & help available in utils.js!*
7
+
8
+ #### The most commonly functions in this library are:
9
+
10
+ *utils.mkEl(t,p,c,s,i) utils.mkDiv(p,c,s,i)* To use these quickly and efficiently, just remember PCSI: Parent, class, style, innerHTML!
11
+
12
+ *utils.center(obj[,only[,type]])* Does what it says on the tin! Input an Element, choose whether you want X, Y, or by default, both, and change the centering type.
13
+
14
+ *UtilRect* Getting the bounds/position of an element used to be a complete mess with incompatibilities across every browser. Not anymore! Use UtilRects to keep track of your object positions! Simply access the *boundingRect* property of any Element and you can access it's *top*, *bottom*, *left*, *right*, *width*, and *height* on the client's screen! If you need this relative to the top of the page, it's as simple as `Element.boundingRect.top + window.scrollY`
15
+
16
+ *[Function].wrap* This can be quite useful in combination with functions like *setTimeout*, for example `setTimeout(console.log.wrap("Hello!"), 50)`. Also sets the *this* object inside the function to *arguments*, in case you want to access the calling arguments as well!
17
+
18
+ ### Custom Classes
19
+ - `UtilRect` Better class for working with element bounds.
20
+ - `Easing` Easing functions for use with *utils.map*.
21
+
22
+ ### Prototype Extensions
23
+ - `[Function].wrap(...) returns Function` Wrap a function so that it always has a preset argument list when called.
24
+ - `String.trim() polyfill`
25
+ - `[String].startsWith(s) returns Boolean` Like Java.
26
+ - `[String].endsWith(s) returns Boolean` Like Java.
27
+ - `[Array].clean(kz)` Remove 'empty' elements like 0, false, ' ', undefined, and NaN from an array. Set 'kz' to true to keep '0's
28
+ - `[Array].remove(item) returns Boolean` Remove first instance of item from array. Returns false if not found.
29
+ - `[Array].each(fn[,start[,end]]) returns Any` Calls fn on each index of array, with optional start and end index.
30
+ - `[Array].eachAsync(fn[,start[,end[,pe=true]]]) returns Any` Like `each` but async.
31
+ - `Number [Element].index` Represents an element's index in it's parent. Set to -1 if the element has no parent.
32
+ - `[Element].insertChildAt(el,i)` Inserts child at index. Appends child to end if index exceeds child count.
33
+ - `UtilRect [Element].boundingRect` Element's bounding rect as a `UtilRect` object.
34
+ - `UtilRect [Element].innerRect` Element's inner rect (excluding margin, padding, and border)
35
+ - `Math.cot(x) returns Number` No idea why this isn't built-in, but it's not.
36
+ - `[TouchList].get(id) returns Touch` Gets touch by id, returns null if none found.
37
+ - `[Uint8Array].toBase64([opts]) returns String` Polyfill; See MDN docs.
38
+ - `Uint8Array.fromBase64(str[, opts]) returns Uint8Array` Polyfill; See MDN docs.
39
+ - `RegExp.escape(string) return String` Polyfill; See MDN docs.
40
+
41
+ ### Main *utils* Class
42
+ - `String utils.VER` Current library version.
43
+ - `Number utils.w` and `Number utils.h` Cross-platform window width and height.
44
+ - `Boolean utils.mobile` Will be **true** if running on a mobile device, based on the UserAgent.
45
+ - `utils.setCookie(name,value,exp,secure)` Set a cookie.
46
+ - `utils.getCookie(name) returns String or null` Get a cookie by name.
47
+ - `utils.remCookie(name)` Remove a cookie by name.
48
+ - `utils.setPropSafe(obj, path, val, onlyNull=false)` Set a nested property, even if higher levels don't exist. Useful for defining settings in a complex config object.
49
+ - `utils.getPropSafe(obj, path) returns Object` Gets a nested property, returns undefined if any level doesn't exist.
50
+ - `utils.copy(o[,sub]) returns Object` Deep (recursive) Object.create. Copies down to given sub levels, all levels if undefined.
51
+ - `utils.skinnedInput(el)` Fallback for when css *'appearance:none'* doesn't work. Generates container for input field for css skinning on unsupported browsers.
52
+ - `utils.numField(field[,min[,max[,decMax[,sym]]]])` Turns your boring input field into a mobile-friendly integer, decimal, or financial entry field with max/min & negative support!
53
+ - `utils.formatCost(n[,sym]) returns String` Format Number as currency. Uses '$' by default.
54
+ - `utils.fromDateTimeBox(el) returns Date` Convert value from 'datetime-local' input to Date object.
55
+ - `utils.toDateTimeBox(d[,sec]) returns String` Convert Date object into format to set 'datetime-local' optionally including seconds if 'sec' is **true**.
56
+ - `utils.formatDate(d[,opts]) returns String` Format Date object into a pretty string, with various options.
57
+ - `utils.months` Array of months from Jan to Dec.
58
+ - `utils.suffix(n) returns String` Add appropriate suffix to number. (ex. 31st, 12th, 22nd)
59
+ - `utils.fixedNum(n,len[,radix=10]) returns String` Fix number to a given minimum length with padded 0's. Adds '0b' for binary *(radix=2)* and '0x' for hex *(radix=16)*
60
+ - `utils.goBack()` For AJAX navigation. Presses back button.
61
+ - `utils.goForward()` For AJAX navigation. Presses forward button.
62
+ - `utils.go(url, data)` Push new history state for AJAX navigation. You can provide additional data to access later.
63
+ - `utils.onNav = function(data)` Callback called when the browser wants to navigate to a page. Provides the optional data argument supplied to `utils.go`. Also called when the page is first loaded, directly after `window.onload`.
64
+ - `utils.mkEl(tag,parent,class,styles,innerHTML) returns Element` Quickly create element with parent, classes, style properties (as key/value pairs), and innerHTML content. All parameters (except for tag) are optional.
65
+ - `utils.mkDiv` Same as utils.mkEl, but assumes 'div' for tag.
66
+ - `utils.addText(el,text)` Appends a TextNode with given text to element.
67
+ - `utils.textWidth(text,font) returns Number` Get predicted width of text given css font style.
68
+ - `utils.define(obj,name,get,set)` Add getter/setter pair to an existing object. Get or set may be null to disable.
69
+ - `utils.proto(obj,name,val[,static])` Define immutable, non-enumerable property or method in an object prototype (or object if 'static' is **true**).
70
+ - `utils.isBlank(o) returns Boolean` Check if string, array, or other object is empty.
71
+ - `utils.firstEmpty(arr) returns Number` Finds first empty (undefined/null) slot in array.
72
+ - `utils.firstEmptyChar(obj) returns String` Like *firstEmpty*, but uses letters from `utils.numToChar` instead.
73
+ - `utils.numToChar(n) returns String` Converts a number into letters (upper and lower) from a to Z.
74
+ - `utils.merge(target,source[,source2...]) returns target` Merges two (or more) objects, giving the last precedence. If both objects contain a property at the same index, and both are Arrays/Objects, they are merged.
75
+ - `utils.bounds(n,min=0,max=1) returns Number` Keeps value within max/min bounds. Also handles NaN or null.
76
+ - `utils.norm` *OR* `utils.normalize(n,min=0,max=1) returns Number` 'Normalizes' a value so that it ranges from min to max, but unlike `utils.bounds`, this function retains input's offset. This can be used to normalize angles.
77
+ - `utils.cutStr(s,rem) returns String` Finds and removes all instances of 'rem' contained within String 's'
78
+ - `utils.dCut(data,startString,endString[,index[,searchStart]]) returns String` Cuts text out of 'data' from first instance of 'startString' to next instance of 'endString'.
79
+ - `utils.dCutToLast(data,startString,endString[,index[,searchStart]]) returns String` Same as *utils.dCut* but using lastIndexOf for end search.
80
+ - `utils.dCutLast(data,startString,endString[,index[,searchStart]]) returns String` Same as *utils.dCut* but using lastIndexOf for start search.
81
+ - `utils.parseCSS(prop) returns Object` Given css property value 'prop', returns object with space-separated values from the property string.
82
+ - `utils.buildCSS(obj) returns String` Rebuilds css string from a *parseCSS* object.
83
+ - `utils.addClass(class,propList)` Create a css class and append it to the current document. Fill 'propList' object with key/value pairs repersenting the properties you want to add to the class.
84
+ - `utils.addId(id,propList)` Create a css selector and append it to the current document.
85
+ - `utils.addKeyframe(name,content)` Create a css keyframe and append it to the current document.
86
+ - `utils.removeSelector(name)` Remove a specific css selector (including the '.' or '#') from all stylesheets in the current document.
87
+ - `utils.getId(id[,parent]) returns Element` Shorthand way to get element by id. Parent defaults to document.
88
+ - `utils.getClassList(class[,parent]) returns Array` Shorthand way to get list of elements with class name. Parent defaults to document.
89
+ - `utils.getClassFirst(class[,parent]) returns Element` Get first element with class, otherwise return null. Parent defaults to document.
90
+ - `utils.getClassOnly(class[,parent]) returns Element` Get element with class if there's only one, otherwise return null. Parent defaults to document.
91
+ - `utils.hexToRgb(hex) returns Number` Converts HEX color to 24-bit RGB.
92
+ - `utils.rand(min,max[,res[,ease]]) returns Number` Generates random from min to max, optionally with a decimal resolution (10, 100, 1000, etc.) or custom ease, changing the probability of various numbers being generated.
93
+ - `utils.fromQuery(str) returns Object` Parses a url query string into an Object.
94
+ - `utils.toQuery(obj) returns String` Converts an object into a url query string.
95
+ - `utils.center(obj[,only[,type]])` Center objects with JavaScript, using a variety of methods. *See utils.js for details.*
96
+ - `utils.loadAjax(path,[callback[, meth[, body[, hdList]]]])` Loads a file and returns it's contents, using GET by default. *See utils.js for details.*
97
+ - `utils.loadJSONP(path,callback,timeout)` Loads a file at the address from a JSONP-enabled server. Callback is fired with either received data, or **false** if unsuccessful.
98
+ - `utils.loadFile(path,callback,timeout)` Good fallback for `utils.loadAjax`. Loads a file at the address via HTML object tag. Callback is fired with either received data, or **false** if unsuccessful.
99
+ - `utils.dlFile(filename,uri) returns Promise` Downloads a file from a link.
100
+ - `utils.dlData(filename,data)` Downloads a file generated from a Blob or ArrayBuffer.
101
+ - `utils.rad(deg)` / `utils.deg(rad)` Convert between radians and degrees.
102
+ - `utils.map(input,minIn,maxIn,minOut,maxOut,ease) returns Number` For unit translation and JS animation! See ease functions in *untils.js*.
103
+ - `utils.delay(ms) returns Promise` setTimeout but async.
package/package.json ADDED
@@ -0,0 +1,18 @@
1
+ {
2
+ "name": "raiutils",
3
+ "version": "8.6.6",
4
+ "type": "module",
5
+ "description": "The ultimate JavaScript companion library. You'll never need jQuery again!",
6
+ "repository": "https://github.com/Pecacheu/Utils.js",
7
+ "homepage": "https://github.com/Pecacheu/Utils.js",
8
+ "author": "Pecacheu",
9
+ "license": "MIT",
10
+ "keywords": [
11
+ "js",
12
+ "jquery",
13
+ "utilities",
14
+ "utils",
15
+ "utils.js",
16
+ "DOM"
17
+ ]
18
+ }
package/utils.js ADDED
@@ -0,0 +1,868 @@
1
+ //https://github.com/Pecacheu/Utils.js; MIT License
2
+
3
+ 'use strict';
4
+ const utils = {VER:'v8.6.6'};
5
+
6
+ //Node.js compat
7
+ let UtilRect, P=(typeof window=='undefined')?
8
+ [{}, {back:()=>{},forward:()=>{}}, class{}, class{}, class{}, class{}, ()=>{}]:
9
+ [window, history, DOMRect, HTMLCollection, Element, NodeList, addEventListener];
10
+
11
+ (() => { //Utils Library
12
+
13
+ const [window, history, DOMRect, HTMLCollection,
14
+ Element, NodeList, addEventListener] = P;
15
+
16
+ //Add getter/setter to object
17
+ utils.define = (obj, name, get, set) => {
18
+ const t={}; if(get) t.get=get; if(set) t.set=set;
19
+ if(Array.isArray(name)) for(let n of name) Object.defineProperty(obj,n,t);
20
+ else Object.defineProperty(obj,name,t);
21
+ }
22
+ //Define prop in object prototype
23
+ utils.proto = (obj, name, val, st) => {
24
+ const t={value:val}; if(!st) obj=obj.prototype;
25
+ if(Array.isArray(name)) for(let n of name) Object.defineProperty(obj,n,t);
26
+ else Object.defineProperty(obj,name,t);
27
+ }
28
+
29
+ //Cookie Parsing
30
+ utils.setCookie = (name,value,exp,secure) => {
31
+ let c=encodeURIComponent(name)+'='+(value==null?'':encodeURIComponent(value))+';path=/';
32
+ if(exp != null) {
33
+ if(!(exp instanceof Date)) exp = new Date(exp);
34
+ c+=';expires='+exp.toUTCString();
35
+ }
36
+ if(secure) c+=';secure'; document.cookie=c;
37
+ }
38
+ utils.remCookie = name => {
39
+ document.cookie = encodeURIComponent(name)+'=;expires='+new Date(0).toUTCString();
40
+ }
41
+ utils.getCookie = name => {
42
+ const n1 = encodeURIComponent(name), n2 = ' '+n1, cl = document.cookie.split(';');
43
+ for(let i=0,l=cl.length,c,eq,sub; i<l; ++i) {
44
+ c = cl[i]; eq = c.indexOf('='); sub = c.substr(0,eq);
45
+ if(sub == n1 || sub == n2) return decodeURIComponent(c.substr(eq+1));
46
+ }
47
+ }
48
+
49
+ //Wrap a function so that it always has a preset argument list when called
50
+ //In the called function, 'this' is set to the caller's arguments, granting access to both
51
+ utils.proto(Function, 'wrap', function(/*...*/) {
52
+ const f=this, a=arguments; return function() {f.apply(arguments,a)}
53
+ })
54
+
55
+ //Deep (recursive) Object.create
56
+ //Copies down to given sub levels. All levels if undefined
57
+ utils.copy = (o, sub) => {
58
+ if(sub===0 || typeof o != 'object') return o;
59
+ sub=sub>0?sub-1:null; let o2;
60
+ if(Array.isArray(o)) o2=new Array(o.length),
61
+ o.forEach((v,i) => {o2[i]=utils.copy(v,sub)});
62
+ else {o2={};for(let k in o) o2[k]=utils.copy(o[k],sub)}
63
+ return o2;
64
+ }
65
+
66
+ //UserAgent-based Mobile device detection
67
+ utils.deviceInfo = ua => {
68
+ const d={}; if(!ua) ua=navigator.userAgent;
69
+ if(!ua.startsWith("Mozilla/5.0 ")) return d;
70
+ let o=ua.indexOf(')'), os=d.rawOS=ua.substring(13,o), o2,o3;
71
+ if(os.startsWith("Windows")) {
72
+ o2=os.split('; '), d.os = "Windows";
73
+ d.type = o2.indexOf('WOW64')!=-1?'x64 PC; x86 Browser':o2.indexOf('x64')!=-1?'x64 PC':'x86 PC';
74
+ o2=os.indexOf("Windows NT "), d.version = os.substring(o2+11,os.indexOf(';',o2+12));
75
+ } else if(os.startsWith("iP")) {
76
+ o2=os.indexOf("OS"), d.os = "iOS", d.type = os.substr(0,os.indexOf(';'));
77
+ d.version = os.substring(o2+3, os.indexOf(' ',o2+4)).replace(/_/g,'.');
78
+ } else if(os.startsWith("Macintosh;")) {
79
+ o2=os.indexOf(" Mac OS X"), d.os = "MacOS", d.type = os.substring(11,o2)+" Mac";
80
+ d.version = os.substr(o2+10).replace(/_/g,'.');
81
+ } else if((o2=os.indexOf("Android"))!=-1) {
82
+ d.os = "Android", d.version = os.substring(o2+8, os.indexOf(';',o2+9));
83
+ o2=os.lastIndexOf(';'), o3=os.indexOf(" Build",o2+2);
84
+ d.type = os.substring(o2+2, o3==-1?undefined:o3);
85
+ } else if(os.startsWith("X11;")) {
86
+ os=os.substr(5).split(/[;\s]+/), o2=os.length;
87
+ d.os = (os[0]=="Linux"?'':"Linux ")+os[0];
88
+ d.type = os[o2-2], d.version = os[o2-1];
89
+ }
90
+ if(o2=Number(d.version)) d.version=o2;
91
+ o2=ua.indexOf(' ',o+2), o3=ua.indexOf(')',o2+1), o3=o3==-1?o2+1:o3+2;
92
+ d.engine = ua.substring(o+2,o2), d.browser = ua.substring(o3);
93
+ d.mobile = !!ua.match(/Mobi/i); return d;
94
+ }
95
+
96
+ utils.device = utils.deviceInfo();
97
+ utils.mobile = utils.device.mobile;
98
+
99
+ //Get touch by id, returns null if none found
100
+ if(window.TouchList) utils.proto(TouchList, 'get', function(id) {
101
+ for(let k in this) if(this[k].identifier == id) return this[k]; return 0;
102
+ })
103
+
104
+ //Generates modified input field for css skinning on unsupported browsers. This is a JavaScript
105
+ //fallback for when css 'appearance:none' doesn't work. For Mobile Safari, this is usually
106
+ //needed with 'datetime-local', 'select-one', and 'select-multiple' input types
107
+ utils.skinnedInput = el => {
108
+ const cont = utils.mkDiv(null,el.className), is = el.style, type = el.type; el.className += ' isSub';
109
+ if(type == 'datetime-local' || type == 'select-one' || type == 'select-multiple') { //Datetime or Select
110
+ is.opacity = 0; is.top = '-100%'; el.siT = utils.mkEl('span',cont,'isText');
111
+ utils.mkEl('span',cont,'isArrow',{borderTopColor:getComputedStyle(el).color});
112
+ let si=siChange.bind(el); el.addEventListener('change',si); el.forceUpdate=si; si();
113
+ }
114
+ el.replaceWith(cont); cont.appendChild(el);
115
+ //Append StyleSheet
116
+ if(!document.isStyles) document.isStyles=true, utils.mkEl('style',document.body,null,null,'.isSub {'+
117
+ 'width:100% !important; height:100% !important; border:none !important; display:inline-block !important;'+
118
+ 'position:relative !important; box-shadow:none !important; margin:0 !important; padding:initial !important;'+
119
+ '} .isText {'+
120
+ 'display:inline-block; height:100%; max-width:95%;'+
121
+ 'overflow:hidden; text-overflow:ellipsis; white-space:nowrap;'+
122
+ '} .isArrow {'+
123
+ 'width:0; height:0; display:inline-block; float:right; top:38%; position:relative;'+
124
+ 'border-left:3px solid transparent; border-right:3px solid transparent;'+
125
+ 'border-top:6px solid #000; vertical-align:middle;'+
126
+ '}');
127
+ }
128
+ function siChange() {
129
+ switch(this.type) {
130
+ case 'datetime-local': this.siT.textContent = utils.formatDate(utils.fromDateTimeBox(this)); break;
131
+ case 'select-one': this.siT.textContent = selBoxLabel(this); break;
132
+ case 'select-multiple': this.siT.textContent = mulBoxLabel(this);
133
+ }
134
+ }
135
+
136
+ function selBoxLabel(sb) {
137
+ const op = sb.options; if(op.selectedIndex != -1) return op[op.selectedIndex].label;
138
+ return "No Options Selected";
139
+ }
140
+ function mulBoxLabel(sb) {
141
+ const op = sb.options; let str = ''; for(let i=0,l=op.length; i<l; ++i)
142
+ if(op[i].selected) str += (str?', ':'')+op[i].label; return str||"No Options Selected";
143
+ }
144
+
145
+ /*Turns your boring <input> into a mobile-friendly number entry field with max/min & negative support!
146
+ min: Min value, default min safe int
147
+ max: Max value, default max safe int
148
+ decMax: Max decimal precision (eg. 3 is 0.001), default 0
149
+ sym: If a symbol (eg. '$') is given, uses currency mode
150
+ Tips:
151
+ - Use field.onnuminput in place of oninput, get number value with field.num
152
+ - On mobile, use star key for decimal point and pound key for negative
153
+ - You can set field.step in order to change the up/down arrow step size
154
+ - Use field.setRange to change min, max, and decMax*/
155
+ utils.numField=(f, min, max, decMax, sym) => {
156
+ const RM=RegExp(`[,${sym?RegExp.escape(sym):''}]`,'g');
157
+ f.type=(utils.mobile||decMax||sym)?'tel':'number';
158
+ f.setAttribute('pattern',"\\d*");
159
+ if(min==null) min=Number.MIN_SAFE_INTEGER;
160
+ if(max==null) max=Number.MAX_SAFE_INTEGER;
161
+ if(decMax==null) decMax=sym?2:0;
162
+ if(!f.step) f.step=1;
163
+ f.num=Math.max(0,min), f.ns='';
164
+ f.value=sym?utils.formatCost(f.num,sym):f.num.toString();
165
+ f.addEventListener('keydown',e => {
166
+ if(e.ctrlKey) return;
167
+ let k=e.key, kn=k.length==1&&Number.isFinite(Number(k)),
168
+ ns=f.ns, len=ns.length, dec=ns.indexOf('.');
169
+
170
+ if(k=='Tab' || k=='Enter') return;
171
+ else if(kn) {if(dec==-1 || len-dec < decMax+1) ns+=k} //Number
172
+ else if(k=='.' || k=='*') {if(decMax && dec==-1
173
+ && f.num!=max && (min>=0 || f.num!=min)) { //Decimal
174
+ if(!len && min>0) ns=Math.floor(min)+'.';
175
+ else ns+='.';
176
+ }} else if(k=='Backspace' || k=='Delete') { //Backspace
177
+ if(min>0 && f.num==min && ns.endsWith('.')) ns='';
178
+ else ns=ns.slice(0,-1);
179
+ } else if(k=='-' || k=='#') {if(min<0 && !len) ns='-'} //Negative
180
+ else if(k=='ArrowUp') ns=null, f.set(f.num+Number(f.step)); //Up
181
+ else if(k=='ArrowDown') ns=null, f.set(f.num-Number(f.step)); //Down
182
+
183
+ if(ns !== null && ns !== f.ns) {
184
+ let neg=ns=='-'||ns=='-.', s=neg?'0':ns+(ns.endsWith('.')?'0':''),
185
+ nr=Number(s), n=Math.min(max,Math.max(min,nr));
186
+ if(!kn || f.num !== n || nr === min) {
187
+ f.ns=ns, f.num=n;
188
+ f.value = sym ? (neg?sym+'-0.00':utils.formatCost(n,sym)):
189
+ ((neg?'-':'')+n+(ns.endsWith('.')&&(min<=0||n!=min)?'.0':''));
190
+ if(f.onnuminput) f.onnuminput.call(f);
191
+ }
192
+ }
193
+ e.preventDefault();
194
+ });
195
+ f.set=n => {
196
+ if(typeof n=='string') n=n.replace(RM,'');
197
+ n=Math.min(max,Math.max(min,Number(n)||0));
198
+ f.num = decMax?Number(n.toFixed(decMax)):Math.round(n);
199
+ f.ns = f.num.toString();
200
+ f.value = sym?utils.formatCost(f.num,sym):f.ns;
201
+ f.ns=f.ns.replace(/^(-?)0+/,'$1');
202
+ if(f.onnuminput) f.onnuminput.call(f);
203
+ }
204
+ f.setRange=(nMin, nMax, nDecMax) => {
205
+ min=nMin, max=nMax, decMax=nDecMax;
206
+ }
207
+ f.addEventListener('input',() => f.set(f.value));
208
+ f.addEventListener('paste',e => {f.set(e.clipboardData.getData('text')); e.preventDefault()});
209
+ return f;
210
+ }
211
+
212
+ //Format Number as currency. Uses '$' by default
213
+ utils.formatCost = (n, sym='$') => {
214
+ if(!n) return sym+'0.00';
215
+ const p=n.toFixed(2).split('.');
216
+ return sym+p[0].split('').reverse().reduce((a,n,i) =>
217
+ n=='-'?n+a:n+(i&&!(i%3)?',':'')+a,'')+'.'+p[1];
218
+ }
219
+
220
+ //Convert value from 'datetime-local' input to Date object
221
+ utils.fromDateTimeBox = el => {
222
+ const v=el.value; if(!v) return new Date();
223
+ return new Date(v.replace(/-/g,'/').replace(/T/g,' '));
224
+ }
225
+
226
+ //Convert Number to fixed-length
227
+ //Set radix to 16 for HEX
228
+ utils.fixedNum = function(n,len,radix=10) {
229
+ let s=Math.abs(n).toString(radix).toUpperCase();
230
+ return (n<0?'-':'')+(radix==16?'0x':radix==2?'0b':'')+'0'.repeat(Math.max(len-s.length,0))+s;
231
+ }
232
+
233
+ utils.months = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
234
+ function fixed2(n) {return n<=9?'0'+n:n}
235
+
236
+ //Convert Date object into format to set 'datetime-local'
237
+ //input value, optionally including seconds if 'sec' is true
238
+ utils.toDateTimeBox = (d, sec) => {
239
+ return d.getFullYear()+'-'+fixed2(d.getMonth()+1)+'-'+fixed2(d.getDate())+'T'+
240
+ fixed2(d.getHours())+':'+fixed2(d.getMinutes())+(sec?':'+fixed2(d.getSeconds()):'');
241
+ }
242
+
243
+ //Format Date object into human-readable string
244
+ //opt:
245
+ // sec: True to include seconds
246
+ // ms: True or 3 to include milliseconds (requires sec), 2 or 1 to limit precision
247
+ // h24: True to use 24-hour time
248
+ // time: True to show time only, false to show date only, null to show both
249
+ // suf: False to drop date suffix (1st, 2nd, etc.)
250
+ // year: False to hide year, or a number to show year only if it differs from given year
251
+ // df: True to put date first instead of time
252
+ utils.formatDate = (d,opt={}) => {
253
+ let t='',yy,dd;
254
+ if(d==null || !d.getDate || !((yy=d.getFullYear())>1969)) return "[Invalid Date]";
255
+ if(opt.time==null||opt.time) {
256
+ let h=d.getHours(),pm=''; if(!opt.h24) {pm=' AM'; if(h>=12) pm=' PM',h-=12; if(!h) h=12}
257
+ t=h+':'+fixed2(d.getMinutes())+(opt.sec?':'+fixed2(d.getSeconds())
258
+ +(opt.ms?(d.getMilliseconds()/1000).toFixed(Number.isFinite(opt.ms)?opt.ms:3).substr(1):''):'');
259
+ t+=pm; if(opt.time) return t;
260
+ }
261
+ dd=d.getDate();
262
+ dd=utils.months[d.getMonth()]+' '+((opt.suf==null||opt.suf)?utils.suffix(dd):dd);
263
+ if((opt.year==null||opt.year) && opt.year!==yy) dd=dd+', '+yy;
264
+ return opt.df?dd+(t&&' '+t):(t&&t+' ')+dd;
265
+ }
266
+
267
+ //Add appropriate suffix to number. (ex. 31st, 12th, 22nd)
268
+ utils.suffix = n => {
269
+ let j=n%10, k=n%100;
270
+ if(j==1 && k!=11) return n+"st";
271
+ if(j==2 && k!=12) return n+"nd";
272
+ if(j==3 && k!=13) return n+"rd";
273
+ return n+"th";
274
+ }
275
+
276
+ //Virtual Page Navigation
277
+ let H=history;
278
+ utils.goBack = H.back.bind(H);
279
+ utils.goForward = H.forward.bind(H);
280
+ utils.go = (url,st) => {H.pushState(st,'',url||location.pathname),doNav(st)}
281
+ addEventListener('popstate', (e) => doNav(e.state));
282
+ addEventListener('load', () => setTimeout(doNav.wrap(H.state),1));
283
+ function doNav(s) {if(utils.onNav) utils.onNav.call(null,s)}
284
+
285
+ //Create element of type with parent, className, style object, and innerHTML string
286
+ //(Just remember the order PCSI!) Use null to leave any parameter blank
287
+ utils.mkEl = (t,p,c,s,i) => {
288
+ const e=document.createElement(t);
289
+ if(c!=null) e.className=c; if(i!=null) e.innerHTML=i;
290
+ if(s && typeof s=='object') for(let k in s) {
291
+ if(k in e.style) e.style[k]=s[k]; else e.style.setProperty(k,s[k]);
292
+ }
293
+ if(p!=null) p.appendChild(e); return e;
294
+ }
295
+ utils.mkDiv = (p,c,s,i) => utils.mkEl('div',p,c,s,i);
296
+ utils.addText = (el, text) => el.appendChild(document.createTextNode(text));
297
+
298
+ //Get predicted width of text given CSS font style
299
+ utils.textWidth = (txt, font) => {
300
+ const c=window.TWCanvas||(window.TWCanvas=utils.mkEl('canvas')),
301
+ ctx=c.getContext('2d'); ctx.font=font; return ctx.measureText(txt).width;
302
+ }
303
+
304
+ //It's useful for any canvas-style app to have the page dimensions on hand
305
+ utils.define(utils, 'w', ()=>innerWidth);
306
+ utils.define(utils, 'h', ()=>innerHeight);
307
+
308
+ /*Set a nested/recursive property in an object, even if the higher levels don't exist. Useful for defining settings in a complex config object
309
+ obj: Object to set property in
310
+ path: String (dot separated) or array defining the path to the property
311
+ val: Value to set
312
+ onlyNull: True to set value only if it doesn't exist*/
313
+ utils.setPropSafe = (obj, path, val, onlyNull=false) => {
314
+ if(typeof path=='string') path=path.split('.');
315
+ let li=path.length-1;
316
+ path.each(p => {typeof obj[p]=='object'?obj=obj[p]:obj=obj[p]={}},0,li);
317
+ li=path[li]; if(!onlyNull || obj[li]==null) return obj[li]=val;
318
+ return obj[li];
319
+ }
320
+
321
+ /*Gets a nested/recursive property in an object, returning undefined if it or any higher level doesn't exist. Useful for reading settings from a complex config object
322
+ obj: Object to read property from
323
+ path: String (dot separated) or array defining the path to the property*/
324
+ utils.getPropSafe = (obj, path) => {
325
+ if(typeof path=='string') path=path.split('.');
326
+ try {for(let p of path) obj=obj[p]; return obj} catch(_) {}
327
+ }
328
+
329
+ //Remove 'empty' elements like 0, false, ' ', undefined, and NaN from array
330
+ //Often useful in combination with Array.split. Set 'keepZero' to true to keep '0's
331
+ //Function by: Pecacheu & https://stackoverflow.com/users/5445/cms
332
+ utils.proto(Array, 'clean', function(kz) {
333
+ for(let i=0,e,l=this.length; i<l; ++i) {
334
+ e=this[i]; if(utils.isBlank(e) || e === false ||
335
+ !kz && e === 0) this.splice(i--,1),l--;
336
+ } return this;
337
+ })
338
+
339
+ //Remove first instance of item from array. Returns false if not found
340
+ //Use a while loop to remove all instances
341
+ utils.proto(Array, 'remove', function(itm) {
342
+ const i=this.indexOf(itm); if(i==-1) return false;
343
+ this.splice(i,1); return true;
344
+ })
345
+
346
+ //Calls fn on each index of array
347
+ //fn: Callback function(element, index, length)
348
+ //st: Start index, optional. If negative, relative to end of array
349
+ //en: End index, optional. If negative, relative to end of array
350
+ //If fn returns '!', it will remove the element from the array
351
+ //Otherwise, if fn returns any non-null value,
352
+ //the loop is broken and the value is returned by each
353
+ function each(fn,st,en) {
354
+ let l=this.length,i=Math.max(st<0?l+st:(st||0),0),r;
355
+ if(en!=null) l=Math.min(en<0?l+en:en,l);
356
+ for(; i<l; ++i) if((r=fn(this[i],i,l))==='!') {
357
+ this instanceof HTMLCollection?this[i].remove():this.splice(i,1); --i,--l;
358
+ } else if(r!=null) return r;
359
+ }
360
+
361
+ //Adds async support to each
362
+ //pe: true (default) to enable parallel async execution
363
+ async function eachAsync(fn,st,en,pe=true) {
364
+ let l=this.length,i=st=Math.max(st<0?l+st:(st||0),0),n,r=[];
365
+ if(en!=null) l=Math.min(en<0?l+en:en,l);
366
+ for(; i<l; ++i) {
367
+ n=fn(this[i],i,l);
368
+ if(!pe) { n=await n; if(n!=='!' && n!=null) return n; }
369
+ r.push(n);
370
+ }
371
+ if(pe) r=await Promise.all(r);
372
+ for(i=st,n=0; i<l; ++i,++n) if(r[n]==='!') {
373
+ this instanceof HTMLCollection?this[i].remove():this.splice(i,1); --i,--l;
374
+ } else if(r[n]!=null) return r[n];
375
+ }
376
+ [Array,HTMLCollection,NodeList].forEach(p => {utils.proto(p,'each',each), utils.proto(p,'eachAsync',eachAsync)});
377
+
378
+ const B64='ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/', B64URL=B64.replace('+/','-_'),
379
+ B64F={43:62,47:63,48:52,49:53,50:54,51:55,52:56,53:57,54:58,55:59,56:60,57:61,65:0,66:1,67:2,68:3,69:4,70:5,71:6,72:7,73:8,
380
+ 74:9,75:10,76:11,77:12,78:13,79:14,80:15,81:16,82:17,83:18,84:19,85:20,86:21,87:22,88:23,89:24,90:25,97:26,98:27,99:28,
381
+ 100:29,101:30,102:31,103:32,104:33,105:34,106:35,107:36,108:37,109:38,110:39,111:40,112:41,113:42,114:43,115:44,116:45,
382
+ 117:46,118:47,119:48,120:49,121:50,122:51,45:62,95:63};
383
+
384
+ //Polyfill for Uint8Array.toBase64
385
+ if(!('toBase64' in Uint8Array.prototype)) utils.proto(Uint8Array, 'toBase64', function(opt) {
386
+ let l=this.byteLength, br=l%3, b=opt&&opt.alphabet==='base64url'?B64URL:B64, i=0,str='',chk; l-=br;
387
+ for(; i<l; i+=3) {
388
+ chk = (this[i]<<16)|(this[i+1]<<8)|this[i+2];
389
+ str += b[(chk&16515072)>>18] + b[(chk&258048)>>12] + b[(chk&4032)>>6] + b[chk&63];
390
+ }
391
+ if(br==1) {
392
+ chk = this[l];
393
+ str += b[(chk&252)>>2] + b[(chk&3)<<4];
394
+ if(!opt || !opt.omitPadding) str += '=';
395
+ } else if(br==2) {
396
+ chk = (this[l]<<8)|this[l+1];
397
+ str += b[(chk&64512)>>10] + b[(chk&1008)>>4] + b[(chk&15)<<2];
398
+ if(!opt || !opt.omitPadding) str += '==';
399
+ }
400
+ return str;
401
+ })
402
+
403
+ function b64Char(s,i) {
404
+ s=B64F[s.charCodeAt(i)];
405
+ if(s==null) throw "Bad char at "+i;
406
+ return s;
407
+ }
408
+
409
+ //Polyfill for Uint8Array.fromBase64
410
+ if(!('fromBase64' in Uint8Array)) utils.proto(Uint8Array, 'fromBase64', str => {
411
+ let l=str.length, i=l-1;
412
+ for(; i>=0; --i) if(str.charCodeAt(i)!==61) break;
413
+ l=i+1,i=0; let br=l%4; l-=br; if(br==1) throw "Bad b64 len";
414
+ let arr=new Uint8Array(l*3/4+(br?br-1:0)), b=-1,chk;
415
+ for(; i<l; i+=4) {
416
+ chk = (b64Char(str,i)<<18)|(b64Char(str,i+1)<<12)|(b64Char(str,i+2)<<6)|b64Char(str,i+3);
417
+ arr[++b]=chk>>16, arr[++b]=chk>>8, arr[++b]=chk;
418
+ }
419
+ if(br==2) {
420
+ arr[++b] = (b64Char(str,i)<<2)|(b64Char(str,i+1)>>4);
421
+ } else if(br==3) {
422
+ chk = (b64Char(str,i)<<10)|(b64Char(str,i+1)<<4)|(b64Char(str,i+2)>>2);
423
+ arr[++b]=chk>>8, arr[++b]=chk;
424
+ }
425
+ return arr;
426
+ },1)
427
+
428
+ const R_ESC1=/[|\\{}()[\]^$+*?.]/g, R_ESC2=/-/g;
429
+
430
+ //Polyfill for RegExp.escape by https://github.com/sindresorhus
431
+ if(!('escape' in RegExp)) utils.proto(RegExp, 'escape', s => {
432
+ return s.replace(R_ESC1,'\\$&').replace(R_ESC2,'\\x2d');
433
+ },1)
434
+
435
+ //Get an element's index in its parent. Returns -1 if the element has no parent
436
+ utils.define(Element.prototype, 'index', function() {
437
+ const p=this.parentElement; if(!p) return -1;
438
+ return Array.prototype.indexOf.call(p.children, this);
439
+ })
440
+
441
+ //Insert child at index
442
+ utils.proto(Element, 'insertChildAt', function(el, i) {
443
+ if(i<0) i=0; if(i >= this.children.length) this.appendChild(el);
444
+ else this.insertBefore(el, this.children[i]);
445
+ })
446
+
447
+ //Get element bounding rect as UtilRect object
448
+ utils.boundingRect=e => new UtilRect(e.getBoundingClientRect());
449
+ utils.innerRect=e => {
450
+ let r=e.getBoundingClientRect(), s=getComputedStyle(e);
451
+ return new UtilRect(r.top+parseFloat(s.paddingTop)+parseFloat(s.borderTopWidth),
452
+ r.bottom-parseFloat(s.paddingBottom)-parseFloat(s.borderBottomWidth),
453
+ r.left+parseFloat(s.paddingLeft)+parseFloat(s.borderLeftWidth),
454
+ r.right-parseFloat(s.paddingRight)-parseFloat(s.borderRightWidth));
455
+ };
456
+ utils.define(Element.prototype,'boundingRect',function() {return utils.boundingRect(this)});
457
+ utils.define(Element.prototype,'innerRect',function() {return utils.innerRect(this)});
458
+
459
+ //No idea why this isn't built-in, but it's not
460
+ Math.cot = x => 1/Math.tan(x);
461
+
462
+ //Check if string, array, or other object is empty
463
+ utils.isBlank = s => {
464
+ if(s == null) return true;
465
+ if(typeof s == 'string') return !/\S/.test(s);
466
+ if(typeof s == 'object') {
467
+ if(typeof s.length == 'number') return s.length === 0;
468
+ return Object.keys(s).length === 0;
469
+ }
470
+ return false;
471
+ }
472
+
473
+ //Finds first empty (undefined) slot in array
474
+ utils.firstEmpty = arr => {
475
+ const len = arr.length;
476
+ for(let i=0; i<len; ++i) if(arr[i] == null) return i;
477
+ return len;
478
+ }
479
+
480
+ //Like 'firstEmpty', but uses letters a-Z instead
481
+ utils.firstEmptyChar = obj => {
482
+ const keys = Object.keys(obj), len = keys.length;
483
+ for(let i=0; i<len; ++i) if(obj[keys[i]] == null) return keys[i];
484
+ return utils.numToChar(len);
485
+ }
486
+
487
+ //Converts a number into letters (upper and lower) from a to Z
488
+ utils.numToChar = n => {
489
+ if(n<=25) return String.fromCharCode(n+97);
490
+ else if(n>=26 && n<=51) return String.fromCharCode(n+39);
491
+ let mVal, fVal;
492
+ if(n<2756) { mVal=rstCount(Math.floor(n/52)-1,52); fVal=rstCount(n,52); }
493
+ else if(n<143364) { mVal=rstCount(Math.floor((n-52)/2704)-1,52); fVal=rstCount(n-52,2704)+52; }
494
+ else if(n<7454980) { mVal=rstCount(Math.floor((n-2756)/140608)-1,52); fVal=rstCount(n-2756,140608)+2756; }
495
+ else return false; //More than "ZZZZ"? No. Just, no
496
+ return utils.numToChar(mVal)+utils.numToChar(fVal);
497
+ }
498
+
499
+ //Use this to reset your counter each time 'maxVal' is reached
500
+ function rstCount(val, maxVal) { while(val >= maxVal) val -= maxVal; return val; }
501
+
502
+ //Semi-recursively merges two (or more) objects, giving the last precedence
503
+ //If both objects contain a property at the same index, and both are Arrays/Objects, they are merged
504
+ utils.merge = function(o/*, src1, src2...*/) {
505
+ for(let a=1,al=arguments.length,n,oP,nP; a<al; ++a) {
506
+ n = arguments[a]; for(let k in n) {
507
+ oP = o[k]; nP = n[k]; if(oP && nP) { //Conflict
508
+ if(oP.length >= 0 && nP.length >= 0) { //Both Array-like
509
+ for(let i=0,l=nP.length,ofs=oP.length; i<l; ++i) oP[i+ofs] = nP[i]; continue;
510
+ } else if(typeof oP == 'object' && typeof nP == 'object') { //Both Objects
511
+ for(let pk in nP) oP[pk] = nP[pk]; continue;
512
+ }
513
+ }
514
+ o[k] = nP;
515
+ }
516
+ }
517
+ return o;
518
+ }
519
+
520
+ //Keeps value within max/min bounds. Also handles NaN or null
521
+ utils.bounds = (n, min=0, max=1) => {
522
+ if(!(n>=min)) return min; if(!(n<=max)) return max; return n;
523
+ }
524
+
525
+ //'Normalizes' a value so that it ranges from min to max, but unlike utils.bounds,
526
+ //this function retains input's offset. This can be used to normalize angles
527
+ utils.norm = utils.normalize = (n, min=0, max=1) => {
528
+ const c = Math.abs(max-min);
529
+ if(n < min) while(n < min) n += c; else while(n >= max) n -= c;
530
+ return n;
531
+ }
532
+
533
+ //Finds and removes all instances of 'rem' contained within s
534
+ utils.cutStr = (s, rem) => {
535
+ let fnd; while((fnd=s.indexOf(rem)) != -1) {
536
+ s = s.slice(0, fnd)+s.slice(fnd+rem.length);
537
+ }
538
+ return s;
539
+ }
540
+
541
+ //Cuts text out of 'data' from first instance of 'startString' to next instance of 'endString'
542
+ //(data,startString,endString[,index[,searchStart]])
543
+ //index: Optional object. index.s and index.t will be set to start and end indexes
544
+ utils.dCut = (d, ss, es, sd, st) => {
545
+ let is = d.indexOf(ss,st?st:undefined)+ss.length, it = d.indexOf(es,is);
546
+ if(sd) sd.s=is,sd.t=it; return (is < ss.length || it <= is)?'':d.substring(is,it);
547
+ }
548
+ utils.dCutToLast = (d, ss, es, sd, st) => {
549
+ let is = d.indexOf(ss,st?st:undefined)+ss.length, it = d.lastIndexOf(es);
550
+ if(sd) sd.s=is,sd.t=it; return (is < ss.length || it <= is)?'':d.substring(is,it);
551
+ }
552
+ utils.dCutLast = (d, ss, es, sd, st) => {
553
+ let is = d.lastIndexOf(ss,st?st:undefined)+ss.length, it = d.indexOf(es,is);
554
+ if(sd) sd.s=is,sd.t=it; return (is < ss.length || it <= is)?'':d.substring(is,it);
555
+ }
556
+
557
+ //Given CSS property value 'prop', returns object with
558
+ //space-separated values from the property string
559
+ utils.parseCSS = prop => {
560
+ const pArr={}, pKey="", keyNum=0; prop=prop.trim();
561
+ function parseInner(str) {
562
+ if(str.indexOf(',') !== -1) {
563
+ const arr = utils.clean(str.split(','));
564
+ for(let i=0, l=arr.length; i<l; ++i) arr[i]=arr[i].trim();
565
+ return arr;
566
+ }
567
+ return str.trim();
568
+ }
569
+ while(prop.length > 0) {
570
+ if(prop[0] == '(' && prop.indexOf(')') !== -1 && pKey) {
571
+ let end=prop.indexOf(')'), pStr=prop.substring(1, end);
572
+ pArr[pKey] = parseInner(pStr);
573
+ pKey = ""; prop = prop.substring(end+1);
574
+ } else if(prop.search(/[#!\w]/) == 0) {
575
+ if(pKey) pArr[keyNum++] = pKey;
576
+ let end=prop.search(/[^#!\w-%]/); if(end==-1) end=prop.length;
577
+ pKey = prop.substring(0, end); prop = prop.substring(end);
578
+ } else {
579
+ prop = prop.substring(1);
580
+ }
581
+ }
582
+ if(pKey) pArr[keyNum] = pKey; return pArr;
583
+ }
584
+
585
+ //Rebuilds CSS string from a parseCSS object
586
+ utils.buildCSS = propArr => {
587
+ const keyArr=Object.keys(propArr), l=keyArr.length; let pStr='', i=0;
588
+ while(i<l) {
589
+ const k=keyArr[i], v=propArr[keyArr[i]]; ++i;
590
+ if(0<=Number(k)) pStr+=v+' '; else pStr+=`${k}(${v}) `;
591
+ }
592
+ return pStr.substring(0, pStr.length-1);
593
+ }
594
+
595
+ function defaultStyle() {
596
+ const ss=document.styleSheets;
597
+ for(let s=0,j=ss.length; s<j; ++s) try { ss[s].cssRules; return ss[s]; } catch(e) {}
598
+ let ns=utils.mkEl('style',document.head); utils.addText(ns,''); return ns.sheet;
599
+ }
600
+ function toKey(k) {
601
+ return k.replace(/[A-Z]/g, s => '-'+s.toLowerCase());
602
+ }
603
+
604
+ //Create a CSS class and append it to the current document. Fill 'propList' object
605
+ //with key/value pairs representing the properties you want to add to the class
606
+ utils.addClass = (className, propList) => {
607
+ const style = defaultStyle(), keys = Object.keys(propList); let str='';
608
+ for(let i=0,l=keys.length; i<l; ++i) str += toKey(keys[i])+":"+propList[keys[i]]+";";
609
+ style.addRule("."+className,str);
610
+ }
611
+
612
+ //Create a CSS selector and append it to the current document
613
+ utils.addId = (idName, propList) => {
614
+ const style = defaultStyle(), keys = Object.keys(propList); let str='';
615
+ for(let i=0,l=keys.length; i<l; ++i) str += toKey(keys[i])+":"+propList[keys[i]]+";";
616
+ style.addRule("#"+idName,str);
617
+ }
618
+
619
+ //Create a CSS keyframe and append it to the current document
620
+ utils.addKeyframe = (name, content) => {
621
+ defaultStyle().addRule("@keyframes "+name,content);
622
+ }
623
+
624
+ //Remove a specific css selector (including the '.' or '#') from all stylesheets in the current document
625
+ utils.removeSelector = name => {
626
+ for(let s=0,style,rList,j=document.styleSheets.length; s<j; ++s) {
627
+ style = document.styleSheets[s]; try { rList=style.cssRules; } catch(e) { continue; }
628
+ for(let key in rList) if(rList[key].constructor.name == "CSSStyleRule"
629
+ && rList[key].selectorText == name) style.deleteRule(key);
630
+ }
631
+ }
632
+
633
+ //Shorthand way to get element by id/class name, within a parent element (defaults to document)
634
+ //(class[,parent])
635
+ utils.getId = (c,p) => (p?p:document)['getElementById'](c);
636
+ utils.getClassList = (c,p) => (p?p:document)['getElementsByClassName'](c);
637
+ utils.getClassFirst = (c,p) => (c=utils.getClassList(c,p),c.length>0?c[0]:0);
638
+ utils.getClassOnly = (c,p) => (c=utils.getClassList(c,p),c.length==1?c[0]:0);
639
+
640
+ //Converts HEX color to 24-bit RGB
641
+ utils.hexToRgb = hex => {
642
+ const c = parseInt(hex.substr(1), 16);
643
+ return [(c >> 16) & 255, (c >> 8) & 255, c & 255];
644
+ }
645
+
646
+ //Generates random integer from min to max
647
+ utils.rand = (min, max, res, ease) => {
648
+ res=res||1; max*=res,min*=res; let r=Math.random();
649
+ return Math.round((ease?ease(r):r)*(max-min)+min)/res;
650
+ }
651
+
652
+ //Parses a url query string into an Object
653
+ //Function by: Pecacheu (From Pecacheu's Apache Test Server)
654
+ utils.fromQuery = str => {
655
+ if(str.startsWith('?')) str = str.substr(1);
656
+ function parse(params, pairs) {
657
+ const pair = pairs[0], spl = pair.indexOf('='),
658
+ key = decodeURIComponent(pair.substr(0,spl)),
659
+ value = decodeURIComponent(pair.substr(spl+1));
660
+ //Handle multiple parameters of the same name
661
+ if(params[key] == null) params[key] = value;
662
+ else if(typeof params[key] == 'array') params[key].push(value);
663
+ else params[key] = [params[key],value];
664
+ return pairs.length == 1 ? params : parse(params, pairs.slice(1));
665
+ } return str.length == 0 ? {} : parse({}, str.split('&'));
666
+ }
667
+
668
+ //Converts an object into a url query string
669
+ utils.toQuery = obj => {
670
+ let str = ''; if(typeof obj != 'object') return encodeURIComponent(obj);
671
+ for(let key in obj) {
672
+ let val = obj[key]; if(typeof val == 'object') val = JSON.stringify(val);
673
+ str += '&'+key+'='+encodeURIComponent(val);
674
+ } return str.slice(1);
675
+ }
676
+
677
+ //Various methods of centering objects using JavaScript
678
+ //obj: Object to center
679
+ //only: 'x' for only x axis centering, 'y' for only y axis, null for both
680
+ //type: 'calc', 'trans', 'move', or null, modes explained below
681
+ utils.center = (obj, only, type) => {
682
+ let os=obj.style;
683
+ if(!os.position) os.position="absolute";
684
+ if(type == 'calc') { //Responsive, but only if object size is static
685
+ if(!only || only == "x") os.left=`calc(50% - ${obj.clientWidth/2}px)`;
686
+ if(!only || only == "y") os.top=`calc(50% - ${obj.clientHeight/2}px)`;
687
+ } else if(type == 'move') { //Not responsive
688
+ if(!only || only == "x") os.left=`${utils.w/2 - obj.clientWidth/2}px`;
689
+ if(!only || only == "y") os.top=`${utils.h/2 - obj.clientHeight/2}px`;
690
+ } else if(type == 'trans') { //Responsive, doesn't create container
691
+ let trans = utils.cutStr(os.transform, "translateX(-50%)");
692
+ trans = utils.cutStr(trans, "translateY(-50%)");
693
+ if(!only || only == "x") os.left="50%", trans+="translateX(-50%)";
694
+ if(!only || only == "y") os.top="50%", trans+="translateY(-50%)";
695
+ if(trans) os.transform=trans;
696
+ } else { //Responsive, largest browser support
697
+ let cont=utils.mkEl("div", obj.parentNode, null, {display:'table',
698
+ position:'absolute', top:0, left:0, width:'100%', height:'100%'});
699
+ cont.appendChild(obj); os.display="table-cell";
700
+ if(!only || only == "x") os.textAlign="center";
701
+ if(!only || only == "y") os.verticalAlign="middle";
702
+ os.position="relative";
703
+ }
704
+ }
705
+
706
+ //Loads a file and returns its contents using HTTP GET
707
+ //Callback parameters: (err, data, req)
708
+ //err: Non-zero on error. Standard HTTP error codes
709
+ //data: Response text
710
+ //req: Full XMLHttpRequest object
711
+ //meth: Optional HTTP method, default is GET
712
+ //body: Optional body content
713
+ //hd: Optional header list
714
+ utils.loadAjax = (path, cb, meth, body, hd) => {
715
+ let R; try {R=new XMLHttpRequest()} catch(e) {return cb(e)}
716
+ if(hd) for(let k in hd) R.setRequestHeader(k,hd[k]);
717
+ R.open(meth||'GET',path); R.onreadystatechange = () => {
718
+ let s=R.status||-1;
719
+ if(R.readyState == R.DONE) cb(s==200?0:s, R.response, R);
720
+ }
721
+ R.send(body||undefined);
722
+ }
723
+
724
+ //Good fallback for loadAjax. Loads a file at the address via HTML object tag
725
+ //Callback is fired with either received data, or 'false' if unsuccessful
726
+ utils.loadFile = (path, cb, timeout) => {
727
+ const obj = utils.mkEl('object', document.body, null, {position:'fixed', opacity:0});
728
+ obj.data = path;
729
+ let tmr = setTimeout(() => {
730
+ obj.remove(); tmr = null; cb(false);
731
+ }, timeout||4000);
732
+ obj.onload = () => {
733
+ if(!tmr) return; clearTimeout(tmr);
734
+ cb(obj.contentDocument.documentElement.outerHTML);
735
+ obj.remove();
736
+ }
737
+ }
738
+
739
+ //Loads a file at the address from a JSONP-enabled server. Callback
740
+ //is fired with either received data, or 'false' if unsuccessful
741
+ utils.loadJSONP = (path, cb, timeout) => {
742
+ const script = utils.mkEl('script', document.head), id = utils.firstEmptyChar(utils.lJSONCall);
743
+ script.type = 'application/javascript';
744
+ script.src = path+(path.indexOf('?')==-1?'?':'&')+'callback=utils.lJSONCall.'+id;
745
+ let tmr = setTimeout(() => { delete utils.lJSONCall[id]; cb(false); }, timeout||4000);
746
+ utils.lJSONCall[id] = data => {
747
+ if(tmr) clearTimeout(tmr); delete utils.lJSONCall[id]; cb(data);
748
+ }
749
+ document.head.removeChild(script);
750
+ }; utils.lJSONCall = [];
751
+
752
+ //Downloads a file from a link
753
+ utils.dlFile = (fn,uri) => {
754
+ return fetch(uri).then(r => { if(r.status != 200) throw "Code "+r.status; return r.blob(); })
755
+ .then(b => { utils.dlData(fn,b); });
756
+ }
757
+ //Downloads a file generated from a Blob or ArrayBuffer
758
+ utils.dlData = (fn,d) => {
759
+ let o,e=utils.mkEl('a',document.body,null,{display:'none'});
760
+ if(typeof d=='string') o=d; else {
761
+ if(!(d instanceof Blob)) d=Blob(d); o=URL.createObjectURL(d);
762
+ }
763
+ e.href=o,e.download=fn; e.click(); e.remove(); URL.revokeObjectURL(o);
764
+ }
765
+
766
+ //Converts from radians to degrees, so you can work in degrees
767
+ //Function by: The a**hole who invented radians
768
+ utils.deg = rad => rad*180/Math.PI;
769
+
770
+ //Converts from degrees to radians, so you can convert back for given stupid library
771
+ //Function by: The a**hole who invented radians
772
+ utils.rad = deg => deg*Math.PI/180;
773
+
774
+ //Pecacheu's ultimate unit translation formula!
775
+ //This Version -- Bounds Checking: NO, Rounding: NO, Max/Min Switching: NO, Easing: YES
776
+ utils.map = (input, minIn, maxIn, minOut, maxOut, ease) => {
777
+ let i=(input-minIn)/(maxIn-minIn); return ((ease?ease(i):i)*(maxOut-minOut))+minOut;
778
+ }
779
+
780
+ //setTimeout but async
781
+ utils.delay = ms => new Promise(r => setTimeout(r,ms));
782
+
783
+ UtilRect = function(t,b,l,r) {
784
+ if(!(this instanceof UtilRect)) return new UtilRect(t,b,l,r);
785
+ const f=Number.isFinite; let tt=0,bb=0,ll=0,rr=0;
786
+ utils.define(this,'x', ()=>ll, v=>{f(v)?(rr+=v-ll,ll=v):0});
787
+ utils.define(this,'y', ()=>tt, v=>{f(v)?(bb+=v-tt,tt=v):0});
788
+ utils.define(this,'top', ()=>tt, v=>{tt=f(v)?v:0});
789
+ utils.define(this,['bottom','y2'], ()=>bb, v=>{bb=f(v)?v:0});
790
+ utils.define(this,'left', ()=>ll, v=>{ll=f(v)?v:0});
791
+ utils.define(this,['right','x2'], ()=>rr, v=>{rr=f(v)?v:0});
792
+ utils.define(this,['width','w'], ()=>rr-ll, v=>{rr=v>=0?ll+v:0});
793
+ utils.define(this,['height','h'], ()=>bb-tt, v=>{bb=v>=0?tt+v:0});
794
+ utils.define(this,'centerX', ()=>ll/2+rr/2);
795
+ utils.define(this,'centerY', ()=>tt/2+bb/2);
796
+ if(t instanceof DOMRect || t instanceof UtilRect) tt=t.top, bb=t.bottom, ll=t.left, rr=t.right;
797
+ else tt=t, bb=b, ll=l, rr=r;
798
+ }
799
+
800
+ //Check if rect contains point, other rect, or Element
801
+ utils.proto(UtilRect, 'contains', function(x,y) {
802
+ if(x instanceof Element) return this.contains(x.boundingRect);
803
+ if(x instanceof UtilRect) return x.x >= this.x && x.x2 <= this.x2 && x.y >= this.y && x.y2 <= this.y2;
804
+ return x >= this.x && x <= this.x2 && y >= this.y && y <= this.y2;
805
+ })
806
+
807
+ //Check if rect overlaps rect or Element
808
+ utils.proto(UtilRect, 'overlaps', function(r) {
809
+ if(r instanceof Element) return this.overlaps(r.boundingRect);
810
+ if(!(r instanceof UtilRect)) return 0; let x,y;
811
+ 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;
812
+ else x = r.x >= this.x && r.x <= this.x2 || r.x2 >= this.x && r.x2 <= this.x2;
813
+ 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;
814
+ else y = r.y >= this.y && r.y <= this.y2 || r.y2 >= this.y && r.y2 <= this.y2;
815
+ return x&&y;
816
+ })
817
+
818
+ //Get distance from this rect to point, other rect, or Element
819
+ utils.proto(UtilRect, 'dist', function(x,y) {
820
+ if(x instanceof Element) return this.dist(x.boundingRect); let n=(x instanceof UtilRect);
821
+ y=Math.abs((n?x.centerY:y)-this.centerY), x=Math.abs((n?x.centerX:x)-this.centerX);
822
+ return Math.sqrt(x*x+y*y);
823
+ })
824
+
825
+ //Expand (or contract if negative) a UtilRect by num of pixels
826
+ //Useful for using UtilRect objects as element hitboxes. Returns self for chaining
827
+ utils.proto(UtilRect, 'expand', function(by) {
828
+ this.top -= by; this.left -= by; this.bottom += by;
829
+ this.right += by; return this;
830
+ })
831
+
832
+ })(); //End of Utils Library
833
+
834
+ //JavaScript Easing Library by: https://github.com/gre & https://gizma.com/easing
835
+ //t should be between 0 and 1
836
+ const Easing = {
837
+ //no easing, no acceleration
838
+ linear:t => t,
839
+ //accelerating from zero velocity
840
+ easeInQuad:t => t*t,
841
+ //decelerating to zero velocity
842
+ easeOutQuad:t => t*(2-t),
843
+ //acceleration until halfway, then deceleration
844
+ easeInOutQuad:t => t<.5 ? 2*t*t : -1+(4-2*t)*t,
845
+ //accelerating from zero velocity
846
+ easeInCubic:t => t*t*t,
847
+ //decelerating to zero velocity
848
+ easeOutCubic:t => (--t)*t*t+1,
849
+ //acceleration until halfway, then deceleration
850
+ easeInOutCubic:t => t<.5 ? 4*t*t*t : (t-1)*(2*t-2)*(2*t-2)+1,
851
+ //accelerating from zero velocity
852
+ easeInQuart:t => t*t*t*t,
853
+ //decelerating to zero velocity
854
+ easeOutQuart:t => 1-(--t)*t*t*t,
855
+ //acceleration until halfway, then deceleration
856
+ easeInOutQuart:t => t<.5 ? 8*t*t*t*t : 1-8*(--t)*t*t*t,
857
+ //accelerating from zero velocity
858
+ easeInQuint:t => t*t*t*t*t,
859
+ //decelerating to zero velocity
860
+ easeOutQuint:t => 1+(--t)*t*t*t*t,
861
+ //acceleration until halfway, then deceleration
862
+ easeInOutQuint:t => t<.5 ? 16*t*t*t*t*t : 1+16*(--t)*t*t*t*t
863
+ }
864
+
865
+ //Node.js compat
866
+ if(typeof global!='undefined') {
867
+ global.utils=utils, global.UtilRect=UtilRect, global.Easing=Easing;
868
+ }
package/utils.min.js ADDED
@@ -0,0 +1,2 @@
1
+ //https://github.com/Pecacheu/Utils.js MIT -- Minified with https://javascript-minifier.com
2
+ "use strict";const utils={VER:"v8.6.6"};let UtilRect,P="undefined"==typeof window?[{},{back(){},forward(){}},class{},class{},class{},class{},()=>{}]:[window,history,DOMRect,HTMLCollection,Element,NodeList,addEventListener];(()=>{let[t,e,i,l,s,n,r]=P;function o(){switch(this.type){case"datetime-local":this.siT.textContent=utils.formatDate(utils.fromDateTimeBox(this));break;case"select-one":this.siT.textContent=u(this);break;case"select-multiple":this.siT.textContent=a(this)}}function u(t){let e=t.options;return -1!=e.selectedIndex?e[e.selectedIndex].label:"No Options Selected"}function a(t){let e=t.options,i="";for(let l=0,s=e.length;l<s;++l)e[l].selected&&(i+=(i?", ":"")+e[l].label);return i||"No Options Selected"}function d(t){return t<=9?"0"+t:t}utils.define=(t,e,i,l)=>{let s={};if(i&&(s.get=i),l&&(s.set=l),Array.isArray(e))for(let n of e)Object.defineProperty(t,n,s);else Object.defineProperty(t,e,s)},utils.proto=(t,e,i,l)=>{let s={value:i};if(l||(t=t.prototype),Array.isArray(e))for(let n of e)Object.defineProperty(t,n,s);else Object.defineProperty(t,e,s)},utils.setCookie=(t,e,i,l)=>{let s=encodeURIComponent(t)+"="+(null==e?"":encodeURIComponent(e))+";path=/";null!=i&&(i instanceof Date||(i=new Date(i)),s+=";expires="+i.toUTCString()),l&&(s+=";secure"),document.cookie=s},utils.remCookie=t=>{document.cookie=encodeURIComponent(t)+"=;expires="+new Date(0).toUTCString()},utils.getCookie=t=>{let e=encodeURIComponent(t),i=" "+e,l=document.cookie.split(";");for(let s=0,n=l.length,r,o,u;s<n;++s)if(o=(r=l[s]).indexOf("="),(u=r.substr(0,o))==e||u==i)return decodeURIComponent(r.substr(o+1))},utils.proto(Function,"wrap",function(){let t=this,e=arguments;return function(){t.apply(arguments,e)}}),utils.copy=(t,e)=>{if(0===e||"object"!=typeof t)return t;e=e>0?e-1:null;let i;if(Array.isArray(t))i=Array(t.length),t.forEach((t,l)=>{i[l]=utils.copy(t,e)});else for(let l in i={},t)i[l]=utils.copy(t[l],e);return i},utils.deviceInfo=t=>{let e={};if(t||(t=navigator.userAgent),!t.startsWith("Mozilla/5.0 "))return e;let i=t.indexOf(")"),l=e.rawOS=t.substring(13,i),s,n;return l.startsWith("Windows")?(s=l.split("; "),e.os="Windows",e.type=-1!=s.indexOf("WOW64")?"x64 PC; x86 Browser":-1!=s.indexOf("x64")?"x64 PC":"x86 PC",s=l.indexOf("Windows NT "),e.version=l.substring(s+11,l.indexOf(";",s+12))):l.startsWith("iP")?(s=l.indexOf("OS"),e.os="iOS",e.type=l.substr(0,l.indexOf(";")),e.version=l.substring(s+3,l.indexOf(" ",s+4)).replace(/_/g,".")):l.startsWith("Macintosh;")?(s=l.indexOf(" Mac OS X"),e.os="MacOS",e.type=l.substring(11,s)+" Mac",e.version=l.substr(s+10).replace(/_/g,".")):-1!=(s=l.indexOf("Android"))?(e.os="Android",e.version=l.substring(s+8,l.indexOf(";",s+9)),s=l.lastIndexOf(";"),n=l.indexOf(" Build",s+2),e.type=l.substring(s+2,-1==n?void 0:n)):l.startsWith("X11;")&&(s=(l=l.substr(5).split(/[;\s]+/)).length,e.os=("Linux"==l[0]?"":"Linux ")+l[0],e.type=l[s-2],e.version=l[s-1]),(s=Number(e.version))&&(e.version=s),s=t.indexOf(" ",i+2),n=-1==(n=t.indexOf(")",s+1))?s+1:n+2,e.engine=t.substring(i+2,s),e.browser=t.substring(n),e.mobile=!!t.match(/Mobi/i),e},utils.device=utils.deviceInfo(),utils.mobile=utils.device.mobile,t.TouchList&&utils.proto(TouchList,"get",function(t){for(let e in this)if(this[e].identifier==t)return this[e];return 0}),utils.skinnedInput=t=>{let e=utils.mkDiv(null,t.className),i=t.style,l=t.type;if(t.className+=" isSub","datetime-local"==l||"select-one"==l||"select-multiple"==l){i.opacity=0,i.top="-100%",t.siT=utils.mkEl("span",e,"isText"),utils.mkEl("span",e,"isArrow",{borderTopColor:getComputedStyle(t).color});let s=o.bind(t);t.addEventListener("change",s),t.forceUpdate=s,s()}t.replaceWith(e),e.appendChild(t),document.isStyles||(document.isStyles=!0,utils.mkEl("style",document.body,null,null,".isSub {width:100% !important; height:100% !important; border:none !important; display:inline-block !important;position:relative !important; box-shadow:none !important; margin:0 !important; padding:initial !important;} .isText {display:inline-block; height:100%; max-width:95%;overflow:hidden; text-overflow:ellipsis; white-space:nowrap;} .isArrow {width:0; height:0; display:inline-block; float:right; top:38%; position:relative;border-left:3px solid transparent; border-right:3px solid transparent;border-top:6px solid #000; vertical-align:middle;}"))},utils.numField=(t,e,i,l,s)=>{let n=RegExp(`[,${s?RegExp.escape(s):""}]`,"g");return t.type=utils.mobile||l||s?"tel":"number",t.setAttribute("pattern","\\d*"),null==e&&(e=Number.MIN_SAFE_INTEGER),null==i&&(i=Number.MAX_SAFE_INTEGER),null==l&&(l=s?2:0),t.step||(t.step=1),t.num=Math.max(0,e),t.ns="",t.value=s?utils.formatCost(t.num,s):t.num.toString(),t.addEventListener("keydown",n=>{if(n.ctrlKey)return;let r=n.key,o=1==r.length&&Number.isFinite(Number(r)),u=t.ns,a=u.length,d=u.indexOf(".");if("Tab"!=r&&"Enter"!=r){if(o?(-1==d||a-d<l+1)&&(u+=r):"."==r||"*"==r?l&&-1==d&&t.num!=i&&(e>=0||t.num!=e)&&(!a&&e>0?u=Math.floor(e)+".":u+="."):"Backspace"==r||"Delete"==r?u=e>0&&t.num==e&&u.endsWith(".")?"":u.slice(0,-1):"-"==r||"#"==r?e<0&&!a&&(u="-"):"ArrowUp"==r?(u=null,t.set(t.num+Number(t.step))):"ArrowDown"==r&&(u=null,t.set(t.num-Number(t.step))),null!==u&&u!==t.ns){let f="-"==u||"-."==u,c=Number(f?"0":u+(u.endsWith(".")?"0":"")),h=Math.min(i,Math.max(e,c));(!o||t.num!==h||c===e)&&(t.ns=u,t.num=h,t.value=s?f?s+"-0.00":utils.formatCost(h,s):(f?"-":"")+h+(u.endsWith(".")&&(e<=0||h!=e)?".0":""),t.onnuminput&&t.onnuminput.call(t))}n.preventDefault()}}),t.set=r=>{"string"==typeof r&&(r=r.replace(n,"")),r=Math.min(i,Math.max(e,Number(r)||0)),t.num=l?Number(r.toFixed(l)):Math.round(r),t.ns=t.num.toString(),t.value=s?utils.formatCost(t.num,s):t.ns,t.ns=t.ns.replace(/^(-?)0+/,"$1"),t.onnuminput&&t.onnuminput.call(t)},t.setRange=(t,s,n)=>{e=t,i=s,l=n},t.addEventListener("input",()=>t.set(t.value)),t.addEventListener("paste",e=>{t.set(e.clipboardData.getData("text")),e.preventDefault()}),t},utils.formatCost=(t,e="$")=>{if(!t)return e+"0.00";let i=t.toFixed(2).split(".");return e+i[0].split("").reverse().reduce((t,e,i)=>"-"==e?e+t:e+(i&&!(i%3)?",":"")+t,"")+"."+i[1]},utils.fromDateTimeBox=t=>{let e=t.value;return e?new Date(e.replace(/-/g,"/").replace(/T/g," ")):new Date},utils.fixedNum=function(t,e,i=10){let l=Math.abs(t).toString(i).toUpperCase();return(t<0?"-":"")+(16==i?"0x":2==i?"0b":"")+"0".repeat(Math.max(e-l.length,0))+l},utils.months=["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"],utils.toDateTimeBox=(t,e)=>t.getFullYear()+"-"+d(t.getMonth()+1)+"-"+d(t.getDate())+"T"+d(t.getHours())+":"+d(t.getMinutes())+(e?":"+d(t.getSeconds()):""),utils.formatDate=(t,e={})=>{let i="",l,s;if(null==t||!t.getDate||!((l=t.getFullYear())>1969))return"[Invalid Date]";if(null==e.time||e.time){let n=t.getHours(),r="";if(e.h24||(r=" AM",n>=12&&(r=" PM",n-=12),n||(n=12)),i=n+":"+d(t.getMinutes())+(e.sec?":"+d(t.getSeconds())+(e.ms?(t.getMilliseconds()/1e3).toFixed(Number.isFinite(e.ms)?e.ms:3).substr(1):""):""),i+=r,e.time)return i}return s=t.getDate(),s=utils.months[t.getMonth()]+" "+(null==e.suf||e.suf?utils.suffix(s):s),(null==e.year||e.year)&&e.year!==l&&(s=s+", "+l),e.df?s+(i&&" "+i):(i&&i+" ")+s},utils.suffix=t=>{let e=t%10,i=t%100;return 1==e&&11!=i?t+"st":2==e&&12!=i?t+"nd":3==e&&13!=i?t+"rd":t+"th"};let f=e;function c(t){utils.onNav&&utils.onNav.call(null,t)}function h(t,e,i){let s=this.length,n=Math.max(e<0?s+e:e||0,0),r;for(null!=i&&(s=Math.min(i<0?s+i:i,s));n<s;++n)if("!"===(r=t(this[n],n,s)))this instanceof l?this[n].remove():this.splice(n,1),--n,--s;else if(null!=r)return r}async function p(t,e,i,s=!0){let n=this.length,r=e=Math.max(e<0?n+e:e||0,0),o,u=[];for(null!=i&&(n=Math.min(i<0?n+i:i,n));r<n;++r){if(o=t(this[r],r,n),!s&&"!"!==(o=await o)&&null!=o)return o;u.push(o)}for(s&&(u=await Promise.all(u)),r=e,o=0;r<n;++r,++o)if("!"===u[o])this instanceof l?this[r].remove():this.splice(r,1),--r,--n;else if(null!=u[o])return u[o]}utils.goBack=f.back.bind(f),utils.goForward=f.forward.bind(f),utils.go=(t,e)=>{f.pushState(e,"",t||location.pathname),c(e)},r("popstate",t=>c(t.state)),r("load",()=>setTimeout(c.wrap(f.state),1)),utils.mkEl=(t,e,i,l,s)=>{let n=document.createElement(t);if(null!=i&&(n.className=i),null!=s&&(n.innerHTML=s),l&&"object"==typeof l)for(let r in l)r in n.style?n.style[r]=l[r]:n.style.setProperty(r,l[r]);return null!=e&&e.appendChild(n),n},utils.mkDiv=(t,e,i,l)=>utils.mkEl("div",t,e,i,l),utils.addText=(t,e)=>t.appendChild(document.createTextNode(e)),utils.textWidth=(e,i)=>{let l=t.TWCanvas||(t.TWCanvas=utils.mkEl("canvas")),s=l.getContext("2d");return s.font=i,s.measureText(e).width},utils.define(utils,"w",()=>innerWidth),utils.define(utils,"h",()=>innerHeight),utils.setPropSafe=(t,e,i,l=!1)=>{"string"==typeof e&&(e=e.split("."));let s=e.length-1;return(e.each(e=>{t="object"==typeof t[e]?t[e]:t[e]={}},0,s),s=e[s],l&&null!=t[s])?t[s]:t[s]=i},utils.getPropSafe=(t,e)=>{"string"==typeof e&&(e=e.split("."));try{for(let i of e)t=t[i];return t}catch(l){}},utils.proto(Array,"clean",function(t){for(let e=0,i,l=this.length;e<l;++e)i=this[e],(utils.isBlank(i)||!1===i||!t&&0===i)&&(this.splice(e--,1),l--);return this}),utils.proto(Array,"remove",function(t){let e=this.indexOf(t);return -1!=e&&(this.splice(e,1),!0)}),[Array,l,n].forEach(t=>{utils.proto(t,"each",h),utils.proto(t,"eachAsync",p)});let $="ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/",_=$.replace("+/","-_"),g={43:62,47:63,48:52,49:53,50:54,51:55,52:56,53:57,54:58,55:59,56:60,57:61,65:0,66:1,67:2,68:3,69:4,70:5,71:6,72:7,73:8,74:9,75:10,76:11,77:12,78:13,79:14,80:15,81:16,82:17,83:18,84:19,85:20,86:21,87:22,88:23,89:24,90:25,97:26,98:27,99:28,100:29,101:30,102:31,103:32,104:33,105:34,106:35,107:36,108:37,109:38,110:39,111:40,112:41,113:42,114:43,115:44,116:45,117:46,118:47,119:48,120:49,121:50,122:51,45:62,95:63};function m(t,e){if(null==(t=g[t.charCodeAt(e)]))throw"Bad char at "+e;return t}"toBase64"in Uint8Array.prototype||utils.proto(Uint8Array,"toBase64",function(t){let e=this.byteLength,i=e%3,l=t&&"base64url"===t.alphabet?_:$,s=0,n="",r;for(e-=i;s<e;s+=3)n+=l[(16515072&(r=this[s]<<16|this[s+1]<<8|this[s+2]))>>18]+l[(258048&r)>>12]+l[(4032&r)>>6]+l[63&r];return 1==i?(n+=l[(252&(r=this[e]))>>2]+l[(3&r)<<4],t&&t.omitPadding||(n+="=")):2!=i||(n+=l[(64512&(r=this[e]<<8|this[e+1]))>>10]+l[(1008&r)>>4]+l[(15&r)<<2],t&&t.omitPadding||(n+="==")),n}),"fromBase64"in Uint8Array||utils.proto(Uint8Array,"fromBase64",t=>{let e=t.length,i=e-1;for(;i>=0&&61===t.charCodeAt(i);--i);e=i+1,i=0;let l=e%4;if(e-=l,1==l)throw"Bad b64 len";let s=new Uint8Array(3*e/4+(l?l-1:0)),n=-1,r;for(;i<e;i+=4)r=m(t,i)<<18|m(t,i+1)<<12|m(t,i+2)<<6|m(t,i+3),s[++n]=r>>16,s[++n]=r>>8,s[++n]=r;return 2==l?s[++n]=m(t,i)<<2|m(t,i+1)>>4:3==l&&(r=m(t,i)<<10|m(t,i+1)<<4|m(t,i+2)>>2,s[++n]=r>>8,s[++n]=r),s},1);let y=/[|\\{}()[\]^$+*?.]/g,x=/-/g;function b(t,e){for(;t>=e;)t-=e;return t}function C(){let t=document.styleSheets;for(let e=0,i=t.length;e<i;++e)try{return t[e].cssRules,t[e]}catch(l){}let s=utils.mkEl("style",document.head);return utils.addText(s,""),s.sheet}function v(t){return t.replace(/[A-Z]/g,t=>"-"+t.toLowerCase())}"escape"in RegExp||utils.proto(RegExp,"escape",t=>t.replace(y,"\\$&").replace(x,"\\x2d"),1),utils.define(s.prototype,"index",function(){let t=this.parentElement;return t?Array.prototype.indexOf.call(t.children,this):-1}),utils.proto(s,"insertChildAt",function(t,e){e<0&&(e=0),e>=this.children.length?this.appendChild(t):this.insertBefore(t,this.children[e])}),utils.boundingRect=t=>new UtilRect(t.getBoundingClientRect()),utils.innerRect=t=>{let e=t.getBoundingClientRect(),i=getComputedStyle(t);return new UtilRect(e.top+parseFloat(i.paddingTop)+parseFloat(i.borderTopWidth),e.bottom-parseFloat(i.paddingBottom)-parseFloat(i.borderBottomWidth),e.left+parseFloat(i.paddingLeft)+parseFloat(i.borderLeftWidth),e.right-parseFloat(i.paddingRight)-parseFloat(i.borderRightWidth))},utils.define(s.prototype,"boundingRect",function(){return utils.boundingRect(this)}),utils.define(s.prototype,"innerRect",function(){return utils.innerRect(this)}),Math.cot=t=>1/Math.tan(t),utils.isBlank=t=>null==t||("string"==typeof t?!/\S/.test(t):"object"==typeof t&&("number"==typeof t.length?0===t.length:0===Object.keys(t).length)),utils.firstEmpty=t=>{let e=t.length;for(let i=0;i<e;++i)if(null==t[i])return i;return e},utils.firstEmptyChar=t=>{let e=Object.keys(t),i=e.length;for(let l=0;l<i;++l)if(null==t[e[l]])return e[l];return utils.numToChar(i)},utils.numToChar=t=>{if(t<=25)return String.fromCharCode(t+97);if(t>=26&&t<=51)return String.fromCharCode(t+39);let e,i;if(t<2756)e=b(Math.floor(t/52)-1,52),i=b(t,52);else if(t<143364)e=b(Math.floor((t-52)/2704)-1,52),i=b(t-52,2704)+52;else{if(!(t<7454980))return!1;e=b(Math.floor((t-2756)/140608)-1,52),i=b(t-2756,140608)+2756}return utils.numToChar(e)+utils.numToChar(i)},utils.merge=function(t){for(let e=1,i=arguments.length,l,s,n;e<i;++e)for(let r in l=arguments[e]){if(s=t[r],n=l[r],s&&n){if(s.length>=0&&n.length>=0){for(let o=0,u=n.length,a=s.length;o<u;++o)s[o+a]=n[o];continue}if("object"==typeof s&&"object"==typeof n){for(let d in n)s[d]=n[d];continue}}t[r]=n}return t},utils.bounds=(t,e=0,i=1)=>t>=e?t<=i?t:i:e,utils.norm=utils.normalize=(t,e=0,i=1)=>{let l=Math.abs(i-e);if(t<e)for(;t<e;)t+=l;else for(;t>=i;)t-=l;return t},utils.cutStr=(t,e)=>{let i;for(;-1!=(i=t.indexOf(e));)t=t.slice(0,i)+t.slice(i+e.length);return t},utils.dCut=(t,e,i,l,s)=>{let n=t.indexOf(e,s||void 0)+e.length,r=t.indexOf(i,n);return l&&(l.s=n,l.t=r),n<e.length||r<=n?"":t.substring(n,r)},utils.dCutToLast=(t,e,i,l,s)=>{let n=t.indexOf(e,s||void 0)+e.length,r=t.lastIndexOf(i);return l&&(l.s=n,l.t=r),n<e.length||r<=n?"":t.substring(n,r)},utils.dCutLast=(t,e,i,l,s)=>{let n=t.lastIndexOf(e,s||void 0)+e.length,r=t.indexOf(i,n);return l&&(l.s=n,l.t=r),n<e.length||r<=n?"":t.substring(n,r)},utils.parseCSS=t=>{let e={},i="",l=0;function s(t){if(-1!==t.indexOf(",")){let e=utils.clean(t.split(","));for(let i=0,l=e.length;i<l;++i)e[i]=e[i].trim();return e}return t.trim()}for(t=t.trim();t.length>0;)if("("==t[0]&&-1!==t.indexOf(")")&&i){let n=t.indexOf(")"),r=t.substring(1,n);e[i]=s(r),i="",t=t.substring(n+1)}else if(0==t.search(/[#!\w]/)){i&&(e[l++]=i);let o=t.search(/[^#!\w-%]/);-1==o&&(o=t.length),i=t.substring(0,o),t=t.substring(o)}else t=t.substring(1);return i&&(e[l]=i),e},utils.buildCSS=t=>{let e=Object.keys(t),i=e.length,l="",s=0;for(;s<i;){let n=e[s],r=t[e[s]];++s,0<=Number(n)?l+=r+" ":l+=`${n}(${r}) `}return l.substring(0,l.length-1)},utils.addClass=(t,e)=>{let i=C(),l=Object.keys(e),s="";for(let n=0,r=l.length;n<r;++n)s+=v(l[n])+":"+e[l[n]]+";";i.addRule("."+t,s)},utils.addId=(t,e)=>{let i=C(),l=Object.keys(e),s="";for(let n=0,r=l.length;n<r;++n)s+=v(l[n])+":"+e[l[n]]+";";i.addRule("#"+t,s)},utils.addKeyframe=(t,e)=>{C().addRule("@keyframes "+t,e)},utils.removeSelector=t=>{for(let e=0,i,l,s=document.styleSheets.length;e<s;++e){i=document.styleSheets[e];try{l=i.cssRules}catch(n){continue}for(let r in l)"CSSStyleRule"==l[r].constructor.name&&l[r].selectorText==t&&i.deleteRule(r)}},utils.getId=(t,e)=>(e||document).getElementById(t),utils.getClassList=(t,e)=>(e||document).getElementsByClassName(t),utils.getClassFirst=(t,e)=>(t=utils.getClassList(t,e)).length>0?t[0]:0,utils.getClassOnly=(t,e)=>1==(t=utils.getClassList(t,e)).length?t[0]:0,utils.hexToRgb=t=>{let e=parseInt(t.substr(1),16);return[e>>16&255,e>>8&255,255&e]},utils.rand=(t,e,i,l)=>{e*=i=i||1,t*=i;let s=Math.random();return Math.round((l?l(s):s)*(e-t)+t)/i},utils.fromQuery=t=>{function e(t,i){let l=i[0],s=l.indexOf("="),n=decodeURIComponent(l.substr(0,s)),r=decodeURIComponent(l.substr(s+1));return null==t[n]?t[n]=r:"array"==typeof t[n]?t[n].push(r):t[n]=[t[n],r],1==i.length?t:e(t,i.slice(1))}return t.startsWith("?")&&(t=t.substr(1)),0==t.length?{}:e({},t.split("&"))},utils.toQuery=t=>{let e="";if("object"!=typeof t)return encodeURIComponent(t);for(let i in t){let l=t[i];"object"==typeof l&&(l=JSON.stringify(l)),e+="&"+i+"="+encodeURIComponent(l)}return e.slice(1)},utils.center=(t,e,i)=>{let l=t.style;if(l.position||(l.position="absolute"),"calc"==i)e&&"x"!=e||(l.left=`calc(50% - ${t.clientWidth/2}px)`),e&&"y"!=e||(l.top=`calc(50% - ${t.clientHeight/2}px)`);else if("move"==i)e&&"x"!=e||(l.left=`${utils.w/2-t.clientWidth/2}px`),e&&"y"!=e||(l.top=`${utils.h/2-t.clientHeight/2}px`);else if("trans"==i){let s=utils.cutStr(l.transform,"translateX(-50%)");s=utils.cutStr(s,"translateY(-50%)"),e&&"x"!=e||(l.left="50%",s+="translateX(-50%)"),e&&"y"!=e||(l.top="50%",s+="translateY(-50%)"),s&&(l.transform=s)}else utils.mkEl("div",t.parentNode,null,{display:"table",position:"absolute",top:0,left:0,width:"100%",height:"100%"}).appendChild(t),l.display="table-cell",e&&"x"!=e||(l.textAlign="center"),e&&"y"!=e||(l.verticalAlign="middle"),l.position="relative"},utils.loadAjax=(t,e,i,l,s)=>{let n;try{n=new XMLHttpRequest}catch(r){return e(r)}if(s)for(let o in s)n.setRequestHeader(o,s[o]);n.open(i||"GET",t),n.onreadystatechange=()=>{let t=n.status||-1;n.readyState==n.DONE&&e(200==t?0:t,n.response,n)},n.send(l||void 0)},utils.loadFile=(t,e,i)=>{let l=utils.mkEl("object",document.body,null,{position:"fixed",opacity:0});l.data=t;let s=setTimeout(()=>{l.remove(),s=null,e(!1)},i||4e3);l.onload=()=>{s&&(clearTimeout(s),e(l.contentDocument.documentElement.outerHTML),l.remove())}},utils.loadJSONP=(t,e,i)=>{let l=utils.mkEl("script",document.head),s=utils.firstEmptyChar(utils.lJSONCall);l.type="application/javascript",l.src=t+(-1==t.indexOf("?")?"?":"&")+"callback=utils.lJSONCall."+s;let n=setTimeout(()=>{delete utils.lJSONCall[s],e(!1)},i||4e3);utils.lJSONCall[s]=t=>{n&&clearTimeout(n),delete utils.lJSONCall[s],e(t)},document.head.removeChild(l)},utils.lJSONCall=[],utils.dlFile=(t,e)=>fetch(e).then(t=>{if(200!=t.status)throw"Code "+t.status;return t.blob()}).then(e=>{utils.dlData(t,e)}),utils.dlData=(t,e)=>{let i,l=utils.mkEl("a",document.body,null,{display:"none"});"string"==typeof e?i=e:(e instanceof Blob||(e=Blob(e)),i=URL.createObjectURL(e)),l.href=i,l.download=t,l.click(),l.remove(),URL.revokeObjectURL(i)},utils.deg=t=>180*t/Math.PI,utils.rad=t=>t*Math.PI/180,utils.map=(t,e,i,l,s,n)=>{let r=(t-e)/(i-e);return(n?n(r):r)*(s-l)+l},utils.delay=t=>new Promise(e=>setTimeout(e,t)),UtilRect=function(t,e,l,s){if(!(this instanceof UtilRect))return new UtilRect(t,e,l,s);let n=Number.isFinite,r=0,o=0,u=0,a=0;utils.define(this,"x",()=>u,t=>{n(t)&&(a+=t-u,u=t)}),utils.define(this,"y",()=>r,t=>{n(t)&&(o+=t-r,r=t)}),utils.define(this,"top",()=>r,t=>{r=n(t)?t:0}),utils.define(this,["bottom","y2"],()=>o,t=>{o=n(t)?t:0}),utils.define(this,"left",()=>u,t=>{u=n(t)?t:0}),utils.define(this,["right","x2"],()=>a,t=>{a=n(t)?t:0}),utils.define(this,["width","w"],()=>a-u,t=>{a=t>=0?u+t:0}),utils.define(this,["height","h"],()=>o-r,t=>{o=t>=0?r+t:0}),utils.define(this,"centerX",()=>u/2+a/2),utils.define(this,"centerY",()=>r/2+o/2),t instanceof i||t instanceof UtilRect?(r=t.top,o=t.bottom,u=t.left,a=t.right):(r=t,o=e,u=l,a=s)},utils.proto(UtilRect,"contains",function(t,e){return t instanceof s?this.contains(t.boundingRect):t instanceof UtilRect?t.x>=this.x&&t.x2<=this.x2&&t.y>=this.y&&t.y2<=this.y2:t>=this.x&&t<=this.x2&&e>=this.y&&e<=this.y2}),utils.proto(UtilRect,"overlaps",function(t){if(t instanceof s)return this.overlaps(t.boundingRect);if(!(t instanceof UtilRect))return 0;let e,i;return e=t.x2-t.x>=this.x2-this.x?this.x>=t.x&&this.x<=t.x2||this.x2>=t.x&&this.x2<=t.x2:t.x>=this.x&&t.x<=this.x2||t.x2>=this.x&&t.x2<=this.x2,i=t.y2-t.y>=this.y2-this.y?this.y>=t.y&&this.y<=t.y2||this.y2>=t.y&&this.y2<=t.y2:t.y>=this.y&&t.y<=this.y2||t.y2>=this.y&&t.y2<=this.y2,e&&i}),utils.proto(UtilRect,"dist",function(t,e){if(t instanceof s)return this.dist(t.boundingRect);let i=t instanceof UtilRect;return e=Math.abs((i?t.centerY:e)-this.centerY),Math.sqrt((t=Math.abs((i?t.centerX:t)-this.centerX))*t+e*e)}),utils.proto(UtilRect,"expand",function(t){return this.top-=t,this.left-=t,this.bottom+=t,this.right+=t,this})})();const Easing={linear:t=>t,easeInQuad:t=>t*t,easeOutQuad:t=>t*(2-t),easeInOutQuad:t=>t<.5?2*t*t:-1+(4-2*t)*t,easeInCubic:t=>t*t*t,easeOutCubic:t=>--t*t*t+1,easeInOutCubic:t=>t<.5?4*t*t*t:(t-1)*(2*t-2)*(2*t-2)+1,easeInQuart:t=>t*t*t*t,easeOutQuart:t=>1- --t*t*t*t,easeInOutQuart:t=>t<.5?8*t*t*t*t:1-8*--t*t*t*t,easeInQuint:t=>t*t*t*t*t,easeOutQuint:t=>1+--t*t*t*t*t,easeInOutQuint:t=>t<.5?16*t*t*t*t*t:1+16*--t*t*t*t*t};"undefined"!=typeof global&&(global.utils=utils,global.UtilRect=UtilRect,global.Easing=Easing);