npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2025 – Pkg Stats / Ryan Hefner

@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:

  1. useInspector - React hook for controlling an iframe-embedded Inspector
  2. updateJSXSource - AST-based utility for updating JSX/TSX source code
  3. inspectorHookPlugin - 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); // Stop

startInspecting()

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 editor
  • updateImmediately: 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 editor
  • updateImmediately: 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 object
  • options: 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_ENV for Babel compatibility
  • Sets process.platform to "browser"
  • Sets process.version for 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
  • useInspector hook for iframe communication
  • updateJSXSource utility for AST-based code updates
  • inspectorHookPlugin Vite plugin for Babel configuration
  • Full TypeScript support
  • Comprehensive callback system
  • Theme and label customization

📞 Support

For issues, questions, or contributions, please visit:


Made with ❤️ by Promake