@zargaryanvh/react-component-inspector 1.0.0

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.
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Generate a unique component ID
3
+ */
4
+ export const generateComponentId = (componentName, instanceIndex) => {
5
+ return `${componentName.toLowerCase().replace(/\s+/g, "-")}-${instanceIndex}`;
6
+ };
7
+ /**
8
+ * Format props signature for display
9
+ */
10
+ export const formatPropsSignature = (props) => {
11
+ const keyProps = [];
12
+ // Include props that affect behavior/rendering
13
+ const importantProps = ["variant", "role", "type", "mode", "status", "disabled", "selected", "active"];
14
+ for (const key of importantProps) {
15
+ if (key in props && props[key] !== undefined && props[key] !== null) {
16
+ const value = props[key];
17
+ if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
18
+ keyProps.push(`${key}=${String(value)}`);
19
+ }
20
+ }
21
+ }
22
+ return keyProps.length > 0 ? keyProps.join(", ") : "default";
23
+ };
24
+ /**
25
+ * Get component name from component type
26
+ */
27
+ export const getComponentName = (component) => {
28
+ if (typeof component === "string") {
29
+ return component;
30
+ }
31
+ return component.displayName || component.name || "Unknown";
32
+ };
33
+ /**
34
+ * Build usage path from component hierarchy
35
+ */
36
+ export const buildUsagePath = (hierarchy) => {
37
+ return hierarchy.join(" > ");
38
+ };
39
+ /**
40
+ * Format metadata for clipboard with full element details
41
+ */
42
+ export const formatMetadataForClipboard = (metadata, element) => {
43
+ const lines = [];
44
+ // Element identification (most important for AI understanding)
45
+ lines.push(`=== ELEMENT IDENTIFICATION ===`);
46
+ lines.push(`Element Type: ${element.tagName.toLowerCase()}`);
47
+ // Get element text content (label/button text)
48
+ const textContent = element.textContent?.trim() || "";
49
+ const visibleText = textContent.substring(0, 100).replace(/\s+/g, " ");
50
+ if (visibleText) {
51
+ lines.push(`Element Text/Label: "${visibleText}"`);
52
+ }
53
+ // Element ID (if exists)
54
+ if (element.id) {
55
+ lines.push(`Element ID: ${element.id}`);
56
+ }
57
+ // Element classes (first few meaningful ones)
58
+ if (element.className) {
59
+ // Convert className to string - it can be a DOMTokenList or string
60
+ const classNameStr = typeof element.className === "string"
61
+ ? element.className
62
+ : String(element.className);
63
+ const classes = classNameStr.split(/\s+/).filter(c => c && !c.startsWith("Mui")).slice(0, 5);
64
+ if (classes.length > 0) {
65
+ lines.push(`Element Classes: ${classes.join(", ")}`);
66
+ }
67
+ }
68
+ // Data attributes that might help identify the element
69
+ const dataAttrs = [];
70
+ Array.from(element.attributes).forEach(attr => {
71
+ if (attr.name.startsWith("data-") && !attr.name.startsWith("data-inspection-")) {
72
+ dataAttrs.push(`${attr.name}="${attr.value}"`);
73
+ }
74
+ });
75
+ if (dataAttrs.length > 0) {
76
+ lines.push(`Data Attributes: ${dataAttrs.slice(0, 3).join(", ")}`);
77
+ }
78
+ // Role/aria-label for accessibility
79
+ const role = element.getAttribute("role") || metadata.role;
80
+ const ariaLabel = element.getAttribute("aria-label");
81
+ if (role) {
82
+ lines.push(`Role: ${role}`);
83
+ }
84
+ if (ariaLabel) {
85
+ lines.push(`Aria Label: "${ariaLabel}"`);
86
+ }
87
+ // Component metadata
88
+ lines.push(``);
89
+ lines.push(`=== COMPONENT METADATA ===`);
90
+ lines.push(`Component Name: ${metadata.componentName}`);
91
+ lines.push(`Component ID: ${metadata.componentId}`);
92
+ if (metadata.variant) {
93
+ lines.push(`Variant: ${metadata.variant}`);
94
+ }
95
+ lines.push(`Usage Path: ${metadata.usagePath}`);
96
+ lines.push(`Instance: ${metadata.instanceIndex}`);
97
+ lines.push(`Props: ${metadata.propsSignature}`);
98
+ lines.push(`Source File: ${metadata.sourceFile}`);
99
+ // CSS selector for direct targeting
100
+ lines.push(``);
101
+ lines.push(`=== CSS SELECTOR ===`);
102
+ let selector = element.tagName.toLowerCase();
103
+ if (element.id) {
104
+ selector = `#${element.id}`;
105
+ }
106
+ else if (element.className) {
107
+ // Convert className to string - it can be a DOMTokenList or string
108
+ const classNameStr = typeof element.className === "string"
109
+ ? element.className
110
+ : String(element.className);
111
+ const firstClass = classNameStr.split(/\s+/).find(c => c && !c.startsWith("Mui"));
112
+ if (firstClass) {
113
+ selector = `${element.tagName.toLowerCase()}.${firstClass}`;
114
+ }
115
+ }
116
+ lines.push(`Selector: ${selector}`);
117
+ // Visual description
118
+ const rect = element.getBoundingClientRect();
119
+ lines.push(`Position: (${Math.round(rect.left)}, ${Math.round(rect.top)})`);
120
+ lines.push(`Size: ${Math.round(rect.width)}x${Math.round(rect.height)}px`);
121
+ return lines.join("\n");
122
+ };
123
+ /**
124
+ * Track component instances for instance indexing
125
+ */
126
+ const componentInstanceCounts = new Map();
127
+ /**
128
+ * Get next instance index for a component
129
+ */
130
+ export const getNextInstanceIndex = (componentName) => {
131
+ const current = componentInstanceCounts.get(componentName) || 0;
132
+ componentInstanceCounts.set(componentName, current + 1);
133
+ return current;
134
+ };
135
+ /**
136
+ * Reset instance counts (useful for testing or remounting)
137
+ */
138
+ export const resetInstanceCounts = () => {
139
+ componentInstanceCounts.clear();
140
+ };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Interceptors to block API and Firebase requests when CTRL is pressed
3
+ * Only active in development
4
+ */
5
+ /**
6
+ * Set inspection active state (called from InspectionContext)
7
+ */
8
+ export declare const setInspectionActive: (active: boolean) => void;
9
+ /**
10
+ * Check if inspection is active and requests should be blocked
11
+ */
12
+ export declare const shouldBlockRequest: () => boolean;
13
+ /**
14
+ * Intercept fetch requests (for API calls)
15
+ */
16
+ export declare const setupFetchInterceptor: () => void;
17
+ /**
18
+ * Intercept Firebase Firestore operations
19
+ * Note: Firestore operations are harder to intercept at the SDK level
20
+ * We'll rely on blocking at the fetch level (which Firestore uses internally)
21
+ * and also provide a wrapper utility for direct Firestore calls
22
+ */
23
+ export declare const setupFirestoreInterceptor: () => void;
24
+ /**
25
+ * Setup all interceptors
26
+ */
27
+ export declare const setupInterceptors: () => void;
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Interceptors to block API and Firebase requests when CTRL is pressed
3
+ * Only active in development
4
+ */
5
+ let isInspectionActive = false;
6
+ /**
7
+ * Set inspection active state (called from InspectionContext)
8
+ */
9
+ export const setInspectionActive = (active) => {
10
+ if (process.env.NODE_ENV !== "development") {
11
+ return;
12
+ }
13
+ isInspectionActive = active;
14
+ };
15
+ /**
16
+ * Check if inspection is active and requests should be blocked
17
+ */
18
+ export const shouldBlockRequest = () => {
19
+ if (process.env.NODE_ENV !== "development") {
20
+ return false;
21
+ }
22
+ return isInspectionActive;
23
+ };
24
+ /**
25
+ * Intercept fetch requests (for API calls)
26
+ */
27
+ export const setupFetchInterceptor = () => {
28
+ if (process.env.NODE_ENV !== "development") {
29
+ return;
30
+ }
31
+ const originalFetch = window.fetch;
32
+ window.fetch = async (...args) => {
33
+ if (shouldBlockRequest()) {
34
+ console.warn("[Inspection] Blocked API request (CTRL held):", args[0]);
35
+ // Return a rejected promise to prevent the request
36
+ return Promise.reject(new Error("Request blocked: Inspection mode active (CTRL held)"));
37
+ }
38
+ return originalFetch(...args);
39
+ };
40
+ };
41
+ /**
42
+ * Intercept Firebase Firestore operations
43
+ * Note: Firestore operations are harder to intercept at the SDK level
44
+ * We'll rely on blocking at the fetch level (which Firestore uses internally)
45
+ * and also provide a wrapper utility for direct Firestore calls
46
+ */
47
+ export const setupFirestoreInterceptor = () => {
48
+ if (process.env.NODE_ENV !== "development") {
49
+ return;
50
+ }
51
+ // Firestore uses fetch internally, so the fetch interceptor will catch most operations
52
+ // For direct Firestore SDK calls, we'll need to wrap them manually
53
+ // This is a limitation of the Firestore SDK architecture
54
+ };
55
+ /**
56
+ * Setup all interceptors
57
+ */
58
+ export const setupInterceptors = () => {
59
+ if (process.env.NODE_ENV !== "development") {
60
+ return;
61
+ }
62
+ setupFetchInterceptor();
63
+ setupFirestoreInterceptor();
64
+ };
@@ -0,0 +1,32 @@
1
+ import React from "react";
2
+ /**
3
+ * Hook to add inspection metadata to a component
4
+ *
5
+ * Usage:
6
+ * ```tsx
7
+ * const MyComponent = (props) => {
8
+ * const inspectionProps = useInspectionMetadata({
9
+ * componentName: "MyComponent",
10
+ * variant: props.variant,
11
+ * usagePath: "ActivityPage > TransactionList",
12
+ * props,
13
+ * sourceFile: "src/components/MyComponent.tsx",
14
+ * });
15
+ *
16
+ * return <Box {...inspectionProps}>...</Box>;
17
+ * };
18
+ * ```
19
+ */
20
+ export declare const useInspectionMetadata: (config: {
21
+ componentName: string;
22
+ variant?: string;
23
+ role?: string;
24
+ usagePath: string;
25
+ props: Record<string, any>;
26
+ sourceFile: string;
27
+ }) => {
28
+ onMouseEnter: (e: React.MouseEvent) => void;
29
+ onMouseLeave: () => void;
30
+ "data-inspection-id": string;
31
+ "data-inspection-name": string;
32
+ };
@@ -0,0 +1,52 @@
1
+ import { useMemo } from "react";
2
+ import { useInspection } from "./InspectionContext";
3
+ import { generateComponentId, formatPropsSignature, getNextInstanceIndex, } from "./inspection";
4
+ /**
5
+ * Hook to add inspection metadata to a component
6
+ *
7
+ * Usage:
8
+ * ```tsx
9
+ * const MyComponent = (props) => {
10
+ * const inspectionProps = useInspectionMetadata({
11
+ * componentName: "MyComponent",
12
+ * variant: props.variant,
13
+ * usagePath: "ActivityPage > TransactionList",
14
+ * props,
15
+ * sourceFile: "src/components/MyComponent.tsx",
16
+ * });
17
+ *
18
+ * return <Box {...inspectionProps}>...</Box>;
19
+ * };
20
+ * ```
21
+ */
22
+ export const useInspectionMetadata = (config) => {
23
+ const { isInspectionActive, setHoveredComponent } = useInspection();
24
+ const instanceIndex = useMemo(() => getNextInstanceIndex(config.componentName), [config.componentName]);
25
+ const metadata = useMemo(() => ({
26
+ componentName: config.componentName,
27
+ componentId: generateComponentId(config.componentName, instanceIndex),
28
+ variant: config.variant,
29
+ role: config.role,
30
+ usagePath: config.usagePath,
31
+ instanceIndex,
32
+ propsSignature: formatPropsSignature(config.props),
33
+ sourceFile: config.sourceFile,
34
+ }), [config, instanceIndex]);
35
+ const handleMouseEnter = (e) => {
36
+ if (!isInspectionActive)
37
+ return;
38
+ const target = e.currentTarget;
39
+ setHoveredComponent(metadata, target);
40
+ };
41
+ const handleMouseLeave = () => {
42
+ if (!isInspectionActive)
43
+ return;
44
+ setHoveredComponent(null, null);
45
+ };
46
+ return {
47
+ onMouseEnter: handleMouseEnter,
48
+ onMouseLeave: handleMouseLeave,
49
+ "data-inspection-id": metadata.componentId,
50
+ "data-inspection-name": config.componentName,
51
+ };
52
+ };
package/package.json ADDED
@@ -0,0 +1,54 @@
1
+ {
2
+ "name": "@zargaryanvh/react-component-inspector",
3
+ "version": "1.0.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/react": "^18.0.0",
47
+ "@types/react-dom": "^18.0.0",
48
+ "typescript": "^5.0.0"
49
+ },
50
+ "repository": {
51
+ "type": "git",
52
+ "url": "git+https://github.com/zargaryanvh/react-component-inspector.git"
53
+ }
54
+ }