react-native-grab 0.0.2 → 0.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.
Files changed (75) hide show
  1. package/README.md +5 -5
  2. package/dist/cjs/metro/withReactNativeGrab.js +31 -31
  3. package/dist/cjs/metro/withReactNativeGrab.js.map +1 -1
  4. package/dist/cjs/react-native/copy-payload.js +65 -69
  5. package/dist/cjs/react-native/copy-payload.js.map +1 -1
  6. package/dist/cjs/react-native/dom-traversal.js +178 -129
  7. package/dist/cjs/react-native/dom-traversal.js.map +1 -1
  8. package/dist/cjs/react-native/fiber.js +16 -16
  9. package/dist/cjs/react-native/fiber.js.map +1 -1
  10. package/dist/cjs/react-native/get-dev-server.js +9 -0
  11. package/dist/cjs/react-native/get-dev-server.js.map +1 -0
  12. package/dist/cjs/react-native/index.js +346 -260
  13. package/dist/cjs/react-native/index.js.map +1 -1
  14. package/dist/cjs/react-native/index.web.js +8 -0
  15. package/dist/cjs/react-native/index.web.js.map +1 -0
  16. package/dist/cjs/react-native/pointer-events.js +25 -28
  17. package/dist/cjs/react-native/pointer-events.js.map +1 -1
  18. package/dist/cjs/react-native/selection-trigger.js +1 -1
  19. package/dist/cjs/react-native/settings.js +7 -10
  20. package/dist/cjs/react-native/settings.js.map +1 -1
  21. package/dist/cjs/react-native/symbolicate.js.map +1 -1
  22. package/dist/cjs/react-native/types.js +0 -4
  23. package/dist/cjs/react-native/types.js.map +1 -1
  24. package/dist/cjs/react-native/z-index.js +10 -2
  25. package/dist/cjs/react-native/z-index.js.map +1 -1
  26. package/dist/esm/metro/index.js +1 -1
  27. package/dist/esm/metro/withReactNativeGrab.js +33 -33
  28. package/dist/esm/metro/withReactNativeGrab.js.map +1 -1
  29. package/dist/esm/react-native/copy-payload.js +67 -71
  30. package/dist/esm/react-native/copy-payload.js.map +1 -1
  31. package/dist/esm/react-native/dom-traversal.js +181 -130
  32. package/dist/esm/react-native/dom-traversal.js.map +1 -1
  33. package/dist/esm/react-native/fiber.js +17 -17
  34. package/dist/esm/react-native/fiber.js.map +1 -1
  35. package/dist/esm/react-native/get-dev-server.js +3 -0
  36. package/dist/esm/react-native/get-dev-server.js.map +1 -0
  37. package/dist/esm/react-native/index.js +351 -262
  38. package/dist/esm/react-native/index.js.map +1 -1
  39. package/dist/esm/react-native/index.web.js +4 -0
  40. package/dist/esm/react-native/index.web.js.map +1 -0
  41. package/dist/esm/react-native/pointer-events.js +26 -29
  42. package/dist/esm/react-native/pointer-events.js.map +1 -1
  43. package/dist/esm/react-native/selection-trigger.js +1 -1
  44. package/dist/esm/react-native/settings.js +6 -6
  45. package/dist/esm/react-native/settings.js.map +1 -1
  46. package/dist/esm/react-native/symbolicate.js +2 -2
  47. package/dist/esm/react-native/symbolicate.js.map +1 -1
  48. package/dist/esm/react-native/types.js +0 -4
  49. package/dist/esm/react-native/types.js.map +1 -1
  50. package/dist/esm/react-native/z-index.js +11 -3
  51. package/dist/esm/react-native/z-index.js.map +1 -1
  52. package/dist/types/metro/index.d.ts +1 -1
  53. package/dist/types/metro/withReactNativeGrab.d.ts.map +1 -1
  54. package/dist/types/react-native/copy-payload.d.ts +1 -1
  55. package/dist/types/react-native/copy-payload.d.ts.map +1 -1
  56. package/dist/types/react-native/dom-traversal.d.ts +19 -6
  57. package/dist/types/react-native/dom-traversal.d.ts.map +1 -1
  58. package/dist/types/react-native/fiber.d.ts +1 -1
  59. package/dist/types/react-native/fiber.d.ts.map +1 -1
  60. package/dist/types/react-native/get-dev-server.d.ts +3 -0
  61. package/dist/types/react-native/get-dev-server.d.ts.map +1 -0
  62. package/dist/types/react-native/index.d.ts +1 -1
  63. package/dist/types/react-native/index.d.ts.map +1 -1
  64. package/dist/types/react-native/index.web.d.ts +2 -0
  65. package/dist/types/react-native/index.web.d.ts.map +1 -0
  66. package/dist/types/react-native/pointer-events.d.ts +2 -2
  67. package/dist/types/react-native/pointer-events.d.ts.map +1 -1
  68. package/dist/types/react-native/settings.d.ts.map +1 -1
  69. package/dist/types/react-native/symbolicate.d.ts +1 -1
  70. package/dist/types/react-native/symbolicate.d.ts.map +1 -1
  71. package/dist/types/react-native/types.d.ts +3 -11
  72. package/dist/types/react-native/types.d.ts.map +1 -1
  73. package/dist/types/react-native/z-index.d.ts +1 -1
  74. package/dist/types/react-native/z-index.d.ts.map +1 -1
  75. package/package.json +26 -26
