rn-studio 0.1.0 → 0.2.0
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 +6 -2
- package/dist/StudioProvider.d.ts +9 -1
- package/dist/StudioProvider.d.ts.map +1 -1
- package/dist/StudioProvider.js +20 -3
- package/dist/StudioProvider.js.map +1 -1
- package/dist/ast/AstEngine.d.ts +1 -4
- package/dist/ast/AstEngine.d.ts.map +1 -1
- package/dist/ast/AstEngine.js +165 -37
- package/dist/ast/AstEngine.js.map +1 -1
- package/dist/components/ComponentTree.d.ts.map +1 -1
- package/dist/components/ComponentTree.js +2 -2
- package/dist/components/ComponentTree.js.map +1 -1
- package/dist/components/FloatingBubble.js +1 -1
- package/dist/components/InspectorPanel.d.ts +3 -1
- package/dist/components/InspectorPanel.d.ts.map +1 -1
- package/dist/components/InspectorPanel.js +42 -101
- package/dist/components/InspectorPanel.js.map +1 -1
- package/dist/components/SelectionOverlay.d.ts +5 -7
- package/dist/components/SelectionOverlay.d.ts.map +1 -1
- package/dist/components/SelectionOverlay.js +215 -73
- package/dist/components/SelectionOverlay.js.map +1 -1
- package/dist/components/StyleEditor.d.ts.map +1 -1
- package/dist/components/StyleEditor.js +4 -4
- package/dist/components/StyleEditor.js.map +1 -1
- package/package.json +7 -7
|
@@ -39,71 +39,42 @@ const react_native_1 = require("react-native");
|
|
|
39
39
|
const StudioProvider_1 = require("../StudioProvider");
|
|
40
40
|
const StyleEditor_1 = require("./StyleEditor");
|
|
41
41
|
const ComponentTree_1 = require("./ComponentTree");
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
// overlay remains functional in environments where reanimated is not
|
|
45
|
-
// yet configured.
|
|
46
|
-
let Reanimated = null;
|
|
47
|
-
let useSharedValue = null;
|
|
48
|
-
let useAnimatedStyle = null;
|
|
49
|
-
let withSpring = null;
|
|
50
|
-
let withTiming = null;
|
|
51
|
-
try {
|
|
52
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
53
|
-
const r = require('react-native-reanimated');
|
|
54
|
-
Reanimated = r.default;
|
|
55
|
-
useSharedValue = r.useSharedValue;
|
|
56
|
-
useAnimatedStyle = r.useAnimatedStyle;
|
|
57
|
-
withSpring = r.withSpring;
|
|
58
|
-
withTiming = r.withTiming;
|
|
59
|
-
}
|
|
60
|
-
catch { }
|
|
61
|
-
const { height: SCREEN_HEIGHT } = react_native_1.Dimensions.get('window');
|
|
62
|
-
const PANEL_HEIGHT = SCREEN_HEIGHT * 0.6;
|
|
42
|
+
const SCREEN_HEIGHT = react_native_1.Dimensions.get('window').height;
|
|
43
|
+
const PANEL_HEIGHT = Math.round(SCREEN_HEIGHT * 0.6);
|
|
63
44
|
/**
|
|
64
45
|
* InspectorPanel
|
|
65
46
|
*
|
|
66
47
|
* Bottom sheet shown when a component is selected. Three tabs:
|
|
67
|
-
* Styles | Tree | Props. Slides up with a spring animation
|
|
48
|
+
* Styles | Tree | Props. Slides up with a spring animation powered
|
|
49
|
+
* by the stock `Animated` API — no reanimated dependency, so there's
|
|
50
|
+
* no worklets babel plugin requirement on the consumer side.
|
|
68
51
|
*/
|
|
69
52
|
const InspectorPanel = () => {
|
|
70
53
|
const { selectedComponent, clearSelection } = (0, StudioProvider_1.useStudio)();
|
|
71
54
|
const [tab, setTab] = (0, react_1.useState)('styles');
|
|
55
|
+
const translateY = (0, react_1.useRef)(new react_native_1.Animated.Value(PANEL_HEIGHT)).current;
|
|
72
56
|
const visible = !!selectedComponent;
|
|
73
|
-
if (Reanimated && useSharedValue) {
|
|
74
|
-
return (react_1.default.createElement(ReanimatedPanel, { visible: visible, onDismiss: clearSelection, tab: tab, setTab: setTab }));
|
|
75
|
-
}
|
|
76
|
-
// Fallback: render plainly when reanimated is unavailable.
|
|
77
|
-
if (!visible)
|
|
78
|
-
return null;
|
|
79
|
-
return (react_1.default.createElement(react_native_1.View, { style: [styles.panel, { transform: [{ translateY: 0 }] }] },
|
|
80
|
-
react_1.default.createElement(PanelChrome, { tab: tab, setTab: setTab, onDismiss: clearSelection }),
|
|
81
|
-
tab === 'styles' && react_1.default.createElement(StyleEditor_1.StyleEditor, null),
|
|
82
|
-
tab === 'tree' && react_1.default.createElement(ComponentTree_1.ComponentTree, null),
|
|
83
|
-
tab === 'props' && react_1.default.createElement(PropsView, null)));
|
|
84
|
-
};
|
|
85
|
-
exports.InspectorPanel = InspectorPanel;
|
|
86
|
-
const ReanimatedPanel = ({ visible, onDismiss, tab, setTab }) => {
|
|
87
|
-
const translateY = useSharedValue(PANEL_HEIGHT);
|
|
88
57
|
(0, react_1.useEffect)(() => {
|
|
89
|
-
translateY
|
|
90
|
-
?
|
|
91
|
-
:
|
|
58
|
+
react_native_1.Animated.spring(translateY, {
|
|
59
|
+
toValue: visible ? 0 : PANEL_HEIGHT,
|
|
60
|
+
useNativeDriver: true,
|
|
61
|
+
damping: 20,
|
|
62
|
+
stiffness: 200,
|
|
63
|
+
mass: 1,
|
|
64
|
+
}).start();
|
|
92
65
|
}, [visible, translateY]);
|
|
93
|
-
|
|
94
|
-
transform: [{ translateY: translateY.value }],
|
|
95
|
-
}));
|
|
96
|
-
if (!visible)
|
|
66
|
+
if (!selectedComponent)
|
|
97
67
|
return null;
|
|
98
|
-
const AnimatedView = Reanimated.View;
|
|
99
68
|
return (react_1.default.createElement(react_1.default.Fragment, null,
|
|
100
|
-
react_1.default.createElement(react_native_1.TouchableOpacity, { activeOpacity: 1, onPress:
|
|
101
|
-
react_1.default.createElement(
|
|
102
|
-
react_1.default.createElement(PanelChrome, { tab: tab, setTab: setTab, onDismiss:
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
69
|
+
react_1.default.createElement(react_native_1.TouchableOpacity, { activeOpacity: 1, onPress: clearSelection, style: styles.backdrop }),
|
|
70
|
+
react_1.default.createElement(react_native_1.Animated.View, { style: [styles.panel, { transform: [{ translateY }] }] },
|
|
71
|
+
react_1.default.createElement(PanelChrome, { tab: tab, setTab: setTab, onDismiss: clearSelection }),
|
|
72
|
+
react_1.default.createElement(react_native_1.View, { style: styles.tabBody },
|
|
73
|
+
tab === 'styles' && react_1.default.createElement(StyleEditor_1.StyleEditor, null),
|
|
74
|
+
tab === 'tree' && react_1.default.createElement(ComponentTree_1.ComponentTree, null),
|
|
75
|
+
tab === 'props' && react_1.default.createElement(PropsView, null)))));
|
|
106
76
|
};
|
|
77
|
+
exports.InspectorPanel = InspectorPanel;
|
|
107
78
|
const PanelChrome = ({ tab, setTab, onDismiss }) => {
|
|
108
79
|
const { selectedComponent } = (0, StudioProvider_1.useStudio)();
|
|
109
80
|
return (react_1.default.createElement(react_native_1.View, null,
|
|
@@ -118,22 +89,24 @@ const PanelChrome = ({ tab, setTab, onDismiss }) => {
|
|
|
118
89
|
react_1.default.createElement(TabButton, { label: "Tree", active: tab === 'tree', onPress: () => setTab('tree') }),
|
|
119
90
|
react_1.default.createElement(TabButton, { label: "Props", active: tab === 'props', onPress: () => setTab('props') }))));
|
|
120
91
|
};
|
|
121
|
-
const TabButton = ({ label, active, onPress
|
|
92
|
+
const TabButton = ({ label, active, onPress }) => (react_1.default.createElement(react_native_1.TouchableOpacity, { style: [styles.tab, active && styles.tabActive], onPress: onPress },
|
|
122
93
|
react_1.default.createElement(react_native_1.Text, { style: [styles.tabText, active && styles.tabTextActive] }, label)));
|
|
123
94
|
const PropsView = () => {
|
|
124
95
|
const { selectedComponent } = (0, StudioProvider_1.useStudio)();
|
|
125
|
-
if (!selectedComponent)
|
|
126
|
-
return
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
96
|
+
if (!selectedComponent)
|
|
97
|
+
return null;
|
|
98
|
+
const entries = Object.entries(selectedComponent.props || {}).filter(([k]) => k !== 'style' &&
|
|
99
|
+
k !== 'children' &&
|
|
100
|
+
k !== '__rnStudioSource' &&
|
|
101
|
+
k !== '__source' &&
|
|
102
|
+
k !== '__self');
|
|
130
103
|
if (!entries.length) {
|
|
131
104
|
return (react_1.default.createElement(react_native_1.View, { style: styles.empty },
|
|
132
105
|
react_1.default.createElement(react_native_1.Text, { style: styles.emptyText }, "No inspectable props")));
|
|
133
106
|
}
|
|
134
|
-
return (react_1.default.createElement(react_native_1.
|
|
107
|
+
return (react_1.default.createElement(react_native_1.ScrollView, { style: { flex: 1 }, contentContainerStyle: { padding: 16, paddingBottom: 40 }, showsVerticalScrollIndicator: true }, entries.map(([k, v]) => (react_1.default.createElement(react_native_1.View, { key: k, style: styles.propRow },
|
|
135
108
|
react_1.default.createElement(react_native_1.Text, { style: styles.propKey }, k),
|
|
136
|
-
react_1.default.createElement(react_native_1.Text, { style: styles.propValue, numberOfLines:
|
|
109
|
+
react_1.default.createElement(react_native_1.Text, { style: styles.propValue, numberOfLines: 4 }, safeStringify(v)))))));
|
|
137
110
|
};
|
|
138
111
|
function safeStringify(v) {
|
|
139
112
|
if (v === null)
|
|
@@ -175,6 +148,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
175
148
|
shadowOpacity: 0.4,
|
|
176
149
|
shadowRadius: 12,
|
|
177
150
|
},
|
|
151
|
+
tabBody: { flex: 1, minHeight: 0 },
|
|
178
152
|
handleWrap: { alignItems: 'center', paddingTop: 8 },
|
|
179
153
|
handle: {
|
|
180
154
|
width: 40,
|
|
@@ -189,36 +163,13 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
189
163
|
paddingHorizontal: 16,
|
|
190
164
|
paddingVertical: 12,
|
|
191
165
|
},
|
|
192
|
-
title: {
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
},
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
fontSize: 18,
|
|
200
|
-
},
|
|
201
|
-
tabs: {
|
|
202
|
-
flexDirection: 'row',
|
|
203
|
-
backgroundColor: '#1a1a1a',
|
|
204
|
-
},
|
|
205
|
-
tab: {
|
|
206
|
-
flex: 1,
|
|
207
|
-
paddingVertical: 12,
|
|
208
|
-
alignItems: 'center',
|
|
209
|
-
},
|
|
210
|
-
tabActive: {
|
|
211
|
-
borderBottomWidth: 2,
|
|
212
|
-
borderBottomColor: '#C6F135',
|
|
213
|
-
},
|
|
214
|
-
tabText: {
|
|
215
|
-
color: '#888',
|
|
216
|
-
fontSize: 13,
|
|
217
|
-
fontWeight: '600',
|
|
218
|
-
},
|
|
219
|
-
tabTextActive: {
|
|
220
|
-
color: '#C6F135',
|
|
221
|
-
},
|
|
166
|
+
title: { color: '#fff', fontSize: 16, fontWeight: '700' },
|
|
167
|
+
close: { color: '#888', fontSize: 18 },
|
|
168
|
+
tabs: { flexDirection: 'row', backgroundColor: '#1a1a1a' },
|
|
169
|
+
tab: { flex: 1, paddingVertical: 12, alignItems: 'center' },
|
|
170
|
+
tabActive: { borderBottomWidth: 2, borderBottomColor: '#7C9BFF' },
|
|
171
|
+
tabText: { color: '#888', fontSize: 13, fontWeight: '600' },
|
|
172
|
+
tabTextActive: { color: '#7C9BFF' },
|
|
222
173
|
empty: { padding: 32, alignItems: 'center' },
|
|
223
174
|
emptyText: { color: '#888', fontSize: 13 },
|
|
224
175
|
propRow: {
|
|
@@ -226,17 +177,7 @@ const styles = react_native_1.StyleSheet.create({
|
|
|
226
177
|
borderBottomWidth: react_native_1.StyleSheet.hairlineWidth,
|
|
227
178
|
borderBottomColor: '#222',
|
|
228
179
|
},
|
|
229
|
-
propKey: {
|
|
230
|
-
|
|
231
|
-
fontSize: 12,
|
|
232
|
-
fontFamily: 'Menlo',
|
|
233
|
-
fontWeight: '600',
|
|
234
|
-
},
|
|
235
|
-
propValue: {
|
|
236
|
-
color: '#ddd',
|
|
237
|
-
fontSize: 12,
|
|
238
|
-
fontFamily: 'Menlo',
|
|
239
|
-
marginTop: 2,
|
|
240
|
-
},
|
|
180
|
+
propKey: { color: '#7C9BFF', fontSize: 12, fontWeight: '600' },
|
|
181
|
+
propValue: { color: '#ddd', fontSize: 12, marginTop: 2 },
|
|
241
182
|
});
|
|
242
183
|
//# sourceMappingURL=InspectorPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"InspectorPanel.js","sourceRoot":"","sources":["../../src/components/InspectorPanel.tsx"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,+
|
|
1
|
+
{"version":3,"file":"InspectorPanel.js","sourceRoot":"","sources":["../../src/components/InspectorPanel.tsx"],"names":[],"mappings":"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"}
|
|
@@ -3,13 +3,11 @@ import React from 'react';
|
|
|
3
3
|
* SelectionOverlay
|
|
4
4
|
*
|
|
5
5
|
* Full-screen overlay that dims the app when selection mode is active
|
|
6
|
-
* and draws
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
* (see fiberWalker.ts) to find the nearest component with
|
|
12
|
-
* `__rnStudioSource` metadata.
|
|
6
|
+
* and draws an accent-colored highlight box around the selected
|
|
7
|
+
* component. Touches are routed via `getInspectorDataForViewAtPoint`
|
|
8
|
+
* against the app root view (provided by StudioProvider via
|
|
9
|
+
* `appRootRef`). The nearest user-code fiber is then resolved by
|
|
10
|
+
* walking the fiber `.return` chain.
|
|
13
11
|
*/
|
|
14
12
|
export declare const SelectionOverlay: React.FC;
|
|
15
13
|
//# sourceMappingURL=SelectionOverlay.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SelectionOverlay.d.ts","sourceRoot":"","sources":["../../src/components/SelectionOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;
|
|
1
|
+
{"version":3,"file":"SelectionOverlay.d.ts","sourceRoot":"","sources":["../../src/components/SelectionOverlay.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAsC,MAAM,OAAO,CAAC;AA6M3D;;;;;;;;;GASG;AACH,eAAO,MAAM,gBAAgB,EAAE,KAAK,CAAC,EAmHpC,CAAC"}
|
|
@@ -37,116 +37,258 @@ exports.SelectionOverlay = void 0;
|
|
|
37
37
|
const react_1 = __importStar(require("react"));
|
|
38
38
|
const react_native_1 = require("react-native");
|
|
39
39
|
const StudioProvider_1 = require("../StudioProvider");
|
|
40
|
-
|
|
40
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
41
|
+
// React Native's built-in inspector data API. Uses the React DevTools
|
|
42
|
+
// hook under the hood and works on both the Fabric and legacy
|
|
43
|
+
// architectures. We import it lazily so consumers that somehow run
|
|
44
|
+
// this file in production don't trip on the dev-only module path.
|
|
45
|
+
let getInspectorDataForViewAtPoint = null;
|
|
46
|
+
try {
|
|
47
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
48
|
+
const mod = require('react-native/src/private/devsupport/devmenu/elementinspector/getInspectorDataForViewAtPoint');
|
|
49
|
+
getInspectorDataForViewAtPoint = (mod && (mod.default || mod)) || null;
|
|
50
|
+
}
|
|
51
|
+
catch (e) {
|
|
52
|
+
// eslint-disable-next-line no-console
|
|
53
|
+
console.warn('[rn-studio] Unable to load getInspectorDataForViewAtPoint:', e && e.message);
|
|
54
|
+
}
|
|
55
|
+
function triggerHaptic(type) {
|
|
56
|
+
try {
|
|
57
|
+
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
58
|
+
const Haptic = require('react-native-haptic-feedback').default;
|
|
59
|
+
Haptic.trigger(type, {
|
|
60
|
+
enableVibrateFallback: false,
|
|
61
|
+
ignoreAndroidSystemSettings: false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
catch { }
|
|
65
|
+
}
|
|
66
|
+
function inferStyleType(key, value) {
|
|
67
|
+
if (typeof value === 'number')
|
|
68
|
+
return 'number';
|
|
69
|
+
if (typeof value === 'boolean')
|
|
70
|
+
return 'boolean';
|
|
71
|
+
if (typeof value === 'string') {
|
|
72
|
+
if (/color/i.test(key) ||
|
|
73
|
+
/^#[0-9a-f]{3,8}$/i.test(value) ||
|
|
74
|
+
/^rgba?\(/i.test(value) ||
|
|
75
|
+
/^hsla?\(/i.test(value)) {
|
|
76
|
+
return 'color';
|
|
77
|
+
}
|
|
78
|
+
return 'string';
|
|
79
|
+
}
|
|
80
|
+
return 'string';
|
|
81
|
+
}
|
|
82
|
+
function extractStyles(rawStyle) {
|
|
83
|
+
const flat = (react_native_1.StyleSheet.flatten(rawStyle) || {});
|
|
84
|
+
const out = [];
|
|
85
|
+
for (const key of Object.keys(flat)) {
|
|
86
|
+
const value = flat[key];
|
|
87
|
+
if (value == null)
|
|
88
|
+
continue;
|
|
89
|
+
if (typeof value === 'object')
|
|
90
|
+
continue; // skip nested (shadowOffset etc.)
|
|
91
|
+
out.push({ key, value, type: inferStyleType(key, value) });
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* Excludes known library paths so the AST engine only ever rewrites
|
|
97
|
+
* files in the user's project.
|
|
98
|
+
*/
|
|
99
|
+
function isUserCodePath(file) {
|
|
100
|
+
if (!file || typeof file !== 'string')
|
|
101
|
+
return false;
|
|
102
|
+
if (file.indexOf('/node_modules/') !== -1)
|
|
103
|
+
return false;
|
|
104
|
+
if (file.indexOf('react-native/Libraries/') !== -1)
|
|
105
|
+
return false;
|
|
106
|
+
if (file.indexOf('react-native/src/') !== -1)
|
|
107
|
+
return false;
|
|
108
|
+
if (file === 'unknown' || file === '<anonymous>')
|
|
109
|
+
return false;
|
|
110
|
+
return true;
|
|
111
|
+
}
|
|
112
|
+
function sourceFromProps(p, fallbackName) {
|
|
113
|
+
if (!p)
|
|
114
|
+
return null;
|
|
115
|
+
if (p.__rnStudioSource && p.__rnStudioSource.file) {
|
|
116
|
+
return {
|
|
117
|
+
file: p.__rnStudioSource.file,
|
|
118
|
+
line: p.__rnStudioSource.line,
|
|
119
|
+
column: p.__rnStudioSource.column || 0,
|
|
120
|
+
componentName: p.__rnStudioSource.componentName || fallbackName || 'Component',
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
if (p.__source && p.__source.fileName) {
|
|
124
|
+
return {
|
|
125
|
+
file: p.__source.fileName,
|
|
126
|
+
line: p.__source.lineNumber,
|
|
127
|
+
column: p.__source.columnNumber || 0,
|
|
128
|
+
componentName: fallbackName || 'Component',
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
return null;
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Walks a fiber's `.return` chain, reading each fiber's own
|
|
135
|
+
* memoizedProps for a source location. This is the correct strategy
|
|
136
|
+
* because `getInspectorDataForViewAtPoint`'s `hierarchy` items all
|
|
137
|
+
* share the deepest host's props, which is useless for mapping back
|
|
138
|
+
* to user JSX.
|
|
139
|
+
*/
|
|
140
|
+
function walkFiberForUserSource(fiber) {
|
|
141
|
+
let current = fiber;
|
|
142
|
+
let libraryFallback = null;
|
|
143
|
+
let safety = 0;
|
|
144
|
+
while (current && safety < 200) {
|
|
145
|
+
safety++;
|
|
146
|
+
const p = current.memoizedProps;
|
|
147
|
+
if (p && typeof p === 'object') {
|
|
148
|
+
const src = sourceFromProps(p, (current.type && (current.type.displayName || current.type.name)) ||
|
|
149
|
+
null);
|
|
150
|
+
if (src) {
|
|
151
|
+
const resolved = {
|
|
152
|
+
source: src,
|
|
153
|
+
props: p,
|
|
154
|
+
componentName: src.componentName,
|
|
155
|
+
};
|
|
156
|
+
if (isUserCodePath(src.file))
|
|
157
|
+
return resolved;
|
|
158
|
+
if (!libraryFallback)
|
|
159
|
+
libraryFallback = resolved;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
current = current.return;
|
|
163
|
+
}
|
|
164
|
+
return libraryFallback;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Resolve the nearest user-code JSX source location from the data
|
|
168
|
+
* returned by `getInspectorDataForViewAtPoint`.
|
|
169
|
+
*/
|
|
170
|
+
function resolveSourceFromViewData(viewData) {
|
|
171
|
+
if (!viewData)
|
|
172
|
+
return null;
|
|
173
|
+
if (viewData.closestInstance) {
|
|
174
|
+
const found = walkFiberForUserSource(viewData.closestInstance);
|
|
175
|
+
if (found && isUserCodePath(found.source.file))
|
|
176
|
+
return found;
|
|
177
|
+
if (found) {
|
|
178
|
+
// eslint-disable-next-line no-console
|
|
179
|
+
console.warn('[rn-studio] fiber walk only found library source:', found.source.file);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
const topSrc = sourceFromProps(viewData.props, null);
|
|
183
|
+
if (topSrc && isUserCodePath(topSrc.file)) {
|
|
184
|
+
return {
|
|
185
|
+
source: topSrc,
|
|
186
|
+
props: (viewData.props || {}),
|
|
187
|
+
componentName: topSrc.componentName,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
// eslint-disable-next-line no-console
|
|
191
|
+
console.warn('[rn-studio] no user-code source found; closestInstance?', !!viewData.closestInstance, 'topLevelSrc=', topSrc && topSrc.file);
|
|
192
|
+
return null;
|
|
193
|
+
}
|
|
41
194
|
/**
|
|
42
195
|
* SelectionOverlay
|
|
43
196
|
*
|
|
44
197
|
* Full-screen overlay that dims the app when selection mode is active
|
|
45
|
-
* and draws
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* (see fiberWalker.ts) to find the nearest component with
|
|
51
|
-
* `__rnStudioSource` metadata.
|
|
198
|
+
* and draws an accent-colored highlight box around the selected
|
|
199
|
+
* component. Touches are routed via `getInspectorDataForViewAtPoint`
|
|
200
|
+
* against the app root view (provided by StudioProvider via
|
|
201
|
+
* `appRootRef`). The nearest user-code fiber is then resolved by
|
|
202
|
+
* walking the fiber `.return` chain.
|
|
52
203
|
*/
|
|
53
204
|
const SelectionOverlay = () => {
|
|
54
205
|
const { isActive, isSelecting, selectedComponent, selectComponent } = (0, StudioProvider_1.useStudio)();
|
|
55
|
-
const rootRef = (0, react_1.useRef)(null);
|
|
56
206
|
const [highlight, setHighlight] = (0, react_1.useState)(null);
|
|
57
207
|
const opacity = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
58
|
-
const borderOpacity = (0, react_1.useRef)(new react_native_1.Animated.Value(0)).current;
|
|
59
208
|
(0, react_1.useEffect)(() => {
|
|
60
209
|
react_native_1.Animated.timing(opacity, {
|
|
61
|
-
toValue: isActive ?
|
|
210
|
+
toValue: isActive ? 0.3 : 0,
|
|
62
211
|
duration: 200,
|
|
63
212
|
useNativeDriver: true,
|
|
64
213
|
}).start();
|
|
65
214
|
}, [isActive, opacity]);
|
|
66
215
|
(0, react_1.useEffect)(() => {
|
|
67
|
-
if (selectedComponent)
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
duration: 150,
|
|
71
|
-
useNativeDriver: true,
|
|
72
|
-
}).start();
|
|
73
|
-
}
|
|
74
|
-
else {
|
|
75
|
-
borderOpacity.setValue(0);
|
|
76
|
-
}
|
|
77
|
-
}, [selectedComponent, borderOpacity]);
|
|
216
|
+
if (!selectedComponent)
|
|
217
|
+
setHighlight(null);
|
|
218
|
+
}, [selectedComponent]);
|
|
78
219
|
const handleTouch = (evt) => {
|
|
79
220
|
if (!isSelecting)
|
|
80
|
-
return
|
|
221
|
+
return;
|
|
81
222
|
const { pageX, pageY } = evt.nativeEvent;
|
|
82
|
-
const
|
|
83
|
-
if (!
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
223
|
+
const appRoot = StudioProvider_1.appRootRef && StudioProvider_1.appRootRef.current;
|
|
224
|
+
if (!getInspectorDataForViewAtPoint || !appRoot) {
|
|
225
|
+
// eslint-disable-next-line no-console
|
|
226
|
+
console.warn('[rn-studio] inspector API or app root ref not available', {
|
|
227
|
+
hasAPI: !!getInspectorDataForViewAtPoint,
|
|
228
|
+
hasRoot: !!appRoot,
|
|
229
|
+
});
|
|
230
|
+
return;
|
|
231
|
+
}
|
|
87
232
|
try {
|
|
88
|
-
|
|
89
|
-
if (!
|
|
90
|
-
return;
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
233
|
+
getInspectorDataForViewAtPoint(appRoot, pageX, pageY, (viewData) => {
|
|
234
|
+
if (!viewData)
|
|
235
|
+
return false;
|
|
236
|
+
const resolved = resolveSourceFromViewData(viewData);
|
|
237
|
+
if (!resolved) {
|
|
238
|
+
if (viewData.frame) {
|
|
239
|
+
setHighlight({
|
|
240
|
+
x: viewData.frame.left,
|
|
241
|
+
y: viewData.frame.top,
|
|
242
|
+
width: viewData.frame.width,
|
|
243
|
+
height: viewData.frame.height,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
return true;
|
|
247
|
+
}
|
|
248
|
+
const nodeStyles = extractStyles(resolved.props.style);
|
|
249
|
+
const node = {
|
|
250
|
+
id: `${resolved.source.file}:${resolved.source.line}:${resolved.source.column}`,
|
|
251
|
+
componentName: resolved.componentName,
|
|
252
|
+
source: resolved.source,
|
|
253
|
+
props: resolved.props,
|
|
254
|
+
styles: nodeStyles,
|
|
255
|
+
children: [],
|
|
256
|
+
};
|
|
257
|
+
if (viewData.frame) {
|
|
258
|
+
setHighlight({
|
|
259
|
+
x: viewData.frame.left,
|
|
260
|
+
y: viewData.frame.top,
|
|
261
|
+
width: viewData.frame.width,
|
|
262
|
+
height: viewData.frame.height,
|
|
263
|
+
});
|
|
102
264
|
}
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
setHighlight({
|
|
107
|
-
x: pageX - 30,
|
|
108
|
-
y: pageY - 30,
|
|
109
|
-
width: 60,
|
|
110
|
-
height: 60,
|
|
111
|
-
});
|
|
265
|
+
selectComponent(node);
|
|
266
|
+
triggerHaptic('impactMedium');
|
|
267
|
+
return true;
|
|
112
268
|
});
|
|
113
269
|
}
|
|
114
|
-
catch {
|
|
115
|
-
//
|
|
116
|
-
|
|
117
|
-
// feedback.
|
|
270
|
+
catch (err) {
|
|
271
|
+
// eslint-disable-next-line no-console
|
|
272
|
+
console.warn('[rn-studio] hit-test error:', err && err.message);
|
|
118
273
|
}
|
|
119
|
-
triggerHaptic('impactMedium');
|
|
120
|
-
return true;
|
|
121
274
|
};
|
|
122
275
|
if (!isActive)
|
|
123
276
|
return null;
|
|
124
|
-
|
|
125
|
-
return (react_1.default.createElement(react_native_1.View, { ref: rootRef, pointerEvents: isSelecting ? 'box-only' : 'none', style: react_native_1.StyleSheet.absoluteFill, onStartShouldSetResponder: () => isSelecting, onResponderGrant: handleTouch },
|
|
277
|
+
return (react_1.default.createElement(react_native_1.View, { pointerEvents: isSelecting ? 'box-only' : 'none', style: react_native_1.StyleSheet.absoluteFill, onStartShouldSetResponder: () => isSelecting, onResponderGrant: handleTouch },
|
|
126
278
|
react_1.default.createElement(react_native_1.Animated.View, { pointerEvents: "none", style: [
|
|
127
279
|
react_native_1.StyleSheet.absoluteFill,
|
|
128
|
-
{ backgroundColor: '
|
|
280
|
+
{ backgroundColor: '#000', opacity },
|
|
129
281
|
] }),
|
|
130
|
-
highlight && (react_1.default.createElement(react_native_1.
|
|
282
|
+
highlight && (react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: {
|
|
131
283
|
position: 'absolute',
|
|
132
284
|
left: highlight.x,
|
|
133
285
|
top: highlight.y,
|
|
134
286
|
width: highlight.width,
|
|
135
287
|
height: highlight.height,
|
|
136
288
|
borderWidth: 2,
|
|
137
|
-
borderColor: '#
|
|
289
|
+
borderColor: '#7C9BFF',
|
|
138
290
|
borderRadius: 4,
|
|
139
|
-
|
|
140
|
-
} })),
|
|
141
|
-
react_1.default.createElement(react_native_1.View, { pointerEvents: "none", style: { position: 'absolute', width, height } })));
|
|
291
|
+
} }))));
|
|
142
292
|
};
|
|
143
293
|
exports.SelectionOverlay = SelectionOverlay;
|
|
144
|
-
function triggerHaptic(type) {
|
|
145
|
-
try {
|
|
146
|
-
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
|
147
|
-
const Haptic = require('react-native-haptic-feedback').default;
|
|
148
|
-
Haptic.trigger(type, { enableVibrateFallback: false, ignoreAndroidSystemSettings: false });
|
|
149
|
-
}
|
|
150
|
-
catch { }
|
|
151
|
-
}
|
|
152
294
|
//# sourceMappingURL=SelectionOverlay.js.map
|