zero-query 1.0.9 → 1.2.0
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/LICENSE +21 -21
- package/README.md +2 -0
- package/cli/args.js +33 -33
- package/cli/commands/build-api.js +443 -0
- package/cli/commands/build.js +254 -216
- package/cli/commands/bundle.js +1228 -1183
- package/cli/commands/create.js +137 -121
- package/cli/commands/dev/devtools/index.js +56 -56
- package/cli/commands/dev/devtools/js/components.js +49 -49
- package/cli/commands/dev/devtools/js/core.js +423 -423
- package/cli/commands/dev/devtools/js/elements.js +421 -421
- package/cli/commands/dev/devtools/js/network.js +166 -166
- package/cli/commands/dev/devtools/js/performance.js +73 -73
- package/cli/commands/dev/devtools/js/router.js +105 -105
- package/cli/commands/dev/devtools/js/source.js +132 -132
- package/cli/commands/dev/devtools/js/stats.js +35 -35
- package/cli/commands/dev/devtools/js/tabs.js +79 -79
- package/cli/commands/dev/devtools/panel.html +95 -95
- package/cli/commands/dev/devtools/styles.css +244 -244
- package/cli/commands/dev/index.js +107 -107
- package/cli/commands/dev/logger.js +75 -75
- package/cli/commands/dev/overlay.js +858 -858
- package/cli/commands/dev/server.js +220 -167
- package/cli/commands/dev/validator.js +94 -94
- package/cli/commands/dev/watcher.js +172 -172
- package/cli/help.js +114 -112
- package/cli/index.js +52 -52
- package/cli/scaffold/default/LICENSE +21 -21
- package/cli/scaffold/default/app/app.js +207 -207
- package/cli/scaffold/default/app/components/about.js +201 -201
- package/cli/scaffold/default/app/components/api-demo.js +143 -143
- package/cli/scaffold/default/app/components/contact-card.js +231 -231
- package/cli/scaffold/default/app/components/contacts/contacts.css +706 -706
- package/cli/scaffold/default/app/components/contacts/contacts.html +200 -200
- package/cli/scaffold/default/app/components/contacts/contacts.js +196 -196
- package/cli/scaffold/default/app/components/counter.js +127 -127
- package/cli/scaffold/default/app/components/home.js +249 -249
- package/cli/scaffold/default/app/components/not-found.js +16 -16
- package/cli/scaffold/default/app/components/playground/playground.css +115 -115
- package/cli/scaffold/default/app/components/playground/playground.html +161 -161
- package/cli/scaffold/default/app/components/playground/playground.js +116 -116
- package/cli/scaffold/default/app/components/todos.js +225 -225
- package/cli/scaffold/default/app/components/toolkit/toolkit.css +97 -97
- package/cli/scaffold/default/app/components/toolkit/toolkit.html +146 -146
- package/cli/scaffold/default/app/components/toolkit/toolkit.js +280 -280
- package/cli/scaffold/default/app/routes.js +15 -15
- package/cli/scaffold/default/app/store.js +101 -101
- package/cli/scaffold/default/global.css +552 -552
- package/cli/scaffold/default/index.html +99 -99
- package/cli/scaffold/minimal/app/app.js +85 -85
- package/cli/scaffold/minimal/app/components/about.js +68 -68
- package/cli/scaffold/minimal/app/components/counter.js +122 -122
- package/cli/scaffold/minimal/app/components/home.js +68 -68
- package/cli/scaffold/minimal/app/components/not-found.js +16 -16
- package/cli/scaffold/minimal/app/routes.js +9 -9
- package/cli/scaffold/minimal/app/store.js +36 -36
- package/cli/scaffold/minimal/global.css +300 -300
- package/cli/scaffold/minimal/index.html +44 -44
- package/cli/scaffold/ssr/app/app.js +41 -41
- package/cli/scaffold/ssr/app/components/about.js +55 -55
- package/cli/scaffold/ssr/app/components/blog/index.js +65 -65
- package/cli/scaffold/ssr/app/components/blog/post.js +86 -86
- package/cli/scaffold/ssr/app/components/home.js +37 -37
- package/cli/scaffold/ssr/app/components/not-found.js +15 -15
- package/cli/scaffold/ssr/app/routes.js +8 -8
- package/cli/scaffold/ssr/global.css +228 -228
- package/cli/scaffold/ssr/index.html +37 -37
- package/cli/scaffold/ssr/package.json +8 -8
- package/cli/scaffold/ssr/server/data/posts.js +144 -144
- package/cli/scaffold/ssr/server/index.js +213 -213
- package/cli/scaffold/webrtc/app/app.js +11 -0
- package/cli/scaffold/webrtc/app/components/video-room.js +295 -0
- package/cli/scaffold/webrtc/app/lib/room.js +252 -0
- package/cli/scaffold/webrtc/assets/.gitkeep +0 -0
- package/cli/scaffold/webrtc/global.css +250 -0
- package/cli/scaffold/webrtc/index.html +21 -0
- package/cli/utils.js +305 -287
- package/dist/API.md +7264 -0
- package/dist/zquery.dist.zip +0 -0
- package/dist/zquery.js +10313 -6252
- package/dist/zquery.min.js +8 -601
- package/index.d.ts +570 -365
- package/index.js +311 -232
- package/package.json +76 -69
- package/src/component.js +1709 -1454
- package/src/core.js +921 -921
- package/src/diff.js +497 -497
- package/src/errors.js +209 -209
- package/src/expression.js +922 -922
- package/src/http.js +242 -242
- package/src/package.json +1 -1
- package/src/reactive.js +255 -254
- package/src/router.js +843 -773
- package/src/ssr.js +418 -418
- package/src/store.js +318 -272
- package/src/utils.js +515 -515
- package/src/webrtc/e2ee.js +351 -0
- package/src/webrtc/errors.js +116 -0
- package/src/webrtc/ice.js +301 -0
- package/src/webrtc/index.js +131 -0
- package/src/webrtc/joinToken.js +119 -0
- package/src/webrtc/observe.js +172 -0
- package/src/webrtc/peer.js +351 -0
- package/src/webrtc/reactive.js +268 -0
- package/src/webrtc/room.js +625 -0
- package/src/webrtc/sdp.js +302 -0
- package/src/webrtc/sfu/index.js +43 -0
- package/src/webrtc/sfu/livekit.js +131 -0
- package/src/webrtc/sfu/mediasoup.js +150 -0
- package/src/webrtc/signaling.js +373 -0
- package/src/webrtc/turn.js +237 -0
- package/tests/_helpers/webrtcFakes.js +289 -0
- package/tests/audit.test.js +4158 -4158
- package/tests/cli.test.js +1136 -1023
- package/tests/compare.test.js +497 -0
- package/tests/component.test.js +3969 -3938
- package/tests/core.test.js +1910 -1910
- package/tests/dev-server.test.js +489 -0
- package/tests/diff.test.js +1416 -1416
- package/tests/docs.test.js +1664 -0
- package/tests/electron-features.test.js +864 -0
- package/tests/errors.test.js +619 -619
- package/tests/expression.test.js +1056 -1056
- package/tests/http.test.js +648 -648
- package/tests/reactive.test.js +819 -819
- package/tests/router.test.js +2327 -2327
- package/tests/ssr.test.js +870 -870
- package/tests/store.test.js +830 -830
- package/tests/test-minifier.js +153 -153
- package/tests/test-ssr.js +27 -27
- package/tests/utils.test.js +1377 -1377
- package/tests/webrtc/e2ee.test.js +283 -0
- package/tests/webrtc/ice.test.js +202 -0
- package/tests/webrtc/joinToken.test.js +89 -0
- package/tests/webrtc/observe.test.js +111 -0
- package/tests/webrtc/peer.test.js +373 -0
- package/tests/webrtc/reactive.test.js +235 -0
- package/tests/webrtc/room.test.js +406 -0
- package/tests/webrtc/sdp.test.js +151 -0
- package/tests/webrtc/sfu-livekit.test.js +119 -0
- package/tests/webrtc/sfu.test.js +160 -0
- package/tests/webrtc/signaling.test.js +251 -0
- package/tests/webrtc/turn.test.js +256 -0
- package/types/collection.d.ts +383 -383
- package/types/component.d.ts +186 -186
- package/types/errors.d.ts +135 -135
- package/types/http.d.ts +92 -92
- package/types/misc.d.ts +201 -201
- package/types/reactive.d.ts +98 -98
- package/types/router.d.ts +190 -190
- package/types/ssr.d.ts +102 -102
- package/types/store.d.ts +146 -145
- package/types/utils.d.ts +245 -245
- package/types/webrtc.d.ts +653 -0
|
@@ -1,421 +1,421 @@
|
|
|
1
|
-
// ===================================================================
|
|
2
|
-
// DOM Tree
|
|
3
|
-
// ===================================================================
|
|
4
|
-
function getNodePath(node) {
|
|
5
|
-
var parts = [];
|
|
6
|
-
var cur = node;
|
|
7
|
-
while (cur && cur.nodeType === 1) {
|
|
8
|
-
var tag = cur.tagName.toLowerCase();
|
|
9
|
-
var idx = 0;
|
|
10
|
-
var sib = cur.previousElementSibling;
|
|
11
|
-
while (sib) { if (sib.tagName === cur.tagName) idx++; sib = sib.previousElementSibling; }
|
|
12
|
-
parts.unshift(tag + (idx ? ':' + idx : ''));
|
|
13
|
-
cur = cur.parentElement;
|
|
14
|
-
}
|
|
15
|
-
return parts.join('>');
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
function refreshComponentNames() {
|
|
19
|
-
componentNames = {};
|
|
20
|
-
try {
|
|
21
|
-
if (targetWin && targetWin.$ && typeof targetWin.$.components === 'function') {
|
|
22
|
-
var reg = targetWin.$.components();
|
|
23
|
-
var keys = Object.keys(reg);
|
|
24
|
-
for (var i = 0; i < keys.length; i++) componentNames[keys[i].toLowerCase()] = true;
|
|
25
|
-
}
|
|
26
|
-
} catch(e) {}
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function buildDOMTree() {
|
|
30
|
-
refreshComponentNames();
|
|
31
|
-
var tree = document.getElementById('dom-tree');
|
|
32
|
-
tree.innerHTML = '';
|
|
33
|
-
if (!targetDoc) return;
|
|
34
|
-
var root = targetDoc.documentElement;
|
|
35
|
-
tree.appendChild(buildNode(root, 0));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function buildNode(node, depth) {
|
|
39
|
-
var wrap = document.createElement('div');
|
|
40
|
-
wrap.className = 'tree-node';
|
|
41
|
-
|
|
42
|
-
if (node.nodeType === 3) {
|
|
43
|
-
var text = node.textContent.trim();
|
|
44
|
-
if (!text) return wrap;
|
|
45
|
-
var row = document.createElement('div');
|
|
46
|
-
row.className = 'tree-row';
|
|
47
|
-
var truncated = text.length > 60;
|
|
48
|
-
row.innerHTML = '<span class="tree-toggle leaf"></span><span class="tree-text">' +
|
|
49
|
-
esc(truncated ? text.slice(0, 60) + '...' : text) + '</span>' +
|
|
50
|
-
(truncated ? '<span class="tree-peek" title="View full text">👁</span>' : '');
|
|
51
|
-
if (truncated) {
|
|
52
|
-
row.__sourceText = text;
|
|
53
|
-
row.__sourceLabel = 'Text content';
|
|
54
|
-
}
|
|
55
|
-
wrap.appendChild(row);
|
|
56
|
-
return wrap;
|
|
57
|
-
}
|
|
58
|
-
if (node.nodeType === 8) {
|
|
59
|
-
var row = document.createElement('div');
|
|
60
|
-
row.className = 'tree-row';
|
|
61
|
-
row.innerHTML = '<span class="tree-toggle leaf"></span><span class="tree-comment"><!-- ' +
|
|
62
|
-
esc(node.textContent.trim().slice(0, 40)) + ' --></span>';
|
|
63
|
-
wrap.appendChild(row);
|
|
64
|
-
return wrap;
|
|
65
|
-
}
|
|
66
|
-
if (node.nodeType !== 1) return wrap;
|
|
67
|
-
|
|
68
|
-
// Skip devtools-injected overlay
|
|
69
|
-
if (node.id === '__zq_error_overlay' || node.id === '__zq_devbar') return wrap;
|
|
70
|
-
|
|
71
|
-
var tag = node.tagName.toLowerCase();
|
|
72
|
-
var nodePath = getNodePath(node);
|
|
73
|
-
var hasChildren = false;
|
|
74
|
-
var childNodes = node.childNodes;
|
|
75
|
-
// style/script content is shown inline via the peek button - treat as leaf
|
|
76
|
-
if (tag !== 'style' && tag !== 'script') {
|
|
77
|
-
for (var i = 0; i < childNodes.length; i++) {
|
|
78
|
-
var cn = childNodes[i];
|
|
79
|
-
if (cn.nodeType === 1 || (cn.nodeType === 3 && cn.textContent.trim())) { hasChildren = true; break; }
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
// Detect special nodes
|
|
84
|
-
var isScopedStyle = tag === 'style' && node.hasAttribute('data-zq-scope');
|
|
85
|
-
var isComponent = !isScopedStyle && (componentNames[tag] || node.hasAttribute('data-zq-component'));
|
|
86
|
-
var isRouterView = tag === 'div' && (node.id === 'app' || node.hasAttribute('data-zq-router'));
|
|
87
|
-
var isEntryPoint = tag === 'body' || isRouterView;
|
|
88
|
-
|
|
89
|
-
// Build row
|
|
90
|
-
var row = document.createElement('div');
|
|
91
|
-
row.className = 'tree-row';
|
|
92
|
-
row.__targetNode = node;
|
|
93
|
-
|
|
94
|
-
var toggleCls = hasChildren ? 'tree-toggle' : 'tree-toggle leaf';
|
|
95
|
-
var html = '<span class="' + toggleCls + '">▶</span>';
|
|
96
|
-
html += '<span class="tree-tag"><' + tag + '</span>';
|
|
97
|
-
|
|
98
|
-
// Show key attributes inline (skip internal zq attrs on scoped styles)
|
|
99
|
-
var attrs = node.attributes;
|
|
100
|
-
var shown = 0;
|
|
101
|
-
for (var i = 0; i < attrs.length && shown < 4; i++) {
|
|
102
|
-
var a = attrs[i];
|
|
103
|
-
if (a.name === 'style' || a.name === 'class' && a.value.length > 40) continue;
|
|
104
|
-
if (isScopedStyle && /^data-zq-/.test(a.name)) continue;
|
|
105
|
-
html += ' <span class="tree-attr-name">' + esc(a.name) + '</span>';
|
|
106
|
-
if (a.value) html += '=<span class="tree-attr-val">"' + esc(a.value.length > 30 ? a.value.slice(0, 30) + '...' : a.value) + '"</span>';
|
|
107
|
-
shown++;
|
|
108
|
-
}
|
|
109
|
-
html += '<span class="tree-tag">></span>';
|
|
110
|
-
|
|
111
|
-
// Badges for components, router, entry-point, scoped css
|
|
112
|
-
if (isScopedStyle) {
|
|
113
|
-
var scopeComp = node.getAttribute('data-zq-component');
|
|
114
|
-
html += '<span class="tree-badge scoped">scoped css' + (scopeComp ? ' · ' + esc(scopeComp) : '') + '</span>';
|
|
115
|
-
} else if (isComponent) html += '<span class="tree-badge comp">component</span>';
|
|
116
|
-
if (isRouterView) html += '<span class="tree-badge router">router</span>';
|
|
117
|
-
else if (isEntryPoint) html += '<span class="tree-badge entry">entry</span>';
|
|
118
|
-
|
|
119
|
-
// Expand / collapse action buttons (visible on hover)
|
|
120
|
-
if (hasChildren) {
|
|
121
|
-
html += '<span class="tree-actions">';
|
|
122
|
-
html += '<button class="tree-action" data-act="expand" title="Expand all">+</button>';
|
|
123
|
-
html += '<button class="tree-action" data-act="collapse" title="Collapse all">−</button>';
|
|
124
|
-
html += '</span>';
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
// Inline text for leaf elements; add peek button for style/script with any content, or long text
|
|
128
|
-
var hasInlineContent = false;
|
|
129
|
-
if (tag === 'style' || tag === 'script') {
|
|
130
|
-
var fullText = node.textContent.trim();
|
|
131
|
-
if (fullText) {
|
|
132
|
-
var preview = fullText.length > 50 ? fullText.slice(0, 50) + '...' : fullText;
|
|
133
|
-
html += '<span class="tree-text">' + esc(preview) + '</span>';
|
|
134
|
-
html += '<span class="tree-peek" title="View full source">👁</span>';
|
|
135
|
-
hasInlineContent = true;
|
|
136
|
-
}
|
|
137
|
-
} else if (!hasChildren || (childNodes.length === 1 && childNodes[0].nodeType === 3)) {
|
|
138
|
-
var text = node.textContent.trim();
|
|
139
|
-
if (text && text.length < 60) {
|
|
140
|
-
html += '<span class="tree-text">' + esc(text) + '</span>';
|
|
141
|
-
html += '<span class="tree-tag"></' + tag + '></span>';
|
|
142
|
-
} else if (text && text.length >= 60) {
|
|
143
|
-
html += '<span class="tree-text">' + esc(text.slice(0, 50) + '...') + '</span>';
|
|
144
|
-
html += '<span class="tree-peek" title="View full text">👁</span>';
|
|
145
|
-
hasInlineContent = true;
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
row.innerHTML = html;
|
|
150
|
-
// Attach source data for peek overlay
|
|
151
|
-
if (hasInlineContent) {
|
|
152
|
-
row.__sourceText = node.textContent.trim();
|
|
153
|
-
row.__sourceIsCSS = (tag === 'style');
|
|
154
|
-
if (tag === 'style') {
|
|
155
|
-
var scopeComp = node.getAttribute('data-zq-component');
|
|
156
|
-
row.__sourceLabel = '<span class="tree-tag"><style></span>' + (scopeComp ? ' <span class="tree-badge scoped">scoped css \u00b7 ' + esc(scopeComp) + '</span>' : '');
|
|
157
|
-
} else if (tag === 'script') {
|
|
158
|
-
row.__sourceLabel = '<span class="tree-tag"><script></span>';
|
|
159
|
-
} else {
|
|
160
|
-
row.__sourceLabel = '<span class="tree-tag"><' + tag + '></span> text content';
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
wrap.appendChild(row);
|
|
164
|
-
|
|
165
|
-
// Children container
|
|
166
|
-
if (hasChildren) {
|
|
167
|
-
var childDiv = document.createElement('div');
|
|
168
|
-
childDiv.className = 'tree-children';
|
|
169
|
-
|
|
170
|
-
// Restore expanded state or auto-expand; entry/router always expand, changed paths auto-expand (capped at depth 5)
|
|
171
|
-
var alwaysOpen = isEntryPoint || isRouterView;
|
|
172
|
-
var changedNow = changedPaths[nodePath] && depth < 6;
|
|
173
|
-
var isExpanded = alwaysOpen || (expandedPaths.hasOwnProperty(nodePath) ? expandedPaths[nodePath] : (depth < 3 || changedNow));
|
|
174
|
-
if (isExpanded) {
|
|
175
|
-
childDiv.classList.add('open');
|
|
176
|
-
row.querySelector('.tree-toggle').classList.add('open');
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Flash only actual mutation targets, not ancestors
|
|
180
|
-
if (mutatedPaths[nodePath]) {
|
|
181
|
-
row.classList.add('morph-changed');
|
|
182
|
-
row.setAttribute('data-changed', '1');
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
for (var i = 0; i < childNodes.length; i++) {
|
|
186
|
-
var child = buildNode(childNodes[i], depth + 1);
|
|
187
|
-
if (child.innerHTML) childDiv.appendChild(child);
|
|
188
|
-
}
|
|
189
|
-
wrap.appendChild(childDiv);
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
// Toggle click (on the arrow only)
|
|
193
|
-
var toggleEl = row.querySelector('.tree-toggle');
|
|
194
|
-
if (hasChildren) {
|
|
195
|
-
toggleEl.addEventListener('click', function(e) {
|
|
196
|
-
e.stopPropagation();
|
|
197
|
-
var children = row.nextElementSibling;
|
|
198
|
-
if (children && children.classList.contains('tree-children')) {
|
|
199
|
-
var opening = !children.classList.contains('open');
|
|
200
|
-
children.classList.toggle('open');
|
|
201
|
-
toggleEl.classList.toggle('open');
|
|
202
|
-
expandedPaths[nodePath] = opening;
|
|
203
|
-
}
|
|
204
|
-
});
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
// Expand / collapse action buttons
|
|
208
|
-
row.addEventListener('click', function(e) {
|
|
209
|
-
var actBtn = e.target.closest('.tree-action');
|
|
210
|
-
if (!actBtn) return;
|
|
211
|
-
e.stopPropagation();
|
|
212
|
-
var childContainer = row.nextElementSibling;
|
|
213
|
-
if (!childContainer || !childContainer.classList.contains('tree-children')) return;
|
|
214
|
-
var opening = actBtn.dataset.act === 'expand';
|
|
215
|
-
if (opening) { childContainer.classList.add('open'); toggleEl.classList.add('open'); }
|
|
216
|
-
else { childContainer.classList.remove('open'); toggleEl.classList.remove('open'); }
|
|
217
|
-
expandedPaths[nodePath] = opening;
|
|
218
|
-
var maxDepth = opening ? 4 : Infinity;
|
|
219
|
-
function toggleNested(container, level) {
|
|
220
|
-
if (level >= maxDepth) return;
|
|
221
|
-
var children = container.children;
|
|
222
|
-
for (var j = 0; j < children.length; j++) {
|
|
223
|
-
var rowEl = children[j].querySelector(':scope > .tree-row');
|
|
224
|
-
var nested = children[j].querySelector(':scope > .tree-children');
|
|
225
|
-
if (nested) {
|
|
226
|
-
if (opening) nested.classList.add('open');
|
|
227
|
-
else nested.classList.remove('open');
|
|
228
|
-
var arrow = nested.previousElementSibling ? nested.previousElementSibling.querySelector('.tree-toggle') : null;
|
|
229
|
-
if (arrow) { if (opening) arrow.classList.add('open'); else arrow.classList.remove('open'); }
|
|
230
|
-
// Persist state so it survives tree rebuilds from live DOM mutations
|
|
231
|
-
if (rowEl && rowEl.__targetNode) {
|
|
232
|
-
expandedPaths[getNodePath(rowEl.__targetNode)] = opening;
|
|
233
|
-
}
|
|
234
|
-
toggleNested(nested, level + 1);
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
toggleNested(childContainer, 0);
|
|
239
|
-
});
|
|
240
|
-
|
|
241
|
-
// Row click - select element (not toggle)
|
|
242
|
-
row.addEventListener('click', function(e) {
|
|
243
|
-
// Don't select when clicking toggle arrow, badge, action buttons, or peek
|
|
244
|
-
if (e.target.closest('.tree-toggle') || e.target.closest('.tree-badge') || e.target.closest('.tree-action') || e.target.closest('.tree-peek')) return;
|
|
245
|
-
document.querySelectorAll('.tree-row.selected').forEach(function(r) { r.classList.remove('selected'); });
|
|
246
|
-
row.classList.add('selected');
|
|
247
|
-
selectedEl = node;
|
|
248
|
-
showDetail(node);
|
|
249
|
-
try { highlightElement(node); } catch(err) {}
|
|
250
|
-
});
|
|
251
|
-
|
|
252
|
-
// Double-click to expand/collapse
|
|
253
|
-
row.addEventListener('dblclick', function(e) {
|
|
254
|
-
if (!hasChildren) return;
|
|
255
|
-
e.preventDefault();
|
|
256
|
-
var children = row.nextElementSibling;
|
|
257
|
-
if (children && children.classList.contains('tree-children')) {
|
|
258
|
-
var opening = !children.classList.contains('open');
|
|
259
|
-
children.classList.toggle('open');
|
|
260
|
-
toggleEl.classList.toggle('open');
|
|
261
|
-
expandedPaths[nodePath] = opening;
|
|
262
|
-
}
|
|
263
|
-
});
|
|
264
|
-
|
|
265
|
-
return wrap;
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
// ===================================================================
|
|
269
|
-
// Element highlighting
|
|
270
|
-
// ===================================================================
|
|
271
|
-
function highlightElement(el) {
|
|
272
|
-
if (!targetWin || !targetDoc) return;
|
|
273
|
-
// Remove previous highlight
|
|
274
|
-
var prev = targetDoc.getElementById('__zq_highlight');
|
|
275
|
-
if (prev) prev.remove();
|
|
276
|
-
|
|
277
|
-
var rect = el.getBoundingClientRect();
|
|
278
|
-
if (!rect.width && !rect.height) return;
|
|
279
|
-
|
|
280
|
-
var box = targetDoc.createElement('div');
|
|
281
|
-
box.id = '__zq_highlight';
|
|
282
|
-
box.style.cssText = 'position:fixed;z-index:2147483645;pointer-events:none;' +
|
|
283
|
-
'border:2px solid rgba(88,166,255,0.8);background:rgba(88,166,255,0.1);' +
|
|
284
|
-
'transition:all .15s ease;' +
|
|
285
|
-
'top:' + rect.top + 'px;left:' + rect.left + 'px;' +
|
|
286
|
-
'width:' + rect.width + 'px;height:' + rect.height + 'px;';
|
|
287
|
-
targetDoc.body.appendChild(box);
|
|
288
|
-
setTimeout(function() { if (box.parentNode) box.remove(); }, 2000);
|
|
289
|
-
}
|
|
290
|
-
|
|
291
|
-
// ===================================================================
|
|
292
|
-
// Detail panel
|
|
293
|
-
// ===================================================================
|
|
294
|
-
function showDetail(node) {
|
|
295
|
-
var detail = document.getElementById('dom-detail');
|
|
296
|
-
detail.style.display = 'block';
|
|
297
|
-
var html = '<button class="detail-close" id="detail-close" title="Close">×</button>';
|
|
298
|
-
|
|
299
|
-
// Tag and ID
|
|
300
|
-
html += '<div class="detail-section"><h4>Element</h4>';
|
|
301
|
-
html += '<div class="detail-row"><span class="detail-key">Tag</span><span class="detail-val">' + node.tagName.toLowerCase() + '</span></div>';
|
|
302
|
-
if (node.id) html += '<div class="detail-row"><span class="detail-key">ID</span><span class="detail-val">' + esc(node.id) + '</span></div>';
|
|
303
|
-
if (node.className) html += '<div class="detail-row"><span class="detail-key">Classes</span><span class="detail-val">' + esc(node.className) + '</span></div>';
|
|
304
|
-
html += '</div>';
|
|
305
|
-
|
|
306
|
-
// Attributes
|
|
307
|
-
if (node.attributes.length) {
|
|
308
|
-
html += '<div class="detail-section"><h4>Attributes</h4>';
|
|
309
|
-
for (var i = 0; i < node.attributes.length; i++) {
|
|
310
|
-
var a = node.attributes[i];
|
|
311
|
-
var val = esc(a.value);
|
|
312
|
-
if (/^(src|href|action|data-src|poster|srcset)$/i.test(a.name) && a.value) {
|
|
313
|
-
val = '<a href="' + esc(a.value) + '" target="_blank" rel="noopener" title="Open in new tab">' + val + '</a>';
|
|
314
|
-
}
|
|
315
|
-
html += '<div class="detail-row"><span class="detail-key">' + esc(a.name) + '</span><span class="detail-val">' + val + '</span></div>';
|
|
316
|
-
}
|
|
317
|
-
html += '</div>';
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
// Dimensions
|
|
321
|
-
var rect = node.getBoundingClientRect();
|
|
322
|
-
html += '<div class="detail-section"><h4>Box Model</h4>';
|
|
323
|
-
html += '<div class="detail-row"><span class="detail-key">Size</span><span class="detail-val">' + Math.round(rect.width) + ' \u00d7 ' + Math.round(rect.height) + '</span></div>';
|
|
324
|
-
html += '<div class="detail-row"><span class="detail-key">Position</span><span class="detail-val">(' + Math.round(rect.left) + ', ' + Math.round(rect.top) + ')</span></div>';
|
|
325
|
-
html += '</div>';
|
|
326
|
-
|
|
327
|
-
// Component state
|
|
328
|
-
try {
|
|
329
|
-
if (targetWin.$ && targetWin.$.getInstance) {
|
|
330
|
-
var inst = targetWin.$.getInstance(node);
|
|
331
|
-
if (inst && inst.state) {
|
|
332
|
-
html += '<div class="detail-section"><h4>Component State</h4>';
|
|
333
|
-
var stateKeys = Object.keys(inst.state);
|
|
334
|
-
for (var i = 0; i < stateKeys.length; i++) {
|
|
335
|
-
var k = stateKeys[i];
|
|
336
|
-
var v = inst.state[k];
|
|
337
|
-
var display = typeof v === 'object' ? JSON.stringify(v, null, 1) : String(v);
|
|
338
|
-
html += '<div class="detail-row"><span class="detail-key">' + esc(k) + '</span><span class="detail-val">' + esc(display) + '</span></div>';
|
|
339
|
-
}
|
|
340
|
-
html += '</div>';
|
|
341
|
-
}
|
|
342
|
-
}
|
|
343
|
-
} catch(err) {}
|
|
344
|
-
|
|
345
|
-
detail.innerHTML = html;
|
|
346
|
-
document.getElementById('detail-close').addEventListener('click', function() {
|
|
347
|
-
detail.style.display = 'none';
|
|
348
|
-
document.querySelectorAll('.tree-row.selected').forEach(function(r) { r.classList.remove('selected'); });
|
|
349
|
-
selectedEl = null;
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// ===================================================================
|
|
354
|
-
// MutationObserver - watch target document for live DOM changes
|
|
355
|
-
// ===================================================================
|
|
356
|
-
function startObserver() {
|
|
357
|
-
if (!targetDoc || observer) return;
|
|
358
|
-
observer = new MutationObserver(function(mutations) {
|
|
359
|
-
// Collect paths of mutated nodes so we can auto-expand + highlight them
|
|
360
|
-
var dominated = false;
|
|
361
|
-
for (var i = 0; i < mutations.length; i++) {
|
|
362
|
-
var m = mutations[i];
|
|
363
|
-
var target = m.target.nodeType === 1 ? m.target : m.target.parentElement;
|
|
364
|
-
if (!target) continue;
|
|
365
|
-
// Skip mutations caused by the devtools highlight overlay
|
|
366
|
-
if (target.id === '__zq_highlight' || target.id === '__zq_error_overlay' || target.id === '__zq_devbar') continue;
|
|
367
|
-
// Skip virtual-scroll hydration/dehydration churn (docs lazy chunks)
|
|
368
|
-
if (target.classList && target.classList.contains('docs-lazy-chunk')) continue;
|
|
369
|
-
if (target.closest && target.closest('.docs-lazy-chunk:not(.hydrated)')) continue;
|
|
370
|
-
var isHighlightMutation = false;
|
|
371
|
-
if (m.addedNodes) { for (var k = 0; k < m.addedNodes.length; k++) { if (m.addedNodes[k].id === '__zq_highlight') { isHighlightMutation = true; break; } } }
|
|
372
|
-
if (!isHighlightMutation && m.removedNodes) { for (var k = 0; k < m.removedNodes.length; k++) { if (m.removedNodes[k].id === '__zq_highlight') { isHighlightMutation = true; break; } } }
|
|
373
|
-
if (isHighlightMutation) continue;
|
|
374
|
-
dominated = true;
|
|
375
|
-
var tp = getNodePath(target);
|
|
376
|
-
changedPaths[tp] = true;
|
|
377
|
-
mutatedPaths[tp] = true;
|
|
378
|
-
// Also mark added nodes
|
|
379
|
-
if (m.addedNodes) {
|
|
380
|
-
for (var j = 0; j < m.addedNodes.length; j++) {
|
|
381
|
-
if (m.addedNodes[j].nodeType === 1) {
|
|
382
|
-
var ap = getNodePath(m.addedNodes[j]);
|
|
383
|
-
changedPaths[ap] = true;
|
|
384
|
-
mutatedPaths[ap] = true;
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
// Mark ancestor paths so they auto-expand to reveal changes
|
|
389
|
-
var cur = target.parentElement;
|
|
390
|
-
while (cur && cur.nodeType === 1) {
|
|
391
|
-
var p = getNodePath(cur);
|
|
392
|
-
if (!expandedPaths.hasOwnProperty(p)) changedPaths[p] = true;
|
|
393
|
-
cur = cur.parentElement;
|
|
394
|
-
}
|
|
395
|
-
}
|
|
396
|
-
|
|
397
|
-
if (!dominated) return; // All mutations were from devtools highlight - skip rebuild
|
|
398
|
-
|
|
399
|
-
// Debounce tree rebuild
|
|
400
|
-
clearTimeout(startObserver._timer);
|
|
401
|
-
startObserver._timer = setTimeout(function() {
|
|
402
|
-
buildDOMTree();
|
|
403
|
-
updateStats();
|
|
404
|
-
|
|
405
|
-
// Scroll to deepest (last in DOM order) changed row
|
|
406
|
-
var allChanged = document.querySelectorAll('.tree-row[data-changed]');
|
|
407
|
-
if (allChanged.length) {
|
|
408
|
-
allChanged[allChanged.length - 1].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
409
|
-
}
|
|
410
|
-
// Clear changed paths after applying
|
|
411
|
-
changedPaths = {};
|
|
412
|
-
mutatedPaths = {};
|
|
413
|
-
}, 300);
|
|
414
|
-
});
|
|
415
|
-
observer.observe(targetDoc.documentElement, {
|
|
416
|
-
childList: true,
|
|
417
|
-
subtree: true,
|
|
418
|
-
attributes: true,
|
|
419
|
-
characterData: true
|
|
420
|
-
});
|
|
421
|
-
}
|
|
1
|
+
// ===================================================================
|
|
2
|
+
// DOM Tree
|
|
3
|
+
// ===================================================================
|
|
4
|
+
function getNodePath(node) {
|
|
5
|
+
var parts = [];
|
|
6
|
+
var cur = node;
|
|
7
|
+
while (cur && cur.nodeType === 1) {
|
|
8
|
+
var tag = cur.tagName.toLowerCase();
|
|
9
|
+
var idx = 0;
|
|
10
|
+
var sib = cur.previousElementSibling;
|
|
11
|
+
while (sib) { if (sib.tagName === cur.tagName) idx++; sib = sib.previousElementSibling; }
|
|
12
|
+
parts.unshift(tag + (idx ? ':' + idx : ''));
|
|
13
|
+
cur = cur.parentElement;
|
|
14
|
+
}
|
|
15
|
+
return parts.join('>');
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function refreshComponentNames() {
|
|
19
|
+
componentNames = {};
|
|
20
|
+
try {
|
|
21
|
+
if (targetWin && targetWin.$ && typeof targetWin.$.components === 'function') {
|
|
22
|
+
var reg = targetWin.$.components();
|
|
23
|
+
var keys = Object.keys(reg);
|
|
24
|
+
for (var i = 0; i < keys.length; i++) componentNames[keys[i].toLowerCase()] = true;
|
|
25
|
+
}
|
|
26
|
+
} catch(e) {}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildDOMTree() {
|
|
30
|
+
refreshComponentNames();
|
|
31
|
+
var tree = document.getElementById('dom-tree');
|
|
32
|
+
tree.innerHTML = '';
|
|
33
|
+
if (!targetDoc) return;
|
|
34
|
+
var root = targetDoc.documentElement;
|
|
35
|
+
tree.appendChild(buildNode(root, 0));
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function buildNode(node, depth) {
|
|
39
|
+
var wrap = document.createElement('div');
|
|
40
|
+
wrap.className = 'tree-node';
|
|
41
|
+
|
|
42
|
+
if (node.nodeType === 3) {
|
|
43
|
+
var text = node.textContent.trim();
|
|
44
|
+
if (!text) return wrap;
|
|
45
|
+
var row = document.createElement('div');
|
|
46
|
+
row.className = 'tree-row';
|
|
47
|
+
var truncated = text.length > 60;
|
|
48
|
+
row.innerHTML = '<span class="tree-toggle leaf"></span><span class="tree-text">' +
|
|
49
|
+
esc(truncated ? text.slice(0, 60) + '...' : text) + '</span>' +
|
|
50
|
+
(truncated ? '<span class="tree-peek" title="View full text">👁</span>' : '');
|
|
51
|
+
if (truncated) {
|
|
52
|
+
row.__sourceText = text;
|
|
53
|
+
row.__sourceLabel = 'Text content';
|
|
54
|
+
}
|
|
55
|
+
wrap.appendChild(row);
|
|
56
|
+
return wrap;
|
|
57
|
+
}
|
|
58
|
+
if (node.nodeType === 8) {
|
|
59
|
+
var row = document.createElement('div');
|
|
60
|
+
row.className = 'tree-row';
|
|
61
|
+
row.innerHTML = '<span class="tree-toggle leaf"></span><span class="tree-comment"><!-- ' +
|
|
62
|
+
esc(node.textContent.trim().slice(0, 40)) + ' --></span>';
|
|
63
|
+
wrap.appendChild(row);
|
|
64
|
+
return wrap;
|
|
65
|
+
}
|
|
66
|
+
if (node.nodeType !== 1) return wrap;
|
|
67
|
+
|
|
68
|
+
// Skip devtools-injected overlay
|
|
69
|
+
if (node.id === '__zq_error_overlay' || node.id === '__zq_devbar') return wrap;
|
|
70
|
+
|
|
71
|
+
var tag = node.tagName.toLowerCase();
|
|
72
|
+
var nodePath = getNodePath(node);
|
|
73
|
+
var hasChildren = false;
|
|
74
|
+
var childNodes = node.childNodes;
|
|
75
|
+
// style/script content is shown inline via the peek button - treat as leaf
|
|
76
|
+
if (tag !== 'style' && tag !== 'script') {
|
|
77
|
+
for (var i = 0; i < childNodes.length; i++) {
|
|
78
|
+
var cn = childNodes[i];
|
|
79
|
+
if (cn.nodeType === 1 || (cn.nodeType === 3 && cn.textContent.trim())) { hasChildren = true; break; }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Detect special nodes
|
|
84
|
+
var isScopedStyle = tag === 'style' && node.hasAttribute('data-zq-scope');
|
|
85
|
+
var isComponent = !isScopedStyle && (componentNames[tag] || node.hasAttribute('data-zq-component'));
|
|
86
|
+
var isRouterView = tag === 'div' && (node.id === 'app' || node.hasAttribute('data-zq-router'));
|
|
87
|
+
var isEntryPoint = tag === 'body' || isRouterView;
|
|
88
|
+
|
|
89
|
+
// Build row
|
|
90
|
+
var row = document.createElement('div');
|
|
91
|
+
row.className = 'tree-row';
|
|
92
|
+
row.__targetNode = node;
|
|
93
|
+
|
|
94
|
+
var toggleCls = hasChildren ? 'tree-toggle' : 'tree-toggle leaf';
|
|
95
|
+
var html = '<span class="' + toggleCls + '">▶</span>';
|
|
96
|
+
html += '<span class="tree-tag"><' + tag + '</span>';
|
|
97
|
+
|
|
98
|
+
// Show key attributes inline (skip internal zq attrs on scoped styles)
|
|
99
|
+
var attrs = node.attributes;
|
|
100
|
+
var shown = 0;
|
|
101
|
+
for (var i = 0; i < attrs.length && shown < 4; i++) {
|
|
102
|
+
var a = attrs[i];
|
|
103
|
+
if (a.name === 'style' || a.name === 'class' && a.value.length > 40) continue;
|
|
104
|
+
if (isScopedStyle && /^data-zq-/.test(a.name)) continue;
|
|
105
|
+
html += ' <span class="tree-attr-name">' + esc(a.name) + '</span>';
|
|
106
|
+
if (a.value) html += '=<span class="tree-attr-val">"' + esc(a.value.length > 30 ? a.value.slice(0, 30) + '...' : a.value) + '"</span>';
|
|
107
|
+
shown++;
|
|
108
|
+
}
|
|
109
|
+
html += '<span class="tree-tag">></span>';
|
|
110
|
+
|
|
111
|
+
// Badges for components, router, entry-point, scoped css
|
|
112
|
+
if (isScopedStyle) {
|
|
113
|
+
var scopeComp = node.getAttribute('data-zq-component');
|
|
114
|
+
html += '<span class="tree-badge scoped">scoped css' + (scopeComp ? ' · ' + esc(scopeComp) : '') + '</span>';
|
|
115
|
+
} else if (isComponent) html += '<span class="tree-badge comp">component</span>';
|
|
116
|
+
if (isRouterView) html += '<span class="tree-badge router">router</span>';
|
|
117
|
+
else if (isEntryPoint) html += '<span class="tree-badge entry">entry</span>';
|
|
118
|
+
|
|
119
|
+
// Expand / collapse action buttons (visible on hover)
|
|
120
|
+
if (hasChildren) {
|
|
121
|
+
html += '<span class="tree-actions">';
|
|
122
|
+
html += '<button class="tree-action" data-act="expand" title="Expand all">+</button>';
|
|
123
|
+
html += '<button class="tree-action" data-act="collapse" title="Collapse all">−</button>';
|
|
124
|
+
html += '</span>';
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
// Inline text for leaf elements; add peek button for style/script with any content, or long text
|
|
128
|
+
var hasInlineContent = false;
|
|
129
|
+
if (tag === 'style' || tag === 'script') {
|
|
130
|
+
var fullText = node.textContent.trim();
|
|
131
|
+
if (fullText) {
|
|
132
|
+
var preview = fullText.length > 50 ? fullText.slice(0, 50) + '...' : fullText;
|
|
133
|
+
html += '<span class="tree-text">' + esc(preview) + '</span>';
|
|
134
|
+
html += '<span class="tree-peek" title="View full source">👁</span>';
|
|
135
|
+
hasInlineContent = true;
|
|
136
|
+
}
|
|
137
|
+
} else if (!hasChildren || (childNodes.length === 1 && childNodes[0].nodeType === 3)) {
|
|
138
|
+
var text = node.textContent.trim();
|
|
139
|
+
if (text && text.length < 60) {
|
|
140
|
+
html += '<span class="tree-text">' + esc(text) + '</span>';
|
|
141
|
+
html += '<span class="tree-tag"></' + tag + '></span>';
|
|
142
|
+
} else if (text && text.length >= 60) {
|
|
143
|
+
html += '<span class="tree-text">' + esc(text.slice(0, 50) + '...') + '</span>';
|
|
144
|
+
html += '<span class="tree-peek" title="View full text">👁</span>';
|
|
145
|
+
hasInlineContent = true;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
row.innerHTML = html;
|
|
150
|
+
// Attach source data for peek overlay
|
|
151
|
+
if (hasInlineContent) {
|
|
152
|
+
row.__sourceText = node.textContent.trim();
|
|
153
|
+
row.__sourceIsCSS = (tag === 'style');
|
|
154
|
+
if (tag === 'style') {
|
|
155
|
+
var scopeComp = node.getAttribute('data-zq-component');
|
|
156
|
+
row.__sourceLabel = '<span class="tree-tag"><style></span>' + (scopeComp ? ' <span class="tree-badge scoped">scoped css \u00b7 ' + esc(scopeComp) + '</span>' : '');
|
|
157
|
+
} else if (tag === 'script') {
|
|
158
|
+
row.__sourceLabel = '<span class="tree-tag"><script></span>';
|
|
159
|
+
} else {
|
|
160
|
+
row.__sourceLabel = '<span class="tree-tag"><' + tag + '></span> text content';
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
wrap.appendChild(row);
|
|
164
|
+
|
|
165
|
+
// Children container
|
|
166
|
+
if (hasChildren) {
|
|
167
|
+
var childDiv = document.createElement('div');
|
|
168
|
+
childDiv.className = 'tree-children';
|
|
169
|
+
|
|
170
|
+
// Restore expanded state or auto-expand; entry/router always expand, changed paths auto-expand (capped at depth 5)
|
|
171
|
+
var alwaysOpen = isEntryPoint || isRouterView;
|
|
172
|
+
var changedNow = changedPaths[nodePath] && depth < 6;
|
|
173
|
+
var isExpanded = alwaysOpen || (expandedPaths.hasOwnProperty(nodePath) ? expandedPaths[nodePath] : (depth < 3 || changedNow));
|
|
174
|
+
if (isExpanded) {
|
|
175
|
+
childDiv.classList.add('open');
|
|
176
|
+
row.querySelector('.tree-toggle').classList.add('open');
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Flash only actual mutation targets, not ancestors
|
|
180
|
+
if (mutatedPaths[nodePath]) {
|
|
181
|
+
row.classList.add('morph-changed');
|
|
182
|
+
row.setAttribute('data-changed', '1');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
for (var i = 0; i < childNodes.length; i++) {
|
|
186
|
+
var child = buildNode(childNodes[i], depth + 1);
|
|
187
|
+
if (child.innerHTML) childDiv.appendChild(child);
|
|
188
|
+
}
|
|
189
|
+
wrap.appendChild(childDiv);
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Toggle click (on the arrow only)
|
|
193
|
+
var toggleEl = row.querySelector('.tree-toggle');
|
|
194
|
+
if (hasChildren) {
|
|
195
|
+
toggleEl.addEventListener('click', function(e) {
|
|
196
|
+
e.stopPropagation();
|
|
197
|
+
var children = row.nextElementSibling;
|
|
198
|
+
if (children && children.classList.contains('tree-children')) {
|
|
199
|
+
var opening = !children.classList.contains('open');
|
|
200
|
+
children.classList.toggle('open');
|
|
201
|
+
toggleEl.classList.toggle('open');
|
|
202
|
+
expandedPaths[nodePath] = opening;
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Expand / collapse action buttons
|
|
208
|
+
row.addEventListener('click', function(e) {
|
|
209
|
+
var actBtn = e.target.closest('.tree-action');
|
|
210
|
+
if (!actBtn) return;
|
|
211
|
+
e.stopPropagation();
|
|
212
|
+
var childContainer = row.nextElementSibling;
|
|
213
|
+
if (!childContainer || !childContainer.classList.contains('tree-children')) return;
|
|
214
|
+
var opening = actBtn.dataset.act === 'expand';
|
|
215
|
+
if (opening) { childContainer.classList.add('open'); toggleEl.classList.add('open'); }
|
|
216
|
+
else { childContainer.classList.remove('open'); toggleEl.classList.remove('open'); }
|
|
217
|
+
expandedPaths[nodePath] = opening;
|
|
218
|
+
var maxDepth = opening ? 4 : Infinity;
|
|
219
|
+
function toggleNested(container, level) {
|
|
220
|
+
if (level >= maxDepth) return;
|
|
221
|
+
var children = container.children;
|
|
222
|
+
for (var j = 0; j < children.length; j++) {
|
|
223
|
+
var rowEl = children[j].querySelector(':scope > .tree-row');
|
|
224
|
+
var nested = children[j].querySelector(':scope > .tree-children');
|
|
225
|
+
if (nested) {
|
|
226
|
+
if (opening) nested.classList.add('open');
|
|
227
|
+
else nested.classList.remove('open');
|
|
228
|
+
var arrow = nested.previousElementSibling ? nested.previousElementSibling.querySelector('.tree-toggle') : null;
|
|
229
|
+
if (arrow) { if (opening) arrow.classList.add('open'); else arrow.classList.remove('open'); }
|
|
230
|
+
// Persist state so it survives tree rebuilds from live DOM mutations
|
|
231
|
+
if (rowEl && rowEl.__targetNode) {
|
|
232
|
+
expandedPaths[getNodePath(rowEl.__targetNode)] = opening;
|
|
233
|
+
}
|
|
234
|
+
toggleNested(nested, level + 1);
|
|
235
|
+
}
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
toggleNested(childContainer, 0);
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Row click - select element (not toggle)
|
|
242
|
+
row.addEventListener('click', function(e) {
|
|
243
|
+
// Don't select when clicking toggle arrow, badge, action buttons, or peek
|
|
244
|
+
if (e.target.closest('.tree-toggle') || e.target.closest('.tree-badge') || e.target.closest('.tree-action') || e.target.closest('.tree-peek')) return;
|
|
245
|
+
document.querySelectorAll('.tree-row.selected').forEach(function(r) { r.classList.remove('selected'); });
|
|
246
|
+
row.classList.add('selected');
|
|
247
|
+
selectedEl = node;
|
|
248
|
+
showDetail(node);
|
|
249
|
+
try { highlightElement(node); } catch(err) {}
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
// Double-click to expand/collapse
|
|
253
|
+
row.addEventListener('dblclick', function(e) {
|
|
254
|
+
if (!hasChildren) return;
|
|
255
|
+
e.preventDefault();
|
|
256
|
+
var children = row.nextElementSibling;
|
|
257
|
+
if (children && children.classList.contains('tree-children')) {
|
|
258
|
+
var opening = !children.classList.contains('open');
|
|
259
|
+
children.classList.toggle('open');
|
|
260
|
+
toggleEl.classList.toggle('open');
|
|
261
|
+
expandedPaths[nodePath] = opening;
|
|
262
|
+
}
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
return wrap;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
// ===================================================================
|
|
269
|
+
// Element highlighting
|
|
270
|
+
// ===================================================================
|
|
271
|
+
function highlightElement(el) {
|
|
272
|
+
if (!targetWin || !targetDoc) return;
|
|
273
|
+
// Remove previous highlight
|
|
274
|
+
var prev = targetDoc.getElementById('__zq_highlight');
|
|
275
|
+
if (prev) prev.remove();
|
|
276
|
+
|
|
277
|
+
var rect = el.getBoundingClientRect();
|
|
278
|
+
if (!rect.width && !rect.height) return;
|
|
279
|
+
|
|
280
|
+
var box = targetDoc.createElement('div');
|
|
281
|
+
box.id = '__zq_highlight';
|
|
282
|
+
box.style.cssText = 'position:fixed;z-index:2147483645;pointer-events:none;' +
|
|
283
|
+
'border:2px solid rgba(88,166,255,0.8);background:rgba(88,166,255,0.1);' +
|
|
284
|
+
'transition:all .15s ease;' +
|
|
285
|
+
'top:' + rect.top + 'px;left:' + rect.left + 'px;' +
|
|
286
|
+
'width:' + rect.width + 'px;height:' + rect.height + 'px;';
|
|
287
|
+
targetDoc.body.appendChild(box);
|
|
288
|
+
setTimeout(function() { if (box.parentNode) box.remove(); }, 2000);
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
// ===================================================================
|
|
292
|
+
// Detail panel
|
|
293
|
+
// ===================================================================
|
|
294
|
+
function showDetail(node) {
|
|
295
|
+
var detail = document.getElementById('dom-detail');
|
|
296
|
+
detail.style.display = 'block';
|
|
297
|
+
var html = '<button class="detail-close" id="detail-close" title="Close">×</button>';
|
|
298
|
+
|
|
299
|
+
// Tag and ID
|
|
300
|
+
html += '<div class="detail-section"><h4>Element</h4>';
|
|
301
|
+
html += '<div class="detail-row"><span class="detail-key">Tag</span><span class="detail-val">' + node.tagName.toLowerCase() + '</span></div>';
|
|
302
|
+
if (node.id) html += '<div class="detail-row"><span class="detail-key">ID</span><span class="detail-val">' + esc(node.id) + '</span></div>';
|
|
303
|
+
if (node.className) html += '<div class="detail-row"><span class="detail-key">Classes</span><span class="detail-val">' + esc(node.className) + '</span></div>';
|
|
304
|
+
html += '</div>';
|
|
305
|
+
|
|
306
|
+
// Attributes
|
|
307
|
+
if (node.attributes.length) {
|
|
308
|
+
html += '<div class="detail-section"><h4>Attributes</h4>';
|
|
309
|
+
for (var i = 0; i < node.attributes.length; i++) {
|
|
310
|
+
var a = node.attributes[i];
|
|
311
|
+
var val = esc(a.value);
|
|
312
|
+
if (/^(src|href|action|data-src|poster|srcset)$/i.test(a.name) && a.value) {
|
|
313
|
+
val = '<a href="' + esc(a.value) + '" target="_blank" rel="noopener" title="Open in new tab">' + val + '</a>';
|
|
314
|
+
}
|
|
315
|
+
html += '<div class="detail-row"><span class="detail-key">' + esc(a.name) + '</span><span class="detail-val">' + val + '</span></div>';
|
|
316
|
+
}
|
|
317
|
+
html += '</div>';
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Dimensions
|
|
321
|
+
var rect = node.getBoundingClientRect();
|
|
322
|
+
html += '<div class="detail-section"><h4>Box Model</h4>';
|
|
323
|
+
html += '<div class="detail-row"><span class="detail-key">Size</span><span class="detail-val">' + Math.round(rect.width) + ' \u00d7 ' + Math.round(rect.height) + '</span></div>';
|
|
324
|
+
html += '<div class="detail-row"><span class="detail-key">Position</span><span class="detail-val">(' + Math.round(rect.left) + ', ' + Math.round(rect.top) + ')</span></div>';
|
|
325
|
+
html += '</div>';
|
|
326
|
+
|
|
327
|
+
// Component state
|
|
328
|
+
try {
|
|
329
|
+
if (targetWin.$ && targetWin.$.getInstance) {
|
|
330
|
+
var inst = targetWin.$.getInstance(node);
|
|
331
|
+
if (inst && inst.state) {
|
|
332
|
+
html += '<div class="detail-section"><h4>Component State</h4>';
|
|
333
|
+
var stateKeys = Object.keys(inst.state);
|
|
334
|
+
for (var i = 0; i < stateKeys.length; i++) {
|
|
335
|
+
var k = stateKeys[i];
|
|
336
|
+
var v = inst.state[k];
|
|
337
|
+
var display = typeof v === 'object' ? JSON.stringify(v, null, 1) : String(v);
|
|
338
|
+
html += '<div class="detail-row"><span class="detail-key">' + esc(k) + '</span><span class="detail-val">' + esc(display) + '</span></div>';
|
|
339
|
+
}
|
|
340
|
+
html += '</div>';
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
} catch(err) {}
|
|
344
|
+
|
|
345
|
+
detail.innerHTML = html;
|
|
346
|
+
document.getElementById('detail-close').addEventListener('click', function() {
|
|
347
|
+
detail.style.display = 'none';
|
|
348
|
+
document.querySelectorAll('.tree-row.selected').forEach(function(r) { r.classList.remove('selected'); });
|
|
349
|
+
selectedEl = null;
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// ===================================================================
|
|
354
|
+
// MutationObserver - watch target document for live DOM changes
|
|
355
|
+
// ===================================================================
|
|
356
|
+
function startObserver() {
|
|
357
|
+
if (!targetDoc || observer) return;
|
|
358
|
+
observer = new MutationObserver(function(mutations) {
|
|
359
|
+
// Collect paths of mutated nodes so we can auto-expand + highlight them
|
|
360
|
+
var dominated = false;
|
|
361
|
+
for (var i = 0; i < mutations.length; i++) {
|
|
362
|
+
var m = mutations[i];
|
|
363
|
+
var target = m.target.nodeType === 1 ? m.target : m.target.parentElement;
|
|
364
|
+
if (!target) continue;
|
|
365
|
+
// Skip mutations caused by the devtools highlight overlay
|
|
366
|
+
if (target.id === '__zq_highlight' || target.id === '__zq_error_overlay' || target.id === '__zq_devbar') continue;
|
|
367
|
+
// Skip virtual-scroll hydration/dehydration churn (docs lazy chunks)
|
|
368
|
+
if (target.classList && target.classList.contains('docs-lazy-chunk')) continue;
|
|
369
|
+
if (target.closest && target.closest('.docs-lazy-chunk:not(.hydrated)')) continue;
|
|
370
|
+
var isHighlightMutation = false;
|
|
371
|
+
if (m.addedNodes) { for (var k = 0; k < m.addedNodes.length; k++) { if (m.addedNodes[k].id === '__zq_highlight') { isHighlightMutation = true; break; } } }
|
|
372
|
+
if (!isHighlightMutation && m.removedNodes) { for (var k = 0; k < m.removedNodes.length; k++) { if (m.removedNodes[k].id === '__zq_highlight') { isHighlightMutation = true; break; } } }
|
|
373
|
+
if (isHighlightMutation) continue;
|
|
374
|
+
dominated = true;
|
|
375
|
+
var tp = getNodePath(target);
|
|
376
|
+
changedPaths[tp] = true;
|
|
377
|
+
mutatedPaths[tp] = true;
|
|
378
|
+
// Also mark added nodes
|
|
379
|
+
if (m.addedNodes) {
|
|
380
|
+
for (var j = 0; j < m.addedNodes.length; j++) {
|
|
381
|
+
if (m.addedNodes[j].nodeType === 1) {
|
|
382
|
+
var ap = getNodePath(m.addedNodes[j]);
|
|
383
|
+
changedPaths[ap] = true;
|
|
384
|
+
mutatedPaths[ap] = true;
|
|
385
|
+
}
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
// Mark ancestor paths so they auto-expand to reveal changes
|
|
389
|
+
var cur = target.parentElement;
|
|
390
|
+
while (cur && cur.nodeType === 1) {
|
|
391
|
+
var p = getNodePath(cur);
|
|
392
|
+
if (!expandedPaths.hasOwnProperty(p)) changedPaths[p] = true;
|
|
393
|
+
cur = cur.parentElement;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
if (!dominated) return; // All mutations were from devtools highlight - skip rebuild
|
|
398
|
+
|
|
399
|
+
// Debounce tree rebuild
|
|
400
|
+
clearTimeout(startObserver._timer);
|
|
401
|
+
startObserver._timer = setTimeout(function() {
|
|
402
|
+
buildDOMTree();
|
|
403
|
+
updateStats();
|
|
404
|
+
|
|
405
|
+
// Scroll to deepest (last in DOM order) changed row
|
|
406
|
+
var allChanged = document.querySelectorAll('.tree-row[data-changed]');
|
|
407
|
+
if (allChanged.length) {
|
|
408
|
+
allChanged[allChanged.length - 1].scrollIntoView({ block: 'center', behavior: 'smooth' });
|
|
409
|
+
}
|
|
410
|
+
// Clear changed paths after applying
|
|
411
|
+
changedPaths = {};
|
|
412
|
+
mutatedPaths = {};
|
|
413
|
+
}, 300);
|
|
414
|
+
});
|
|
415
|
+
observer.observe(targetDoc.documentElement, {
|
|
416
|
+
childList: true,
|
|
417
|
+
subtree: true,
|
|
418
|
+
attributes: true,
|
|
419
|
+
characterData: true
|
|
420
|
+
});
|
|
421
|
+
}
|