diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt index 0415dca1126b..9f023587c000 100644 --- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt +++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt @@ -130,6 +130,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat private var keyListener: InternalKeyListener? = null private var detectScrollMovement = false private var onKeyPress = false + private var selectionWasCollapsed = true private val textAttributes: TextAttributes private var typefaceDirty = false private var fontFamily: String? = null @@ -492,6 +493,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat if (selectionWatcher != null && hasFocus()) { selectionWatcher?.onSelectionChanged(selStart, selEnd) } + maybeShowSoftKeyboardForSelection(selStart, selEnd) } override fun onFocusChanged(focused: Boolean, direction: Int, previouslyFocusedRect: Rect?) { @@ -499,6 +501,21 @@ public open class ReactEditText public constructor(context: Context) : AppCompat if (focused && selectionWatcher != null) { selectionWatcher?.onSelectionChanged(selectionStart, selectionEnd) } + if (focused) { + maybeShowSoftKeyboardForSelection(selectionStart, selectionEnd) + } + } + + private fun maybeShowSoftKeyboardForSelection(selectionStart: Int, selectionEnd: Int) { + if (!hasFocus() || !showSoftInputOnFocus) { + return + } + + val selectionIsCollapsed = selectionStart == selectionEnd + if (!selectionIsCollapsed && selectionWasCollapsed) { + showSoftKeyboard() + } + selectionWasCollapsed = selectionIsCollapsed } internal fun setSelectionWatcher(selectionWatcher: SelectionWatcher?) { diff --git a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt index d99a2c8be1cd..710314fc902d 100644 --- a/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt +++ b/packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt @@ -11,6 +11,7 @@ package com.facebook.react.views.textinput +import android.content.Context import android.graphics.Color import android.os.Build import android.text.InputFilter @@ -46,6 +47,15 @@ import org.robolectric.RuntimeEnvironment @RunWith(RobolectricTestRunner::class) class ReactTextInputPropertyTest { + private class TestReactEditText(context: Context) : ReactEditText(context) { + var showSoftKeyboardCallCount = 0 + + override fun showSoftKeyboard(): Boolean { + showSoftKeyboardCallCount++ + return true + } + } + private lateinit var context: BridgeReactContext private lateinit var catalystInstanceMock: CatalystInstance private lateinit var themedContext: ThemedReactContext @@ -74,6 +84,17 @@ class ReactTextInputPropertyTest { view = manager.createViewInstance(themedContext) } + @Test + fun testShowsSoftKeyboardWhenSelectionStartsWhileFocused() { + val textInput = TestReactEditText(themedContext) + textInput.setText("hello") + textInput.onFocusChanged(true, View.FOCUS_DOWN, null) + + textInput.setSelection(1, 3) + + assertThat(textInput.showSoftKeyboardCallCount).isEqualTo(1) + } + @Test fun testAutoCorrect() { manager.updateProperties(view, buildStyles()) diff --git a/private/react-native-fantom/__docs__/README.md b/private/react-native-fantom/__docs__/README.md index d5997c130e8d..dbfb007c8c1b 100644 --- a/private/react-native-fantom/__docs__/README.md +++ b/private/react-native-fantom/__docs__/README.md @@ -187,12 +187,12 @@ hi underlying native (Fabric/TurboModules) code, so tests stay close to real usage and are more resilient to internal refactors. Observe results through the public surface too — e.g. assert the rendered output - (`root.getRenderedOutput(...)`) or values delivered to public listeners - rather than reading private state. + (`root.getRenderedOutput(...)`) or values delivered to public listeners rather + than reading private state. - When Fantom doesn't support something (a native module, a capability, a way to observe a result, etc.), it's fine to reach into internals to work around - that limitation. Prefer a short comment explaining why the internal access is - necessary. + that limitation. Prefer a short comment explaining why the internal access + is necessary. - Place test files in `__tests__` directories alongside the code being tested. - Benchmark tests use the `-benchmark-itest.js` suffix. - Use `Fantom.runTask()` to render and run synchronous operations; it ensures