11
11
#include < UI.Xaml.Controls.h>
12
12
#include < Utils/ValueUtils.h>
13
13
#include < winrt/Microsoft.UI.Content.h>
14
+ #include < winrt/Microsoft.UI.Input.h>
14
15
#include < winrt/Windows.UI.Composition.h>
15
16
#include " CompositionContextHelper.h"
16
17
#include " RootComponentView.h"
17
18
18
19
#include " Composition.ContentIslandComponentView.g.cpp"
19
20
21
+ #include " CompositionDynamicAutomationProvider.h"
22
+
20
23
namespace winrt ::Microsoft::ReactNative::Composition::implementation {
21
24
22
25
ContentIslandComponentView::ContentIslandComponentView (
@@ -47,6 +50,22 @@ void ContentIslandComponentView::OnMounted() noexcept {
47
50
winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual (Visual ())
48
51
.as <winrt::Microsoft::UI::Composition::ContainerVisual>());
49
52
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
+
50
69
if (m_islandToConnect) {
51
70
m_childSiteLink.Connect (m_islandToConnect);
52
71
m_islandToConnect = nullptr ;
@@ -70,6 +89,12 @@ void ContentIslandComponentView::OnMounted() noexcept {
70
89
71
90
void ContentIslandComponentView::OnUnmounted () noexcept {
72
91
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
73
98
}
74
99
75
100
void ContentIslandComponentView::ParentLayoutChanged () noexcept {
@@ -92,7 +117,79 @@ void ContentIslandComponentView::ParentLayoutChanged() noexcept {
92
117
#endif
93
118
}
94
119
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
+
95
168
ContentIslandComponentView::~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
96
193
if (m_islandToConnect) {
97
194
m_islandToConnect.Close ();
98
195
}
@@ -132,11 +229,65 @@ void ContentIslandComponentView::Connect(const winrt::Microsoft::UI::Content::Co
132
229
} else {
133
230
m_islandToConnect = contentIsland;
134
231
}
135
- #endif
232
+ #endif // USE_EXPERIMENTAL_WINUI3
136
233
}
137
234
138
235
void ContentIslandComponentView::prepareForRecycle () noexcept {
139
236
Super::prepareForRecycle ();
140
237
}
141
238
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
+
142
293
} // namespace winrt::Microsoft::ReactNative::Composition::implementation
0 commit comments