Skip to content

Backport TextInput PRs #7056 #7285 #7658 to 0.63 (Diff inside issue) #7877

@creambyemute

Description

@creambyemute

Proposal: Backport TextInput PRs #7056 #7285 #7658 to 0.63

Summary

Backports TextInput Cursor and Controlled TextInput fixes from 0.64/0.65 to 0.63

Motivation

A lot of TextInput cursor/controlled TextInput behaviour has been fixed recently for 0.65 and 0.64 which is great! Unfortunately not everyone is able to update to 0.64/0.65 at the current point and TextInput is a, I would say, critical component to work correctly.

In our use-case, we aren't able to update to 0.64 due to some issues with native modules and a white screen on production build where I haven't had the time to figure out what is causing it and we're just about to release. See see #7537 for further details.

The changes mentioned in the title though are critical for our App to work correctly from a user-viewpoint.

Basic example

I have created a patch-package fix for myself including the above mentioned PRs:

Would it be possible to integrate that into a 0.63 release or would you rather advice to keep it as a patch with patch-package?

diff --git a/node_modules/react-native-windows/Libraries/Components/TextInput/TextInput.windows.js b/node_modules/react-native-windows/Libraries/Components/TextInput/TextInput.windows.js
index 33114f0..d344cf1 100644
--- a/node_modules/react-native-windows/Libraries/Components/TextInput/TextInput.windows.js
+++ b/node_modules/react-native-windows/Libraries/Components/TextInput/TextInput.windows.js
@@ -1140,6 +1140,7 @@ function InternalTextInput(props: Props): React.Node {
         ref={_setNativeRef}
         {...props}
         dataDetectorTypes={props.dataDetectorTypes}
+        mostRecentEventCount={mostRecentEventCount}
         onBlur={_onBlur}
         onChange={_onChange}
         onContentSizeChange={props.onContentSizeChange}
diff --git a/node_modules/react-native-windows/Microsoft.ReactNative/Views/TextInputViewManager.cpp b/node_modules/react-native-windows/Microsoft.ReactNative/Views/TextInputViewManager.cpp
index 892ca95..1e5d1cd 100644
--- a/node_modules/react-native-windows/Microsoft.ReactNative/Views/TextInputViewManager.cpp
+++ b/node_modules/react-native-windows/Microsoft.ReactNative/Views/TextInputViewManager.cpp
@@ -114,6 +114,7 @@ class TextInputShadowNode : public ShadowNodeBase {
   void setPasswordBoxPlaceholderForeground(xaml::Controls::PasswordBox passwordBox, folly::dynamic color);
   winrt::Shape FindCaret(xaml::DependencyObject element);
 
+  bool m_initialUpdateComplete = false;
   bool m_shouldClearTextOnFocus = false;
   bool m_shouldSelectTextOnFocus = false;
   bool m_contextMenuHidden = false;
@@ -153,6 +154,10 @@ void TextInputShadowNode::createView() {
 }
 
 void TextInputShadowNode::dispatchTextInputChangeEvent(winrt::hstring newText) {
+  if (!m_initialUpdateComplete) {
+    return;
+  }
+
   auto wkinstance = GetViewManager()->GetReactInstance();
 
   if (auto instance = wkinstance.lock()) {
@@ -182,14 +187,20 @@ void TextInputShadowNode::registerEvents() {
   // another TextChanged event with correct event count.
   if (m_isTextBox) {
     m_passwordBoxPasswordChangingRevoker = {};
-    m_textBoxTextChangingRevoker = control.as<xaml::Controls::TextBox>().TextChanging(
-        winrt::auto_revoke, [=](auto &&, auto &&) { m_nativeEventCount++; });
+    m_textBoxTextChangingRevoker =
+        control.as<xaml::Controls::TextBox>().TextChanging(winrt::auto_revoke, [=](auto &&, auto &&) {
+          if (m_initialUpdateComplete)
+            m_nativeEventCount++;
+        });
   } else {
     m_textBoxTextChangingRevoker = {};
 
     if (control.try_as<xaml::Controls::IPasswordBox4>()) {
-      m_passwordBoxPasswordChangingRevoker = control.as<xaml::Controls::IPasswordBox4>().PasswordChanging(
-          winrt::auto_revoke, [=](auto &&, auto &&) { m_nativeEventCount++; });
+      m_passwordBoxPasswordChangingRevoker =
+          control.as<xaml::Controls::IPasswordBox4>().PasswordChanging(winrt::auto_revoke, [=](auto &&, auto &&) {
+            if (m_initialUpdateComplete)
+              m_nativeEventCount++;
+          });
     }
   }
 
@@ -556,10 +567,17 @@ void TextInputShadowNode::updateProperties(const folly::dynamic &&props) {
         } else if (propertyName == "text") {
           if (m_mostRecentEventCount == m_nativeEventCount) {
             if (propertyValue.isString()) {
+              auto oldCursor = textBox.SelectionStart();
+              auto oldSelectionLength = textBox.SelectionLength();
               auto oldValue = textBox.Text();
               auto newValue = asHstring(propertyValue);
               if (oldValue != newValue) {
                 textBox.Text(newValue);
+                if (oldValue.size() == newValue.size()) {
+                  textBox.SelectionStart(oldCursor);
+                } else {
+                  textBox.SelectionStart(newValue.size());
+                }
               }
             } else if (propertyValue.isNull())
               textBox.ClearValue(xaml::Controls::TextBox::TextProperty());
@@ -596,6 +614,7 @@ void TextInputShadowNode::updateProperties(const folly::dynamic &&props) {
 
   Super::updateProperties(std::move(props));
   m_updating = false;
+  m_initialUpdateComplete = true;
 }
 
 TextInputViewManager::TextInputViewManager(const std::shared_ptr<IReactInstance> &reactInstance)

Metadata

Metadata

Assignees

Labels

Type

No type

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions