zero-query 0.9.5 → 0.9.7
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 +6 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +107 -8
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +1 -1
- package/index.js +1 -0
- package/package.json +1 -1
- package/src/component.js +66 -5
- package/src/http.js +37 -0
- package/tests/component.test.js +1185 -0
- package/tests/http.test.js +200 -0
- package/types/http.d.ts +15 -4
package/README.md
CHANGED
|
@@ -15,19 +15,19 @@
|
|
|
15
15
|
|
|
16
16
|
</p>
|
|
17
17
|
|
|
18
|
-
> **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit — all in a single ~
|
|
18
|
+
> **Lightweight, zero-dependency frontend library that combines jQuery-style DOM manipulation with a modern reactive component system, SPA router, global state management, HTTP client, and utility toolkit — all in a single ~100 KB minified browser bundle. Works out of the box with ES modules. An optional CLI bundler is available for single-file production builds.**
|
|
19
19
|
|
|
20
20
|
## Features
|
|
21
21
|
|
|
22
22
|
| Module | Highlights |
|
|
23
23
|
| --- | --- |
|
|
24
|
-
| **Components** | Reactive state, template literals, `@event` delegation (
|
|
24
|
+
| **Components** | Reactive state, template literals, `@event` delegation (22 modifiers — key filters, system keys, `.outside`, timing, and more), `z-model` two-way binding (with `z-trim`, `z-number`, `z-lazy`, `z-debounce`, `z-uppercase`, `z-lowercase`), computed properties, watch callbacks, slot-based content projection, directives (`z-if`/`z-else-if`/`z-else`, `z-for`, `z-show`, `z-bind`/`:attr`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip`), DOM morphing engine with LIS-based keyed reconciliation (no innerHTML rebuild), CSP-safe expression evaluation with AST caching, scoped styles, external templates (`templateUrl` / `styleUrl`), lifecycle hooks, auto-injected base styles |
|
|
25
25
|
| **Router** | History & hash mode, route params (`:id`), wildcards, guards (`beforeEach`/`afterEach`), lazy loading, `z-link` navigation, `z-to-top` scroll modifier (`instant`/`smooth`), sub-route history substates (`pushSubstate`/`onSubstate`) |
|
|
26
26
|
| **Directives** | `z-if`, `z-for`, `z-model`, `z-show`, `z-bind`, `z-class`, `z-style`, `z-text`, `z-html`, `z-ref`, `z-cloak`, `z-pre`, `z-key`, `z-skip` — 17 built-in template directives |
|
|
27
27
|
| **Reactive** | Deep proxy reactivity, Signals (`.value`, `.peek()`), computed values, effects (auto-tracked with dispose) |
|
|
28
28
|
| **Store** | Reactive global state, named actions, computed getters, middleware, subscriptions, action history, snapshots |
|
|
29
29
|
| **Selectors & DOM** | jQuery-like chainable selectors, traversal, DOM manipulation, events, animation |
|
|
30
|
-
| **HTTP** | Fetch wrapper with auto-JSON, interceptors, timeout/abort, base URL |
|
|
30
|
+
| **HTTP** | Fetch wrapper with auto-JSON, interceptors (with unsubscribe & clear), HEAD requests, parallel requests (`http.all`), config inspection (`getConfig`), timeout/abort, base URL |
|
|
31
31
|
| **Utils** | debounce, throttle, pipe, once, sleep, memoize, escapeHtml, stripHtml, uuid, capitalize, truncate, range, chunk, groupBy, unique, pick, omit, getPath/setPath, isEmpty, clamp, retry, timeout, deepClone, deepMerge, storage/session wrappers, event bus |
|
|
32
32
|
| **Dev Tools** | CLI dev server with live-reload, CSS hot-swap, full-screen error overlay, floating toolbar, dark-themed inspector panel (Router view, DOM tree, network log, component viewer, performance dashboard), fetch interceptor, render instrumentation, CLI bundler for single-file production builds |
|
|
33
33
|
|
|
@@ -75,7 +75,7 @@ If you prefer **zero tooling**, download `dist/zquery.min.js` from the [dist/ fo
|
|
|
75
75
|
git clone https://github.com/tonywied17/zero-query.git
|
|
76
76
|
cd zero-query
|
|
77
77
|
npx zquery build
|
|
78
|
-
# → dist/zquery.min.js (~
|
|
78
|
+
# → dist/zquery.min.js (~100 KB)
|
|
79
79
|
```
|
|
80
80
|
|
|
81
81
|
### Include in HTML
|
|
@@ -261,7 +261,7 @@ location / {
|
|
|
261
261
|
| `$.style` | Dynamically load global stylesheet file(s) at runtime |
|
|
262
262
|
| `$.router` `$.getRouter` | SPA router |
|
|
263
263
|
| `$.store` `$.getStore` | State management |
|
|
264
|
-
| `$.http` `$.get` `$.post` `$.put` `$.patch` `$.delete` | HTTP client |
|
|
264
|
+
| `$.http` `$.get` `$.post` `$.put` `$.patch` `$.delete` `$.head` | HTTP client |
|
|
265
265
|
| `$.reactive` `$.Signal` `$.signal` `$.computed` `$.effect` | Reactive primitives |
|
|
266
266
|
| `$.debounce` `$.throttle` `$.pipe` `$.once` `$.sleep` `$.memoize` | Function utils |
|
|
267
267
|
| `$.escapeHtml` `$.stripHtml` `$.html` `$.trust` `$.TrustedHTML` `$.uuid` `$.camelCase` `$.kebabCase` `$.capitalize` `$.truncate` | String utils |
|
|
@@ -271,7 +271,7 @@ location / {
|
|
|
271
271
|
| `$.retry` `$.timeout` | Async utils |
|
|
272
272
|
| `$.param` `$.parseQuery` | URL utils |
|
|
273
273
|
| `$.storage` `$.session` | Storage wrappers |
|
|
274
|
-
| `$.EventBus` `$.bus` | Event bus || `$.onError` `$.ZQueryError` `$.ErrorCode` `$.guardCallback` `$.validate` | Error handling || `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~
|
|
274
|
+
| `$.EventBus` `$.bus` | Event bus || `$.onError` `$.ZQueryError` `$.ErrorCode` `$.guardCallback` `$.validate` | Error handling || `$.version` | Library version |\n| `$.libSize` | Minified bundle size string (e.g. `\"~100 KB\"`) |
|
|
275
275
|
| `$.meta` | Build metadata (populated by CLI bundler) |
|
|
276
276
|
| `$.noConflict` | Release `$` global |
|
|
277
277
|
|
package/dist/zquery.dist.zip
CHANGED
|
Binary file
|
package/dist/zquery.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* zQuery (zeroQuery) v0.9.
|
|
2
|
+
* zQuery (zeroQuery) v0.9.7
|
|
3
3
|
* Lightweight Frontend Library
|
|
4
4
|
* https://github.com/tonywied17/zero-query
|
|
5
5
|
* (c) 2026 Anthony Wiedman - MIT License
|
|
@@ -2887,7 +2887,7 @@ class Component {
|
|
|
2887
2887
|
const defaultSlotNodes = [];
|
|
2888
2888
|
[...el.childNodes].forEach(node => {
|
|
2889
2889
|
if (node.nodeType === 1 && node.hasAttribute('slot')) {
|
|
2890
|
-
const slotName = node.getAttribute('slot');
|
|
2890
|
+
const slotName = node.getAttribute('slot') || 'default';
|
|
2891
2891
|
if (!this._slotContent[slotName]) this._slotContent[slotName] = '';
|
|
2892
2892
|
this._slotContent[slotName] += node.outerHTML;
|
|
2893
2893
|
} else if (node.nodeType === 1 || (node.nodeType === 3 && node.textContent.trim())) {
|
|
@@ -3296,6 +3296,24 @@ class Component {
|
|
|
3296
3296
|
for (const [event, bindings] of eventMap) {
|
|
3297
3297
|
this._attachDelegatedEvent(event, bindings);
|
|
3298
3298
|
}
|
|
3299
|
+
|
|
3300
|
+
// .outside — attach a document-level listener for bindings that need
|
|
3301
|
+
// to detect clicks/events outside their element.
|
|
3302
|
+
this._outsideListeners = this._outsideListeners || [];
|
|
3303
|
+
for (const [event, bindings] of eventMap) {
|
|
3304
|
+
for (const binding of bindings) {
|
|
3305
|
+
if (!binding.modifiers.includes('outside')) continue;
|
|
3306
|
+
const outsideHandler = (e) => {
|
|
3307
|
+
if (binding.el.contains(e.target)) return;
|
|
3308
|
+
const match = binding.methodExpr.match(/^(\w+)(?:\(([^)]*)\))?$/);
|
|
3309
|
+
if (!match) return;
|
|
3310
|
+
const fn = this[match[1]];
|
|
3311
|
+
if (typeof fn === 'function') fn.call(this, e);
|
|
3312
|
+
};
|
|
3313
|
+
document.addEventListener(event, outsideHandler, true);
|
|
3314
|
+
this._outsideListeners.push({ event, handler: outsideHandler });
|
|
3315
|
+
}
|
|
3316
|
+
}
|
|
3299
3317
|
}
|
|
3300
3318
|
|
|
3301
3319
|
// Attach a single delegated listener for an event type
|
|
@@ -3340,6 +3358,28 @@ class Component {
|
|
|
3340
3358
|
// .self — only fire if target is the element itself
|
|
3341
3359
|
if (modifiers.includes('self') && e.target !== el) continue;
|
|
3342
3360
|
|
|
3361
|
+
// .outside — only fire if event target is OUTSIDE the element
|
|
3362
|
+
if (modifiers.includes('outside')) {
|
|
3363
|
+
if (el.contains(e.target)) continue;
|
|
3364
|
+
}
|
|
3365
|
+
|
|
3366
|
+
// Key modifiers — filter keyboard events by key
|
|
3367
|
+
const _keyMap = { enter: 'Enter', escape: 'Escape', tab: 'Tab', space: ' ', delete: 'Delete|Backspace', up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight' };
|
|
3368
|
+
let keyFiltered = false;
|
|
3369
|
+
for (const mod of modifiers) {
|
|
3370
|
+
if (_keyMap[mod]) {
|
|
3371
|
+
const keys = _keyMap[mod].split('|');
|
|
3372
|
+
if (!e.key || !keys.includes(e.key)) { keyFiltered = true; break; }
|
|
3373
|
+
}
|
|
3374
|
+
}
|
|
3375
|
+
if (keyFiltered) continue;
|
|
3376
|
+
|
|
3377
|
+
// System key modifiers — require modifier keys to be held
|
|
3378
|
+
if (modifiers.includes('ctrl') && !e.ctrlKey) continue;
|
|
3379
|
+
if (modifiers.includes('shift') && !e.shiftKey) continue;
|
|
3380
|
+
if (modifiers.includes('alt') && !e.altKey) continue;
|
|
3381
|
+
if (modifiers.includes('meta') && !e.metaKey) continue;
|
|
3382
|
+
|
|
3343
3383
|
// Handle modifiers
|
|
3344
3384
|
if (modifiers.includes('prevent')) e.preventDefault();
|
|
3345
3385
|
if (modifiers.includes('stop')) {
|
|
@@ -3425,9 +3465,12 @@ class Component {
|
|
|
3425
3465
|
// textarea, select (single & multiple), contenteditable
|
|
3426
3466
|
// Nested state keys: z-model="user.name" → this.state.user.name
|
|
3427
3467
|
// Modifiers (boolean attributes on the same element):
|
|
3428
|
-
// z-lazy
|
|
3429
|
-
// z-trim
|
|
3430
|
-
// z-number
|
|
3468
|
+
// z-lazy — listen on 'change' instead of 'input' (update on blur / commit)
|
|
3469
|
+
// z-trim — trim whitespace before writing to state
|
|
3470
|
+
// z-number — force Number() conversion regardless of input type
|
|
3471
|
+
// z-debounce — debounce state writes (default 250ms, or z-debounce="300")
|
|
3472
|
+
// z-uppercase — convert string to uppercase before writing to state
|
|
3473
|
+
// z-lowercase — convert string to lowercase before writing to state
|
|
3431
3474
|
//
|
|
3432
3475
|
// Writes to reactive state so the rest of the UI stays in sync.
|
|
3433
3476
|
// Focus and cursor position are preserved in _render() via focusInfo.
|
|
@@ -3443,6 +3486,10 @@ class Component {
|
|
|
3443
3486
|
const isLazy = el.hasAttribute('z-lazy');
|
|
3444
3487
|
const isTrim = el.hasAttribute('z-trim');
|
|
3445
3488
|
const isNum = el.hasAttribute('z-number');
|
|
3489
|
+
const isUpper = el.hasAttribute('z-uppercase');
|
|
3490
|
+
const isLower = el.hasAttribute('z-lowercase');
|
|
3491
|
+
const hasDebounce = el.hasAttribute('z-debounce');
|
|
3492
|
+
const debounceMs = hasDebounce ? (parseInt(el.getAttribute('z-debounce'), 10) || 250) : 0;
|
|
3446
3493
|
|
|
3447
3494
|
// Read current state value (supports dot-path keys)
|
|
3448
3495
|
const currentVal = _getPath(this.state, key);
|
|
@@ -3483,6 +3530,8 @@ class Component {
|
|
|
3483
3530
|
|
|
3484
3531
|
// Apply modifiers
|
|
3485
3532
|
if (isTrim && typeof val === 'string') val = val.trim();
|
|
3533
|
+
if (isUpper && typeof val === 'string') val = val.toUpperCase();
|
|
3534
|
+
if (isLower && typeof val === 'string') val = val.toLowerCase();
|
|
3486
3535
|
if (isNum || type === 'number' || type === 'range') val = Number(val);
|
|
3487
3536
|
|
|
3488
3537
|
// Write through the reactive proxy (triggers re-render).
|
|
@@ -3490,7 +3539,15 @@ class Component {
|
|
|
3490
3539
|
_setPath(this.state, key, val);
|
|
3491
3540
|
};
|
|
3492
3541
|
|
|
3493
|
-
|
|
3542
|
+
if (hasDebounce) {
|
|
3543
|
+
let timer = null;
|
|
3544
|
+
el.addEventListener(event, () => {
|
|
3545
|
+
clearTimeout(timer);
|
|
3546
|
+
timer = setTimeout(handler, debounceMs);
|
|
3547
|
+
});
|
|
3548
|
+
} else {
|
|
3549
|
+
el.addEventListener(event, handler);
|
|
3550
|
+
}
|
|
3494
3551
|
});
|
|
3495
3552
|
}
|
|
3496
3553
|
|
|
@@ -3776,6 +3833,10 @@ class Component {
|
|
|
3776
3833
|
}
|
|
3777
3834
|
this._listeners.forEach(({ event, handler }) => this._el.removeEventListener(event, handler));
|
|
3778
3835
|
this._listeners = [];
|
|
3836
|
+
if (this._outsideListeners) {
|
|
3837
|
+
this._outsideListeners.forEach(({ event, handler }) => document.removeEventListener(event, handler, true));
|
|
3838
|
+
this._outsideListeners = [];
|
|
3839
|
+
}
|
|
3779
3840
|
this._delegatedEvents = null;
|
|
3780
3841
|
this._eventBindings = null;
|
|
3781
3842
|
// Clear any pending debounce/throttle timers to prevent stale closures.
|
|
@@ -5066,6 +5127,7 @@ const http = {
|
|
|
5066
5127
|
put: (url, data, opts) => request('PUT', url, data, opts),
|
|
5067
5128
|
patch: (url, data, opts) => request('PATCH', url, data, opts),
|
|
5068
5129
|
delete: (url, data, opts) => request('DELETE', url, data, opts),
|
|
5130
|
+
head: (url, opts) => request('HEAD', url, undefined, opts),
|
|
5069
5131
|
|
|
5070
5132
|
/**
|
|
5071
5133
|
* Configure defaults
|
|
@@ -5076,20 +5138,56 @@ const http = {
|
|
|
5076
5138
|
if (opts.timeout !== undefined) _config.timeout = opts.timeout;
|
|
5077
5139
|
},
|
|
5078
5140
|
|
|
5141
|
+
/**
|
|
5142
|
+
* Read-only snapshot of current configuration
|
|
5143
|
+
*/
|
|
5144
|
+
getConfig() {
|
|
5145
|
+
return {
|
|
5146
|
+
baseURL: _config.baseURL,
|
|
5147
|
+
headers: { ..._config.headers },
|
|
5148
|
+
timeout: _config.timeout,
|
|
5149
|
+
};
|
|
5150
|
+
},
|
|
5151
|
+
|
|
5079
5152
|
/**
|
|
5080
5153
|
* Add request interceptor
|
|
5081
5154
|
* @param {Function} fn — (fetchOpts, url) → void | false | { url, options }
|
|
5155
|
+
* @returns {Function} unsubscribe function
|
|
5082
5156
|
*/
|
|
5083
5157
|
onRequest(fn) {
|
|
5084
5158
|
_interceptors.request.push(fn);
|
|
5159
|
+
return () => {
|
|
5160
|
+
const idx = _interceptors.request.indexOf(fn);
|
|
5161
|
+
if (idx !== -1) _interceptors.request.splice(idx, 1);
|
|
5162
|
+
};
|
|
5085
5163
|
},
|
|
5086
5164
|
|
|
5087
5165
|
/**
|
|
5088
5166
|
* Add response interceptor
|
|
5089
5167
|
* @param {Function} fn — (result) → void
|
|
5168
|
+
* @returns {Function} unsubscribe function
|
|
5090
5169
|
*/
|
|
5091
5170
|
onResponse(fn) {
|
|
5092
5171
|
_interceptors.response.push(fn);
|
|
5172
|
+
return () => {
|
|
5173
|
+
const idx = _interceptors.response.indexOf(fn);
|
|
5174
|
+
if (idx !== -1) _interceptors.response.splice(idx, 1);
|
|
5175
|
+
};
|
|
5176
|
+
},
|
|
5177
|
+
|
|
5178
|
+
/**
|
|
5179
|
+
* Clear interceptors — all, or just 'request' / 'response'
|
|
5180
|
+
*/
|
|
5181
|
+
clearInterceptors(type) {
|
|
5182
|
+
if (!type || type === 'request') _interceptors.request.length = 0;
|
|
5183
|
+
if (!type || type === 'response') _interceptors.response.length = 0;
|
|
5184
|
+
},
|
|
5185
|
+
|
|
5186
|
+
/**
|
|
5187
|
+
* Run multiple requests in parallel
|
|
5188
|
+
*/
|
|
5189
|
+
all(requests) {
|
|
5190
|
+
return Promise.all(requests);
|
|
5093
5191
|
},
|
|
5094
5192
|
|
|
5095
5193
|
/**
|
|
@@ -5689,6 +5787,7 @@ $.post = http.post;
|
|
|
5689
5787
|
$.put = http.put;
|
|
5690
5788
|
$.patch = http.patch;
|
|
5691
5789
|
$.delete = http.delete;
|
|
5790
|
+
$.head = http.head;
|
|
5692
5791
|
|
|
5693
5792
|
// --- Utilities -------------------------------------------------------------
|
|
5694
5793
|
$.debounce = debounce;
|
|
@@ -5737,8 +5836,8 @@ $.guardCallback = guardCallback;
|
|
|
5737
5836
|
$.validate = validate;
|
|
5738
5837
|
|
|
5739
5838
|
// --- Meta ------------------------------------------------------------------
|
|
5740
|
-
$.version = '0.9.
|
|
5741
|
-
$.libSize = '~
|
|
5839
|
+
$.version = '0.9.7';
|
|
5840
|
+
$.libSize = '~100 KB';
|
|
5742
5841
|
$.meta = {}; // populated at build time by CLI bundler
|
|
5743
5842
|
|
|
5744
5843
|
$.noConflict = () => {
|