react-native-windows 0.81.4 → 0.81.5

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 (61) hide show
  1. package/Libraries/Core/ReactNativeVersion.js +1 -1
  2. package/Libraries/NativeComponent/ViewConfigIgnore.windows.js +45 -0
  3. package/Libraries/Renderer/implementations/ReactFabric-dev.js +38 -35
  4. package/Libraries/Renderer/implementations/ReactFabric-prod.js +51 -22
  5. package/Libraries/Renderer/implementations/ReactFabric-profiling.js +54 -24
  6. package/Libraries/Renderer/implementations/ReactNativeRenderer-dev.js +36 -33
  7. package/Libraries/Renderer/implementations/ReactNativeRenderer-prod.js +5 -5
  8. package/Libraries/Renderer/implementations/ReactNativeRenderer-profiling.js +5 -5
  9. package/Libraries/Renderer/shims/ReactNativeTypes.js +23 -11
  10. package/Libraries/Renderer/shims/ReactNativeTypes.windows.js +23 -12
  11. package/Microsoft.ReactNative/ComponentView.idl +2 -0
  12. package/Microsoft.ReactNative/Composition.Input.idl +7 -0
  13. package/Microsoft.ReactNative/CompositionComponentView.idl +5 -0
  14. package/Microsoft.ReactNative/CompositionHwndHost.idl +1 -0
  15. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +19 -1
  16. package/Microsoft.ReactNative/Fabric/ComponentView.h +10 -1
  17. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.cpp +12 -0
  18. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.h +15 -0
  19. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +75 -0
  20. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +1 -0
  21. package/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +10 -45
  22. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +86 -98
  23. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +4 -0
  24. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +80 -48
  25. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +11 -3
  26. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +61 -74
  27. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +4 -3
  28. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +2 -1
  29. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.cpp +245 -0
  30. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.h +80 -0
  31. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +33 -1
  32. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +17 -0
  33. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +47 -23
  34. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +3 -0
  35. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +3 -1
  36. package/Microsoft.ReactNative/Modules/LogBoxModule.cpp +20 -95
  37. package/Microsoft.ReactNative/Modules/LogBoxModule.h +1 -1
  38. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +0 -41
  39. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +0 -11
  40. package/Microsoft.ReactNative/ReactNativeIsland.idl +2 -3
  41. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +31 -101
  42. package/Microsoft.ReactNative/ReactNativeWin32App.h +2 -13
  43. package/Microsoft.ReactNative/ReactNativeWindow.idl +44 -0
  44. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  45. package/Shared/Shared.vcxitems +7 -0
  46. package/Shared/Shared.vcxitems.filters +6 -0
  47. package/codegen/react/components/rnwcore/ActivityIndicatorView.g.h +2 -1
  48. package/codegen/react/components/rnwcore/AndroidDrawerLayout.g.h +26 -9
  49. package/codegen/react/components/rnwcore/AndroidHorizontalScrollContentView.g.h +2 -1
  50. package/codegen/react/components/rnwcore/AndroidProgressBar.g.h +2 -1
  51. package/codegen/react/components/rnwcore/AndroidSwipeRefreshLayout.g.h +8 -3
  52. package/codegen/react/components/rnwcore/AndroidSwitch.g.h +8 -3
  53. package/codegen/react/components/rnwcore/DebuggingOverlay.g.h +1 -0
  54. package/codegen/react/components/rnwcore/InputAccessory.g.h +2 -1
  55. package/codegen/react/components/rnwcore/ModalHostView.g.h +26 -9
  56. package/codegen/react/components/rnwcore/PullToRefreshView.g.h +8 -3
  57. package/codegen/react/components/rnwcore/SafeAreaView.g.h +1 -0
  58. package/codegen/react/components/rnwcore/Switch.g.h +8 -3
  59. package/codegen/react/components/rnwcore/UnimplementedNativeView.g.h +2 -1
  60. package/codegen/react/components/rnwcore/VirtualView.g.h +8 -3
  61. package/package.json +21 -21
@@ -323,6 +323,32 @@ void CompositionEventHandler::Initialize() noexcept {
323
323
  }
324
324
  }
325
325
  });
