react-native-windows 0.82.0-preview.10 → 0.82.0-preview.12

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.
@@ -154,6 +154,9 @@ namespace Microsoft.ReactNative.Composition
154
154
  [webhosthidden]
155
155
  [default_interface]
156
156
  runtimeclass ScrollViewComponentView : ViewComponentView {
157
+ // Issue #15557: Event fired when scroll position changes.
158
+ // ContentIslandComponentView uses this to update LocalToParentTransformMatrix.
159
+ event Windows.Foundation.EventHandler<IInspectable> ViewChanged;
157
160
  };
158
161
 
159
162
  [experimental]
@@ -45,9 +45,16 @@ enum SnapPointsAlignment {
45
45
  void Opacity(Single value);
46
46
  void BlurRadius(Single value);
47
47
  void Color(Windows.UI.Color value);
48
+ void Mask(IBrush mask);
49
+ void SourcePolicy(CompositionDropShadowSourcePolicy policy);
48
50
  }
49
51
 
50
- [webhosthidden][experimental] interface IVisual {
52
+ [webhosthidden][experimental] enum CompositionDropShadowSourcePolicy {
53
+ Default = 0,
54
+ InheritedOnly = 1
55
+ };
56
+
57
+ [webhosthidden][experimental] interface IVisual {
51
58
  void InsertAt(IVisual visual, Int32 index);
52
59
  void Remove(IVisual visual);
53
60
  IVisual GetAt(UInt32 index);
@@ -50,6 +50,7 @@ struct CompositionTypeTraits<WindowsTypeTag> {
50
50
  using CompositionStretch = winrt::Windows::UI::Composition::CompositionStretch;
51
51
  using CompositionStrokeCap = winrt::Windows::UI::Composition::CompositionStrokeCap;
52
52
  using CompositionSurfaceBrush = winrt::Windows::UI::Composition::CompositionSurfaceBrush;
53
+ using CompositionDropShadowSourcePolicy = winrt::Windows::UI::Composition::CompositionDropShadowSourcePolicy;
53
54
  using Compositor = winrt::Windows::UI::Composition::Compositor;
54
55
  using ContainerVisual = winrt::Windows::UI::Composition::ContainerVisual;
55
56
  using CubicBezierEasingFunction = winrt::Windows::UI::Composition::CubicBezierEasingFunction;
@@ -122,6 +123,7 @@ struct CompositionTypeTraits<MicrosoftTypeTag> {
122
123
  using CompositionStretch = winrt::Microsoft::UI::Composition::CompositionStretch;
123
124
  using CompositionStrokeCap = winrt::Microsoft::UI::Composition::CompositionStrokeCap;
124
125
  using CompositionSurfaceBrush = winrt::Microsoft::UI::Composition::CompositionSurfaceBrush;
126
+ using CompositionDropShadowSourcePolicy = winrt::Microsoft::UI::Composition::CompositionDropShadowSourcePolicy;
125
127
  using Compositor = winrt::Microsoft::UI::Composition::Compositor;
126
128
  using ContainerVisual = winrt::Microsoft::UI::Composition::ContainerVisual;
127
129
  using CubicBezierEasingFunction = winrt::Microsoft::UI::Composition::CubicBezierEasingFunction;
@@ -218,6 +220,19 @@ struct CompDropShadow : public winrt::implements<
218
220
  m_shadow.Color(color);
219
221
  }
220
222
 
223
+ void Mask(winrt::Microsoft::ReactNative::Composition::Experimental::IBrush const &mask) noexcept {
224
+ if (mask) {
225
+ m_shadow.Mask(mask.as<typename TTypeRedirects::IInnerCompositionBrush>()->InnerBrush());
226
+ } else {
227
+ m_shadow.Mask(nullptr);
228
+ }
229
+ }
230
+
231
+ void SourcePolicy(
232
+ winrt::Microsoft::ReactNative::Composition::Experimental::CompositionDropShadowSourcePolicy policy) noexcept {
233
+ m_shadow.SourcePolicy(static_cast<typename TTypeRedirects::CompositionDropShadowSourcePolicy>(policy));
234
+ }
235
+
221
236
  private:
222
237
  typename TTypeRedirects::DropShadow m_shadow;
223
238
  };
@@ -36,10 +36,11 @@ constexpr float FOCUS_VISUAL_RADIUS = 3.0f;
36
36
 
37
37
  // m_outerVisual
38
38
  // |
39
- // |
40
39
  // ----- m_visual <-- Background / clip - Can be a custom visual depending on Component type
41
40
  // |
42
41
  // ----- Border Visuals x N (BorderPrimitive attached to m_visual)
42
+ // ----- <children> (default: directly in m_visual after border visuals)
43
+ // ----- m_childrenContainer (created on demand when overflow:hidden, children moved here)
43
44
  // ------Focus Visual Container (created when hosting focus visuals)
44
45
  // |
45
46
  // |------Inner Focus Visual
@@ -708,7 +709,86 @@ void ComponentView::applyShadowProps(const facebook::react::ViewProps &viewProps
708
709
  shadow.Color(theme()->Color(*viewProps.shadowColor));
709
710
  }
710
711
 
711
- Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
712
+ // Check if any border radius is set
713
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(m_layoutMetrics, viewProps);
714
+ bool hasBorderRadius = borderMetrics.borderRadii.topLeft.horizontal != 0 ||
715
+ borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 ||
716
+ borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 ||
717
+ borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 ||
718
+ borderMetrics.borderRadii.bottomRight.vertical != 0;
719
+
720
+ if (hasBorderRadius) {
721
+ // When borderRadius is set, we need to create a shadow mask that follows the rounded rectangle shape.
722
+ // Use CompositionVisualSurface to capture the clipped visual's appearance as the shadow mask.
723
+ bool maskSet = false;
724
+
725
+ // Try Microsoft (WinUI3) Composition first
726
+ auto msCompositor =
727
+ winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerCompositor(
728
+ m_compContext);
729
+ if (msCompositor) {
730
+ auto innerVisual =
731
+ winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual(
732
+ Visual());
733
+ if (innerVisual) {
734
+ // Create a VisualSurface that captures the visual (with its clip applied)
735
+ auto visualSurface = msCompositor.CreateVisualSurface();
736
+ visualSurface.SourceVisual(innerVisual);
737
+ visualSurface.SourceSize(
738
+ {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
739
+ m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
740
+
741
+ // Create a brush from the visual surface to use as shadow mask
742
+ auto maskBrush = msCompositor.CreateSurfaceBrush(visualSurface);
743
+ maskBrush.Stretch(winrt::Microsoft::UI::Composition::CompositionStretch::Fill);
744
+
745
+ // Get the inner shadow and set the mask
746
+ auto innerShadow = winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::
747
+ InnerDropShadow(shadow);
748
+ if (innerShadow) {
749
+ innerShadow.Mask(maskBrush);
750
+ maskSet = true;
751
+ }
752
+ }
753
+ }
754
+
755
+ // Fallback to System (Windows.UI) Composition if Microsoft Composition is not available
756
+ if (!maskSet) {
757
+ auto sysCompositor =
758
+ winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerCompositor(
759
+ m_compContext);
760
+ if (sysCompositor) {
761
+ auto innerVisual =
762
+ winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerVisual(
763
+ Visual());
764
+ if (innerVisual) {
765
+ auto visualSurface = sysCompositor.CreateVisualSurface();
766
+ visualSurface.SourceVisual(innerVisual);
767
+ visualSurface.SourceSize(
768
+ {m_layoutMetrics.frame.size.width * m_layoutMetrics.pointScaleFactor,
769
+ m_layoutMetrics.frame.size.height * m_layoutMetrics.pointScaleFactor});
770
+
771
+ auto maskBrush = sysCompositor.CreateSurfaceBrush(visualSurface);
772
+ maskBrush.Stretch(winrt::Windows::UI::Composition::CompositionStretch::Fill);
773
+
774
+ auto innerShadow =
775
+ winrt::Microsoft::ReactNative::Composition::Experimental::SystemCompositionContextHelper::InnerDropShadow(
776
+ shadow);
777
+ if (innerShadow) {
778
+ innerShadow.Mask(maskBrush);
779
+ }
780
+ }
781
+ }
782
+ }
783
+
784
+ // Apply shadow to OuterVisual (which is not clipped) so the shadow can extend beyond the clip
785
+ OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
786
+ Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
787
+ } else {
788
+ // No border radius - apply shadow directly to Visual (original behavior)
789
+ Visual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(shadow);
790
+ OuterVisual().as<winrt::Microsoft::ReactNative::Composition::Experimental::ISpriteVisual>().Shadow(nullptr);
791
+ }
712
792
  }
713
793
 
714
794
  void ComponentView::updateTransformProps(
@@ -892,23 +972,26 @@ void ComponentView::updateClippingPath(
892
972
  const facebook::react::ViewProps &viewProps) noexcept {
893
973
  auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
894
974
 
895
- if (borderMetrics.borderRadii.topLeft.horizontal == 0 && borderMetrics.borderRadii.topRight.horizontal == 0 &&
896
- borderMetrics.borderRadii.bottomLeft.horizontal == 0 && borderMetrics.borderRadii.bottomRight.horizontal == 0 &&
897
- borderMetrics.borderRadii.topLeft.vertical == 0 && borderMetrics.borderRadii.topRight.vertical == 0 &&
898
- borderMetrics.borderRadii.bottomLeft.vertical == 0 && borderMetrics.borderRadii.bottomRight.vertical == 0) {
899
- Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
900
- } else {
975
+ bool hasRoundedCorners = borderMetrics.borderRadii.topLeft.horizontal != 0 ||
976
+ borderMetrics.borderRadii.topRight.horizontal != 0 || borderMetrics.borderRadii.bottomLeft.horizontal != 0 ||
977
+ borderMetrics.borderRadii.bottomRight.horizontal != 0 || borderMetrics.borderRadii.topLeft.vertical != 0 ||
978
+ borderMetrics.borderRadii.topRight.vertical != 0 || borderMetrics.borderRadii.bottomLeft.vertical != 0 ||
979
+ borderMetrics.borderRadii.bottomRight.vertical != 0;
980
+
981
+ const float scale = layoutMetrics.pointScaleFactor;
982
+ const float viewWidth = layoutMetrics.frame.size.width * scale;
983
+ const float viewHeight = layoutMetrics.frame.size.height * scale;
984
+
985
+ // Apply clipping to m_visual only for rounded corners
986
+ // overflow:hidden clipping is handled separately via m_childrenContainer in ViewComponentView
987
+ if (hasRoundedCorners) {
901
988
  winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
902
- m_compContext,
903
- borderMetrics.borderRadii,
904
- {0, 0, 0, 0},
905
- {0,
906
- 0,
907
- layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
908
- layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
989
+ m_compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, {0, 0, viewWidth, viewHeight});
909
990
 
910
991
  Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
911
992
  pathGeometry.get());
993
+ } else {
994
+ Visual().as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(nullptr);
912
995
  }
913
996
  }
914
997
 
@@ -1083,6 +1166,11 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual
1083
1166
  ViewComponentView::VisualToMountChildrenInto() noexcept {
1084
1167
  if (m_builder && m_builder->VisualToMountChildrenIntoHandler())
1085
1168
  return m_builder->VisualToMountChildrenIntoHandler()(*this);
1169
+ // When overflow:hidden, children are hosted in m_childrenContainer (child of m_visual)
1170
+ // so we can apply clipping without affecting borders/background.
1171
+ // Otherwise children go directly into Visual() (the original behavior).
1172
+ if (m_childrenContainer)
1173
+ return m_childrenContainer;
1086
1174
  return Visual();
1087
1175
  }
1088
1176
 
@@ -1091,9 +1179,14 @@ void ViewComponentView::MountChildComponentView(
1091
1179
  uint32_t index) noexcept {
1092
1180
  base_type::MountChildComponentView(childComponentView, index);
1093
1181
 
1094
- indexOffsetForBorder(index);
1095
1182
  ensureVisual();
1096
1183
 
1184
+ // When children are in Visual() directly, offset past border visuals.
1185
+ // When children are in m_childrenContainer, no offset needed.
1186
+ if (!m_childrenContainer) {
1187
+ indexOffsetForBorder(index);
1188
+ }
1189
+
1097
1190
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1098
1191
  auto visualIndex = index;
1099
1192
  // Most of the time child index will align with visual index.
@@ -1105,6 +1198,7 @@ void ViewComponentView::MountChildComponentView(
1105
1198
  }
1106
1199
  }
1107
1200
  }
1201
+
1108
1202
  VisualToMountChildrenInto().InsertAt(compositionChild->OuterVisual(), visualIndex);
1109
1203
  } else {
1110
1204
  m_hasNonVisualChildren = true;
@@ -1116,7 +1210,6 @@ void ViewComponentView::UnmountChildComponentView(
1116
1210
  uint32_t index) noexcept {
1117
1211
  base_type::UnmountChildComponentView(childComponentView, index);
1118
1212
 
1119
- indexOffsetForBorder(index);
1120
1213
  if (auto compositionChild = childComponentView.try_as<ComponentView>()) {
1121
1214
  VisualToMountChildrenInto().Remove(compositionChild->OuterVisual());
1122
1215
  }
@@ -1316,6 +1409,57 @@ void ViewComponentView::updateLayoutMetrics(
1316
1409
  Visual().Size(
1317
1410
  {layoutMetrics.frame.size.width * layoutMetrics.pointScaleFactor,
1318
1411
  layoutMetrics.frame.size.height * layoutMetrics.pointScaleFactor});
1412
+
1413
+ // Update children container clipping for overflow:hidden
1414
+ updateChildrenClippingPath(layoutMetrics, *viewProps());
1415
+ }
1416
+
1417
+ void ViewComponentView::updateChildrenClippingPath(
1418
+ facebook::react::LayoutMetrics const &layoutMetrics,
1419
+ const facebook::react::ViewProps &viewProps) noexcept {
1420
+ const float scale = layoutMetrics.pointScaleFactor;
1421
+ const float viewWidth = layoutMetrics.frame.size.width * scale;
1422
+ const float viewHeight = layoutMetrics.frame.size.height * scale;
1423
+
1424
+ if (viewProps.getClipsContentToBounds()) {
1425
+ // Create m_childrenContainer on demand (like iOS _containerView pattern)
1426
+ // m_childrenContainer is a child of m_visual, placed after border visuals.
1427
+ if (!m_childrenContainer) {
1428
+ m_childrenContainer = m_compContext.CreateSpriteVisual();
1429
+
1430
+ // Insert at the end of m_visual's children (after border visuals + existing children)
1431
+ // Then move existing children from m_visual into m_childrenContainer
1432
+ uint32_t borderCount = 0;
1433
+ indexOffsetForBorder(borderCount);
1434
+
1435
+ // Move existing child visuals from m_visual to m_childrenContainer
1436
+ uint32_t childVisualIndex = 0;
1437
+ for (auto it = m_children.begin(); it != m_children.end(); ++it) {
1438
+ if (auto compositionChild = (*it).try_as<ComponentView>()) {
1439
+ Visual().Remove(compositionChild->OuterVisual());
1440
+ m_childrenContainer.InsertAt(compositionChild->OuterVisual(), childVisualIndex++);
1441
+ }
1442
+ }
1443
+
1444
+ // Insert m_childrenContainer after border visuals in m_visual
1445
+ Visual().InsertAt(m_childrenContainer, borderCount);
1446
+
1447
+ // Use relative sizing so container automatically tracks parent's size
1448
+ m_childrenContainer.RelativeSizeWithOffset({0, 0}, {1, 1});
1449
+ }
1450
+
1451
+ // Clip children to view bounds using outer border radii (matches iOS default behavior)
1452
+ auto borderMetrics = BorderPrimitive::resolveAndAlignBorderMetrics(layoutMetrics, viewProps);
1453
+ winrt::com_ptr<ID2D1PathGeometry> pathGeometry = BorderPrimitive::GenerateRoundedRectPathGeometry(
1454
+ m_compContext, borderMetrics.borderRadii, {0, 0, 0, 0}, {0, 0, viewWidth, viewHeight});
1455
+
1456
+ m_childrenContainer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1457
+ pathGeometry.get());
1458
+ } else if (m_childrenContainer) {
1459
+ // overflow changed from hidden to visible. Keep container, just remove clip.
1460
+ m_childrenContainer.as<::Microsoft::ReactNative::Composition::Experimental::IVisualInterop>()->SetClippingPath(
1461
+ nullptr);
1462
+ }
1319
1463
  }
1320
1464
 
1321
1465
  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
  };
@@ -14,6 +14,7 @@
14
14
  #include <winrt/Windows.UI.Composition.h>
15
15
  #include "CompositionContextHelper.h"
16
16
  #include "RootComponentView.h"
17
+ #include "ScrollViewComponentView.h"
17
18
 
18
19
  #include "Composition.ContentIslandComponentView.g.cpp"
19
20
 
@@ -49,6 +50,14 @@ void ContentIslandComponentView::OnMounted() noexcept {
49
50
  .as<winrt::Microsoft::UI::Composition::ContainerVisual>());
50
51
  m_childSiteLink.ActualSize({m_layoutMetrics.frame.size.width, m_layoutMetrics.frame.size.height});
51
52
 
53
+ // Issue #15557: Set initial LocalToParentTransformMatrix synchronously before Connect.
54
+ // This fixes popup position being wrong even without scrolling.
55
+ // Note: getClientRect() returns physical pixels, but LocalToParentTransformMatrix expects DIPs.
56
+ auto clientRect = getClientRect();
57
+ float scaleFactor = m_layoutMetrics.pointScaleFactor;
58
+ m_childSiteLink.LocalToParentTransformMatrix(winrt::Windows::Foundation::Numerics::make_float4x4_translation(
59
+ static_cast<float>(clientRect.left) / scaleFactor, static_cast<float>(clientRect.top) / scaleFactor, 0.0f));
60
+
52
61
  m_navigationHost = winrt::Microsoft::UI::Input::InputFocusNavigationHost::GetForSiteLink(m_childSiteLink);
53
62
 
54
63
  m_navigationHostDepartFocusRequestedToken =
@@ -80,12 +89,34 @@ void ContentIslandComponentView::OnMounted() noexcept {
80
89
  strongThis->ParentLayoutChanged();
81
90
  }
82
91
  }));
92
+
93
+ // Issue #15557: Register for ViewChanged on parent ScrollViews to update transform
94
+ // when scroll position changes, ensuring correct XAML popup positioning.
95
+ if (auto scrollView = view.try_as<winrt::Microsoft::ReactNative::Composition::ScrollViewComponentView>()) {
96
+ auto token =
97
+ scrollView.ViewChanged([wkThis = get_weak()](const winrt::IInspectable &, const winrt::IInspectable &) {
98
+ if (auto strongThis = wkThis.get()) {
99
+ strongThis->ParentLayoutChanged();
100
+ }
101
+ });
102
+ m_viewChangedSubscriptions.push_back({scrollView, token});
103
+ }
104
+
83
105
  view = view.Parent();
84
106
  }
85
107
  }
86
108
 
87
109
  void ContentIslandComponentView::OnUnmounted() noexcept {
88
110
  m_layoutMetricChangedRevokers.clear();
111
+
112
+ // Issue #15557: Unsubscribe from parent ScrollView events
113
+ for (auto &subscription : m_viewChangedSubscriptions) {
114
+ if (auto scrollView = subscription.scrollView.get()) {
115
+ scrollView.ViewChanged(subscription.token);
116
+ }
117
+ }
118
+ m_viewChangedSubscriptions.clear();
119
+
89
120
  if (m_navigationHostDepartFocusRequestedToken && m_navigationHost) {
90
121
  m_navigationHost.DepartFocusRequested(m_navigationHostDepartFocusRequestedToken);
91
122
  m_navigationHostDepartFocusRequestedToken = {};
@@ -93,21 +124,25 @@ void ContentIslandComponentView::OnUnmounted() noexcept {
93
124
  }
94
125
 
95
126
  void ContentIslandComponentView::ParentLayoutChanged() noexcept {
96
- if (m_layoutChangePosted)
97
- return;
98
-
99
- m_layoutChangePosted = true;
100
- ReactContext().UIDispatcher().Post([wkThis = get_weak()]() {
101
- if (auto strongThis = wkThis.get()) {
102
- auto clientRect = strongThis->getClientRect();
103
-
104
- strongThis->m_childSiteLink.LocalToParentTransformMatrix(
105
- winrt::Windows::Foundation::Numerics::make_float4x4_translation(
106
- static_cast<float>(clientRect.left), static_cast<float>(clientRect.top), 0.0f));
107
-
108
- strongThis->m_layoutChangePosted = false;
109
- }
110
- });
127
+ // Issue #15557: Update transform synchronously to ensure correct popup position
128
+ // when user clicks. Async updates via UIDispatcher().Post() were causing the
129
+ // popup to open with stale transform values.
130
+ //
131
+ // Note: The original async approach was for batching notifications during layout passes.
132
+ // However, LocalToParentTransformMatrix is a cheap call (just sets a matrix), and
133
+ // synchronous updates are required to ensure correct popup position when clicked.
134
+ //
135
+ // getClientRect() returns values in physical pixels (scaled by pointScaleFactor),
136
+ // but LocalToParentTransformMatrix expects logical pixels (DIPs). We need to divide
137
+ // by the scale factor to convert.
138
+ auto clientRect = getClientRect();
139
+ float scaleFactor = m_layoutMetrics.pointScaleFactor;
140
+
141
+ float x = static_cast<float>(clientRect.left) / scaleFactor;
142
+ float y = static_cast<float>(clientRect.top) / scaleFactor;
143
+
144
+ m_childSiteLink.LocalToParentTransformMatrix(
145
+ winrt::Windows::Foundation::Numerics::make_float4x4_translation(x, y, 0.0f));
111
146
  }
112
147
 
113
148
  winrt::Windows::Foundation::IInspectable ContentIslandComponentView::CreateAutomationProvider() noexcept {
@@ -68,6 +68,13 @@ struct ContentIslandComponentView : ContentIslandComponentViewT<ContentIslandCom
68
68
  winrt::Microsoft::UI::Input::InputFocusNavigationHost m_navigationHost{nullptr};
69
69
  winrt::event_token m_navigationHostDepartFocusRequestedToken{};
70
70
 
71
+ // Issue #15557: Store ViewChanged subscriptions to parent ScrollViews for transform updates
72
+ struct ViewChangedSubscription {
73
+ winrt::weak_ref<winrt::Microsoft::ReactNative::Composition::ScrollViewComponentView> scrollView;
74
+ winrt::event_token token;
75
+ };
76
+ std::vector<ViewChangedSubscription> m_viewChangedSubscriptions;
77
+
71
78
  // Automation
72
79
  void ConfigureChildSiteLinkAutomation() noexcept;
73
80
  };
@@ -6,6 +6,7 @@
6
6
 
7
7
  #include "ScrollViewComponentView.h"
8
8
 
9
+ #include <Fabric/ComponentView.h>
9
10
  #include <Utils/ValueUtils.h>
10
11
 
11
12
  #pragma warning(push)
@@ -19,6 +20,8 @@
19
20
  #include <AutoDraw.h>
20
21
  #include <Fabric/DWriteHelpers.h>
21
22
  #include <unicode.h>
23
+ #include <functional>
24
+ #include "ContentIslandComponentView.h"
22
25
  #include "JSValueReader.h"
23
26
  #include "RootComponentView.h"
24
27
 
@@ -884,6 +887,13 @@ void ScrollViewComponentView::updateContentVisualSize() noexcept {
884
887
 
885
888
  void ScrollViewComponentView::prepareForRecycle() noexcept {}
886
889
 
890
+ void ScrollViewComponentView::updateChildrenClippingPath(
891
+ facebook::react::LayoutMetrics const & /*layoutMetrics*/,
892
+ const facebook::react::ViewProps & /*viewProps*/) noexcept {
893
+ // No-op: ScrollView mounts children into m_scrollVisual (not Visual()),
894
+ // and scroll visuals inherently clip their content.
895
+ }
896
+
887
897
  /*
888
898
  ScrollViewComponentView::ScrollInteractionTrackerOwner::ScrollInteractionTrackerOwner(
889
899
  ScrollViewComponentView *outer)
@@ -1325,6 +1335,10 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1325
1335
  m_allowNextScrollNoMatterWhat = false;
1326
1336
  }
1327
1337
  }
1338
+
1339
+ // Issue #15557: Notify listeners that scroll position has changed,
1340
+ // so ContentIslandComponentView can update LocalToParentTransformMatrix
1341
+ FireViewChanged();
1328
1342
  });
1329
1343
 
1330
1344
  m_scrollBeginDragRevoker = m_scrollVisual.ScrollBeginDrag(
@@ -1332,6 +1346,9 @@ winrt::Microsoft::ReactNative::Composition::Experimental::IVisual ScrollViewComp
1332
1346
  [this](
1333
1347
  winrt::IInspectable const & /*sender*/,
1334
1348
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) {
1349
+ // Issue #15557: Notify listeners that scroll position has changed
1350
+ FireViewChanged();
1351
+
1335
1352
  m_allowNextScrollNoMatterWhat = true; // Ensure next scroll event is recorded, regardless of throttle
1336
1353
  updateStateWithContentOffset();
1337
1354
  auto eventEmitter = GetEventEmitter();
@@ -1478,4 +1495,20 @@ void ScrollViewComponentView::updateShowsVerticalScrollIndicator(bool value) noe
1478
1495
  void ScrollViewComponentView::updateDecelerationRate(float value) noexcept {
1479
1496
  m_scrollVisual.SetDecelerationRate({value, value, value});
1480
1497
  }
1498
+
1499
+ // Issue #15557: Notify listeners that scroll position has changed.
1500
+ // ContentIslandComponentView subscribes to this to update LocalToParentTransformMatrix.
1501
+ void ScrollViewComponentView::FireViewChanged() noexcept {
1502
+ m_viewChangedEvent(*this, nullptr);
1503
+ }
1504
+
1505
+ // Issue #15557: Event accessors for ViewChanged
1506
+ winrt::event_token ScrollViewComponentView::ViewChanged(
1507
+ winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable> const &handler) noexcept {
1508
+ return m_viewChangedEvent.add(handler);
1509
+ }
1510
+
1511
+ void ScrollViewComponentView::ViewChanged(winrt::event_token const &token) noexcept {
1512
+ m_viewChangedEvent.remove(token);
1513
+ }
1481
1514
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -118,6 +118,18 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
118
118
  double getVerticalSize() noexcept;
119
119
  double getHorizontalSize() noexcept;
120
120
 
121
+ // Issue #15557: Event accessors for ViewChanged (used by ContentIslandComponentView for transform update)
122
+ winrt::event_token ViewChanged(
123
+ winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable> const &handler) noexcept;
124
+ void ViewChanged(winrt::event_token const &token) noexcept;
125
+
126
+ protected:
127
+ // ScrollView mounts children into m_scrollVisual (not Visual()), and scroll visuals
128
+ // inherently clip their content, so we skip the children container clipping logic.
129
+ void updateChildrenClippingPath(
130
+ facebook::react::LayoutMetrics const &layoutMetrics,
131
+ const facebook::react::ViewProps &viewProps) noexcept override;
132
+
121
133
  private:
122
134
  void updateDecelerationRate(float value) noexcept;
123
135
  void updateContentVisualSize() noexcept;
@@ -129,6 +141,8 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
129
141
  bool scrollRight(float delta, bool animate) noexcept;
130
142
  void updateBackgroundColor(const facebook::react::SharedColor &color) noexcept;
131
143
  void updateStateWithContentOffset() noexcept;
144
+ // Issue #15557: Notify listeners that scroll position has changed
145
+ void FireViewChanged() noexcept;
132
146
  facebook::react::ScrollViewEventEmitter::Metrics getScrollMetrics(
133
147
  facebook::react::SharedViewEventEmitter const &eventEmitter,
134
148
  winrt::Microsoft::ReactNative::Composition::Experimental::IScrollPositionChangedArgs const &args) noexcept;
@@ -160,6 +174,9 @@ struct ScrollInteractionTrackerOwner : public winrt::implements<
160
174
  bool m_allowNextScrollNoMatterWhat{false};
161
175
  std::chrono::steady_clock::time_point m_lastScrollEventTime{};
162
176
  std::shared_ptr<facebook::react::ScrollViewShadowNode::ConcreteState const> m_state;
177
+
178
+ // Issue #15557: Event for notifying listeners when scroll position changes
179
+ winrt::event<winrt::Windows::Foundation::EventHandler<winrt::Windows::Foundation::IInspectable>> m_viewChangedEvent;
163
180
  };
164
181
 
165
182
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -1227,8 +1227,11 @@ void WindowsTextInputComponentView::updateState(
1227
1227
  if (m_mostRecentEventCount == m_state->getData().mostRecentEventCount) {
1228
1228
  m_comingFromState = true;
1229
1229
  auto &fragments = m_state->getData().attributedStringBox.getValue().getFragments();
1230
- UpdateText(fragments.size() ? fragments[0].string : "");
1231
-
1230
+ {
1231
+ // DrawBlock defers DrawText() until after UpdateText completes
1232
+ DrawBlock db(*this);
1233
+ UpdateText(fragments.size() ? fragments[0].string : "");
1234
+ }
1232
1235
  m_comingFromState = false;
1233
1236
  }
1234
1237
  }
@@ -1377,7 +1380,7 @@ void WindowsTextInputComponentView::EmitOnScrollEvent() noexcept {
1377
1380
  }
1378
1381
 
1379
1382
  void WindowsTextInputComponentView::OnSelectionChanged(LONG start, LONG end) noexcept {
1380
- if (m_eventEmitter && !m_comingFromState /* && !m_comingFromJS ?? */) {
1383
+ if (m_eventEmitter && !m_comingFromState && !m_comingFromJS) {
1381
1384
  auto emitter = std::static_pointer_cast<const facebook::react::WindowsTextInputEventEmitter>(m_eventEmitter);
1382
1385
  facebook::react::WindowsTextInputEventEmitter::OnSelectionChange onSelectionChangeArgs;
1383
1386
  onSelectionChangeArgs.selection.start = start;
@@ -10,11 +10,11 @@
10
10
  -->
11
11
  <Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
12
12
  <PropertyGroup>
13
- <ReactNativeWindowsVersion>0.82.0-preview.10</ReactNativeWindowsVersion>
13
+ <ReactNativeWindowsVersion>0.82.0-preview.12</ReactNativeWindowsVersion>
14
14
  <ReactNativeWindowsMajor>0</ReactNativeWindowsMajor>
15
15
  <ReactNativeWindowsMinor>82</ReactNativeWindowsMinor>
16
16
  <ReactNativeWindowsPatch>0</ReactNativeWindowsPatch>
17
17
  <ReactNativeWindowsCanary>false</ReactNativeWindowsCanary>
18
- <ReactNativeWindowsCommitId>8dab2200781d9264a8a3ad08005d3a04b44e7c18</ReactNativeWindowsCommitId>
18
+ <ReactNativeWindowsCommitId>5c806505f373a3b5161faef635f560ebb53fa2a1</ReactNativeWindowsCommitId>
19
19
  </PropertyGroup>
20
20
  </Project>
@@ -10,7 +10,7 @@
10
10
  <WinUI3ExperimentalVersion Condition="'$(WinUI3ExperimentalVersion)'==''">2.0.0-experimental3</WinUI3ExperimentalVersion>
11
11
  <!-- This value is also used by the CLI, see /packages/@react-native-windows/cli/.../autolinkWindows.ts -->
12
12
  <WinUI3Version Condition="'$(WinUI3Version)'=='' AND '$(UseExperimentalWinUI3)'=='true'">$(WinUI3ExperimentalVersion)</WinUI3Version>
13
- <WinUI3Version Condition="'$(WinUI3Version)'==''">1.8.251106002</WinUI3Version>
13
+ <WinUI3Version Condition="'$(WinUI3Version)'==''">1.8.260209005</WinUI3Version>
14
14
  <!-- This is needed to prevent build errors with WinAppSDK >= 1.7 trying to double build WindowsAppRuntimeAutoInitializer.cpp -->
15
15
  <WindowsAppSdkAutoInitialize Condition="'$(WindowsAppSdkAutoInitialize)'=='' And $([MSBuild]::VersionGreaterThan('$(WinUI3Version)', '1.7.0'))">false</WindowsAppSdkAutoInitialize>
16
16
  </PropertyGroup>
@@ -20,23 +20,17 @@ try {
20
20
  $packagesSolutions = (Get-ChildItem -File -Recurse -Path $RepoRoot\packages -Filter *.sln ) | Where-Object { !$_.FullName.Contains('node_modules') -and !$_.FullName.Contains('e2etest') }
21
21
  $vnextSolutions = (Get-ChildItem -File -Path $RepoRoot\vnext -Filter *.sln)
22
22
 
23
- # Run all solutions for each platform to ensure all projects are restored
24
- # (some projects are only configured for specific platforms)
25
- $platforms = @("x64","x86","ARM64")
23
+ # Run all solutions with their defaults
26
24
  $($packagesSolutions; $vnextSolutions) | Foreach-Object {
27
- foreach ($platform in $platforms) {
28
- Write-Host Restoring $_.FullName with Platform=$platform
29
- & msbuild /t:Restore /p:RestoreForceEvaluate=true /p:Platform=$platform $_.FullName
30
- }
25
+ Write-Host Restoring $_.FullName with defaults
26
+ & msbuild /t:Restore /p:RestoreForceEvaluate=true $_.FullName
31
27
  }
32
28
 
33
29
  # Re-run solutions that build with UseExperimentalWinUI3
34
30
  $experimentalSolutions = @("playground-composition.sln", "Microsoft.ReactNative.NewArch.sln", "ReactWindows-Desktop.sln");
35
31
  $($packagesSolutions; $vnextSolutions) | Where-Object { $experimentalSolutions -contains $_.Name } | Foreach-Object {
36
- foreach ($platform in $platforms) {
37
- Write-Host Restoring $_.FullName with UseExperimentalWinUI3=true Platform=$platform
38
- & msbuild /t:Restore /p:RestoreForceEvaluate=true /p:UseExperimentalWinUI3=true /p:Platform=$platform $_.FullName
39
- }
32
+ Write-Host Restoring $_.FullName with UseExperimentalWinUI3=true
33
+ & msbuild /t:Restore /p:RestoreForceEvaluate=true /p:UseExperimentalWinUI3=true $_.FullName
40
34
  }
41
35
  }
42
36
  finally {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-windows",
3
- "version": "0.82.0-preview.10",
3
+ "version": "0.82.0-preview.12",
4
4
  "license": "MIT",
5
5
  "repository": {
6
6
  "type": "git",