Skip to content

Commit 7ab5eb4

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
AndroidTextInput: support using commands instead of setNativeProps (native change)
Summary: In AndroidTextInput, support codegen'd ViewCommands in native and add three commands that will eventually replace usage of setNativeProps on Android. TextInput will use these commands in a future diff. Changelog: [Internal] Reviewed By: TheSavior Differential Revision: D18612150 fbshipit-source-id: 5d427040686e8c5ab504dd845bc8ef863f558c35
1 parent 98b8a17 commit 7ab5eb4

File tree

4 files changed

+105
-24
lines changed

4 files changed

+105
-24
lines changed

Libraries/Components/TextInput/AndroidTextInputNativeComponent.js

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,13 @@ import type {
1818
Float,
1919
Int32,
2020
WithDefault,
21-
} from 'react-native/Libraries/Types/CodegenTypes';
21+
} from '../../Types/CodegenTypes';
2222
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
2323
import type {TextStyleProp, ViewStyleProp} from '../../StyleSheet/StyleSheet';
2424
import type {ColorValue} from '../../StyleSheet/StyleSheetTypes';
2525
import {requireNativeComponent} from 'react-native';
26+
import codegenNativeCommands from '../../Utilities/codegenNativeCommands';
27+
import * as React from 'react';
2628

2729
export type KeyboardType =
2830
// Cross Platform
@@ -534,6 +536,33 @@ export type NativeProps = $ReadOnly<{|
534536
text?: ?string,
535537
|}>;
536538

539+
type NativeType = HostComponent<NativeProps>;
540+
541+
interface NativeCommands {
542+
+focus: (viewRef: React.ElementRef<NativeType>) => void;
543+
+blur: (viewRef: React.ElementRef<NativeType>) => void;
544+
+setMostRecentEventCount: (
545+
viewRef: React.ElementRef<NativeType>,
546+
eventCount: Int32,
547+
) => void;
548+
+setTextAndSelection: (
549+
viewRef: React.ElementRef<NativeType>,
550+
mostRecentEventCount: Int32,
551+
value: ?string, // in theory this is nullable
552+
start: Int32,
553+
end: Int32,
554+
) => void;
555+
}
556+
557+
export const Commands: NativeCommands = codegenNativeCommands<NativeCommands>({
558+
supportedCommands: [
559+
'focus',
560+
'blur',
561+
'setMostRecentEventCount',
562+
'setTextAndSelection',
563+
],
564+
});
565+
537566
const AndroidTextInputNativeComponent: HostComponent<NativeProps> = requireNativeComponent<NativeProps>(
538567
'AndroidTextInput',
539568
);

