@zargaryanvh/react-component-inspector 1.0.2 → 1.0.4

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,253 +1,211 @@
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 [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
+ 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 hoveredComponentRef = useRef(hoveredComponent);
21
+ const hKeyPressedRef = useRef(false);
22
+ // Touch support for locking only (double-tap to lock tooltip)
23
+ const lastTapRef = useRef(0);
24
+ const [isMobile, setIsMobile] = useState(false);
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
+ setIsMobile(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
+ // Only block API/fetch when CTRL is physically held
46
+ useEffect(() => {
47
+ setInspectionActive(ctrlHeld);
48
+ }, [ctrlHeld]);
49
+ // Mobile touch handlers for activation and locking
50
+ React.useEffect(() => {
51
+ if (process.env.NODE_ENV !== "development") {
52
+ return; // Only in development
53
+ }
54
+ // Double-tap for locking tooltip (touch devices)
55
+ const handleTouchEnd = (e) => {
56
+ if (!('ontouchstart' in window) && navigator.maxTouchPoints === 0)
57
+ return;
58
+ const now = Date.now();
59
+ const timeSinceLastTap = now - lastTapRef.current;
60
+ if (timeSinceLastTap < 300 && timeSinceLastTap > 0) {
61
+ if (isInspectionActiveRef.current && hoveredComponentRef.current && !isLockedRef.current) {
62
+ setIsLocked(true);
63
+ }
64
+ else if (isLockedRef.current) {
65
+ setIsLocked(false);
66
+ }
67
+ lastTapRef.current = 0;
68
+ }
69
+ else {
70
+ lastTapRef.current = now;
71
+ }
72
+ };
73
+ // Keyboard: CTRL (hold = inspection), CTRL+Shift+R = hard refresh (do not capture), CTRL+ALT (margin/padding), CTRL+H (lock)
74
+ const handleKeyDown = (e) => {
75
+ // Do NOT capture CTRL+Shift+R - let browser do hard refresh
76
+ if (e.key && e.key.toLowerCase() === "r" && e.ctrlKey && e.shiftKey) {
77
+ return;
78
+ }
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);
85
+ }
86
+ // H key pressed while CTRL is held - lock tooltip position
87
+ if (e.key && e.key.toLowerCase() === "h" && e.ctrlKey) {
88
+ e.preventDefault();
89
+ e.stopPropagation();
90
+ // Ignore repeated keydown events (when key is held down)
91
+ if (e.repeat) {
92
+ return;
93
+ }
94
+ // Only lock if inspection is active, we have a hovered component, and H is not already being held
95
+ if (isInspectionActiveRef.current && hoveredComponentRef.current && !hKeyPressedRef.current) {
96
+ hKeyPressedRef.current = true;
97
+ setIsLocked(true);
98
+ if (process.env.NODE_ENV === "development") {
99
+ console.log("[Inspection] Tooltip LOCKED - Hold H to keep locked. Release H to unlock.");
100
+ }
101
+ }
102
+ return;
103
+ }
104
+ // CTRL key pressed
105
+ if (e.key === "Control" && !e.repeat) {
106
+ setCtrlHeld(true);
107
+ }
108
+ };
109
+ const handleKeyUp = (e) => {
110
+ // H key released - unlock tooltip but keep inspection active if CTRL is still held
111
+ if (e.key && e.key.toLowerCase() === "h") {
112
+ e.preventDefault();
113
+ e.stopPropagation();
114
+ // Only process if H was actually being held
115
+ if (hKeyPressedRef.current) {
116
+ const wasLocked = isLockedRef.current;
117
+ hKeyPressedRef.current = false;
118
+ // Only unlock if we were locked and CTRL is still held
119
+ if (wasLocked && e.ctrlKey && isInspectionActiveRef.current) {
120
+ setIsLocked(false);
121
+ if (process.env.NODE_ENV === "development") {
122
+ console.log("[Inspection] Tooltip UNLOCKED - inspection continues while CTRL is held.");
123
+ }
124
+ }
125
+ }
126
+ return;
127
+ }
128
+ // ALT or CTRL released - turn off margin/padding mode (CTRL+ALT only, hold to use)
129
+ if (e.key === "Alt") {
130
+ setIsMarginPaddingMode(false);
131
+ }
132
+ // CTRL key released - stop inspecting and clear state
133
+ if (e.key === "Control") {
134
+ setCtrlHeld(false);
135
+ hKeyPressedRef.current = false;
136
+ setIsMarginPaddingMode(false);
137
+ setIsLocked(false);
138
+ setHoveredComponentState(null);
139
+ setHoveredElement(null);
140
+ }
141
+ };
142
+ window.addEventListener("keydown", handleKeyDown);
143
+ window.addEventListener("keyup", handleKeyUp);
144
+ if ('ontouchstart' in window || navigator.maxTouchPoints > 0) {
145
+ window.addEventListener("touchend", handleTouchEnd, { passive: true });
146
+ }
147
+ return () => {
148
+ window.removeEventListener("keydown", handleKeyDown);
149
+ window.removeEventListener("keyup", handleKeyUp);
150
+ window.removeEventListener("touchend", handleTouchEnd);
151
+ };
152
+ }, []); // Empty deps - refs handle state access
153
+ const setHoveredComponentRef = useRef(() => { });
154
+ const setHoveredComponent = useCallback((component, element) => {
155
+ if (process.env.NODE_ENV !== "development") {
156
+ return; // Only in development
157
+ }
158
+ // Validate element is still in DOM before setting
159
+ if (element && !document.body.contains(element)) {
160
+ setHoveredComponentState(null);
161
+ setHoveredElement(null);
162
+ return;
163
+ }
164
+ setHoveredComponentState(component);
165
+ setHoveredElement(element);
166
+ }, []);
167
+ setHoveredComponentRef.current = setHoveredComponent;
168
+ // Setup automatic inspection detection via data attributes
169
+ useEffect(() => {
170
+ if (process.env.NODE_ENV !== "development") {
171
+ return;
172
+ }
173
+ const cleanup = setupAutoInspection(setHoveredComponent, isInspectionActive, isLocked);
174
+ return cleanup;
175
+ }, [isInspectionActive, isLocked, setHoveredComponent]);
176
+ // Don't render provider in production
177
+ if (process.env.NODE_ENV !== "development") {
178
+ return _jsx(_Fragment, { children: children });
179
+ }
180
+ return (_jsx(InspectionContext.Provider, { value: {
181
+ isInspectionActive,
182
+ isLocked,
183
+ isMobile,
184
+ isMarginPaddingMode,
185
+ hoveredComponent,
186
+ hoveredElement,
187
+ setHoveredComponent,
188
+ }, children: children }));
189
+ };
190
+ /**
191
+ * Hook to access inspection context
192
+ */
193
+ export const useInspection = () => {
194
+ const context = useContext(InspectionContext);
195
+ if (process.env.NODE_ENV !== "development") {
196
+ // Return dummy state in production
197
+ return {
198
+ isInspectionActive: false,
199
+ isLocked: false,
200
+ isMobile: false,
201
+ isMarginPaddingMode: false,
202
+ hoveredComponent: null,
203
+ hoveredElement: null,
204
+ setHoveredComponent: () => { },
205
+ };
206
+ }
207
+ if (!context) {
208
+ throw new Error("useInspection must be used within InspectionProvider");
209
+ }
210
+ return context;
211
+ };
@@ -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;