zero-query 0.9.9 → 1.0.0

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.
Files changed (97) hide show
  1. package/README.md +33 -32
  2. package/cli/args.js +1 -1
  3. package/cli/commands/build.js +2 -2
  4. package/cli/commands/bundle.js +15 -15
  5. package/cli/commands/create.js +2 -2
  6. package/cli/commands/dev/devtools/index.js +1 -1
  7. package/cli/commands/dev/devtools/js/core.js +14 -14
  8. package/cli/commands/dev/devtools/js/elements.js +4 -4
  9. package/cli/commands/dev/devtools/js/stats.js +1 -1
  10. package/cli/commands/dev/devtools/styles.css +2 -2
  11. package/cli/commands/dev/index.js +2 -2
  12. package/cli/commands/dev/logger.js +1 -1
  13. package/cli/commands/dev/overlay.js +21 -14
  14. package/cli/commands/dev/server.js +5 -5
  15. package/cli/commands/dev/validator.js +7 -7
  16. package/cli/commands/dev/watcher.js +6 -6
  17. package/cli/help.js +3 -3
  18. package/cli/index.js +2 -2
  19. package/cli/scaffold/default/app/app.js +17 -18
  20. package/cli/scaffold/default/app/components/about.js +9 -9
  21. package/cli/scaffold/default/app/components/api-demo.js +6 -6
  22. package/cli/scaffold/default/app/components/contact-card.js +4 -4
  23. package/cli/scaffold/default/app/components/contacts/contacts.css +2 -2
  24. package/cli/scaffold/default/app/components/contacts/contacts.html +3 -3
  25. package/cli/scaffold/default/app/components/contacts/contacts.js +11 -11
  26. package/cli/scaffold/default/app/components/counter.js +8 -8
  27. package/cli/scaffold/default/app/components/home.js +13 -13
  28. package/cli/scaffold/default/app/components/not-found.js +1 -1
  29. package/cli/scaffold/default/app/components/playground/playground.css +1 -1
  30. package/cli/scaffold/default/app/components/playground/playground.html +11 -11
  31. package/cli/scaffold/default/app/components/playground/playground.js +11 -11
  32. package/cli/scaffold/default/app/components/todos.js +8 -8
  33. package/cli/scaffold/default/app/components/toolkit/toolkit.css +1 -1
  34. package/cli/scaffold/default/app/components/toolkit/toolkit.html +4 -4
  35. package/cli/scaffold/default/app/components/toolkit/toolkit.js +7 -7
  36. package/cli/scaffold/default/app/routes.js +1 -1
  37. package/cli/scaffold/default/app/store.js +1 -1
  38. package/cli/scaffold/default/global.css +2 -2
  39. package/cli/scaffold/default/index.html +2 -2
  40. package/cli/scaffold/minimal/app/app.js +6 -7
  41. package/cli/scaffold/minimal/app/components/about.js +5 -5
  42. package/cli/scaffold/minimal/app/components/counter.js +6 -6
  43. package/cli/scaffold/minimal/app/components/home.js +8 -8
  44. package/cli/scaffold/minimal/app/components/not-found.js +1 -1
  45. package/cli/scaffold/minimal/app/routes.js +1 -1
  46. package/cli/scaffold/minimal/app/store.js +1 -1
  47. package/cli/scaffold/minimal/global.css +2 -2
  48. package/cli/scaffold/minimal/index.html +1 -1
  49. package/cli/scaffold/ssr/app/app.js +1 -2
  50. package/cli/scaffold/ssr/app/components/about.js +5 -5
  51. package/cli/scaffold/ssr/app/components/home.js +2 -2
  52. package/cli/scaffold/ssr/app/components/not-found.js +1 -1
  53. package/cli/scaffold/ssr/app/routes.js +1 -1
  54. package/cli/scaffold/ssr/global.css +2 -2
  55. package/cli/scaffold/ssr/index.html +2 -2
  56. package/cli/scaffold/ssr/server/index.js +4 -4
  57. package/cli/utils.js +6 -6
  58. package/dist/zquery.dist.zip +0 -0
  59. package/dist/zquery.js +508 -227
  60. package/dist/zquery.min.js +2 -2
  61. package/index.d.ts +16 -13
  62. package/index.js +7 -5
  63. package/package.json +2 -2
  64. package/src/component.js +64 -63
  65. package/src/core.js +15 -15
  66. package/src/diff.js +38 -38
  67. package/src/errors.js +17 -17
  68. package/src/expression.js +15 -17
  69. package/src/http.js +4 -4
  70. package/src/reactive.js +75 -9
  71. package/src/router.js +104 -24
  72. package/src/ssr.js +28 -28
  73. package/src/store.js +103 -21
  74. package/src/utils.js +64 -12
  75. package/tests/audit.test.js +143 -15
  76. package/tests/cli.test.js +20 -20
  77. package/tests/component.test.js +121 -121
  78. package/tests/core.test.js +56 -56
  79. package/tests/diff.test.js +42 -42
  80. package/tests/errors.test.js +5 -5
  81. package/tests/expression.test.js +58 -53
  82. package/tests/http.test.js +20 -20
  83. package/tests/reactive.test.js +185 -24
  84. package/tests/router.test.js +501 -74
  85. package/tests/ssr.test.js +15 -13
  86. package/tests/store.test.js +264 -23
  87. package/tests/utils.test.js +163 -26
  88. package/types/collection.d.ts +2 -2
  89. package/types/component.d.ts +5 -5
  90. package/types/errors.d.ts +3 -3
  91. package/types/http.d.ts +3 -3
  92. package/types/misc.d.ts +9 -9
  93. package/types/reactive.d.ts +25 -3
  94. package/types/router.d.ts +10 -6
  95. package/types/ssr.d.ts +2 -2
  96. package/types/store.d.ts +40 -5
  97. package/types/utils.d.ts +1 -1
