zero-query 0.7.5 → 0.8.7
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 +39 -30
- package/cli/commands/build.js +110 -1
- package/cli/commands/bundle.js +127 -50
- package/cli/commands/create.js +1 -1
- package/cli/commands/dev/devtools/index.js +56 -0
- package/cli/commands/dev/devtools/js/components.js +49 -0
- package/cli/commands/dev/devtools/js/core.js +409 -0
- package/cli/commands/dev/devtools/js/elements.js +413 -0
- package/cli/commands/dev/devtools/js/network.js +166 -0
- package/cli/commands/dev/devtools/js/performance.js +73 -0
- package/cli/commands/dev/devtools/js/router.js +105 -0
- package/cli/commands/dev/devtools/js/source.js +132 -0
- package/cli/commands/dev/devtools/js/stats.js +35 -0
- package/cli/commands/dev/devtools/js/tabs.js +79 -0
- package/cli/commands/dev/devtools/panel.html +95 -0
- package/cli/commands/dev/devtools/styles.css +244 -0
- package/cli/commands/dev/index.js +28 -3
- package/cli/commands/dev/logger.js +6 -1
- package/cli/commands/dev/overlay.js +377 -0
- package/cli/commands/dev/server.js +8 -0
- package/cli/commands/dev/watcher.js +26 -1
- package/cli/help.js +8 -5
- package/cli/scaffold/{scripts → app}/app.js +1 -1
- package/cli/scaffold/{scripts → app}/components/about.js +4 -4
- package/cli/scaffold/{scripts → app}/components/api-demo.js +1 -1
- package/cli/scaffold/app/components/home.js +137 -0
- package/cli/scaffold/{scripts → app}/routes.js +1 -1
- package/cli/scaffold/{scripts → app}/store.js +6 -6
- package/cli/scaffold/assets/.gitkeep +0 -0
- package/cli/scaffold/{styles/styles.css → global.css} +3 -2
- package/cli/scaffold/index.html +11 -11
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +740 -226
- package/dist/zquery.min.js +2 -2
- package/index.d.ts +11 -11
- package/index.js +15 -10
- package/package.json +3 -2
- package/src/component.js +154 -139
- package/src/core.js +57 -11
- package/src/diff.js +256 -58
- package/src/expression.js +33 -3
- package/src/reactive.js +37 -5
- package/src/router.js +196 -7
- package/src/ssr.js +1 -1
- package/tests/component.test.js +582 -0
- package/tests/core.test.js +251 -0
- package/tests/diff.test.js +333 -2
- package/tests/expression.test.js +148 -0
- package/tests/http.test.js +108 -0
- package/tests/reactive.test.js +148 -0
- package/tests/router.test.js +317 -0
- package/tests/store.test.js +126 -0
- package/tests/utils.test.js +161 -2
- package/types/collection.d.ts +17 -2
- package/types/component.d.ts +10 -34
- package/types/misc.d.ts +13 -0
- package/types/router.d.ts +30 -1
- package/cli/commands/dev.old.js +0 -520
- package/cli/scaffold/scripts/components/home.js +0 -137
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.css +0 -0
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.html +0 -0
- /package/cli/scaffold/{scripts → app}/components/contacts/contacts.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/counter.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/not-found.js +0 -0
- /package/cli/scaffold/{scripts → app}/components/todos.js +0 -0
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* cli/commands/dev/devtools/index.js — DevTools HTML assembler
|
|
3
|
+
*
|
|
4
|
+
* Reads CSS, HTML, and JS partials from this folder and concatenates them
|
|
5
|
+
* into a single self-contained HTML page served at /_devtools.
|
|
6
|
+
*
|
|
7
|
+
* Communication:
|
|
8
|
+
* - window.opener: direct DOM access (same-origin popup)
|
|
9
|
+
* - BroadcastChannel('__zq_devtools'): cross-tab fallback
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
'use strict';
|
|
13
|
+
|
|
14
|
+
const { readFileSync } = require('fs');
|
|
15
|
+
const { join } = require('path');
|
|
16
|
+
|
|
17
|
+
const dir = __dirname;
|
|
18
|
+
const read = (f) => readFileSync(join(dir, f), 'utf8');
|
|
19
|
+
|
|
20
|
+
const css = read('styles.css');
|
|
21
|
+
const html = read('panel.html');
|
|
22
|
+
|
|
23
|
+
const jsFiles = [
|
|
24
|
+
'js/core.js',
|
|
25
|
+
'js/tabs.js',
|
|
26
|
+
'js/source.js',
|
|
27
|
+
'js/elements.js',
|
|
28
|
+
'js/network.js',
|
|
29
|
+
'js/components.js',
|
|
30
|
+
'js/performance.js',
|
|
31
|
+
'js/router.js',
|
|
32
|
+
'js/stats.js'
|
|
33
|
+
];
|
|
34
|
+
|
|
35
|
+
const js = jsFiles.map(read).join('\n\n');
|
|
36
|
+
|
|
37
|
+
module.exports = `<!DOCTYPE html>
|
|
38
|
+
<html lang="en">
|
|
39
|
+
<head>
|
|
40
|
+
<meta charset="UTF-8">
|
|
41
|
+
<meta name="viewport" content="width=device-width,initial-scale=1">
|
|
42
|
+
<title>zQuery DevTools</title>
|
|
43
|
+
<style>
|
|
44
|
+
${css}
|
|
45
|
+
</style>
|
|
46
|
+
</head>
|
|
47
|
+
<body>
|
|
48
|
+
${html}
|
|
49
|
+
<script>
|
|
50
|
+
(function() {
|
|
51
|
+
'use strict';
|
|
52
|
+
${js}
|
|
53
|
+
})();
|
|
54
|
+
</script>
|
|
55
|
+
</body>
|
|
56
|
+
</html>`;
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
// ===================================================================
|
|
2
|
+
// Components
|
|
3
|
+
// ===================================================================
|
|
4
|
+
function renderComponents() {
|
|
5
|
+
var list = document.getElementById('comp-list');
|
|
6
|
+
if (!targetWin || !targetWin.$) {
|
|
7
|
+
list.innerHTML = '<div class="empty-state">Waiting for zQuery to load...</div>';
|
|
8
|
+
return;
|
|
9
|
+
}
|
|
10
|
+
var $ = targetWin.$;
|
|
11
|
+
var html = '';
|
|
12
|
+
|
|
13
|
+
// Iterate mounted components if components registry exists
|
|
14
|
+
if ($ && typeof $.components === 'function') {
|
|
15
|
+
var names = Object.keys($.components());
|
|
16
|
+
if (!names.length) {
|
|
17
|
+
list.innerHTML = '<div class="empty-state">No components registered</div>';
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
for (var n = 0; n < names.length; n++) {
|
|
21
|
+
var name = names[n];
|
|
22
|
+
// Find mounted instances (exclude scoped <style> tags that carry data-zq-component)
|
|
23
|
+
var hosts = targetDoc.querySelectorAll(name + ':not(style)');
|
|
24
|
+
html += '<div class="comp-card">';
|
|
25
|
+
html += '<div class="comp-name"><' + name + '></div>';
|
|
26
|
+
html += '<div class="comp-host">' + hosts.length + ' instance' + (hosts.length !== 1 ? 's' : '') + ' mounted</div>';
|
|
27
|
+
hosts.forEach(function(host) {
|
|
28
|
+
try {
|
|
29
|
+
var inst = $.getInstance(host);
|
|
30
|
+
if (inst && inst.state) {
|
|
31
|
+
html += '<div class="comp-state">';
|
|
32
|
+
var keys = Object.keys(inst.state);
|
|
33
|
+
for (var k = 0; k < keys.length; k++) {
|
|
34
|
+
var key = keys[k];
|
|
35
|
+
var val = inst.state[key];
|
|
36
|
+
var display = typeof val === 'object' ? JSON.stringify(val) : String(val);
|
|
37
|
+
if (display.length > 80) display = display.slice(0, 80) + '...';
|
|
38
|
+
html += '<div class="detail-row"><span class="comp-state-key">' + esc(key) + '</span><span class="comp-state-val">' + esc(display) + '</span></div>';
|
|
39
|
+
}
|
|
40
|
+
html += '</div>';
|
|
41
|
+
}
|
|
42
|
+
} catch(e) {}
|
|
43
|
+
});
|
|
44
|
+
html += '</div>';
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
if (!html) html = '<div class="empty-state">No components found</div>';
|
|
48
|
+
list.innerHTML = html;
|
|
49
|
+
}
|
|
@@ -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
|
+
}
|