326
+
327
+ m_contextMenuKeyToken =
328
+ keyboardSource.ContextMenuKey([wkThis = weak_from_this()](
329
+ winrt::Microsoft::UI::Input::InputKeyboardSource const & /*source*/,
330
+ winrt::Microsoft::UI::Input::ContextMenuKeyEventArgs const &args) {
331
+ if (auto strongThis = wkThis.lock()) {
332
+ if (auto strongRootView = strongThis->m_wkRootView.get()) {
333
+ if (strongThis->SurfaceId() == -1)
334
+ return;
335
+
336
+ auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
337
+ if (focusedComponent) {
338
+ auto tag =
339
+ winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
340
+ ->Tag();
341
+ auto contextMenuArgs = winrt::make<
342
+ winrt::Microsoft::ReactNative::Composition::Input::implementation::ContextMenuKeyEventArgs>(tag);
343
+ winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
344
+ ->OnContextMenuKey(contextMenuArgs);
345
+ if (contextMenuArgs.Handled()) {
346
+ args.Handled(true);
347
+ }
348
+ }
349
+ }
350
+ }
351
+ });
326
352
  }
327
353
  #endif
328
354
  }
@@ -341,6 +367,7 @@ CompositionEventHandler::~CompositionEventHandler() {
341
367
  keyboardSource.KeyDown(m_keyDownToken);
342
368
  keyboardSource.KeyUp(m_keyUpToken);
343
369
  keyboardSource.CharacterReceived(m_characterReceivedToken);
370
+ keyboardSource.ContextMenuKey(m_contextMenuKeyToken);
344
371
  }
345
372
  }
346
373
  #endif
@@ -449,6 +476,54 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
449
476
  }
450
477
  return 0;
451
478
  }
479
+ case WM_RBUTTONDOWN: {
480
+ if (auto strongRootView = m_wkRootView.get()) {
481
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
482
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
483
+ onPointerPressed(pp, GetKeyModifiers(wParam));
484
+ }
485
+ return 0;
486
+ }
487
+ case WM_RBUTTONUP: {
488
+ if (auto strongRootView = m_wkRootView.get()) {
489
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
490
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
491
+ onPointerReleased(pp, GetKeyModifiers(wParam));
492
+ }
493
+ return 0;
494
+ }
495
+ case WM_MBUTTONDOWN: {
496
+ if (auto strongRootView = m_wkRootView.get()) {
497
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
498
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
499
+ onPointerPressed(pp, GetKeyModifiers(wParam));
500
+ }
501
+ return 0;
502
+ }
503
+ case WM_MBUTTONUP: {
504
+ if (auto strongRootView = m_wkRootView.get()) {
505
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
506
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
507
+ onPointerReleased(pp, GetKeyModifiers(wParam));
508
+ }
509
+ return 0;
510
+ }
511
+ case WM_XBUTTONDOWN: {
512
+ if (auto strongRootView = m_wkRootView.get()) {
513
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
514
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
515
+ onPointerPressed(pp, GetKeyModifiers(wParam));
516
+ }
517
+ return 0;
518
+ }
519
+ case WM_XBUTTONUP: {
520
+ if (auto strongRootView = m_wkRootView.get()) {
521
+ auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
522
+ hwnd, msg, wParam, lParam, strongRootView.ScaleFactor());
523
+ onPointerReleased(pp, GetKeyModifiers(wParam));
524
+ }
525
+ return 0;
526
+ }
452
527
  case WM_POINTERUP: {
453
528
  if (auto strongRootView = m_wkRootView.get()) {
454
529
  auto pp = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerPoint>(
@@ -176,6 +176,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
176
176
  winrt::event_token m_keyDownToken;
177
177
  winrt::event_token m_keyUpToken;
178
178
  winrt::event_token m_characterReceivedToken;
179
+ winrt::event_token m_contextMenuKeyToken;
179
180
  #endif
180
181
  };
181
182
 
@@ -32,55 +32,20 @@ CompositionHwndHost::CompositionHwndHost() noexcept {}
32
32
  void CompositionHwndHost::Initialize(uint64_t hwnd) noexcept {
33
33
  m_hwnd = (HWND)hwnd;
34
34
 
35
- auto compositionContext =
36
- winrt::Microsoft::ReactNative::Composition::implementation::CompositionUIService::GetCompositionContext(
37
- ReactViewHost().ReactNativeHost().InstanceSettings().Properties());
38
- #if USE_WINUI3
39
- if (auto liftedCompositor =
40
- winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(
41
- compositionContext)) {
42
- m_compRootView = winrt::Microsoft::ReactNative::ReactNativeIsland(liftedCompositor);
43
-
44
- auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
45
- liftedCompositor, winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd));
35
+ auto compositor = winrt::Microsoft::ReactNative::Composition::implementation::CompositionUIService::GetCompositor(
36
+ ReactViewHost().ReactNativeHost().InstanceSettings().Properties());
37
+ m_compRootView = winrt::Microsoft::ReactNative::ReactNativeIsland(compositor);
46
38
 
47
- auto island = m_compRootView.Island();
39
+ auto bridge = winrt::Microsoft::UI::Content::DesktopChildSiteBridge::Create(
40
+ compositor, winrt::Microsoft::UI::GetWindowIdFromWindow(m_hwnd));
48
41
 
49
- bridge.Connect(island);
50
- bridge.Show();
42
+ auto island = m_compRootView.Island();
51
43
 
52
- m_compRootView.ScaleFactor(ScaleFactor());
53
- bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow);
54
- } else {
55
- m_compRootView = winrt::Microsoft::ReactNative::ReactNativeIsland();
56
- m_compRootView.as<winrt::Microsoft::ReactNative::Composition::Experimental::IInternalCompositionRootView>()
57
- .SetWindow(reinterpret_cast<uint64_t>(m_hwnd));
58
-
59
- #endif
60
- auto compositor =
61
- winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerCompositor(
62
- compositionContext);
63
- auto interop = compositor.as<ABI::Windows::UI::Composition::Desktop::ICompositorDesktopInterop>();
64
- winrt::Windows::UI::Composition::Desktop::DesktopWindowTarget target{nullptr};
65
- check_hresult(interop->CreateDesktopWindowTarget(
66
- m_hwnd,
67
- false,
68
- reinterpret_cast<ABI::Windows::UI::Composition::Desktop::IDesktopWindowTarget **>(put_abi(target))));
69
-
70
- auto root = compositor.CreateContainerVisual();
71
- root.RelativeSizeAdjustment({1.0f, 1.0f});
72
- root.Offset({0, 0, 0});
73
- root.Comment(L"Root Visual");
74
- target.Root(root);
75
-
76
- m_compRootView.as<winrt::Microsoft::ReactNative::Composition::Experimental::IInternalCompositionRootView>()
77
- .InternalRootVisual(
78
- winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::CreateVisual(
79
- target.Root()));
44
+ bridge.Connect(island);
45
+ bridge.Show();
80
46
 
81
- #if USE_WINUI3
82
- }
83
- #endif
47
+ m_compRootView.ScaleFactor(ScaleFactor());
48
+ bridge.ResizePolicy(winrt::Microsoft::UI::Content::ContentSizePolicy::ResizeContentToParentWindow);
84
49
 
