zero-query 1.1.1 → 1.2.2

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 (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +442 -442
  5. package/cli/commands/build.js +253 -247
  6. package/cli/commands/bundle.js +1226 -1224
  7. package/cli/commands/create.js +178 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -220
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +441 -0
  73. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  74. package/cli/scaffold/webrtc/global.css +250 -0
  75. package/cli/scaffold/webrtc/index.html +21 -0
  76. package/cli/scaffold/webrtc/package.json +16 -0
  77. package/cli/scaffold/webrtc/server/index.js +198 -0
  78. package/cli/utils.js +305 -287
  79. package/dist/API.md +661 -0
  80. package/dist/zquery.dist.zip +0 -0
  81. package/dist/zquery.js +10454 -6614
  82. package/dist/zquery.min.js +8 -631
  83. package/index.d.ts +570 -371
  84. package/index.js +311 -240
  85. package/package.json +77 -70
  86. package/src/component.js +1758 -1691
  87. package/src/core.js +929 -921
  88. package/src/diff.js +497 -497
  89. package/src/errors.js +209 -209
  90. package/src/expression.js +929 -922
  91. package/src/http.js +242 -242
  92. package/src/package.json +1 -1
  93. package/src/reactive.js +271 -255
  94. package/src/router.js +878 -843
  95. package/src/ssr.js +421 -418
  96. package/src/store.js +325 -318
  97. package/src/utils.js +534 -515
  98. package/src/webrtc/e2ee.js +351 -0
  99. package/src/webrtc/errors.js +116 -0
  100. package/src/webrtc/ice.js +301 -0
  101. package/src/webrtc/index.js +131 -0
  102. package/src/webrtc/joinToken.js +119 -0
  103. package/src/webrtc/observe.js +172 -0
  104. package/src/webrtc/peer.js +351 -0
  105. package/src/webrtc/reactive.js +268 -0
  106. package/src/webrtc/room.js +625 -0
  107. package/src/webrtc/sdp.js +302 -0
  108. package/src/webrtc/sfu/index.js +43 -0
  109. package/src/webrtc/sfu/livekit.js +131 -0
  110. package/src/webrtc/sfu/mediasoup.js +150 -0
  111. package/src/webrtc/signaling.js +373 -0
  112. package/src/webrtc/turn.js +237 -0
  113. package/tests/_helpers/webrtcFakes.js +289 -0
  114. package/tests/audit.test.js +4158 -4158
  115. package/tests/bench/render.bench.js +77 -0
  116. package/tests/cli.test.js +1136 -1103
  117. package/tests/compare.test.js +497 -486
  118. package/tests/component.test.js +4076 -3938
  119. package/tests/core.test.js +1926 -1910
  120. package/tests/dev-server.test.js +489 -489
  121. package/tests/diff.test.js +1416 -1416
  122. package/tests/docs.test.js +1664 -1650
  123. package/tests/electron-features.test.js +864 -864
  124. package/tests/errors.test.js +619 -619
  125. package/tests/expression.test.js +1087 -1056
  126. package/tests/http.test.js +693 -648
  127. package/tests/reactive.test.js +844 -819
  128. package/tests/router.test.js +2423 -2327
  129. package/tests/ssr.test.js +916 -870
  130. package/tests/store.test.js +840 -830
  131. package/tests/test-minifier.js +153 -153
  132. package/tests/test-ssr.js +27 -27
  133. package/tests/utils.test.js +1411 -1377
  134. package/tests/webrtc/e2ee.test.js +283 -0
  135. package/tests/webrtc/ice.test.js +202 -0
  136. package/tests/webrtc/joinToken.test.js +89 -0
  137. package/tests/webrtc/observe.test.js +111 -0
  138. package/tests/webrtc/peer.test.js +373 -0
  139. package/tests/webrtc/reactive.test.js +235 -0
  140. package/tests/webrtc/room.test.js +406 -0
  141. package/tests/webrtc/sdp.test.js +151 -0
  142. package/tests/webrtc/sfu-livekit.test.js +119 -0
  143. package/tests/webrtc/sfu.test.js +160 -0
  144. package/tests/webrtc/signaling.test.js +251 -0
  145. package/tests/webrtc/turn.test.js +256 -0
  146. package/types/collection.d.ts +383 -383
  147. package/types/component.d.ts +186 -186
  148. package/types/errors.d.ts +135 -135
  149. package/types/http.d.ts +92 -92
  150. package/types/misc.d.ts +201 -201
  151. package/types/reactive.d.ts +98 -98
  152. package/types/router.d.ts +190 -190
  153. package/types/ssr.d.ts +102 -102
  154. package/types/store.d.ts +146 -146
  155. package/types/utils.d.ts +245 -245
  156. package/types/webrtc.d.ts +653 -0
package/src/core.js CHANGED
@@ -1,921 +1,929 @@
1
- /**
2
- * zQuery Core - Selector engine & chainable DOM collection
3
- *
4
- * Extends the quick-ref pattern (Id, Class, Classes, Children)
5
- * into a full jQuery-like chainable wrapper with modern APIs.
6
- */
7
-
8
- import { morph as _morph, morphElement as _morphElement } from './diff.js';
9
-
10
- // ---------------------------------------------------------------------------
11
- // ZQueryCollection - wraps an array of elements with chainable methods
12
- // ---------------------------------------------------------------------------
13
- export class ZQueryCollection {
14
- constructor(elements) {
15
- this.elements = Array.isArray(elements) ? elements : (elements ? [elements] : []);
16
- this.length = this.elements.length;
17
- this.elements.forEach((el, i) => { this[i] = el; });
18
- }
19
-
20
- // --- Iteration -----------------------------------------------------------
21
-
22
- each(fn) {
23
- this.elements.forEach((el, i) => fn.call(el, i, el));
24
- return this;
25
- }
26
-
27
- map(fn) {
28
- return this.elements.map((el, i) => fn.call(el, i, el));
29
- }
30
-
31
- forEach(fn) {
32
- this.elements.forEach((el, i) => fn(el, i, this.elements));
33
- return this;
34
- }
35
-
36
- first() { return this.elements[0] || null; }
37
- last() { return this.elements[this.length - 1] || null; }
38
- eq(i) { return new ZQueryCollection(this.elements[i] ? [this.elements[i]] : []); }
39
- toArray(){ return [...this.elements]; }
40
-
41
- [Symbol.iterator]() { return this.elements[Symbol.iterator](); }
42
-
43
- // --- Traversal -----------------------------------------------------------
44
-
45
- find(selector) {
46
- const found = [];
47
- this.elements.forEach(el => found.push(...el.querySelectorAll(selector)));
48
- return new ZQueryCollection(found);
49
- }
50
-
51
- parent() {
52
- const parents = [...new Set(this.elements.map(el => el.parentElement).filter(Boolean))];
53
- return new ZQueryCollection(parents);
54
- }
55
-
56
- closest(selector) {
57
- return new ZQueryCollection(
58
- this.elements.map(el => el.closest(selector)).filter(Boolean)
59
- );
60
- }
61
-
62
- children(selector) {
63
- const kids = [];
64
- this.elements.forEach(el => {
65
- kids.push(...(selector
66
- ? el.querySelectorAll(`:scope > ${selector}`)
67
- : el.children));
68
- });
69
- return new ZQueryCollection([...kids]);
70
- }
71
-
72
- siblings(selector) {
73
- const sibs = [];
74
- this.elements.forEach(el => {
75
- if (!el.parentElement) return;
76
- const all = [...el.parentElement.children].filter(c => c !== el);
77
- sibs.push(...(selector ? all.filter(c => c.matches(selector)) : all));
78
- });
79
- return new ZQueryCollection(sibs);
80
- }
81
-
82
- next(selector) {
83
- const els = this.elements.map(el => el.nextElementSibling).filter(Boolean);
84
- return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
85
- }
86
-
87
- prev(selector) {
88
- const els = this.elements.map(el => el.previousElementSibling).filter(Boolean);
89
- return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
90
- }
91
-
92
- nextAll(selector) {
93
- const result = [];
94
- this.elements.forEach(el => {
95
- let sib = el.nextElementSibling;
96
- while (sib) {
97
- if (!selector || sib.matches(selector)) result.push(sib);
98
- sib = sib.nextElementSibling;
99
- }
100
- });
101
- return new ZQueryCollection(result);
102
- }
103
-
104
- nextUntil(selector, filter) {
105
- const result = [];
106
- this.elements.forEach(el => {
107
- let sib = el.nextElementSibling;
108
- while (sib) {
109
- if (selector && sib.matches(selector)) break;
110
- if (!filter || sib.matches(filter)) result.push(sib);
111
- sib = sib.nextElementSibling;
112
- }
113
- });
114
- return new ZQueryCollection(result);
115
- }
116
-
117
- prevAll(selector) {
118
- const result = [];
119
- this.elements.forEach(el => {
120
- let sib = el.previousElementSibling;
121
- while (sib) {
122
- if (!selector || sib.matches(selector)) result.push(sib);
123
- sib = sib.previousElementSibling;
124
- }
125
- });
126
- return new ZQueryCollection(result);
127
- }
128
-
129
- prevUntil(selector, filter) {
130
- const result = [];
131
- this.elements.forEach(el => {
132
- let sib = el.previousElementSibling;
133
- while (sib) {
134
- if (selector && sib.matches(selector)) break;
135
- if (!filter || sib.matches(filter)) result.push(sib);
136
- sib = sib.previousElementSibling;
137
- }
138
- });
139
- return new ZQueryCollection(result);
140
- }
141
-
142
- parents(selector) {
143
- const result = [];
144
- this.elements.forEach(el => {
145
- let parent = el.parentElement;
146
- while (parent) {
147
- if (!selector || parent.matches(selector)) result.push(parent);
148
- parent = parent.parentElement;
149
- }
150
- });
151
- return new ZQueryCollection([...new Set(result)]);
152
- }
153
-
154
- parentsUntil(selector, filter) {
155
- const result = [];
156
- this.elements.forEach(el => {
157
- let parent = el.parentElement;
158
- while (parent) {
159
- if (selector && parent.matches(selector)) break;
160
- if (!filter || parent.matches(filter)) result.push(parent);
161
- parent = parent.parentElement;
162
- }
163
- });
164
- return new ZQueryCollection([...new Set(result)]);
165
- }
166
-
167
- contents() {
168
- const result = [];
169
- this.elements.forEach(el => result.push(...el.childNodes));
170
- return new ZQueryCollection(result);
171
- }
172
-
173
- filter(selector) {
174
- if (typeof selector === 'function') {
175
- return new ZQueryCollection(this.elements.filter(selector));
176
- }
177
- return new ZQueryCollection(this.elements.filter(el => el.matches(selector)));
178
- }
179
-
180
- not(selector) {
181
- if (typeof selector === 'function') {
182
- return new ZQueryCollection(this.elements.filter((el, i) => !selector.call(el, i, el)));
183
- }
184
- return new ZQueryCollection(this.elements.filter(el => !el.matches(selector)));
185
- }
186
-
187
- has(selector) {
188
- return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector)));
189
- }
190
-
191
- is(selector) {
192
- if (typeof selector === 'function') {
193
- return this.elements.some((el, i) => selector.call(el, i, el));
194
- }
195
- return this.elements.some(el => el.matches(selector));
196
- }
197
-
198
- slice(start, end) {
199
- return new ZQueryCollection(this.elements.slice(start, end));
200
- }
201
-
202
- add(selector, context) {
203
- const toAdd = (selector instanceof ZQueryCollection)
204
- ? selector.elements
205
- : (selector instanceof Node)
206
- ? [selector]
207
- : Array.from((context || document).querySelectorAll(selector));
208
- return new ZQueryCollection([...this.elements, ...toAdd]);
209
- }
210
-
211
- get(index) {
212
- if (index === undefined) return [...this.elements];
213
- return index < 0 ? this.elements[this.length + index] : this.elements[index];
214
- }
215
-
216
- index(selector) {
217
- if (selector === undefined) {
218
- const el = this.first();
219
- if (!el || !el.parentElement) return -1;
220
- return Array.from(el.parentElement.children).indexOf(el);
221
- }
222
- const target = (typeof selector === 'string')
223
- ? document.querySelector(selector)
224
- : selector;
225
- return this.elements.indexOf(target);
226
- }
227
-
228
- // --- Classes -------------------------------------------------------------
229
-
230
- addClass(...names) {
231
- // Fast path: single class, no spaces - avoids flatMap + regex split allocation
232
- if (names.length === 1 && names[0].indexOf(' ') === -1) {
233
- const c = names[0];
234
- for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(c);
235
- return this;
236
- }
237
- const classes = names.flatMap(n => n.split(/\s+/));
238
- for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(...classes);
239
- return this;
240
- }
241
-
242
- removeClass(...names) {
243
- if (names.length === 1 && names[0].indexOf(' ') === -1) {
244
- const c = names[0];
245
- for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(c);
246
- return this;
247
- }
248
- const classes = names.flatMap(n => n.split(/\s+/));
249
- for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(...classes);
250
- return this;
251
- }
252
-
253
- toggleClass(...args) {
254
- const force = typeof args[args.length - 1] === 'boolean' ? args.pop() : undefined;
255
- // Fast path: single class, no spaces
256
- if (args.length === 1 && args[0].indexOf(' ') === -1) {
257
- const c = args[0];
258
- for (let i = 0; i < this.elements.length; i++) {
259
- force !== undefined ? this.elements[i].classList.toggle(c, force) : this.elements[i].classList.toggle(c);
260
- }
261
- return this;
262
- }
263
- const classes = args.flatMap(n => n.split(/\s+/));
264
- for (let i = 0; i < this.elements.length; i++) {
265
- const el = this.elements[i];
266
- for (let j = 0; j < classes.length; j++) {
267
- force !== undefined ? el.classList.toggle(classes[j], force) : el.classList.toggle(classes[j]);
268
- }
269
- }
270
- return this;
271
- }
272
-
273
- hasClass(name) {
274
- return this.first()?.classList.contains(name) || false;
275
- }
276
-
277
- // --- Attributes ----------------------------------------------------------
278
-
279
- attr(name, value) {
280
- if (typeof name === 'object' && name !== null) {
281
- return this.each((_, el) => {
282
- for (const [k, v] of Object.entries(name)) el.setAttribute(k, v);
283
- });
284
- }
285
- if (value === undefined) return this.first()?.getAttribute(name);
286
- return this.each((_, el) => el.setAttribute(name, value));
287
- }
288
-
289
- removeAttr(name) {
290
- return this.each((_, el) => el.removeAttribute(name));
291
- }
292
-
293
- prop(name, value) {
294
- if (value === undefined) return this.first()?.[name];
295
- return this.each((_, el) => { el[name] = value; });
296
- }
297
-
298
- data(key, value) {
299
- if (value === undefined) {
300
- if (key === undefined) return this.first()?.dataset;
301
- const raw = this.first()?.dataset[key];
302
- try { return JSON.parse(raw); } catch { return raw; }
303
- }
304
- return this.each((_, el) => { el.dataset[key] = typeof value === 'object' ? JSON.stringify(value) : value; });
305
- }
306
-
307
- // --- CSS / Dimensions ----------------------------------------------------
308
-
309
- css(props, value) {
310
- if (typeof props === 'string' && value !== undefined) {
311
- return this.each((_, el) => { el.style[props] = value; });
312
- }
313
- if (typeof props === 'string') {
314
- const el = this.first();
315
- return el ? getComputedStyle(el)[props] : undefined;
316
- }
317
- return this.each((_, el) => Object.assign(el.style, props));
318
- }
319
-
320
- width() { return this.first()?.getBoundingClientRect().width; }
321
- height() { return this.first()?.getBoundingClientRect().height; }
322
-
323
- offset() {
324
- const r = this.first()?.getBoundingClientRect();
325
- return r ? { top: r.top + window.scrollY, left: r.left + window.scrollX, width: r.width, height: r.height } : null;
326
- }
327
-
328
- position() {
329
- const el = this.first();
330
- return el ? { top: el.offsetTop, left: el.offsetLeft } : null;
331
- }
332
-
333
- scrollTop(value) {
334
- if (value === undefined) {
335
- const el = this.first();
336
- return el === window ? window.scrollY : el?.scrollTop;
337
- }
338
- return this.each((_, el) => {
339
- if (el === window) window.scrollTo(window.scrollX, value);
340
- else el.scrollTop = value;
341
- });
342
- }
343
-
344
- scrollLeft(value) {
345
- if (value === undefined) {
346
- const el = this.first();
347
- return el === window ? window.scrollX : el?.scrollLeft;
348
- }
349
- return this.each((_, el) => {
350
- if (el === window) window.scrollTo(value, window.scrollY);
351
- else el.scrollLeft = value;
352
- });
353
- }
354
-
355
- innerWidth() {
356
- const el = this.first();
357
- return el?.clientWidth;
358
- }
359
-
360
- innerHeight() {
361
- const el = this.first();
362
- return el?.clientHeight;
363
- }
364
-
365
- outerWidth(includeMargin = false) {
366
- const el = this.first();
367
- if (!el) return undefined;
368
- let w = el.offsetWidth;
369
- if (includeMargin) {
370
- const style = getComputedStyle(el);
371
- w += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
372
- }
373
- return w;
374
- }
375
-
376
- outerHeight(includeMargin = false) {
377
- const el = this.first();
378
- if (!el) return undefined;
379
- let h = el.offsetHeight;
380
- if (includeMargin) {
381
- const style = getComputedStyle(el);
382
- h += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
383
- }
384
- return h;
385
- }
386
-
387
- // --- Content -------------------------------------------------------------
388
-
389
- html(content) {
390
- if (content === undefined) return this.first()?.innerHTML;
391
- // Auto-morph: if the element already has children, use the diff engine
392
- // to patch the DOM (preserves focus, scroll, state, keyed reorder via LIS).
393
- // Empty elements get raw innerHTML for fast first-paint - same strategy
394
- // the component system uses (first render = innerHTML, updates = morph).
395
- return this.each((_, el) => {
396
- if (el.childNodes.length > 0) {
397
- _morph(el, content);
398
- } else {
399
- el.innerHTML = content;
400
- }
401
- });
402
- }
403
-
404
- morph(content) {
405
- return this.each((_, el) => { _morph(el, content); });
406
- }
407
-
408
- text(content) {
409
- if (content === undefined) return this.first()?.textContent;
410
- return this.each((_, el) => { el.textContent = content; });
411
- }
412
-
413
- val(value) {
414
- if (value === undefined) return this.first()?.value;
415
- return this.each((_, el) => { el.value = value; });
416
- }
417
-
418
- // --- DOM Manipulation ----------------------------------------------------
419
-
420
- append(content) {
421
- return this.each((_, el) => {
422
- if (typeof content === 'string') el.insertAdjacentHTML('beforeend', content);
423
- else if (content instanceof ZQueryCollection) content.each((__, c) => el.appendChild(c));
424
- else if (content instanceof Node) el.appendChild(content);
425
- });
426
- }
427
-
428
- prepend(content) {
429
- return this.each((_, el) => {
430
- if (typeof content === 'string') el.insertAdjacentHTML('afterbegin', content);
431
- else if (content instanceof Node) el.insertBefore(content, el.firstChild);
432
- });
433
- }
434
-
435
- after(content) {
436
- return this.each((_, el) => {
437
- if (typeof content === 'string') el.insertAdjacentHTML('afterend', content);
438
- else if (content instanceof Node) el.parentNode.insertBefore(content, el.nextSibling);
439
- });
440
- }
441
-
442
- before(content) {
443
- return this.each((_, el) => {
444
- if (typeof content === 'string') el.insertAdjacentHTML('beforebegin', content);
445
- else if (content instanceof Node) el.parentNode.insertBefore(content, el);
446
- });
447
- }
448
-
449
- wrap(wrapper) {
450
- return this.each((_, el) => {
451
- const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
452
- if (!w || !el.parentNode) return;
453
- el.parentNode.insertBefore(w, el);
454
- w.appendChild(el);
455
- });
456
- }
457
-
458
- remove() {
459
- return this.each((_, el) => el.remove());
460
- }
461
-
462
- empty() {
463
- // textContent = '' clears all children without invoking the HTML parser
464
- return this.each((_, el) => { el.textContent = ''; });
465
- }
466
-
467
- clone(deep = true) {
468
- return new ZQueryCollection(this.elements.map(el => el.cloneNode(deep)));
469
- }
470
-
471
- replaceWith(content) {
472
- return this.each((_, el) => {
473
- if (typeof content === 'string') {
474
- // Auto-morph: diff attributes + children when the tag name matches
475
- // instead of destroying and re-creating the element.
476
- _morphElement(el, content);
477
- } else if (content instanceof Node) {
478
- el.parentNode.replaceChild(content, el);
479
- }
480
- });
481
- }
482
-
483
- appendTo(target) {
484
- const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
485
- if (dest) this.each((_, el) => dest.appendChild(el));
486
- return this;
487
- }
488
-
489
- prependTo(target) {
490
- const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
491
- if (dest) this.each((_, el) => dest.insertBefore(el, dest.firstChild));
492
- return this;
493
- }
494
-
495
- insertAfter(target) {
496
- const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
497
- if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref.nextSibling));
498
- return this;
499
- }
500
-
501
- insertBefore(target) {
502
- const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
503
- if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref));
504
- return this;
505
- }
506
-
507
- replaceAll(target) {
508
- const targets = typeof target === 'string'
509
- ? Array.from(document.querySelectorAll(target))
510
- : target instanceof ZQueryCollection ? target.elements : [target];
511
- targets.forEach((t, i) => {
512
- const nodes = i === 0 ? this.elements : this.elements.map(el => el.cloneNode(true));
513
- nodes.forEach(el => t.parentNode.insertBefore(el, t));
514
- t.remove();
515
- });
516
- return this;
517
- }
518
-
519
- unwrap(selector) {
520
- this.elements.forEach(el => {
521
- const parent = el.parentElement;
522
- if (!parent || parent === document.body) return;
523
- if (selector && !parent.matches(selector)) return;
524
- parent.replaceWith(...parent.childNodes);
525
- });
526
- return this;
527
- }
528
-
529
- wrapAll(wrapper) {
530
- const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
531
- const first = this.first();
532
- if (!first) return this;
533
- first.parentNode.insertBefore(w, first);
534
- this.each((_, el) => w.appendChild(el));
535
- return this;
536
- }
537
-
538
- wrapInner(wrapper) {
539
- return this.each((_, el) => {
540
- const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
541
- while (el.firstChild) w.appendChild(el.firstChild);
542
- el.appendChild(w);
543
- });
544
- }
545
-
546
- detach() {
547
- return this.each((_, el) => el.remove());
548
- }
549
-
550
- // --- Visibility ----------------------------------------------------------
551
-
552
- show(display = '') {
553
- return this.each((_, el) => { el.style.display = display; });
554
- }
555
-
556
- hide() {
557
- return this.each((_, el) => { el.style.display = 'none'; });
558
- }
559
-
560
- toggle(display = '') {
561
- return this.each((_, el) => {
562
- // Check inline style first (cheap) before forcing layout via getComputedStyle
563
- const hidden = el.style.display === 'none' || (el.style.display !== '' ? false : getComputedStyle(el).display === 'none');
564
- el.style.display = hidden ? display : 'none';
565
- });
566
- }
567
-
568
- // --- Events --------------------------------------------------------------
569
-
570
- on(event, selectorOrHandler, handler) {
571
- // Support multiple events: "click mouseenter"
572
- const events = event.split(/\s+/);
573
- return this.each((_, el) => {
574
- events.forEach(evt => {
575
- if (typeof selectorOrHandler === 'function') {
576
- el.addEventListener(evt, selectorOrHandler);
577
- } else if (typeof selectorOrHandler === 'string') {
578
- // Delegated event - store wrapper so off() can remove it
579
- const wrapper = (e) => {
580
- if (!e.target || typeof e.target.closest !== 'function') return;
581
- const target = e.target.closest(selectorOrHandler);
582
- if (target && el.contains(target)) handler.call(target, e);
583
- };
584
- wrapper._zqOriginal = handler;
585
- wrapper._zqSelector = selectorOrHandler;
586
- el.addEventListener(evt, wrapper);
587
- // Track delegated handlers for removal
588
- if (!el._zqDelegated) el._zqDelegated = [];
589
- el._zqDelegated.push({ evt, wrapper });
590
- }
591
- });
592
- });
593
- }
594
-
595
- off(event, handler) {
596
- const events = event.split(/\s+/);
597
- return this.each((_, el) => {
598
- events.forEach(evt => {
599
- // Try direct removal first
600
- el.removeEventListener(evt, handler);
601
- // Also check delegated handlers
602
- if (el._zqDelegated) {
603
- el._zqDelegated = el._zqDelegated.filter(d => {
604
- if (d.evt === evt && d.wrapper._zqOriginal === handler) {
605
- el.removeEventListener(evt, d.wrapper);
606
- return false;
607
- }
608
- return true;
609
- });
610
- }
611
- });
612
- });
613
- }
614
-
615
- one(event, handler) {
616
- return this.each((_, el) => {
617
- el.addEventListener(event, handler, { once: true });
618
- });
619
- }
620
-
621
- trigger(event, detail) {
622
- return this.each((_, el) => {
623
- el.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
624
- });
625
- }
626
-
627
- // Convenience event shorthands
628
- click(fn) { return fn ? this.on('click', fn) : this.trigger('click'); }
629
- submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); }
630
- focus() { this.first()?.focus(); return this; }
631
- blur() { this.first()?.blur(); return this; }
632
- hover(enterFn, leaveFn) {
633
- this.on('mouseenter', enterFn);
634
- return this.on('mouseleave', leaveFn || enterFn);
635
- }
636
-
637
- // --- Animation -----------------------------------------------------------
638
-
639
- animate(props, duration = 300, easing = 'ease') {
640
- // Empty collection - resolve immediately
641
- if (this.length === 0) return Promise.resolve(this);
642
- return new Promise(resolve => {
643
- let resolved = false;
644
- const count = { done: 0 };
645
- const listeners = [];
646
- this.each((_, el) => {
647
- el.style.transition = `all ${duration}ms ${easing}`;
648
- requestAnimationFrame(() => {
649
- Object.assign(el.style, props);
650
- const onEnd = () => {
651
- el.removeEventListener('transitionend', onEnd);
652
- el.style.transition = '';
653
- if (!resolved && ++count.done >= this.length) {
654
- resolved = true;
655
- resolve(this);
656
- }
657
- };
658
- el.addEventListener('transitionend', onEnd);
659
- listeners.push({ el, onEnd });
660
- });
661
- });
662
- // Fallback in case transitionend doesn't fire
663
- setTimeout(() => {
664
- if (!resolved) {
665
- resolved = true;
666
- // Clean up any remaining transitionend listeners
667
- for (const { el, onEnd } of listeners) {
668
- el.removeEventListener('transitionend', onEnd);
669
- el.style.transition = '';
670
- }
671
- resolve(this);
672
- }
673
- }, duration + 50);
674
- });
675
- }
676
-
677
- fadeIn(duration = 300) {
678
- return this.css({ opacity: '0', display: '' }).animate({ opacity: '1' }, duration);
679
- }
680
-
681
- fadeOut(duration = 300) {
682
- return this.animate({ opacity: '0' }, duration).then(col => col.hide());
683
- }
684
-
685
- fadeToggle(duration = 300) {
686
- return Promise.all(this.elements.map(el => {
687
- const cs = getComputedStyle(el);
688
- const visible = cs.opacity !== '0' && cs.display !== 'none';
689
- const col = new ZQueryCollection([el]);
690
- return visible ? col.fadeOut(duration) : col.fadeIn(duration);
691
- })).then(() => this);
692
- }
693
-
694
- fadeTo(duration, opacity) {
695
- return this.animate({ opacity: String(opacity) }, duration);
696
- }
697
-
698
- slideDown(duration = 300) {
699
- return this.each((_, el) => {
700
- el.style.display = '';
701
- el.style.overflow = 'hidden';
702
- const h = el.scrollHeight + 'px';
703
- el.style.maxHeight = '0';
704
- el.style.transition = `max-height ${duration}ms ease`;
705
- requestAnimationFrame(() => { el.style.maxHeight = h; });
706
- setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
707
- });
708
- }
709
-
710
- slideUp(duration = 300) {
711
- return this.each((_, el) => {
712
- el.style.overflow = 'hidden';
713
- el.style.maxHeight = el.scrollHeight + 'px';
714
- el.style.transition = `max-height ${duration}ms ease`;
715
- requestAnimationFrame(() => { el.style.maxHeight = '0'; });
716
- setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
717
- });
718
- }
719
-
720
- slideToggle(duration = 300) {
721
- return this.each((_, el) => {
722
- if (el.style.display === 'none' || getComputedStyle(el).display === 'none') {
723
- el.style.display = '';
724
- el.style.overflow = 'hidden';
725
- const h = el.scrollHeight + 'px';
726
- el.style.maxHeight = '0';
727
- el.style.transition = `max-height ${duration}ms ease`;
728
- requestAnimationFrame(() => { el.style.maxHeight = h; });
729
- setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
730
- } else {
731
- el.style.overflow = 'hidden';
732
- el.style.maxHeight = el.scrollHeight + 'px';
733
- el.style.transition = `max-height ${duration}ms ease`;
734
- requestAnimationFrame(() => { el.style.maxHeight = '0'; });
735
- setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
736
- }
737
- });
738
- }
739
-
740
- // --- Form helpers --------------------------------------------------------
741
-
742
- serialize() {
743
- const form = this.first();
744
- if (!form || form.tagName !== 'FORM') return '';
745
- return new URLSearchParams(new FormData(form)).toString();
746
- }
747
-
748
- serializeObject() {
749
- const form = this.first();
750
- if (!form || form.tagName !== 'FORM') return {};
751
- const obj = {};
752
- new FormData(form).forEach((v, k) => {
753
- if (obj[k] !== undefined) {
754
- if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
755
- obj[k].push(v);
756
- } else {
757
- obj[k] = v;
758
- }
759
- });
760
- return obj;
761
- }
762
- }
763
-
764
-
765
- // ---------------------------------------------------------------------------
766
- // Helper - create document fragment from HTML string
767
- // ---------------------------------------------------------------------------
768
- function createFragment(html) {
769
- const tpl = document.createElement('template');
770
- tpl.innerHTML = html.trim();
771
- return tpl.content;
772
- }
773
-
774
-
775
- // ---------------------------------------------------------------------------
776
- // $() - main selector / creator (returns ZQueryCollection, like jQuery)
777
- // ---------------------------------------------------------------------------
778
- export function query(selector, context) {
779
- // null / undefined
780
- if (!selector) return new ZQueryCollection([]);
781
-
782
- // Already a collection - return as-is
783
- if (selector instanceof ZQueryCollection) return selector;
784
-
785
- // DOM element or Window - wrap in collection
786
- if (selector instanceof Node || selector === window) {
787
- return new ZQueryCollection([selector]);
788
- }
789
-
790
- // NodeList / HTMLCollection / Array - wrap in collection
791
- if (selector instanceof NodeList || selector instanceof HTMLCollection || Array.isArray(selector)) {
792
- return new ZQueryCollection(Array.from(selector));
793
- }
794
-
795
- // HTML string → create elements, wrap in collection
796
- if (typeof selector === 'string' && selector.trim().startsWith('<')) {
797
- const fragment = createFragment(selector);
798
- return new ZQueryCollection([...fragment.childNodes].filter(n => n.nodeType === 1));
799
- }
800
-
801
- // CSS selector string → querySelectorAll (collection)
802
- if (typeof selector === 'string') {
803
- const root = context
804
- ? (typeof context === 'string' ? document.querySelector(context) : context)
805
- : document;
806
- return new ZQueryCollection([...root.querySelectorAll(selector)]);
807
- }
808
-
809
- return new ZQueryCollection([]);
810
- }
811
-
812
-
813
- // ---------------------------------------------------------------------------
814
- // $.all() - collection selector (returns ZQueryCollection for CSS selectors)
815
- // ---------------------------------------------------------------------------
816
- export function queryAll(selector, context) {
817
- // null / undefined
818
- if (!selector) return new ZQueryCollection([]);
819
-
820
- // Already a collection
821
- if (selector instanceof ZQueryCollection) return selector;
822
-
823
- // DOM element or Window
824
- if (selector instanceof Node || selector === window) {
825
- return new ZQueryCollection([selector]);
826
- }
827
-
828
- // NodeList / HTMLCollection / Array
829
- if (selector instanceof NodeList || selector instanceof HTMLCollection || Array.isArray(selector)) {
830
- return new ZQueryCollection(Array.from(selector));
831
- }
832
-
833
- // HTML string → create elements
834
- if (typeof selector === 'string' && selector.trim().startsWith('<')) {
835
- const fragment = createFragment(selector);
836
- return new ZQueryCollection([...fragment.childNodes].filter(n => n.nodeType === 1));
837
- }
838
-
839
- // CSS selector string → querySelectorAll (collection)
840
- if (typeof selector === 'string') {
841
- const root = context
842
- ? (typeof context === 'string' ? document.querySelector(context) : context)
843
- : document;
844
- return new ZQueryCollection([...root.querySelectorAll(selector)]);
845
- }
846
-
847
- return new ZQueryCollection([]);
848
- }
849
-
850
-
851
- // ---------------------------------------------------------------------------
852
- // Quick-ref shortcuts, on $ namespace)
853
- // ---------------------------------------------------------------------------
854
- query.id = (id) => document.getElementById(id);
855
- query.class = (name) => document.querySelector(`.${name}`);
856
- query.classes = (name) => new ZQueryCollection(Array.from(document.getElementsByClassName(name)));
857
- query.tag = (name) => new ZQueryCollection(Array.from(document.getElementsByTagName(name)));
858
- Object.defineProperty(query, 'name', {
859
- value: (name) => new ZQueryCollection(Array.from(document.getElementsByName(name))),
860
- writable: true, configurable: true
861
- });
862
- query.children = (parentId) => {
863
- const p = document.getElementById(parentId);
864
- return new ZQueryCollection(p ? Array.from(p.children) : []);
865
- };
866
- query.qs = (sel, ctx = document) => ctx.querySelector(sel);
867
- query.qsa = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
868
-
869
- // Create element shorthand - returns ZQueryCollection for chaining
870
- query.create = (tag, attrs = {}, ...children) => {
871
- const el = document.createElement(tag);
872
- for (const [k, v] of Object.entries(attrs)) {
873
- if (k === 'class') el.className = v;
874
- else if (k === 'style' && typeof v === 'object') Object.assign(el.style, v);
875
- else if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2).toLowerCase(), v);
876
- else if (k === 'data' && typeof v === 'object') Object.entries(v).forEach(([dk, dv]) => { el.dataset[dk] = dv; });
877
- else el.setAttribute(k, v);
878
- }
879
- children.flat().forEach(child => {
880
- if (typeof child === 'string') el.appendChild(document.createTextNode(child));
881
- else if (child instanceof Node) el.appendChild(child);
882
- });
883
- return new ZQueryCollection(el);
884
- };
885
-
886
- // DOM ready
887
- query.ready = (fn) => {
888
- if (document.readyState !== 'loading') fn();
889
- else document.addEventListener('DOMContentLoaded', fn);
890
- };
891
-
892
- // Global event listeners - supports direct, delegated, and target-bound forms
893
- // $.on('keydown', handler) → direct listener on document
894
- // $.on('click', '.btn', handler) → delegated via closest()
895
- // $.on('scroll', window, handler) direct listener on target
896
- query.on = (event, selectorOrHandler, handler) => {
897
- if (typeof selectorOrHandler === 'function') {
898
- // 2-arg: direct document listener (keydown, resize, etc.)
899
- document.addEventListener(event, selectorOrHandler);
900
- return;
901
- }
902
- // EventTarget (window, element, etc.) - direct listener on target
903
- if (typeof selectorOrHandler === 'object' && typeof selectorOrHandler.addEventListener === 'function') {
904
- selectorOrHandler.addEventListener(event, handler);
905
- return;
906
- }
907
- // 3-arg string: delegated
908
- document.addEventListener(event, (e) => {
909
- if (!e.target || typeof e.target.closest !== 'function') return;
910
- const target = e.target.closest(selectorOrHandler);
911
- if (target) handler.call(target, e);
912
- });
913
- };
914
-
915
- // Remove a direct global listener
916
- query.off = (event, handler) => {
917
- document.removeEventListener(event, handler);
918
- };
919
-
920
- // Extend collection prototype (like $.fn in jQuery)
921
- query.fn = ZQueryCollection.prototype;
1
+ /**
2
+ * zQuery Core - Selector engine & chainable DOM collection
3
+ *
4
+ * Extends the quick-ref pattern (Id, Class, Classes, Children)
5
+ * into a full jQuery-like chainable wrapper with modern APIs.
6
+ */
7
+
8
+ import { morph as _morph, morphElement as _morphElement } from './diff.js';
9
+
10
+ // ---------------------------------------------------------------------------
11
+ // ZQueryCollection - wraps an array of elements with chainable methods
12
+ // ---------------------------------------------------------------------------
13
+ export class ZQueryCollection {
14
+ constructor(elements) {
15
+ this.elements = Array.isArray(elements) ? elements : (elements ? [elements] : []);
16
+ this.length = this.elements.length;
17
+ this.elements.forEach((el, i) => { this[i] = el; });
18
+ }
19
+
20
+ // --- Iteration -----------------------------------------------------------
21
+
22
+ each(fn) {
23
+ this.elements.forEach((el, i) => fn.call(el, i, el));
24
+ return this;
25
+ }
26
+
27
+ map(fn) {
28
+ return this.elements.map((el, i) => fn.call(el, i, el));
29
+ }
30
+
31
+ forEach(fn) {
32
+ this.elements.forEach((el, i) => fn(el, i, this.elements));
33
+ return this;
34
+ }
35
+
36
+ first() { return this.elements[0] || null; }
37
+ last() { return this.elements[this.length - 1] || null; }
38
+ eq(i) { return new ZQueryCollection(this.elements[i] ? [this.elements[i]] : []); }
39
+ toArray(){ return [...this.elements]; }
40
+
41
+ [Symbol.iterator]() { return this.elements[Symbol.iterator](); }
42
+
43
+ // --- Traversal -----------------------------------------------------------
44
+
45
+ find(selector) {
46
+ const found = [];
47
+ this.elements.forEach(el => found.push(...el.querySelectorAll(selector)));
48
+ return new ZQueryCollection(found);
49
+ }
50
+
51
+ parent() {
52
+ const parents = [...new Set(this.elements.map(el => el.parentElement).filter(Boolean))];
53
+ return new ZQueryCollection(parents);
54
+ }
55
+
56
+ closest(selector) {
57
+ return new ZQueryCollection(
58
+ this.elements.map(el => el.closest(selector)).filter(Boolean)
59
+ );
60
+ }
61
+
62
+ children(selector) {
63
+ const kids = [];
64
+ this.elements.forEach(el => {
65
+ kids.push(...(selector
66
+ ? el.querySelectorAll(`:scope > ${selector}`)
67
+ : el.children));
68
+ });
69
+ return new ZQueryCollection([...kids]);
70
+ }
71
+
72
+ siblings(selector) {
73
+ const sibs = [];
74
+ this.elements.forEach(el => {
75
+ if (!el.parentElement) return;
76
+ const all = [...el.parentElement.children].filter(c => c !== el);
77
+ sibs.push(...(selector ? all.filter(c => c.matches(selector)) : all));
78
+ });
79
+ return new ZQueryCollection(sibs);
80
+ }
81
+
82
+ next(selector) {
83
+ const els = this.elements.map(el => el.nextElementSibling).filter(Boolean);
84
+ return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
85
+ }
86
+
87
+ prev(selector) {
88
+ const els = this.elements.map(el => el.previousElementSibling).filter(Boolean);
89
+ return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
90
+ }
91
+
92
+ nextAll(selector) {
93
+ const result = [];
94
+ this.elements.forEach(el => {
95
+ let sib = el.nextElementSibling;
96
+ while (sib) {
97
+ if (!selector || sib.matches(selector)) result.push(sib);
98
+ sib = sib.nextElementSibling;
99
+ }
100
+ });
101
+ return new ZQueryCollection(result);
102
+ }
103
+
104
+ nextUntil(selector, filter) {
105
+ const result = [];
106
+ this.elements.forEach(el => {
107
+ let sib = el.nextElementSibling;
108
+ while (sib) {
109
+ if (selector && sib.matches(selector)) break;
110
+ if (!filter || sib.matches(filter)) result.push(sib);
111
+ sib = sib.nextElementSibling;
112
+ }
113
+ });
114
+ return new ZQueryCollection(result);
115
+ }
116
+
117
+ prevAll(selector) {
118
+ const result = [];
119
+ this.elements.forEach(el => {
120
+ let sib = el.previousElementSibling;
121
+ while (sib) {
122
+ if (!selector || sib.matches(selector)) result.push(sib);
123
+ sib = sib.previousElementSibling;
124
+ }
125
+ });
126
+ return new ZQueryCollection(result);
127
+ }
128
+
129
+ prevUntil(selector, filter) {
130
+ const result = [];
131
+ this.elements.forEach(el => {
132
+ let sib = el.previousElementSibling;
133
+ while (sib) {
134
+ if (selector && sib.matches(selector)) break;
135
+ if (!filter || sib.matches(filter)) result.push(sib);
136
+ sib = sib.previousElementSibling;
137
+ }
138
+ });
139
+ return new ZQueryCollection(result);
140
+ }
141
+
142
+ parents(selector) {
143
+ const result = [];
144
+ this.elements.forEach(el => {
145
+ let parent = el.parentElement;
146
+ while (parent) {
147
+ if (!selector || parent.matches(selector)) result.push(parent);
148
+ parent = parent.parentElement;
149
+ }
150
+ });
151
+ return new ZQueryCollection([...new Set(result)]);
152
+ }
153
+
154
+ parentsUntil(selector, filter) {
155
+ const result = [];
156
+ this.elements.forEach(el => {
157
+ let parent = el.parentElement;
158
+ while (parent) {
159
+ if (selector && parent.matches(selector)) break;
160
+ if (!filter || parent.matches(filter)) result.push(parent);
161
+ parent = parent.parentElement;
162
+ }
163
+ });
164
+ return new ZQueryCollection([...new Set(result)]);
165
+ }
166
+
167
+ contents() {
168
+ const result = [];
169
+ this.elements.forEach(el => result.push(...el.childNodes));
170
+ return new ZQueryCollection(result);
171
+ }
172
+
173
+ filter(selector) {
174
+ if (typeof selector === 'function') {
175
+ return new ZQueryCollection(this.elements.filter(selector));
176
+ }
177
+ return new ZQueryCollection(this.elements.filter(el => el.matches(selector)));
178
+ }
179
+
180
+ not(selector) {
181
+ if (typeof selector === 'function') {
182
+ return new ZQueryCollection(this.elements.filter((el, i) => !selector.call(el, i, el)));
183
+ }
184
+ return new ZQueryCollection(this.elements.filter(el => !el.matches(selector)));
185
+ }
186
+
187
+ has(selector) {
188
+ return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector)));
189
+ }
190
+
191
+ is(selector) {
192
+ if (typeof selector === 'function') {
193
+ return this.elements.some((el, i) => selector.call(el, i, el));
194
+ }
195
+ return this.elements.some(el => el.matches(selector));
196
+ }
197
+
198
+ slice(start, end) {
199
+ return new ZQueryCollection(this.elements.slice(start, end));
200
+ }
201
+
202
+ add(selector, context) {
203
+ const toAdd = (selector instanceof ZQueryCollection)
204
+ ? selector.elements
205
+ : (selector instanceof Node)
206
+ ? [selector]
207
+ : Array.from((context || document).querySelectorAll(selector));
208
+ return new ZQueryCollection([...this.elements, ...toAdd]);
209
+ }
210
+
211
+ get(index) {
212
+ if (index === undefined) return [...this.elements];
213
+ return index < 0 ? this.elements[this.length + index] : this.elements[index];
214
+ }
215
+
216
+ index(selector) {
217
+ if (selector === undefined) {
218
+ const el = this.first();
219
+ if (!el || !el.parentElement) return -1;
220
+ return Array.from(el.parentElement.children).indexOf(el);
221
+ }
222
+ const target = (typeof selector === 'string')
223
+ ? document.querySelector(selector)
224
+ : selector;
225
+ return this.elements.indexOf(target);
226
+ }
227
+
228
+ // --- Classes -------------------------------------------------------------
229
+
230
+ addClass(...names) {
231
+ // Fast path: single class, no spaces - avoids flatMap + regex split allocation
232
+ if (names.length === 1 && names[0].indexOf(' ') === -1) {
233
+ const c = names[0];
234
+ for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(c);
235
+ return this;
236
+ }
237
+ const classes = names.flatMap(n => n.split(/\s+/));
238
+ for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(...classes);
239
+ return this;
240
+ }
241
+
242
+ removeClass(...names) {
243
+ if (names.length === 1 && names[0].indexOf(' ') === -1) {
244
+ const c = names[0];
245
+ for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(c);
246
+ return this;
247
+ }
248
+ const classes = names.flatMap(n => n.split(/\s+/));
249
+ for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(...classes);
250
+ return this;
251
+ }
252
+
253
+ toggleClass(...args) {
254
+ const force = typeof args[args.length - 1] === 'boolean' ? args.pop() : undefined;
255
+ // Fast path: single class, no spaces
256
+ if (args.length === 1 && args[0].indexOf(' ') === -1) {
257
+ const c = args[0];
258
+ for (let i = 0; i < this.elements.length; i++) {
259
+ force !== undefined ? this.elements[i].classList.toggle(c, force) : this.elements[i].classList.toggle(c);
260
+ }
261
+ return this;
262
+ }
263
+ const classes = args.flatMap(n => n.split(/\s+/));
264
+ for (let i = 0; i < this.elements.length; i++) {
265
+ const el = this.elements[i];
266
+ for (let j = 0; j < classes.length; j++) {
267
+ force !== undefined ? el.classList.toggle(classes[j], force) : el.classList.toggle(classes[j]);
268
+ }
269
+ }
270
+ return this;
271
+ }
272
+
273
+ hasClass(name) {
274
+ return this.first()?.classList.contains(name) || false;
275
+ }
276
+
277
+ // --- Attributes ----------------------------------------------------------
278
+
279
+ attr(name, value) {
280
+ if (typeof name === 'object' && name !== null) {
281
+ return this.each((_, el) => {
282
+ for (const [k, v] of Object.entries(name)) el.setAttribute(k, v);
283
+ });
284
+ }
285
+ if (value === undefined) return this.first()?.getAttribute(name);
286
+ // Fast path: tight loop, no closure allocation, no `each` overhead.
287
+ // Mirrors the addClass/removeClass single-name fast path.
288
+ const els = this.elements;
289
+ for (let i = 0; i < els.length; i++) els[i].setAttribute(name, value);
290
+ return this;
291
+ }
292
+
293
+ removeAttr(name) {
294
+ const els = this.elements;
295
+ for (let i = 0; i < els.length; i++) els[i].removeAttribute(name);
296
+ return this;
297
+ }
298
+
299
+ prop(name, value) {
300
+ if (value === undefined) return this.first()?.[name];
301
+ return this.each((_, el) => { el[name] = value; });
302
+ }
303
+
304
+ data(key, value) {
305
+ if (value === undefined) {
306
+ if (key === undefined) return this.first()?.dataset;
307
+ const raw = this.first()?.dataset[key];
308
+ try { return JSON.parse(raw); } catch { return raw; }
309
+ }
310
+ return this.each((_, el) => { el.dataset[key] = typeof value === 'object' ? JSON.stringify(value) : value; });
311
+ }
312
+
313
+ // --- CSS / Dimensions ----------------------------------------------------
314
+
315
+ css(props, value) {
316
+ if (typeof props === 'string' && value !== undefined) {
317
+ return this.each((_, el) => { el.style[props] = value; });
318
+ }
319
+ if (typeof props === 'string') {
320
+ const el = this.first();
321
+ return el ? getComputedStyle(el)[props] : undefined;
322
+ }
323
+ return this.each((_, el) => Object.assign(el.style, props));
324
+ }
325
+
326
+ width() { return this.first()?.getBoundingClientRect().width; }
327
+ height() { return this.first()?.getBoundingClientRect().height; }
328
+
329
+ offset() {
330
+ const r = this.first()?.getBoundingClientRect();
331
+ return r ? { top: r.top + window.scrollY, left: r.left + window.scrollX, width: r.width, height: r.height } : null;
332
+ }
333
+
334
+ position() {
335
+ const el = this.first();
336
+ return el ? { top: el.offsetTop, left: el.offsetLeft } : null;
337
+ }
338
+
339
+ scrollTop(value) {
340
+ if (value === undefined) {
341
+ const el = this.first();
342
+ return el === window ? window.scrollY : el?.scrollTop;
343
+ }
344
+ return this.each((_, el) => {
345
+ if (el === window) window.scrollTo(window.scrollX, value);
346
+ else el.scrollTop = value;
347
+ });
348
+ }
349
+
350
+ scrollLeft(value) {
351
+ if (value === undefined) {
352
+ const el = this.first();
353
+ return el === window ? window.scrollX : el?.scrollLeft;
354
+ }
355
+ return this.each((_, el) => {
356
+ if (el === window) window.scrollTo(value, window.scrollY);
357
+ else el.scrollLeft = value;
358
+ });
359
+ }
360
+
361
+ innerWidth() {
362
+ const el = this.first();
363
+ return el?.clientWidth;
364
+ }
365
+
366
+ innerHeight() {
367
+ const el = this.first();
368
+ return el?.clientHeight;
369
+ }
370
+
371
+ outerWidth(includeMargin = false) {
372
+ const el = this.first();
373
+ if (!el) return undefined;
374
+ let w = el.offsetWidth;
375
+ if (includeMargin) {
376
+ const style = getComputedStyle(el);
377
+ w += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
378
+ }
379
+ return w;
380
+ }
381
+
382
+ outerHeight(includeMargin = false) {
383
+ const el = this.first();
384
+ if (!el) return undefined;
385
+ let h = el.offsetHeight;
386
+ if (includeMargin) {
387
+ const style = getComputedStyle(el);
388
+ h += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
389
+ }
390
+ return h;
391
+ }
392
+
393
+ // --- Content -------------------------------------------------------------
394
+
395
+ html(content) {
396
+ if (content === undefined) return this.first()?.innerHTML;
397
+ // Auto-morph: if the element already has children, use the diff engine
398
+ // to patch the DOM (preserves focus, scroll, state, keyed reorder via LIS).
399
+ // Empty elements get raw innerHTML for fast first-paint - same strategy
400
+ // the component system uses (first render = innerHTML, updates = morph).
401
+ return this.each((_, el) => {
402
+ if (el.childNodes.length > 0) {
403
+ _morph(el, content);
404
+ } else {
405
+ el.innerHTML = content;
406
+ }
407
+ });
408
+ }
409
+
410
+ morph(content) {
411
+ return this.each((_, el) => { _morph(el, content); });
412
+ }
413
+
414
+ text(content) {
415
+ if (content === undefined) return this.first()?.textContent;
416
+ return this.each((_, el) => { el.textContent = content; });
417
+ }
418
+
419
+ val(value) {
420
+ if (value === undefined) return this.first()?.value;
421
+ return this.each((_, el) => { el.value = value; });
422
+ }
423
+
424
+ // --- DOM Manipulation ----------------------------------------------------
425
+
426
+ append(content) {
427
+ return this.each((_, el) => {
428
+ if (typeof content === 'string') el.insertAdjacentHTML('beforeend', content);
429
+ else if (content instanceof ZQueryCollection) content.each((__, c) => el.appendChild(c));
430
+ else if (content instanceof Node) el.appendChild(content);
431
+ });
432
+ }
433
+
434
+ prepend(content) {
435
+ return this.each((_, el) => {
436
+ if (typeof content === 'string') el.insertAdjacentHTML('afterbegin', content);
437
+ else if (content instanceof Node) el.insertBefore(content, el.firstChild);
438
+ });
439
+ }
440
+
441
+ after(content) {
442
+ return this.each((_, el) => {
443
+ if (typeof content === 'string') el.insertAdjacentHTML('afterend', content);
444
+ else if (content instanceof Node) el.parentNode.insertBefore(content, el.nextSibling);
445
+ });
446
+ }
447
+
448
+ before(content) {
449
+ return this.each((_, el) => {
450
+ if (typeof content === 'string') el.insertAdjacentHTML('beforebegin', content);
451
+ else if (content instanceof Node) el.parentNode.insertBefore(content, el);
452
+ });
453
+ }
454
+
455
+ wrap(wrapper) {
456
+ return this.each((_, el) => {
457
+ const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
458
+ if (!w || !el.parentNode) return;
459
+ el.parentNode.insertBefore(w, el);
460
+ w.appendChild(el);
461
+ });
462
+ }
463
+
464
+ remove() {
465
+ return this.each((_, el) => el.remove());
466
+ }
467
+
468
+ empty() {
469
+ // textContent = '' clears all children without invoking the HTML parser
470
+ return this.each((_, el) => { el.textContent = ''; });
471
+ }
472
+
473
+ clone(deep = true) {
474
+ return new ZQueryCollection(this.elements.map(el => el.cloneNode(deep)));
475
+ }
476
+
477
+ replaceWith(content) {
478
+ return this.each((_, el) => {
479
+ if (typeof content === 'string') {
480
+ // Auto-morph: diff attributes + children when the tag name matches
481
+ // instead of destroying and re-creating the element.
482
+ _morphElement(el, content);
483
+ } else if (content instanceof Node) {
484
+ el.parentNode.replaceChild(content, el);
485
+ }
486
+ });
487
+ }
488
+
489
+ appendTo(target) {
490
+ const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
491
+ if (dest) this.each((_, el) => dest.appendChild(el));
492
+ return this;
493
+ }
494
+
495
+ prependTo(target) {
496
+ const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
497
+ if (dest) this.each((_, el) => dest.insertBefore(el, dest.firstChild));
498
+ return this;
499
+ }
500
+
501
+ insertAfter(target) {
502
+ const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
503
+ if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref.nextSibling));
504
+ return this;
505
+ }
506
+
507
+ insertBefore(target) {
508
+ const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
509
+ if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref));
510
+ return this;
511
+ }
512
+
513
+ replaceAll(target) {
514
+ const targets = typeof target === 'string'
515
+ ? Array.from(document.querySelectorAll(target))
516
+ : target instanceof ZQueryCollection ? target.elements : [target];
517
+ targets.forEach((t, i) => {
518
+ const nodes = i === 0 ? this.elements : this.elements.map(el => el.cloneNode(true));
519
+ nodes.forEach(el => t.parentNode.insertBefore(el, t));
520
+ t.remove();
521
+ });
522
+ return this;
523
+ }
524
+
525
+ unwrap(selector) {
526
+ this.elements.forEach(el => {
527
+ const parent = el.parentElement;
528
+ if (!parent || parent === document.body) return;
529
+ if (selector && !parent.matches(selector)) return;
530
+ parent.replaceWith(...parent.childNodes);
531
+ });
532
+ return this;
533
+ }
534
+
535
+ wrapAll(wrapper) {
536
+ const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
537
+ const first = this.first();
538
+ if (!first) return this;
539
+ first.parentNode.insertBefore(w, first);
540
+ this.each((_, el) => w.appendChild(el));
541
+ return this;
542
+ }
543
+
544
+ wrapInner(wrapper) {
545
+ return this.each((_, el) => {
546
+ const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
547
+ while (el.firstChild) w.appendChild(el.firstChild);
548
+ el.appendChild(w);
549
+ });
550
+ }
551
+
552
+ detach() {
553
+ return this.each((_, el) => el.remove());
554
+ }
555
+
556
+ // --- Visibility ----------------------------------------------------------
557
+
558
+ show(display = '') {
559
+ return this.each((_, el) => { el.style.display = display; });
560
+ }
561
+
562
+ hide() {
563
+ return this.each((_, el) => { el.style.display = 'none'; });
564
+ }
565
+
566
+ toggle(display = '') {
567
+ return this.each((_, el) => {
568
+ // Check inline style first (cheap) before forcing layout via getComputedStyle
569
+ const hidden = el.style.display === 'none' || (el.style.display !== '' ? false : getComputedStyle(el).display === 'none');
570
+ el.style.display = hidden ? display : 'none';
571
+ });
572
+ }
573
+
574
+ // --- Events --------------------------------------------------------------
575
+
576
+ on(event, selectorOrHandler, handler) {
577
+ // Support multiple events: "click mouseenter"
578
+ const events = event.split(/\s+/);
579
+ return this.each((_, el) => {
580
+ events.forEach(evt => {
581
+ if (typeof selectorOrHandler === 'function') {
582
+ el.addEventListener(evt, selectorOrHandler);
583
+ } else if (typeof selectorOrHandler === 'string') {
584
+ // Delegated event - store wrapper so off() can remove it
585
+ const wrapper = (e) => {
586
+ if (!e.target || typeof e.target.closest !== 'function') return;
587
+ const target = e.target.closest(selectorOrHandler);
588
+ if (target && el.contains(target)) handler.call(target, e);
589
+ };
590
+ wrapper._zqOriginal = handler;
591
+ wrapper._zqSelector = selectorOrHandler;
592
+ el.addEventListener(evt, wrapper);
593
+ // Track delegated handlers for removal
594
+ if (!el._zqDelegated) el._zqDelegated = [];
595
+ el._zqDelegated.push({ evt, wrapper });
596
+ }
597
+ });
598
+ });
599
+ }
600
+
601
+ off(event, handler) {
602
+ const events = event.split(/\s+/);
603
+ return this.each((_, el) => {
604
+ events.forEach(evt => {
605
+ // Try direct removal first
606
+ el.removeEventListener(evt, handler);
607
+ // Also check delegated handlers
608
+ if (el._zqDelegated) {
609
+ el._zqDelegated = el._zqDelegated.filter(d => {
610
+ if (d.evt === evt && d.wrapper._zqOriginal === handler) {
611
+ el.removeEventListener(evt, d.wrapper);
612
+ return false;
613
+ }
614
+ return true;
615
+ });
616
+ }
617
+ });
618
+ });
619
+ }
620
+
621
+ one(event, handler) {
622
+ return this.each((_, el) => {
623
+ el.addEventListener(event, handler, { once: true });
624
+ });
625
+ }
626
+
627
+ trigger(event, detail) {
628
+ return this.each((_, el) => {
629
+ el.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true }));
630
+ });
631
+ }
632
+
633
+ // Convenience event shorthands
634
+ click(fn) { return fn ? this.on('click', fn) : this.trigger('click'); }
635
+ submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); }
636
+ focus() { this.first()?.focus(); return this; }
637
+ blur() { this.first()?.blur(); return this; }
638
+ hover(enterFn, leaveFn) {
639
+ this.on('mouseenter', enterFn);
640
+ return this.on('mouseleave', leaveFn || enterFn);
641
+ }
642
+
643
+ // --- Animation -----------------------------------------------------------
644
+
645
+ animate(props, duration = 300, easing = 'ease') {
646
+ // Empty collection - resolve immediately
647
+ if (this.length === 0) return Promise.resolve(this);
648
+ return new Promise(resolve => {
649
+ let resolved = false;
650
+ const count = { done: 0 };
651
+ const listeners = [];
652
+ this.each((_, el) => {
653
+ el.style.transition = `all ${duration}ms ${easing}`;
654
+ requestAnimationFrame(() => {
655
+ Object.assign(el.style, props);
656
+ const onEnd = () => {
657
+ el.removeEventListener('transitionend', onEnd);
658
+ el.style.transition = '';
659
+ if (!resolved && ++count.done >= this.length) {
660
+ resolved = true;
661
+ resolve(this);
662
+ }
663
+ };
664
+ el.addEventListener('transitionend', onEnd);
665
+ listeners.push({ el, onEnd });
666
+ });
667
+ });
668
+ // Fallback in case transitionend doesn't fire
669
+ setTimeout(() => {
670
+ if (!resolved) {
671
+ resolved = true;
672
+ // Clean up any remaining transitionend listeners
673
+ for (const { el, onEnd } of listeners) {
674
+ el.removeEventListener('transitionend', onEnd);
675
+ el.style.transition = '';
676
+ }
677
+ resolve(this);
678
+ }
679
+ }, duration + 50);
680
+ });
681
+ }
682
+
683
+ fadeIn(duration = 300) {
684
+ return this.css({ opacity: '0', display: '' }).animate({ opacity: '1' }, duration);
685
+ }
686
+
687
+ fadeOut(duration = 300) {
688
+ return this.animate({ opacity: '0' }, duration).then(col => col.hide());
689
+ }
690
+
691
+ fadeToggle(duration = 300) {
692
+ return Promise.all(this.elements.map(el => {
693
+ const cs = getComputedStyle(el);
694
+ const visible = cs.opacity !== '0' && cs.display !== 'none';
695
+ const col = new ZQueryCollection([el]);
696
+ return visible ? col.fadeOut(duration) : col.fadeIn(duration);
697
+ })).then(() => this);
698
+ }
699
+
700
+ fadeTo(duration, opacity) {
701
+ return this.animate({ opacity: String(opacity) }, duration);
702
+ }
703
+
704
+ slideDown(duration = 300) {
705
+ return this.each((_, el) => {
706
+ el.style.display = '';
707
+ el.style.overflow = 'hidden';
708
+ const h = el.scrollHeight + 'px';
709
+ el.style.maxHeight = '0';
710
+ el.style.transition = `max-height ${duration}ms ease`;
711
+ requestAnimationFrame(() => { el.style.maxHeight = h; });
712
+ setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
713
+ });
714
+ }
715
+
716
+ slideUp(duration = 300) {
717
+ return this.each((_, el) => {
718
+ el.style.overflow = 'hidden';
719
+ el.style.maxHeight = el.scrollHeight + 'px';
720
+ el.style.transition = `max-height ${duration}ms ease`;
721
+ requestAnimationFrame(() => { el.style.maxHeight = '0'; });
722
+ setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
723
+ });
724
+ }
725
+
726
+ slideToggle(duration = 300) {
727
+ return this.each((_, el) => {
728
+ if (el.style.display === 'none' || getComputedStyle(el).display === 'none') {
729
+ el.style.display = '';
730
+ el.style.overflow = 'hidden';
731
+ const h = el.scrollHeight + 'px';
732
+ el.style.maxHeight = '0';
733
+ el.style.transition = `max-height ${duration}ms ease`;
734
+ requestAnimationFrame(() => { el.style.maxHeight = h; });
735
+ setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
736
+ } else {
737
+ el.style.overflow = 'hidden';
738
+ el.style.maxHeight = el.scrollHeight + 'px';
739
+ el.style.transition = `max-height ${duration}ms ease`;
740
+ requestAnimationFrame(() => { el.style.maxHeight = '0'; });
741
+ setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
742
+ }
743
+ });
744
+ }
745
+
746
+ // --- Form helpers --------------------------------------------------------
747
+
748
+ serialize() {
749
+ const form = this.first();
750
+ if (!form || form.tagName !== 'FORM') return '';
751
+ return new URLSearchParams(new FormData(form)).toString();
752
+ }
753
+
754
+ serializeObject() {
755
+ const form = this.first();
756
+ if (!form || form.tagName !== 'FORM') return {};
757
+ const obj = {};
758
+ new FormData(form).forEach((v, k) => {
759
+ if (obj[k] !== undefined) {
760
+ if (!Array.isArray(obj[k])) obj[k] = [obj[k]];
761
+ obj[k].push(v);
762
+ } else {
763
+ obj[k] = v;
764
+ }
765
+ });
766
+ return obj;
767
+ }
768
+ }
769
+
770
+
771
+ // ---------------------------------------------------------------------------
772
+ // Helper - create document fragment from HTML string
773
+ // ---------------------------------------------------------------------------
774
+ function createFragment(html) {
775
+ const tpl = document.createElement('template');
776
+ tpl.innerHTML = html.trim();
777
+ return tpl.content;
778
+ }
779
+
780
+
781
+ // ---------------------------------------------------------------------------
782
+ // $() - main selector / creator (returns ZQueryCollection, like jQuery)
783
+ // ---------------------------------------------------------------------------
784
+ export function query(selector, context) {
785
+ // null / undefined
786
+ if (!selector) return new ZQueryCollection([]);
787
+
788
+ // Already a collection - return as-is
789
+ if (selector instanceof ZQueryCollection) return selector;
790
+
791
+ // DOM element or Window - wrap in collection
792
+ if (selector instanceof Node || selector === window) {
793
+ return new ZQueryCollection([selector]);
794
+ }
795
+
796
+ // NodeList / HTMLCollection / Array - wrap in collection
797
+ if (selector instanceof NodeList || selector instanceof HTMLCollection || Array.isArray(selector)) {
798
+ return new ZQueryCollection(Array.from(selector));
799
+ }
800
+
801
+ // HTML string → create elements, wrap in collection
802
+ if (typeof selector === 'string' && selector.trim().startsWith('<')) {
803
+ const fragment = createFragment(selector);
804
+ return new ZQueryCollection([...fragment.childNodes].filter(n => n.nodeType === 1));
805
+ }
806
+
807
+ // CSS selector string → querySelectorAll (collection)
808
+ if (typeof selector === 'string') {
809
+ const root = context
810
+ ? (typeof context === 'string' ? document.querySelector(context) : context)
811
+ : document;
812
+ return new ZQueryCollection([...root.querySelectorAll(selector)]);
813
+ }
814
+
815
+ return new ZQueryCollection([]);
816
+ }
817
+
818
+
819
+ // ---------------------------------------------------------------------------
820
+ // $.all() - collection selector (returns ZQueryCollection for CSS selectors)
821
+ // ---------------------------------------------------------------------------
822
+ export function queryAll(selector, context) {
823
+ // null / undefined
824
+ if (!selector) return new ZQueryCollection([]);
825
+
826
+ // Already a collection
827
+ if (selector instanceof ZQueryCollection) return selector;
828
+
829
+ // DOM element or Window
830
+ if (selector instanceof Node || selector === window) {
831
+ return new ZQueryCollection([selector]);
832
+ }
833
+
834
+ // NodeList / HTMLCollection / Array
835
+ if (selector instanceof NodeList || selector instanceof HTMLCollection || Array.isArray(selector)) {
836
+ return new ZQueryCollection(Array.from(selector));
837
+ }
838
+
839
+ // HTML string → create elements
840
+ if (typeof selector === 'string' && selector.trim().startsWith('<')) {
841
+ const fragment = createFragment(selector);
842
+ return new ZQueryCollection([...fragment.childNodes].filter(n => n.nodeType === 1));
843
+ }
844
+
845
+ // CSS selector string → querySelectorAll (collection)
846
+ if (typeof selector === 'string') {
847
+ const root = context
848
+ ? (typeof context === 'string' ? document.querySelector(context) : context)
849
+ : document;
850
+ return new ZQueryCollection([...root.querySelectorAll(selector)]);
851
+ }
852
+
853
+ return new ZQueryCollection([]);
854
+ }
855
+
856
+
857
+ // ---------------------------------------------------------------------------
858
+ // Quick-ref shortcuts, on $ namespace)
859
+ // ---------------------------------------------------------------------------
860
+ query.id = (id) => document.getElementById(id);
861
+ // Escape the class name so callers can safely pass identifiers containing
862
+ // dots, colons, leading digits, etc. without breaking the selector.
863
+ query.class = (name) => document.querySelector(`.${typeof CSS !== 'undefined' && CSS.escape ? CSS.escape(name) : name}`);
864
+ query.classes = (name) => new ZQueryCollection(Array.from(document.getElementsByClassName(name)));
865
+ query.tag = (name) => new ZQueryCollection(Array.from(document.getElementsByTagName(name)));
866
+ Object.defineProperty(query, 'name', {
867
+ value: (name) => new ZQueryCollection(Array.from(document.getElementsByName(name))),
868
+ writable: true, configurable: true
869
+ });
870
+ query.children = (parentId) => {
871
+ const p = document.getElementById(parentId);
872
+ return new ZQueryCollection(p ? Array.from(p.children) : []);
873
+ };
874
+ query.qs = (sel, ctx = document) => ctx.querySelector(sel);
875
+ query.qsa = (sel, ctx = document) => Array.from(ctx.querySelectorAll(sel));
876
+
877
+ // Create element shorthand - returns ZQueryCollection for chaining
878
+ query.create = (tag, attrs = {}, ...children) => {
879
+ const el = document.createElement(tag);
880
+ for (const [k, v] of Object.entries(attrs)) {
881
+ if (k === 'class') el.className = v;
882
+ else if (k === 'style' && typeof v === 'object') Object.assign(el.style, v);
883
+ else if (k.startsWith('on') && typeof v === 'function') el.addEventListener(k.slice(2).toLowerCase(), v);
884
+ else if (k === 'data' && typeof v === 'object') Object.entries(v).forEach(([dk, dv]) => { el.dataset[dk] = dv; });
885
+ else el.setAttribute(k, v);
886
+ }
887
+ children.flat().forEach(child => {
888
+ if (typeof child === 'string') el.appendChild(document.createTextNode(child));
889
+ else if (child instanceof Node) el.appendChild(child);
890
+ });
891
+ return new ZQueryCollection(el);
892
+ };
893
+
894
+ // DOM ready
895
+ query.ready = (fn) => {
896
+ if (document.readyState !== 'loading') fn();
897
+ else document.addEventListener('DOMContentLoaded', fn);
898
+ };
899
+
900
+ // Global event listeners - supports direct, delegated, and target-bound forms
901
+ // $.on('keydown', handler) → direct listener on document
902
+ // $.on('click', '.btn', handler) delegated via closest()
903
+ // $.on('scroll', window, handler) → direct listener on target
904
+ query.on = (event, selectorOrHandler, handler) => {
905
+ if (typeof selectorOrHandler === 'function') {
906
+ // 2-arg: direct document listener (keydown, resize, etc.)
907
+ document.addEventListener(event, selectorOrHandler);
908
+ return;
909
+ }
910
+ // EventTarget (window, element, etc.) - direct listener on target
911
+ if (typeof selectorOrHandler === 'object' && typeof selectorOrHandler.addEventListener === 'function') {
912
+ selectorOrHandler.addEventListener(event, handler);
913
+ return;
914
+ }
915
+ // 3-arg string: delegated
916
+ document.addEventListener(event, (e) => {
917
+ if (!e.target || typeof e.target.closest !== 'function') return;
918
+ const target = e.target.closest(selectorOrHandler);
919
+ if (target) handler.call(target, e);
920
+ });
921
+ };
922
+
923
+ // Remove a direct global listener
924
+ query.off = (event, handler) => {
925
+ document.removeEventListener(event, handler);
926
+ };
927
+
928
+ // Extend collection prototype (like $.fn in jQuery)
929
+ query.fn = ZQueryCollection.prototype;