Skip to content

Commit

Permalink
Rework modal to be implemented entirely using public APIs (#14256)
Browse files Browse the repository at this point in the history
* Rework modal implementation to use public APIs

* shutdown fix

* Change files

* format

* remove dead code

* format

* fixes

* fix

* UIA tree for root component should be kept distinct from main UIA tree

* input offset fix for sub rootviews

* Split modal into two componentview's so that we dont have multiple roots in our componentview tree

* format

* Ensure rootview removes itself from the island, before a new one adds itself on instance reload

* defork some modal test pages

* remove override
  • Loading branch information
acoates-ms authored Jan 10, 2025
1 parent a610c68 commit fb5c92b
Show file tree
Hide file tree
Showing 50 changed files with 3,692 additions and 804 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "prerelease",
"comment": "Rework modal implementation to use public APIs",
"packageName": "react-native-windows",
"email": "[email protected]",
"dependentChangeType": "patch"
}
6 changes: 0 additions & 6 deletions packages/@react-native-windows/tester/overrides.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,6 @@
"baseHash": "8583dee020400ce4d2754c3e04fd08dfde341c49",
"issue": 12869
},
{
"type": "patch",
"file": "src/js/examples/Modal/ModalOnShow.windows.js",
"baseFile": "packages/rn-tester/js/examples/Modal/ModalOnShow.js",
"baseHash": "911507abcf9172b5fdd1bb68faaf02562df704d4"
},
{
"type": "patch",
"file": "src/js/examples/Modal/ModalPresentation.windows.js",
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -348,13 +348,9 @@ const styles = StyleSheet.create({
marginTop: 6,
},
modalContainer: {
// [Windows
width: 500,
height: 500,
// flex: 1,
// justifyContent: 'center',
// padding: 20,
// Windows ]
flex: 1,
justifyContent: 'center',
padding: 20,
},
modalInnerContainer: {
borderRadius: 10,
Expand Down
14 changes: 13 additions & 1 deletion vnext/Microsoft.ReactNative/CompositionComponentView.idl
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import "ViewProps.idl";
import "Composition.Input.idl";
import "CompositionSwitcher.idl";
import "IReactContext.idl";
import "ReactNativeIsland.idl";

#include "DocString.h"

Expand Down Expand Up @@ -110,8 +111,19 @@ namespace Microsoft.ReactNative.Composition
[default_interface]
runtimeclass RootComponentView : ViewComponentView {
Microsoft.ReactNative.ComponentView GetFocusedComponent();
Microsoft.ReactNative.ReactNativeIsland ReactNativeIsland { get; };
DOC_STRING("Is non-null if this RootComponentView is the content of a portal")
PortalComponentView Portal { get; };
};


[experimental]
[webhosthidden]
[default_interface]
DOC_STRING("Used to implement UI that is hosted outside the main UI tree, such as modal.")
runtimeclass PortalComponentView : Microsoft.ReactNative.ComponentView {
RootComponentView ContentRoot { get; };
};

[experimental]
[webhosthidden]
[default_interface]
Expand Down
1 change: 0 additions & 1 deletion vnext/Microsoft.ReactNative/Fabric/ComponentView.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -639,7 +639,6 @@ facebook::react::Tag ComponentView::hitTest(
}

winrt::IInspectable ComponentView::EnsureUiaProvider() noexcept {
assert(false);
return nullptr;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,6 @@
#include <Fabric/Composition/CompositionViewComponentView.h>
#include <Fabric/Composition/DebuggingOverlayComponentView.h>
#include <Fabric/Composition/ImageComponentView.h>
#include <Fabric/Composition/Modal/WindowsModalHostViewComponentView.h>
#include <Fabric/Composition/Modal/WindowsModalHostViewShadowNode.h>
#include <Fabric/Composition/ParagraphComponentView.h>
#include <Fabric/Composition/RootComponentView.h>
#include <Fabric/Composition/ScrollViewComponentView.h>
Expand Down Expand Up @@ -59,9 +57,6 @@ ComponentViewDescriptor const &ComponentViewRegistry::dequeueComponentViewWithCo
} else if (componentHandle == facebook::react::ImageShadowNode::Handle()) {
view = winrt::Microsoft::ReactNative::Composition::implementation::ImageComponentView::Create(
compContext, tag, m_context);
} else if (componentHandle == facebook::react::WindowsModalHostViewShadowNode::Handle()) {
view = winrt::Microsoft::ReactNative::Composition::implementation::WindowsModalHostComponentView::Create(
compContext, tag, m_context);
} else if (componentHandle == facebook::react::WindowsTextInputShadowNode::Handle()) {
view = winrt::Microsoft::ReactNative::Composition::implementation::WindowsTextInputComponentView::Create(
compContext, tag, m_context);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -192,8 +192,6 @@ HRESULT __stdcall CompositionDynamicAutomationProvider::GetPatternProvider(PATTE
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(strongView)->props());
if (props == nullptr)
return UIA_E_ELEMENTNOTAVAILABLE;
auto accessibilityRole =
props->accessibilityRole.empty() ? compositionView->DefaultControlType() : props->accessibilityRole;
// Invoke control pattern is used to support controls that do not maintain state
// when activated but rather initiate or perform a single, unambiguous action.
if (patternId == UIA_InvokePatternId && (props->onAccessibilityTap)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,9 +142,8 @@ struct CompositionInputKeyboardSource : winrt::implements<

CompositionEventHandler::CompositionEventHandler(
const winrt::Microsoft::ReactNative::ReactContext &context,
const winrt::Microsoft::ReactNative::ReactNativeIsland &reactNativeIsland,
const int fragmentTag)
: m_fragmentTag(fragmentTag), m_context(context), m_wkRootView(reactNativeIsland) {}
const winrt::Microsoft::ReactNative::ReactNativeIsland &reactNativeIsland)
: m_context(context), m_wkRootView(reactNativeIsland) {}

void CompositionEventHandler::Initialize() noexcept {
#ifdef USE_WINUI3
Expand Down Expand Up @@ -348,11 +347,8 @@ facebook::react::SurfaceId CompositionEventHandler::SurfaceId() const noexcept {

winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView &
CompositionEventHandler::RootComponentView() const noexcept {
auto rootComponentViewDescriptor = (::Microsoft::ReactNative::FabricUIManager::FromProperties(m_context.Properties()))
->GetViewRegistry()
.componentViewDescriptorWithTag(SurfaceId());
return *rootComponentViewDescriptor.view
.as<winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView>();
auto island = m_wkRootView.get();
return *winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(island)->GetComponentView();
}

void CompositionEventHandler::onPointerWheelChanged(
Expand All @@ -365,6 +361,9 @@ void CompositionEventHandler::onPointerWheelChanged(
facebook::react::Point ptLocal;
facebook::react::Point ptScaled = {static_cast<float>(position.X), static_cast<float>(position.Y)};

// In the case of a sub rootview, we may have a non-zero origin. hitTest takes a pt in the parent coords, so we
// need to apply the current origin
ptScaled += RootComponentView().layoutMetrics().frame.origin;
auto tag = RootComponentView().hitTest(ptScaled, ptLocal);

if (tag == -1)
Expand Down Expand Up @@ -977,6 +976,11 @@ void CompositionEventHandler::getTargetPointerArgs(
tag = -1;

ptScaled = {position.X, position.Y};

// In the case of a sub rootview, we may have a non-zero origin. hitTest takes a pt in the parent coords, so we need
// to apply the current origin
ptScaled += RootComponentView().layoutMetrics().frame.origin;

if (std::find(m_capturedPointers.begin(), m_capturedPointers.end(), pointerId) != m_capturedPointers.end()) {
assert(m_pointerCapturingComponentTag != -1);
tag = m_pointerCapturingComponentTag;
Expand All @@ -989,30 +993,7 @@ void CompositionEventHandler::getTargetPointerArgs(
ptLocal.y = ptScaled.y - (clientRect.top / strongRootView.ScaleFactor());
}
} else {
if (m_fragmentTag == -1) {
tag = RootComponentView().hitTest(ptScaled, ptLocal);
return;
}

// check if the fragment tag exists
if (!fabricuiManager->GetViewRegistry().findComponentViewWithTag(m_fragmentTag)) {
return;
}

auto fagmentView = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(m_fragmentTag).view;
auto fagmentchildren = fagmentView.Children();

// call the hitTest with the fargment as the RootComponent
for (auto index = fagmentchildren.Size(); index > 0; index--) {
auto childView = fagmentchildren.GetAt(index - 1);
auto targetTag =
winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(childView)->hitTest(
ptScaled, ptLocal);
if (targetTag != -1) {
tag = targetTag;
break;
}
}
tag = RootComponentView().hitTest(ptScaled, ptLocal);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
public:
CompositionEventHandler(
const winrt::Microsoft::ReactNative::ReactContext &context,
const winrt::Microsoft::ReactNative::ReactNativeIsland &ReactNativeIsland,
const int fragmentTag);
const winrt::Microsoft::ReactNative::ReactNativeIsland &ReactNativeIsland);
virtual ~CompositionEventHandler();

void Initialize() noexcept;
Expand Down Expand Up @@ -152,7 +151,6 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE

std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
PointerId m_touchId = 0;
int m_fragmentTag = -1;

std::map<PointerId, std::vector<ReactTaggedView>> m_currentlyHoveredViewsPerPointer;
winrt::weak_ref<winrt::Microsoft::ReactNative::ReactNativeIsland> m_wkRootView;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -167,7 +167,7 @@ HRESULT __stdcall CompositionRootAutomationProvider::get_ProviderOptions(Provide
return S_OK;
}

winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView>
CompositionRootAutomationProvider::rootComponentView() noexcept {
if (auto rootView = m_wkRootView.get()) {
auto innerRootView = winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,8 @@ class CompositionRootAutomationProvider : public winrt::implements<
};

private:
winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *rootComponentView() noexcept;
winrt::com_ptr<winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView>
rootComponentView() noexcept;

HRESULT AdvisePropertiesAdded(SAFEARRAY *psaProperties) noexcept;
HRESULT AdvisePropertiesRemoved(SAFEARRAY *psaProperties) noexcept;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -468,7 +468,7 @@ bool ComponentView::CapturePointer(const winrt::Microsoft::ReactNative::Composit
if (!root)
return false;

auto rootView{uiManager->GetReactNativeIsland(root->Tag())};
auto rootView{root->ReactNativeIsland()};
if (!rootView) {
return false;
}
Expand All @@ -487,7 +487,7 @@ void ComponentView::ReleasePointerCapture(
if (!root)
return;

auto rootView{uiManager->GetReactNativeIsland(root->Tag())};
auto rootView{root->ReactNativeIsland()};
if (!rootView) {
return;
}
Expand Down Expand Up @@ -1330,7 +1330,7 @@ winrt::Microsoft::ReactNative::ComponentView lastDeepChild(
}

// Walks the tree calling the function fn on each node.
// If fn returns true, then walkTree stops itterating over the tree, and returns true.
// If fn returns true, then walkTree stops iterating over the tree, and returns true.
// If the tree walk completes without fn returning true, then walkTree returns false.
bool walkTree(
const winrt::Microsoft::ReactNative::ComponentView &view,
Expand Down
Loading

0 comments on commit fb5c92b

Please sign in to comment.