@@ -1,13 +1,10 @@
1
1
  "use strict";
2
- var __importDefault = (this && this.__importDefault) || function (mod) {
3
- return (mod && mod.__esModule) ? mod : { "default": mod };
4
- };
5
2
  Object.defineProperty(exports, "__esModule", { value: true });
6
3
  exports.ElementInspector = void 0;
7
4
  const jsx_runtime_1 = require("react/jsx-runtime");
8
5
  const react_1 = require("react");
9
6
  const react_native_1 = require("react-native");
10
- const getDevServer_1 = __importDefault(require("react-native/Libraries/Core/Devtools/getDevServer"));
7
+ const get_dev_server_1 = require("./get-dev-server");
11
8
  const react_native_screens_1 = require("react-native-screens");
12
9
  const copy_payload_1 = require("./copy-payload");
13
10
  const dom_traversal_1 = require("./dom-traversal");
@@ -17,26 +14,19 @@ const SETTLE_MS = 80;
17
14
  const MOVEMENT_EPSILON_PX = 8;
18
15
  const MAX_STACK_LINES = 3;
19
16
  const MAX_PREVIEW_TEXT = 120;
20
- const INSPECTOR_COPY_ENDPOINT = '/__react-native-grab/copy';
17
+ const INSPECTOR_COPY_ENDPOINT = "/__react-native-grab/copy";
21
18
  const FAB_SIZE = 56;
22
19
  const FAB_SCREEN_PADDING = 8;
23
20
  const FAB_DEFAULT_RIGHT_OFFSET = 20;
24
21
  const FAB_DEFAULT_BOTTOM_OFFSET = 32;
25
22
  const getMetroBaseUrl = () => {
26
- const devServer = (0, getDevServer_1.default)();
23
+ const devServer = (0, get_dev_server_1.getDevServer)();
27
24
  if (!devServer?.url)
28
25
  return null;
29
- return devServer.url.replace(/\/$/, '');
26
+ return devServer.url.replace(/\/$/, "");
30
27
  };
