zero-query 0.4.9 → 0.5.2

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 CHANGED
@@ -162,7 +162,7 @@ my-app/
162
162
 
163
163
  ## CLI Bundler
164
164
 
165
- The CLI can compile your entire app — ES modules, the library, external templates, and assets — into a **single bundled file**.
165
+ The CLI compiles your entire app — ES modules, the library, external templates, and assets — into a **single production-ready bundle**. It outputs two builds in one step: a `server/` build for deploying to any web server, and a `local/` build that works straight from disk. No config, no flags — just point it at your app.
166
166
 
167
167
  ```bash
168
168
  # Auto-detect entry from any .html with a module script
@@ -170,6 +170,9 @@ npx zquery bundle
170
170
 
171
171
  # Or point to an app directory from anywhere
172
172
  npx zquery bundle my-app/
173
+
174
+ # Or pass a direct entry file (skips auto-detection)
175
+ npx zquery bundle my-app/scripts/main.js
173
176
  ```
174
177
 
175
178
  Output goes to `dist/` next to your `index.html`:
@@ -192,7 +195,8 @@ dist/
192
195
  | Flag | Short | Description |
193
196
  | --- | --- | --- |
194
197
  | `--out <path>` | `-o` | Custom output directory |
195
- | `--html <file>` | | Use a specific HTML file |
198
+ | `--index <file>` | `-i` | Index HTML file (default: auto-detected) |
199
+ | `--minimal` | `-m` | Only output HTML + bundled JS (skip static assets) |
196
200
 
197
201
  ### What the Bundler Does
198
202
 
