@zargaryanvh/react-component-inspector 1.0.3 → 1.0.6
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/LICENSE +21 -21
- package/README.md +321 -274
- package/dist/InspectionContext.js +20 -45
- package/dist/InspectionHighlight.js +1 -1
- package/dist/InspectionTooltip.js +52 -28
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/inspection.d.ts +22 -0
- package/dist/inspection.js +76 -3
- package/package.json +55 -54
package/LICENSE
CHANGED
|
@@ -1,21 +1,21 @@
|
|
|
1
|
-
MIT License
|
|
2
|
-
|
|
3
|
-
Copyright (c) 2026
|
|
4
|
-
|
|
5
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
-
in the Software without restriction, including without limitation the rights
|
|
8
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
-
furnished to do so, subject to the following conditions:
|
|
11
|
-
|
|
12
|
-
The above copyright notice and this permission notice shall be included in all
|
|
13
|
-
copies or substantial portions of the Software.
|
|
14
|
-
|
|
15
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
-
SOFTWARE.
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,274 +1,321 @@
|
|
|
1
|
-
# React Component Inspector
|
|
2
|
-
|
|
3
|
-
> **A powerful development tool for inspecting React components with AI-friendly metadata extraction. Fully designed by Cursor AI.**
|
|
4
|
-
|
|
5
|
-
## 🎯 What is This?
|
|
6
|
-
|
|
7
|
-
React Component Inspector is a development-only tool that helps you identify, inspect, and extract detailed metadata from React components in your application. It's designed to work seamlessly with AI coding assistants (like Cursor) by providing structured, copyable metadata about any component in your UI.
|
|
8
|
-
|
|
9
|
-
## 🔍 What Problem Does It Solve?
|
|
10
|
-
|
|
11
|
-
### The Challenge
|
|
12
|
-
When working with AI assistants to fix or modify frontend code, you often need to:
|
|
13
|
-
- Identify which component is responsible for a specific UI element
|
|
14
|
-
- Understand the component's props, variants, and usage context
|
|
15
|
-
- Get the exact file path and component structure
|
|
16
|
-
- Extract CSS selectors and element identifiers for precise targeting
|
|
17
|
-
|
|
18
|
-
**Without this tool**, you'd need to:
|
|
19
|
-
- Manually inspect the DOM
|
|
20
|
-
- Search through codebases
|
|
21
|
-
- Guess component names and file locations
|
|
22
|
-
- Manually extract element information
|
|
23
|
-
|
|
24
|
-
### The Solution
|
|
25
|
-
React Component Inspector provides:
|
|
26
|
-
- **One-click component identification** -
|
|
27
|
-
- **
|
|
28
|
-
- **
|
|
29
|
-
- **
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
-
|
|
46
|
-
-
|
|
47
|
-
-
|
|
48
|
-
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
```
|
|
107
|
-
|
|
108
|
-
###
|
|
109
|
-
|
|
110
|
-
```
|
|
111
|
-
"
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
```
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
```
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
import {
|
|
155
|
-
|
|
156
|
-
function
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
-
|
|
221
|
-
-
|
|
222
|
-
-
|
|
223
|
-
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
##
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
-
|
|
260
|
-
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
1
|
+
# React Component Inspector
|
|
2
|
+
|
|
3
|
+
> **A powerful development tool for inspecting React components with AI-friendly metadata extraction. Fully designed by Cursor AI.**
|
|
4
|
+
|
|
5
|
+
## 🎯 What is This?
|
|
6
|
+
|
|
7
|
+
React Component Inspector is a development-only tool that helps you identify, inspect, and extract detailed metadata from React components in your application. It's designed to work seamlessly with AI coding assistants (like Cursor) by providing structured, copyable metadata about any component in your UI.
|
|
8
|
+
|
|
9
|
+
## 🔍 What Problem Does It Solve?
|
|
10
|
+
|
|
11
|
+
### The Challenge
|
|
12
|
+
When working with AI assistants to fix or modify frontend code, you often need to:
|
|
13
|
+
- Identify which component is responsible for a specific UI element
|
|
14
|
+
- Understand the component's props, variants, and usage context
|
|
15
|
+
- Get the exact file path and component structure
|
|
16
|
+
- Extract CSS selectors and element identifiers for precise targeting
|
|
17
|
+
|
|
18
|
+
**Without this tool**, you'd need to:
|
|
19
|
+
- Manually inspect the DOM
|
|
20
|
+
- Search through codebases
|
|
21
|
+
- Guess component names and file locations
|
|
22
|
+
- Manually extract element information
|
|
23
|
+
|
|
24
|
+
### The Solution
|
|
25
|
+
React Component Inspector provides:
|
|
26
|
+
- **One-click component identification** - Hold CTRL and hover over any element
|
|
27
|
+
- **Margin, padding & gap inspection** - Hold CTRL+ALT to see spacing (margin, padding, flex/grid gap) and copy it for Cursor
|
|
28
|
+
- **Rich metadata extraction** - Component name, props, file path, usage context
|
|
29
|
+
- **AI-optimized format** - Copy-paste ready metadata for AI assistants (Component, Margin, Padding, Gap)
|
|
30
|
+
- **How to find in code** - Tooltip shows DOM path, target element, and step-by-step how to find and modify in Cursor
|
|
31
|
+
- **Zero production overhead** - Completely disabled in production builds
|
|
32
|
+
|
|
33
|
+
## 📊 What Data Does It Provide?
|
|
34
|
+
|
|
35
|
+
When you hold **CTRL** and hover over an element, a tooltip appears showing component metadata, copy buttons (Component / Margin / Padding / Gap), and the "How to find in code" section:
|
|
36
|
+
|
|
37
|
+

|
|
38
|
+
|
|
39
|
+
When you inspect a component, you get:
|
|
40
|
+
|
|
41
|
+
### Element Identification
|
|
42
|
+
- Element type (HTML tag)
|
|
43
|
+
- Element text/label content
|
|
44
|
+
- Element ID
|
|
45
|
+
- CSS classes
|
|
46
|
+
- CSS selector
|
|
47
|
+
- Position and size
|
|
48
|
+
- Role and accessibility attributes
|
|
49
|
+
|
|
50
|
+
### Component Metadata
|
|
51
|
+
- Component name
|
|
52
|
+
- Component ID (unique instance identifier)
|
|
53
|
+
- Variant (if applicable)
|
|
54
|
+
- Usage path (component hierarchy)
|
|
55
|
+
- Instance index
|
|
56
|
+
- Props signature (key props affecting behavior)
|
|
57
|
+
- Source file path
|
|
58
|
+
|
|
59
|
+
### Example Output (Copy Component)
|
|
60
|
+
```
|
|
61
|
+
=== TYPE: COMPONENT ===
|
|
62
|
+
|
|
63
|
+
=== ELEMENT IDENTIFICATION ===
|
|
64
|
+
Element Type: button
|
|
65
|
+
Element Text/Label: "Save Transaction"
|
|
66
|
+
...
|
|
67
|
+
DOM Path: body > div#root > div.MuiBox-root > button.MuiButton-root
|
|
68
|
+
Parent: div.MuiBox-root
|
|
69
|
+
Role in tree: leaf element; parent: div.MuiBox-root
|
|
70
|
+
|
|
71
|
+
=== TARGET (use this to instruct Cursor) ===
|
|
72
|
+
TARGET: The SaveButton with class MuiButton-root - position (450, 320), size 120x36px. It is the element in the DOM path above, NOT a child.
|
|
73
|
+
|
|
74
|
+
=== HOW TO FIND AND MODIFY THIS COMPONENT IN CODE ===
|
|
75
|
+
1. Use the DOM Path to locate the correct element...
|
|
76
|
+
2. Open src/components/buttons/SaveButton.tsx and find the component...
|
|
77
|
+
3. Modify the component's props, sx, or styles as needed.
|
|
78
|
+
|
|
79
|
+
=== COMPONENT METADATA ===
|
|
80
|
+
...
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
When you copy **Margin**, **Padding**, or **Gap**, you get the same structure with current values and instructions for changing that spacing in code (e.g. `sx={{ m: 1 }}`, `sx={{ p: 0.5 }}`, `sx={{ gap: 1 }}`).
|
|
84
|
+
|
|
85
|
+
## 🔎 How to Find and Modify in Cursor
|
|
86
|
+
|
|
87
|
+
The tooltip shows a **“How to find in code”** section (same on desktop and mobile):
|
|
88
|
+
|
|
89
|
+
- **DOM Path** – Full path from `body` to the element (e.g. `body > div#root > div.MuiBox-root > main.MuiBox-root > …`)
|
|
90
|
+
- **Parent** – Direct parent selector
|
|
91
|
+
- **Role in tree** – e.g. “has 3 child element(s); parent: div.MuiStack-root”
|
|
92
|
+
- **TARGET** – One-line description for Cursor: “The Card with class MuiPaper-root – the element with margin 0px 0px 16px 0px. It is the PARENT in the DOM path above, NOT a child.”
|
|
93
|
+
- **Steps** – Numbered steps: use DOM path, open source file, then how to change (margin, padding, gap, or component)
|
|
94
|
+
|
|
95
|
+
When you click **Copy Component**, **Copy Margin**, **Copy Padding**, or **Copy Gap**, the clipboard gets the full block (element identification, DOM path, TARGET, and “How to find and modify in code”). Paste that into Cursor and ask it to change the component, margin, padding, or gap; the text is written so Cursor can locate the right element and apply the change.
|
|
96
|
+
|
|
97
|
+
## 🚀 How to Use This Data for AI-Powered Frontend Optimization
|
|
98
|
+
|
|
99
|
+
### 1. **Precise Component Targeting**
|
|
100
|
+
Copy the metadata and ask your AI assistant:
|
|
101
|
+
```
|
|
102
|
+
"I need to modify the SaveButton component. Here's the metadata:
|
|
103
|
+
[paste metadata]
|
|
104
|
+
|
|
105
|
+
Change the button color to green and add an icon."
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
### 2. **Context-Aware Refactoring**
|
|
109
|
+
The usage path tells you exactly where the component is used:
|
|
110
|
+
```
|
|
111
|
+
"Refactor the TransactionCard component used in:
|
|
112
|
+
ActivityPage > TransactionList > TransactionCard
|
|
113
|
+
|
|
114
|
+
Make it accept a new 'priority' prop."
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 3. **CSS Selector Generation**
|
|
118
|
+
Use the CSS selector for automated testing or styling:
|
|
119
|
+
```javascript
|
|
120
|
+
// The metadata provides: button#save-button
|
|
121
|
+
const saveButton = document.querySelector('button#save-button');
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### 4. **Component Discovery**
|
|
125
|
+
Find all instances of a component:
|
|
126
|
+
```
|
|
127
|
+
"Find all instances of TransactionCard in the codebase.
|
|
128
|
+
The component is defined in: src/components/transactions/TransactionCard.tsx"
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
### 5. **AI-Powered Debugging**
|
|
132
|
+
Share component metadata with AI to debug issues:
|
|
133
|
+
```
|
|
134
|
+
"This button isn't working. Component metadata:
|
|
135
|
+
[paste metadata]
|
|
136
|
+
|
|
137
|
+
The onClick handler should be in: src/components/buttons/SaveButton.tsx"
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
## 📦 Installation
|
|
141
|
+
|
|
142
|
+
```bash
|
|
143
|
+
npm install react-component-inspector
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 🔧 Setup
|
|
147
|
+
|
|
148
|
+
### 1. Wrap Your App
|
|
149
|
+
|
|
150
|
+
```tsx
|
|
151
|
+
import { InspectionProvider } from 'react-component-inspector';
|
|
152
|
+
import { InspectionTooltip } from 'react-component-inspector';
|
|
153
|
+
import { InspectionHighlight } from 'react-component-inspector';
|
|
154
|
+
import { setupInterceptors } from 'react-component-inspector';
|
|
155
|
+
|
|
156
|
+
function App() {
|
|
157
|
+
// Setup request interceptors (optional - blocks API calls when CTRL is held)
|
|
158
|
+
useEffect(() => {
|
|
159
|
+
setupInterceptors();
|
|
160
|
+
}, []);
|
|
161
|
+
|
|
162
|
+
return (
|
|
163
|
+
<InspectionProvider>
|
|
164
|
+
<YourApp />
|
|
165
|
+
<InspectionTooltip />
|
|
166
|
+
<InspectionHighlight />
|
|
167
|
+
</InspectionProvider>
|
|
168
|
+
);
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
### 2. Add Metadata to Components
|
|
173
|
+
|
|
174
|
+
#### Option A: Using the Hook (Recommended)
|
|
175
|
+
|
|
176
|
+
```tsx
|
|
177
|
+
import { useInspectionMetadata } from 'react-component-inspector';
|
|
178
|
+
|
|
179
|
+
function MyButton({ variant, disabled, onClick }) {
|
|
180
|
+
const inspectionProps = useInspectionMetadata({
|
|
181
|
+
componentName: "MyButton",
|
|
182
|
+
variant: variant,
|
|
183
|
+
usagePath: "HomePage > ActionBar",
|
|
184
|
+
props: { variant, disabled },
|
|
185
|
+
sourceFile: "src/components/MyButton.tsx",
|
|
186
|
+
});
|
|
187
|
+
|
|
188
|
+
return (
|
|
189
|
+
<button {...inspectionProps} onClick={onClick}>
|
|
190
|
+
Click me
|
|
191
|
+
</button>
|
|
192
|
+
);
|
|
193
|
+
}
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
#### Option B: Using the Wrapper Component
|
|
197
|
+
|
|
198
|
+
```tsx
|
|
199
|
+
import { InspectionWrapper } from 'react-component-inspector';
|
|
200
|
+
|
|
201
|
+
function MyComponent({ variant, children }) {
|
|
202
|
+
return (
|
|
203
|
+
<InspectionWrapper
|
|
204
|
+
componentName="MyComponent"
|
|
205
|
+
variant={variant}
|
|
206
|
+
usagePath="HomePage > ContentArea"
|
|
207
|
+
props={{ variant }}
|
|
208
|
+
sourceFile="src/components/MyComponent.tsx"
|
|
209
|
+
>
|
|
210
|
+
<div>{children}</div>
|
|
211
|
+
</InspectionWrapper>
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
```
|
|
215
|
+
|
|
216
|
+
#### Option C: Using Data Attributes (Manual)
|
|
217
|
+
|
|
218
|
+
```tsx
|
|
219
|
+
<div
|
|
220
|
+
data-inspection-name="MyComponent"
|
|
221
|
+
data-inspection-id="my-component-0"
|
|
222
|
+
data-inspection-variant="primary"
|
|
223
|
+
data-inspection-usage-path="HomePage > ContentArea"
|
|
224
|
+
data-inspection-instance="0"
|
|
225
|
+
data-inspection-props="variant=primary"
|
|
226
|
+
data-inspection-file="src/components/MyComponent.tsx"
|
|
227
|
+
>
|
|
228
|
+
Content
|
|
229
|
+
</div>
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
## 🎮 Usage
|
|
233
|
+
|
|
234
|
+
### Keyboard shortcuts (desktop)
|
|
235
|
+
|
|
236
|
+
| Shortcut | Action |
|
|
237
|
+
|----------|--------|
|
|
238
|
+
| **Hold CTRL** | Enter inspection mode. Hover to inspect component (box/element). |
|
|
239
|
+
| **Release CTRL** | Exit inspection mode and clear tooltip. |
|
|
240
|
+
| **Hold CTRL+ALT** | Enter margin/padding mode. See orange (margin), green (padding), purple (gap). |
|
|
241
|
+
| **CTRL+H** | Lock tooltip position so you can click copy buttons. |
|
|
242
|
+
| **CTRL+Shift+R** | Hard refresh (browser default; not captured by the inspector). |
|
|
243
|
+
|
|
244
|
+
### Basic flow
|
|
245
|
+
|
|
246
|
+
1. **Activate**: Hold `CTRL` (or `Cmd` on Mac)
|
|
247
|
+
2. **Inspect**: Hover over any element — tooltip shows component metadata and **How to find in code**
|
|
248
|
+
3. **Copy**: Use the copy icon (component) or **Margin** / **Padding** / **Gap** buttons to copy metadata to clipboard
|
|
249
|
+
4. **Lock** (optional): Press `CTRL+H` to lock the tooltip so it doesn’t follow the cursor
|
|
250
|
+
5. **Deactivate**: Release `CTRL` to exit inspection mode
|
|
251
|
+
|
|
252
|
+
### Margin, padding & gap inspection
|
|
253
|
+
|
|
254
|
+
- **Hold CTRL+ALT** (while holding CTRL) to enter margin/padding mode. Overlays show:
|
|
255
|
+
- **Orange** = margin (outside the element)
|
|
256
|
+
- **Green** = padding (inside the element)
|
|
257
|
+
- **Purple dashed** = parent’s flex/grid gap (when the parent has `gap`)
|
|
258
|
+
- If the current element has no margin, **dashed orange** outlines show ancestors that have margin; **click an outline** to inspect that ancestor.
|
|
259
|
+
- Use the **Margin**, **Padding**, or **Gap** copy buttons in the tooltip to copy Cursor-ready text for that spacing.
|
|
260
|
+
- The tooltip always shows **DOM Path**, **Parent**, **Role in tree**, **TARGET**, and **How to find** steps so you (and Cursor) know exactly which element to change.
|
|
261
|
+
|
|
262
|
+
## 🛡️ Safety Features
|
|
263
|
+
|
|
264
|
+
- **Development Only**: Completely disabled in production (`NODE_ENV !== "development"`)
|
|
265
|
+
- **Request Blocking**: When CTRL is held, all API/Firebase requests are blocked to prevent accidental mutations
|
|
266
|
+
- **Zero Overhead**: No code included in production builds
|
|
267
|
+
- **Non-Intrusive**: Doesn't modify your components or affect their behavior
|
|
268
|
+
|
|
269
|
+
## 📚 Documentation
|
|
270
|
+
|
|
271
|
+
- [Quick Start Guide](./docs/QUICK_START.md)
|
|
272
|
+
- [API Reference](./docs/API.md)
|
|
273
|
+
- [Advanced Usage](./docs/ADVANCED.md)
|
|
274
|
+
- [AI Integration Guide](./docs/AI_INTEGRATION.md)
|
|
275
|
+
|
|
276
|
+
## 🎨 Features
|
|
277
|
+
|
|
278
|
+
- ✅ Visual component highlighting (box/element outline)
|
|
279
|
+
- ✅ **Margin, padding & gap inspection** (hold CTRL+ALT) with orange/green/purple overlays
|
|
280
|
+
- ✅ **Ancestor margin detection** – when the element has no margin, dashed outlines show ancestors with margin; click to inspect
|
|
281
|
+
- ✅ **Copy Component / Margin / Padding / Gap** – Cursor-ready text with DOM path, TARGET, and how to find in code
|
|
282
|
+
- ✅ **“How to find in code” in tooltip** – DOM path, parent, role in tree, TARGET, and numbered steps (desktop & mobile)
|
|
283
|
+
- ✅ Rich metadata extraction
|
|
284
|
+
- ✅ Automatic component detection (with or without `data-inspection-*`)
|
|
285
|
+
- ✅ CSS selector generation
|
|
286
|
+
- ✅ Usage path tracking
|
|
287
|
+
- ✅ Instance indexing
|
|
288
|
+
- ✅ Props signature extraction
|
|
289
|
+
- ✅ Request blocking during inspection (when CTRL is held)
|
|
290
|
+
- ✅ Production-safe (zero overhead)
|
|
291
|
+
|
|
292
|
+
## 🤖 Designed by Cursor AI
|
|
293
|
+
|
|
294
|
+
This tool was fully designed and developed using [Cursor](https://cursor.sh), an AI-powered code editor. The entire codebase, architecture, and documentation were created through AI-assisted development, demonstrating the power of AI in building developer tools.
|
|
295
|
+
|
|
296
|
+
## 📄 License
|
|
297
|
+
|
|
298
|
+
MIT
|
|
299
|
+
|
|
300
|
+
## 🤝 Contributing
|
|
301
|
+
|
|
302
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
303
|
+
|
|
304
|
+
## ⚠️ Important Notes
|
|
305
|
+
|
|
306
|
+
- This tool is **development-only** and will not work in production
|
|
307
|
+
- Requires Material-UI (MUI) for the tooltip UI components
|
|
308
|
+
- Works best with TypeScript but supports JavaScript
|
|
309
|
+
- Request interceptors are optional but recommended for safety
|
|
310
|
+
|
|
311
|
+
## 💡 Tips for AI Integration
|
|
312
|
+
|
|
313
|
+
1. **Always copy the full metadata** - It contains all context needed
|
|
314
|
+
2. **Include the usage path** - Helps AI understand component hierarchy
|
|
315
|
+
3. **Share the source file** - Directs AI to the exact location
|
|
316
|
+
4. **Use CSS selectors** - For precise element targeting in AI prompts
|
|
317
|
+
5. **Copy element text** - Helps AI understand component purpose
|
|
318
|
+
|
|
319
|
+
---
|
|
320
|
+
|
|
321
|
+
**Made with ❤️ using Cursor AI**
|
|
@@ -8,19 +8,18 @@ const InspectionContext = createContext(undefined);
|
|
|
8
8
|
*/
|
|
9
9
|
export const InspectionProvider = ({ children }) => {
|
|
10
10
|
const [ctrlHeld, setCtrlHeld] = useState(false);
|
|
11
|
-
const [isStickyInspection, setIsStickyInspection] = useState(false);
|
|
12
|
-
const isInspectionActive = ctrlHeld || isStickyInspection;
|
|
13
|
-
const [isLocked, setIsLocked] = useState(false);
|
|
14
11
|
const [isMarginPaddingMode, setIsMarginPaddingMode] = useState(false);
|
|
12
|
+
const [isLocked, setIsLocked] = useState(false);
|
|
13
|
+
// Inspection active only when CTRL is held (or CTRL+ALT for margin/padding). Release CTRL = stop inspecting.
|
|
14
|
+
const isInspectionActive = ctrlHeld || isMarginPaddingMode;
|
|
15
15
|
const [hoveredComponent, setHoveredComponentState] = useState(null);
|
|
16
16
|
const [hoveredElement, setHoveredElement] = useState(null);
|
|
17
17
|
// Use refs to always access latest state values in event handlers
|
|
18
18
|
const isInspectionActiveRef = useRef(isInspectionActive);
|
|
19
19
|
const isLockedRef = useRef(isLocked);
|
|
20
|
-
const isStickyInspectionRef = useRef(isStickyInspection);
|
|
21
20
|
const hoveredComponentRef = useRef(hoveredComponent);
|
|
22
21
|
const hKeyPressedRef = useRef(false);
|
|
23
|
-
// Touch support for locking only (
|
|
22
|
+
// Touch support for locking only (double-tap to lock tooltip)
|
|
24
23
|
const lastTapRef = useRef(0);
|
|
25
24
|
const [isMobile, setIsMobile] = useState(false);
|
|
26
25
|
useEffect(() => {
|
|
@@ -43,10 +42,7 @@ export const InspectionProvider = ({ children }) => {
|
|
|
43
42
|
useEffect(() => {
|
|
44
43
|
hoveredComponentRef.current = hoveredComponent;
|
|
45
44
|
}, [hoveredComponent]);
|
|
46
|
-
|
|
47
|
-
isStickyInspectionRef.current = isStickyInspection;
|
|
48
|
-
}, [isStickyInspection]);
|
|
49
|
-
// Only block API/fetch when CTRL is physically held (not when sticky inspection is on)
|
|
45
|
+
// Only block API/fetch when CTRL is physically held
|
|
50
46
|
useEffect(() => {
|
|
51
47
|
setInspectionActive(ctrlHeld);
|
|
52
48
|
}, [ctrlHeld]);
|
|
@@ -74,37 +70,18 @@ export const InspectionProvider = ({ children }) => {
|
|
|
74
70
|
lastTapRef.current = now;
|
|
75
71
|
}
|
|
76
72
|
};
|
|
77
|
-
// Keyboard: CTRL, CTRL+Shift+R (
|
|
73
|
+
// Keyboard: CTRL (hold = inspection), CTRL+Shift+R = hard refresh (do not capture), CTRL+ALT (margin/padding), CTRL+H (lock)
|
|
78
74
|
const handleKeyDown = (e) => {
|
|
79
|
-
//
|
|
75
|
+
// Do NOT capture CTRL+Shift+R - let browser do hard refresh
|
|
80
76
|
if (e.key && e.key.toLowerCase() === "r" && e.ctrlKey && e.shiftKey) {
|
|
81
|
-
e.preventDefault();
|
|
82
|
-
e.stopPropagation();
|
|
83
|
-
if (!e.repeat) {
|
|
84
|
-
setIsStickyInspection(prev => {
|
|
85
|
-
const next = !prev;
|
|
86
|
-
if (!next) {
|
|
87
|
-
setHoveredComponentState(null);
|
|
88
|
-
setHoveredElement(null);
|
|
89
|
-
setIsLocked(false);
|
|
90
|
-
}
|
|
91
|
-
if (process.env.NODE_ENV === "development") {
|
|
92
|
-
console.log("[Inspection] Inspection toggled (Ctrl+Shift+R):", next ? "ON" : "OFF");
|
|
93
|
-
}
|
|
94
|
-
return next;
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
77
|
return;
|
|
98
78
|
}
|
|
99
|
-
//
|
|
100
|
-
if (e.key
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
setCtrlHeld(true);
|
|
106
|
-
}
|
|
107
|
-
return;
|
|
79
|
+
// CTRL+ALT (hold both) - margin/padding/box mode; desktop and mobile
|
|
80
|
+
if (e.key === "Control" && e.altKey && !e.repeat) {
|
|
81
|
+
setIsMarginPaddingMode(true);
|
|
82
|
+
}
|
|
83
|
+
if (e.key === "Alt" && e.ctrlKey && !e.repeat) {
|
|
84
|
+
setIsMarginPaddingMode(true);
|
|
108
85
|
}
|
|
109
86
|
// H key pressed while CTRL is held - lock tooltip position
|
|
110
87
|
if (e.key && e.key.toLowerCase() === "h" && e.ctrlKey) {
|
|
@@ -148,20 +125,18 @@ export const InspectionProvider = ({ children }) => {
|
|
|
148
125
|
}
|
|
149
126
|
return;
|
|
150
127
|
}
|
|
151
|
-
//
|
|
152
|
-
if (e.key
|
|
128
|
+
// ALT or CTRL released - turn off margin/padding mode (CTRL+ALT only, hold to use)
|
|
129
|
+
if (e.key === "Alt") {
|
|
153
130
|
setIsMarginPaddingMode(false);
|
|
154
131
|
}
|
|
155
|
-
// CTRL key released -
|
|
132
|
+
// CTRL key released - stop inspecting and clear state
|
|
156
133
|
if (e.key === "Control") {
|
|
157
134
|
setCtrlHeld(false);
|
|
158
135
|
hKeyPressedRef.current = false;
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
setHoveredElement(null);
|
|
164
|
-
}
|
|
136
|
+
setIsMarginPaddingMode(false);
|
|
137
|
+
setIsLocked(false);
|
|
138
|
+
setHoveredComponentState(null);
|
|
139
|
+
setHoveredElement(null);
|
|
165
140
|
}
|
|
166
141
|
};
|
|
167
142
|
window.addEventListener("keydown", handleKeyDown);
|
|
@@ -19,7 +19,7 @@ const parsePx = (value) => {
|
|
|
19
19
|
};
|
|
20
20
|
/**
|
|
21
21
|
* Highlight overlay that shows the boundary of the hovered component
|
|
22
|
-
* When hold CTRL+
|
|
22
|
+
* When hold CTRL+ALT: orange = margin, green = padding, purple = gap (hold-to-use, release to exit)
|
|
23
23
|
* Otherwise: blue outline for component
|
|
24
24
|
*/
|
|
25
25
|
const stripStyle = (left, top, width, height, color, bg) => ({
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
2
|
import { useState, useEffect, useRef, useMemo } from "react";
|
|
3
|
-
import { Box, Paper, Typography, IconButton, Tooltip as MuiTooltip, Divider
|
|
3
|
+
import { Box, Paper, Typography, IconButton, Tooltip as MuiTooltip, Divider } from "@mui/material";
|
|
4
4
|
import ContentCopyIcon from "@mui/icons-material/ContentCopy";
|
|
5
5
|
import { useInspection } from "./InspectionContext";
|
|
6
|
-
import { formatMetadataForClipboard, getParentWithGap } from "./inspection";
|
|
6
|
+
import { formatMetadataForClipboard, getParentWithGap, getTooltipHowToFindInfo } from "./inspection";
|
|
7
7
|
import { parseInspectionMetadata } from "./autoInspection";
|
|
8
8
|
/**
|
|
9
9
|
* Helper: Get element text content (first 100 chars)
|
|
@@ -87,7 +87,6 @@ export const InspectionTooltip = () => {
|
|
|
87
87
|
const [position, setPosition] = useState({ x: 0, y: 0 });
|
|
88
88
|
const [stablePosition, setStablePosition] = useState(null);
|
|
89
89
|
const [copied, setCopied] = useState(null);
|
|
90
|
-
const [copyMenuAnchor, setCopyMenuAnchor] = useState(null);
|
|
91
90
|
const tooltipRef = useRef(null);
|
|
92
91
|
// Update position based on cursor, but keep it stable when locked or mouse is near tooltip
|
|
93
92
|
useEffect(() => {
|
|
@@ -137,8 +136,29 @@ export const InspectionTooltip = () => {
|
|
|
137
136
|
propsSignature: "default",
|
|
138
137
|
sourceFile: "DOM",
|
|
139
138
|
} : null);
|
|
139
|
+
// On mobile (or when locking) there is no cursor; position tooltip over/near the inspected element
|
|
140
|
+
const positionFromElement = useMemo(() => {
|
|
141
|
+
if (!hoveredElement || !document.body.contains(hoveredElement))
|
|
142
|
+
return null;
|
|
143
|
+
const padding = 10;
|
|
144
|
+
const estimatedWidth = 400;
|
|
145
|
+
const estimatedHeight = 200;
|
|
146
|
+
const rect = hoveredElement.getBoundingClientRect();
|
|
147
|
+
// Prefer below and to the right of the element, clamped to viewport
|
|
148
|
+
let x = rect.left + 15;
|
|
149
|
+
let y = rect.bottom + 10;
|
|
150
|
+
if (x + estimatedWidth > window.innerWidth - padding)
|
|
151
|
+
x = window.innerWidth - estimatedWidth - padding;
|
|
152
|
+
if (x < padding)
|
|
153
|
+
x = padding;
|
|
154
|
+
if (y + estimatedHeight > window.innerHeight - padding)
|
|
155
|
+
y = rect.top - estimatedHeight - 10;
|
|
156
|
+
if (y < padding)
|
|
157
|
+
y = padding;
|
|
158
|
+
return { x, y };
|
|
159
|
+
}, [hoveredElement]);
|
|
140
160
|
// Calculate adjusted position to avoid going off-screen
|
|
141
|
-
// Use stable position if available
|
|
161
|
+
// Use stable position if available; on mobile or when locked use element-based position so tooltip appears over inspected area
|
|
142
162
|
const adjustedPosition = useMemo(() => {
|
|
143
163
|
if (!displayComponent) {
|
|
144
164
|
return { x: position.x + 15, y: position.y + 15 };
|
|
@@ -147,7 +167,11 @@ export const InspectionTooltip = () => {
|
|
|
147
167
|
if (stablePosition) {
|
|
148
168
|
return stablePosition;
|
|
149
169
|
}
|
|
150
|
-
//
|
|
170
|
+
// Mobile or just locked: place tooltip near the inspected element (not cursor)
|
|
171
|
+
if ((isMobile || isLocked) && positionFromElement) {
|
|
172
|
+
return positionFromElement;
|
|
173
|
+
}
|
|
174
|
+
// Desktop: calculate from cursor position
|
|
151
175
|
const padding = 10;
|
|
152
176
|
let x = position.x + 15;
|
|
153
177
|
let y = position.y + 15;
|
|
@@ -170,7 +194,7 @@ export const InspectionTooltip = () => {
|
|
|
170
194
|
y = padding;
|
|
171
195
|
}
|
|
172
196
|
return { x, y };
|
|
173
|
-
}, [position, displayComponent, stablePosition]);
|
|
197
|
+
}, [position, displayComponent, stablePosition, isMobile, isLocked, positionFromElement]);
|
|
174
198
|
// Set stable position once when tooltip first appears for a new element, or when locked
|
|
175
199
|
const lastComponentIdRef = useRef(null);
|
|
176
200
|
useEffect(() => {
|
|
@@ -232,7 +256,6 @@ export const InspectionTooltip = () => {
|
|
|
232
256
|
const metadata = parseInspectionMetadata(parentWithGap);
|
|
233
257
|
if (!metadata)
|
|
234
258
|
return;
|
|
235
|
-
setCopyMenuAnchor(null);
|
|
236
259
|
const text = formatMetadataForClipboard(metadata, parentWithGap, "gap");
|
|
237
260
|
const showCopied = () => {
|
|
238
261
|
setCopied("gap");
|
|
@@ -260,7 +283,6 @@ export const InspectionTooltip = () => {
|
|
|
260
283
|
}
|
|
261
284
|
if (!displayComponent)
|
|
262
285
|
return;
|
|
263
|
-
setCopyMenuAnchor(null);
|
|
264
286
|
const text = formatMetadataForClipboard(displayComponent, hoveredElement, type);
|
|
265
287
|
const showCopied = () => {
|
|
266
288
|
setCopied(type);
|
|
@@ -292,13 +314,6 @@ export const InspectionTooltip = () => {
|
|
|
292
314
|
setStablePosition(null);
|
|
293
315
|
}
|
|
294
316
|
}, [hoveredElement, isLocked]);
|
|
295
|
-
// Close copy menu when tooltip is hidden so MUI Menu never has a detached anchorEl
|
|
296
|
-
const tooltipVisible = isInspectionActive && !!displayComponent && !(isMobile && !isLocked);
|
|
297
|
-
useEffect(() => {
|
|
298
|
-
if (!tooltipVisible) {
|
|
299
|
-
setCopyMenuAnchor(null);
|
|
300
|
-
}
|
|
301
|
-
}, [tooltipVisible]);
|
|
302
317
|
const parentWithGap = hoveredElement ? getParentWithGap(hoveredElement) : null;
|
|
303
318
|
// Show tooltip when inspection active; on mobile only when locked (H pressed or double-tap)
|
|
304
319
|
if (!isInspectionActive || !displayComponent) {
|
|
@@ -320,15 +335,21 @@ export const InspectionTooltip = () => {
|
|
|
320
335
|
backdropFilter: "blur(8px)",
|
|
321
336
|
border: "1px solid rgba(255, 255, 255, 0.1)",
|
|
322
337
|
transition: stablePosition ? "none" : "left 0.1s ease-out, top 0.1s ease-out",
|
|
323
|
-
}, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isMarginPaddingMode && (_jsx(Typography, { variant: "caption", sx: { color: "#ff9800", fontSize: "0.65rem" }, children: "M/P mode" })), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release H to unlock)" }))] }),
|
|
338
|
+
}, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isMarginPaddingMode && (_jsx(Typography, { variant: "caption", sx: { color: "#ff9800", fontSize: "0.65rem" }, children: "M/P mode" })), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release H to unlock)" }))] }), _jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 0.75 }, children: [_jsx(MuiTooltip, { title: copied === "component" ? "Copied!" : "Copy component", children: _jsx(IconButton, { size: "small", onClick: (e) => { e.preventDefault(); handleCopy("component"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("component"); }, sx: {
|
|
339
|
+
color: copied === "component" ? "#4caf50" : "#fff",
|
|
340
|
+
"&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)" },
|
|
341
|
+
p: 0.5,
|
|
342
|
+
minWidth: 36,
|
|
343
|
+
minHeight: 36,
|
|
344
|
+
}, "aria-label": "Copy component", children: _jsx(ContentCopyIcon, { fontSize: "small" }) }) }), _jsxs(Box, { sx: {
|
|
324
345
|
display: "flex",
|
|
325
346
|
alignItems: "stretch",
|
|
326
347
|
border: "1px solid rgba(255,255,255,0.25)",
|
|
327
348
|
borderRadius: 1,
|
|
328
349
|
overflow: "hidden",
|
|
329
|
-
}, children: [_jsx(Typography, { component: "button", type: "button", variant: "caption", onPointerDown: (e) => { e.preventDefault(); handleCopy("margin"); }, sx: {
|
|
350
|
+
}, children: [_jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("margin"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("margin"); }, sx: {
|
|
330
351
|
color: copied === "margin" ? "#4caf50" : "rgba(255,255,255,0.9)",
|
|
331
|
-
fontSize: "0.65rem",
|
|
352
|
+
fontSize: isMobile ? "0.65rem" : "0.7rem",
|
|
332
353
|
cursor: "pointer",
|
|
333
354
|
background: "none",
|
|
334
355
|
border: "none",
|
|
@@ -336,9 +357,10 @@ export const InspectionTooltip = () => {
|
|
|
336
357
|
padding: "4px 6px",
|
|
337
358
|
fontFamily: "inherit",
|
|
338
359
|
minWidth: 44,
|
|
339
|
-
|
|
360
|
+
"&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
|
|
361
|
+
}, children: "Margin" }), _jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("padding"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("padding"); }, sx: {
|
|
340
362
|
color: copied === "padding" ? "#4caf50" : "rgba(255,255,255,0.9)",
|
|
341
|
-
fontSize: "0.65rem",
|
|
363
|
+
fontSize: isMobile ? "0.65rem" : "0.7rem",
|
|
342
364
|
cursor: "pointer",
|
|
343
365
|
background: "none",
|
|
344
366
|
border: "none",
|
|
@@ -346,20 +368,18 @@ export const InspectionTooltip = () => {
|
|
|
346
368
|
padding: "4px 6px",
|
|
347
369
|
fontFamily: "inherit",
|
|
348
370
|
minWidth: 44,
|
|
349
|
-
|
|
371
|
+
"&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
|
|
372
|
+
}, children: "Padding" }), parentWithGap && (_jsx(Typography, { component: "button", type: "button", variant: "caption", onClick: (e) => { e.preventDefault(); handleCopy("gap"); }, onPointerDown: (e) => { e.preventDefault(); handleCopy("gap"); }, sx: {
|
|
350
373
|
color: copied === "gap" ? "#4caf50" : "rgba(156, 39, 176, 0.95)",
|
|
351
|
-
fontSize: "0.65rem",
|
|
374
|
+
fontSize: isMobile ? "0.65rem" : "0.7rem",
|
|
352
375
|
cursor: "pointer",
|
|
353
376
|
background: "none",
|
|
354
377
|
border: "none",
|
|
355
378
|
padding: "4px 6px",
|
|
356
379
|
fontFamily: "inherit",
|
|
357
380
|
minWidth: 44,
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
"&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)" },
|
|
361
|
-
p: 0.5,
|
|
362
|
-
}, children: _jsx(ContentCopyIcon, { fontSize: "small" }) }) }), _jsxs(Menu, { anchorEl: copyMenuAnchor && document.body.contains(copyMenuAnchor) ? copyMenuAnchor : null, open: !!copyMenuAnchor && document.body.contains(copyMenuAnchor), onClose: () => setCopyMenuAnchor(null), anchorOrigin: { vertical: "bottom", horizontal: "right" }, transformOrigin: { vertical: "top", horizontal: "right" }, PaperProps: { sx: { backgroundColor: "rgba(18, 18, 18, 0.95)", minWidth: 140 } }, MenuListProps: { dense: true }, children: [_jsx(MenuItem, { onClick: () => handleCopy("component"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Component" }), _jsx(MenuItem, { onClick: () => handleCopy("margin"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Margin" }), _jsx(MenuItem, { onClick: () => handleCopy("padding"), sx: { color: "#fff", fontSize: "0.8rem" }, children: "Copy Padding" }), parentWithGap && (_jsx(MenuItem, { onClick: () => handleCopy("gap"), sx: { color: "#9c27b0", fontSize: "0.8rem" }, children: "Copy Gap (parent)" }))] })] }))] }), isMarginPaddingMode && hoveredElement && (() => {
|
|
381
|
+
"&:hover": { backgroundColor: "rgba(255,255,255,0.08)" },
|
|
382
|
+
}, children: "Gap" }))] })] })] }), isMarginPaddingMode && hoveredElement && (() => {
|
|
363
383
|
try {
|
|
364
384
|
const cs = window.getComputedStyle(hoveredElement);
|
|
365
385
|
const mt = cs.marginTop;
|
|
@@ -378,5 +398,9 @@ export const InspectionTooltip = () => {
|
|
|
378
398
|
catch {
|
|
379
399
|
return (_jsxs(Box, { sx: { mb: 1, p: 0.75, borderRadius: 1, backgroundColor: "rgba(255,255,255,0.06)" }, children: [_jsx(Typography, { variant: "caption", sx: { color: "#ff9800" }, children: "Orange = Margin (outside)" }), _jsx(Typography, { variant: "caption", sx: { color: "#4caf50", display: "block" }, children: "Green = Padding (inside)" })] }));
|
|
380
400
|
}
|
|
381
|
-
})(), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx(Typography, { variant: "body2", sx: { color: "#fff", fontWeight: 500, mb: 0.5 }, children: displayComponent.componentName }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), _jsx(ElementIdentificationSection, { element: hoveredElement, role: displayComponent.role }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }),
|
|
401
|
+
})(), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 1 }, children: [_jsx(Typography, { variant: "body2", sx: { color: "#fff", fontWeight: 500, mb: 0.5 }, children: displayComponent.componentName }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), _jsx(ElementIdentificationSection, { element: hoveredElement, role: displayComponent.role }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } }), hoveredElement && (() => {
|
|
402
|
+
const howToType = isMarginPaddingMode ? "margin" : "component";
|
|
403
|
+
const info = getTooltipHowToFindInfo(displayComponent, hoveredElement, howToType);
|
|
404
|
+
return (_jsxs(_Fragment, { children: [_jsx(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.9)", fontSize: "0.7rem", fontWeight: 600 }, children: "=== HOW TO FIND IN CODE ===" }), _jsxs(Box, { sx: { display: "flex", flexDirection: "column", gap: 0.25, pl: 0.5 }, children: [_jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem", wordBreak: "break-all" }, children: [_jsx("strong", { children: "DOM Path:" }), " ", info.domPath] }), info.parent && (_jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem" }, children: [_jsx("strong", { children: "Parent:" }), " ", info.parent] })), _jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem" }, children: [_jsx("strong", { children: "Role in tree:" }), " ", info.roleInTree] }), _jsxs(Typography, { variant: "caption", sx: { color: "rgba(255, 255, 255, 0.85)", fontSize: "0.65rem", mt: 0.5 }, children: [_jsx("strong", { children: "TARGET:" }), " ", info.target] }), _jsx(Box, { component: "ol", sx: { m: 0, pl: 1.5, fontSize: "0.65rem", color: "rgba(255, 255, 255, 0.7)" }, children: info.howToFindSteps.map((step, i) => (_jsx(Typography, { component: "li", variant: "caption", sx: { color: "rgba(255, 255, 255, 0.7)", fontSize: "0.65rem", mb: 0.25 }, children: step }, i))) })] }), _jsx(Divider, { sx: { borderColor: "rgba(255, 255, 255, 0.1)", my: 0.5 } })] }));
|
|
405
|
+
})(), _jsx(ComponentMetadataSection, { metadata: displayComponent })] })] }));
|
|
382
406
|
};
|
package/dist/index.d.ts
CHANGED
|
@@ -5,6 +5,6 @@ export { InspectionOverlays } from './InspectionOverlays';
|
|
|
5
5
|
export { InspectionWrapper, withInspection } from './InspectionWrapper';
|
|
6
6
|
export { useInspectionMetadata } from './useInspectionMetadata';
|
|
7
7
|
export { setupInterceptors, setInspectionActive, shouldBlockRequest } from './inspectionInterceptors';
|
|
8
|
-
export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getComponentName, getNextInstanceIndex } from './inspection';
|
|
8
|
+
export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getTooltipHowToFindInfo, getComponentName, getNextInstanceIndex } from './inspection';
|
|
9
9
|
export type { CopyType } from './inspection';
|
|
10
10
|
export { setupAutoInspection, parseInspectionMetadata } from './autoInspection';
|
package/dist/index.js
CHANGED
|
@@ -6,5 +6,5 @@ export { InspectionOverlays } from './InspectionOverlays';
|
|
|
6
6
|
export { InspectionWrapper, withInspection } from './InspectionWrapper';
|
|
7
7
|
export { useInspectionMetadata } from './useInspectionMetadata';
|
|
8
8
|
export { setupInterceptors, setInspectionActive, shouldBlockRequest } from './inspectionInterceptors';
|
|
9
|
-
export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getComponentName, getNextInstanceIndex } from './inspection';
|
|
9
|
+
export { generateComponentId, formatPropsSignature, formatMetadataForClipboard, formatMarginForClipboard, formatPaddingForClipboard, formatGapForClipboard, getParentWithGap, getAncestorsWithMargin, getTooltipHowToFindInfo, getComponentName, getNextInstanceIndex } from './inspection';
|
|
10
10
|
export { setupAutoInspection, parseInspectionMetadata } from './autoInspection';
|
package/dist/inspection.d.ts
CHANGED
|
@@ -30,6 +30,18 @@ export declare const getAncestorsWithMargin: (element: HTMLElement, maxCount?: n
|
|
|
30
30
|
mb: number;
|
|
31
31
|
ml: number;
|
|
32
32
|
}>;
|
|
33
|
+
/**
|
|
34
|
+
* Build DOM path from body to element (helps Cursor identify exact element)
|
|
35
|
+
*/
|
|
36
|
+
export declare const buildDomPath: (element: HTMLElement) => string;
|
|
37
|
+
/**
|
|
38
|
+
* Build parent element description
|
|
39
|
+
*/
|
|
40
|
+
export declare const buildParentInfo: (element: HTMLElement) => string | null;
|
|
41
|
+
/**
|
|
42
|
+
* Build role/disambiguation (outer vs inner, position in tree)
|
|
43
|
+
*/
|
|
44
|
+
export declare const buildRoleInTree: (element: HTMLElement, type: "margin" | "padding" | "component") => string;
|
|
33
45
|
/**
|
|
34
46
|
* Format metadata for clipboard with full element details
|
|
35
47
|
*/
|
|
@@ -46,6 +58,16 @@ export declare const formatPaddingForClipboard: (metadata: ComponentMetadata, el
|
|
|
46
58
|
* Format gap info for clipboard (alias for formatMetadataForClipboard with type="gap")
|
|
47
59
|
*/
|
|
48
60
|
export declare const formatGapForClipboard: (metadata: ComponentMetadata, element: HTMLElement) => string;
|
|
61
|
+
/**
|
|
62
|
+
* Get "how to find" display info for tooltip (same logic for desktop and mobile)
|
|
63
|
+
*/
|
|
64
|
+
export declare const getTooltipHowToFindInfo: (metadata: ComponentMetadata, element: HTMLElement, type: CopyType) => {
|
|
65
|
+
domPath: string;
|
|
66
|
+
parent: string | null;
|
|
67
|
+
roleInTree: string;
|
|
68
|
+
target: string;
|
|
69
|
+
howToFindSteps: string[];
|
|
70
|
+
};
|
|
49
71
|
/**
|
|
50
72
|
* Get next instance index for a component
|
|
51
73
|
*/
|
package/dist/inspection.js
CHANGED
|
@@ -103,7 +103,7 @@ export const getAncestorsWithMargin = (element, maxCount = 2) => {
|
|
|
103
103
|
/**
|
|
104
104
|
* Build DOM path from body to element (helps Cursor identify exact element)
|
|
105
105
|
*/
|
|
106
|
-
const buildDomPath = (element) => {
|
|
106
|
+
export const buildDomPath = (element) => {
|
|
107
107
|
const segments = [];
|
|
108
108
|
let current = element;
|
|
109
109
|
while (current && current !== document.body) {
|
|
@@ -122,7 +122,7 @@ const buildDomPath = (element) => {
|
|
|
122
122
|
/**
|
|
123
123
|
* Build parent element description
|
|
124
124
|
*/
|
|
125
|
-
const buildParentInfo = (element) => {
|
|
125
|
+
export const buildParentInfo = (element) => {
|
|
126
126
|
const parent = element.parentElement;
|
|
127
127
|
if (!parent || parent === document.body)
|
|
128
128
|
return null;
|
|
@@ -138,7 +138,7 @@ const buildParentInfo = (element) => {
|
|
|
138
138
|
/**
|
|
139
139
|
* Build role/disambiguation (outer vs inner, position in tree)
|
|
140
140
|
*/
|
|
141
|
-
const buildRoleInTree = (element, type) => {
|
|
141
|
+
export const buildRoleInTree = (element, type) => {
|
|
142
142
|
const parent = element.parentElement;
|
|
143
143
|
const children = element.children;
|
|
144
144
|
const childCount = Array.from(children).length;
|
|
@@ -400,6 +400,79 @@ export const formatPaddingForClipboard = (metadata, element) => {
|
|
|
400
400
|
export const formatGapForClipboard = (metadata, element) => {
|
|
401
401
|
return formatMetadataForClipboard(metadata, element, "gap");
|
|
402
402
|
};
|
|
403
|
+
/**
|
|
404
|
+
* Get "how to find" display info for tooltip (same logic for desktop and mobile)
|
|
405
|
+
*/
|
|
406
|
+
export const getTooltipHowToFindInfo = (metadata, element, type) => {
|
|
407
|
+
const domPath = buildDomPath(element);
|
|
408
|
+
const parent = buildParentInfo(element);
|
|
409
|
+
if (type === "component") {
|
|
410
|
+
const rect = element.getBoundingClientRect();
|
|
411
|
+
const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
|
|
412
|
+
const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
|
|
413
|
+
const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
|
|
414
|
+
const target = `The ${desc} - position (${Math.round(rect.left)}, ${Math.round(rect.top)}), size ${Math.round(rect.width)}x${Math.round(rect.height)}px. It is the element in the DOM path above, NOT a child.`;
|
|
415
|
+
const steps = [
|
|
416
|
+
"Use the DOM Path to locate the correct element - do NOT change a child if the path shows this element is the parent.",
|
|
417
|
+
metadata.sourceFile !== "DOM"
|
|
418
|
+
? `Open ${metadata.sourceFile} and find the component that renders this element.`
|
|
419
|
+
: "Search for the parent component that renders this layout. Look for the component in Usage Path or by Element Text/Label.",
|
|
420
|
+
"Modify the component's props, sx, or styles as needed.",
|
|
421
|
+
];
|
|
422
|
+
return { domPath, parent, roleInTree: buildRoleInTree(element, "component"), target, howToFindSteps: steps };
|
|
423
|
+
}
|
|
424
|
+
if (type === "margin" || type === "padding") {
|
|
425
|
+
const cs = window.getComputedStyle(element);
|
|
426
|
+
const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
|
|
427
|
+
const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
|
|
428
|
+
const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
|
|
429
|
+
if (type === "margin") {
|
|
430
|
+
const mt = getCssValue(cs.marginTop);
|
|
431
|
+
const mr = getCssValue(cs.marginRight);
|
|
432
|
+
const mb = getCssValue(cs.marginBottom);
|
|
433
|
+
const ml = getCssValue(cs.marginLeft);
|
|
434
|
+
const target = `The ${desc} - the element with margin ${mt} ${mr} ${mb} ${ml}. It is the PARENT in the DOM path above, NOT a child.`;
|
|
435
|
+
const steps = [
|
|
436
|
+
"Use the DOM Path to locate the correct element - do NOT change a child (e.g. CardContent, MuiBox) if the path shows this element is the parent.",
|
|
437
|
+
metadata.sourceFile !== "DOM"
|
|
438
|
+
? `Open ${metadata.sourceFile} and find the component that renders this element.`
|
|
439
|
+
: "Search for the parent component that renders this layout. Look for MUI components (Box, Card, etc.) - the element with these margin values may use sx={{ m: ... }} or style props.",
|
|
440
|
+
"Change margin: sx={{ margin: 0 }} or margin: \"4px\" or mt: 1, mr: 1, mb: 1, ml: 1 (MUI theme spacing).",
|
|
441
|
+
];
|
|
442
|
+
return { domPath, parent, roleInTree: buildRoleInTree(element, "margin"), target, howToFindSteps: steps };
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
const pt = getCssValue(cs.paddingTop);
|
|
446
|
+
const pr = getCssValue(cs.paddingRight);
|
|
447
|
+
const pb = getCssValue(cs.paddingBottom);
|
|
448
|
+
const pl = getCssValue(cs.paddingLeft);
|
|
449
|
+
const target = `The ${desc} - the element with padding ${pt} ${pr} ${pb} ${pl}. It is the PARENT in the DOM path above, NOT a child.`;
|
|
450
|
+
const steps = [
|
|
451
|
+
"Use the DOM Path to locate the correct element - do NOT change a child (e.g. CardContent, MuiBox) if the path shows this element is the parent.",
|
|
452
|
+
metadata.sourceFile !== "DOM"
|
|
453
|
+
? `Open ${metadata.sourceFile} and find the component that renders this element.`
|
|
454
|
+
: "Search for the parent component that renders this layout. Look for MUI components (Box, Card, CardContent, etc.) - the element with these exact padding values may use sx={{ p: ... }} or padding props.",
|
|
455
|
+
"Change padding: sx={{ padding: 0 }} or p: \"4px\" or pt: 1, pr: 1, pb: 1, pl: 1 (MUI theme spacing).",
|
|
456
|
+
];
|
|
457
|
+
return { domPath, parent, roleInTree: buildRoleInTree(element, "padding"), target, howToFindSteps: steps };
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
// type === "gap" - element is the parent with gap
|
|
461
|
+
const cs = window.getComputedStyle(element);
|
|
462
|
+
const gap = getCssValue(cs.gap);
|
|
463
|
+
const classNameStr = element.className ? (typeof element.className === "string" ? element.className : String(element.className)) : "";
|
|
464
|
+
const firstClass = classNameStr.split(/\s+/).find((c) => c && (c.startsWith("Mui") || c.startsWith("css-")));
|
|
465
|
+
const desc = firstClass ? `${metadata.componentName} with class ${firstClass}` : metadata.componentName;
|
|
466
|
+
const target = `The ${desc} - the flex/grid container with gap ${gap}. It is the PARENT in the DOM path above.`;
|
|
467
|
+
const steps = [
|
|
468
|
+
"Use the DOM Path to locate the flex/grid container.",
|
|
469
|
+
metadata.sourceFile !== "DOM"
|
|
470
|
+
? `Open ${metadata.sourceFile} and find the component that renders this element.`
|
|
471
|
+
: "Search for the component that renders this layout (Box, Stack, Grid, etc.).",
|
|
472
|
+
"Change gap: sx={{ gap: 1 }} or gap: \"8px\" or rowGap/columnGap (MUI theme spacing).",
|
|
473
|
+
];
|
|
474
|
+
return { domPath, parent, roleInTree: "flex/grid container", target, howToFindSteps: steps };
|
|
475
|
+
};
|
|
403
476
|
/**
|
|
404
477
|
* Track component instances for instance indexing
|
|
405
478
|
*/
|
package/package.json
CHANGED
|
@@ -1,54 +1,55 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@zargaryanvh/react-component-inspector",
|
|
3
|
-
"version": "1.0.
|
|
4
|
-
"description": "A development tool for inspecting React components with AI-friendly metadata extraction",
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"exports": {
|
|
8
|
-
".": {
|
|
9
|
-
"import": "./dist/index.js",
|
|
10
|
-
"require": "./dist/index.js",
|
|
11
|
-
"types": "./dist/index.d.ts"
|
|
12
|
-
}
|
|
13
|
-
},
|
|
14
|
-
"files": [
|
|
15
|
-
"dist/**/*",
|
|
16
|
-
"README.md",
|
|
17
|
-
"LICENSE",
|
|
18
|
-
"package.json"
|
|
19
|
-
],
|
|
20
|
-
"scripts": {
|
|
21
|
-
"build": "tsc",
|
|
22
|
-
"prepublishOnly": "npm run build",
|
|
23
|
-
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
-
},
|
|
25
|
-
"keywords": [
|
|
26
|
-
"react",
|
|
27
|
-
"component",
|
|
28
|
-
"inspection",
|
|
29
|
-
"development",
|
|
30
|
-
"debugging",
|
|
31
|
-
"ai",
|
|
32
|
-
"metadata",
|
|
33
|
-
"tool",
|
|
34
|
-
"cursor",
|
|
35
|
-
"cursor-ai"
|
|
36
|
-
],
|
|
37
|
-
"author": "zargaryanvh",
|
|
38
|
-
"license": "MIT",
|
|
39
|
-
"peerDependencies": {
|
|
40
|
-
"react": ">=16.8.0",
|
|
41
|
-
"react-dom": ">=16.8.0",
|
|
42
|
-
"@mui/material": ">=5.0.0",
|
|
43
|
-
"@mui/icons-material": ">=5.0.0"
|
|
44
|
-
},
|
|
45
|
-
"devDependencies": {
|
|
46
|
-
"@types/
|
|
47
|
-
"@types/react
|
|
48
|
-
"
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
"
|
|
53
|
-
|
|
54
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@zargaryanvh/react-component-inspector",
|
|
3
|
+
"version": "1.0.6",
|
|
4
|
+
"description": "A development tool for inspecting React components with AI-friendly metadata extraction",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"import": "./dist/index.js",
|
|
10
|
+
"require": "./dist/index.js",
|
|
11
|
+
"types": "./dist/index.d.ts"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist/**/*",
|
|
16
|
+
"README.md",
|
|
17
|
+
"LICENSE",
|
|
18
|
+
"package.json"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"prepublishOnly": "npm run build",
|
|
23
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
24
|
+
},
|
|
25
|
+
"keywords": [
|
|
26
|
+
"react",
|
|
27
|
+
"component",
|
|
28
|
+
"inspection",
|
|
29
|
+
"development",
|
|
30
|
+
"debugging",
|
|
31
|
+
"ai",
|
|
32
|
+
"metadata",
|
|
33
|
+
"tool",
|
|
34
|
+
"cursor",
|
|
35
|
+
"cursor-ai"
|
|
36
|
+
],
|
|
37
|
+
"author": "zargaryanvh",
|
|
38
|
+
"license": "MIT",
|
|
39
|
+
"peerDependencies": {
|
|
40
|
+
"react": ">=16.8.0",
|
|
41
|
+
"react-dom": ">=16.8.0",
|
|
42
|
+
"@mui/material": ">=5.0.0",
|
|
43
|
+
"@mui/icons-material": ">=5.0.0"
|
|
44
|
+
},
|
|
45
|
+
"devDependencies": {
|
|
46
|
+
"@types/node": "^20.0.0",
|
|
47
|
+
"@types/react": "^18.0.0",
|
|
48
|
+
"@types/react-dom": "^18.0.0",
|
|
49
|
+
"typescript": "^5.0.0"
|
|
50
|
+
},
|
|
51
|
+
"repository": {
|
|
52
|
+
"type": "git",
|
|
53
|
+
"url": "git+https://github.com/zargaryanvh/react-component-inspector.git"
|
|
54
|
+
}
|
|
55
|
+
}
|