85
50
  m_compRootView.ReactViewHost(std::move(m_reactViewHost));
86
51
  m_compRootView.ScaleFactor(ScaleFactor());
@@ -38,10 +38,11 @@ constexpr float FOCUS_VISUAL_RADIUS = 3.0f;
38
38
 
39
39
  // m_outerVisual
40
40
  // |
41
- // |
42
41
  // ----- m_visual <-- Background / clip - Can be a custom visual depending on Component type
43
42
  // |
44
43
  // ----- Border Visuals x N (BorderPrimitive attached to m_visual)
44
+ // ----- <children> (default: directly in m_visual after border visuals)
45
+ // ----- m_childrenContainer (created on demand when overflow:hidden, children moved here)
45
46
  // ------Focus Visual Container (created when hosting focus visuals)
46
47
  // |
47
48
  // |------Inner Focus Visual
@@ -527,10 +528,10 @@ facebook::react::RectangleEdges<bool> ComponentView::focusNudges() const noexcep
527
528
 
528
529
  Assert(m_componentHostingFocusVisual);
529
530
 
530
- if (layoutMetrics.frame.origin.x < 0) {
531
+ if (layoutMetrics.frame.origin.x < m_componentHostingFocusVisual->m_layoutMetrics.frame.origin.x) {
531
532
  nudgeEdges.left = true;
532
533
  }
533
- if (layoutMetrics.frame.origin.y < 0) {
534
+ if (layoutMetrics.frame.origin.y < m_componentHostingFocusVisual->m_layoutMetrics.frame.origin.y) {
534
535
  nudgeEdges.top = true;
535
536
  }
536
537
  if (layoutMetrics.frame.getMaxX() > m_componentHostingFocusVisual->m_layoutMetrics.frame.getMaxX()) {
@@ -710,86 +711,9 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps
710
711
  shadow.Color(theme()->Color(*viewProps.shadowColor));
711
712
  }
712
713
 
713
- // Check if any border radius is set
714
- auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, viewProps);
715
- bool hasBorderRadius = borderMetrics.borderRadii.topLeft.horizontal != 0 ||
716
- borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 ||
717
- borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 ||
718
- borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 ||
719
- borderMetrics.borderRadii.bottomRight.vertical != 0;
720
-
721
- if (hasBorderRadius) {
722
- // When borderRadius is set, we need to create a shadow mask that follows the rounded rectangle shape.
723
- // Use CompositionVisualSurface to capture the clipped visual's appearance as the shadow mask.
724
- bool maskSet = false;
725
-
726
- // Try Microsoft (WinUI3) Composition first
727
- auto msCompositor =
728
- winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(
729
- m_compContext);
730
- if (msCompositor) {
731
- auto innerVisual =
732
- winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual(
733
- Visual());
734
- if (innerVisual) {
735
- // Create a VisualSurface that captures the visual (with its clip applied)
736
- auto visualSurface = msCompositor.CreateVisualSurface();
737
- visualSurface.SourceVisual(innerVisual);
738
- visualSurface.SourceSize(
739
- {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
740
- m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
741
-
742
- // Create a brush from the visual surface to use as shadow mask
743
- auto maskBrush = msCompositor.CreateSurfaceBrush(visualSurface);
744
- maskBrush.Stretch(winrt::Microsoft::UI::Composition::CompositionStretch::Fill);
745
-
746
- // Get the inner shadow and set the mask
747
- auto innerShadow = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::
748
- InnerDropShadow(shadow);
749
- if (innerShadow) {
750
- innerShadow.Mask(maskBrush);
751
- maskSet = true;
752
- }
753
- }
754
- }
755
-
756
- // Fallback to System (Windows.UI) Composition if Microsoft Composition is not available
757
- if (!maskSet) {
758
- auto sysCompositor =
759
- winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerCompositor(
760
- m_compContext);
761
- if (sysCompositor) {
762
- auto innerVisual =
763
- winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerVisual(
764
- Visual());
765
- if (innerVisual) {
766
- auto visualSurface = sysCompositor.CreateVisualSurface();
767
- visualSurface.SourceVisual(innerVisual);
768
- visualSurface.SourceSize(
769
- {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
770
- m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
771
-
772
- auto maskBrush = sysCompositor.CreateSurfaceBrush(visualSurface);
773
- maskBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::Fill);
774
-
775
- auto innerShadow =
776
- winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerDropShadow(
777
- shadow);
778
- if (innerShadow) {
779
- innerShadow.Mask(maskBrush);
780
- }
781
- }
782
- }
783
- }
784
-
785
- // Apply shadow to OuterVisual (which is not clipped) so the shadow can extend beyond the clip
786
- OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
787
- Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
788
- } else {
789
- // No border radius - apply shadow directly to Visual (original behavior)
790
- Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
791
- OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
792
- }
714
+ // Apply shadow to OuterVisual (not Visual) because Visual may have a rounded-corner clip
715
+ // from updateClippingPath, which would clip the shadow. OuterVisual is not clipped.
716
+ OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
793
717
  }
794
718
 
795
719
  void ComponentView::updateTransformProps(
@@ -973,23 +897,26 @@ void ComponentView::updateClippingPath(
973
897
  const facebook::react::ViewProps &viewProps) noexcept {
974
898
  auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
975
899
 
976
- if (borderMetrics.borderRadii.topLeft.horizontal == 0 && borderMetrics.borderRadii.topRight.horizontal == 0 &&
977
- borderMetrics.borderRadii.bottomLeft.horizontal == 0 && borderMetrics.borderRadii.bottomRight.horizontal == 0 &&
978
- borderMetrics.borderRadii.topLeft.vertical == 0 && borderMetrics.borderRadii.topRight.vertical == 0 &&
979
- borderMetrics.borderRadii.bottomLeft.vertical == 0 && borderMetrics.borderRadii.bottomRight.vertical == 0) {
980
- Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
981
- } else {
900
+ bool hasRoundedCorners = borderMetrics.borderRadii.topLeft.horizontal != 0 ||
901
+ borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 ||
902
+ borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 ||
903
+ borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 ||
904
+ borderMetrics.borderRadii.bottomRight.vertical != 0;
905
+
906
+ const float scale = layoutMetrics.pointScaleFactor;
907
+ const float viewWidth = layoutMetrics.frame.size.width * scale;
908
+ const float viewHeight = layoutMetrics.frame.size.height * scale;
909
+
910
+ // Apply clipping to m_visual only for rounded corners
911
+ // overflow:hidden clipping is handled separately via m_childrenContainer in ViewComponentView
912
+ if (hasRoundedCorners) {
982
913
  winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
983
- m_compContext,
984
- borderMetrics.borderRadii,
985
- {0, 0, 0, 0},
986
- {0,
987
- 0,
988
- layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
989
- layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
914
+ m_compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, {0, 0, viewWidth, viewHeight});
990
915
 
991
916
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
992
917
  pathGeometry.get());
918
+ } else {
919
+ Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
993
920
  }
994
921
  }
995
922
 
@@ -1164,6 +1091,11 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
1164
1091
  ViewComponentView::VisualToMountChildrenInto() noexcept {
1165
1092
  if (m_builder && m_builder->VisualToMountChildrenIntoHandler())
1166
1093
  return m_builder->VisualToMountChildrenIntoHandler()(*this);
1094
+ // When overflow:hidden, children are hosted in m_childrenContainer (child of m_visual)
1095
+ // so we can apply clipping without affecting borders/background.
1096
+ // Otherwise children go directly into Visual() (the original behavior).
1097
+ if (m_childrenContainer)
1098
+ return m_childrenContainer;
1167
1099
  return Visual();
1168
1100
  }
1169
1101
 
@@ -1172,9 +1104,14 @@ void ViewComponentView::MountChildComponentView(
1172
1104
  uint32_t index) noexcept {
1173
1105
  base_type::MountChildComponentView(childComponentView, index);
1174
1106
 
1175
- indexOffsetForBorder(index);
1176
1107
  ensureVisual();
1177
1108
 
1109
+ // When children are in Visual() directly, offset past border visuals.
1110
+ // When children are in m_childrenContainer, no offset needed.
1111
+ if (!m_childrenContainer) {
1112
+ indexOffsetForBorder(index);
1113
+ }
1114
+
1178
1115
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1179
1116
  auto visualIndex = index;
1180
1117
  // Most of the time child index will align with visual index.
@@ -1186,6 +1123,7 @@ void ViewComponentView::MountChildComponentView(
1186
1123
  }
1187
1124
  }
1188
1125
  }
1126
+
1189
1127
  VisualToMountChildrenInto().InsertAt(compositionChild->OuterVisual(), visualIndex);
1190
1128
  } else {
1191
1129
  m_hasNonVisualChildren = true;
@@ -1197,7 +1135,6 @@ void ViewComponentView::UnmountChildComponentView(
1197
1135
  uint32_t index) noexcept {
1198
1136
  base_type::UnmountChildComponentView(childComponentView, index);
1199
1137
 
1200
- indexOffsetForBorder(index);
1201
1138
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1202
1139
  VisualToMountChildrenInto().Remove(compositionChild->OuterVisual());
1203
1140
  }
@@ -1397,6 +1334,57 @@ void ViewComponentView::updateLayoutMetrics(
1397
1334
  Visual().Size(
1398
1335
  {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
1399
1336
  layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
1337
+
1338
+ // Update children container clipping for overflow:hidden
1339
+ updateChildrenClippingPath(layoutMetrics, *viewProps());
1340
+ }
1341
+
1342
+ void ViewComponentView::updateChildrenClippingPath(
1343
+ facebook::react::LayoutMetrics const &layoutMetrics,
1344
+ const facebook::react::ViewProps &viewProps) noexcept {
1345
+ const float scale = layoutMetrics.pointScaleFactor;
1346
+ const float viewWidth = layoutMetrics.frame.size.width * scale;
1347
+ const float viewHeight = layoutMetrics.frame.size.height * scale;
1348
+
1349
+ if (viewProps.getClipsContentToBounds()) {
1350
+ // Create m_childrenContainer on demand (like iOS _containerView pattern)
1351
+ // m_childrenContainer is a child of m_visual, placed after border visuals.
1352
+ if (!m_childrenContainer) {
1353
+ m_childrenContainer = m_compContext.CreateSpriteVisual();
1354
+
1355
+ // Insert at the end of m_visual's children (after border visuals + existing children)
1356
+ // Then move existing children from m_visual into m_childrenContainer
1357
+ uint32_t borderCount = 0;
1358
+ indexOffsetForBorder(borderCount);
1359
+
1360
+ // Move existing child visuals from m_visual to m_childrenContainer
1361
+ uint32_t childVisualIndex = 0;
1362
+ for (auto it = m_children.begin(); it != m_children.end(); ++it) {
1363
+ if (auto compositionChild = (*it).try_as<ComponentView>()) {
1364
+ Visual().Remove(compositionChild->OuterVisual());
1365
+ m_childrenContainer.InsertAt(compositionChild->OuterVisual(), childVisualIndex++);
1366
+ }
1367
+ }
1368
+
1369
+ // Insert m_childrenContainer after border visuals in m_visual
1370
+ Visual().InsertAt(m_childrenContainer, borderCount);
1371
+
1372
+ // Use relative sizing so container automatically tracks parent's size
1373
+ m_childrenContainer.RelativeSizeWithOffset({0, 0}, {1, 1});
1374
+ }
1375
+
1376
+ // Clip children to view bounds using outer border radii (matches iOS default behavior)
1377
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1378
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
1379
+ m_compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, {0, 0, viewWidth, viewHeight});
1380
+
1381
+ m_childrenContainer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1382
+ pathGeometry.get());
1383
+ } else if (m_childrenContainer) {
1384
+ // overflow changed from hidden to visible. Keep container, just remove clip.
1385
+ m_childrenContainer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1386
+ nullptr);
1387
+ }
1400
1388
  }
1401
1389
 
1402
1390
  void ViewComponentView::prepareForRecycle() noexcept {}
@@ -233,11 +233,15 @@ struct ViewComponentView : public ViewComponentViewT<
233
233
 
234
234
  protected:
235
235
  virtual winrt::Microsoft::ReactNative::ViewProps ViewPropsInner() noexcept;
236
+ virtual void updateChildrenClippingPath(
237
+ facebook::react::LayoutMetrics const &layoutMetrics,
238
+ const facebook::react::ViewProps &viewProps) noexcept;
236
239
 
237
240
  private:
238
241
  bool m_hasNonVisualChildren{false};
239
242
  facebook::react::SharedViewProps m_props;
240
243
  winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_visual{nullptr};
244
+ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual m_childrenContainer{nullptr};
241
245
  winrt::Microsoft::ReactNative::Composition::Experimental::CreateInternalVisualDelegate m_createInternalVisualHandler{
242
246
  nullptr};
243
247
  };
@@ -15,6 +15,7 @@
15
15
  #include <winrt/Windows.UI.Composition.h>
16
16
  #include "CompositionContextHelper.h"
17
17
  #include "RootComponentView.h"
18
+ #include "ScrollViewComponentView.h"
18
19
 
19
20
  #include "Composition.ContentIslandComponentView.g.cpp"
20
21
 
@@ -50,15 +51,31 @@ winrt::Microsoft::UI::Content::ContentIsland ContentIslandComponentView::ParentC
50
51
  return root->parentContentIsland();
51
52
  }
