Skip to content

Commit

Permalink
TextInput: support editing completely empty TextInputs
Browse files Browse the repository at this point in the history
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
  • Loading branch information
JoshuaGross authored and facebook-github-bot committed Dec 5, 2019
1 parent 0556e86 commit b9491b7
Show file tree
Hide file tree
Showing 2 changed files with 49 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,12 @@ AttributedString AndroidTextInputShadowNode::getPlaceholderAttributedString(
auto textAttributes = TextAttributes::defaultTextAttributes();
textAttributes.apply(getProps()->textAttributes);

// If there's no text, it's possible that this Fragment isn't actually
// appended to the AttributedString (see implementation of appendFragment)
fragment.textAttributes = textAttributes;
fragment.parentShadowView = ShadowView(*this);
textAttributedString.appendFragment(fragment);

return textAttributedString;
}

Expand Down Expand Up @@ -112,10 +115,20 @@ void AndroidTextInputShadowNode::updateStateIfNeeded() {
return;
}

// Store default TextAttributes in state.
// In the case where the TextInput is completely empty (no value, no
// defaultValue, no placeholder, no children) there are therefore no fragments
// in the AttributedString, and when State is updated, it needs some way to
// reconstruct a Fragment with default TextAttributes.
auto defaultTextAttributes = TextAttributes::defaultTextAttributes();
defaultTextAttributes.apply(getProps()->textAttributes);

setStateData(AndroidTextInputState{state.mostRecentEventCount,
reactTreeAttributedString,
reactTreeAttributedString,
getProps()->paragraphAttributes,
defaultTextAttributes,
ShadowView(*this),
textLayoutManager_});
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,20 @@ class AndroidTextInputState final {
*/
ParagraphAttributes paragraphAttributes{};

/**
* Default TextAttributes used if we need to construct a new Fragment.
* Only used if text is inserted into an AttributedString with no existing
* Fragments.
*/
TextAttributes defaultTextAttributes;

/**
* Default parent ShadowView used if we need to construct a new Fragment.
* Only used if text is inserted into an AttributedString with no existing
* Fragments.
*/
ShadowView defaultParentShadowView;

/*
* `TextLayoutManager` provides a connection to platform-specific
* text rendering infrastructure which is capable to render the
Expand All @@ -55,6 +69,8 @@ class AndroidTextInputState final {

#ifdef ANDROID
AttributedString updateAttributedString(
TextAttributes const &defaultTextAttributes,
ShadowView const &defaultParentShadowView,
AttributedString const &original,
folly::dynamic const &data) {
if (data["textChanged"].empty()) {
Expand Down Expand Up @@ -82,6 +98,15 @@ class AndroidTextInputState final {
i++;
}

if (fragments.size() > original.getFragments().size()) {
for (; i < fragments.size(); i++) {
str.appendFragment(
AttributedString::Fragment{fragments[i]["string"].getString(),
defaultTextAttributes,
defaultParentShadowView});
}
}

return str;
}

Expand All @@ -90,21 +115,30 @@ class AndroidTextInputState final {
AttributedString const &attributedString,
AttributedString const &reactTreeAttributedString,
ParagraphAttributes const &paragraphAttributes,
TextAttributes const &defaultTextAttributes,
ShadowView const &defaultParentShadowView,
SharedTextLayoutManager const &layoutManager)
: mostRecentEventCount(mostRecentEventCount),
attributedString(attributedString),
reactTreeAttributedString(reactTreeAttributedString),
paragraphAttributes(paragraphAttributes),
defaultTextAttributes(defaultTextAttributes),
defaultParentShadowView(defaultParentShadowView),
layoutManager(layoutManager) {}
AndroidTextInputState() = default;
AndroidTextInputState(
AndroidTextInputState const &previousState,
folly::dynamic const &data)
: mostRecentEventCount((int64_t)data["mostRecentEventCount"].getInt()),
attributedString(
updateAttributedString(previousState.attributedString, data)),
attributedString(updateAttributedString(
previousState.defaultTextAttributes,
previousState.defaultParentShadowView,
previousState.attributedString,
data)),
reactTreeAttributedString(previousState.reactTreeAttributedString),
paragraphAttributes(previousState.paragraphAttributes),
defaultTextAttributes(previousState.defaultTextAttributes),
defaultParentShadowView(previousState.defaultParentShadowView),
layoutManager(previousState.layoutManager){};
folly::dynamic getDynamic() const;
#endif
Expand Down

0 comments on commit b9491b7

Please sign in to comment.