@mobileai/react-native 0.9.3 → 0.9.4

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.
@@ -5,37 +5,65 @@
5
5
  *
6
6
  * Strategy chain (tried in order, first success wins):
7
7
  *
8
- * 1. onChangeText (controlled) — developer wired up React state. Most common.
9
- * → calls props.onChangeText(text) directly.
8
+ * 1. onChangeText — developer's controlled component wired React state.
9
+ * → calls element.props.onChangeText(text) directly.
10
10
  *
11
- * 2. onChange (controlled, lower-level)some forms use onChange instead of
12
- * onChangeText. RN's TextInput fires onChange before calling onChangeText.
13
- * calls props.onChange({ nativeEvent: { text, eventCount: 1 } })
11
+ * 2. Fiber subtree walkfor uncontrolled TextInputs (defaultValue only).
12
+ * React Native's TextInput.js always creates an internal `_onChange` handler
13
+ * and passes it as `onChange` to the native host view (RCTTextInputView /
14
+ * AndroidTextInput). That `_onChange` internally calls `props.onChangeText`
15
+ * and updates native text state.
14
16
  *
15
- * 3. setNativeProps (uncontrolled, Old Arch) for TextInputs with defaultValue
16
- * only. React doesn't manage the native value after initial render for
17
- * uncontrolled inputs, so setNativeProps is stable (no re-render wipe).
18
- * Also focuses the input first so the native layer treats it as user input.
19
- * → stateNode.focus() + stateNode.setNativeProps({ text })
17
+ * We walk the fiber subtree (behavior-based, no tag numbers) to find any
18
+ * descendant node whose memoizedProps.onChange is a function. Then we call
19
+ * it with a synthetic TextInputChangeEvent. This triggers RN's internal
20
+ * `_onChange` which calls `setLastNativeText` and updates the native view.
20
21
  *
