@promakeai/inspector-hook 1.0.1 → 1.0.2
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 +1499 -0
- package/dist/utils/jsxUpdater.d.ts.map +1 -1
- package/dist/utils/jsxUpdater.js +7 -2
- package/package.json +1 -1
package/README.md
ADDED
|
@@ -0,0 +1,1499 @@
|
|
|
1
|
+
# @promakeai/inspector-hook
|
|
2
|
+
|
|
3
|
+
React hook and utilities for controlling the Promake Inspector from parent applications via iframe communication.
|
|
4
|
+
|
|
5
|
+
## 📦 Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install @promakeai/inspector-hook
|
|
9
|
+
# or
|
|
10
|
+
yarn add @promakeai/inspector-hook
|
|
11
|
+
# or
|
|
12
|
+
bun add @promakeai/inspector-hook
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## 🎯 Overview
|
|
16
|
+
|
|
17
|
+
This package provides three main exports:
|
|
18
|
+
|
|
19
|
+
1. **`useInspector`** - React hook for controlling an iframe-embedded Inspector
|
|
20
|
+
2. **`updateJSXSource`** - AST-based utility for updating JSX/TSX source code
|
|
21
|
+
3. **`inspectorHookPlugin`** - Vite plugin for proper Babel configuration
|
|
22
|
+
|
|
23
|
+
## 🚀 Quick Start
|
|
24
|
+
|
|
25
|
+
### Basic Usage
|
|
26
|
+
|
|
27
|
+
```typescript
|
|
28
|
+
import { useInspector } from "@promakeai/inspector-hook";
|
|
29
|
+
import { useRef } from "react";
|
|
30
|
+
|
|
31
|
+
function ParentApp() {
|
|
32
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
33
|
+
|
|
34
|
+
const inspector = useInspector(iframeRef, {
|
|
35
|
+
onElementSelected: (data) => {
|
|
36
|
+
console.log("Element selected:", data);
|
|
37
|
+
},
|
|
38
|
+
onStyleUpdated: (data) => {
|
|
39
|
+
console.log("Style updated:", data);
|
|
40
|
+
},
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
return (
|
|
44
|
+
<div>
|
|
45
|
+
<button onClick={() => inspector.toggleInspector()}>
|
|
46
|
+
{inspector.isInspecting ? "Stop" : "Start"} Inspector
|
|
47
|
+
</button>
|
|
48
|
+
|
|
49
|
+
<iframe
|
|
50
|
+
ref={iframeRef}
|
|
51
|
+
src="http://localhost:5173"
|
|
52
|
+
style={{ width: "100%", height: "800px" }}
|
|
53
|
+
/>
|
|
54
|
+
</div>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### With Vite Plugin
|
|
60
|
+
|
|
61
|
+
```typescript
|
|
62
|
+
// vite.config.ts
|
|
63
|
+
import { defineConfig } from "vite";
|
|
64
|
+
import react from "@vitejs/plugin-react";
|
|
65
|
+
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
|
|
66
|
+
|
|
67
|
+
export default defineConfig({
|
|
68
|
+
plugins: [
|
|
69
|
+
inspectorHookPlugin(), // Required for proper Babel configuration
|
|
70
|
+
react(),
|
|
71
|
+
],
|
|
72
|
+
});
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
## 📚 API Reference
|
|
76
|
+
|
|
77
|
+
### `useInspector(iframeRef, callbacks?, labels?, theme?)`
|
|
78
|
+
|
|
79
|
+
Main React hook for controlling the Inspector.
|
|
80
|
+
|
|
81
|
+
#### Parameters
|
|
82
|
+
|
|
83
|
+
| Parameter | Type | Description |
|
|
84
|
+
| ----------- | ------------------------------ | ------------------------------- |
|
|
85
|
+
| `iframeRef` | `RefObject<HTMLIFrameElement>` | Reference to the iframe element |
|
|
86
|
+
| `callbacks` | `InspectorCallbacks` | Optional event callbacks |
|
|
87
|
+
| `labels` | `InspectorLabels` | Optional custom labels for i18n |
|
|
88
|
+
| `theme` | `InspectorTheme` | Optional custom theme colors |
|
|
89
|
+
|
|
90
|
+
#### Returns: `UseInspectorReturn`
|
|
91
|
+
|
|
92
|
+
```typescript
|
|
93
|
+
interface UseInspectorReturn {
|
|
94
|
+
// State
|
|
95
|
+
isInspecting: boolean;
|
|
96
|
+
|
|
97
|
+
// Control Methods
|
|
98
|
+
toggleInspector: (active?: boolean) => void;
|
|
99
|
+
startInspecting: () => void;
|
|
100
|
+
stopInspecting: () => void;
|
|
101
|
+
|
|
102
|
+
// UI Control Methods
|
|
103
|
+
showContentInput: (show: boolean, updateImmediately?: boolean) => void;
|
|
104
|
+
showImageInput: (show: boolean, updateImmediately?: boolean) => void;
|
|
105
|
+
showStyleEditor: (show: boolean) => void;
|
|
106
|
+
setBadgeVisible: (visible: boolean) => void;
|
|
107
|
+
setShowChildBorders: (show: boolean) => void;
|
|
108
|
+
|
|
109
|
+
// Element Methods
|
|
110
|
+
highlightElement: (
|
|
111
|
+
identifier: string | SelectedElementData,
|
|
112
|
+
options?: HighlightOptions
|
|
113
|
+
) => void;
|
|
114
|
+
getElementByInspectorId: (inspectorId: string) => void;
|
|
115
|
+
}
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### Methods
|
|
119
|
+
|
|
120
|
+
#### `toggleInspector(active?: boolean)`
|
|
121
|
+
|
|
122
|
+
Toggles inspector mode on/off.
|
|
123
|
+
|
|
124
|
+
```typescript
|
|
125
|
+
// Toggle
|
|
126
|
+
inspector.toggleInspector();
|
|
127
|
+
|
|
128
|
+
// Set explicitly
|
|
129
|
+
inspector.toggleInspector(true); // Start
|
|
130
|
+
inspector.toggleInspector(false); // Stop
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
#### `startInspecting()`
|
|
134
|
+
|
|
135
|
+
Start inspector mode.
|
|
136
|
+
|
|
137
|
+
```typescript
|
|
138
|
+
inspector.startInspecting();
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### `stopInspecting()`
|
|
142
|
+
|
|
143
|
+
Stop inspector mode.
|
|
144
|
+
|
|
145
|
+
```typescript
|
|
146
|
+
inspector.stopInspecting();
|
|
147
|
+
```
|
|
148
|
+
|
|
149
|
+
#### `showContentInput(show: boolean, updateImmediately?: boolean)`
|
|
150
|
+
|
|
151
|
+
Show or hide the text content editor.
|
|
152
|
+
|
|
153
|
+
```typescript
|
|
154
|
+
// Show content editor
|
|
155
|
+
inspector.showContentInput(true);
|
|
156
|
+
|
|
157
|
+
// Show and apply changes immediately without confirmation
|
|
158
|
+
inspector.showContentInput(true, true);
|
|
159
|
+
|
|
160
|
+
// Hide content editor
|
|
161
|
+
inspector.showContentInput(false);
|
|
162
|
+
```
|
|
163
|
+
|
|
164
|
+
**Parameters:**
|
|
165
|
+
|
|
166
|
+
- `show`: Whether to show or hide the content editor
|
|
167
|
+
- `updateImmediately`: If true, applies changes immediately without requiring user confirmation (default: false)
|
|
168
|
+
|
|
169
|
+
#### `showImageInput(show: boolean, updateImmediately?: boolean)`
|
|
170
|
+
|
|
171
|
+
Show or hide the image editor.
|
|
172
|
+
|
|
173
|
+
```typescript
|
|
174
|
+
// Show image editor
|
|
175
|
+
inspector.showImageInput(true);
|
|
176
|
+
|
|
177
|
+
// Show and apply changes immediately
|
|
178
|
+
inspector.showImageInput(true, true);
|
|
179
|
+
|
|
180
|
+
// Hide image editor
|
|
181
|
+
inspector.showImageInput(false);
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
**Parameters:**
|
|
185
|
+
|
|
186
|
+
- `show`: Whether to show or hide the image editor
|
|
187
|
+
- `updateImmediately`: If true, applies changes immediately without requiring user confirmation (default: false)
|
|
188
|
+
|
|
189
|
+
#### `showStyleEditor(show: boolean)`
|
|
190
|
+
|
|
191
|
+
Show or hide the style editor.
|
|
192
|
+
|
|
193
|
+
```typescript
|
|
194
|
+
// Show style editor
|
|
195
|
+
inspector.showStyleEditor(true);
|
|
196
|
+
|
|
197
|
+
// Hide style editor
|
|
198
|
+
inspector.showStyleEditor(false);
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
#### `setBadgeVisible(visible: boolean)`
|
|
202
|
+
|
|
203
|
+
Show or hide the "Built with Promake" badge.
|
|
204
|
+
|
|
205
|
+
```typescript
|
|
206
|
+
// Show badge
|
|
207
|
+
inspector.setBadgeVisible(true);
|
|
208
|
+
|
|
209
|
+
// Hide badge
|
|
210
|
+
inspector.setBadgeVisible(false);
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
#### `setShowChildBorders(show: boolean)`
|
|
214
|
+
|
|
215
|
+
Toggle visibility of child element borders during inspection.
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Show borders
|
|
219
|
+
inspector.setShowChildBorders(true);
|
|
220
|
+
|
|
221
|
+
// Hide borders
|
|
222
|
+
inspector.setShowChildBorders(false);
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
#### `highlightElement(identifier: string | SelectedElementData, options?: HighlightOptions)`
|
|
226
|
+
|
|
227
|
+
Highlight a specific element in the iframe with visual feedback.
|
|
228
|
+
|
|
229
|
+
```typescript
|
|
230
|
+
// Highlight by inspector ID
|
|
231
|
+
inspector.highlightElement("inspector-id-123");
|
|
232
|
+
|
|
233
|
+
// Highlight with custom options
|
|
234
|
+
inspector.highlightElement("inspector-id-123", {
|
|
235
|
+
duration: 5000,
|
|
236
|
+
color: "#ff0000",
|
|
237
|
+
animation: "pulse",
|
|
238
|
+
scrollIntoView: true,
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
// Highlight with navigation to a specific route
|
|
242
|
+
inspector.highlightElement("inspector-id-123", {
|
|
243
|
+
targetRoute: "/about",
|
|
244
|
+
duration: 3000,
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// Highlight using element data
|
|
248
|
+
inspector.highlightElement(elementData, {
|
|
249
|
+
animation: "fade",
|
|
250
|
+
});
|
|
251
|
+
```
|
|
252
|
+
|
|
253
|
+
**Parameters:**
|
|
254
|
+
|
|
255
|
+
- `identifier`: Inspector ID (string) or full SelectedElementData object
|
|
256
|
+
- `options`: Optional highlight configuration
|
|
257
|
+
|
|
258
|
+
**HighlightOptions:**
|
|
259
|
+
|
|
260
|
+
```typescript
|
|
261
|
+
interface HighlightOptions {
|
|
262
|
+
duration?: number; // Highlight duration in ms (default: 3000)
|
|
263
|
+
scrollIntoView?: boolean; // Scroll to element (default: true)
|
|
264
|
+
color?: string; // Highlight color (default: '#4417db')
|
|
265
|
+
animation?: "pulse" | "fade" | "none"; // Animation type (default: 'pulse')
|
|
266
|
+
targetRoute?: string; // Navigate to this route before highlighting
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
#### `getElementByInspectorId(inspectorId: string)`
|
|
271
|
+
|
|
272
|
+
Request detailed information about an element by its inspector ID. The response will be sent via the `onElementInfoReceived` callback.
|
|
273
|
+
|
|
274
|
+
```typescript
|
|
275
|
+
// Request element info
|
|
276
|
+
inspector.getElementByInspectorId("inspector-id-123");
|
|
277
|
+
|
|
278
|
+
// Handle response in callbacks
|
|
279
|
+
const inspector = useInspector(iframeRef, {
|
|
280
|
+
onElementInfoReceived: (data) => {
|
|
281
|
+
if (data.found) {
|
|
282
|
+
console.log("Element found:", data.element);
|
|
283
|
+
} else {
|
|
284
|
+
console.log("Element not found:", data.error);
|
|
285
|
+
}
|
|
286
|
+
},
|
|
287
|
+
});
|
|
288
|
+
```
|
|
289
|
+
|
|
290
|
+
**Response via `onElementInfoReceived`:**
|
|
291
|
+
|
|
292
|
+
```typescript
|
|
293
|
+
interface ElementInfoData {
|
|
294
|
+
found: boolean;
|
|
295
|
+
element?: SelectedElementData;
|
|
296
|
+
error?: string;
|
|
297
|
+
}
|
|
298
|
+
```
|
|
299
|
+
|
|
300
|
+
## 📢 Events & Callbacks
|
|
301
|
+
|
|
302
|
+
### `InspectorCallbacks`
|
|
303
|
+
|
|
304
|
+
```typescript
|
|
305
|
+
interface InspectorCallbacks {
|
|
306
|
+
onElementSelected?: (data: SelectedElementData) => void;
|
|
307
|
+
onUrlChange?: (data: UrlChangeData) => void;
|
|
308
|
+
onPromptSubmitted?: (data: PromptSubmittedData) => void;
|
|
309
|
+
onTextUpdated?: (data: TextUpdatedData) => void;
|
|
310
|
+
onImageUpdated?: (data: ImageUpdatedData) => void;
|
|
311
|
+
onStyleUpdated?: (data: StyleUpdatedData) => void;
|
|
312
|
+
onInspectorClosed?: () => void;
|
|
313
|
+
onError?: (data: ErrorData) => void;
|
|
314
|
+
onElementInfoReceived?: (data: ElementInfoData) => void;
|
|
315
|
+
}
|
|
316
|
+
```
|
|
317
|
+
|
|
318
|
+
### Event Examples
|
|
319
|
+
|
|
320
|
+
#### `onElementSelected`
|
|
321
|
+
|
|
322
|
+
Called when user selects an element in the iframe.
|
|
323
|
+
|
|
324
|
+
```typescript
|
|
325
|
+
const inspector = useInspector(iframeRef, {
|
|
326
|
+
onElementSelected: (data) => {
|
|
327
|
+
console.log("Selected:", data.tagName);
|
|
328
|
+
console.log("Component:", data.component?.name);
|
|
329
|
+
console.log("Position:", data.position);
|
|
330
|
+
console.log("Parents:", data.parents);
|
|
331
|
+
console.log("Children:", data.children);
|
|
332
|
+
},
|
|
333
|
+
});
|
|
334
|
+
```
|
|
335
|
+
|
|
336
|
+
**SelectedElementData:**
|
|
337
|
+
|
|
338
|
+
```typescript
|
|
339
|
+
interface SelectedElementData {
|
|
340
|
+
id: string; // Unique inspector ID
|
|
341
|
+
tagName: string; // HTML tag name
|
|
342
|
+
className: string; // CSS classes
|
|
343
|
+
component: ComponentInfo | null; // React component info
|
|
344
|
+
position: ElementPosition; // Screen position
|
|
345
|
+
isTextNode?: boolean; // Is text element
|
|
346
|
+
textContent?: string; // Text content
|
|
347
|
+
isImageNode?: boolean; // Is image element
|
|
348
|
+
imageUrl?: string; // Image URL
|
|
349
|
+
selector?: string; // CSS selector
|
|
350
|
+
currentRoute?: string; // Current route path
|
|
351
|
+
parents?: ElementReference[]; // Parent elements (3 generations)
|
|
352
|
+
children?: ElementReference[]; // Child elements
|
|
353
|
+
}
|
|
354
|
+
```
|
|
355
|
+
|
|
356
|
+
#### `onStyleUpdated`
|
|
357
|
+
|
|
358
|
+
Called when user updates element styles.
|
|
359
|
+
|
|
360
|
+
```typescript
|
|
361
|
+
const inspector = useInspector(iframeRef, {
|
|
362
|
+
onStyleUpdated: (data) => {
|
|
363
|
+
console.log("Element:", data.element);
|
|
364
|
+
console.log("Raw styles:", data.styles);
|
|
365
|
+
console.log("Inline styles:", data.inlineStyles);
|
|
366
|
+
console.log("Tailwind classes:", data.tailwindClasses);
|
|
367
|
+
|
|
368
|
+
// Use updateJSXSource to apply changes to source code
|
|
369
|
+
const result = updateJSXSource({
|
|
370
|
+
sourceCode: originalSourceCode,
|
|
371
|
+
lineNumber: data.element.component?.lineNumber!,
|
|
372
|
+
columnNumber: data.element.component?.columnNumber!,
|
|
373
|
+
tagName: data.element.tagName,
|
|
374
|
+
styles: data.inlineStyles,
|
|
375
|
+
className: data.tailwindClasses.join(" "),
|
|
376
|
+
});
|
|
377
|
+
|
|
378
|
+
if (result.success) {
|
|
379
|
+
// Save updated code
|
|
380
|
+
saveSourceCode(result.code);
|
|
381
|
+
}
|
|
382
|
+
},
|
|
383
|
+
});
|
|
384
|
+
```
|
|
385
|
+
|
|
386
|
+
**StyleUpdatedData:**
|
|
387
|
+
|
|
388
|
+
```typescript
|
|
389
|
+
interface StyleUpdatedData {
|
|
390
|
+
element: SelectedElementData;
|
|
391
|
+
styles: StyleChanges; // Raw style values
|
|
392
|
+
inlineStyles: Record<string, string>; // React inline style object
|
|
393
|
+
tailwindClasses: string[]; // Tailwind class names
|
|
394
|
+
appliedStyles: {
|
|
395
|
+
// Detailed breakdown
|
|
396
|
+
layout?: {
|
|
397
|
+
backgroundColor?: string;
|
|
398
|
+
height?: string;
|
|
399
|
+
width?: string;
|
|
400
|
+
// ... more layout properties
|
|
401
|
+
};
|
|
402
|
+
text?: {
|
|
403
|
+
color?: string;
|
|
404
|
+
fontSize?: string;
|
|
405
|
+
// ... more text properties
|
|
406
|
+
};
|
|
407
|
+
border?: {
|
|
408
|
+
borderRadius?: string;
|
|
409
|
+
// ... more border properties
|
|
410
|
+
};
|
|
411
|
+
spacing?: {
|
|
412
|
+
paddingVertical?: string;
|
|
413
|
+
// ... more spacing properties
|
|
414
|
+
};
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
```
|
|
418
|
+
|
|
419
|
+
#### `onTextUpdated`
|
|
420
|
+
|
|
421
|
+
Called when user updates text content.
|
|
422
|
+
|
|
423
|
+
```typescript
|
|
424
|
+
const inspector = useInspector(iframeRef, {
|
|
425
|
+
onTextUpdated: (data) => {
|
|
426
|
+
console.log("New text:", data.text);
|
|
427
|
+
console.log("Original text:", data.originalText);
|
|
428
|
+
console.log("Element:", data.element);
|
|
429
|
+
},
|
|
430
|
+
});
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
**TextUpdatedData:**
|
|
434
|
+
|
|
435
|
+
```typescript
|
|
436
|
+
interface TextUpdatedData {
|
|
437
|
+
text: string; // New text content
|
|
438
|
+
originalText: string; // Original text content
|
|
439
|
+
element: SelectedElementData;
|
|
440
|
+
}
|
|
441
|
+
```
|
|
442
|
+
|
|
443
|
+
#### `onImageUpdated`
|
|
444
|
+
|
|
445
|
+
Called when user uploads a new image.
|
|
446
|
+
|
|
447
|
+
```typescript
|
|
448
|
+
const inspector = useInspector(iframeRef, {
|
|
449
|
+
onImageUpdated: (data) => {
|
|
450
|
+
console.log("Image data:", data.imageData); // Base64
|
|
451
|
+
console.log("File info:", data.imageFile);
|
|
452
|
+
console.log("Original URL:", data.originalImageUrl);
|
|
453
|
+
|
|
454
|
+
// Upload image to your server
|
|
455
|
+
uploadImage(data.imageData, data.imageFile).then((url) => {
|
|
456
|
+
console.log("New image URL:", url);
|
|
457
|
+
});
|
|
458
|
+
},
|
|
459
|
+
});
|
|
460
|
+
```
|
|
461
|
+
|
|
462
|
+
**ImageUpdatedData:**
|
|
463
|
+
|
|
464
|
+
```typescript
|
|
465
|
+
interface ImageUpdatedData {
|
|
466
|
+
imageData: string; // Base64 encoded image
|
|
467
|
+
imageFile: {
|
|
468
|
+
name: string;
|
|
469
|
+
size: number;
|
|
470
|
+
type: string;
|
|
471
|
+
};
|
|
472
|
+
originalImageUrl: string;
|
|
473
|
+
element: SelectedElementData;
|
|
474
|
+
}
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
#### `onPromptSubmitted`
|
|
478
|
+
|
|
479
|
+
Called when user submits an AI prompt.
|
|
480
|
+
|
|
481
|
+
```typescript
|
|
482
|
+
const inspector = useInspector(iframeRef, {
|
|
483
|
+
onPromptSubmitted: (data) => {
|
|
484
|
+
console.log("Prompt:", data.prompt);
|
|
485
|
+
console.log("Element:", data.element);
|
|
486
|
+
|
|
487
|
+
// Send to your AI service
|
|
488
|
+
generateAIContent(data.prompt, data.element).then((result) => {
|
|
489
|
+
console.log("AI result:", result);
|
|
490
|
+
});
|
|
491
|
+
},
|
|
492
|
+
});
|
|
493
|
+
```
|
|
494
|
+
|
|
495
|
+
**PromptSubmittedData:**
|
|
496
|
+
|
|
497
|
+
```typescript
|
|
498
|
+
interface PromptSubmittedData {
|
|
499
|
+
prompt: string;
|
|
500
|
+
element: SelectedElementData;
|
|
501
|
+
}
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
#### `onUrlChange`
|
|
505
|
+
|
|
506
|
+
Called when iframe navigates to a new URL.
|
|
507
|
+
|
|
508
|
+
```typescript
|
|
509
|
+
const inspector = useInspector(iframeRef, {
|
|
510
|
+
onUrlChange: (data) => {
|
|
511
|
+
console.log("New URL:", data.url);
|
|
512
|
+
console.log("Pathname:", data.pathname);
|
|
513
|
+
console.log("Search:", data.search);
|
|
514
|
+
console.log("Hash:", data.hash);
|
|
515
|
+
},
|
|
516
|
+
});
|
|
517
|
+
```
|
|
518
|
+
|
|
519
|
+
**UrlChangeData:**
|
|
520
|
+
|
|
521
|
+
```typescript
|
|
522
|
+
interface UrlChangeData {
|
|
523
|
+
url: string;
|
|
524
|
+
pathname: string;
|
|
525
|
+
search: string;
|
|
526
|
+
hash: string;
|
|
527
|
+
}
|
|
528
|
+
```
|
|
529
|
+
|
|
530
|
+
#### `onError`
|
|
531
|
+
|
|
532
|
+
Called when an error occurs in the iframe.
|
|
533
|
+
|
|
534
|
+
```typescript
|
|
535
|
+
const inspector = useInspector(iframeRef, {
|
|
536
|
+
onError: (data) => {
|
|
537
|
+
console.error("Error type:", data.type);
|
|
538
|
+
console.error("Message:", data.message);
|
|
539
|
+
console.error("Stack:", data.stack);
|
|
540
|
+
console.error("File:", data.fileName);
|
|
541
|
+
console.error("Line:", data.lineNumber);
|
|
542
|
+
|
|
543
|
+
if (data.type === "vite") {
|
|
544
|
+
console.error("Plugin:", data.plugin);
|
|
545
|
+
console.error("Frame:", data.frame);
|
|
546
|
+
}
|
|
547
|
+
},
|
|
548
|
+
});
|
|
549
|
+
```
|
|
550
|
+
|
|
551
|
+
**ErrorData:**
|
|
552
|
+
|
|
553
|
+
```typescript
|
|
554
|
+
interface ErrorData {
|
|
555
|
+
type: "javascript" | "promise" | "console" | "vite";
|
|
556
|
+
message: string;
|
|
557
|
+
stack?: string;
|
|
558
|
+
fileName?: string;
|
|
559
|
+
lineNumber?: number;
|
|
560
|
+
columnNumber?: number;
|
|
561
|
+
timestamp: number;
|
|
562
|
+
// Vite-specific fields
|
|
563
|
+
frame?: string; // Code frame
|
|
564
|
+
plugin?: string; // Plugin name
|
|
565
|
+
}
|
|
566
|
+
```
|
|
567
|
+
|
|
568
|
+
#### `onInspectorClosed`
|
|
569
|
+
|
|
570
|
+
Called when user closes the inspector.
|
|
571
|
+
|
|
572
|
+
```typescript
|
|
573
|
+
const inspector = useInspector(iframeRef, {
|
|
574
|
+
onInspectorClosed: () => {
|
|
575
|
+
console.log("Inspector closed");
|
|
576
|
+
// Clean up or save state
|
|
577
|
+
},
|
|
578
|
+
});
|
|
579
|
+
```
|
|
580
|
+
|
|
581
|
+
#### `onElementInfoReceived`
|
|
582
|
+
|
|
583
|
+
Called in response to `getElementByInspectorId()`.
|
|
584
|
+
|
|
585
|
+
```typescript
|
|
586
|
+
const inspector = useInspector(iframeRef, {
|
|
587
|
+
onElementInfoReceived: (data) => {
|
|
588
|
+
if (data.found) {
|
|
589
|
+
console.log("Element found:", data.element);
|
|
590
|
+
} else {
|
|
591
|
+
console.error("Element not found:", data.error);
|
|
592
|
+
}
|
|
593
|
+
},
|
|
594
|
+
});
|
|
595
|
+
```
|
|
596
|
+
|
|
597
|
+
## 🎨 Customization
|
|
598
|
+
|
|
599
|
+
### Labels (i18n)
|
|
600
|
+
|
|
601
|
+
Customize all UI text for internationalization:
|
|
602
|
+
|
|
603
|
+
```typescript
|
|
604
|
+
const labels = {
|
|
605
|
+
// Text Editor
|
|
606
|
+
editText: "Edit Text",
|
|
607
|
+
textContentLabel: "Content",
|
|
608
|
+
updateText: "Update",
|
|
609
|
+
|
|
610
|
+
// Image Editor
|
|
611
|
+
editImage: "Edit Image",
|
|
612
|
+
imageUploadTitle: "Select Image",
|
|
613
|
+
updateImage: "Update",
|
|
614
|
+
|
|
615
|
+
// Style Editor
|
|
616
|
+
styleEditorTitle: "Styles",
|
|
617
|
+
layoutSectionTitle: "Layout",
|
|
618
|
+
textSectionTitle: "Text",
|
|
619
|
+
|
|
620
|
+
// Properties
|
|
621
|
+
backgroundColorLabel: "Background",
|
|
622
|
+
colorLabel: "Color",
|
|
623
|
+
fontSizeLabel: "Size",
|
|
624
|
+
|
|
625
|
+
// ... see InspectorLabels type for all options
|
|
626
|
+
};
|
|
627
|
+
|
|
628
|
+
const inspector = useInspector(iframeRef, callbacks, labels);
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
See the full `InspectorLabels` interface in the [Types](#types) section for all available label options.
|
|
632
|
+
|
|
633
|
+
### Theme
|
|
634
|
+
|
|
635
|
+
Customize colors to match your application:
|
|
636
|
+
|
|
637
|
+
```typescript
|
|
638
|
+
const theme = {
|
|
639
|
+
// Control Box
|
|
640
|
+
backgroundColor: "#ffffff",
|
|
641
|
+
textColor: "#111827",
|
|
642
|
+
|
|
643
|
+
// Buttons
|
|
644
|
+
buttonColor: "#4417db",
|
|
645
|
+
buttonTextColor: "#ffffff",
|
|
646
|
+
buttonHoverColor: "#3a13c0",
|
|
647
|
+
|
|
648
|
+
// Inputs
|
|
649
|
+
inputBackgroundColor: "#f9fafb",
|
|
650
|
+
inputBorderColor: "#d1d5db",
|
|
651
|
+
inputFocusBorderColor: "#4417db",
|
|
652
|
+
|
|
653
|
+
// Status Colors
|
|
654
|
+
warningColor: "#f59e0b",
|
|
655
|
+
successColor: "#10b981",
|
|
656
|
+
errorColor: "#ef4444",
|
|
657
|
+
|
|
658
|
+
// Overlay
|
|
659
|
+
overlayColor: "#4417db",
|
|
660
|
+
overlayOpacity: 0.2,
|
|
661
|
+
|
|
662
|
+
// ... see InspectorTheme type for all options
|
|
663
|
+
};
|
|
664
|
+
|
|
665
|
+
const inspector = useInspector(iframeRef, callbacks, labels, theme);
|
|
666
|
+
```
|
|
667
|
+
|
|
668
|
+
See the full `InspectorTheme` interface in the [Types](#types) section for all available theme options.
|
|
669
|
+
|
|
670
|
+
## 🛠️ Utilities
|
|
671
|
+
|
|
672
|
+
### `updateJSXSource(options): UpdateJSXSourceResult`
|
|
673
|
+
|
|
674
|
+
AST-based utility for programmatically updating JSX/TSX source code. Intelligently merges styles and classNames without breaking existing code.
|
|
675
|
+
|
|
676
|
+
#### Parameters
|
|
677
|
+
|
|
678
|
+
```typescript
|
|
679
|
+
interface UpdateJSXSourceOptions {
|
|
680
|
+
sourceCode: string; // Original JSX/TSX source code
|
|
681
|
+
lineNumber: number; // Target element's line number (1-indexed)
|
|
682
|
+
columnNumber: number; // Target element's column number (0-indexed)
|
|
683
|
+
tagName: string; // HTML tag name for validation
|
|
684
|
+
styles?: Record<string, string>; // Inline styles to apply
|
|
685
|
+
className?: string; // Class names to add
|
|
686
|
+
}
|
|
687
|
+
```
|
|
688
|
+
|
|
689
|
+
#### Returns
|
|
690
|
+
|
|
691
|
+
```typescript
|
|
692
|
+
interface UpdateJSXSourceResult {
|
|
693
|
+
success: boolean;
|
|
694
|
+
code: string; // Updated source code
|
|
695
|
+
message?: string; // Error or success message
|
|
696
|
+
}
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
#### Examples
|
|
700
|
+
|
|
701
|
+
**Add inline styles:**
|
|
702
|
+
|
|
703
|
+
```typescript
|
|
704
|
+
import { updateJSXSource } from "@promakeai/inspector-hook";
|
|
705
|
+
|
|
706
|
+
const sourceCode = `
|
|
707
|
+
function MyComponent() {
|
|
708
|
+
return <div className="container">Hello</div>;
|
|
709
|
+
}
|
|
710
|
+
`;
|
|
711
|
+
|
|
712
|
+
const result = updateJSXSource({
|
|
713
|
+
sourceCode,
|
|
714
|
+
lineNumber: 3,
|
|
715
|
+
columnNumber: 9,
|
|
716
|
+
tagName: "div",
|
|
717
|
+
styles: {
|
|
718
|
+
backgroundColor: "red",
|
|
719
|
+
padding: "20px",
|
|
720
|
+
},
|
|
721
|
+
});
|
|
722
|
+
|
|
723
|
+
if (result.success) {
|
|
724
|
+
console.log(result.code);
|
|
725
|
+
// Output:
|
|
726
|
+
// function MyComponent() {
|
|
727
|
+
// return <div className="container" style={{ backgroundColor: "red", padding: "20px" }}>Hello</div>;
|
|
728
|
+
// }
|
|
729
|
+
}
|
|
730
|
+
```
|
|
731
|
+
|
|
732
|
+
**Add class names:**
|
|
733
|
+
|
|
734
|
+
```typescript
|
|
735
|
+
const result = updateJSXSource({
|
|
736
|
+
sourceCode,
|
|
737
|
+
lineNumber: 3,
|
|
738
|
+
columnNumber: 9,
|
|
739
|
+
tagName: "div",
|
|
740
|
+
className: "bg-red-500 p-4",
|
|
741
|
+
});
|
|
742
|
+
|
|
743
|
+
// Result: <div className="container bg-red-500 p-4">Hello</div>
|
|
744
|
+
```
|
|
745
|
+
|
|
746
|
+
**Merge with existing styles:**
|
|
747
|
+
|
|
748
|
+
```typescript
|
|
749
|
+
const sourceCode = `
|
|
750
|
+
<div style={{ color: 'blue', margin: '10px' }}>
|
|
751
|
+
Hello
|
|
752
|
+
</div>
|
|
753
|
+
`;
|
|
754
|
+
|
|
755
|
+
const result = updateJSXSource({
|
|
756
|
+
sourceCode,
|
|
757
|
+
lineNumber: 2,
|
|
758
|
+
columnNumber: 0,
|
|
759
|
+
tagName: "div",
|
|
760
|
+
styles: {
|
|
761
|
+
color: "red", // Overrides existing 'blue'
|
|
762
|
+
padding: "20px", // Adds new property
|
|
763
|
+
},
|
|
764
|
+
});
|
|
765
|
+
|
|
766
|
+
// Result: <div style={{ margin: "10px", color: "red", padding: "20px" }}>
|
|
767
|
+
```
|
|
768
|
+
|
|
769
|
+
**Usage with Inspector callbacks:**
|
|
770
|
+
|
|
771
|
+
```typescript
|
|
772
|
+
const inspector = useInspector(iframeRef, {
|
|
773
|
+
onStyleUpdated: async (data) => {
|
|
774
|
+
const { element, inlineStyles, tailwindClasses } = data;
|
|
775
|
+
const component = element.component;
|
|
776
|
+
|
|
777
|
+
if (!component?.fileName || !component?.lineNumber) {
|
|
778
|
+
console.error("Missing component info");
|
|
779
|
+
return;
|
|
780
|
+
}
|
|
781
|
+
|
|
782
|
+
// Fetch original source code
|
|
783
|
+
const sourceCode = await fetchSourceCode(component.fileName);
|
|
784
|
+
|
|
785
|
+
// Update source code
|
|
786
|
+
const result = updateJSXSource({
|
|
787
|
+
sourceCode,
|
|
788
|
+
lineNumber: component.lineNumber,
|
|
789
|
+
columnNumber: component.columnNumber || 0,
|
|
790
|
+
tagName: element.tagName,
|
|
791
|
+
styles: inlineStyles,
|
|
792
|
+
className: tailwindClasses.join(" "),
|
|
793
|
+
});
|
|
794
|
+
|
|
795
|
+
if (result.success) {
|
|
796
|
+
// Save updated code back to file
|
|
797
|
+
await saveSourceCode(component.fileName, result.code);
|
|
798
|
+
console.log("✅ Source code updated successfully");
|
|
799
|
+
} else {
|
|
800
|
+
console.error("❌ Failed to update:", result.message);
|
|
801
|
+
}
|
|
802
|
+
},
|
|
803
|
+
});
|
|
804
|
+
```
|
|
805
|
+
|
|
806
|
+
**Features:**
|
|
807
|
+
|
|
808
|
+
- ✅ Preserves existing code structure and formatting
|
|
809
|
+
- ✅ Intelligently merges styles without duplicating properties
|
|
810
|
+
- ✅ Handles various className formats (string, template literal, expression)
|
|
811
|
+
- ✅ Validates tag name to ensure correct element is updated
|
|
812
|
+
- ✅ Full TypeScript support with AST-based parsing
|
|
813
|
+
- ✅ Supports JSX and TSX files
|
|
814
|
+
|
|
815
|
+
### `inspectorHookPlugin(): Plugin`
|
|
816
|
+
|
|
817
|
+
Vite plugin that configures Babel packages for browser compatibility. Required when using `updateJSXSource` in the browser.
|
|
818
|
+
|
|
819
|
+
#### Usage
|
|
820
|
+
|
|
821
|
+
```typescript
|
|
822
|
+
// vite.config.ts
|
|
823
|
+
import { defineConfig } from "vite";
|
|
824
|
+
import react from "@vitejs/plugin-react";
|
|
825
|
+
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
|
|
826
|
+
|
|
827
|
+
export default defineConfig({
|
|
828
|
+
plugins: [
|
|
829
|
+
inspectorHookPlugin(), // Must be included before react()
|
|
830
|
+
react(),
|
|
831
|
+
],
|
|
832
|
+
});
|
|
833
|
+
```
|
|
834
|
+
|
|
835
|
+
**What it does:**
|
|
836
|
+
|
|
837
|
+
- Defines `process.env.NODE_ENV` for Babel compatibility
|
|
838
|
+
- Sets `process.platform` to "browser"
|
|
839
|
+
- Sets `process.version` for version checks
|
|
840
|
+
|
|
841
|
+
**Note:** This plugin is only needed if you're using `updateJSXSource` in the browser. If you're only using it on the server/backend, you don't need this plugin.
|
|
842
|
+
|
|
843
|
+
## 📘 Types
|
|
844
|
+
|
|
845
|
+
All types are re-exported from `@promakeai/inspector-types` for convenience.
|
|
846
|
+
|
|
847
|
+
### Core Types
|
|
848
|
+
|
|
849
|
+
```typescript
|
|
850
|
+
// Component information from React Fiber or vite-plugin-component-debugger
|
|
851
|
+
interface ComponentInfo {
|
|
852
|
+
id?: string;
|
|
853
|
+
name?: string;
|
|
854
|
+
path?: string;
|
|
855
|
+
fileName?: string;
|
|
856
|
+
lineNumber?: number;
|
|
857
|
+
columnNumber?: number;
|
|
858
|
+
component?: string;
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
// Element position on screen
|
|
862
|
+
interface ElementPosition {
|
|
863
|
+
top: number;
|
|
864
|
+
left: number;
|
|
865
|
+
width: number;
|
|
866
|
+
height: number;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Element reference for parent/child tracking
|
|
870
|
+
interface ElementReference {
|
|
871
|
+
id: string;
|
|
872
|
+
tagName: string;
|
|
873
|
+
className: string;
|
|
874
|
+
selector?: string;
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
// Selected element data
|
|
878
|
+
interface SelectedElementData {
|
|
879
|
+
id: string;
|
|
880
|
+
tagName: string;
|
|
881
|
+
className: string;
|
|
882
|
+
component: ComponentInfo | null;
|
|
883
|
+
position: ElementPosition;
|
|
884
|
+
isTextNode?: boolean;
|
|
885
|
+
textContent?: string;
|
|
886
|
+
isImageNode?: boolean;
|
|
887
|
+
imageUrl?: string;
|
|
888
|
+
selector?: string;
|
|
889
|
+
currentRoute?: string;
|
|
890
|
+
parents?: ElementReference[]; // [parent, grandparent, great-grandparent]
|
|
891
|
+
children?: ElementReference[]; // Array of child elements
|
|
892
|
+
}
|
|
893
|
+
```
|
|
894
|
+
|
|
895
|
+
### Style Types
|
|
896
|
+
|
|
897
|
+
```typescript
|
|
898
|
+
// Style changes with all supported properties
|
|
899
|
+
interface StyleChanges {
|
|
900
|
+
// Layout
|
|
901
|
+
backgroundColor?: string;
|
|
902
|
+
height?: string;
|
|
903
|
+
width?: string;
|
|
904
|
+
display?: string;
|
|
905
|
+
opacity?: string;
|
|
906
|
+
flex?: string;
|
|
907
|
+
flexDirection?: string;
|
|
908
|
+
justifyContent?: string;
|
|
909
|
+
alignItems?: string;
|
|
910
|
+
|
|
911
|
+
// Image
|
|
912
|
+
objectFit?: string;
|
|
913
|
+
|
|
914
|
+
// Text
|
|
915
|
+
color?: string;
|
|
916
|
+
fontSize?: string;
|
|
917
|
+
fontWeight?: string;
|
|
918
|
+
fontFamily?: string;
|
|
919
|
+
textAlign?: string;
|
|
920
|
+
textDecoration?: string;
|
|
921
|
+
|
|
922
|
+
// Border
|
|
923
|
+
borderRadius?: string;
|
|
924
|
+
borderWidth?: string;
|
|
925
|
+
borderColor?: string;
|
|
926
|
+
borderStyle?: string;
|
|
927
|
+
|
|
928
|
+
// Spacing - Vertical/Horizontal (combined)
|
|
929
|
+
paddingVertical?: string;
|
|
930
|
+
paddingHorizontal?: string;
|
|
931
|
+
marginVertical?: string;
|
|
932
|
+
marginHorizontal?: string;
|
|
933
|
+
|
|
934
|
+
// Spacing - Individual sides
|
|
935
|
+
paddingTop?: string;
|
|
936
|
+
paddingRight?: string;
|
|
937
|
+
paddingBottom?: string;
|
|
938
|
+
paddingLeft?: string;
|
|
939
|
+
marginTop?: string;
|
|
940
|
+
marginRight?: string;
|
|
941
|
+
marginBottom?: string;
|
|
942
|
+
marginLeft?: string;
|
|
943
|
+
}
|
|
944
|
+
```
|
|
945
|
+
|
|
946
|
+
### Full InspectorLabels Interface
|
|
947
|
+
|
|
948
|
+
The `InspectorLabels` interface contains 100+ customizable text labels. Here are the main categories:
|
|
949
|
+
|
|
950
|
+
```typescript
|
|
951
|
+
interface InspectorLabels {
|
|
952
|
+
// Text Editor (4 labels)
|
|
953
|
+
editText?: string;
|
|
954
|
+
textContentLabel?: string;
|
|
955
|
+
textPlaceholder?: string;
|
|
956
|
+
linkUrlLabel?: string;
|
|
957
|
+
updateText?: string;
|
|
958
|
+
|
|
959
|
+
// Image Editor (4 labels)
|
|
960
|
+
editImage?: string;
|
|
961
|
+
imageUploadTitle?: string;
|
|
962
|
+
imageUploadHint?: string;
|
|
963
|
+
updateImage?: string;
|
|
964
|
+
|
|
965
|
+
// Prompt Input (1 label)
|
|
966
|
+
promptPlaceholder?: string;
|
|
967
|
+
|
|
968
|
+
// Style Editor Sections (6 labels)
|
|
969
|
+
styleEditorTitle?: string;
|
|
970
|
+
layoutSectionTitle?: string;
|
|
971
|
+
displaySectionTitle?: string;
|
|
972
|
+
imageSectionTitle?: string;
|
|
973
|
+
textSectionTitle?: string;
|
|
974
|
+
borderSectionTitle?: string;
|
|
975
|
+
spacingSectionTitle?: string;
|
|
976
|
+
|
|
977
|
+
// Style Properties (26 labels)
|
|
978
|
+
backgroundColorLabel?: string;
|
|
979
|
+
heightLabel?: string;
|
|
980
|
+
widthLabel?: string;
|
|
981
|
+
displayLabel?: string;
|
|
982
|
+
opacityLabel?: string;
|
|
983
|
+
// ... and 21 more property labels
|
|
984
|
+
|
|
985
|
+
// Element Types (10 labels)
|
|
986
|
+
elementContainer?: string;
|
|
987
|
+
elementText?: string;
|
|
988
|
+
elementImage?: string;
|
|
989
|
+
elementButton?: string;
|
|
990
|
+
// ... and 6 more element type labels
|
|
991
|
+
|
|
992
|
+
// Dropdown Options (50+ labels)
|
|
993
|
+
// Display options (6)
|
|
994
|
+
displayBlock?: string;
|
|
995
|
+
displayInline?: string;
|
|
996
|
+
// ...
|
|
997
|
+
|
|
998
|
+
// Flex direction options (4)
|
|
999
|
+
flexDirectionRow?: string;
|
|
1000
|
+
// ...
|
|
1001
|
+
|
|
1002
|
+
// Justify content options (6)
|
|
1003
|
+
justifyContentFlexStart?: string;
|
|
1004
|
+
// ...
|
|
1005
|
+
|
|
1006
|
+
// Align items options (5)
|
|
1007
|
+
alignItemsFlexStart?: string;
|
|
1008
|
+
// ...
|
|
1009
|
+
|
|
1010
|
+
// Font size options (9)
|
|
1011
|
+
fontSizeXS?: string;
|
|
1012
|
+
// ...
|
|
1013
|
+
|
|
1014
|
+
// Font weight options (8)
|
|
1015
|
+
fontWeightThin?: string;
|
|
1016
|
+
// ...
|
|
1017
|
+
|
|
1018
|
+
// Text decoration options (4)
|
|
1019
|
+
textDecorationNone?: string;
|
|
1020
|
+
// ...
|
|
1021
|
+
|
|
1022
|
+
// Border style options (9)
|
|
1023
|
+
borderStyleSolid?: string;
|
|
1024
|
+
// ...
|
|
1025
|
+
|
|
1026
|
+
// Object fit options (5)
|
|
1027
|
+
objectFitContain?: string;
|
|
1028
|
+
// ...
|
|
1029
|
+
|
|
1030
|
+
// Action Buttons (3 labels)
|
|
1031
|
+
saveButton?: string;
|
|
1032
|
+
resetButton?: string;
|
|
1033
|
+
cancelButton?: string;
|
|
1034
|
+
|
|
1035
|
+
// Status Messages (3 labels)
|
|
1036
|
+
unsavedChangesText?: string;
|
|
1037
|
+
savingText?: string;
|
|
1038
|
+
hintText?: string;
|
|
1039
|
+
|
|
1040
|
+
// Unsaved Changes Dialog (5 labels)
|
|
1041
|
+
unsavedDialogTitle?: string;
|
|
1042
|
+
unsavedDialogMessage?: string;
|
|
1043
|
+
saveChangesButton?: string;
|
|
1044
|
+
discardChangesButton?: string;
|
|
1045
|
+
continueEditingButton?: string;
|
|
1046
|
+
|
|
1047
|
+
// Tab Names (3 labels)
|
|
1048
|
+
textTabLabel?: string;
|
|
1049
|
+
imageTabLabel?: string;
|
|
1050
|
+
styleTabLabel?: string;
|
|
1051
|
+
|
|
1052
|
+
// Badge (2 labels)
|
|
1053
|
+
badgeText?: string;
|
|
1054
|
+
badgeUrl?: string;
|
|
1055
|
+
}
|
|
1056
|
+
```
|
|
1057
|
+
|
|
1058
|
+
### Full InspectorTheme Interface
|
|
1059
|
+
|
|
1060
|
+
The `InspectorTheme` interface contains 30+ customizable color properties:
|
|
1061
|
+
|
|
1062
|
+
```typescript
|
|
1063
|
+
interface InspectorTheme {
|
|
1064
|
+
// Control Box (3 colors)
|
|
1065
|
+
backgroundColor?: string; // Default: #ffffff
|
|
1066
|
+
textColor?: string; // Default: #111827
|
|
1067
|
+
secondaryTextColor?: string; // Default: #6b7280
|
|
1068
|
+
|
|
1069
|
+
// Buttons (7 colors)
|
|
1070
|
+
buttonColor?: string; // Default: #4417db
|
|
1071
|
+
buttonTextColor?: string; // Default: #ffffff
|
|
1072
|
+
buttonHoverColor?: string; // Default: #3a13c0
|
|
1073
|
+
secondaryButtonColor?: string; // Default: #f3f4f6
|
|
1074
|
+
secondaryButtonTextColor?: string; // Default: #6b7280
|
|
1075
|
+
secondaryButtonHoverColor?: string; // Default: #e5e7eb
|
|
1076
|
+
dangerButtonColor?: string; // Default: #ef4444
|
|
1077
|
+
dangerButtonTextColor?: string; // Default: #ffffff
|
|
1078
|
+
|
|
1079
|
+
// Inputs (5 colors)
|
|
1080
|
+
inputBackgroundColor?: string; // Default: #f9fafb
|
|
1081
|
+
inputTextColor?: string; // Default: #111827
|
|
1082
|
+
inputBorderColor?: string; // Default: #d1d5db
|
|
1083
|
+
inputFocusBorderColor?: string; // Default: #4417db
|
|
1084
|
+
inputPlaceholderColor?: string; // Default: #9ca3af
|
|
1085
|
+
|
|
1086
|
+
// Borders & Dividers (1 color)
|
|
1087
|
+
borderColor?: string; // Default: #e5e7eb
|
|
1088
|
+
|
|
1089
|
+
// Status Colors (3 colors)
|
|
1090
|
+
warningColor?: string; // Default: #f59e0b
|
|
1091
|
+
successColor?: string; // Default: #10b981
|
|
1092
|
+
errorColor?: string; // Default: #ef4444
|
|
1093
|
+
|
|
1094
|
+
// Tabs (5 colors)
|
|
1095
|
+
tabContainerBg?: string; // Default: #f9fafb
|
|
1096
|
+
tabActiveBg?: string; // Default: #4417db
|
|
1097
|
+
tabInactiveBg?: string; // Default: transparent
|
|
1098
|
+
tabActiveColor?: string; // Default: #ffffff
|
|
1099
|
+
tabInactiveColor?: string; // Default: #6b7280
|
|
1100
|
+
|
|
1101
|
+
// Badge (3 colors)
|
|
1102
|
+
badgeGradientStart?: string; // Default: #411E93
|
|
1103
|
+
badgeGradientEnd?: string; // Default: #E87C85
|
|
1104
|
+
badgeTextColor?: string; // Default: #ffffff
|
|
1105
|
+
|
|
1106
|
+
// Overlay (2 colors)
|
|
1107
|
+
overlayColor?: string; // Default: #4417db
|
|
1108
|
+
overlayOpacity?: number; // Default: 0.2
|
|
1109
|
+
|
|
1110
|
+
// Dialog (4 colors)
|
|
1111
|
+
dialogBackdropColor?: string; // Default: rgba(0, 0, 0, 0.6)
|
|
1112
|
+
dialogBackgroundColor?: string; // Default: #ffffff
|
|
1113
|
+
dialogTextColor?: string; // Default: #111827
|
|
1114
|
+
dialogSecondaryTextColor?: string; // Default: #6b7280
|
|
1115
|
+
}
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
## 🔧 Advanced Examples
|
|
1119
|
+
|
|
1120
|
+
### Complete Parent Application Example
|
|
1121
|
+
|
|
1122
|
+
```typescript
|
|
1123
|
+
import { useInspector, updateJSXSource } from "@promakeai/inspector-hook";
|
|
1124
|
+
import { useRef, useState } from "react";
|
|
1125
|
+
|
|
1126
|
+
function ParentApp() {
|
|
1127
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1128
|
+
const [selectedElement, setSelectedElement] = useState(null);
|
|
1129
|
+
|
|
1130
|
+
const inspector = useInspector(
|
|
1131
|
+
iframeRef,
|
|
1132
|
+
{
|
|
1133
|
+
// Element selection
|
|
1134
|
+
onElementSelected: (data) => {
|
|
1135
|
+
setSelectedElement(data);
|
|
1136
|
+
console.log("Selected:", data);
|
|
1137
|
+
},
|
|
1138
|
+
|
|
1139
|
+
// Style updates
|
|
1140
|
+
onStyleUpdated: async (data) => {
|
|
1141
|
+
const component = data.element.component;
|
|
1142
|
+
if (!component?.fileName) return;
|
|
1143
|
+
|
|
1144
|
+
// Fetch source code from your backend
|
|
1145
|
+
const sourceCode = await fetch(
|
|
1146
|
+
`/api/source/${component.fileName}`
|
|
1147
|
+
).then((r) => r.text());
|
|
1148
|
+
|
|
1149
|
+
// Update JSX source
|
|
1150
|
+
const result = updateJSXSource({
|
|
1151
|
+
sourceCode,
|
|
1152
|
+
lineNumber: component.lineNumber!,
|
|
1153
|
+
columnNumber: component.columnNumber || 0,
|
|
1154
|
+
tagName: data.element.tagName,
|
|
1155
|
+
styles: data.inlineStyles,
|
|
1156
|
+
className: data.tailwindClasses.join(" "),
|
|
1157
|
+
});
|
|
1158
|
+
|
|
1159
|
+
if (result.success) {
|
|
1160
|
+
// Save back to your backend
|
|
1161
|
+
await fetch(`/api/source/${component.fileName}`, {
|
|
1162
|
+
method: "POST",
|
|
1163
|
+
headers: { "Content-Type": "application/json" },
|
|
1164
|
+
body: JSON.stringify({ code: result.code }),
|
|
1165
|
+
});
|
|
1166
|
+
|
|
1167
|
+
alert("✅ Styles saved successfully!");
|
|
1168
|
+
}
|
|
1169
|
+
},
|
|
1170
|
+
|
|
1171
|
+
// Text updates
|
|
1172
|
+
onTextUpdated: async (data) => {
|
|
1173
|
+
console.log("Text changed:", data.text);
|
|
1174
|
+
// Save to your backend or update state
|
|
1175
|
+
},
|
|
1176
|
+
|
|
1177
|
+
// Image uploads
|
|
1178
|
+
onImageUpdated: async (data) => {
|
|
1179
|
+
// Upload image to your storage
|
|
1180
|
+
const formData = new FormData();
|
|
1181
|
+
const blob = await fetch(data.imageData).then((r) => r.blob());
|
|
1182
|
+
formData.append("image", blob, data.imageFile.name);
|
|
1183
|
+
|
|
1184
|
+
const response = await fetch("/api/upload-image", {
|
|
1185
|
+
method: "POST",
|
|
1186
|
+
body: formData,
|
|
1187
|
+
});
|
|
1188
|
+
|
|
1189
|
+
const { url } = await response.json();
|
|
1190
|
+
console.log("Image uploaded:", url);
|
|
1191
|
+
},
|
|
1192
|
+
|
|
1193
|
+
// AI prompts
|
|
1194
|
+
onPromptSubmitted: async (data) => {
|
|
1195
|
+
// Send to your AI service
|
|
1196
|
+
const response = await fetch("/api/ai-generate", {
|
|
1197
|
+
method: "POST",
|
|
1198
|
+
headers: { "Content-Type": "application/json" },
|
|
1199
|
+
body: JSON.stringify({
|
|
1200
|
+
prompt: data.prompt,
|
|
1201
|
+
element: data.element,
|
|
1202
|
+
}),
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const result = await response.json();
|
|
1206
|
+
console.log("AI result:", result);
|
|
1207
|
+
},
|
|
1208
|
+
|
|
1209
|
+
// URL navigation
|
|
1210
|
+
onUrlChange: (data) => {
|
|
1211
|
+
console.log("Navigated to:", data.pathname);
|
|
1212
|
+
},
|
|
1213
|
+
|
|
1214
|
+
// Error handling
|
|
1215
|
+
onError: (data) => {
|
|
1216
|
+
console.error("Error in iframe:", data);
|
|
1217
|
+
// Show error notification
|
|
1218
|
+
},
|
|
1219
|
+
|
|
1220
|
+
// Inspector closed
|
|
1221
|
+
onInspectorClosed: () => {
|
|
1222
|
+
console.log("Inspector closed");
|
|
1223
|
+
setSelectedElement(null);
|
|
1224
|
+
},
|
|
1225
|
+
},
|
|
1226
|
+
// Custom labels (i18n)
|
|
1227
|
+
{
|
|
1228
|
+
editText: "Edit Text",
|
|
1229
|
+
editImage: "Edit Image",
|
|
1230
|
+
styleEditorTitle: "Styles",
|
|
1231
|
+
updateText: "Update",
|
|
1232
|
+
cancelButton: "Cancel",
|
|
1233
|
+
},
|
|
1234
|
+
// Custom theme
|
|
1235
|
+
{
|
|
1236
|
+
buttonColor: "#ff6b6b",
|
|
1237
|
+
buttonHoverColor: "#ee5a5a",
|
|
1238
|
+
overlayColor: "#ff6b6b",
|
|
1239
|
+
}
|
|
1240
|
+
);
|
|
1241
|
+
|
|
1242
|
+
return (
|
|
1243
|
+
<div style={{ padding: "20px" }}>
|
|
1244
|
+
<div style={{ marginBottom: "20px", display: "flex", gap: "10px" }}>
|
|
1245
|
+
<button onClick={() => inspector.toggleInspector()}>
|
|
1246
|
+
{inspector.isInspecting ? "⏹ Stop" : "▶ Start"} Inspector
|
|
1247
|
+
</button>
|
|
1248
|
+
|
|
1249
|
+
<button onClick={() => inspector.showContentInput(true)}>
|
|
1250
|
+
📝 Edit Text
|
|
1251
|
+
</button>
|
|
1252
|
+
|
|
1253
|
+
<button onClick={() => inspector.showImageInput(true)}>
|
|
1254
|
+
🖼️ Edit Image
|
|
1255
|
+
</button>
|
|
1256
|
+
|
|
1257
|
+
<button onClick={() => inspector.showStyleEditor(true)}>
|
|
1258
|
+
🎨 Edit Styles
|
|
1259
|
+
</button>
|
|
1260
|
+
|
|
1261
|
+
<button onClick={() => inspector.setBadgeVisible(true)}>
|
|
1262
|
+
🏷️ Show Badge
|
|
1263
|
+
</button>
|
|
1264
|
+
|
|
1265
|
+
<button
|
|
1266
|
+
onClick={() =>
|
|
1267
|
+
inspector.highlightElement("some-id", {
|
|
1268
|
+
color: "#ff0000",
|
|
1269
|
+
duration: 5000,
|
|
1270
|
+
})
|
|
1271
|
+
}
|
|
1272
|
+
>
|
|
1273
|
+
🔦 Highlight Element
|
|
1274
|
+
</button>
|
|
1275
|
+
</div>
|
|
1276
|
+
|
|
1277
|
+
{selectedElement && (
|
|
1278
|
+
<div
|
|
1279
|
+
style={{
|
|
1280
|
+
marginBottom: "20px",
|
|
1281
|
+
padding: "10px",
|
|
1282
|
+
background: "#f0f0f0",
|
|
1283
|
+
}}
|
|
1284
|
+
>
|
|
1285
|
+
<strong>Selected:</strong> {selectedElement.tagName}
|
|
1286
|
+
{selectedElement.component?.name && (
|
|
1287
|
+
<span> ({selectedElement.component.name})</span>
|
|
1288
|
+
)}
|
|
1289
|
+
</div>
|
|
1290
|
+
)}
|
|
1291
|
+
|
|
1292
|
+
<iframe
|
|
1293
|
+
ref={iframeRef}
|
|
1294
|
+
src="http://localhost:5173"
|
|
1295
|
+
style={{
|
|
1296
|
+
width: "100%",
|
|
1297
|
+
height: "800px",
|
|
1298
|
+
border: "1px solid #ccc",
|
|
1299
|
+
borderRadius: "8px",
|
|
1300
|
+
}}
|
|
1301
|
+
/>
|
|
1302
|
+
</div>
|
|
1303
|
+
);
|
|
1304
|
+
}
|
|
1305
|
+
|
|
1306
|
+
export default ParentApp;
|
|
1307
|
+
```
|
|
1308
|
+
|
|
1309
|
+
### Programmatic Element Highlighting
|
|
1310
|
+
|
|
1311
|
+
```typescript
|
|
1312
|
+
// Highlight element by ID
|
|
1313
|
+
inspector.highlightElement("inspector-id-123", {
|
|
1314
|
+
duration: 5000,
|
|
1315
|
+
color: "#ff0000",
|
|
1316
|
+
animation: "pulse",
|
|
1317
|
+
});
|
|
1318
|
+
|
|
1319
|
+
// Highlight and navigate to a specific route first
|
|
1320
|
+
inspector.highlightElement("inspector-id-123", {
|
|
1321
|
+
targetRoute: "/products",
|
|
1322
|
+
scrollIntoView: true,
|
|
1323
|
+
});
|
|
1324
|
+
|
|
1325
|
+
// Highlight using full element data
|
|
1326
|
+
inspector.highlightElement(selectedElementData, {
|
|
1327
|
+
animation: "fade",
|
|
1328
|
+
duration: 2000,
|
|
1329
|
+
});
|
|
1330
|
+
```
|
|
1331
|
+
|
|
1332
|
+
### Getting Element Information
|
|
1333
|
+
|
|
1334
|
+
```typescript
|
|
1335
|
+
// Request element info
|
|
1336
|
+
inspector.getElementByInspectorId("inspector-id-123");
|
|
1337
|
+
|
|
1338
|
+
// Handle response
|
|
1339
|
+
const inspector = useInspector(iframeRef, {
|
|
1340
|
+
onElementInfoReceived: (data) => {
|
|
1341
|
+
if (data.found && data.element) {
|
|
1342
|
+
console.log("Component:", data.element.component);
|
|
1343
|
+
console.log("Position:", data.element.position);
|
|
1344
|
+
console.log("Parents:", data.element.parents);
|
|
1345
|
+
console.log("Children:", data.element.children);
|
|
1346
|
+
} else {
|
|
1347
|
+
console.error("Element not found:", data.error);
|
|
1348
|
+
}
|
|
1349
|
+
},
|
|
1350
|
+
});
|
|
1351
|
+
```
|
|
1352
|
+
|
|
1353
|
+
## 🤝 Integration with @promakeai/inspector
|
|
1354
|
+
|
|
1355
|
+
This package is designed to work seamlessly with `@promakeai/inspector`. Here's the complete setup:
|
|
1356
|
+
|
|
1357
|
+
### Child Application (iframe content)
|
|
1358
|
+
|
|
1359
|
+
```typescript
|
|
1360
|
+
// vite.config.ts
|
|
1361
|
+
import { defineConfig } from "vite";
|
|
1362
|
+
import react from "@vitejs/plugin-react";
|
|
1363
|
+
import { inspectorDebugger } from "@promakeai/inspector/plugin";
|
|
1364
|
+
|
|
1365
|
+
export default defineConfig({
|
|
1366
|
+
plugins: [inspectorDebugger({ enabled: true }), react()],
|
|
1367
|
+
});
|
|
1368
|
+
```
|
|
1369
|
+
|
|
1370
|
+
```typescript
|
|
1371
|
+
// main.tsx
|
|
1372
|
+
import React from "react";
|
|
1373
|
+
import ReactDOM from "react-dom/client";
|
|
1374
|
+
import { Inspector } from "@promakeai/inspector";
|
|
1375
|
+
import "@promakeai/inspector/inspector.css";
|
|
1376
|
+
import App from "./App";
|
|
1377
|
+
|
|
1378
|
+
ReactDOM.createRoot(document.getElementById("root")!).render(
|
|
1379
|
+
<React.StrictMode>
|
|
1380
|
+
<Inspector />
|
|
1381
|
+
<App />
|
|
1382
|
+
</React.StrictMode>
|
|
1383
|
+
);
|
|
1384
|
+
```
|
|
1385
|
+
|
|
1386
|
+
### Parent Application (iframe host)
|
|
1387
|
+
|
|
1388
|
+
```typescript
|
|
1389
|
+
// vite.config.ts
|
|
1390
|
+
import { defineConfig } from "vite";
|
|
1391
|
+
import react from "@vitejs/plugin-react";
|
|
1392
|
+
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
|
|
1393
|
+
|
|
1394
|
+
export default defineConfig({
|
|
1395
|
+
plugins: [inspectorHookPlugin(), react()],
|
|
1396
|
+
});
|
|
1397
|
+
```
|
|
1398
|
+
|
|
1399
|
+
```typescript
|
|
1400
|
+
// ParentApp.tsx
|
|
1401
|
+
import { useInspector } from "@promakeai/inspector-hook";
|
|
1402
|
+
import { useRef } from "react";
|
|
1403
|
+
|
|
1404
|
+
function ParentApp() {
|
|
1405
|
+
const iframeRef = useRef<HTMLIFrameElement>(null);
|
|
1406
|
+
|
|
1407
|
+
const inspector = useInspector(iframeRef, {
|
|
1408
|
+
onElementSelected: (data) => console.log("Selected:", data),
|
|
1409
|
+
onStyleUpdated: (data) => console.log("Style updated:", data),
|
|
1410
|
+
});
|
|
1411
|
+
|
|
1412
|
+
return (
|
|
1413
|
+
<div>
|
|
1414
|
+
<button onClick={() => inspector.toggleInspector()}>
|
|
1415
|
+
Toggle Inspector
|
|
1416
|
+
</button>
|
|
1417
|
+
<iframe ref={iframeRef} src="http://localhost:5173" />
|
|
1418
|
+
</div>
|
|
1419
|
+
);
|
|
1420
|
+
}
|
|
1421
|
+
```
|
|
1422
|
+
|
|
1423
|
+
## 📋 Requirements
|
|
1424
|
+
|
|
1425
|
+
- **React**: `>=18.0.0`
|
|
1426
|
+
- **React DOM**: `>=18.0.0`
|
|
1427
|
+
- **Vite**: `>=5.0.0` (optional, only if using the Vite plugin)
|
|
1428
|
+
|
|
1429
|
+
## 📝 License
|
|
1430
|
+
|
|
1431
|
+
MIT
|
|
1432
|
+
|
|
1433
|
+
## 🐛 Troubleshooting
|
|
1434
|
+
|
|
1435
|
+
### Messages not being received
|
|
1436
|
+
|
|
1437
|
+
Make sure the iframe is fully loaded before sending messages:
|
|
1438
|
+
|
|
1439
|
+
```typescript
|
|
1440
|
+
<iframe
|
|
1441
|
+
ref={iframeRef}
|
|
1442
|
+
src="http://localhost:5173"
|
|
1443
|
+
onLoad={() => {
|
|
1444
|
+
console.log("Iframe loaded, ready to communicate");
|
|
1445
|
+
}}
|
|
1446
|
+
/>
|
|
1447
|
+
```
|
|
1448
|
+
|
|
1449
|
+
### Babel errors when using updateJSXSource
|
|
1450
|
+
|
|
1451
|
+
Make sure you've added the Vite plugin:
|
|
1452
|
+
|
|
1453
|
+
```typescript
|
|
1454
|
+
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
|
|
1455
|
+
|
|
1456
|
+
export default defineConfig({
|
|
1457
|
+
plugins: [
|
|
1458
|
+
inspectorHookPlugin(), // Required!
|
|
1459
|
+
react(),
|
|
1460
|
+
],
|
|
1461
|
+
});
|
|
1462
|
+
```
|
|
1463
|
+
|
|
1464
|
+
### Styles not updating correctly
|
|
1465
|
+
|
|
1466
|
+
Ensure you're providing the correct line and column numbers from the component info:
|
|
1467
|
+
|
|
1468
|
+
```typescript
|
|
1469
|
+
const result = updateJSXSource({
|
|
1470
|
+
sourceCode,
|
|
1471
|
+
lineNumber: data.element.component.lineNumber, // ✅ Use from component info
|
|
1472
|
+
columnNumber: data.element.component.columnNumber || 0,
|
|
1473
|
+
tagName: data.element.tagName,
|
|
1474
|
+
styles: data.inlineStyles,
|
|
1475
|
+
});
|
|
1476
|
+
```
|
|
1477
|
+
|
|
1478
|
+
## 🚀 Changelog
|
|
1479
|
+
|
|
1480
|
+
### v1.0.1
|
|
1481
|
+
|
|
1482
|
+
- Initial release
|
|
1483
|
+
- `useInspector` hook for iframe communication
|
|
1484
|
+
- `updateJSXSource` utility for AST-based code updates
|
|
1485
|
+
- `inspectorHookPlugin` Vite plugin for Babel configuration
|
|
1486
|
+
- Full TypeScript support
|
|
1487
|
+
- Comprehensive callback system
|
|
1488
|
+
- Theme and label customization
|
|
1489
|
+
|
|
1490
|
+
## 📞 Support
|
|
1491
|
+
|
|
1492
|
+
For issues, questions, or contributions, please visit:
|
|
1493
|
+
|
|
1494
|
+
- GitHub: [github.com/promakeai/inspector](https://github.com/promakeai/inspector)
|
|
1495
|
+
- Documentation: [promake.ai/docs](https://promake.ai/docs)
|
|
1496
|
+
|
|
1497
|
+
---
|
|
1498
|
+
|
|
1499
|
+
Made with ❤️ by [Promake](https://promake.ai)
|