zero-query 0.9.1 → 0.9.5
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/README.md +10 -7
- package/cli/commands/dev/devtools/js/elements.js +5 -0
- package/cli/scaffold/app/app.js +1 -1
- package/cli/scaffold/global.css +1 -2
- package/cli/scaffold/index.html +2 -1
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +363 -30
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +46 -2
- package/index.js +32 -4
- package/package.json +1 -1
- package/src/component.js +32 -3
- package/src/core.js +3 -1
- package/src/expression.js +103 -16
- package/src/http.js +6 -2
- package/src/router.js +6 -1
- package/src/utils.js +191 -5
- package/tests/audit.test.js +4030 -0
- package/tests/core.test.js +41 -0
- package/tests/expression.test.js +10 -10
- package/tests/utils.test.js +543 -1
- package/types/utils.d.ts +103 -0
package/dist/zquery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* zQuery (zeroQuery) v0.9.
|
|
2
|
+
* zQuery (zeroQuery) v0.9.5
|
|
3
3
|
* Lightweight Frontend Library
|
|
4
4
|
* https://github.com/tonywied17/zero-query
|
|
5
5
|
* (c) 2026 Anthony Wiedman - MIT License
|
|
@@ -1218,6 +1218,8 @@ query.children = (parentId) => {
|
|
|
1218
1218
|
const p = document.getElementById(parentId);
|
|
1219
1219
|
return new ZQueryCollection(p ? Array.from(p.children) : []);
|
|
1220
1220
|
};
|
|
1221
|
+
query.qs = (sel, ctx = document) => ctx.querySelector(sel);
|
|
1222
|
+
query.qsa = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
|
|
1221
1223
|
|
|
1222
1224
|
// Create element shorthand — returns ZQueryCollection for chaining
|
|
1223
1225
|
query.create = (tag, attrs = {}, ...children) => {
|
|
@@ -1225,7 +1227,7 @@ query.create = (tag, attrs = {}, ...children) => {
|
|
|
1225
1227
|
for (const [k, v] of Object.entries(attrs)) {
|
|
1226
1228
|
if (k === 'class') el.className = v;
|
|
1227
1229
|
else if (k === 'style' && typeof v === 'object') Object.assign(el.style, v);
|
|
1228
|
-
else if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2), v);
|
|
1230
|
+
else if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2).toLowerCase(), v);
|
|
1229
1231
|
else if (k === 'data' && typeof v === 'object') Object.entries(v).forEach(([dk, dv]) => { el.dataset[dk] = dv; });
|
|
1230
1232
|
else el.setAttribute(k, v);
|
|
1231
1233
|
}
|
|
@@ -1445,6 +1447,11 @@ function tokenize(expr) {
|
|
|
1445
1447
|
tokens.push({ t: T.OP, v: ch });
|
|
1446
1448
|
i++; continue;
|
|
1447
1449
|
}
|
|
1450
|
+
// Spread operator: ...
|
|
1451
|
+
if (ch === '.' && i + 2 < len && expr[i + 1] === '.' && expr[i + 2] === '.') {
|
|
1452
|
+
tokens.push({ t: T.OP, v: '...' });
|
|
1453
|
+
i += 3; continue;
|
|
1454
|
+
}
|
|
1448
1455
|
if ('()[]{},.?:'.includes(ch)) {
|
|
1449
1456
|
tokens.push({ t: T.PUNC, v: ch });
|
|
1450
1457
|
i++; continue;
|
|
@@ -1508,7 +1515,7 @@ class Parser {
|
|
|
1508
1515
|
this.next(); // consume ?
|
|
1509
1516
|
const truthy = this.parseExpression(0);
|
|
1510
1517
|
this.expect(T.PUNC, ':');
|
|
1511
|
-
const falsy = this.parseExpression(
|
|
1518
|
+
const falsy = this.parseExpression(0);
|
|
1512
1519
|
left = { type: 'ternary', cond: left, truthy, falsy };
|
|
1513
1520
|
continue;
|
|
1514
1521
|
}
|
|
@@ -1649,7 +1656,12 @@ class Parser {
|
|
|
1649
1656
|
this.expect(T.PUNC, '(');
|
|
1650
1657
|
const args = [];
|
|
1651
1658
|
while (!(this.peek().t === T.PUNC && this.peek().v === ')') && this.peek().t !== T.EOF) {
|
|
1652
|
-
|
|
1659
|
+
if (this.peek().t === T.OP && this.peek().v === '...') {
|
|
1660
|
+
this.next();
|
|
1661
|
+
args.push({ type: 'spread', arg: this.parseExpression(0) });
|
|
1662
|
+
} else {
|
|
1663
|
+
args.push(this.parseExpression(0));
|
|
1664
|
+
}
|
|
1653
1665
|
if (this.peek().t === T.PUNC && this.peek().v === ',') this.next();
|
|
1654
1666
|
}
|
|
1655
1667
|
this.expect(T.PUNC, ')');
|
|
@@ -1725,7 +1737,12 @@ class Parser {
|
|
|
1725
1737
|
this.next();
|
|
1726
1738
|
const elements = [];
|
|
1727
1739
|
while (!(this.peek().t === T.PUNC && this.peek().v === ']') && this.peek().t !== T.EOF) {
|
|
1728
|
-
|
|
1740
|
+
if (this.peek().t === T.OP && this.peek().v === '...') {
|
|
1741
|
+
this.next();
|
|
1742
|
+
elements.push({ type: 'spread', arg: this.parseExpression(0) });
|
|
1743
|
+
} else {
|
|
1744
|
+
elements.push(this.parseExpression(0));
|
|
1745
|
+
}
|
|
1729
1746
|
if (this.peek().t === T.PUNC && this.peek().v === ',') this.next();
|
|
1730
1747
|
}
|
|
1731
1748
|
this.expect(T.PUNC, ']');
|
|
@@ -1737,6 +1754,14 @@ class Parser {
|
|
|
1737
1754
|
this.next();
|
|
1738
1755
|
const properties = [];
|
|
1739
1756
|
while (!(this.peek().t === T.PUNC && this.peek().v === '}') && this.peek().t !== T.EOF) {
|
|
1757
|
+
// Spread in object: { ...obj }
|
|
1758
|
+
if (this.peek().t === T.OP && this.peek().v === '...') {
|
|
1759
|
+
this.next();
|
|
1760
|
+
properties.push({ spread: true, value: this.parseExpression(0) });
|
|
1761
|
+
if (this.peek().t === T.PUNC && this.peek().v === ',') this.next();
|
|
1762
|
+
continue;
|
|
1763
|
+
}
|
|
1764
|
+
|
|
1740
1765
|
const keyTok = this.next();
|
|
1741
1766
|
let key;
|
|
1742
1767
|
if (keyTok.t === T.IDENT || keyTok.t === T.STR) key = keyTok.v;
|
|
@@ -1768,7 +1793,13 @@ class Parser {
|
|
|
1768
1793
|
|
|
1769
1794
|
// new keyword
|
|
1770
1795
|
if (tok.v === 'new') {
|
|
1771
|
-
|
|
1796
|
+
let classExpr = this.parsePrimary();
|
|
1797
|
+
// Handle member access (e.g. ns.MyClass) without consuming call args
|
|
1798
|
+
while (this.peek().t === T.PUNC && this.peek().v === '.') {
|
|
1799
|
+
this.next();
|
|
1800
|
+
const prop = this.next();
|
|
1801
|
+
classExpr = { type: 'member', obj: classExpr, prop: prop.v, computed: false };
|
|
1802
|
+
}
|
|
1772
1803
|
let args = [];
|
|
1773
1804
|
if (this.peek().t === T.PUNC && this.peek().v === '(') {
|
|
1774
1805
|
args = this._parseArgs();
|
|
@@ -1880,6 +1911,12 @@ function evaluate(node, scope) {
|
|
|
1880
1911
|
if (name === 'encodeURIComponent') return encodeURIComponent;
|
|
1881
1912
|
if (name === 'decodeURIComponent') return decodeURIComponent;
|
|
1882
1913
|
if (name === 'console') return console;
|
|
1914
|
+
if (name === 'Map') return Map;
|
|
1915
|
+
if (name === 'Set') return Set;
|
|
1916
|
+
if (name === 'RegExp') return RegExp;
|
|
1917
|
+
if (name === 'Error') return Error;
|
|
1918
|
+
if (name === 'URL') return URL;
|
|
1919
|
+
if (name === 'URLSearchParams') return URLSearchParams;
|
|
1883
1920
|
return undefined;
|
|
1884
1921
|
}
|
|
1885
1922
|
|
|
@@ -1918,10 +1955,21 @@ function evaluate(node, scope) {
|
|
|
1918
1955
|
}
|
|
1919
1956
|
|
|
1920
1957
|
case 'optional_call': {
|
|
1921
|
-
const
|
|
1958
|
+
const calleeNode = node.callee;
|
|
1959
|
+
const args = _evalArgs(node.args, scope);
|
|
1960
|
+
// Method call: obj?.method() — bind `this` to obj
|
|
1961
|
+
if (calleeNode.type === 'member' || calleeNode.type === 'optional_member') {
|
|
1962
|
+
const obj = evaluate(calleeNode.obj, scope);
|
|
1963
|
+
if (obj == null) return undefined;
|
|
1964
|
+
const prop = calleeNode.computed ? evaluate(calleeNode.prop, scope) : calleeNode.prop;
|
|
1965
|
+
if (!_isSafeAccess(obj, prop)) return undefined;
|
|
1966
|
+
const fn = obj[prop];
|
|
1967
|
+
if (typeof fn !== 'function') return undefined;
|
|
1968
|
+
return fn.apply(obj, args);
|
|
1969
|
+
}
|
|
1970
|
+
const callee = evaluate(calleeNode, scope);
|
|
1922
1971
|
if (callee == null) return undefined;
|
|
1923
1972
|
if (typeof callee !== 'function') return undefined;
|
|
1924
|
-
const args = node.args.map(a => evaluate(a, scope));
|
|
1925
1973
|
return callee(...args);
|
|
1926
1974
|
}
|
|
1927
1975
|
|
|
@@ -1931,7 +1979,7 @@ function evaluate(node, scope) {
|
|
|
1931
1979
|
// Only allow safe constructors
|
|
1932
1980
|
if (Ctor === Date || Ctor === Array || Ctor === Map || Ctor === Set ||
|
|
1933
1981
|
Ctor === RegExp || Ctor === Error || Ctor === URL || Ctor === URLSearchParams) {
|
|
1934
|
-
const args = node.args
|
|
1982
|
+
const args = _evalArgs(node.args, scope);
|
|
1935
1983
|
return new Ctor(...args);
|
|
1936
1984
|
}
|
|
1937
1985
|
return undefined;
|
|
@@ -1961,13 +2009,32 @@ function evaluate(node, scope) {
|
|
|
1961
2009
|
return cond ? evaluate(node.truthy, scope) : evaluate(node.falsy, scope);
|
|
1962
2010
|
}
|
|
1963
2011
|
|
|
1964
|
-
case 'array':
|
|
1965
|
-
|
|
2012
|
+
case 'array': {
|
|
2013
|
+
const arr = [];
|
|
2014
|
+
for (const e of node.elements) {
|
|
2015
|
+
if (e.type === 'spread') {
|
|
2016
|
+
const iterable = evaluate(e.arg, scope);
|
|
2017
|
+
if (iterable != null && typeof iterable[Symbol.iterator] === 'function') {
|
|
2018
|
+
for (const v of iterable) arr.push(v);
|
|
2019
|
+
}
|
|
2020
|
+
} else {
|
|
2021
|
+
arr.push(evaluate(e, scope));
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
return arr;
|
|
2025
|
+
}
|
|
1966
2026
|
|
|
1967
2027
|
case 'object': {
|
|
1968
2028
|
const obj = {};
|
|
1969
|
-
for (const
|
|
1970
|
-
|
|
2029
|
+
for (const prop of node.properties) {
|
|
2030
|
+
if (prop.spread) {
|
|
2031
|
+
const source = evaluate(prop.value, scope);
|
|
2032
|
+
if (source != null && typeof source === 'object') {
|
|
2033
|
+
Object.assign(obj, source);
|
|
2034
|
+
}
|
|
2035
|
+
} else {
|
|
2036
|
+
obj[prop.key] = evaluate(prop.value, scope);
|
|
2037
|
+
}
|
|
1971
2038
|
}
|
|
1972
2039
|
return obj;
|
|
1973
2040
|
}
|
|
@@ -1988,12 +2055,30 @@ function evaluate(node, scope) {
|
|
|
1988
2055
|
}
|
|
1989
2056
|
}
|
|
1990
2057
|
|
|
2058
|
+
/**
|
|
2059
|
+
* Evaluate a list of argument AST nodes, flattening any spread elements.
|
|
2060
|
+
*/
|
|
2061
|
+
function _evalArgs(argNodes, scope) {
|
|
2062
|
+
const result = [];
|
|
2063
|
+
for (const a of argNodes) {
|
|
2064
|
+
if (a.type === 'spread') {
|
|
2065
|
+
const iterable = evaluate(a.arg, scope);
|
|
2066
|
+
if (iterable != null && typeof iterable[Symbol.iterator] === 'function') {
|
|
2067
|
+
for (const v of iterable) result.push(v);
|
|
2068
|
+
}
|
|
2069
|
+
} else {
|
|
2070
|
+
result.push(evaluate(a, scope));
|
|
2071
|
+
}
|
|
2072
|
+
}
|
|
2073
|
+
return result;
|
|
2074
|
+
}
|
|
2075
|
+
|
|
1991
2076
|
/**
|
|
1992
2077
|
* Resolve and execute a function call safely.
|
|
1993
2078
|
*/
|
|
1994
2079
|
function _resolveCall(node, scope) {
|
|
1995
2080
|
const callee = node.callee;
|
|
1996
|
-
const args = node.args
|
|
2081
|
+
const args = _evalArgs(node.args, scope);
|
|
1997
2082
|
|
|
1998
2083
|
// Method call: obj.method() — bind `this` to obj
|
|
1999
2084
|
if (callee.type === 'member' || callee.type === 'optional_member') {
|
|
@@ -2067,8 +2152,9 @@ function _evalBinary(node, scope) {
|
|
|
2067
2152
|
* @returns {*} — evaluation result, or undefined on error
|
|
2068
2153
|
*/
|
|
2069
2154
|
|
|
2070
|
-
// AST cache — avoids re-tokenizing and re-parsing the same expression
|
|
2071
|
-
//
|
|
2155
|
+
// AST cache (LRU) — avoids re-tokenizing and re-parsing the same expression.
|
|
2156
|
+
// Uses Map insertion-order: on hit, delete + re-set moves entry to the end.
|
|
2157
|
+
// Eviction removes the least-recently-used (first) entry when at capacity.
|
|
2072
2158
|
const _astCache = new Map();
|
|
2073
2159
|
const _AST_CACHE_MAX = 512;
|
|
2074
2160
|
|
|
@@ -2088,9 +2174,12 @@ function safeEval(expr, scope) {
|
|
|
2088
2174
|
// Fall through to full parser for built-in globals (Math, JSON, etc.)
|
|
2089
2175
|
}
|
|
2090
2176
|
|
|
2091
|
-
// Check AST cache
|
|
2177
|
+
// Check AST cache (LRU: move to end on hit)
|
|
2092
2178
|
let ast = _astCache.get(trimmed);
|
|
2093
|
-
if (
|
|
2179
|
+
if (ast) {
|
|
2180
|
+
_astCache.delete(trimmed);
|
|
2181
|
+
_astCache.set(trimmed, ast);
|
|
2182
|
+
} else {
|
|
2094
2183
|
const tokens = tokenize(trimmed);
|
|
2095
2184
|
const parser = new Parser(tokens, scope);
|
|
2096
2185
|
ast = parser.parse();
|
|
@@ -3220,15 +3309,44 @@ class Component {
|
|
|
3220
3309
|
const handler = (e) => {
|
|
3221
3310
|
// Read bindings from live map — always up to date after re-renders
|
|
3222
3311
|
const currentBindings = this._eventBindings?.get(event) || [];
|
|
3223
|
-
|
|
3224
|
-
|
|
3312
|
+
|
|
3313
|
+
// Collect matching bindings with their matched elements, then sort
|
|
3314
|
+
// deepest-first so .stop correctly prevents ancestor handlers
|
|
3315
|
+
// (mimics real DOM bubbling order within delegated events).
|
|
3316
|
+
const hits = [];
|
|
3317
|
+
for (const binding of currentBindings) {
|
|
3318
|
+
const matched = e.target.closest(binding.selector);
|
|
3319
|
+
if (!matched) continue;
|
|
3320
|
+
hits.push({ ...binding, matched });
|
|
3321
|
+
}
|
|
3322
|
+
hits.sort((a, b) => {
|
|
3323
|
+
if (a.matched === b.matched) return 0;
|
|
3324
|
+
return a.matched.contains(b.matched) ? 1 : -1;
|
|
3325
|
+
});
|
|
3326
|
+
|
|
3327
|
+
let stoppedAt = null; // Track elements that called .stop
|
|
3328
|
+
for (const { selector, methodExpr, modifiers, el, matched } of hits) {
|
|
3329
|
+
|
|
3330
|
+
// In delegated events, .stop should prevent ancestor bindings from
|
|
3331
|
+
// firing — stopPropagation alone only stops real DOM bubbling.
|
|
3332
|
+
if (stoppedAt) {
|
|
3333
|
+
let blocked = false;
|
|
3334
|
+
for (const stopped of stoppedAt) {
|
|
3335
|
+
if (matched.contains(stopped) && matched !== stopped) { blocked = true; break; }
|
|
3336
|
+
}
|
|
3337
|
+
if (blocked) continue;
|
|
3338
|
+
}
|
|
3225
3339
|
|
|
3226
3340
|
// .self — only fire if target is the element itself
|
|
3227
3341
|
if (modifiers.includes('self') && e.target !== el) continue;
|
|
3228
3342
|
|
|
3229
3343
|
// Handle modifiers
|
|
3230
3344
|
if (modifiers.includes('prevent')) e.preventDefault();
|
|
3231
|
-
if (modifiers.includes('stop'))
|
|
3345
|
+
if (modifiers.includes('stop')) {
|
|
3346
|
+
e.stopPropagation();
|
|
3347
|
+
if (!stoppedAt) stoppedAt = [];
|
|
3348
|
+
stoppedAt.push(matched);
|
|
3349
|
+
}
|
|
3232
3350
|
|
|
3233
3351
|
// Build the invocation function
|
|
3234
3352
|
const invoke = (evt) => {
|
|
@@ -4529,7 +4647,12 @@ class Router {
|
|
|
4529
4647
|
if (typeof matched.component === 'string') {
|
|
4530
4648
|
const container = document.createElement(matched.component);
|
|
4531
4649
|
this._el.appendChild(container);
|
|
4532
|
-
|
|
4650
|
+
try {
|
|
4651
|
+
this._instance = mount(container, matched.component, props);
|
|
4652
|
+
} catch (err) {
|
|
4653
|
+
reportError(ErrorCode.COMP_NOT_FOUND, `Failed to mount component for route "${matched.path}"`, { component: matched.component, path: matched.path }, err);
|
|
4654
|
+
return;
|
|
4655
|
+
}
|
|
4533
4656
|
if (_routeStart) window.__zqRenderHook(this._el, performance.now() - _routeStart, 'route', matched.component);
|
|
4534
4657
|
}
|
|
4535
4658
|
// If component is a render function
|
|
@@ -4862,8 +4985,9 @@ async function request(method, url, data, options = {}) {
|
|
|
4862
4985
|
} else {
|
|
4863
4986
|
fetchOpts.signal = controller.signal;
|
|
4864
4987
|
}
|
|
4988
|
+
let _timedOut = false;
|
|
4865
4989
|
if (timeout > 0) {
|
|
4866
|
-
timer = setTimeout(() => controller.abort(), timeout);
|
|
4990
|
+
timer = setTimeout(() => { _timedOut = true; controller.abort(); }, timeout);
|
|
4867
4991
|
}
|
|
4868
4992
|
|
|
4869
4993
|
// Run request interceptors
|
|
@@ -4923,7 +5047,10 @@ async function request(method, url, data, options = {}) {
|
|
|
4923
5047
|
} catch (err) {
|
|
4924
5048
|
if (timer) clearTimeout(timer);
|
|
4925
5049
|
if (err.name === 'AbortError') {
|
|
4926
|
-
|
|
5050
|
+
if (_timedOut) {
|
|
5051
|
+
throw new Error(`Request timeout after ${timeout}ms: ${method} ${fullURL}`);
|
|
5052
|
+
}
|
|
5053
|
+
throw new Error(`Request aborted: ${method} ${fullURL}`);
|
|
4927
5054
|
}
|
|
4928
5055
|
throw err;
|
|
4929
5056
|
}
|
|
@@ -5060,6 +5187,10 @@ function escapeHtml(str) {
|
|
|
5060
5187
|
return String(str).replace(/[&<>"']/g, c => map[c]);
|
|
5061
5188
|
}
|
|
5062
5189
|
|
|
5190
|
+
function stripHtml(str) {
|
|
5191
|
+
return String(str).replace(/<[^>]*>/g, '');
|
|
5192
|
+
}
|
|
5193
|
+
|
|
5063
5194
|
/**
|
|
5064
5195
|
* Template tag for auto-escaping interpolated values
|
|
5065
5196
|
* Usage: $.html`<div>${userInput}</div>`
|
|
@@ -5105,7 +5236,10 @@ function camelCase(str) {
|
|
|
5105
5236
|
* CamelCase to kebab-case
|
|
5106
5237
|
*/
|
|
5107
5238
|
function kebabCase(str) {
|
|
5108
|
-
return str
|
|
5239
|
+
return str
|
|
5240
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
5241
|
+
.replace(/([a-z\d])([A-Z])/g, '$1-$2')
|
|
5242
|
+
.toLowerCase();
|
|
5109
5243
|
}
|
|
5110
5244
|
|
|
5111
5245
|
|
|
@@ -5146,15 +5280,19 @@ function deepMerge(target, ...sources) {
|
|
|
5146
5280
|
/**
|
|
5147
5281
|
* Simple object equality check
|
|
5148
5282
|
*/
|
|
5149
|
-
function isEqual(a, b) {
|
|
5283
|
+
function isEqual(a, b, _seen) {
|
|
5150
5284
|
if (a === b) return true;
|
|
5151
5285
|
if (typeof a !== typeof b) return false;
|
|
5152
5286
|
if (typeof a !== 'object' || a === null || b === null) return false;
|
|
5153
5287
|
if (Array.isArray(a) !== Array.isArray(b)) return false;
|
|
5288
|
+
// Guard against circular references
|
|
5289
|
+
if (!_seen) _seen = new Set();
|
|
5290
|
+
if (_seen.has(a)) return true;
|
|
5291
|
+
_seen.add(a);
|
|
5154
5292
|
const keysA = Object.keys(a);
|
|
5155
5293
|
const keysB = Object.keys(b);
|
|
5156
5294
|
if (keysA.length !== keysB.length) return false;
|
|
5157
|
-
return keysA.every(k => isEqual(a[k], b[k]));
|
|
5295
|
+
return keysA.every(k => isEqual(a[k], b[k], _seen));
|
|
5158
5296
|
}
|
|
5159
5297
|
|
|
5160
5298
|
|
|
@@ -5255,7 +5393,182 @@ class EventBus {
|
|
|
5255
5393
|
clear() { this._handlers.clear(); }
|
|
5256
5394
|
}
|
|
5257
5395
|
|
|
5258
|
-
const bus = new EventBus();
|
|
5396
|
+
const bus = new EventBus();
|
|
5397
|
+
|
|
5398
|
+
|
|
5399
|
+
// ---------------------------------------------------------------------------
|
|
5400
|
+
// Array utilities
|
|
5401
|
+
// ---------------------------------------------------------------------------
|
|
5402
|
+
|
|
5403
|
+
function range(startOrEnd, end, step) {
|
|
5404
|
+
let s, e, st;
|
|
5405
|
+
if (end === undefined) { s = 0; e = startOrEnd; st = 1; }
|
|
5406
|
+
else { s = startOrEnd; e = end; st = step !== undefined ? step : 1; }
|
|
5407
|
+
if (st === 0) return [];
|
|
5408
|
+
const result = [];
|
|
5409
|
+
if (st > 0) { for (let i = s; i < e; i += st) result.push(i); }
|
|
5410
|
+
else { for (let i = s; i > e; i += st) result.push(i); }
|
|
5411
|
+
return result;
|
|
5412
|
+
}
|
|
5413
|
+
|
|
5414
|
+
function unique(arr, keyFn) {
|
|
5415
|
+
if (!keyFn) return [...new Set(arr)];
|
|
5416
|
+
const seen = new Set();
|
|
5417
|
+
return arr.filter(item => {
|
|
5418
|
+
const k = keyFn(item);
|
|
5419
|
+
if (seen.has(k)) return false;
|
|
5420
|
+
seen.add(k);
|
|
5421
|
+
return true;
|
|
5422
|
+
});
|
|
5423
|
+
}
|
|
5424
|
+
|
|
5425
|
+
function chunk(arr, size) {
|
|
5426
|
+
const result = [];
|
|
5427
|
+
for (let i = 0; i < arr.length; i += size) result.push(arr.slice(i, i + size));
|
|
5428
|
+
return result;
|
|
5429
|
+
}
|
|
5430
|
+
|
|
5431
|
+
function groupBy(arr, keyFn) {
|
|
5432
|
+
const result = {};
|
|
5433
|
+
for (const item of arr) {
|
|
5434
|
+
const k = keyFn(item);
|
|
5435
|
+
(result[k] ??= []).push(item);
|
|
5436
|
+
}
|
|
5437
|
+
return result;
|
|
5438
|
+
}
|
|
5439
|
+
|
|
5440
|
+
|
|
5441
|
+
// ---------------------------------------------------------------------------
|
|
5442
|
+
// Object utilities
|
|
5443
|
+
// ---------------------------------------------------------------------------
|
|
5444
|
+
|
|
5445
|
+
function pick(obj, keys) {
|
|
5446
|
+
const result = {};
|
|
5447
|
+
for (const k of keys) { if (k in obj) result[k] = obj[k]; }
|
|
5448
|
+
return result;
|
|
5449
|
+
}
|
|
5450
|
+
|
|
5451
|
+
function omit(obj, keys) {
|
|
5452
|
+
const exclude = new Set(keys);
|
|
5453
|
+
const result = {};
|
|
5454
|
+
for (const k of Object.keys(obj)) { if (!exclude.has(k)) result[k] = obj[k]; }
|
|
5455
|
+
return result;
|
|
5456
|
+
}
|
|
5457
|
+
|
|
5458
|
+
function getPath(obj, path, fallback) {
|
|
5459
|
+
const keys = path.split('.');
|
|
5460
|
+
let cur = obj;
|
|
5461
|
+
for (const k of keys) {
|
|
5462
|
+
if (cur == null || typeof cur !== 'object') return fallback;
|
|
5463
|
+
cur = cur[k];
|
|
5464
|
+
}
|
|
5465
|
+
return cur === undefined ? fallback : cur;
|
|
5466
|
+
}
|
|
5467
|
+
|
|
5468
|
+
function setPath(obj, path, value) {
|
|
5469
|
+
const keys = path.split('.');
|
|
5470
|
+
let cur = obj;
|
|
5471
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
5472
|
+
const k = keys[i];
|
|
5473
|
+
if (cur[k] == null || typeof cur[k] !== 'object') cur[k] = {};
|
|
5474
|
+
cur = cur[k];
|
|
5475
|
+
}
|
|
5476
|
+
cur[keys[keys.length - 1]] = value;
|
|
5477
|
+
return obj;
|
|
5478
|
+
}
|
|
5479
|
+
|
|
5480
|
+
function isEmpty(val) {
|
|
5481
|
+
if (val == null) return true;
|
|
5482
|
+
if (typeof val === 'string' || Array.isArray(val)) return val.length === 0;
|
|
5483
|
+
if (val instanceof Map || val instanceof Set) return val.size === 0;
|
|
5484
|
+
if (typeof val === 'object') return Object.keys(val).length === 0;
|
|
5485
|
+
return false;
|
|
5486
|
+
}
|
|
5487
|
+
|
|
5488
|
+
|
|
5489
|
+
// ---------------------------------------------------------------------------
|
|
5490
|
+
// String utilities
|
|
5491
|
+
// ---------------------------------------------------------------------------
|
|
5492
|
+
|
|
5493
|
+
function capitalize(str) {
|
|
5494
|
+
if (!str) return '';
|
|
5495
|
+
return str[0].toUpperCase() + str.slice(1).toLowerCase();
|
|
5496
|
+
}
|
|
5497
|
+
|
|
5498
|
+
function truncate(str, maxLen, suffix = '…') {
|
|
5499
|
+
if (str.length <= maxLen) return str;
|
|
5500
|
+
const end = Math.max(0, maxLen - suffix.length);
|
|
5501
|
+
return str.slice(0, end) + suffix;
|
|
5502
|
+
}
|
|
5503
|
+
|
|
5504
|
+
|
|
5505
|
+
// ---------------------------------------------------------------------------
|
|
5506
|
+
// Number utilities
|
|
5507
|
+
// ---------------------------------------------------------------------------
|
|
5508
|
+
|
|
5509
|
+
function clamp(val, min, max) {
|
|
5510
|
+
return val < min ? min : val > max ? max : val;
|
|
5511
|
+
}
|
|
5512
|
+
|
|
5513
|
+
|
|
5514
|
+
// ---------------------------------------------------------------------------
|
|
5515
|
+
// Function utilities
|
|
5516
|
+
// ---------------------------------------------------------------------------
|
|
5517
|
+
|
|
5518
|
+
function memoize(fn, keyFnOrOpts) {
|
|
5519
|
+
let keyFn, maxSize = 0;
|
|
5520
|
+
if (typeof keyFnOrOpts === 'function') keyFn = keyFnOrOpts;
|
|
5521
|
+
else if (keyFnOrOpts && typeof keyFnOrOpts === 'object') maxSize = keyFnOrOpts.maxSize || 0;
|
|
5522
|
+
|
|
5523
|
+
const cache = new Map();
|
|
5524
|
+
|
|
5525
|
+
const memoized = (...args) => {
|
|
5526
|
+
const key = keyFn ? keyFn(...args) : args[0];
|
|
5527
|
+
if (cache.has(key)) return cache.get(key);
|
|
5528
|
+
const result = fn(...args);
|
|
5529
|
+
cache.set(key, result);
|
|
5530
|
+
if (maxSize > 0 && cache.size > maxSize) {
|
|
5531
|
+
cache.delete(cache.keys().next().value);
|
|
5532
|
+
}
|
|
5533
|
+
return result;
|
|
5534
|
+
};
|
|
5535
|
+
|
|
5536
|
+
memoized.clear = () => cache.clear();
|
|
5537
|
+
return memoized;
|
|
5538
|
+
}
|
|
5539
|
+
|
|
5540
|
+
|
|
5541
|
+
// ---------------------------------------------------------------------------
|
|
5542
|
+
// Async utilities
|
|
5543
|
+
// ---------------------------------------------------------------------------
|
|
5544
|
+
|
|
5545
|
+
function retry(fn, opts = {}) {
|
|
5546
|
+
const { attempts = 3, delay = 1000, backoff = 1 } = opts;
|
|
5547
|
+
return new Promise((resolve, reject) => {
|
|
5548
|
+
let attempt = 0, currentDelay = delay;
|
|
5549
|
+
const tryOnce = () => {
|
|
5550
|
+
attempt++;
|
|
5551
|
+
fn(attempt).then(resolve, (err) => {
|
|
5552
|
+
if (attempt >= attempts) return reject(err);
|
|
5553
|
+
const d = currentDelay;
|
|
5554
|
+
currentDelay *= backoff;
|
|
5555
|
+
setTimeout(tryOnce, d);
|
|
5556
|
+
});
|
|
5557
|
+
};
|
|
5558
|
+
tryOnce();
|
|
5559
|
+
});
|
|
5560
|
+
}
|
|
5561
|
+
|
|
5562
|
+
function timeout(promise, ms, message) {
|
|
5563
|
+
let timer;
|
|
5564
|
+
const race = Promise.race([
|
|
5565
|
+
promise,
|
|
5566
|
+
new Promise((_, reject) => {
|
|
5567
|
+
timer = setTimeout(() => reject(new Error(message || `Timed out after ${ms}ms`)), ms);
|
|
5568
|
+
})
|
|
5569
|
+
]);
|
|
5570
|
+
return race.finally(() => clearTimeout(timer));
|
|
5571
|
+
}
|
|
5259
5572
|
|
|
5260
5573
|
// --- index.js (assembly) ------------------------------------------
|
|
5261
5574
|
/**
|
|
@@ -5314,6 +5627,8 @@ Object.defineProperty($, 'name', {
|
|
|
5314
5627
|
value: query.name, writable: true, configurable: true
|
|
5315
5628
|
});
|
|
5316
5629
|
$.children = query.children;
|
|
5630
|
+
$.qs = query.qs;
|
|
5631
|
+
$.qsa = query.qsa;
|
|
5317
5632
|
|
|
5318
5633
|
// --- Collection selector ---------------------------------------------------
|
|
5319
5634
|
/**
|
|
@@ -5382,8 +5697,10 @@ $.pipe = pipe;
|
|
|
5382
5697
|
$.once = once;
|
|
5383
5698
|
$.sleep = sleep;
|
|
5384
5699
|
$.escapeHtml = escapeHtml;
|
|
5700
|
+
$.stripHtml = stripHtml;
|
|
5385
5701
|
$.html = html;
|
|
5386
5702
|
$.trust = trust;
|
|
5703
|
+
$.TrustedHTML = TrustedHTML;
|
|
5387
5704
|
$.uuid = uuid;
|
|
5388
5705
|
$.camelCase = camelCase;
|
|
5389
5706
|
$.kebabCase = kebabCase;
|
|
@@ -5394,7 +5711,23 @@ $.param = param;
|
|
|
5394
5711
|
$.parseQuery = parseQuery;
|
|
5395
5712
|
$.storage = storage;
|
|
5396
5713
|
$.session = session;
|
|
5714
|
+
$.EventBus = EventBus;
|
|
5397
5715
|
$.bus = bus;
|
|
5716
|
+
$.range = range;
|
|
5717
|
+
$.unique = unique;
|
|
5718
|
+
$.chunk = chunk;
|
|
5719
|
+
$.groupBy = groupBy;
|
|
5720
|
+
$.pick = pick;
|
|
5721
|
+
$.omit = omit;
|
|
5722
|
+
$.getPath = getPath;
|
|
5723
|
+
$.setPath = setPath;
|
|
5724
|
+
$.isEmpty = isEmpty;
|
|
5725
|
+
$.capitalize = capitalize;
|
|
5726
|
+
$.truncate = truncate;
|
|
5727
|
+
$.clamp = clamp;
|
|
5728
|
+
$.memoize = memoize;
|
|
5729
|
+
$.retry = retry;
|
|
5730
|
+
$.timeout = timeout;
|
|
5398
5731
|
|
|
5399
5732
|
// --- Error handling --------------------------------------------------------
|
|
5400
5733
|
$.onError = onError;
|
|
@@ -5404,8 +5737,8 @@ $.guardCallback = guardCallback;
|
|
|
5404
5737
|
$.validate = validate;
|
|
5405
5738
|
|
|
5406
5739
|
// --- Meta ------------------------------------------------------------------
|
|
5407
|
-
$.version = '0.9.
|
|
5408
|
-
$.libSize = '~
|
|
5740
|
+
$.version = '0.9.5';
|
|
5741
|
+
$.libSize = '~98 KB';
|
|
5409
5742
|
$.meta = {}; // populated at build time by CLI bundler
|
|
5410
5743
|
|
|
5411
5744
|
$.noConflict = () => {
|