52
53
 
54
+ winrt::Microsoft::UI::Content::ChildSiteLink ContentIslandComponentView::ChildSiteLink() noexcept {
55
+ if (!isMounted())
56
+ return nullptr;
57
+ if (!m_childSiteLink) {
58
+ m_childSiteLink = winrt::Microsoft::UI::Content::ChildSiteLink::Create(
59
+ m_parentContentIsland,
60
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(Visual())
61
+ .as<winrt::Microsoft::UI::Composition::ContainerVisual>());
62
+ }
63
+ return m_childSiteLink;
64
+ }
65
+
53
66
  void ContentIslandComponentView::ConnectInternal() noexcept {
54
67
  if (!m_islandToConnect)
55
68
  return;
56
69
 
57
- m_childSiteLink = winrt::Microsoft::UI::Content::ChildSiteLink::Create(
58
- m_parentContentIsland,
59
- winrt::Microsoft::ReactNative::Composition::Experimental::CompositionContextHelper::InnerVisual(Visual())
60
- .as<winrt::Microsoft::UI::Composition::ContainerVisual>());
61
- m_childSiteLink.ActualSize({m_layoutMetrics.frame.size.width, m_layoutMetrics.frame.size.height});
70
+ ChildSiteLink().ActualSize({m_layoutMetrics.frame.size.width, m_layoutMetrics.frame.size.height});
71
+
72
+ // Issue #15557: Set initial LocalToParentTransformMatrix synchronously before Connect.
73
+ // This fixes popup position being wrong even without scrolling.
74
+ // Note: getClientRect() returns physical pixels, but LocalToParentTransformMatrix expects DIPs.
75
+ auto clientRect = getClientRect();
76
+ float scaleFactor = m_layoutMetrics.pointScaleFactor;
77
+ m_childSiteLink.LocalToParentTransformMatrix(winrt::Windows::Foundation::Numerics::make_float4x4_translation(
78
+ static_cast<float>(clientRect.left) / scaleFactor, static_cast<float>(clientRect.top) / scaleFactor, 0.0f));
62
79
 
63
80
  m_navigationHost = winrt::Microsoft::UI::Input::InputFocusNavigationHost::GetForSiteLink(m_childSiteLink);
64
81
 
@@ -76,10 +93,18 @@ void ContentIslandComponentView::ConnectInternal() noexcept {
76
93
  ConfigureChildSiteLinkAutomation();
77
94
 
78
95
  if (m_islandToConnect) {
96
+ Assert(m_childSiteLink.SiteView().IsConnected());
97
+ Assert(!m_islandToConnect.IsConnected());
98
+
79
99
  m_childSiteLink.Connect(m_islandToConnect);
80
100
  m_islandToConnect = nullptr;
81
101
  }
82
- UnregisterForRootIslandEvents();
102
+
103
+ if (m_pendingNavigateFocus) {
104
+ m_navigationHost.NavigateFocus(
105
+ winrt::Microsoft::UI::Input::FocusNavigationRequest::Create(*m_pendingNavigateFocus));
106
+ m_pendingNavigateFocus.reset();
107
+ }
83
108
 
84
109
  ParentLayoutChanged();
85
110
  auto view = Parent();
@@ -92,64 +117,65 @@ void ContentIslandComponentView::ConnectInternal() noexcept {
92
117
  strongThis->ParentLayoutChanged();
93
118
  }
94
119
  }));