21
- * Why no 100% reliable cross-arch pure-JS solution for uncontrolled inputs:
22
- * React's reconciliation model intentionally prevents external mutation of
23
- * controlled values. For uncontrolled inputs, setNativeProps is the
24
- * industry-standard JS approach (used by React Native's own test utils).
25
- * True cross-arch reliability requires native keyboard injection (Detox/Appium).
22
+ * Why this is robust:
23
+ * - No hardcoded fiber tag numbers (host component tags can change across versions)
24
+ * - No dependency on setNativeProps (deprecated in New Architecture / Fabric)
25
+ * - Uses the same code path as a real user typing — RN's own _onChange handler
26
26
  */
27
27
 
28
- import { findNodeHandle } from 'react-native';
29
28
  import { walkFiberTree } from "../core/FiberTreeWalker.js";
30
- /** Build a synthetic RN-style text change event object */
31
- function makeSyntheticChangeEvent(text, nodeHandle) {
32
- return {
33
- nativeEvent: {
34
- text,
35
- eventCount: 1,
36
- target: nodeHandle ?? 0
29
+ /**
30
+ * Walk a fiber subtree to find the first descendant whose memoizedProps
31
+ * has an `onChange` function. This is behavior-based — we don't check
32
+ * fiber tags, component names, or any React internal that could change.
33
+ * We just look for the prop that RN's TextInput always wires.
34
+ */
35
+ function findNativeOnChange(fiber, maxDepth = 8) {
36
+ if (!fiber) return null;
37
+ let current = fiber.child;
38
+
39
+ // BFS-style walk through the fiber children
40
+ const queue = [];
41
+ if (current) queue.push({
42
+ node: current,
43
+ depth: 0
44
+ });
45
+ while (queue.length > 0) {
46
+ const {
47
+ node,
48
+ depth: d
49
+ } = queue.shift();
50
+ if (d > maxDepth) continue;
51
+ const props = node.memoizedProps;
52
+ if (props && typeof props.onChange === 'function') {
53
+ return props.onChange;
37
54
  }
38
- };
55
+
56
+ // Add child and sibling
57
+ if (node.child) queue.push({
58
+ node: node.child,
59
+ depth: d + 1
60
+ });
61
+ if (node.sibling) queue.push({
62
+ node: node.sibling,
63
+ depth: d + 1
64
+ });
65
+ }
66
+ return null;
39
67
  }
40
68
  export function createTypeTool(context) {
41
69
  return {
@@ -62,11 +90,10 @@ export function createTypeTool(context) {
62
90
  return `❌ Element with index ${args.index} not found.`;
63
91
  }
64
92
  const props = element.props;
65
- const stateNode = element.fiberNode?.stateNode;
66
- const nodeHandle = stateNode ? findNodeHandle(stateNode) ?? null : null;
67
93
  const label = element.label || `[${element.type}]`;
68
94
 
69
95
  // ── Strategy 1: controlled via onChangeText ───────────────────────────
96
+ // App developer wired onChangeText={setText} — most reliable.
70
97
  if (typeof props.onChangeText === 'function') {
71
98
  try {
72
99
  props.onChangeText(args.text);
@@ -77,37 +104,33 @@ export function createTypeTool(context) {
77
104
  }
78
105
  }
79
106
 
80
- // ── Strategy 2: controlled via onChange (lower-level event) ───────────
81
- if (typeof props.onChange === 'function') {
82
- try {
83
- props.onChange(makeSyntheticChangeEvent(args.text, nodeHandle));
84
- await new Promise(resolve => setTimeout(resolve, 300));
85
- return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
86
- } catch (err) {
87
- return `❌ onChange failed: ${err.message}`;
88
- }
89
- }
90
-
91
- // ── Strategy 3: uncontrolled — focus + setNativeProps ─────────────────
92
- // For defaultValue-only TextInputs, React doesn't own the native value
93
- // after initial render, so setNativeProps is stable across re-renders.
94
- if (stateNode && typeof stateNode.setNativeProps === 'function') {
95
- try {
96
- // Focus first so the native layer treats this as active input
97
- if (typeof stateNode.focus === 'function') {
98
- stateNode.focus();
99
- await new Promise(resolve => setTimeout(resolve, 200));
107
+ // ── Strategy 2: fiber subtree walk for native onChange ─────────────────
108
+ // For uncontrolled TextInputs (defaultValue only, no onChangeText).
109
+ // RN's TextInput.js creates _onChange internally and passes it as
110
+ // onChange to the native host view. We find that handler and call it
111
+ // with a synthetic TextInputChangeEvent.
112
+ const fiberNode = element.fiberNode;
113
+ if (fiberNode) {
114
+ // First check the matched fiber itself
115
+ const directOnChange = fiberNode.memoizedProps?.onChange;
116
+ const onChange = typeof directOnChange === 'function' ? directOnChange : findNativeOnChange(fiberNode);
117
+ if (onChange) {
118
+ try {
119
+ onChange({
120
+ nativeEvent: {
121
+ text: args.text,
122
+ eventCount: 0,
123
+ target: 0
124
+ }
125
+ });
126
+ await new Promise(resolve => setTimeout(resolve, 300));
127
+ return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
128
+ } catch (err) {
129
+ return `❌ onChange failed: ${err.message}`;
100
130
  }
101
- stateNode.setNativeProps({
102
- text: args.text
103
- });
104
- await new Promise(resolve => setTimeout(resolve, 300));
105
- return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
106
- } catch (err) {
107
- return `❌ setNativeProps failed: ${err.message}`;
108
131
  }
109
132
  }
110
- return `❌ Element [${args.index}] "${label}" is not a typeable text input.`;
133
+ return `❌ Element [${args.index}] "${label}" is not a typeable text input. No onChangeText or onChange handler found in fiber tree.`;
111
134
  }
112
135
  };
113
136
  }
@@ -1 +1 @@
1
- {"version":3,"names":["findNodeHandle","walkFiberTree","makeSyntheticChangeEvent","text","nodeHandle","nativeEvent","eventCount","target","createTypeTool","context","name","description","parameters","index","type","required","execute","args","interactives","elements","getRootRef","getWalkConfig","element","find","el","props","stateNode","fiberNode","label","onChangeText","Promise","resolve","setTimeout","err","message","onChange","setNativeProps","focus"],"sourceRoot":"../../../src","sources":["tools/typeTool.ts"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["walkFiberTree","findNativeOnChange","fiber","maxDepth","current","child","queue","push","node","depth","length","d","shift","props","memoizedProps","onChange","sibling","createTypeTool","context","name","description","parameters","index","type","required","text","execute","args","interactives","elements","getRootRef","getWalkConfig","element","find","el","label","onChangeText","Promise","resolve","setTimeout","err","message","fiberNode","directOnChange","nativeEvent","eventCount","target"],"sourceRoot":"../../../src","sources":["tools/typeTool.ts"],"mappings":"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","ignoreList":[]}
@@ -3,24 +3,24 @@
3
3
  *
4
4
  * Strategy chain (tried in order, first success wins):
5
5
  *
6
- * 1. onChangeText (controlled) — developer wired up React state. Most common.
7
- * → calls props.onChangeText(text) directly.
6
+ * 1. onChangeText — developer's controlled component wired React state.
7
+ * → calls element.props.onChangeText(text) directly.
8
8
  *
9
- * 2. onChange (controlled, lower-level)some forms use onChange instead of
10
- * onChangeText. RN's TextInput fires onChange before calling onChangeText.
11
- * calls props.onChange({ nativeEvent: { text, eventCount: 1 } })
9
+ * 2. Fiber subtree walkfor uncontrolled TextInputs (defaultValue only).
10
+ * React Native's TextInput.js always creates an internal `_onChange` handler
11
+ * and passes it as `onChange` to the native host view (RCTTextInputView /
12
+ * AndroidTextInput). That `_onChange` internally calls `props.onChangeText`
13
+ * and updates native text state.
12
14
  *
13
- * 3. setNativeProps (uncontrolled, Old Arch) for TextInputs with defaultValue
14
- * only. React doesn't manage the native value after initial render for
15
- * uncontrolled inputs, so setNativeProps is stable (no re-render wipe).
16
- * Also focuses the input first so the native layer treats it as user input.
17
- * → stateNode.focus() + stateNode.setNativeProps({ text })
15
+ * We walk the fiber subtree (behavior-based, no tag numbers) to find any
16
+ * descendant node whose memoizedProps.onChange is a function. Then we call
17
+ * it with a synthetic TextInputChangeEvent. This triggers RN's internal
18
+ * `_onChange` which calls `setLastNativeText` and updates the native view.
18
19
  *
19
- * Why no 100% reliable cross-arch pure-JS solution for uncontrolled inputs:
20
- * React's reconciliation model intentionally prevents external mutation of
21
- * controlled values. For uncontrolled inputs, setNativeProps is the
22
- * industry-standard JS approach (used by React Native's own test utils).
23
- * True cross-arch reliability requires native keyboard injection (Detox/Appium).
20
+ * Why this is robust:
21
+ * - No hardcoded fiber tag numbers (host component tags can change across versions)
22
+ * - No dependency on setNativeProps (deprecated in New Architecture / Fabric)
23
+ * - Uses the same code path as a real user typing — RN's own _onChange handler
24
24
  */
25
25
  import type { AgentTool, ToolContext } from './types';
26
26
  export declare function createTypeTool(context: ToolContext): AgentTool;
@@ -1 +1 @@
1
- {"version":3,"file":"typeTool.d.ts","sourceRoot":"","sources":["../../../../src/tools/typeTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAIH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAatD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAgE9D"}
1
+ {"version":3,"file":"typeTool.d.ts","sourceRoot":"","sources":["../../../../src/tools/typeTool.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAkCtD,wBAAgB,cAAc,CAAC,OAAO,EAAE,WAAW,GAAG,SAAS,CAgE9D"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mobileai/react-native",
3
- "version": "0.9.3",
3
+ "version": "0.9.4",
4
4
  "description": "Build autonomous AI agents for React Native and Expo apps. Provides AI-native UI traversal, tool calling, and structured reasoning.",
5
5
  "main": "./lib/module/index.js",
6
6
  "source": "./src/index.ts",
@@ -3,39 +3,59 @@
3
3
  *
4
4
  * Strategy chain (tried in order, first success wins):
5
5
  *
6
- * 1. onChangeText (controlled) — developer wired up React state. Most common.
7
- * → calls props.onChangeText(text) directly.
6
+ * 1. onChangeText — developer's controlled component wired React state.
7
+ * → calls element.props.onChangeText(text) directly.
8
8
  *
9
- * 2. onChange (controlled, lower-level)some forms use onChange instead of
10
- * onChangeText. RN's TextInput fires onChange before calling onChangeText.
11
- * calls props.onChange({ nativeEvent: { text, eventCount: 1 } })
9
+ * 2. Fiber subtree walkfor uncontrolled TextInputs (defaultValue only).
10
+ * React Native's TextInput.js always creates an internal `_onChange` handler
11
+ * and passes it as `onChange` to the native host view (RCTTextInputView /
12
+ * AndroidTextInput). That `_onChange` internally calls `props.onChangeText`
13
+ * and updates native text state.
12
14
  *
13
- * 3. setNativeProps (uncontrolled, Old Arch) for TextInputs with defaultValue
14
- * only. React doesn't manage the native value after initial render for
15
- * uncontrolled inputs, so setNativeProps is stable (no re-render wipe).
16
- * Also focuses the input first so the native layer treats it as user input.
17
- * → stateNode.focus() + stateNode.setNativeProps({ text })
15
+ * We walk the fiber subtree (behavior-based, no tag numbers) to find any
16
+ * descendant node whose memoizedProps.onChange is a function. Then we call
17
+ * it with a synthetic TextInputChangeEvent. This triggers RN's internal
18
+ * `_onChange` which calls `setLastNativeText` and updates the native view.
18
19
  *
19
- * Why no 100% reliable cross-arch pure-JS solution for uncontrolled inputs:
20
- * React's reconciliation model intentionally prevents external mutation of
21
- * controlled values. For uncontrolled inputs, setNativeProps is the
22
- * industry-standard JS approach (used by React Native's own test utils).
23
- * True cross-arch reliability requires native keyboard injection (Detox/Appium).
20
+ * Why this is robust:
21
+ * - No hardcoded fiber tag numbers (host component tags can change across versions)
22
+ * - No dependency on setNativeProps (deprecated in New Architecture / Fabric)
23
+ * - Uses the same code path as a real user typing — RN's own _onChange handler
24
24
  */
25
25
 
26
- import { findNodeHandle } from 'react-native';
27
26
  import { walkFiberTree } from '../core/FiberTreeWalker';
28
27
  import type { AgentTool, ToolContext } from './types';
29
28
 
30
- /** Build a synthetic RN-style text change event object */
31
- function makeSyntheticChangeEvent(text: string, nodeHandle: number | null) {
32
- return {
33
- nativeEvent: {
34
- text,
35
- eventCount: 1,
36
- target: nodeHandle ?? 0,
37
- },
38
- };
29
+ /**
30
+ * Walk a fiber subtree to find the first descendant whose memoizedProps
31
+ * has an `onChange` function. This is behavior-based — we don't check
32
+ * fiber tags, component names, or any React internal that could change.
33
+ * We just look for the prop that RN's TextInput always wires.
34
+ */
35
+ function findNativeOnChange(fiber: any, maxDepth = 8): ((event: any) => void) | null {
36
+ if (!fiber) return null;
37
+
38
+ let current: any = fiber.child;
39
+
40
+ // BFS-style walk through the fiber children
41
+ const queue: { node: any; depth: number }[] = [];
42
+ if (current) queue.push({ node: current, depth: 0 });
43
+
44
+ while (queue.length > 0) {
45
+ const { node, depth: d } = queue.shift()!;
46
+ if (d > maxDepth) continue;
47
+
48
+ const props = node.memoizedProps;
49
+ if (props && typeof props.onChange === 'function') {
50
+ return props.onChange;
51
+ }
52
+
53
+ // Add child and sibling
54
+ if (node.child) queue.push({ node: node.child, depth: d + 1 });
55
+ if (node.sibling) queue.push({ node: node.sibling, depth: d + 1 });
56
+ }
57
+
58
+ return null;
39
59
  }
40
60
 
41
61
  export function createTypeTool(context: ToolContext): AgentTool {
@@ -55,11 +75,10 @@ export function createTypeTool(context: ToolContext): AgentTool {
55
75
  }
56
76
 
57
77
  const props = element.props;
58
- const stateNode = element.fiberNode?.stateNode;
59
- const nodeHandle: number | null = stateNode ? (findNodeHandle(stateNode) ?? null) : null;
60
78
  const label = element.label || `[${element.type}]`;
61
79
 
62
80
  // ── Strategy 1: controlled via onChangeText ───────────────────────────
81
+ // App developer wired onChangeText={setText} — most reliable.
63
82
  if (typeof props.onChangeText === 'function') {
64
83
  try {
65
84
  props.onChangeText(args.text);
@@ -70,36 +89,37 @@ export function createTypeTool(context: ToolContext): AgentTool {
70
89
  }
71
90
  }
72
91
 
73
- // ── Strategy 2: controlled via onChange (lower-level event) ───────────
74
- if (typeof props.onChange === 'function') {
75
- try {
76
- props.onChange(makeSyntheticChangeEvent(args.text, nodeHandle));
77
- await new Promise(resolve => setTimeout(resolve, 300));
78
- return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
79
- } catch (err: any) {
80
- return `❌ onChange failed: ${err.message}`;
81
- }
82
- }
92
+ // ── Strategy 2: fiber subtree walk for native onChange ─────────────────
93
+ // For uncontrolled TextInputs (defaultValue only, no onChangeText).
94
+ // RN's TextInput.js creates _onChange internally and passes it as
95
+ // onChange to the native host view. We find that handler and call it
96
+ // with a synthetic TextInputChangeEvent.
97
+ const fiberNode = element.fiberNode;
98
+ if (fiberNode) {
99
+ // First check the matched fiber itself
100
+ const directOnChange = fiberNode.memoizedProps?.onChange;
101
+ const onChange = typeof directOnChange === 'function'
102
+ ? directOnChange
103
+ : findNativeOnChange(fiberNode);
83
104
 
84
- // ── Strategy 3: uncontrolled — focus + setNativeProps ─────────────────
85
- // For defaultValue-only TextInputs, React doesn't own the native value
86
- // after initial render, so setNativeProps is stable across re-renders.
87
- if (stateNode && typeof stateNode.setNativeProps === 'function') {
88
- try {
89
- // Focus first so the native layer treats this as active input
90
- if (typeof stateNode.focus === 'function') {
91
- stateNode.focus();
92
- await new Promise(resolve => setTimeout(resolve, 200));
105
+ if (onChange) {
106
+ try {
107
+ onChange({
108
+ nativeEvent: {
109
+ text: args.text,
110
+ eventCount: 0,
111
+ target: 0,
112
+ },
113
+ });
114
+ await new Promise(resolve => setTimeout(resolve, 300));
115
+ return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
116
+ } catch (err: any) {
117
+ return `❌ onChange failed: ${err.message}`;
93
118
  }
94
- stateNode.setNativeProps({ text: args.text });
95
- await new Promise(resolve => setTimeout(resolve, 300));
96
- return `✅ Typed "${args.text}" into [${args.index}] "${label}"`;
97
- } catch (err: any) {
98
- return `❌ setNativeProps failed: ${err.message}`;
99
119
  }
100
120
  }
101
121
 
102
- return `❌ Element [${args.index}] "${label}" is not a typeable text input.`;
122
+ return `❌ Element [${args.index}] "${label}" is not a typeable text input. No onChangeText or onChange handler found in fiber tree.`;
103
123
  },
104
124
  };
105
125
  }