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.
Files changed (41) hide show
  1. package/README.md +6 -6
  2. package/cli/commands/build.js +3 -3
  3. package/cli/commands/bundle.js +286 -8
  4. package/cli/commands/dev/index.js +2 -2
  5. package/cli/commands/dev/overlay.js +51 -2
  6. package/cli/commands/dev/server.js +34 -5
  7. package/cli/commands/dev/watcher.js +33 -0
  8. package/cli/scaffold/index.html +1 -0
  9. package/cli/scaffold/scripts/app.js +15 -22
  10. package/cli/scaffold/scripts/components/contacts/contacts.css +0 -7
  11. package/cli/scaffold/scripts/components/contacts/contacts.html +3 -3
  12. package/cli/scaffold/styles/styles.css +1 -0
  13. package/cli/utils.js +111 -6
  14. package/dist/zquery.dist.zip +0 -0
  15. package/dist/zquery.js +379 -27
  16. package/dist/zquery.min.js +3 -16
  17. package/index.d.ts +127 -1290
  18. package/package.json +5 -5
  19. package/src/component.js +11 -1
  20. package/src/core.js +305 -10
  21. package/src/router.js +49 -2
  22. package/tests/component.test.js +304 -0
  23. package/tests/core.test.js +726 -0
  24. package/tests/diff.test.js +194 -0
  25. package/tests/errors.test.js +162 -0
  26. package/tests/expression.test.js +334 -0
  27. package/tests/http.test.js +181 -0
  28. package/tests/reactive.test.js +191 -0
  29. package/tests/router.test.js +332 -0
  30. package/tests/store.test.js +253 -0
  31. package/tests/utils.test.js +353 -0
  32. package/types/collection.d.ts +368 -0
  33. package/types/component.d.ts +210 -0
  34. package/types/errors.d.ts +103 -0
  35. package/types/http.d.ts +81 -0
  36. package/types/misc.d.ts +166 -0
  37. package/types/reactive.d.ts +76 -0
  38. package/types/router.d.ts +132 -0
  39. package/types/ssr.d.ts +49 -0
  40. package/types/store.d.ts +107 -0
  41. package/types/utils.d.ts +142 -0
