Skip to content

Commit 863f76b

Browse files
lukeharveymeta-codesync[bot]
authored andcommitted
Fix autoCapitalize bitmask collision with numeric inputType flags (facebook#55786)
Summary: `setAutoCapitalize` applies `AUTOCAPITALIZE_FLAGS` (`0x7000`) to all input types, but this mask overlaps `TYPE_NUMBER_FLAG_SIGNED` (`0x1000`) and `TYPE_NUMBER_FLAG_DECIMAL` (`0x2000`). On every Fabric re-render, this strips the signed/decimal flags from numeric inputs, causing `commitStagedInputType` to call `setInputType()` with corrupted flags, which restarts the IME and breaks composing-based input (e.g. Samsung keyboards' shared `.`/`-` key). The fix skips `setAutoCapitalize` for `TYPE_CLASS_NUMBER` inputs, where autocapitalize is meaningless. ## Changelog: <!-- Help reviewers and the release process by writing your own changelog entry. Pick one each for the category and type tags: [ANDROID|GENERAL|IOS|INTERNAL] [BREAKING|ADDED|CHANGED|DEPRECATED|REMOVED|FIXED|SECURITY] - Message For more details, see: https://reactnative.dev/contributing/changelogs-in-pull-requests --> [Android] [Fixed] - Fix `setAutoCapitalize` stripping numeric `TextInput` flags (`TYPE_NUMBER_FLAG_SIGNED`/`DECIMAL`), affecting minus sign input on Samsung keyboards Pull Request resolved: facebook#55786 Test Plan: A unit test was added to `ReactTextInputPropertyTest.kt` that verifies `setAutoCapitalize` does not strip `TYPE_NUMBER_FLAG_SIGNED` or `TYPE_NUMBER_FLAG_DECIMAL` when the input type class is `TYPE_CLASS_NUMBER`. Reviewed By: javache Differential Revision: D95049029 Pulled By: NickGerleman fbshipit-source-id: 695b1394f7df04ace782319c5847b40bfd2a5d7f
1 parent 9ebb325 commit 863f76b

3 files changed

Lines changed: 86 additions & 1 deletion

File tree

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactEditText.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ public open class ReactEditText public constructor(context: Context) : AppCompat
117117
private var listeners: CopyOnWriteArrayList<TextWatcher>?
118118

119119
public var stagedInputType: Int
120+
internal var stagedAutoCapitalize: Int = 0
120121
public var submitBehavior: String? = null
121122
public var dragAndDropFilter: List<String>? = null
122123

packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.kt

Lines changed: 26 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -718,7 +718,9 @@ public open class ReactTextInputManager public constructor() :
718718
}
719719
}
720720

721-
updateStagedInputTypeFlag(view, AUTOCAPITALIZE_FLAGS, autoCapitalizeValue)
721+
// Deferred to onAfterUpdateTransaction() so we can reconcile with the resolved
722+
// keyboard type — AUTOCAPITALIZE_FLAGS collides with numeric inputType flags.
723+
view.stagedAutoCapitalize = autoCapitalizeValue
722724
}
723725

724726
@ReactProp(name = "keyboardType")
@@ -882,6 +884,7 @@ public open class ReactTextInputManager public constructor() :
882884
override fun onAfterUpdateTransaction(view: ReactEditText) {
883885
super.onAfterUpdateTransaction(view)
884886
view.maybeUpdateTypeface()
887+
reconcileAutoCapitalize(view)
885888
view.commitStagedInputType()
886889
}
887890

@@ -1129,6 +1132,28 @@ public open class ReactTextInputManager public constructor() :
11291132

11301133
private const val IME_ACTION_ID = 0x670
11311134