@@ -261,8 +265,8 @@ location / {
261
265
  | CLI Command | Description |
262
266
  | --- | --- |
263
267
  | `zquery create [dir]` | Scaffold a new project (index.html, components, store, styles) |
264
- | `zquery dev [root]` | Dev server with live-reload &amp; error overlay (port 3100) |
265
- | `zquery bundle [dir]` | Bundle app into a single IIFE file |
268
+ | `zquery dev [root]` | Dev server with live-reload &amp; error overlay (port 3100). `--index` for custom HTML. |
269
+ | `zquery bundle [dir\|file]` | Bundle app into a single IIFE file. Accepts dir or direct entry file. |
266
270
  | `zquery build` | Build the zQuery library (`dist/zQuery.min.js`) |
267
271
  | `zquery --help` | Show CLI usage |
268
272
 
@@ -13,7 +13,7 @@ const fs = require('fs');
13
13
  const path = require('path');
14
14
  const crypto = require('crypto');
15
15
 
16
- const { args, option } = require('../args');
16
+ const { args, flag, option } = require('../args');
17
17
  const { minify, sizeKB } = require('../utils');
18
18
  const buildLibrary = require('./build');
19
19
 
@@ -104,6 +104,8 @@ function rewriteResourceUrls(code, filePath, projectRoot) {
104
104
  (match, prefix, quote, url) => {
105
105
  if (url.startsWith('/') || url.includes('://')) return match;
106
106
  const abs = path.resolve(fileDir, url);
107
+ // Only rewrite if the file actually exists — avoids mangling code examples
108
+ if (!fs.existsSync(abs)) return match;
107
109
  const rel = path.relative(projectRoot, abs).replace(/\\/g, '/');
108
110
  return `${prefix}${quote}${rel}${quote}`;
109
111
  }
@@ -379,18 +381,68 @@ function rewriteHtml(projectRoot, htmlRelPath, bundleFile, includeLib, bundledFi
379
381
  console.log(` ✓ Copied ${copiedCount} asset(s) into both dist dirs`);
380
382
  }
381
383
 
384
+ // ---------------------------------------------------------------------------
385
+ // Static asset copying
386
+ // ---------------------------------------------------------------------------
387
+
388
+ /**
389
+ * Copy the entire app directory into both dist/server and dist/local,
390
+ * skipping only build outputs and tooling dirs. This ensures all static
391
+ * assets (icons/, images/, fonts/, styles/, scripts/, manifests, etc.)
392
+ * are available in the built output without maintaining a fragile whitelist.
393
+ */
394
+ function copyStaticAssets(appRoot, serverDir, localDir, bundledFiles) {
395
+ const SKIP_DIRS = new Set(['dist', 'node_modules', '.git', '.vscode', 'scripts']);
396
+
397
+ let copiedCount = 0;
398
+
399
+ function copyEntry(srcPath, relPath) {
400
+ const stat = fs.statSync(srcPath);
401
+ if (stat.isDirectory()) {
402
+ if (SKIP_DIRS.has(path.basename(srcPath))) return;
403
+ for (const child of fs.readdirSync(srcPath)) {
404
+ copyEntry(path.join(srcPath, child), path.join(relPath, child));
405
+ }
406
+ } else {
407
+ if (bundledFiles && bundledFiles.has(path.resolve(srcPath))) return;
408
+ for (const distDir of [serverDir, localDir]) {
409
+ const dest = path.join(distDir, relPath);
410
+ if (fs.existsSync(dest)) continue; // already copied by rewriteHtml
411
+ const dir = path.dirname(dest);
412
+ if (!fs.existsSync(dir)) fs.mkdirSync(dir, { recursive: true });
413
+ fs.copyFileSync(srcPath, dest);
414
+ }
415
+ copiedCount++;
416
+ }
417
+ }
418
+
419
+ for (const entry of fs.readdirSync(appRoot, { withFileTypes: true })) {
420
+ if (entry.isDirectory()) {
421
+ if (SKIP_DIRS.has(entry.name)) continue;
422
+ copyEntry(path.join(appRoot, entry.name), entry.name);
423
+ } else {
424
+ copyEntry(path.join(appRoot, entry.name), entry.name);
425
+ }
426
+ }
427
+
428
+ if (copiedCount > 0) {
429
+ console.log(` \u2713 Copied ${copiedCount} additional static asset(s) into both dist dirs`);
430
+ }
431
+ }
432
+
382
433
  // ---------------------------------------------------------------------------
383
434
  // Main bundleApp function
384
435
  // ---------------------------------------------------------------------------
385
436
 
386
437
  function bundleApp() {
387
438
  const projectRoot = process.cwd();
439
+ const minimal = flag('minimal', 'm');
388
440
 
389
- // Entry point — accepts a directory (auto-detects entry inside it) or a file
441
+ // Entry point — positional arg (directory or file) or auto-detection
390
442
  let entry = null;
391
443
  let targetDir = null;
392
444
  for (let i = 1; i < args.length; i++) {
393
- if (!args[i].startsWith('-') && args[i - 1] !== '-o' && args[i - 1] !== '--out' && args[i - 1] !== '--html') {
445
+ if (!args[i].startsWith('-') && args[i - 1] !== '-o' && args[i - 1] !== '--out' && args[i - 1] !== '--index') {
394
446
  const resolved = path.resolve(projectRoot, args[i]);
395
447
  if (fs.existsSync(resolved) && fs.statSync(resolved).isDirectory()) {
396
448
  targetDir = resolved;
@@ -405,16 +457,19 @@ function bundleApp() {
405
457
 
406
458
  if (!entry || !fs.existsSync(entry)) {
407
459
  console.error(`\n \u2717 Could not find entry file.`);
408
- console.error(` Provide an app directory: zquery bundle my-app/\n`);
460
+ console.error(` Provide an app directory: zquery bundle my-app/`);
461
+ console.error(` Or pass a direct entry file: zquery bundle my-app/scripts/main.js\n`);
409
462
  process.exit(1);
410
463
  }
411
464
 
412
465
  const outPath = option('out', 'o', null);
413
466
 
414
- // Auto-detect index.html
415
- let htmlFile = option('html', null, null);
467
+ // Auto-detect HTML file
468
+ let htmlFile = option('index', 'i', null);
416
469
  let htmlAbs = htmlFile ? path.resolve(projectRoot, htmlFile) : null;
417
470
  if (!htmlFile) {
471
+ // Strategy: first look for index.html walking up from entry, then
472
+ // scan for any .html that references the entry via a module script tag.
418
473
  const htmlCandidates = [];
419
474
  let entryDir = path.dirname(entry);
420
475
  while (entryDir.length >= projectRoot.length) {
@@ -432,6 +487,47 @@ function bundleApp() {
432
487
  break;
433
488
  }
434
489
  }
490
+
491
+ // If no index.html found, scan for any .html file that references
492
+ // the entry point (supports home.html, app.html, etc.)
493
+ if (!htmlAbs) {
494
+ const searchRoot = targetDir || projectRoot;
495
+ const htmlScan = [];
496
+ for (const e of fs.readdirSync(searchRoot, { withFileTypes: true })) {
497
+ if (e.isFile() && e.name.endsWith('.html')) {
498
+ htmlScan.push(path.join(searchRoot, e.name));
499
+ } else if (e.isDirectory() && !e.name.startsWith('.') && e.name !== 'node_modules' && e.name !== 'dist') {
500
+ try {
501
+ for (const child of fs.readdirSync(path.join(searchRoot, e.name), { withFileTypes: true })) {
502
+ if (child.isFile() && child.name.endsWith('.html')) {
503
+ htmlScan.push(path.join(searchRoot, e.name, child.name));
504
+ }
505
+ }
506
+ } catch { /* skip */ }
507
+ }
508
+ }
509
+ // Prefer the HTML file that references our entry via a module script
510
+ const moduleScriptRe = /<script[^>]+type\s*=\s*["']module["'][^>]+src\s*=\s*["']([^"']+)["']/g;
511
+ for (const hp of htmlScan) {
512
+ const content = fs.readFileSync(hp, 'utf-8');
513
+ let m;
514
+ moduleScriptRe.lastIndex = 0;
515
+ while ((m = moduleScriptRe.exec(content)) !== null) {
516
+ const resolved = path.resolve(path.dirname(hp), m[1]);
517
+ if (resolved === path.resolve(entry)) {
518
+ htmlAbs = hp;
519
+ htmlFile = path.relative(projectRoot, hp);
520
+ break;
521
+ }
522
+ }
523
+ if (htmlAbs) break;
524
+ }
525
+ // Last resort: use the first .html found
526
+ if (!htmlAbs && htmlScan.length > 0) {
527
+ htmlAbs = htmlScan[0];
528
+ htmlFile = path.relative(projectRoot, htmlScan[0]);
529
+ }
530
+ }
435
531
  }
436
532
 
437
533
  // Output directory
@@ -459,7 +555,8 @@ function bundleApp() {
459
555
  console.log(` Entry: ${entryRel}`);
460
556
  console.log(` Output: ${path.relative(projectRoot, baseDistDir)}/server/ & local/`);
461
557
  console.log(` Library: embedded`);
462
- console.log(` HTML: ${htmlFile || 'not found (no index.html detected)'}`);
558
+ console.log(` HTML: ${htmlFile || 'not found (no HTML detected)'}`);
559
+ if (minimal) console.log(` Mode: minimal (HTML + bundle only)`);
463
560
  console.log('');
464
561
 
465
562
  // ------ doBuild (inlined) ------
@@ -571,10 +668,16 @@ function bundleApp() {
571
668
  console.log(`\n ✓ ${bundleBase} (${sizeKB(fs.readFileSync(bundleFile))} KB)`);
572
669
  console.log(` ✓ ${minBase} (${sizeKB(fs.readFileSync(minFile))} KB)`);
573
670
 
574
- // Rewrite HTML
671
+ // Rewrite HTML (use full bundle — minified version mangles template literal whitespace)
672
+ const bundledFileSet = new Set(files);
575
673
  if (htmlFile) {
576
- const bundledFileSet = new Set(files);
577
- rewriteHtml(projectRoot, htmlFile, minFile, true, bundledFileSet, serverDir, localDir);
674
+ rewriteHtml(projectRoot, htmlFile, bundleFile, true, bundledFileSet, serverDir, localDir);
675
+ }
676
+
677
+ // Copy static asset directories (icons/, images/, fonts/, etc.)
678
+ if (!minimal) {
679
+ const appRoot = htmlAbs ? path.dirname(htmlAbs) : (targetDir || projectRoot);
680
+ copyStaticAssets(appRoot, serverDir, localDir, bundledFileSet);
578
681
  }
579
682
 
580
683
  const elapsed = Date.now() - start;
@@ -321,10 +321,13 @@ function devServer() {
321
321
 
322
322
  const { createApp, static: serveStatic } = zeroHttp;
323
323
 
324
+ // Custom HTML entry file (default: index.html)
325
+ const htmlEntry = option('index', 'i', 'index.html');
326
+
324
327
  // Determine the project root to serve
325
328
  let root = null;
326
329
  for (let i = 1; i < args.length; i++) {
327
- if (!args[i].startsWith('-') && args[i - 1] !== '-p' && args[i - 1] !== '--port') {
330
+ if (!args[i].startsWith('-') && args[i - 1] !== '-p' && args[i - 1] !== '--port' && args[i - 1] !== '--index') {
328
331
  root = path.resolve(process.cwd(), args[i]);
329
332
  break;
330
333
  }
@@ -336,7 +339,7 @@ function devServer() {
336
339
  path.join(process.cwd(), 'src'),
337
340
  ];
338
341
  for (const c of candidates) {
339
- if (fs.existsSync(path.join(c, 'index.html'))) { root = c; break; }
342
+ if (fs.existsSync(path.join(c, htmlEntry))) { root = c; break; }
340
343
  }
341
344
  if (!root) root = process.cwd();
342
345
  }
@@ -389,9 +392,9 @@ function devServer() {
389
392
  res.status(404).send('Not Found');
390
393
  return;
391
394
  }
392
- const indexPath = path.join(root, 'index.html');
395
+ const indexPath = path.join(root, htmlEntry);
393
396
  if (!fs.existsSync(indexPath)) {
394
- res.status(404).send('index.html not found');
397
+ res.status(404).send(`${htmlEntry} not found`);
395
398
  return;
396
399
  }
397
400
  let html = fs.readFileSync(indexPath, 'utf-8');
@@ -495,6 +498,7 @@ function devServer() {
495
498
  console.log(` \x1b[2m${'-'.repeat(40)}\x1b[0m`);
496
499
  console.log(` Local: \x1b[36mhttp://localhost:${PORT}/\x1b[0m`);
497
500
  console.log(` Root: ${path.relative(process.cwd(), root) || '.'}`);
501
+ if (htmlEntry !== 'index.html') console.log(` HTML: \x1b[36m${htmlEntry}\x1b[0m`);
498
502
  console.log(` Live Reload: \x1b[32menabled\x1b[0m (SSE)`);
499
503
  console.log(` Overlay: \x1b[32menabled\x1b[0m (syntax + runtime errors)`);
500
504
  if (noIntercept) console.log(` Intercept: \x1b[33mdisabled\x1b[0m (--no-intercept)`);
package/cli/help.js CHANGED
@@ -12,6 +12,7 @@ function showHelp() {
12
12
 
13
13
  dev [root] Start a dev server with live-reload
14
14
  --port, -p <number> Port number (default: 3100)
15
+ --index, -i <file> Index HTML file (default: index.html)
15
16
  --no-intercept Disable auto-resolution of zquery.min.js
16
17
  (serve the on-disk vendor copy instead)
17
18
 
@@ -20,9 +21,10 @@ function showHelp() {
20
21
  overlay in the browser. Runtime errors and
21
22
  unhandled rejections are also captured.
22
23
 
23
- bundle [dir] Bundle app ES modules into a single file
24
- --out, -o <path> Output directory (default: dist/ next to index.html)
25
- --html <file> Use a specific HTML file (default: auto-detected)
24
+ bundle [dir|file] Bundle app ES modules into a single file
25
+ --out, -o <path> Output directory (default: dist/ next to HTML file)
26
+ --index, -i <file> Index HTML file (default: auto-detected)
27
+ --minimal, -m Only output HTML + bundled JS (skip static assets)
26
28
 
27
29
  build Build the zQuery library \u2192 dist/ --watch, -w Watch src/ and rebuild on changes (must be run from the project root where src/ lives)
28
30
 
@@ -34,9 +36,10 @@ function showHelp() {
34
36
  2. Within HTML: module script pointing to app.js, else first module script
35
37
  3. JS scan: $.router( first (entry point), then $.mount( / $.store(
36
38
  4. Convention fallbacks (scripts/app.js, app.js, etc.)
37
- \u2022 zquery.min.js is always embedded (auto-built from source if not found)
38
- \u2022 index.html is rewritten for both server and local (file://) use
39
- \u2022 Output goes to dist/server/ and dist/local/ next to the detected index.html
39
+ Passing a directory auto-detects the entry; passing a file uses it directly
40
+ zquery.min.js is always embedded (auto-built from source if not found)
41
+ HTML file is auto-detected (any .html, not just index.html)
42
+ • Output goes to dist/server/ and dist/local/ next to the detected HTML file
40
43
 
41
44
  OUTPUT
42
45
 
@@ -79,9 +82,18 @@ function showHelp() {
79
82
  # Bundle an app from the project root
80
83
  zquery bundle my-app/
81
84
 
85
+ # Pass a direct entry file (skip auto-detection)
86
+ zquery bundle my-app/scripts/main.js
87
+
82
88
  # Custom output directory
83
89
  zquery bundle my-app/ -o build/
84
90
 
91
+ # Minimal build (only HTML + bundled JS, no static assets)
92
+ zquery bundle my-app/ --minimal
93
+
94
+ # Dev server with a custom index page
95
+ zquery dev my-app/ --index home.html
96
+
85
97
  The bundler walks the ES module import graph starting from the entry
86
98
  file, topologically sorts dependencies, strips import/export syntax,
87
99
  and concatenates everything into a single IIFE with content-hashed
Binary file
package/dist/zquery.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zQuery (zeroQuery) v0.4.9
2
+ * zQuery (zeroQuery) v0.5.2
3
3
  * Lightweight Frontend Library
4
4
  * https://github.com/tonywied17/zero-query
5
5
  * (c) 2026 Anthony Wiedman — MIT License
@@ -605,6 +605,16 @@ query.id = (id) => document.getElementById(id);
605
605
  query.class = (name) => document.querySelector(`.${name}`);
606
606
  query.classes = (name) => Array.from(document.getElementsByClassName(name));
607
607
  query.tag = (name) => Array.from(document.getElementsByTagName(name));
608
+ Object.defineProperty(query, 'name', {
609
+ value: (name) => Array.from(document.getElementsByName(name)),
610
+ writable: true, configurable: true
611
+ });
612
+ query.attr = (attr, value) => Array.from(
613
+ document.querySelectorAll(value !== undefined ? `[${attr}="${value}"]` : `[${attr}]`)
614
+ );
615
+ query.data = (key, value) => Array.from(
616
+ document.querySelectorAll(value !== undefined ? `[data-${key}="${value}"]` : `[data-${key}]`)
617
+ );
608
618
  query.children = (parentId) => {
609
619
  const p = document.getElementById(parentId);
610
620
  return p ? Array.from(p.children) : [];
@@ -2862,11 +2872,16 @@ function $(selector, context) {
2862
2872
  }
2863
2873
 
2864
2874
 
2865
- // --- Quick refs ------------------------------------------------------------
2875
+ // --- Quick refs (DOM selectors) --------------------------------------------
2866
2876
  $.id = query.id;
2867
2877
  $.class = query.class;
2868
2878
  $.classes = query.classes;
2869
2879
  $.tag = query.tag;
2880
+ Object.defineProperty($, 'name', {
2881
+ value: query.name, writable: true, configurable: true
2882
+ });
2883
+ $.attr = query.attr;
2884
+ $.data = query.data;
2870
2885
  $.children = query.children;
2871
2886
 
2872
2887
  // --- Collection selector ---------------------------------------------------
@@ -2895,6 +2910,7 @@ $.fn = query.fn;
2895
2910
 
2896
2911
  // --- Reactive primitives ---------------------------------------------------
2897
2912
  $.reactive = reactive;
2913
+ $.Signal = Signal;
2898
2914
  $.signal = signal;
2899
2915
  $.computed = computed;
2900
2916
  $.effect = effect;
@@ -2946,7 +2962,7 @@ $.session = session;
2946
2962
  $.bus = bus;
2947
2963
 
2948
2964
  // --- Meta ------------------------------------------------------------------
2949
- $.version = '0.4.9';
2965
+ $.version = '0.5.2';
2950
2966
  $.meta = {}; // populated at build time by CLI bundler
2951
2967
 
2952
2968
  $.noConflict = () => {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * zQuery (zeroQuery) v0.4.9
2
+ * zQuery (zeroQuery) v0.5.2
3
3
  * Lightweight Frontend Library
4
4
  * https://github.com/tonywied17/zero-query
5
5
  * (c) 2026 Anthony Wiedman — MIT License
@@ -7,11 +7,11 @@
7
7
  (function(global) {
8
8
  'use strict';
9
9
  function reactive(target, onChange, _path = '') { if (typeof target !== 'object' || target === null) return target; const proxyCache = new WeakMap(); const handler = { get(obj, key) { if (key === '__isReactive') return true; if (key === '__raw') return obj; const value = obj[key]; if (typeof value === 'object' && value !== null) { if (proxyCache.has(value)) return proxyCache.get(value); const childProxy = new Proxy(value, handler); proxyCache.set(value, childProxy); return childProxy; } return value; }, set(obj, key, value) { const old = obj[key]; if (old === value) return true; obj[key] = value; onChange(key, value, old); return true; }, deleteProperty(obj, key) { const old = obj[key]; delete obj[key]; onChange(key, undefined, old); return true; } }; return new Proxy(target, handler);
10
- class ZQueryCollection { constructor(elements) { this.elements = Array.isArray(elements) ? elements : [elements]; this.length = this.elements.length; this.elements.forEach((el, i) => { this[i] = el; }); } each(fn) { this.elements.forEach((el, i) => fn.call(el, i, el)); return this; } map(fn) { return this.elements.map((el, i) => fn.call(el, i, el)); } first() { return this.elements[0] || null; } last() { return this.elements[this.length - 1] || null; } eq(i) { return new ZQueryCollection(this.elements[i] ? [this.elements[i]] : []); } toArray(){ return [...this.elements]; } [Symbol.iterator]() { return this.elements[Symbol.iterator](); } find(selector) { const found = []; this.elements.forEach(el => found.push(...el.querySelectorAll(selector))); return new ZQueryCollection(found); } parent() { const parents = [...new Set(this.elements.map(el => el.parentElement).filter(Boolean))]; return new ZQueryCollection(parents); } closest(selector) { return new ZQueryCollection( this.elements.map(el => el.closest(selector)).filter(Boolean) ); } children(selector) { const kids = []; this.elements.forEach(el => { kids.push(...(selector ? el.querySelectorAll(`:scope > ${selector}`) : el.children)); }); return new ZQueryCollection([...kids]); } siblings() { const sibs = []; this.elements.forEach(el => { sibs.push(...[...el.parentElement.children].filter(c => c !== el)); }); return new ZQueryCollection(sibs); } next() { return new ZQueryCollection(this.elements.map(el => el.nextElementSibling).filter(Boolean)); } prev() { return new ZQueryCollection(this.elements.map(el => el.previousElementSibling).filter(Boolean)); } filter(selector) { if (typeof selector === 'function') { return new ZQueryCollection(this.elements.filter(selector)); } return new ZQueryCollection(this.elements.filter(el => el.matches(selector))); } not(selector) { if (typeof selector === 'function') { return new ZQueryCollection(this.elements.filter((el, i) => !selector.call(el, i, el))); } return new ZQueryCollection(this.elements.filter(el => !el.matches(selector))); } has(selector) { return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector))); } addClass(...names) { const classes = names.flatMap(n => n.split(/\s+/)); return this.each((_, el) => el.classList.add(...classes)); } removeClass(...names) { const classes = names.flatMap(n => n.split(/\s+/)); return this.each((_, el) => el.classList.remove(...classes)); } toggleClass(name, force) { return this.each((_, el) => el.classList.toggle(name, force)); } hasClass(name) { return this.first()?.classList.contains(name) || false; } attr(name, value) { if (value === undefined) return this.first()?.getAttribute(name); return this.each((_, el) => el.setAttribute(name, value)); } removeAttr(name) { return this.each((_, el) => el.removeAttribute(name)); } prop(name, value) { if (value === undefined) return this.first()?.[name]; return this.each((_, el) => { el[name] = value; }); } data(key, value) { if (value === undefined) { if (key === undefined) return this.first()?.dataset; const raw = this.first()?.dataset[key]; try { return JSON.parse(raw); } catch { return raw; } } return this.each((_, el) => { el.dataset[key] = typeof value === 'object' ? JSON.stringify(value) : value; }); } css(props) { if (typeof props === 'string') { return getComputedStyle(this.first())[props]; } return this.each((_, el) => Object.assign(el.style, props)); } width() { return this.first()?.getBoundingClientRect().width; } height() { return this.first()?.getBoundingClientRect().height; } offset() { const r = this.first()?.getBoundingClientRect(); return r ? { top: r.top + window.scrollY, left: r.left + window.scrollX, width: r.width, height: r.height } : null; } position() { const el = this.first(); return el ? { top: el.offsetTop, left: el.offsetLeft } : null; } html(content) { if (content === undefined) return this.first()?.innerHTML; return this.each((_, el) => { el.innerHTML = content; }); } text(content) { if (content === undefined) return this.first()?.textContent; return this.each((_, el) => { el.textContent = content; }); } val(value) { if (value === undefined) return this.first()?.value; return this.each((_, el) => { el.value = value; }); } append(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('beforeend', content); else if (content instanceof ZQueryCollection) content.each((__, c) => el.appendChild(c)); else if (content instanceof Node) el.appendChild(content); }); } prepend(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('afterbegin', content); else if (content instanceof Node) el.insertBefore(content, el.firstChild); }); } after(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('afterend', content); else if (content instanceof Node) el.parentNode.insertBefore(content, el.nextSibling); }); } before(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('beforebegin', content); else if (content instanceof Node) el.parentNode.insertBefore(content, el); }); } wrap(wrapper) { return this.each((_, el) => { const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true); el.parentNode.insertBefore(w, el); w.appendChild(el); }); } remove() { return this.each((_, el) => el.remove()); } empty() { return this.each((_, el) => { el.innerHTML = ''; }); } clone(deep = true) { return new ZQueryCollection(this.elements.map(el => el.cloneNode(deep))); } replaceWith(content) { return this.each((_, el) => { if (typeof content === 'string') { el.insertAdjacentHTML('afterend', content); el.remove(); } else if (content instanceof Node) { el.parentNode.replaceChild(content, el); } }); } show(display = '') { return this.each((_, el) => { el.style.display = display; }); } hide() { return this.each((_, el) => { el.style.display = 'none'; }); } toggle(display = '') { return this.each((_, el) => { el.style.display = (el.style.display === 'none' || getComputedStyle(el).display === 'none') ? display : 'none'; }); } on(event, selectorOrHandler, handler) { const events = event.split(/\s+/); return this.each((_, el) => { events.forEach(evt => { if (typeof selectorOrHandler === 'function') { el.addEventListener(evt, selectorOrHandler); } else { el.addEventListener(evt, (e) => { const target = e.target.closest(selectorOrHandler); if (target && el.contains(target)) handler.call(target, e); }); } }); }); } off(event, handler) { const events = event.split(/\s+/); return this.each((_, el) => { events.forEach(evt => el.removeEventListener(evt, handler)); }); } one(event, handler) { return this.each((_, el) => { el.addEventListener(event, handler, { once: true }); }); } trigger(event, detail) { return this.each((_, el) => { el.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true })); }); } click(fn) { return fn ? this.on('click', fn) : this.trigger('click'); } submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); } focus() { this.first()?.focus(); return this; } blur() { this.first()?.blur(); return this; } animate(props, duration = 300, easing = 'ease') { return new Promise(resolve => { const count = { done: 0 }; this.each((_, el) => { el.style.transition = `all ${duration}ms ${easing}`; requestAnimationFrame(() => { Object.assign(el.style, props); const onEnd = () => { el.removeEventListener('transitionend', onEnd); el.style.transition = ''; if (++count.done >= this.length) resolve(this); }; el.addEventListener('transitionend', onEnd); }); }); setTimeout(() => resolve(this), duration + 50); }); } fadeIn(duration = 300) { return this.css({ opacity: '0', display: '' }).animate({ opacity: '1' }, duration); } fadeOut(duration = 300) { return this.animate({ opacity: '0' }, duration).then(col => col.hide()); } slideToggle(duration = 300) { return this.each((_, el) => { if (el.style.display === 'none' || getComputedStyle(el).display === 'none') { el.style.display = ''; el.style.overflow = 'hidden'; const h = el.scrollHeight + 'px'; el.style.maxHeight = '0'; el.style.transition = `max-height ${duration}ms ease`; requestAnimationFrame(() => { el.style.maxHeight = h; }); setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration); } else { el.style.overflow = 'hidden'; el.style.maxHeight = el.scrollHeight + 'px'; el.style.transition = `max-height ${duration}ms ease`; requestAnimationFrame(() => { el.style.maxHeight = '0'; }); setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration); } }); } serialize() { const form = this.first(); if (!form || form.tagName !== 'FORM') return ''; return new URLSearchParams(new FormData(form)).toString(); } serializeObject() { const form = this.first(); if (!form || form.tagName !== 'FORM') return {}; const obj = {}; new FormData(form).forEach((v, k) => { if (obj[k] !== undefined) { if (!Array.isArray(obj[k])) obj[k] = [obj[k]]; obj[k].push(v); } else { obj[k] = v; } }); return obj; }
10
+ class ZQueryCollection { constructor(elements) { this.elements = Array.isArray(elements) ? elements : [elements]; this.length = this.elements.length; this.elements.forEach((el, i) => { this[i] = el; }); } each(fn) { this.elements.forEach((el, i) => fn.call(el, i, el)); return this; } map(fn) { return this.elements.map((el, i) => fn.call(el, i, el)); } first() { return this.elements[0] || null; } last() { return this.elements[this.length - 1] || null; } eq(i) { return new ZQueryCollection(this.elements[i] ? [this.elements[i]] : []); } toArray(){ return [...this.elements]; } [Symbol.iterator]() { return this.elements[Symbol.iterator](); } find(selector) { const found = []; this.elements.forEach(el => found.push(...el.querySelectorAll(selector))); return new ZQueryCollection(found); } parent() { const parents = [...new Set(this.elements.map(el => el.parentElement).filter(Boolean))]; return new ZQueryCollection(parents); } closest(selector) { return new ZQueryCollection( this.elements.map(el => el.closest(selector)).filter(Boolean) ); } children(selector) { const kids = []; this.elements.forEach(el => { kids.push(...(selector ? el.querySelectorAll(`:scope > ${selector}`) : el.children)); }); return new ZQueryCollection([...kids]); } siblings() { const sibs = []; this.elements.forEach(el => { sibs.push(...[...el.parentElement.children].filter(c => c !== el)); }); return new ZQueryCollection(sibs); } next() { return new ZQueryCollection(this.elements.map(el => el.nextElementSibling).filter(Boolean)); } prev() { return new ZQueryCollection(this.elements.map(el => el.previousElementSibling).filter(Boolean)); } filter(selector) { if (typeof selector === 'function') { return new ZQueryCollection(this.elements.filter(selector)); } return new ZQueryCollection(this.elements.filter(el => el.matches(selector))); } not(selector) { if (typeof selector === 'function') { return new ZQueryCollection(this.elements.filter((el, i) => !selector.call(el, i, el))); } return new ZQueryCollection(this.elements.filter(el => !el.matches(selector))); } has(selector) { return new ZQueryCollection(this.elements.filter(el => el.querySelector(selector))); } addClass(...names) { const classes = names.flatMap(n => n.split(/\s+/)); return this.each((_, el) => el.classList.add(...classes)); } removeClass(...names) { const classes = names.flatMap(n => n.split(/\s+/)); return this.each((_, el) => el.classList.remove(...classes)); } toggleClass(name, force) { return this.each((_, el) => el.classList.toggle(name, force)); } hasClass(name) { return this.first()?.classList.contains(name) || false; } attr(name, value) { if (value === undefined) return this.first()?.getAttribute(name); return this.each((_, el) => el.setAttribute(name, value)); } removeAttr(name) { return this.each((_, el) => el.removeAttribute(name)); } prop(name, value) { if (value === undefined) return this.first()?.[name]; return this.each((_, el) => { el[name] = value; }); } data(key, value) { if (value === undefined) { if (key === undefined) return this.first()?.dataset; const raw = this.first()?.dataset[key]; try { return JSON.parse(raw); } catch { return raw; } } return this.each((_, el) => { el.dataset[key] = typeof value === 'object' ? JSON.stringify(value) : value; }); } css(props) { if (typeof props === 'string') { return getComputedStyle(this.first())[props]; } return this.each((_, el) => Object.assign(el.style, props)); } width() { return this.first()?.getBoundingClientRect().width; } height() { return this.first()?.getBoundingClientRect().height; } offset() { const r = this.first()?.getBoundingClientRect(); return r ? { top: r.top + window.scrollY, left: r.left + window.scrollX, width: r.width, height: r.height } : null; } position() { const el = this.first(); return el ? { top: el.offsetTop, left: el.offsetLeft } : null; } html(content) { if (content === undefined) return this.first()?.innerHTML; return this.each((_, el) => { el.innerHTML = content; }); } text(content) { if (content === undefined) return this.first()?.textContent; return this.each((_, el) => { el.textContent = content; }); } val(value) { if (value === undefined) return this.first()?.value; return this.each((_, el) => { el.value = value; }); } append(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('beforeend', content); else if (content instanceof ZQueryCollection) content.each((__, c) => el.appendChild(c)); else if (content instanceof Node) el.appendChild(content); }); } prepend(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('afterbegin', content); else if (content instanceof Node) el.insertBefore(content, el.firstChild); }); } after(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('afterend', content); else if (content instanceof Node) el.parentNode.insertBefore(content, el.nextSibling); }); } before(content) { return this.each((_, el) => { if (typeof content === 'string') el.insertAdjacentHTML('beforebegin', content); else if (content instanceof Node) el.parentNode.insertBefore(content, el); }); } wrap(wrapper) { return this.each((_, el) => { const w = typeof wrapper === 'string' ? createFragment(wrapper).firstElementChild : wrapper.cloneNode(true); el.parentNode.insertBefore(w, el); w.appendChild(el); }); } remove() { return this.each((_, el) => el.remove()); } empty() { return this.each((_, el) => { el.innerHTML = ''; }); } clone(deep = true) { return new ZQueryCollection(this.elements.map(el => el.cloneNode(deep))); } replaceWith(content) { return this.each((_, el) => { if (typeof content === 'string') { el.insertAdjacentHTML('afterend', content); el.remove(); } else if (content instanceof Node) { el.parentNode.replaceChild(content, el); } }); } show(display = '') { return this.each((_, el) => { el.style.display = display; }); } hide() { return this.each((_, el) => { el.style.display = 'none'; }); } toggle(display = '') { return this.each((_, el) => { el.style.display = (el.style.display === 'none' || getComputedStyle(el).display === 'none') ? display : 'none'; }); } on(event, selectorOrHandler, handler) { const events = event.split(/\s+/); return this.each((_, el) => { events.forEach(evt => { if (typeof selectorOrHandler === 'function') { el.addEventListener(evt, selectorOrHandler); } else { el.addEventListener(evt, (e) => { const target = e.target.closest(selectorOrHandler); if (target && el.contains(target)) handler.call(target, e); }); } }); }); } off(event, handler) { const events = event.split(/\s+/); return this.each((_, el) => { events.forEach(evt => el.removeEventListener(evt, handler)); }); } one(event, handler) { return this.each((_, el) => { el.addEventListener(event, handler, { once: true }); }); } trigger(event, detail) { return this.each((_, el) => { el.dispatchEvent(new CustomEvent(event, { detail, bubbles: true, cancelable: true })); }); } click(fn) { return fn ? this.on('click', fn) : this.trigger('click'); } submit(fn) { return fn ? this.on('submit', fn) : this.trigger('submit'); } focus() { this.first()?.focus(); return this; } blur() { this.first()?.blur(); return this; } animate(props, duration = 300, easing = 'ease') { return new Promise(resolve => { const count = { done: 0 }; this.each((_, el) => { el.style.transition = `all ${duration}ms ${easing}`; requestAnimationFrame(() => { Object.assign(el.style, props); const onEnd = () => { el.removeEventListener('transitionend', onEnd); el.style.transition = ''; if (++count.done >= this.length) resolve(this); }; el.addEventListener('transitionend', onEnd); }); }); setTimeout(() => resolve(this), duration + 50); }); } fadeIn(duration = 300) { return this.css({ opacity: '0', display: '' }).animate({ opacity: '1' }, duration); } fadeOut(duration = 300) { return this.animate({ opacity: '0' }, duration).then(col => col.hide()); } slideToggle(duration = 300) { return this.each((_, el) => { if (el.style.display === 'none' || getComputedStyle(el).display === 'none') { el.style.display = ''; el.style.overflow = 'hidden'; const h = el.scrollHeight + 'px'; el.style.maxHeight = '0'; el.style.transition = `max-height ${duration}ms ease`; requestAnimationFrame(() => { el.style.maxHeight = h; }); setTimeout(() => { el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration); } else { el.style.overflow = 'hidden'; el.style.maxHeight = el.scrollHeight + 'px'; el.style.transition = `max-height ${duration}ms ease`; requestAnimationFrame(() => { el.style.maxHeight = '0'; }); setTimeout(() => { el.style.display = 'none'; el.style.maxHeight = ''; el.style.overflow = ''; el.style.transition = ''; }, duration); } }); } serialize() { const form = this.first(); if (!form || form.tagName !== 'FORM') return ''; return new URLSearchParams(new FormData(form)).toString(); } serializeObject() { const form = this.first(); if (!form || form.tagName !== 'FORM') return {}; const obj = {}; new FormData(form).forEach((v, k) => { if (obj[k] !== undefined) { if (!Array.isArray(obj[k])) obj[k] = [obj[k]]; obj[k].push(v); } else { obj[k] = v; } }); return obj; }
11
11
  const _registry = new Map(); const _instances = new Map(); const _resourceCache = new Map(); let _uid = 0;
12
12
  class Router { constructor(config = {}) { this._el = null; const isFile = typeof location !== 'undefined' && location.protocol === 'file:'; this._mode = isFile ? 'hash' : (config.mode || 'history'); let rawBase = config.base; if (rawBase == null) { rawBase = (typeof window !== 'undefined' && window.__ZQ_BASE) || ''; if (!rawBase && typeof document !== 'undefined') { const baseEl = document.querySelector('base'); if (baseEl) { try { rawBase = new URL(baseEl.href).pathname; } catch { rawBase = baseEl.getAttribute('href') || ''; } if (rawBase === '/') rawBase = ''; } } } this._base = String(rawBase).replace(/\/+$/, ''); if (this._base && !this._base.startsWith('/')) this._base = '/' + this._base; this._routes = []; this._fallback = config.fallback || null; this._current = null; this._guards = { before: [], after: [] }; this._listeners = new Set(); this._instance = null; this._resolving = false; if (config.el) { this._el = typeof config.el === 'string' ? document.querySelector(config.el) : config.el; } if (config.routes) { config.routes.forEach(r => this.add(r)); } if (this._mode === 'hash') { window.addEventListener('hashchange', () => this._resolve()); } else { window.addEventListener('popstate', () => this._resolve()); } document.addEventListener('click', (e) => { if (e.metaKey || e.ctrlKey || e.shiftKey || e.altKey) return; const link = e.target.closest('[z-link]'); if (!link) return; if (link.getAttribute('target') === '_blank') return; e.preventDefault(); this.navigate(link.getAttribute('z-link')); }); if (this._el) { queueMicrotask(() => this._resolve()); } } add(route) { const keys = []; const pattern = route.path .replace(/:(\w+)/g, (_, key) => { keys.push(key); return '([^/]+)'; }) .replace(/\*/g, '(.*)'); const regex = new RegExp(`^${pattern}$`); this._routes.push({ ...route, _regex: regex, _keys: keys }); if (route.fallback) { const fbKeys = []; const fbPattern = route.fallback .replace(/:(\w+)/g, (_, key) => { fbKeys.push(key); return '([^/]+)'; }) .replace(/\*/g, '(.*)'); const fbRegex = new RegExp(`^${fbPattern}$`); this._routes.push({ ...route, path: route.fallback, _regex: fbRegex, _keys: fbKeys }); } return this; } remove(path) { this._routes = this._routes.filter(r => r.path !== path); return this; } navigate(path, options = {}) { const [cleanPath, fragment] = (path || '').split('#'); let normalized = this._normalizePath(cleanPath); const hash = fragment ? '#' + fragment : ''; if (this._mode === 'hash') { if (fragment) window.__zqScrollTarget = fragment; window.location.hash = '#' + normalized; } else { window.history.pushState(options.state || {}, '', this._base + normalized + hash); this._resolve(); } return this; } replace(path, options = {}) { const [cleanPath, fragment] = (path || '').split('#'); let normalized = this._normalizePath(cleanPath); const hash = fragment ? '#' + fragment : ''; if (this._mode === 'hash') { if (fragment) window.__zqScrollTarget = fragment; window.location.replace('#' + normalized); } else { window.history.replaceState(options.state || {}, '', this._base + normalized + hash); this._resolve(); } return this; } _normalizePath(path) { let p = path && path.startsWith('/') ? path : (path ? `/${path}` : '/'); if (this._base) { if (p === this._base) return '/'; if (p.startsWith(this._base + '/')) p = p.slice(this._base.length) || '/'; } return p; } resolve(path) { const normalized = path && path.startsWith('/') ? path : (path ? `/${path}` : '/'); return this._base + normalized; } back() { window.history.back(); return this; } forward() { window.history.forward(); return this; } go(n) { window.history.go(n); return this; } beforeEach(fn) { this._guards.before.push(fn); return this; } afterEach(fn) { this._guards.after.push(fn); return this; } onChange(fn) { this._listeners.add(fn); return () => this._listeners.delete(fn); } get current() { return this._current; } get base() { return this._base; } get path() { if (this._mode === 'hash') { const raw = window.location.hash.slice(1) || '/'; if (raw && !raw.startsWith('/')) { window.__zqScrollTarget = raw; const fallbackPath = (this._current && this._current.path) || '/'; window.location.replace('#' + fallbackPath); return fallbackPath; } return raw; } let pathname = window.location.pathname || '/'; if (pathname.length > 1 && pathname.endsWith('/')) { pathname = pathname.slice(0, -1); } if (this._base) { if (pathname === this._base) return '/'; if (pathname.startsWith(this._base + '/')) { return pathname.slice(this._base.length) || '/'; } } return pathname; } get query() { const search = this._mode === 'hash' ? (window.location.hash.split('?')[1] || '') : window.location.search.slice(1); return Object.fromEntries(new URLSearchParams(search)); } async _resolve() { if (this._resolving) return; this._resolving = true; try { await this.__resolve(); } finally { this._resolving = false; } } async __resolve() { const fullPath = this.path; const [pathPart, queryString] = fullPath.split('?'); const path = pathPart || '/'; const query = Object.fromEntries(new URLSearchParams(queryString || '')); let matched = null; let params = {}; for (const route of this._routes) { const m = path.match(route._regex); if (m) { matched = route; route._keys.forEach((key, i) => { params[key] = m[i + 1]; }); break; } } if (!matched && this._fallback) { matched = { component: this._fallback, path: '*', _keys: [], _regex: /.*/ }; } if (!matched) return; const to = { route: matched, params, query, path }; const from = this._current; for (const guard of this._guards.before) { const result = await guard(to, from); if (result === false) return; if (typeof result === 'string') { return this.navigate(result); } } if (matched.load) { try { await matched.load(); } catch (err) { console.error(`zQuery Router: Failed to load module for "${matched.path}"`, err); return; } } this._current = to; if (this._el && matched.component) { if (this._instance) { this._instance.destroy(); this._instance = null; } this._el.innerHTML = ''; const props = { ...params, $route: to, $query: query, $params: params }; if (typeof matched.component === 'string') { const container = document.createElement(matched.component); this._el.appendChild(container); this._instance = mount(container, matched.component, props); } else if (typeof matched.component === 'function') { this._el.innerHTML = matched.component(to); } } for (const guard of this._guards.after) { await guard(to, from); } this._listeners.forEach(fn => fn(to, from)); } destroy() { if (this._instance) this._instance.destroy(); this._listeners.clear(); this._routes = []; this._guards = { before: [], after: [] }; }
13
13
  class Store { constructor(config = {}) { this._subscribers = new Map(); this._wildcards = new Set(); this._actions = config.actions || {}; this._getters = config.getters || {}; this._middleware = []; this._history = []; this._debug = config.debug || false; const initial = typeof config.state === 'function' ? config.state() : { ...(config.state || {}) }; this.state = reactive(initial, (key, value, old) => { const subs = this._subscribers.get(key); if (subs) subs.forEach(fn => fn(value, old, key)); this._wildcards.forEach(fn => fn(key, value, old)); }); this.getters = {}; for (const [name, fn] of Object.entries(this._getters)) { Object.defineProperty(this.getters, name, { get: () => fn(this.state.__raw || this.state), enumerable: true }); } } dispatch(name, ...args) { const action = this._actions[name]; if (!action) { console.warn(`zQuery Store: Unknown action "${name}"`); return; } for (const mw of this._middleware) { const result = mw(name, args, this.state); if (result === false) return; } if (this._debug) { console.log(`%c[Store] ${name}`, 'color: #4CAF50; font-weight: bold;', ...args); } const result = action(this.state, ...args); this._history.push({ action: name, args, timestamp: Date.now() }); return result; } subscribe(keyOrFn, fn) { if (typeof keyOrFn === 'function') { this._wildcards.add(keyOrFn); return () => this._wildcards.delete(keyOrFn); } if (!this._subscribers.has(keyOrFn)) { this._subscribers.set(keyOrFn, new Set()); } this._subscribers.get(keyOrFn).add(fn); return () => this._subscribers.get(keyOrFn)?.delete(fn); } snapshot() { return JSON.parse(JSON.stringify(this.state.__raw || this.state)); } replaceState(newState) { const raw = this.state.__raw || this.state; for (const key of Object.keys(raw)) { delete this.state[key]; } Object.assign(this.state, newState); } use(fn) { this._middleware.push(fn); return this; } get history() { return [...this._history]; } reset(initialState) { this.replaceState(initialState); this._history = []; }
14
14
  const _config = { baseURL: '', headers: { 'Content-Type': 'application/json' }, timeout: 30000,
15
15
  function debounce(fn, ms = 250) { let timer; const debounced = (...args) => { clearTimeout(timer); timer = setTimeout(() => fn(...args), ms); }; debounced.cancel = () => clearTimeout(timer); return debounced;
16
- function $(selector, context) { if (typeof selector === 'function') { query.ready(selector); return; } return query(selector, context);
16
+ function $(selector, context) { if (typeof selector === 'function') { query.ready(selector); return; } return query(selector, context);
17
17
  })(typeof window !== 'undefined' ? window : globalThis);
package/index.d.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * Lightweight modern frontend library — jQuery-like selectors, reactive
5
5
  * components, SPA router, state management, HTTP client & utilities.
6
6
  *
7
- * @version 0.4.9
7
+ * @version 0.5.2
8
8
  * @license MIT
9
9
  * @see https://z-query.com/docs
10
10
  */
@@ -1086,6 +1086,12 @@ interface ZQueryStatic {
1086
1086
  classes(name: string): Element[];
1087
1087
  /** `document.getElementsByTagName(name)` as array. */
1088
1088
  tag(name: string): Element[];
1089
+ /** `document.getElementsByName(name)` as array. */
1090
+ name(name: string): Element[];
1091
+ /** `document.querySelectorAll('[attr]')` or `[attr="value"]` as array. */
1092
+ attr(attr: string, value?: string): Element[];
1093
+ /** `document.querySelectorAll('[data-key]')` or `[data-key="value"]` as array. */
1094
+ data(key: string, value?: string): Element[];
1089
1095
  /** Children of `#parentId` as array. */
1090
1096
  children(parentId: string): Element[];
1091
1097
 
@@ -1117,6 +1123,7 @@ interface ZQueryStatic {
1117
1123
 
1118
1124
  // -- Reactive ------------------------------------------------------------
1119
1125
  reactive: typeof reactive;
1126
+ Signal: typeof Signal;
1120
1127
  signal: typeof signal;
1121
1128
  computed: typeof computed;
1122
1129
  effect: typeof effect;
package/index.js CHANGED
@@ -10,7 +10,7 @@
10
10
  */
11
11
 
12
12
  import { query, queryAll, ZQueryCollection } from './src/core.js';
13
- import { reactive, signal, computed, effect } from './src/reactive.js';
13
+ import { reactive, Signal, signal, computed, effect } from './src/reactive.js';
14
14
  import { component, mount, mountAll, getInstance, destroy, getRegistry, style } from './src/component.js';
15
15
  import { createRouter, getRouter } from './src/router.js';
16
16
  import { createStore, getStore } from './src/store.js';
@@ -49,11 +49,16 @@ function $(selector, context) {
49
49
  }
50
50
 
51
51
 
52
- // --- Quick refs ------------------------------------------------------------
52
+ // --- Quick refs (DOM selectors) --------------------------------------------
53
53
  $.id = query.id;
54
54
  $.class = query.class;
55
55
  $.classes = query.classes;
56
56
  $.tag = query.tag;
57
+ Object.defineProperty($, 'name', {
58
+ value: query.name, writable: true, configurable: true
59
+ });
60
+ $.attr = query.attr;
61
+ $.data = query.data;
57
62
  $.children = query.children;
58
63
 
59
64
  // --- Collection selector ---------------------------------------------------
@@ -82,6 +87,7 @@ $.fn = query.fn;
82
87
 
83
88
  // --- Reactive primitives ---------------------------------------------------
84
89
  $.reactive = reactive;
90
+ $.Signal = Signal;
85
91
  $.signal = signal;
86
92
  $.computed = computed;
87
93
  $.effect = effect;
@@ -161,7 +167,7 @@ export {
161
167
  $ as zQuery,
162
168
  ZQueryCollection,
163
169
  queryAll,
164
- reactive, signal, computed, effect,
170
+ reactive, Signal, signal, computed, effect,
165
171
  component, mount, mountAll, getInstance, destroy, getRegistry, style,
166
172
  createRouter, getRouter,
167
173
  createStore, getStore,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zero-query",
3
- "version": "0.4.9",
3
+ "version": "0.5.2",
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",
@@ -21,7 +21,7 @@
21
21
  "dev": "node cli/index.js dev zquery-website",
22
22
  "dev-lib": "node cli/index.js build --watch",
23
23
  "bundle": "node cli/index.js bundle",
24
- "bundle:app": "node cli/index.js bundle zquery-website"
24
+ "bundle:app": "node cli/index.js bundle zquery-website --minimal"
25
25
  },
26
26
  "keywords": [
27
27
  "dom",
package/src/core.js CHANGED
@@ -468,6 +468,16 @@ query.id = (id) => document.getElementById(id);
468
468
  query.class = (name) => document.querySelector(`.${name}`);
469
469
  query.classes = (name) => Array.from(document.getElementsByClassName(name));
470
470
  query.tag = (name) => Array.from(document.getElementsByTagName(name));
471
+ Object.defineProperty(query, 'name', {
472
+ value: (name) => Array.from(document.getElementsByName(name)),
473
+ writable: true, configurable: true
474
+ });
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
+ );
471
481
  query.children = (parentId) => {
472
482
  const p = document.getElementById(parentId);
473
483
  return p ? Array.from(p.children) : [];