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