Skip to content

Commit b9491b7

Browse files
JoshuaGrossfacebook-github-bot
authored andcommitted
TextInput: support editing completely empty TextInputs
Summary: Before this change, C++ couldn't propagate changes that updated TextInputs that were completely empty. In C++ the AttributedString cannot contain Fragments with empty text, so a completely empty TextInput would have no Fragments; this breaks the C++ state value updating infra since it relies on copying over existing fragments. Instead, now we propagate default TextAttributes and ShadowView through State, so that State updates can use them to construct new Fragments. Changelog: [Internal] Reviewed By: shergin, mdvacca Differential Revision: D18835048 fbshipit-source-id: 58ac94c5454c8610c6287b096b62199045e5879b
1 parent 0556e86 commit b9491b7

File tree

2 files changed

+49
-2
lines changed

2 files changed

+49
-2
lines changed

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

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,9 +81,12 @@ AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString(
8181
auto textAttributes = TextAttributes::defaultTextAttributes();
8282
textAttributes.apply(getProps()->textAttributes);
8383

84+
// If there's no text, it's possible that this Fragment isn't actually
85+
// appended to the AttributedString (see implementation of appendFragment)
8486
fragment.textAttributes = textAttributes;
8587
fragment.parentShadowView = ShadowView(*this);
8688
textAttributedString.appendFragment(fragment);
89+
8790
return textAttributedString;
8891
}
8992

@@ -112,10 +115,20 @@ void AndroidTextInputShadowNode::updateStateIfNeeded() {
112115
return;
113116
}
114117

118+
// Store default TextAttributes in state.
119+
// In the case where the TextInput is completely empty (no value, no
120+
// defaultValue, no placeholder, no children) there are therefore no fragments
121+
// in the AttributedString, and when State is updated, it needs some way to
122+
// reconstruct a Fragment with default TextAttributes.
123+
auto defaultTextAttributes = TextAttributes::defaultTextAttributes();
124+
defaultTextAttributes.apply(getProps()->textAttributes);
125+
115126
setStateData(AndroidTextInputState{state.mostRecentEventCount,
116127
reactTreeAttributedString,
117128
reactTreeAttributedString,
118129
getProps()->paragraphAttributes,
130+
defaultTextAttributes,
131+
ShadowView(*this),
119132
textLayoutManager_});
120133
}
121134

ReactCommon/fabric/components/textinput/androidtextinput/AndroidTextInputState.h

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,20 @@ class AndroidTextInputState final {
4646
*/
4747
ParagraphAttributes paragraphAttributes{};
4848

49+
/**
50+
* Default TextAttributes used if we need to construct a new Fragment.
51+
* Only used if text is inserted into an AttributedString with no existing
52+
* Fragments.
53+
*/
54+
TextAttributes defaultTextAttributes;
55+
56+
/**
57+
* Default parent ShadowView used if we need to construct a new Fragment.
58+
* Only used if text is inserted into an AttributedString with no existing
59+
* Fragments.
60+
*/
61+
ShadowView defaultParentShadowView;
62+
4963
/*
5064
* `TextLayoutManager` provides a connection to platform-specific
5165
* text rendering infrastructure which is capable to render the
@@ -55,6 +69,8 @@ class AndroidTextInputState final {
5569

5670
#ifdef ANDROID
5771
AttributedString updateAttributedString(
72+
TextAttributes const &defaultTextAttributes,
73+
ShadowView const &defaultParentShadowView,
5874
AttributedString const &original,
5975
folly::dynamic const &data) {
6076
if (data["textChanged"].empty()) {
@@ -82,6 +98,15 @@ class AndroidTextInputState final {
8298
i++;
8399
}
84100

101+
if (fragments.size() > original.getFragments().size()) {
102+
for (; i < fragments.size(); i++) {
103+
str.appendFragment(
104+
AttributedString::Fragment{fragments[i]["string"].getString(),
105+
defaultTextAttributes,
106+
defaultParentShadowView});
107+
}
108+
}
109+
85110
return str;
86111
}
87112

@@ -90,21 +115,30 @@ class AndroidTextInputState final {
90115
AttributedString const &attributedString,
91116
AttributedString const &reactTreeAttributedString,
92117
ParagraphAttributes const &paragraphAttributes,
118+
TextAttributes const &defaultTextAttributes,
119+
ShadowView const &defaultParentShadowView,
93120
SharedTextLayoutManager const &layoutManager)
94121
: mostRecentEventCount(mostRecentEventCount),
95122
attributedString(attributedString),
96123
reactTreeAttributedString(reactTreeAttributedString),
97124
paragraphAttributes(paragraphAttributes),
125+
defaultTextAttributes(defaultTextAttributes),
126+
defaultParentShadowView(defaultParentShadowView),
98127
layoutManager(layoutManager) {}
99128
AndroidTextInputState() = default;
100129
AndroidTextInputState(
101130
AndroidTextInputState const &previousState,
102131
folly::dynamic const &data)
103132
: mostRecentEventCount((int64_t)data["mostRecentEventCount"].getInt()),
104-
attributedString(
105-
updateAttributedString(previousState.attributedString, data)),
133+
attributedString(updateAttributedString(
134+
previousState.defaultTextAttributes,
135+
previousState.defaultParentShadowView,
136+
previousState.attributedString,
137+
data)),
106138
reactTreeAttributedString(previousState.reactTreeAttributedString),
107139
paragraphAttributes(previousState.paragraphAttributes),
140+
defaultTextAttributes(previousState.defaultTextAttributes),
141+
defaultParentShadowView(previousState.defaultParentShadowView),
108142
layoutManager(previousState.layoutManager){};
109143
folly::dynamic getDynamic() const;
110144
#endif

0 commit comments

Comments
 (0)