diff --git a/change/react-native-windows-2019-11-22-14-27-58-treeDump.json b/change/react-native-windows-2019-11-22-14-27-58-treeDump.json
new file mode 100644
index 0000000000..2c63e59f74
--- /dev/null
+++ b/change/react-native-windows-2019-11-22-14-27-58-treeDump.json
@@ -0,0 +1,8 @@
+{
+ "type": "prerelease",
+ "comment": "Properly support ScrollView KeyboardDismissMode",
+ "packageName": "react-native-windows",
+ "email": "dida@ntdev.microsoft.com",
+ "commit": "6b4df0e13a371763a12ecee273011ad762bd9322",
+ "date": "2019-11-22T22:27:58.216Z"
+}
\ No newline at end of file
diff --git a/packages/playground/Samples/scrollViewSnapSample.tsx b/packages/playground/Samples/scrollViewSnapSample.tsx
index e8b1012b1a..8000cd51bc 100644
--- a/packages/playground/Samples/scrollViewSnapSample.tsx
+++ b/packages/playground/Samples/scrollViewSnapSample.tsx
@@ -30,6 +30,7 @@ export default class Bootstrap extends React.Component<{}, any> {
zoomValue: false,
alignToStartValue: true,
refreshing: false,
+ keyboardDismiss: false,
snapToOffsets: true,
pagingEnabled: false,
};
@@ -62,6 +63,10 @@ export default class Bootstrap extends React.Component<{}, any> {
this.setState({pagingEnabled: value});
};
+ toggleSwitch8 = (value: boolean) => {
+ this.setState({keyboardDismiss: value});
+ };
+
onRefresh = () => {
this.setState({refreshing: true});
wait(2000).then(() => this.setState({refreshing: false}));
@@ -190,16 +195,29 @@ export default class Bootstrap extends React.Component<{}, any> {
justifyContent: 'center',
padding: 20,
}}>
-
- {this.state.pagingEnabled
- ? 'pagingEnabled on'
- : 'pagingEnabled off'}
-
+ {'PagingEnabled'}
+
+
+ {'KeyboardDismiss: '.concat(
+ this.state.keyboardDismiss ? 'on-drag' : 'none',
+ )}
+
+
+
{
onRefresh={this.onRefresh}
/>
}
+ keyboardDismissMode={
+ this.state.keyboardDismiss ? 'on-drag' : 'none'
+ }
snapToOffsets={
this.state.snapToOffsets ? [100.0, 500.0] : undefined
}
diff --git a/vnext/ReactUWP/Utils/Helpers.cpp b/vnext/ReactUWP/Utils/Helpers.cpp
index c6919e0b69..cbcb19c448 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 50c46a234b..ad12e1a9ee 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 c385a01a55..0e9f8bcc5f 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 f950020c90..80754f3d4f 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 f3c7ec7c4b..48a81af30e 100644
--- a/vnext/ReactUWP/Views/SIPEventHandler.cpp
+++ b/vnext/ReactUWP/Views/SIPEventHandler.cpp
@@ -7,6 +7,7 @@
#include
+#include
#include
#include
@@ -15,43 +16,96 @@ using namespace Windows::Foundation;
using namespace Windows::Foundation::Collections;
using namespace Windows::UI::ViewManagement::Core;
using namespace Windows::UI::Xaml;
+using namespace Windows::UI::Xaml::Media;
} // namespace winrt
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) {
- 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());
- }
- }
-
- 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));
- }
- }
- });
- }
-}
+ : m_wkReactInstance(reactInstance), m_fireKeyboradEvents(false), m_finalRect(winrt::RectHelper::Empty()){};
SIPEventHandler::~SIPEventHandler() {
m_occlusionsChanged_revoker = {};
+ m_loadedRevoker = {};
+}
+// keyboardDidHide and keyboardDidShow events works on >= RS3
+// TryShow and TryHide works on >= RS5
+
+void SIPEventHandler::AttachView(XamlView xamlView, bool fireKeyboardEvents) {
+ m_fireKeyboradEvents = fireKeyboardEvents;
+ // hookup CoreInputView only after element is in the tree
+ m_view = winrt::make_weak(xamlView);
+ if (winrt::VisualTreeHelper::GetParent(xamlView)) {
+ InitializeCoreInputView();
+ } else {
+ m_loadedRevoker = xamlView.as().Loaded(
+ winrt::auto_revoke, [this](const auto &sender, const auto &) { InitializeCoreInputView(); });
+ }
+}
+
+void SIPEventHandler::InitializeCoreInputView() {
+ if (const auto xamlView = m_view.get()) {
+ 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()) {
+ 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));
+ }
+ }
+ }
+ });
+ }
+ }
+}
+/*
+void SIPEventHandler::TryShow() {
+if (IsRS5OrHigher() && m_coreInputView && !m_isShowing) { // CoreInputView.TryShow is only avaliable after RS5
+ m_coreInputView.TryShow();
+}
+}
+*/
+
+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 142bd258f5..67d9652e29 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,27 @@ 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};
+ winrt::weak_ref m_view{};
+ winrt::Windows::UI::Xaml::FrameworkElement::Loaded_revoker m_loadedRevoker{};
+ bool m_isShowing{false};
+ bool m_fireKeyboradEvents;
+
+ void InitializeCoreInputView();
};
} // namespace uwp
diff --git a/vnext/ReactUWP/Views/ScrollViewManager.cpp b/vnext/ReactUWP/Views/ScrollViewManager.cpp
index 25b245aac9..1a7aa8375c 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,9 @@ 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;
winrt::FrameworkElement::SizeChanged_revoker m_scrollViewerSizeChangedRevoker{};
winrt::FrameworkElement::SizeChanged_revoker m_contentSizeChangedRevoker{};
@@ -56,6 +61,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 +195,16 @@ 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");
+ if (m_dismissKeyboardOnDrag) {
+ auto wkinstance = GetViewManager()->GetReactInstance();
+ m_SIPEventHandler = std::make_unique(wkinstance);
+ m_SIPEventHandler->AttachView(GetView(), false /*fireKeyboardEvents*/);
+ }
+ }
} else if (propertyName == "snapToAlignment") {
const auto [valid, snapToAlignment] = getPropertyAndValidity(propertyValue, winrt::SnapPointsAlignment::Near);
if (valid) {
@@ -241,6 +260,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,
@@ -402,7 +426,7 @@ folly::dynamic ScrollViewManager::GetNativeProps() const {
"showsHorizontalScrollIndicator", "boolean")("showsVerticalScrollIndicator", "boolean")(
"minimumZoomScale", "float")("maximumZoomScale", "float")("zoomScale", "float")("snapToInterval", "float")(
"snapToOffsets", "array")("snapToAlignment", "number")("snapToStart", "boolean")("snapToEnd", "boolean")(
- "pagingEnabled", "boolean"));
+ "pagingEnabled", "boolean")("keyboardDismissMode", "string"));
return props;
}