From d5e7b02e722a73678c0358ba3da62009dd3f4dd1 Mon Sep 17 00:00:00 2001 From: Alan Lee Date: Tue, 6 Jan 2026 09:17:49 -0800 Subject: [PATCH] Add selection to TextInput onChange event (native) (#55044) Summary: This change adds `selection` data to the `TextInput.onChange` event for both iOS and Android platforms. NOTE: `selection` only represents the cursor location when returned via `onChange` as this will not be invoked on a pure selection change without text change. We should also add this note to the documentation. ## Why On the web, text input elements provide `selectionStart` and `selectionEnd` properties that are always accessible during input events. React Native's `onChange` event previously included `selection` on iOS (Fabric), but this was removed in PR [#51051](https://github.com/facebook/react-native/pull/51051) to unify with Android (which never had it). This change restores and extends this capability to both platforms, better aligning React Native with web standards. This is also to support pollyfill added in `react-strict-dom`; https://github.com/facebook/react-strict-dom/pull/435/ ## What Changed 1. **iOS/macOS (C++)**: Enable selection in `onChange` event via `TextInputEventEmitter` 2. **Android (Kotlin)**: Added selection data to `ReactTextChangedEvent` Changelog: [General][Added] - TextInput onChange event now includes selection data (cursor location) on iOS and Android Reviewed By: cipolleschi, javache Differential Revision: D90123295 --- .../react/views/textinput/ReactTextChangedEvent.kt | 8 ++++++++ .../react/views/textinput/ReactTextInputTextWatcher.kt | 2 ++ .../components/textinput/TextInputEventEmitter.cpp | 3 ++- 3 files changed, 12 insertions(+), 1 deletion(-) diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.kt index b4e4a7caad87..f4c0daabb000 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextChangedEvent.kt @@ -17,6 +17,8 @@ internal class ReactTextChangedEvent( viewId: Int, private val text: String, private val eventCount: Int, + private val selectionStart: Int, + private val selectionEnd: Int, ) : Event(surfaceId, viewId) { override fun getEventName(): String = EVENT_NAME @@ -25,6 +27,12 @@ internal class ReactTextChangedEvent( putString("text", text) putInt("eventCount", eventCount) putInt("target", viewTag) + val selectionData = + Arguments.createMap().apply { + putInt("start", selectionStart) + putInt("end", selectionEnd) + } + putMap("selection", selectionData) } } diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt index 887251ccbbce..e6e13a00955a 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputTextWatcher.kt @@ -63,6 +63,8 @@ internal class ReactTextInputTextWatcher( editText.id, s.toString(), editText.incrementAndGetEventCounter(), + editText.selectionStart, + editText.selectionEnd, ) ) } diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp index a9bc219f8bf7..2982b12fe014 100644 --- a/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp +++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/TextInputEventEmitter.cpp @@ -140,7 +140,8 @@ void TextInputEventEmitter::onBlur(const Metrics& textInputMetrics) const { } void TextInputEventEmitter::onChange(const Metrics& textInputMetrics) const { - dispatchTextInputEvent("change", textInputMetrics); + dispatchTextInputEvent( + "change", textInputMetrics, /* includeSelectionState */ true); } void TextInputEventEmitter::onContentSizeChange(