@inspecto-dev/core 0.2.0-alpha.0

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