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