react-native-windows 0.74.24 → 0.74.26

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (25) hide show
  1. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +8 -0
  2. package/Microsoft.ReactNative/Fabric/ComponentView.h +5 -1
  3. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +5 -0
  4. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +4 -0
  5. package/Microsoft.ReactNative/Fabric/Composition/CompositionRootAutomationProvider.cpp +8 -32
  6. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +190 -84
  7. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +15 -10
  8. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +2 -1
  9. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +23 -0
  10. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +3 -0
  11. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.cpp +20 -0
  12. package/Microsoft.ReactNative/Fabric/Composition/RootComponentView.h +3 -0
  13. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +10 -0
  14. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +3 -0
  15. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +16 -4
  16. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +1 -0
  17. package/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +1 -1
  18. package/Microsoft.ReactNative/packages.lock.json +55 -83
  19. package/Microsoft.ReactNative.Cxx/ComponentView.Experimental.interop.h +14 -0
  20. package/Microsoft.ReactNative.Managed/packages.lock.json +107 -107
  21. package/PropertySheets/CppAppConsumeCSharpModule.props +2 -0
  22. package/PropertySheets/External/Microsoft.ReactNative.CSharp.Dependencies.props +1 -1
  23. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  24. package/ReactCommon/packages.lock.json +2 -2
  25. package/package.json +1 -1
@@ -363,6 +363,14 @@ RECT ComponentView::getClientRect() const noexcept {
363
363
  return {};
364
364
  }
365
365
 
366
+ winrt::Windows::Foundation::Point ComponentView::ScreenToLocal(winrt::Windows::Foundation::Point pt) noexcept {
367
+ return rootComponentView()->ConvertScreenToLocal(pt);
368
+ }
369
+
370
+ winrt::Windows::Foundation::Point ComponentView::LocalToScreen(winrt::Windows::Foundation::Point pt) noexcept {
371
+ return rootComponentView()->ConvertLocalToScreen(pt);
372
+ }
373
+
366
374
  // The offset from this elements parent to its children (accounts for things like scroll position)
367
375
  facebook::react::Point ComponentView::getClientOffset() const noexcept {
368
376
  assert(false);
@@ -11,6 +11,7 @@
11
11
  #include <react/renderer/components/view/ViewProps.h>
12
12
  #include <react/renderer/core/LayoutMetrics.h>
13
13
 
14
+ #include <ComponentView.Experimental.interop.h>
14
15
  #include <Fabric/Composition/Theme.h>
15
16
  #include <uiautomationcore.h>
16
17
  #include <winrt/Microsoft.ReactNative.Composition.Input.h>
@@ -77,7 +78,8 @@ struct UnmountChildComponentViewArgs : public UnmountChildComponentViewArgsT<Unm
77
78
  uint32_t m_index;
78
79
  };
79
80
 
