vite-plugin-specter 0.1.1 → 0.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/dist/client.js +582 -344
- package/dist/index.cjs +583 -345
- package/dist/index.js +583 -345
- package/extension/content.js +582 -344
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -32,41 +32,55 @@ function getClientScript(options) {
|
|
|
32
32
|
'use strict';
|
|
33
33
|
if (window.__specter) return; window.__specter = true;
|
|
34
34
|
|
|
35
|
-
// \u2500\u2500\u2500
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
35
|
+
// \u2500\u2500\u2500 Constants \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
36
|
+
var PURPLE = '#AD24D3';
|
|
37
|
+
var RED = '#ED3E61';
|
|
38
|
+
var TIP_BG = '#292B32';
|
|
39
|
+
var GREEN = '#22C55E';
|
|
40
|
+
var LABEL = '#8B8D94';
|
|
41
|
+
var MONO = "'JetBrains Mono', 'SF Mono', 'Fira Code', monospace";
|
|
42
|
+
var ZAP = '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"></polygon></svg>';
|
|
43
|
+
var ACTIVATE = ${JSON.stringify(activateShortcut)};
|
|
43
44
|
|
|
44
|
-
|
|
45
|
+
// \u2500\u2500\u2500 State \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
46
|
+
var fiActive = false;
|
|
47
|
+
var measureMode = false;
|
|
48
|
+
var pinEl = null;
|
|
49
|
+
var pinHighlight = null;
|
|
50
|
+
var lastHovered = null;
|
|
51
|
+
var optionHeld = false;
|
|
52
|
+
var pillExpanded = false;
|
|
53
|
+
var sideRight = false;
|
|
54
|
+
var outlinedEl = null;
|
|
55
|
+
var prevOutline = '';
|
|
56
|
+
var measureHL = null;
|
|
57
|
+
var copiedTimer = null;
|
|
58
|
+
var flashTimer = null;
|
|
59
|
+
var lastMouse = { x: 0, y: 0 };
|
|
60
|
+
var selectedEls = [];
|
|
61
|
+
var selectedBadges = [];
|
|
45
62
|
|
|
46
63
|
// \u2500\u2500\u2500 Tooltip \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
47
|
-
|
|
64
|
+
var tooltip = document.createElement('div');
|
|
48
65
|
Object.assign(tooltip.style, {
|
|
49
66
|
position: 'fixed',
|
|
50
67
|
zIndex: '2147483646',
|
|
51
68
|
pointerEvents: 'none',
|
|
52
|
-
fontFamily:
|
|
53
|
-
fontSize: '
|
|
54
|
-
lineHeight: '
|
|
55
|
-
background:
|
|
56
|
-
color: '#
|
|
57
|
-
padding: '
|
|
69
|
+
fontFamily: MONO,
|
|
70
|
+
fontSize: '13px',
|
|
71
|
+
lineHeight: '20px',
|
|
72
|
+
background: TIP_BG,
|
|
73
|
+
color: '#FFFFFF',
|
|
74
|
+
padding: '24px',
|
|
58
75
|
borderRadius: '8px',
|
|
59
|
-
maxWidth: '
|
|
60
|
-
boxShadow: '0
|
|
61
|
-
whiteSpace: 'pre-wrap',
|
|
62
|
-
wordBreak: 'break-word',
|
|
76
|
+
maxWidth: '480px',
|
|
77
|
+
boxShadow: '0 4px 16px rgba(0,0,0,0.3)',
|
|
63
78
|
display: 'none',
|
|
64
|
-
border: '1px solid rgba(255,255,255,0.08)',
|
|
65
79
|
});
|
|
66
80
|
document.body.appendChild(tooltip);
|
|
67
81
|
|
|
68
82
|
// Measure overlay
|
|
69
|
-
|
|
83
|
+
var measureOverlay = document.createElement('div');
|
|
70
84
|
Object.assign(measureOverlay.style, {
|
|
71
85
|
position: 'fixed',
|
|
72
86
|
inset: '0',
|
|
@@ -77,102 +91,156 @@ function getClientScript(options) {
|
|
|
77
91
|
document.body.appendChild(measureOverlay);
|
|
78
92
|
|
|
79
93
|
// \u2500\u2500\u2500 Pill \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
80
|
-
|
|
81
|
-
Object.assign(
|
|
94
|
+
var pillWrap = document.createElement('div');
|
|
95
|
+
Object.assign(pillWrap.style, {
|
|
82
96
|
position: 'fixed',
|
|
83
|
-
bottom: '
|
|
84
|
-
left: '
|
|
97
|
+
bottom: '4px',
|
|
98
|
+
left: '4px',
|
|
85
99
|
zIndex: '2147483647',
|
|
100
|
+
padding: '12px',
|
|
86
101
|
display: 'none',
|
|
102
|
+
boxSizing: 'border-box',
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
var pill = document.createElement('div');
|
|
106
|
+
Object.assign(pill.style, {
|
|
107
|
+
display: 'flex',
|
|
87
108
|
alignItems: 'center',
|
|
88
109
|
gap: '8px',
|
|
89
|
-
background:
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
padding: '
|
|
93
|
-
fontFamily:
|
|
110
|
+
background: PURPLE,
|
|
111
|
+
borderRadius: '16px',
|
|
112
|
+
height: '32px',
|
|
113
|
+
padding: '0 9px',
|
|
114
|
+
fontFamily: MONO,
|
|
94
115
|
fontSize: '11px',
|
|
95
|
-
|
|
116
|
+
fontWeight: '400',
|
|
117
|
+
color: '#fff',
|
|
96
118
|
cursor: 'default',
|
|
97
119
|
userSelect: 'none',
|
|
98
|
-
|
|
120
|
+
boxShadow: '0 4px 12px rgba(173,36,211,0.4)',
|
|
99
121
|
overflow: 'hidden',
|
|
100
122
|
maxWidth: '32px',
|
|
101
|
-
|
|
123
|
+
transition: 'max-width 0.2s ease',
|
|
124
|
+
boxSizing: 'border-box',
|
|
125
|
+
whiteSpace: 'nowrap',
|
|
102
126
|
});
|
|
103
127
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
128
|
+
var iconBtn = document.createElement('div');
|
|
129
|
+
Object.assign(iconBtn.style, {
|
|
130
|
+
position: 'relative',
|
|
131
|
+
width: '14px',
|
|
132
|
+
height: '14px',
|
|
133
|
+
flexShrink: '0',
|
|
134
|
+
display: 'flex',
|
|
135
|
+
alignItems: 'center',
|
|
136
|
+
justifyContent: 'center',
|
|
137
|
+
});
|
|
107
138
|
|
|
108
|
-
|
|
109
|
-
|
|
139
|
+
var zapEl = document.createElement('span');
|
|
140
|
+
zapEl.innerHTML = ZAP;
|
|
141
|
+
Object.assign(zapEl.style, {
|
|
142
|
+
position: 'absolute',
|
|
143
|
+
display: 'flex',
|
|
144
|
+
transition: 'transform 0.4s ease, opacity 0.2s ease',
|
|
145
|
+
});
|
|
110
146
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
color: '#666',
|
|
117
|
-
fontSize: '15px',
|
|
147
|
+
var closeEl = document.createElement('span');
|
|
148
|
+
closeEl.textContent = '\xD7';
|
|
149
|
+
Object.assign(closeEl.style, {
|
|
150
|
+
position: 'absolute',
|
|
151
|
+
fontSize: '17px',
|
|
118
152
|
lineHeight: '1',
|
|
119
|
-
|
|
153
|
+
opacity: '0',
|
|
154
|
+
transition: 'opacity 0.2s ease',
|
|
155
|
+
cursor: 'pointer',
|
|
156
|
+
});
|
|
157
|
+
closeEl.addEventListener('click', function (e) { e.stopPropagation(); deactivate(); });
|
|
158
|
+
|
|
159
|
+
iconBtn.appendChild(zapEl);
|
|
160
|
+
iconBtn.appendChild(closeEl);
|
|
161
|
+
|
|
162
|
+
var pillText = document.createElement('span');
|
|
163
|
+
Object.assign(pillText.style, { display: 'none', color: '#f3d9fb' });
|
|
164
|
+
|
|
165
|
+
var chevron = document.createElement('span');
|
|
166
|
+
chevron.textContent = '\u203A';
|
|
167
|
+
chevron.title = 'Move to other side';
|
|
168
|
+
Object.assign(chevron.style, {
|
|
169
|
+
display: 'none',
|
|
170
|
+
cursor: 'pointer',
|
|
171
|
+
color: '#fff',
|
|
172
|
+
fontSize: '14px',
|
|
120
173
|
flexShrink: '0',
|
|
121
174
|
padding: '0 2px',
|
|
175
|
+
opacity: '0.8',
|
|
122
176
|
});
|
|
123
|
-
|
|
177
|
+
chevron.addEventListener('click', function (e) { e.stopPropagation(); moveSide(); });
|
|
124
178
|
|
|
125
|
-
pill.appendChild(
|
|
179
|
+
pill.appendChild(iconBtn);
|
|
126
180
|
pill.appendChild(pillText);
|
|
127
|
-
pill.appendChild(
|
|
128
|
-
|
|
181
|
+
pill.appendChild(chevron);
|
|
182
|
+
pillWrap.appendChild(pill);
|
|
183
|
+
document.body.appendChild(pillWrap);
|
|
129
184
|
|
|
130
|
-
|
|
185
|
+
pillWrap.addEventListener('mouseenter', function () {
|
|
131
186
|
clearMeasureOverlay();
|
|
187
|
+
clearMeasureTargetHL();
|
|
132
188
|
hideTooltip();
|
|
133
189
|
expandPill();
|
|
190
|
+
zapEl.style.transform = 'rotate(360deg)';
|
|
191
|
+
zapEl.style.opacity = '0';
|
|
192
|
+
closeEl.style.opacity = '1';
|
|
134
193
|
});
|
|
135
|
-
|
|
194
|
+
pillWrap.addEventListener('mouseleave', function () {
|
|
136
195
|
if (selectedEls.length === 0 && !pinEl) collapsePill();
|
|
196
|
+
zapEl.style.transform = 'rotate(0deg)';
|
|
197
|
+
zapEl.style.opacity = '1';
|
|
198
|
+
closeEl.style.opacity = '0';
|
|
137
199
|
});
|
|
138
200
|
|
|
139
|
-
function
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
201
|
+
function moveSide() {
|
|
202
|
+
sideRight = !sideRight;
|
|
203
|
+
if (sideRight) {
|
|
204
|
+
pillWrap.style.left = 'auto';
|
|
205
|
+
pillWrap.style.right = '4px';
|
|
206
|
+
chevron.textContent = '\u2039';
|
|
207
|
+
} else {
|
|
208
|
+
pillWrap.style.right = 'auto';
|
|
209
|
+
pillWrap.style.left = '4px';
|
|
210
|
+
chevron.textContent = '\u203A';
|
|
146
211
|
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function expandPill(text) {
|
|
215
|
+
pill.style.maxWidth = '760px';
|
|
216
|
+
pillText.style.display = 'inline';
|
|
217
|
+
chevron.style.display = 'inline';
|
|
218
|
+
pillExpanded = true;
|
|
219
|
+
if (text) { pillText.textContent = text; return; }
|
|
147
220
|
if (selectedEls.length > 0) {
|
|
148
|
-
pillText.textContent =
|
|
221
|
+
pillText.textContent = selectedEls.length + ' selected \xB7 Cmd+C copy all \xB7 Esc clear';
|
|
149
222
|
} else if (measureMode) {
|
|
150
|
-
pillText.textContent = 'Measure \xB7 hover
|
|
223
|
+
pillText.textContent = 'Measure \xB7 hover distances \xB7 M pin \xB7 Cmd+C copy \xB7 Option toggle';
|
|
151
224
|
} else {
|
|
152
|
-
pillText.textContent = 'Properties \xB7 hover
|
|
225
|
+
pillText.textContent = 'Properties \xB7 hover inspect \xB7 P pick \xB7 Cmd+C copy \xB7 Option measure';
|
|
153
226
|
}
|
|
154
227
|
}
|
|
155
228
|
|
|
156
229
|
function collapsePill() {
|
|
157
230
|
pill.style.maxWidth = '32px';
|
|
158
|
-
pillText.style.
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
function updatePillForSelection() {
|
|
163
|
-
if (selectedEls.length > 0) {
|
|
164
|
-
expandPill();
|
|
165
|
-
} else if (!pill.matches(':hover')) {
|
|
166
|
-
collapsePill();
|
|
167
|
-
}
|
|
231
|
+
pillText.style.display = 'none';
|
|
232
|
+
chevron.style.display = 'none';
|
|
233
|
+
pillExpanded = false;
|
|
168
234
|
}
|
|
169
235
|
|
|
170
236
|
function flashMode() {
|
|
171
|
-
|
|
237
|
+
if (pillExpanded && pillWrap.matches(':hover')) return;
|
|
238
|
+
var text = measureMode ? '\u2B21 Measure mode' : '\u25C9 Properties mode';
|
|
172
239
|
expandPill(text);
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
240
|
+
clearTimeout(flashTimer);
|
|
241
|
+
flashTimer = setTimeout(function () {
|
|
242
|
+
if (!pillWrap.matches(':hover') && selectedEls.length === 0 && !pinEl) collapsePill();
|
|
243
|
+
else expandPill();
|
|
176
244
|
}, 1200);
|
|
177
245
|
}
|
|
178
246
|
|
|
@@ -180,7 +248,8 @@ function getClientScript(options) {
|
|
|
180
248
|
function activate() {
|
|
181
249
|
fiActive = true;
|
|
182
250
|
measureMode = false;
|
|
183
|
-
|
|
251
|
+
pillWrap.style.display = 'block';
|
|
252
|
+
document.body.style.cursor = 'crosshair';
|
|
184
253
|
collapsePill();
|
|
185
254
|
}
|
|
186
255
|
|
|
@@ -191,7 +260,10 @@ function getClientScript(options) {
|
|
|
191
260
|
lastHovered = null;
|
|
192
261
|
clearSelection();
|
|
193
262
|
clearPin();
|
|
194
|
-
|
|
263
|
+
clearHoverOutline();
|
|
264
|
+
clearMeasureTargetHL();
|
|
265
|
+
pillWrap.style.display = 'none';
|
|
266
|
+
document.body.style.cursor = '';
|
|
195
267
|
hideTooltip();
|
|
196
268
|
clearMeasureOverlay();
|
|
197
269
|
}
|
|
@@ -199,15 +271,19 @@ function getClientScript(options) {
|
|
|
199
271
|
// \u2500\u2500\u2500 Helpers \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
200
272
|
function hideTooltip() { tooltip.style.display = 'none'; }
|
|
201
273
|
|
|
274
|
+
function esc(s) {
|
|
275
|
+
return String(s).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
276
|
+
}
|
|
277
|
+
|
|
202
278
|
function positionTooltip(x, y) {
|
|
203
279
|
tooltip.style.display = 'block';
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
280
|
+
var tw = tooltip.offsetWidth;
|
|
281
|
+
var th = tooltip.offsetHeight;
|
|
282
|
+
var vw = window.innerWidth;
|
|
283
|
+
var vh = window.innerHeight;
|
|
284
|
+
var margin = 16;
|
|
285
|
+
var left = x + 16;
|
|
286
|
+
var top = y + 16;
|
|
211
287
|
if (left + tw > vw - margin) left = x - tw - 16;
|
|
212
288
|
if (top + th > vh - margin) top = y - th - 16;
|
|
213
289
|
if (left < margin) left = margin;
|
|
@@ -217,204 +293,335 @@ function getClientScript(options) {
|
|
|
217
293
|
}
|
|
218
294
|
|
|
219
295
|
function toHex(r, g, b) {
|
|
220
|
-
return '#' + [r, g, b].map(v
|
|
296
|
+
return '#' + [r, g, b].map(function (v) { return Math.round(v).toString(16).padStart(2, '0'); }).join('');
|
|
221
297
|
}
|
|
222
298
|
|
|
223
299
|
function parseColor(str) {
|
|
224
300
|
if (!str || str === 'transparent' || str === 'rgba(0, 0, 0, 0)') return null;
|
|
225
|
-
|
|
301
|
+
var m = str.match(/rgba?\\((\\d+),\\s*(\\d+),\\s*(\\d+)(?:,\\s*([\\d.]+))?\\)/);
|
|
226
302
|
if (!m) return { hex: str, alpha: 1 };
|
|
227
|
-
|
|
228
|
-
return { hex: toHex(r, g, b), alpha: parseFloat(a) };
|
|
303
|
+
return { hex: toHex(m[1], m[2], m[3]), alpha: m[4] !== undefined ? parseFloat(m[4]) : 1 };
|
|
229
304
|
}
|
|
230
305
|
|
|
231
306
|
function colorLabel(str) {
|
|
232
|
-
|
|
307
|
+
var c = parseColor(str);
|
|
233
308
|
if (!c) return null;
|
|
234
|
-
if (c.alpha < 1) return
|
|
309
|
+
if (c.alpha < 1) return c.hex + ' (' + Math.round(c.alpha * 100) + '% opacity)';
|
|
235
310
|
return c.hex;
|
|
236
311
|
}
|
|
237
312
|
|
|
313
|
+
function colorObj(str) {
|
|
314
|
+
var c = parseColor(str);
|
|
315
|
+
if (!c) return null;
|
|
316
|
+
return { raw: str, hex: c.hex, alpha: c.alpha, label: colorLabel(str) };
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
function edge(t, r, b, l) {
|
|
320
|
+
if ([t, r, b, l].every(function (v) { return v === '0px'; })) return null;
|
|
321
|
+
return { value: t + ' ' + r + ' ' + b + ' ' + l };
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
function weightName(w) {
|
|
325
|
+
var m = { '100': 'Thin', '200': 'ExtraLight', '300': 'Light', '400': 'Regular', '500': 'Medium', '600': 'Semibold', '700': 'Bold', '800': 'ExtraBold', '900': 'Black', 'normal': 'Regular', 'bold': 'Bold' };
|
|
326
|
+
return m[w] || w;
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
function swatch(hex) {
|
|
330
|
+
return '<span style="display:inline-block;width:9px;height:9px;border-radius:2px;background:' + hex + ';margin-right:6px;vertical-align:middle;border:1px solid rgba(255,255,255,0.2)"></span>';
|
|
331
|
+
}
|
|
332
|
+
|
|
238
333
|
function getComponentName(el) {
|
|
239
|
-
for (
|
|
334
|
+
for (var key in el) {
|
|
240
335
|
if (key.startsWith('__reactFiber$') || key.startsWith('__reactInternalInstance$')) {
|
|
241
|
-
|
|
336
|
+
var fiber = el[key];
|
|
242
337
|
while (fiber) {
|
|
243
|
-
|
|
338
|
+
var name = (fiber.type && (fiber.type.displayName || fiber.type.name));
|
|
244
339
|
if (name && name.length > 3 && /^[A-Z]/.test(name)) return name;
|
|
245
340
|
fiber = fiber.return;
|
|
246
341
|
}
|
|
247
342
|
}
|
|
248
343
|
}
|
|
249
344
|
if (el.__vueParentComponent) {
|
|
250
|
-
|
|
251
|
-
if (
|
|
345
|
+
var vname = el.__vueParentComponent.type && (el.__vueParentComponent.type.name || el.__vueParentComponent.type.__name);
|
|
346
|
+
if (vname && vname.length > 3) return vname;
|
|
252
347
|
}
|
|
253
348
|
return null;
|
|
254
349
|
}
|
|
255
350
|
|
|
256
351
|
function getMatchingRules(el) {
|
|
257
|
-
|
|
352
|
+
var results = [];
|
|
258
353
|
try {
|
|
259
|
-
for (
|
|
260
|
-
|
|
261
|
-
|
|
354
|
+
for (var s = 0; s < document.styleSheets.length; s++) {
|
|
355
|
+
var sheet = document.styleSheets[s];
|
|
356
|
+
var rules;
|
|
357
|
+
try { rules = sheet.cssRules; } catch (e) { continue; }
|
|
262
358
|
if (!rules) continue;
|
|
263
|
-
for (
|
|
359
|
+
for (var r = 0; r < rules.length; r++) {
|
|
360
|
+
var rule = rules[r];
|
|
264
361
|
if (rule.type !== 1) continue;
|
|
265
|
-
|
|
362
|
+
var sel = rule.selectorText;
|
|
266
363
|
if (!sel) continue;
|
|
267
364
|
if (/^[*,]|^:root|^html|^body$|^::before|^::after/.test(sel.trim())) continue;
|
|
268
365
|
try {
|
|
269
366
|
if (el.matches(sel)) {
|
|
270
|
-
|
|
271
|
-
for (
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
if (val) props.push(
|
|
367
|
+
var props = [];
|
|
368
|
+
for (var i = 0; i < rule.style.length; i++) {
|
|
369
|
+
var prop = rule.style[i];
|
|
370
|
+
var val = rule.style.getPropertyValue(prop);
|
|
371
|
+
if (val) props.push(' ' + prop + ': ' + val + ';');
|
|
275
372
|
}
|
|
276
|
-
if (props.length) results.push(
|
|
373
|
+
if (props.length) results.push(sel + ' {\\n' + props.join('\\n') + '\\n}');
|
|
277
374
|
}
|
|
278
|
-
} catch {}
|
|
375
|
+
} catch (e) {}
|
|
279
376
|
}
|
|
280
377
|
}
|
|
281
|
-
} catch {}
|
|
378
|
+
} catch (e) {}
|
|
282
379
|
return results;
|
|
283
380
|
}
|
|
284
381
|
|
|
285
382
|
function getSelector(el) {
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
for (
|
|
289
|
-
|
|
383
|
+
var parts = [];
|
|
384
|
+
var cur = el;
|
|
385
|
+
for (var i = 0; i < 3 && cur && cur !== document.body; i++) {
|
|
386
|
+
var part = cur.tagName.toLowerCase();
|
|
290
387
|
if (cur.id) { part += '#' + cur.id; parts.unshift(part); break; }
|
|
291
|
-
|
|
388
|
+
var cls = Array.prototype.slice.call(cur.classList).filter(function (c) { return c.indexOf('__specter') !== 0; });
|
|
389
|
+
if (cls.length) {
|
|
390
|
+
part += '.' + cls.slice(0, 2).join('.');
|
|
391
|
+
} else if (cur.parentElement) {
|
|
392
|
+
var sameTag = Array.prototype.slice.call(cur.parentElement.children).filter(function (c) { return c.tagName === cur.tagName; });
|
|
393
|
+
if (sameTag.length > 1) {
|
|
394
|
+
part += ':nth-child(' + (Array.prototype.indexOf.call(cur.parentElement.children, cur) + 1) + ')';
|
|
395
|
+
}
|
|
396
|
+
}
|
|
292
397
|
parts.unshift(part);
|
|
293
398
|
cur = cur.parentElement;
|
|
294
399
|
}
|
|
295
400
|
return parts.join(' > ');
|
|
296
401
|
}
|
|
297
402
|
|
|
298
|
-
// \u2500\u2500\u2500
|
|
403
|
+
// \u2500\u2500\u2500 Structured data model \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
299
404
|
function buildInfo(el) {
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
405
|
+
var cs = getComputedStyle(el);
|
|
406
|
+
var rect = el.getBoundingClientRect();
|
|
407
|
+
var ff = cs.fontFamily.split(',')[0].replace(/['"]/g, '').trim();
|
|
408
|
+
var raw = el.textContent ? el.textContent.trim() : '';
|
|
409
|
+
return {
|
|
410
|
+
el: el,
|
|
411
|
+
tag: el.tagName.toLowerCase(),
|
|
412
|
+
component: getComponentName(el),
|
|
413
|
+
styleKey: el.dataset ? el.dataset.style : null,
|
|
414
|
+
width: Math.round(rect.width),
|
|
415
|
+
height: Math.round(rect.height),
|
|
416
|
+
text: raw.slice(0, 40),
|
|
417
|
+
textTrunc: raw.length > 40,
|
|
418
|
+
font: { family: ff, weight: cs.fontWeight, size: cs.fontSize, lineHeight: cs.lineHeight },
|
|
419
|
+
color: colorObj(cs.color),
|
|
420
|
+
bg: colorObj(cs.backgroundColor),
|
|
421
|
+
padding: edge(cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft),
|
|
422
|
+
margin: edge(cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft),
|
|
423
|
+
radius: (cs.borderRadius && cs.borderRadius !== '0px') ? cs.borderRadius : null,
|
|
424
|
+
display: ['flex', 'grid', 'inline-flex', 'inline-grid'].indexOf(cs.display) >= 0 ? cs.display : null,
|
|
425
|
+
gap: (cs.gap && cs.gap !== 'normal') ? cs.gap : null,
|
|
426
|
+
flexDir: (cs.display.indexOf('flex') >= 0 && cs.flexDirection !== 'row') ? cs.flexDirection : null,
|
|
427
|
+
rules: getMatchingRules(el),
|
|
428
|
+
};
|
|
429
|
+
}
|
|
315
430
|
|
|
316
|
-
|
|
317
|
-
|
|
431
|
+
function row(label, val) {
|
|
432
|
+
return '<div style="display:flex;margin-bottom:8px"><span style="color:' + LABEL + ';min-width:76px;flex-shrink:0">' + label + '</span><span style="color:#fff;word-break:break-word">' + val + '</span></div>';
|
|
433
|
+
}
|
|
318
434
|
|
|
319
|
-
|
|
320
|
-
|
|
435
|
+
function buildHumanDisplay(data) {
|
|
436
|
+
var h = '';
|
|
437
|
+
var id = '<span style="color:#fff"><' + data.tag + '></span>';
|
|
438
|
+
if (data.component) id += ' <span style="color:#E0A3F5;font-weight:600">' + esc(data.component) + '</span>';
|
|
439
|
+
if (data.styleKey) id += ' <span style="color:' + LABEL + '">.' + esc(data.styleKey) + '</span>';
|
|
440
|
+
id += ' <span style="color:' + LABEL + '">' + data.width + '\xD7' + data.height + '</span>';
|
|
441
|
+
h += '<div style="margin-bottom:8px">' + id + '</div>';
|
|
442
|
+
if (data.text) h += '<div style="color:' + LABEL + ';font-style:italic;margin-bottom:8px">"' + esc(data.text) + (data.textTrunc ? '\u2026' : '') + '"</div>';
|
|
443
|
+
h += '<div style="height:8px"></div>';
|
|
444
|
+
h += row('Font', esc(data.font.family) + ' \xB7 ' + weightName(data.font.weight) + ' \xB7 ' + data.font.size + '/' + data.font.lineHeight);
|
|
445
|
+
if (data.color) h += row('Color', swatch(data.color.hex) + data.color.label);
|
|
446
|
+
if (data.bg) h += row('Bg', swatch(data.bg.hex) + data.bg.label);
|
|
447
|
+
if (data.padding) h += row('Padding', data.padding.value);
|
|
448
|
+
if (data.margin) h += row('Margin', data.margin.value);
|
|
449
|
+
if (data.radius) h += row('Radius', data.radius);
|
|
450
|
+
if (data.display) {
|
|
451
|
+
var d = data.display;
|
|
452
|
+
if (data.gap) d += ' \xB7 gap ' + data.gap;
|
|
453
|
+
if (data.flexDir) d += ' \xB7 ' + data.flexDir;
|
|
454
|
+
h += row('Display', d);
|
|
455
|
+
}
|
|
456
|
+
return h;
|
|
457
|
+
}
|
|
321
458
|
|
|
322
|
-
|
|
323
|
-
|
|
459
|
+
function buildLLMClipboard(data) {
|
|
460
|
+
var lines = ['[Specter]'];
|
|
461
|
+
var head = '<' + data.tag + '>';
|
|
462
|
+
if (data.component) head += ' ' + data.component;
|
|
463
|
+
if (data.styleKey) head += ' .' + data.styleKey;
|
|
464
|
+
head += ' ' + data.width + '\xD7' + data.height;
|
|
465
|
+
lines.push(head);
|
|
466
|
+
if (data.text) lines.push('"' + data.text + (data.textTrunc ? '\u2026' : '') + '"');
|
|
467
|
+
lines.push('font: ' + data.font.family + ' ' + data.font.weight + ' ' + data.font.size + '/' + data.font.lineHeight);
|
|
468
|
+
if (data.color) lines.push('color: ' + data.color.label);
|
|
469
|
+
if (data.bg) lines.push('bg: ' + data.bg.label);
|
|
470
|
+
if (data.padding) lines.push('padding: ' + data.padding.value);
|
|
471
|
+
if (data.margin) lines.push('margin: ' + data.margin.value);
|
|
472
|
+
if (data.radius) lines.push('radius: ' + data.radius);
|
|
473
|
+
if (data.display) {
|
|
474
|
+
var d = 'display: ' + data.display;
|
|
475
|
+
if (data.gap) d += ' gap: ' + data.gap;
|
|
476
|
+
if (data.flexDir) d += ' dir: ' + data.flexDir;
|
|
477
|
+
lines.push(d);
|
|
478
|
+
}
|
|
479
|
+
lines.push('selector: ' + getSelector(data.el));
|
|
480
|
+
var out = lines.join('\\n');
|
|
481
|
+
if (data.rules && data.rules.length) out += '\\n\\n--- CSS Rules ---\\n' + data.rules.join('\\n\\n');
|
|
482
|
+
return out;
|
|
483
|
+
}
|
|
324
484
|
|
|
325
|
-
|
|
326
|
-
|
|
485
|
+
function buildMultiSelectCopyText() {
|
|
486
|
+
return selectedEls.map(function (el, i) {
|
|
487
|
+
var body = buildLLMClipboard(buildInfo(el));
|
|
488
|
+
return body.replace('[Specter]', '[Specter ' + (i + 1) + '/' + selectedEls.length + ']');
|
|
489
|
+
}).join('\\n\\n' + Array(41).join('\u2500') + '\\n\\n');
|
|
490
|
+
}
|
|
327
491
|
|
|
328
|
-
|
|
329
|
-
|
|
492
|
+
function linesToHTML(str) {
|
|
493
|
+
return str.split('\\n').map(function (l) {
|
|
494
|
+
return '<div style="margin-bottom:4px;color:#fff">' + esc(l) + '</div>';
|
|
495
|
+
}).join('');
|
|
496
|
+
}
|
|
330
497
|
|
|
331
|
-
|
|
498
|
+
// \u2500\u2500\u2500 Hover outline \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
499
|
+
function setHoverOutline(el) {
|
|
500
|
+
if (outlinedEl === el) return;
|
|
501
|
+
clearHoverOutline();
|
|
502
|
+
if (!el) return;
|
|
503
|
+
outlinedEl = el;
|
|
504
|
+
prevOutline = el.style.outline;
|
|
505
|
+
el.style.outline = '2px solid ' + PURPLE;
|
|
506
|
+
el.style.outlineOffset = '-1px';
|
|
507
|
+
}
|
|
332
508
|
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (disp.includes('flex') && cs.flexDirection !== 'row') d += \` dir: \${cs.flexDirection}\`;
|
|
338
|
-
lines.push(d);
|
|
509
|
+
function clearHoverOutline() {
|
|
510
|
+
if (outlinedEl) {
|
|
511
|
+
outlinedEl.style.outline = prevOutline;
|
|
512
|
+
outlinedEl = null;
|
|
339
513
|
}
|
|
340
|
-
|
|
341
|
-
return lines.join('\\n');
|
|
342
514
|
}
|
|
343
515
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
516
|
+
// \u2500\u2500\u2500 Copied feedback \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
517
|
+
function showCopied() {
|
|
518
|
+
tooltip.innerHTML = '<div style="color:' + GREEN + ';font-weight:600;display:flex;align-items:center;gap:6px"><span>\u2713</span><span>Copied!</span></div>';
|
|
519
|
+
tooltip.style.display = 'block';
|
|
520
|
+
clearTimeout(copiedTimer);
|
|
521
|
+
copiedTimer = setTimeout(function () {
|
|
522
|
+
if (fiActive && lastHovered) reRenderTooltip();
|
|
523
|
+
else hideTooltip();
|
|
524
|
+
}, 1200);
|
|
350
525
|
}
|
|
351
526
|
|
|
352
|
-
function
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
527
|
+
function reRenderTooltip() {
|
|
528
|
+
if (measureMode) {
|
|
529
|
+
var text = (pinEl && pinEl !== lastHovered) ? measureBetween(pinEl, lastHovered) : measureToNeighbor(lastHovered);
|
|
530
|
+
tooltip.innerHTML = linesToHTML(text);
|
|
531
|
+
} else {
|
|
532
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(lastHovered));
|
|
533
|
+
}
|
|
534
|
+
positionTooltip(lastMouse.x, lastMouse.y);
|
|
360
535
|
}
|
|
361
536
|
|
|
362
537
|
// \u2500\u2500\u2500 Multi-select \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
363
538
|
function makeBadge(el, index) {
|
|
364
|
-
|
|
365
|
-
|
|
539
|
+
var rect = el.getBoundingClientRect();
|
|
540
|
+
var badge = document.createElement('div');
|
|
366
541
|
badge.textContent = String(index + 1);
|
|
367
542
|
Object.assign(badge.style, {
|
|
368
543
|
position: 'fixed',
|
|
369
|
-
left: rect.left + 'px',
|
|
370
|
-
top: rect.top + 'px',
|
|
371
|
-
width: '
|
|
372
|
-
height: '
|
|
373
|
-
background:
|
|
544
|
+
left: (rect.left - 4) + 'px',
|
|
545
|
+
top: (rect.top - 4) + 'px',
|
|
546
|
+
width: '20px',
|
|
547
|
+
height: '20px',
|
|
548
|
+
background: PURPLE,
|
|
374
549
|
color: '#fff',
|
|
375
|
-
fontSize: '
|
|
550
|
+
fontSize: '11px',
|
|
376
551
|
fontWeight: '700',
|
|
377
|
-
fontFamily:
|
|
552
|
+
fontFamily: MONO,
|
|
378
553
|
borderRadius: '50%',
|
|
379
554
|
display: 'flex',
|
|
380
555
|
alignItems: 'center',
|
|
381
556
|
justifyContent: 'center',
|
|
382
557
|
zIndex: '2147483644',
|
|
383
558
|
pointerEvents: 'none',
|
|
384
|
-
boxShadow: '0
|
|
385
|
-
transform: 'translate(-50%, -50%)',
|
|
559
|
+
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
|
|
386
560
|
});
|
|
387
561
|
document.body.appendChild(badge);
|
|
388
562
|
return badge;
|
|
389
563
|
}
|
|
390
564
|
|
|
391
565
|
function rebuildBadges() {
|
|
392
|
-
selectedBadges.forEach(b
|
|
566
|
+
selectedBadges.forEach(function (b) { b.remove(); });
|
|
393
567
|
selectedBadges.length = 0;
|
|
394
|
-
selectedEls.forEach((el, i)
|
|
568
|
+
selectedEls.forEach(function (el, i) {
|
|
395
569
|
selectedBadges.push(makeBadge(el, i));
|
|
396
570
|
});
|
|
397
571
|
}
|
|
398
572
|
|
|
399
573
|
function toggleSelection(el) {
|
|
400
|
-
|
|
574
|
+
var idx = selectedEls.indexOf(el);
|
|
401
575
|
if (idx >= 0) {
|
|
402
576
|
selectedEls.splice(idx, 1);
|
|
577
|
+
el.style.outline = '';
|
|
403
578
|
} else {
|
|
404
579
|
selectedEls.push(el);
|
|
580
|
+
el.style.outline = '2px solid ' + PURPLE;
|
|
581
|
+
el.style.outlineOffset = '-1px';
|
|
405
582
|
}
|
|
406
583
|
rebuildBadges();
|
|
407
584
|
updatePillForSelection();
|
|
408
585
|
}
|
|
409
586
|
|
|
410
587
|
function clearSelection() {
|
|
588
|
+
selectedEls.forEach(function (el) { el.style.outline = ''; });
|
|
411
589
|
selectedEls.length = 0;
|
|
412
|
-
selectedBadges.forEach(b
|
|
590
|
+
selectedBadges.forEach(function (b) { b.remove(); });
|
|
413
591
|
selectedBadges.length = 0;
|
|
414
592
|
}
|
|
415
593
|
|
|
416
|
-
|
|
417
|
-
|
|
594
|
+
function updatePillForSelection() {
|
|
595
|
+
if (selectedEls.length > 0) expandPill();
|
|
596
|
+
else if (!pillWrap.matches(':hover')) collapsePill();
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
// \u2500\u2500\u2500 Pin (measure) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
600
|
+
function setPin(el) {
|
|
601
|
+
pinEl = el;
|
|
602
|
+
updatePinHL();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
function updatePinHL() {
|
|
606
|
+
if (!pinEl) return;
|
|
607
|
+
var r = pinEl.getBoundingClientRect();
|
|
608
|
+
if (!pinHighlight) {
|
|
609
|
+
pinHighlight = document.createElement('div');
|
|
610
|
+
Object.assign(pinHighlight.style, {
|
|
611
|
+
position: 'fixed',
|
|
612
|
+
border: '2px dashed ' + RED,
|
|
613
|
+
background: 'rgba(237,62,97,0.05)',
|
|
614
|
+
boxSizing: 'border-box',
|
|
615
|
+
pointerEvents: 'none',
|
|
616
|
+
zIndex: '2147483643',
|
|
617
|
+
});
|
|
618
|
+
document.body.appendChild(pinHighlight);
|
|
619
|
+
}
|
|
620
|
+
pinHighlight.style.left = r.left + 'px';
|
|
621
|
+
pinHighlight.style.top = r.top + 'px';
|
|
622
|
+
pinHighlight.style.width = r.width + 'px';
|
|
623
|
+
pinHighlight.style.height = r.height + 'px';
|
|
624
|
+
}
|
|
418
625
|
|
|
419
626
|
function clearPin() {
|
|
420
627
|
if (pinHighlight) { pinHighlight.remove(); pinHighlight = null; }
|
|
@@ -426,14 +633,51 @@ function getClientScript(options) {
|
|
|
426
633
|
measureOverlay.style.display = 'none';
|
|
427
634
|
}
|
|
428
635
|
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
636
|
+
function showMeasureTargetHL(el) {
|
|
637
|
+
clearMeasureTargetHL();
|
|
638
|
+
var r = el.getBoundingClientRect();
|
|
639
|
+
measureHL = document.createElement('div');
|
|
640
|
+
Object.assign(measureHL.style, {
|
|
641
|
+
position: 'fixed',
|
|
642
|
+
left: r.left + 'px',
|
|
643
|
+
top: r.top + 'px',
|
|
644
|
+
width: r.width + 'px',
|
|
645
|
+
height: r.height + 'px',
|
|
646
|
+
background: 'rgba(173,36,211,0.08)',
|
|
647
|
+
border: '1.5px solid ' + PURPLE,
|
|
648
|
+
boxSizing: 'border-box',
|
|
435
649
|
pointerEvents: 'none',
|
|
650
|
+
zIndex: '2147483643',
|
|
436
651
|
});
|
|
652
|
+
document.body.appendChild(measureHL);
|
|
653
|
+
}
|
|
654
|
+
|
|
655
|
+
function clearMeasureTargetHL() {
|
|
656
|
+
if (measureHL) { measureHL.remove(); measureHL = null; }
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
// \u2500\u2500\u2500 Measurement drawing \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
660
|
+
function cap(x, y, mainHoriz) {
|
|
661
|
+
var c = document.createElement('div');
|
|
662
|
+
Object.assign(c.style, { position: 'absolute', background: RED, pointerEvents: 'none' });
|
|
663
|
+
if (mainHoriz) {
|
|
664
|
+
c.style.left = (x - 0.5) + 'px';
|
|
665
|
+
c.style.top = (y - 4) + 'px';
|
|
666
|
+
c.style.width = '1px';
|
|
667
|
+
c.style.height = '8px';
|
|
668
|
+
} else {
|
|
669
|
+
c.style.left = (x - 4) + 'px';
|
|
670
|
+
c.style.top = (y - 0.5) + 'px';
|
|
671
|
+
c.style.width = '8px';
|
|
672
|
+
c.style.height = '1px';
|
|
673
|
+
}
|
|
674
|
+
measureOverlay.appendChild(c);
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
function drawLine(x1, y1, x2, y2, label) {
|
|
678
|
+
var isHoriz = Math.abs(y2 - y1) < 1;
|
|
679
|
+
var line = document.createElement('div');
|
|
680
|
+
Object.assign(line.style, { position: 'absolute', background: RED, pointerEvents: 'none' });
|
|
437
681
|
if (isHoriz) {
|
|
438
682
|
Object.assign(line.style, {
|
|
439
683
|
left: Math.min(x1, x2) + 'px', top: (y1 - 0.5) + 'px',
|
|
@@ -446,156 +690,149 @@ function getClientScript(options) {
|
|
|
446
690
|
});
|
|
447
691
|
}
|
|
448
692
|
measureOverlay.appendChild(line);
|
|
693
|
+
cap(x1, y1, isHoriz);
|
|
694
|
+
cap(x2, y2, isHoriz);
|
|
449
695
|
|
|
450
696
|
if (label && label !== '0px') {
|
|
451
|
-
|
|
697
|
+
var lbl = document.createElement('div');
|
|
452
698
|
lbl.textContent = label;
|
|
453
699
|
Object.assign(lbl.style, {
|
|
454
700
|
position: 'absolute',
|
|
455
|
-
background:
|
|
701
|
+
background: RED,
|
|
456
702
|
color: '#fff',
|
|
457
703
|
fontSize: '10px',
|
|
458
|
-
fontFamily:
|
|
704
|
+
fontFamily: MONO,
|
|
459
705
|
padding: '1px 4px',
|
|
460
706
|
borderRadius: '3px',
|
|
461
707
|
pointerEvents: 'none',
|
|
462
708
|
whiteSpace: 'nowrap',
|
|
463
709
|
});
|
|
464
710
|
if (isHoriz) {
|
|
465
|
-
|
|
466
|
-
lbl.style.left =
|
|
467
|
-
lbl.style.top = (y1 -
|
|
711
|
+
var cxl = (Math.min(x1, x2) + Math.max(x1, x2)) / 2;
|
|
712
|
+
lbl.style.left = cxl + 'px';
|
|
713
|
+
lbl.style.top = (y1 - 16) + 'px';
|
|
468
714
|
lbl.style.transform = 'translateX(-50%)';
|
|
469
715
|
} else {
|
|
470
|
-
|
|
716
|
+
var cyl = (Math.min(y1, y2) + Math.max(y1, y2)) / 2;
|
|
471
717
|
lbl.style.left = (x1 + 6) + 'px';
|
|
472
|
-
lbl.style.top =
|
|
718
|
+
lbl.style.top = cyl + 'px';
|
|
473
719
|
lbl.style.transform = 'translateY(-50%)';
|
|
474
720
|
}
|
|
475
721
|
measureOverlay.appendChild(lbl);
|
|
476
722
|
}
|
|
477
723
|
}
|
|
478
724
|
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
clearMeasureOverlay();
|
|
498
|
-
const tr = targetEl.getBoundingClientRect();
|
|
499
|
-
let measured = false;
|
|
500
|
-
let cur = targetEl.parentElement;
|
|
501
|
-
|
|
502
|
-
for (let depth = 0; depth < 3 && cur && !measured; depth++) {
|
|
503
|
-
const children = Array.from(cur.children).filter(c => c !== targetEl && c.offsetParent !== null);
|
|
504
|
-
if (children.length === 0) { cur = cur.parentElement; continue; }
|
|
505
|
-
|
|
506
|
-
const closest = { top: null, bottom: null, left: null, right: null };
|
|
507
|
-
const gaps = { top: Infinity, bottom: Infinity, left: Infinity, right: Infinity };
|
|
508
|
-
|
|
509
|
-
for (const sib of children) {
|
|
510
|
-
const sr = sib.getBoundingClientRect();
|
|
511
|
-
if (sr.bottom <= tr.top) {
|
|
512
|
-
const g = tr.top - sr.bottom;
|
|
513
|
-
if (g < gaps.top) { gaps.top = g; closest.top = sib; }
|
|
514
|
-
}
|
|
515
|
-
if (sr.top >= tr.bottom) {
|
|
516
|
-
const g = sr.top - tr.bottom;
|
|
517
|
-
if (g < gaps.bottom) { gaps.bottom = g; closest.bottom = sib; }
|
|
518
|
-
}
|
|
519
|
-
if (sr.right <= tr.left) {
|
|
520
|
-
const g = tr.left - sr.right;
|
|
521
|
-
if (g < gaps.left) { gaps.left = g; closest.left = sib; }
|
|
522
|
-
}
|
|
523
|
-
if (sr.left >= tr.right) {
|
|
524
|
-
const g = sr.left - tr.right;
|
|
525
|
-
if (g < gaps.right) { gaps.right = g; closest.right = sib; }
|
|
526
|
-
}
|
|
725
|
+
// \u2500\u2500\u2500 Neighbor computation (parent-first w/ container fallback) \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
726
|
+
function edgeToAncestor(targetEl, tr, dir) {
|
|
727
|
+
var cur = targetEl.parentElement;
|
|
728
|
+
var ctx = 'parent';
|
|
729
|
+
for (var i = 0; i < 4 && cur && cur !== document.body; i++) {
|
|
730
|
+
var pr = cur.getBoundingClientRect();
|
|
731
|
+
var gap;
|
|
732
|
+
if (dir === 'top') gap = tr.top - pr.top;
|
|
733
|
+
else if (dir === 'bottom') gap = pr.bottom - tr.bottom;
|
|
734
|
+
else if (dir === 'left') gap = tr.left - pr.left;
|
|
735
|
+
else gap = pr.right - tr.right;
|
|
736
|
+
if (gap > 0.5) {
|
|
737
|
+
var ed;
|
|
738
|
+
if (dir === 'top') ed = pr.top;
|
|
739
|
+
else if (dir === 'bottom') ed = pr.bottom;
|
|
740
|
+
else if (dir === 'left') ed = pr.left;
|
|
741
|
+
else ed = pr.right;
|
|
742
|
+
return { gap: gap, ctx: ctx, edge: ed };
|
|
527
743
|
}
|
|
528
|
-
|
|
529
|
-
if (closest.top || closest.bottom || closest.left || closest.right) {
|
|
530
|
-
measured = true;
|
|
531
|
-
const cx = (tr.left + tr.right) / 2;
|
|
532
|
-
const cy = (tr.top + tr.bottom) / 2;
|
|
533
|
-
if (closest.top) drawLine(cx, closest.top.getBoundingClientRect().bottom, cx, tr.top, Math.round(gaps.top) + 'px');
|
|
534
|
-
if (closest.bottom) drawLine(cx, tr.bottom, cx, closest.bottom.getBoundingClientRect().top, Math.round(gaps.bottom) + 'px');
|
|
535
|
-
if (closest.left) drawLine(closest.left.getBoundingClientRect().right, cy, tr.left, cy, Math.round(gaps.left) + 'px');
|
|
536
|
-
if (closest.right) drawLine(tr.right, cy, closest.right.getBoundingClientRect().left, cy, Math.round(gaps.right) + 'px');
|
|
537
|
-
} else {
|
|
538
|
-
const pr = cur.getBoundingClientRect();
|
|
539
|
-
const cx = (tr.left + tr.right) / 2;
|
|
540
|
-
const cy = (tr.top + tr.bottom) / 2;
|
|
541
|
-
const gT = tr.top - pr.top, gB = pr.bottom - tr.bottom;
|
|
542
|
-
const gL = tr.left - pr.left, gR = pr.right - tr.right;
|
|
543
|
-
if (gT > 0) drawLine(cx, pr.top, cx, tr.top, Math.round(gT) + 'px');
|
|
544
|
-
if (gB > 0) drawLine(cx, tr.bottom, cx, pr.bottom, Math.round(gB) + 'px');
|
|
545
|
-
if (gL > 0) drawLine(pr.left, cy, tr.left, cy, Math.round(gL) + 'px');
|
|
546
|
-
if (gR > 0) drawLine(tr.right, cy, pr.right, cy, Math.round(gR) + 'px');
|
|
547
|
-
measured = true;
|
|
548
|
-
}
|
|
549
|
-
|
|
550
744
|
cur = cur.parentElement;
|
|
745
|
+
ctx = 'container';
|
|
551
746
|
}
|
|
747
|
+
return null;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function computeNeighbors(targetEl, tr) {
|
|
751
|
+
var out = { top: null, bottom: null, left: null, right: null };
|
|
752
|
+
var parent = targetEl.parentElement;
|
|
753
|
+
if (parent) {
|
|
754
|
+
var sibs = Array.prototype.slice.call(parent.children).filter(function (c) { return c !== targetEl && c.offsetParent !== null; });
|
|
755
|
+
sibs.forEach(function (sib) {
|
|
756
|
+
var sr = sib.getBoundingClientRect();
|
|
757
|
+
if (sr.bottom <= tr.top) { var g = tr.top - sr.bottom; if (!out.top || g < out.top.gap) out.top = { gap: g, ctx: 'sibling', edge: sr.bottom }; }
|
|
758
|
+
if (sr.top >= tr.bottom) { var g2 = sr.top - tr.bottom; if (!out.bottom || g2 < out.bottom.gap) out.bottom = { gap: g2, ctx: 'sibling', edge: sr.top }; }
|
|
759
|
+
if (sr.right <= tr.left) { var g3 = tr.left - sr.right; if (!out.left || g3 < out.left.gap) out.left = { gap: g3, ctx: 'sibling', edge: sr.right }; }
|
|
760
|
+
if (sr.left >= tr.right) { var g4 = sr.left - tr.right; if (!out.right || g4 < out.right.gap) out.right = { gap: g4, ctx: 'sibling', edge: sr.left }; }
|
|
761
|
+
});
|
|
762
|
+
}
|
|
763
|
+
['top', 'bottom', 'left', 'right'].forEach(function (k) { if (!out[k]) out[k] = edgeToAncestor(targetEl, tr, k); });
|
|
764
|
+
return out;
|
|
765
|
+
}
|
|
552
766
|
|
|
767
|
+
function measureToNeighbor(targetEl) {
|
|
768
|
+
clearMeasureOverlay();
|
|
769
|
+
var tr = targetEl.getBoundingClientRect();
|
|
770
|
+
var dirs = computeNeighbors(targetEl, tr);
|
|
771
|
+
var cx = (tr.left + tr.right) / 2;
|
|
772
|
+
var cy = (tr.top + tr.bottom) / 2;
|
|
773
|
+
if (dirs.top) drawLine(cx, dirs.top.edge, cx, tr.top, Math.round(dirs.top.gap) + 'px');
|
|
774
|
+
if (dirs.bottom) drawLine(cx, tr.bottom, cx, dirs.bottom.edge, Math.round(dirs.bottom.gap) + 'px');
|
|
775
|
+
if (dirs.left) drawLine(dirs.left.edge, cy, tr.left, cy, Math.round(dirs.left.gap) + 'px');
|
|
776
|
+
if (dirs.right) drawLine(tr.right, cy, dirs.right.edge, cy, Math.round(dirs.right.gap) + 'px');
|
|
553
777
|
measureOverlay.style.display = 'block';
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
778
|
+
var tag = targetEl.tagName.toLowerCase();
|
|
779
|
+
var lines = ['\u2B21 Measure (M pin \xB7 Cmd+C copy)', '<' + tag + '> ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height)];
|
|
780
|
+
['top', 'right', 'bottom', 'left'].forEach(function (k) {
|
|
781
|
+
if (dirs[k]) lines.push(k + ': ' + Math.round(dirs[k].gap) + 'px (to ' + dirs[k].ctx + ')');
|
|
782
|
+
});
|
|
783
|
+
return lines.join('\\n');
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
function buildNeighborCopyText(el) {
|
|
787
|
+
var tr = el.getBoundingClientRect();
|
|
788
|
+
var dirs = computeNeighbors(el, tr);
|
|
789
|
+
var lines = ['[Specter Measure]', '<' + el.tagName.toLowerCase() + '> ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height), 'selector: ' + getSelector(el)];
|
|
790
|
+
['top', 'right', 'bottom', 'left'].forEach(function (k) {
|
|
791
|
+
if (dirs[k]) lines.push(k + ': ' + Math.round(dirs[k].gap) + 'px (to ' + dirs[k].ctx + ')');
|
|
792
|
+
});
|
|
793
|
+
return lines.join('\\n');
|
|
557
794
|
}
|
|
558
795
|
|
|
559
796
|
function measureBetween(fromEl, toEl) {
|
|
560
797
|
clearMeasureOverlay();
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
798
|
+
var fr = fromEl.getBoundingClientRect();
|
|
799
|
+
var tr = toEl.getBoundingClientRect();
|
|
800
|
+
var fTag = fromEl.tagName.toLowerCase(), tTag = toEl.tagName.toLowerCase();
|
|
801
|
+
var fComp = getComponentName(fromEl), tComp = getComponentName(toEl);
|
|
565
802
|
|
|
566
|
-
|
|
567
|
-
lines.push(
|
|
568
|
-
lines.push(
|
|
803
|
+
var lines = ['\u2B21 Measure (Cmd+C copy)'];
|
|
804
|
+
lines.push('From: <' + fTag + '>' + (fComp ? ' ' + fComp : '') + ' ' + Math.round(fr.width) + '\xD7' + Math.round(fr.height));
|
|
805
|
+
lines.push('To: <' + tTag + '>' + (tComp ? ' ' + tComp : '') + ' ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height));
|
|
569
806
|
|
|
570
|
-
|
|
571
|
-
|
|
807
|
+
var fromContainsTo = fr.left <= tr.left && fr.top <= tr.top && fr.right >= tr.right && fr.bottom >= tr.bottom;
|
|
808
|
+
var toContainsFrom = tr.left <= fr.left && tr.top <= fr.top && tr.right >= fr.right && tr.bottom >= fr.bottom;
|
|
572
809
|
|
|
573
810
|
if (fromContainsTo || toContainsFrom) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
811
|
+
var outer = fromContainsTo ? fr : tr;
|
|
812
|
+
var inner = fromContainsTo ? tr : fr;
|
|
813
|
+
var cx = (inner.left + inner.right) / 2, cy = (inner.top + inner.bottom) / 2;
|
|
814
|
+
var iT = inner.top - outer.top, iB = outer.bottom - inner.bottom;
|
|
815
|
+
var iL = inner.left - outer.left, iR = outer.right - inner.right;
|
|
579
816
|
if (iT > 0) drawLine(cx, outer.top, cx, inner.top, Math.round(iT) + 'px');
|
|
580
817
|
if (iB > 0) drawLine(cx, inner.bottom, cx, outer.bottom, Math.round(iB) + 'px');
|
|
581
818
|
if (iL > 0) drawLine(outer.left, cy, inner.left, cy, Math.round(iL) + 'px');
|
|
582
819
|
if (iR > 0) drawLine(inner.right, cy, outer.right, cy, Math.round(iR) + 'px');
|
|
583
|
-
lines.push(
|
|
820
|
+
lines.push('inset: ' + Math.round(iT) + 'px ' + Math.round(iR) + 'px ' + Math.round(iB) + 'px ' + Math.round(iL) + 'px');
|
|
584
821
|
} else {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
822
|
+
var vGap = fr.bottom <= tr.top ? tr.top - fr.bottom : tr.bottom <= fr.top ? fr.top - tr.bottom : 0;
|
|
823
|
+
var hGap = fr.right <= tr.left ? tr.left - fr.right : tr.right <= fr.left ? fr.left - tr.right : 0;
|
|
824
|
+
var cx2 = (fr.left + fr.right) / 2, cy2 = (fr.top + fr.bottom) / 2;
|
|
588
825
|
if (vGap > 0) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
drawLine(
|
|
592
|
-
lines.push(
|
|
826
|
+
var y1 = fr.bottom <= tr.top ? fr.bottom : tr.bottom;
|
|
827
|
+
var y2 = fr.bottom <= tr.top ? tr.top : fr.top;
|
|
828
|
+
drawLine(cx2, y1, cx2, y2, Math.round(vGap) + 'px');
|
|
829
|
+
lines.push('vertical gap: ' + Math.round(vGap) + 'px');
|
|
593
830
|
}
|
|
594
831
|
if (hGap > 0) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
drawLine(x1,
|
|
598
|
-
lines.push(
|
|
832
|
+
var x1 = fr.right <= tr.left ? fr.right : tr.right;
|
|
833
|
+
var x2 = fr.right <= tr.left ? tr.left : fr.left;
|
|
834
|
+
drawLine(x1, cy2, x2, cy2, Math.round(hGap) + 'px');
|
|
835
|
+
lines.push('horizontal gap: ' + Math.round(hGap) + 'px');
|
|
599
836
|
}
|
|
600
837
|
if (vGap === 0 && hGap === 0) lines.push('Elements overlap');
|
|
601
838
|
}
|
|
@@ -605,28 +842,26 @@ function getClientScript(options) {
|
|
|
605
842
|
}
|
|
606
843
|
|
|
607
844
|
function buildMeasureCopyText(fromEl, toEl) {
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
lines.push(
|
|
618
|
-
lines.push(
|
|
619
|
-
lines.push(\`To: <\${tTag}>\${tComp ? ' ' + tComp : ''} \${Math.round(tr.width)}\xD7\${Math.round(tr.height)}\${tText ? ' "' + tText + '"' : ''}\`);
|
|
620
|
-
lines.push(\` selector: \${getSelector(toEl)}\`);
|
|
845
|
+
var fr = fromEl.getBoundingClientRect(), tr = toEl.getBoundingClientRect();
|
|
846
|
+
var fromContainsTo = fr.left <= tr.left && fr.top <= tr.top && fr.right >= tr.right && fr.bottom >= tr.bottom;
|
|
847
|
+
var toContainsFrom = tr.left <= fr.left && tr.top <= fr.top && tr.right >= fr.right && tr.bottom >= fr.bottom;
|
|
848
|
+
var fTag = fromEl.tagName.toLowerCase(), tTag = toEl.tagName.toLowerCase();
|
|
849
|
+
var fComp = getComponentName(fromEl), tComp = getComponentName(toEl);
|
|
850
|
+
|
|
851
|
+
var lines = ['[Specter Measure]'];
|
|
852
|
+
lines.push('From: <' + fTag + '>' + (fComp ? ' ' + fComp : '') + ' ' + Math.round(fr.width) + '\xD7' + Math.round(fr.height));
|
|
853
|
+
lines.push(' selector: ' + getSelector(fromEl));
|
|
854
|
+
lines.push('To: <' + tTag + '>' + (tComp ? ' ' + tComp : '') + ' ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height));
|
|
855
|
+
lines.push(' selector: ' + getSelector(toEl));
|
|
621
856
|
|
|
622
857
|
if (fromContainsTo || toContainsFrom) {
|
|
623
|
-
|
|
624
|
-
lines.push(
|
|
858
|
+
var outer = fromContainsTo ? fr : tr, inner = fromContainsTo ? tr : fr;
|
|
859
|
+
lines.push('inset: ' + Math.round(inner.top - outer.top) + 'px ' + Math.round(outer.right - inner.right) + 'px ' + Math.round(outer.bottom - inner.bottom) + 'px ' + Math.round(inner.left - outer.left) + 'px');
|
|
625
860
|
} else {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (vGap > 0) lines.push(
|
|
629
|
-
if (hGap > 0) lines.push(
|
|
861
|
+
var vGap = fr.bottom <= tr.top ? tr.top - fr.bottom : tr.bottom <= fr.top ? fr.top - tr.bottom : 0;
|
|
862
|
+
var hGap = fr.right <= tr.left ? tr.left - fr.right : tr.right <= fr.left ? fr.left - tr.right : 0;
|
|
863
|
+
if (vGap > 0) lines.push('vertical gap: ' + Math.round(vGap) + 'px');
|
|
864
|
+
if (hGap > 0) lines.push('horizontal gap: ' + Math.round(hGap) + 'px');
|
|
630
865
|
if (vGap === 0 && hGap === 0) lines.push('Elements overlap');
|
|
631
866
|
}
|
|
632
867
|
return lines.join('\\n');
|
|
@@ -634,51 +869,55 @@ function getClientScript(options) {
|
|
|
634
869
|
|
|
635
870
|
// \u2500\u2500\u2500 Shortcut parser \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
636
871
|
function matchesActivate(e) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
872
|
+
var parts = ACTIVATE.toLowerCase().split('+');
|
|
873
|
+
var needCtrl = parts.indexOf('ctrl') >= 0;
|
|
874
|
+
var needAlt = parts.indexOf('alt') >= 0;
|
|
875
|
+
var needShift = parts.indexOf('shift') >= 0;
|
|
876
|
+
var needMeta = parts.indexOf('meta') >= 0 || parts.indexOf('cmd') >= 0;
|
|
877
|
+
var key = parts.filter(function (p) { return ['ctrl', 'alt', 'shift', 'meta', 'cmd'].indexOf(p) < 0; })[0];
|
|
643
878
|
if (needCtrl !== e.ctrlKey) return false;
|
|
644
879
|
if (needAlt !== e.altKey) return false;
|
|
645
880
|
if (needShift !== e.shiftKey) return false;
|
|
646
881
|
if (needMeta !== e.metaKey) return false;
|
|
647
882
|
if (!key) return false;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return eKey === key || eCode ===
|
|
883
|
+
var eKey = e.key.toLowerCase();
|
|
884
|
+
var eCode = e.code.toLowerCase();
|
|
885
|
+
return eKey === key || eCode === 'key' + key || (key === 'period' && (eKey === '.' || eCode === 'period'));
|
|
651
886
|
}
|
|
652
887
|
|
|
653
888
|
// \u2500\u2500\u2500 Mouse \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
654
889
|
function onMouseMove(e) {
|
|
655
890
|
if (!fiActive) return;
|
|
656
|
-
|
|
657
|
-
|
|
891
|
+
lastMouse.x = e.clientX; lastMouse.y = e.clientY;
|
|
892
|
+
var target = e.target;
|
|
893
|
+
if (!target || target === pillWrap || pillWrap.contains(target)) return;
|
|
658
894
|
if (target === tooltip || tooltip.contains(target)) return;
|
|
659
895
|
|
|
660
896
|
lastHovered = target;
|
|
661
897
|
|
|
662
898
|
if (measureMode) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
tooltip.
|
|
899
|
+
clearHoverOutline();
|
|
900
|
+
showMeasureTargetHL(target);
|
|
901
|
+
var text = (pinEl && pinEl !== target) ? measureBetween(pinEl, target) : measureToNeighbor(target);
|
|
902
|
+
tooltip.innerHTML = linesToHTML(text);
|
|
667
903
|
positionTooltip(e.clientX, e.clientY);
|
|
904
|
+
if (pinEl) updatePinHL();
|
|
668
905
|
} else {
|
|
669
906
|
clearMeasureOverlay();
|
|
670
|
-
|
|
907
|
+
clearMeasureTargetHL();
|
|
908
|
+
setHoverOutline(target);
|
|
909
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(target));
|
|
671
910
|
positionTooltip(e.clientX, e.clientY);
|
|
672
911
|
}
|
|
673
912
|
}
|
|
674
913
|
|
|
675
914
|
// \u2500\u2500\u2500 Keyboard \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500
|
|
676
915
|
function isInputFocused() {
|
|
677
|
-
|
|
916
|
+
var el = document.activeElement;
|
|
678
917
|
return !!el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable);
|
|
679
918
|
}
|
|
680
919
|
|
|
681
|
-
document.addEventListener('keydown', (e)
|
|
920
|
+
document.addEventListener('keydown', function (e) {
|
|
682
921
|
if (matchesActivate(e)) {
|
|
683
922
|
e.preventDefault();
|
|
684
923
|
if (fiActive) deactivate(); else activate();
|
|
@@ -687,51 +926,48 @@ function getClientScript(options) {
|
|
|
687
926
|
|
|
688
927
|
if (!fiActive) return;
|
|
689
928
|
|
|
690
|
-
// Option tap (keydown only \u2014 actual toggle fires on keyup)
|
|
691
929
|
if (e.key === 'Alt' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
|
692
930
|
optionHeld = true;
|
|
693
931
|
return;
|
|
694
932
|
}
|
|
695
933
|
|
|
696
|
-
// Cmd+C \u2014 copy
|
|
697
934
|
if ((e.metaKey || e.ctrlKey) && e.key === 'c' && !e.shiftKey && !e.altKey) {
|
|
698
935
|
e.preventDefault();
|
|
699
|
-
|
|
936
|
+
var text;
|
|
700
937
|
if (selectedEls.length > 0) {
|
|
701
938
|
text = buildMultiSelectCopyText();
|
|
702
939
|
} else if (measureMode && pinEl && lastHovered && lastHovered !== pinEl) {
|
|
703
940
|
text = buildMeasureCopyText(pinEl, lastHovered);
|
|
941
|
+
} else if (measureMode && lastHovered) {
|
|
942
|
+
text = buildNeighborCopyText(lastHovered);
|
|
704
943
|
} else if (lastHovered) {
|
|
705
|
-
text =
|
|
944
|
+
text = buildLLMClipboard(buildInfo(lastHovered));
|
|
706
945
|
}
|
|
707
|
-
if (text) navigator.clipboard.writeText(text).catch(()
|
|
946
|
+
if (text) navigator.clipboard.writeText(text).then(showCopied).catch(function () {});
|
|
708
947
|
return;
|
|
709
948
|
}
|
|
710
949
|
|
|
711
950
|
if (isInputFocused()) return;
|
|
712
951
|
|
|
713
|
-
// P \u2014 pick/unpick element (multi-select), Properties mode only
|
|
714
952
|
if (e.key === 'p' || e.key === 'P') {
|
|
715
953
|
if (measureMode || !lastHovered) return;
|
|
954
|
+
clearHoverOutline();
|
|
716
955
|
toggleSelection(lastHovered);
|
|
717
956
|
return;
|
|
718
957
|
}
|
|
719
958
|
|
|
720
|
-
// M \u2014 pin element for measure-from, Measure mode only
|
|
721
959
|
if (e.key === 'm' || e.key === 'M') {
|
|
722
960
|
if (!measureMode || !lastHovered) return;
|
|
723
961
|
if (pinEl) {
|
|
724
962
|
clearPin();
|
|
725
963
|
collapsePill();
|
|
726
964
|
} else {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
expandPill('Pinned \xB7 hover another element \xB7 Cmd+C \u2192 copy \xB7 Esc \u2192 clear');
|
|
965
|
+
setPin(lastHovered);
|
|
966
|
+
expandPill('Pinned \xB7 hover another element \xB7 Cmd+C copy \xB7 Esc clear');
|
|
730
967
|
}
|
|
731
968
|
return;
|
|
732
969
|
}
|
|
733
970
|
|
|
734
|
-
// Escape cascade: pin \u2192 selection \u2192 exit
|
|
735
971
|
if (e.key === 'Escape') {
|
|
736
972
|
if (pinEl) {
|
|
737
973
|
clearPin();
|
|
@@ -748,12 +984,14 @@ function getClientScript(options) {
|
|
|
748
984
|
}
|
|
749
985
|
}, true);
|
|
750
986
|
|
|
751
|
-
document.addEventListener('keyup', (e)
|
|
987
|
+
document.addEventListener('keyup', function (e) {
|
|
752
988
|
if (!fiActive) return;
|
|
753
989
|
if (e.key === 'Alt' && optionHeld) {
|
|
754
990
|
optionHeld = false;
|
|
755
991
|
measureMode = !measureMode;
|
|
756
992
|
clearMeasureOverlay();
|
|
993
|
+
clearHoverOutline();
|
|
994
|
+
clearMeasureTargetHL();
|
|
757
995
|
if (!measureMode) clearPin();
|
|
758
996
|
hideTooltip();
|
|
759
997
|
flashMode();
|
|
@@ -773,7 +1011,7 @@ function specter(options = {}) {
|
|
|
773
1011
|
apply: "serve",
|
|
774
1012
|
transformIndexHtml(html) {
|
|
775
1013
|
const script = getClientScript(options);
|
|
776
|
-
return html.replace("</body>", `<script>
|
|
1014
|
+
return html.replace("</body>", () => `<script>
|
|
777
1015
|
${script}
|
|
778
1016
|
</script>
|
|
779
1017
|
</body>`);
|