zero-query 0.6.3 → 0.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +39 -29
- package/cli/commands/build.js +113 -4
- package/cli/commands/bundle.js +392 -29
- package/cli/commands/create.js +1 -1
- package/cli/commands/dev/devtools/index.js +56 -0
- package/cli/commands/dev/devtools/js/components.js +49 -0
- package/cli/commands/dev/devtools/js/core.js +409 -0
- package/cli/commands/dev/devtools/js/elements.js +413 -0
- package/cli/commands/dev/devtools/js/network.js +166 -0
- package/cli/commands/dev/devtools/js/performance.js +73 -0
- package/cli/commands/dev/devtools/js/router.js +105 -0
- package/cli/commands/dev/devtools/js/source.js +132 -0
- package/cli/commands/dev/devtools/js/stats.js +35 -0
- package/cli/commands/dev/devtools/js/tabs.js +79 -0
- package/cli/commands/dev/devtools/panel.html +95 -0
- package/cli/commands/dev/devtools/styles.css +244 -0
- package/cli/commands/dev/index.js +29 -4
- package/cli/commands/dev/logger.js +6 -1
- package/cli/commands/dev/overlay.js +428 -2
- package/cli/commands/dev/server.js +42 -5
- package/cli/commands/dev/watcher.js +59 -1
- package/cli/help.js +8 -5
- package/cli/scaffold/{scripts → app}/app.js +16 -23
- package/cli/scaffold/{scripts → app}/components/about.js +4 -4
- package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -7
- package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +3 -3
- package/cli/scaffold/app/components/home.js +137 -0
- package/cli/scaffold/{scripts → app}/routes.js +1 -1
- package/cli/scaffold/{scripts → app}/store.js +6 -6
- package/cli/scaffold/assets/.gitkeep +0 -0
- package/cli/scaffold/{styles/styles.css → global.css} +4 -2
- package/cli/scaffold/index.html +12 -11
- package/cli/utils.js +111 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1122 -158
- package/dist/zquery.min.js +3 -16
- package/index.d.ts +129 -1290
- package/index.js +15 -10
- package/package.json +7 -6
- package/src/component.js +172 -49
- package/src/core.js +359 -18
- package/src/diff.js +256 -58
- package/src/expression.js +33 -3
- package/src/reactive.js +37 -5
- package/src/router.js +243 -7
- package/tests/component.test.js +886 -0
- package/tests/core.test.js +977 -0
- package/tests/diff.test.js +525 -0
- package/tests/errors.test.js +162 -0
- package/tests/expression.test.js +482 -0
- package/tests/http.test.js +289 -0
- package/tests/reactive.test.js +339 -0
- package/tests/router.test.js +649 -0
- package/tests/store.test.js +379 -0
- package/tests/utils.test.js +512 -0
- package/types/collection.d.ts +383 -0
- package/types/component.d.ts +217 -0
- package/types/errors.d.ts +103 -0
- package/types/http.d.ts +81 -0
- package/types/misc.d.ts +179 -0
- package/types/reactive.d.ts +76 -0
- package/types/router.d.ts +161 -0
- package/types/ssr.d.ts +49 -0
- package/types/store.d.ts +107 -0
- package/types/utils.d.ts +142 -0
- package/cli/commands/dev.old.js +0 -520
- package/cli/scaffold/scripts/components/home.js +0 -137
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
package/src/core.js
CHANGED
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
* into a full jQuery-like chainable wrapper with modern APIs.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
+
import { morph as _morph, morphElement as _morphElement } from './diff.js';
|
|
9
|
+
|
|
8
10
|
// ---------------------------------------------------------------------------
|
|
9
11
|
// ZQueryCollection — wraps an array of elements with chainable methods
|
|
10
12
|
// ---------------------------------------------------------------------------
|
|
@@ -75,8 +77,96 @@ export class ZQueryCollection {
|
|
|
75
77
|
return new ZQueryCollection(sibs);
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
next() {
|
|
79
|
-
|
|
80
|
+
next(selector) {
|
|
81
|
+
const els = this.elements.map(el => el.nextElementSibling).filter(Boolean);
|
|
82
|
+
return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
prev(selector) {
|
|
86
|
+
const els = this.elements.map(el => el.previousElementSibling).filter(Boolean);
|
|
87
|
+
return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
nextAll(selector) {
|
|
91
|
+
const result = [];
|
|
92
|
+
this.elements.forEach(el => {
|
|
93
|
+
let sib = el.nextElementSibling;
|
|
94
|
+
while (sib) {
|
|
95
|
+
if (!selector || sib.matches(selector)) result.push(sib);
|
|
96
|
+
sib = sib.nextElementSibling;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
return new ZQueryCollection(result);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
nextUntil(selector, filter) {
|
|
103
|
+
const result = [];
|
|
104
|
+
this.elements.forEach(el => {
|
|
105
|
+
let sib = el.nextElementSibling;
|
|
106
|
+
while (sib) {
|
|
107
|
+
if (selector && sib.matches(selector)) break;
|
|
108
|
+
if (!filter || sib.matches(filter)) result.push(sib);
|
|
109
|
+
sib = sib.nextElementSibling;
|
|
110
|
+
}
|
|
111
|
+
});
|
|
112
|
+
return new ZQueryCollection(result);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
prevAll(selector) {
|
|
116
|
+
const result = [];
|
|
117
|
+
this.elements.forEach(el => {
|
|
118
|
+
let sib = el.previousElementSibling;
|
|
119
|
+
while (sib) {
|
|
120
|
+
if (!selector || sib.matches(selector)) result.push(sib);
|
|
121
|
+
sib = sib.previousElementSibling;
|
|
122
|
+
}
|
|
123
|
+
});
|
|
124
|
+
return new ZQueryCollection(result);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
prevUntil(selector, filter) {
|
|
128
|
+
const result = [];
|
|
129
|
+
this.elements.forEach(el => {
|
|
130
|
+
let sib = el.previousElementSibling;
|
|
131
|
+
while (sib) {
|
|
132
|
+
if (selector && sib.matches(selector)) break;
|
|
133
|
+
if (!filter || sib.matches(filter)) result.push(sib);
|
|
134
|
+
sib = sib.previousElementSibling;
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
return new ZQueryCollection(result);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
parents(selector) {
|
|
141
|
+
const result = [];
|
|
142
|
+
this.elements.forEach(el => {
|
|
143
|
+
let parent = el.parentElement;
|
|
144
|
+
while (parent) {
|
|
145
|
+
if (!selector || parent.matches(selector)) result.push(parent);
|
|
146
|
+
parent = parent.parentElement;
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
return new ZQueryCollection([...new Set(result)]);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
parentsUntil(selector, filter) {
|
|
153
|
+
const result = [];
|
|
154
|
+
this.elements.forEach(el => {
|
|
155
|
+
let parent = el.parentElement;
|
|
156
|
+
while (parent) {
|
|
157
|
+
if (selector && parent.matches(selector)) break;
|
|
158
|
+
if (!filter || parent.matches(filter)) result.push(parent);
|
|
159
|
+
parent = parent.parentElement;
|
|
160
|
+
}
|
|
161
|
+
});
|
|
162
|
+
return new ZQueryCollection([...new Set(result)]);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
contents() {
|
|
166
|
+
const result = [];
|
|
167
|
+
this.elements.forEach(el => result.push(...el.childNodes));
|
|
168
|
+
return new ZQueryCollection(result);
|
|
169
|
+
}
|
|
80
170
|
|
|
81
171
|
filter(selector) {
|
|
82
172
|
if (typeof selector === 'function') {
|
|
@@ -96,20 +186,85 @@ export class ZQueryCollection {
|
|
|
96
186
|
return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector)));
|
|
97
187
|
}
|
|
98
188
|
|
|
189
|
+
is(selector) {
|
|
190
|
+
if (typeof selector === 'function') {
|
|
191
|
+
return this.elements.some((el, i) => selector.call(el, i, el));
|
|
192
|
+
}
|
|
193
|
+
return this.elements.some(el => el.matches(selector));
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
slice(start, end) {
|
|
197
|
+
return new ZQueryCollection(this.elements.slice(start, end));
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
add(selector, context) {
|
|
201
|
+
const toAdd = (selector instanceof ZQueryCollection)
|
|
202
|
+
? selector.elements
|
|
203
|
+
: (selector instanceof Node)
|
|
204
|
+
? [selector]
|
|
205
|
+
: Array.from((context || document).querySelectorAll(selector));
|
|
206
|
+
return new ZQueryCollection([...this.elements, ...toAdd]);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
get(index) {
|
|
210
|
+
if (index === undefined) return [...this.elements];
|
|
211
|
+
return index < 0 ? this.elements[this.length + index] : this.elements[index];
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
index(selector) {
|
|
215
|
+
if (selector === undefined) {
|
|
216
|
+
const el = this.first();
|
|
217
|
+
return el ? Array.from(el.parentElement.children).indexOf(el) : -1;
|
|
218
|
+
}
|
|
219
|
+
const target = (typeof selector === 'string')
|
|
220
|
+
? document.querySelector(selector)
|
|
221
|
+
: selector;
|
|
222
|
+
return this.elements.indexOf(target);
|
|
223
|
+
}
|
|
224
|
+
|
|
99
225
|
// --- Classes -------------------------------------------------------------
|
|
100
226
|
|
|
101
227
|
addClass(...names) {
|
|
228
|
+
// Fast path: single class, no spaces — avoids flatMap + regex split allocation
|
|
229
|
+
if (names.length === 1 && names[0].indexOf(' ') === -1) {
|
|
230
|
+
const c = names[0];
|
|
231
|
+
for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(c);
|
|
232
|
+
return this;
|
|
233
|
+
}
|
|
102
234
|
const classes = names.flatMap(n => n.split(/\s+/));
|
|
103
|
-
|
|
235
|
+
for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.add(...classes);
|
|
236
|
+
return this;
|
|
104
237
|
}
|
|
105
238
|
|
|
106
239
|
removeClass(...names) {
|
|
240
|
+
if (names.length === 1 && names[0].indexOf(' ') === -1) {
|
|
241
|
+
const c = names[0];
|
|
242
|
+
for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(c);
|
|
243
|
+
return this;
|
|
244
|
+
}
|
|
107
245
|
const classes = names.flatMap(n => n.split(/\s+/));
|
|
108
|
-
|
|
246
|
+
for (let i = 0; i < this.elements.length; i++) this.elements[i].classList.remove(...classes);
|
|
247
|
+
return this;
|
|
109
248
|
}
|
|
110
249
|
|
|
111
|
-
toggleClass(
|
|
112
|
-
|
|
250
|
+
toggleClass(...args) {
|
|
251
|
+
const force = typeof args[args.length - 1] === 'boolean' ? args.pop() : undefined;
|
|
252
|
+
// Fast path: single class, no spaces
|
|
253
|
+
if (args.length === 1 && args[0].indexOf(' ') === -1) {
|
|
254
|
+
const c = args[0];
|
|
255
|
+
for (let i = 0; i < this.elements.length; i++) {
|
|
256
|
+
force !== undefined ? this.elements[i].classList.toggle(c, force) : this.elements[i].classList.toggle(c);
|
|
257
|
+
}
|
|
258
|
+
return this;
|
|
259
|
+
}
|
|
260
|
+
const classes = args.flatMap(n => n.split(/\s+/));
|
|
261
|
+
for (let i = 0; i < this.elements.length; i++) {
|
|
262
|
+
const el = this.elements[i];
|
|
263
|
+
for (let j = 0; j < classes.length; j++) {
|
|
264
|
+
force !== undefined ? el.classList.toggle(classes[j], force) : el.classList.toggle(classes[j]);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
return this;
|
|
113
268
|
}
|
|
114
269
|
|
|
115
270
|
hasClass(name) {
|
|
@@ -145,7 +300,8 @@ export class ZQueryCollection {
|
|
|
145
300
|
|
|
146
301
|
css(props) {
|
|
147
302
|
if (typeof props === 'string') {
|
|
148
|
-
|
|
303
|
+
const el = this.first();
|
|
304
|
+
return el ? getComputedStyle(el)[props] : undefined;
|
|
149
305
|
}
|
|
150
306
|
return this.each((_, el) => Object.assign(el.style, props));
|
|
151
307
|
}
|
|
@@ -163,11 +319,79 @@ export class ZQueryCollection {
|
|
|
163
319
|
return el ? { top: el.offsetTop, left: el.offsetLeft } : null;
|
|
164
320
|
}
|
|
165
321
|
|
|
322
|
+
scrollTop(value) {
|
|
323
|
+
if (value === undefined) {
|
|
324
|
+
const el = this.first();
|
|
325
|
+
return el === window ? window.scrollY : el?.scrollTop;
|
|
326
|
+
}
|
|
327
|
+
return this.each((_, el) => {
|
|
328
|
+
if (el === window) window.scrollTo(window.scrollX, value);
|
|
329
|
+
else el.scrollTop = value;
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
scrollLeft(value) {
|
|
334
|
+
if (value === undefined) {
|
|
335
|
+
const el = this.first();
|
|
336
|
+
return el === window ? window.scrollX : el?.scrollLeft;
|
|
337
|
+
}
|
|
338
|
+
return this.each((_, el) => {
|
|
339
|
+
if (el === window) window.scrollTo(value, window.scrollY);
|
|
340
|
+
else el.scrollLeft = value;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
innerWidth() {
|
|
345
|
+
const el = this.first();
|
|
346
|
+
return el?.clientWidth;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
innerHeight() {
|
|
350
|
+
const el = this.first();
|
|
351
|
+
return el?.clientHeight;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
outerWidth(includeMargin = false) {
|
|
355
|
+
const el = this.first();
|
|
356
|
+
if (!el) return undefined;
|
|
357
|
+
let w = el.offsetWidth;
|
|
358
|
+
if (includeMargin) {
|
|
359
|
+
const style = getComputedStyle(el);
|
|
360
|
+
w += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
|
361
|
+
}
|
|
362
|
+
return w;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
outerHeight(includeMargin = false) {
|
|
366
|
+
const el = this.first();
|
|
367
|
+
if (!el) return undefined;
|
|
368
|
+
let h = el.offsetHeight;
|
|
369
|
+
if (includeMargin) {
|
|
370
|
+
const style = getComputedStyle(el);
|
|
371
|
+
h += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
|
|
372
|
+
}
|
|
373
|
+
return h;
|
|
374
|
+
}
|
|
375
|
+
|
|
166
376
|
// --- Content -------------------------------------------------------------
|
|
167
377
|
|
|
168
378
|
html(content) {
|
|
169
379
|
if (content === undefined) return this.first()?.innerHTML;
|
|
170
|
-
|
|
380
|
+
// Auto-morph: if the element already has children, use the diff engine
|
|
381
|
+
// to patch the DOM (preserves focus, scroll, state, keyed reorder via LIS).
|
|
382
|
+
// Empty elements get raw innerHTML for fast first-paint — same strategy
|
|
383
|
+
// the component system uses (first render = innerHTML, updates = morph).
|
|
384
|
+
return this.each((_, el) => {
|
|
385
|
+
if (el.childNodes.length > 0) {
|
|
386
|
+
_morph(el, content);
|
|
387
|
+
} else {
|
|
388
|
+
el.innerHTML = content;
|
|
389
|
+
}
|
|
390
|
+
});
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
morph(content) {
|
|
394
|
+
return this.each((_, el) => { _morph(el, content); });
|
|
171
395
|
}
|
|
172
396
|
|
|
173
397
|
text(content) {
|
|
@@ -224,7 +448,8 @@ export class ZQueryCollection {
|
|
|
224
448
|
}
|
|
225
449
|
|
|
226
450
|
empty() {
|
|
227
|
-
|
|
451
|
+
// textContent = '' clears all children without invoking the HTML parser
|
|
452
|
+
return this.each((_, el) => { el.textContent = ''; });
|
|
228
453
|
}
|
|
229
454
|
|
|
230
455
|
clone(deep = true) {
|
|
@@ -234,14 +459,82 @@ export class ZQueryCollection {
|
|
|
234
459
|
replaceWith(content) {
|
|
235
460
|
return this.each((_, el) => {
|
|
236
461
|
if (typeof content === 'string') {
|
|
237
|
-
|
|
238
|
-
|
|
462
|
+
// Auto-morph: diff attributes + children when the tag name matches
|
|
463
|
+
// instead of destroying and re-creating the element.
|
|
464
|
+
_morphElement(el, content);
|
|
239
465
|
} else if (content instanceof Node) {
|
|
240
466
|
el.parentNode.replaceChild(content, el);
|
|
241
467
|
}
|
|
242
468
|
});
|
|
243
469
|
}
|
|
244
470
|
|
|
471
|
+
appendTo(target) {
|
|
472
|
+
const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
473
|
+
if (dest) this.each((_, el) => dest.appendChild(el));
|
|
474
|
+
return this;
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
prependTo(target) {
|
|
478
|
+
const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
479
|
+
if (dest) this.each((_, el) => dest.insertBefore(el, dest.firstChild));
|
|
480
|
+
return this;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
insertAfter(target) {
|
|
484
|
+
const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
485
|
+
if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref.nextSibling));
|
|
486
|
+
return this;
|
|
487
|
+
}
|
|
488
|
+
|
|
489
|
+
insertBefore(target) {
|
|
490
|
+
const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
491
|
+
if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref));
|
|
492
|
+
return this;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
replaceAll(target) {
|
|
496
|
+
const targets = typeof target === 'string'
|
|
497
|
+
? Array.from(document.querySelectorAll(target))
|
|
498
|
+
: target instanceof ZQueryCollection ? target.elements : [target];
|
|
499
|
+
targets.forEach((t, i) => {
|
|
500
|
+
const nodes = i === 0 ? this.elements : this.elements.map(el => el.cloneNode(true));
|
|
501
|
+
nodes.forEach(el => t.parentNode.insertBefore(el, t));
|
|
502
|
+
t.remove();
|
|
503
|
+
});
|
|
504
|
+
return this;
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
unwrap(selector) {
|
|
508
|
+
this.elements.forEach(el => {
|
|
509
|
+
const parent = el.parentElement;
|
|
510
|
+
if (!parent || parent === document.body) return;
|
|
511
|
+
if (selector && !parent.matches(selector)) return;
|
|
512
|
+
parent.replaceWith(...parent.childNodes);
|
|
513
|
+
});
|
|
514
|
+
return this;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
wrapAll(wrapper) {
|
|
518
|
+
const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
|
|
519
|
+
const first = this.first();
|
|
520
|
+
if (!first) return this;
|
|
521
|
+
first.parentNode.insertBefore(w, first);
|
|
522
|
+
this.each((_, el) => w.appendChild(el));
|
|
523
|
+
return this;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
wrapInner(wrapper) {
|
|
527
|
+
return this.each((_, el) => {
|
|
528
|
+
const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
|
|
529
|
+
while (el.firstChild) w.appendChild(el.firstChild);
|
|
530
|
+
el.appendChild(w);
|
|
531
|
+
});
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
detach() {
|
|
535
|
+
return this.each((_, el) => el.remove());
|
|
536
|
+
}
|
|
537
|
+
|
|
245
538
|
// --- Visibility ----------------------------------------------------------
|
|
246
539
|
|
|
247
540
|
show(display = '') {
|
|
@@ -254,7 +547,9 @@ export class ZQueryCollection {
|
|
|
254
547
|
|
|
255
548
|
toggle(display = '') {
|
|
256
549
|
return this.each((_, el) => {
|
|
257
|
-
|
|
550
|
+
// Check inline style first (cheap) before forcing layout via getComputedStyle
|
|
551
|
+
const hidden = el.style.display === 'none' || (el.style.display !== '' ? false : getComputedStyle(el).display === 'none');
|
|
552
|
+
el.style.display = hidden ? display : 'none';
|
|
258
553
|
});
|
|
259
554
|
}
|
|
260
555
|
|
|
@@ -267,9 +562,10 @@ export class ZQueryCollection {
|
|
|
267
562
|
events.forEach(evt => {
|
|
268
563
|
if (typeof selectorOrHandler === 'function') {
|
|
269
564
|
el.addEventListener(evt, selectorOrHandler);
|
|
270
|
-
} else {
|
|
271
|
-
// Delegated event
|
|
565
|
+
} else if (typeof selectorOrHandler === 'string') {
|
|
566
|
+
// Delegated event — only works on elements that support closest()
|
|
272
567
|
el.addEventListener(evt, (e) => {
|
|
568
|
+
if (!e.target || typeof e.target.closest !== 'function') return;
|
|
273
569
|
const target = e.target.closest(selectorOrHandler);
|
|
274
570
|
if (target && el.contains(target)) handler.call(target, e);
|
|
275
571
|
});
|
|
@@ -302,6 +598,10 @@ export class ZQueryCollection {
|
|
|
302
598
|
submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); }
|
|
303
599
|
focus() { this.first()?.focus(); return this; }
|
|
304
600
|
blur() { this.first()?.blur(); return this; }
|
|
601
|
+
hover(enterFn, leaveFn) {
|
|
602
|
+
this.on('mouseenter', enterFn);
|
|
603
|
+
return this.on('mouseleave', leaveFn || enterFn);
|
|
604
|
+
}
|
|
305
605
|
|
|
306
606
|
// --- Animation -----------------------------------------------------------
|
|
307
607
|
|
|
@@ -333,6 +633,40 @@ export class ZQueryCollection {
|
|
|
333
633
|
return this.animate({ opacity: '0' }, duration).then(col => col.hide());
|
|
334
634
|
}
|
|
335
635
|
|
|
636
|
+
fadeToggle(duration = 300) {
|
|
637
|
+
return Promise.all(this.elements.map(el => {
|
|
638
|
+
const visible = getComputedStyle(el).opacity !== '0' && getComputedStyle(el).display !== 'none';
|
|
639
|
+
const col = new ZQueryCollection([el]);
|
|
640
|
+
return visible ? col.fadeOut(duration) : col.fadeIn(duration);
|
|
641
|
+
})).then(() => this);
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
fadeTo(duration, opacity) {
|
|
645
|
+
return this.animate({ opacity: String(opacity) }, duration);
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
slideDown(duration = 300) {
|
|
649
|
+
return this.each((_, el) => {
|
|
650
|
+
el.style.display = '';
|
|
651
|
+
el.style.overflow = 'hidden';
|
|
652
|
+
const h = el.scrollHeight + 'px';
|
|
653
|
+
el.style.maxHeight = '0';
|
|
654
|
+
el.style.transition = `max-height ${duration}ms ease`;
|
|
655
|
+
requestAnimationFrame(() => { el.style.maxHeight = h; });
|
|
656
|
+
setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
|
|
657
|
+
});
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
slideUp(duration = 300) {
|
|
661
|
+
return this.each((_, el) => {
|
|
662
|
+
el.style.overflow = 'hidden';
|
|
663
|
+
el.style.maxHeight = el.scrollHeight + 'px';
|
|
664
|
+
el.style.transition = `max-height ${duration}ms ease`;
|
|
665
|
+
requestAnimationFrame(() => { el.style.maxHeight = '0'; });
|
|
666
|
+
setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
|
|
667
|
+
});
|
|
668
|
+
}
|
|
669
|
+
|
|
336
670
|
slideToggle(duration = 300) {
|
|
337
671
|
return this.each((_, el) => {
|
|
338
672
|
if (el.style.display === 'none' || getComputedStyle(el).display === 'none') {
|
|
@@ -480,7 +814,7 @@ query.children = (parentId) => {
|
|
|
480
814
|
return new ZQueryCollection(p ? Array.from(p.children) : []);
|
|
481
815
|
};
|
|
482
816
|
|
|
483
|
-
// Create element shorthand
|
|
817
|
+
// Create element shorthand — returns ZQueryCollection for chaining
|
|
484
818
|
query.create = (tag, attrs = {}, ...children) => {
|
|
485
819
|
const el = document.createElement(tag);
|
|
486
820
|
for (const [k, v] of Object.entries(attrs)) {
|
|
@@ -494,7 +828,7 @@ query.create = (tag, attrs = {}, ...children) => {
|
|
|
494
828
|
if (typeof child === 'string') el.appendChild(document.createTextNode(child));
|
|
495
829
|
else if (child instanceof Node) el.appendChild(child);
|
|
496
830
|
});
|
|
497
|
-
return el;
|
|
831
|
+
return new ZQueryCollection(el);
|
|
498
832
|
};
|
|
499
833
|
|
|
500
834
|
// DOM ready
|
|
@@ -503,17 +837,24 @@ query.ready = (fn) => {
|
|
|
503
837
|
else document.addEventListener('DOMContentLoaded', fn);
|
|
504
838
|
};
|
|
505
839
|
|
|
506
|
-
// Global event listeners — supports direct and
|
|
840
|
+
// Global event listeners — supports direct, delegated, and target-bound forms
|
|
507
841
|
// $.on('keydown', handler) → direct listener on document
|
|
508
842
|
// $.on('click', '.btn', handler) → delegated via closest()
|
|
843
|
+
// $.on('scroll', window, handler) → direct listener on target
|
|
509
844
|
query.on = (event, selectorOrHandler, handler) => {
|
|
510
845
|
if (typeof selectorOrHandler === 'function') {
|
|
511
846
|
// 2-arg: direct document listener (keydown, resize, etc.)
|
|
512
847
|
document.addEventListener(event, selectorOrHandler);
|
|
513
848
|
return;
|
|
514
849
|
}
|
|
515
|
-
//
|
|
850
|
+
// EventTarget (window, element, etc.) — direct listener on target
|
|
851
|
+
if (typeof selectorOrHandler === 'object' && typeof selectorOrHandler.addEventListener === 'function') {
|
|
852
|
+
selectorOrHandler.addEventListener(event, handler);
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
// 3-arg string: delegated
|
|
516
856
|
document.addEventListener(event, (e) => {
|
|
857
|
+
if (!e.target || typeof e.target.closest !== 'function') return;
|
|
517
858
|
const target = e.target.closest(selectorOrHandler);
|
|
518
859
|
if (target) handler.call(target, e);
|
|
519
860
|
});
|