@zargaryanvh/react-component-inspector 1.0.1 → 1.0.3

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.
@@ -1,35 +1,37 @@
1
- import React, { ReactNode } from "react";
2
- /**
3
- * Component inspection metadata
4
- */
5
- export interface ComponentMetadata {
6
- componentName: string;
7
- componentId: string;
8
- variant?: string;
9
- role?: string;
10
- usagePath: string;
11
- instanceIndex: number;
12
- propsSignature: string;
13
- sourceFile: string;
14
- }
15
- /**
16
- * Inspection context state
17
- */
18
- interface InspectionState {
19
- isInspectionActive: boolean;
20
- isLocked: boolean;
21
- hoveredComponent: ComponentMetadata | null;
22
- hoveredElement: HTMLElement | null;
23
- setHoveredComponent: (component: ComponentMetadata | null, element: HTMLElement | null) => void;
24
- }
25
- /**
26
- * Inspection Provider - Only active in development
27
- */
28
- export declare const InspectionProvider: React.FC<{
29
- children: ReactNode;
30
- }>;
31
- /**
32
- * Hook to access inspection context
33
- */
34
- export declare const useInspection: () => InspectionState;
35
- export {};
1
+ import React, { ReactNode } from "react";
2
+ /**
3
+ * Component inspection metadata
4
+ */
5
+ export interface ComponentMetadata {
6
+ componentName: string;
7
+ componentId: string;
8
+ variant?: string;
9
+ role?: string;
10
+ usagePath: string;
11
+ instanceIndex: number;
12
+ propsSignature: string;
13
+ sourceFile: string;
14
+ }
15
+ /**
16
+ * Inspection context state
17
+ */
18
+ interface InspectionState {
19
+ isInspectionActive: boolean;
20
+ isLocked: boolean;
21
+ isMobile: boolean;
22
+ isMarginPaddingMode: boolean;
23
+ hoveredComponent: ComponentMetadata | null;
24
+ hoveredElement: HTMLElement | null;
25
+ setHoveredComponent: (component: ComponentMetadata | null, element: HTMLElement | null) => void;
26
+ }
27
+ /**
28
+ * Inspection Provider - Only active in development
29
+ */
30
+ export declare const InspectionProvider: React.FC<{
31
+ children: ReactNode;
32
+ }>;
33
+ /**
34
+ * Hook to access inspection context
35
+ */
36
+ export declare const useInspection: () => InspectionState;
37
+ export {};
@@ -1,153 +1,236 @@
1
- import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from "react";
3
- import { setInspectionActive } from "./inspectionInterceptors";
4
- import { setupAutoInspection } from "./autoInspection";
5
- const InspectionContext = createContext(undefined);
6
- /**
7
- * Inspection Provider - Only active in development
8
- */
9
- export const InspectionProvider = ({ children }) => {
10
- const [isInspectionActive, setIsInspectionActive] = useState(false);
11
- const [isLocked, setIsLocked] = useState(false);
12
- const [hoveredComponent, setHoveredComponentState] = useState(null);
13
- const [hoveredElement, setHoveredElement] = useState(null);
14
- // Use refs to always access latest state values in event handlers
15
- const isInspectionActiveRef = useRef(isInspectionActive);
16
- const isLockedRef = useRef(isLocked);
17
- const hoveredComponentRef = useRef(hoveredComponent);
18
- const hKeyPressedRef = useRef(false); // Track if H key is currently being held
19
- // Keep refs in sync with state
20
- useEffect(() => {
21
- isInspectionActiveRef.current = isInspectionActive;
22
- }, [isInspectionActive]);
23
- useEffect(() => {
24
- isLockedRef.current = isLocked;
25
- }, [isLocked]);
26
- useEffect(() => {
27
- hoveredComponentRef.current = hoveredComponent;
28
- }, [hoveredComponent]);
29
- // Track CTRL key state and CTRL+H for locking
30
- React.useEffect(() => {
31
- if (process.env.NODE_ENV !== "development") {
32
- return; // Only in development
33
- }
34
- const handleKeyDown = (e) => {
35
- // H key pressed while CTRL is held - lock tooltip position
36
- if (e.key.toLowerCase() === "h" && e.ctrlKey) {
37
- e.preventDefault();
38
- e.stopPropagation();
39
- // Ignore repeated keydown events (when key is held down)
40
- if (e.repeat) {
41
- return;
42
- }
43
- // Only lock if inspection is active, we have a hovered component, and H is not already being held
44
- if (isInspectionActiveRef.current && hoveredComponentRef.current && !hKeyPressedRef.current) {
45
- hKeyPressedRef.current = true;
46
- setIsLocked(true);
47
- if (process.env.NODE_ENV === "development") {
48
- console.log("[Inspection] Tooltip LOCKED - Hold H to keep locked. Release H to unlock.");
49
- }
50
- }
51
- return;
52
- }
53
- // CTRL key pressed
54
- if (e.key === "Control" && !e.repeat) {
55
- setIsInspectionActive(true);
56
- setInspectionActive(true);
57
- if (process.env.NODE_ENV === "development") {
58
- console.log("[Inspection] Activated - Hold CTRL and hover over components. Press H to lock tooltip position.");
59
- }
60
- }
61
- };
62
- const handleKeyUp = (e) => {
63
- // H key released - unlock tooltip but keep inspection active if CTRL is still held
64
- if (e.key.toLowerCase() === "h") {
65
- e.preventDefault();
66
- e.stopPropagation();
67
- // Only process if H was actually being held
68
- if (hKeyPressedRef.current) {
69
- const wasLocked = isLockedRef.current;
70
- hKeyPressedRef.current = false;
71
- // Only unlock if we were locked and CTRL is still held
72
- if (wasLocked && e.ctrlKey && isInspectionActiveRef.current) {
73
- setIsLocked(false);
74
- if (process.env.NODE_ENV === "development") {
75
- console.log("[Inspection] Tooltip UNLOCKED - inspection continues while CTRL is held.");
76
- }
77
- }
78
- }
79
- return;
80
- }
81
- // CTRL key released - unlock and clear
82
- if (e.key === "Control") {
83
- setIsInspectionActive(false);
84
- setIsLocked(false);
85
- hKeyPressedRef.current = false;
86
- setInspectionActive(false);
87
- setHoveredComponentState(null);
88
- setHoveredElement(null);
89
- if (process.env.NODE_ENV === "development") {
90
- console.log("[Inspection] Deactivated");
91
- }
92
- }
93
- };
94
- window.addEventListener("keydown", handleKeyDown);
95
- window.addEventListener("keyup", handleKeyUp);
96
- return () => {
97
- window.removeEventListener("keydown", handleKeyDown);
98
- window.removeEventListener("keyup", handleKeyUp);
99
- };
100
- }, []); // Empty deps - refs handle state access
101
- const setHoveredComponent = useCallback((component, element) => {
102
- if (process.env.NODE_ENV !== "development") {
103
- return; // Only in development
104
- }
105
- // Validate element is still in DOM before setting
106
- if (element && !document.body.contains(element)) {
107
- setHoveredComponentState(null);
108
- setHoveredElement(null);
109
- return;
110
- }
111
- setHoveredComponentState(component);
112
- setHoveredElement(element);
113
- }, []);
114
- // Setup automatic inspection detection via data attributes
115
- useEffect(() => {
116
- if (process.env.NODE_ENV !== "development") {
117
- return;
118
- }
119
- const cleanup = setupAutoInspection(setHoveredComponent, isInspectionActive, isLocked);
120
- return cleanup;
121
- }, [isInspectionActive, isLocked, setHoveredComponent]);
122
- // Don't render provider in production
123
- if (process.env.NODE_ENV !== "development") {
124
- return _jsx(_Fragment, { children: children });
125
- }
126
- return (_jsx(InspectionContext.Provider, { value: {
127
- isInspectionActive,
128
- isLocked,
129
- hoveredComponent,
130
- hoveredElement,
131
- setHoveredComponent,
132
- }, children: children }));
133
- };
134
- /**
135
- * Hook to access inspection context
136
- */
137
- export const useInspection = () => {
138
- const context = useContext(InspectionContext);
139
- if (process.env.NODE_ENV !== "development") {
140
- // Return dummy state in production
141
- return {
142
- isInspectionActive: false,
143
- isLocked: false,
144
- hoveredComponent: null,
145
- hoveredElement: null,
146
- setHoveredComponent: () => { },
147
- };
148
- }
149
- if (!context) {
150
- throw new Error("useInspection must be used within InspectionProvider");
151
- }
152
- return context;
153
- };
1
+ import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
+ import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from "react";
3
+ import { setInspectionActive } from "./inspectionInterceptors";
4
+ import { setupAutoInspection } from "./autoInspection";
5
+ const InspectionContext = createContext(undefined);
6
+ /**
7
+ * Inspection Provider - Only active in development
8
+ */
9
+ export const InspectionProvider = ({ children }) => {
10
+ const [ctrlHeld, setCtrlHeld] = useState(false);
11
+ const [isStickyInspection, setIsStickyInspection] = useState(false);
12
+ const isInspectionActive = ctrlHeld || isStickyInspection;
13
+ const [isLocked, setIsLocked] = useState(false);
14
+ const [isMarginPaddingMode, setIsMarginPaddingMode] = useState(false);
15
+ const [hoveredComponent, setHoveredComponentState] = useState(null);
16
+ const [hoveredElement, setHoveredElement] = useState(null);
17
+ // Use refs to always access latest state values in event handlers
18
+ const isInspectionActiveRef = useRef(isInspectionActive);
19
+ const isLockedRef = useRef(isLocked);
20
+ const isStickyInspectionRef = useRef(isStickyInspection);
21
+ const hoveredComponentRef = useRef(hoveredComponent);
22
+ const hKeyPressedRef = useRef(false);
23
+ // Touch support for locking only (3/4 finger activation removed - use Ctrl+Shift+R on laptop)
24
+ const lastTapRef = useRef(0);
25
+ const [isMobile, setIsMobile] = useState(false);
26
+ useEffect(() => {
27
+ const checkMobile = () => {
28
+ if (typeof window === 'undefined')
29
+ return false;
30
+ return 'ontouchstart' in window ||
31
+ navigator.maxTouchPoints > 0 ||
32
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
33
+ };
34
+ setIsMobile(checkMobile());
35
+ }, []);
36
+ // Keep refs in sync with state
37
+ useEffect(() => {
38
+ isInspectionActiveRef.current = isInspectionActive;
39
+ }, [isInspectionActive]);
40
+ useEffect(() => {
41
+ isLockedRef.current = isLocked;
42
+ }, [isLocked]);
43
+ useEffect(() => {
44
+ hoveredComponentRef.current = hoveredComponent;
45
+ }, [hoveredComponent]);
46
+ useEffect(() => {
47
+ isStickyInspectionRef.current = isStickyInspection;
48
+ }, [isStickyInspection]);
49
+ // Only block API/fetch when CTRL is physically held (not when sticky inspection is on)
50
+ useEffect(() => {
51
+ setInspectionActive(ctrlHeld);
52
+ }, [ctrlHeld]);
53
+ // Mobile touch handlers for activation and locking
54
+ React.useEffect(() => {
55
+ if (process.env.NODE_ENV !== "development") {
56
+ return; // Only in development
57
+ }
58
+ // Double-tap for locking tooltip (touch devices)
59
+ const handleTouchEnd = (e) => {
60
+ if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0)
61
+ return;
62
+ const now = Date.now();
63
+ const timeSinceLastTap = now - lastTapRef.current;
64
+ if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
65
+ if (isInspectionActiveRef.current && hoveredComponentRef.current && !isLockedRef.current) {
66
+ setIsLocked(true);
67
+ }
68
+ else if (isLockedRef.current) {
69
+ setIsLocked(false);
70
+ }
71
+ lastTapRef.current = 0;
72
+ }
73
+ else {
74
+ lastTapRef.current = now;
75
+ }
76
+ };
77
+ // Keyboard: CTRL, CTRL+Shift+R (toggle inspection), CTRL+M (margin/padding), CTRL+H (lock)
78
+ const handleKeyDown = (e) => {
79
+ // R key with CTRL+Shift - toggle inspection on/off (sticky, for mobile viewport on laptop)
80
+ 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
+ return;
98
+ }
99
+ // M key with CTRL (hold) - margin/padding mode while held, inspect on mouse move
100
+ if (e.key && e.key.toLowerCase() === "m" && e.ctrlKey && !e.shiftKey && !e.altKey) {
101
+ e.preventDefault();
102
+ e.stopPropagation();
103
+ if (!e.repeat) {
104
+ setIsMarginPaddingMode(true);
105
+ setCtrlHeld(true);
106
+ }
107
+ return;
108
+ }
109
+ // H key pressed while CTRL is held - lock tooltip position
110
+ if (e.key && e.key.toLowerCase() === "h" && e.ctrlKey) {
111
+ e.preventDefault();
112
+ e.stopPropagation();
113
+ // Ignore repeated keydown events (when key is held down)
114
+ if (e.repeat) {
115
+ return;
116
+ }
117
+ // Only lock if inspection is active, we have a hovered component, and H is not already being held
118
+ if (isInspectionActiveRef.current && hoveredComponentRef.current && !hKeyPressedRef.current) {
119
+ hKeyPressedRef.current = true;
120
+ setIsLocked(true);
121
+ if (process.env.NODE_ENV === "development") {
122
+ console.log("[Inspection] Tooltip LOCKED - Hold H to keep locked. Release H to unlock.");
123
+ }
124
+ }
125
+ return;
126
+ }
127
+ // CTRL key pressed
128
+ if (e.key === "Control" && !e.repeat) {
129
+ setCtrlHeld(true);
130
+ }
131
+ };
132
+ const handleKeyUp = (e) => {
133
+ // H key released - unlock tooltip but keep inspection active if CTRL is still held
134
+ if (e.key && e.key.toLowerCase() === "h") {
135
+ e.preventDefault();
136
+ e.stopPropagation();
137
+ // Only process if H was actually being held
138
+ if (hKeyPressedRef.current) {
139
+ const wasLocked = isLockedRef.current;
140
+ hKeyPressedRef.current = false;
141
+ // Only unlock if we were locked and CTRL is still held
142
+ if (wasLocked && e.ctrlKey && isInspectionActiveRef.current) {
143
+ setIsLocked(false);
144
+ if (process.env.NODE_ENV === "development") {
145
+ console.log("[Inspection] Tooltip UNLOCKED - inspection continues while CTRL is held.");
146
+ }
147
+ }
148
+ }
149
+ return;
150
+ }
151
+ // M key released - turn off margin/padding mode (hold-to-use, no toggle)
152
+ if (e.key && e.key.toLowerCase() === "m") {
153
+ setIsMarginPaddingMode(false);
154
+ }
155
+ // CTRL key released - clear only if not in sticky mode
156
+ if (e.key === "Control") {
157
+ setCtrlHeld(false);
158
+ hKeyPressedRef.current = false;
159
+ if (!isStickyInspectionRef.current) {
160
+ setIsMarginPaddingMode(false);
161
+ setIsLocked(false);
162
+ setHoveredComponentState(null);
163
+ setHoveredElement(null);
164
+ }
165
+ }
166
+ };
167
+ window.addEventListener("keydown", handleKeyDown);
168
+ window.addEventListener("keyup", handleKeyUp);
169
+ if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
170
+ window.addEventListener("touchend", handleTouchEnd, { passive: true });
171
+ }
172
+ return () => {
173
+ window.removeEventListener("keydown", handleKeyDown);
174
+ window.removeEventListener("keyup", handleKeyUp);
175
+ window.removeEventListener("touchend", handleTouchEnd);
176
+ };
177
+ }, []); // Empty deps - refs handle state access
178
+ const setHoveredComponentRef = useRef(() => { });
179
+ const setHoveredComponent = useCallback((component, element) => {
180
+ if (process.env.NODE_ENV !== "development") {
181
+ return; // Only in development
182
+ }
183
+ // Validate element is still in DOM before setting
184
+ if (element && !document.body.contains(element)) {
185
+ setHoveredComponentState(null);
186
+ setHoveredElement(null);
187
+ return;
188
+ }
189
+ setHoveredComponentState(component);
190
+ setHoveredElement(element);
191
+ }, []);
192
+ setHoveredComponentRef.current = setHoveredComponent;
193
+ // Setup automatic inspection detection via data attributes
194
+ useEffect(() => {
195
+ if (process.env.NODE_ENV !== "development") {
196
+ return;
197
+ }
198
+ const cleanup = setupAutoInspection(setHoveredComponent, isInspectionActive, isLocked);
199
+ return cleanup;
200
+ }, [isInspectionActive, isLocked, setHoveredComponent]);
201
+ // Don't render provider in production
202
+ if (process.env.NODE_ENV !== "development") {
203
+ return _jsx(_Fragment, { children: children });
204
+ }
205
+ return (_jsx(InspectionContext.Provider, { value: {
206
+ isInspectionActive,
207
+ isLocked,
208
+ isMobile,
209
+ isMarginPaddingMode,
210
+ hoveredComponent,
211
+ hoveredElement,
212
+ setHoveredComponent,
213
+ }, children: children }));
214
+ };
215
+ /**
216
+ * Hook to access inspection context
217
+ */
218
+ export const useInspection = () => {
219
+ const context = useContext(InspectionContext);
220
+ if (process.env.NODE_ENV !== "development") {
221
+ // Return dummy state in production
222
+ return {
223
+ isInspectionActive: false,
224
+ isLocked: false,
225
+ isMobile: false,
226
+ isMarginPaddingMode: false,
227
+ hoveredComponent: null,
228
+ hoveredElement: null,
229
+ setHoveredComponent: () => { },
230
+ };
231
+ }
232
+ if (!context) {
233
+ throw new Error("useInspection must be used within InspectionProvider");
234
+ }
235
+ return context;
236
+ };
@@ -1,6 +1,2 @@
1
- import React from "react";
2
- /**
3
- * Highlight overlay that shows the boundary of the hovered component
4
- * Only visible when CTRL is held and a component is hovered
5
- */
6
- export declare const InspectionHighlight: React.FC;
1
+ import React from "react";
2
+ export declare const InspectionHighlight: React.FC;