@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.
- package/README.md +72 -25
- package/dist/InspectionContext.d.ts +37 -35
- package/dist/InspectionContext.js +211 -253
- package/dist/InspectionHighlight.d.ts +2 -6
- package/dist/InspectionHighlight.js +258 -65
- package/dist/InspectionOverlays.d.ts +7 -0
- package/dist/InspectionOverlays.js +32 -0
- package/dist/InspectionTooltip.d.ts +6 -6
- package/dist/InspectionTooltip.js +406 -232
- package/dist/InspectionWrapper.d.ts +28 -28
- package/dist/InspectionWrapper.js +102 -102
- package/dist/autoInspection.d.ts +18 -14
- package/dist/autoInspection.js +329 -320
- package/dist/index.d.ts +10 -8
- package/dist/index.js +10 -9
- package/dist/inspection.d.ts +78 -29
- package/dist/inspection.js +493 -140
- package/dist/inspectionInterceptors.d.ts +27 -27
- package/dist/inspectionInterceptors.js +68 -64
- package/dist/useInspectionMetadata.d.ts +34 -34
- package/dist/useInspectionMetadata.js +67 -67
- package/package.json +1 -1
|
@@ -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 [
|
|
11
|
-
const [
|
|
12
|
-
const [
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
const
|
|
21
|
-
const
|
|
22
|
-
|
|
23
|
-
const
|
|
24
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
155
|
-
|
|
156
|
-
|
|
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
|
-
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;
|