31
- const ElementInspector = () => {
32
- if (react_native_1.Platform.OS === 'web') {
33
- return null;
34
- }
35
- return (0, jsx_runtime_1.jsx)(ElementInspectorImpl, {});
36
- };
37
- exports.ElementInspector = ElementInspector;
38
28
  const getInitialFabPosition = () => {
39
- const { width, height } = react_native_1.Dimensions.get('window');
29
+ const { width, height } = react_native_1.Dimensions.get("window");
40
30
  return {
41
31
  x: Math.max(FAB_SCREEN_PADDING, width - FAB_DEFAULT_RIGHT_OFFSET - FAB_SIZE),
42
32
  y: Math.max(FAB_SCREEN_PADDING, height - FAB_DEFAULT_BOTTOM_OFFSET - FAB_SIZE),
@@ -50,74 +40,227 @@ const clampFabPosition = (x, y, windowWidth, windowHeight) => {
50
40
  y: Math.min(Math.max(y, FAB_SCREEN_PADDING), maxY),
51
41
  };
52
42
  };
53
- const ElementInspectorImpl = () => {
54
- const { width: windowWidth, height: windowHeight } = (0, react_native_1.useWindowDimensions)();
55
- const statusBarOffset = react_native_1.Platform.OS === 'android' ? (react_native_1.StatusBar.currentHeight ?? 0) : 0;
56
- const [state, setState] = (0, react_1.useState)({ status: 'idle' });
57
- const [highlightRect, setHighlightRect] = (0, react_1.useState)(null);
58
- const [toastVisible, setToastVisible] = (0, react_1.useState)(false);
59
- const [selectionPillVisible, setSelectionPillVisible] = (0, react_1.useState)(() => (0, settings_1.getInspectorSettings)().selectionPillVisible);
60
- const isClosingRef = (0, react_1.useRef)(false);
61
- const anchorRef = (0, react_1.useRef)(null);
62
- const inspectorRootRef = (0, react_1.useRef)(null);
63
- const overlayRef = (0, react_1.useRef)(null);
64
- const selectedHitTargetRef = (0, react_1.useRef)(null);
65
- const lastQueryPositionRef = (0, react_1.useRef)(null);
66
- const movePositionRef = (0, react_1.useRef)({ x: 0, y: 0 });
67
- const settleTimeoutRef = (0, react_1.useRef)(null);
68
- const stateRef = (0, react_1.useRef)(state);
69
- stateRef.current = state;
70
- const selectingHintAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
71
- const toastAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
72
- const fabAppearAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
73
- const fabPressAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
74
- const fabPosition = (0, react_1.useRef)(new react_native_1.Animated.ValueXY(getInitialFabPosition())).current;
75
- const fabPositionRef = (0, react_1.useRef)(getInitialFabPosition());
76
- const fabDragStartOffsetRef = (0, react_1.useRef)({ x: 0, y: 0 });
77
- const windowSizeRef = (0, react_1.useRef)({ width: windowWidth, height: windowHeight });
78
- windowSizeRef.current = { width: windowWidth, height: windowHeight };
79
- const skipRefs = [inspectorRootRef, overlayRef];
80
- const idle = state.status === 'idle';
81
- const selecting = state.status === 'selecting';
82
- const overlayVisible = state.status !== 'idle';
83
- const activeHighlightRect = state.status === 'copying' ? state.selectedRect : highlightRect;
84
- const startSelecting = () => {
85
- selectedHitTargetRef.current = null;
86
- setState({ status: 'selecting' });
87
- };
88
- const performQuery = (x, y, offset) => {
89
- const appY = y - statusBarOffset;
90
- const hitTarget = (0, dom_traversal_1.findHitTargetAtPoint)(anchorRef, skipRefs, x, appY, react_native_1.Platform.OS === 'ios'
43
+ const useLatestRef = (value) => {
44
+ const ref = (0, react_1.useRef)(value);
45
+ ref.current = value;
46
+ return ref;
47
+ };
48
+ const useTraversalSelectionSession = ({ anchorRef, inspectorRootRef, overlayRef, statusBarOffset, setHighlightRect, }) => {
49
+ const traversalSessionRef = (0, react_1.useRef)(null);
50
+ const skipRefsRef = (0, react_1.useRef)([inspectorRootRef, overlayRef]);
51
+ const stopSession = (0, react_1.useCallback)(() => {
52
+ traversalSessionRef.current?.stop();
53
+ traversalSessionRef.current = null;
54
+ }, []);
55
+ const startSession = (0, react_1.useCallback)(() => {
56
+ const skipPredicate = react_native_1.Platform.OS === "ios"
91
57
  ? (node) => {
92
58
  const inspectorRootNode = inspectorRootRef.current;
93
59
  if (!inspectorRootNode)
94
60
  return false;
95
61
  return node === inspectorRootNode.parentElement;
96
62
  }
97
- : undefined, offset);
63
+ : undefined;
64
+ stopSession();
65
+ traversalSessionRef.current = (0, dom_traversal_1.startSession)({
66
+ anchorRef,
67
+ skipRefs: skipRefsRef.current,
68
+ offset: statusBarOffset,
69
+ skipPredicate,
70
+ });
71
+ setHighlightRect(null);
72
+ }, [anchorRef, inspectorRootRef, setHighlightRect, statusBarOffset, stopSession]);
73
+ const queryAtPoint = (0, react_1.useCallback)((x, y) => {
74
+ const session = traversalSessionRef.current;
75
+ if (!session) {
76
+ setHighlightRect(null);
77
+ return null;
78
+ }
79
+ const hitTarget = session.move({
80
+ x,
81
+ y: y - statusBarOffset,
82
+ });
98
83
  setHighlightRect(hitTarget?.rect ?? null);
99
84
  return hitTarget;
85
+ }, [setHighlightRect, statusBarOffset]);
86
+ (0, react_1.useEffect)(() => {
87
+ return () => {
88
+ stopSession();
89
+ };
90
+ }, [stopSession]);
91
+ return {
92
+ startSession,
93
+ queryAtPoint,
94
+ stopSession,
100
95
  };
101
- const performQueryRef = (0, react_1.useRef)(performQuery);
102
- performQueryRef.current = performQuery;
103
- const closeOverlayImmediately = () => {
96
+ };
97
+ const useSelectionGesture = ({ isSelecting, queryAtPoint, onHitTarget, onCancel, }) => {
98
+ const isSelectingRef = useLatestRef(isSelecting);
99
+ const queryAtPointRef = useLatestRef(queryAtPoint);
100
+ const onHitTargetRef = useLatestRef(onHitTarget);
101
+ const onCancelRef = useLatestRef(onCancel);
102
+ const settleTimeoutRef = (0, react_1.useRef)(null);
103
+ const lastQueryPositionRef = (0, react_1.useRef)(null);
104
+ const movePositionRef = (0, react_1.useRef)({ x: 0, y: 0 });
105
+ const clearSettleTimeout = (0, react_1.useCallback)(() => {
104
106
  if (settleTimeoutRef.current != null) {
105
107
  clearTimeout(settleTimeoutRef.current);
106
108
  settleTimeoutRef.current = null;
107
109
  }
108
- selectedHitTargetRef.current = null;
110
+ }, []);
111
+ const resetGestureState = (0, react_1.useCallback)(() => {
112
+ clearSettleTimeout();
109
113
  lastQueryPositionRef.current = null;
110
- setState({ status: 'idle' });
111
- setHighlightRect(null);
114
+ }, [clearSettleTimeout]);
115
+ const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
116
+ onStartShouldSetPanResponder: () => true,
117
+ onMoveShouldSetPanResponder: () => true,
118
+ onPanResponderGrant: (evt) => {
119
+ if (!isSelectingRef.current)
120
+ return;
121
+ const x = evt.nativeEvent.locationX;
122
+ const y = evt.nativeEvent.locationY;
123
+ queryAtPointRef.current(x, y);
124
+ lastQueryPositionRef.current = { x, y };
125
+ },
126
+ onPanResponderMove: (evt) => {
127
+ if (!isSelectingRef.current)
128
+ return;
129
+ movePositionRef.current = {
130
+ x: evt.nativeEvent.locationX,
131
+ y: evt.nativeEvent.locationY,
132
+ };
133
+ clearSettleTimeout();
134
+ settleTimeoutRef.current = setTimeout(() => {
135
+ settleTimeoutRef.current = null;
136
+ if (!isSelectingRef.current)
137
+ return;
138
+ const pos = movePositionRef.current;
139
+ const last = lastQueryPositionRef.current;
140
+ const distance = last ? Math.hypot(pos.x - last.x, pos.y - last.y) : Infinity;
141
+ if (distance < MOVEMENT_EPSILON_PX)
142
+ return;
143
+ lastQueryPositionRef.current = pos;
144
+ queryAtPointRef.current(pos.x, pos.y);
145
+ }, SETTLE_MS);
146
+ },
147
+ onPanResponderRelease: (evt) => {
148
+ if (!isSelectingRef.current)
149
+ return;
150
+ const hitTarget = queryAtPointRef.current(evt.nativeEvent.locationX, evt.nativeEvent.locationY);
151
+ if (!hitTarget) {
152
+ onCancelRef.current();
153
+ return;
154
+ }
155
+ onHitTargetRef.current(hitTarget);
156
+ },
157
+ onPanResponderTerminate: () => {
158
+ if (isSelectingRef.current) {
159
+ onCancelRef.current();
160
+ }
161
+ },
162
+ })).current;
163
+ (0, react_1.useEffect)(() => {
164
+ return () => {
165
+ clearSettleTimeout();
166
+ };
167
+ }, [clearSettleTimeout]);
168
+ return {
169
+ panHandlers: panResponder.panHandlers,
170
+ resetGestureState,
112
171
  };
113
- const closeOverlay = () => {
172
+ };
173
+ const useFabController = (windowWidth, windowHeight) => {
174
+ const initialFabPosition = (0, react_1.useRef)(getInitialFabPosition()).current;
175
+ const fabAppearAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
176
+ const fabPressAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(1)).current;
177
+ const fabPosition = (0, react_1.useRef)(new react_native_1.Animated.ValueXY(initialFabPosition)).current;
178
+ const fabPositionRef = (0, react_1.useRef)(initialFabPosition);
179
+ const fabDragStartOffsetRef = (0, react_1.useRef)({ x: 0, y: 0 });
180
+ const windowSizeRef = (0, react_1.useRef)({ width: windowWidth, height: windowHeight });
181
+ windowSizeRef.current = { width: windowWidth, height: windowHeight };
182
+ const animateFabPress = (0, react_1.useCallback)((toValue) => {
183
+ react_native_1.Animated.spring(fabPressAnim, {
184
+ toValue,
185
+ useNativeDriver: true,
186
+ tension: 220,
187
+ friction: 16,
188
+ }).start();
189
+ }, [fabPressAnim]);
190
+ const fabPanResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
191
+ onStartShouldSetPanResponder: () => false,
192
+ onMoveShouldSetPanResponder: (_evt, gestureState) => Math.abs(gestureState.dx) > 2 || Math.abs(gestureState.dy) > 2,
193
+ onPanResponderGrant: (_evt, gestureState) => {
194
+ fabDragStartOffsetRef.current = {
195
+ x: fabPositionRef.current.x - gestureState.x0,
196
+ y: fabPositionRef.current.y - gestureState.y0,
197
+ };
198
+ },
199
+ onPanResponderMove: (_evt, gestureState) => {
200
+ const { width, height } = windowSizeRef.current;
201
+ const nextPosition = clampFabPosition(gestureState.moveX + fabDragStartOffsetRef.current.x, gestureState.moveY + fabDragStartOffsetRef.current.y, width, height);
202
+ fabPositionRef.current = nextPosition;
203
+ fabPosition.setValue(nextPosition);
204
+ },
205
+ onPanResponderTerminationRequest: () => true,
206
+ onPanResponderTerminate: () => {
207
+ fabPressAnim.setValue(1);
208
+ },
209
+ onPanResponderRelease: () => {
210
+ fabPressAnim.setValue(1);
211
+ },
212
+ })).current;
213
+ (0, react_1.useEffect)(() => {
214
+ const nextPosition = clampFabPosition(fabPositionRef.current.x, fabPositionRef.current.y, windowWidth, windowHeight);
215
+ fabPositionRef.current = nextPosition;
216
+ fabPosition.setValue(nextPosition);
217
+ }, [fabPosition, windowHeight, windowWidth]);
218
+ return {
219
+ animateFabPress,
220
+ fabAppearAnim,
221
+ fabPanHandlers: fabPanResponder.panHandlers,
222
+ fabPosition,
223
+ fabPressAnim,
224
+ };
225
+ };
226
+ const ElementInspector = () => {
227
+ const { width: windowWidth, height: windowHeight } = (0, react_native_1.useWindowDimensions)();
228
+ const statusBarOffset = react_native_1.Platform.OS === "android" ? (react_native_1.StatusBar.currentHeight ?? 0) : 0;
229
+ const [state, setState] = (0, react_1.useState)({ status: "idle" });
230
+ const [highlightRect, setHighlightRect] = (0, react_1.useState)(null);
231
+ const [toastVisible, setToastVisible] = (0, react_1.useState)(false);
232
+ const [selectionPillVisible, setSelectionPillVisible] = (0, react_1.useState)(() => (0, settings_1.getInspectorSettings)().selectionPillVisible);
233
+ const isClosingRef = (0, react_1.useRef)(false);
234
+ const anchorRef = (0, react_1.useRef)(null);
235
+ const inspectorRootRef = (0, react_1.useRef)(null);
236
+ const overlayRef = (0, react_1.useRef)(null);
237
+ const selectingHintAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
238
+ const toastAnim = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
239
+ const idle = state.status === "idle";
240
+ const selecting = state.status === "selecting";
241
+ const overlayVisible = state.status !== "idle";
242
+ const activeHighlightRect = state.status === "copying" ? state.selectedRect : highlightRect;
243
+ const { animateFabPress, fabAppearAnim, fabPanHandlers, fabPosition, fabPressAnim } = useFabController(windowWidth, windowHeight);
244
+ const { queryAtPoint, startSession, stopSession } = useTraversalSelectionSession({
245
+ anchorRef,
246
+ inspectorRootRef,
247
+ overlayRef,
248
+ statusBarOffset,
249
+ setHighlightRect,
250
+ });
251
+ const closeOverlayImmediately = (0, react_1.useCallback)(() => {
252
+ stopSession();
253
+ setState({ status: "idle" });
254
+ setHighlightRect(null);
255
+ }, [stopSession]);
256
+ const closeOverlay = (0, react_1.useCallback)(() => {
114
257
  if (isClosingRef.current)
115
258
  return;
116
259
  isClosingRef.current = true;
117
260
  closeOverlayImmediately();
118
261
  isClosingRef.current = false;
119
- };
120
- const showCopiedToast = () => {
262
+ }, [closeOverlayImmediately]);
263
+ const showCopiedToast = (0, react_1.useCallback)(() => {
121
264
  setToastVisible(true);
122
265
  toastAnim.setValue(0);
123
266
  react_native_1.Animated.sequence([
@@ -133,15 +276,15 @@ const ElementInspectorImpl = () => {
133
276
  useNativeDriver: true,
134
277
  }),
135
278
  ]).start(() => setToastVisible(false));
136
- };
137
- const copyFeedbackToHostClipboard = async (text) => {
279
+ }, [toastAnim]);
280
+ const copyFeedbackToHostClipboard = (0, react_1.useCallback)(async (text) => {
138
281
  const baseUrl = getMetroBaseUrl();
139
282
  if (!baseUrl)
140
283
  return false;
141
284
  try {
142
285
  const response = await fetch(`${baseUrl}${INSPECTOR_COPY_ENDPOINT}`, {
143
- method: 'POST',
144
- headers: { 'Content-Type': 'application/json' },
286
+ method: "POST",
287
+ headers: { "Content-Type": "application/json" },
145
288
  body: JSON.stringify({ text }),
146
289
  });
147
290
  return response.ok;
@@ -149,7 +292,33 @@ const ElementInspectorImpl = () => {
149
292
  catch {
150
293
  return false;
151
294
  }
152
- };
295
+ }, []);
296
+ const handleSelectionHit = (0, react_1.useCallback)((hitTarget) => {
297
+ setState({ status: "copying", selectedRect: hitTarget.rect });
298
+ void (async () => {
299
+ const clipboardText = await (0, copy_payload_1.buildInspectorCopyPayload)(hitTarget, {
300
+ maxStackLines: MAX_STACK_LINES,
301
+ maxPreviewText: MAX_PREVIEW_TEXT,
302
+ pathRootHint: null,
303
+ });
304
+ const copied = await copyFeedbackToHostClipboard(clipboardText);
305
+ if (copied) {
306
+ showCopiedToast();
307
+ }
308
+ closeOverlay();
309
+ })();
310
+ }, [closeOverlay, copyFeedbackToHostClipboard, showCopiedToast]);
311
+ const { panHandlers: selectionPanHandlers, resetGestureState } = useSelectionGesture({
312
+ isSelecting: selecting,
313
+ queryAtPoint,
314
+ onHitTarget: handleSelectionHit,
315
+ onCancel: closeOverlay,
316
+ });
317
+ const startSelecting = (0, react_1.useCallback)(() => {
318
+ resetGestureState();
319
+ startSession();
320
+ setState({ status: "selecting" });
321
+ }, [resetGestureState, startSession]);
153
322
  (0, react_1.useEffect)(() => {
154
323
  return (0, settings_1.subscribeInspectorSettings)(() => {
155
324
  setSelectionPillVisible((0, settings_1.getInspectorSettings)().selectionPillVisible);
@@ -157,15 +326,14 @@ const ElementInspectorImpl = () => {
157
326
  }, []);
158
327
  (0, react_1.useEffect)(() => {
159
328
  return (0, selection_trigger_1.subscribeInspectorSelectionRequest)(() => {
160
- setHighlightRect(null);
161
329
  startSelecting();
162
330
  });
163
- }, []);
331
+ }, [startSelecting]);
164
332
  (0, react_1.useEffect)(() => {
165
333
  void (0, settings_1.initializeInspectorSettings)();
166
334
  }, []);
167
335
  (0, react_1.useEffect)(() => {
168
- react_native_1.DevSettings.addMenuItem('Toggle React Native Grab', () => {
336
+ react_native_1.DevSettings.addMenuItem("Toggle React Native Grab", () => {
169
337
  void (async () => {
170
338
  await (0, settings_1.initializeInspectorSettings)();
171
339
  const current = (0, settings_1.getInspectorSettings)().selectionPillVisible;
@@ -173,104 +341,11 @@ const ElementInspectorImpl = () => {
173
341
  await (0, settings_1.updateInspectorSettings)({ selectionPillVisible: !current });
174
342
  })();
175
343
  });
176
- react_native_1.DevSettings.addMenuItem('Start React Native Grab Selection', () => {
177
- console.log('[InspectorFeedback] Start selection menu item pressed');
344
+ react_native_1.DevSettings.addMenuItem("Start React Native Grab Selection", () => {
345
+ console.log("[InspectorFeedback] Start selection menu item pressed");
178
346
  (0, selection_trigger_1.requestInspectorSelection)();
179
347
  });
180
348
  }, []);
181
- (0, react_1.useEffect)(() => {
182
- const nextPosition = clampFabPosition(fabPositionRef.current.x, fabPositionRef.current.y, windowWidth, windowHeight);
183
- fabPositionRef.current = nextPosition;
184
- fabPosition.setValue(nextPosition);
185
- }, [fabPosition, windowHeight, windowWidth]);
186
- const panResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
187
- onStartShouldSetPanResponder: () => true,
188
- onMoveShouldSetPanResponder: () => true,
189
- onPanResponderGrant: (evt) => {
190
- if (stateRef.current.status !== 'selecting')
191
- return;
192
- const x = evt.nativeEvent.locationX;
193
- const y = evt.nativeEvent.locationY;
194
- performQueryRef.current(x, y, statusBarOffset);
195
- lastQueryPositionRef.current = { x, y };
196
- },
197
- onPanResponderMove: (evt) => {
198
- if (stateRef.current.status !== 'selecting')
199
- return;
200
- const x = evt.nativeEvent.locationX;
201
- const y = evt.nativeEvent.locationY;
202
- movePositionRef.current = { x, y };
203
- if (settleTimeoutRef.current != null) {
204
- clearTimeout(settleTimeoutRef.current);
205
- settleTimeoutRef.current = null;
206
- }
207
- settleTimeoutRef.current = setTimeout(() => {
208
- settleTimeoutRef.current = null;
209
- if (stateRef.current.status !== 'selecting')
210
- return;
211
- const pos = movePositionRef.current;
212
- const last = lastQueryPositionRef.current;
213
- const distance = last
214
- ? Math.hypot(pos.x - last.x, pos.y - last.y)
215
- : Infinity;
216
- if (distance < MOVEMENT_EPSILON_PX)
217
- return;
218
- lastQueryPositionRef.current = pos;
219
- performQueryRef.current(pos.x, pos.y, statusBarOffset);
220
- }, SETTLE_MS);
221
- },
222
- onPanResponderRelease: (evt) => {
223
- if (stateRef.current.status !== 'selecting')
224
- return;
225
- const hitTarget = performQueryRef.current(evt.nativeEvent.locationX, evt.nativeEvent.locationY, statusBarOffset);
226
- if (!hitTarget) {
227
- closeOverlay();
228
- return;
229
- }
230
- selectedHitTargetRef.current = hitTarget;
231
- setState({ status: 'copying', selectedRect: hitTarget.rect });
232
- void (async () => {
233
- const clipboardText = await (0, copy_payload_1.buildInspectorCopyPayload)(hitTarget, {
234
- maxStackLines: MAX_STACK_LINES,
235
- maxPreviewText: MAX_PREVIEW_TEXT,
236
- pathRootHint: null,
237
- });
238
- const copied = await copyFeedbackToHostClipboard(clipboardText);
239
- if (copied) {
240
- showCopiedToast();
241
- }
242
- closeOverlay();
243
- })();
244
- },
245
- onPanResponderTerminate: () => {
246
- if (stateRef.current.status === 'selecting') {
247
- closeOverlay();
248
- }
249
- },
250
- })).current;
251
- const fabPanResponder = (0, react_1.useRef)(react_native_1.PanResponder.create({
252
- onStartShouldSetPanResponder: () => false,
253
- onMoveShouldSetPanResponder: (_evt, gestureState) => Math.abs(gestureState.dx) > 2 || Math.abs(gestureState.dy) > 2,
254
- onPanResponderGrant: (_evt, gestureState) => {
255
- fabDragStartOffsetRef.current = {
256
- x: fabPositionRef.current.x - gestureState.x0,
257
- y: fabPositionRef.current.y - gestureState.y0,
258
- };
259
- },
260
- onPanResponderMove: (_evt, gestureState) => {
261
- const { width, height } = windowSizeRef.current;
262
- const nextPosition = clampFabPosition(gestureState.moveX + fabDragStartOffsetRef.current.x, gestureState.moveY + fabDragStartOffsetRef.current.y, width, height);
263
- fabPositionRef.current = nextPosition;
264
- fabPosition.setValue(nextPosition);
265
- },
266
- onPanResponderTerminationRequest: () => true,
267
- onPanResponderTerminate: () => {
268
- fabPressAnim.setValue(1);
269
- },
270
- onPanResponderRelease: () => {
271
- fabPressAnim.setValue(1);
272
- },
273
- })).current;
274
349
  (0, react_1.useEffect)(() => {
275
350
  if (!selecting) {
276
351
  selectingHintAnim.stopAnimation();
@@ -299,76 +374,87 @@ const ElementInspectorImpl = () => {
299
374
  useNativeDriver: true,
300
375
  }).start();
301
376
  }, [fabAppearAnim, idle]);
302
- return ((0, jsx_runtime_1.jsx)(InspectorRootContainer, { children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: inspectorRootRef, style: react_native_1.StyleSheet.absoluteFill, pointerEvents: "box-none", children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { ref: anchorRef, style: [react_native_1.StyleSheet.absoluteFill, styles.anchor], pointerEvents: "none" }), idle && selectionPillVisible && ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { ...fabPanResponder.panHandlers, style: [
303
- styles.fab,
304
- {
305
- opacity: fabAppearAnim,
306
- transform: [
307
- { translateX: fabPosition.x },
308
- {
309
- translateY: react_native_1.Animated.add(fabPosition.y, fabAppearAnim.interpolate({
310
- inputRange: [0, 1],
311
- outputRange: [8, 0],
312
- })),
313
- },
314
- { scale: fabPressAnim },
315
- ],
316
- },
317
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: styles.fabInner, onPress: startSelecting, onPressIn: () => {
318
- react_native_1.Animated.spring(fabPressAnim, {
319
- toValue: 0.96,
320
- useNativeDriver: true,
321
- tension: 220,
322
- friction: 16,
323
- }).start();
324
- }, onPressOut: () => {
325
- react_native_1.Animated.spring(fabPressAnim, {
326
- toValue: 1,
327
- useNativeDriver: true,
328
- tension: 220,
329
- friction: 16,
330
- }).start();
331
- }, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.fabIcon, allowFontScaling: false, children: "\u2316" }) }) })), overlayVisible && ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: overlayRef, style: styles.overlay, ...(selecting ? panResponder.panHandlers : {}), children: [activeHighlightRect && ((0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [
332
- styles.highlight,
333
- {
334
- left: activeHighlightRect.x,
335
- top: activeHighlightRect.y + statusBarOffset,
336
- width: activeHighlightRect.width,
337
- height: activeHighlightRect.height,
338
- },
339
- ] })), selecting && ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: "none", style: [
340
- styles.selectionHint,
341
- {
342
- opacity: selectingHintAnim.interpolate({
343
- inputRange: [0, 1],
344
- outputRange: [0.76, 1],
345
- }),
346
- transform: [
347
- {
348
- translateY: selectingHintAnim.interpolate({
349
- inputRange: [0, 1],
350
- outputRange: [0, -2],
351
- }),
352
- },
353
- ],
354
- },
355
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.selectionHintText, children: "Drag or tap to find a source" }) }))] })), toastVisible && ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: "none", style: [
356
- styles.toast,
357
- {
358
- opacity: toastAnim,
359
- transform: [
360
- {
361
- translateY: toastAnim.interpolate({
362
- inputRange: [0, 1],
363
- outputRange: [-8, 0],
364
- }),
365
- },
366
- ],
367
- },
368
- ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.toastText, children: "Source location copied" }) }))] }) }));
377
+ (0, react_1.useEffect)(() => {
378
+ return () => {
379
+ resetGestureState();
380
+ stopSession();
381
+ };
382
+ }, [resetGestureState, stopSession]);
383
+ return ((0, jsx_runtime_1.jsx)(InspectorRootContainer, { children: (0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: inspectorRootRef, style: react_native_1.StyleSheet.absoluteFill, pointerEvents: "box-none", children: [(0, jsx_runtime_1.jsx)(react_native_1.View, { ref: anchorRef, style: [react_native_1.StyleSheet.absoluteFill, styles.anchor], pointerEvents: "none" }), (0, jsx_runtime_1.jsx)(SelectionFab, { visible: idle && selectionPillVisible, onPress: startSelecting, onPressIn: () => animateFabPress(0.96), onPressOut: () => animateFabPress(1), panHandlers: fabPanHandlers, fabAppearAnim: fabAppearAnim, fabPosition: fabPosition, fabPressAnim: fabPressAnim }), (0, jsx_runtime_1.jsx)(SelectionOverlay, { visible: overlayVisible, selecting: selecting, overlayRef: overlayRef, panHandlers: selectionPanHandlers, highlightRect: activeHighlightRect, statusBarOffset: statusBarOffset, selectingHintAnim: selectingHintAnim }), (0, jsx_runtime_1.jsx)(CopiedToast, { visible: toastVisible, toastAnim: toastAnim })] }) }));
384
+ };
385
+ exports.ElementInspector = ElementInspector;
386
+ const SelectionFab = ({ visible, onPress, onPressIn, onPressOut, panHandlers, fabAppearAnim, fabPosition, fabPressAnim, }) => {
387
+ if (!visible) {
388
+ return null;
389
+ }
390
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { ...panHandlers, style: [
391
+ styles.fab,
392
+ {
393
+ opacity: fabAppearAnim,
394
+ transform: [
395
+ { translateX: fabPosition.x },
396
+ {
397
+ translateY: react_native_1.Animated.add(fabPosition.y, fabAppearAnim.interpolate({
398
+ inputRange: [0, 1],
399
+ outputRange: [8, 0],
400
+ })),
401
+ },
402
+ { scale: fabPressAnim },
403
+ ],
404
+ },
405
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Pressable, { style: styles.fabInner, onPress: onPress, onPressIn: onPressIn, onPressOut: onPressOut, children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.fabIcon, allowFontScaling: false, children: "\u2316" }) }) }));
406
+ };
407
+ const SelectionOverlay = ({ visible, selecting, overlayRef, panHandlers, highlightRect, statusBarOffset, selectingHintAnim, }) => {
408
+ if (!visible) {
409
+ return null;
410
+ }
411
+ return ((0, jsx_runtime_1.jsxs)(react_native_1.View, { ref: overlayRef, style: styles.overlay, ...(selecting ? panHandlers : {}), children: [highlightRect && ((0, jsx_runtime_1.jsx)(react_native_1.View, { pointerEvents: "none", style: [
412
+ styles.highlight,
413
+ {
414
+ left: highlightRect.x,
415
+ top: highlightRect.y + statusBarOffset,
416
+ width: highlightRect.width,
417
+ height: highlightRect.height,
418
+ },
419
+ ] })), selecting && ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: "none", style: [
420
+ styles.selectionHint,
421
+ {
422
+ opacity: selectingHintAnim.interpolate({
423
+ inputRange: [0, 1],
424
+ outputRange: [0.76, 1],
425
+ }),
426
+ transform: [
427
+ {
428
+ translateY: selectingHintAnim.interpolate({
429
+ inputRange: [0, 1],
430
+ outputRange: [0, -2],
431
+ }),
432
+ },
433
+ ],
434
+ },
435
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.selectionHintText, children: "Drag or tap to find a source" }) }))] }));
436
+ };
437
+ const CopiedToast = ({ visible, toastAnim }) => {
438
+ if (!visible) {
439
+ return null;
440
+ }
441
+ return ((0, jsx_runtime_1.jsx)(react_native_1.Animated.View, { pointerEvents: "none", style: [
442
+ styles.toast,
443
+ {
444
+ opacity: toastAnim,
445
+ transform: [
446
+ {
447
+ translateY: toastAnim.interpolate({
448
+ inputRange: [0, 1],
449
+ outputRange: [-8, 0],
450
+ }),
451
+ },
452
+ ],
453
+ },
454
+ ], children: (0, jsx_runtime_1.jsx)(react_native_1.Text, { style: styles.toastText, children: "Source location copied" }) }));
369
455
  };
