zero-query 0.7.5 → 0.8.6

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 (64) hide show
  1. package/README.md +37 -27
  2. package/cli/commands/build.js +110 -1
  3. package/cli/commands/bundle.js +107 -22
  4. package/cli/commands/create.js +1 -1
  5. package/cli/commands/dev/devtools/index.js +56 -0
  6. package/cli/commands/dev/devtools/js/components.js +49 -0
  7. package/cli/commands/dev/devtools/js/core.js +409 -0
  8. package/cli/commands/dev/devtools/js/elements.js +413 -0
  9. package/cli/commands/dev/devtools/js/network.js +166 -0
  10. package/cli/commands/dev/devtools/js/performance.js +73 -0
  11. package/cli/commands/dev/devtools/js/router.js +105 -0
  12. package/cli/commands/dev/devtools/js/source.js +132 -0
  13. package/cli/commands/dev/devtools/js/stats.js +35 -0
  14. package/cli/commands/dev/devtools/js/tabs.js +79 -0
  15. package/cli/commands/dev/devtools/panel.html +95 -0
  16. package/cli/commands/dev/devtools/styles.css +244 -0
  17. package/cli/commands/dev/index.js +28 -3
  18. package/cli/commands/dev/logger.js +6 -1
  19. package/cli/commands/dev/overlay.js +377 -0
  20. package/cli/commands/dev/server.js +8 -0
  21. package/cli/commands/dev/watcher.js +26 -1
  22. package/cli/help.js +8 -5
  23. package/cli/scaffold/{scripts → app}/app.js +1 -1
  24. package/cli/scaffold/{scripts → app}/components/about.js +4 -4
  25. package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
  26. package/cli/scaffold/app/components/home.js +137 -0
  27. package/cli/scaffold/{scripts → app}/routes.js +1 -1
  28. package/cli/scaffold/{scripts → app}/store.js +6 -6
  29. package/cli/scaffold/assets/.gitkeep +0 -0
  30. package/cli/scaffold/{styles/styles.css → global.css} +3 -2
  31. package/cli/scaffold/index.html +11 -11
  32. package/dist/zquery.dist.zip +0 -0
  33. package/dist/zquery.js +746 -134
  34. package/dist/zquery.min.js +2 -2
  35. package/index.d.ts +11 -9
  36. package/index.js +15 -10
  37. package/package.json +3 -2
  38. package/src/component.js +161 -48
  39. package/src/core.js +57 -11
  40. package/src/diff.js +256 -58
  41. package/src/expression.js +33 -3
  42. package/src/reactive.js +37 -5
  43. package/src/router.js +195 -6
  44. package/tests/component.test.js +582 -0
  45. package/tests/core.test.js +251 -0
  46. package/tests/diff.test.js +333 -2
  47. package/tests/expression.test.js +148 -0
  48. package/tests/http.test.js +108 -0
  49. package/tests/reactive.test.js +148 -0
  50. package/tests/router.test.js +317 -0
  51. package/tests/store.test.js +126 -0
  52. package/tests/utils.test.js +161 -2
  53. package/types/collection.d.ts +17 -2
  54. package/types/component.d.ts +7 -0
  55. package/types/misc.d.ts +13 -0
  56. package/types/router.d.ts +30 -1
  57. package/cli/commands/dev.old.js +0 -520
  58. package/cli/scaffold/scripts/components/home.js +0 -137
  59. /package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -0
  60. /package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +0 -0
  61. /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
  62. /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
  63. /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
  64. /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
