zero-query 1.0.9 → 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.
- package/LICENSE +21 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -0
- package/cli/commands/build.js +254 -216
- package/cli/commands/bundle.js +1228 -1183
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -167
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +7264 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6252
- package/dist/zquery.min.js +8 -601
- package/index.d.ts +570 -365
- package/index.js +311 -232
- package/package.json +76 -69
- package/src/component.js +1709 -1454
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -254
- package/src/router.js +843 -773
- package/src/ssr.js +418 -418
- package/src/store.js +318 -272
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1023
- package/tests/compare.test.js +497 -0
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -0
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -0
- package/tests/electron-features.test.js +864 -0
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -145
- package/types/utils.d.ts +245 -245
- 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;
|