zero-query 0.9.5 → 0.9.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/README.md CHANGED
@@ -21,7 +21,7 @@
21
21
 
22
22
  | Module | Highlights |
23
23
  | --- | --- |
24
- | **Components** | Reactive state, template literals, `@event` delegation (8 modifiers), `z-model` two-way binding, 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 |
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) |
Binary file
package/dist/zquery.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zQuery (zeroQuery) v0.9.5
2
+ * zQuery (zeroQuery) v0.9.6
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 — listen on 'change' instead of 'input' (update on blur / commit)
3429
- // z-trim — trim whitespace before writing to state
3430
- // z-number — force Number() conversion regardless of input type
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
- el.addEventListener(event, handler);
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.
@@ -5737,8 +5798,8 @@ $.guardCallback = guardCallback;
5737
5798
  $.validate = validate;
5738
5799
 
5739
5800
  // --- Meta ------------------------------------------------------------------
5740
- $.version = '0.9.5';
5741
- $.libSize = '~98 KB';
5801
+ $.version = '0.9.6';
5802
+ $.libSize = '~100 KB';
5742
5803
  $.meta = {}; // populated at build time by CLI bundler
5743
5804
 
5744
5805
  $.noConflict = () => {