Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ import android.view.MotionEvent
import android.view.View
import android.view.ViewGroup
import android.view.accessibility.AccessibilityNodeInfo
import android.view.inputmethod.BaseInputConnection
import android.view.inputmethod.EditorInfo
import android.view.inputmethod.InputConnection
import android.view.inputmethod.InputMethodManager
Expand Down Expand Up @@ -679,9 +680,19 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
disableTextDiffing = true

// On some devices, when the text is cleared, buggy keyboards will not clear the composing
// text so, we have to set text to null, which will clear the currently composing text.
// text. We remove composing spans explicitly and clear the text via replace() on the existing
// Editable rather than setText(null), which would recreate the buffer and cause the cursor
// to lose its gravity-based positioning (e.g. jumping to the right for centered text).
if (reactTextUpdate.text.length == 0) {
text = null
val currentText = editableText
if (currentText != null) {
BaseInputConnection.removeComposingSpans(currentText)
if (currentText.isNotEmpty()) {
currentText.replace(0, currentText.length, "")
}
} else {
text = null
}
} else {
// When we update text, we trigger onChangeText code that will
// try to update state if the wrapper is available. Temporarily disable
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import android.text.InputFilter
import android.text.InputFilter.AllCaps
import android.text.InputType
import android.text.Layout
import android.text.SpannableStringBuilder
import android.util.DisplayMetrics
import android.view.Gravity
import android.view.View
Expand All @@ -32,6 +33,7 @@ import com.facebook.react.uimanager.DisplayMetricsHolder
import com.facebook.react.uimanager.ReactStylesDiffMap
import com.facebook.react.uimanager.ThemedReactContext
import com.facebook.react.views.text.DefaultStyleValuesUtil.getDefaultTextColorHint
import com.facebook.react.views.text.ReactTextUpdate
import org.assertj.core.api.Assertions.assertThat
import org.junit.Before
import org.junit.Test
Expand Down Expand Up @@ -473,6 +475,35 @@ class ReactTextInputPropertyTest {
// endregion
}

@Test
fun testClearingTextPreservesEditableBufferAndGravity() {
// Regression test for #55457. Clearing a centered TextInput must not recreate the
// underlying Editable buffer, otherwise the EditText loses its gravity-based caret
// positioning and the cursor jumps to the right edge.
manager.updateProperties(view, buildStyles("textAlign", "center"))
assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK)
.isEqualTo(Gravity.CENTER_HORIZONTAL)

manager.updateExtraData(
view,
ReactTextUpdate(SpannableStringBuilder("hello"), 0, Gravity.CENTER_HORIZONTAL, 0, 0))
val bufferBeforeClear = view.editableText
assertThat(view.text.toString()).isEqualTo("hello")
assertThat(bufferBeforeClear).isNotNull()

manager.updateExtraData(
view, ReactTextUpdate(SpannableStringBuilder(""), 0, Gravity.CENTER_HORIZONTAL, 0, 0))

// Behavioral guarantee of the fix: the Editable instance is reused via replace(),
// not swapped out via setText(null). This is what keeps the cursor centered.
assertThat(view.editableText).isSameAs(bufferBeforeClear)
assertThat(view.text.toString()).isEmpty()
assertThat(view.gravity and Gravity.HORIZONTAL_GRAVITY_MASK)
.isEqualTo(Gravity.CENTER_HORIZONTAL)
assertThat(view.selectionStart).isEqualTo(0)
assertThat(view.selectionEnd).isEqualTo(0)
}

@Test
fun testMaxLength() {
val filters = arrayOf<InputFilter>(AllCaps())
Expand Down