@promakeai/inspector-hook
v1.5.1
Published
React hook for controlling inspector in parent applications
Readme
@promakeai/inspector-hook
React hook and utilities for controlling the Promake Inspector from parent applications via iframe communication.
📦 Installation
npm install @promakeai/inspector-hook
# or
yarn add @promakeai/inspector-hook
# or
bun add @promakeai/inspector-hook🎯 Overview
This package provides three main exports:
useInspector- React hook for controlling an iframe-embedded InspectorupdateJSXSource- AST-based utility for updating JSX/TSX source codeinspectorHookPlugin- Vite plugin for proper Babel configuration
🚀 Quick Start
Basic Usage
import { useInspector } from "@promakeai/inspector-hook";
import { useRef } from "react";
function ParentApp() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const inspector = useInspector(iframeRef, {
onElementSelected: (data) => {
console.log("Element selected:", data);
},
onStyleUpdated: (data) => {
console.log("Style updated:", data);
},
});
return (
<div>
<button onClick={() => inspector.toggleInspector()}>
{inspector.isInspecting ? "Stop" : "Start"} Inspector
</button>
<iframe
ref={iframeRef}
src="http://localhost:5173"
style={{ width: "100%", height: "800px" }}
/>
</div>
);
}With Vite Plugin
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
export default defineConfig({
plugins: [
inspectorHookPlugin(), // Required for proper Babel configuration
react(),
],
});📚 API Reference
useInspector(iframeRef, callbacks?, labels?, theme?)
Main React hook for controlling the Inspector.
Parameters
| Parameter | Type | Description |
| ----------- | ------------------------------ | ------------------------------- |
| iframeRef | RefObject<HTMLIFrameElement> | Reference to the iframe element |
| callbacks | InspectorCallbacks | Optional event callbacks |
| labels | InspectorLabels | Optional custom labels for i18n |
| theme | InspectorTheme | Optional custom theme colors |
Returns: UseInspectorReturn
interface UseInspectorReturn {
// State
isInspecting: boolean;
// Control Methods
toggleInspector: (active?: boolean) => void;
startInspecting: () => void;
stopInspecting: () => void;
// UI Control Methods
showContentInput: (show: boolean, updateImmediately?: boolean) => void;
showImageInput: (show: boolean, updateImmediately?: boolean) => void;
showStyleEditor: (show: boolean) => void;
setBadgeVisible: (visible: boolean) => void;
setShowChildBorders: (show: boolean) => void;
// Element Methods
highlightElement: (
identifier: string | SelectedElementData,
options?: HighlightOptions
) => void;
getElementByInspectorId: (inspectorId: string) => void;
}Methods
toggleInspector(active?: boolean)
Toggles inspector mode on/off.
// Toggle
inspector.toggleInspector();
// Set explicitly
inspector.toggleInspector(true); // Start
inspector.toggleInspector(false); // StopstartInspecting()
Start inspector mode.
inspector.startInspecting();stopInspecting()
Stop inspector mode.
inspector.stopInspecting();showContentInput(show: boolean, updateImmediately?: boolean)
Show or hide the text content editor.
// Show content editor
inspector.showContentInput(true);
// Show and apply changes immediately without confirmation
inspector.showContentInput(true, true);
// Hide content editor
inspector.showContentInput(false);Parameters:
show: Whether to show or hide the content editorupdateImmediately: If true, applies changes immediately without requiring user confirmation (default: false)
showImageInput(show: boolean, updateImmediately?: boolean)
Show or hide the image editor.
// Show image editor
inspector.showImageInput(true);
// Show and apply changes immediately
inspector.showImageInput(true, true);
// Hide image editor
inspector.showImageInput(false);Parameters:
show: Whether to show or hide the image editorupdateImmediately: If true, applies changes immediately without requiring user confirmation (default: false)
showStyleEditor(show: boolean)
Show or hide the style editor.
// Show style editor
inspector.showStyleEditor(true);
// Hide style editor
inspector.showStyleEditor(false);setBadgeVisible(visible: boolean)
Show or hide the "Built with Promake" badge.
// Show badge
inspector.setBadgeVisible(true);
// Hide badge
inspector.setBadgeVisible(false);setShowChildBorders(show: boolean)
Toggle visibility of child element borders during inspection.
// Show borders
inspector.setShowChildBorders(true);
// Hide borders
inspector.setShowChildBorders(false);highlightElement(identifier: string | SelectedElementData, options?: HighlightOptions)
Highlight a specific element in the iframe with visual feedback.
// Highlight by inspector ID
inspector.highlightElement("inspector-id-123");
// Highlight with custom options
inspector.highlightElement("inspector-id-123", {
duration: 5000,
color: "#ff0000",
animation: "pulse",
scrollIntoView: true,
});
// Highlight with navigation to a specific route
inspector.highlightElement("inspector-id-123", {
targetRoute: "/about",
duration: 3000,
});
// Highlight using element data
inspector.highlightElement(elementData, {
animation: "fade",
});Parameters:
identifier: Inspector ID (string) or full SelectedElementData objectoptions: Optional highlight configuration
HighlightOptions:
interface HighlightOptions {
duration?: number; // Highlight duration in ms (default: 3000)
scrollIntoView?: boolean; // Scroll to element (default: true)
color?: string; // Highlight color (default: '#4417db')
animation?: "pulse" | "fade" | "none"; // Animation type (default: 'pulse')
targetRoute?: string; // Navigate to this route before highlighting
}getElementByInspectorId(inspectorId: string)
Request detailed information about an element by its inspector ID. The response will be sent via the onElementInfoReceived callback.
// Request element info
inspector.getElementByInspectorId("inspector-id-123");
// Handle response in callbacks
const inspector = useInspector(iframeRef, {
onElementInfoReceived: (data) => {
if (data.found) {
console.log("Element found:", data.element);
} else {
console.log("Element not found:", data.error);
}
},
});Response via onElementInfoReceived:
interface ElementInfoData {
found: boolean;
element?: SelectedElementData;
error?: string;
}📢 Events & Callbacks
InspectorCallbacks
interface InspectorCallbacks {
onElementSelected?: (data: SelectedElementData) => void;
onUrlChange?: (data: UrlChangeData) => void;
onPromptSubmitted?: (data: PromptSubmittedData) => void;
onTextUpdated?: (data: TextUpdatedData) => void;
onImageUpdated?: (data: ImageUpdatedData) => void;
onStyleUpdated?: (data: StyleUpdatedData) => void;
onInspectorClosed?: () => void;
onError?: (data: ErrorData) => void;
onElementInfoReceived?: (data: ElementInfoData) => void;
}Event Examples
onElementSelected
Called when user selects an element in the iframe.
const inspector = useInspector(iframeRef, {
onElementSelected: (data) => {
console.log("Selected:", data.tagName);
console.log("Component:", data.component?.name);
console.log("Position:", data.position);
console.log("Parents:", data.parents);
console.log("Children:", data.children);
},
});SelectedElementData:
interface SelectedElementData {
id: string; // Unique inspector ID
tagName: string; // HTML tag name
className: string; // CSS classes
component: ComponentInfo | null; // React component info
position: ElementPosition; // Screen position
isTextNode?: boolean; // Is text element
textContent?: string; // Text content
isImageNode?: boolean; // Is image element
imageUrl?: string; // Image URL
selector?: string; // CSS selector
currentRoute?: string; // Current route path
parents?: ElementReference[]; // Parent elements (3 generations)
children?: ElementReference[]; // Child elements
}onStyleUpdated
Called when user updates element styles.
const inspector = useInspector(iframeRef, {
onStyleUpdated: (data) => {
console.log("Element:", data.element);
console.log("Raw styles:", data.styles);
console.log("Inline styles:", data.inlineStyles);
console.log("Tailwind classes:", data.tailwindClasses);
// Use updateJSXSource to apply changes to source code
const result = updateJSXSource({
sourceCode: originalSourceCode,
lineNumber: data.element.component?.lineNumber!,
columnNumber: data.element.component?.columnNumber!,
tagName: data.element.tagName,
styles: data.inlineStyles,
className: data.tailwindClasses.join(" "),
});
if (result.success) {
// Save updated code
saveSourceCode(result.code);
}
},
});StyleUpdatedData:
interface StyleUpdatedData {
element: SelectedElementData;
styles: StyleChanges; // Raw style values
inlineStyles: Record<string, string>; // React inline style object
tailwindClasses: string[]; // Tailwind class names
appliedStyles: {
// Detailed breakdown
layout?: {
backgroundColor?: string;
height?: string;
width?: string;
// ... more layout properties
};
text?: {
color?: string;
fontSize?: string;
// ... more text properties
};
border?: {
borderRadius?: string;
// ... more border properties
};
spacing?: {
paddingVertical?: string;
// ... more spacing properties
};
};
}onTextUpdated
Called when user updates text content.
const inspector = useInspector(iframeRef, {
onTextUpdated: (data) => {
console.log("New text:", data.text);
console.log("Original text:", data.originalText);
console.log("Element:", data.element);
},
});TextUpdatedData:
interface TextUpdatedData {
text: string; // New text content
originalText: string; // Original text content
element: SelectedElementData;
}onImageUpdated
Called when user uploads a new image.
const inspector = useInspector(iframeRef, {
onImageUpdated: (data) => {
console.log("Image data:", data.imageData); // Base64
console.log("File info:", data.imageFile);
console.log("Original URL:", data.originalImageUrl);
// Upload image to your server
uploadImage(data.imageData, data.imageFile).then((url) => {
console.log("New image URL:", url);
});
},
});ImageUpdatedData:
interface ImageUpdatedData {
imageData: string; // Base64 encoded image
imageFile: {
name: string;
size: number;
type: string;
};
originalImageUrl: string;
element: SelectedElementData;
}onPromptSubmitted
Called when user submits an AI prompt.
const inspector = useInspector(iframeRef, {
onPromptSubmitted: (data) => {
console.log("Prompt:", data.prompt);
console.log("Element:", data.element);
// Send to your AI service
generateAIContent(data.prompt, data.element).then((result) => {
console.log("AI result:", result);
});
},
});PromptSubmittedData:
interface PromptSubmittedData {
prompt: string;
element: SelectedElementData;
}onUrlChange
Called when iframe navigates to a new URL.
const inspector = useInspector(iframeRef, {
onUrlChange: (data) => {
console.log("New URL:", data.url);
console.log("Pathname:", data.pathname);
console.log("Search:", data.search);
console.log("Hash:", data.hash);
},
});UrlChangeData:
interface UrlChangeData {
oldUrl: string;
newUrl: string;
timestamp: number;
}onError
Called when an error occurs in the iframe.
const inspector = useInspector(iframeRef, {
onError: (data) => {
console.error("Error type:", data.type);
console.error("Message:", data.message);
console.error("Stack:", data.stack);
console.error("File:", data.fileName);
console.error("Line:", data.lineNumber);
if (data.type === "vite") {
console.error("Plugin:", data.plugin);
console.error("Frame:", data.frame);
}
},
});ErrorData:
interface ErrorData {
type: "javascript" | "promise" | "console" | "vite";
message: string;
stack?: string;
fileName?: string;
lineNumber?: number;
columnNumber?: number;
timestamp: number;
// Vite-specific fields
frame?: string; // Code frame
plugin?: string; // Plugin name
}onInspectorClosed
Called when user closes the inspector.
const inspector = useInspector(iframeRef, {
onInspectorClosed: () => {
console.log("Inspector closed");
// Clean up or save state
},
});onElementInfoReceived
Called in response to getElementByInspectorId().
const inspector = useInspector(iframeRef, {
onElementInfoReceived: (data) => {
if (data.found) {
console.log("Element found:", data.element);
} else {
console.error("Element not found:", data.error);
}
},
});🎨 Customization
Labels (i18n)
Customize all UI text for internationalization:
const labels = {
// Text Editor
editText: "Edit Text",
textContentLabel: "Content",
updateText: "Update",
// Image Editor
editImage: "Edit Image",
imageUploadTitle: "Select Image",
updateImage: "Update",
// Style Editor
styleEditorTitle: "Styles",
layoutSectionTitle: "Layout",
textSectionTitle: "Text",
// Properties
backgroundColorLabel: "Background",
colorLabel: "Color",
fontSizeLabel: "Size",
// ... see InspectorLabels type for all options
};
const inspector = useInspector(iframeRef, callbacks, labels);See the full InspectorLabels interface in the Types section for all available label options.
Theme
Customize colors to match your application:
const theme = {
// Control Box
backgroundColor: "#ffffff",
textColor: "#111827",
// Buttons
buttonColor: "#4417db",
buttonTextColor: "#ffffff",
buttonHoverColor: "#3a13c0",
// Inputs
inputBackgroundColor: "#f9fafb",
inputBorderColor: "#d1d5db",
inputFocusBorderColor: "#4417db",
// Status Colors
warningColor: "#f59e0b",
successColor: "#10b981",
errorColor: "#ef4444",
// Overlay
overlayColor: "#4417db",
overlayOpacity: 0.2,
// ... see InspectorTheme type for all options
};
const inspector = useInspector(iframeRef, callbacks, labels, theme);See the full InspectorTheme interface in the Types section for all available theme options.
🛠️ Utilities
updateJSXSource(options): UpdateJSXSourceResult
AST-based utility for programmatically updating JSX/TSX source code. Intelligently merges styles and classNames without breaking existing code.
Parameters
interface UpdateJSXSourceOptions {
sourceCode: string; // Original JSX/TSX source code
lineNumber: number; // Target element's line number (1-indexed)
columnNumber: number; // Target element's column number (0-indexed)
tagName: string; // HTML tag name for validation
styles?: Record<string, string>; // Inline styles to apply
className?: string; // Class names to add
}Returns
interface UpdateJSXSourceResult {
success: boolean;
code: string; // Updated source code
message?: string; // Error or success message
}Examples
Add inline styles:
import { updateJSXSource } from "@promakeai/inspector-hook";
const sourceCode = `
function MyComponent() {
return <div className="container">Hello</div>;
}
`;
const result = updateJSXSource({
sourceCode,
lineNumber: 3,
columnNumber: 9,
tagName: "div",
styles: {
backgroundColor: "red",
padding: "20px",
},
});
if (result.success) {
console.log(result.code);
// Output:
// function MyComponent() {
// return <div className="container" style={{ backgroundColor: "red", padding: "20px" }}>Hello</div>;
// }
}Add class names:
const result = updateJSXSource({
sourceCode,
lineNumber: 3,
columnNumber: 9,
tagName: "div",
className: "bg-red-500 p-4",
});
// Result: <div className="container bg-red-500 p-4">Hello</div>Merge with existing styles:
const sourceCode = `
<div style={{ color: 'blue', margin: '10px' }}>
Hello
</div>
`;
const result = updateJSXSource({
sourceCode,
lineNumber: 2,
columnNumber: 0,
tagName: "div",
styles: {
color: "red", // Overrides existing 'blue'
padding: "20px", // Adds new property
},
});
// Result: <div style={{ margin: "10px", color: "red", padding: "20px" }}>Usage with Inspector callbacks:
const inspector = useInspector(iframeRef, {
onStyleUpdated: async (data) => {
const { element, inlineStyles, tailwindClasses } = data;
const component = element.component;
if (!component?.fileName || !component?.lineNumber) {
console.error("Missing component info");
return;
}
// Fetch original source code
const sourceCode = await fetchSourceCode(component.fileName);
// Update source code
const result = updateJSXSource({
sourceCode,
lineNumber: component.lineNumber,
columnNumber: component.columnNumber || 0,
tagName: element.tagName,
styles: inlineStyles,
className: tailwindClasses.join(" "),
});
if (result.success) {
// Save updated code back to file
await saveSourceCode(component.fileName, result.code);
console.log("✅ Source code updated successfully");
} else {
console.error("❌ Failed to update:", result.message);
}
},
});Features:
- ✅ Preserves existing code structure and formatting
- ✅ Intelligently merges styles without duplicating properties
- ✅ Handles various className formats (string, template literal, expression)
- ✅ Validates tag name to ensure correct element is updated
- ✅ Full TypeScript support with AST-based parsing
- ✅ Supports JSX and TSX files
inspectorHookPlugin(): Plugin
Vite plugin that configures Babel packages for browser compatibility. Required when using updateJSXSource in the browser.
Usage
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
export default defineConfig({
plugins: [
inspectorHookPlugin(), // Must be included before react()
react(),
],
});What it does:
- Defines
process.env.NODE_ENVfor Babel compatibility - Sets
process.platformto "browser" - Sets
process.versionfor version checks
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.
📘 Types
All types are re-exported from @promakeai/inspector-types for convenience.
Core Types
// Component information from React Fiber or vite-plugin-component-debugger
interface ComponentInfo {
id?: string;
name?: string;
path?: string;
fileName?: string;
lineNumber?: number;
columnNumber?: number;
component?: string;
}
// Element position on screen
interface ElementPosition {
top: number;
left: number;
width: number;
height: number;
}
// Element reference for parent/child tracking
interface ElementReference {
id: string;
tagName: string;
className: string;
selector?: string;
}
// Selected element data
interface SelectedElementData {
id: string;
tagName: string;
className: string;
component: ComponentInfo | null;
position: ElementPosition;
isTextNode?: boolean;
textContent?: string;
isImageNode?: boolean;
imageUrl?: string;
selector?: string;
currentRoute?: string;
parents?: ElementReference[]; // [parent, grandparent, great-grandparent]
children?: ElementReference[]; // Array of child elements
}Style Types
// Style changes with all supported properties
interface StyleChanges {
// Layout
backgroundColor?: string;
height?: string;
width?: string;
display?: string;
opacity?: string;
flex?: string;
flexDirection?: string;
justifyContent?: string;
alignItems?: string;
// Image
objectFit?: string;
// Text
color?: string;
fontSize?: string;
fontWeight?: string;
fontFamily?: string;
textAlign?: string;
textDecoration?: string;
// Border
borderRadius?: string;
borderWidth?: string;
borderColor?: string;
borderStyle?: string;
// Spacing - Vertical/Horizontal (combined)
paddingVertical?: string;
paddingHorizontal?: string;
marginVertical?: string;
marginHorizontal?: string;
// Spacing - Individual sides
paddingTop?: string;
paddingRight?: string;
paddingBottom?: string;
paddingLeft?: string;
marginTop?: string;
marginRight?: string;
marginBottom?: string;
marginLeft?: string;
}Full InspectorLabels Interface
The InspectorLabels interface contains 100+ customizable text labels. Here are the main categories:
interface InspectorLabels {
// Text Editor (4 labels)
editText?: string;
textContentLabel?: string;
textPlaceholder?: string;
linkUrlLabel?: string;
updateText?: string;
// Image Editor (4 labels)
editImage?: string;
imageUploadTitle?: string;
imageUploadHint?: string;
updateImage?: string;
// Prompt Input (1 label)
promptPlaceholder?: string;
// Style Editor Sections (6 labels)
styleEditorTitle?: string;
layoutSectionTitle?: string;
displaySectionTitle?: string;
imageSectionTitle?: string;
textSectionTitle?: string;
borderSectionTitle?: string;
spacingSectionTitle?: string;
// Style Properties (26 labels)
backgroundColorLabel?: string;
heightLabel?: string;
widthLabel?: string;
displayLabel?: string;
opacityLabel?: string;
// ... and 21 more property labels
// Element Types (10 labels)
elementContainer?: string;
elementText?: string;
elementImage?: string;
elementButton?: string;
// ... and 6 more element type labels
// Dropdown Options (50+ labels)
// Display options (6)
displayBlock?: string;
displayInline?: string;
// ...
// Flex direction options (4)
flexDirectionRow?: string;
// ...
// Justify content options (6)
justifyContentFlexStart?: string;
// ...
// Align items options (5)
alignItemsFlexStart?: string;
// ...
// Font size options (9)
fontSizeXS?: string;
// ...
// Font weight options (8)
fontWeightThin?: string;
// ...
// Text decoration options (4)
textDecorationNone?: string;
// ...
// Border style options (9)
borderStyleSolid?: string;
// ...
// Object fit options (5)
objectFitContain?: string;
// ...
// Action Buttons (3 labels)
saveButton?: string;
resetButton?: string;
cancelButton?: string;
// Status Messages (3 labels)
unsavedChangesText?: string;
savingText?: string;
hintText?: string;
// Unsaved Changes Dialog (5 labels)
unsavedDialogTitle?: string;
unsavedDialogMessage?: string;
saveChangesButton?: string;
discardChangesButton?: string;
continueEditingButton?: string;
// Tab Names (3 labels)
textTabLabel?: string;
imageTabLabel?: string;
styleTabLabel?: string;
// Badge (2 labels)
badgeText?: string;
badgeUrl?: string;
}Full InspectorTheme Interface
The InspectorTheme interface contains 30+ customizable color properties:
interface InspectorTheme {
// Control Box (3 colors)
backgroundColor?: string; // Default: #ffffff
textColor?: string; // Default: #111827
secondaryTextColor?: string; // Default: #6b7280
// Buttons (7 colors)
buttonColor?: string; // Default: #4417db
buttonTextColor?: string; // Default: #ffffff
buttonHoverColor?: string; // Default: #3a13c0
secondaryButtonColor?: string; // Default: #f3f4f6
secondaryButtonTextColor?: string; // Default: #6b7280
secondaryButtonHoverColor?: string; // Default: #e5e7eb
dangerButtonColor?: string; // Default: #ef4444
dangerButtonTextColor?: string; // Default: #ffffff
// Inputs (5 colors)
inputBackgroundColor?: string; // Default: #f9fafb
inputTextColor?: string; // Default: #111827
inputBorderColor?: string; // Default: #d1d5db
inputFocusBorderColor?: string; // Default: #4417db
inputPlaceholderColor?: string; // Default: #9ca3af
// Borders & Dividers (1 color)
borderColor?: string; // Default: #e5e7eb
// Status Colors (3 colors)
warningColor?: string; // Default: #f59e0b
successColor?: string; // Default: #10b981
errorColor?: string; // Default: #ef4444
// Tabs (5 colors)
tabContainerBg?: string; // Default: #f9fafb
tabActiveBg?: string; // Default: #4417db
tabInactiveBg?: string; // Default: transparent
tabActiveColor?: string; // Default: #ffffff
tabInactiveColor?: string; // Default: #6b7280
// Badge (3 colors)
badgeGradientStart?: string; // Default: #411E93
badgeGradientEnd?: string; // Default: #E87C85
badgeTextColor?: string; // Default: #ffffff
// Overlay (2 colors)
overlayColor?: string; // Default: #4417db
overlayOpacity?: number; // Default: 0.2
// Dialog (4 colors)
dialogBackdropColor?: string; // Default: rgba(0, 0, 0, 0.6)
dialogBackgroundColor?: string; // Default: #ffffff
dialogTextColor?: string; // Default: #111827
dialogSecondaryTextColor?: string; // Default: #6b7280
}🔧 Advanced Examples
Complete Parent Application Example
import { useInspector, updateJSXSource } from "@promakeai/inspector-hook";
import { useRef, useState } from "react";
function ParentApp() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const [selectedElement, setSelectedElement] = useState(null);
const inspector = useInspector(
iframeRef,
{
// Element selection
onElementSelected: (data) => {
setSelectedElement(data);
console.log("Selected:", data);
},
// Style updates
onStyleUpdated: async (data) => {
const component = data.element.component;
if (!component?.fileName) return;
// Fetch source code from your backend
const sourceCode = await fetch(
`/api/source/${component.fileName}`
).then((r) => r.text());
// Update JSX source
const result = updateJSXSource({
sourceCode,
lineNumber: component.lineNumber!,
columnNumber: component.columnNumber || 0,
tagName: data.element.tagName,
styles: data.inlineStyles,
className: data.tailwindClasses.join(" "),
});
if (result.success) {
// Save back to your backend
await fetch(`/api/source/${component.fileName}`, {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ code: result.code }),
});
alert("✅ Styles saved successfully!");
}
},
// Text updates
onTextUpdated: async (data) => {
console.log("Text changed:", data.text);
// Save to your backend or update state
},
// Image uploads
onImageUpdated: async (data) => {
// Upload image to your storage
const formData = new FormData();
const blob = await fetch(data.imageData).then((r) => r.blob());
formData.append("image", blob, data.imageFile.name);
const response = await fetch("/api/upload-image", {
method: "POST",
body: formData,
});
const { url } = await response.json();
console.log("Image uploaded:", url);
},
// AI prompts
onPromptSubmitted: async (data) => {
// Send to your AI service
const response = await fetch("/api/ai-generate", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
prompt: data.prompt,
element: data.element,
}),
});
const result = await response.json();
console.log("AI result:", result);
},
// URL navigation
onUrlChange: (data) => {
console.log("Navigated to:", data.pathname);
},
// Error handling
onError: (data) => {
console.error("Error in iframe:", data);
// Show error notification
},
// Inspector closed
onInspectorClosed: () => {
console.log("Inspector closed");
setSelectedElement(null);
},
},
// Custom labels (i18n)
{
editText: "Edit Text",
editImage: "Edit Image",
styleEditorTitle: "Styles",
updateText: "Update",
cancelButton: "Cancel",
},
// Custom theme
{
buttonColor: "#ff6b6b",
buttonHoverColor: "#ee5a5a",
overlayColor: "#ff6b6b",
}
);
return (
<div style={{ padding: "20px" }}>
<div style={{ marginBottom: "20px", display: "flex", gap: "10px" }}>
<button onClick={() => inspector.toggleInspector()}>
{inspector.isInspecting ? "⏹ Stop" : "▶ Start"} Inspector
</button>
<button onClick={() => inspector.showContentInput(true)}>
📝 Edit Text
</button>
<button onClick={() => inspector.showImageInput(true)}>
🖼️ Edit Image
</button>
<button onClick={() => inspector.showStyleEditor(true)}>
🎨 Edit Styles
</button>
<button onClick={() => inspector.setBadgeVisible(true)}>
🏷️ Show Badge
</button>
<button
onClick={() =>
inspector.highlightElement("some-id", {
color: "#ff0000",
duration: 5000,
})
}
>
🔦 Highlight Element
</button>
</div>
{selectedElement && (
<div
style={{
marginBottom: "20px",
padding: "10px",
background: "#f0f0f0",
}}
>
<strong>Selected:</strong> {selectedElement.tagName}
{selectedElement.component?.name && (
<span> ({selectedElement.component.name})</span>
)}
</div>
)}
<iframe
ref={iframeRef}
src="http://localhost:5173"
style={{
width: "100%",
height: "800px",
border: "1px solid #ccc",
borderRadius: "8px",
}}
/>
</div>
);
}
export default ParentApp;Programmatic Element Highlighting
// Highlight element by ID
inspector.highlightElement("inspector-id-123", {
duration: 5000,
color: "#ff0000",
animation: "pulse",
});
// Highlight and navigate to a specific route first
inspector.highlightElement("inspector-id-123", {
targetRoute: "/products",
scrollIntoView: true,
});
// Highlight using full element data
inspector.highlightElement(selectedElementData, {
animation: "fade",
duration: 2000,
});Getting Element Information
// Request element info
inspector.getElementByInspectorId("inspector-id-123");
// Handle response
const inspector = useInspector(iframeRef, {
onElementInfoReceived: (data) => {
if (data.found && data.element) {
console.log("Component:", data.element.component);
console.log("Position:", data.element.position);
console.log("Parents:", data.element.parents);
console.log("Children:", data.element.children);
} else {
console.error("Element not found:", data.error);
}
},
});🤝 Integration with @promakeai/inspector
This package is designed to work seamlessly with @promakeai/inspector. Here's the complete setup:
Child Application (iframe content)
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { inspectorDebugger } from "@promakeai/inspector/plugin";
export default defineConfig({
plugins: [inspectorDebugger({ enabled: true }), react()],
});// main.tsx
import React from "react";
import ReactDOM from "react-dom/client";
import { Inspector } from "@promakeai/inspector";
import "@promakeai/inspector/inspector.css";
import App from "./App";
ReactDOM.createRoot(document.getElementById("root")!).render(
<React.StrictMode>
<Inspector />
<App />
</React.StrictMode>
);Parent Application (iframe host)
// vite.config.ts
import { defineConfig } from "vite";
import react from "@vitejs/plugin-react";
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
export default defineConfig({
plugins: [inspectorHookPlugin(), react()],
});// ParentApp.tsx
import { useInspector } from "@promakeai/inspector-hook";
import { useRef } from "react";
function ParentApp() {
const iframeRef = useRef<HTMLIFrameElement>(null);
const inspector = useInspector(iframeRef, {
onElementSelected: (data) => console.log("Selected:", data),
onStyleUpdated: (data) => console.log("Style updated:", data),
});
return (
<div>
<button onClick={() => inspector.toggleInspector()}>
Toggle Inspector
</button>
<iframe ref={iframeRef} src="http://localhost:5173" />
</div>
);
}📋 Requirements
- React:
>=18.0.0 - React DOM:
>=18.0.0 - Vite:
>=5.0.0(optional, only if using the Vite plugin)
📝 License
MIT
🐛 Troubleshooting
Messages not being received
Make sure the iframe is fully loaded before sending messages:
<iframe
ref={iframeRef}
src="http://localhost:5173"
onLoad={() => {
console.log("Iframe loaded, ready to communicate");
}}
/>Babel errors when using updateJSXSource
Make sure you've added the Vite plugin:
import { inspectorHookPlugin } from "@promakeai/inspector-hook";
export default defineConfig({
plugins: [
inspectorHookPlugin(), // Required!
react(),
],
});Styles not updating correctly
Ensure you're providing the correct line and column numbers from the component info:
const result = updateJSXSource({
sourceCode,
lineNumber: data.element.component.lineNumber, // ✅ Use from component info
columnNumber: data.element.component.columnNumber || 0,
tagName: data.element.tagName,
styles: data.inlineStyles,
});🚀 Changelog
v1.0.1
- Initial release
useInspectorhook for iframe communicationupdateJSXSourceutility for AST-based code updatesinspectorHookPluginVite plugin for Babel configuration- Full TypeScript support
- Comprehensive callback system
- Theme and label customization
📞 Support
For issues, questions, or contributions, please visit:
- GitHub: github.com/promakeai/inspector
- Documentation: promake.ai/docs
Made with ❤️ by Promake
