react-native-windows 0.74.25 → 0.74.27

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 (87) hide show
  1. package/Libraries/Modal/Modal.windows.js +352 -0
  2. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +26 -46
  3. package/Microsoft.ReactNative/Fabric/ComponentView.h +6 -19
  4. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +5 -0
  5. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +4 -0
  6. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +164 -3
  7. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +7 -0
  8. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +205 -101
  9. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +21 -13
  10. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +9 -2
  11. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +2 -1
  12. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +59 -9
  13. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.h +2 -0
  14. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.cpp +78 -30
  15. package/Microsoft.ReactNative/Fabric/Composition/ReactCompositionViewComponentBuilder.h +21 -1
  16. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +10 -0
  17. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +3 -0
  18. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +14 -1
  19. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +4 -0
  20. package/Microsoft.ReactNative/Fabric/Composition/UnimplementedNativeViewComponentView.cpp +1 -1
  21. package/Microsoft.ReactNative/IReactCompositionViewComponentBuilder.idl +25 -0
  22. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  23. package/codegen/NativeAccessibilityInfoSpec.g.h +1 -0
  24. package/codegen/NativeAccessibilityManagerSpec.g.h +1 -0
  25. package/codegen/NativeActionSheetManagerSpec.g.h +1 -0
  26. package/codegen/NativeAlertManagerSpec.g.h +1 -0
  27. package/codegen/NativeAnimatedModuleSpec.g.h +1 -0
  28. package/codegen/NativeAnimatedTurboModuleSpec.g.h +1 -0
  29. package/codegen/NativeAnimationsDebugModuleSpec.g.h +1 -0
  30. package/codegen/NativeAppStateSpec.g.h +1 -0
  31. package/codegen/NativeAppThemeSpec.g.h +1 -0
  32. package/codegen/NativeAppearanceSpec.g.h +1 -0
  33. package/codegen/NativeBlobModuleSpec.g.h +1 -0
  34. package/codegen/NativeBugReportingSpec.g.h +1 -0
  35. package/codegen/NativeClipboardSpec.g.h +1 -0
  36. package/codegen/NativeDevLoadingViewSpec.g.h +1 -0
  37. package/codegen/NativeDevMenuSpec.g.h +1 -0
  38. package/codegen/NativeDevSettingsSpec.g.h +1 -0
  39. package/codegen/NativeDevToolsSettingsManagerSpec.g.h +1 -0
  40. package/codegen/NativeDeviceEventManagerSpec.g.h +1 -0
  41. package/codegen/NativeDeviceInfoSpec.g.h +1 -0
  42. package/codegen/NativeDialogManagerAndroidSpec.g.h +1 -0
  43. package/codegen/NativeDialogManagerWindowsSpec.g.h +1 -0
  44. package/codegen/NativeExceptionsManagerSpec.g.h +1 -0
  45. package/codegen/NativeFileReaderModuleSpec.g.h +1 -0
  46. package/codegen/NativeFrameRateLoggerSpec.g.h +1 -0
  47. package/codegen/NativeHeadlessJsTaskSupportSpec.g.h +1 -0
  48. package/codegen/NativeI18nManagerSpec.g.h +1 -0
  49. package/codegen/NativeImageEditorSpec.g.h +1 -0
  50. package/codegen/NativeImageLoaderAndroidSpec.g.h +1 -0
  51. package/codegen/NativeImageLoaderIOSSpec.g.h +1 -0
  52. package/codegen/NativeImageStoreAndroidSpec.g.h +1 -0
  53. package/codegen/NativeImageStoreIOSSpec.g.h +1 -0
  54. package/codegen/NativeIntentAndroidSpec.g.h +1 -0
  55. package/codegen/NativeIntersectionObserverSpec.g.h +1 -0
  56. package/codegen/NativeJSCHeapCaptureSpec.g.h +1 -0
  57. package/codegen/NativeJSCSamplingProfilerSpec.g.h +1 -0
  58. package/codegen/NativeKeyboardObserverSpec.g.h +1 -0
  59. package/codegen/NativeLinkingManagerSpec.g.h +1 -0
  60. package/codegen/NativeLogBoxSpec.g.h +1 -0
  61. package/codegen/NativeModalManagerSpec.g.h +1 -0
  62. package/codegen/NativeMutationObserverSpec.g.h +1 -0
  63. package/codegen/NativeNetworkingAndroidSpec.g.h +1 -0
  64. package/codegen/NativeNetworkingIOSSpec.g.h +1 -0
  65. package/codegen/NativePerformanceObserverSpec.g.h +1 -0
  66. package/codegen/NativePerformanceSpec.g.h +1 -0
  67. package/codegen/NativePermissionsAndroidSpec.g.h +1 -0
  68. package/codegen/NativePlatformConstantsAndroidSpec.g.h +1 -0
  69. package/codegen/NativePlatformConstantsIOSSpec.g.h +1 -0
  70. package/codegen/NativePlatformConstantsWinSpec.g.h +1 -0
  71. package/codegen/NativePushNotificationManagerIOSSpec.g.h +1 -0
  72. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +1 -0
  73. package/codegen/NativeRedBoxSpec.g.h +1 -0
  74. package/codegen/NativeSampleTurboModuleSpec.g.h +1 -0
  75. package/codegen/NativeSegmentFetcherSpec.g.h +1 -0
  76. package/codegen/NativeSettingsManagerSpec.g.h +1 -0
  77. package/codegen/NativeShareModuleSpec.g.h +1 -0
  78. package/codegen/NativeSoundManagerSpec.g.h +1 -0
  79. package/codegen/NativeSourceCodeSpec.g.h +1 -0
  80. package/codegen/NativeStatusBarManagerAndroidSpec.g.h +1 -0
  81. package/codegen/NativeStatusBarManagerIOSSpec.g.h +1 -0
  82. package/codegen/NativeTimingSpec.g.h +1 -0
  83. package/codegen/NativeToastAndroidSpec.g.h +1 -0
  84. package/codegen/NativeUIManagerSpec.g.h +1 -0
  85. package/codegen/NativeVibrationSpec.g.h +1 -0
  86. package/codegen/NativeWebSocketModuleSpec.g.h +1 -0
  87. package/package.json +3 -3