370
456
  const InspectorRootContainer = ({ children }) => {
371
- if (react_native_1.Platform.OS === 'ios') {
457
+ if (react_native_1.Platform.OS === "ios") {
372
458
  return (0, jsx_runtime_1.jsx)(react_native_screens_1.FullWindowOverlay, { children: children });
373
459
  }
374
460
  return (0, jsx_runtime_1.jsx)(react_1.Fragment, { children: children });
@@ -378,75 +464,75 @@ const styles = react_native_1.StyleSheet.create({
378
464
  opacity: 0,
379
465
  },
380
466
  fab: {
381
- position: 'absolute',
467
+ position: "absolute",
382
468
  left: 0,
383
469
  top: 0,
384
470
  zIndex: 9999,
385
471
  width: FAB_SIZE,
386
472
  height: FAB_SIZE,
387
473
  borderRadius: 9999,
388
- backgroundColor: 'rgba(14,20,26,0.98)',
474
+ backgroundColor: "rgba(14,20,26,0.98)",
389
475
  borderWidth: 1,
390
- borderColor: 'rgba(255,255,255,0.14)',
476
+ borderColor: "rgba(255,255,255,0.14)",
391
477
  elevation: 4,
392
- shadowColor: '#000',
478
+ shadowColor: "#000",
393
479
  shadowOffset: { width: 0, height: 2 },
394
480
  shadowOpacity: 0.28,
395
481
  shadowRadius: 6,
396
482
  },
397
483
  fabInner: {
398
484
  flex: 1,
399
- flexDirection: 'row',
400
- justifyContent: 'center',
401
- alignItems: 'center',
402
- width: '100%',
403
- height: '100%',
485
+ flexDirection: "row",
486
+ justifyContent: "center",
487
+ alignItems: "center",
488
+ width: "100%",
489
+ height: "100%",
404
490
  },
405
491
  fabIcon: {
406
- color: '#F5F7FA',
492
+ color: "#F5F7FA",
407
493
  fontSize: 28,
408
- fontWeight: '500',
494
+ fontWeight: "500",
409
495
  },
410
496
  overlay: {
411
497
  ...react_native_1.StyleSheet.absoluteFillObject,
412
- backgroundColor: 'rgba(0,0,0,0.015)',
498
+ backgroundColor: "rgba(0,0,0,0.015)",
413
499
  },
414
500
  selectionHint: {
415
- position: 'absolute',
501
+ position: "absolute",
416
502
  top: 56,
417
- alignSelf: 'center',
418
- backgroundColor: 'rgba(14,20,26,0.9)',
503
+ alignSelf: "center",
504
+ backgroundColor: "rgba(14,20,26,0.9)",
419
505
  borderRadius: 18,
420
506
  paddingHorizontal: 14,
421
507
  paddingVertical: 8,
422
508
  borderWidth: 1,
423
- borderColor: 'rgba(255,255,255,0.14)',
509
+ borderColor: "rgba(255,255,255,0.14)",
424
510
  },
425
511
  selectionHintText: {
426
- color: '#F5F7FA',
512
+ color: "#F5F7FA",
427
513
  fontSize: 13,
428
- fontWeight: '600',
514
+ fontWeight: "600",
429
515
  },
430
516
  highlight: {
431
- position: 'absolute',
517
+ position: "absolute",
432
518
  borderWidth: 2,
433
- borderColor: '#1264A3',
434
- backgroundColor: 'rgba(18,100,163,0.16)',
519
+ borderColor: "#1264A3",
520
+ backgroundColor: "rgba(18,100,163,0.16)",
435
521
  },
436
522
  toast: {
437
- position: 'absolute',
523
+ position: "absolute",
438
524
  top: 52,
439
525
  left: 16,
440
526
  right: 16,
441
- alignItems: 'center',
527
+ alignItems: "center",
442
528
  paddingVertical: 10,
443
529
  borderRadius: 10,
444
- backgroundColor: 'rgba(18,100,163,0.96)',
530
+ backgroundColor: "rgba(18,100,163,0.96)",
445
531
  },
446
532
  toastText: {
447
- color: '#fff',
533
+ color: "#fff",
448
534
  fontSize: 14,
449
- fontWeight: '700',
535
+ fontWeight: "700",
450
536
  },
451
537
  });
452
538
  //# sourceMappingURL=index.js.map