zero-query 1.1.1 → 1.2.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.
Files changed (156) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +2 -0
  3. package/cli/args.js +33 -33
  4. package/cli/commands/build-api.js +442 -442
  5. package/cli/commands/build.js +253 -247
  6. package/cli/commands/bundle.js +1226 -1224
  7. package/cli/commands/create.js +178 -121
  8. package/cli/commands/dev/devtools/index.js +56 -56
  9. package/cli/commands/dev/devtools/js/components.js +49 -49
  10. package/cli/commands/dev/devtools/js/core.js +423 -423
  11. package/cli/commands/dev/devtools/js/elements.js +421 -421
  12. package/cli/commands/dev/devtools/js/network.js +166 -166
  13. package/cli/commands/dev/devtools/js/performance.js +73 -73
  14. package/cli/commands/dev/devtools/js/router.js +105 -105
  15. package/cli/commands/dev/devtools/js/source.js +132 -132
  16. package/cli/commands/dev/devtools/js/stats.js +35 -35
  17. package/cli/commands/dev/devtools/js/tabs.js +79 -79
  18. package/cli/commands/dev/devtools/panel.html +95 -95
  19. package/cli/commands/dev/devtools/styles.css +244 -244
  20. package/cli/commands/dev/index.js +107 -107
  21. package/cli/commands/dev/logger.js +75 -75
  22. package/cli/commands/dev/overlay.js +858 -858
  23. package/cli/commands/dev/server.js +220 -220
  24. package/cli/commands/dev/validator.js +94 -94
  25. package/cli/commands/dev/watcher.js +172 -172
  26. package/cli/help.js +114 -112
  27. package/cli/index.js +52 -52
  28. package/cli/scaffold/default/LICENSE +21 -21
  29. package/cli/scaffold/default/app/app.js +207 -207
  30. package/cli/scaffold/default/app/components/about.js +201 -201
  31. package/cli/scaffold/default/app/components/api-demo.js +143 -143
  32. package/cli/scaffold/default/app/components/contact-card.js +231 -231
  33. package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
  34. package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
  35. package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
  36. package/cli/scaffold/default/app/components/counter.js +127 -127
  37. package/cli/scaffold/default/app/components/home.js +249 -249
  38. package/cli/scaffold/default/app/components/not-found.js +16 -16
  39. package/cli/scaffold/default/app/components/playground/playground.css +115 -115
  40. package/cli/scaffold/default/app/components/playground/playground.html +161 -161
  41. package/cli/scaffold/default/app/components/playground/playground.js +116 -116
  42. package/cli/scaffold/default/app/components/todos.js +225 -225
  43. package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
  44. package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
  45. package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
  46. package/cli/scaffold/default/app/routes.js +15 -15
  47. package/cli/scaffold/default/app/store.js +101 -101
  48. package/cli/scaffold/default/global.css +552 -552
  49. package/cli/scaffold/default/index.html +99 -99
  50. package/cli/scaffold/minimal/app/app.js +85 -85
  51. package/cli/scaffold/minimal/app/components/about.js +68 -68
  52. package/cli/scaffold/minimal/app/components/counter.js +122 -122
  53. package/cli/scaffold/minimal/app/components/home.js +68 -68
  54. package/cli/scaffold/minimal/app/components/not-found.js +16 -16
  55. package/cli/scaffold/minimal/app/routes.js +9 -9
  56. package/cli/scaffold/minimal/app/store.js +36 -36
  57. package/cli/scaffold/minimal/global.css +300 -300
  58. package/cli/scaffold/minimal/index.html +44 -44
  59. package/cli/scaffold/ssr/app/app.js +41 -41
  60. package/cli/scaffold/ssr/app/components/about.js +55 -55
  61. package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
  62. package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
  63. package/cli/scaffold/ssr/app/components/home.js +37 -37
  64. package/cli/scaffold/ssr/app/components/not-found.js +15 -15
  65. package/cli/scaffold/ssr/app/routes.js +8 -8
  66. package/cli/scaffold/ssr/global.css +228 -228
  67. package/cli/scaffold/ssr/index.html +37 -37
  68. package/cli/scaffold/ssr/package.json +8 -8
  69. package/cli/scaffold/ssr/server/data/posts.js +144 -144
  70. package/cli/scaffold/ssr/server/index.js +213 -213
  71. package/cli/scaffold/webrtc/app/app.js +11 -0
  72. package/cli/scaffold/webrtc/app/components/video-room.js +441 -0
  73. package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
  74. package/cli/scaffold/webrtc/global.css +250 -0
  75. package/cli/scaffold/webrtc/index.html +21 -0
  76. package/cli/scaffold/webrtc/package.json +16 -0
  77. package/cli/scaffold/webrtc/server/index.js +198 -0
  78. package/cli/utils.js +305 -287
  79. package/dist/API.md +661 -0
  80. package/dist/zquery.dist.zip +0 -0
  81. package/dist/zquery.js +10454 -6614
  82. package/dist/zquery.min.js +8 -631
  83. package/index.d.ts +570 -371
  84. package/index.js +311 -240
  85. package/package.json +77 -70
  86. package/src/component.js +1758 -1691
  87. package/src/core.js +929 -921
  88. package/src/diff.js +497 -497
  89. package/src/errors.js +209 -209
  90. package/src/expression.js +929 -922
  91. package/src/http.js +242 -242
  92. package/src/package.json +1 -1
  93. package/src/reactive.js +271 -255
  94. package/src/router.js +878 -843
  95. package/src/ssr.js +421 -418
  96. package/src/store.js +325 -318
  97. package/src/utils.js +534 -515
  98. package/src/webrtc/e2ee.js +351 -0
  99. package/src/webrtc/errors.js +116 -0
  100. package/src/webrtc/ice.js +301 -0
  101. package/src/webrtc/index.js +131 -0
  102. package/src/webrtc/joinToken.js +119 -0
  103. package/src/webrtc/observe.js +172 -0
  104. package/src/webrtc/peer.js +351 -0
  105. package/src/webrtc/reactive.js +268 -0
  106. package/src/webrtc/room.js +625 -0
  107. package/src/webrtc/sdp.js +302 -0
  108. package/src/webrtc/sfu/index.js +43 -0
  109. package/src/webrtc/sfu/livekit.js +131 -0
  110. package/src/webrtc/sfu/mediasoup.js +150 -0
  111. package/src/webrtc/signaling.js +373 -0
  112. package/src/webrtc/turn.js +237 -0
  113. package/tests/_helpers/webrtcFakes.js +289 -0
  114. package/tests/audit.test.js +4158 -4158
  115. package/tests/bench/render.bench.js +77 -0
  116. package/tests/cli.test.js +1136 -1103
  117. package/tests/compare.test.js +497 -486
  118. package/tests/component.test.js +4076 -3938
  119. package/tests/core.test.js +1926 -1910
  120. package/tests/dev-server.test.js +489 -489
  121. package/tests/diff.test.js +1416 -1416
  122. package/tests/docs.test.js +1664 -1650
  123. package/tests/electron-features.test.js +864 -864
  124. package/tests/errors.test.js +619 -619
  125. package/tests/expression.test.js +1087 -1056
  126. package/tests/http.test.js +693 -648
  127. package/tests/reactive.test.js +844 -819
  128. package/tests/router.test.js +2423 -2327
  129. package/tests/ssr.test.js +916 -870
  130. package/tests/store.test.js +840 -830
  131. package/tests/test-minifier.js +153 -153
  132. package/tests/test-ssr.js +27 -27
  133. package/tests/utils.test.js +1411 -1377
  134. package/tests/webrtc/e2ee.test.js +283 -0
  135. package/tests/webrtc/ice.test.js +202 -0
  136. package/tests/webrtc/joinToken.test.js +89 -0
  137. package/tests/webrtc/observe.test.js +111 -0
  138. package/tests/webrtc/peer.test.js +373 -0
  139. package/tests/webrtc/reactive.test.js +235 -0
  140. package/tests/webrtc/room.test.js +406 -0
  141. package/tests/webrtc/sdp.test.js +151 -0
  142. package/tests/webrtc/sfu-livekit.test.js +119 -0
  143. package/tests/webrtc/sfu.test.js +160 -0
  144. package/tests/webrtc/signaling.test.js +251 -0
  145. package/tests/webrtc/turn.test.js +256 -0
  146. package/types/collection.d.ts +383 -383
  147. package/types/component.d.ts +186 -186
  148. package/types/errors.d.ts +135 -135
  149. package/types/http.d.ts +92 -92
  150. package/types/misc.d.ts +201 -201
  151. package/types/reactive.d.ts +98 -98
  152. package/types/router.d.ts +190 -190
  153. package/types/ssr.d.ts +102 -102
  154. package/types/store.d.ts +146 -146
  155. package/types/utils.d.ts +245 -245
  156. package/types/webrtc.d.ts +653 -0
