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