@promakeai/inspector 0.1.2 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +165 -0
- package/dist/hook.d.ts.map +1 -1
- package/dist/hook.js +24 -0
- package/dist/plugin.d.ts.map +1 -1
- package/dist/plugin.js +604 -109
- package/dist/types.d.ts +31 -0
- package/dist/types.d.ts.map +1 -1
- package/package.json +20 -4
package/README.md
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# @promakeai/inspector
|
|
2
2
|
|
|
3
3
|
[](https://github.com/promakeai/inspector/actions/workflows/build.yml)
|
|
4
|
+
[](https://github.com/promakeai/inspector/actions/workflows/test.yml)
|
|
4
5
|
[](https://www.npmjs.com/package/@promakeai/inspector)
|
|
5
6
|
[](https://opensource.org/licenses/MIT)
|
|
6
7
|
|
|
@@ -24,7 +25,12 @@ Visual element inspector for React apps in iframe with AI prompt support. Perfec
|
|
|
24
25
|
- 🔍 **Text Node Detection** - Automatically detect if selected element is a text node
|
|
25
26
|
- 🖼️ **Image Node Detection** - Automatically detect if selected element is an image
|
|
26
27
|
- 🏷️ **Promake Badge** - Optional "Built by Promake" badge with smooth animations
|
|
28
|
+
- 🔍 **Element Highlighting** - Revisit and highlight previously inspected elements
|
|
29
|
+
- 🆔 **Persistent Element IDs** - Structural signature-based IDs that survive page reloads
|
|
30
|
+
- 🔄 **Reload-Safe Highlighting** - Same element gets same ID even after refresh
|
|
31
|
+
- 🎯 **Smart Fallback** - Position-based matching when DOM structure changes
|
|
27
32
|
- 📦 **TypeScript** - Full type safety
|
|
33
|
+
- ✅ **Well Tested** - 42 unit tests + 35 e2e tests with 90%+ coverage
|
|
28
34
|
|
|
29
35
|
## Installation
|
|
30
36
|
|
|
@@ -211,6 +217,8 @@ function App() {
|
|
|
211
217
|
- **showContentInput**: `(show: boolean, updateImmediately?: boolean) => void` - Show or hide text content input dynamically. If `updateImmediately` is true, DOM is updated immediately on submit (useful for slow backend responses)
|
|
212
218
|
- **showImageInput**: `(show: boolean, updateImmediately?: boolean) => void` - Show or hide image input dynamically. If `updateImmediately` is true, DOM is updated immediately on submit (useful for slow backend responses)
|
|
213
219
|
- **setBadgeVisible**: `(visible: boolean) => void` - Show or hide "Built by Promake" badge in bottom-right corner
|
|
220
|
+
- **highlightElement**: `(identifier: string | ElementIdentifier, options?: HighlightOptions) => void` - Highlight an element in the iframe by ID or selector
|
|
221
|
+
- **getElementByInspectorId**: `(inspectorId: string) => void` - Request element info by unique inspector ID
|
|
214
222
|
|
|
215
223
|
## Types
|
|
216
224
|
|
|
@@ -227,9 +235,115 @@ interface SelectedElementData {
|
|
|
227
235
|
textContent?: string; // Text content of the element (if text node)
|
|
228
236
|
isImageNode?: boolean; // Whether the element is an image node
|
|
229
237
|
imageUrl?: string; // Image URL (if image node)
|
|
238
|
+
inspectorId?: string; // Unique inspector ID for element tracking
|
|
239
|
+
selector?: string; // CSS selector for element identification
|
|
230
240
|
}
|
|
231
241
|
```
|
|
232
242
|
|
|
243
|
+
### `ElementIdentifier`
|
|
244
|
+
|
|
245
|
+
```typescript
|
|
246
|
+
interface ElementIdentifier {
|
|
247
|
+
inspectorId: string; // Unique ID (primary)
|
|
248
|
+
selector: string; // CSS selector (fallback)
|
|
249
|
+
position?: {
|
|
250
|
+
// Position (for disambiguation)
|
|
251
|
+
top: number;
|
|
252
|
+
left: number;
|
|
253
|
+
};
|
|
254
|
+
component?: {
|
|
255
|
+
// Component info (metadata)
|
|
256
|
+
name: string;
|
|
257
|
+
fileName: string;
|
|
258
|
+
lineNumber?: number;
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
```
|
|
262
|
+
|
|
263
|
+
### `HighlightOptions`
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
interface HighlightOptions {
|
|
267
|
+
duration?: number; // Highlight duration in ms (default: 3000)
|
|
268
|
+
scrollIntoView?: boolean; // Scroll to element (default: true)
|
|
269
|
+
color?: string; // Highlight color (default: '#4417db')
|
|
270
|
+
animation?: "pulse" | "fade" | "none"; // Animation type (default: 'pulse')
|
|
271
|
+
}
|
|
272
|
+
```
|
|
273
|
+
|
|
274
|
+
## Element Identification & Persistence
|
|
275
|
+
|
|
276
|
+
Inspector uses a **structural signature-based ID system** to ensure the same element gets the same ID across page reloads, making it perfect for features like chat history highlighting.
|
|
277
|
+
|
|
278
|
+
### How Persistent IDs Work
|
|
279
|
+
|
|
280
|
+
#### ID Format
|
|
281
|
+
|
|
282
|
+
```
|
|
283
|
+
pi-{hash}
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
Example: `pi-1f2a3b4c`
|
|
287
|
+
|
|
288
|
+
#### ID Generation
|
|
289
|
+
|
|
290
|
+
IDs are generated based on element's structural properties:
|
|
291
|
+
|
|
292
|
+
1. **XPath** - DOM tree position (primary identifier)
|
|
293
|
+
2. **Static Identifiers** - `id`, `data-id`, `data-testid`, `data-component`, `data-cy`
|
|
294
|
+
3. **Semantic Properties** - `tag`, `role`, `type`, `name`, `href`, `src`
|
|
295
|
+
4. **Structural Context** - sibling index, parent tag (if no static ID)
|
|
296
|
+
|
|
297
|
+
**Excluded for Stability:**
|
|
298
|
+
|
|
299
|
+
- ❌ Class names (frequently change)
|
|
300
|
+
- ❌ Text content (dynamic)
|
|
301
|
+
- ❌ Style attributes (dynamic)
|
|
302
|
+
|
|
303
|
+
#### Consistency Guarantees
|
|
304
|
+
|
|
305
|
+
✅ **Same element → Same ID** (across page reloads)
|
|
306
|
+
✅ **Class changes → ID unchanged**
|
|
307
|
+
✅ **Text changes → ID unchanged**
|
|
308
|
+
✅ **Style changes → ID unchanged**
|
|
309
|
+
❌ **DOM position changes → Different ID** (new structure = new ID)
|
|
310
|
+
|
|
311
|
+
#### Best Practices for Maximum Consistency
|
|
312
|
+
|
|
313
|
+
To ensure the most reliable element identification:
|
|
314
|
+
|
|
315
|
+
```html
|
|
316
|
+
<!-- ✅ Best: Use data-id for elements you want to reliably track -->
|
|
317
|
+
<div data-id="hero-section">...</div>
|
|
318
|
+
<button data-id="cta-button">...</button>
|
|
319
|
+
|
|
320
|
+
<!-- ✅ Good: Use element.id -->
|
|
321
|
+
<div id="main-content">...</div>
|
|
322
|
+
|
|
323
|
+
<!-- ✅ Good: Use data-testid -->
|
|
324
|
+
<button data-testid="submit-form">...</button>
|
|
325
|
+
|
|
326
|
+
<!-- ⚠️ Okay: Relies on DOM position (changes if DOM structure changes) -->
|
|
327
|
+
<div class="card">...</div>
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
#### Fallback System
|
|
331
|
+
|
|
332
|
+
If an element's ID changes (e.g., DOM restructure), the system uses smart fallbacks:
|
|
333
|
+
|
|
334
|
+
1. **Primary**: Try to find by `inspectorId`
|
|
335
|
+
2. **Fallback 1**: Use CSS selector
|
|
336
|
+
3. **Fallback 2**: Use position matching (if multiple matches)
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
// Full identifier with fallback support
|
|
340
|
+
highlightElement({
|
|
341
|
+
inspectorId: "pi-1f2a3b4c", // Primary
|
|
342
|
+
selector: "button.cta-primary", // Fallback 1
|
|
343
|
+
position: { top: 500, left: 100 }, // Fallback 2
|
|
344
|
+
});
|
|
345
|
+
```
|
|
346
|
+
|
|
233
347
|
### `ComponentInfo`
|
|
234
348
|
|
|
235
349
|
```typescript
|
|
@@ -556,6 +670,57 @@ Perfect for integrating with error tracking services like Sentry, LogRocket, or
|
|
|
556
670
|
|
|
557
671
|
## Examples
|
|
558
672
|
|
|
673
|
+
### Element Highlighting from Chat History
|
|
674
|
+
|
|
675
|
+
```typescript
|
|
676
|
+
const { highlightElement, getElementByInspectorId } = useInspector(iframeRef, {
|
|
677
|
+
onElementSelected: (data) => {
|
|
678
|
+
// Store inspectorId in your chat message
|
|
679
|
+
const message = {
|
|
680
|
+
text: `Selected ${data.tagName} element`,
|
|
681
|
+
inspectorId: data.inspectorId,
|
|
682
|
+
selector: data.selector,
|
|
683
|
+
position: data.position,
|
|
684
|
+
};
|
|
685
|
+
saveToChatHistory(message);
|
|
686
|
+
},
|
|
687
|
+
onElementInfoReceived: (data) => {
|
|
688
|
+
if (data.found) {
|
|
689
|
+
console.log("Element found:", data.element);
|
|
690
|
+
} else {
|
|
691
|
+
console.log("Element not found:", data.error);
|
|
692
|
+
}
|
|
693
|
+
},
|
|
694
|
+
});
|
|
695
|
+
|
|
696
|
+
// Later: Click on a chat message to re-highlight the element
|
|
697
|
+
function handleChatMessageClick(message) {
|
|
698
|
+
// Highlight with full identifier (with fallback support)
|
|
699
|
+
highlightElement(
|
|
700
|
+
{
|
|
701
|
+
inspectorId: message.inspectorId,
|
|
702
|
+
selector: message.selector,
|
|
703
|
+
position: message.position,
|
|
704
|
+
},
|
|
705
|
+
{
|
|
706
|
+
duration: 4000,
|
|
707
|
+
color: "#10b981",
|
|
708
|
+
animation: "pulse",
|
|
709
|
+
}
|
|
710
|
+
);
|
|
711
|
+
}
|
|
712
|
+
|
|
713
|
+
// Or simply by ID
|
|
714
|
+
function quickHighlight(inspectorId) {
|
|
715
|
+
highlightElement(inspectorId);
|
|
716
|
+
}
|
|
717
|
+
|
|
718
|
+
// Request element info
|
|
719
|
+
function checkElementStatus(inspectorId) {
|
|
720
|
+
getElementByInspectorId(inspectorId);
|
|
721
|
+
}
|
|
722
|
+
```
|
|
723
|
+
|
|
559
724
|
### Dynamic Content Input Based on AI Response
|
|
560
725
|
|
|
561
726
|
```typescript
|
package/dist/hook.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AAEH,OAAO,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,kBAAkB,
|
|
1
|
+
{"version":3,"file":"hook.d.ts","sourceRoot":"","sources":["../src/hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6GG;AAEH,OAAO,EAAoC,SAAS,EAAE,MAAM,OAAO,CAAC;AACpE,OAAO,KAAK,EACV,kBAAkB,EAClB,eAAe,EACf,cAAc,EACd,kBAAkB,EAUnB,MAAM,SAAS,CAAC;AAmEjB,wBAAgB,YAAY,CAC1B,SAAS,EAAE,SAAS,CAAC,iBAAiB,CAAC,EACvC,SAAS,CAAC,EAAE,kBAAkB,EAC9B,MAAM,CAAC,EAAE,eAAe,EACxB,KAAK,CAAC,EAAE,cAAc,GACrB,kBAAkB,CA6NpB;AAGD,mBAAmB,SAAS,CAAC"}
|
package/dist/hook.js
CHANGED
|
@@ -182,6 +182,25 @@ export function useInspector(iframeRef, callbacks, labels, theme) {
|
|
|
182
182
|
badgeText: labels?.badgeText,
|
|
183
183
|
});
|
|
184
184
|
}, [sendMessage, labels]);
|
|
185
|
+
/**
|
|
186
|
+
* Highlight an element in the iframe
|
|
187
|
+
*/
|
|
188
|
+
const highlightElement = useCallback((identifier, options) => {
|
|
189
|
+
sendMessage({
|
|
190
|
+
type: "HIGHLIGHT_ELEMENT",
|
|
191
|
+
identifier: identifier,
|
|
192
|
+
options: options,
|
|
193
|
+
});
|
|
194
|
+
}, [sendMessage]);
|
|
195
|
+
/**
|
|
196
|
+
* Request element info by inspector ID
|
|
197
|
+
*/
|
|
198
|
+
const getElementByInspectorId = useCallback((inspectorId) => {
|
|
199
|
+
sendMessage({
|
|
200
|
+
type: "GET_ELEMENT_BY_ID",
|
|
201
|
+
inspectorId: inspectorId,
|
|
202
|
+
});
|
|
203
|
+
}, [sendMessage]);
|
|
185
204
|
/**
|
|
186
205
|
* Listen for messages from iframe
|
|
187
206
|
*/
|
|
@@ -227,6 +246,9 @@ export function useInspector(iframeRef, callbacks, labels, theme) {
|
|
|
227
246
|
case "INSPECTOR_ERROR":
|
|
228
247
|
callbacks?.onError?.(messageData.data);
|
|
229
248
|
break;
|
|
249
|
+
case "ELEMENT_INFO_RESPONSE":
|
|
250
|
+
callbacks?.onElementInfoReceived?.(messageData.data);
|
|
251
|
+
break;
|
|
230
252
|
default:
|
|
231
253
|
// Unknown message type - ignore
|
|
232
254
|
break;
|
|
@@ -260,5 +282,7 @@ export function useInspector(iframeRef, callbacks, labels, theme) {
|
|
|
260
282
|
showContentInput,
|
|
261
283
|
showImageInput,
|
|
262
284
|
setBadgeVisible,
|
|
285
|
+
highlightElement,
|
|
286
|
+
getElementByInspectorId,
|
|
263
287
|
};
|
|
264
288
|
}
|
package/dist/plugin.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,wBAAgB,eAAe,IAAI,MAAM,
|
|
1
|
+
{"version":3,"file":"plugin.d.ts","sourceRoot":"","sources":["../src/plugin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAwCG;AAEH,OAAO,EAAE,MAAM,EAAE,MAAM,MAAM,CAAC;AAE9B,wBAAgB,eAAe,IAAI,MAAM,CA2uDxC"}
|
package/dist/plugin.js
CHANGED
|
@@ -54,6 +54,347 @@ export function inspectorPlugin() {
|
|
|
54
54
|
},
|
|
55
55
|
children: `
|
|
56
56
|
(function() {
|
|
57
|
+
// ElementTracker class - inline implementation
|
|
58
|
+
class ElementTracker {
|
|
59
|
+
constructor() {
|
|
60
|
+
this.ID_ATTRIBUTE = 'data-inspector-id';
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
ensureElementId(element) {
|
|
64
|
+
const existingId = element.getAttribute(this.ID_ATTRIBUTE);
|
|
65
|
+
if (existingId) return existingId;
|
|
66
|
+
|
|
67
|
+
const persistentId = this.generatePersistentId(element);
|
|
68
|
+
element.setAttribute(this.ID_ATTRIBUTE, persistentId);
|
|
69
|
+
return persistentId;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
generatePersistentId(element) {
|
|
73
|
+
const hasUniqueId = element.id || element.getAttribute('data-id');
|
|
74
|
+
|
|
75
|
+
const signatureParts = [];
|
|
76
|
+
|
|
77
|
+
signatureParts.push(\`xpath:\${this.getXPath(element)}\`);
|
|
78
|
+
|
|
79
|
+
if (element.id) signatureParts.push(\`id:\${element.id}\`);
|
|
80
|
+
|
|
81
|
+
const dataId = element.getAttribute('data-id');
|
|
82
|
+
if (dataId) signatureParts.push(\`data-id:\${dataId}\`);
|
|
83
|
+
|
|
84
|
+
const dataTestId = element.getAttribute('data-testid');
|
|
85
|
+
if (dataTestId) signatureParts.push(\`data-testid:\${dataTestId}\`);
|
|
86
|
+
|
|
87
|
+
const dataComponent = element.getAttribute('data-component');
|
|
88
|
+
if (dataComponent) signatureParts.push(\`data-component:\${dataComponent}\`);
|
|
89
|
+
|
|
90
|
+
const dataCy = element.getAttribute('data-cy');
|
|
91
|
+
if (dataCy) signatureParts.push(\`data-cy:\${dataCy}\`);
|
|
92
|
+
|
|
93
|
+
signatureParts.push(\`tag:\${element.tagName.toLowerCase()}\`);
|
|
94
|
+
|
|
95
|
+
const role = element.getAttribute('role');
|
|
96
|
+
if (role) signatureParts.push(\`role:\${role}\`);
|
|
97
|
+
|
|
98
|
+
const type = element.getAttribute('type');
|
|
99
|
+
if (type) signatureParts.push(\`type:\${type}\`);
|
|
100
|
+
|
|
101
|
+
const name = element.getAttribute('name');
|
|
102
|
+
if (name) signatureParts.push(\`name:\${name}\`);
|
|
103
|
+
|
|
104
|
+
const href = element.getAttribute('href');
|
|
105
|
+
if (href) signatureParts.push(\`href:\${href}\`);
|
|
106
|
+
|
|
107
|
+
const src = element.getAttribute('src');
|
|
108
|
+
if (src) signatureParts.push(\`src:\${src}\`);
|
|
109
|
+
|
|
110
|
+
if (!hasUniqueId) {
|
|
111
|
+
signatureParts.push(\`sibling:\${this.getTypedSiblingIndex(element)}\`);
|
|
112
|
+
const parentTag = element.parentElement?.tagName.toLowerCase();
|
|
113
|
+
if (parentTag) signatureParts.push(\`parent:\${parentTag}\`);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const signature = signatureParts.join('|');
|
|
117
|
+
const hash = this.createStableHash(signature);
|
|
118
|
+
return \`pi-\${hash}\`;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
getXPath(element) {
|
|
122
|
+
if (element.id) {
|
|
123
|
+
return \`//*[@id="\${element.id}"]\`;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
const dataId = element.getAttribute('data-id');
|
|
127
|
+
if (dataId) {
|
|
128
|
+
return \`//*[@data-id="\${dataId}"]\`;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
const parts = [];
|
|
132
|
+
let current = element;
|
|
133
|
+
|
|
134
|
+
while (current && current.nodeType === Node.ELEMENT_NODE) {
|
|
135
|
+
const tagName = current.tagName.toLowerCase();
|
|
136
|
+
const index = this.getTypedSiblingIndex(current);
|
|
137
|
+
parts.unshift(\`\${tagName}[\${index}]\`);
|
|
138
|
+
current = current.parentElement;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return '/' + parts.join('/');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
getTypedSiblingIndex(element) {
|
|
145
|
+
let index = 1;
|
|
146
|
+
let sibling = element.previousElementSibling;
|
|
147
|
+
|
|
148
|
+
while (sibling) {
|
|
149
|
+
if (sibling.tagName === element.tagName) {
|
|
150
|
+
index++;
|
|
151
|
+
}
|
|
152
|
+
sibling = sibling.previousElementSibling;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
return index;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
createStableHash(str) {
|
|
159
|
+
let hash = 5381;
|
|
160
|
+
for (let i = 0; i < str.length; i++) {
|
|
161
|
+
const char = str.charCodeAt(i);
|
|
162
|
+
hash = ((hash << 5) + hash) + char;
|
|
163
|
+
hash = hash | 0;
|
|
164
|
+
}
|
|
165
|
+
return (hash >>> 0).toString(36);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
findElementById(doc, id) {
|
|
169
|
+
return doc.querySelector(\`[\${this.ID_ATTRIBUTE}="\${id}"]\`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
findElementWithFallback(doc, identifier) {
|
|
173
|
+
const byId = this.findElementById(doc, identifier.inspectorId);
|
|
174
|
+
if (byId) return byId;
|
|
175
|
+
|
|
176
|
+
if (identifier.selector && identifier.position) {
|
|
177
|
+
return this.findByPositionMatch(doc, identifier.selector, identifier.position);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
if (identifier.selector) {
|
|
181
|
+
return doc.querySelector(identifier.selector);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
findByPositionMatch(doc, selector, expectedPosition) {
|
|
188
|
+
const elements = Array.from(doc.querySelectorAll(selector));
|
|
189
|
+
|
|
190
|
+
if (elements.length === 0) return null;
|
|
191
|
+
if (elements.length === 1) return elements[0];
|
|
192
|
+
|
|
193
|
+
return elements.reduce((closest, element) => {
|
|
194
|
+
const rect = element.getBoundingClientRect();
|
|
195
|
+
const distance = Math.sqrt(
|
|
196
|
+
Math.pow(rect.top - expectedPosition.top, 2) +
|
|
197
|
+
Math.pow(rect.left - expectedPosition.left, 2)
|
|
198
|
+
);
|
|
199
|
+
|
|
200
|
+
const closestRect = closest.getBoundingClientRect();
|
|
201
|
+
const closestDistance = Math.sqrt(
|
|
202
|
+
Math.pow(closestRect.top - expectedPosition.top, 2) +
|
|
203
|
+
Math.pow(closestRect.left - expectedPosition.left, 2)
|
|
204
|
+
);
|
|
205
|
+
|
|
206
|
+
return distance < closestDistance ? element : closest;
|
|
207
|
+
});
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// ElementHighlighter class - inline implementation
|
|
212
|
+
class ElementHighlighter {
|
|
213
|
+
constructor() {
|
|
214
|
+
this.activeOverlay = null;
|
|
215
|
+
this.scrollListener = null;
|
|
216
|
+
this.resizeListener = null;
|
|
217
|
+
this.cleanupTimeout = null;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
highlight(element, options = {}) {
|
|
221
|
+
this.clearHighlight();
|
|
222
|
+
|
|
223
|
+
const duration = options.duration || 3000;
|
|
224
|
+
const scrollIntoView = options.scrollIntoView !== false;
|
|
225
|
+
const color = options.color || '#4417db';
|
|
226
|
+
const animation = options.animation || 'pulse';
|
|
227
|
+
|
|
228
|
+
if (scrollIntoView) {
|
|
229
|
+
element.scrollIntoView({
|
|
230
|
+
behavior: 'smooth',
|
|
231
|
+
block: 'nearest',
|
|
232
|
+
inline: 'center'
|
|
233
|
+
});
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
const rect = element.getBoundingClientRect();
|
|
237
|
+
const overlay = document.createElement('div');
|
|
238
|
+
overlay.id = 'inspector-highlight-overlay';
|
|
239
|
+
|
|
240
|
+
overlay.style.cssText = \`
|
|
241
|
+
position: fixed;
|
|
242
|
+
top: \${rect.top}px;
|
|
243
|
+
left: \${rect.left}px;
|
|
244
|
+
width: \${rect.width}px;
|
|
245
|
+
height: \${rect.height}px;
|
|
246
|
+
border: 3px solid \${color};
|
|
247
|
+
background: \${color}22;
|
|
248
|
+
border-radius: 8px;
|
|
249
|
+
pointer-events: none;
|
|
250
|
+
z-index: 999999;
|
|
251
|
+
box-shadow: 0 0 20px \${color}66;
|
|
252
|
+
\`;
|
|
253
|
+
|
|
254
|
+
if (animation === 'pulse') {
|
|
255
|
+
overlay.style.animation = 'inspectorPulse 0.6s ease-in-out infinite';
|
|
256
|
+
} else if (animation === 'fade') {
|
|
257
|
+
overlay.style.animation = 'inspectorFade 0.5s ease-in-out';
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
this.injectStyles();
|
|
261
|
+
|
|
262
|
+
document.body.appendChild(overlay);
|
|
263
|
+
this.activeOverlay = overlay;
|
|
264
|
+
|
|
265
|
+
const updatePosition = () => {
|
|
266
|
+
const newRect = element.getBoundingClientRect();
|
|
267
|
+
overlay.style.top = \`\${newRect.top}px\`;
|
|
268
|
+
overlay.style.left = \`\${newRect.left}px\`;
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
this.scrollListener = updatePosition;
|
|
272
|
+
this.resizeListener = updatePosition;
|
|
273
|
+
|
|
274
|
+
window.addEventListener('scroll', this.scrollListener, true);
|
|
275
|
+
window.addEventListener('resize', this.resizeListener);
|
|
276
|
+
|
|
277
|
+
this.cleanupTimeout = setTimeout(() => {
|
|
278
|
+
this.clearHighlight();
|
|
279
|
+
}, duration);
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
clearHighlight() {
|
|
283
|
+
if (this.activeOverlay) {
|
|
284
|
+
this.activeOverlay.remove();
|
|
285
|
+
this.activeOverlay = null;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if (this.scrollListener) {
|
|
289
|
+
window.removeEventListener('scroll', this.scrollListener, true);
|
|
290
|
+
this.scrollListener = null;
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
if (this.resizeListener) {
|
|
294
|
+
window.removeEventListener('resize', this.resizeListener);
|
|
295
|
+
this.resizeListener = null;
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
if (this.cleanupTimeout !== null) {
|
|
299
|
+
clearTimeout(this.cleanupTimeout);
|
|
300
|
+
this.cleanupTimeout = null;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
injectStyles() {
|
|
305
|
+
const styleId = 'inspector-highlight-styles';
|
|
306
|
+
|
|
307
|
+
if (document.getElementById(styleId)) return;
|
|
308
|
+
|
|
309
|
+
const style = document.createElement('style');
|
|
310
|
+
style.id = styleId;
|
|
311
|
+
style.textContent = \`
|
|
312
|
+
@keyframes inspectorPulse {
|
|
313
|
+
0%, 100% { opacity: 0.7; transform: scale(1); }
|
|
314
|
+
50% { opacity: 1; transform: scale(1.005); }
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
@keyframes inspectorFade {
|
|
318
|
+
0% { opacity: 0; transform: scale(0.98); }
|
|
319
|
+
100% { opacity: 0.7; transform: scale(1); }
|
|
320
|
+
}
|
|
321
|
+
\`;
|
|
322
|
+
|
|
323
|
+
document.head.appendChild(style);
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Initialize tracker and highlighter
|
|
328
|
+
const elementTracker = new ElementTracker();
|
|
329
|
+
const elementHighlighter = new ElementHighlighter();
|
|
330
|
+
|
|
331
|
+
// Assign IDs to all elements on page load
|
|
332
|
+
function assignIdsToAllElements() {
|
|
333
|
+
// Wait for DOM to be ready
|
|
334
|
+
if (document.readyState === 'loading') {
|
|
335
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
336
|
+
assignIdsToAllElements();
|
|
337
|
+
});
|
|
338
|
+
return;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
// Get all elements in the document
|
|
342
|
+
const allElements = document.querySelectorAll('body *');
|
|
343
|
+
allElements.forEach(element => {
|
|
344
|
+
// Skip inspector's own elements
|
|
345
|
+
if (element.hasAttribute('data-inspector-ignore') ||
|
|
346
|
+
element.closest('[data-inspector-ignore]')) {
|
|
347
|
+
return;
|
|
348
|
+
}
|
|
349
|
+
elementTracker.ensureElementId(element);
|
|
350
|
+
});
|
|
351
|
+
console.log(\`🆔 Inspector: Assigned IDs to \${allElements.length} elements\`);
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Watch for dynamically added elements
|
|
355
|
+
function observeNewElements() {
|
|
356
|
+
if (!document.body) return;
|
|
357
|
+
|
|
358
|
+
const observer = new MutationObserver((mutations) => {
|
|
359
|
+
mutations.forEach(mutation => {
|
|
360
|
+
mutation.addedNodes.forEach(node => {
|
|
361
|
+
// Only process element nodes
|
|
362
|
+
if (node.nodeType === 1) {
|
|
363
|
+
const element = node;
|
|
364
|
+
|
|
365
|
+
// Skip inspector's own elements
|
|
366
|
+
if (element.hasAttribute('data-inspector-ignore') ||
|
|
367
|
+
element.closest('[data-inspector-ignore]')) {
|
|
368
|
+
return;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// Assign ID to the new element
|
|
372
|
+
elementTracker.ensureElementId(element);
|
|
373
|
+
|
|
374
|
+
// Also assign IDs to all children
|
|
375
|
+
const children = element.querySelectorAll('*');
|
|
376
|
+
children.forEach(child => {
|
|
377
|
+
if (!child.hasAttribute('data-inspector-ignore') &&
|
|
378
|
+
!child.closest('[data-inspector-ignore]')) {
|
|
379
|
+
elementTracker.ensureElementId(child);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
}
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
// Start observing
|
|
388
|
+
observer.observe(document.body, {
|
|
389
|
+
childList: true,
|
|
390
|
+
subtree: true
|
|
391
|
+
});
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
// Initialize on page load
|
|
395
|
+
assignIdsToAllElements();
|
|
396
|
+
observeNewElements();
|
|
397
|
+
|
|
57
398
|
let inspectMode = false;
|
|
58
399
|
let isPaused = false;
|
|
59
400
|
let hoveredElement = null;
|
|
@@ -137,53 +478,13 @@ export function inspectorPlugin() {
|
|
|
137
478
|
controlBox.setAttribute('data-is-image-element', isImageElement);
|
|
138
479
|
controlBox.setAttribute('data-current-image-url', currentImageUrl);
|
|
139
480
|
|
|
140
|
-
//
|
|
141
|
-
const viewportWidth = window.innerWidth;
|
|
142
|
-
const viewportHeight = window.innerHeight;
|
|
143
|
-
const padding = 10; // Minimum padding from viewport edges
|
|
144
|
-
|
|
145
|
-
// Calculate box width (responsive, between 320 and 600px, but never wider than viewport)
|
|
146
|
-
const maxWidth = Math.min(600, viewportWidth - (padding * 2));
|
|
147
|
-
const minWidth = Math.min(320, maxWidth);
|
|
148
|
-
const boxWidth = Math.max(minWidth, Math.min(rect.width, maxWidth));
|
|
149
|
-
|
|
150
|
-
// Calculate horizontal position (centered on element, but within viewport)
|
|
151
|
-
let centerLeft = rect.left + (rect.width / 2) - (boxWidth / 2);
|
|
152
|
-
// Ensure box doesn't overflow left or right
|
|
153
|
-
centerLeft = Math.max(padding, Math.min(centerLeft, viewportWidth - boxWidth - padding));
|
|
154
|
-
|
|
155
|
-
// Calculate vertical position
|
|
156
|
-
const spaceBelow = viewportHeight - rect.bottom;
|
|
157
|
-
const spaceAbove = rect.top;
|
|
158
|
-
|
|
159
|
-
// Estimate box height (only prompt input now, smaller)
|
|
160
|
-
const estimatedBoxHeight = 100;
|
|
161
|
-
|
|
162
|
-
// Calculate available space and max height
|
|
163
|
-
const maxBoxHeight = Math.min(400, viewportHeight - (padding * 4)); // Max 400px or viewport - padding
|
|
164
|
-
|
|
165
|
-
// Determine if box should be above or below element
|
|
166
|
-
let topPosition;
|
|
167
|
-
if (spaceBelow < estimatedBoxHeight + padding && spaceAbove > spaceBelow) {
|
|
168
|
-
// Show above element
|
|
169
|
-
topPosition = Math.max(padding, rect.top - maxBoxHeight - 10);
|
|
170
|
-
controlBox.setAttribute('data-position', 'above');
|
|
171
|
-
} else {
|
|
172
|
-
// Show below element
|
|
173
|
-
topPosition = rect.bottom + 10;
|
|
174
|
-
// Ensure box fits in viewport
|
|
175
|
-
if (topPosition + maxBoxHeight > viewportHeight - padding) {
|
|
176
|
-
topPosition = Math.max(padding, viewportHeight - maxBoxHeight - padding);
|
|
177
|
-
}
|
|
178
|
-
controlBox.setAttribute('data-position', 'below');
|
|
179
|
-
}
|
|
180
|
-
|
|
481
|
+
// Initial CSS (position will be calculated after render)
|
|
181
482
|
controlBox.style.cssText = \`
|
|
182
483
|
position: fixed;
|
|
183
|
-
top:
|
|
184
|
-
left:
|
|
185
|
-
width:
|
|
186
|
-
max-height:
|
|
484
|
+
top: 0px;
|
|
485
|
+
left: 0px;
|
|
486
|
+
width: 320px;
|
|
487
|
+
max-height: 400px;
|
|
187
488
|
background: \${theme.backgroundColor};
|
|
188
489
|
border: 1px solid #e5e7eb;
|
|
189
490
|
border-radius: 14px;
|
|
@@ -196,6 +497,8 @@ export function inspectorPlugin() {
|
|
|
196
497
|
flex-direction: column;
|
|
197
498
|
gap: 0;
|
|
198
499
|
overflow-y: auto;
|
|
500
|
+
opacity: 0;
|
|
501
|
+
pointer-events: none;
|
|
199
502
|
\`;
|
|
200
503
|
|
|
201
504
|
// Only prompt input and buttons (always shown)
|
|
@@ -281,8 +584,13 @@ export function inspectorPlugin() {
|
|
|
281
584
|
unpauseInspection();
|
|
282
585
|
});
|
|
283
586
|
|
|
284
|
-
//
|
|
285
|
-
setTimeout(() =>
|
|
587
|
+
// Calculate and set proper position after DOM render
|
|
588
|
+
setTimeout(() => {
|
|
589
|
+
adjustControlBoxPosition();
|
|
590
|
+
controlBox.style.opacity = '1';
|
|
591
|
+
controlBox.style.pointerEvents = 'auto';
|
|
592
|
+
promptInput.focus();
|
|
593
|
+
}, 10);
|
|
286
594
|
}
|
|
287
595
|
|
|
288
596
|
// Show or hide content input section
|
|
@@ -388,23 +696,23 @@ export function inspectorPlugin() {
|
|
|
388
696
|
// Prevent input events from bubbling
|
|
389
697
|
textInput.addEventListener('click', (e) => e.stopPropagation());
|
|
390
698
|
|
|
391
|
-
//
|
|
392
|
-
setTimeout(() =>
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
699
|
+
// Adjust box height and focus text input after DOM update
|
|
700
|
+
setTimeout(() => {
|
|
701
|
+
adjustControlBoxPosition();
|
|
702
|
+
textInput.focus();
|
|
703
|
+
}, 10);
|
|
396
704
|
} else if (!show && contentSection) {
|
|
397
705
|
// Remove content input section
|
|
398
706
|
contentSection.remove();
|
|
399
707
|
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
708
|
+
// Adjust box height and refocus prompt input after DOM update
|
|
709
|
+
setTimeout(() => {
|
|
710
|
+
adjustControlBoxPosition();
|
|
711
|
+
const promptInput = controlBox.querySelector('#inspector-prompt-input');
|
|
712
|
+
if (promptInput) {
|
|
713
|
+
promptInput.focus();
|
|
714
|
+
}
|
|
715
|
+
}, 10);
|
|
408
716
|
}
|
|
409
717
|
}
|
|
410
718
|
|
|
@@ -615,20 +923,22 @@ export function inspectorPlugin() {
|
|
|
615
923
|
toggleInspectMode(false);
|
|
616
924
|
});
|
|
617
925
|
|
|
618
|
-
// Adjust box height
|
|
619
|
-
|
|
926
|
+
// Adjust box height after DOM update
|
|
927
|
+
setTimeout(() => {
|
|
928
|
+
adjustControlBoxPosition();
|
|
929
|
+
}, 10);
|
|
620
930
|
} else if (!show && imageSection) {
|
|
621
931
|
// Remove image input section
|
|
622
932
|
imageSection.remove();
|
|
623
933
|
|
|
624
|
-
//
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
934
|
+
// Adjust box height and refocus prompt input after DOM update
|
|
935
|
+
setTimeout(() => {
|
|
936
|
+
adjustControlBoxPosition();
|
|
937
|
+
const promptInput = controlBox.querySelector('#inspector-prompt-input');
|
|
938
|
+
if (promptInput) {
|
|
939
|
+
promptInput.focus();
|
|
940
|
+
}
|
|
941
|
+
}, 10);
|
|
632
942
|
}
|
|
633
943
|
}
|
|
634
944
|
|
|
@@ -654,22 +964,30 @@ export function inspectorPlugin() {
|
|
|
654
964
|
const spaceBelow = viewportHeight - rect.bottom;
|
|
655
965
|
const spaceAbove = rect.top;
|
|
656
966
|
|
|
657
|
-
// Get actual box height and
|
|
967
|
+
// Get actual box height and calculate max height
|
|
658
968
|
const boxHeight = controlBox.offsetHeight;
|
|
659
969
|
const maxBoxHeight = Math.min(400, viewportHeight - (padding * 4));
|
|
660
970
|
|
|
971
|
+
// Use the smaller of actual height or max height
|
|
972
|
+
const effectiveHeight = Math.min(boxHeight, maxBoxHeight);
|
|
973
|
+
|
|
661
974
|
// Determine position with viewport bounds
|
|
662
975
|
let topPosition;
|
|
663
|
-
if (spaceBelow <
|
|
664
|
-
// Show above element
|
|
665
|
-
topPosition =
|
|
976
|
+
if (spaceBelow < effectiveHeight + padding && spaceAbove > spaceBelow) {
|
|
977
|
+
// Show above element - use actual box height, not max
|
|
978
|
+
topPosition = rect.top - effectiveHeight - 10;
|
|
979
|
+
// Ensure it doesn't go above viewport
|
|
980
|
+
if (topPosition < padding) {
|
|
981
|
+
topPosition = padding;
|
|
982
|
+
}
|
|
666
983
|
controlBox.setAttribute('data-position', 'above');
|
|
667
984
|
} else {
|
|
668
985
|
// Show below element
|
|
669
986
|
topPosition = rect.bottom + 10;
|
|
670
987
|
// Ensure box fits in viewport
|
|
671
|
-
if (topPosition +
|
|
672
|
-
|
|
988
|
+
if (topPosition + effectiveHeight > viewportHeight - padding) {
|
|
989
|
+
// If it doesn't fit below, try to fit it as much as possible
|
|
990
|
+
topPosition = Math.max(padding, viewportHeight - effectiveHeight - padding);
|
|
673
991
|
}
|
|
674
992
|
controlBox.setAttribute('data-position', 'below');
|
|
675
993
|
}
|
|
@@ -728,6 +1046,24 @@ export function inspectorPlugin() {
|
|
|
728
1046
|
}
|
|
729
1047
|
}
|
|
730
1048
|
|
|
1049
|
+
// Generate CSS selector for an element
|
|
1050
|
+
function generateSelector(element) {
|
|
1051
|
+
if (element.id) {
|
|
1052
|
+
return '#' + element.id;
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
let selector = element.tagName.toLowerCase();
|
|
1056
|
+
|
|
1057
|
+
if (element.className && typeof element.className === 'string') {
|
|
1058
|
+
const classes = element.className.trim().split(/\\s+/).filter(c => c);
|
|
1059
|
+
if (classes.length > 0) {
|
|
1060
|
+
selector += '.' + classes.join('.');
|
|
1061
|
+
}
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
return selector;
|
|
1065
|
+
}
|
|
1066
|
+
|
|
731
1067
|
// Get React Fiber node from DOM element
|
|
732
1068
|
function getReactFiber(element) {
|
|
733
1069
|
const key = Object.keys(element).find(k =>
|
|
@@ -865,6 +1201,11 @@ export function inspectorPlugin() {
|
|
|
865
1201
|
e.stopPropagation();
|
|
866
1202
|
|
|
867
1203
|
const element = e.target;
|
|
1204
|
+
|
|
1205
|
+
// Assign unique inspector ID
|
|
1206
|
+
const inspectorId = elementTracker.ensureElementId(element);
|
|
1207
|
+
const selector = generateSelector(element);
|
|
1208
|
+
|
|
868
1209
|
const fiber = getReactFiber(element);
|
|
869
1210
|
const componentInfo = fiber ? getComponentInfo(fiber) : null;
|
|
870
1211
|
|
|
@@ -890,7 +1231,10 @@ export function inspectorPlugin() {
|
|
|
890
1231
|
isTextNode: isTextNode,
|
|
891
1232
|
textContent: textContent,
|
|
892
1233
|
isImageNode: isImageNode,
|
|
893
|
-
imageUrl: imageUrl
|
|
1234
|
+
imageUrl: imageUrl,
|
|
1235
|
+
inspectorId: inspectorId,
|
|
1236
|
+
selector: selector,
|
|
1237
|
+
currentRoute: window.location.pathname
|
|
894
1238
|
};
|
|
895
1239
|
|
|
896
1240
|
// Send info to parent window
|
|
@@ -969,6 +1313,95 @@ export function inspectorPlugin() {
|
|
|
969
1313
|
labels.badgeText = event.data.badgeText;
|
|
970
1314
|
}
|
|
971
1315
|
setBadgeVisible(event.data.visible);
|
|
1316
|
+
} else if (event.data.type === 'HIGHLIGHT_ELEMENT') {
|
|
1317
|
+
// Handle highlight element request
|
|
1318
|
+
const identifier = event.data.identifier;
|
|
1319
|
+
const options = event.data.options || {};
|
|
1320
|
+
|
|
1321
|
+
// Handle route navigation if needed
|
|
1322
|
+
(async () => {
|
|
1323
|
+
if (options.targetRoute && window.location.pathname !== options.targetRoute) {
|
|
1324
|
+
window.history.pushState(null, '', options.targetRoute);
|
|
1325
|
+
// Wait for route change to process
|
|
1326
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1327
|
+
}
|
|
1328
|
+
|
|
1329
|
+
let element = null;
|
|
1330
|
+
|
|
1331
|
+
if (typeof identifier === 'string') {
|
|
1332
|
+
// Try as inspector ID first
|
|
1333
|
+
element = elementTracker.findElementById(document, identifier);
|
|
1334
|
+
|
|
1335
|
+
// Fallback to selector
|
|
1336
|
+
if (!element) {
|
|
1337
|
+
element = document.querySelector(identifier);
|
|
1338
|
+
}
|
|
1339
|
+
} else {
|
|
1340
|
+
// Full identifier object with fallback
|
|
1341
|
+
element = elementTracker.findElementWithFallback(document, identifier);
|
|
1342
|
+
}
|
|
1343
|
+
|
|
1344
|
+
if (element) {
|
|
1345
|
+
elementHighlighter.highlight(element, options);
|
|
1346
|
+
} else {
|
|
1347
|
+
console.warn('[Inspector] Element not found for highlighting:', identifier);
|
|
1348
|
+
}
|
|
1349
|
+
})();
|
|
1350
|
+
} else if (event.data.type === 'GET_ELEMENT_BY_ID') {
|
|
1351
|
+
// Handle get element by ID request
|
|
1352
|
+
const inspectorId = event.data.inspectorId;
|
|
1353
|
+
const element = elementTracker.findElementById(document, inspectorId);
|
|
1354
|
+
|
|
1355
|
+
if (element) {
|
|
1356
|
+
const fiber = getReactFiber(element);
|
|
1357
|
+
const componentInfo = fiber ? getComponentInfo(fiber) : null;
|
|
1358
|
+
const isTextNode = element.textContent && element.children.length === 0;
|
|
1359
|
+
const textContent = isTextNode ? element.textContent.trim() : '';
|
|
1360
|
+
const isImageNode = element.tagName === 'IMG';
|
|
1361
|
+
const imageUrl = isImageNode ? element.src : '';
|
|
1362
|
+
const selector = generateSelector(element);
|
|
1363
|
+
|
|
1364
|
+
const elementData = {
|
|
1365
|
+
tagName: element.tagName,
|
|
1366
|
+
className: element.className,
|
|
1367
|
+
id: element.id,
|
|
1368
|
+
component: componentInfo,
|
|
1369
|
+
position: {
|
|
1370
|
+
top: element.getBoundingClientRect().top,
|
|
1371
|
+
left: element.getBoundingClientRect().left,
|
|
1372
|
+
width: element.getBoundingClientRect().width,
|
|
1373
|
+
height: element.getBoundingClientRect().height
|
|
1374
|
+
},
|
|
1375
|
+
isTextNode: isTextNode,
|
|
1376
|
+
textContent: textContent,
|
|
1377
|
+
isImageNode: isImageNode,
|
|
1378
|
+
imageUrl: imageUrl,
|
|
1379
|
+
inspectorId: inspectorId,
|
|
1380
|
+
selector: selector
|
|
1381
|
+
};
|
|
1382
|
+
|
|
1383
|
+
if (window.parent !== window) {
|
|
1384
|
+
const message = JSON.stringify({
|
|
1385
|
+
type: 'ELEMENT_INFO_RESPONSE',
|
|
1386
|
+
data: {
|
|
1387
|
+
found: true,
|
|
1388
|
+
element: elementData
|
|
1389
|
+
}
|
|
1390
|
+
});
|
|
1391
|
+
window.parent.postMessage(message, '*');
|
|
1392
|
+
}
|
|
1393
|
+
} else {
|
|
1394
|
+
if (window.parent !== window) {
|
|
1395
|
+
const message = JSON.stringify({
|
|
1396
|
+
type: 'ELEMENT_INFO_RESPONSE',
|
|
1397
|
+
data: {
|
|
1398
|
+
found: false,
|
|
1399
|
+
error: 'Element not found'
|
|
1400
|
+
}
|
|
1401
|
+
});
|
|
1402
|
+
window.parent.postMessage(message, '*');
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
972
1405
|
}
|
|
973
1406
|
});
|
|
974
1407
|
|
|
@@ -1245,52 +1678,76 @@ export function inspectorPlugin() {
|
|
|
1245
1678
|
console.log('🏷️ Badge auto-enabled for preview.promake.ai');
|
|
1246
1679
|
}
|
|
1247
1680
|
|
|
1248
|
-
// 🖼️ Auto cache-bust all images on page load
|
|
1681
|
+
// 🖼️ Auto cache-bust all images on page load and for dynamically added images
|
|
1249
1682
|
// This ensures images are always fresh when page is refreshed/reloaded
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1683
|
+
const cacheBustTimestamp = Date.now();
|
|
1684
|
+
|
|
1685
|
+
function cacheBustImage(img) {
|
|
1686
|
+
if (img.src && !img.src.startsWith('data:') && !img.hasAttribute('data-cache-busted')) {
|
|
1687
|
+
try {
|
|
1688
|
+
const url = new URL(img.src);
|
|
1689
|
+
// Remove old cache busting params
|
|
1690
|
+
url.searchParams.delete('_t');
|
|
1691
|
+
url.searchParams.delete('_cache_bust');
|
|
1692
|
+
url.searchParams.delete('v');
|
|
1693
|
+
// Add new timestamp
|
|
1694
|
+
url.searchParams.set('_t', cacheBustTimestamp.toString());
|
|
1695
|
+
|
|
1696
|
+
const newSrc = url.toString();
|
|
1697
|
+
|
|
1698
|
+
// Force reload by clearing and resetting src
|
|
1699
|
+
img.removeAttribute('src');
|
|
1700
|
+
// Use setTimeout to ensure the browser registers the change
|
|
1701
|
+
setTimeout(() => {
|
|
1702
|
+
img.src = newSrc;
|
|
1703
|
+
}, 0);
|
|
1704
|
+
|
|
1705
|
+
img.setAttribute('data-cache-busted', 'true');
|
|
1706
|
+
return true;
|
|
1707
|
+
} catch (e) {
|
|
1708
|
+
// Ignore invalid URLs
|
|
1709
|
+
}
|
|
1710
|
+
}
|
|
1711
|
+
return false;
|
|
1712
|
+
}
|
|
1713
|
+
|
|
1714
|
+
function cacheBustBackgroundImage(el) {
|
|
1715
|
+
if (el.hasAttribute('data-cache-busted')) return false;
|
|
1253
1716
|
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
if (
|
|
1717
|
+
const bgImage = window.getComputedStyle(el).backgroundImage;
|
|
1718
|
+
if (bgImage && bgImage !== 'none' && bgImage.includes('url(')) {
|
|
1719
|
+
const urlMatch = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
|
|
1720
|
+
if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:')) {
|
|
1258
1721
|
try {
|
|
1259
|
-
const url = new URL(
|
|
1260
|
-
// Remove old cache busting params
|
|
1722
|
+
const url = new URL(urlMatch[1], window.location.origin);
|
|
1261
1723
|
url.searchParams.delete('_t');
|
|
1262
1724
|
url.searchParams.delete('_cache_bust');
|
|
1263
1725
|
url.searchParams.delete('v');
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1726
|
+
url.searchParams.set('_t', cacheBustTimestamp.toString());
|
|
1727
|
+
el.style.backgroundImage = 'url("' + url.toString() + '")';
|
|
1728
|
+
el.setAttribute('data-cache-busted', 'true');
|
|
1729
|
+
return true;
|
|
1268
1730
|
} catch (e) {
|
|
1269
1731
|
// Ignore invalid URLs
|
|
1270
1732
|
}
|
|
1271
1733
|
}
|
|
1734
|
+
}
|
|
1735
|
+
return false;
|
|
1736
|
+
}
|
|
1737
|
+
|
|
1738
|
+
function cacheBustAllImages() {
|
|
1739
|
+
let imageCount = 0;
|
|
1740
|
+
|
|
1741
|
+
// Cache-bust all <img> tags
|
|
1742
|
+
const images = document.querySelectorAll('img');
|
|
1743
|
+
images.forEach((img) => {
|
|
1744
|
+
if (cacheBustImage(img)) imageCount++;
|
|
1272
1745
|
});
|
|
1273
1746
|
|
|
1274
1747
|
// Cache-bust background images
|
|
1275
1748
|
const allElements = document.querySelectorAll('*');
|
|
1276
1749
|
allElements.forEach((el) => {
|
|
1277
|
-
|
|
1278
|
-
if (bgImage && bgImage !== 'none' && bgImage.includes('url(')) {
|
|
1279
|
-
const urlMatch = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
|
|
1280
|
-
if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:')) {
|
|
1281
|
-
try {
|
|
1282
|
-
const url = new URL(urlMatch[1], window.location.origin);
|
|
1283
|
-
url.searchParams.delete('_t');
|
|
1284
|
-
url.searchParams.delete('_cache_bust');
|
|
1285
|
-
url.searchParams.delete('v');
|
|
1286
|
-
url.searchParams.set('_t', timestamp.toString());
|
|
1287
|
-
el.style.backgroundImage = 'url("' + url.toString() + '")';
|
|
1288
|
-
imageCount++;
|
|
1289
|
-
} catch (e) {
|
|
1290
|
-
// Ignore invalid URLs
|
|
1291
|
-
}
|
|
1292
|
-
}
|
|
1293
|
-
}
|
|
1750
|
+
if (cacheBustBackgroundImage(el)) imageCount++;
|
|
1294
1751
|
});
|
|
1295
1752
|
|
|
1296
1753
|
if (imageCount > 0) {
|
|
@@ -1298,12 +1755,50 @@ export function inspectorPlugin() {
|
|
|
1298
1755
|
}
|
|
1299
1756
|
}
|
|
1300
1757
|
|
|
1758
|
+
// Watch for dynamically added images
|
|
1759
|
+
function observeNewImages() {
|
|
1760
|
+
if (!document.body) return;
|
|
1761
|
+
|
|
1762
|
+
const imageObserver = new MutationObserver((mutations) => {
|
|
1763
|
+
mutations.forEach(mutation => {
|
|
1764
|
+
mutation.addedNodes.forEach(node => {
|
|
1765
|
+
if (node.nodeType === 1) {
|
|
1766
|
+
const element = node;
|
|
1767
|
+
|
|
1768
|
+
// If it's an img element, cache-bust it
|
|
1769
|
+
if (element.tagName === 'IMG') {
|
|
1770
|
+
cacheBustImage(element);
|
|
1771
|
+
}
|
|
1772
|
+
|
|
1773
|
+
// Check for img children
|
|
1774
|
+
const imgChildren = element.querySelectorAll('img');
|
|
1775
|
+
imgChildren.forEach(img => cacheBustImage(img));
|
|
1776
|
+
|
|
1777
|
+
// Check for background images
|
|
1778
|
+
if (cacheBustBackgroundImage(element)) {
|
|
1779
|
+
// Successfully cache-busted
|
|
1780
|
+
}
|
|
1781
|
+
}
|
|
1782
|
+
});
|
|
1783
|
+
});
|
|
1784
|
+
});
|
|
1785
|
+
|
|
1786
|
+
imageObserver.observe(document.body, {
|
|
1787
|
+
childList: true,
|
|
1788
|
+
subtree: true
|
|
1789
|
+
});
|
|
1790
|
+
}
|
|
1791
|
+
|
|
1301
1792
|
// Run cache-bust after DOM is fully loaded
|
|
1302
1793
|
if (document.readyState === 'loading') {
|
|
1303
|
-
document.addEventListener('DOMContentLoaded',
|
|
1794
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
1795
|
+
cacheBustAllImages();
|
|
1796
|
+
observeNewImages();
|
|
1797
|
+
});
|
|
1304
1798
|
} else {
|
|
1305
1799
|
// DOM already loaded, run immediately
|
|
1306
1800
|
cacheBustAllImages();
|
|
1801
|
+
observeNewImages();
|
|
1307
1802
|
}
|
|
1308
1803
|
|
|
1309
1804
|
console.log('🔍 Inspector plugin loaded');
|
package/dist/types.d.ts
CHANGED
|
@@ -26,6 +26,9 @@ export interface SelectedElementData {
|
|
|
26
26
|
textContent?: string;
|
|
27
27
|
isImageNode?: boolean;
|
|
28
28
|
imageUrl?: string;
|
|
29
|
+
inspectorId?: string;
|
|
30
|
+
selector?: string;
|
|
31
|
+
currentRoute?: string;
|
|
29
32
|
}
|
|
30
33
|
export interface UrlChangeData {
|
|
31
34
|
url: string;
|
|
@@ -61,6 +64,31 @@ export interface ErrorData {
|
|
|
61
64
|
columnNumber?: number;
|
|
62
65
|
timestamp: number;
|
|
63
66
|
}
|
|
67
|
+
export interface ElementIdentifier {
|
|
68
|
+
inspectorId: string;
|
|
69
|
+
selector: string;
|
|
70
|
+
position?: {
|
|
71
|
+
top: number;
|
|
72
|
+
left: number;
|
|
73
|
+
};
|
|
74
|
+
component?: {
|
|
75
|
+
name: string;
|
|
76
|
+
fileName: string;
|
|
77
|
+
lineNumber?: number;
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
export interface HighlightOptions {
|
|
81
|
+
duration?: number;
|
|
82
|
+
scrollIntoView?: boolean;
|
|
83
|
+
color?: string;
|
|
84
|
+
animation?: 'pulse' | 'fade' | 'none';
|
|
85
|
+
targetRoute?: string;
|
|
86
|
+
}
|
|
87
|
+
export interface ElementInfoData {
|
|
88
|
+
found: boolean;
|
|
89
|
+
element?: SelectedElementData;
|
|
90
|
+
error?: string;
|
|
91
|
+
}
|
|
64
92
|
export interface InspectorLabels {
|
|
65
93
|
editText?: string;
|
|
66
94
|
textPlaceholder?: string;
|
|
@@ -95,6 +123,7 @@ export interface InspectorCallbacks {
|
|
|
95
123
|
onImageUpdated?: (data: ImageUpdatedData) => void;
|
|
96
124
|
onInspectorClosed?: () => void;
|
|
97
125
|
onError?: (data: ErrorData) => void;
|
|
126
|
+
onElementInfoReceived?: (data: ElementInfoData) => void;
|
|
98
127
|
}
|
|
99
128
|
export interface UseInspectorReturn {
|
|
100
129
|
isInspecting: boolean;
|
|
@@ -104,5 +133,7 @@ export interface UseInspectorReturn {
|
|
|
104
133
|
showContentInput: (show: boolean, updateImmediately?: boolean) => void;
|
|
105
134
|
showImageInput: (show: boolean, updateImmediately?: boolean) => void;
|
|
106
135
|
setBadgeVisible: (visible: boolean) => void;
|
|
136
|
+
highlightElement: (identifier: string | ElementIdentifier, options?: HighlightOptions) => void;
|
|
137
|
+
getElementByInspectorId: (inspectorId: string) => void;
|
|
107
138
|
}
|
|
108
139
|
//# sourceMappingURL=types.d.ts.map
|
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAGH,MAAM,WAAW,aAAa;IAC5B,aAAa,EAAE,MAAM,CAAC;IACtB,QAAQ,EAAE,MAAM,CAAC;IACjB,UAAU,EAAE,MAAM,CAAC;IACnB,YAAY,EAAE,MAAM,CAAC;CACtB;AAGD,MAAM,WAAW,eAAe;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;CAChB;AAGD,MAAM,WAAW,mBAAmB;IAClC,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,aAAa,GAAG,IAAI,CAAC;IAChC,QAAQ,EAAE,eAAe,CAAC;IAC1B,UAAU,CAAC,EAAE,OAAO,CAAC;IACrB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,WAAW,CAAC,EAAE,OAAO,CAAC;IACtB,QAAQ,CAAC,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@promakeai/inspector",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "Visual element inspector for React apps in iframe with AI prompt support",
|
|
5
5
|
"author": "Promake",
|
|
6
6
|
"type": "module",
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
"scripts": {
|
|
34
34
|
"build": "tsc && tsc -p tsconfig.plugin.json",
|
|
35
35
|
"dev": "concurrently -n \"HOOK,PLUGIN\" -c \"blue,magenta\" \"tsc --watch\" \"tsc -p tsconfig.plugin.json --watch\"",
|
|
36
|
+
"test": "vitest run",
|
|
37
|
+
"test:watch": "vitest",
|
|
38
|
+
"test:ui": "vitest --ui",
|
|
39
|
+
"test:coverage": "vitest run --coverage",
|
|
40
|
+
"test:e2e": "cd test && npm test",
|
|
41
|
+
"test:e2e:ui": "cd test && npm run test:ui",
|
|
42
|
+
"test:e2e:headed": "cd test && npm run test:headed",
|
|
43
|
+
"test:all": "npm test && npm run test:e2e",
|
|
36
44
|
"prepublishOnly": "npm run build",
|
|
37
45
|
"release": "npm run build && npm publish --access public"
|
|
38
46
|
},
|
|
@@ -48,13 +56,21 @@
|
|
|
48
56
|
"prompt"
|
|
49
57
|
],
|
|
50
58
|
"peerDependencies": {
|
|
51
|
-
"
|
|
52
|
-
"vite": ">5.0.0"
|
|
59
|
+
"vite": ">=5.0.0"
|
|
53
60
|
},
|
|
54
61
|
"devDependencies": {
|
|
62
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
63
|
+
"@testing-library/react": "^16.3.0",
|
|
55
64
|
"@types/react": "^18.2.0",
|
|
65
|
+
"@vitest/coverage-v8": "^4.0.8",
|
|
66
|
+
"@vitest/ui": "^4.0.8",
|
|
56
67
|
"concurrently": "^9.1.0",
|
|
68
|
+
"happy-dom": "^20.0.10",
|
|
69
|
+
"jsdom": "^27.2.0",
|
|
70
|
+
"react": "^18.3.1",
|
|
71
|
+
"react-dom": "^18.3.1",
|
|
57
72
|
"typescript": "^5.3.0",
|
|
58
|
-
"vite": "^5.4.0"
|
|
73
|
+
"vite": "^5.4.0",
|
|
74
|
+
"vitest": "^4.0.8"
|
|
59
75
|
}
|
|
60
76
|
}
|