@promakeai/inspector 0.1.2 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,7 @@
1
1
  # @promakeai/inspector
2
2
 
3
3
  [![Build Status](https://github.com/promakeai/inspector/actions/workflows/build.yml/badge.svg)](https://github.com/promakeai/inspector/actions/workflows/build.yml)
4
+ [![Tests](https://github.com/promakeai/inspector/actions/workflows/test.yml/badge.svg)](https://github.com/promakeai/inspector/actions/workflows/test.yml)
4
5
  [![npm version](https://badge.fury.io/js/@promakeai%2Finspector.svg)](https://www.npmjs.com/package/@promakeai/inspector)
5
6
  [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](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
@@ -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,EAOnB,MAAM,SAAS,CAAC;AAsDjB,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,CA4LpB;AAGD,mBAAmB,SAAS,CAAC"}
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
  }
@@ -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,CA4vCxC"}
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,CAwvDxC"}
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: 'center',
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;
@@ -728,6 +1069,24 @@ export function inspectorPlugin() {
728
1069
  }
729
1070
  }
730
1071
 
1072
+ // Generate CSS selector for an element
1073
+ function generateSelector(element) {
1074
+ if (element.id) {
1075
+ return '#' + element.id;
1076
+ }
1077
+
1078
+ let selector = element.tagName.toLowerCase();
1079
+
1080
+ if (element.className && typeof element.className === 'string') {
1081
+ const classes = element.className.trim().split(/\\s+/).filter(c => c);
1082
+ if (classes.length > 0) {
1083
+ selector += '.' + classes.join('.');
1084
+ }
1085
+ }
1086
+
1087
+ return selector;
1088
+ }
1089
+
731
1090
  // Get React Fiber node from DOM element
732
1091
  function getReactFiber(element) {
733
1092
  const key = Object.keys(element).find(k =>
@@ -865,6 +1224,11 @@ export function inspectorPlugin() {
865
1224
  e.stopPropagation();
866
1225
 
867
1226
  const element = e.target;
1227
+
1228
+ // Assign unique inspector ID
1229
+ const inspectorId = elementTracker.ensureElementId(element);
1230
+ const selector = generateSelector(element);
1231
+
868
1232
  const fiber = getReactFiber(element);
869
1233
  const componentInfo = fiber ? getComponentInfo(fiber) : null;
870
1234
 
@@ -890,7 +1254,9 @@ export function inspectorPlugin() {
890
1254
  isTextNode: isTextNode,
891
1255
  textContent: textContent,
892
1256
  isImageNode: isImageNode,
893
- imageUrl: imageUrl
1257
+ imageUrl: imageUrl,
1258
+ inspectorId: inspectorId,
1259
+ selector: selector
894
1260
  };
895
1261
 
896
1262
  // Send info to parent window
@@ -969,6 +1335,86 @@ export function inspectorPlugin() {
969
1335
  labels.badgeText = event.data.badgeText;
970
1336
  }
971
1337
  setBadgeVisible(event.data.visible);
1338
+ } else if (event.data.type === 'HIGHLIGHT_ELEMENT') {
1339
+ // Handle highlight element request
1340
+ const identifier = event.data.identifier;
1341
+ const options = event.data.options || {};
1342
+
1343
+ let element = null;
1344
+
1345
+ if (typeof identifier === 'string') {
1346
+ // Try as inspector ID first
1347
+ element = elementTracker.findElementById(document, identifier);
1348
+
1349
+ // Fallback to selector
1350
+ if (!element) {
1351
+ element = document.querySelector(identifier);
1352
+ }
1353
+ } else {
1354
+ // Full identifier object with fallback
1355
+ element = elementTracker.findElementWithFallback(document, identifier);
1356
+ }
1357
+
1358
+ if (element) {
1359
+ elementHighlighter.highlight(element, options);
1360
+ } else {
1361
+ console.warn('[Inspector] Element not found for highlighting:', identifier);
1362
+ }
1363
+ } else if (event.data.type === 'GET_ELEMENT_BY_ID') {
1364
+ // Handle get element by ID request
1365
+ const inspectorId = event.data.inspectorId;
1366
+ const element = elementTracker.findElementById(document, inspectorId);
1367
+
1368
+ if (element) {
1369
+ const fiber = getReactFiber(element);
1370
+ const componentInfo = fiber ? getComponentInfo(fiber) : null;
1371
+ const isTextNode = element.textContent && element.children.length === 0;
1372
+ const textContent = isTextNode ? element.textContent.trim() : '';
1373
+ const isImageNode = element.tagName === 'IMG';
1374
+ const imageUrl = isImageNode ? element.src : '';
1375
+ const selector = generateSelector(element);
1376
+
1377
+ const elementData = {
1378
+ tagName: element.tagName,
1379
+ className: element.className,
1380
+ id: element.id,
1381
+ component: componentInfo,
1382
+ position: {
1383
+ top: element.getBoundingClientRect().top,
1384
+ left: element.getBoundingClientRect().left,
1385
+ width: element.getBoundingClientRect().width,
1386
+ height: element.getBoundingClientRect().height
1387
+ },
1388
+ isTextNode: isTextNode,
1389
+ textContent: textContent,
1390
+ isImageNode: isImageNode,
1391
+ imageUrl: imageUrl,
1392
+ inspectorId: inspectorId,
1393
+ selector: selector
1394
+ };
1395
+
1396
+ if (window.parent !== window) {
1397
+ const message = JSON.stringify({
1398
+ type: 'ELEMENT_INFO_RESPONSE',
1399
+ data: {
1400
+ found: true,
1401
+ element: elementData
1402
+ }
1403
+ });
1404
+ window.parent.postMessage(message, '*');
1405
+ }
1406
+ } else {
1407
+ if (window.parent !== window) {
1408
+ const message = JSON.stringify({
1409
+ type: 'ELEMENT_INFO_RESPONSE',
1410
+ data: {
1411
+ found: false,
1412
+ error: 'Element not found'
1413
+ }
1414
+ });
1415
+ window.parent.postMessage(message, '*');
1416
+ }
1417
+ }
972
1418
  }
973
1419
  });
974
1420
 
@@ -1245,52 +1691,76 @@ export function inspectorPlugin() {
1245
1691
  console.log('🏷️ Badge auto-enabled for preview.promake.ai');
1246
1692
  }
1247
1693
 
1248
- // 🖼️ Auto cache-bust all images on page load
1694
+ // 🖼️ Auto cache-bust all images on page load and for dynamically added images
1249
1695
  // This ensures images are always fresh when page is refreshed/reloaded
1250
- function cacheBustAllImages() {
1251
- const timestamp = Date.now();
1252
- let imageCount = 0;
1696
+ const cacheBustTimestamp = Date.now();
1697
+
1698
+ function cacheBustImage(img) {
1699
+ if (img.src && !img.src.startsWith('data:') && !img.hasAttribute('data-cache-busted')) {
1700
+ try {
1701
+ const url = new URL(img.src);
1702
+ // Remove old cache busting params
1703
+ url.searchParams.delete('_t');
1704
+ url.searchParams.delete('_cache_bust');
1705
+ url.searchParams.delete('v');
1706
+ // Add new timestamp
1707
+ url.searchParams.set('_t', cacheBustTimestamp.toString());
1708
+
1709
+ const newSrc = url.toString();
1710
+
1711
+ // Force reload by clearing and resetting src
1712
+ img.removeAttribute('src');
1713
+ // Use setTimeout to ensure the browser registers the change
1714
+ setTimeout(() => {
1715
+ img.src = newSrc;
1716
+ }, 0);
1717
+
1718
+ img.setAttribute('data-cache-busted', 'true');
1719
+ return true;
1720
+ } catch (e) {
1721
+ // Ignore invalid URLs
1722
+ }
1723
+ }
1724
+ return false;
1725
+ }
1726
+
1727
+ function cacheBustBackgroundImage(el) {
1728
+ if (el.hasAttribute('data-cache-busted')) return false;
1253
1729
 
1254
- // Cache-bust all <img> tags
1255
- const images = document.querySelectorAll('img');
1256
- images.forEach((img) => {
1257
- if (img.src && !img.src.startsWith('data:')) {
1730
+ const bgImage = window.getComputedStyle(el).backgroundImage;
1731
+ if (bgImage && bgImage !== 'none' && bgImage.includes('url(')) {
1732
+ const urlMatch = bgImage.match(/url\(['"]?([^'"]+)['"]?\)/);
1733
+ if (urlMatch && urlMatch[1] && !urlMatch[1].startsWith('data:')) {
1258
1734
  try {
1259
- const url = new URL(img.src);
1260
- // Remove old cache busting params
1735
+ const url = new URL(urlMatch[1], window.location.origin);
1261
1736
  url.searchParams.delete('_t');
1262
1737
  url.searchParams.delete('_cache_bust');
1263
1738
  url.searchParams.delete('v');
1264
- // Add new timestamp
1265
- url.searchParams.set('_t', timestamp.toString());
1266
- img.src = url.toString();
1267
- imageCount++;
1739
+ url.searchParams.set('_t', cacheBustTimestamp.toString());
1740
+ el.style.backgroundImage = 'url("' + url.toString() + '")';
1741
+ el.setAttribute('data-cache-busted', 'true');
1742
+ return true;
1268
1743
  } catch (e) {
1269
1744
  // Ignore invalid URLs
1270
1745
  }
1271
1746
  }
1747
+ }
1748
+ return false;
1749
+ }
1750
+
1751
+ function cacheBustAllImages() {
1752
+ let imageCount = 0;
1753
+
1754
+ // Cache-bust all <img> tags
1755
+ const images = document.querySelectorAll('img');
1756
+ images.forEach((img) => {
1757
+ if (cacheBustImage(img)) imageCount++;
1272
1758
  });
1273
1759
 
1274
1760
  // Cache-bust background images
1275
1761
  const allElements = document.querySelectorAll('*');
1276
1762
  allElements.forEach((el) => {
1277
- const bgImage = window.getComputedStyle(el).backgroundImage;
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
- }
1763
+ if (cacheBustBackgroundImage(el)) imageCount++;
1294
1764
  });
1295
1765
 
1296
1766
  if (imageCount > 0) {
@@ -1298,12 +1768,50 @@ export function inspectorPlugin() {
1298
1768
  }
1299
1769
  }
1300
1770
 
1771
+ // Watch for dynamically added images
1772
+ function observeNewImages() {
1773
+ if (!document.body) return;
1774
+
1775
+ const imageObserver = new MutationObserver((mutations) => {
1776
+ mutations.forEach(mutation => {
1777
+ mutation.addedNodes.forEach(node => {
1778
+ if (node.nodeType === 1) {
1779
+ const element = node;
1780
+
1781
+ // If it's an img element, cache-bust it
1782
+ if (element.tagName === 'IMG') {
1783
+ cacheBustImage(element);
1784
+ }
1785
+
1786
+ // Check for img children
1787
+ const imgChildren = element.querySelectorAll('img');
1788
+ imgChildren.forEach(img => cacheBustImage(img));
1789
+
1790
+ // Check for background images
1791
+ if (cacheBustBackgroundImage(element)) {
1792
+ // Successfully cache-busted
1793
+ }
1794
+ }
1795
+ });
1796
+ });
1797
+ });
1798
+
1799
+ imageObserver.observe(document.body, {
1800
+ childList: true,
1801
+ subtree: true
1802
+ });
1803
+ }
1804
+
1301
1805
  // Run cache-bust after DOM is fully loaded
1302
1806
  if (document.readyState === 'loading') {
1303
- document.addEventListener('DOMContentLoaded', cacheBustAllImages);
1807
+ document.addEventListener('DOMContentLoaded', () => {
1808
+ cacheBustAllImages();
1809
+ observeNewImages();
1810
+ });
1304
1811
  } else {
1305
1812
  // DOM already loaded, run immediately
1306
1813
  cacheBustAllImages();
1814
+ observeNewImages();
1307
1815
  }
1308
1816
 
1309
1817
  console.log('🔍 Inspector plugin loaded');
package/dist/types.d.ts CHANGED
@@ -26,6 +26,8 @@ export interface SelectedElementData {
26
26
  textContent?: string;
27
27
  isImageNode?: boolean;
28
28
  imageUrl?: string;
29
+ inspectorId?: string;
30
+ selector?: string;
29
31
  }
30
32
  export interface UrlChangeData {
31
33
  url: string;
@@ -61,6 +63,30 @@ export interface ErrorData {
61
63
  columnNumber?: number;
62
64
  timestamp: number;
63
65
  }
66
+ export interface ElementIdentifier {
67
+ inspectorId: string;
68
+ selector: string;
69
+ position?: {
70
+ top: number;
71
+ left: number;
72
+ };
73
+ component?: {
74
+ name: string;
75
+ fileName: string;
76
+ lineNumber?: number;
77
+ };
78
+ }
79
+ export interface HighlightOptions {
80
+ duration?: number;
81
+ scrollIntoView?: boolean;
82
+ color?: string;
83
+ animation?: 'pulse' | 'fade' | 'none';
84
+ }
85
+ export interface ElementInfoData {
86
+ found: boolean;
87
+ element?: SelectedElementData;
88
+ error?: string;
89
+ }
64
90
  export interface InspectorLabels {
65
91
  editText?: string;
66
92
  textPlaceholder?: string;
@@ -95,6 +121,7 @@ export interface InspectorCallbacks {
95
121
  onImageUpdated?: (data: ImageUpdatedData) => void;
96
122
  onInspectorClosed?: () => void;
97
123
  onError?: (data: ErrorData) => void;
124
+ onElementInfoReceived?: (data: ElementInfoData) => void;
98
125
  }
99
126
  export interface UseInspectorReturn {
100
127
  isInspecting: boolean;
@@ -104,5 +131,7 @@ export interface UseInspectorReturn {
104
131
  showContentInput: (show: boolean, updateImmediately?: boolean) => void;
105
132
  showImageInput: (show: boolean, updateImmediately?: boolean) => void;
106
133
  setBadgeVisible: (visible: boolean) => void;
134
+ highlightElement: (identifier: string | ElementIdentifier, options?: HighlightOptions) => void;
135
+ getElementByInspectorId: (inspectorId: string) => void;
107
136
  }
108
137
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
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.2",
3
+ "version": "0.2.0",
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
- "react": ">18.0.0",
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
  }