80
- struct ComponentView : public ComponentViewT<ComponentView> {
81
+ struct ComponentView
82
+ : public ComponentViewT<ComponentView, ::Microsoft::ReactNative::Composition::Experimental::IComponentViewInterop> {
81
83
  ComponentView(facebook::react::Tag tag, winrt::Microsoft::ReactNative::ReactContext const &reactContext);
82
84
 
83
85
  virtual std::vector<facebook::react::ComponentDescriptorProvider> supplementalComponentDescriptorProviders() noexcept;
@@ -105,6 +107,8 @@ struct ComponentView : public ComponentViewT<ComponentView> {
105
107
  // returns true if the fn ever returned true
106
108
  bool runOnChildren(bool forward, Mso::Functor<bool(ComponentView &)> &fn) noexcept;
107
109
  virtual RECT getClientRect() const noexcept;
110
+ winrt::Windows::Foundation::Point ScreenToLocal(winrt::Windows::Foundation::Point pt) noexcept;
111
+ winrt::Windows::Foundation::Point LocalToScreen(winrt::Windows::Foundation::Point pt) noexcept;
108
112
  // The offset from this elements parent to its children (accounts for things like scroll position)
109
113
  virtual facebook::react::Point getClientOffset() const noexcept;
110
114
  virtual void onLosingFocus(const winrt::Microsoft::ReactNative::LosingFocusEventArgs &args) noexcept;
@@ -818,6 +818,11 @@ uint8_t BorderPrimitive::numberOfVisuals() const noexcept {
818
818
  return m_numBorderVisuals;
819
819
  }
820
820
 
821
+ void BorderPrimitive::setOuter(
822
+ winrt::Microsoft::ReactNative::Composition::implementation::ComponentView *outer) noexcept {
823
+ m_outer = outer;
824
+ }
825
+
821
826
  bool BorderPrimitive::TryUpdateSpecialBorderLayers(
822
827
  winrt::Microsoft::ReactNative::Composition::implementation::Theme *theme,
823
828
  std::array<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual, SpecialBorderLayerCount>
@@ -29,6 +29,10 @@ struct BorderPrimitive {
29
29
 
30
30
  void markNeedsUpdate() noexcept;
31
31
 
32
+ // We hoist focus visuals up the tree to allow them to be higher in the z-order.
33
+ // This means a single BorderPrimitive may change the owning ComponentView as focus moves around
34
+ void setOuter(winrt::Microsoft::ReactNative::Composition::implementation::ComponentView *outer) noexcept;
35
+
32
36
  void updateProps(
33
37
  const facebook::react::ViewProps &oldViewProps,
34
38
  const facebook::react::ViewProps &newViewProps) noexcept;
@@ -189,40 +189,16 @@ HRESULT __stdcall CompositionRootAutomationProvider::ElementProviderFromPoint(
189
189
  *pRetVal = nullptr;
190
190
 
191
191
  if (auto rootView = rootComponentView()) {
192
- #ifdef USE_WINUI3
193
- if (m_island) {
194
- auto cc = m_island.CoordinateConverter();
195
- auto local = cc.ConvertScreenToLocal(
196
- winrt::Windows::Graphics::PointInt32{static_cast<int32_t>(x), static_cast<int32_t>(y)});
197
- auto provider = rootView->UiaProviderFromPoint(
198
- {static_cast<LONG>(local.X * m_island.RasterizationScale()),
199
- static_cast<LONG>(local.Y * m_island.RasterizationScale())});
200
- auto spFragment = provider.try_as<IRawElementProviderFragment>();
201
- if (spFragment) {
202
- *pRetVal = spFragment.detach();
203
- }
204
-
205
- return S_OK;
192
+ auto local = rootView->ConvertScreenToLocal({static_cast<float>(x), static_cast<float>(y)});
193
+ auto provider = rootView->UiaProviderFromPoint(
194
+ {static_cast<LONG>(local.X * rootView->LayoutMetrics().PointScaleFactor),
195
+ static_cast<LONG>(local.Y * rootView->LayoutMetrics().PointScaleFactor)});
196
+ auto spFragment = provider.try_as<IRawElementProviderFragment>();
197
+ if (spFragment) {
198
+ *pRetVal = spFragment.detach();
206
199
  }
207
- #endif
208
200
 
209
- if (m_hwnd) {
210
- if (!IsWindow(m_hwnd)) {
211
- // TODO: Add support for non-HWND based hosting
212
- assert(false);
213
- return E_FAIL;
214
- }
215
-
216
- POINT clientPoint{static_cast<LONG>(x), static_cast<LONG>(y)};
217
- ScreenToClient(m_hwnd, &clientPoint);
218
-
219
- auto provider = rootView->UiaProviderFromPoint(clientPoint);
220
- auto spFragment = provider.try_as<IRawElementProviderFragment>();
221
- if (spFragment) {
222
- *pRetVal = spFragment.detach();
223
- return S_OK;
224
- }
225
- }
201
+ return S_OK;
226
202
  }
227
203
 
228
204
  AddRef();
@@ -40,7 +40,7 @@ constexpr float FOCUS_VISUAL_WIDTH = 2.0f;
40
40
  // ----- m_visual <-- Background / clip - Can be a custom visual depending on Component type
41
41
  // |
42
42
  // ----- Border Visuals x N (BorderPrimitive attached to m_visual)
43
- // ------Focus Visual Container
43
+ // ------Focus Visual Container (created when hosting focus visuals)
44
44
  // |
45
45
  // |------Inner Focus Visual
46
46
  // |
@@ -57,9 +57,6 @@ ComponentView::ComponentView(
57
57
  : base_type(tag, reactContext), m_compContext(compContext), m_flags(flags) {
58
58
  m_outerVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
59
59
  // CreateContainerVisual in ICompositionContext
60
- m_focusVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
61
- // CreateContainerVisual in ICompositionContext
62
- m_outerVisual.InsertAt(m_focusVisual, 0);
63
60
  }
64
61
 
65
62
  ComponentView::~ComponentView() {
@@ -90,13 +87,17 @@ void ComponentView::onThemeChanged() noexcept {
90
87
  m_borderPrimitive->onThemeChanged(
91
88
  m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps()));
92
89
  }
93
- if (m_focusInnerPrimitive) {
94
- auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
95
- m_focusInnerPrimitive->onThemeChanged(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
96
- }
97
- if (m_focusOuterPrimitive) {
98
- auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
99
- m_focusOuterPrimitive->onThemeChanged(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
90
+ if (m_componentHostingFocusVisual) {
91
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
92
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
93
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->onThemeChanged(
94
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
95
+ }
96
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
97
+ auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
98
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->onThemeChanged(
99
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
100
+ }
100
101
  }
101
102
 
102
103
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
@@ -160,14 +161,18 @@ void ComponentView::updateProps(
160
161
  if (m_borderPrimitive) {
161
162
  m_borderPrimitive->updateProps(oldViewProps, newViewProps);
162
163
  }
163
- if (!newViewProps.enableFocusRing) {
164
- showFocusVisual(false);
165
- }
166
- if (m_focusInnerPrimitive) {
167
- m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
168
- }
169
- if (m_focusOuterPrimitive) {
170
- m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
164
+
165
+ if (m_componentHostingFocusVisual) {
166
+ if (!newViewProps.enableFocusRing) {
167
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
168
+ }
169
+
170
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
171
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
172
+ }
173
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
174
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
175
+ }
171
176
  }
172
177
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
173
178
  updateShadowProps(oldViewProps, newViewProps);
@@ -200,44 +205,70 @@ void ComponentView::updateLayoutMetrics(
200
205
  });
201
206
  }
202
207
 
203
- updateFocusLayoutMetrics(layoutMetrics);
208
+ base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
204
209
 
205
210
  if (layoutMetrics != oldLayoutMetrics) {
206
211
  if (m_borderPrimitive) {
207
212
  m_borderPrimitive->markNeedsUpdate();
208
213
  }
209
- if (m_focusInnerPrimitive) {
210
- m_focusInnerPrimitive->markNeedsUpdate();
211
- }
212
- if (m_focusOuterPrimitive) {
213
- m_focusOuterPrimitive->markNeedsUpdate();
214
+
215
+ if (m_componentHostingFocusVisual) {
216
+ m_componentHostingFocusVisual->updateFocusLayoutMetrics();
214
217
  }
215
218
  }
216
219
 
217
- base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
218
220
  UpdateCenterPropertySet();
219
221
  }
220
222
 
221
- void ComponentView::updateFocusLayoutMetrics(facebook::react::LayoutMetrics const &layoutMetrics) noexcept {
222
- if (m_focusInnerPrimitive) {
223
- auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
224
- m_focusInnerPrimitive->RootVisual().Size(
225
- {innerFocusMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
226
- innerFocusMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
227
- m_focusInnerPrimitive->RootVisual().Offset(
228
- {-FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor,
229
- -FOCUS_VISUAL_WIDTH * layoutMetrics.pointScaleFactor,
230
- 0.0f});
231
- }
232
- if (m_focusOuterPrimitive) {
233
- auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
234
- m_focusOuterPrimitive->RootVisual().Size(
235
- {outerFocusMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
236
- outerFocusMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
237
- m_focusOuterPrimitive->RootVisual().Offset(
238
- {-(FOCUS_VISUAL_WIDTH * 2 * m_layoutMetrics.pointScaleFactor),
239
- -(FOCUS_VISUAL_WIDTH * 2 * m_layoutMetrics.pointScaleFactor),
240
- 0.0f});
223
+ void ComponentView::updateFocusLayoutMetrics() noexcept {
224
+ facebook::react::RectangleEdges<bool> nudgeEdges;
225
+ auto scaleFactor = m_focusPrimitive->m_focusVisualComponent->m_layoutMetrics.pointScaleFactor;
226
+ if (m_focusPrimitive) {
227
+ if (m_focusPrimitive->m_focusOuterPrimitive) {
228
+ auto outerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(false /*inner*/);
229
+
230
+ if (outerFocusMetrics.frame.origin.x < 0) {
231
+ nudgeEdges.left = true;
232
+ }
233
+ if (outerFocusMetrics.frame.origin.y < 0) {
234
+ nudgeEdges.top = true;
235
+ }
236
+ if (outerFocusMetrics.frame.getMaxX() > m_layoutMetrics.frame.getMaxX()) {
237
+ nudgeEdges.right = true;
238
+ }
239
+ if (outerFocusMetrics.frame.getMaxY() > m_layoutMetrics.frame.getMaxY()) {
240
+ nudgeEdges.bottom = true;
241
+ }
242
+
243
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Size(
244
+ {outerFocusMetrics.frame.size.width * scaleFactor -
245
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
246
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0),
247
+ outerFocusMetrics.frame.size.height * scaleFactor -
248
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
249
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0)});
250
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Offset(
251
+ {nudgeEdges.left ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
252
+ nudgeEdges.top ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
253
+ 0.0f});
254
+ m_focusPrimitive->m_focusOuterPrimitive->markNeedsUpdate();
255
+ }
256
+
257
+ if (m_focusPrimitive->m_focusInnerPrimitive) {
258
+ auto innerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(true /*inner*/);
259
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Size(
260
+ {innerFocusMetrics.frame.size.width * scaleFactor -
261
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
262
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0),
263
+ innerFocusMetrics.frame.size.height * scaleFactor -
264
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
265
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0)});
266
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Offset(
267
+ {nudgeEdges.left ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
268
+ nudgeEdges.top ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
269
+ 0.0f});
270
+ m_focusPrimitive->m_focusInnerPrimitive->markNeedsUpdate();
271
+ }
241
272
  }
242
273
  }
243
274
 
@@ -288,13 +319,17 @@ void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentView
288
319
  }
289
320
  }
290
321
 
291
- if (m_focusInnerPrimitive) {
292
- auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
293
- m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
294
- }
295
- if (m_focusOuterPrimitive) {
296
- auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
297
- m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
322
+ if (m_componentHostingFocusVisual) {
323
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
324
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
325
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->finalize(
326
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
327
+ }
328
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
329
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
330
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->finalize(
331
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
332
+ }
298
333
  }
299
334
 
300
335
  if (m_FinalizeTransform) {
@@ -308,7 +343,12 @@ void ComponentView::onLostFocus(
308
343
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
309
344
  if (args.OriginalSource() == Tag()) {
310
345
  m_eventEmitter->onBlur();
311
- showFocusVisual(false);
346
+
347
+ if (m_componentHostingFocusVisual) {
348
+ auto s = get_strong();
349
+
350
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
351
+ }
312
352
  if (m_uiaProvider) {
313
353
  winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
314
354
  m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false);
@@ -317,12 +357,47 @@ void ComponentView::onLostFocus(
317
357
  base_type::onLostFocus(args);
318
358
  }
319
359
 
360
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::visualToHostFocus() noexcept {
361
+ return OuterVisual();
362
+ }
363
+
364
+ // We want to host focus visuals as close to the focused component as possible. However since the focus visuals extend
365
+ // past the bounds of the component, in cases where additional components are positioned directly next to this one, we'd
366
+ // get zorder issues causing most of the focus rect to be obscured. So we go up the tree until we find a component who's
367
+ // bounds will fix the entire focus rect.
368
+ winrt::com_ptr<ComponentView> ComponentView::focusVisualRoot(const facebook::react::Rect &focusRect) noexcept {
369
+ auto compVisual =
370
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(OuterVisual());
371
+ if (!compVisual) {
372
+ return get_strong();
373
+ // When not using lifted composition, force the focus visual to host within its own component, as we do not support
374
+ // ParentForTransform
375
+ }
376
+
377
+ if (facebook::react::Rect::intersect(focusRect, m_layoutMetrics.frame) == focusRect) {
378
+ return get_strong();
379
+ }
380
+
381
+ if (!m_parent) {
382
+ return get_strong();
383
+ }
384
+
385
+ return m_parent.as<ComponentView>()->focusVisualRoot(
386
+ {{focusRect.origin.x + m_layoutMetrics.frame.origin.x, focusRect.origin.y + m_layoutMetrics.frame.origin.y},
387
+ focusRect.size});
388
+ }
389
+
320
390
  void ComponentView::onGotFocus(
321
391
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
322
392
  if (args.OriginalSource() == Tag()) {
323
393
  m_eventEmitter->onFocus();
324
394
  if (viewProps()->enableFocusRing) {
325
- showFocusVisual(true);
395
+ facebook::react::Rect focusRect = m_layoutMetrics.frame;
396
+ focusRect.origin.x -= (FOCUS_VISUAL_WIDTH * 2);
397
+ focusRect.origin.y -= (FOCUS_VISUAL_WIDTH * 2);
398
+ focusRect.size.width += (FOCUS_VISUAL_WIDTH * 2);
399
+ focusRect.size.height += (FOCUS_VISUAL_WIDTH * 2);
400
+ focusVisualRoot(focusRect)->hostFocusVisual(true, get_strong());
326
401
  }
327
402
  if (m_uiaProvider) {
328
403
  auto spProviderSimple = m_uiaProvider.try_as<IRawElementProviderSimple>();
@@ -493,39 +568,70 @@ facebook::react::BorderMetrics ComponentView::focusBorderMetrics(
493
568
  return metrics;
494
569
  }
495
570
 
496
- void ComponentView::showFocusVisual(bool show) noexcept {
497
- if ((m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
498
- if (show && !m_showingFocusVisual) {
499
- m_showingFocusVisual = true;
500
-
501
- m_focusVisual.IsVisible(true);
502
- assert(viewProps()->enableFocusRing);
503
- if (!m_focusInnerPrimitive) {
504
- m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
505
- m_focusVisual.InsertAt(m_focusInnerPrimitive->RootVisual(), 0);
571
+ void ComponentView::hostFocusVisual(bool show, winrt::com_ptr<ComponentView> view) noexcept {
572
+ if ((view->m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
573
+ // Any previous view showing focus visuals should have removed them before another shows it
574
+ assert(
575
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
576
+ m_focusPrimitive->m_focusVisualComponent == view);
577
+ assert(
578
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
579
+ view->m_componentHostingFocusVisual.get() == this);
580
+ if (show && !view->m_componentHostingFocusVisual) {
581
+ view->m_componentHostingFocusVisual = get_strong();
582
+
583
+ if (!m_focusPrimitive) {
584
+ m_focusPrimitive = std::make_unique<FocusPrimitive>();
506
585
  }
507
- if (!m_focusOuterPrimitive) {
508
- m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
509
- m_focusVisual.InsertAt(m_focusOuterPrimitive->RootVisual(), 0);
586
+ m_focusPrimitive->m_focusVisualComponent = view;
587
+
588
+ if (!m_focusPrimitive->m_focusVisual) {
589
+ m_focusPrimitive->m_focusVisual = m_compContext.CreateSpriteVisual();
590
+ auto hostingVisual =
591
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
592
+ visualToHostFocus())
593
+ .as<winrt::Microsoft::UI::Composition::ContainerVisual>();
594
+ if (hostingVisual) {
595
+ hostingVisual.Children().InsertAtTop(
596
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
597
+ m_focusPrimitive->m_focusVisual));
598
+ } else {
599
+ assert(
600
+ view.get() ==
601
+ this); // When not using lifted comp, focus visuals should always host within their own component
602
+ OuterVisual().InsertAt(m_focusPrimitive->m_focusVisual, 1);
603
+ }
510
604
  }
511
- updateFocusLayoutMetrics(m_layoutMetrics);
512
- auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
513
- m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
514
- auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
515
- m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
516
- } else if (!show && m_showingFocusVisual) {
517
- m_showingFocusVisual = false;
518
- m_focusVisual.IsVisible(false);
519
- if (m_focusInnerPrimitive) {
520
- m_focusInnerPrimitive->markNeedsUpdate();
521
- auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
522
- m_focusInnerPrimitive->finalize(innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
605
+
606
+ m_focusPrimitive->m_focusVisual.IsVisible(true);
607
+ assert(view->viewProps()->enableFocusRing);
608
+ if (!m_focusPrimitive->m_focusInnerPrimitive) {
609
+ m_focusPrimitive->m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
610
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusInnerPrimitive->RootVisual(), 0);
611
+ }
612
+ if (!m_focusPrimitive->m_focusOuterPrimitive) {
613
+ m_focusPrimitive->m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
614
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusOuterPrimitive->RootVisual(), 0);
523
615
  }
524
- if (m_focusOuterPrimitive) {
525
- m_focusOuterPrimitive->markNeedsUpdate();
526
- auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
527
- m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
616
+ if (auto focusVisual =
617
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
618
+ m_focusPrimitive->m_focusVisual)) {
619
+ auto outerVisual =
620
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
621
+ view->OuterVisual());
622
+ focusVisual.ParentForTransform(outerVisual);
528
623
  }
624
+ updateFocusLayoutMetrics();
625
+ auto innerFocusMetrics = view->focusLayoutMetrics(true /*inner*/);
626
+ m_focusPrimitive->m_focusInnerPrimitive->finalize(
627
+ innerFocusMetrics, view->focusBorderMetrics(true /*inner*/, innerFocusMetrics));
628
+ auto outerFocusMetrics = view->focusLayoutMetrics(false /*inner*/);
629
+ m_focusPrimitive->m_focusOuterPrimitive->finalize(
630
+ outerFocusMetrics, view->focusBorderMetrics(false /*inner*/, outerFocusMetrics));
631
+ } else if (!show && view->m_componentHostingFocusVisual && m_focusPrimitive) {
632
+ m_focusPrimitive->m_focusVisualComponent = nullptr;
633
+ m_focusPrimitive->m_focusVisual.IsVisible(false);
634
+ view->m_componentHostingFocusVisual = nullptr;
529
635
  }
530
636
  }
531
637
  }
@@ -20,6 +20,13 @@ struct CompContext;
20
20
 
21
21
  namespace winrt::Microsoft::ReactNative::Composition::implementation {
22
22
 
23
+ struct FocusPrimitive {
24
+ std::shared_ptr<BorderPrimitive> m_focusInnerPrimitive;
25
+ std::shared_ptr<BorderPrimitive> m_focusOuterPrimitive;
26
+ winrt::com_ptr<ComponentView> m_focusVisualComponent{nullptr}; // The owning component of focus visuals being hosted
27
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_focusVisual{nullptr};
28
+ };
29
+
23
30
  struct ComponentView : public ComponentViewT<
24
31
  ComponentView,
25
32
  winrt::Microsoft::ReactNative::implementation::ComponentView,
@@ -125,13 +132,10 @@ struct ComponentView : public ComponentViewT<
125
132
  facebook::react::SharedViewEventEmitter m_eventEmitter;
126
133
 
127
134
  private:
128
- void updateFocusLayoutMetrics(facebook::react::LayoutMetrics const &layoutMetrics) noexcept;
135
+ void updateFocusLayoutMetrics() noexcept;
129
136
  void updateClippingPath(
130
137
  facebook::react::LayoutMetrics const &layoutMetrics,
131
138
  const facebook::react::ViewProps &viewProps) noexcept;
132
- void finalizeFocusVisual(
133
- facebook::react::LayoutMetrics const &layoutMetrics,
134
- const facebook::react::ViewProps &viewProps) noexcept;
135
139
  void UpdateCenterPropertySet() noexcept;
136
140
  void FinalizeTransform(
137
141
  facebook::react::LayoutMetrics const &layoutMetrics,
@@ -140,16 +144,18 @@ struct ComponentView : public ComponentViewT<
140
144
  facebook::react::BorderMetrics focusBorderMetrics(bool inner, const facebook::react::LayoutMetrics &layoutMetrics)
141
145
  const noexcept;
142
146
 
147
+ virtual winrt::Microsoft::ReactNative::Composition::Experimental::IVisual visualToHostFocus() noexcept;
148
+ virtual winrt::com_ptr<ComponentView> focusVisualRoot(const facebook::react::Rect &focusRect) noexcept;
149
+
143
150
  bool m_hasTransformMatrixFacade : 1 {false};
144
- bool m_showingFocusVisual : 1 {false};
145
151
  bool m_FinalizeTransform : 1 {false};
146
152
  bool m_tooltipTracked : 1 {false};
147
153
  ComponentViewFeatures m_flags;
148
- void showFocusVisual(bool show) noexcept;
154
+ void hostFocusVisual(bool show, winrt::com_ptr<ComponentView> view) noexcept;
155
+ winrt::com_ptr<ComponentView>
156
+ m_componentHostingFocusVisual; // The component that we are showing our focus visuals within
149
157
  std::shared_ptr<BorderPrimitive> m_borderPrimitive;
150
- std::shared_ptr<BorderPrimitive> m_focusInnerPrimitive;
151
- std::shared_ptr<BorderPrimitive> m_focusOuterPrimitive;
152
- winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_focusVisual{nullptr};
158
+ std::unique_ptr<FocusPrimitive> m_focusPrimitive{nullptr};
153
159
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_outerVisual{nullptr};
154
160
  winrt::event<winrt::Windows::Foundation::EventHandler<winrt::IInspectable>> m_themeChangedEvent;
155
161
  };
@@ -176,7 +182,6 @@ struct ViewComponentView : public ViewComponentViewT<
176
182
  facebook::react::LayoutMetrics const &layoutMetrics,
177
183
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept override;
178
184
  void prepareForRecycle() noexcept override;
179
- bool TryFocus() noexcept;
180
185
  bool focusable() const noexcept override;
181
186
  void OnKeyDown(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
182
187
  void OnKeyUp(const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept override;
@@ -76,7 +76,8 @@ void WindowsModalHostComponentView::EnsureModalCreated() {
76
76
  // get the root hwnd
77
77
  m_prevWindowID =
78
78
  winrt::Microsoft::ReactNative::ReactCoreInjection::GetTopLevelWindowId(m_reactContext.Properties().Handle());
79
- auto roothwnd = reinterpret_cast<HWND>(m_prevWindowID);
79
+
80
+ auto roothwnd = GetHwndForParenting();
80
81
 
81
82
  m_hwnd = CreateWindow(
82
83
  c_modalWindowClassName,
@@ -360,6 +360,29 @@ winrt::IInspectable ReactNativeIsland::GetUiaProvider() noexcept {
360
360
  return m_uiaProvider;
361
361
  }
362
362
 
363
+ winrt::Windows::Foundation::Point ReactNativeIsland::ConvertScreenToLocal(
364
+ winrt::Windows::Foundation::Point pt) noexcept {
365
+ if (m_island) {
366
+ auto pp = m_island.CoordinateConverter().ConvertScreenToLocal(
367
+ winrt::Windows::Graphics::PointInt32{static_cast<int32_t>(pt.X), static_cast<int32_t>(pt.Y)});
368
+ return {static_cast<float>(pp.X), static_cast<float>(pp.Y)};
369
+ }
370
+ POINT p{static_cast<LONG>(pt.X), static_cast<LONG>(pt.Y)};
371
+ ScreenToClient(m_hwnd, &p);
372
+ return {static_cast<float>(p.x) / m_scaleFactor, static_cast<float>(p.y) / m_scaleFactor};
373
+ }
374
+
375
+ winrt::Windows::Foundation::Point ReactNativeIsland::ConvertLocalToScreen(
376
+ winrt::Windows::Foundation::Point pt) noexcept {
377
+ if (m_island) {
378
+ auto pp = m_island.CoordinateConverter().ConvertLocalToScreen(pt);
379
+ return {static_cast<float>(pp.X), static_cast<float>(pp.Y)};
380
+ }
381
+ POINT p{static_cast<LONG>(pt.X * m_scaleFactor), static_cast<LONG>(pt.Y * m_scaleFactor)};
382
+ ClientToScreen(m_hwnd, &p);
383
+ return {static_cast<float>(p.x), static_cast<float>(p.y)};
384
+ }
385
+
363
386
  void ReactNativeIsland::SetWindow(uint64_t hwnd) noexcept {
364
387
  m_hwnd = reinterpret_cast<HWND>(hwnd);
365
388
  }
@@ -114,6 +114,9 @@ struct ReactNativeIsland
114
114
  void SetWindow(uint64_t hwnd) noexcept;
115
115
  int64_t SendMessage(uint32_t msg, uint64_t wParam, int64_t lParam) noexcept;
116
116
 
117
+ winrt::Windows::Foundation::Point ConvertScreenToLocal(winrt::Windows::Foundation::Point pt) noexcept;
118
+ winrt::Windows::Foundation::Point ConvertLocalToScreen(winrt::Windows::Foundation::Point pt) noexcept;
119
+
117
120
  bool CapturePointer(
118
121
  const winrt::Microsoft::ReactNative::Composition::Input::Pointer &pointer,
119
122
  facebook::react::Tag tag) noexcept;
@@ -248,6 +248,26 @@ float RootComponentView::FontSizeMultiplier() const noexcept {
248
248
  return 1.0f;
249
249
  }
250
250
 
251
+ winrt::Windows::Foundation::Point RootComponentView::ConvertScreenToLocal(
252
+ winrt::Windows::Foundation::Point pt) noexcept {
253
+ if (auto rootView = m_wkRootView.get()) {
254
+ return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)
255
+ ->ConvertScreenToLocal(pt);
256
+ }
257
+ assert(false);
258
+ return {};
259
+ }
260
+
261
+ winrt::Windows::Foundation::Point RootComponentView::ConvertLocalToScreen(
262
+ winrt::Windows::Foundation::Point pt) noexcept {
263
+ if (auto rootView = m_wkRootView.get()) {
264
+ return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)
265
+ ->ConvertLocalToScreen(pt);
266
+ }
267
+ assert(false);
268
+ return {};
269
+ }
270
+
251
271
  winrt::Microsoft::UI::Content::ContentIsland RootComponentView::parentContentIsland() noexcept {
252
272
  if (auto rootView = m_wkRootView.get()) {
253
273
  return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(rootView)->Island();
@@ -38,6 +38,9 @@ struct RootComponentView : RootComponentViewT<RootComponentView, ViewComponentVi
38
38
 
39
39
  RootComponentView *rootComponentView() const noexcept override;
40
40
 
41
+ winrt::Windows::Foundation::Point ConvertScreenToLocal(winrt::Windows::Foundation::Point pt) noexcept;
42
+ winrt::Windows::Foundation::Point ConvertLocalToScreen(winrt::Windows::Foundation::Point pt) noexcept;
43
+
41
44
  winrt::Microsoft::UI::Content::ContentIsland parentContentIsland() noexcept;
42
45
 
43
46
  // Index that visuals can be inserted into OuterVisual for debugging UI
@@ -1258,4 +1258,14 @@ std::string ScrollViewComponentView::DefaultControlType() const noexcept {
1258
1258
  return "scrollbar";
1259
1259
  }
1260
1260
 
1261
+ winrt::com_ptr<ComponentView> ScrollViewComponentView::focusVisualRoot(
1262
+ const facebook::react::Rect &focusRect) noexcept {
1263
+ return get_strong();
1264
+ }
1265
+
1266
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
1267
+ ScrollViewComponentView::visualToHostFocus() noexcept {
1268
+ return m_scrollVisual;
1269
+ }
1270
+
1261
1271
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation