@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.
- package/dist/InspectionContext.d.ts +37 -35
- package/dist/InspectionContext.js +236 -153
- 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 +382 -232
- package/dist/InspectionWrapper.d.ts +28 -28
- package/dist/InspectionWrapper.js +102 -68
- package/dist/autoInspection.d.ts +18 -14
- package/dist/autoInspection.js +329 -258
- package/dist/index.d.ts +10 -8
- package/dist/index.js +10 -9
- package/dist/inspection.d.ts +56 -29
- package/dist/inspection.js +420 -140
- package/dist/inspectionInterceptors.d.ts +27 -27
- package/dist/inspectionInterceptors.js +68 -64
- package/dist/useInspectionMetadata.d.ts +34 -32
- package/dist/useInspectionMetadata.js +67 -52
- package/package.json +1 -1
|
@@ -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
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
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 [
|
|
11
|
-
const [
|
|
12
|
-
const
|
|
13
|
-
const [
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
const
|
|
17
|
-
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
useEffect(() => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
if (
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
if (
|
|
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
|
-
|
|
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;
|