@rn-ave/core 0.1.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.
@@ -0,0 +1,163 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.findFiberByNativeTag = findFiberByNativeTag;
4
+ exports.getDisplayName = getDisplayName;
5
+ exports.getComponentHierarchy = getComponentHierarchy;
6
+ exports.getCleanProps = getCleanProps;
7
+ exports.findNearestComposite = findNearestComposite;
8
+ exports.collectAllHostFibers = collectAllHostFibers;
9
+ const source_1 = require("./source");
10
+ // Fiber tag constants (from React)
11
+ const FunctionComponent = 0;
12
+ const ClassComponent = 1;
13
+ const HostRoot = 3;
14
+ const HostComponent = 5;
15
+ const HostText = 6;
16
+ const ForwardRef = 11;
17
+ const MemoComponent = 14;
18
+ const SimpleMemoComponent = 15;
19
+ /**
20
+ * Finds a fiber node by its native tag
21
+ */
22
+ function findFiberByNativeTag(nativeTag, fiberRoot) {
23
+ let result = null;
24
+ function traverse(fiber) {
25
+ if (!fiber || result)
26
+ return;
27
+ // Check if this fiber's stateNode matches the native tag
28
+ if (fiber.stateNode &&
29
+ typeof fiber.stateNode === 'object' &&
30
+ fiber.stateNode._nativeTag === nativeTag) {
31
+ result = fiber;
32
+ return;
33
+ }
34
+ // Traverse children
35
+ traverse(fiber.child);
36
+ // Traverse siblings
37
+ traverse(fiber.sibling);
38
+ }
39
+ traverse(fiberRoot.current);
40
+ return result;
41
+ }
42
+ /**
43
+ * Checks if a fiber represents a composite component (function/class)
44
+ */
45
+ function isCompositeFiber(fiber) {
46
+ return (fiber.tag === FunctionComponent ||
47
+ fiber.tag === ClassComponent ||
48
+ fiber.tag === ForwardRef ||
49
+ fiber.tag === MemoComponent ||
50
+ fiber.tag === SimpleMemoComponent);
51
+ }
52
+ /**
53
+ * Gets the display name of a component from its fiber
54
+ */
55
+ function getDisplayName(fiber) {
56
+ const { type, tag } = fiber;
57
+ // For host components (View, Text, etc.)
58
+ if (tag === HostComponent) {
59
+ return typeof type === 'string' ? type : 'HostComponent';
60
+ }
61
+ // For text nodes
62
+ if (tag === HostText) {
63
+ return 'Text';
64
+ }
65
+ // For composite components
66
+ if (typeof type === 'function') {
67
+ return type.displayName || type.name || 'Anonymous';
68
+ }
69
+ // For ForwardRef
70
+ if (tag === ForwardRef && type && typeof type === 'object') {
71
+ const wrappedType = type.render;
72
+ if (typeof wrappedType === 'function') {
73
+ return wrappedType.displayName || wrappedType.name || 'ForwardRef';
74
+ }
75
+ }
76
+ // For Memo
77
+ if ((tag === MemoComponent || tag === SimpleMemoComponent) && type && typeof type === 'object') {
78
+ const wrappedType = type.type;
79
+ if (typeof wrappedType === 'function') {
80
+ return wrappedType.displayName || wrappedType.name || 'Memo';
81
+ }
82
+ }
83
+ return 'Unknown';
84
+ }
85
+ /**
86
+ * Gets the component hierarchy from a fiber node up to the root
87
+ */
88
+ function getComponentHierarchy(fiber) {
89
+ const hierarchy = [];
90
+ let current = fiber;
91
+ while (current) {
92
+ // Only include composite components in hierarchy
93
+ if (isCompositeFiber(current)) {
94
+ hierarchy.push({
95
+ name: getDisplayName(current),
96
+ props: current.memoizedProps || {},
97
+ source: (0, source_1.getSourceLocation)(current),
98
+ });
99
+ }
100
+ current = current.return;
101
+ }
102
+ return hierarchy;
103
+ }
104
+ /**
105
+ * Gets all props from a fiber, excluding internal ones
106
+ */
107
+ function getCleanProps(fiber) {
108
+ const props = fiber.memoizedProps || {};
109
+ const cleanProps = {};
110
+ for (const [key, value] of Object.entries(props)) {
111
+ // Skip internal props
112
+ if (key.startsWith('__'))
113
+ continue;
114
+ if (key === 'children')
115
+ continue;
116
+ cleanProps[key] = value;
117
+ }
118
+ return cleanProps;
119
+ }
120
+ /**
121
+ * Finds the nearest composite component ancestor
122
+ * @param fiber - The starting fiber node
123
+ * @param skipNodeModules - If true, skips components from node_modules and returns the nearest user-code component
124
+ */
125
+ function findNearestComposite(fiber, skipNodeModules = false) {
126
+ let current = fiber;
127
+ let fallback = null; // First composite found (even if from node_modules)
128
+ while (current) {
129
+ if (isCompositeFiber(current)) {
130
+ // Always save the first composite as fallback
131
+ if (!fallback) {
132
+ fallback = current;
133
+ }
134
+ // If not filtering, or if this is user code, return it
135
+ if (!skipNodeModules || !(0, source_1.isFromNodeModules)(current)) {
136
+ return current;
137
+ }
138
+ }
139
+ current = current.return;
140
+ }
141
+ // Fallback to node_modules component if no user code found
142
+ return fallback;
143
+ }
144
+ /**
145
+ * Collects all host component fibers (View, Text, etc.) from the tree
146
+ */
147
+ function collectAllHostFibers(fiberRoot) {
148
+ const hostFibers = [];
149
+ function traverse(fiber) {
150
+ if (!fiber)
151
+ return;
152
+ // Collect host components (View, Text, Image, etc.)
153
+ if (fiber.tag === HostComponent && fiber.stateNode) {
154
+ hostFibers.push(fiber);
155
+ }
156
+ // Traverse children first (for depth-first order)
157
+ traverse(fiber.child);
158
+ // Then siblings
159
+ traverse(fiber.sibling);
160
+ }
161
+ traverse(fiberRoot.current);
162
+ return hostFibers;
163
+ }
@@ -0,0 +1,17 @@
1
+ import { type PanResponderInstance } from 'react-native';
2
+ import type { GestureConfig } from '../types';
3
+ /**
4
+ * Creates a PanResponder for long press detection
5
+ * Simpler approach that doesn't require react-native-gesture-handler
6
+ */
7
+ export declare function createGestureDetector(config: GestureConfig): PanResponderInstance;
8
+ /**
9
+ * Simplified gesture detector that uses callback refs
10
+ * This approach allows us to track which element was pressed
11
+ */
12
+ export interface SimplePressDetector {
13
+ onPressIn: (nativeTag: number) => void;
14
+ onPressOut: () => void;
15
+ isActive: boolean;
16
+ }
17
+ export declare function createSimplePressDetector(onLongPress: (nativeTag: number) => void, duration?: number): SimplePressDetector;
@@ -0,0 +1,84 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.createGestureDetector = createGestureDetector;
4
+ exports.createSimplePressDetector = createSimplePressDetector;
5
+ const react_native_1 = require("react-native");
6
+ /**
7
+ * Creates a PanResponder for long press detection
8
+ * Simpler approach that doesn't require react-native-gesture-handler
9
+ */
10
+ function createGestureDetector(config) {
11
+ const { longPressDuration = 500, onElementSelected } = config;
12
+ let pressTimer = null;
13
+ let startPosition = { x: 0, y: 0 };
14
+ let hasTriggered = false;
15
+ return react_native_1.PanResponder.create({
16
+ // Intercept all touches
17
+ onStartShouldSetPanResponder: () => true,
18
+ onMoveShouldSetPanResponder: () => false,
19
+ onPanResponderGrant: (evt) => {
20
+ const { pageX, pageY } = evt.nativeEvent;
21
+ startPosition = { x: pageX, y: pageY };
22
+ hasTriggered = false;
23
+ // Start timer for long press
24
+ pressTimer = setTimeout(() => {
25
+ if (!hasTriggered) {
26
+ hasTriggered = true;
27
+ // For now, we'll need to get the native tag from the event
28
+ // This is a limitation - we might need a native module for proper hit testing
29
+ // For MVP, we can use a workaround by adding touch handlers to components
30
+ onElementSelected(0, { x: pageX, y: pageY });
31
+ }
32
+ }, longPressDuration);
33
+ },
34
+ onPanResponderMove: (evt) => {
35
+ const { pageX, pageY } = evt.nativeEvent;
36
+ const distance = Math.sqrt(Math.pow(pageX - startPosition.x, 2) +
37
+ Math.pow(pageY - startPosition.y, 2));
38
+ // Cancel if finger moved too far (more than 10px)
39
+ if (distance > 10 && pressTimer) {
40
+ clearTimeout(pressTimer);
41
+ pressTimer = null;
42
+ hasTriggered = false;
43
+ }
44
+ },
45
+ onPanResponderRelease: () => {
46
+ if (pressTimer) {
47
+ clearTimeout(pressTimer);
48
+ pressTimer = null;
49
+ }
50
+ hasTriggered = false;
51
+ },
52
+ onPanResponderTerminate: () => {
53
+ if (pressTimer) {
54
+ clearTimeout(pressTimer);
55
+ pressTimer = null;
56
+ }
57
+ hasTriggered = false;
58
+ },
59
+ });
60
+ }
61
+ function createSimplePressDetector(onLongPress, duration = 500) {
62
+ let timer = null;
63
+ let currentTag = null;
64
+ return {
65
+ onPressIn: (nativeTag) => {
66
+ currentTag = nativeTag;
67
+ timer = setTimeout(() => {
68
+ if (currentTag !== null) {
69
+ onLongPress(currentTag);
70
+ }
71
+ }, duration);
72
+ },
73
+ onPressOut: () => {
74
+ if (timer) {
75
+ clearTimeout(timer);
76
+ timer = null;
77
+ }
78
+ currentTag = null;
79
+ },
80
+ get isActive() {
81
+ return timer !== null;
82
+ },
83
+ };
84
+ }
@@ -0,0 +1,2 @@
1
+ export { RNAVEProvider } from './provider';
2
+ export type { ExtractedContext, SourceLocation, AgentType, } from './types';
package/dist/index.js ADDED
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ // rn-ave - React Native Agent Visual Edit
3
+ // Only auto-initialization is supported - import '@rn-ave/core/auto' in your app
4
+ Object.defineProperty(exports, "__esModule", { value: true });
5
+ exports.RNAVEProvider = void 0;
6
+ // Internal provider (used by auto.ts)
7
+ var provider_1 = require("./provider");
8
+ Object.defineProperty(exports, "RNAVEProvider", { enumerable: true, get: function () { return provider_1.RNAVEProvider; } });
@@ -0,0 +1,14 @@
1
+ import React from 'react';
2
+ import type { SelectedElement } from '../types';
3
+ type AgentType = 'claude-code' | 'cursor' | 'opencode';
4
+ interface HighlightOverlayProps {
5
+ selectedElement: SelectedElement | null;
6
+ onDismiss: () => void;
7
+ onSendToAgent?: (agentType: AgentType, prompt?: string) => Promise<{
8
+ success: boolean;
9
+ error?: string;
10
+ }>;
11
+ onOpenInEditor?: () => void;
12
+ }
13
+ export declare function HighlightOverlay({ selectedElement, onDismiss, onSendToAgent, onOpenInEditor, }: HighlightOverlayProps): React.JSX.Element | null;
14
+ export {};
@@ -0,0 +1,177 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.HighlightOverlay = HighlightOverlay;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const tooltip_1 = require("./tooltip");
40
+ // Box model colors matching RN inspector
41
+ const COLORS = {
42
+ margin: 'rgba(246, 178, 107, 0.65)', // Coral/orange
43
+ padding: 'rgba(147, 196, 125, 0.65)', // Green
44
+ content: 'rgba(111, 168, 220, 0.65)', // Blue
45
+ };
46
+ // Default edges (all zeros)
47
+ const defaultEdges = () => ({ top: 0, right: 0, bottom: 0, left: 0 });
48
+ /**
49
+ * Box Model Overlay - renders the margin/padding/content visualization
50
+ * directly on the selected element (colors only, no labels)
51
+ */
52
+ function BoxModelOverlay({ bounds, boxModel }) {
53
+ const margin = boxModel?.margin ?? defaultEdges();
54
+ const padding = boxModel?.padding ?? defaultEdges();
55
+ // Calculate layer dimensions
56
+ // Margin layer extends OUTSIDE the element bounds
57
+ const marginLayer = {
58
+ left: bounds.x - margin.left,
59
+ top: bounds.y - margin.top,
60
+ width: bounds.width + margin.left + margin.right,
61
+ height: bounds.height + margin.top + margin.bottom,
62
+ };
63
+ // Content layer is INSIDE the element, after padding
64
+ const contentLayer = {
65
+ left: bounds.x + padding.left,
66
+ top: bounds.y + padding.top,
67
+ width: Math.max(0, bounds.width - padding.left - padding.right),
68
+ height: Math.max(0, bounds.height - padding.top - padding.bottom),
69
+ };
70
+ // Check if we have any margin or padding to show
71
+ const hasMargin = margin.top > 0 || margin.right > 0 || margin.bottom > 0 || margin.left > 0;
72
+ const hasPadding = padding.top > 0 || padding.right > 0 || padding.bottom > 0 || padding.left > 0;
73
+ return (<>
74
+ {/* MARGIN LAYER - Coral/Orange strips (no labels) */}
75
+ {hasMargin && (<>
76
+ {margin.top > 0 && (<react_native_1.View style={[styles.layer, {
77
+ backgroundColor: COLORS.margin,
78
+ left: marginLayer.left,
79
+ top: marginLayer.top,
80
+ width: marginLayer.width,
81
+ height: margin.top,
82
+ }]} pointerEvents="none"/>)}
83
+ {margin.bottom > 0 && (<react_native_1.View style={[styles.layer, {
84
+ backgroundColor: COLORS.margin,
85
+ left: marginLayer.left,
86
+ top: bounds.y + bounds.height,
87
+ width: marginLayer.width,
88
+ height: margin.bottom,
89
+ }]} pointerEvents="none"/>)}
90
+ {margin.left > 0 && (<react_native_1.View style={[styles.layer, {
91
+ backgroundColor: COLORS.margin,
92
+ left: marginLayer.left,
93
+ top: bounds.y,
94
+ width: margin.left,
95
+ height: bounds.height,
96
+ }]} pointerEvents="none"/>)}
97
+ {margin.right > 0 && (<react_native_1.View style={[styles.layer, {
98
+ backgroundColor: COLORS.margin,
99
+ left: bounds.x + bounds.width,
100
+ top: bounds.y,
101
+ width: margin.right,
102
+ height: bounds.height,
103
+ }]} pointerEvents="none"/>)}
104
+ </>)}
105
+
106
+ {/* PADDING LAYER - Green strips (no labels) */}
107
+ {hasPadding && (<>
108
+ {padding.top > 0 && (<react_native_1.View style={[styles.layer, {
109
+ backgroundColor: COLORS.padding,
110
+ left: bounds.x,
111
+ top: bounds.y,
112
+ width: bounds.width,
113
+ height: padding.top,
114
+ }]} pointerEvents="none"/>)}
115
+ {padding.bottom > 0 && (<react_native_1.View style={[styles.layer, {
116
+ backgroundColor: COLORS.padding,
117
+ left: bounds.x,
118
+ top: bounds.y + bounds.height - padding.bottom,
119
+ width: bounds.width,
120
+ height: padding.bottom,
121
+ }]} pointerEvents="none"/>)}
122
+ {padding.left > 0 && (<react_native_1.View style={[styles.layer, {
123
+ backgroundColor: COLORS.padding,
124
+ left: bounds.x,
125
+ top: bounds.y + padding.top,
126
+ width: padding.left,
127
+ height: Math.max(0, bounds.height - padding.top - padding.bottom),
128
+ }]} pointerEvents="none"/>)}
129
+ {padding.right > 0 && (<react_native_1.View style={[styles.layer, {
130
+ backgroundColor: COLORS.padding,
131
+ left: bounds.x + bounds.width - padding.right,
132
+ top: bounds.y + padding.top,
133
+ width: padding.right,
134
+ height: Math.max(0, bounds.height - padding.top - padding.bottom),
135
+ }]} pointerEvents="none"/>)}
136
+ </>)}
137
+
138
+ {/* CONTENT LAYER - Blue */}
139
+ <react_native_1.View style={[styles.layer, {
140
+ backgroundColor: COLORS.content,
141
+ left: contentLayer.left,
142
+ top: contentLayer.top,
143
+ width: contentLayer.width,
144
+ height: contentLayer.height,
145
+ }]} pointerEvents="none"/>
146
+ </>);
147
+ }
148
+ function HighlightOverlay({ selectedElement, onDismiss, onSendToAgent, onOpenInEditor, }) {
149
+ const [bounds, setBounds] = (0, react_1.useState)(null);
150
+ (0, react_1.useEffect)(() => {
151
+ if (!selectedElement) {
152
+ setBounds(null);
153
+ return;
154
+ }
155
+ // Try to measure the element
156
+ const { nativeTag } = selectedElement;
157
+ if (react_native_1.Platform.OS === 'ios' || react_native_1.Platform.OS === 'android') {
158
+ react_native_1.UIManager.measureInWindow(nativeTag, (x, y, width, height) => {
159
+ setBounds({ x, y, width, height });
160
+ });
161
+ }
162
+ }, [selectedElement]);
163
+ if (!selectedElement)
164
+ return null;
165
+ return (<react_native_1.View style={react_native_1.StyleSheet.absoluteFill} pointerEvents="box-none">
166
+ {/* Box Model Visualization - rendered on the element */}
167
+ {bounds && (<BoxModelOverlay bounds={bounds} boxModel={selectedElement.componentInfo.boxModel}/>)}
168
+
169
+ {/* Info tooltip */}
170
+ <tooltip_1.InfoTooltip componentInfo={selectedElement.componentInfo} bounds={bounds} onDismiss={onDismiss} onSendToAgent={onSendToAgent} onOpenInEditor={onOpenInEditor}/>
171
+ </react_native_1.View>);
172
+ }
173
+ const styles = react_native_1.StyleSheet.create({
174
+ layer: {
175
+ position: 'absolute',
176
+ },
177
+ });
@@ -0,0 +1,15 @@
1
+ import React from 'react';
2
+ import type { ExtractedContext, Bounds } from '../types';
3
+ type AgentType = 'claude-code' | 'cursor' | 'opencode';
4
+ interface InfoTooltipProps {
5
+ componentInfo: ExtractedContext;
6
+ bounds: Bounds | null;
7
+ onDismiss: () => void;
8
+ onSendToAgent?: (agentType: AgentType, prompt?: string, contextOverride?: any) => Promise<{
9
+ success: boolean;
10
+ error?: string;
11
+ }>;
12
+ onOpenInEditor?: (sourceOverride?: any) => void;
13
+ }
14
+ export declare function InfoTooltip({ componentInfo, bounds, onDismiss, onSendToAgent, onOpenInEditor, }: InfoTooltipProps): React.JSX.Element;
15
+ export {};