-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Adds declarative props to clear/submit TextInput #7333
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
7e4a53a
a30a077
499f549
83b6ef9
da2653a
eb517c8
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,7 @@ | ||
| { | ||
| "type": "prerelease", | ||
| "comment": "Adds declarative props to clear/submit TextInput", | ||
| "packageName": "react-native-windows", | ||
| "email": "erozell@outlook.com", | ||
| "dependentChangeType": "patch" | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -124,6 +124,7 @@ class TextInputShadowNode : public ShadowNodeBase { | |
| private: | ||
| void dispatchTextInputChangeEvent(winrt::hstring newText); | ||
| void registerEvents(); | ||
| void registerPreviewKeyDown(); | ||
| void HideCaretIfNeeded(); | ||
| void setPasswordBoxPlaceholderForeground( | ||
| xaml::Controls::PasswordBox passwordBox, | ||
|
|
@@ -138,6 +139,8 @@ class TextInputShadowNode : public ShadowNodeBase { | |
| bool m_hideCaret = false; | ||
| bool m_isTextBox = true; | ||
| winrt::Microsoft::ReactNative::JSValue m_placeholderTextColor; | ||
| bool m_shouldClearTextOnSubmit = false; | ||
| std::vector<HandledKeyboardEvent> m_submitKeyEvents{}; | ||
|
|
||
| // Javascripts is running in a different thread. If the typing is very fast, | ||
| // It's possible that two TextChanged are raised but TextInput just got the | ||
|
|
@@ -158,7 +161,7 @@ class TextInputShadowNode : public ShadowNodeBase { | |
|
|
||
| xaml::Controls::Control::GotFocus_revoker m_controlGotFocusRevoker{}; | ||
| xaml::Controls::Control::LostFocus_revoker m_controlLostFocusRevoker{}; | ||
| xaml::Controls::Control::KeyDown_revoker m_controlKeyDownRevoker{}; | ||
| xaml::Controls::Control::PreviewKeyDown_revoker m_controlPreviewKeyDownRevoker{}; | ||
| xaml::Controls::Control::SizeChanged_revoker m_controlSizeChangedRevoker{}; | ||
| xaml::Controls::Control::CharacterReceived_revoker m_controlCharacterReceivedRevoker{}; | ||
| xaml::Controls::ScrollViewer::ViewChanging_revoker m_scrollViewerViewChangingRevoker{}; | ||
|
|
@@ -277,21 +280,7 @@ void TextInputShadowNode::registerEvents() { | |
| } | ||
| }); | ||
|
|
||
| m_controlKeyDownRevoker = | ||
| control.KeyDown(winrt::auto_revoke, [=](auto &&, xaml::Input::KeyRoutedEventArgs const &args) { | ||
| if (args.Key() == winrt::Windows::System::VirtualKey::Enter && !args.Handled()) { | ||
| folly::dynamic eventDataSubmitEditing = {}; | ||
| if (m_isTextBox) { | ||
| eventDataSubmitEditing = folly::dynamic::object("target", tag)( | ||
| "text", react::uwp::HstringToDynamic(control.as<xaml::Controls::TextBox>().Text())); | ||
| } else { | ||
| eventDataSubmitEditing = folly::dynamic::object("target", tag)( | ||
| "text", react::uwp::HstringToDynamic(control.as<xaml::Controls::PasswordBox>().Password())); | ||
| } | ||
| GetViewManager()->GetReactContext().DispatchEvent( | ||
| tag, "topTextInputSubmitEditing", std::move(eventDataSubmitEditing)); | ||
| } | ||
| }); | ||
| registerPreviewKeyDown(); | ||
|
|
||
| if (m_isTextBox) { | ||
| auto textBox = control.as<xaml::Controls::TextBox>(); | ||
|
|
@@ -373,6 +362,61 @@ void TextInputShadowNode::registerEvents() { | |
| true); | ||
| } | ||
|
|
||
| void TextInputShadowNode::registerPreviewKeyDown() { | ||
| auto control = GetView().as<xaml::Controls::Control>(); | ||
| auto tag = m_tag; | ||
| m_controlPreviewKeyDownRevoker = | ||
| control.PreviewKeyDown(winrt::auto_revoke, [=](auto &&, xaml::Input::KeyRoutedEventArgs const &args) { | ||
| auto isMultiline = m_isTextBox && control.as<xaml::Controls::TextBox>().AcceptsReturn(); | ||
| auto shouldSubmit = !args.Handled(); | ||
rectified95 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| if (shouldSubmit) { | ||
| if (!isMultiline && m_submitKeyEvents.size() == 0) { | ||
| // If no 'submitKeyEvents' are supplied, use the default behavior for single-line TextInput | ||
| shouldSubmit = args.Key() == winrt::Windows::System::VirtualKey::Enter; | ||
| } else if (m_submitKeyEvents.size() > 0) { | ||
| // If 'submitKeyEvents' are supplied, use them to determine whether to emit | ||
| // 'onSubmitEditing' for either single-line or multi-line TextInput | ||
|
|
||
| // This must be kept in sync with the default value for HandledKeyboardEvent.handledEventPhase | ||
| auto defaultEventPhase = HandledEventPhase::Bubbling; | ||
| auto currentEvent = KeyboardHelper::CreateKeyboardEvent(defaultEventPhase, args); | ||
| shouldSubmit = KeyboardHelper::ShouldMarkKeyboardHandled(m_submitKeyEvents, currentEvent); | ||
| } else { | ||
| // If no 'submitKeyEvents' are supplied, do not emit 'onSubmitEditing' for multi-line TextInput | ||
| shouldSubmit = false; | ||
| } | ||
| } | ||
|
|
||
| if (shouldSubmit) { | ||
| folly::dynamic eventDataSubmitEditing = {}; | ||
| if (m_isTextBox) { | ||
| eventDataSubmitEditing = folly::dynamic::object("target", tag)( | ||
| "text", react::uwp::HstringToDynamic(control.as<xaml::Controls::TextBox>().Text())); | ||
| } else { | ||
| eventDataSubmitEditing = folly::dynamic::object("target", tag)( | ||
| "text", react::uwp::HstringToDynamic(control.as<xaml::Controls::PasswordBox>().Password())); | ||
| } | ||
|
|
||
| if (m_shouldClearTextOnSubmit) { | ||
| if (m_isTextBox) { | ||
| control.as<xaml::Controls::TextBox>().ClearValue(xaml::Controls::TextBox::TextProperty()); | ||
| } else { | ||
| control.as<xaml::Controls::PasswordBox>().ClearValue(xaml::Controls::PasswordBox::PasswordProperty()); | ||
| } | ||
| } | ||
|
|
||
| GetViewManager()->GetReactContext().DispatchEvent( | ||
| tag, "topTextInputSubmitEditing", std::move(eventDataSubmitEditing)); | ||
|
|
||
| // For multi-line TextInput, we have to mark the PreviewKeyDown event as | ||
| // handled to prevent the TextInput from adding a newline character | ||
| if (isMultiline) { | ||
| args.Handled(true); | ||
| } | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| xaml::Shapes::Shape TextInputShadowNode::FindCaret(xaml::DependencyObject element) { | ||
| if (element == nullptr) | ||
| return nullptr; | ||
|
|
@@ -423,6 +467,7 @@ void TextInputShadowNode::updateProperties(winrt::Microsoft::ReactNative::JSValu | |
| auto control = GetView().as<xaml::Controls::Control>(); | ||
| auto textBox = control.try_as<xaml::Controls::TextBox>(); | ||
| auto passwordBox = control.try_as<xaml::Controls::PasswordBox>(); | ||
| auto hasKeyDownEvents = false; | ||
|
|
||
| for (auto &pair : props) { | ||
| const std::string &propertyName = pair.first; | ||
|
|
@@ -544,6 +589,16 @@ void TextInputShadowNode::updateProperties(winrt::Microsoft::ReactNative::JSValu | |
| } else if (m_isTextBox != true && react::uwp::IsValidColorValue(propertyValue)) { | ||
| setPasswordBoxPlaceholderForeground(passwordBox, propertyValue); | ||
| } | ||
| } else if (propertyName == "clearTextOnSubmit") { | ||
| if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Boolean) | ||
rozele marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| m_shouldClearTextOnSubmit = propertyValue.AsBoolean(); | ||
| } else if (propertyName == "submitKeyEvents") { | ||
| if (propertyValue.Type() == winrt::Microsoft::ReactNative::JSValueType::Array) | ||
| m_submitKeyEvents = KeyboardHelper::FromJS(propertyValue); | ||
| else if (propertyValue.IsNull()) | ||
| m_submitKeyEvents.clear(); | ||
rozele marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } else if (propertyName == "keyDownEvents") { | ||
| hasKeyDownEvents = propertyValue.ItemCount() > 0; | ||
| } else { | ||
| if (m_isTextBox) { // Applicable properties for TextBox | ||
| if (TryUpdateTextAlignment(textBox, propertyName, propertyValue)) { | ||
|
|
@@ -602,6 +657,13 @@ void TextInputShadowNode::updateProperties(winrt::Microsoft::ReactNative::JSValu | |
| } | ||
|
|
||
| Super::updateProperties(props); | ||
|
|
||
| // We need to re-register the PreviewKeyDown handler so it is invoked after the ShadowNodeBase handler | ||
| if (hasKeyDownEvents) { | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this necessary? I thought
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes - this is necessary. Although the root-level keyboard event handler for emitting key events gets subscribed before any of these props get set, the view-level keyboard event handler that sets If we subscribe to Note, this wasn't a problem before because you could just use
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Got it. Then, I have a few questions:
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Just for my information - this relies on the event handlers being called in the order of their registration for a given event type - is that guaranteed not to change (and documented somewhere)? |
||
| m_controlPreviewKeyDownRevoker.revoke(); | ||
| registerPreviewKeyDown(); | ||
| } | ||
|
|
||
| m_updating = false; | ||
| } | ||
|
|
||
|
|
@@ -697,6 +759,8 @@ void TextInputViewManager::GetNativeProps(const winrt::Microsoft::ReactNative::I | |
| React::WriteProperty(writer, L"contextMenuHidden", L"boolean"); | ||
| React::WriteProperty(writer, L"caretHidden", L"boolean"); | ||
| React::WriteProperty(writer, L"autoCapitalize", L"string"); | ||
| React::WriteProperty(writer, L"clearTextOnSubmit", L"boolean"); | ||
| React::WriteProperty(writer, L"submitKeyEvents", L"array"); | ||
| } | ||
|
|
||
| void TextInputViewManager::GetExportedCustomDirectEventTypeConstants( | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.