vite-plugin-specter 0.1.2 → 0.2.1
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 +588 -344
- package/dist/index.cjs +588 -344
- package/dist/index.js +588 -344
- package/extension/content.js +588 -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,341 @@ 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
|
+
id: el.id || null,
|
|
413
|
+
classes: Array.prototype.slice.call(el.classList).filter(function (c) { return c.indexOf('__specter') !== 0; }),
|
|
414
|
+
component: getComponentName(el),
|
|
415
|
+
styleKey: el.dataset ? el.dataset.style : null,
|
|
416
|
+
width: Math.round(rect.width),
|
|
417
|
+
height: Math.round(rect.height),
|
|
418
|
+
text: raw.slice(0, 40),
|
|
419
|
+
textTrunc: raw.length > 40,
|
|
420
|
+
font: { family: ff, weight: cs.fontWeight, size: cs.fontSize, lineHeight: cs.lineHeight },
|
|
421
|
+
color: colorObj(cs.color),
|
|
422
|
+
bg: colorObj(cs.backgroundColor),
|
|
423
|
+
padding: edge(cs.paddingTop, cs.paddingRight, cs.paddingBottom, cs.paddingLeft),
|
|
424
|
+
margin: edge(cs.marginTop, cs.marginRight, cs.marginBottom, cs.marginLeft),
|
|
425
|
+
radius: (cs.borderRadius && cs.borderRadius !== '0px') ? cs.borderRadius : null,
|
|
426
|
+
display: ['flex', 'grid', 'inline-flex', 'inline-grid'].indexOf(cs.display) >= 0 ? cs.display : null,
|
|
427
|
+
gap: (cs.gap && cs.gap !== 'normal') ? cs.gap : null,
|
|
428
|
+
flexDir: (cs.display.indexOf('flex') >= 0 && cs.flexDirection !== 'row') ? cs.flexDirection : null,
|
|
429
|
+
rules: getMatchingRules(el),
|
|
430
|
+
};
|
|
431
|
+
}
|
|
315
432
|
|
|
316
|
-
|
|
317
|
-
|
|
433
|
+
function row(label, val) {
|
|
434
|
+
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>';
|
|
435
|
+
}
|
|
318
436
|
|
|
319
|
-
|
|
320
|
-
|
|
437
|
+
function buildHumanDisplay(data) {
|
|
438
|
+
var h = '';
|
|
439
|
+
var id = '<span style="color:#fff"><' + data.tag + '></span>';
|
|
440
|
+
if (data.id) id += '<span style="color:#F0B86E">#' + esc(data.id) + '</span>';
|
|
441
|
+
if (data.classes.length) id += '<span style="color:#5FD3C0">.' + data.classes.map(esc).join('.') + '</span>';
|
|
442
|
+
if (data.component) id += ' <span style="color:#E0A3F5;font-weight:600">' + esc(data.component) + '</span>';
|
|
443
|
+
if (data.styleKey) id += ' <span style="color:' + LABEL + '">[' + esc(data.styleKey) + ']</span>';
|
|
444
|
+
id += ' <span style="color:' + LABEL + '">' + data.width + '\xD7' + data.height + '</span>';
|
|
445
|
+
h += '<div style="margin-bottom:8px">' + id + '</div>';
|
|
446
|
+
if (data.text) h += '<div style="color:' + LABEL + ';font-style:italic;margin-bottom:8px">"' + esc(data.text) + (data.textTrunc ? '\u2026' : '') + '"</div>';
|
|
447
|
+
h += '<div style="height:8px"></div>';
|
|
448
|
+
h += row('Font', esc(data.font.family) + ' \xB7 ' + weightName(data.font.weight) + ' \xB7 ' + data.font.size + '/' + data.font.lineHeight);
|
|
449
|
+
if (data.color) h += row('Color', swatch(data.color.hex) + data.color.label);
|
|
450
|
+
if (data.bg) h += row('Bg', swatch(data.bg.hex) + data.bg.label);
|
|
451
|
+
if (data.padding) h += row('Padding', data.padding.value);
|
|
452
|
+
if (data.margin) h += row('Margin', data.margin.value);
|
|
453
|
+
if (data.radius) h += row('Radius', data.radius);
|
|
454
|
+
if (data.display) {
|
|
455
|
+
var d = data.display;
|
|
456
|
+
if (data.gap) d += ' \xB7 gap ' + data.gap;
|
|
457
|
+
if (data.flexDir) d += ' \xB7 ' + data.flexDir;
|
|
458
|
+
h += row('Display', d);
|
|
459
|
+
}
|
|
460
|
+
return h;
|
|
461
|
+
}
|
|
321
462
|
|
|
322
|
-
|
|
323
|
-
|
|
463
|
+
function buildLLMClipboard(data) {
|
|
464
|
+
var lines = ['[Specter]'];
|
|
465
|
+
var head = '<' + data.tag + '>';
|
|
466
|
+
if (data.id) head += '#' + data.id;
|
|
467
|
+
if (data.classes.length) head += '.' + data.classes.join('.');
|
|
468
|
+
if (data.component) head += ' ' + data.component;
|
|
469
|
+
if (data.styleKey) head += ' [' + data.styleKey + ']';
|
|
470
|
+
head += ' ' + data.width + '\xD7' + data.height;
|
|
471
|
+
lines.push(head);
|
|
472
|
+
if (data.text) lines.push('"' + data.text + (data.textTrunc ? '\u2026' : '') + '"');
|
|
473
|
+
lines.push('font: ' + data.font.family + ' ' + data.font.weight + ' ' + data.font.size + '/' + data.font.lineHeight);
|
|
474
|
+
if (data.color) lines.push('color: ' + data.color.label);
|
|
475
|
+
if (data.bg) lines.push('bg: ' + data.bg.label);
|
|
476
|
+
if (data.padding) lines.push('padding: ' + data.padding.value);
|
|
477
|
+
if (data.margin) lines.push('margin: ' + data.margin.value);
|
|
478
|
+
if (data.radius) lines.push('radius: ' + data.radius);
|
|
479
|
+
if (data.display) {
|
|
480
|
+
var d = 'display: ' + data.display;
|
|
481
|
+
if (data.gap) d += ' gap: ' + data.gap;
|
|
482
|
+
if (data.flexDir) d += ' dir: ' + data.flexDir;
|
|
483
|
+
lines.push(d);
|
|
484
|
+
}
|
|
485
|
+
lines.push('selector: ' + getSelector(data.el));
|
|
486
|
+
var out = lines.join('\\n');
|
|
487
|
+
if (data.rules && data.rules.length) out += '\\n\\n--- CSS Rules ---\\n' + data.rules.join('\\n\\n');
|
|
488
|
+
return out;
|
|
489
|
+
}
|
|
324
490
|
|
|
325
|
-
|
|
326
|
-
|
|
491
|
+
function buildMultiSelectCopyText() {
|
|
492
|
+
return selectedEls.map(function (el, i) {
|
|
493
|
+
var body = buildLLMClipboard(buildInfo(el));
|
|
494
|
+
return body.replace('[Specter]', '[Specter ' + (i + 1) + '/' + selectedEls.length + ']');
|
|
495
|
+
}).join('\\n\\n' + Array(41).join('\u2500') + '\\n\\n');
|
|
496
|
+
}
|
|
327
497
|
|
|
328
|
-
|
|
329
|
-
|
|
498
|
+
function linesToHTML(str) {
|
|
499
|
+
return str.split('\\n').map(function (l) {
|
|
500
|
+
return '<div style="margin-bottom:4px;color:#fff">' + esc(l) + '</div>';
|
|
501
|
+
}).join('');
|
|
502
|
+
}
|
|
330
503
|
|
|
331
|
-
|
|
504
|
+
// \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
|
|
505
|
+
function setHoverOutline(el) {
|
|
506
|
+
if (outlinedEl === el) return;
|
|
507
|
+
clearHoverOutline();
|
|
508
|
+
if (!el) return;
|
|
509
|
+
outlinedEl = el;
|
|
510
|
+
prevOutline = el.style.outline;
|
|
511
|
+
el.style.outline = '2px solid ' + PURPLE;
|
|
512
|
+
el.style.outlineOffset = '-1px';
|
|
513
|
+
}
|
|
332
514
|
|
|
333
|
-
|
|
334
|
-
if (
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
if (disp.includes('flex') && cs.flexDirection !== 'row') d += \` dir: \${cs.flexDirection}\`;
|
|
338
|
-
lines.push(d);
|
|
515
|
+
function clearHoverOutline() {
|
|
516
|
+
if (outlinedEl) {
|
|
517
|
+
outlinedEl.style.outline = prevOutline;
|
|
518
|
+
outlinedEl = null;
|
|
339
519
|
}
|
|
340
|
-
|
|
341
|
-
return lines.join('\\n');
|
|
342
520
|
}
|
|
343
521
|
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
522
|
+
// \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
|
|
523
|
+
function showCopied() {
|
|
524
|
+
tooltip.innerHTML = '<div style="color:' + GREEN + ';font-weight:600;display:flex;align-items:center;gap:6px"><span>\u2713</span><span>Copied!</span></div>';
|
|
525
|
+
tooltip.style.display = 'block';
|
|
526
|
+
clearTimeout(copiedTimer);
|
|
527
|
+
copiedTimer = setTimeout(function () {
|
|
528
|
+
if (fiActive && lastHovered) reRenderTooltip();
|
|
529
|
+
else hideTooltip();
|
|
530
|
+
}, 1200);
|
|
350
531
|
}
|
|
351
532
|
|
|
352
|
-
function
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
533
|
+
function reRenderTooltip() {
|
|
534
|
+
if (measureMode) {
|
|
535
|
+
var text = (pinEl && pinEl !== lastHovered) ? measureBetween(pinEl, lastHovered) : measureToNeighbor(lastHovered);
|
|
536
|
+
tooltip.innerHTML = linesToHTML(text);
|
|
537
|
+
} else {
|
|
538
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(lastHovered));
|
|
539
|
+
}
|
|
540
|
+
positionTooltip(lastMouse.x, lastMouse.y);
|
|
360
541
|
}
|
|
361
542
|
|
|
362
543
|
// \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
544
|
function makeBadge(el, index) {
|
|
364
|
-
|
|
365
|
-
|
|
545
|
+
var rect = el.getBoundingClientRect();
|
|
546
|
+
var badge = document.createElement('div');
|
|
366
547
|
badge.textContent = String(index + 1);
|
|
367
548
|
Object.assign(badge.style, {
|
|
368
549
|
position: 'fixed',
|
|
369
|
-
left: rect.left + 'px',
|
|
370
|
-
top: rect.top + 'px',
|
|
371
|
-
width: '
|
|
372
|
-
height: '
|
|
373
|
-
background:
|
|
550
|
+
left: (rect.left - 4) + 'px',
|
|
551
|
+
top: (rect.top - 4) + 'px',
|
|
552
|
+
width: '20px',
|
|
553
|
+
height: '20px',
|
|
554
|
+
background: PURPLE,
|
|
374
555
|
color: '#fff',
|
|
375
|
-
fontSize: '
|
|
556
|
+
fontSize: '11px',
|
|
376
557
|
fontWeight: '700',
|
|
377
|
-
fontFamily:
|
|
558
|
+
fontFamily: MONO,
|
|
378
559
|
borderRadius: '50%',
|
|
379
560
|
display: 'flex',
|
|
380
561
|
alignItems: 'center',
|
|
381
562
|
justifyContent: 'center',
|
|
382
563
|
zIndex: '2147483644',
|
|
383
564
|
pointerEvents: 'none',
|
|
384
|
-
boxShadow: '0
|
|
385
|
-
transform: 'translate(-50%, -50%)',
|
|
565
|
+
boxShadow: '0 2px 6px rgba(0,0,0,0.3)',
|
|
386
566
|
});
|
|
387
567
|
document.body.appendChild(badge);
|
|
388
568
|
return badge;
|
|
389
569
|
}
|
|
390
570
|
|
|
391
571
|
function rebuildBadges() {
|
|
392
|
-
selectedBadges.forEach(b
|
|
572
|
+
selectedBadges.forEach(function (b) { b.remove(); });
|
|
393
573
|
selectedBadges.length = 0;
|
|
394
|
-
selectedEls.forEach((el, i)
|
|
574
|
+
selectedEls.forEach(function (el, i) {
|
|
395
575
|
selectedBadges.push(makeBadge(el, i));
|
|
396
576
|
});
|
|
397
577
|
}
|
|
398
578
|
|
|
399
579
|
function toggleSelection(el) {
|
|
400
|
-
|
|
580
|
+
var idx = selectedEls.indexOf(el);
|
|
401
581
|
if (idx >= 0) {
|
|
402
582
|
selectedEls.splice(idx, 1);
|
|
583
|
+
el.style.outline = '';
|
|
403
584
|
} else {
|
|
404
585
|
selectedEls.push(el);
|
|
586
|
+
el.style.outline = '2px solid ' + PURPLE;
|
|
587
|
+
el.style.outlineOffset = '-1px';
|
|
405
588
|
}
|
|
406
589
|
rebuildBadges();
|
|
407
590
|
updatePillForSelection();
|
|
408
591
|
}
|
|
409
592
|
|
|
410
593
|
function clearSelection() {
|
|
594
|
+
selectedEls.forEach(function (el) { el.style.outline = ''; });
|
|
411
595
|
selectedEls.length = 0;
|
|
412
|
-
selectedBadges.forEach(b
|
|
596
|
+
selectedBadges.forEach(function (b) { b.remove(); });
|
|
413
597
|
selectedBadges.length = 0;
|
|
414
598
|
}
|
|
415
599
|
|
|
416
|
-
|
|
417
|
-
|
|
600
|
+
function updatePillForSelection() {
|
|
601
|
+
if (selectedEls.length > 0) expandPill();
|
|
602
|
+
else if (!pillWrap.matches(':hover')) collapsePill();
|
|
603
|
+
}
|
|
604
|
+
|
|
605
|
+
// \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
|
|
606
|
+
function setPin(el) {
|
|
607
|
+
pinEl = el;
|
|
608
|
+
updatePinHL();
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
function updatePinHL() {
|
|
612
|
+
if (!pinEl) return;
|
|
613
|
+
var r = pinEl.getBoundingClientRect();
|
|
614
|
+
if (!pinHighlight) {
|
|
615
|
+
pinHighlight = document.createElement('div');
|
|
616
|
+
Object.assign(pinHighlight.style, {
|
|
617
|
+
position: 'fixed',
|
|
618
|
+
border: '2px dashed ' + RED,
|
|
619
|
+
background: 'rgba(237,62,97,0.05)',
|
|
620
|
+
boxSizing: 'border-box',
|
|
621
|
+
pointerEvents: 'none',
|
|
622
|
+
zIndex: '2147483643',
|
|
623
|
+
});
|
|
624
|
+
document.body.appendChild(pinHighlight);
|
|
625
|
+
}
|
|
626
|
+
pinHighlight.style.left = r.left + 'px';
|
|
627
|
+
pinHighlight.style.top = r.top + 'px';
|
|
628
|
+
pinHighlight.style.width = r.width + 'px';
|
|
629
|
+
pinHighlight.style.height = r.height + 'px';
|
|
630
|
+
}
|
|
418
631
|
|
|
419
632
|
function clearPin() {
|
|
420
633
|
if (pinHighlight) { pinHighlight.remove(); pinHighlight = null; }
|
|
@@ -426,14 +639,51 @@ function getClientScript(options) {
|
|
|
426
639
|
measureOverlay.style.display = 'none';
|
|
427
640
|
}
|
|
428
641
|
|
|
429
|
-
function
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
642
|
+
function showMeasureTargetHL(el) {
|
|
643
|
+
clearMeasureTargetHL();
|
|
644
|
+
var r = el.getBoundingClientRect();
|
|
645
|
+
measureHL = document.createElement('div');
|
|
646
|
+
Object.assign(measureHL.style, {
|
|
647
|
+
position: 'fixed',
|
|
648
|
+
left: r.left + 'px',
|
|
649
|
+
top: r.top + 'px',
|
|
650
|
+
width: r.width + 'px',
|
|
651
|
+
height: r.height + 'px',
|
|
652
|
+
background: 'rgba(173,36,211,0.08)',
|
|
653
|
+
border: '1.5px solid ' + PURPLE,
|
|
654
|
+
boxSizing: 'border-box',
|
|
435
655
|
pointerEvents: 'none',
|
|
656
|
+
zIndex: '2147483643',
|
|
436
657
|
});
|
|
658
|
+
document.body.appendChild(measureHL);
|
|
659
|
+
}
|
|
660
|
+
|
|
661
|
+
function clearMeasureTargetHL() {
|
|
662
|
+
if (measureHL) { measureHL.remove(); measureHL = null; }
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
// \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
|
|
666
|
+
function cap(x, y, mainHoriz) {
|
|
667
|
+
var c = document.createElement('div');
|
|
668
|
+
Object.assign(c.style, { position: 'absolute', background: RED, pointerEvents: 'none' });
|
|
669
|
+
if (mainHoriz) {
|
|
670
|
+
c.style.left = (x - 0.5) + 'px';
|
|
671
|
+
c.style.top = (y - 4) + 'px';
|
|
672
|
+
c.style.width = '1px';
|
|
673
|
+
c.style.height = '8px';
|
|
674
|
+
} else {
|
|
675
|
+
c.style.left = (x - 4) + 'px';
|
|
676
|
+
c.style.top = (y - 0.5) + 'px';
|
|
677
|
+
c.style.width = '8px';
|
|
678
|
+
c.style.height = '1px';
|
|
679
|
+
}
|
|
680
|
+
measureOverlay.appendChild(c);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
function drawLine(x1, y1, x2, y2, label) {
|
|
684
|
+
var isHoriz = Math.abs(y2 - y1) < 1;
|
|
685
|
+
var line = document.createElement('div');
|
|
686
|
+
Object.assign(line.style, { position: 'absolute', background: RED, pointerEvents: 'none' });
|
|
437
687
|
if (isHoriz) {
|
|
438
688
|
Object.assign(line.style, {
|
|
439
689
|
left: Math.min(x1, x2) + 'px', top: (y1 - 0.5) + 'px',
|
|
@@ -446,156 +696,149 @@ function getClientScript(options) {
|
|
|
446
696
|
});
|
|
447
697
|
}
|
|
448
698
|
measureOverlay.appendChild(line);
|
|
699
|
+
cap(x1, y1, isHoriz);
|
|
700
|
+
cap(x2, y2, isHoriz);
|
|
449
701
|
|
|
450
702
|
if (label && label !== '0px') {
|
|
451
|
-
|
|
703
|
+
var lbl = document.createElement('div');
|
|
452
704
|
lbl.textContent = label;
|
|
453
705
|
Object.assign(lbl.style, {
|
|
454
706
|
position: 'absolute',
|
|
455
|
-
background:
|
|
707
|
+
background: RED,
|
|
456
708
|
color: '#fff',
|
|
457
709
|
fontSize: '10px',
|
|
458
|
-
fontFamily:
|
|
710
|
+
fontFamily: MONO,
|
|
459
711
|
padding: '1px 4px',
|
|
460
712
|
borderRadius: '3px',
|
|
461
713
|
pointerEvents: 'none',
|
|
462
714
|
whiteSpace: 'nowrap',
|
|
463
715
|
});
|
|
464
716
|
if (isHoriz) {
|
|
465
|
-
|
|
466
|
-
lbl.style.left =
|
|
467
|
-
lbl.style.top = (y1 -
|
|
717
|
+
var cxl = (Math.min(x1, x2) + Math.max(x1, x2)) / 2;
|
|
718
|
+
lbl.style.left = cxl + 'px';
|
|
719
|
+
lbl.style.top = (y1 - 16) + 'px';
|
|
468
720
|
lbl.style.transform = 'translateX(-50%)';
|
|
469
721
|
} else {
|
|
470
|
-
|
|
722
|
+
var cyl = (Math.min(y1, y2) + Math.max(y1, y2)) / 2;
|
|
471
723
|
lbl.style.left = (x1 + 6) + 'px';
|
|
472
|
-
lbl.style.top =
|
|
724
|
+
lbl.style.top = cyl + 'px';
|
|
473
725
|
lbl.style.transform = 'translateY(-50%)';
|
|
474
726
|
}
|
|
475
727
|
measureOverlay.appendChild(lbl);
|
|
476
728
|
}
|
|
477
729
|
}
|
|
478
730
|
|
|
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
|
-
}
|
|
731
|
+
// \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
|
|
732
|
+
function edgeToAncestor(targetEl, tr, dir) {
|
|
733
|
+
var cur = targetEl.parentElement;
|
|
734
|
+
var ctx = 'parent';
|
|
735
|
+
for (var i = 0; i < 4 && cur && cur !== document.body; i++) {
|
|
736
|
+
var pr = cur.getBoundingClientRect();
|
|
737
|
+
var gap;
|
|
738
|
+
if (dir === 'top') gap = tr.top - pr.top;
|
|
739
|
+
else if (dir === 'bottom') gap = pr.bottom - tr.bottom;
|
|
740
|
+
else if (dir === 'left') gap = tr.left - pr.left;
|
|
741
|
+
else gap = pr.right - tr.right;
|
|
742
|
+
if (gap > 0.5) {
|
|
743
|
+
var ed;
|
|
744
|
+
if (dir === 'top') ed = pr.top;
|
|
745
|
+
else if (dir === 'bottom') ed = pr.bottom;
|
|
746
|
+
else if (dir === 'left') ed = pr.left;
|
|
747
|
+
else ed = pr.right;
|
|
748
|
+
return { gap: gap, ctx: ctx, edge: ed };
|
|
527
749
|
}
|
|
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
750
|
cur = cur.parentElement;
|
|
751
|
+
ctx = 'container';
|
|
551
752
|
}
|
|
753
|
+
return null;
|
|
754
|
+
}
|
|
755
|
+
|
|
756
|
+
function computeNeighbors(targetEl, tr) {
|
|
757
|
+
var out = { top: null, bottom: null, left: null, right: null };
|
|
758
|
+
var parent = targetEl.parentElement;
|
|
759
|
+
if (parent) {
|
|
760
|
+
var sibs = Array.prototype.slice.call(parent.children).filter(function (c) { return c !== targetEl && c.offsetParent !== null; });
|
|
761
|
+
sibs.forEach(function (sib) {
|
|
762
|
+
var sr = sib.getBoundingClientRect();
|
|
763
|
+
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 }; }
|
|
764
|
+
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 }; }
|
|
765
|
+
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 }; }
|
|
766
|
+
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 }; }
|
|
767
|
+
});
|
|
768
|
+
}
|
|
769
|
+
['top', 'bottom', 'left', 'right'].forEach(function (k) { if (!out[k]) out[k] = edgeToAncestor(targetEl, tr, k); });
|
|
770
|
+
return out;
|
|
771
|
+
}
|
|
552
772
|
|
|
773
|
+
function measureToNeighbor(targetEl) {
|
|
774
|
+
clearMeasureOverlay();
|
|
775
|
+
var tr = targetEl.getBoundingClientRect();
|
|
776
|
+
var dirs = computeNeighbors(targetEl, tr);
|
|
777
|
+
var cx = (tr.left + tr.right) / 2;
|
|
778
|
+
var cy = (tr.top + tr.bottom) / 2;
|
|
779
|
+
if (dirs.top) drawLine(cx, dirs.top.edge, cx, tr.top, Math.round(dirs.top.gap) + 'px');
|
|
780
|
+
if (dirs.bottom) drawLine(cx, tr.bottom, cx, dirs.bottom.edge, Math.round(dirs.bottom.gap) + 'px');
|
|
781
|
+
if (dirs.left) drawLine(dirs.left.edge, cy, tr.left, cy, Math.round(dirs.left.gap) + 'px');
|
|
782
|
+
if (dirs.right) drawLine(tr.right, cy, dirs.right.edge, cy, Math.round(dirs.right.gap) + 'px');
|
|
553
783
|
measureOverlay.style.display = 'block';
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
784
|
+
var tag = targetEl.tagName.toLowerCase();
|
|
785
|
+
var lines = ['\u2B21 Measure (M pin \xB7 Cmd+C copy)', '<' + tag + '> ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height)];
|
|
786
|
+
['top', 'right', 'bottom', 'left'].forEach(function (k) {
|
|
787
|
+
if (dirs[k]) lines.push(k + ': ' + Math.round(dirs[k].gap) + 'px (to ' + dirs[k].ctx + ')');
|
|
788
|
+
});
|
|
789
|
+
return lines.join('\\n');
|
|
790
|
+
}
|
|
791
|
+
|
|
792
|
+
function buildNeighborCopyText(el) {
|
|
793
|
+
var tr = el.getBoundingClientRect();
|
|
794
|
+
var dirs = computeNeighbors(el, tr);
|
|
795
|
+
var lines = ['[Specter Measure]', '<' + el.tagName.toLowerCase() + '> ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height), 'selector: ' + getSelector(el)];
|
|
796
|
+
['top', 'right', 'bottom', 'left'].forEach(function (k) {
|
|
797
|
+
if (dirs[k]) lines.push(k + ': ' + Math.round(dirs[k].gap) + 'px (to ' + dirs[k].ctx + ')');
|
|
798
|
+
});
|
|
799
|
+
return lines.join('\\n');
|
|
557
800
|
}
|
|
558
801
|
|
|
559
802
|
function measureBetween(fromEl, toEl) {
|
|
560
803
|
clearMeasureOverlay();
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
804
|
+
var fr = fromEl.getBoundingClientRect();
|
|
805
|
+
var tr = toEl.getBoundingClientRect();
|
|
806
|
+
var fTag = fromEl.tagName.toLowerCase(), tTag = toEl.tagName.toLowerCase();
|
|
807
|
+
var fComp = getComponentName(fromEl), tComp = getComponentName(toEl);
|
|
565
808
|
|
|
566
|
-
|
|
567
|
-
lines.push(
|
|
568
|
-
lines.push(
|
|
809
|
+
var lines = ['\u2B21 Measure (Cmd+C copy)'];
|
|
810
|
+
lines.push('From: <' + fTag + '>' + (fComp ? ' ' + fComp : '') + ' ' + Math.round(fr.width) + '\xD7' + Math.round(fr.height));
|
|
811
|
+
lines.push('To: <' + tTag + '>' + (tComp ? ' ' + tComp : '') + ' ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height));
|
|
569
812
|
|
|
570
|
-
|
|
571
|
-
|
|
813
|
+
var fromContainsTo = fr.left <= tr.left && fr.top <= tr.top && fr.right >= tr.right && fr.bottom >= tr.bottom;
|
|
814
|
+
var toContainsFrom = tr.left <= fr.left && tr.top <= fr.top && tr.right >= fr.right && tr.bottom >= fr.bottom;
|
|
572
815
|
|
|
573
816
|
if (fromContainsTo || toContainsFrom) {
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
817
|
+
var outer = fromContainsTo ? fr : tr;
|
|
818
|
+
var inner = fromContainsTo ? tr : fr;
|
|
819
|
+
var cx = (inner.left + inner.right) / 2, cy = (inner.top + inner.bottom) / 2;
|
|
820
|
+
var iT = inner.top - outer.top, iB = outer.bottom - inner.bottom;
|
|
821
|
+
var iL = inner.left - outer.left, iR = outer.right - inner.right;
|
|
579
822
|
if (iT > 0) drawLine(cx, outer.top, cx, inner.top, Math.round(iT) + 'px');
|
|
580
823
|
if (iB > 0) drawLine(cx, inner.bottom, cx, outer.bottom, Math.round(iB) + 'px');
|
|
581
824
|
if (iL > 0) drawLine(outer.left, cy, inner.left, cy, Math.round(iL) + 'px');
|
|
582
825
|
if (iR > 0) drawLine(inner.right, cy, outer.right, cy, Math.round(iR) + 'px');
|
|
583
|
-
lines.push(
|
|
826
|
+
lines.push('inset: ' + Math.round(iT) + 'px ' + Math.round(iR) + 'px ' + Math.round(iB) + 'px ' + Math.round(iL) + 'px');
|
|
584
827
|
} else {
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
828
|
+
var vGap = fr.bottom <= tr.top ? tr.top - fr.bottom : tr.bottom <= fr.top ? fr.top - tr.bottom : 0;
|
|
829
|
+
var hGap = fr.right <= tr.left ? tr.left - fr.right : tr.right <= fr.left ? fr.left - tr.right : 0;
|
|
830
|
+
var cx2 = (fr.left + fr.right) / 2, cy2 = (fr.top + fr.bottom) / 2;
|
|
588
831
|
if (vGap > 0) {
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
drawLine(
|
|
592
|
-
lines.push(
|
|
832
|
+
var y1 = fr.bottom <= tr.top ? fr.bottom : tr.bottom;
|
|
833
|
+
var y2 = fr.bottom <= tr.top ? tr.top : fr.top;
|
|
834
|
+
drawLine(cx2, y1, cx2, y2, Math.round(vGap) + 'px');
|
|
835
|
+
lines.push('vertical gap: ' + Math.round(vGap) + 'px');
|
|
593
836
|
}
|
|
594
837
|
if (hGap > 0) {
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
drawLine(x1,
|
|
598
|
-
lines.push(
|
|
838
|
+
var x1 = fr.right <= tr.left ? fr.right : tr.right;
|
|
839
|
+
var x2 = fr.right <= tr.left ? tr.left : fr.left;
|
|
840
|
+
drawLine(x1, cy2, x2, cy2, Math.round(hGap) + 'px');
|
|
841
|
+
lines.push('horizontal gap: ' + Math.round(hGap) + 'px');
|
|
599
842
|
}
|
|
600
843
|
if (vGap === 0 && hGap === 0) lines.push('Elements overlap');
|
|
601
844
|
}
|
|
@@ -605,28 +848,26 @@ function getClientScript(options) {
|
|
|
605
848
|
}
|
|
606
849
|
|
|
607
850
|
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)}\`);
|
|
851
|
+
var fr = fromEl.getBoundingClientRect(), tr = toEl.getBoundingClientRect();
|
|
852
|
+
var fromContainsTo = fr.left <= tr.left && fr.top <= tr.top && fr.right >= tr.right && fr.bottom >= tr.bottom;
|
|
853
|
+
var toContainsFrom = tr.left <= fr.left && tr.top <= fr.top && tr.right >= fr.right && tr.bottom >= fr.bottom;
|
|
854
|
+
var fTag = fromEl.tagName.toLowerCase(), tTag = toEl.tagName.toLowerCase();
|
|
855
|
+
var fComp = getComponentName(fromEl), tComp = getComponentName(toEl);
|
|
856
|
+
|
|
857
|
+
var lines = ['[Specter Measure]'];
|
|
858
|
+
lines.push('From: <' + fTag + '>' + (fComp ? ' ' + fComp : '') + ' ' + Math.round(fr.width) + '\xD7' + Math.round(fr.height));
|
|
859
|
+
lines.push(' selector: ' + getSelector(fromEl));
|
|
860
|
+
lines.push('To: <' + tTag + '>' + (tComp ? ' ' + tComp : '') + ' ' + Math.round(tr.width) + '\xD7' + Math.round(tr.height));
|
|
861
|
+
lines.push(' selector: ' + getSelector(toEl));
|
|
621
862
|
|
|
622
863
|
if (fromContainsTo || toContainsFrom) {
|
|
623
|
-
|
|
624
|
-
lines.push(
|
|
864
|
+
var outer = fromContainsTo ? fr : tr, inner = fromContainsTo ? tr : fr;
|
|
865
|
+
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
866
|
} else {
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
if (vGap > 0) lines.push(
|
|
629
|
-
if (hGap > 0) lines.push(
|
|
867
|
+
var vGap = fr.bottom <= tr.top ? tr.top - fr.bottom : tr.bottom <= fr.top ? fr.top - tr.bottom : 0;
|
|
868
|
+
var hGap = fr.right <= tr.left ? tr.left - fr.right : tr.right <= fr.left ? fr.left - tr.right : 0;
|
|
869
|
+
if (vGap > 0) lines.push('vertical gap: ' + Math.round(vGap) + 'px');
|
|
870
|
+
if (hGap > 0) lines.push('horizontal gap: ' + Math.round(hGap) + 'px');
|
|
630
871
|
if (vGap === 0 && hGap === 0) lines.push('Elements overlap');
|
|
631
872
|
}
|
|
632
873
|
return lines.join('\\n');
|
|
@@ -634,51 +875,55 @@ function getClientScript(options) {
|
|
|
634
875
|
|
|
635
876
|
// \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
877
|
function matchesActivate(e) {
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
878
|
+
var parts = ACTIVATE.toLowerCase().split('+');
|
|
879
|
+
var needCtrl = parts.indexOf('ctrl') >= 0;
|
|
880
|
+
var needAlt = parts.indexOf('alt') >= 0;
|
|
881
|
+
var needShift = parts.indexOf('shift') >= 0;
|
|
882
|
+
var needMeta = parts.indexOf('meta') >= 0 || parts.indexOf('cmd') >= 0;
|
|
883
|
+
var key = parts.filter(function (p) { return ['ctrl', 'alt', 'shift', 'meta', 'cmd'].indexOf(p) < 0; })[0];
|
|
643
884
|
if (needCtrl !== e.ctrlKey) return false;
|
|
644
885
|
if (needAlt !== e.altKey) return false;
|
|
645
886
|
if (needShift !== e.shiftKey) return false;
|
|
646
887
|
if (needMeta !== e.metaKey) return false;
|
|
647
888
|
if (!key) return false;
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
return eKey === key || eCode ===
|
|
889
|
+
var eKey = e.key.toLowerCase();
|
|
890
|
+
var eCode = e.code.toLowerCase();
|
|
891
|
+
return eKey === key || eCode === 'key' + key || (key === 'period' && (eKey === '.' || eCode === 'period'));
|
|
651
892
|
}
|
|
652
893
|
|
|
653
894
|
// \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
895
|
function onMouseMove(e) {
|
|
655
896
|
if (!fiActive) return;
|
|
656
|
-
|
|
657
|
-
|
|
897
|
+
lastMouse.x = e.clientX; lastMouse.y = e.clientY;
|
|
898
|
+
var target = e.target;
|
|
899
|
+
if (!target || target === pillWrap || pillWrap.contains(target)) return;
|
|
658
900
|
if (target === tooltip || tooltip.contains(target)) return;
|
|
659
901
|
|
|
660
902
|
lastHovered = target;
|
|
661
903
|
|
|
662
904
|
if (measureMode) {
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
tooltip.
|
|
905
|
+
clearHoverOutline();
|
|
906
|
+
showMeasureTargetHL(target);
|
|
907
|
+
var text = (pinEl && pinEl !== target) ? measureBetween(pinEl, target) : measureToNeighbor(target);
|
|
908
|
+
tooltip.innerHTML = linesToHTML(text);
|
|
667
909
|
positionTooltip(e.clientX, e.clientY);
|
|
910
|
+
if (pinEl) updatePinHL();
|
|
668
911
|
} else {
|
|
669
912
|
clearMeasureOverlay();
|
|
670
|
-
|
|
913
|
+
clearMeasureTargetHL();
|
|
914
|
+
setHoverOutline(target);
|
|
915
|
+
tooltip.innerHTML = buildHumanDisplay(buildInfo(target));
|
|
671
916
|
positionTooltip(e.clientX, e.clientY);
|
|
672
917
|
}
|
|
673
918
|
}
|
|
674
919
|
|
|
675
920
|
// \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
921
|
function isInputFocused() {
|
|
677
|
-
|
|
922
|
+
var el = document.activeElement;
|
|
678
923
|
return !!el && (el.tagName === 'INPUT' || el.tagName === 'TEXTAREA' || el.isContentEditable);
|
|
679
924
|
}
|
|
680
925
|
|
|
681
|
-
document.addEventListener('keydown', (e)
|
|
926
|
+
document.addEventListener('keydown', function (e) {
|
|
682
927
|
if (matchesActivate(e)) {
|
|
683
928
|
e.preventDefault();
|
|
684
929
|
if (fiActive) deactivate(); else activate();
|
|
@@ -687,51 +932,48 @@ function getClientScript(options) {
|
|
|
687
932
|
|
|
688
933
|
if (!fiActive) return;
|
|
689
934
|
|
|
690
|
-
// Option tap (keydown only \u2014 actual toggle fires on keyup)
|
|
691
935
|
if (e.key === 'Alt' && !e.ctrlKey && !e.metaKey && !e.shiftKey) {
|
|
692
936
|
optionHeld = true;
|
|
693
937
|
return;
|
|
694
938
|
}
|
|
695
939
|
|
|
696
|
-
// Cmd+C \u2014 copy
|
|
697
940
|
if ((e.metaKey || e.ctrlKey) && e.key === 'c' && !e.shiftKey && !e.altKey) {
|
|
698
941
|
e.preventDefault();
|
|
699
|
-
|
|
942
|
+
var text;
|
|
700
943
|
if (selectedEls.length > 0) {
|
|
701
944
|
text = buildMultiSelectCopyText();
|
|
702
945
|
} else if (measureMode && pinEl && lastHovered && lastHovered !== pinEl) {
|
|
703
946
|
text = buildMeasureCopyText(pinEl, lastHovered);
|
|
947
|
+
} else if (measureMode && lastHovered) {
|
|
948
|
+
text = buildNeighborCopyText(lastHovered);
|
|
704
949
|
} else if (lastHovered) {
|
|
705
|
-
text =
|
|
950
|
+
text = buildLLMClipboard(buildInfo(lastHovered));
|
|
706
951
|
}
|
|
707
|
-
if (text) navigator.clipboard.writeText(text).catch(()
|
|
952
|
+
if (text) navigator.clipboard.writeText(text).then(showCopied).catch(function () {});
|
|
708
953
|
return;
|
|
709
954
|
}
|
|
710
955
|
|
|
711
956
|
if (isInputFocused()) return;
|
|
712
957
|
|
|
713
|
-
// P \u2014 pick/unpick element (multi-select), Properties mode only
|
|
714
958
|
if (e.key === 'p' || e.key === 'P') {
|
|
715
959
|
if (measureMode || !lastHovered) return;
|
|
960
|
+
clearHoverOutline();
|
|
716
961
|
toggleSelection(lastHovered);
|
|
717
962
|
return;
|
|
718
963
|
}
|
|
719
964
|
|
|
720
|
-
// M \u2014 pin element for measure-from, Measure mode only
|
|
721
965
|
if (e.key === 'm' || e.key === 'M') {
|
|
722
966
|
if (!measureMode || !lastHovered) return;
|
|
723
967
|
if (pinEl) {
|
|
724
968
|
clearPin();
|
|
725
969
|
collapsePill();
|
|
726
970
|
} else {
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
expandPill('Pinned \xB7 hover another element \xB7 Cmd+C \u2192 copy \xB7 Esc \u2192 clear');
|
|
971
|
+
setPin(lastHovered);
|
|
972
|
+
expandPill('Pinned \xB7 hover another element \xB7 Cmd+C copy \xB7 Esc clear');
|
|
730
973
|
}
|
|
731
974
|
return;
|
|
732
975
|
}
|
|
733
976
|
|
|
734
|
-
// Escape cascade: pin \u2192 selection \u2192 exit
|
|
735
977
|
if (e.key === 'Escape') {
|
|
736
978
|
if (pinEl) {
|
|
737
979
|
clearPin();
|
|
@@ -748,12 +990,14 @@ function getClientScript(options) {
|
|
|
748
990
|
}
|
|
749
991
|
}, true);
|
|
750
992
|
|
|
751
|
-
document.addEventListener('keyup', (e)
|
|
993
|
+
document.addEventListener('keyup', function (e) {
|
|
752
994
|
if (!fiActive) return;
|
|
753
995
|
if (e.key === 'Alt' && optionHeld) {
|
|
754
996
|
optionHeld = false;
|
|
755
997
|
measureMode = !measureMode;
|
|
756
998
|
clearMeasureOverlay();
|
|
999
|
+
clearHoverOutline();
|
|
1000
|
+
clearMeasureTargetHL();
|
|
757
1001
|
if (!measureMode) clearPin();
|
|
758
1002
|
hideTooltip();
|
|
759
1003
|
flashMode();
|