@@ -27,7 +27,7 @@ const router = $.router({
27
27
  // Highlight the active nav link on every route change
28
28
  router.onChange((to) => {
29
29
  $.all('.nav-link').removeClass('active');
30
- $.all(`.nav-link[z-link="${to.path}"]`).addClass('active');
30
+ $(`.nav-link[z-link="${to.path}"]`).addClass('active');
31
31
 
32
32
  // Close mobile menu on navigate
33
33
  closeMobileMenu();
@@ -36,26 +36,20 @@ router.onChange((to) => {
36
36
  // ---------------------------------------------------------------------------
37
37
  // Responsive sidebar toggle
38
38
  // ---------------------------------------------------------------------------
39
- const sidebar = $.id('sidebar');
40
- const overlay = $.id('overlay');
41
- const toggle = $.id('menu-toggle');
39
+ const $sidebar = $('#sidebar');
40
+ const $overlay = $('#overlay');
41
+ const $toggle = $('#menu-toggle');
42
42
 
43
- function openMobileMenu() {
44
- sidebar.classList.add('open');
45
- overlay.classList.add('visible');
46
- toggle.classList.add('active');
43
+ function toggleMobileMenu(open) {
44
+ $sidebar.toggleClass('open', open);
45
+ $overlay.toggleClass('visible', open);
46
+ $toggle.toggleClass('active', open);
47
47
  }
48
48
 
49
- function closeMobileMenu() {
50
- sidebar.classList.remove('open');
51
- overlay.classList.remove('visible');
52
- toggle.classList.remove('active');
53
- }
49
+ function closeMobileMenu() { toggleMobileMenu(false); }
54
50
 
55
51
  // $.on — global delegated event listeners
56
- $.on('click', '#menu-toggle', () => {
57
- sidebar.classList.contains('open') ? closeMobileMenu() : openMobileMenu();
58
- });
52
+ $.on('click', '#menu-toggle', () => toggleMobileMenu(!$sidebar.hasClass('open')));
59
53
  $.on('click', '#overlay', closeMobileMenu);
60
54
 
61
55
  // Close sidebar on Escape key — using $.on direct (no selector needed)
@@ -69,14 +63,13 @@ $.on('keydown', (e) => {
69
63
  // Any component can emit: $.bus.emit('toast', { message, type })
70
64
  // Types: 'success', 'error', 'info'
71
65
  $.bus.on('toast', ({ message, type = 'info' }) => {
72
- const container = $.id('toasts');
73
- const toast = $.create('div');
74
- toast.className = `toast toast-${type}`;
75
- toast.textContent = message;
76
- container.appendChild(toast);
66
+ const toast = $.create('div')
67
+ .addClass('toast', `toast-${type}`)
68
+ .text(message)
69
+ .appendTo('#toasts');
77
70
  // Auto-remove after 3 seconds
78
71
  setTimeout(() => {
79
- toast.classList.add('toast-exit');
72
+ toast.addClass('toast-exit');
80
73
  setTimeout(() => toast.remove(), 300);
81
74
  }, 3000);
82
75
  });
@@ -216,13 +216,6 @@
216
216
  font-weight: 500;
217
217
  }
218
218
 
219
- @keyframes slide-in {
220
- from { opacity: 0; transform: translateY(-6px); }
221
- to { opacity: 1; transform: translateY(0); }
222
- }
223
- }
224
-
225
- /* -- Animation -- */
226
219
  @keyframes slide-in {
227
220
  from { opacity: 0; transform: translateY(-6px); }
228
221
  to { opacity: 1; transform: translateY(0); }
@@ -68,7 +68,7 @@
68
68
  </div>
69
69
 
70
70
  <!-- Contact count -->
71
- <div class="card" z-if="contacts.length > 0">
71
+ <div class="card" z-if="contacts.length > 0" z-key="contacts-list">
72
72
  <!-- Contacts list — z-for renders each item -->
73
73
  <ul class="contacts-list">
74
74
  <li
@@ -102,14 +102,14 @@
102
102
  </div>
103
103
 
104
104
  <!-- Empty state -->
105
- <div class="card" z-else>
105
+ <div class="card" z-else z-key="contacts-empty">
106
106
  <div class="empty-state">
107
107
  <p>No contacts yet — add one above!</p>
108
108
  </div>
109
109
  </div>
110
110
 
111
111
  <!-- Selected contact detail panel — z-if conditional rendering -->
112
- <div class="card contact-detail" z-if="selectedId !== null">
112
+ <div class="card contact-detail" z-if="selectedId !== null" z-key="contact-detail">
113
113
  <div class="detail-header">
114
114
  <div>
115
115
  <h3 z-text="selectedName"></h3>
@@ -103,6 +103,7 @@ code { background: var(--bg-hover); padding: 2px 6px; border-radius: 4px; font-s
103
103
  transition: all 0.15s ease;
104
104
  border-left: 3px solid transparent;
105
105
  cursor: pointer;
106
+ user-select: none;
106
107
  }
107
108
 
108
109
  .nav-link:hover {
package/cli/utils.js CHANGED
@@ -83,15 +83,120 @@ function stripComments(code) {
83
83
  }
84
84
 
85
85
  // ---------------------------------------------------------------------------
86
- // minify — quick minification (strips comments + collapses whitespace)
86
+ // minify — single-pass minification
87
+ // Strips comments, collapses whitespace to the minimum required,
88
+ // and preserves string / template-literal / regex content verbatim.
87
89
  // ---------------------------------------------------------------------------
88
90
 
89
91
  function minify(code, banner) {
90
- const body = stripComments(code.replace(banner, ''))
91
- .replace(/^\s*\n/gm, '')
92
- .replace(/\n\s+/g, '\n')
93
- .replace(/\s{2,}/g, ' ');
94
- return banner + '\n' + body;
92
+ return banner + '\n' + _minifyBody(code.replace(banner, ''));
93
+ }
94
+
95
+ /**
96
+ * Single-pass minifier: walks character-by-character, skips strings/regex,
97
+ * strips comments, and emits a space only when both neighbours are
98
+ * identifier-like characters (or when collapsing would create ++, --, // or /*).
99
+ */
100
+ function _minifyBody(code) {
101
+ let out = '';
102
+ let i = 0;
103
+
104
+ while (i < code.length) {
105
+ const ch = code[i];
106
+ const nx = code[i + 1];
107
+
108
+ // ── String / template literal: copy verbatim ────────────────
109
+ if (ch === '"' || ch === "'" || ch === '`') {
110
+ const q = ch;
111
+ out += ch; i++;
112
+ while (i < code.length) {
113
+ if (code[i] === '\\') { out += code[i] + (code[i + 1] || ''); i += 2; continue; }
114
+ out += code[i];
115
+ if (code[i] === q) { i++; break; }
116
+ i++;
117
+ }
118
+ continue;
119
+ }
120
+
121
+ // ── Block comment: skip ─────────────────────────────────────
122
+ if (ch === '/' && nx === '*') {
123
+ i += 2;
124
+ while (i < code.length && !(code[i] === '*' && code[i + 1] === '/')) i++;
125
+ i += 2;
126
+ continue;
127
+ }
128
+
129
+ // ── Line comment: skip ──────────────────────────────────────
130
+ if (ch === '/' && nx === '/') {
131
+ i += 2;
132
+ while (i < code.length && code[i] !== '\n') i++;
133
+ continue;
134
+ }
135
+
136
+ // ── Regex literal: copy verbatim ────────────────────────────
137
+ if (ch === '/') {
138
+ if (_isRegexCtx(out)) {
139
+ out += ch; i++;
140
+ let inCC = false;
141
+ while (i < code.length) {
142
+ const rc = code[i];
143
+ if (rc === '\\') { out += rc + (code[i + 1] || ''); i += 2; continue; }
144
+ if (rc === '[') inCC = true;
145
+ if (rc === ']') inCC = false;
146
+ out += rc; i++;
147
+ if (rc === '/' && !inCC) {
148
+ while (i < code.length && /[gimsuy]/.test(code[i])) { out += code[i]; i++; }
149
+ break;
150
+ }
151
+ }
152
+ continue;
153
+ }
154
+ }
155
+
156
+ // ── Whitespace: collapse ────────────────────────────────────
157
+ if (ch === ' ' || ch === '\t' || ch === '\n' || ch === '\r') {
158
+ while (i < code.length && (code[i] === ' ' || code[i] === '\t' || code[i] === '\n' || code[i] === '\r')) i++;
159
+ const before = out[out.length - 1];
160
+ const after = code[i];
161
+ if (_needsSpace(before, after)) out += ' ';
162
+ continue;
163
+ }
164
+
165
+ out += ch;
166
+ i++;
167
+ }
168
+
169
+ return out;
170
+ }
171
+
172
+ /** True when removing the space between a and b would change semantics. */
173
+ function _needsSpace(a, b) {
174
+ if (!a || !b) return false;
175
+ const idA = (a >= 'a' && a <= 'z') || (a >= 'A' && a <= 'Z') || (a >= '0' && a <= '9') || a === '_' || a === '$';
176
+ const idB = (b >= 'a' && b <= 'z') || (b >= 'A' && b <= 'Z') || (b >= '0' && b <= '9') || b === '_' || b === '$';
177
+ if (idA && idB) return true; // e.g. const x, return value
178
+ if (a === '+' && b === '+') return true; // prevent ++
179
+ if (a === '-' && b === '-') return true; // prevent --
180
+ if (a === '/' && (b === '/' || b === '*')) return true; // prevent // or /*
181
+ return false;
182
+ }
183
+
184
+ /** Heuristic: is the next '/' a regex start (vs division)? */
185
+ function _isRegexCtx(out) {
186
+ let end = out.length - 1;
187
+ while (end >= 0 && out[end] === ' ') end--;
188
+ if (end < 0) return true;
189
+ const last = out[end];
190
+ if ('=({[,;:!&|?~+-*/%^>'.includes(last)) return true;
191
+ const tail = out.substring(Math.max(0, end - 7), end + 1);
192
+ const kws = ['return', 'typeof', 'case', 'in', 'delete', 'void', 'throw', 'new'];
193
+ for (const kw of kws) {
194
+ if (tail.endsWith(kw)) {
195
+ const pos = end - kw.length;
196
+ if (pos < 0 || !((out[pos] >= 'a' && out[pos] <= 'z') || (out[pos] >= 'A' && out[pos] <= 'Z') || (out[pos] >= '0' && out[pos] <= '9') || out[pos] === '_' || out[pos] === '$')) return true;
197
+ }
198
+ }
199
+ return false;
95
200
  }
96
201
 
97
202
  // ---------------------------------------------------------------------------
Binary file