zero-query 1.1.1 → 1.2.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 (154) 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 +443 -442
  5. package/cli/commands/build.js +254 -247
  6. package/cli/commands/bundle.js +1228 -1224
  7. package/cli/commands/create.js +137 -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 +295 -0
  73. package/cli/scaffold/webrtc/app/lib/room.js +252 -0
  74. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  75. package/cli/scaffold/webrtc/global.css +250 -0
  76. package/cli/scaffold/webrtc/index.html +21 -0
  77. package/cli/utils.js +305 -287
  78. package/dist/API.md +661 -0
  79. package/dist/zquery.dist.zip +0 -0
  80. package/dist/zquery.js +10313 -6614
  81. package/dist/zquery.min.js +8 -631
  82. package/index.d.ts +570 -371
  83. package/index.js +311 -240
  84. package/package.json +76 -70
  85. package/src/component.js +1709 -1691
  86. package/src/core.js +921 -921
  87. package/src/diff.js +497 -497
  88. package/src/errors.js +209 -209
  89. package/src/expression.js +922 -922
  90. package/src/http.js +242 -242
  91. package/src/package.json +1 -1
  92. package/src/reactive.js +255 -255
  93. package/src/router.js +843 -843
  94. package/src/ssr.js +418 -418
  95. package/src/store.js +318 -318
  96. package/src/utils.js +515 -515
  97. package/src/webrtc/e2ee.js +351 -0
  98. package/src/webrtc/errors.js +116 -0
  99. package/src/webrtc/ice.js +301 -0
  100. package/src/webrtc/index.js +131 -0
  101. package/src/webrtc/joinToken.js +119 -0
  102. package/src/webrtc/observe.js +172 -0
  103. package/src/webrtc/peer.js +351 -0
  104. package/src/webrtc/reactive.js +268 -0
  105. package/src/webrtc/room.js +625 -0
  106. package/src/webrtc/sdp.js +302 -0
  107. package/src/webrtc/sfu/index.js +43 -0
  108. package/src/webrtc/sfu/livekit.js +131 -0
  109. package/src/webrtc/sfu/mediasoup.js +150 -0
  110. package/src/webrtc/signaling.js +373 -0
  111. package/src/webrtc/turn.js +237 -0
  112. package/tests/_helpers/webrtcFakes.js +289 -0
  113. package/tests/audit.test.js +4158 -4158
  114. package/tests/cli.test.js +1136 -1103
  115. package/tests/compare.test.js +497 -486
  116. package/tests/component.test.js +3969 -3938
  117. package/tests/core.test.js +1910 -1910
  118. package/tests/dev-server.test.js +489 -489
  119. package/tests/diff.test.js +1416 -1416
  120. package/tests/docs.test.js +1664 -1650
  121. package/tests/electron-features.test.js +864 -864
  122. package/tests/errors.test.js +619 -619
  123. package/tests/expression.test.js +1056 -1056
  124. package/tests/http.test.js +648 -648
  125. package/tests/reactive.test.js +819 -819
  126. package/tests/router.test.js +2327 -2327
  127. package/tests/ssr.test.js +870 -870
  128. package/tests/store.test.js +830 -830
  129. package/tests/test-minifier.js +153 -153
  130. package/tests/test-ssr.js +27 -27
  131. package/tests/utils.test.js +1377 -1377
  132. package/tests/webrtc/e2ee.test.js +283 -0
  133. package/tests/webrtc/ice.test.js +202 -0
  134. package/tests/webrtc/joinToken.test.js +89 -0
  135. package/tests/webrtc/observe.test.js +111 -0
  136. package/tests/webrtc/peer.test.js +373 -0
  137. package/tests/webrtc/reactive.test.js +235 -0
  138. package/tests/webrtc/room.test.js +406 -0
  139. package/tests/webrtc/sdp.test.js +151 -0
  140. package/tests/webrtc/sfu-livekit.test.js +119 -0
  141. package/tests/webrtc/sfu.test.js +160 -0
  142. package/tests/webrtc/signaling.test.js +251 -0
  143. package/tests/webrtc/turn.test.js +256 -0
  144. package/types/collection.d.ts +383 -383
  145. package/types/component.d.ts +186 -186
  146. package/types/errors.d.ts +135 -135
  147. package/types/http.d.ts +92 -92
  148. package/types/misc.d.ts +201 -201
  149. package/types/reactive.d.ts +98 -98
  150. package/types/router.d.ts +190 -190
  151. package/types/ssr.d.ts +102 -102
  152. package/types/store.d.ts +146 -146
  153. package/types/utils.d.ts +245 -245
  154. package/types/webrtc.d.ts +653 -0
package/src/core.js CHANGED
@@ -1,921 +1,921 @@
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
+ 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;