@@ -12,15 +12,18 @@
12
12
  #include <Views/ShadowNodeBase.h>
13
13
  #include <windows.h>
14
14
  #include <windowsx.h>
15
+ #include <winrt/Windows.UI.Core.h>
15
16
  #include <winrt/Windows.UI.Input.h>
16
17
  #include "Composition.Input.h"
17
18
  #include "CompositionViewComponentView.h"
18
19
  #include "ReactNativeIsland.h"
19
20
  #include "RootComponentView.h"
20
21
 
21
- #ifdef USE_WINUI3
22
- #include <winrt/Microsoft.UI.Input.h>
23
- #endif
22
+ namespace ABI::Microsoft::UI::Input {
23
+ struct IInputCursor;
24
+ }
25
+
26
+ #include <Microsoft.UI.Input.InputCursor.Interop.h>
24
27
 
25
28
  namespace Microsoft::ReactNative {
26
29
 
@@ -328,6 +331,11 @@ CompositionEventHandler::~CompositionEventHandler() {
328
331
  }
329
332
  }
330
333
  #endif
334
+
335
+ if (m_hcursorOwned) {
336
+ ::DestroyCursor(m_hcursor);
337
+ m_hcursor = nullptr;
338
+ }
331
339
  }
332
340
 
333
341
  facebook::react::SurfaceId CompositionEventHandler::SurfaceId() const noexcept {
@@ -507,6 +515,10 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
507
515
  }
508
516
  break;
509
517
  }
518
+ case WM_SETCURSOR: {
519
+ UpdateCursor();
520
+ return 1;
521
+ }
510
522
  }
511
523
 
512
524
  return 0;
@@ -753,6 +765,155 @@ void CompositionEventHandler::HandleIncomingPointerEvent(
753
765
  hoveredViews.emplace_back(ReactTaggedView(componentViewDescriptor.view));
754
766
  }
755
767
  m_currentlyHoveredViewsPerPointer[pointerId] = std::move(hoveredViews);