95
- view = view.Parent();
96
- }
97
- }
98
-
99
- void ContentIslandComponentView::RegisterForRootIslandEvents() noexcept {
100
- m_parentContentIsland = ParentContentIsland();
101
120
 
102
- if (m_parentContentIsland.IsConnected()) {
103
- ConnectInternal();
104
- } else {
105
- m_islandStateChangedToken = m_parentContentIsland.StateChanged(
106
- [wkThis = get_weak()](
107
- const winrt::Microsoft::UI::Content::ContentIsland & /*island*/,
108
- const winrt::Microsoft::UI::Content::ContentIslandStateChangedEventArgs & /*args*/) {
109
- if (auto strongThis = wkThis.get()) {
110
- strongThis->ConnectInternal();
111
- }
112
- });
113
- }
114
- }
121
+ // Issue #15557: Register for ViewChanged on parent ScrollViews to update transform
122
+ // when scroll position changes, ensuring correct XAML popup positioning.
123
+ if (auto scrollView = view.try_as<winrt::Microsoft::ReactNative::Composition::ScrollViewComponentView>()) {
124
+ auto token =
125
+ scrollView.ViewChanged([wkThis = get_weak()](const winrt::IInspectable &, const winrt::IInspectable &) {
126
+ if (auto strongThis = wkThis.get()) {
127
+ strongThis->ParentLayoutChanged();
128
+ }
129
+ });
130
+ m_viewChangedSubscriptions.push_back({scrollView, token});
131
+ }
115
132
 
116
- void ContentIslandComponentView::UnregisterForRootIslandEvents() noexcept {
117
- if (m_islandStateChangedToken) {
118
- m_parentContentIsland.StateChanged(m_islandStateChangedToken);
119
- m_islandStateChangedToken = {};
120
- m_parentContentIsland = nullptr;
133
+ view = view.Parent();
121
134
  }
122
135
  }
123
136
 
124
137
  void ContentIslandComponentView::OnMounted() noexcept {
125
- RegisterForRootIslandEvents();
138
+ m_parentContentIsland = ParentContentIsland();
139
+ ConnectInternal();
126
140
  }
127
141
 
128
142
  void ContentIslandComponentView::OnUnmounted() noexcept {
129
143
  m_layoutMetricChangedRevokers.clear();
144
+
145
+ // Issue #15557: Unsubscribe from parent ScrollView events
146
+ for (auto &subscription : m_viewChangedSubscriptions) {
147
+ if (auto scrollView = subscription.scrollView.get()) {
148
+ scrollView.ViewChanged(subscription.token);
149
+ }
150
+ }
151
+ m_viewChangedSubscriptions.clear();
152
+
130
153
  if (m_navigationHostDepartFocusRequestedToken && m_navigationHost) {
131
154
  m_navigationHost.DepartFocusRequested(m_navigationHostDepartFocusRequestedToken);
132
155
  m_navigationHostDepartFocusRequestedToken = {};
133
156
  }
134
- UnregisterForRootIslandEvents();
135
157
  }
136
158
 
137
159
  void ContentIslandComponentView::ParentLayoutChanged() noexcept {
138
- if (m_layoutChangePosted)
139
- return;
140
-
141
- m_layoutChangePosted = true;
142
- ReactContext().UIDispatcher().Post([wkThis = get_weak()]() {
143
- if (auto strongThis = wkThis.get()) {
144
- auto clientRect = strongThis->getClientRect();
145
-
146
- strongThis->m_childSiteLink.LocalToParentTransformMatrix(
147
- winrt::Windows::Foundation::Numerics::make_float4x4_translation(
148
- static_cast<float>(clientRect.left), static_cast<float>(clientRect.top), 0.0f));
149
-
150
- strongThis->m_layoutChangePosted = false;
151
- }
152
- });
160
+ // Issue #15557: Update transform synchronously to ensure correct popup position
161
+ // when user clicks. Async updates via UIDispatcher().Post() were causing the
162
+ // popup to open with stale transform values.
163
+ //
164
+ // Note: The original async approach was for batching notifications during layout passes.
165
+ // However, LocalToParentTransformMatrix is a cheap call (just sets a matrix), and
166
+ // synchronous updates are required to ensure correct popup position when clicked.
167
+ //
168
+ // getClientRect() returns values in physical pixels (scaled by pointScaleFactor),
169
+ // but LocalToParentTransformMatrix expects logical pixels (DIPs). We need to divide
170
+ // by the scale factor to convert.
171
+ auto clientRect = getClientRect();
172
+ float scaleFactor = m_layoutMetrics.pointScaleFactor;
173
+
174
+ float x = static_cast<float>(clientRect.left) / scaleFactor;
175
+ float y = static_cast<float>(clientRect.top) / scaleFactor;
176
+
177
+ m_childSiteLink.LocalToParentTransformMatrix(
178
+ winrt::Windows::Foundation::Numerics::make_float4x4_translation(x, y, 0.0f));
153
179
  }
154
180
 
155
181
  winrt::Windows::Foundation::IInspectable ContentIslandComponentView::CreateAutomationProvider() noexcept {
@@ -205,7 +231,10 @@ void ContentIslandComponentView::onGotFocus(
205
231
  const winrt::Microsoft::ReactNative::Composition::Input::RoutedEventArgs &args) noexcept {
206
232
  auto gotFocusEventArgs = args.as<winrt::Microsoft::ReactNative::implementation::GotFocusEventArgs>();
207
233
  const auto navigationReason = GetFocusNavigationReason(gotFocusEventArgs->Direction());
208
- m_navigationHost.NavigateFocus(winrt::Microsoft::UI::Input::FocusNavigationRequest::Create(navigationReason));
234
+ if (m_navigationHost)
235
+ m_navigationHost.NavigateFocus(winrt::Microsoft::UI::Input::FocusNavigationRequest::Create(navigationReason));
236
+ else
237
+ m_pendingNavigateFocus = navigationReason;
209
238
  }
210
239
 
211
240
  ContentIslandComponentView::~ContentIslandComponentView() noexcept {
@@ -264,6 +293,9 @@ void ContentIslandComponentView::Connect(const winrt::Microsoft::UI::Content::Co
264
293
  m_childSiteLink.Connect(contentIsland);
265
294
  } else {
266
295
  m_islandToConnect = contentIsland;
296
+ if (isMounted()) {
297
+ ConnectInternal();
298
+ }
267
299
  }
268
300
  }
269
301