package/index.d.ts CHANGED
@@ -1,10 +1,10 @@
1
1
  /**
2
- * zQuery (zeroQuery) TypeScript Declarations
2
+ * zQuery (zeroQuery) - TypeScript Declarations
3
3
  *
4
- * Lightweight modern frontend library jQuery-like selectors, reactive
4
+ * Lightweight modern frontend library - jQuery-like selectors, reactive
5
5
  * components, SPA router, state management, HTTP client & utilities.
6
6
  *
7
- * @version 0.9.9
7
+ * @version 1.0.0
8
8
  * @license MIT
9
9
  * @see https://z-query.com/docs
10
10
  */
@@ -22,6 +22,8 @@ export {
22
22
  signal,
23
23
  computed,
24
24
  effect,
25
+ batch,
26
+ untracked,
25
27
  } from './types/reactive';
26
28
 
27
29
  export {
@@ -135,15 +137,14 @@ export {
135
137
  SSRApp,
136
138
  createSSRApp,
137
139
  renderToString,
138
- escapeHtml as escapeHtmlSSR,
139
140
  } from './types/ssr';
140
141
 
141
142
  // ---------------------------------------------------------------------------
142
- // $ Main function & namespace
143
+ // $ - Main function & namespace
143
144
  // ---------------------------------------------------------------------------
144
145
 
145
146
  import type { ZQueryCollection } from './types/collection';
146
- import type { reactive, Signal, signal, computed, effect } from './types/reactive';
147
+ import type { reactive, Signal, signal, computed, effect, batch, untracked } from './types/reactive';
147
148
  import type { component, mount, mountAll, getInstance, destroy, getRegistry, prefetch, style } from './types/component';
148
149
  import type { createRouter, getRouter } from './types/router';
149
150
  import type { createStore, getStore } from './types/store';
@@ -162,7 +163,7 @@ import type { onError, ZQueryError, ErrorCode, guardCallback, validate } from '.
162
163
  import type { morph, morphElement, safeEval } from './types/misc';
163
164
 
164
165
  /**
165
- * Main selector / DOM-ready function always returns a `ZQueryCollection` (like jQuery).
166
+ * Main selector / DOM-ready function - always returns a `ZQueryCollection` (like jQuery).
166
167
  *
167
168
  * - `$('selector')` → ZQueryCollection via `querySelectorAll`
168
169
  * - `$('<div>…</div>')` → ZQueryCollection from created elements
@@ -177,7 +178,7 @@ interface ZQueryStatic {
177
178
 
178
179
  // -- Collection selector -------------------------------------------------
179
180
  /**
180
- * Collection selector returns a `ZQueryCollection`.
181
+ * Collection selector - returns a `ZQueryCollection`.
181
182
  *
182
183
  * - `$.all('.card')` → all matching elements
183
184
  * - `$.all('<div>…</div>')` → create elements as collection
@@ -201,9 +202,9 @@ interface ZQueryStatic {
201
202
  name(name: string): ZQueryCollection;
202
203
  /** Children of `#parentId` as `ZQueryCollection`. */
203
204
  children(parentId: string): ZQueryCollection;
204
- /** `document.querySelector(selector)` raw Element or null. */
205
+ /** `document.querySelector(selector)` - raw Element or null. */
205
206
  qs(selector: string, context?: Element | Document): Element | null;
206
- /** `document.querySelectorAll(selector)` as a real `Array<Element>`. */
207
+ /** `document.querySelectorAll(selector)` - as a real `Array<Element>`. */
207
208
  qsa(selector: string, context?: Element | Document): Element[];
208
209
 
209
210
  // -- Static helpers ------------------------------------------------------
@@ -232,7 +233,7 @@ interface ZQueryStatic {
232
233
  /** Remove a direct global event listener previously attached with `$.on(event, handler)`. */
233
234
  off(event: string, handler: (e: Event) => void): void;
234
235
 
235
- /** Alias for `ZQueryCollection.prototype` extend to add custom collection methods. */
236
+ /** Alias for `ZQueryCollection.prototype` - extend to add custom collection methods. */
236
237
  fn: typeof ZQueryCollection.prototype;
237
238
 
238
239
  // -- Reactive ------------------------------------------------------------
@@ -241,6 +242,8 @@ interface ZQueryStatic {
241
242
  signal: typeof signal;
242
243
  computed: typeof computed;
243
244
  effect: typeof effect;
245
+ batch: typeof batch;
246
+ untracked: typeof untracked;
244
247
 
245
248
  // -- Components ----------------------------------------------------------
246
249
  component: typeof component;
@@ -254,7 +257,7 @@ interface ZQueryStatic {
254
257
  prefetch: typeof prefetch;
255
258
  style: typeof style;
256
259
  morph: typeof morph;
257
- /** Morph a single element in place preserves identity when tag name matches. */
260
+ /** Morph a single element in place - preserves identity when tag name matches. */
258
261
  morphElement: typeof morphElement;
259
262
  safeEval: typeof safeEval;
260
263
 
@@ -353,7 +356,7 @@ interface ZQueryStatic {
353
356
  export const $: ZQueryStatic;
354
357
  export { $ as zQuery };
355
358
 
356
- /** Collection selector function same as `$.all()`. */
359
+ /** Collection selector function - same as `$.all()`. */
357
360
  export function queryAll(selector: string, context?: string | Element): ZQueryCollection;
358
361
  export function queryAll(element: Element): ZQueryCollection;
359
362
  export function queryAll(nodeList: NodeList | HTMLCollection | Element[]): ZQueryCollection;
package/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * ┌---------------------------------------------------------┐
3
- * │ zQuery (zeroQuery) Lightweight Frontend Library │
3
+ * │ zQuery (zeroQuery) - Lightweight Frontend Library │
4
4
  * │ │
5
5
  * │ jQuery-like selectors · Reactive components │
6
6
  * │ SPA router · State management · Zero dependencies │
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { query, queryAll, ZQueryCollection } from './src/core.js';
13
- import { reactive, Signal, signal, computed, effect } from './src/reactive.js';
13
+ import { reactive, Signal, signal, computed, effect, batch, untracked } from './src/reactive.js';
14
14
  import { component, mount, mountAll, getInstance, destroy, getRegistry, prefetch, style } from './src/component.js';
15
15
  import { createRouter, getRouter } from './src/router.js';
16
16
  import { createStore, getStore } from './src/store.js';
@@ -31,11 +31,11 @@ import { ZQueryError, ErrorCode, onError, reportError, guardCallback, guardAsync
31
31
 
32
32
 
33
33
  // ---------------------------------------------------------------------------
34
- // $ The main function & namespace
34
+ // $ - The main function & namespace
35
35
  // ---------------------------------------------------------------------------
36
36
 
37
37
  /**
38
- * Main selector function always returns a ZQueryCollection (like jQuery).
38
+ * Main selector function - always returns a ZQueryCollection (like jQuery).
39
39
  *
40
40
  * $('selector') → ZQueryCollection (querySelectorAll)
41
41
  * $('<div>hello</div>') → ZQueryCollection from created elements
@@ -98,6 +98,8 @@ $.Signal = Signal;
98
98
  $.signal = signal;
99
99
  $.computed = computed;
100
100
  $.effect = effect;
101
+ $.batch = batch;
102
+ $.untracked = untracked;
101
103
 
102
104
  // --- Components ------------------------------------------------------------
103
105
  $.component = component;
@@ -208,7 +210,7 @@ export {
208
210
  $ as zQuery,
209
211
  ZQueryCollection,
210
212
  queryAll,
211
- reactive, Signal, signal, computed, effect,
213
+ reactive, Signal, signal, computed, effect, batch, untracked,
212
214
  component, mount, mountAll, getInstance, destroy, getRegistry, prefetch, style,
213
215
  morph, morphElement,
214
216
  safeEval,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "zero-query",
3
- "version": "0.9.9",
4
- "description": "Lightweight modern frontend library jQuery-like selectors, reactive components, SPA router, and state management with zero dependencies.",
3
+ "version": "1.0.0",
4
+ "description": "Lightweight modern frontend library - jQuery-like selectors, reactive components, SPA router, and state management with zero dependencies.",
5
5
  "main": "index.js",
6
6
  "types": "index.d.ts",
7
7
  "exports": {
package/src/component.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zQuery Component Lightweight reactive component system
2
+ * zQuery Component - Lightweight reactive component system
3
3
  *
4
4
  * Declarative components using template literals with directive support.
5
5
  * Proxy-based state triggers targeted re-renders via event delegation.
@@ -15,7 +15,7 @@
15
15
  * - Scoped styles (inline or via styleUrl)
16
16
  * - External templates via templateUrl (with {{expression}} interpolation)
17
17
  * - External styles via styleUrl (fetched & scoped automatically)
18
- * - Relative path resolution templateUrl and styleUrl
18
+ * - Relative path resolution - templateUrl and styleUrl
19
19
  * resolve relative to the component file automatically
20
20
  */
21
21
 
@@ -23,6 +23,7 @@ import { reactive } from './reactive.js';
23
23
  import { morph } from './diff.js';
24
24
  import { safeEval } from './expression.js';
25
25
  import { reportError, ErrorCode, ZQueryError } from './errors.js';
26
+ import { escapeHtml } from './utils.js';
26
27
 
27
28
  // ---------------------------------------------------------------------------
28
29
  // Component registry & external resource cache
@@ -48,7 +49,7 @@ const _throttleTimers = new WeakMap();
48
49
 
49
50
  /**
50
51
  * Fetch and cache a text resource (HTML template or CSS file).
51
- * @param {string} url URL to fetch
52
+ * @param {string} url - URL to fetch
52
53
  * @returns {Promise<string>}
53
54
  */
54
55
  function _fetchResource(url) {
@@ -92,23 +93,23 @@ function _fetchResource(url) {
92
93
  * - If `base` is an absolute URL (http/https/file), resolve directly.
93
94
  * - If `base` is a relative path string, resolve it against the page root
94
95
  * (or <base href>) first, then resolve `url` against that.
95
- * - If `base` is falsy, return `url` unchanged _fetchResource's own
96
+ * - If `base` is falsy, return `url` unchanged - _fetchResource's own
96
97
  * fallback (page root / <base href>) handles it.
97
98
  *
98
- * @param {string} url URL or relative path to resolve
99
- * @param {string} [base] auto-detected caller URL or explicit base path
99
+ * @param {string} url - URL or relative path to resolve
100
+ * @param {string} [base] - auto-detected caller URL or explicit base path
100
101
  * @returns {string}
101
102
  */
102
103
  function _resolveUrl(url, base) {
103
104
  if (!base || !url || typeof url !== 'string') return url;
104
- // Already absolute nothing to do
105
+ // Already absolute - nothing to do
105
106
  if (url.startsWith('/') || url.includes('://') || url.startsWith('//')) return url;
106
107
  try {
107
108
  if (base.includes('://')) {
108
109
  // Absolute base (auto-detected module URL)
109
110
  return new URL(url, base).href;
110
111
  }
111
- // Relative base string resolve against page root first
112
+ // Relative base string - resolve against page root first
112
113
  const baseEl = document.querySelector('base');
113
114
  const root = baseEl ? baseEl.href : (window.location.origin + '/');
114
115
  const absBase = new URL(base.endsWith('/') ? base : base + '/', root).href;
@@ -140,13 +141,13 @@ function _detectCallerBase() {
140
141
  for (const raw of urls) {
141
142
  // Strip line:col suffixes e.g. ":3:5" or ":12:1"
142
143
  const url = raw.replace(/:\d+:\d+$/, '').replace(/:\d+$/, '');
143
- // Skip the zQuery library itself by filename pattern and captured URL
144
+ // Skip the zQuery library itself - by filename pattern and captured URL
144
145
  if (/zquery(\.min)?\.js$/i.test(url)) continue;
145
146
  if (_ownScriptUrl && url.replace(/[?#].*$/, '') === _ownScriptUrl) continue;
146
147
  // Return directory (strip filename, keep trailing slash)
147
148
  return url.replace(/\/[^/]*$/, '/');
148
149
  }
149
- } catch { /* stack parsing unsupported fall back silently */ }
150
+ } catch { /* stack parsing unsupported - fall back silently */ }
150
151
  return undefined;
151
152
  }
152
153
 
@@ -225,7 +226,7 @@ class Component {
225
226
  }
226
227
  });
227
228
 
228
- // Computed properties lazy getters derived from state
229
+ // Computed properties - lazy getters derived from state
229
230
  this.computed = {};
230
231
  if (definition.computed) {
231
232
  for (const [name, fn] of Object.entries(definition.computed)) {
@@ -354,7 +355,7 @@ class Component {
354
355
  this._loadExternals().then(() => {
355
356
  if (!this._destroyed) this._render();
356
357
  });
357
- return; // Skip this render will re-render after load
358
+ return; // Skip this render - will re-render after load
358
359
  }
359
360
 
360
361
  // Expose multi-template map on instance (if available)
@@ -379,7 +380,7 @@ class Component {
379
380
  this.state.__raw || this.state,
380
381
  { props: this.props, computed: this.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
381
382
  ]);
382
- return result != null ? result : '';
383
+ return result != null ? escapeHtml(String(result)) : '';
383
384
  } catch { return ''; }
384
385
  });
385
386
  } else {
@@ -425,13 +426,13 @@ class Component {
425
426
  const trimmed = selector.trim();
426
427
  // Don't scope @-rules themselves
427
428
  if (trimmed.startsWith('@')) {
428
- // @keyframes and @font-face contain non-selector content skip scoping inside them
429
+ // @keyframes and @font-face contain non-selector content - skip scoping inside them
429
430
  if (/^@(keyframes|font-face)\b/.test(trimmed)) {
430
431
  noScopeDepth = braceDepth;
431
432
  }
432
433
  return match;
433
434
  }
434
- // Inside @keyframes or @font-face don't scope inner rules
435
+ // Inside @keyframes or @font-face - don't scope inner rules
435
436
  if (noScopeDepth > 0 && braceDepth > noScopeDepth) {
436
437
  return match;
437
438
  }
@@ -484,7 +485,7 @@ class Component {
484
485
  }
485
486
  }
486
487
 
487
- // Update DOM via morphing (diffing) preserves unchanged nodes
488
+ // Update DOM via morphing (diffing) - preserves unchanged nodes
488
489
  // First render uses innerHTML for speed; subsequent renders morph.
489
490
  const _renderStart = typeof window !== 'undefined' && (window.__zqMorphHook || window.__zqRenderHook) ? performance.now() : 0;
490
491
  if (!this._mounted) {
@@ -503,8 +504,8 @@ class Component {
503
504
  this._bindModels();
504
505
 
505
506
  // Restore focus if the morph replaced the focused element.
506
- // Always restore selectionRange even when the element is still
507
- // the activeElement because _bindModels or morph attribute syncing
507
+ // Always restore selectionRange - even when the element is still
508
+ // the activeElement - because _bindModels or morph attribute syncing
508
509
  // can alter the value and move the cursor.
509
510
  if (_focusInfo) {
510
511
  const el = this._el.querySelector(_focusInfo.selector);
@@ -540,7 +541,7 @@ class Component {
540
541
  // Optimization: on the FIRST render, we scan for event attributes, build
541
542
  // a delegated handler map, and attach one listener per event type to the
542
543
  // component root. On subsequent renders (re-bind), we only rebuild the
543
- // internal binding map existing DOM listeners are reused since they
544
+ // internal binding map - existing DOM listeners are reused since they
544
545
  // delegate to event.target.closest(selector) at fire time.
545
546
  _bindEvents() {
546
547
  // Always rebuild the binding map from current DOM
@@ -581,11 +582,11 @@ class Component {
581
582
  // Store binding map for the delegated handlers to reference
582
583
  this._eventBindings = eventMap;
583
584
 
584
- // Only attach DOM listeners once reuse on subsequent renders.
585
+ // Only attach DOM listeners once - reuse on subsequent renders.
585
586
  // The handlers close over `this` and read `this._eventBindings`
586
587
  // at fire time, so they always use the latest binding map.
587
588
  if (this._delegatedEvents) {
588
- // Already attached just make sure new event types are covered
589
+ // Already attached - just make sure new event types are covered
589
590
  for (const event of eventMap.keys()) {
590
591
  if (!this._delegatedEvents.has(event)) {
591
592
  this._attachDelegatedEvent(event, eventMap.get(event));
@@ -611,7 +612,7 @@ class Component {
611
612
  this._attachDelegatedEvent(event, bindings);
612
613
  }
613
614
 
614
- // .outside attach a document-level listener for bindings that need
615
+ // .outside - attach a document-level listener for bindings that need
615
616
  // to detect clicks/events outside their element.
616
617
  this._outsideListeners = this._outsideListeners || [];
617
618
  for (const [event, bindings] of eventMap) {
@@ -639,7 +640,7 @@ class Component {
639
640
  : false;
640
641
 
641
642
  const handler = (e) => {
642
- // Read bindings from live map always up to date after re-renders
643
+ // Read bindings from live map - always up to date after re-renders
643
644
  const currentBindings = this._eventBindings?.get(event) || [];
644
645
 
645
646
  // Collect matching bindings with their matched elements, then sort
@@ -660,7 +661,7 @@ class Component {
660
661
  for (const { selector, methodExpr, modifiers, el, matched } of hits) {
661
662
 
662
663
  // In delegated events, .stop should prevent ancestor bindings from
663
- // firing stopPropagation alone only stops real DOM bubbling.
664
+ // firing - stopPropagation alone only stops real DOM bubbling.
664
665
  if (stoppedAt) {
665
666
  let blocked = false;
666
667
  for (const stopped of stoppedAt) {
@@ -669,15 +670,15 @@ class Component {
669
670
  if (blocked) continue;
670
671
  }
671
672
 
672
- // .self only fire if target is the element itself
673
+ // .self - only fire if target is the element itself
673
674
  if (modifiers.includes('self') && e.target !== el) continue;
674
675
 
675
- // .outside only fire if event target is OUTSIDE the element
676
+ // .outside - only fire if event target is OUTSIDE the element
676
677
  if (modifiers.includes('outside')) {
677
678
  if (el.contains(e.target)) continue;
678
679
  }
679
680
 
680
- // Key modifiers filter keyboard events by key
681
+ // Key modifiers - filter keyboard events by key
681
682
  const _keyMap = { enter: 'Enter', escape: 'Escape', tab: 'Tab', space: ' ', delete: 'Delete|Backspace', up: 'ArrowUp', down: 'ArrowDown', left: 'ArrowLeft', right: 'ArrowRight' };
682
683
  let keyFiltered = false;
683
684
  for (const mod of modifiers) {
@@ -688,7 +689,7 @@ class Component {
688
689
  }
689
690
  if (keyFiltered) continue;
690
691
 
691
- // System key modifiers require modifier keys to be held
692
+ // System key modifiers - require modifier keys to be held
692
693
  if (modifiers.includes('ctrl') && !e.ctrlKey) continue;
693
694
  if (modifiers.includes('shift') && !e.shiftKey) continue;
694
695
  if (modifiers.includes('alt') && !e.altKey) continue;
@@ -728,7 +729,7 @@ class Component {
728
729
  }
729
730
  };
730
731
 
731
- // .debounce.{ms} delay invocation until idle
732
+ // .debounce.{ms} - delay invocation until idle
732
733
  const debounceIdx = modifiers.indexOf('debounce');
733
734
  if (debounceIdx !== -1) {
734
735
  const ms = parseInt(modifiers[debounceIdx + 1], 10) || 250;
@@ -739,7 +740,7 @@ class Component {
739
740
  continue;
740
741
  }
741
742
 
742
- // .throttle.{ms} fire at most once per interval
743
+ // .throttle.{ms} - fire at most once per interval
743
744
  const throttleIdx = modifiers.indexOf('throttle');
744
745
  if (throttleIdx !== -1) {
745
746
  const ms = parseInt(modifiers[throttleIdx + 1], 10) || 250;
@@ -751,7 +752,7 @@ class Component {
751
752
  continue;
752
753
  }
753
754
 
754
- // .once fire once then ignore
755
+ // .once - fire once then ignore
755
756
  if (modifiers.includes('once')) {
756
757
  if (el.dataset.zqOnce === event) continue;
757
758
  el.dataset.zqOnce = event;
@@ -779,12 +780,12 @@ class Component {
779
780
  // textarea, select (single & multiple), contenteditable
780
781
  // Nested state keys: z-model="user.name" → this.state.user.name
781
782
  // Modifiers (boolean attributes on the same element):
782
- // z-lazy listen on 'change' instead of 'input' (update on blur / commit)
783
- // z-trim trim whitespace before writing to state
784
- // z-number force Number() conversion regardless of input type
785
- // z-debounce debounce state writes (default 250ms, or z-debounce="300")
786
- // z-uppercase convert string to uppercase before writing to state
787
- // z-lowercase convert string to lowercase before writing to state
783
+ // z-lazy - listen on 'change' instead of 'input' (update on blur / commit)
784
+ // z-trim - trim whitespace before writing to state
785
+ // z-number - force Number() conversion regardless of input type
786
+ // z-debounce - debounce state writes (default 250ms, or z-debounce="300")
787
+ // z-uppercase - convert string to uppercase before writing to state
788
+ // z-lowercase - convert string to lowercase before writing to state
788
789
  //
789
790
  // Writes to reactive state so the rest of the UI stays in sync.
790
791
  // Focus and cursor position are preserved in _render() via focusInfo.
@@ -866,7 +867,7 @@ class Component {
866
867
  }
867
868
 
868
869
  // ---------------------------------------------------------------------------
869
- // Expression evaluator CSP-safe parser (no eval / new Function)
870
+ // Expression evaluator - CSP-safe parser (no eval / new Function)
870
871
  // ---------------------------------------------------------------------------
871
872
  _evalExpr(expr) {
872
873
  return safeEval(expr, [
@@ -876,7 +877,7 @@ class Component {
876
877
  }
877
878
 
878
879
  // ---------------------------------------------------------------------------
879
- // z-for Expand list-rendering directives (pre-innerHTML, string level)
880
+ // z-for - Expand list-rendering directives (pre-innerHTML, string level)
880
881
  //
881
882
  // <li z-for="item in items">{{item.name}}</li>
882
883
  // <li z-for="(item, i) in items">{{i}}: {{item.name}}</li>
@@ -942,7 +943,7 @@ class Component {
942
943
  this.state.__raw || this.state,
943
944
  { props: this.props, computed: this.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
944
945
  ]);
945
- return result != null ? result : '';
946
+ return result != null ? escapeHtml(String(result)) : '';
946
947
  } catch { return ''; }
947
948
  });
948
949
 
@@ -965,7 +966,7 @@ class Component {
965
966
  }
966
967
 
967
968
  // ---------------------------------------------------------------------------
968
- // _expandContentDirectives Pre-morph z-html & z-text expansion
969
+ // _expandContentDirectives - Pre-morph z-html & z-text expansion
969
970
  //
970
971
  // Evaluates z-html and z-text directives at the string level so the morph
971
972
  // engine receives HTML with the actual content inline. This lets the diff
@@ -1000,7 +1001,7 @@ class Component {
1000
1001
  }
1001
1002
 
1002
1003
  // ---------------------------------------------------------------------------
1003
- // _processDirectives Post-innerHTML DOM-level directive processing
1004
+ // _processDirectives - Post-innerHTML DOM-level directive processing
1004
1005
  // ---------------------------------------------------------------------------
1005
1006
  _processDirectives() {
1006
1007
  // z-pre: skip all directive processing on subtrees
@@ -1051,7 +1052,7 @@ class Component {
1051
1052
  });
1052
1053
 
1053
1054
  // -- z-bind:attr / :attr (dynamic attribute binding) -----------
1054
- // Use TreeWalker instead of querySelectorAll('*') avoids
1055
+ // Use TreeWalker instead of querySelectorAll('*') - avoids
1055
1056
  // creating a flat array of every single descendant element.
1056
1057
  // TreeWalker visits nodes lazily; FILTER_REJECT skips z-pre subtrees
1057
1058
  // at the walker level (faster than per-node closest('[z-pre]') checks).
@@ -1189,8 +1190,8 @@ const _reservedKeys = new Set([
1189
1190
 
1190
1191
  /**
1191
1192
  * Register a component
1192
- * @param {string} name tag name (must contain a hyphen, e.g. 'app-counter')
1193
- * @param {object} definition component definition
1193
+ * @param {string} name - tag name (must contain a hyphen, e.g. 'app-counter')
1194
+ * @param {object} definition - component definition
1194
1195
  */
1195
1196
  export function component(name, definition) {
1196
1197
  if (!name || typeof name !== 'string') {
@@ -1215,9 +1216,9 @@ export function component(name, definition) {
1215
1216
 
1216
1217
  /**
1217
1218
  * Mount a component into a target element
1218
- * @param {string|Element} target selector or element to mount into
1219
- * @param {string} componentName registered component name
1220
- * @param {object} props props to pass
1219
+ * @param {string|Element} target - selector or element to mount into
1220
+ * @param {string} componentName - registered component name
1221
+ * @param {object} props - props to pass
1221
1222
  * @returns {Component}
1222
1223
  */
1223
1224
  export function mount(target, componentName, props = {}) {
@@ -1238,7 +1239,7 @@ export function mount(target, componentName, props = {}) {
1238
1239
 
1239
1240
  /**
1240
1241
  * Scan a container for custom component tags and auto-mount them
1241
- * @param {Element} root root element to scan (default: document.body)
1242
+ * @param {Element} root - root element to scan (default: document.body)
1242
1243
  */
1243
1244
  export function mountAll(root = document.body) {
1244
1245
  for (const [name, def] of _registry) {
@@ -1263,7 +1264,7 @@ export function mountAll(root = document.body) {
1263
1264
  [...tag.attributes].forEach(attr => {
1264
1265
  if (attr.name.startsWith('@') || attr.name.startsWith('z-')) return;
1265
1266
 
1266
- // Dynamic prop: :propName="expression" evaluate in parent context
1267
+ // Dynamic prop: :propName="expression" - evaluate in parent context
1267
1268
  if (attr.name.startsWith(':')) {
1268
1269
  const propName = attr.name.slice(1);
1269
1270
  if (parentInstance) {
@@ -1272,7 +1273,7 @@ export function mountAll(root = document.body) {
1272
1273
  { props: parentInstance.props, refs: parentInstance.refs, computed: parentInstance.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
1273
1274
  ]);
1274
1275
  } else {
1275
- // No parent try JSON parse
1276
+ // No parent - try JSON parse
1276
1277
  try { props[propName] = JSON.parse(attr.value); }
1277
1278
  catch { props[propName] = attr.value; }
1278
1279
  }
@@ -1321,8 +1322,8 @@ export function getRegistry() {
1321
1322
  /**
1322
1323
  * Pre-load a component's external templates and styles so the next mount
1323
1324
  * renders synchronously (no blank flash while fetching).
1324
- * Safe to call multiple times skips if already loaded.
1325
- * @param {string} name registered component name
1325
+ * Safe to call multiple times - skips if already loaded.
1326
+ * @param {string} name - registered component name
1326
1327
  * @returns {Promise<void>}
1327
1328
  */
1328
1329
  export async function prefetch(name) {
@@ -1350,27 +1351,27 @@ const _globalStyles = new Map(); // url → <link> element
1350
1351
  *
1351
1352
  * $.style('app.css') // critical by default
1352
1353
  * $.style(['app.css', 'theme.css']) // multiple files
1353
- * $.style('/assets/global.css') // absolute used as-is
1354
+ * $.style('/assets/global.css') // absolute - used as-is
1354
1355
  * $.style('app.css', { critical: false }) // opt out of FOUC prevention
1355
1356
  *
1356
1357
  * Options:
1357
- * critical (boolean, default true) When true, zQuery injects a tiny
1358
+ * critical - (boolean, default true) When true, zQuery injects a tiny
1358
1359
  * inline style that hides the page (`visibility: hidden`) and
1359
1360
  * removes it once the stylesheet has loaded. This prevents
1360
- * FOUC (Flash of Unstyled Content) entirely no special
1361
+ * FOUC (Flash of Unstyled Content) entirely - no special
1361
1362
  * markup needed in the HTML file. Set to false to load
1362
1363
  * the stylesheet without blocking paint.
1363
- * bg (string, default '#0d1117') Background color applied while
1364
+ * bg - (string, default '#0d1117') Background color applied while
1364
1365
  * the page is hidden during critical load. Prevents a white
1365
1366
  * flash on dark-themed apps. Only used when critical is true.
1366
1367
  *
1367
1368
  * Duplicate URLs are ignored (idempotent).
1368
1369
  *
1369
- * @param {string|string[]} urls stylesheet URL(s) to load
1370
- * @param {object} [opts] options
1371
- * @param {boolean} [opts.critical=true] hide page until loaded (prevents FOUC)
1372
- * @param {string} [opts.bg] background color while hidden (default '#0d1117')
1373
- * @returns {{ remove: Function, ready: Promise }} .remove() to unload, .ready resolves when loaded
1370
+ * @param {string|string[]} urls - stylesheet URL(s) to load
1371
+ * @param {object} [opts] - options
1372
+ * @param {boolean} [opts.critical=true] - hide page until loaded (prevents FOUC)
1373
+ * @param {string} [opts.bg] - background color while hidden (default '#0d1117')
1374
+ * @returns {{ remove: Function, ready: Promise }} - .remove() to unload, .ready resolves when loaded
1374
1375
  */
1375
1376
  export function style(urls, opts = {}) {
1376
1377
  const callerBase = _detectCallerBase();
@@ -1379,7 +1380,7 @@ export function style(urls, opts = {}) {
1379
1380
  const loadPromises = [];
1380
1381
 
1381
1382
  // Critical mode (default: true): inject a tiny inline <style> that hides the
1382
- // page and sets a background color. Fully self-contained no markup needed
1383
+ // page and sets a background color. Fully self-contained - no markup needed
1383
1384
  // in the HTML file. The style is removed once the sheet loads.
1384
1385
  let _criticalStyle = null;
1385
1386
  if (opts.critical !== false) {