768
+
769
+ if (IsMousePointerEvent(event)) {
770
+ UpdateCursor();
771
+ }
772
+ }
773
+
774
+ void CompositionEventHandler::UpdateCursor() noexcept {
775
+ for (auto &taggedView : m_currentlyHoveredViewsPerPointer[MOUSE_POINTER_ID]) {
776
+ if (auto view = taggedView.view()) {
777
+ if (auto viewcomponent =
778
+ view.try_as<winrt::Microsoft::ReactNative::Composition::implementation::ComponentView>()) {
779
+ auto cursorInfo = viewcomponent->cursor();
780
+ if (cursorInfo.first != facebook::react::Cursor::Auto || cursorInfo.second != nullptr) {
781
+ SetCursor(cursorInfo.first, cursorInfo.second);
782
+ return;
783
+ }
784
+ }
785
+ }
786
+ }
787
+
788
+ SetCursor(facebook::react::Cursor::Auto, nullptr);
789
+ }
790
+
791
+ void CompositionEventHandler::SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept {
792
+ if (m_currentCursor == cursor && m_hcursor == hcur)
793
+ return;
794
+
795
+ if (auto strongRootView = m_wkRootView.get()) {
796
+ if (auto island = strongRootView.Island()) {
797
+ auto pointerSource = winrt::Microsoft::UI::Input::InputPointerSource::GetForIsland(island);
798
+
799
+ if (!hcur) {
800
+ winrt::Windows::UI::Core::CoreCursorType type = winrt::Windows::UI::Core::CoreCursorType::Arrow;
801
+ switch (cursor) {
802
+ case facebook::react::Cursor::Pointer:
803
+ type = winrt::Windows::UI::Core::CoreCursorType::Hand;
804
+ break;
805
+ /* -- Additional cursors not added in core until later version
806
+ case facebook::react::Cursor::Help:
807
+ type = winrt::Windows::UI::Core::CoreCursorType::Help;
808
+ break;
809
+ case facebook::react::Cursor::NotAllowed:
810
+ type = winrt::Windows::UI::Core::CoreCursorType::UniversalNo;
811
+ break;
812
+ case facebook::react::Cursor::Wait:
813
+ type = winrt::Windows::UI::Core::CoreCursorType::Wait;
814
+ break;
815
+ case facebook::react::Cursor::Move:
816
+ type = winrt::Windows::UI::Core::CoreCursorType::SizeAll;
817
+ break;
818
+ case facebook::react::Cursor::NESWResize:
819
+ type = winrt::Windows::UI::Core::CoreCursorType::SizeNortheastSouthwest;
820
+ break;
821
+ case facebook::react::Cursor::NSResize:
822
+ type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthSouth;
823
+ break;
824
+ case facebook::react::Cursor::NWSEResize:
825
+ type = winrt::Windows::UI::Core::CoreCursorType::SizeNorthwestSoutheast;
826
+ break;
827
+ case facebook::react::Cursor::EWResize:
828
+ type = winrt::Windows::UI::Core::CoreCursorType::SizeWestEast;
829
+ break;
830
+ case facebook::react::Cursor::Text:
831
+ type = winrt::Windows::UI::Core::CoreCursorType::IBeam;
832
+ break;
833
+ case facebook::react::Cursor::Progress:
834
+ type = winrt::Windows::UI::Core::CoreCursorType::Wait; // IDC_APPSTARTING not mapped to CoreCursor?
835
+ break;
836
+ case facebook::react::Cursor::Crosshair:
837
+ type = winrt::Windows::UI::Core::CoreCursorType::Cross;
838
+ break;
839
+ */
840
+ default:
841
+ break;
842
+ }
843
+
844
+ m_inputCursor = winrt::Microsoft::UI::Input::InputCursor::CreateFromCoreCursor(
845
+ winrt::Windows::UI::Core::CoreCursor(type, 0));
846
+ m_hcursor = hcur;
847
+ } else {
848
+ auto cursorInterop = winrt::get_activation_factory<
849
+ winrt::Microsoft::UI::Input::InputCursor,
850
+ ABI::Microsoft::UI::Input::IInputCursorStaticsInterop>();
851
+ winrt::com_ptr<IUnknown> spunk;
852
+ winrt::check_hresult(cursorInterop->CreateFromHCursor(
853
+ hcur, reinterpret_cast<ABI::Microsoft::UI::Input::IInputCursor **>(spunk.put_void())));
854
+ m_hcursor = hcur;
855
+ m_inputCursor = spunk.as<winrt::Microsoft::UI::Input::InputCursor>();
856
+ }
857
+
858
+ pointerSource.Cursor(m_inputCursor);
859
+ } else {
860
+ if (m_hcursorOwned) {
861
+ ::DestroyCursor(m_hcursor);
862
+ m_hcursorOwned = false;
863
+ }
864
+ if (hcur == nullptr) {
865
+ const WCHAR *idc = IDC_ARROW;
866
+ switch (cursor) {
867
+ case facebook::react::Cursor::Pointer:
868
+ idc = IDC_HAND;
869
+ break;
870
+ /* -- Additional cursors not added in core until later version
871
+ case facebook::react::Cursor::Help:
872
+ idc = IDC_HELP;
873
+ break;
874
+ case facebook::react::Cursor::NotAllowed:
875
+ idc = IDC_NO;
876
+ break;
877
+ case facebook::react::Cursor::Wait:
878
+ idc = IDC_WAIT;
879
+ break;
880
+ case facebook::react::Cursor::Move:
881
+ idc = IDC_SIZEALL;
882
+ break;
883
+ case facebook::react::Cursor::NESWResize:
884
+ idc = IDC_SIZENESW;
885
+ break;
886
+ case facebook::react::Cursor::NSResize:
887
+ idc = IDC_SIZENS;
888
+ break;
889
+ case facebook::react::Cursor::NWSEResize:
890
+ idc = IDC_SIZENWSE;
891
+ break;
892
+ case facebook::react::Cursor::EWResize:
893
+ idc = IDC_SIZEWE;
894
+ break;
895
+ case facebook::react::Cursor::Text:
896
+ idc = IDC_IBEAM;
897
+ break;
898
+ case facebook::react::Cursor::Progress:
899
+ idc = IDC_APPSTARTING;
900
+ break;
901
+ case facebook::react::Cursor::Crosshair:
902
+ idc = IDC_CROSS;
903
+ break;
904
+ */
905
+ default:
906
+ break;
907
+ }
908
+ m_hcursor = ::LoadCursor(nullptr, idc);
909
+ m_hcursorOwned = true;
910
+ } else {
911
+ m_hcursor = hcur;
912
+ }
913
+ ::SetCursor(m_hcursor);
914
+ }
915
+ m_currentCursor = cursor;
916
+ }
756
917
  }