@@ -0,0 +1,409 @@
1
+ // ===================================================================
2
+ // Shared state
3
+ // ===================================================================
4
+ var targetWin = null;
5
+ var targetDoc = null;
6
+ var channel;
7
+ var morphCount = 0;
8
+ var requests = [];
9
+ var selectedEl = null;
10
+ var rootEl = document.getElementById('root');
11
+ var iframe = document.getElementById('app-frame');
12
+ var mode = 'split-h'; // split-h | split-v | devtools-only | popup
13
+ var expandedPaths = {}; // track expanded nodes by path to survive rebuilds
14
+ var componentNames = {}; // cache of registered component tag names
15
+ var changedPaths = {}; // paths mutated since last rebuild (for auto-expand)
16
+ var mutatedPaths = {}; // actual mutation targets (for highlight + scroll)
17
+ var observer;
18
+ var morphEvents = [];
19
+ var routerEvents = [];
20
+
21
+ try { channel = new BroadcastChannel('__zq_devtools'); } catch(e) {}
22
+
23
+ // ===================================================================
24
+ // Utilities
25
+ // ===================================================================
26
+ function esc(s) {
27
+ var d = document.createElement('span');
28
+ d.textContent = s;
29
+ return d.innerHTML;
30
+ }
31
+
32
+ function formatTime(ts) {
33
+ if (!ts) return '';
34
+ var d = new Date(ts);
35
+ var h = d.getHours(), m = d.getMinutes(), s = d.getSeconds();
36
+ var ampm = h >= 12 ? 'PM' : 'AM';
37
+ h = h % 12 || 12;
38
+ return h + ':' + String(m).padStart(2, '0') + ':' + String(s).padStart(2, '0') + ' ' + ampm;
39
+ }
40
+
41
+ // ===================================================================
42
+ // Connection — find target window (opener popup → iframe fallback)
43
+ // ===================================================================
44
+ function isConnected() {
45
+ try { return targetWin && (targetWin === window.opener ? !targetWin.closed : true) && targetWin.document; }
46
+ catch(e) { return false; }
47
+ }
48
+
49
+ function detectMode() {
50
+ if (window.opener) {
51
+ // Opened as popup — hide iframe, use opener
52
+ mode = 'popup';
53
+ targetWin = window.opener;
54
+ } else {
55
+ // Standalone tab — embed app in iframe
56
+ mode = 'split-h';
57
+ targetWin = null; // will set from iframe.contentWindow
58
+ }
59
+ rootEl.className = mode;
60
+ }
61
+
62
+ detectMode();
63
+
64
+ // ===================================================================
65
+ // Layout toggle: split-h → split-v → devtools-only → split-h
66
+ // ===================================================================
67
+ var modeToggle = document.getElementById('mode-toggle');
68
+
69
+ function setMode(newMode) {
70
+ mode = newMode;
71
+ rootEl.style.gridTemplateColumns = '';
72
+ rootEl.style.gridTemplateRows = '';
73
+ rootEl.className = mode;
74
+ modeToggle.textContent = mode === 'split-h' ? '\u2b82' : mode === 'split-v' ? '\u2b81' : '\u25a1';
75
+ // Reset toolbar drag position
76
+ var tb = document.getElementById('divider-toolbar');
77
+ if (tb) { tb.style.left = ''; tb.style.top = ''; }
78
+ }
79
+
80
+ modeToggle.addEventListener('click', function() {
81
+ if (mode === 'popup') return;
82
+ if (mode === 'split-h') setMode('split-v');
83
+ else if (mode === 'split-v') setMode('devtools-only');
84
+ else setMode('split-h');
85
+ // Reset viewport presets to desktop when switching modes
86
+ var vBtns = document.querySelectorAll('.viewport-btn');
87
+ vBtns.forEach(function(b) { b.classList.remove('active'); });
88
+ var desk = document.querySelector('.viewport-btn[data-width="0"]');
89
+ if (desk) desk.classList.add('active');
90
+ });
91
+
92
+ // ===================================================================
93
+ // Divider drag-to-resize
94
+ // ===================================================================
95
+ var divider = document.getElementById('divider');
96
+ var toolbar = document.getElementById('divider-toolbar');
97
+
98
+ // --- Toolbar drag (Y-axis in split-h, X-axis in split-v) ------------------
99
+ var tbDragging = false;
100
+ (function() {
101
+ var startPos, startOffset;
102
+ toolbar.addEventListener('mousedown', function(e) {
103
+ if (e.target.closest('button') || e.target.closest('.route-label')) return;
104
+ e.preventDefault();
105
+ e.stopPropagation();
106
+ tbDragging = true;
107
+ toolbar.classList.add('dragging');
108
+ iframe.style.pointerEvents = 'none';
109
+ document.body.style.userSelect = 'none';
110
+
111
+ var isH = mode === 'split-h';
112
+ if (isH) {
113
+ // Vertical column divider — drag toolbar up/down
114
+ startPos = e.clientY;
115
+ startOffset = parseInt(toolbar.style.top, 10) || toolbar.offsetTop;
116
+ } else {
117
+ // Horizontal row divider — drag toolbar left/right
118
+ startPos = e.clientX;
119
+ startOffset = parseInt(toolbar.style.left, 10) || toolbar.offsetLeft;
120
+ }
121
+
122
+ function onMove(e2) {
123
+ if (isH) {
124
+ var delta = e2.clientY - startPos;
125
+ var max = divider.offsetHeight - toolbar.offsetHeight - 4;
126
+ toolbar.style.top = Math.max(4, Math.min(max, startOffset + delta)) + 'px';
127
+ } else {
128
+ var delta = e2.clientX - startPos;
129
+ var max = divider.offsetWidth - toolbar.offsetWidth - 4;
130
+ toolbar.style.left = Math.max(4, Math.min(max, startOffset + delta)) + 'px';
131
+ }
132
+ }
133
+ function onUp() {
134
+ tbDragging = false;
135
+ toolbar.classList.remove('dragging');
136
+ iframe.style.pointerEvents = '';
137
+ document.body.style.userSelect = '';
138
+ document.removeEventListener('mousemove', onMove);
139
+ document.removeEventListener('mouseup', onUp);
140
+ }
141
+ document.addEventListener('mousemove', onMove);
142
+ document.addEventListener('mouseup', onUp);
143
+ });
144
+ })();
145
+
146
+ divider.addEventListener('mousedown', function(e) {
147
+ // Don't start drag when clicking toolbar buttons or dragging toolbar
148
+ if (e.target.closest('.divider-toolbar')) return;
149
+ e.preventDefault();
150
+ var startX = e.clientX, startY = e.clientY;
151
+ var isH = mode === 'split-h';
152
+ var total = isH ? rootEl.offsetWidth : rootEl.offsetHeight;
153
+ var startFrac = isH
154
+ ? (iframe.offsetWidth / total)
155
+ : (iframe.offsetHeight / total);
156
+
157
+ // Prevent iframe from stealing mouse events during drag
158
+ iframe.style.pointerEvents = 'none';
159
+ document.body.style.userSelect = 'none';
160
+ document.body.style.cursor = isH ? 'col-resize' : 'row-resize';
161
+
162
+ function onMove(e2) {
163
+ var delta = isH ? (e2.clientX - startX) / total : (e2.clientY - startY) / total;
164
+ var frac = Math.min(0.85, Math.max(0.15, startFrac + delta));
165
+ var pct = (frac * 100).toFixed(1) + '%';
166
+ var rest = ((1 - frac) * 100).toFixed(1) + '%';
167
+ if (isH) rootEl.style.gridTemplateColumns = pct + ' 4px ' + rest;
168
+ else rootEl.style.gridTemplateRows = pct + ' 4px ' + rest;
169
+ updateViewportActive();
170
+ }
171
+ function onUp() {
172
+ iframe.style.pointerEvents = '';
173
+ document.body.style.userSelect = '';
174
+ document.body.style.cursor = '';
175
+ document.removeEventListener('mousemove', onMove);
176
+ document.removeEventListener('mouseup', onUp);
177
+ }
178
+ document.addEventListener('mousemove', onMove);
179
+ document.addEventListener('mouseup', onUp);
180
+ });
181
+
182
+ // ===================================================================
183
+ // Refresh button — reload the embedded iframe
184
+ // ===================================================================
185
+ document.getElementById('btn-refresh').addEventListener('click', function(e) {
186
+ e.stopPropagation();
187
+ if (mode === 'popup' && targetWin && !targetWin.closed) {
188
+ targetWin.location.reload();
189
+ } else if (iframe && iframe.contentWindow) {
190
+ iframe.contentWindow.location.reload();
191
+ }
192
+ });
193
+
194
+ // ===================================================================
195
+ // Viewport preset buttons — resize browser pane to mobile/tablet/desktop
196
+ // ===================================================================
197
+ var viewportBtns = document.querySelectorAll('.viewport-btn');
198
+
199
+ function updateViewportActive() {
200
+ var w = iframe.offsetWidth;
201
+ viewportBtns.forEach(function(b) {
202
+ var target = parseInt(b.getAttribute('data-width'), 10);
203
+ if (target === 0) {
204
+ // Desktop is active when width doesn't match any preset
205
+ b.classList.toggle('active', w > 780);
206
+ } else {
207
+ b.classList.toggle('active', Math.abs(w - target) < 20);
208
+ }
209
+ });
210
+ }
211
+
212
+ viewportBtns.forEach(function(btn) {
213
+ btn.addEventListener('click', function(e) {
214
+ e.stopPropagation();
215
+ if (mode === 'popup') return;
216
+ // Ensure we're in side-by-side mode
217
+ if (mode !== 'split-h') setMode('split-h');
218
+ var targetWidth = parseInt(this.getAttribute('data-width'), 10);
219
+ var total = rootEl.offsetWidth;
220
+
221
+ if (targetWidth === 0) {
222
+ // Desktop — reset to default CSS proportions
223
+ rootEl.style.gridTemplateColumns = '';
224
+ } else {
225
+ // Mobile/Tablet — set iframe column to exact pixel width
226
+ var pct = Math.min(85, Math.max(15, (targetWidth / total) * 100));
227
+ rootEl.style.gridTemplateColumns = pct.toFixed(1) + '% 4px 1fr';
228
+ }
229
+
230
+ viewportBtns.forEach(function(b) { b.classList.remove('active'); });
231
+ this.classList.add('active');
232
+ });
233
+ });
234
+
235
+ // ===================================================================
236
+ // Route indicator — toggle label showing current path + hash
237
+ // ===================================================================
238
+ var routeBtn = document.getElementById('btn-route');
239
+ var routeLabel = document.getElementById('route-label');
240
+
241
+ function getRoutePath() {
242
+ try {
243
+ var win = (mode === 'popup' && targetWin) ? targetWin : (iframe && iframe.contentWindow);
244
+ if (!win) return '/';
245
+ var loc = win.location;
246
+ return (loc.pathname || '/') + (loc.hash || '');
247
+ } catch(e) { return '/'; }
248
+ }
249
+
250
+ routeBtn.addEventListener('click', function(e) {
251
+ e.stopPropagation();
252
+ var isOpen = routeLabel.classList.toggle('open');
253
+ if (isOpen) {
254
+ routeLabel.textContent = getRoutePath();
255
+ this.classList.add('active');
256
+ } else {
257
+ this.classList.remove('active');
258
+ }
259
+ });
260
+
261
+ // Keep label in sync while open
262
+ setInterval(function() {
263
+ if (routeLabel.classList.contains('open')) {
264
+ routeLabel.textContent = getRoutePath();
265
+ }
266
+ }, 500);
267
+
268
+ // ===================================================================
269
+ // init — connect to target window (popup or iframe)
270
+ // ===================================================================
271
+ function init() {
272
+ // If popup mode, targetWin is already set
273
+ // If iframe mode, wait for iframe to load
274
+ if (!isConnected()) {
275
+ if (mode !== 'popup') {
276
+ // Wait for iframe
277
+ iframe.addEventListener('load', function() {
278
+ try {
279
+ targetWin = iframe.contentWindow;
280
+ targetDoc = targetWin.document;
281
+ // Inject dark scrollbar styles into the iframe content
282
+ try {
283
+ var s = targetDoc.createElement('style');
284
+ s.textContent = '::-webkit-scrollbar{width:4px;height:4px}::-webkit-scrollbar-track{background:transparent}::-webkit-scrollbar-thumb{background:rgba(255,255,255,0.12);border-radius:2px}::-webkit-scrollbar-thumb:hover{background:rgba(255,255,255,0.2)}html{scrollbar-color:rgba(255,255,255,0.12) transparent;scrollbar-width:thin}';
285
+ targetDoc.head.appendChild(s);
286
+ } catch(se) {}
287
+ } catch(e) {
288
+ document.getElementById('disconnected').style.display = 'flex';
289
+ return;
290
+ }
291
+ document.getElementById('disconnected').style.display = 'none';
292
+ connectToTarget();
293
+ });
294
+ return;
295
+ }
296
+ document.getElementById('disconnected').style.display = 'flex';
297
+ // Retry for popup opener
298
+ setTimeout(function() {
299
+ targetWin = window.opener;
300
+ if (isConnected()) {
301
+ document.getElementById('disconnected').style.display = 'none';
302
+ init();
303
+ }
304
+ }, 1000);
305
+ return;
306
+ }
307
+
308
+ targetDoc = targetWin.document;
309
+ connectToTarget();
310
+ }
311
+
312
+ // ===================================================================
313
+ // connectToTarget — read existing data, start listeners, periodic sync
314
+ // ===================================================================
315
+ function connectToTarget() {
316
+
317
+ // Read existing requests and render events from opener
318
+ if (targetWin.__zqDevTools) {
319
+ requests = targetWin.__zqDevTools.requests.slice();
320
+ morphCount = targetWin.__zqDevTools.morphCount || 0;
321
+ var stored = targetWin.__zqDevTools.morphEvents;
322
+ if (stored && stored.length) {
323
+ morphEvents = stored.slice();
324
+ }
325
+ var storedRouter = targetWin.__zqDevTools.routerEvents;
326
+ if (storedRouter && storedRouter.length) {
327
+ routerEvents = storedRouter.slice();
328
+ }
329
+ }
330
+
331
+ buildDOMTree();
332
+ renderNetwork();
333
+ renderComponents();
334
+ renderPerf();
335
+ renderRouter();
336
+ startObserver();
337
+ updateStats();
338
+
339
+ // Listen for BroadcastChannel messages
340
+ if (channel) {
341
+ channel.onmessage = function(e) {
342
+ var msg = e.data;
343
+ if (msg.type === 'http') {
344
+ requests.push(msg.data);
345
+ if (requests.length > 500) requests.shift();
346
+ renderNetwork();
347
+ updateStats();
348
+ } else if (msg.type === 'morph') {
349
+ morphCount++;
350
+ updateStats();
351
+ renderPerf();
352
+ } else if (msg.type === 'morph-detail') {
353
+ morphCount++;
354
+ recordMorphEvent(msg.data);
355
+ updateStats();
356
+ renderPerf();
357
+ } else if (msg.type === 'render-detail') {
358
+ morphCount++;
359
+ recordMorphEvent({ target: msg.data.target, elapsed: msg.data.elapsed, kind: msg.data.kind, timestamp: msg.data.timestamp });
360
+ updateStats();
361
+ renderPerf();
362
+ } else if (msg.type === 'router') {
363
+ routerEvents.push(msg.data);
364
+ if (routerEvents.length > 200) routerEvents.shift();
365
+ var activeTab = document.querySelector('.tab.active');
366
+ if (activeTab && activeTab.dataset.tab === 'router') renderRouter();
367
+ updateStats();
368
+ }
369
+ };
370
+ }
371
+
372
+ // Periodic refresh for components + perf (fast when tab is visible)
373
+ setInterval(function() {
374
+ if (!isConnected()) {
375
+ document.getElementById('disconnected').style.display = 'flex';
376
+ return;
377
+ }
378
+ // Keep targetDoc fresh — the opener may have reloaded (live-reload)
379
+ try {
380
+ var freshDoc = targetWin.document;
381
+ if (freshDoc !== targetDoc) {
382
+ targetDoc = freshDoc;
383
+ // Re-attach MutationObserver to the new document
384
+ if (observer) { observer.disconnect(); observer = null; }
385
+ startObserver();
386
+ buildDOMTree();
387
+ }
388
+ } catch(e) {}
389
+ var activeTab = document.querySelector('.tab.active');
390
+ var tabName = activeTab ? activeTab.dataset.tab : '';
391
+ if (tabName === 'components') renderComponents();
392
+ updateStats();
393
+ // Sync requests and render events from opener
394
+ if (targetWin.__zqDevTools) {
395
+ requests = targetWin.__zqDevTools.requests.slice();
396
+ morphCount = targetWin.__zqDevTools.morphCount || 0;
397
+ var stored = targetWin.__zqDevTools.morphEvents;
398
+ if (stored && stored.length) {
399
+ morphEvents = stored.slice();
400
+ }
401
+ var storedRouter = targetWin.__zqDevTools.routerEvents;
402
+ if (storedRouter && storedRouter.length) {
403
+ routerEvents = storedRouter.slice();
404
+ }
405
+ }
406
+ if (tabName === 'perf') renderPerf();
407
+ if (tabName === 'router') renderRouter();
408
+ }, 800);
409
+ }