1111#include < UI.Xaml.Controls.h>
1212#include < Utils/ValueUtils.h>
1313#include < winrt/Microsoft.UI.Content.h>
14+ #include < winrt/Microsoft.UI.Input.h>
1415#include < winrt/Windows.UI.Composition.h>
1516#include " CompositionContextHelper.h"
1617#include " RootComponentView.h"
1718
1819#include " Composition.ContentIslandComponentView.g.cpp"
1920
21+ #include " CompositionDynamicAutomationProvider.h"
22+
2023namespace winrt ::Microsoft::ReactNative::Composition::implementation {
2124
2225ContentIslandComponentView::ContentIslandComponentView (
@@ -47,6 +50,22 @@ void ContentIslandComponentView::OnMounted() noexcept {
4750 winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual (Visual ())
4851 .as <winrt::Microsoft::UI::Composition::ContainerVisual>());
4952 m_childSiteLink.ActualSize ({m_layoutMetrics.frame .size .width , m_layoutMetrics.frame .size .height });
53+
54+ m_navigationHost = winrt::Microsoft::UI::Input::InputFocusNavigationHost::GetForSiteLink (m_childSiteLink);
55+
56+ m_navigationHostDepartFocusRequestedToken =
57+ m_navigationHost.DepartFocusRequested ([wkThis = get_weak ()](const auto &, const auto &args) {
58+ if (auto strongThis = wkThis.get ()) {
59+ const bool next = (args.Request ().Reason () != winrt::Microsoft::UI::Input::FocusNavigationReason::Last);
60+ strongThis->rootComponentView ()->TryMoveFocus (next);
61+ args.Result (winrt::Microsoft::UI::Input::FocusNavigationResult::Moved);
62+ }
63+ });
64+
65+ // We configure automation even if there's no UIA client at this point, because it's possible the first UIA
66+ // request we'll get will be for a child of this island calling upward in the UIA tree.
67+ ConfigureChildSiteLinkAutomation ();
68+
5069 if (m_islandToConnect) {
5170 m_childSiteLink.Connect (m_islandToConnect);
5271 m_islandToConnect = nullptr ;
@@ -70,6 +89,12 @@ void ContentIslandComponentView::OnMounted() noexcept {
7089
7190void ContentIslandComponentView::OnUnmounted () noexcept {
7291 m_layoutMetricChangedRevokers.clear ();
92+ #ifdef USE_EXPERIMENTAL_WINUI3
93+ if (m_navigationHostDepartFocusRequestedToken && m_navigationHost) {
94+ m_navigationHost.DepartFocusRequested (m_navigationHostDepartFocusRequestedToken);
95+ m_navigationHostDepartFocusRequestedToken = {};
96+ }
97+ #endif
7398}
7499
75100void ContentIslandComponentView::ParentLayoutChanged () noexcept {
@@ -92,7 +117,79 @@ void ContentIslandComponentView::ParentLayoutChanged() noexcept {
92117#endif
93118}
94119
120+ winrt::IInspectable ContentIslandComponentView::EnsureUiaProvider () noexcept {
121+ #ifdef USE_EXPERIMENTAL_WINUI3
122+ if (m_uiaProvider == nullptr ) {
123+ m_uiaProvider = winrt::make<winrt::Microsoft::ReactNative::implementation::CompositionDynamicAutomationProvider>(
124+ *get_strong (), m_childSiteLink);
125+ }
126+ return m_uiaProvider;
127+ #else
128+ return Super::EnsureUiaProvider ();
129+ #endif
130+ }
131+
132+ bool ContentIslandComponentView::focusable () const noexcept {
133+ #ifdef USE_EXPERIMENTAL_WINUI3
134+ // We don't have a way to check to see if the ContentIsland has focusable content,
135+ // so we'll always return true. We'll have to handle the case where the content doesn't have
136+ // focusable content in the OnGotFocus handler.
137+ return true ;
138+ #else
139+ return Super::focusable ();
140+ #endif
141+ }
142+
143+ // Helper to convert a FocusNavigationDirection to a FocusNavigationReason.
144+ winrt::Microsoft::UI::Input::FocusNavigationReason GetFocusNavigationReason (
145+ winrt::Microsoft::ReactNative::FocusNavigationDirection direction) noexcept {
146+ switch (direction) {
147+ case winrt::Microsoft::ReactNative::FocusNavigationDirection::First:
148+ case winrt::Microsoft::ReactNative::FocusNavigationDirection::Next:
149+ return winrt::Microsoft::UI::Input::FocusNavigationReason::First;
150+ case winrt::Microsoft::ReactNative::FocusNavigationDirection::Last:
151+ case winrt::Microsoft::ReactNative::FocusNavigationDirection::Previous:
152+ return winrt::Microsoft::UI::Input::FocusNavigationReason::Last;
153+ }
154+ return winrt::Microsoft::UI::Input::FocusNavigationReason::Restore;
155+ }
156+
157+ void ContentIslandComponentView::onGotFocus (
158+ const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
159+ #ifdef USE_EXPERIMENTAL_WINUI3
160+ auto gotFocusEventArgs = args.as <winrt::Microsoft::ReactNative::implementation::GotFocusEventArgs>();
161+ const auto navigationReason = GetFocusNavigationReason (gotFocusEventArgs->Direction ());
162+ m_navigationHost.NavigateFocus (winrt::Microsoft::UI::Input::FocusNavigationRequest::Create (navigationReason));
163+ #else
164+ return Super::onGotFocus (args);
165+ #endif // USE_EXPERIMENTAL_WINUI3
166+ }
167+
95168ContentIslandComponentView::~ContentIslandComponentView () noexcept {
169+ #ifdef USE_EXPERIMENTAL_WINUI3
170+ if (m_navigationHostDepartFocusRequestedToken && m_navigationHost) {
171+ m_navigationHost.DepartFocusRequested (m_navigationHostDepartFocusRequestedToken);
172+ m_navigationHostDepartFocusRequestedToken = {};
173+ }
174+ if (m_childSiteLink) {
175+ if (m_fragmentRootAutomationProviderRequestedToken) {
176+ m_childSiteLink.FragmentRootAutomationProviderRequested (m_fragmentRootAutomationProviderRequestedToken);
177+ m_fragmentRootAutomationProviderRequestedToken = {};
178+ }
179+ if (m_parentAutomationProviderRequestedToken) {
180+ m_childSiteLink.ParentAutomationProviderRequested (m_parentAutomationProviderRequestedToken);
181+ m_parentAutomationProviderRequestedToken = {};
182+ }
183+ if (m_nextSiblingAutomationProviderRequestedToken) {
184+ m_childSiteLink.NextSiblingAutomationProviderRequested (m_nextSiblingAutomationProviderRequestedToken);
185+ m_nextSiblingAutomationProviderRequestedToken = {};
186+ }
187+ if (m_previousSiblingAutomationProviderRequestedToken) {
188+ m_childSiteLink.PreviousSiblingAutomationProviderRequested (m_previousSiblingAutomationProviderRequestedToken);
189+ m_previousSiblingAutomationProviderRequestedToken = {};
190+ }
191+ }
192+ #endif // USE_EXPERIMENTAL_WINUI3
96193 if (m_islandToConnect) {
97194 m_islandToConnect.Close ();
98195 }
@@ -132,11 +229,65 @@ void ContentIslandComponentView::Connect(const winrt::Microsoft::UI::Content::Co
132229 } else {
133230 m_islandToConnect = contentIsland;
134231 }
135- #endif
232+ #endif // USE_EXPERIMENTAL_WINUI3
136233}
137234
138235void ContentIslandComponentView::prepareForRecycle () noexcept {
139236 Super::prepareForRecycle ();
140237}
141238
239+ #ifdef USE_EXPERIMENTAL_WINUI3
240+ void ContentIslandComponentView::ConfigureChildSiteLinkAutomation () noexcept {
241+ // This automation mode must be set before connecting the child ContentIsland.
242+ // It puts the child content into a mode where it won't own its own framework root. Instead, the child island's
243+ // automation peers will use the same framework root as the automation peer of this ContentIslandComponentView.
244+ m_childSiteLink.AutomationTreeOption (winrt::Microsoft::UI::Content::AutomationTreeOptions::FragmentBased);
245+
246+ // These events are raised in response to the child ContentIsland asking for providers.
247+ // For example, the ContentIsland.FragmentRootAutomationProvider property will return
248+ // the provider we provide here in FragmentRootAutomationProviderRequested.
249+
250+ // We capture "this" as a raw pointer because ContentIslandComponentView doesn't currently support weak ptrs.
251+ // It's safe because we disconnect these events in the destructor.
252+
253+ m_fragmentRootAutomationProviderRequestedToken = m_childSiteLink.FragmentRootAutomationProviderRequested (
254+ [this ](
255+ const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
256+ const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
257+ // The child island's fragment tree doesn't have its own fragment root.
258+ // Here's how we can provide the correct fragment root to the child's UIA logic.
259+ winrt::com_ptr<IRawElementProviderFragmentRoot> fragmentRoot{nullptr };
260+ auto uiaProvider = this ->EnsureUiaProvider ();
261+ uiaProvider.as <IRawElementProviderFragment>()->get_FragmentRoot (fragmentRoot.put ());
262+ args.AutomationProvider (fragmentRoot.as <IInspectable>());
263+ args.Handled (true );
264+ });
265+
266+ m_parentAutomationProviderRequestedToken = m_childSiteLink.ParentAutomationProviderRequested (
267+ [this ](
268+ const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
269+ const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
270+ auto uiaProvider = this ->EnsureUiaProvider ();
271+ args.AutomationProvider (uiaProvider);
272+ args.Handled (true );
273+ });
274+
275+ m_nextSiblingAutomationProviderRequestedToken = m_childSiteLink.NextSiblingAutomationProviderRequested (
276+ [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
277+ const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
278+ // The ContentIsland will always be the one and only child of this node, so it won't have siblings.
279+ args.AutomationProvider (nullptr );
280+ args.Handled (true );
281+ });
282+
283+ m_previousSiblingAutomationProviderRequestedToken = m_childSiteLink.PreviousSiblingAutomationProviderRequested (
284+ [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
285+ const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
286+ // The ContentIsland will always be the one and only child of this node, so it won't have siblings.
287+ args.AutomationProvider (nullptr );
288+ args.Handled (true );
289+ });
290+ }
291+ #endif // USE_EXPERIMENTAL_WINUI3
292+
142293} // namespace winrt::Microsoft::ReactNative::Composition::implementation
0 commit comments