757
918
 
758
919
  void CompositionEventHandler::UpdateActiveTouch(
@@ -147,6 +147,9 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
147
147
  static void
148
148
  UpdateActiveTouch(ActiveTouch &activeTouch, facebook::react::Point ptScaled, facebook::react::Point ptLocal) noexcept;
149
149
 
150
+ void UpdateCursor() noexcept;
151
+ void SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept;
152
+
150
153
  std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
151
154
  PointerId m_touchId = 0;
152
155
  int m_fragmentTag = -1;
@@ -157,6 +160,10 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
157
160
 
158
161
  facebook::react::Tag m_pointerCapturingComponentTag{-1}; // Component that has captured input
159
162
  std::vector<PointerId> m_capturedPointers;
163
+ HCURSOR m_hcursor{nullptr};
164
+ bool m_hcursorOwned{false}; // If we create the cursor, so we need to destroy it
165
+ facebook::react::Cursor m_currentCursor{facebook::react::Cursor::Auto};
166
+ winrt::Microsoft::UI::Input::InputCursor m_inputCursor{nullptr};
160
167
 
161
168
  #ifdef USE_WINUI3
162
169
  winrt::event_token m_pointerPressedToken;
@@ -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
  // |
@@ -53,13 +53,11 @@ ComponentView::ComponentView(
53
53
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
54
54
  facebook::react::Tag tag,
55
55
  winrt::Microsoft::ReactNative::ReactContext const &reactContext,
56
- ComponentViewFeatures flags)
57
- : base_type(tag, reactContext), m_compContext(compContext), m_flags(flags) {
56
+ ComponentViewFeatures flags,
57
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder)
58
+ : base_type(tag, reactContext, builder), m_compContext(compContext), m_flags(flags) {
58
59
  m_outerVisual = compContext.CreateSpriteVisual(); // TODO could be a raw ContainerVisual if we had a
59
60
  // 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
61
  }
64
62
 
65
63
  ComponentView::~ComponentView() {
@@ -90,13 +88,17 @@ void ComponentView::onThemeChanged() noexcept {
90
88
  m_borderPrimitive->onThemeChanged(
91
89
  m_layoutMetrics, BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, *viewProps()));
92
90
  }
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));
91
+ if (m_componentHostingFocusVisual) {
92
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
93
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
94
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->onThemeChanged(
95
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
96
+ }
97
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
98
+ auto outerFocusMetrics = focusLayoutMetrics(true /*inner*/);
99
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->onThemeChanged(
100
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
101
+ }
100
102
  }