1135+
// AUTOCAPITALIZE_FLAGS (0x7000) shares bit positions with TYPE_NUMBER_FLAG_SIGNED
1136+
// (0x1000) and TYPE_NUMBER_FLAG_DECIMAL (0x2000). We apply autocapitalize here
1137+
// after all props are set so the resolved input class determines whether the
1138+
// flags are meaningful.
1139+
private fun reconcileAutoCapitalize(view: ReactEditText) {
1140+
val autoCapValue = view.stagedAutoCapitalize
1141+
val inputClass = view.stagedInputType and InputType.TYPE_MASK_CLASS
1142+
1143+
// Only strip 0x4000 (CAP_SENTENCES) for non-text classes — 0x1000/0x2000 are
1144+
// valid numeric flags (SIGNED/DECIMAL) and must not be cleared.
1145+
val reconciled =
1146+
if (inputClass == InputType.TYPE_CLASS_TEXT) {
1147+
(view.stagedInputType and AUTOCAPITALIZE_FLAGS.inv()) or autoCapValue
1148+
} else {
1149+
view.stagedInputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES.inv()
1150+
}
1151+
1152+
if (view.stagedInputType != reconciled) {
1153+
view.stagedInputType = reconciled
1154+
}
1155+
}
1156+
11321157
// Sets the correct password type, since numeric and text passwords have different types
11331158
private fun checkPasswordType(view: ReactEditText) {
11341159
if (

packages/react-native/ReactAndroid/src/test/java/com/facebook/react/views/textinput/ReactTextInputPropertyTest.kt

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,65 @@ class ReactTextInputPropertyTest {
125125
assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS).isZero
126126
}
127127

128+
@Test
129+
fun testAutoCapitalizeDoesNotStripNumericFlags() {
130+
val numericTypeFlags =
131+
(InputType.TYPE_CLASS_NUMBER or
132+
InputType.TYPE_NUMBER_FLAG_DECIMAL or
133+
InputType.TYPE_NUMBER_FLAG_SIGNED)
134+
135+
manager.updateProperties(view, buildStyles("keyboardType", "numeric"))
136+
assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags)
137+
138+
manager.updateProperties(
139+
view,
140+
buildStyles("autoCapitalize", InputType.TYPE_TEXT_FLAG_CAP_SENTENCES),
141+
)
142+
assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_SIGNED).isNotZero
143+
assertThat(view.inputType and InputType.TYPE_NUMBER_FLAG_DECIMAL).isNotZero
144+
}
145+
146+
@Test
147+
fun testAutoCapitalizeAndNumericKeyboardInSameTransaction() {
148+
val numericTypeFlags =
149+
(InputType.TYPE_CLASS_NUMBER or
150+
InputType.TYPE_NUMBER_FLAG_DECIMAL or
151+
InputType.TYPE_NUMBER_FLAG_SIGNED)
152+
153+
manager.updateProperties(
154+
view,
155+
buildStyles(
156+
"autoCapitalize",
157+
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
158+
"keyboardType",
159+
"numeric",
160+
),
161+
)
162+
assertThat(view.inputType and numericTypeFlags).isEqualTo(numericTypeFlags)
163+
assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero
164+
}
165+
166+
@Test
167+
fun testAutoCapitalizeReappliesWhenKeyboardTypeChangesFromNumericToText() {
168+
// CAP_SENTENCES (0x4000) doesn't share a bit position with any numeric flag,
169+
// unlike CAP_WORDS (0x2000) / CAP_CHARACTERS (0x1000).
170+
manager.updateProperties(
171+
view,
172+
buildStyles(
173+
"autoCapitalize",
174+
InputType.TYPE_TEXT_FLAG_CAP_SENTENCES,
175+
"keyboardType",
176+
"numeric",
177+
),
178+
)
179+
assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_NUMBER)
180+
assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isZero
181+
182+
manager.updateProperties(view, buildStyles("keyboardType", "default"))
183+
assertThat(view.inputType and InputType.TYPE_MASK_CLASS).isEqualTo(InputType.TYPE_CLASS_TEXT)
184+
assertThat(view.inputType and InputType.TYPE_TEXT_FLAG_CAP_SENTENCES).isNotZero
185+
}
186+
128187
@Test
129188
fun testPlaceholder() {
130189
manager.updateProperties(view, buildStyles())

0 commit comments

Comments
 (0)