@zargaryanvh/react-component-inspector 1.0.2 → 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,253 +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
- // Mobile touch support
20
- const longPressTimerRef = useRef(null);
21
- const touchStartTimeRef = useRef(0);
22
- const lastTapRef = useRef(0);
23
- const isMobileRef = useRef(false);
24
- // Detect mobile device
25
- useEffect(() => {
26
- const checkMobile = () => {
27
- if (typeof window === 'undefined')
28
- return false;
29
- return 'ontouchstart' in window ||
30
- navigator.maxTouchPoints > 0 ||
31
- /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
32
- };
33
- isMobileRef.current = checkMobile();
34
- }, []);
35
- // Keep refs in sync with state
36
- useEffect(() => {
37
- isInspectionActiveRef.current = isInspectionActive;
38
- }, [isInspectionActive]);
39
- useEffect(() => {
40
- isLockedRef.current = isLocked;
41
- }, [isLocked]);
42
- useEffect(() => {
43
- hoveredComponentRef.current = hoveredComponent;
44
- }, [hoveredComponent]);
45
- // Mobile touch handlers for activation and locking
46
- React.useEffect(() => {
47
- if (process.env.NODE_ENV !== "development") {
48
- return; // Only in development
49
- }
50
- // Long-press to activate inspection mode (mobile)
51
- const handleTouchStart = (e) => {
52
- if (!isMobileRef.current)
53
- return;
54
- // Only activate if touching with 3 fingers (to avoid accidental activation)
55
- if (e.touches.length === 3) {
56
- touchStartTimeRef.current = Date.now();
57
- longPressTimerRef.current = setTimeout(() => {
58
- setIsInspectionActive(true);
59
- setInspectionActive(true);
60
- if (process.env.NODE_ENV === "development") {
61
- console.log("[Inspection] Activated (mobile) - Long-press with 3 fingers. Double-tap tooltip to lock.");
62
- }
63
- }, 800); // 800ms long-press
64
- }
65
- };
66
- const handleTouchEnd = (e) => {
67
- if (!isMobileRef.current)
68
- return;
69
- // Clear long-press timer
70
- if (longPressTimerRef.current) {
71
- clearTimeout(longPressTimerRef.current);
72
- longPressTimerRef.current = null;
73
- }
74
- // Double-tap detection for locking (on tooltip or component)
75
- const now = Date.now();
76
- const timeSinceLastTap = now - lastTapRef.current;
77
- if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
78
- // Double-tap detected
79
- if (isInspectionActiveRef.current && hoveredComponentRef.current && !isLockedRef.current) {
80
- setIsLocked(true);
81
- if (process.env.NODE_ENV === "development") {
82
- console.log("[Inspection] Tooltip LOCKED (mobile) - Double-tap again to unlock.");
83
- }
84
- }
85
- else if (isLockedRef.current) {
86
- // Unlock on double-tap when locked
87
- setIsLocked(false);
88
- if (process.env.NODE_ENV === "development") {
89
- console.log("[Inspection] Tooltip UNLOCKED (mobile) - inspection continues.");
90
- }
91
- }
92
- lastTapRef.current = 0; // Reset
93
- }
94
- else {
95
- lastTapRef.current = now;
96
- }
97
- // Deactivate if 3-finger touch ends and inspection was active
98
- if (e.touches.length === 0 && isInspectionActiveRef.current) {
99
- const touchDuration = Date.now() - touchStartTimeRef.current;
100
- // Only deactivate if it was a quick tap (not a long-press that activated)
101
- if (touchDuration < 800) {
102
- setIsInspectionActive(false);
103
- setIsLocked(false);
104
- hKeyPressedRef.current = false;
105
- setInspectionActive(false);
106
- setHoveredComponentState(null);
107
- setHoveredElement(null);
108
- if (process.env.NODE_ENV === "development") {
109
- console.log("[Inspection] Deactivated (mobile)");
110
- }
111
- }
112
- }
113
- };
114
- const handleTouchCancel = () => {
115
- if (!isMobileRef.current)
116
- return;
117
- if (longPressTimerRef.current) {
118
- clearTimeout(longPressTimerRef.current);
119
- longPressTimerRef.current = null;
120
- }
121
- };
122
- // Track CTRL key state and CTRL+H for locking (desktop)
123
- const handleKeyDown = (e) => {
124
- // H key pressed while CTRL is held - lock tooltip position
125
- if (e.key.toLowerCase() === "h" && e.ctrlKey) {
126
- e.preventDefault();
127
- e.stopPropagation();
128
- // Ignore repeated keydown events (when key is held down)
129
- if (e.repeat) {
130
- return;
131
- }
132
- // Only lock if inspection is active, we have a hovered component, and H is not already being held
133
- if (isInspectionActiveRef.current && hoveredComponentRef.current && !hKeyPressedRef.current) {
134
- hKeyPressedRef.current = true;
135
- setIsLocked(true);
136
- if (process.env.NODE_ENV === "development") {
137
- console.log("[Inspection] Tooltip LOCKED - Hold H to keep locked. Release H to unlock.");
138
- }
139
- }
140
- return;
141
- }
142
- // CTRL key pressed
143
- if (e.key === "Control" && !e.repeat) {
144
- setIsInspectionActive(true);
145
- setInspectionActive(true);
146
- if (process.env.NODE_ENV === "development") {
147
- console.log("[Inspection] Activated - Hold CTRL and hover over components. Press H to lock tooltip position.");
148
- }
149
- }
150
- };
151
- const handleKeyUp = (e) => {
152
- // H key released - unlock tooltip but keep inspection active if CTRL is still held
153
- if (e.key.toLowerCase() === "h") {
154
- e.preventDefault();
155
- e.stopPropagation();
156
- // Only process if H was actually being held
157
- if (hKeyPressedRef.current) {
158
- const wasLocked = isLockedRef.current;
159
- hKeyPressedRef.current = false;
160
- // Only unlock if we were locked and CTRL is still held
161
- if (wasLocked && e.ctrlKey && isInspectionActiveRef.current) {
162
- setIsLocked(false);
163
- if (process.env.NODE_ENV === "development") {
164
- console.log("[Inspection] Tooltip UNLOCKED - inspection continues while CTRL is held.");
165
- }
166
- }
167
- }
168
- return;
169
- }
170
- // CTRL key released - unlock and clear
171
- if (e.key === "Control") {
172
- setIsInspectionActive(false);
173
- setIsLocked(false);
174
- hKeyPressedRef.current = false;
175
- setInspectionActive(false);
176
- setHoveredComponentState(null);
177
- setHoveredElement(null);
178
- if (process.env.NODE_ENV === "development") {
179
- console.log("[Inspection] Deactivated");
180
- }
181
- }
182
- };
183
- window.addEventListener("keydown", handleKeyDown);
184
- window.addEventListener("keyup", handleKeyUp);
185
- // Mobile touch events
186
- window.addEventListener("touchstart", handleTouchStart, { passive: true });
187
- window.addEventListener("touchend", handleTouchEnd, { passive: true });
188
- window.addEventListener("touchcancel", handleTouchCancel, { passive: true });
189
- return () => {
190
- window.removeEventListener("keydown", handleKeyDown);
191
- window.removeEventListener("keyup", handleKeyUp);
192
- window.removeEventListener("touchstart", handleTouchStart);
193
- window.removeEventListener("touchend", handleTouchEnd);
194
- window.removeEventListener("touchcancel", handleTouchCancel);
195
- // Cleanup timers
196
- if (longPressTimerRef.current) {
197
- clearTimeout(longPressTimerRef.current);
198
- }
199
- };
200
- }, []); // Empty deps - refs handle state access
201
- const setHoveredComponent = useCallback((component, element) => {
202
- if (process.env.NODE_ENV !== "development") {
203
- return; // Only in development
204
- }
205
- // Validate element is still in DOM before setting
206
- if (element && !document.body.contains(element)) {
207
- setHoveredComponentState(null);
208
- setHoveredElement(null);
209
- return;
210
- }
211
- setHoveredComponentState(component);
212
- setHoveredElement(element);
213
- }, []);
214
- // Setup automatic inspection detection via data attributes
215
- useEffect(() => {
216
- if (process.env.NODE_ENV !== "development") {
217
- return;
218
- }
219
- const cleanup = setupAutoInspection(setHoveredComponent, isInspectionActive, isLocked);
220
- return cleanup;
221
- }, [isInspectionActive, isLocked, setHoveredComponent]);
222
- // Don't render provider in production
223
- if (process.env.NODE_ENV !== "development") {
224
- return _jsx(_Fragment, { children: children });
225
- }
226
- return (_jsx(InspectionContext.Provider, { value: {
227
- isInspectionActive,
228
- isLocked,
229
- hoveredComponent,
230
- hoveredElement,
231
- setHoveredComponent,
232
- }, children: children }));
233
- };
234
- /**
235
- * Hook to access inspection context
236
- */
237
- export const useInspection = () => {
238
- const context = useContext(InspectionContext);
239
- if (process.env.NODE_ENV !== "development") {
240
- // Return dummy state in production
241
- return {
242
- isInspectionActive: false,
243
- isLocked: false,
244
- hoveredComponent: null,
245
- hoveredElement: null,
246
- setHoveredComponent: () => { },
247
- };
248
- }
249
- if (!context) {
250
- throw new Error("useInspection must be used within InspectionProvider");
251
- }
252
- return context;
253
- };
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;