@inspecto-dev/core 0.2.0-alpha.1 → 0.2.0-alpha.3
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/chunk-C4KO2HLL.js +25 -0
- package/dist/chunk-C4KO2HLL.js.map +1 -0
- package/dist/component-4WPU23TV.js +1097 -0
- package/dist/component-4WPU23TV.js.map +1 -0
- package/dist/index.cjs +486 -574
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +19 -1261
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,1268 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
var menuItemClass = "inspecto-menu-item";
|
|
5
|
-
var loadingSpinnerClass = "inspecto-spinner";
|
|
6
|
-
var errorMsgClass = "inspecto-error";
|
|
7
|
-
var badgeClass = "inspecto-badge";
|
|
8
|
-
var menuInputClass = "inspecto-menu-input";
|
|
9
|
-
var menuInputWrapperClass = "inspecto-menu-input-wrapper";
|
|
10
|
-
var menuInputIconClass = "inspecto-menu-input-icon";
|
|
11
|
-
var tooltipClass = "inspecto-tooltip";
|
|
12
|
-
var tooltipTopClass = "inspecto-tooltip-top";
|
|
13
|
-
var tooltipBottomClass = "inspecto-tooltip-bottom";
|
|
14
|
-
var tagClass = "inspecto-tag";
|
|
15
|
-
var idClass = "inspecto-id";
|
|
16
|
-
var classClass = "inspecto-class";
|
|
17
|
-
var dimClass = "inspecto-dim";
|
|
18
|
-
var separatorClass = "inspecto-separator";
|
|
19
|
-
var sourceClass = "inspecto-source";
|
|
20
|
-
var shortcutIconClass = "ai-shortcut-icon";
|
|
21
|
-
var darkVars = `
|
|
22
|
-
--inspecto-menu-bg: #252526;
|
|
23
|
-
--inspecto-menu-border: #454545;
|
|
24
|
-
--inspecto-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.5);
|
|
25
|
-
--inspecto-text: #cccccc;
|
|
26
|
-
--inspecto-text-muted: #858585;
|
|
27
|
-
--inspecto-hover-bg: #04395e;
|
|
28
|
-
--inspecto-hover-text: #ffffff;
|
|
29
|
-
--inspecto-hover-icon: #ffffff;
|
|
30
|
-
--inspecto-input-bg: #3c3c3c;
|
|
31
|
-
--inspecto-input-border: #007acc;
|
|
32
|
-
--inspecto-shortcut-text: #858585;
|
|
33
|
-
--inspecto-badge-bg: rgba(30, 30, 30, 0.7);
|
|
34
|
-
--inspecto-badge-text: #e5e5e5;
|
|
35
|
-
--inspecto-badge-active-bg: #007acc;
|
|
36
|
-
--inspecto-badge-active-text: #ffffff;
|
|
37
|
-
--inspecto-badge-border: 1px solid rgba(255, 255, 255, 0.1);
|
|
38
|
-
|
|
39
|
-
--inspecto-tooltip-bg: #222222;
|
|
40
|
-
--inspecto-tooltip-text: #cccccc;
|
|
41
|
-
--inspecto-tooltip-border: #444;
|
|
42
|
-
--inspecto-tooltip-shadow: 0 2px 10px rgba(0,0,0,0.5);
|
|
43
|
-
--inspecto-tag-color: #d16969;
|
|
44
|
-
--inspecto-id-color: #d16969;
|
|
45
|
-
--inspecto-class-color: #9cdcfe;
|
|
46
|
-
--inspecto-dim-color: #858585;
|
|
47
|
-
--inspecto-error-color: #ef4444;
|
|
48
|
-
`;
|
|
49
|
-
var inspectorStyles = `
|
|
50
|
-
:host {
|
|
51
|
-
/* Light theme (default) */
|
|
52
|
-
--inspecto-menu-bg: #ffffff;
|
|
53
|
-
--inspecto-menu-border: #d4d4d4;
|
|
54
|
-
--inspecto-menu-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
|
|
55
|
-
--inspecto-text: #333333;
|
|
56
|
-
--inspecto-text-muted: #6b7280;
|
|
57
|
-
--inspecto-hover-bg: #0060c0;
|
|
58
|
-
--inspecto-hover-text: #ffffff;
|
|
59
|
-
--inspecto-hover-icon: #ffffff;
|
|
60
|
-
--inspecto-input-bg: #ffffff;
|
|
61
|
-
--inspecto-input-border: #007acc;
|
|
62
|
-
--inspecto-shortcut-text: #9ca3af;
|
|
63
|
-
--inspecto-badge-bg: rgba(30, 30, 30, 0.7);
|
|
64
|
-
--inspecto-badge-text: #e5e5e5;
|
|
65
|
-
--inspecto-badge-active-bg: #007acc;
|
|
66
|
-
--inspecto-badge-active-text: #ffffff;
|
|
67
|
-
--inspecto-badge-border: 1px solid rgba(255, 255, 255, 0.1);
|
|
68
|
-
|
|
69
|
-
/* Chrome DevTools like colors */
|
|
70
|
-
--inspecto-overlay-border: #4285f4; /* Google Blue */
|
|
71
|
-
--inspecto-overlay-bg: rgba(66, 133, 244, 0.2);
|
|
72
|
-
--inspecto-tooltip-bg: #ffffff;
|
|
73
|
-
--inspecto-tooltip-text: #333333;
|
|
74
|
-
--inspecto-tooltip-border: #ccc;
|
|
75
|
-
--inspecto-tooltip-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
76
|
-
|
|
77
|
-
--inspecto-tag-color: #8b008b; /* Dark magenta */
|
|
78
|
-
--inspecto-id-color: #8b008b;
|
|
79
|
-
--inspecto-class-color: #00008b; /* Dark blue */
|
|
80
|
-
--inspecto-dim-color: #555555;
|
|
81
|
-
--inspecto-error-color: #ef4444;
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
:host([data-theme="dark"]) {
|
|
85
|
-
${darkVars}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
@media (prefers-color-scheme: dark) {
|
|
89
|
-
:host(:not([data-theme="light"])) {
|
|
90
|
-
${darkVars}
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
.${overlayClass} {
|
|
95
|
-
position: fixed;
|
|
96
|
-
pointer-events: none;
|
|
97
|
-
z-index: 2147483646;
|
|
98
|
-
border: 1px dashed var(--inspecto-overlay-border);
|
|
99
|
-
background: var(--inspecto-overlay-bg);
|
|
100
|
-
box-sizing: border-box;
|
|
101
|
-
transition: all 0.05s linear;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
.${tooltipClass} {
|
|
105
|
-
position: fixed;
|
|
106
|
-
pointer-events: none;
|
|
107
|
-
z-index: 2147483647;
|
|
108
|
-
background: var(--inspecto-tooltip-bg);
|
|
109
|
-
color: var(--inspecto-tooltip-text);
|
|
110
|
-
border: 1px solid var(--inspecto-tooltip-border);
|
|
111
|
-
border-radius: 4px;
|
|
112
|
-
box-shadow: var(--inspecto-tooltip-shadow);
|
|
113
|
-
padding: 6px 10px;
|
|
114
|
-
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
|
115
|
-
font-size: 12px;
|
|
116
|
-
line-height: 1.4;
|
|
117
|
-
transition: all 0.05s linear;
|
|
118
|
-
display: flex;
|
|
119
|
-
flex-direction: column;
|
|
120
|
-
gap: 4px;
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
/* Create the small pointer arrow like Chrome DevTools */
|
|
124
|
-
.${tooltipClass}::after {
|
|
125
|
-
content: '';
|
|
126
|
-
position: absolute;
|
|
127
|
-
width: 0;
|
|
128
|
-
height: 0;
|
|
129
|
-
border-style: solid;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
.${tooltipTopClass}::after {
|
|
133
|
-
bottom: -6px;
|
|
134
|
-
left: var(--inspecto-arrow-left, 10px);
|
|
135
|
-
border-width: 6px 6px 0 6px;
|
|
136
|
-
border-color: var(--inspecto-tooltip-bg) transparent transparent transparent;
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
.${tooltipBottomClass}::after {
|
|
140
|
-
top: -6px;
|
|
141
|
-
left: var(--inspecto-arrow-left, 10px);
|
|
142
|
-
border-width: 0 6px 6px 6px;
|
|
143
|
-
border-color: transparent transparent var(--inspecto-tooltip-bg) transparent;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
/* Outline for the arrow to match border */
|
|
147
|
-
.${tooltipClass}::before {
|
|
148
|
-
content: '';
|
|
149
|
-
position: absolute;
|
|
150
|
-
width: 0;
|
|
151
|
-
height: 0;
|
|
152
|
-
border-style: solid;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
.${tooltipTopClass}::before {
|
|
156
|
-
bottom: -7px;
|
|
157
|
-
left: calc(var(--inspecto-arrow-left, 10px) - 1px);
|
|
158
|
-
border-width: 7px 7px 0 7px;
|
|
159
|
-
border-color: var(--inspecto-tooltip-border) transparent transparent transparent;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
.${tooltipBottomClass}::before {
|
|
163
|
-
top: -7px;
|
|
164
|
-
left: calc(var(--inspecto-arrow-left, 10px) - 1px);
|
|
165
|
-
border-width: 0 7px 7px 7px;
|
|
166
|
-
border-color: transparent transparent var(--inspecto-tooltip-border) transparent;
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
.${tagClass} {
|
|
170
|
-
color: var(--inspecto-tag-color);
|
|
171
|
-
font-weight: 600;
|
|
172
|
-
font-family: monospace;
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
.${idClass} {
|
|
176
|
-
color: var(--inspecto-id-color);
|
|
177
|
-
font-weight: 600;
|
|
178
|
-
font-family: monospace;
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
.${classClass} {
|
|
182
|
-
color: var(--inspecto-class-color);
|
|
183
|
-
font-family: monospace;
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
.${dimClass} {
|
|
187
|
-
color: var(--inspecto-dim-color);
|
|
188
|
-
margin-left: 4px;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
.${separatorClass} {
|
|
192
|
-
height: 1px;
|
|
193
|
-
background: var(--inspecto-tooltip-border);
|
|
194
|
-
margin: 2px -10px;
|
|
195
|
-
opacity: 0.5;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.${sourceClass} {
|
|
199
|
-
font-family: 'SF Mono', 'Fira Code', monospace;
|
|
200
|
-
font-size: 11px;
|
|
201
|
-
color: var(--inspecto-text-muted);
|
|
202
|
-
white-space: nowrap;
|
|
203
|
-
overflow: hidden;
|
|
204
|
-
text-overflow: ellipsis;
|
|
205
|
-
max-width: 300px;
|
|
206
|
-
}
|
|
207
|
-
|
|
208
|
-
.${menuClass} {
|
|
209
|
-
position: fixed;
|
|
210
|
-
z-index: 2147483647;
|
|
211
|
-
background: var(--inspecto-menu-bg);
|
|
212
|
-
border: 1px solid var(--inspecto-menu-border);
|
|
213
|
-
border-radius: 6px;
|
|
214
|
-
padding: 6px;
|
|
215
|
-
min-width: 300px;
|
|
216
|
-
box-shadow: var(--inspecto-menu-shadow);
|
|
217
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
218
|
-
color: var(--inspecto-text);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
.${menuInputWrapperClass} {
|
|
222
|
-
display: flex;
|
|
223
|
-
align-items: center;
|
|
224
|
-
border: 1px solid var(--inspecto-menu-border);
|
|
225
|
-
border-radius: 4px;
|
|
226
|
-
padding: 6px 8px;
|
|
227
|
-
margin: 4px;
|
|
228
|
-
margin-bottom: 8px;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
.${menuInputWrapperClass}:focus-within {
|
|
232
|
-
border-color: var(--inspecto-input-border);
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
.${menuInputClass} {
|
|
236
|
-
width: 100%;
|
|
237
|
-
border: none;
|
|
238
|
-
outline: none;
|
|
239
|
-
font-size: 13px;
|
|
240
|
-
color: var(--inspecto-text);
|
|
241
|
-
background: transparent;
|
|
242
|
-
}
|
|
243
|
-
|
|
244
|
-
.${menuInputClass}::placeholder {
|
|
245
|
-
color: var(--inspecto-text-muted);
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
.${menuInputIconClass} {
|
|
249
|
-
color: var(--inspecto-text-muted);
|
|
250
|
-
display: flex;
|
|
251
|
-
align-items: center;
|
|
252
|
-
justify-content: center;
|
|
253
|
-
margin-left: 4px;
|
|
254
|
-
}
|
|
255
|
-
|
|
256
|
-
.${menuInputIconClass}:hover {
|
|
257
|
-
color: var(--inspecto-text);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
.${menuItemClass} {
|
|
261
|
-
display: flex;
|
|
262
|
-
align-items: center;
|
|
263
|
-
gap: 8px;
|
|
264
|
-
width: 100%;
|
|
265
|
-
padding: 6px 10px;
|
|
266
|
-
margin: 2px 0;
|
|
267
|
-
border: none;
|
|
268
|
-
border-radius: 4px;
|
|
269
|
-
background: transparent;
|
|
270
|
-
color: var(--inspecto-text);
|
|
271
|
-
font-size: 13px;
|
|
272
|
-
cursor: pointer;
|
|
273
|
-
text-align: left;
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.${menuItemClass}:hover {
|
|
277
|
-
background: var(--inspecto-hover-bg);
|
|
278
|
-
color: var(--inspecto-hover-text);
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
.${menuItemClass} .${shortcutIconClass} {
|
|
282
|
-
margin-left: auto;
|
|
283
|
-
color: var(--inspecto-text-muted);
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
.${menuItemClass}:hover .${shortcutIconClass} {
|
|
287
|
-
color: var(--inspecto-hover-icon);
|
|
288
|
-
}
|
|
289
|
-
|
|
290
|
-
@keyframes spin {
|
|
291
|
-
to { transform: rotate(360deg); }
|
|
292
|
-
}
|
|
293
|
-
|
|
294
|
-
.${loadingSpinnerClass} {
|
|
295
|
-
width: 14px;
|
|
296
|
-
height: 14px;
|
|
297
|
-
border: 2px solid var(--inspecto-overlay-border);
|
|
298
|
-
border-top-color: transparent;
|
|
299
|
-
border-radius: 50%;
|
|
300
|
-
animation: spin 0.7s linear infinite;
|
|
301
|
-
margin: 4px auto;
|
|
302
|
-
display: block;
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
.${errorMsgClass} {
|
|
306
|
-
font-size: 11px;
|
|
307
|
-
color: var(--inspecto-error-color);
|
|
308
|
-
padding: 4px 8px;
|
|
309
|
-
text-align: center;
|
|
310
|
-
}
|
|
311
|
-
|
|
312
|
-
.${badgeClass} {
|
|
313
|
-
position: fixed;
|
|
314
|
-
bottom: 16px;
|
|
315
|
-
right: 16px;
|
|
316
|
-
z-index: 2147483645;
|
|
317
|
-
background: var(--inspecto-badge-bg);
|
|
318
|
-
color: var(--inspecto-badge-text);
|
|
319
|
-
border: var(--inspecto-badge-border);
|
|
320
|
-
border-radius: 20px;
|
|
321
|
-
padding: 6px 12px;
|
|
322
|
-
font-size: 12px;
|
|
323
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
|
|
324
|
-
cursor: grab;
|
|
325
|
-
opacity: 0.85;
|
|
326
|
-
transition: background 0.2s, color 0.2s, opacity 0.2s, box-shadow 0.2s;
|
|
327
|
-
pointer-events: all;
|
|
328
|
-
backdrop-filter: blur(4px);
|
|
329
|
-
-webkit-backdrop-filter: blur(4px);
|
|
330
|
-
display: flex;
|
|
331
|
-
align-items: center;
|
|
332
|
-
gap: 6px;
|
|
333
|
-
user-select: none;
|
|
334
|
-
-webkit-user-select: none;
|
|
335
|
-
touch-action: none;
|
|
336
|
-
}
|
|
337
|
-
|
|
338
|
-
.${badgeClass}:active {
|
|
339
|
-
cursor: grabbing;
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
.${badgeClass}-close {
|
|
343
|
-
display: inline-flex;
|
|
344
|
-
align-items: center;
|
|
345
|
-
justify-content: center;
|
|
346
|
-
width: 16px;
|
|
347
|
-
height: 16px;
|
|
348
|
-
border-radius: 50%;
|
|
349
|
-
background: transparent;
|
|
350
|
-
color: currentColor;
|
|
351
|
-
font-size: 14px;
|
|
352
|
-
line-height: 1;
|
|
353
|
-
opacity: 0.5;
|
|
354
|
-
transition: opacity 0.2s, background 0.2s;
|
|
355
|
-
margin-left: 2px;
|
|
356
|
-
cursor: pointer;
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
.${badgeClass}-close:hover {
|
|
360
|
-
opacity: 1;
|
|
361
|
-
background: rgba(255, 255, 255, 0.2);
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
.${badgeClass}.active {
|
|
365
|
-
background: var(--inspecto-badge-active-bg);
|
|
366
|
-
color: var(--inspecto-badge-active-text);
|
|
367
|
-
border: 1px solid transparent;
|
|
368
|
-
box-shadow: 0 0 10px rgba(0, 122, 204, 0.3);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
.${badgeClass}.disabled {
|
|
372
|
-
background: rgba(30, 30, 30, 0.4);
|
|
373
|
-
color: rgba(229, 229, 229, 0.5);
|
|
374
|
-
text-decoration: line-through;
|
|
375
|
-
border: 1px dashed rgba(255, 255, 255, 0.1);
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
.${badgeClass}.disabled .${badgeClass}-close {
|
|
379
|
-
opacity: 0.8;
|
|
380
|
-
text-decoration: none;
|
|
381
|
-
transform: rotate(45deg);
|
|
382
|
-
}
|
|
383
|
-
|
|
384
|
-
.${badgeClass}:hover {
|
|
385
|
-
opacity: 1;
|
|
386
|
-
}
|
|
387
|
-
`;
|
|
388
|
-
|
|
389
|
-
// src/overlay.ts
|
|
390
|
-
var GAP = 8;
|
|
391
|
-
var EDGE_MARGIN = 4;
|
|
392
|
-
function createOverlay(shadowRoot) {
|
|
393
|
-
const overlay = document.createElement("div");
|
|
394
|
-
overlay.className = overlayClass;
|
|
395
|
-
overlay.style.display = "none";
|
|
396
|
-
const tooltip = document.createElement("div");
|
|
397
|
-
tooltip.className = tooltipClass;
|
|
398
|
-
const tagSpan = document.createElement("span");
|
|
399
|
-
tagSpan.className = tagClass;
|
|
400
|
-
const idSpan = document.createElement("span");
|
|
401
|
-
idSpan.className = idClass;
|
|
402
|
-
const classSpan = document.createElement("span");
|
|
403
|
-
classSpan.className = classClass;
|
|
404
|
-
const dimSpan = document.createElement("span");
|
|
405
|
-
dimSpan.className = dimClass;
|
|
406
|
-
const separator = document.createElement("div");
|
|
407
|
-
separator.className = separatorClass;
|
|
408
|
-
const sourceSpan = document.createElement("div");
|
|
409
|
-
sourceSpan.className = sourceClass;
|
|
410
|
-
tooltip.appendChild(tagSpan);
|
|
411
|
-
tooltip.appendChild(idSpan);
|
|
412
|
-
tooltip.appendChild(classSpan);
|
|
413
|
-
tooltip.appendChild(document.createTextNode(" "));
|
|
414
|
-
tooltip.appendChild(dimSpan);
|
|
415
|
-
tooltip.appendChild(separator);
|
|
416
|
-
tooltip.appendChild(sourceSpan);
|
|
417
|
-
shadowRoot.appendChild(overlay);
|
|
418
|
-
shadowRoot.appendChild(tooltip);
|
|
419
|
-
function show(el, sourceLabel) {
|
|
420
|
-
const rect = el.getBoundingClientRect();
|
|
421
|
-
overlay.style.display = "block";
|
|
422
|
-
overlay.style.left = `${rect.left}px`;
|
|
423
|
-
overlay.style.top = `${rect.top}px`;
|
|
424
|
-
overlay.style.width = `${rect.width}px`;
|
|
425
|
-
overlay.style.height = `${rect.height}px`;
|
|
426
|
-
const tagName = el.tagName.toLowerCase();
|
|
427
|
-
tagSpan.textContent = tagName;
|
|
428
|
-
idSpan.textContent = el.id ? `#${el.id}` : "";
|
|
429
|
-
const classes = Array.from(el.classList).map((c) => `.${c}`).join("");
|
|
430
|
-
classSpan.textContent = classes;
|
|
431
|
-
dimSpan.textContent = `${Math.round(rect.width)} \xD7 ${Math.round(rect.height)}`;
|
|
432
|
-
sourceSpan.textContent = sourceLabel;
|
|
433
|
-
tooltip.style.visibility = "hidden";
|
|
434
|
-
tooltip.style.display = "block";
|
|
435
|
-
const tooltipRect = tooltip.getBoundingClientRect();
|
|
436
|
-
const viewportWidth = document.documentElement.clientWidth || window.innerWidth;
|
|
437
|
-
const viewportHeight = document.documentElement.clientHeight || window.innerHeight;
|
|
438
|
-
let tooltipTop = rect.top - tooltipRect.height - GAP;
|
|
439
|
-
let isBottom = false;
|
|
440
|
-
if (tooltipTop < EDGE_MARGIN) {
|
|
441
|
-
tooltipTop = rect.bottom + GAP;
|
|
442
|
-
if (tooltipTop + tooltipRect.height > viewportHeight - EDGE_MARGIN) {
|
|
443
|
-
tooltipTop = viewportHeight - tooltipRect.height - EDGE_MARGIN;
|
|
444
|
-
}
|
|
445
|
-
isBottom = true;
|
|
446
|
-
}
|
|
447
|
-
tooltip.classList.toggle(tooltipBottomClass, isBottom);
|
|
448
|
-
tooltip.classList.toggle(tooltipTopClass, !isBottom);
|
|
449
|
-
let tooltipLeft = rect.left;
|
|
450
|
-
if (tooltipLeft + tooltipRect.width > viewportWidth - EDGE_MARGIN) {
|
|
451
|
-
tooltipLeft = viewportWidth - tooltipRect.width - EDGE_MARGIN;
|
|
452
|
-
}
|
|
453
|
-
if (tooltipLeft < EDGE_MARGIN) {
|
|
454
|
-
tooltipLeft = EDGE_MARGIN;
|
|
455
|
-
}
|
|
456
|
-
const targetPointX = rect.left + Math.min(15, rect.width / 2);
|
|
457
|
-
let arrowLeft = targetPointX - tooltipLeft;
|
|
458
|
-
arrowLeft = Math.max(6, Math.min(arrowLeft, tooltipRect.width - 18));
|
|
459
|
-
tooltip.style.left = `${tooltipLeft}px`;
|
|
460
|
-
tooltip.style.top = `${tooltipTop}px`;
|
|
461
|
-
tooltip.style.setProperty("--inspecto-arrow-left", `${arrowLeft}px`);
|
|
462
|
-
tooltip.style.visibility = "visible";
|
|
463
|
-
}
|
|
464
|
-
function hide() {
|
|
465
|
-
overlay.style.display = "none";
|
|
466
|
-
tooltip.style.display = "none";
|
|
467
|
-
}
|
|
468
|
-
return { show, hide };
|
|
469
|
-
}
|
|
470
|
-
|
|
471
|
-
// src/intents.ts
|
|
472
|
-
function CUSTOM_PROMPT_TEMPLATE(userPrompt) {
|
|
473
|
-
return userPrompt;
|
|
474
|
-
}
|
|
475
|
-
var DEFAULT_INTENTS = [
|
|
476
|
-
// ── 1. Explain Component ─────────────────────────────────────────────────
|
|
477
|
-
// Frequency: ★★★★★ — Most common action when navigating unfamiliar codebases
|
|
478
|
-
{
|
|
479
|
-
id: "explain",
|
|
480
|
-
label: "Explain Component",
|
|
481
|
-
prompt: `The following is a {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
482
|
-
|
|
483
|
-
Please explain:
|
|
484
|
-
|
|
485
|
-
1. What this component does and its responsibility in the UI
|
|
486
|
-
2. Key props and their purpose
|
|
487
|
-
3. Important state or side effects (if applicable)
|
|
488
|
-
4. Any non-obvious logic or edge cases worth noting`
|
|
489
|
-
},
|
|
490
|
-
// ── 2. Fix Bug ───────────────────────────────────────────────────────────
|
|
491
|
-
// Frequency: ★★★★★ — Debugging is the highest time-cost activity in frontend dev
|
|
492
|
-
{
|
|
493
|
-
id: "fix-bug",
|
|
494
|
-
label: "Fix Bug",
|
|
495
|
-
prompt: `I found a bug in the following {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
496
|
-
|
|
497
|
-
Please:
|
|
498
|
-
|
|
499
|
-
1. Identify potential bugs or issues in this code
|
|
500
|
-
2. Explain the root cause of each issue
|
|
501
|
-
3. Provide a fixed version with minimal changes
|
|
502
|
-
|
|
503
|
-
If you need more context (e.g. parent component, API response shape),
|
|
504
|
-
please ask before suggesting a fix.`
|
|
505
|
-
},
|
|
506
|
-
// ── 3. Fix Styles ────────────────────────────────────────────────────────
|
|
507
|
-
// Frequency: ★★★★ — Styling issues are top-3 daily pain points for frontend devs
|
|
508
|
-
{
|
|
509
|
-
id: "fix-styles",
|
|
510
|
-
label: "Fix Styles",
|
|
511
|
-
prompt: `The following component from \`{{file}}\` (line {{line}}) has a styling issue.
|
|
512
|
-
|
|
513
|
-
Please:
|
|
514
|
-
|
|
515
|
-
1. Review the current styles (className / inline styles / CSS-in-JS / Style blocks)
|
|
516
|
-
2. Identify common issues: layout shifts, overflow, z-index conflicts,
|
|
517
|
-
responsive breakpoints, or visual inconsistencies
|
|
518
|
-
3. Suggest fixes using the same styling approach already in use
|
|
519
|
-
|
|
520
|
-
Note: Maintain the existing styling conventions (e.g. Tailwind, CSS Modules, scoped styles).`
|
|
521
|
-
},
|
|
522
|
-
// ── 4. Refactor Component ────────────────────────────────────────────────
|
|
523
|
-
// Frequency: ★★★★ — Sustained demand after features stabilize; large component splits
|
|
524
|
-
{
|
|
525
|
-
id: "refactor",
|
|
526
|
-
label: "Refactor Component",
|
|
527
|
-
prompt: `Please refactor the following {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
528
|
-
|
|
529
|
-
Refactoring goals (apply as relevant):
|
|
530
|
-
|
|
531
|
-
- Extract reusable sub-components or composables/hooks
|
|
532
|
-
- Improve readability and reduce complexity
|
|
533
|
-
- Remove redundant state or unnecessary re-renders
|
|
534
|
-
- Apply {{framework}} best practices
|
|
535
|
-
- Maintain existing behavior \u2014 no functional changes
|
|
536
|
-
|
|
537
|
-
Please show the refactored version with a brief explanation of each change.`
|
|
538
|
-
},
|
|
539
|
-
// ── 5. Code Review ───────────────────────────────────────────────────────
|
|
540
|
-
// Frequency: ★★★ — Concentrated at PR stage; slightly less frequent than daily fixes
|
|
541
|
-
{
|
|
542
|
-
id: "code-review",
|
|
543
|
-
label: "Code Review",
|
|
544
|
-
prompt: `Please do a code review for the following {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
545
|
-
|
|
546
|
-
Review dimensions:
|
|
547
|
-
|
|
548
|
-
- Correctness: logic errors, edge cases, race conditions
|
|
549
|
-
- {{framework}} best practices: lifecycle usage, key props, reactivity rules
|
|
550
|
-
- Performance: unnecessary renders, missing memoization
|
|
551
|
-
- Accessibility: ARIA attributes, keyboard navigation, semantic HTML
|
|
552
|
-
- Security: XSS risks, unsafe HTML injection, user input handling
|
|
553
|
-
- Maintainability: naming clarity, code duplication, complexity
|
|
554
|
-
|
|
555
|
-
Format your response as a prioritized list: \u{1F534} Critical / \u{1F7E1} Warning / \u{1F7E2} Suggestion.`
|
|
556
|
-
},
|
|
557
|
-
// ── 6. Generate Test ─────────────────────────────────────────────────────
|
|
558
|
-
// Frequency: ★★★ — Common but often deferred; great AI use case
|
|
559
|
-
{
|
|
560
|
-
id: "generate-test",
|
|
561
|
-
label: "Generate Test",
|
|
562
|
-
prompt: `Please generate unit tests for the following {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
563
|
-
|
|
564
|
-
Requirements:
|
|
565
|
-
|
|
566
|
-
- Use Vitest + Testing Library (or Jest if the codebase implies it)
|
|
567
|
-
- Cover: render correctness, user interactions, edge cases, error states
|
|
568
|
-
- Mock external dependencies (API calls, context/providers, router)
|
|
569
|
-
- Use accessible queries (getByRole, getByLabelText) over getByTestId
|
|
570
|
-
|
|
571
|
-
Generate a complete, runnable test file. Include all import statements.`
|
|
572
|
-
},
|
|
573
|
-
// ── 7. Performance Analysis ──────────────────────────────────────────────
|
|
574
|
-
// Frequency: ★★ — Targeted use during perf sprints; not a daily operation
|
|
575
|
-
{
|
|
576
|
-
id: "performance",
|
|
577
|
-
label: "Performance Analysis",
|
|
578
|
-
prompt: `Please analyze the performance of the following {{framework}} component from \`{{file}}\` (line {{line}}).
|
|
579
|
-
|
|
580
|
-
Focus on:
|
|
581
|
-
|
|
582
|
-
1. Unnecessary re-renders or reactive updates
|
|
583
|
-
2. Expensive computations that should be memoized/computed
|
|
584
|
-
3. Heavy operations in the render path
|
|
585
|
-
4. Dependency arrays or watchers \u2014 missing or over-specified
|
|
586
|
-
5. Large bundle impact (heavy imports that could be lazy-loaded)
|
|
587
|
-
|
|
588
|
-
For each issue found, provide: problem description \u2192 recommended fix \u2192 expected impact.`
|
|
589
|
-
},
|
|
590
|
-
// ── 8. Open in Editor ────────────────────────────────────────────────────
|
|
591
|
-
// Type: local action (no AI prompt) — jumps directly to source in IDE
|
|
592
|
-
{
|
|
593
|
-
id: "open-in-editor",
|
|
594
|
-
label: "Open in Editor",
|
|
595
|
-
prompt: "",
|
|
596
|
-
// unused — isAction handles this
|
|
597
|
-
isAction: true
|
|
598
|
-
}
|
|
599
|
-
];
|
|
600
|
-
function detectFramework(fileName) {
|
|
601
|
-
const ext = fileName.split(".").pop()?.toLowerCase() || "";
|
|
602
|
-
switch (ext) {
|
|
603
|
-
case "vue":
|
|
604
|
-
return "Vue";
|
|
605
|
-
case "svelte":
|
|
606
|
-
return "Svelte";
|
|
607
|
-
case "astro":
|
|
608
|
-
return "Astro";
|
|
609
|
-
case "jsx":
|
|
610
|
-
case "tsx":
|
|
611
|
-
return "React";
|
|
612
|
-
case "ts":
|
|
613
|
-
case "js":
|
|
614
|
-
return "JavaScript/TypeScript";
|
|
615
|
-
default:
|
|
616
|
-
return "UI";
|
|
617
|
-
}
|
|
618
|
-
}
|
|
619
|
-
function buildPrompt(template, location, snippetResult) {
|
|
620
|
-
const shortFile = location.file.split("/").pop() ?? location.file;
|
|
621
|
-
const ext = shortFile.split(".").pop()?.toLowerCase() || "tsx";
|
|
622
|
-
const framework = detectFramework(shortFile);
|
|
623
|
-
let finalPrompt = template.replace(/\{\{file\}\}/g, location.file).replace(/\{\{line\}\}/g, String(location.line)).replace(/\{\{column\}\}/g, String(location.column)).replace(/\{\{ext\}\}/g, ext).replace(/\{\{framework\}\}/g, framework).replace(/\{\{name\}\}/g, shortFile);
|
|
624
|
-
if (snippetResult && snippetResult.snippet) {
|
|
625
|
-
const name = snippetResult.name ?? shortFile;
|
|
626
|
-
finalPrompt = finalPrompt.replace(/\{\{name\}\}/g, name);
|
|
627
|
-
finalPrompt += `
|
|
628
|
-
|
|
629
|
-
Context from \`${location.file}\` (line ${location.line}):
|
|
630
|
-
\`\`\`${ext}
|
|
631
|
-
${snippetResult.snippet}
|
|
632
|
-
\`\`\``;
|
|
633
|
-
}
|
|
634
|
-
return finalPrompt;
|
|
635
|
-
}
|
|
636
|
-
|
|
637
|
-
// src/http.ts
|
|
638
|
-
var BASE_URL = globalThis.__AI_INSPECTOR_SERVER_URL__ || "http://127.0.0.1:5678";
|
|
639
|
-
function setBaseUrl(url) {
|
|
640
|
-
BASE_URL = url.replace(/\/$/, "");
|
|
641
|
-
}
|
|
642
|
-
var cachedConfig = null;
|
|
643
|
-
async function fetchIdeInfo(force = false) {
|
|
644
|
-
if (cachedConfig && !force) return cachedConfig;
|
|
645
|
-
try {
|
|
646
|
-
const res = await fetch(`${BASE_URL}/config`);
|
|
647
|
-
if (!res.ok) return null;
|
|
648
|
-
cachedConfig = await res.json();
|
|
649
|
-
return cachedConfig;
|
|
650
|
-
} catch {
|
|
651
|
-
return null;
|
|
652
|
-
}
|
|
653
|
-
}
|
|
654
|
-
async function openFile(req) {
|
|
655
|
-
try {
|
|
656
|
-
const res = await fetch(`${BASE_URL}/open`, {
|
|
657
|
-
method: "POST",
|
|
658
|
-
headers: { "Content-Type": "application/json" },
|
|
659
|
-
body: JSON.stringify(req)
|
|
660
|
-
});
|
|
661
|
-
return res.ok;
|
|
662
|
-
} catch {
|
|
663
|
-
return false;
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
async function fetchSnippet(file, line, column, maxLines = 100) {
|
|
667
|
-
const params = new URLSearchParams({
|
|
668
|
-
file,
|
|
669
|
-
line: String(line),
|
|
670
|
-
column: String(column),
|
|
671
|
-
maxLines: String(maxLines)
|
|
672
|
-
});
|
|
673
|
-
const res = await fetch(`${BASE_URL}/snippet?${params}`);
|
|
674
|
-
if (!res.ok) {
|
|
675
|
-
const err = await res.json().catch(() => ({}));
|
|
676
|
-
throw Object.assign(new Error("snippet fetch failed"), { errorCode: err.errorCode });
|
|
677
|
-
}
|
|
678
|
-
return res.json();
|
|
679
|
-
}
|
|
680
|
-
async function sendToAi(req) {
|
|
681
|
-
const res = await fetch(`${BASE_URL}/send-to-ai`, {
|
|
682
|
-
method: "POST",
|
|
683
|
-
headers: { "Content-Type": "application/json" },
|
|
684
|
-
body: JSON.stringify(req)
|
|
685
|
-
});
|
|
686
|
-
if (!res.ok) {
|
|
687
|
-
const err = await res.json().catch(() => ({}));
|
|
688
|
-
return {
|
|
689
|
-
success: false,
|
|
690
|
-
error: err.error ?? "Request failed",
|
|
691
|
-
errorCode: err.errorCode
|
|
692
|
-
};
|
|
693
|
-
}
|
|
694
|
-
return res.json();
|
|
695
|
-
}
|
|
696
|
-
|
|
697
|
-
// src/menu.ts
|
|
698
|
-
var MENU_WIDTH = 280;
|
|
699
|
-
function showIntentMenu(shadowRoot, location, clickX, clickY, options, onClose) {
|
|
700
|
-
const maxSnippetLines = options.maxSnippetLines ?? 100;
|
|
701
|
-
const includeSnippet = options.includeSnippet ?? false;
|
|
702
|
-
const menu = document.createElement("div");
|
|
703
|
-
menu.className = menuClass;
|
|
704
|
-
const { input, inputWrapper, sendIcon } = createAskInput(options.askPlaceholder);
|
|
705
|
-
menu.appendChild(inputWrapper);
|
|
706
|
-
const separator = document.createElement("div");
|
|
707
|
-
separator.style.height = "1px";
|
|
708
|
-
separator.style.background = "var(--inspecto-menu-border)";
|
|
709
|
-
separator.style.margin = "8px 4px 6px 4px";
|
|
710
|
-
menu.appendChild(separator);
|
|
711
|
-
const loadingEl = document.createElement("div");
|
|
712
|
-
loadingEl.className = loadingSpinnerClass;
|
|
713
|
-
menu.appendChild(loadingEl);
|
|
714
|
-
menu.style.left = `${Math.min(clickX, window.innerWidth - MENU_WIDTH)}px`;
|
|
715
|
-
menu.style.visibility = "hidden";
|
|
716
|
-
menu.style.display = "block";
|
|
717
|
-
shadowRoot.appendChild(menu);
|
|
718
|
-
const updatePosition = () => {
|
|
719
|
-
const rect = menu.getBoundingClientRect();
|
|
720
|
-
menu.style.top = `${Math.min(clickY + 8, window.innerHeight - rect.height - 8)}px`;
|
|
721
|
-
};
|
|
722
|
-
updatePosition();
|
|
723
|
-
menu.style.visibility = "visible";
|
|
724
|
-
setTimeout(() => input.focus(), 0);
|
|
725
|
-
const onDocClick = (e) => {
|
|
726
|
-
const path = e.composedPath();
|
|
727
|
-
if (path.includes(menu)) return;
|
|
728
|
-
cleanup();
|
|
729
|
-
};
|
|
730
|
-
setTimeout(() => document.addEventListener("click", onDocClick, { capture: true }), 0);
|
|
731
|
-
function cleanup() {
|
|
732
|
-
document.removeEventListener("click", onDocClick, { capture: true });
|
|
733
|
-
menu.remove();
|
|
734
|
-
onClose();
|
|
735
|
-
}
|
|
736
|
-
const handleSend = async (promptText, snippetText, disable, restore) => {
|
|
737
|
-
disable();
|
|
738
|
-
await openFile(location);
|
|
739
|
-
await new Promise((r) => setTimeout(r, 100));
|
|
740
|
-
const result = await sendToAi({ location, snippet: snippetText, prompt: promptText });
|
|
741
|
-
if (result.success) {
|
|
742
|
-
cleanup();
|
|
743
|
-
} else {
|
|
744
|
-
restore();
|
|
745
|
-
showError(menu, result.error ?? "Unknown error", result.errorCode);
|
|
746
|
-
}
|
|
747
|
-
};
|
|
748
|
-
const submitAsk = async () => {
|
|
749
|
-
if (!input.value.trim()) return;
|
|
750
|
-
input.disabled = true;
|
|
751
|
-
sendIcon.style.pointerEvents = "none";
|
|
752
|
-
try {
|
|
753
|
-
let snippetResult = null;
|
|
754
|
-
if (includeSnippet) {
|
|
755
|
-
snippetResult = await fetchSnippet(
|
|
756
|
-
location.file,
|
|
757
|
-
location.line,
|
|
758
|
-
location.column,
|
|
759
|
-
maxSnippetLines
|
|
760
|
-
);
|
|
761
|
-
}
|
|
762
|
-
const prompt = buildPrompt(
|
|
763
|
-
CUSTOM_PROMPT_TEMPLATE(input.value.trim()),
|
|
764
|
-
location,
|
|
765
|
-
snippetResult
|
|
766
|
-
);
|
|
767
|
-
await handleSend(
|
|
768
|
-
prompt,
|
|
769
|
-
snippetResult?.snippet || "",
|
|
770
|
-
() => {
|
|
771
|
-
},
|
|
772
|
-
// already disabled
|
|
773
|
-
() => {
|
|
774
|
-
input.disabled = false;
|
|
775
|
-
sendIcon.style.pointerEvents = "auto";
|
|
776
|
-
}
|
|
777
|
-
);
|
|
778
|
-
} catch (err) {
|
|
779
|
-
input.disabled = false;
|
|
780
|
-
sendIcon.style.pointerEvents = "auto";
|
|
781
|
-
showError(menu, err.message, err.errorCode);
|
|
782
|
-
}
|
|
783
|
-
};
|
|
784
|
-
input.addEventListener("keydown", (e) => {
|
|
785
|
-
if (e.key === "Enter") submitAsk();
|
|
786
|
-
});
|
|
787
|
-
sendIcon.addEventListener("click", submitAsk);
|
|
788
|
-
fetchIdeInfo().then((ideInfo) => {
|
|
789
|
-
loadingEl.remove();
|
|
790
|
-
const intents = resolveIntents(ideInfo?.prompts);
|
|
791
|
-
for (const intent of intents) {
|
|
792
|
-
if (intent.isAction && intent.id === "open-in-editor") {
|
|
793
|
-
const btn2 = document.createElement("button");
|
|
794
|
-
btn2.className = menuItemClass;
|
|
795
|
-
const span = document.createElement("span");
|
|
796
|
-
span.textContent = intent.label ?? "Unknown";
|
|
797
|
-
btn2.appendChild(span);
|
|
798
|
-
const shortcutDiv = document.createElement("div");
|
|
799
|
-
shortcutDiv.className = shortcutIconClass;
|
|
800
|
-
shortcutDiv.textContent = "\u21B5";
|
|
801
|
-
btn2.appendChild(shortcutDiv);
|
|
802
|
-
btn2.addEventListener("click", (e) => {
|
|
803
|
-
e.stopPropagation();
|
|
804
|
-
openFile(location);
|
|
805
|
-
cleanup();
|
|
806
|
-
});
|
|
807
|
-
menu.appendChild(btn2);
|
|
808
|
-
continue;
|
|
809
|
-
}
|
|
810
|
-
let fullPromptTemplate = intent.prompt ?? "";
|
|
811
|
-
if (intent.prependPrompt)
|
|
812
|
-
fullPromptTemplate = intent.prependPrompt + "\n\n" + fullPromptTemplate;
|
|
813
|
-
if (intent.appendPrompt)
|
|
814
|
-
fullPromptTemplate = fullPromptTemplate + "\n\n" + intent.appendPrompt;
|
|
815
|
-
const label = intent.label ?? intent.id ?? "Unknown";
|
|
816
|
-
const btn = document.createElement("button");
|
|
817
|
-
btn.className = menuItemClass;
|
|
818
|
-
btn.textContent = label;
|
|
819
|
-
btn.addEventListener("click", async (e) => {
|
|
820
|
-
e.stopPropagation();
|
|
821
|
-
btn.disabled = true;
|
|
822
|
-
btn.textContent = "Sending...";
|
|
823
|
-
try {
|
|
824
|
-
let snippetResult = null;
|
|
825
|
-
if (includeSnippet) {
|
|
826
|
-
snippetResult = await fetchSnippet(
|
|
827
|
-
location.file,
|
|
828
|
-
location.line,
|
|
829
|
-
location.column,
|
|
830
|
-
maxSnippetLines
|
|
831
|
-
);
|
|
832
|
-
}
|
|
833
|
-
const prompt = buildPrompt(fullPromptTemplate, location, snippetResult);
|
|
834
|
-
await handleSend(
|
|
835
|
-
prompt,
|
|
836
|
-
snippetResult?.snippet || "",
|
|
837
|
-
() => {
|
|
838
|
-
},
|
|
839
|
-
// already disabled
|
|
840
|
-
() => {
|
|
841
|
-
btn.disabled = false;
|
|
842
|
-
btn.textContent = label;
|
|
843
|
-
}
|
|
844
|
-
);
|
|
845
|
-
} catch (err) {
|
|
846
|
-
btn.disabled = false;
|
|
847
|
-
btn.textContent = label;
|
|
848
|
-
showError(menu, err.message, err.errorCode);
|
|
849
|
-
}
|
|
850
|
-
});
|
|
851
|
-
menu.appendChild(btn);
|
|
852
|
-
}
|
|
853
|
-
updatePosition();
|
|
854
|
-
}).catch((err) => {
|
|
855
|
-
loadingEl.remove();
|
|
856
|
-
const isServerDown = err instanceof TypeError;
|
|
857
|
-
showError(
|
|
858
|
-
menu,
|
|
859
|
-
isServerDown ? "Cannot connect to inspector server. Is the dev server running?" : err.message,
|
|
860
|
-
err.errorCode ?? "UNKNOWN"
|
|
861
|
-
);
|
|
862
|
-
updatePosition();
|
|
863
|
-
});
|
|
864
|
-
return cleanup;
|
|
865
|
-
}
|
|
866
|
-
function resolveIntents(serverPrompts) {
|
|
867
|
-
const baseMap = /* @__PURE__ */ new Map();
|
|
868
|
-
for (const intent of DEFAULT_INTENTS) {
|
|
869
|
-
if (intent.id) baseMap.set(intent.id, { ...intent });
|
|
870
|
-
}
|
|
871
|
-
const defaults = () => ensureOpenInEditorLast(Array.from(baseMap.values()));
|
|
872
|
-
if (!serverPrompts) return defaults();
|
|
873
|
-
const isReplace = !Array.isArray(serverPrompts) && typeof serverPrompts === "object" && serverPrompts.$replace === true;
|
|
874
|
-
const promptsArray = Array.isArray(serverPrompts) ? serverPrompts : isReplace ? serverPrompts.items : [];
|
|
875
|
-
if (!promptsArray || promptsArray.length === 0) return defaults();
|
|
876
|
-
if (isReplace) {
|
|
877
|
-
const result = [];
|
|
878
|
-
for (const item of promptsArray) {
|
|
879
|
-
if (typeof item === "string") {
|
|
880
|
-
if (baseMap.has(item)) {
|
|
881
|
-
result.push(baseMap.get(item));
|
|
882
|
-
} else {
|
|
883
|
-
console.warn(
|
|
884
|
-
`[inspecto] Unknown built-in intent id: "${item}". Available: ${[...baseMap.keys()].join(", ")}`
|
|
885
|
-
);
|
|
886
|
-
}
|
|
887
|
-
} else if (typeof item === "object") {
|
|
888
|
-
if (!item.id) {
|
|
889
|
-
console.warn('[inspecto] Intent object missing required "id" field, skipping.');
|
|
890
|
-
continue;
|
|
891
|
-
}
|
|
892
|
-
if (item.enabled === false) {
|
|
893
|
-
console.warn(
|
|
894
|
-
`[inspecto] Intent "${item.id}" is listed in $replace but has enabled:false \u2014 it will be excluded.`
|
|
895
|
-
);
|
|
896
|
-
continue;
|
|
897
|
-
}
|
|
898
|
-
if (item.isAction && item.id !== "open-in-editor") {
|
|
899
|
-
console.warn(
|
|
900
|
-
`[inspecto] isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
901
|
-
);
|
|
902
|
-
continue;
|
|
903
|
-
}
|
|
904
|
-
result.push(baseMap.has(item.id) ? { ...baseMap.get(item.id), ...item } : item);
|
|
905
|
-
}
|
|
906
|
-
}
|
|
907
|
-
return ensureOpenInEditorLast(result);
|
|
908
|
-
}
|
|
909
|
-
const merged = Array.from(baseMap.values());
|
|
910
|
-
for (const item of promptsArray) {
|
|
911
|
-
if (typeof item === "string") {
|
|
912
|
-
if (!baseMap.has(item)) {
|
|
913
|
-
console.warn(
|
|
914
|
-
`[inspecto] Unknown built-in intent id: "${item}". In append mode, strings have no effect on ordering \u2014 use $replace to control order.`
|
|
915
|
-
);
|
|
916
|
-
}
|
|
917
|
-
continue;
|
|
918
|
-
}
|
|
919
|
-
if (typeof item === "object") {
|
|
920
|
-
if (!item.id) {
|
|
921
|
-
console.warn('[inspecto] Intent object missing required "id" field, skipping.');
|
|
922
|
-
continue;
|
|
923
|
-
}
|
|
924
|
-
if (item.isAction && item.id !== "open-in-editor") {
|
|
925
|
-
console.warn(
|
|
926
|
-
`[inspecto] isAction is reserved for built-in actions. Ignoring intent "${item.id}".`
|
|
927
|
-
);
|
|
928
|
-
continue;
|
|
929
|
-
}
|
|
930
|
-
const existingIdx = merged.findIndex((i) => i.id === item.id);
|
|
931
|
-
if (existingIdx !== -1) {
|
|
932
|
-
if (item.enabled === false) {
|
|
933
|
-
merged.splice(existingIdx, 1);
|
|
934
|
-
} else {
|
|
935
|
-
merged[existingIdx] = { ...merged[existingIdx], ...item };
|
|
936
|
-
}
|
|
937
|
-
} else {
|
|
938
|
-
if (item.enabled !== false) {
|
|
939
|
-
merged.push(item);
|
|
940
|
-
}
|
|
941
|
-
}
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
return ensureOpenInEditorLast(merged);
|
|
945
|
-
}
|
|
946
|
-
function ensureOpenInEditorLast(intents) {
|
|
947
|
-
const idx = intents.findIndex((i) => i.id === "open-in-editor");
|
|
948
|
-
if (idx === -1 || idx === intents.length - 1) return intents;
|
|
949
|
-
const result = [...intents];
|
|
950
|
-
const item = result.splice(idx, 1)[0];
|
|
951
|
-
result.push(item);
|
|
952
|
-
return result;
|
|
953
|
-
}
|
|
954
|
-
function createAskInput(placeholder) {
|
|
955
|
-
const inputWrapper = document.createElement("div");
|
|
956
|
-
inputWrapper.className = menuInputWrapperClass;
|
|
957
|
-
const input = document.createElement("input");
|
|
958
|
-
input.className = menuInputClass;
|
|
959
|
-
input.type = "text";
|
|
960
|
-
input.placeholder = placeholder ?? "Describe how to change this component...";
|
|
961
|
-
const sendIcon = document.createElement("div");
|
|
962
|
-
sendIcon.className = menuInputIconClass;
|
|
963
|
-
sendIcon.innerHTML = `<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"></line><polygon points="22 2 15 22 11 13 2 9 22 2"></polygon></svg>`;
|
|
964
|
-
sendIcon.style.cursor = "pointer";
|
|
965
|
-
inputWrapper.appendChild(input);
|
|
966
|
-
inputWrapper.appendChild(sendIcon);
|
|
967
|
-
return { input, inputWrapper, sendIcon };
|
|
968
|
-
}
|
|
969
|
-
function showError(menu, message, errorCode) {
|
|
970
|
-
menu.querySelector(`.${errorMsgClass}`)?.remove();
|
|
971
|
-
const errEl = document.createElement("div");
|
|
972
|
-
errEl.className = errorMsgClass;
|
|
973
|
-
errEl.textContent = errorCode === "FILE_NOT_FOUND" ? "Source file not found. Is the server running?" : `Error: ${message}`;
|
|
974
|
-
menu.appendChild(errEl);
|
|
975
|
-
}
|
|
976
|
-
|
|
977
|
-
// src/component.ts
|
|
978
|
-
var ATTR_NAME = "data-inspecto";
|
|
979
|
-
function parseAttrValue(value) {
|
|
980
|
-
const parts = value.split(":");
|
|
981
|
-
if (parts.length < 3) return null;
|
|
982
|
-
const col = parseInt(parts[parts.length - 1], 10);
|
|
983
|
-
const line = parseInt(parts[parts.length - 2], 10);
|
|
984
|
-
const file = parts.slice(0, parts.length - 2).join(":");
|
|
985
|
-
if (isNaN(line) || isNaN(col) || !file) return null;
|
|
986
|
-
return { file, line, column: col };
|
|
987
|
-
}
|
|
988
|
-
function findInspectable(el) {
|
|
989
|
-
while (el) {
|
|
990
|
-
if (el.hasAttribute(ATTR_NAME)) return el;
|
|
991
|
-
el = el.parentElement;
|
|
992
|
-
}
|
|
993
|
-
return null;
|
|
994
|
-
}
|
|
995
|
-
function hotKeysHeld(event, hotKeys) {
|
|
996
|
-
return hotKeys.every((key) => event[key]);
|
|
997
|
-
}
|
|
998
|
-
var BaseElement = typeof HTMLElement !== "undefined" ? HTMLElement : class {
|
|
999
|
-
};
|
|
1000
|
-
var InspectoElement = class extends BaseElement {
|
|
1001
|
-
constructor() {
|
|
1002
|
-
super(...arguments);
|
|
1003
|
-
this.options = {};
|
|
1004
|
-
this.serverHotKeys = null;
|
|
1005
|
-
this.active = false;
|
|
1006
|
-
this.disabled = false;
|
|
1007
|
-
this.isDragging = false;
|
|
1008
|
-
this.hasMoved = false;
|
|
1009
|
-
this.dragStartX = 0;
|
|
1010
|
-
this.dragStartY = 0;
|
|
1011
|
-
this.badgeInitialRight = 16;
|
|
1012
|
-
this.badgeInitialBottom = 16;
|
|
1013
|
-
this.cleanupMenu = null;
|
|
1014
|
-
this.onDragStart = (e) => {
|
|
1015
|
-
if (e.button !== 0) return;
|
|
1016
|
-
if (e.target.classList?.contains(`${badgeClass}-close`)) return;
|
|
1017
|
-
e.preventDefault();
|
|
1018
|
-
this.isDragging = true;
|
|
1019
|
-
this.hasMoved = false;
|
|
1020
|
-
const rect = this.badge.getBoundingClientRect();
|
|
1021
|
-
this.dragStartX = e.clientX - rect.left;
|
|
1022
|
-
this.dragStartY = e.clientY - rect.top;
|
|
1023
|
-
document.addEventListener("mousemove", this.onDragMove);
|
|
1024
|
-
document.addEventListener("mouseup", this.onDragEnd);
|
|
1025
|
-
};
|
|
1026
|
-
this.onDragMove = (e) => {
|
|
1027
|
-
if (!this.isDragging) return;
|
|
1028
|
-
this.hasMoved = true;
|
|
1029
|
-
let newLeft = e.clientX - this.dragStartX;
|
|
1030
|
-
let newTop = e.clientY - this.dragStartY;
|
|
1031
|
-
const badgeWidth = this.badge.offsetWidth;
|
|
1032
|
-
const badgeHeight = this.badge.offsetHeight;
|
|
1033
|
-
newLeft = Math.max(0, Math.min(newLeft, window.innerWidth - badgeWidth));
|
|
1034
|
-
newTop = Math.max(0, Math.min(newTop, window.innerHeight - badgeHeight));
|
|
1035
|
-
this.badge.style.transition = "none";
|
|
1036
|
-
this.badge.style.right = "auto";
|
|
1037
|
-
this.badge.style.bottom = "auto";
|
|
1038
|
-
this.badge.style.left = `${newLeft}px`;
|
|
1039
|
-
this.badge.style.top = `${newTop}px`;
|
|
1040
|
-
};
|
|
1041
|
-
this.onDragEnd = () => {
|
|
1042
|
-
document.removeEventListener("mousemove", this.onDragMove);
|
|
1043
|
-
document.removeEventListener("mouseup", this.onDragEnd);
|
|
1044
|
-
this.badge.style.transition = "";
|
|
1045
|
-
setTimeout(() => {
|
|
1046
|
-
this.isDragging = false;
|
|
1047
|
-
}, 0);
|
|
1048
|
-
};
|
|
1049
|
-
this.onMouseMove = (e) => {
|
|
1050
|
-
const isActive = this.isInspectorActive(e);
|
|
1051
|
-
if (!isActive) {
|
|
1052
|
-
this.overlay.hide();
|
|
1053
|
-
return;
|
|
1054
|
-
}
|
|
1055
|
-
const target = findInspectable(e.target);
|
|
1056
|
-
if (!target) {
|
|
1057
|
-
this.overlay.hide();
|
|
1058
|
-
return;
|
|
1059
|
-
}
|
|
1060
|
-
const attrValue = target.getAttribute(ATTR_NAME);
|
|
1061
|
-
const loc = parseAttrValue(attrValue);
|
|
1062
|
-
const label = loc ? `${loc.file.split("/").pop() ?? ""}:${loc.line}` : attrValue;
|
|
1063
|
-
this.overlay.show(target, label);
|
|
1064
|
-
e.stopPropagation();
|
|
1065
|
-
};
|
|
1066
|
-
this.onClick = (e) => {
|
|
1067
|
-
this.handleTrigger(e);
|
|
1068
|
-
};
|
|
1069
|
-
this.onContextMenu = (e) => {
|
|
1070
|
-
if (this.isInspectorActive(e)) {
|
|
1071
|
-
this.handleTrigger(e);
|
|
1072
|
-
}
|
|
1073
|
-
};
|
|
1074
|
-
this.onKeyDown = (e) => {
|
|
1075
|
-
if (e.key === "Escape") {
|
|
1076
|
-
this.cleanupMenu?.();
|
|
1077
|
-
this.overlay.hide();
|
|
1078
|
-
}
|
|
1079
|
-
};
|
|
1080
|
-
}
|
|
1081
|
-
connectedCallback() {
|
|
1082
|
-
this.shadowRootEl = this.attachShadow({ mode: "open" });
|
|
1083
|
-
const style = document.createElement("style");
|
|
1084
|
-
style.textContent = inspectorStyles;
|
|
1085
|
-
this.shadowRootEl.appendChild(style);
|
|
1086
|
-
this.overlay = createOverlay(this.shadowRootEl);
|
|
1087
|
-
this.badge = this.createBadge();
|
|
1088
|
-
this.setupListeners();
|
|
1089
|
-
if (this.options.defaultActive) {
|
|
1090
|
-
this.setActive(true);
|
|
1091
|
-
}
|
|
1092
|
-
}
|
|
1093
|
-
disconnectedCallback() {
|
|
1094
|
-
this.teardownListeners();
|
|
1095
|
-
}
|
|
1096
|
-
configure(options) {
|
|
1097
|
-
this.options = options;
|
|
1098
|
-
if (options.serverUrl) {
|
|
1099
|
-
setBaseUrl(options.serverUrl);
|
|
1100
|
-
}
|
|
1101
|
-
if (options.theme === "dark") {
|
|
1102
|
-
this.setAttribute("data-theme", "dark");
|
|
1103
|
-
} else if (options.theme === "light") {
|
|
1104
|
-
this.setAttribute("data-theme", "light");
|
|
1105
|
-
} else {
|
|
1106
|
-
this.removeAttribute("data-theme");
|
|
1107
|
-
}
|
|
1108
|
-
fetchIdeInfo(true).then((info) => {
|
|
1109
|
-
if (info?.hotKeys !== void 0) {
|
|
1110
|
-
this.serverHotKeys = info.hotKeys;
|
|
1111
|
-
this.updateBadgeContent();
|
|
1112
|
-
}
|
|
1113
|
-
if (info?.includeSnippet !== void 0) {
|
|
1114
|
-
this.options.includeSnippet = info.includeSnippet;
|
|
1115
|
-
}
|
|
1116
|
-
}).catch(() => {
|
|
1117
|
-
});
|
|
1118
|
-
}
|
|
1119
|
-
createBadge() {
|
|
1120
|
-
const btn = document.createElement("button");
|
|
1121
|
-
btn.className = badgeClass;
|
|
1122
|
-
btn.style.display = "flex";
|
|
1123
|
-
const textSpan = document.createElement("span");
|
|
1124
|
-
textSpan.textContent = "Inspecto Ready";
|
|
1125
|
-
const closeBtn = document.createElement("span");
|
|
1126
|
-
closeBtn.className = `${badgeClass}-close`;
|
|
1127
|
-
closeBtn.innerHTML = "\xD7";
|
|
1128
|
-
closeBtn.title = "Pause Inspector";
|
|
1129
|
-
closeBtn.addEventListener("click", (e) => {
|
|
1130
|
-
e.stopPropagation();
|
|
1131
|
-
this.toggleDisabled();
|
|
1132
|
-
});
|
|
1133
|
-
btn.appendChild(textSpan);
|
|
1134
|
-
btn.appendChild(closeBtn);
|
|
1135
|
-
btn.addEventListener("mousedown", this.onDragStart);
|
|
1136
|
-
btn.addEventListener("click", (e) => {
|
|
1137
|
-
if (this.hasMoved) {
|
|
1138
|
-
this.hasMoved = false;
|
|
1139
|
-
return;
|
|
1140
|
-
}
|
|
1141
|
-
if (this.disabled) {
|
|
1142
|
-
this.toggleDisabled();
|
|
1143
|
-
} else {
|
|
1144
|
-
this.setActive(!this.active);
|
|
1145
|
-
}
|
|
1146
|
-
});
|
|
1147
|
-
this.shadowRootEl.appendChild(btn);
|
|
1148
|
-
return btn;
|
|
1149
|
-
}
|
|
1150
|
-
toggleDisabled() {
|
|
1151
|
-
this.disabled = !this.disabled;
|
|
1152
|
-
if (this.disabled) {
|
|
1153
|
-
this.active = false;
|
|
1154
|
-
this.overlay.hide();
|
|
1155
|
-
this.cleanupMenu?.();
|
|
1156
|
-
this.cleanupMenu = null;
|
|
1157
|
-
}
|
|
1158
|
-
this.updateBadgeContent();
|
|
1159
|
-
}
|
|
1160
|
-
dismiss() {
|
|
1161
|
-
this.badge.style.display = "none";
|
|
1162
|
-
this.setActive(false);
|
|
1163
|
-
}
|
|
1164
|
-
getHotKeyHint() {
|
|
1165
|
-
const hotKeys = this.getEffectiveHotKeys();
|
|
1166
|
-
if (hotKeys === false || hotKeys.length === 0) return "Inspecto Ready";
|
|
1167
|
-
const isMac = typeof navigator !== "undefined" && /Mac|iPod|iPhone|iPad/.test(navigator.platform);
|
|
1168
|
-
const keys = hotKeys.map((k) => {
|
|
1169
|
-
if (k === "altKey") return isMac ? "\u2325" : "Alt";
|
|
1170
|
-
if (k === "metaKey") return isMac ? "\u2318" : "Win";
|
|
1171
|
-
if (k === "ctrlKey") return isMac ? "\u2303" : "Ctrl";
|
|
1172
|
-
if (k === "shiftKey") return isMac ? "\u21E7" : "Shift";
|
|
1173
|
-
return k;
|
|
1174
|
-
});
|
|
1175
|
-
return `Hold ${keys.join(" + ")} to Inspect`;
|
|
1176
|
-
}
|
|
1177
|
-
getEffectiveHotKeys() {
|
|
1178
|
-
if (this.options.hotKeys !== void 0) return this.options.hotKeys;
|
|
1179
|
-
if (this.serverHotKeys !== null) return this.serverHotKeys;
|
|
1180
|
-
return ["altKey"];
|
|
1181
|
-
}
|
|
1182
|
-
updateBadgeContent() {
|
|
1183
|
-
const textSpan = this.badge.querySelector("span");
|
|
1184
|
-
if (!textSpan) return;
|
|
1185
|
-
if (this.disabled) {
|
|
1186
|
-
textSpan.textContent = "Inspector Paused";
|
|
1187
|
-
this.badge.classList.remove("active");
|
|
1188
|
-
this.badge.classList.add("disabled");
|
|
1189
|
-
} else if (this.active) {
|
|
1190
|
-
textSpan.textContent = "\u{1F50D} Inspecting...";
|
|
1191
|
-
this.badge.classList.remove("disabled");
|
|
1192
|
-
this.badge.classList.add("active");
|
|
1193
|
-
} else {
|
|
1194
|
-
textSpan.textContent = this.getHotKeyHint();
|
|
1195
|
-
this.badge.classList.remove("active", "disabled");
|
|
1196
|
-
}
|
|
1197
|
-
}
|
|
1198
|
-
setActive(value) {
|
|
1199
|
-
this.active = value;
|
|
1200
|
-
this.updateBadgeContent();
|
|
1201
|
-
if (!value) {
|
|
1202
|
-
this.overlay.hide();
|
|
1203
|
-
this.cleanupMenu?.();
|
|
1204
|
-
this.cleanupMenu = null;
|
|
1205
|
-
}
|
|
1206
|
-
}
|
|
1207
|
-
handleTrigger(e) {
|
|
1208
|
-
if (!this.isInspectorActive(e)) return;
|
|
1209
|
-
const target = findInspectable(e.target);
|
|
1210
|
-
if (!target) return;
|
|
1211
|
-
e.preventDefault();
|
|
1212
|
-
e.stopPropagation();
|
|
1213
|
-
const attrValue = target.getAttribute(ATTR_NAME);
|
|
1214
|
-
const loc = parseAttrValue(attrValue);
|
|
1215
|
-
if (!loc) return;
|
|
1216
|
-
this.cleanupMenu?.();
|
|
1217
|
-
this.cleanupMenu = showIntentMenu(
|
|
1218
|
-
this.shadowRootEl,
|
|
1219
|
-
loc,
|
|
1220
|
-
e.clientX,
|
|
1221
|
-
e.clientY,
|
|
1222
|
-
this.options,
|
|
1223
|
-
() => {
|
|
1224
|
-
this.cleanupMenu = null;
|
|
1225
|
-
}
|
|
1226
|
-
);
|
|
1227
|
-
}
|
|
1228
|
-
isInspectorActive(e) {
|
|
1229
|
-
if (this.disabled) return false;
|
|
1230
|
-
if (this.active) return true;
|
|
1231
|
-
const hotKeys = this.getEffectiveHotKeys();
|
|
1232
|
-
if (hotKeys === false) return false;
|
|
1233
|
-
return hotKeysHeld(e, hotKeys);
|
|
1234
|
-
}
|
|
1235
|
-
setupListeners() {
|
|
1236
|
-
document.addEventListener("mousemove", this.onMouseMove, true);
|
|
1237
|
-
document.addEventListener("click", this.onClick, true);
|
|
1238
|
-
document.addEventListener("contextmenu", this.onContextMenu, true);
|
|
1239
|
-
document.addEventListener("keydown", this.onKeyDown, true);
|
|
1240
|
-
}
|
|
1241
|
-
teardownListeners() {
|
|
1242
|
-
document.removeEventListener("mousemove", this.onMouseMove, true);
|
|
1243
|
-
document.removeEventListener("click", this.onClick, true);
|
|
1244
|
-
document.removeEventListener("contextmenu", this.onContextMenu, true);
|
|
1245
|
-
document.removeEventListener("keydown", this.onKeyDown, true);
|
|
1246
|
-
}
|
|
1247
|
-
};
|
|
1248
|
-
if (typeof customElements !== "undefined") {
|
|
1249
|
-
customElements.define("inspecto-overlay", InspectoElement);
|
|
1250
|
-
}
|
|
1
|
+
import {
|
|
2
|
+
__async
|
|
3
|
+
} from "./chunk-C4KO2HLL.js";
|
|
1251
4
|
|
|
1252
5
|
// src/index.ts
|
|
1253
6
|
var TAG_NAME = "inspecto-overlay";
|
|
1254
|
-
function mountInspector(
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
7
|
+
function mountInspector() {
|
|
8
|
+
return __async(this, arguments, function* (options = {}) {
|
|
9
|
+
if (typeof document === "undefined") return null;
|
|
10
|
+
const { InspectoElement } = yield import("./component-4WPU23TV.js");
|
|
11
|
+
const existing = document.querySelector(TAG_NAME);
|
|
12
|
+
if (existing) {
|
|
13
|
+
;
|
|
14
|
+
existing.configure(options);
|
|
15
|
+
return existing;
|
|
16
|
+
}
|
|
17
|
+
const el = document.createElement(TAG_NAME);
|
|
18
|
+
el.configure(options);
|
|
19
|
+
document.body.appendChild(el);
|
|
20
|
+
return el;
|
|
21
|
+
});
|
|
1264
22
|
}
|
|
1265
23
|
function unmountInspector() {
|
|
24
|
+
if (typeof document === "undefined") return;
|
|
1266
25
|
const existing = document.querySelector(TAG_NAME);
|
|
1267
26
|
if (existing) {
|
|
1268
27
|
existing.remove();
|
|
@@ -1275,7 +34,6 @@ if (typeof window !== "undefined") {
|
|
|
1275
34
|
};
|
|
1276
35
|
}
|
|
1277
36
|
export {
|
|
1278
|
-
InspectoElement,
|
|
1279
37
|
mountInspector,
|
|
1280
38
|
unmountInspector
|
|
1281
39
|
};
|