@tekyzinc/gsd-t 2.70.16 → 2.71.11
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/CHANGELOG.md +11 -0
- package/README.md +5 -3
- package/commands/gsd-t-design-build.md +387 -0
- package/commands/gsd-t-design-review.md +155 -0
- package/commands/gsd-t-help.md +2 -0
- package/package.json +2 -2
- package/scripts/gsd-t-design-review-inject.js +941 -0
- package/scripts/gsd-t-design-review-server.js +392 -0
- package/scripts/gsd-t-design-review.html +1677 -0
- package/templates/CLAUDE-global.md +2 -0
|
@@ -0,0 +1,941 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* GSD-T Design Review — Inject Script
|
|
3
|
+
* Runs inside the proxied app page (iframe). Provides:
|
|
4
|
+
* - Hover highlight overlay
|
|
5
|
+
* - Click to lock element selection
|
|
6
|
+
* - Sends computed styles to parent review UI via postMessage
|
|
7
|
+
* - Receives style changes from parent and applies them live
|
|
8
|
+
*/
|
|
9
|
+
(function () {
|
|
10
|
+
"use strict";
|
|
11
|
+
if (window.__gsdtReviewInjected) return;
|
|
12
|
+
window.__gsdtReviewInjected = true;
|
|
13
|
+
|
|
14
|
+
let active = false;
|
|
15
|
+
let locked = false;
|
|
16
|
+
let lockedEl = null;
|
|
17
|
+
let currentEl = null;
|
|
18
|
+
|
|
19
|
+
// ── Overlay elements ────────────────────────────────────────────
|
|
20
|
+
const overlay = document.createElement("div");
|
|
21
|
+
overlay.id = "__gsdt-overlay";
|
|
22
|
+
Object.assign(overlay.style, {
|
|
23
|
+
position: "fixed",
|
|
24
|
+
pointerEvents: "none",
|
|
25
|
+
zIndex: "999998",
|
|
26
|
+
border: "2px solid #3b82f6",
|
|
27
|
+
backgroundColor: "rgba(59, 130, 246, 0.08)",
|
|
28
|
+
transition: "all 0.1s ease",
|
|
29
|
+
display: "none",
|
|
30
|
+
});
|
|
31
|
+
document.body.appendChild(overlay);
|
|
32
|
+
|
|
33
|
+
const label = document.createElement("div");
|
|
34
|
+
label.id = "__gsdt-label";
|
|
35
|
+
Object.assign(label.style, {
|
|
36
|
+
position: "fixed",
|
|
37
|
+
zIndex: "999999",
|
|
38
|
+
pointerEvents: "none",
|
|
39
|
+
backgroundColor: "#1e293b",
|
|
40
|
+
color: "#f8fafc",
|
|
41
|
+
fontSize: "11px",
|
|
42
|
+
fontFamily: "monospace",
|
|
43
|
+
padding: "2px 6px",
|
|
44
|
+
borderRadius: "3px",
|
|
45
|
+
whiteSpace: "nowrap",
|
|
46
|
+
display: "none",
|
|
47
|
+
});
|
|
48
|
+
document.body.appendChild(label);
|
|
49
|
+
|
|
50
|
+
// ── Margin/padding guides ───────────────────────────────────────
|
|
51
|
+
const marginOverlay = document.createElement("div");
|
|
52
|
+
marginOverlay.id = "__gsdt-margin";
|
|
53
|
+
Object.assign(marginOverlay.style, {
|
|
54
|
+
position: "fixed",
|
|
55
|
+
pointerEvents: "none",
|
|
56
|
+
zIndex: "999997",
|
|
57
|
+
border: "1px dashed #f97316",
|
|
58
|
+
backgroundColor: "rgba(249, 115, 22, 0.06)",
|
|
59
|
+
display: "none",
|
|
60
|
+
});
|
|
61
|
+
document.body.appendChild(marginOverlay);
|
|
62
|
+
|
|
63
|
+
const paddingOverlay = document.createElement("div");
|
|
64
|
+
paddingOverlay.id = "__gsdt-padding";
|
|
65
|
+
Object.assign(paddingOverlay.style, {
|
|
66
|
+
position: "fixed",
|
|
67
|
+
pointerEvents: "none",
|
|
68
|
+
zIndex: "999997",
|
|
69
|
+
border: "1px dashed #22c55e",
|
|
70
|
+
backgroundColor: "rgba(34, 197, 94, 0.06)",
|
|
71
|
+
display: "none",
|
|
72
|
+
});
|
|
73
|
+
document.body.appendChild(paddingOverlay);
|
|
74
|
+
|
|
75
|
+
// ── Zone highlight flash ────────────────────────────────────────
|
|
76
|
+
const zoneContainer = document.createElement("div");
|
|
77
|
+
zoneContainer.id = "__gsdt-zone-container";
|
|
78
|
+
Object.assign(zoneContainer.style, {
|
|
79
|
+
position: "fixed",
|
|
80
|
+
top: "0", left: "0",
|
|
81
|
+
width: "100%", height: "100%",
|
|
82
|
+
pointerEvents: "none",
|
|
83
|
+
zIndex: "999999",
|
|
84
|
+
transition: "opacity 0.3s ease",
|
|
85
|
+
});
|
|
86
|
+
document.body.appendChild(zoneContainer);
|
|
87
|
+
|
|
88
|
+
let zoneFlashTimer = null;
|
|
89
|
+
|
|
90
|
+
function clearFlash() {
|
|
91
|
+
zoneContainer.innerHTML = "";
|
|
92
|
+
zoneContainer.style.opacity = "1";
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function scheduleFlashFade() {
|
|
96
|
+
clearTimeout(zoneFlashTimer);
|
|
97
|
+
zoneFlashTimer = setTimeout(() => {
|
|
98
|
+
zoneContainer.style.opacity = "0";
|
|
99
|
+
setTimeout(clearFlash, 300);
|
|
100
|
+
}, 1500);
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function addFlashDiv(top, left, width, height, styles) {
|
|
104
|
+
if (height < 1) height = 3;
|
|
105
|
+
if (width < 1) width = 3;
|
|
106
|
+
const div = document.createElement("div");
|
|
107
|
+
Object.assign(div.style, {
|
|
108
|
+
position: "fixed",
|
|
109
|
+
top: top + "px", left: left + "px",
|
|
110
|
+
width: width + "px", height: height + "px",
|
|
111
|
+
pointerEvents: "none",
|
|
112
|
+
boxSizing: "border-box",
|
|
113
|
+
...styles,
|
|
114
|
+
});
|
|
115
|
+
zoneContainer.appendChild(div);
|
|
116
|
+
return div;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function flashZone(el, property) {
|
|
120
|
+
if (!el) return;
|
|
121
|
+
clearFlash();
|
|
122
|
+
|
|
123
|
+
const rect = el.getBoundingClientRect();
|
|
124
|
+
const s = getComputedStyle(el);
|
|
125
|
+
const pt = parseFloat(s.paddingTop) || 0;
|
|
126
|
+
const pr = parseFloat(s.paddingRight) || 0;
|
|
127
|
+
const pb = parseFloat(s.paddingBottom) || 0;
|
|
128
|
+
const pl = parseFloat(s.paddingLeft) || 0;
|
|
129
|
+
const mt = parseFloat(s.marginTop) || 0;
|
|
130
|
+
const mr = parseFloat(s.marginRight) || 0;
|
|
131
|
+
const mb = parseFloat(s.marginBottom) || 0;
|
|
132
|
+
const ml = parseFloat(s.marginLeft) || 0;
|
|
133
|
+
|
|
134
|
+
// Content box coordinates
|
|
135
|
+
const cx = rect.left + pl, cy = rect.top + pt;
|
|
136
|
+
const cw = rect.width - pl - pr, ch = rect.height - pt - pb;
|
|
137
|
+
|
|
138
|
+
// ── Margin zones (orange) ──
|
|
139
|
+
if (property === "marginTop") {
|
|
140
|
+
addFlashDiv(rect.top - mt, rect.left, rect.width, mt, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
|
|
141
|
+
} else if (property === "marginBottom") {
|
|
142
|
+
addFlashDiv(rect.bottom, rect.left, rect.width, mb, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
|
|
143
|
+
} else if (property === "marginLeft") {
|
|
144
|
+
addFlashDiv(rect.top, rect.left - ml, ml, rect.height, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
|
|
145
|
+
} else if (property === "marginRight") {
|
|
146
|
+
addFlashDiv(rect.top, rect.right, mr, rect.height, { backgroundColor: "rgba(249, 115, 22, 0.5)" });
|
|
147
|
+
|
|
148
|
+
// ── Padding zones (green) ──
|
|
149
|
+
} else if (property === "paddingTop") {
|
|
150
|
+
addFlashDiv(rect.top, rect.left, rect.width, pt, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
|
|
151
|
+
} else if (property === "paddingBottom") {
|
|
152
|
+
addFlashDiv(rect.bottom - pb, rect.left, rect.width, pb, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
|
|
153
|
+
} else if (property === "paddingLeft") {
|
|
154
|
+
addFlashDiv(rect.top, rect.left, pl, rect.height, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
|
|
155
|
+
} else if (property === "paddingRight") {
|
|
156
|
+
addFlashDiv(rect.top, rect.right - pr, pr, rect.height, { backgroundColor: "rgba(34, 197, 94, 0.5)" });
|
|
157
|
+
|
|
158
|
+
// ── Height: horizontal edge lines + light fill ──
|
|
159
|
+
} else if (property === "height") {
|
|
160
|
+
// Light fill for content area
|
|
161
|
+
addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(59, 130, 246, 0.15)" });
|
|
162
|
+
// Top edge — bright blue line
|
|
163
|
+
addFlashDiv(cy, cx - 20, cw + 40, 2, { backgroundColor: "#3b82f6" });
|
|
164
|
+
// Bottom edge — bright blue line
|
|
165
|
+
addFlashDiv(cy + ch - 2, cx - 20, cw + 40, 2, { backgroundColor: "#3b82f6" });
|
|
166
|
+
// Dimension label
|
|
167
|
+
const lbl = addFlashDiv(cy + ch / 2 - 9, cx + cw + 6, 50, 18, {
|
|
168
|
+
backgroundColor: "#1e293b", borderRadius: "3px",
|
|
169
|
+
fontSize: "10px", color: "#3b82f6", fontFamily: "monospace",
|
|
170
|
+
fontWeight: "600", textAlign: "center", lineHeight: "18px",
|
|
171
|
+
});
|
|
172
|
+
lbl.textContent = Math.round(ch) + "px";
|
|
173
|
+
|
|
174
|
+
// ── Width: vertical edge lines + light fill ──
|
|
175
|
+
} else if (property === "width") {
|
|
176
|
+
addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(59, 130, 246, 0.15)" });
|
|
177
|
+
// Left edge
|
|
178
|
+
addFlashDiv(cy - 20, cx, 2, ch + 40, { backgroundColor: "#3b82f6" });
|
|
179
|
+
// Right edge
|
|
180
|
+
addFlashDiv(cy - 20, cx + cw - 2, 2, ch + 40, { backgroundColor: "#3b82f6" });
|
|
181
|
+
// Dimension label
|
|
182
|
+
const lbl = addFlashDiv(cy - 18, cx + cw / 2 - 20, 50, 16, {
|
|
183
|
+
backgroundColor: "#1e293b", borderRadius: "3px",
|
|
184
|
+
fontSize: "10px", color: "#3b82f6", fontFamily: "monospace",
|
|
185
|
+
fontWeight: "600", textAlign: "center", lineHeight: "16px",
|
|
186
|
+
});
|
|
187
|
+
lbl.textContent = Math.round(cw) + "px";
|
|
188
|
+
|
|
189
|
+
// ── lineHeight: amber horizontal lines at each text line ──
|
|
190
|
+
} else if (property === "lineHeight") {
|
|
191
|
+
const lh = parseFloat(s.lineHeight) || parseFloat(s.fontSize) * 1.2;
|
|
192
|
+
const lines = Math.max(1, Math.round(ch / lh));
|
|
193
|
+
for (let i = 0; i <= lines; i++) {
|
|
194
|
+
const y = cy + i * lh;
|
|
195
|
+
if (y > cy + ch + 1) break;
|
|
196
|
+
addFlashDiv(y, cx - 10, cw + 20, 2, {
|
|
197
|
+
backgroundColor: i === 0 || i === lines ? "#f59e0b" : "rgba(245, 158, 11, 0.5)",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
// Fill between first two lines to show one line-height unit
|
|
201
|
+
if (lines >= 1) {
|
|
202
|
+
addFlashDiv(cy, cx, cw, Math.min(lh, ch), { backgroundColor: "rgba(245, 158, 11, 0.15)" });
|
|
203
|
+
const lbl = addFlashDiv(cy + 2, cx + cw + 6, 50, 16, {
|
|
204
|
+
backgroundColor: "#1e293b", borderRadius: "3px",
|
|
205
|
+
fontSize: "10px", color: "#f59e0b", fontFamily: "monospace",
|
|
206
|
+
fontWeight: "600", textAlign: "center", lineHeight: "16px",
|
|
207
|
+
});
|
|
208
|
+
lbl.textContent = Math.round(lh) + "px";
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ── fontSize: cyan underline beneath text ──
|
|
212
|
+
} else if (property === "fontSize") {
|
|
213
|
+
const fs = parseFloat(s.fontSize) || 14;
|
|
214
|
+
// Highlight text area with cyan
|
|
215
|
+
addFlashDiv(cy, cx, cw, ch, { backgroundColor: "rgba(6, 182, 212, 0.15)" });
|
|
216
|
+
// Underline at text baseline
|
|
217
|
+
addFlashDiv(cy + ch - 2, cx, cw, 2, { backgroundColor: "#06b6d4" });
|
|
218
|
+
const lbl = addFlashDiv(cy - 16, cx, 40, 14, {
|
|
219
|
+
backgroundColor: "#1e293b", borderRadius: "3px",
|
|
220
|
+
fontSize: "10px", color: "#06b6d4", fontFamily: "monospace",
|
|
221
|
+
fontWeight: "600", textAlign: "center", lineHeight: "14px",
|
|
222
|
+
});
|
|
223
|
+
lbl.textContent = Math.round(fs) + "px";
|
|
224
|
+
|
|
225
|
+
// ── fontWeight / fontFamily / letterSpacing: highlight text with cyan outline ──
|
|
226
|
+
} else if (property === "fontWeight" || property === "fontFamily" || property === "letterSpacing") {
|
|
227
|
+
addFlashDiv(cy, cx, cw, ch, {
|
|
228
|
+
border: "2px solid #06b6d4",
|
|
229
|
+
backgroundColor: "rgba(6, 182, 212, 0.1)",
|
|
230
|
+
borderRadius: "2px",
|
|
231
|
+
});
|
|
232
|
+
|
|
233
|
+
// ── textAlign: arrow showing alignment direction ──
|
|
234
|
+
} else if (property === "textAlign") {
|
|
235
|
+
const align = s.textAlign;
|
|
236
|
+
let arrowX = cx;
|
|
237
|
+
if (align === "center") arrowX = cx + cw / 2 - 15;
|
|
238
|
+
else if (align === "right" || align === "end") arrowX = cx + cw - 35;
|
|
239
|
+
// Arrow indicator
|
|
240
|
+
addFlashDiv(cy + ch / 2 - 1, cx, cw, 2, { backgroundColor: "rgba(6, 182, 212, 0.4)" });
|
|
241
|
+
const arrow = addFlashDiv(cy + ch / 2 - 9, arrowX, 30, 18, {
|
|
242
|
+
backgroundColor: "#06b6d4", borderRadius: "3px",
|
|
243
|
+
fontSize: "10px", color: "#fff", fontFamily: "monospace",
|
|
244
|
+
fontWeight: "700", textAlign: "center", lineHeight: "18px",
|
|
245
|
+
});
|
|
246
|
+
arrow.textContent = align === "center" ? "◆" : align === "right" || align === "end" ? "→▐" : "▐←";
|
|
247
|
+
|
|
248
|
+
// ── color: colored border around text content ──
|
|
249
|
+
} else if (property === "color") {
|
|
250
|
+
addFlashDiv(cy, cx, cw, ch, {
|
|
251
|
+
border: "2px solid " + s.color,
|
|
252
|
+
backgroundColor: "transparent",
|
|
253
|
+
borderRadius: "2px",
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
// ── gap: purple bars in the spaces between children ──
|
|
257
|
+
} else if (property === "gap") {
|
|
258
|
+
const gap = parseFloat(s.gap) || 0;
|
|
259
|
+
const isCol = s.flexDirection === "column";
|
|
260
|
+
const children = Array.from(el.children).filter(c => !["SCRIPT", "STYLE"].includes(c.tagName));
|
|
261
|
+
for (let i = 0; i < children.length - 1; i++) {
|
|
262
|
+
const a = children[i].getBoundingClientRect();
|
|
263
|
+
const b = children[i + 1].getBoundingClientRect();
|
|
264
|
+
if (isCol) {
|
|
265
|
+
addFlashDiv(a.bottom, rect.left, rect.width, b.top - a.bottom, {
|
|
266
|
+
backgroundColor: "rgba(168, 85, 247, 0.4)",
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
addFlashDiv(rect.top, a.right, b.left - a.right, rect.height, {
|
|
270
|
+
backgroundColor: "rgba(168, 85, 247, 0.4)",
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
if (gap > 0) {
|
|
275
|
+
const lbl = addFlashDiv(rect.top - 16, rect.left, 40, 14, {
|
|
276
|
+
backgroundColor: "#1e293b", borderRadius: "3px",
|
|
277
|
+
fontSize: "10px", color: "#a855f7", fontFamily: "monospace",
|
|
278
|
+
fontWeight: "600", textAlign: "center", lineHeight: "14px",
|
|
279
|
+
});
|
|
280
|
+
lbl.textContent = Math.round(gap) + "px";
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// ── border / borderRadius: dashed outline ──
|
|
284
|
+
} else if (property === "border" || property === "borderRadius") {
|
|
285
|
+
addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
|
|
286
|
+
border: "2px dashed #eab308",
|
|
287
|
+
backgroundColor: "transparent",
|
|
288
|
+
borderRadius: property === "borderRadius" ? s.borderRadius : "0",
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
// ── backgroundColor: flash fill ──
|
|
292
|
+
} else if (property === "backgroundColor") {
|
|
293
|
+
addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
|
|
294
|
+
backgroundColor: "rgba(234, 179, 8, 0.25)",
|
|
295
|
+
border: "2px solid rgba(234, 179, 8, 0.6)",
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
// ── boxShadow / opacity: subtle full-element glow ──
|
|
299
|
+
} else if (property === "boxShadow" || property === "opacity") {
|
|
300
|
+
addFlashDiv(rect.top - 4, rect.left - 4, rect.width + 8, rect.height + 8, {
|
|
301
|
+
border: "2px solid rgba(168, 85, 247, 0.5)",
|
|
302
|
+
backgroundColor: "rgba(168, 85, 247, 0.08)",
|
|
303
|
+
borderRadius: "4px",
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
// ── display / flexDirection / alignItems / justifyContent: layout arrow ──
|
|
307
|
+
} else if (property === "display" || property === "flexDirection" ||
|
|
308
|
+
property === "alignItems" || property === "justifyContent" ||
|
|
309
|
+
property === "gridTemplateColumns" || property === "gridTemplateRows") {
|
|
310
|
+
addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
|
|
311
|
+
border: "2px solid rgba(168, 85, 247, 0.6)",
|
|
312
|
+
backgroundColor: "rgba(168, 85, 247, 0.08)",
|
|
313
|
+
});
|
|
314
|
+
if (property === "flexDirection") {
|
|
315
|
+
const isCol = s.flexDirection === "column";
|
|
316
|
+
const arrow = addFlashDiv(
|
|
317
|
+
isCol ? cy : cy + ch / 2 - 9,
|
|
318
|
+
isCol ? cx + cw / 2 - 9 : cx,
|
|
319
|
+
isCol ? 18 : cw,
|
|
320
|
+
isCol ? ch : 18,
|
|
321
|
+
{
|
|
322
|
+
fontSize: "14px", color: "#a855f7", fontWeight: "700",
|
|
323
|
+
textAlign: "center", lineHeight: isCol ? ch + "px" : "18px",
|
|
324
|
+
}
|
|
325
|
+
);
|
|
326
|
+
arrow.textContent = isCol ? "↓" : "→";
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ── position / top / left / overflow: dotted outline ──
|
|
330
|
+
} else if (property === "position" || property === "top" || property === "left" || property === "overflow") {
|
|
331
|
+
addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
|
|
332
|
+
border: "2px dotted rgba(148, 163, 184, 0.6)",
|
|
333
|
+
backgroundColor: "rgba(148, 163, 184, 0.08)",
|
|
334
|
+
});
|
|
335
|
+
|
|
336
|
+
// ── Generic fallback ──
|
|
337
|
+
} else {
|
|
338
|
+
addFlashDiv(rect.top, rect.left, rect.width, rect.height, {
|
|
339
|
+
backgroundColor: "rgba(59, 130, 246, 0.2)",
|
|
340
|
+
border: "1px solid rgba(59, 130, 246, 0.4)",
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
scheduleFlashFade();
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// ── Style extraction ────────────────────────────────────────────
|
|
348
|
+
function getElementPath(el) {
|
|
349
|
+
const parts = [];
|
|
350
|
+
let cur = el;
|
|
351
|
+
while (cur && cur !== document.body) {
|
|
352
|
+
let desc = cur.tagName.toLowerCase();
|
|
353
|
+
if (cur.id) desc += `#${cur.id}`;
|
|
354
|
+
else if (cur.className && typeof cur.className === "string") {
|
|
355
|
+
const cls = cur.className.trim().split(/\s+/).slice(0, 3).join(".");
|
|
356
|
+
if (cls) desc += `.${cls}`;
|
|
357
|
+
}
|
|
358
|
+
parts.unshift(desc);
|
|
359
|
+
cur = cur.parentElement;
|
|
360
|
+
}
|
|
361
|
+
return parts.join(" > ");
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
function extractStyles(el) {
|
|
365
|
+
const s = getComputedStyle(el);
|
|
366
|
+
const rect = el.getBoundingClientRect();
|
|
367
|
+
return {
|
|
368
|
+
// Box model
|
|
369
|
+
width: Math.round(rect.width) + "px",
|
|
370
|
+
height: Math.round(rect.height) + "px",
|
|
371
|
+
paddingTop: s.paddingTop,
|
|
372
|
+
paddingRight: s.paddingRight,
|
|
373
|
+
paddingBottom: s.paddingBottom,
|
|
374
|
+
paddingLeft: s.paddingLeft,
|
|
375
|
+
marginTop: s.marginTop,
|
|
376
|
+
marginRight: s.marginRight,
|
|
377
|
+
marginBottom: s.marginBottom,
|
|
378
|
+
marginLeft: s.marginLeft,
|
|
379
|
+
// Layout
|
|
380
|
+
display: s.display,
|
|
381
|
+
flexDirection: s.flexDirection,
|
|
382
|
+
alignItems: s.alignItems,
|
|
383
|
+
justifyContent: s.justifyContent,
|
|
384
|
+
gap: s.gap,
|
|
385
|
+
gridTemplateColumns: s.gridTemplateColumns,
|
|
386
|
+
gridTemplateRows: s.gridTemplateRows,
|
|
387
|
+
// Position
|
|
388
|
+
position: s.position,
|
|
389
|
+
top: s.top,
|
|
390
|
+
left: s.left,
|
|
391
|
+
// Visual
|
|
392
|
+
backgroundColor: s.backgroundColor,
|
|
393
|
+
color: s.color,
|
|
394
|
+
borderRadius: s.borderRadius,
|
|
395
|
+
border: s.border,
|
|
396
|
+
boxShadow: s.boxShadow,
|
|
397
|
+
// Typography
|
|
398
|
+
fontSize: s.fontSize,
|
|
399
|
+
fontWeight: s.fontWeight,
|
|
400
|
+
fontFamily: s.fontFamily,
|
|
401
|
+
lineHeight: s.lineHeight,
|
|
402
|
+
letterSpacing: s.letterSpacing,
|
|
403
|
+
textAlign: s.textAlign,
|
|
404
|
+
// Overflow
|
|
405
|
+
overflow: s.overflow,
|
|
406
|
+
// Opacity
|
|
407
|
+
opacity: s.opacity,
|
|
408
|
+
};
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function extractBoxModel(el) {
|
|
412
|
+
const s = getComputedStyle(el);
|
|
413
|
+
const rect = el.getBoundingClientRect();
|
|
414
|
+
return {
|
|
415
|
+
content: { width: Math.round(rect.width), height: Math.round(rect.height) },
|
|
416
|
+
padding: {
|
|
417
|
+
top: parseFloat(s.paddingTop) || 0,
|
|
418
|
+
right: parseFloat(s.paddingRight) || 0,
|
|
419
|
+
bottom: parseFloat(s.paddingBottom) || 0,
|
|
420
|
+
left: parseFloat(s.paddingLeft) || 0,
|
|
421
|
+
},
|
|
422
|
+
margin: {
|
|
423
|
+
top: parseFloat(s.marginTop) || 0,
|
|
424
|
+
right: parseFloat(s.marginRight) || 0,
|
|
425
|
+
bottom: parseFloat(s.marginBottom) || 0,
|
|
426
|
+
left: parseFloat(s.marginLeft) || 0,
|
|
427
|
+
},
|
|
428
|
+
border: {
|
|
429
|
+
top: parseFloat(s.borderTopWidth) || 0,
|
|
430
|
+
right: parseFloat(s.borderRightWidth) || 0,
|
|
431
|
+
bottom: parseFloat(s.borderBottomWidth) || 0,
|
|
432
|
+
left: parseFloat(s.borderLeftWidth) || 0,
|
|
433
|
+
},
|
|
434
|
+
};
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
// ── Overlay positioning ─────────────────────────────────────────
|
|
438
|
+
function positionOverlay(el) {
|
|
439
|
+
if (!el) return;
|
|
440
|
+
const rect = el.getBoundingClientRect();
|
|
441
|
+
const s = getComputedStyle(el);
|
|
442
|
+
|
|
443
|
+
// Main element overlay
|
|
444
|
+
Object.assign(overlay.style, {
|
|
445
|
+
display: "block",
|
|
446
|
+
top: rect.top + "px",
|
|
447
|
+
left: rect.left + "px",
|
|
448
|
+
width: rect.width + "px",
|
|
449
|
+
height: rect.height + "px",
|
|
450
|
+
});
|
|
451
|
+
|
|
452
|
+
// Label
|
|
453
|
+
const tagName = el.tagName.toLowerCase();
|
|
454
|
+
const cls = (typeof el.className === "string" ? el.className : "").trim().split(/\s+/).slice(0, 2).join(".");
|
|
455
|
+
const dim = `${Math.round(rect.width)}×${Math.round(rect.height)}`;
|
|
456
|
+
label.textContent = `${tagName}${cls ? "." + cls : ""} — ${dim}`;
|
|
457
|
+
Object.assign(label.style, {
|
|
458
|
+
display: "block",
|
|
459
|
+
top: Math.max(0, rect.top - 20) + "px",
|
|
460
|
+
left: rect.left + "px",
|
|
461
|
+
});
|
|
462
|
+
|
|
463
|
+
// Margin overlay
|
|
464
|
+
const mt = parseFloat(s.marginTop) || 0;
|
|
465
|
+
const mr = parseFloat(s.marginRight) || 0;
|
|
466
|
+
const mb = parseFloat(s.marginBottom) || 0;
|
|
467
|
+
const ml = parseFloat(s.marginLeft) || 0;
|
|
468
|
+
if (mt || mr || mb || ml) {
|
|
469
|
+
Object.assign(marginOverlay.style, {
|
|
470
|
+
display: "block",
|
|
471
|
+
top: (rect.top - mt) + "px",
|
|
472
|
+
left: (rect.left - ml) + "px",
|
|
473
|
+
width: (rect.width + ml + mr) + "px",
|
|
474
|
+
height: (rect.height + mt + mb) + "px",
|
|
475
|
+
});
|
|
476
|
+
} else {
|
|
477
|
+
marginOverlay.style.display = "none";
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Padding overlay
|
|
481
|
+
const pt = parseFloat(s.paddingTop) || 0;
|
|
482
|
+
const pr = parseFloat(s.paddingRight) || 0;
|
|
483
|
+
const pb = parseFloat(s.paddingBottom) || 0;
|
|
484
|
+
const pl = parseFloat(s.paddingLeft) || 0;
|
|
485
|
+
if (pt || pr || pb || pl) {
|
|
486
|
+
Object.assign(paddingOverlay.style, {
|
|
487
|
+
display: "block",
|
|
488
|
+
top: (rect.top + pt) + "px",
|
|
489
|
+
left: (rect.left + pl) + "px",
|
|
490
|
+
width: (rect.width - pl - pr) + "px",
|
|
491
|
+
height: (rect.height - pt - pb) + "px",
|
|
492
|
+
});
|
|
493
|
+
} else {
|
|
494
|
+
paddingOverlay.style.display = "none";
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
function hideOverlay() {
|
|
499
|
+
overlay.style.display = "none";
|
|
500
|
+
label.style.display = "none";
|
|
501
|
+
marginOverlay.style.display = "none";
|
|
502
|
+
paddingOverlay.style.display = "none";
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// ── Reposition on scroll/resize/zoom ─────────────────────────────
|
|
506
|
+
let rafPending = false;
|
|
507
|
+
function refreshOverlay() {
|
|
508
|
+
if (rafPending) return;
|
|
509
|
+
rafPending = true;
|
|
510
|
+
requestAnimationFrame(() => {
|
|
511
|
+
rafPending = false;
|
|
512
|
+
const el = locked ? lockedEl : currentEl;
|
|
513
|
+
if (el) positionOverlay(el);
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
window.addEventListener("scroll", refreshOverlay, { passive: true, capture: true });
|
|
518
|
+
window.addEventListener("resize", refreshOverlay, { passive: true });
|
|
519
|
+
document.addEventListener("scroll", refreshOverlay, { passive: true, capture: true });
|
|
520
|
+
|
|
521
|
+
// ── Event handlers ──────────────────────────────────────────────
|
|
522
|
+
function isOwnElement(el) {
|
|
523
|
+
return el === overlay || el === label || el === marginOverlay || el === paddingOverlay;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
document.addEventListener("mousemove", (e) => {
|
|
527
|
+
if (!active || locked) return;
|
|
528
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
529
|
+
if (!el || isOwnElement(el)) return;
|
|
530
|
+
if (el === currentEl) return;
|
|
531
|
+
currentEl = el;
|
|
532
|
+
positionOverlay(el);
|
|
533
|
+
// Send styles to parent
|
|
534
|
+
window.parent.postMessage({
|
|
535
|
+
type: "gsdt-hover",
|
|
536
|
+
path: getElementPath(el),
|
|
537
|
+
styles: extractStyles(el),
|
|
538
|
+
boxModel: extractBoxModel(el),
|
|
539
|
+
tagName: el.tagName.toLowerCase(),
|
|
540
|
+
className: typeof el.className === "string" ? el.className : "",
|
|
541
|
+
textContent: (el.textContent || "").trim().substring(0, 100),
|
|
542
|
+
childCount: el.children.length,
|
|
543
|
+
}, "*");
|
|
544
|
+
}, { passive: true });
|
|
545
|
+
|
|
546
|
+
document.addEventListener("click", (e) => {
|
|
547
|
+
if (!active) return;
|
|
548
|
+
const el = document.elementFromPoint(e.clientX, e.clientY);
|
|
549
|
+
if (!el || isOwnElement(el)) return;
|
|
550
|
+
|
|
551
|
+
e.preventDefault();
|
|
552
|
+
e.stopPropagation();
|
|
553
|
+
|
|
554
|
+
if (locked && lockedEl === el) {
|
|
555
|
+
// Unlock
|
|
556
|
+
locked = false;
|
|
557
|
+
lockedEl = null;
|
|
558
|
+
overlay.style.borderColor = "#3b82f6";
|
|
559
|
+
overlay.style.backgroundColor = "rgba(59, 130, 246, 0.08)";
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// Lock to this element
|
|
564
|
+
locked = true;
|
|
565
|
+
lockedEl = el;
|
|
566
|
+
currentEl = el;
|
|
567
|
+
overlay.style.borderColor = "#ef4444";
|
|
568
|
+
overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
|
|
569
|
+
positionOverlay(el);
|
|
570
|
+
|
|
571
|
+
window.parent.postMessage({
|
|
572
|
+
type: "gsdt-select",
|
|
573
|
+
path: getElementPath(el),
|
|
574
|
+
styles: extractStyles(el),
|
|
575
|
+
boxModel: extractBoxModel(el),
|
|
576
|
+
tagName: el.tagName.toLowerCase(),
|
|
577
|
+
className: typeof el.className === "string" ? el.className : "",
|
|
578
|
+
textContent: (el.textContent || "").trim().substring(0, 100),
|
|
579
|
+
childCount: el.children.length,
|
|
580
|
+
}, "*");
|
|
581
|
+
}, { capture: true });
|
|
582
|
+
|
|
583
|
+
// Keyboard: Escape to unlock, Ctrl+Shift+I to toggle
|
|
584
|
+
document.addEventListener("keydown", (e) => {
|
|
585
|
+
if (e.key === "Escape" && locked) {
|
|
586
|
+
locked = false;
|
|
587
|
+
lockedEl = null;
|
|
588
|
+
overlay.style.borderColor = "#3b82f6";
|
|
589
|
+
overlay.style.backgroundColor = "rgba(59, 130, 246, 0.08)";
|
|
590
|
+
}
|
|
591
|
+
});
|
|
592
|
+
|
|
593
|
+
// ── Messages from parent ────────────────────────────────────────
|
|
594
|
+
window.addEventListener("message", (e) => {
|
|
595
|
+
const msg = e.data;
|
|
596
|
+
if (!msg || !msg.type) return;
|
|
597
|
+
|
|
598
|
+
switch (msg.type) {
|
|
599
|
+
case "gsdt-activate":
|
|
600
|
+
active = true;
|
|
601
|
+
document.body.style.cursor = "crosshair";
|
|
602
|
+
break;
|
|
603
|
+
|
|
604
|
+
case "gsdt-deactivate":
|
|
605
|
+
active = false;
|
|
606
|
+
locked = false;
|
|
607
|
+
lockedEl = null;
|
|
608
|
+
currentEl = null;
|
|
609
|
+
document.body.style.cursor = "";
|
|
610
|
+
hideOverlay();
|
|
611
|
+
break;
|
|
612
|
+
|
|
613
|
+
case "gsdt-set-style":
|
|
614
|
+
// Apply a style change to the locked element (+ smart propagation)
|
|
615
|
+
if (lockedEl && msg.property && msg.value !== undefined) {
|
|
616
|
+
const cssName = msg.property.replace(/([A-Z])/g, "-$1").toLowerCase();
|
|
617
|
+
const propagate = msg.propagate !== false; // default true
|
|
618
|
+
let propagatedCount = 0;
|
|
619
|
+
|
|
620
|
+
// Apply to primary element
|
|
621
|
+
lockedEl.style.setProperty(cssName, msg.value, "important");
|
|
622
|
+
|
|
623
|
+
// Typography props cascade to descendants
|
|
624
|
+
const inheritProps = new Set(["font-size", "font-weight", "font-family", "line-height",
|
|
625
|
+
"letter-spacing", "text-align", "color"]);
|
|
626
|
+
if (inheritProps.has(cssName)) {
|
|
627
|
+
for (const child of lockedEl.querySelectorAll("*")) {
|
|
628
|
+
child.style.setProperty(cssName, msg.value, "important");
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
// Smart propagation for table and flex elements
|
|
633
|
+
if (propagate) {
|
|
634
|
+
const tag = lockedEl.tagName.toLowerCase();
|
|
635
|
+
const parent = lockedEl.parentElement;
|
|
636
|
+
|
|
637
|
+
// Column-scoped props: apply to same column position across all rows
|
|
638
|
+
const columnProps = new Set(["text-align", "width", "min-width", "max-width"]);
|
|
639
|
+
// Row-scoped props: apply to the tr (or all trs), not individual cells
|
|
640
|
+
const rowProps = new Set(["height", "min-height", "max-height"]);
|
|
641
|
+
|
|
642
|
+
if (tag === "td" || tag === "th") {
|
|
643
|
+
const section = lockedEl.closest("tbody") || lockedEl.closest("thead") || lockedEl.closest("tfoot");
|
|
644
|
+
const row = lockedEl.closest("tr");
|
|
645
|
+
|
|
646
|
+
if (columnProps.has(cssName) && section && row) {
|
|
647
|
+
// Column-scoped: find this cell's column index, apply to same column in all rows
|
|
648
|
+
const colIdx = Array.from(row.children).indexOf(lockedEl);
|
|
649
|
+
const rows = section.querySelectorAll("tr");
|
|
650
|
+
for (const r of rows) {
|
|
651
|
+
const cell = r.children[colIdx];
|
|
652
|
+
if (!cell || cell === lockedEl) continue;
|
|
653
|
+
cell.style.setProperty(cssName, msg.value, "important");
|
|
654
|
+
if (inheritProps.has(cssName)) {
|
|
655
|
+
for (const child of cell.querySelectorAll("*")) {
|
|
656
|
+
child.style.setProperty(cssName, msg.value, "important");
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
propagatedCount++;
|
|
660
|
+
}
|
|
661
|
+
} else if (rowProps.has(cssName) && section) {
|
|
662
|
+
// Row-scoped: set on the parent tr, propagate to all trs in section
|
|
663
|
+
if (row) {
|
|
664
|
+
row.style.setProperty(cssName, msg.value, "important");
|
|
665
|
+
}
|
|
666
|
+
const rows = section.querySelectorAll("tr");
|
|
667
|
+
for (const r of rows) {
|
|
668
|
+
if (r === row) continue;
|
|
669
|
+
r.style.setProperty(cssName, msg.value, "important");
|
|
670
|
+
propagatedCount++;
|
|
671
|
+
}
|
|
672
|
+
} else if (section) {
|
|
673
|
+
// Everything else (padding, font-size, color, etc.): all cells in section
|
|
674
|
+
const cells = section.querySelectorAll(tag);
|
|
675
|
+
for (const cell of cells) {
|
|
676
|
+
if (cell === lockedEl) continue;
|
|
677
|
+
cell.style.setProperty(cssName, msg.value, "important");
|
|
678
|
+
if (inheritProps.has(cssName)) {
|
|
679
|
+
for (const child of cell.querySelectorAll("*")) {
|
|
680
|
+
child.style.setProperty(cssName, msg.value, "important");
|
|
681
|
+
}
|
|
682
|
+
}
|
|
683
|
+
propagatedCount++;
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else if (tag === "tr") {
|
|
687
|
+
// Propagate to all rows in the same section (tbody, thead, or tfoot)
|
|
688
|
+
const section = lockedEl.closest("tbody") || lockedEl.closest("thead") || lockedEl.closest("tfoot");
|
|
689
|
+
if (section) {
|
|
690
|
+
const rows = section.querySelectorAll("tr");
|
|
691
|
+
for (const row of rows) {
|
|
692
|
+
if (row === lockedEl) continue;
|
|
693
|
+
row.style.setProperty(cssName, msg.value, "important");
|
|
694
|
+
propagatedCount++;
|
|
695
|
+
}
|
|
696
|
+
}
|
|
697
|
+
} else if (tag === "div" && parent) {
|
|
698
|
+
// For bar segments: propagate to sibling divs in the same flex container
|
|
699
|
+
const parentStyle = getComputedStyle(parent);
|
|
700
|
+
if ((parentStyle.display === "flex" || parentStyle.display === "inline-flex") &&
|
|
701
|
+
parent.classList.contains("overflow-hidden")) {
|
|
702
|
+
for (const sib of parent.children) {
|
|
703
|
+
if (sib === lockedEl || sib.tagName !== "DIV") continue;
|
|
704
|
+
sib.style.setProperty(cssName, msg.value, "important");
|
|
705
|
+
propagatedCount++;
|
|
706
|
+
}
|
|
707
|
+
}
|
|
708
|
+
}
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
// Determine propagation scope label
|
|
712
|
+
let propagateScope = "";
|
|
713
|
+
if (propagatedCount > 0) {
|
|
714
|
+
const tag = lockedEl.tagName.toLowerCase();
|
|
715
|
+
const columnProps = new Set(["text-align", "width", "min-width", "max-width"]);
|
|
716
|
+
const rowProps = new Set(["height", "min-height", "max-height"]);
|
|
717
|
+
if ((tag === "td" || tag === "th") && columnProps.has(cssName)) {
|
|
718
|
+
const row = lockedEl.closest("tr");
|
|
719
|
+
const colIdx = row ? Array.from(row.children).indexOf(lockedEl) + 1 : 0;
|
|
720
|
+
propagateScope = "Col " + colIdx;
|
|
721
|
+
} else if ((tag === "td" || tag === "th") && rowProps.has(cssName)) {
|
|
722
|
+
propagateScope = "all rows";
|
|
723
|
+
} else if (tag === "tr") {
|
|
724
|
+
propagateScope = "all rows";
|
|
725
|
+
} else {
|
|
726
|
+
propagateScope = "similar";
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
positionOverlay(lockedEl);
|
|
731
|
+
window.parent.postMessage({
|
|
732
|
+
type: "gsdt-style-updated",
|
|
733
|
+
path: getElementPath(lockedEl),
|
|
734
|
+
property: msg.property,
|
|
735
|
+
value: msg.value,
|
|
736
|
+
styles: extractStyles(lockedEl),
|
|
737
|
+
boxModel: extractBoxModel(lockedEl),
|
|
738
|
+
propagated: propagatedCount,
|
|
739
|
+
propagateScope,
|
|
740
|
+
}, "*");
|
|
741
|
+
}
|
|
742
|
+
break;
|
|
743
|
+
|
|
744
|
+
case "gsdt-highlight-zone":
|
|
745
|
+
if (lockedEl && msg.property) {
|
|
746
|
+
flashZone(lockedEl, msg.property);
|
|
747
|
+
}
|
|
748
|
+
break;
|
|
749
|
+
|
|
750
|
+
case "gsdt-reset-styles":
|
|
751
|
+
// Reset all inline style overrides on the locked element and its children
|
|
752
|
+
if (lockedEl) {
|
|
753
|
+
lockedEl.style.cssText = "";
|
|
754
|
+
for (const child of lockedEl.querySelectorAll("*")) {
|
|
755
|
+
child.style.cssText = "";
|
|
756
|
+
}
|
|
757
|
+
positionOverlay(lockedEl);
|
|
758
|
+
window.parent.postMessage({
|
|
759
|
+
type: "gsdt-style-updated",
|
|
760
|
+
path: getElementPath(lockedEl),
|
|
761
|
+
styles: extractStyles(lockedEl),
|
|
762
|
+
boxModel: extractBoxModel(lockedEl),
|
|
763
|
+
}, "*");
|
|
764
|
+
}
|
|
765
|
+
break;
|
|
766
|
+
|
|
767
|
+
case "gsdt-scroll-to":
|
|
768
|
+
// Scroll to a specific element by selector
|
|
769
|
+
if (msg.selector) {
|
|
770
|
+
const el = document.querySelector(msg.selector);
|
|
771
|
+
if (el) {
|
|
772
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
773
|
+
// Auto-select it
|
|
774
|
+
locked = true;
|
|
775
|
+
lockedEl = el;
|
|
776
|
+
currentEl = el;
|
|
777
|
+
overlay.style.borderColor = "#ef4444";
|
|
778
|
+
overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
|
|
779
|
+
positionOverlay(el);
|
|
780
|
+
window.parent.postMessage({
|
|
781
|
+
type: "gsdt-select",
|
|
782
|
+
path: getElementPath(el),
|
|
783
|
+
styles: extractStyles(el),
|
|
784
|
+
boxModel: extractBoxModel(el),
|
|
785
|
+
tagName: el.tagName.toLowerCase(),
|
|
786
|
+
className: typeof el.className === "string" ? el.className : "",
|
|
787
|
+
textContent: (el.textContent || "").trim().substring(0, 100),
|
|
788
|
+
childCount: el.children.length,
|
|
789
|
+
}, "*");
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
break;
|
|
793
|
+
|
|
794
|
+
case "gsdt-get-tree":
|
|
795
|
+
// Return the DOM subtree of a component for the tree view
|
|
796
|
+
if (msg.selector) {
|
|
797
|
+
const root = document.querySelector(msg.selector);
|
|
798
|
+
if (root) {
|
|
799
|
+
// Store element references by key for select-by-key
|
|
800
|
+
window.__gsdtTreeElements = new Map();
|
|
801
|
+
let keyCounter = 0;
|
|
802
|
+
|
|
803
|
+
function buildTree(el, depth) {
|
|
804
|
+
if (depth > 4) return null; // limit depth
|
|
805
|
+
const tag = el.tagName.toLowerCase();
|
|
806
|
+
const s = getComputedStyle(el);
|
|
807
|
+
const rect = el.getBoundingClientRect();
|
|
808
|
+
const text = el.children.length === 0 ? (el.textContent || "").trim().substring(0, 40) : "";
|
|
809
|
+
|
|
810
|
+
// Smart label with structural context
|
|
811
|
+
let label = tag;
|
|
812
|
+
if (tag === "table") {
|
|
813
|
+
const cols = el.querySelector("tr") ? el.querySelector("tr").children.length : 0;
|
|
814
|
+
const rows = el.querySelectorAll("tbody tr").length || el.querySelectorAll("tr").length;
|
|
815
|
+
label = "table " + rows + "×" + cols;
|
|
816
|
+
} else if (tag === "thead") {
|
|
817
|
+
label = "header";
|
|
818
|
+
} else if (tag === "tbody") {
|
|
819
|
+
label = "body (" + el.querySelectorAll("tr").length + " rows)";
|
|
820
|
+
} else if (tag === "tfoot") {
|
|
821
|
+
label = "footer";
|
|
822
|
+
} else if (tag === "tr") {
|
|
823
|
+
const section = el.closest("tbody") || el.closest("thead") || el.closest("tfoot");
|
|
824
|
+
const rows = section ? Array.from(section.querySelectorAll("tr")) : [];
|
|
825
|
+
const rowIdx = rows.indexOf(el) + 1;
|
|
826
|
+
const sectionName = section ? section.tagName.toLowerCase().replace("thead", "hdr").replace("tbody", "").replace("tfoot", "ftr") : "";
|
|
827
|
+
label = (sectionName ? sectionName + " " : "") + "row " + rowIdx;
|
|
828
|
+
} else if (tag === "th") {
|
|
829
|
+
const row = el.closest("tr");
|
|
830
|
+
const colIdx = row ? Array.from(row.children).indexOf(el) + 1 : 0;
|
|
831
|
+
label = "Col " + colIdx;
|
|
832
|
+
if (text && text.length < 15) label += ' "' + text + '"';
|
|
833
|
+
} else if (tag === "td") {
|
|
834
|
+
const row = el.closest("tr");
|
|
835
|
+
const colIdx = row ? Array.from(row.children).indexOf(el) + 1 : 0;
|
|
836
|
+
label = "Col " + colIdx;
|
|
837
|
+
if (text && text.length < 15) label += ' "' + text + '"';
|
|
838
|
+
} else if (tag === "svg") {
|
|
839
|
+
label = "svg";
|
|
840
|
+
} else if (tag === "circle") {
|
|
841
|
+
label = "arc/circle";
|
|
842
|
+
} else if (tag === "path") {
|
|
843
|
+
label = "path";
|
|
844
|
+
} else if (s.display === "flex" || s.display === "inline-flex") {
|
|
845
|
+
label = "flex " + (s.flexDirection === "column" ? "col" : "row");
|
|
846
|
+
} else if (s.display === "grid") {
|
|
847
|
+
label = "grid";
|
|
848
|
+
}
|
|
849
|
+
if (tag !== "th" && tag !== "td" && text && text.length < 20) label += ' "' + text + '"';
|
|
850
|
+
|
|
851
|
+
const key = "n" + (keyCounter++);
|
|
852
|
+
window.__gsdtTreeElements.set(key, el);
|
|
853
|
+
|
|
854
|
+
const node = {
|
|
855
|
+
tag, label, text,
|
|
856
|
+
width: Math.round(rect.width),
|
|
857
|
+
height: Math.round(rect.height),
|
|
858
|
+
display: s.display,
|
|
859
|
+
key,
|
|
860
|
+
props: {},
|
|
861
|
+
};
|
|
862
|
+
|
|
863
|
+
// Add contextual props based on element type
|
|
864
|
+
if (tag === "table" || tag === "thead" || tag === "tbody") {
|
|
865
|
+
node.props.rows = el.querySelectorAll("tr").length;
|
|
866
|
+
}
|
|
867
|
+
if (tag === "tr") {
|
|
868
|
+
node.props.cells = el.children.length;
|
|
869
|
+
node.props.height = Math.round(rect.height) + "px";
|
|
870
|
+
}
|
|
871
|
+
if (tag === "th" || tag === "td") {
|
|
872
|
+
node.props.width = Math.round(rect.width) + "px";
|
|
873
|
+
node.props.textAlign = s.textAlign;
|
|
874
|
+
node.props.padding = s.padding;
|
|
875
|
+
node.props.fontSize = s.fontSize;
|
|
876
|
+
node.props.fontWeight = s.fontWeight;
|
|
877
|
+
if (tag === "th") node.props.background = s.backgroundColor;
|
|
878
|
+
}
|
|
879
|
+
if (tag === "circle") {
|
|
880
|
+
node.props.stroke = el.getAttribute("stroke");
|
|
881
|
+
node.props.strokeWidth = el.getAttribute("stroke-width");
|
|
882
|
+
node.props.r = el.getAttribute("r");
|
|
883
|
+
}
|
|
884
|
+
if (s.display === "flex" || s.display === "inline-flex") {
|
|
885
|
+
node.props.gap = s.gap;
|
|
886
|
+
node.props.alignItems = s.alignItems;
|
|
887
|
+
node.props.justifyContent = s.justifyContent;
|
|
888
|
+
}
|
|
889
|
+
if (s.backgroundColor && s.backgroundColor !== "rgba(0, 0, 0, 0)") {
|
|
890
|
+
node.props.background = s.backgroundColor;
|
|
891
|
+
}
|
|
892
|
+
|
|
893
|
+
// Recurse into children (skip text-only nodes, limit count)
|
|
894
|
+
const children = Array.from(el.children)
|
|
895
|
+
.filter(c => !["SCRIPT", "STYLE"].includes(c.tagName))
|
|
896
|
+
.slice(0, 20);
|
|
897
|
+
if (children.length > 0) {
|
|
898
|
+
node.children = children.map(c => buildTree(c, depth + 1)).filter(Boolean);
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
return node;
|
|
902
|
+
}
|
|
903
|
+
window.parent.postMessage({
|
|
904
|
+
type: "gsdt-tree",
|
|
905
|
+
tree: buildTree(root, 0),
|
|
906
|
+
selector: msg.selector,
|
|
907
|
+
}, "*");
|
|
908
|
+
}
|
|
909
|
+
}
|
|
910
|
+
break;
|
|
911
|
+
|
|
912
|
+
case "gsdt-select-by-key":
|
|
913
|
+
// Select an element by its key from the tree element map
|
|
914
|
+
if (msg.key && window.__gsdtTreeElements) {
|
|
915
|
+
const el = window.__gsdtTreeElements.get(msg.key);
|
|
916
|
+
if (!el) break;
|
|
917
|
+
locked = true;
|
|
918
|
+
lockedEl = el;
|
|
919
|
+
currentEl = el;
|
|
920
|
+
overlay.style.borderColor = "#ef4444";
|
|
921
|
+
overlay.style.backgroundColor = "rgba(239, 68, 68, 0.08)";
|
|
922
|
+
el.scrollIntoView({ behavior: "smooth", block: "center" });
|
|
923
|
+
positionOverlay(el);
|
|
924
|
+
window.parent.postMessage({
|
|
925
|
+
type: "gsdt-select",
|
|
926
|
+
path: getElementPath(el),
|
|
927
|
+
styles: extractStyles(el),
|
|
928
|
+
boxModel: extractBoxModel(el),
|
|
929
|
+
tagName: el.tagName.toLowerCase(),
|
|
930
|
+
className: typeof el.className === "string" ? el.className : "",
|
|
931
|
+
textContent: (el.textContent || "").trim().substring(0, 100),
|
|
932
|
+
childCount: el.children.length,
|
|
933
|
+
}, "*");
|
|
934
|
+
}
|
|
935
|
+
break;
|
|
936
|
+
}
|
|
937
|
+
});
|
|
938
|
+
|
|
939
|
+
// Notify parent we're ready
|
|
940
|
+
window.parent.postMessage({ type: "gsdt-inject-ready" }, "*");
|
|
941
|
+
})();
|