diff --git a/change/react-native-windows-2019-11-15-16-31-57-keyboardDismissMode.json b/change/react-native-windows-2019-11-15-16-31-57-keyboardDismissMode.json new file mode 100644 index 00000000000..32ebe6f5fb8 --- /dev/null +++ b/change/react-native-windows-2019-11-15-16-31-57-keyboardDismissMode.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Support keyboardDismissMode on-drag for ScrollView", + "packageName": "react-native-windows", + "email": "dida@ntdev.microsoft.com", + "commit": "bfbf63cc3293f15622d069778fd0283c6f84b12b", + "date": "2019-11-16T00:31:57.734Z" +} \ No newline at end of file diff --git a/vnext/ReactUWP/Utils/Helpers.cpp b/vnext/ReactUWP/Utils/Helpers.cpp index c6919e0b697..cbcb19c448a 100644 --- a/vnext/ReactUWP/Utils/Helpers.cpp +++ b/vnext/ReactUWP/Utils/Helpers.cpp @@ -58,12 +58,37 @@ bool IsAPIContractVxAvailable() { return isAPIContractVxAvailable; } +bool IsAPIContractV5Available() { + return IsAPIContractVxAvailable<5>(); +} + bool IsAPIContractV6Available() { return IsAPIContractVxAvailable<6>(); } +bool IsAPIContractV7Available() { + return IsAPIContractVxAvailable<7>(); +} + +bool IsAPIContractV8Available() { + return IsAPIContractVxAvailable<8>(); +} + +bool IsRS3OrHigher() { + return IsAPIContractV5Available(); +} + bool IsRS4OrHigher() { return IsAPIContractV6Available(); } + +bool IsRS5OrHigher() { + return IsAPIContractV7Available(); +} + +bool Is19H1OrHigher() { + return IsAPIContractV8Available(); +} + } // namespace uwp }; // namespace react diff --git a/vnext/ReactUWP/Utils/Helpers.h b/vnext/ReactUWP/Utils/Helpers.h index 50c46a234b3..ad12e1a9ee6 100644 --- a/vnext/ReactUWP/Utils/Helpers.h +++ b/vnext/ReactUWP/Utils/Helpers.h @@ -30,6 +30,9 @@ inline typename T asEnum(folly::dynamic const &obj) { ReactId getViewId(_In_ IReactInstance *instance, winrt::FrameworkElement const &fe); std::int32_t CountOpenPopups(); +bool IsRS3OrHigher(); bool IsRS4OrHigher(); +bool IsRS5OrHigher(); +bool Is19H1OrHigher(); } // namespace uwp } // namespace react diff --git a/vnext/ReactUWP/Views/KeyboardEventHandler.cpp b/vnext/ReactUWP/Views/KeyboardEventHandler.cpp index c385a01a550..0e9f8bcc5f2 100644 --- a/vnext/ReactUWP/Views/KeyboardEventHandler.cpp +++ b/vnext/ReactUWP/Views/KeyboardEventHandler.cpp @@ -68,11 +68,13 @@ PreviewKeyboardEventHandler::PreviewKeyboardEventHandler(KeyboardEventCallback & void PreviewKeyboardEventHandler::hook(XamlView xamlView) { auto uiElement = xamlView.as(); - if (m_keyDownCallback) - m_previewKeyDownRevoker = uiElement.PreviewKeyDown(winrt::auto_revoke, m_keyDownCallback); + if (uiElement.try_as()) { + if (m_keyDownCallback) + m_previewKeyDownRevoker = uiElement.PreviewKeyDown(winrt::auto_revoke, m_keyDownCallback); - if (m_keyUpCallback) - m_previewKeyUpRevoker = uiElement.PreviewKeyUp(winrt::auto_revoke, m_keyUpCallback); + if (m_keyUpCallback) + m_previewKeyUpRevoker = uiElement.PreviewKeyUp(winrt::auto_revoke, m_keyUpCallback); + } } void PreviewKeyboardEventHandler::unhook() { diff --git a/vnext/ReactUWP/Views/ReactControl.cpp b/vnext/ReactUWP/Views/ReactControl.cpp index f950020c90e..80754f3d4f9 100644 --- a/vnext/ReactUWP/Views/ReactControl.cpp +++ b/vnext/ReactUWP/Views/ReactControl.cpp @@ -203,6 +203,7 @@ void ReactControl::AttachRoot() noexcept { m_touchEventHandler->AddTouchHandlers(m_xamlRootView); m_previewKeyboardEventHandlerOnRoot->hook(m_xamlRootView); + m_SIPEventHandler->AttachView(m_xamlRootView, true /*fireKeyboradEvents*/); auto initialProps = m_initialProps; m_reactInstance->AttachMeasuredRootView(m_pParent, std::move(initialProps)); diff --git a/vnext/ReactUWP/Views/SIPEventHandler.cpp b/vnext/ReactUWP/Views/SIPEventHandler.cpp index f3c7ec7c4b3..68e2430781c 100644 --- a/vnext/ReactUWP/Views/SIPEventHandler.cpp +++ b/vnext/ReactUWP/Views/SIPEventHandler.cpp @@ -7,6 +7,7 @@ #include +#include #include #include @@ -20,38 +21,77 @@ namespace react { namespace uwp { SIPEventHandler::SIPEventHandler(const std::weak_ptr &reactInstance) - : m_wkReactInstance(reactInstance) { - auto coreInputView = winrt::CoreInputView::GetForCurrentView(); - if (coreInputView) { - m_occlusionsChanged_revoker = coreInputView.OcclusionsChanged( - winrt::auto_revoke, [=](auto &&, const winrt::CoreInputViewOcclusionsChangedEventArgs &e) { + : m_wkReactInstance(reactInstance), m_fireKeyboradEvents(false), m_finalRect(winrt::RectHelper::Empty()){}; + +SIPEventHandler::~SIPEventHandler() { + m_occlusionsChanged_revoker = {}; +} +// keyboardDidHide and keyboardDidShow events works on >= RS3 +// TryShow and TryHide works on >= RS5 + +void SIPEventHandler::AttachView(XamlView xamlView, bool fireKeyboardEvents) { + m_fireKeyboradEvents = fireKeyboardEvents; + + if (!IsRS3OrHigher()) { + return; // CoreInputView is only supported on >= RS3. + } + + if (Is19H1OrHigher()) { + // 19H1 and higher supports island scenarios + auto uiElement(xamlView.as()); + m_coreInputView = winrt::CoreInputView::GetForUIContext(uiElement.UIContext()); + } else { + m_coreInputView = winrt::CoreInputView::GetForCurrentView(); + } + + if (m_coreInputView) { + auto occlusions = m_coreInputView.GetCoreInputViewOcclusions(); + m_isShowing = !IsOcclusionsEmpty(occlusions); + m_occlusionsChanged_revoker = m_coreInputView.OcclusionsChanged( + winrt::auto_revoke, [this](auto &&, const winrt::CoreInputViewOcclusionsChangedEventArgs &e) { if (!e.Handled()) { - winrt::Rect finalRect = winrt::RectHelper::Empty(); - winrt::IVectorView occlusions = e.Occlusions(); - for (uint32_t i = 0; i < occlusions.Size(); i++) { - winrt::CoreInputViewOcclusion occlusion = occlusions.GetAt(i); - if (occlusion.OcclusionKind() == winrt::CoreInputViewOcclusionKind::Docked) { - finalRect = winrt::RectHelper::Union(finalRect, occlusion.OccludingRect()); + bool wasShowing = m_isShowing; + m_isShowing = !IsOcclusionsEmpty(e.Occlusions()); + if (wasShowing != m_isShowing && m_fireKeyboradEvents) { + if (!m_isShowing) { + folly::dynamic params = folly::dynamic::object("screenY", 0)("screenX", 0)("width", 0)("height", 0); + SendEvent("keyboardDidHide", std::move(params)); + } else { + folly::dynamic params = folly::dynamic::object( + "endCoordinates", + folly::dynamic::object("screenY", m_finalRect.Y)("screenX", m_finalRect.X)( + "width", m_finalRect.Width)("height", m_finalRect.Height)); + SendEvent("keyboardDidShow", std::move(params)); } } - - if (winrt::RectHelper::GetIsEmpty(finalRect)) { - folly::dynamic params = folly::dynamic::object("screenY", 0)("screenX", 0)("width", 0)("height", 0); - SendEvent("keyboardDidHide", std::move(params)); - } else { - folly::dynamic params = folly::dynamic::object( - "endCoordinates", - folly::dynamic::object("screenY", finalRect.Y)("screenX", finalRect.X)("width", finalRect.Width)( - "height", finalRect.Height)); - SendEvent("keyboardDidShow", std::move(params)); - } } }); } } +/* +void SIPEventHandler::TryShow() { + if (IsRS5OrHigher() && m_coreInputView && !m_isShowing) { // CoreInputView.TryShow is only avaliable after RS5 + m_coreInputView.TryShow(); + } +} +*/ -SIPEventHandler::~SIPEventHandler() { - m_occlusionsChanged_revoker = {}; +void SIPEventHandler::TryHide() { + if (IsRS5OrHigher() && m_coreInputView && m_isShowing) { // CoreInputView.TryHide is only avaliable after RS5 + m_coreInputView.TryHide(); + } +} + +bool SIPEventHandler::IsOcclusionsEmpty(winrt::IVectorView occlusions) { + m_finalRect = winrt::RectHelper::Empty(); + if (occlusions) { + for (const auto &occlusion : occlusions) { + if (occlusion.OcclusionKind() == winrt::CoreInputViewOcclusionKind::Docked) { + m_finalRect = winrt::RectHelper::Union(m_finalRect, occlusion.OccludingRect()); + } + } + } + return (winrt::RectHelper::GetIsEmpty(m_finalRect)); } void SIPEventHandler::SendEvent(std::string &&eventName, folly::dynamic &¶meters) { diff --git a/vnext/ReactUWP/Views/SIPEventHandler.h b/vnext/ReactUWP/Views/SIPEventHandler.h index 142bd258f55..defbd64c5b0 100644 --- a/vnext/ReactUWP/Views/SIPEventHandler.h +++ b/vnext/ReactUWP/Views/SIPEventHandler.h @@ -8,7 +8,7 @@ #include namespace winrt { -using namespace Windows::Foundation; +using namespace Windows::Foundation::Collections; using namespace Windows::UI::ViewManagement::Core; } // namespace winrt @@ -20,10 +20,23 @@ class SIPEventHandler { SIPEventHandler(const std::weak_ptr &reactInstance); virtual ~SIPEventHandler(); + bool IsSIPShowing() { + return m_isShowing; + } + + void AttachView(XamlView xamlView, bool fireKeyboardEvents); + // void TryShow(); + void TryHide(); + private: + bool IsOcclusionsEmpty(winrt::IVectorView occlusions); void SendEvent(std::string &&eventName, folly::dynamic &¶meters); std::weak_ptr m_wkReactInstance; winrt::CoreInputView::OcclusionsChanged_revoker m_occlusionsChanged_revoker; + winrt::Rect m_finalRect; + winrt::CoreInputView m_coreInputView{nullptr}; + bool m_isShowing{false}; + bool m_fireKeyboradEvents; }; } // namespace uwp diff --git a/vnext/ReactUWP/Views/ScrollViewManager.cpp b/vnext/ReactUWP/Views/ScrollViewManager.cpp index 80cbd684bb0..2b876545a83 100644 --- a/vnext/ReactUWP/Views/ScrollViewManager.cpp +++ b/vnext/ReactUWP/Views/ScrollViewManager.cpp @@ -3,6 +3,7 @@ #include "pch.h" +#include #include #include "Impl/ScrollViewUWPImplementation.h" #include "ScrollViewManager.h" @@ -20,6 +21,7 @@ class ScrollViewShadowNode : public ShadowNodeBase { public: ScrollViewShadowNode(); + ~ScrollViewShadowNode(); void dispatchCommand(int64_t commandId, const folly::dynamic &commandArgs) override; void createView() override; void updateProperties(const folly::dynamic &&props) override; @@ -44,6 +46,10 @@ class ScrollViewShadowNode : public ShadowNodeBase { bool m_isHorizontal = false; bool m_isScrollingEnabled = true; bool m_changeViewAfterLoaded = false; + bool m_dismissKeyboardOnDrag = false; + + std::shared_ptr m_SIPEventHandler; + void RegisterSIPEventsWhenNeeded(); winrt::FrameworkElement::SizeChanged_revoker m_scrollViewerSizeChangedRevoker{}; winrt::FrameworkElement::SizeChanged_revoker m_contentSizeChangedRevoker{}; @@ -56,6 +62,10 @@ class ScrollViewShadowNode : public ShadowNodeBase { ScrollViewShadowNode::ScrollViewShadowNode() {} +ScrollViewShadowNode::~ScrollViewShadowNode() { + m_SIPEventHandler.reset(); +} + void ScrollViewShadowNode::dispatchCommand(int64_t commandId, const folly::dynamic &commandArgs) { const auto scrollViewer = GetView().as(); if (scrollViewer == nullptr) @@ -186,6 +196,12 @@ void ScrollViewShadowNode::updateProperties(const folly::dynamic &&reactDiffMap) if (valid) { ScrollViewUWPImplementation(scrollViewer).SnapToEnd(snapToEnd); } + } else if (propertyName == "keyboardDismissMode") { + m_dismissKeyboardOnDrag = false; + if (propertyValue.isString()) { + m_dismissKeyboardOnDrag = (propertyValue.getString() == "on-drag"); + RegisterSIPEventsWhenNeeded(); + } } else if (propertyName == "snapToAlignment") { const auto [valid, snapToAlignment] = getPropertyAndValidity(propertyValue, winrt::SnapPointsAlignment::Near); if (valid) { @@ -236,6 +252,11 @@ void ScrollViewShadowNode::AddHandlers(const winrt::ScrollViewer &scrollViewer) m_scrollViewerDirectManipulationStartedRevoker = scrollViewer.DirectManipulationStarted(winrt::auto_revoke, [this](const auto &sender, const auto &) { m_isScrolling = true; + + if (m_dismissKeyboardOnDrag && m_SIPEventHandler) { + m_SIPEventHandler->TryHide(); + } + const auto scrollViewer = sender.as(); EmitScrollEvent( scrollViewer, @@ -271,6 +292,8 @@ void ScrollViewShadowNode::AddHandlers(const winrt::ScrollViewer &scrollViewer) m_isScrollingFromInertia = false; }); m_controlLoadedRevoker = scrollViewer.Loaded(winrt::auto_revoke, [this](const auto &sender, const auto &) { + RegisterSIPEventsWhenNeeded(); + if (m_changeViewAfterLoaded) { const auto scrollViewer = sender.as(); scrollViewer.ChangeView(nullptr, nullptr, static_cast(m_zoomFactor)); @@ -279,6 +302,17 @@ void ScrollViewShadowNode::AddHandlers(const winrt::ScrollViewer &scrollViewer) }); } +void ScrollViewShadowNode::RegisterSIPEventsWhenNeeded() { + if (m_dismissKeyboardOnDrag) { + auto view = GetView(); + if (winrt::VisualTreeHelper::GetParent(view)) { + auto wkinstance = GetViewManager()->GetReactInstance(); + m_SIPEventHandler = std::make_unique(wkinstance); + m_SIPEventHandler->AttachView(GetView(), false /*fireKeyboardEvents*/); + } + } +} // namespace uwp + void ScrollViewShadowNode::EmitScrollEvent( const winrt::ScrollViewer &scrollViewer, int64_t tag, @@ -396,7 +430,8 @@ folly::dynamic ScrollViewManager::GetNativeProps() const { props.update(folly::dynamic::object("horizontal", "boolean")("scrollEnabled", "boolean")( "showsHorizontalScrollIndicator", "boolean")("showsVerticalScrollIndicator", "boolean")( "minimumZoomScale", "float")("maximumZoomScale", "float")("zoomScale", "float")("snapToInterval", "float")( - "snapToOffsets", "array")("snapToAlignment", "number")("snapToStart", "boolean")("snapToEnd", "boolean")); + "snapToOffsets", "array")("snapToAlignment", "number")("snapToStart", "boolean")("snapToEnd", "boolean")( + "keyboardDismissMode", "string")); return props; }