@zargaryanvh/react-component-inspector 1.0.0 → 1.0.2

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,5 +1,5 @@
1
1
  import { Fragment as _Fragment, jsx as _jsx } from "react/jsx-runtime";
2
- import React, { createContext, useContext, useState, useCallback, useEffect } from "react";
2
+ import React, { createContext, useContext, useState, useCallback, useEffect, useRef } from "react";
3
3
  import { setInspectionActive } from "./inspectionInterceptors";
4
4
  import { setupAutoInspection } from "./autoInspection";
5
5
  const InspectionContext = createContext(undefined);
@@ -11,34 +11,136 @@ export const InspectionProvider = ({ children }) => {
11
11
  const [isLocked, setIsLocked] = useState(false);
12
12
  const [hoveredComponent, setHoveredComponentState] = useState(null);
13
13
  const [hoveredElement, setHoveredElement] = useState(null);
14
- // Track CTRL key state and CTRL+H for locking
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
15
46
  React.useEffect(() => {
16
47
  if (process.env.NODE_ENV !== "development") {
17
48
  return; // Only in development
18
49
  }
19
- const handleKeyDown = (e) => {
20
- // H key pressed (with or without CTRL) to lock tooltip position while hovering
21
- if (e.key.toLowerCase() === "h") {
22
- // Only lock if inspection is active, we have a hovered component, and it's not already locked
23
- if (isInspectionActive && hoveredComponent && !isLocked) {
24
- e.preventDefault();
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) {
25
80
  setIsLocked(true);
26
81
  if (process.env.NODE_ENV === "development") {
27
- console.log("[Inspection] Tooltip locked - position fixed. Press H again or release CTRL to unlock.");
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.");
28
90
  }
29
91
  }
30
- // If already locked and H is pressed again, unlock (toggle behavior)
31
- else if (isLocked && isInspectionActive) {
32
- e.preventDefault();
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);
33
103
  setIsLocked(false);
104
+ hKeyPressedRef.current = false;
105
+ setInspectionActive(false);
106
+ setHoveredComponentState(null);
107
+ setHoveredElement(null);
34
108
  if (process.env.NODE_ENV === "development") {
35
- console.log("[Inspection] Tooltip unlocked - inspection continues while CTRL is held.");
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.");
36
138
  }
37
139
  }
38
140
  return;
39
141
  }
40
142
  // CTRL key pressed
41
- if (e.key === "Control" || e.ctrlKey) {
143
+ if (e.key === "Control" && !e.repeat) {
42
144
  setIsInspectionActive(true);
43
145
  setInspectionActive(true);
44
146
  if (process.env.NODE_ENV === "development") {
@@ -47,10 +149,29 @@ export const InspectionProvider = ({ children }) => {
47
149
  }
48
150
  };
49
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
+ }
50
170
  // CTRL key released - unlock and clear
51
- if (e.key === "Control" || (!e.ctrlKey && e.key !== "h")) {
171
+ if (e.key === "Control") {
52
172
  setIsInspectionActive(false);
53
173
  setIsLocked(false);
174
+ hKeyPressedRef.current = false;
54
175
  setInspectionActive(false);
55
176
  setHoveredComponentState(null);
56
177
  setHoveredElement(null);
@@ -58,15 +179,25 @@ export const InspectionProvider = ({ children }) => {
58
179
  console.log("[Inspection] Deactivated");
59
180
  }
60
181
  }
61
- // Note: H key release doesn't unlock anymore - use H key press to toggle lock state
62
182
  };
63
183
  window.addEventListener("keydown", handleKeyDown);
64
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 });
65
189
  return () => {
66
190
  window.removeEventListener("keydown", handleKeyDown);
67
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
+ }
68
199
  };
69
- }, [isInspectionActive, hoveredComponent, isLocked]);
200
+ }, []); // Empty deps - refs handle state access
70
201
  const setHoveredComponent = useCallback((component, element) => {
71
202
  if (process.env.NODE_ENV !== "development") {
72
203
  return; // Only in development
@@ -224,7 +224,7 @@ export const InspectionTooltip = () => {
224
224
  backdropFilter: "blur(8px)",
225
225
  border: "1px solid rgba(255, 255, 255, 0.1)",
226
226
  transition: stablePosition ? "none" : "left 0.1s ease-out, top 0.1s ease-out",
227
- }, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release CTRL to unlock)" }))] }), _jsx(MuiTooltip, { title: copied ? "Copied!" : "Copy metadata", children: _jsx(IconButton, { size: "small", onClick: handleCopy, sx: {
227
+ }, children: [_jsxs(Box, { sx: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", mb: 1 }, children: [_jsxs(Box, { sx: { display: "flex", alignItems: "center", gap: 1 }, children: [_jsx(Typography, { variant: "subtitle2", sx: { color: "#fff", fontWeight: 600, fontSize: "0.875rem" }, children: "Component Inspector" }), isLocked && (_jsx(Typography, { variant: "caption", sx: { color: "#4caf50", fontSize: "0.7rem", fontStyle: "italic" }, children: "(Locked - Release H to unlock)" }))] }), _jsx(MuiTooltip, { title: copied ? "Copied!" : "Copy metadata", children: _jsx(IconButton, { size: "small", onClick: handleCopy, sx: {
228
228
  color: copied ? "#4caf50" : "#fff",
229
229
  "&:hover": { backgroundColor: "rgba(255, 255, 255, 0.1)" },
230
230
  p: 0.5,
@@ -29,6 +29,26 @@ export const InspectionWrapper = ({ componentName, variant, role, usagePath, pro
29
29
  return;
30
30
  setHoveredComponent(null, null);
31
31
  };
32
+ // Touch handlers for mobile
33
+ const handleTouchStart = (e) => {
34
+ if (!isInspectionActive)
35
+ return;
36
+ const target = e.currentTarget;
37
+ const metadata = {
38
+ componentName,
39
+ componentId: generateComponentId(componentName, instanceIndex),
40
+ variant,
41
+ role,
42
+ usagePath,
43
+ instanceIndex,
44
+ propsSignature: formatPropsSignature(props),
45
+ sourceFile,
46
+ };
47
+ setHoveredComponent(metadata, target);
48
+ };
49
+ const handleTouchEnd = () => {
50
+ // Don't clear on touch end - let autoInspection handle it
51
+ };
32
52
  // Clone the child element and add inspection handlers
33
53
  if (!React.isValidElement(children)) {
34
54
  return _jsx(_Fragment, { children: children });
@@ -36,6 +56,8 @@ export const InspectionWrapper = ({ componentName, variant, role, usagePath, pro
36
56
  const existingProps = (children.props || {});
37
57
  const existingOnMouseEnter = existingProps.onMouseEnter;
38
58
  const existingOnMouseLeave = existingProps.onMouseLeave;
59
+ const existingOnTouchStart = existingProps.onTouchStart;
60
+ const existingOnTouchEnd = existingProps.onTouchEnd;
39
61
  const childWithProps = React.cloneElement(children, {
40
62
  onMouseEnter: (e) => {
41
63
  handleMouseEnter(e);
@@ -49,6 +71,18 @@ export const InspectionWrapper = ({ componentName, variant, role, usagePath, pro
49
71
  existingOnMouseLeave(e);
50
72
  }
51
73
  },
74
+ onTouchStart: (e) => {
75
+ handleTouchStart(e);
76
+ if (existingOnTouchStart) {
77
+ existingOnTouchStart(e);
78
+ }
79
+ },
80
+ onTouchEnd: (e) => {
81
+ handleTouchEnd();
82
+ if (existingOnTouchEnd) {
83
+ existingOnTouchEnd(e);
84
+ }
85
+ },
52
86
  "data-inspection-id": generateComponentId(componentName, instanceIndex),
53
87
  "data-inspection-name": componentName,
54
88
  });
@@ -130,6 +130,114 @@ export const parseInspectionMetadata = (element) => {
130
130
  sourceFile: "DOM",
131
131
  };
132
132
  };
133
+ /**
134
+ * Helper function to inspect an element (used by both mouse and touch events)
135
+ */
136
+ const inspectElement = (target, isInspectionActive, isLocked, setHoveredComponent) => {
137
+ if (!isInspectionActive) {
138
+ return;
139
+ }
140
+ // If locked, don't update - keep current tooltip fixed
141
+ if (isLocked) {
142
+ return;
143
+ }
144
+ if (!target || !document.body.contains(target)) {
145
+ return;
146
+ }
147
+ // Always show inspection for any element (not just ones with metadata)
148
+ // Walk up the DOM tree to find a meaningful element to inspect
149
+ let current = target;
150
+ let bestElement = null;
151
+ let bestMetadata = null;
152
+ let elementWithExplicitMetadata = null;
153
+ let metadataWithExplicitId = null;
154
+ // Skip text nodes and very small elements
155
+ const isMeaningfulElement = (el) => {
156
+ const rect = el.getBoundingClientRect();
157
+ // Skip elements that are too small (likely text nodes or empty spans)
158
+ if (rect.width < 5 && rect.height < 5) {
159
+ return false;
160
+ }
161
+ // Prefer elements with explicit metadata, IDs, or meaningful classes
162
+ return !!(el.getAttribute("data-inspection-name") ||
163
+ el.id ||
164
+ el.className ||
165
+ el.tagName !== "SPAN" && el.tagName !== "DIV");
166
+ };
167
+ // First pass: Look for elements with explicit inspection metadata
168
+ // This ensures we always find the same component regardless of which child is hovered
169
+ while (current) {
170
+ // Ensure element is still in the DOM
171
+ if (!document.body.contains(current)) {
172
+ break;
173
+ }
174
+ // Check if this element has explicit inspection metadata
175
+ const hasExplicitMetadata = current.getAttribute("data-inspection-name") &&
176
+ current.getAttribute("data-inspection-id");
177
+ if (hasExplicitMetadata) {
178
+ const metadata = parseInspectionMetadata(current);
179
+ if (metadata) {
180
+ elementWithExplicitMetadata = current;
181
+ metadataWithExplicitId = metadata;
182
+ break; // Found explicit metadata, use it
183
+ }
184
+ }
185
+ current = current.parentElement;
186
+ // Stop at body to avoid inspecting the entire page
187
+ if (current && (current.tagName === "BODY" || current.tagName === "HTML")) {
188
+ break;
189
+ }
190
+ }
191
+ // If we found explicit metadata, use it (this ensures consistent IDs)
192
+ if (elementWithExplicitMetadata && metadataWithExplicitId) {
193
+ if (process.env.NODE_ENV === "development") {
194
+ console.log("[Inspection] Found element with explicit metadata:", metadataWithExplicitId.componentName);
195
+ }
196
+ setHoveredComponent(metadataWithExplicitId, elementWithExplicitMetadata);
197
+ return;
198
+ }
199
+ // Second pass: If no explicit metadata found, look for meaningful elements
200
+ // Reset current to target for second pass
201
+ current = target;
202
+ while (current) {
203
+ // Ensure element is still in the DOM
204
+ if (!document.body.contains(current)) {
205
+ break;
206
+ }
207
+ // Check if this is a meaningful element
208
+ if (isMeaningfulElement(current)) {
209
+ const metadata = parseInspectionMetadata(current);
210
+ if (metadata) {
211
+ // Keep the first meaningful element (closest to target)
212
+ if (!bestElement) {
213
+ bestElement = current;
214
+ bestMetadata = metadata;
215
+ }
216
+ }
217
+ }
218
+ current = current.parentElement;
219
+ // Stop at body to avoid inspecting the entire page
220
+ if (current && (current.tagName === "BODY" || current.tagName === "HTML")) {
221
+ break;
222
+ }
223
+ }
224
+ if (bestElement && bestMetadata) {
225
+ if (process.env.NODE_ENV === "development") {
226
+ console.log("[Inspection] Found element:", bestMetadata.componentName);
227
+ }
228
+ setHoveredComponent(bestMetadata, bestElement);
229
+ }
230
+ else {
231
+ // Fallback: show info for the target element itself
232
+ const fallbackMetadata = parseInspectionMetadata(target);
233
+ if (fallbackMetadata) {
234
+ setHoveredComponent(fallbackMetadata, target);
235
+ }
236
+ else {
237
+ setHoveredComponent(null, null);
238
+ }
239
+ }
240
+ };
133
241
  /**
134
242
  * Setup global mouse event listener for automatic inspection detection
135
243
  * This works with components that have data-inspection-* attributes
@@ -138,6 +246,10 @@ export const setupAutoInspection = (setHoveredComponent, isInspectionActive, isL
138
246
  if (process.env.NODE_ENV !== "development") {
139
247
  return () => { }; // No-op in production
140
248
  }
249
+ // Detect if device supports touch
250
+ const isTouchDevice = 'ontouchstart' in window ||
251
+ navigator.maxTouchPoints > 0 ||
252
+ /Android|webOS|iPhone|iPad|iPod|BlackBerry|IEMobile|Opera Mini/i.test(navigator.userAgent);
141
253
  const handleMouseMove = (e) => {
142
254
  if (!isInspectionActive) {
143
255
  return;
@@ -147,101 +259,33 @@ export const setupAutoInspection = (setHoveredComponent, isInspectionActive, isL
147
259
  return;
148
260
  }
149
261
  const target = e.target;
150
- if (!target || !document.body.contains(target)) {
262
+ inspectElement(target, isInspectionActive, isLocked, setHoveredComponent);
263
+ };
264
+ // Touch move handler for mobile devices
265
+ const handleTouchMove = (e) => {
266
+ // Only process if inspection is active and we have touches
267
+ if (!isInspectionActive || e.touches.length === 0) {
151
268
  return;
152
269
  }
153
- // Always show inspection for any element (not just ones with metadata)
154
- // Walk up the DOM tree to find a meaningful element to inspect
155
- let current = target;
156
- let bestElement = null;
157
- let bestMetadata = null;
158
- let elementWithExplicitMetadata = null;
159
- let metadataWithExplicitId = null;
160
- // Skip text nodes and very small elements
161
- const isMeaningfulElement = (el) => {
162
- const rect = el.getBoundingClientRect();
163
- // Skip elements that are too small (likely text nodes or empty spans)
164
- if (rect.width < 5 && rect.height < 5) {
165
- return false;
166
- }
167
- // Prefer elements with explicit metadata, IDs, or meaningful classes
168
- return !!(el.getAttribute("data-inspection-name") ||
169
- el.id ||
170
- el.className ||
171
- el.tagName !== "SPAN" && el.tagName !== "DIV");
172
- };
173
- // First pass: Look for elements with explicit inspection metadata
174
- // This ensures we always find the same component regardless of which child is hovered
175
- while (current) {
176
- // Ensure element is still in the DOM
177
- if (!document.body.contains(current)) {
178
- break;
179
- }
180
- // Check if this element has explicit inspection metadata
181
- const hasExplicitMetadata = current.getAttribute("data-inspection-name") &&
182
- current.getAttribute("data-inspection-id");
183
- if (hasExplicitMetadata) {
184
- const metadata = parseInspectionMetadata(current);
185
- if (metadata) {
186
- elementWithExplicitMetadata = current;
187
- metadataWithExplicitId = metadata;
188
- break; // Found explicit metadata, use it
189
- }
190
- }
191
- current = current.parentElement;
192
- // Stop at body to avoid inspecting the entire page
193
- if (current && (current.tagName === "BODY" || current.tagName === "HTML")) {
194
- break;
195
- }
270
+ // Get the element under the first touch point
271
+ const touch = e.touches[0];
272
+ const target = document.elementFromPoint(touch.clientX, touch.clientY);
273
+ if (target) {
274
+ inspectElement(target, isInspectionActive, isLocked, setHoveredComponent);
196
275
  }
197
- // If we found explicit metadata, use it (this ensures consistent IDs)
198
- if (elementWithExplicitMetadata && metadataWithExplicitId) {
199
- if (process.env.NODE_ENV === "development") {
200
- console.log("[Inspection] Found element with explicit metadata:", metadataWithExplicitId.componentName);
201
- }
202
- setHoveredComponent(metadataWithExplicitId, elementWithExplicitMetadata);
276
+ };
277
+ // Touch start handler for mobile - inspect on touch
278
+ const handleTouchStart = (e) => {
279
+ // Only process if inspection is active and we have touches
280
+ // Skip if it's a 3-finger touch (used for activation)
281
+ if (!isInspectionActive || e.touches.length === 3) {
203
282
  return;
204
283
  }
205
- // Second pass: If no explicit metadata found, look for meaningful elements
206
- // Reset current to target for second pass
207
- current = target;
208
- while (current) {
209
- // Ensure element is still in the DOM
210
- if (!document.body.contains(current)) {
211
- break;
212
- }
213
- // Check if this is a meaningful element
214
- if (isMeaningfulElement(current)) {
215
- const metadata = parseInspectionMetadata(current);
216
- if (metadata) {
217
- // Keep the first meaningful element (closest to target)
218
- if (!bestElement) {
219
- bestElement = current;
220
- bestMetadata = metadata;
221
- }
222
- }
223
- }
224
- current = current.parentElement;
225
- // Stop at body to avoid inspecting the entire page
226
- if (current && (current.tagName === "BODY" || current.tagName === "HTML")) {
227
- break;
228
- }
229
- }
230
- if (bestElement && bestMetadata) {
231
- if (process.env.NODE_ENV === "development") {
232
- console.log("[Inspection] Found element:", bestMetadata.componentName);
233
- }
234
- setHoveredComponent(bestMetadata, bestElement);
235
- }
236
- else {
237
- // Fallback: show info for the target element itself
238
- const fallbackMetadata = parseInspectionMetadata(target);
239
- if (fallbackMetadata) {
240
- setHoveredComponent(fallbackMetadata, target);
241
- }
242
- else {
243
- setHoveredComponent(null, null);
244
- }
284
+ // Get the element under the first touch point
285
+ const touch = e.touches[0];
286
+ const target = document.elementFromPoint(touch.clientX, touch.clientY);
287
+ if (target) {
288
+ inspectElement(target, isInspectionActive, isLocked, setHoveredComponent);
245
289
  }
246
290
  };
247
291
  const handleMouseLeave = () => {
@@ -249,10 +293,28 @@ export const setupAutoInspection = (setHoveredComponent, isInspectionActive, isL
249
293
  setHoveredComponent(null, null);
250
294
  }
251
295
  };
296
+ const handleTouchEnd = () => {
297
+ // Clear inspection on touch end (unless locked)
298
+ if (isInspectionActive && !isLocked) {
299
+ setHoveredComponent(null, null);
300
+ }
301
+ };
302
+ // Add mouse event listeners
252
303
  window.addEventListener("mousemove", handleMouseMove);
253
304
  document.addEventListener("mouseleave", handleMouseLeave);
305
+ // Add touch event listeners for mobile devices
306
+ if (isTouchDevice) {
307
+ window.addEventListener("touchmove", handleTouchMove, { passive: true });
308
+ window.addEventListener("touchstart", handleTouchStart, { passive: true });
309
+ window.addEventListener("touchend", handleTouchEnd, { passive: true });
310
+ }
254
311
  return () => {
255
312
  window.removeEventListener("mousemove", handleMouseMove);
256
313
  document.removeEventListener("mouseleave", handleMouseLeave);
314
+ if (isTouchDevice) {
315
+ window.removeEventListener("touchmove", handleTouchMove);
316
+ window.removeEventListener("touchstart", handleTouchStart);
317
+ window.removeEventListener("touchend", handleTouchEnd);
318
+ }
257
319
  };
258
320
  };
@@ -27,6 +27,8 @@ export declare const useInspectionMetadata: (config: {
27
27
  }) => {
28
28
  onMouseEnter: (e: React.MouseEvent) => void;
29
29
  onMouseLeave: () => void;
30
+ onTouchStart: (e: React.TouchEvent) => void;
31
+ onTouchEnd: () => void;
30
32
  "data-inspection-id": string;
31
33
  "data-inspection-name": string;
32
34
  };
@@ -43,9 +43,24 @@ export const useInspectionMetadata = (config) => {
43
43
  return;
44
44
  setHoveredComponent(null, null);
45
45
  };
46
+ // Touch handlers for mobile
47
+ const handleTouchStart = (e) => {
48
+ if (!isInspectionActive)
49
+ return;
50
+ const target = e.currentTarget;
51
+ setHoveredComponent(metadata, target);
52
+ };
53
+ const handleTouchEnd = () => {
54
+ if (!isInspectionActive)
55
+ return;
56
+ // Don't clear on touch end - let autoInspection handle it
57
+ // This allows touch move to continue inspecting
58
+ };
46
59
  return {
47
60
  onMouseEnter: handleMouseEnter,
48
61
  onMouseLeave: handleMouseLeave,
62
+ onTouchStart: handleTouchStart,
63
+ onTouchEnd: handleTouchEnd,
49
64
  "data-inspection-id": metadata.componentId,
50
65
  "data-inspection-name": config.componentName,
51
66
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@zargaryanvh/react-component-inspector",
3
- "version": "1.0.0",
3
+ "version": "1.0.2",
4
4
  "description": "A development tool for inspecting React components with AI-friendly metadata extraction",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",