diff --git a/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
index 55b770d26a35ef..f5d06e24595213 100644
--- a/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
+++ b/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js
@@ -485,6 +485,11 @@ export type NativeProps = $ReadOnly<{|
*/
selectionColor?: ?ColorValue,
+ /**
+ * The text selection handle color.
+ */
+ selectionHandleColor?: ?ColorValue,
+
/**
* The start and end of the text input's selection. Set start and end to
* the same value to position the cursor.
@@ -692,6 +697,9 @@ export const __INTERNAL_VIEW_CONFIG: PartialViewConfig = {
fontStyle: true,
textShadowOffset: true,
selectionColor: {process: require('../../StyleSheet/processColor').default},
+ selectionHandleColor: {
+ process: require('../../StyleSheet/processColor').default,
+ },
placeholderTextColor: {
process: require('../../StyleSheet/processColor').default,
},
diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts
index 80db8f0a652225..7234cbb3c325c5 100644
--- a/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts
+++ b/packages/react-native/Libraries/Components/TextInput/TextInput.d.ts
@@ -336,6 +336,14 @@ export interface TextInputAndroidProps {
*/
cursorColor?: ColorValue | null | undefined;
+ /**
+ * When provided it will set the color of the selection handles when highlighting text.
+ * Unlike the behavior of `selectionColor` the handle color will be set independently
+ * from the color of the text selection box.
+ * @platform android
+ */
+ selectionHandleColor?: ColorValue | null | undefined;
+
/**
* Determines whether the individual fields in your app should be included in a
* view structure for autofill purposes on Android API Level 26+. Defaults to auto.
diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js
index 0eb8f578d65879..638acd7c7925a2 100644
--- a/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js
+++ b/packages/react-native/Libraries/Components/TextInput/TextInput.flow.js
@@ -332,6 +332,14 @@ type AndroidProps = $ReadOnly<{|
*/
cursorColor?: ?ColorValue,
+ /**
+ * When provided it will set the color of the selection handles when highlighting text.
+ * Unlike the behavior of `selectionColor` the handle color will be set independently
+ * from the color of the text selection box.
+ * @platform android
+ */
+ selectionHandleColor?: ?ColorValue,
+
/**
* When `false`, if there is a small amount of space available around a text input
* (e.g. landscape orientation on a phone), the OS may choose to have the user edit
diff --git a/packages/react-native/Libraries/Components/TextInput/TextInput.js b/packages/react-native/Libraries/Components/TextInput/TextInput.js
index 3e0d8bf7681dd3..0162f2bb007b02 100644
--- a/packages/react-native/Libraries/Components/TextInput/TextInput.js
+++ b/packages/react-native/Libraries/Components/TextInput/TextInput.js
@@ -917,6 +917,12 @@ export type Props = $ReadOnly<{|
*/
selectionColor?: ?ColorValue,
+ /**
+ * The text selection handle color.
+ * @platform android
+ */
+ selectionHandleColor?: ?ColorValue,
+
/**
* If `true`, all text will automatically be selected on focus.
*/
@@ -1111,6 +1117,9 @@ function InternalTextInput(props: Props): React.Node {
id,
tabIndex,
selection: propsSelection,
+ selectionColor,
+ selectionHandleColor,
+ cursorColor,
...otherProps
} = props;
@@ -1506,7 +1515,15 @@ function InternalTextInput(props: Props): React.Node {
if (childCount > 1) {
children = {children};
}
-
+ // For consistency with iOS set cursor/selectionHandle color as selectionColor
+ const colorProps = {
+ selectionColor,
+ selectionHandleColor:
+ selectionHandleColor === undefined
+ ? selectionColor
+ : selectionHandleColor,
+ cursorColor: cursorColor === undefined ? selectionColor : cursorColor,
+ };
textInput = (
/* $FlowFixMe[prop-missing] the types for AndroidTextInput don't match up
* exactly with the props for TextInput. This will need to get fixed */
@@ -1520,6 +1537,7 @@ function InternalTextInput(props: Props): React.Node {
// $FlowFixMe[incompatible-type] - Figure out imperative + forward refs.
ref={ref}
{...otherProps}
+ {...colorProps}
{...eventHandlers}
accessibilityState={_accessibilityState}
accessibilityLabelledBy={_accessibilityLabelledBy}
diff --git a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
index 8496a7d059e4c8..0816eeb102e4f4 100644
--- a/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
+++ b/packages/react-native/ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java
@@ -168,14 +168,11 @@ public class ReactTextInputManager extends BaseViewManager= Build.VERSION_CODES.Q) {
- Drawable cursorDrawable = view.getTextCursorDrawable();
- if (cursorDrawable != null) {
- cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN));
- view.setTextCursorDrawable(cursorDrawable);
+ Drawable drawableCenter = view.getTextSelectHandle().mutate();
+ Drawable drawableLeft = view.getTextSelectHandleLeft().mutate();
+ Drawable drawableRight = view.getTextSelectHandleRight().mutate();
+ if (color != null) {
+ BlendModeColorFilter filter = new BlendModeColorFilter(color, BlendMode.SRC_IN);
+ drawableCenter.setColorFilter(filter);
+ drawableLeft.setColorFilter(filter);
+ drawableRight.setColorFilter(filter);
+ } else {
+ drawableCenter.clearColorFilter();
+ drawableLeft.clearColorFilter();
+ drawableRight.clearColorFilter();
}
+ view.setTextSelectHandle(drawableCenter);
+ view.setTextSelectHandleLeft(drawableLeft);
+ view.setTextSelectHandleRight(drawableRight);
return;
}
+ // Based on https://github.com/facebook/react-native/pull/31007
if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
- // Pre-Android 10, there was no supported API to change the cursor color programmatically.
- // In Android 9.0, they changed the underlying implementation,
- // but also "dark greylisted" the new field, rendering it unusable.
return;
}
- // The evil code that follows uses reflection to achieve this on Android 8.1 and below.
- // Based on https://tinyurl.com/3vff8lyu https://tinyurl.com/vehggzs9
- for (int i = 0; i < DRAWABLE_RESOURCES.length; i++) {
+ // The following code uses reflection to change handles color on Android 8.1 and below.
+ for (int i = 0; i < DRAWABLE_HANDLE_RESOURCES.length; i++) {
try {
- Field drawableResourceField = TextView.class.getDeclaredField(DRAWABLE_RESOURCES[i]);
+ Field drawableResourceField =
+ view.getClass().getDeclaredField(DRAWABLE_HANDLE_RESOURCES[i]);
drawableResourceField.setAccessible(true);
int resourceId = drawableResourceField.getInt(view);
- // The view has no cursor drawable.
+ // The view has no handle drawable.
if (resourceId == 0) {
return;
}
- Drawable drawable = ContextCompat.getDrawable(view.getContext(), resourceId);
-
- Drawable drawableCopy = drawable.mutate();
- drawableCopy.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ Drawable drawable = ContextCompat.getDrawable(view.getContext(), resourceId).mutate();
+ if (color != null) {
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ } else {
+ drawable.clearColorFilter();
+ }
Field editorField = TextView.class.getDeclaredField("mEditor");
editorField.setAccessible(true);
Object editor = editorField.get(view);
- Field cursorDrawableField = editor.getClass().getDeclaredField(DRAWABLE_FIELDS[i]);
+ Field cursorDrawableField = editor.getClass().getDeclaredField(DRAWABLE_HANDLE_FIELDS[i]);
cursorDrawableField.setAccessible(true);
- if (DRAWABLE_RESOURCES[i] == "mCursorDrawableRes") {
- Drawable[] drawables = {drawableCopy, drawableCopy};
- cursorDrawableField.set(editor, drawables);
- } else {
- cursorDrawableField.set(editor, drawableCopy);
- }
+ cursorDrawableField.set(editor, drawable);
} catch (NoSuchFieldException ex) {
- // Ignore errors to avoid crashing if these private fields don't exist on modified
- // or future android versions.
} catch (IllegalAccessException ex) {
}
}
}
+ @ReactProp(name = "cursorColor", customType = "Color")
+ public void setCursorColor(ReactEditText view, @Nullable Integer color) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+ Drawable cursorDrawable = view.getTextCursorDrawable();
+ if (cursorDrawable != null) {
+ if (color != null) {
+ cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN));
+ } else {
+ cursorDrawable.clearColorFilter();
+ }
+ view.setTextCursorDrawable(cursorDrawable);
+ }
+ return;
+ }
+
+ if (Build.VERSION.SDK_INT == Build.VERSION_CODES.P) {
+ // Pre-Android 10, there was no supported API to change the cursor color programmatically.
+ // In Android 9.0, they changed the underlying implementation,
+ // but also "dark greylisted" the new field, rendering it unusable.
+ return;
+ }
+
+ // The evil code that follows uses reflection to achieve this on Android 8.1 and below.
+ // Based on https://tinyurl.com/3vff8lyu https://tinyurl.com/vehggzs9
+ try {
+ Field drawableCursorField = view.getClass().getDeclaredField("mCursorDrawableRes");
+ drawableCursorField.setAccessible(true);
+ int resourceId = drawableCursorField.getInt(view);
+
+ // The view has no cursor drawable.
+ if (resourceId == 0) {
+ return;
+ }
+
+ Drawable drawable = ContextCompat.getDrawable(view.getContext(), resourceId).mutate();
+ if (color != null) {
+ drawable.setColorFilter(color, PorterDuff.Mode.SRC_IN);
+ } else {
+ drawable.clearColorFilter();
+ }
+
+ Field editorField = TextView.class.getDeclaredField("mEditor");
+ editorField.setAccessible(true);
+ Object editor = editorField.get(view);
+
+ Field cursorDrawableField = editor.getClass().getDeclaredField("mCursorDrawable");
+ cursorDrawableField.setAccessible(true);
+ Drawable[] drawables = {drawable, drawable};
+ cursorDrawableField.set(editor, drawables);
+ } catch (NoSuchFieldException ex) {
+ // Ignore errors to avoid crashing if these private fields don't exist on modified
+ // or future android versions.
+ } catch (IllegalAccessException ex) {
+ }
+ }
+
private static boolean shouldHideCursorForEmailTextInput() {
String manufacturer = Build.MANUFACTURER.toLowerCase(Locale.ROOT);
return (Build.VERSION.SDK_INT == Build.VERSION_CODES.Q && manufacturer.contains("xiaomi"));
diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
index 116284f1c6bcaa..2687d64cb3f86f 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.cpp
@@ -134,6 +134,10 @@ AndroidTextInputProps::AndroidTextInputProps(
"selectionColor",
sourceProps.selectionColor,
{})),
+ selectionHandleColor(CoreFeatures::enablePropIteratorSetter? sourceProps.selectionHandleColor : convertRawProp(context, rawProps,
+ "selectionHandleColor",
+ sourceProps.selectionHandleColor,
+ {})),
value(CoreFeatures::enablePropIteratorSetter? sourceProps.value : convertRawProp(context, rawProps, "value", sourceProps.value, {})),
defaultValue(CoreFeatures::enablePropIteratorSetter? sourceProps.defaultValue : convertRawProp(context, rawProps,
"defaultValue",
@@ -347,6 +351,7 @@ void AndroidTextInputProps::setProp(
RAW_SET_PROP_SWITCH_CASE_BASIC(placeholderTextColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(secureTextEntry);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectionColor);
+ RAW_SET_PROP_SWITCH_CASE_BASIC(selectionHandleColor);
RAW_SET_PROP_SWITCH_CASE_BASIC(defaultValue);
RAW_SET_PROP_SWITCH_CASE_BASIC(selectTextOnFocus);
RAW_SET_PROP_SWITCH_CASE_BASIC(submitBehavior);
@@ -446,6 +451,7 @@ folly::dynamic AndroidTextInputProps::getDynamic() const {
props["placeholderTextColor"] = toAndroidRepr(placeholderTextColor);
props["secureTextEntry"] = secureTextEntry;
props["selectionColor"] = toAndroidRepr(selectionColor);
+ props["selectionHandleColor"] = toAndroidRepr(selectionHandleColor);
props["value"] = value;
props["defaultValue"] = defaultValue;
props["selectTextOnFocus"] = selectTextOnFocus;
diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
index a690816906ae2f..82fb75ea1028e4 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
+++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/androidtextinput/react/renderer/components/androidtextinput/AndroidTextInputProps.h
@@ -100,6 +100,7 @@ class AndroidTextInputProps final : public ViewProps, public BaseTextProps {
SharedColor placeholderTextColor{};
bool secureTextEntry{false};
SharedColor selectionColor{};
+ SharedColor selectionHandleColor{};
std::string value{};
std::string defaultValue{};
bool selectTextOnFocus{false};
diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.cpp b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.cpp
index 3e4b712e91d06c..a825971ddbe8c8 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.cpp
+++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.cpp
@@ -62,6 +62,12 @@ TextInputProps::TextInputProps(
"selectionColor",
sourceProps.selectionColor,
{})),
+ selectionHandleColor(convertRawProp(
+ context,
+ rawProps,
+ "selectionHandleColor",
+ sourceProps.selectionHandleColor,
+ {})),
underlineColorAndroid(convertRawProp(
context,
rawProps,
diff --git a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.h b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.h
index 203f762ede10ab..e3db155099c78e 100644
--- a/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.h
+++ b/packages/react-native/ReactCommon/react/renderer/components/textinput/iostextinput/react/renderer/components/iostextinput/TextInputProps.h
@@ -53,6 +53,7 @@ class TextInputProps final : public ViewProps, public BaseTextProps {
*/
const SharedColor cursorColor{};
const SharedColor selectionColor{};
+ const SharedColor selectionHandleColor{};
// TODO: Rename to `tintColor` and make universal.
const SharedColor underlineColorAndroid{};
diff --git a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
index d0ad866155858d..35347b720cedc6 100644
--- a/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
+++ b/packages/rn-tester/js/examples/TextInput/TextInputExample.android.js
@@ -196,10 +196,20 @@ const examples: Array = [
+
+
);
},
@@ -470,7 +480,7 @@ const examples: Array = [
'next',
];
const returnKeyLabels = ['Compile', 'React Native'];
- const examples = returnKeyTypes.map(type => {
+ const returnKeyExamples = returnKeyTypes.map(type => {
return (
= [
});
return (
- {examples}
+ {returnKeyExamples}
{types}
);