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