zero-query 0.6.3 → 0.7.5
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 +6 -6
- package/cli/commands/build.js +3 -3
- package/cli/commands/bundle.js +286 -8
- package/cli/commands/dev/index.js +2 -2
- package/cli/commands/dev/overlay.js +51 -2
- package/cli/commands/dev/server.js +34 -5
- package/cli/commands/dev/watcher.js +33 -0
- package/cli/scaffold/index.html +1 -0
- package/cli/scaffold/scripts/app.js +15 -22
- package/cli/scaffold/scripts/components/contacts/contacts.css +0 -7
- package/cli/scaffold/scripts/components/contacts/contacts.html +3 -3
- package/cli/scaffold/styles/styles.css +1 -0
- package/cli/utils.js +111 -6
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +379 -27
- package/dist/zquery.min.js +3 -16
- package/index.d.ts +127 -1290
- package/package.json +5 -5
- package/src/component.js +11 -1
- package/src/core.js +305 -10
- package/src/router.js +49 -2
- package/tests/component.test.js +304 -0
- package/tests/core.test.js +726 -0
- package/tests/diff.test.js +194 -0
- package/tests/errors.test.js +162 -0
- package/tests/expression.test.js +334 -0
- package/tests/http.test.js +181 -0
- package/tests/reactive.test.js +191 -0
- package/tests/router.test.js +332 -0
- package/tests/store.test.js +253 -0
- package/tests/utils.test.js +353 -0
- package/types/collection.d.ts +368 -0
- package/types/component.d.ts +210 -0
- package/types/errors.d.ts +103 -0
- package/types/http.d.ts +81 -0
- package/types/misc.d.ts +166 -0
- package/types/reactive.d.ts +76 -0
- package/types/router.d.ts +132 -0
- package/types/ssr.d.ts +49 -0
- package/types/store.d.ts +107 -0
- package/types/utils.d.ts +142 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "zero-query",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.7.5",
|
|
4
4
|
"description": "Lightweight modern frontend library — jQuery-like selectors, reactive components, SPA router, and state management with zero dependencies.",
|
|
5
5
|
"main": "index.js",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -9,8 +9,10 @@
|
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"src",
|
|
12
|
+
"tests",
|
|
12
13
|
"dist",
|
|
13
14
|
"cli",
|
|
15
|
+
"types",
|
|
14
16
|
"index.js",
|
|
15
17
|
"index.d.ts",
|
|
16
18
|
"LICENSE",
|
|
@@ -52,11 +54,9 @@
|
|
|
52
54
|
"publishConfig": {
|
|
53
55
|
"access": "public"
|
|
54
56
|
},
|
|
55
|
-
"dependencies": {
|
|
56
|
-
"zero-http": "^0.2.3"
|
|
57
|
-
},
|
|
58
57
|
"devDependencies": {
|
|
59
58
|
"jsdom": "^28.1.0",
|
|
60
|
-
"vitest": "^4.0.18"
|
|
59
|
+
"vitest": "^4.0.18",
|
|
60
|
+
"zero-http": "^0.2.3"
|
|
61
61
|
}
|
|
62
62
|
}
|
package/src/component.js
CHANGED
|
@@ -381,11 +381,14 @@ class Component {
|
|
|
381
381
|
if (def.styleUrl && !def._styleLoaded) {
|
|
382
382
|
const su = def.styleUrl;
|
|
383
383
|
if (typeof su === 'string') {
|
|
384
|
-
|
|
384
|
+
const resolved = _resolveUrl(su, base);
|
|
385
|
+
def._externalStyles = await _fetchResource(resolved);
|
|
386
|
+
def._resolvedStyleUrls = [resolved];
|
|
385
387
|
} else if (Array.isArray(su)) {
|
|
386
388
|
const urls = su.map(u => _resolveUrl(u, base));
|
|
387
389
|
const results = await Promise.all(urls.map(u => _fetchResource(u)));
|
|
388
390
|
def._externalStyles = results.join('\n');
|
|
391
|
+
def._resolvedStyleUrls = urls;
|
|
389
392
|
}
|
|
390
393
|
def._styleLoaded = true;
|
|
391
394
|
}
|
|
@@ -512,6 +515,13 @@ class Component {
|
|
|
512
515
|
const styleEl = document.createElement('style');
|
|
513
516
|
styleEl.textContent = scoped;
|
|
514
517
|
styleEl.setAttribute('data-zq-component', this._def._name || '');
|
|
518
|
+
styleEl.setAttribute('data-zq-scope', scopeAttr);
|
|
519
|
+
if (this._def._resolvedStyleUrls) {
|
|
520
|
+
styleEl.setAttribute('data-zq-style-urls', this._def._resolvedStyleUrls.join(' '));
|
|
521
|
+
if (this._def.styles) {
|
|
522
|
+
styleEl.setAttribute('data-zq-inline', this._def.styles);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
515
525
|
document.head.appendChild(styleEl);
|
|
516
526
|
this._styleEl = styleEl;
|
|
517
527
|
}
|
package/src/core.js
CHANGED
|
@@ -75,8 +75,96 @@ export class ZQueryCollection {
|
|
|
75
75
|
return new ZQueryCollection(sibs);
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
-
next() {
|
|
79
|
-
|
|
78
|
+
next(selector) {
|
|
79
|
+
const els = this.elements.map(el => el.nextElementSibling).filter(Boolean);
|
|
80
|
+
return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
prev(selector) {
|
|
84
|
+
const els = this.elements.map(el => el.previousElementSibling).filter(Boolean);
|
|
85
|
+
return new ZQueryCollection(selector ? els.filter(el => el.matches(selector)) : els);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
nextAll(selector) {
|
|
89
|
+
const result = [];
|
|
90
|
+
this.elements.forEach(el => {
|
|
91
|
+
let sib = el.nextElementSibling;
|
|
92
|
+
while (sib) {
|
|
93
|
+
if (!selector || sib.matches(selector)) result.push(sib);
|
|
94
|
+
sib = sib.nextElementSibling;
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
return new ZQueryCollection(result);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
nextUntil(selector, filter) {
|
|
101
|
+
const result = [];
|
|
102
|
+
this.elements.forEach(el => {
|
|
103
|
+
let sib = el.nextElementSibling;
|
|
104
|
+
while (sib) {
|
|
105
|
+
if (selector && sib.matches(selector)) break;
|
|
106
|
+
if (!filter || sib.matches(filter)) result.push(sib);
|
|
107
|
+
sib = sib.nextElementSibling;
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
return new ZQueryCollection(result);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
prevAll(selector) {
|
|
114
|
+
const result = [];
|
|
115
|
+
this.elements.forEach(el => {
|
|
116
|
+
let sib = el.previousElementSibling;
|
|
117
|
+
while (sib) {
|
|
118
|
+
if (!selector || sib.matches(selector)) result.push(sib);
|
|
119
|
+
sib = sib.previousElementSibling;
|
|
120
|
+
}
|
|
121
|
+
});
|
|
122
|
+
return new ZQueryCollection(result);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
prevUntil(selector, filter) {
|
|
126
|
+
const result = [];
|
|
127
|
+
this.elements.forEach(el => {
|
|
128
|
+
let sib = el.previousElementSibling;
|
|
129
|
+
while (sib) {
|
|
130
|
+
if (selector && sib.matches(selector)) break;
|
|
131
|
+
if (!filter || sib.matches(filter)) result.push(sib);
|
|
132
|
+
sib = sib.previousElementSibling;
|
|
133
|
+
}
|
|
134
|
+
});
|
|
135
|
+
return new ZQueryCollection(result);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
parents(selector) {
|
|
139
|
+
const result = [];
|
|
140
|
+
this.elements.forEach(el => {
|
|
141
|
+
let parent = el.parentElement;
|
|
142
|
+
while (parent) {
|
|
143
|
+
if (!selector || parent.matches(selector)) result.push(parent);
|
|
144
|
+
parent = parent.parentElement;
|
|
145
|
+
}
|
|
146
|
+
});
|
|
147
|
+
return new ZQueryCollection([...new Set(result)]);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
parentsUntil(selector, filter) {
|
|
151
|
+
const result = [];
|
|
152
|
+
this.elements.forEach(el => {
|
|
153
|
+
let parent = el.parentElement;
|
|
154
|
+
while (parent) {
|
|
155
|
+
if (selector && parent.matches(selector)) break;
|
|
156
|
+
if (!filter || parent.matches(filter)) result.push(parent);
|
|
157
|
+
parent = parent.parentElement;
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
return new ZQueryCollection([...new Set(result)]);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
contents() {
|
|
164
|
+
const result = [];
|
|
165
|
+
this.elements.forEach(el => result.push(...el.childNodes));
|
|
166
|
+
return new ZQueryCollection(result);
|
|
167
|
+
}
|
|
80
168
|
|
|
81
169
|
filter(selector) {
|
|
82
170
|
if (typeof selector === 'function') {
|
|
@@ -96,6 +184,42 @@ export class ZQueryCollection {
|
|
|
96
184
|
return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector)));
|
|
97
185
|
}
|
|
98
186
|
|
|
187
|
+
is(selector) {
|
|
188
|
+
if (typeof selector === 'function') {
|
|
189
|
+
return this.elements.some((el, i) => selector.call(el, i, el));
|
|
190
|
+
}
|
|
191
|
+
return this.elements.some(el => el.matches(selector));
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
slice(start, end) {
|
|
195
|
+
return new ZQueryCollection(this.elements.slice(start, end));
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
add(selector, context) {
|
|
199
|
+
const toAdd = (selector instanceof ZQueryCollection)
|
|
200
|
+
? selector.elements
|
|
201
|
+
: (selector instanceof Node)
|
|
202
|
+
? [selector]
|
|
203
|
+
: Array.from((context || document).querySelectorAll(selector));
|
|
204
|
+
return new ZQueryCollection([...this.elements, ...toAdd]);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
get(index) {
|
|
208
|
+
if (index === undefined) return [...this.elements];
|
|
209
|
+
return index < 0 ? this.elements[this.length + index] : this.elements[index];
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
index(selector) {
|
|
213
|
+
if (selector === undefined) {
|
|
214
|
+
const el = this.first();
|
|
215
|
+
return el ? Array.from(el.parentElement.children).indexOf(el) : -1;
|
|
216
|
+
}
|
|
217
|
+
const target = (typeof selector === 'string')
|
|
218
|
+
? document.querySelector(selector)
|
|
219
|
+
: selector;
|
|
220
|
+
return this.elements.indexOf(target);
|
|
221
|
+
}
|
|
222
|
+
|
|
99
223
|
// --- Classes -------------------------------------------------------------
|
|
100
224
|
|
|
101
225
|
addClass(...names) {
|
|
@@ -108,8 +232,12 @@ export class ZQueryCollection {
|
|
|
108
232
|
return this.each((_, el) => el.classList.remove(...classes));
|
|
109
233
|
}
|
|
110
234
|
|
|
111
|
-
toggleClass(
|
|
112
|
-
|
|
235
|
+
toggleClass(...args) {
|
|
236
|
+
const force = typeof args[args.length - 1] === 'boolean' ? args.pop() : undefined;
|
|
237
|
+
const classes = args.flatMap(n => n.split(/\s+/));
|
|
238
|
+
return this.each((_, el) => {
|
|
239
|
+
classes.forEach(c => force !== undefined ? el.classList.toggle(c, force) : el.classList.toggle(c));
|
|
240
|
+
});
|
|
113
241
|
}
|
|
114
242
|
|
|
115
243
|
hasClass(name) {
|
|
@@ -163,6 +291,60 @@ export class ZQueryCollection {
|
|
|
163
291
|
return el ? { top: el.offsetTop, left: el.offsetLeft } : null;
|
|
164
292
|
}
|
|
165
293
|
|
|
294
|
+
scrollTop(value) {
|
|
295
|
+
if (value === undefined) {
|
|
296
|
+
const el = this.first();
|
|
297
|
+
return el === window ? window.scrollY : el?.scrollTop;
|
|
298
|
+
}
|
|
299
|
+
return this.each((_, el) => {
|
|
300
|
+
if (el === window) window.scrollTo(window.scrollX, value);
|
|
301
|
+
else el.scrollTop = value;
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
scrollLeft(value) {
|
|
306
|
+
if (value === undefined) {
|
|
307
|
+
const el = this.first();
|
|
308
|
+
return el === window ? window.scrollX : el?.scrollLeft;
|
|
309
|
+
}
|
|
310
|
+
return this.each((_, el) => {
|
|
311
|
+
if (el === window) window.scrollTo(value, window.scrollY);
|
|
312
|
+
else el.scrollLeft = value;
|
|
313
|
+
});
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
innerWidth() {
|
|
317
|
+
const el = this.first();
|
|
318
|
+
return el?.clientWidth;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
innerHeight() {
|
|
322
|
+
const el = this.first();
|
|
323
|
+
return el?.clientHeight;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
outerWidth(includeMargin = false) {
|
|
327
|
+
const el = this.first();
|
|
328
|
+
if (!el) return undefined;
|
|
329
|
+
let w = el.offsetWidth;
|
|
330
|
+
if (includeMargin) {
|
|
331
|
+
const style = getComputedStyle(el);
|
|
332
|
+
w += parseFloat(style.marginLeft) + parseFloat(style.marginRight);
|
|
333
|
+
}
|
|
334
|
+
return w;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
outerHeight(includeMargin = false) {
|
|
338
|
+
const el = this.first();
|
|
339
|
+
if (!el) return undefined;
|
|
340
|
+
let h = el.offsetHeight;
|
|
341
|
+
if (includeMargin) {
|
|
342
|
+
const style = getComputedStyle(el);
|
|
343
|
+
h += parseFloat(style.marginTop) + parseFloat(style.marginBottom);
|
|
344
|
+
}
|
|
345
|
+
return h;
|
|
346
|
+
}
|
|
347
|
+
|
|
166
348
|
// --- Content -------------------------------------------------------------
|
|
167
349
|
|
|
168
350
|
html(content) {
|
|
@@ -242,6 +424,73 @@ export class ZQueryCollection {
|
|
|
242
424
|
});
|
|
243
425
|
}
|
|
244
426
|
|
|
427
|
+
appendTo(target) {
|
|
428
|
+
const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
429
|
+
if (dest) this.each((_, el) => dest.appendChild(el));
|
|
430
|
+
return this;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
prependTo(target) {
|
|
434
|
+
const dest = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
435
|
+
if (dest) this.each((_, el) => dest.insertBefore(el, dest.firstChild));
|
|
436
|
+
return this;
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
insertAfter(target) {
|
|
440
|
+
const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
441
|
+
if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref.nextSibling));
|
|
442
|
+
return this;
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
insertBefore(target) {
|
|
446
|
+
const ref = typeof target === 'string' ? document.querySelector(target) : target instanceof ZQueryCollection ? target.first() : target;
|
|
447
|
+
if (ref && ref.parentNode) this.each((_, el) => ref.parentNode.insertBefore(el, ref));
|
|
448
|
+
return this;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
replaceAll(target) {
|
|
452
|
+
const targets = typeof target === 'string'
|
|
453
|
+
? Array.from(document.querySelectorAll(target))
|
|
454
|
+
: target instanceof ZQueryCollection ? target.elements : [target];
|
|
455
|
+
targets.forEach((t, i) => {
|
|
456
|
+
const nodes = i === 0 ? this.elements : this.elements.map(el => el.cloneNode(true));
|
|
457
|
+
nodes.forEach(el => t.parentNode.insertBefore(el, t));
|
|
458
|
+
t.remove();
|
|
459
|
+
});
|
|
460
|
+
return this;
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
unwrap(selector) {
|
|
464
|
+
this.elements.forEach(el => {
|
|
465
|
+
const parent = el.parentElement;
|
|
466
|
+
if (!parent || parent === document.body) return;
|
|
467
|
+
if (selector && !parent.matches(selector)) return;
|
|
468
|
+
parent.replaceWith(...parent.childNodes);
|
|
469
|
+
});
|
|
470
|
+
return this;
|
|
471
|
+
}
|
|
472
|
+
|
|
473
|
+
wrapAll(wrapper) {
|
|
474
|
+
const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
|
|
475
|
+
const first = this.first();
|
|
476
|
+
if (!first) return this;
|
|
477
|
+
first.parentNode.insertBefore(w, first);
|
|
478
|
+
this.each((_, el) => w.appendChild(el));
|
|
479
|
+
return this;
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
wrapInner(wrapper) {
|
|
483
|
+
return this.each((_, el) => {
|
|
484
|
+
const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true);
|
|
485
|
+
while (el.firstChild) w.appendChild(el.firstChild);
|
|
486
|
+
el.appendChild(w);
|
|
487
|
+
});
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
detach() {
|
|
491
|
+
return this.each((_, el) => el.remove());
|
|
492
|
+
}
|
|
493
|
+
|
|
245
494
|
// --- Visibility ----------------------------------------------------------
|
|
246
495
|
|
|
247
496
|
show(display = '') {
|
|
@@ -267,9 +516,10 @@ export class ZQueryCollection {
|
|
|
267
516
|
events.forEach(evt => {
|
|
268
517
|
if (typeof selectorOrHandler === 'function') {
|
|
269
518
|
el.addEventListener(evt, selectorOrHandler);
|
|
270
|
-
} else {
|
|
271
|
-
// Delegated event
|
|
519
|
+
} else if (typeof selectorOrHandler === 'string') {
|
|
520
|
+
// Delegated event — only works on elements that support closest()
|
|
272
521
|
el.addEventListener(evt, (e) => {
|
|
522
|
+
if (!e.target || typeof e.target.closest !== 'function') return;
|
|
273
523
|
const target = e.target.closest(selectorOrHandler);
|
|
274
524
|
if (target && el.contains(target)) handler.call(target, e);
|
|
275
525
|
});
|
|
@@ -302,6 +552,10 @@ export class ZQueryCollection {
|
|
|
302
552
|
submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); }
|
|
303
553
|
focus() { this.first()?.focus(); return this; }
|
|
304
554
|
blur() { this.first()?.blur(); return this; }
|
|
555
|
+
hover(enterFn, leaveFn) {
|
|
556
|
+
this.on('mouseenter', enterFn);
|
|
557
|
+
return this.on('mouseleave', leaveFn || enterFn);
|
|
558
|
+
}
|
|
305
559
|
|
|
306
560
|
// --- Animation -----------------------------------------------------------
|
|
307
561
|
|
|
@@ -333,6 +587,40 @@ export class ZQueryCollection {
|
|
|
333
587
|
return this.animate({ opacity: '0' }, duration).then(col => col.hide());
|
|
334
588
|
}
|
|
335
589
|
|
|
590
|
+
fadeToggle(duration = 300) {
|
|
591
|
+
return Promise.all(this.elements.map(el => {
|
|
592
|
+
const visible = getComputedStyle(el).opacity !== '0' && getComputedStyle(el).display !== 'none';
|
|
593
|
+
const col = new ZQueryCollection([el]);
|
|
594
|
+
return visible ? col.fadeOut(duration) : col.fadeIn(duration);
|
|
595
|
+
})).then(() => this);
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
fadeTo(duration, opacity) {
|
|
599
|
+
return this.animate({ opacity: String(opacity) }, duration);
|
|
600
|
+
}
|
|
601
|
+
|
|
602
|
+
slideDown(duration = 300) {
|
|
603
|
+
return this.each((_, el) => {
|
|
604
|
+
el.style.display = '';
|
|
605
|
+
el.style.overflow = 'hidden';
|
|
606
|
+
const h = el.scrollHeight + 'px';
|
|
607
|
+
el.style.maxHeight = '0';
|
|
608
|
+
el.style.transition = `max-height ${duration}ms ease`;
|
|
609
|
+
requestAnimationFrame(() => { el.style.maxHeight = h; });
|
|
610
|
+
setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
|
|
611
|
+
});
|
|
612
|
+
}
|
|
613
|
+
|
|
614
|
+
slideUp(duration = 300) {
|
|
615
|
+
return this.each((_, el) => {
|
|
616
|
+
el.style.overflow = 'hidden';
|
|
617
|
+
el.style.maxHeight = el.scrollHeight + 'px';
|
|
618
|
+
el.style.transition = `max-height ${duration}ms ease`;
|
|
619
|
+
requestAnimationFrame(() => { el.style.maxHeight = '0'; });
|
|
620
|
+
setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration);
|
|
621
|
+
});
|
|
622
|
+
}
|
|
623
|
+
|
|
336
624
|
slideToggle(duration = 300) {
|
|
337
625
|
return this.each((_, el) => {
|
|
338
626
|
if (el.style.display === 'none' || getComputedStyle(el).display === 'none') {
|
|
@@ -480,7 +768,7 @@ query.children = (parentId) => {
|
|
|
480
768
|
return new ZQueryCollection(p ? Array.from(p.children) : []);
|
|
481
769
|
};
|
|
482
770
|
|
|
483
|
-
// Create element shorthand
|
|
771
|
+
// Create element shorthand — returns ZQueryCollection for chaining
|
|
484
772
|
query.create = (tag, attrs = {}, ...children) => {
|
|
485
773
|
const el = document.createElement(tag);
|
|
486
774
|
for (const [k, v] of Object.entries(attrs)) {
|
|
@@ -494,7 +782,7 @@ query.create = (tag, attrs = {}, ...children) => {
|
|
|
494
782
|
if (typeof child === 'string') el.appendChild(document.createTextNode(child));
|
|
495
783
|
else if (child instanceof Node) el.appendChild(child);
|
|
496
784
|
});
|
|
497
|
-
return el;
|
|
785
|
+
return new ZQueryCollection(el);
|
|
498
786
|
};
|
|
499
787
|
|
|
500
788
|
// DOM ready
|
|
@@ -503,17 +791,24 @@ query.ready = (fn) => {
|
|
|
503
791
|
else document.addEventListener('DOMContentLoaded', fn);
|
|
504
792
|
};
|
|
505
793
|
|
|
506
|
-
// Global event listeners — supports direct and
|
|
794
|
+
// Global event listeners — supports direct, delegated, and target-bound forms
|
|
507
795
|
// $.on('keydown', handler) → direct listener on document
|
|
508
796
|
// $.on('click', '.btn', handler) → delegated via closest()
|
|
797
|
+
// $.on('scroll', window, handler) → direct listener on target
|
|
509
798
|
query.on = (event, selectorOrHandler, handler) => {
|
|
510
799
|
if (typeof selectorOrHandler === 'function') {
|
|
511
800
|
// 2-arg: direct document listener (keydown, resize, etc.)
|
|
512
801
|
document.addEventListener(event, selectorOrHandler);
|
|
513
802
|
return;
|
|
514
803
|
}
|
|
515
|
-
//
|
|
804
|
+
// EventTarget (window, element, etc.) — direct listener on target
|
|
805
|
+
if (typeof selectorOrHandler === 'object' && typeof selectorOrHandler.addEventListener === 'function') {
|
|
806
|
+
selectorOrHandler.addEventListener(event, handler);
|
|
807
|
+
return;
|
|
808
|
+
}
|
|
809
|
+
// 3-arg string: delegated
|
|
516
810
|
document.addEventListener(event, (e) => {
|
|
811
|
+
if (!e.target || typeof e.target.closest !== 'function') return;
|
|
517
812
|
const target = e.target.closest(selectorOrHandler);
|
|
518
813
|
if (target) handler.call(target, e);
|
|
519
814
|
});
|
package/src/router.js
CHANGED
|
@@ -78,7 +78,21 @@ class Router {
|
|
|
78
78
|
if (!link) return;
|
|
79
79
|
if (link.getAttribute('target') === '_blank') return;
|
|
80
80
|
e.preventDefault();
|
|
81
|
-
|
|
81
|
+
let href = link.getAttribute('z-link');
|
|
82
|
+
// Support z-link-params for dynamic :param interpolation
|
|
83
|
+
const paramsAttr = link.getAttribute('z-link-params');
|
|
84
|
+
if (paramsAttr) {
|
|
85
|
+
try {
|
|
86
|
+
const params = JSON.parse(paramsAttr);
|
|
87
|
+
href = this._interpolateParams(href, params);
|
|
88
|
+
} catch { /* ignore malformed JSON */ }
|
|
89
|
+
}
|
|
90
|
+
this.navigate(href);
|
|
91
|
+
// z-to-top modifier: scroll to top after navigation
|
|
92
|
+
if (link.hasAttribute('z-to-top')) {
|
|
93
|
+
const scrollBehavior = link.getAttribute('z-to-top') || 'instant';
|
|
94
|
+
window.scrollTo({ top: 0, behavior: scrollBehavior });
|
|
95
|
+
}
|
|
82
96
|
});
|
|
83
97
|
|
|
84
98
|
// Initial resolve
|
|
@@ -122,7 +136,23 @@ class Router {
|
|
|
122
136
|
|
|
123
137
|
// --- Navigation ----------------------------------------------------------
|
|
124
138
|
|
|
139
|
+
/**
|
|
140
|
+
* Interpolate :param placeholders in a path with the given values.
|
|
141
|
+
* @param {string} path — e.g. '/user/:id/posts/:pid'
|
|
142
|
+
* @param {Object} params — e.g. { id: 42, pid: 7 }
|
|
143
|
+
* @returns {string}
|
|
144
|
+
*/
|
|
145
|
+
_interpolateParams(path, params) {
|
|
146
|
+
if (!params || typeof params !== 'object') return path;
|
|
147
|
+
return path.replace(/:([\w]+)/g, (_, key) => {
|
|
148
|
+
const val = params[key];
|
|
149
|
+
return val != null ? encodeURIComponent(String(val)) : ':' + key;
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
125
153
|
navigate(path, options = {}) {
|
|
154
|
+
// Interpolate :param placeholders if options.params is provided
|
|
155
|
+
if (options.params) path = this._interpolateParams(path, options.params);
|
|
126
156
|
// Separate hash fragment (e.g. /docs/getting-started#cli-bundler)
|
|
127
157
|
const [cleanPath, fragment] = (path || '').split('#');
|
|
128
158
|
let normalized = this._normalizePath(cleanPath);
|
|
@@ -140,6 +170,8 @@ class Router {
|
|
|
140
170
|
}
|
|
141
171
|
|
|
142
172
|
replace(path, options = {}) {
|
|
173
|
+
// Interpolate :param placeholders if options.params is provided
|
|
174
|
+
if (options.params) path = this._interpolateParams(path, options.params);
|
|
143
175
|
const [cleanPath, fragment] = (path || '').split('#');
|
|
144
176
|
let normalized = this._normalizePath(cleanPath);
|
|
145
177
|
const hash = fragment ? '#' + fragment : '';
|
|
@@ -253,6 +285,7 @@ class Router {
|
|
|
253
285
|
// Prevent re-entrant calls (e.g. listener triggering navigation)
|
|
254
286
|
if (this._resolving) return;
|
|
255
287
|
this._resolving = true;
|
|
288
|
+
this._redirectCount = 0;
|
|
256
289
|
try {
|
|
257
290
|
await this.__resolve();
|
|
258
291
|
} finally {
|
|
@@ -294,7 +327,21 @@ class Router {
|
|
|
294
327
|
const result = await guard(to, from);
|
|
295
328
|
if (result === false) return; // Cancel
|
|
296
329
|
if (typeof result === 'string') { // Redirect
|
|
297
|
-
|
|
330
|
+
if (++this._redirectCount > 10) {
|
|
331
|
+
reportError(ErrorCode.ROUTER_GUARD, 'Too many guard redirects (possible loop)', { to }, null);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
// Update URL directly and re-resolve (avoids re-entrancy block)
|
|
335
|
+
const [rPath, rFrag] = result.split('#');
|
|
336
|
+
const rNorm = this._normalizePath(rPath || '/');
|
|
337
|
+
const rHash = rFrag ? '#' + rFrag : '';
|
|
338
|
+
if (this._mode === 'hash') {
|
|
339
|
+
if (rFrag) window.__zqScrollTarget = rFrag;
|
|
340
|
+
window.location.replace('#' + rNorm);
|
|
341
|
+
} else {
|
|
342
|
+
window.history.replaceState({}, '', this._base + rNorm + rHash);
|
|
343
|
+
}
|
|
344
|
+
return this.__resolve();
|
|
298
345
|
}
|
|
299
346
|
} catch (err) {
|
|
300
347
|
reportError(ErrorCode.ROUTER_GUARD, 'Before-guard threw', { to, from }, err);
|