101
103
 
102
104
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
@@ -160,14 +162,18 @@ void ComponentView::updateProps(
160
162
  if (m_borderPrimitive) {
161
163
  m_borderPrimitive->updateProps(oldViewProps, newViewProps);
162
164
  }
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);
165
+
166
+ if (m_componentHostingFocusVisual) {
167
+ if (!newViewProps.enableFocusRing) {
168
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
169
+ }
170
+
171
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
172
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->updateProps(oldViewProps, newViewProps);
173
+ }
174
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
175
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->updateProps(oldViewProps, newViewProps);
176
+ }
171
177
  }
172
178
  if ((m_flags & ComponentViewFeatures::ShadowProps) == ComponentViewFeatures::ShadowProps) {
173
179
  updateShadowProps(oldViewProps, newViewProps);
@@ -200,44 +206,70 @@ void ComponentView::updateLayoutMetrics(
200
206
  });
201
207
  }
202
208
 
203
- updateFocusLayoutMetrics(layoutMetrics);
209
+ base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
204
210
 
205
211
  if (layoutMetrics != oldLayoutMetrics) {
206
212
  if (m_borderPrimitive) {
207
213
  m_borderPrimitive->markNeedsUpdate();
208
214
  }
209
- if (m_focusInnerPrimitive) {
210
- m_focusInnerPrimitive->markNeedsUpdate();
211
- }
212
- if (m_focusOuterPrimitive) {
213
- m_focusOuterPrimitive->markNeedsUpdate();
215
+
216
+ if (m_componentHostingFocusVisual) {
217
+ m_componentHostingFocusVisual->updateFocusLayoutMetrics();
214
218
  }
215
219
  }
216
220
 
217
- base_type::updateLayoutMetrics(layoutMetrics, oldLayoutMetrics);
218
221
  UpdateCenterPropertySet();
219
222
  }
220
223
 
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});
224
+ void ComponentView::updateFocusLayoutMetrics() noexcept {
225
+ facebook::react::RectangleEdges<bool> nudgeEdges;
226
+ auto scaleFactor = m_focusPrimitive->m_focusVisualComponent->m_layoutMetrics.pointScaleFactor;
227
+ if (m_focusPrimitive) {
228
+ if (m_focusPrimitive->m_focusOuterPrimitive) {
229
+ auto outerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(false /*inner*/);
230
+
231
+ if (outerFocusMetrics.frame.origin.x < 0) {
232
+ nudgeEdges.left = true;
233
+ }
234
+ if (outerFocusMetrics.frame.origin.y < 0) {
235
+ nudgeEdges.top = true;
236
+ }
237
+ if (outerFocusMetrics.frame.getMaxX() > m_layoutMetrics.frame.getMaxX()) {
238
+ nudgeEdges.right = true;
239
+ }
240
+ if (outerFocusMetrics.frame.getMaxY() > m_layoutMetrics.frame.getMaxY()) {
241
+ nudgeEdges.bottom = true;
242
+ }
243
+
244
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Size(
245
+ {outerFocusMetrics.frame.size.width * scaleFactor -
246
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
247
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0),
248
+ outerFocusMetrics.frame.size.height * scaleFactor -
249
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0) -
250
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * 2 * scaleFactor) : 0)});
251
+ m_focusPrimitive->m_focusOuterPrimitive->RootVisual().Offset(
252
+ {nudgeEdges.left ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
253
+ nudgeEdges.top ? 0 : -(FOCUS_VISUAL_WIDTH * 2 * scaleFactor),
254
+ 0.0f});
255
+ m_focusPrimitive->m_focusOuterPrimitive->markNeedsUpdate();
256
+ }
257
+
258
+ if (m_focusPrimitive->m_focusInnerPrimitive) {
259
+ auto innerFocusMetrics = m_focusPrimitive->m_focusVisualComponent->focusLayoutMetrics(true /*inner*/);
260
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Size(
261
+ {innerFocusMetrics.frame.size.width * scaleFactor -
262
+ (nudgeEdges.left ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
263
+ (nudgeEdges.right ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0),
264
+ innerFocusMetrics.frame.size.height * scaleFactor -
265
+ (nudgeEdges.top ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0) -
266
+ (nudgeEdges.bottom ? (FOCUS_VISUAL_WIDTH * scaleFactor) : 0)});
267
+ m_focusPrimitive->m_focusInnerPrimitive->RootVisual().Offset(
268
+ {nudgeEdges.left ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
269
+ nudgeEdges.top ? 0 : -FOCUS_VISUAL_WIDTH * scaleFactor,
270
+ 0.0f});
271
+ m_focusPrimitive->m_focusInnerPrimitive->markNeedsUpdate();
272
+ }
241
273
  }
