diff --git a/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js b/packages/react-native/Libraries/Components/TextInput/AndroidTextInputNativeComponent.js index 55b770d26a3..f5d06e24595 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 80db8f0a652..7234cbb3c32 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 0eb8f578d65..638acd7c792 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 3e0d8bf7681..0162f2bb007 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 8496a7d059e..0816eeb102e 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 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) { + return; + } + + // 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 = + view.getClass().getDeclaredField(DRAWABLE_HANDLE_RESOURCES[i]); + drawableResourceField.setAccessible(true); + int resourceId = drawableResourceField.getInt(view); + + // The view has no handle 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(DRAWABLE_HANDLE_FIELDS[i]); + cursorDrawableField.setAccessible(true); + cursorDrawableField.set(editor, drawable); + } catch (NoSuchFieldException ex) { + } catch (IllegalAccessException ex) { + } + } } @ReactProp(name = "cursorColor", customType = "Color") public void setCursorColor(ReactEditText view, @Nullable Integer color) { - if (color == null) { - return; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { Drawable cursorDrawable = view.getTextCursorDrawable(); if (cursorDrawable != null) { - cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN)); + if (color != null) { + cursorDrawable.setColorFilter(new BlendModeColorFilter(color, BlendMode.SRC_IN)); + } else { + cursorDrawable.clearColorFilter(); + } view.setTextCursorDrawable(cursorDrawable); } return; @@ -552,39 +607,35 @@ public class ReactTextInputManager extends BaseViewManager = [ + + ); }, @@ -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} );