zero-query 0.5.2 → 0.6.3
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 +8 -6
- package/cli/commands/build.js +4 -2
- package/cli/commands/dev/index.js +82 -0
- package/cli/commands/dev/logger.js +70 -0
- package/cli/commands/dev/overlay.js +317 -0
- package/cli/commands/dev/server.js +129 -0
- package/cli/commands/dev/validator.js +94 -0
- package/cli/commands/dev/watcher.js +114 -0
- package/cli/scaffold/favicon.ico +0 -0
- package/cli/scaffold/scripts/components/about.js +14 -2
- package/cli/scaffold/scripts/components/contacts/contacts.html +5 -4
- package/cli/scaffold/scripts/components/contacts/contacts.js +17 -1
- package/cli/scaffold/scripts/components/counter.js +30 -10
- package/cli/scaffold/scripts/components/home.js +3 -3
- package/cli/scaffold/scripts/components/todos.js +6 -5
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +1542 -105
- package/dist/zquery.min.js +11 -8
- package/index.d.ts +252 -20
- package/index.js +18 -7
- package/package.json +8 -2
- package/src/component.js +175 -44
- package/src/core.js +22 -25
- package/src/diff.js +280 -0
- package/src/errors.js +155 -0
- package/src/expression.js +806 -0
- package/src/http.js +18 -10
- package/src/reactive.js +29 -4
- package/src/router.js +11 -5
- package/src/ssr.js +224 -0
- package/src/store.js +24 -8
- /package/cli/commands/{dev.js → dev.old.js} +0 -0
package/src/component.js
CHANGED
|
@@ -20,6 +20,9 @@
|
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
22
|
import { reactive } from './reactive.js';
|
|
23
|
+
import { morph } from './diff.js';
|
|
24
|
+
import { safeEval } from './expression.js';
|
|
25
|
+
import { reportError, ErrorCode, ZQueryError } from './errors.js';
|
|
23
26
|
|
|
24
27
|
// ---------------------------------------------------------------------------
|
|
25
28
|
// Component registry & external resource cache
|
|
@@ -195,10 +198,27 @@ class Component {
|
|
|
195
198
|
this._destroyed = false;
|
|
196
199
|
this._updateQueued = false;
|
|
197
200
|
this._listeners = [];
|
|
201
|
+
this._watchCleanups = [];
|
|
198
202
|
|
|
199
203
|
// Refs map
|
|
200
204
|
this.refs = {};
|
|
201
205
|
|
|
206
|
+
// Capture slot content before first render replaces it
|
|
207
|
+
this._slotContent = {};
|
|
208
|
+
const defaultSlotNodes = [];
|
|
209
|
+
[...el.childNodes].forEach(node => {
|
|
210
|
+
if (node.nodeType === 1 && node.hasAttribute('slot')) {
|
|
211
|
+
const slotName = node.getAttribute('slot');
|
|
212
|
+
if (!this._slotContent[slotName]) this._slotContent[slotName] = '';
|
|
213
|
+
this._slotContent[slotName] += node.outerHTML;
|
|
214
|
+
} else if (node.nodeType === 1 || (node.nodeType === 3 && node.textContent.trim())) {
|
|
215
|
+
defaultSlotNodes.push(node.nodeType === 1 ? node.outerHTML : node.textContent);
|
|
216
|
+
}
|
|
217
|
+
});
|
|
218
|
+
if (defaultSlotNodes.length) {
|
|
219
|
+
this._slotContent['default'] = defaultSlotNodes.join('');
|
|
220
|
+
}
|
|
221
|
+
|
|
202
222
|
// Props (read-only from parent)
|
|
203
223
|
this.props = Object.freeze({ ...props });
|
|
204
224
|
|
|
@@ -207,10 +227,25 @@ class Component {
|
|
|
207
227
|
? definition.state()
|
|
208
228
|
: { ...(definition.state || {}) };
|
|
209
229
|
|
|
210
|
-
this.state = reactive(initialState, () => {
|
|
211
|
-
if (!this._destroyed)
|
|
230
|
+
this.state = reactive(initialState, (key, value, old) => {
|
|
231
|
+
if (!this._destroyed) {
|
|
232
|
+
// Run watchers for the changed key
|
|
233
|
+
this._runWatchers(key, value, old);
|
|
234
|
+
this._scheduleUpdate();
|
|
235
|
+
}
|
|
212
236
|
});
|
|
213
237
|
|
|
238
|
+
// Computed properties — lazy getters derived from state
|
|
239
|
+
this.computed = {};
|
|
240
|
+
if (definition.computed) {
|
|
241
|
+
for (const [name, fn] of Object.entries(definition.computed)) {
|
|
242
|
+
Object.defineProperty(this.computed, name, {
|
|
243
|
+
get: () => fn.call(this, this.state.__raw || this.state),
|
|
244
|
+
enumerable: true
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
214
249
|
// Bind all user methods to this instance
|
|
215
250
|
for (const [key, val] of Object.entries(definition)) {
|
|
216
251
|
if (typeof val === 'function' && !_reservedKeys.has(key)) {
|
|
@@ -219,7 +254,36 @@ class Component {
|
|
|
219
254
|
}
|
|
220
255
|
|
|
221
256
|
// Init lifecycle
|
|
222
|
-
if (definition.init)
|
|
257
|
+
if (definition.init) {
|
|
258
|
+
try { definition.init.call(this); }
|
|
259
|
+
catch (err) { reportError(ErrorCode.COMP_LIFECYCLE, `Component "${definition._name}" init() threw`, { component: definition._name }, err); }
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// Set up watchers after init so initial state is ready
|
|
263
|
+
if (definition.watch) {
|
|
264
|
+
this._prevWatchValues = {};
|
|
265
|
+
for (const key of Object.keys(definition.watch)) {
|
|
266
|
+
this._prevWatchValues[key] = _getPath(this.state.__raw || this.state, key);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// Run registered watchers for a changed key
|
|
272
|
+
_runWatchers(changedKey, value, old) {
|
|
273
|
+
const watchers = this._def.watch;
|
|
274
|
+
if (!watchers) return;
|
|
275
|
+
for (const [key, handler] of Object.entries(watchers)) {
|
|
276
|
+
// Match exact key or parent key (e.g. watcher on 'user' fires when 'user.name' changes)
|
|
277
|
+
if (changedKey === key || key.startsWith(changedKey + '.') || changedKey.startsWith(key + '.') || changedKey === key) {
|
|
278
|
+
const currentVal = _getPath(this.state.__raw || this.state, key);
|
|
279
|
+
const prevVal = this._prevWatchValues?.[key];
|
|
280
|
+
if (currentVal !== prevVal) {
|
|
281
|
+
const fn = typeof handler === 'function' ? handler : handler.handler;
|
|
282
|
+
if (typeof fn === 'function') fn.call(this, currentVal, prevVal);
|
|
283
|
+
if (this._prevWatchValues) this._prevWatchValues[key] = currentVal;
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
}
|
|
223
287
|
}
|
|
224
288
|
|
|
225
289
|
// Schedule a batched DOM update (microtask)
|
|
@@ -394,17 +458,29 @@ class Component {
|
|
|
394
458
|
// Then do global {{expression}} interpolation on the remaining content
|
|
395
459
|
html = html.replace(/\{\{(.+?)\}\}/g, (_, expr) => {
|
|
396
460
|
try {
|
|
397
|
-
|
|
461
|
+
const result = safeEval(expr.trim(), [
|
|
398
462
|
this.state.__raw || this.state,
|
|
399
|
-
this.props,
|
|
400
|
-
|
|
401
|
-
|
|
463
|
+
{ props: this.props, computed: this.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
|
|
464
|
+
]);
|
|
465
|
+
return result != null ? result : '';
|
|
402
466
|
} catch { return ''; }
|
|
403
467
|
});
|
|
404
468
|
} else {
|
|
405
469
|
html = '';
|
|
406
470
|
}
|
|
407
471
|
|
|
472
|
+
// -- Slot distribution ----------------------------------------
|
|
473
|
+
// Replace <slot> elements with captured slot content from parent.
|
|
474
|
+
// <slot> → default slot content
|
|
475
|
+
// <slot name="header"> → named slot content
|
|
476
|
+
// Fallback content between <slot>...</slot> used when no content provided.
|
|
477
|
+
if (html.includes('<slot')) {
|
|
478
|
+
html = html.replace(/<slot(?:\s+name="([^"]*)")?\s*(?:\/>|>([\s\S]*?)<\/slot>)/g, (_, name, fallback) => {
|
|
479
|
+
const slotName = name || 'default';
|
|
480
|
+
return this._slotContent[slotName] || fallback || '';
|
|
481
|
+
});
|
|
482
|
+
}
|
|
483
|
+
|
|
408
484
|
// Combine inline styles + external styles
|
|
409
485
|
const combinedStyles = [
|
|
410
486
|
this._def.styles || '',
|
|
@@ -415,7 +491,22 @@ class Component {
|
|
|
415
491
|
if (!this._mounted && combinedStyles) {
|
|
416
492
|
const scopeAttr = `z-s${this._uid}`;
|
|
417
493
|
this._el.setAttribute(scopeAttr, '');
|
|
418
|
-
|
|
494
|
+
let inAtBlock = 0;
|
|
495
|
+
const scoped = combinedStyles.replace(/([^{}]+)\{|\}/g, (match, selector) => {
|
|
496
|
+
if (match === '}') {
|
|
497
|
+
if (inAtBlock > 0) inAtBlock--;
|
|
498
|
+
return match;
|
|
499
|
+
}
|
|
500
|
+
const trimmed = selector.trim();
|
|
501
|
+
// Don't scope @-rules (@media, @keyframes, @supports, @container, @layer, @font-face, etc.)
|
|
502
|
+
if (trimmed.startsWith('@')) {
|
|
503
|
+
inAtBlock++;
|
|
504
|
+
return match;
|
|
505
|
+
}
|
|
506
|
+
// Don't scope keyframe stops (from, to, 0%, 50%, etc.)
|
|
507
|
+
if (inAtBlock > 0 && /^[\d%\s,fromto]+$/.test(trimmed.replace(/\s/g, ''))) {
|
|
508
|
+
return match;
|
|
509
|
+
}
|
|
419
510
|
return selector.split(',').map(s => `[${scopeAttr}] ${s.trim()}`).join(', ') + ' {';
|
|
420
511
|
});
|
|
421
512
|
const styleEl = document.createElement('style');
|
|
@@ -426,22 +517,19 @@ class Component {
|
|
|
426
517
|
}
|
|
427
518
|
|
|
428
519
|
// -- Focus preservation ----------------------------------------
|
|
429
|
-
//
|
|
430
|
-
//
|
|
431
|
-
// input/textarea/select inside the component, not only z-model.
|
|
520
|
+
// DOM morphing preserves unchanged nodes naturally, but we still
|
|
521
|
+
// track focus for cases where the focused element's subtree changes.
|
|
432
522
|
let _focusInfo = null;
|
|
433
523
|
const _active = document.activeElement;
|
|
434
524
|
if (_active && this._el.contains(_active)) {
|
|
435
525
|
const modelKey = _active.getAttribute?.('z-model');
|
|
436
526
|
const refKey = _active.getAttribute?.('z-ref');
|
|
437
|
-
// Build a selector that can locate the same element after re-render
|
|
438
527
|
let selector = null;
|
|
439
528
|
if (modelKey) {
|
|
440
529
|
selector = `[z-model="${modelKey}"]`;
|
|
441
530
|
} else if (refKey) {
|
|
442
531
|
selector = `[z-ref="${refKey}"]`;
|
|
443
532
|
} else {
|
|
444
|
-
// Fallback: match by tag + type + name + placeholder combination
|
|
445
533
|
const tag = _active.tagName.toLowerCase();
|
|
446
534
|
if (tag === 'input' || tag === 'textarea' || tag === 'select') {
|
|
447
535
|
let s = tag;
|
|
@@ -461,8 +549,13 @@ class Component {
|
|
|
461
549
|
}
|
|
462
550
|
}
|
|
463
551
|
|
|
464
|
-
// Update DOM
|
|
465
|
-
|
|
552
|
+
// Update DOM via morphing (diffing) — preserves unchanged nodes
|
|
553
|
+
// First render uses innerHTML for speed; subsequent renders morph.
|
|
554
|
+
if (!this._mounted) {
|
|
555
|
+
this._el.innerHTML = html;
|
|
556
|
+
} else {
|
|
557
|
+
morph(this._el, html);
|
|
558
|
+
}
|
|
466
559
|
|
|
467
560
|
// Process structural & attribute directives
|
|
468
561
|
this._processDirectives();
|
|
@@ -472,10 +565,10 @@ class Component {
|
|
|
472
565
|
this._bindRefs();
|
|
473
566
|
this._bindModels();
|
|
474
567
|
|
|
475
|
-
// Restore focus
|
|
568
|
+
// Restore focus if the morph replaced the focused element
|
|
476
569
|
if (_focusInfo) {
|
|
477
570
|
const el = this._el.querySelector(_focusInfo.selector);
|
|
478
|
-
if (el) {
|
|
571
|
+
if (el && el !== document.activeElement) {
|
|
479
572
|
el.focus();
|
|
480
573
|
try {
|
|
481
574
|
if (_focusInfo.start !== null && _focusInfo.start !== undefined) {
|
|
@@ -490,9 +583,15 @@ class Component {
|
|
|
490
583
|
|
|
491
584
|
if (!this._mounted) {
|
|
492
585
|
this._mounted = true;
|
|
493
|
-
if (this._def.mounted)
|
|
586
|
+
if (this._def.mounted) {
|
|
587
|
+
try { this._def.mounted.call(this); }
|
|
588
|
+
catch (err) { reportError(ErrorCode.COMP_LIFECYCLE, `Component "${this._def._name}" mounted() threw`, { component: this._def._name }, err); }
|
|
589
|
+
}
|
|
494
590
|
} else {
|
|
495
|
-
if (this._def.updated)
|
|
591
|
+
if (this._def.updated) {
|
|
592
|
+
try { this._def.updated.call(this); }
|
|
593
|
+
catch (err) { reportError(ErrorCode.COMP_LIFECYCLE, `Component "${this._def._name}" updated() threw`, { component: this._def._name }, err); }
|
|
594
|
+
}
|
|
496
595
|
}
|
|
497
596
|
}
|
|
498
597
|
|
|
@@ -701,18 +800,13 @@ class Component {
|
|
|
701
800
|
}
|
|
702
801
|
|
|
703
802
|
// ---------------------------------------------------------------------------
|
|
704
|
-
// Expression evaluator —
|
|
803
|
+
// Expression evaluator — CSP-safe parser (no eval / new Function)
|
|
705
804
|
// ---------------------------------------------------------------------------
|
|
706
805
|
_evalExpr(expr) {
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
this.props,
|
|
712
|
-
this.refs,
|
|
713
|
-
typeof window !== 'undefined' ? window.$ : undefined
|
|
714
|
-
);
|
|
715
|
-
} catch { return undefined; }
|
|
806
|
+
return safeEval(expr, [
|
|
807
|
+
this.state.__raw || this.state,
|
|
808
|
+
{ props: this.props, refs: this.refs, computed: this.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
|
|
809
|
+
]);
|
|
716
810
|
}
|
|
717
811
|
|
|
718
812
|
// ---------------------------------------------------------------------------
|
|
@@ -774,13 +868,15 @@ class Component {
|
|
|
774
868
|
const evalReplace = (str, item, index) =>
|
|
775
869
|
str.replace(/\{\{(.+?)\}\}/g, (_, inner) => {
|
|
776
870
|
try {
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
871
|
+
const loopScope = {};
|
|
872
|
+
loopScope[itemVar] = item;
|
|
873
|
+
loopScope[indexVar] = index;
|
|
874
|
+
const result = safeEval(inner.trim(), [
|
|
875
|
+
loopScope,
|
|
780
876
|
this.state.__raw || this.state,
|
|
781
|
-
this.props,
|
|
782
|
-
|
|
783
|
-
|
|
877
|
+
{ props: this.props, computed: this.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
|
|
878
|
+
]);
|
|
879
|
+
return result != null ? result : '';
|
|
784
880
|
} catch { return ''; }
|
|
785
881
|
});
|
|
786
882
|
|
|
@@ -945,7 +1041,10 @@ class Component {
|
|
|
945
1041
|
destroy() {
|
|
946
1042
|
if (this._destroyed) return;
|
|
947
1043
|
this._destroyed = true;
|
|
948
|
-
if (this._def.destroyed)
|
|
1044
|
+
if (this._def.destroyed) {
|
|
1045
|
+
try { this._def.destroyed.call(this); }
|
|
1046
|
+
catch (err) { reportError(ErrorCode.COMP_LIFECYCLE, `Component "${this._def._name}" destroyed() threw`, { component: this._def._name }, err); }
|
|
1047
|
+
}
|
|
949
1048
|
this._listeners.forEach(({ event, handler }) => this._el.removeEventListener(event, handler));
|
|
950
1049
|
this._listeners = [];
|
|
951
1050
|
if (this._styleEl) this._styleEl.remove();
|
|
@@ -958,7 +1057,8 @@ class Component {
|
|
|
958
1057
|
// Reserved definition keys (not user methods)
|
|
959
1058
|
const _reservedKeys = new Set([
|
|
960
1059
|
'state', 'render', 'styles', 'init', 'mounted', 'updated', 'destroyed', 'props',
|
|
961
|
-
'templateUrl', 'styleUrl', 'templates', 'pages', 'activePage', 'base'
|
|
1060
|
+
'templateUrl', 'styleUrl', 'templates', 'pages', 'activePage', 'base',
|
|
1061
|
+
'computed', 'watch'
|
|
962
1062
|
]);
|
|
963
1063
|
|
|
964
1064
|
|
|
@@ -972,8 +1072,11 @@ const _reservedKeys = new Set([
|
|
|
972
1072
|
* @param {object} definition — component definition
|
|
973
1073
|
*/
|
|
974
1074
|
export function component(name, definition) {
|
|
1075
|
+
if (!name || typeof name !== 'string') {
|
|
1076
|
+
throw new ZQueryError(ErrorCode.COMP_INVALID_NAME, 'Component name must be a non-empty string');
|
|
1077
|
+
}
|
|
975
1078
|
if (!name.includes('-')) {
|
|
976
|
-
throw new
|
|
1079
|
+
throw new ZQueryError(ErrorCode.COMP_INVALID_NAME, `Component name "${name}" must contain a hyphen (Web Component convention)`);
|
|
977
1080
|
}
|
|
978
1081
|
definition._name = name;
|
|
979
1082
|
|
|
@@ -998,10 +1101,10 @@ export function component(name, definition) {
|
|
|
998
1101
|
*/
|
|
999
1102
|
export function mount(target, componentName, props = {}) {
|
|
1000
1103
|
const el = typeof target === 'string' ? document.querySelector(target) : target;
|
|
1001
|
-
if (!el) throw new
|
|
1104
|
+
if (!el) throw new ZQueryError(ErrorCode.COMP_MOUNT_TARGET, `Mount target "${target}" not found`, { target });
|
|
1002
1105
|
|
|
1003
1106
|
const def = _registry.get(componentName);
|
|
1004
|
-
if (!def) throw new
|
|
1107
|
+
if (!def) throw new ZQueryError(ErrorCode.COMP_NOT_FOUND, `Component "${componentName}" not registered`, { component: componentName });
|
|
1005
1108
|
|
|
1006
1109
|
// Destroy existing instance
|
|
1007
1110
|
if (_instances.has(el)) _instances.get(el).destroy();
|
|
@@ -1024,12 +1127,40 @@ export function mountAll(root = document.body) {
|
|
|
1024
1127
|
|
|
1025
1128
|
// Extract props from attributes
|
|
1026
1129
|
const props = {};
|
|
1130
|
+
|
|
1131
|
+
// Find parent component instance for evaluating dynamic prop expressions
|
|
1132
|
+
let parentInstance = null;
|
|
1133
|
+
let ancestor = tag.parentElement;
|
|
1134
|
+
while (ancestor) {
|
|
1135
|
+
if (_instances.has(ancestor)) {
|
|
1136
|
+
parentInstance = _instances.get(ancestor);
|
|
1137
|
+
break;
|
|
1138
|
+
}
|
|
1139
|
+
ancestor = ancestor.parentElement;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1027
1142
|
[...tag.attributes].forEach(attr => {
|
|
1028
|
-
if (
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1143
|
+
if (attr.name.startsWith('@') || attr.name.startsWith('z-')) return;
|
|
1144
|
+
|
|
1145
|
+
// Dynamic prop: :propName="expression" — evaluate in parent context
|
|
1146
|
+
if (attr.name.startsWith(':')) {
|
|
1147
|
+
const propName = attr.name.slice(1);
|
|
1148
|
+
if (parentInstance) {
|
|
1149
|
+
props[propName] = safeEval(attr.value, [
|
|
1150
|
+
parentInstance.state.__raw || parentInstance.state,
|
|
1151
|
+
{ props: parentInstance.props, refs: parentInstance.refs, computed: parentInstance.computed, $: typeof window !== 'undefined' ? window.$ : undefined }
|
|
1152
|
+
]);
|
|
1153
|
+
} else {
|
|
1154
|
+
// No parent — try JSON parse
|
|
1155
|
+
try { props[propName] = JSON.parse(attr.value); }
|
|
1156
|
+
catch { props[propName] = attr.value; }
|
|
1157
|
+
}
|
|
1158
|
+
return;
|
|
1032
1159
|
}
|
|
1160
|
+
|
|
1161
|
+
// Static prop
|
|
1162
|
+
try { props[attr.name] = JSON.parse(attr.value); }
|
|
1163
|
+
catch { props[attr.name] = attr.value; }
|
|
1033
1164
|
});
|
|
1034
1165
|
|
|
1035
1166
|
const instance = new Component(tag, def, props);
|
package/src/core.js
CHANGED
|
@@ -26,6 +26,11 @@ export class ZQueryCollection {
|
|
|
26
26
|
return this.elements.map((el, i) => fn.call(el, i, el));
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
forEach(fn) {
|
|
30
|
+
this.elements.forEach((el, i) => fn(el, i, this.elements));
|
|
31
|
+
return this;
|
|
32
|
+
}
|
|
33
|
+
|
|
29
34
|
first() { return this.elements[0] || null; }
|
|
30
35
|
last() { return this.elements[this.length - 1] || null; }
|
|
31
36
|
eq(i) { return new ZQueryCollection(this.elements[i] ? [this.elements[i]] : []); }
|
|
@@ -384,42 +389,40 @@ function createFragment(html) {
|
|
|
384
389
|
|
|
385
390
|
|
|
386
391
|
// ---------------------------------------------------------------------------
|
|
387
|
-
// $() — main selector / creator
|
|
392
|
+
// $() — main selector / creator (returns ZQueryCollection, like jQuery)
|
|
388
393
|
// ---------------------------------------------------------------------------
|
|
389
394
|
export function query(selector, context) {
|
|
390
395
|
// null / undefined
|
|
391
|
-
if (!selector) return
|
|
396
|
+
if (!selector) return new ZQueryCollection([]);
|
|
392
397
|
|
|
393
|
-
// Already a collection — return
|
|
394
|
-
if (selector instanceof ZQueryCollection) return selector
|
|
398
|
+
// Already a collection — return as-is
|
|
399
|
+
if (selector instanceof ZQueryCollection) return selector;
|
|
395
400
|
|
|
396
|
-
// DOM element or Window —
|
|
401
|
+
// DOM element or Window — wrap in collection
|
|
397
402
|
if (selector instanceof Node || selector === window) {
|
|
398
|
-
return selector;
|
|
403
|
+
return new ZQueryCollection([selector]);
|
|
399
404
|
}
|
|
400
405
|
|
|
401
|
-
// NodeList / HTMLCollection / Array —
|
|
406
|
+
// NodeList / HTMLCollection / Array — wrap in collection
|
|
402
407
|
if (selector instanceof NodeList || selector instanceof HTMLCollection || Array.isArray(selector)) {
|
|
403
|
-
|
|
404
|
-
return arr[0] || null;
|
|
408
|
+
return new ZQueryCollection(Array.from(selector));
|
|
405
409
|
}
|
|
406
410
|
|
|
407
|
-
// HTML string → create elements,
|
|
411
|
+
// HTML string → create elements, wrap in collection
|
|
408
412
|
if (typeof selector === 'string' && selector.trim().startsWith('<')) {
|
|
409
413
|
const fragment = createFragment(selector);
|
|
410
|
-
|
|
411
|
-
return els[0] || null;
|
|
414
|
+
return new ZQueryCollection([...fragment.childNodes].filter(n => n.nodeType === 1));
|
|
412
415
|
}
|
|
413
416
|
|
|
414
|
-
// CSS selector string →
|
|
417
|
+
// CSS selector string → querySelectorAll (collection)
|
|
415
418
|
if (typeof selector === 'string') {
|
|
416
419
|
const root = context
|
|
417
420
|
? (typeof context === 'string' ? document.querySelector(context) : context)
|
|
418
421
|
: document;
|
|
419
|
-
return root.
|
|
422
|
+
return new ZQueryCollection([...root.querySelectorAll(selector)]);
|
|
420
423
|
}
|
|
421
424
|
|
|
422
|
-
return
|
|
425
|
+
return new ZQueryCollection([]);
|
|
423
426
|
}
|
|
424
427
|
|
|
425
428
|
|
|
@@ -466,21 +469,15 @@ export function queryAll(selector, context) {
|
|
|
466
469
|
// ---------------------------------------------------------------------------
|
|
467
470
|
query.id = (id) => document.getElementById(id);
|
|
468
471
|
query.class = (name) => document.querySelector(`.${name}`);
|
|
469
|
-
query.classes = (name) => Array.from(document.getElementsByClassName(name));
|
|
470
|
-
query.tag = (name) => Array.from(document.getElementsByTagName(name));
|
|
472
|
+
query.classes = (name) => new ZQueryCollection(Array.from(document.getElementsByClassName(name)));
|
|
473
|
+
query.tag = (name) => new ZQueryCollection(Array.from(document.getElementsByTagName(name)));
|
|
471
474
|
Object.defineProperty(query, 'name', {
|
|
472
|
-
value: (name) => Array.from(document.getElementsByName(name)),
|
|
475
|
+
value: (name) => new ZQueryCollection(Array.from(document.getElementsByName(name))),
|
|
473
476
|
writable: true, configurable: true
|
|
474
477
|
});
|
|
475
|
-
query.attr = (attr, value) => Array.from(
|
|
476
|
-
document.querySelectorAll(value !== undefined ? `[${attr}="${value}"]` : `[${attr}]`)
|
|
477
|
-
);
|
|
478
|
-
query.data = (key, value) => Array.from(
|
|
479
|
-
document.querySelectorAll(value !== undefined ? `[data-${key}="${value}"]` : `[data-${key}]`)
|
|
480
|
-
);
|
|
481
478
|
query.children = (parentId) => {
|
|
482
479
|
const p = document.getElementById(parentId);
|
|
483
|
-
return p ? Array.from(p.children) : [];
|
|
480
|
+
return new ZQueryCollection(p ? Array.from(p.children) : []);
|
|
484
481
|
};
|
|
485
482
|
|
|
486
483
|
// Create element shorthand
|