242
274
  }
243
275
 
@@ -288,13 +320,17 @@ void ComponentView::FinalizeUpdates(winrt::Microsoft::ReactNative::ComponentView
288
320
  }
289
321
  }
290
322
 
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));
323
+ if (m_componentHostingFocusVisual) {
324
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive) {
325
+ auto innerFocusMetrics = focusLayoutMetrics(true /*inner*/);
326
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusInnerPrimitive->finalize(
327
+ innerFocusMetrics, focusBorderMetrics(true /*inner*/, innerFocusMetrics));
328
+ }
329
+ if (m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive) {
330
+ auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
331
+ m_componentHostingFocusVisual->m_focusPrimitive->m_focusOuterPrimitive->finalize(
332
+ outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
333
+ }
298
334
  }
299
335
 
300
336
  if (m_FinalizeTransform) {
@@ -308,7 +344,12 @@ void ComponentView::onLostFocus(
308
344
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
309
345
  if (args.OriginalSource() == Tag()) {
310
346
  m_eventEmitter->onBlur();
311
- showFocusVisual(false);
347
+
348
+ if (m_componentHostingFocusVisual) {
349
+ auto s = get_strong();
350
+
351
+ m_componentHostingFocusVisual->hostFocusVisual(false, get_strong());
352
+ }
312
353
  if (m_uiaProvider) {
313
354
  winrt::Microsoft::ReactNative::implementation::UpdateUiaProperty(
314
355
  m_uiaProvider, UIA_HasKeyboardFocusPropertyId, true, false);
@@ -317,12 +358,47 @@ void ComponentView::onLostFocus(
317
358
  base_type::onLostFocus(args);
318
359
  }
319
360
 
361
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ComponentView::visualToHostFocus() noexcept {
362
+ return OuterVisual();
363
+ }
364
+
365
+ // We want to host focus visuals as close to the focused component as possible. However since the focus visuals extend
366
+ // past the bounds of the component, in cases where additional components are positioned directly next to this one, we'd
367
+ // 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
368
+ // bounds will fix the entire focus rect.
369
+ winrt::com_ptr<ComponentView> ComponentView::focusVisualRoot(const facebook::react::Rect &focusRect) noexcept {
370
+ auto compVisual =
371
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(OuterVisual());
372
+ if (!compVisual) {
373
+ return get_strong();
374
+ // When not using lifted composition, force the focus visual to host within its own component, as we do not support
375
+ // ParentForTransform
376
+ }
377
+
378
+ if (facebook::react::Rect::intersect(focusRect, m_layoutMetrics.frame) == focusRect) {
379
+ return get_strong();
380
+ }
381
+
382
+ if (!m_parent) {
383
+ return get_strong();
384
+ }
385
+
386
+ return m_parent.as<ComponentView>()->focusVisualRoot(
387
+ {{focusRect.origin.x + m_layoutMetrics.frame.origin.x, focusRect.origin.y + m_layoutMetrics.frame.origin.y},
388
+ focusRect.size});
389
+ }
390
+
320
391
  void ComponentView::onGotFocus(
321
392
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
322
393
  if (args.OriginalSource() == Tag()) {
323
394
  m_eventEmitter->onFocus();
324
395
  if (viewProps()->enableFocusRing) {
325
- showFocusVisual(true);
396
+ facebook::react::Rect focusRect = m_layoutMetrics.frame;
397
+ focusRect.origin.x -= (FOCUS_VISUAL_WIDTH * 2);
398
+ focusRect.origin.y -= (FOCUS_VISUAL_WIDTH * 2);
399
+ focusRect.size.width += (FOCUS_VISUAL_WIDTH * 2);
400
+ focusRect.size.height += (FOCUS_VISUAL_WIDTH * 2);
401
+ focusVisualRoot(focusRect)->hostFocusVisual(true, get_strong());
326
402
  }
327
403
  if (m_uiaProvider) {
328
404
  auto spProviderSimple = m_uiaProvider.try_as<IRawElementProviderSimple>();
@@ -493,39 +569,70 @@ facebook::react::BorderMetrics ComponentView::focusBorderMetrics(
493
569
  return metrics;
494
570
  }
495
571
 
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);
572
+ void ComponentView::hostFocusVisual(bool show, winrt::com_ptr<ComponentView> view) noexcept {
573
+ if ((view->m_flags & ComponentViewFeatures::FocusVisual) == ComponentViewFeatures::FocusVisual) {
574
+ // Any previous view showing focus visuals should have removed them before another shows it
575
+ assert(
576
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
577
+ m_focusPrimitive->m_focusVisualComponent == view);
578
+ assert(
579
+ !m_focusPrimitive || !m_focusPrimitive->m_focusVisualComponent ||
580
+ view->m_componentHostingFocusVisual.get() == this);
581
+ if (show && !view->m_componentHostingFocusVisual) {
582
+ view->m_componentHostingFocusVisual = get_strong();
583
+
584
+ if (!m_focusPrimitive) {
585
+ m_focusPrimitive = std::make_unique<FocusPrimitive>();
506
586
  }
507
- if (!m_focusOuterPrimitive) {
508
- m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
509
- m_focusVisual.InsertAt(m_focusOuterPrimitive->RootVisual(), 0);
587
+ m_focusPrimitive->m_focusVisualComponent = view;
588
+
589
+ if (!m_focusPrimitive->m_focusVisual) {
590
+ m_focusPrimitive->m_focusVisual = m_compContext.CreateSpriteVisual();
591
+ auto hostingVisual =
592
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
593
+ visualToHostFocus())
594
+ .as<winrt::Microsoft::UI::Composition::ContainerVisual>();
595
+ if (hostingVisual) {
596
+ hostingVisual.Children().InsertAtTop(
597
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
598
+ m_focusPrimitive->m_focusVisual));
599
+ } else {
600
+ assert(
601
+ view.get() ==
602
+ this); // When not using lifted comp, focus visuals should always host within their own component
603
+ OuterVisual().InsertAt(m_focusPrimitive->m_focusVisual, 1);
604
+ }
510
605
  }
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));
606
+
607
+ m_focusPrimitive->m_focusVisual.IsVisible(true);
608
+ assert(view->viewProps()->enableFocusRing);
609
+ if (!m_focusPrimitive->m_focusInnerPrimitive) {
610
+ m_focusPrimitive->m_focusInnerPrimitive = std::make_shared<BorderPrimitive>(*this);
611
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusInnerPrimitive->RootVisual(), 0);
612
+ }
613
+ if (!m_focusPrimitive->m_focusOuterPrimitive) {
614
+ m_focusPrimitive->m_focusOuterPrimitive = std::make_shared<BorderPrimitive>(*this);
615
+ m_focusPrimitive->m_focusVisual.InsertAt(m_focusPrimitive->m_focusOuterPrimitive->RootVisual(), 0);
523
616
  }
524
- if (m_focusOuterPrimitive) {
525
- m_focusOuterPrimitive->markNeedsUpdate();
526
- auto outerFocusMetrics = focusLayoutMetrics(false /*inner*/);
527
- m_focusOuterPrimitive->finalize(outerFocusMetrics, focusBorderMetrics(false /*inner*/, outerFocusMetrics));
617
+ if (auto focusVisual =
618
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
619
+ m_focusPrimitive->m_focusVisual)) {
620
+ auto outerVisual =
621
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(
622
+ view->OuterVisual());
623
+ focusVisual.ParentForTransform(outerVisual);
528
624
  }
625
+ updateFocusLayoutMetrics();
626
+ auto innerFocusMetrics = view->focusLayoutMetrics(true /*inner*/);
627
+ m_focusPrimitive->m_focusInnerPrimitive->finalize(
628
+ innerFocusMetrics, view->focusBorderMetrics(true /*inner*/, innerFocusMetrics));
629
+ auto outerFocusMetrics = view->focusLayoutMetrics(false /*inner*/);
630
+ m_focusPrimitive->m_focusOuterPrimitive->finalize(
631
+ outerFocusMetrics, view->focusBorderMetrics(false /*inner*/, outerFocusMetrics));
632
+ } else if (!show && view->m_componentHostingFocusVisual && m_focusPrimitive) {
633
+ m_focusPrimitive->m_focusVisualComponent = nullptr;
634
+ m_focusPrimitive->m_focusVisual.IsVisible(false);
635
+ view->m_componentHostingFocusVisual = nullptr;
529
636
  }
530
637
  }
531
638
  }
@@ -685,6 +792,10 @@ void ComponentView::updateClippingPath(
685
792
  }
686
793
  }
687
794
 
795
+ std::pair<facebook::react::Cursor, HCURSOR> ComponentView::cursor() const noexcept {
796
+ return {viewProps()->cursor, nullptr};
797
+ }
798
+
688
799
  void ComponentView::indexOffsetForBorder(uint32_t &index) const noexcept {
689
800
  if (m_borderPrimitive) {
690
801
  index += m_borderPrimitive->numberOfVisuals();
@@ -806,24 +917,15 @@ ViewComponentView::ViewComponentView(
806
917
  const winrt::Microsoft::ReactNative::Composition::Experimental::ICompositionContext &compContext,
807
918
  facebook::react::Tag tag,
808
919
  winrt::Microsoft::ReactNative::ReactContext const &reactContext,
809
- ComponentViewFeatures flags)
810
- : base_type(compContext, tag, reactContext, flags),
920
+ ComponentViewFeatures flags,
921
+ winrt::Microsoft::ReactNative::Composition::ReactCompositionViewComponentBuilder *builder)
922
+ : base_type(compContext, tag, reactContext, flags, builder),
811
923
  m_props(defaultProps ? defaultProps : ViewComponentView::defaultProps()) {}
812
924
 
813
925
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ViewComponentView::createVisual() noexcept {
814
926
  return m_compContext.CreateSpriteVisual();
815
927
  }
816
928
 
817
- void ViewComponentView::CreateVisualHandler(
818
- const winrt::Microsoft::ReactNative::Composition::CreateVisualDelegate &handler) {
819
- m_createVisualHandler = handler;
820
- }
821
-
822
- winrt::Microsoft::ReactNative::Composition::CreateVisualDelegate ViewComponentView::CreateVisualHandler()
823
- const noexcept {
824
- return m_createVisualHandler;
825
- }
826
-
827
929
  void ViewComponentView::CreateInternalVisualHandler(
828
930
  const winrt::Microsoft::ReactNative::Composition::Experimental::CreateInternalVisualDelegate &handler) {
829
931
  m_createInternalVisualHandler = handler;
@@ -838,10 +940,10 @@ void ViewComponentView::ensureVisual() noexcept {
838
940
  if (!m_visual) {
839
941
  if (m_createInternalVisualHandler) {
840
942
  m_visual = m_createInternalVisualHandler(*this);
841
- } else if (m_createVisualHandler) {
943
+ } else if (m_builder && m_builder->CreateVisualHandler()) {
842
944
  m_visual =
843
945
  winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::CreateVisual(
844
- m_createVisualHandler(*this));
946
+ m_builder->CreateVisualHandler()(*this));
845
947
  } else {
846
948
  m_visual = createVisual();
847
949
  }
@@ -859,6 +961,8 @@ winrt::Microsoft::ReactNative::ComponentView ViewComponentView::Create(
859
961
 
860
962
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
861
963
  ViewComponentView::VisualToMountChildrenInto() noexcept {
964
+ if (m_builder && m_builder->VisualToMountChildrenIntoHandler())
965
+ return m_builder->VisualToMountChildrenIntoHandler()(*this);
862
966
  return Visual();
863
967
  }
864
968
 
@@ -1106,7 +1210,7 @@ winrt::Microsoft::ReactNative::ViewProps ViewComponentView::ViewProps() noexcept
1106
1210
 
1107
1211
  winrt::Microsoft::ReactNative::ViewProps ViewComponentView::ViewPropsInner() noexcept {
1108
1212
  // If we have AbiViewProps, then we dont need to new up a props wrapper
1109
- if (m_customComponent) {
1213
+ if (m_builder) {
1110
1214
  const auto &abiViewProps = *std::static_pointer_cast<const ::Microsoft::ReactNative::AbiViewProps>(m_props);
1111
1215
  return abiViewProps.ViewProps();
1112
1216
  }