ReactAndroid/src/main/java/com/facebook/react/views/text/ReactTextViewManager.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ public Object updateState(
9696

9797
return new ReactTextUpdate(
9898
spanned,
99-
-1, // TODO add this into local Data?
99+
state.hasKey("mostRecentEventCount") ? state.getInt("mostRecentEventCount") : -1,
100100
false, // TODO add this into local Data
101101
textViewProps.getTextAlign(),
102102
textBreakStrategy,

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

Lines changed: 43 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import android.text.InputType;
1616
import android.text.Layout;
1717
import android.text.Spannable;
18+
import android.text.SpannableStringBuilder;
1819
import android.text.TextWatcher;
1920
import android.view.Gravity;
2021
import android.view.KeyEvent;
@@ -55,6 +56,7 @@
5556
import com.facebook.react.views.text.TextAttributeProps;
5657
import com.facebook.react.views.text.TextInlineImageSpan;
5758
import com.facebook.react.views.text.TextLayoutManager;
59+
import com.facebook.react.views.text.TextTransform;
5860
import com.facebook.yoga.YogaConstants;
5961
import java.lang.reflect.Field;
6062
import java.util.LinkedList;
@@ -72,6 +74,8 @@ public class ReactTextInputManager extends BaseViewManager<ReactEditText, Layout
7274

7375
private static final int FOCUS_TEXT_INPUT = 1;
7476
private static final int BLUR_TEXT_INPUT = 2;
77+
private static final int SET_MOST_RECENT_EVENT_COUNT = 3;
78+
private static final int SET_TEXT_AND_SELECTION = 4;
7579

7680
private static final int INPUT_TYPE_KEYBOARD_NUMBER_PAD = InputType.TYPE_CLASS_NUMBER;
7781
private static final int INPUT_TYPE_KEYBOARD_DECIMAL_PAD =
@@ -177,10 +181,16 @@ public void receiveCommand(
177181
ReactEditText reactEditText, int commandId, @Nullable ReadableArray args) {
178182
switch (commandId) {
179183
case FOCUS_TEXT_INPUT:
180-
reactEditText.requestFocusFromJS();
184+
this.receiveCommand(reactEditText, "focus", args);
181185
break;
182186
case BLUR_TEXT_INPUT:
183-
reactEditText.clearFocusFromJS();
187+
this.receiveCommand(reactEditText, "blur", args);
188+
break;
189+
case SET_MOST_RECENT_EVENT_COUNT:
190+
this.receiveCommand(reactEditText, "setMostRecentEventCount", args);
191+
break;
192+
case SET_TEXT_AND_SELECTION:
193+
this.receiveCommand(reactEditText, "setTextAndSelection", args);
184194
break;
185195
}
186196
}
@@ -197,9 +207,40 @@ public void receiveCommand(
197207
case "blurTextInput":
198208
reactEditText.clearFocusFromJS();
199209
break;
210+
case "setMostRecentEventCount":
211+
reactEditText.setMostRecentEventCount(args.getInt(0));
212+
break;
213+
case "setTextAndSelection":
214+
int mostRecentEventCount = args.getInt(0);
215+
216+
if (mostRecentEventCount != UNSET) {
217+
String text = args.getString(1);
218+
219+
int start = args.getInt(2);
220+
int end = args.getInt(3);
221+
if (end == UNSET) {
222+
end = start;
223+
}
224+
225+
// TODO: construct a ReactTextUpdate and use that with maybeSetText
226+
// instead of calling setText, etc directly - doing that will definitely cause bugs.
227+
reactEditText.maybeSetText(getReactTextUpdate(text, mostRecentEventCount, start, end));
228+
}
229+
break;
200230
}
201231
}
202232

233+
// TODO: if we're able to fill in all these values and call maybeSetText when appropriate
234+
// I think this is all that's needed to fully support TextInput in Fabric
235+
private ReactTextUpdate getReactTextUpdate(
236+
String text, int mostRecentEventCount, int start, int end) {
237+
SpannableStringBuilder sb = new SpannableStringBuilder();
238+
sb.append(TextTransform.apply(text, TextTransform.UNSET));
239+
240+
return new ReactTextUpdate(
241+
sb, mostRecentEventCount, false, 0, 0, 0, 0, Gravity.NO_GRAVITY, 0, 0, start, end);
242+
}
243+
203244
@Override
204245
public void updateExtraData(ReactEditText view, Object extraData) {
205246
if (extraData instanceof ReactTextUpdate) {

ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputShadowNode.cpp

Lines changed: 31 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -36,29 +36,40 @@ AttributedString AndroidTextInputShadowNode::getAttributedString(
3636
textAttributes.apply(getProps()->textAttributes);
3737

3838
// Use BaseTextShadowNode to get attributed string from children
39-
{
40-
auto const &attributedString =
41-
BaseTextShadowNode::getAttributedString(textAttributes, *this);
42-
if (!attributedString.isEmpty() || !usePlaceholders) {
43-
return attributedString;
39+
auto const &attributedString =
40+
BaseTextShadowNode::getAttributedString(textAttributes, *this);
41+
if (!attributedString.isEmpty()) {
42+
return attributedString;
43+
}
44+
if (!getProps()->text.empty() || usePlaceholders) {
45+
// If the BaseTextShadowNode didn't detect any child Text nodes, we
46+
// may actually just have a `text` attribute.
47+
// TODO: figure out why BaseTextShadowNode doesn't pick this up, this
48+
// is a bug. A minimal Playground example that triggers this: P122991121
49+
auto textAttributedString = AttributedString{};
50+
auto fragment = AttributedString::Fragment{};
51+
fragment.string = getProps()->text;
52+
53+
if (usePlaceholders) {
54+
// Return placeholder text instead, if text was empty.
55+
if (fragment.string.empty()) {
56+
fragment.string = getProps()->placeholder;
57+
}
58+
// For measurement purposes, we want to make sure that there's at least a
59+
// single character in the string so that the measured height is greater
60+
// than zero. Otherwise, empty TextInputs with no placeholder don't
61+
// display at all.
62+
if (fragment.string.empty()) {
63+
fragment.string = " ";
64+
}
4465
}
66+
fragment.textAttributes = textAttributes;
67+
fragment.parentShadowView = ShadowView(*this);
68+
textAttributedString.appendFragment(fragment);
69+
return textAttributedString;
4570
}
4671

47-
// Return placeholder text instead, if text was empty.
48-
auto placeholderAttributedString = AttributedString{};
49-
auto fragment = AttributedString::Fragment{};
50-
fragment.string = getProps()->placeholder;
51-
52-
// For measurement purposes, we want to make sure that there's at least a
53-
// single character in the string so that the measured height is greater than
54-
// zero. Otherwise, empty TextInputs with no placeholder don't display at all.
55-
if (fragment.string == "") {
56-
fragment.string = " ";
57-
}
58-
fragment.textAttributes = textAttributes;
59-
fragment.parentShadowView = ShadowView(*this);
60-
placeholderAttributedString.appendFragment(fragment);
61-
return placeholderAttributedString;
72+
return attributedString;
6273
}
6374

6475
void AndroidTextInputShadowNode::setTextLayoutManager(

0 commit comments

Comments
 (0)