@@ -1,423 +1,423 @@
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
- // Retry connection - opener may be mid-mutation, not truly gone
376
- try {
377
- if (mode === 'popup' && window.opener && !window.opener.closed) {
378
- targetWin = window.opener;
379
- targetDoc = targetWin.document;
380
- document.getElementById('disconnected').style.display = 'none';
381
- } else if (iframe && iframe.contentWindow) {
382
- targetWin = iframe.contentWindow;
383
- targetDoc = targetWin.document;
384
- document.getElementById('disconnected').style.display = 'none';
385
- }
386
- } catch(e) {}
387
- if (!isConnected()) {
388
- document.getElementById('disconnected').style.display = 'flex';
389
- return;
390
- }
391
- }
392
- // Keep targetDoc fresh - the opener may have reloaded (live-reload)
393
- try {
394
- var freshDoc = targetWin.document;
395
- if (freshDoc !== targetDoc) {
396
- targetDoc = freshDoc;
397
- // Re-attach MutationObserver to the new document
398
- if (observer) { observer.disconnect(); observer = null; }
399
- startObserver();
400
- buildDOMTree();
401
- }
402
- } catch(e) {}
403
- var activeTab = document.querySelector('.tab.active');
404
- var tabName = activeTab ? activeTab.dataset.tab : '';
405
- if (tabName === 'components') renderComponents();
406
- updateStats();
407
- // Sync requests and render events from opener
408
- if (targetWin.__zqDevTools) {
409
- requests = targetWin.__zqDevTools.requests.slice();
410
- morphCount = targetWin.__zqDevTools.morphCount || 0;
411
- var stored = targetWin.__zqDevTools.morphEvents;
412
- if (stored && stored.length) {
413
- morphEvents = stored.slice();
414
- }
415
- var storedRouter = targetWin.__zqDevTools.routerEvents;
416
- if (storedRouter && storedRouter.length) {
417
- routerEvents = storedRouter.slice();
418
- }
419
- }
420
- if (tabName === 'perf') renderPerf();
421
- if (tabName === 'router') renderRouter();
422
- }, 800);
423
- }
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
+ // Retry connection - opener may be mid-mutation, not truly gone
376
+ try {
377
+ if (mode === 'popup' && window.opener && !window.opener.closed) {
378
+ targetWin = window.opener;
379
+ targetDoc = targetWin.document;
380
+ document.getElementById('disconnected').style.display = 'none';
381
+ } else if (iframe && iframe.contentWindow) {
382
+ targetWin = iframe.contentWindow;
383
+ targetDoc = targetWin.document;
384
+ document.getElementById('disconnected').style.display = 'none';
385
+ }
386
+ } catch(e) {}
387
+ if (!isConnected()) {
388
+ document.getElementById('disconnected').style.display = 'flex';
389
+ return;
390
+ }
391
+ }
392
+ // Keep targetDoc fresh - the opener may have reloaded (live-reload)
393
+ try {
394
+ var freshDoc = targetWin.document;
395
+ if (freshDoc !== targetDoc) {
396
+ targetDoc = freshDoc;
397
+ // Re-attach MutationObserver to the new document
398
+ if (observer) { observer.disconnect(); observer = null; }
399
+ startObserver();
400
+ buildDOMTree();
401
+ }
402
+ } catch(e) {}
403
+ var activeTab = document.querySelector('.tab.active');
404
+ var tabName = activeTab ? activeTab.dataset.tab : '';
405
+ if (tabName === 'components') renderComponents();
406
+ updateStats();
407
+ // Sync requests and render events from opener
408
+ if (targetWin.__zqDevTools) {
409
+ requests = targetWin.__zqDevTools.requests.slice();
410
+ morphCount = targetWin.__zqDevTools.morphCount || 0;
411
+ var stored = targetWin.__zqDevTools.morphEvents;
412
+ if (stored && stored.length) {
413
+ morphEvents = stored.slice();
414
+ }
415
+ var storedRouter = targetWin.__zqDevTools.routerEvents;
416
+ if (storedRouter && storedRouter.length) {
417
+ routerEvents = storedRouter.slice();
418
+ }
419
+ }
420
+ if (tabName === 'perf') renderPerf();
421
+ if (tabName === 'router') renderRouter();
422
+ }, 800);
423
+ }