react-native-windows 0.82.0-preview.1 → 0.82.0-preview.11

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 (63) hide show
  1. package/Libraries/Animated/nodes/AnimatedValue.js +0 -8
  2. package/Libraries/BatchedBridge/BatchedBridge.js +1 -0
  3. package/Libraries/BatchedBridge/MessageQueue.js +1 -0
  4. package/Libraries/Components/Switch/Switch.js +1 -1
  5. package/Libraries/Components/Switch/Switch.windows.js +1 -1
  6. package/Libraries/Core/ReactNativeVersion.js +2 -2
  7. package/Libraries/Core/Timers/JSTimers.js +1 -0
  8. package/Libraries/Core/Timers/NativeTiming.js +1 -0
  9. package/Libraries/Core/Timers/immediateShim.js +1 -0
  10. package/Libraries/Core/setUpPerformance.js +3 -5
  11. package/Libraries/Interaction/PanResponder.js +6 -51
  12. package/Microsoft.ReactNative/ComponentView.idl +2 -0
  13. package/Microsoft.ReactNative/Composition.Input.idl +7 -0
  14. package/Microsoft.ReactNative/CompositionComponentView.idl +3 -0
  15. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +18 -0
  16. package/Microsoft.ReactNative/Fabric/ComponentView.h +9 -0
  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/CompositionViewComponentView.cpp +84 -17
  22. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +4 -0
  23. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +56 -82
  24. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +7 -4
  25. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +82 -14
  26. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.h +11 -4
  27. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +33 -0
  28. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +17 -0
  29. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +59 -31
  30. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +3 -0
  31. package/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp +42 -15
  32. package/Microsoft.ReactNative.Cxx/ReactCommon/react/timing/primitives.h +12 -0
  33. package/PropertySheets/Generated/PackageVersion.g.props +2 -2
  34. package/PropertySheets/Warnings.props +1 -2
  35. package/PropertySheets/WinUI.props +1 -1
  36. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/text/BaseParagraphProps.cpp +174 -0
  37. package/ReactCommon/TEMP_UntilReactCommonUpdate/react/renderer/components/text/BaseParagraphProps.h +69 -0
  38. package/Scripts/NuGetRestoreForceEvaluateAllSolutions.ps1 +5 -11
  39. package/Scripts/rnw-dependencies.ps1 +15 -1
  40. package/Shared/Shared.vcxitems +1 -0
  41. package/Shared/Shared.vcxitems.filters +1 -3
  42. package/codegen/NativePerformanceSpec.g.h +41 -35
  43. package/codegen/NativeReactNativeFeatureFlagsSpec.g.h +55 -49
  44. package/codegen/rnwcoreJSI-generated.cpp +434 -422
  45. package/codegen/rnwcoreJSI.h +18 -0
  46. package/index.js +6 -0
  47. package/index.windows.js +6 -0
  48. package/package.json +15 -14
  49. package/src/private/featureflags/ReactNativeFeatureFlags.js +6 -1
  50. package/src/private/featureflags/specs/NativeReactNativeFeatureFlags.js +2 -1
  51. package/src/private/setup/{setUpPerformanceObserver.js → setUpPerformanceModern.js} +43 -18
  52. package/src/private/specs_DEPRECATED/components/SwitchNativeComponent.js +1 -0
  53. package/src/private/specs_DEPRECATED/modules/NativeTiming.js +1 -0
  54. package/src/private/webapis/performance/EventTiming.js +34 -15
  55. package/src/private/webapis/performance/LongTasks.js +35 -2
  56. package/src/private/webapis/performance/Performance.js +49 -13
  57. package/src/private/webapis/performance/PerformanceEntry.js +21 -8
  58. package/src/private/webapis/performance/PerformanceObserver.js +30 -1
  59. package/src/private/webapis/performance/ReactNativeStartupTiming.js +3 -24
  60. package/src/private/webapis/performance/ResourceTiming.js +29 -18
  61. package/src/private/webapis/performance/UserTiming.js +33 -28
  62. package/src/private/webapis/performance/internals/RawPerformanceEntry.js +3 -4
  63. package/src/private/webapis/performance/specs/NativePerformance.js +2 -0
@@ -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 {
@@ -171,24 +206,6 @@ ContentIslandComponentView::~ContentIslandComponentView() noexcept {
171
206
  m_navigationHost.DepartFocusRequested(m_navigationHostDepartFocusRequestedToken);
172
207
  m_navigationHostDepartFocusRequestedToken = {};
173
208
  }
174
- if (m_childSiteLink) {
175
- if (m_fragmentRootAutomationProviderRequestedToken) {
176
- m_childSiteLink.FragmentRootAutomationProviderRequested(m_fragmentRootAutomationProviderRequestedToken);
177
- m_fragmentRootAutomationProviderRequestedToken = {};
178
- }
179
- if (m_parentAutomationProviderRequestedToken) {
180
- m_childSiteLink.ParentAutomationProviderRequested(m_parentAutomationProviderRequestedToken);
181
- m_parentAutomationProviderRequestedToken = {};
182
- }
183
- if (m_nextSiblingAutomationProviderRequestedToken) {
184
- m_childSiteLink.NextSiblingAutomationProviderRequested(m_nextSiblingAutomationProviderRequestedToken);
185
- m_nextSiblingAutomationProviderRequestedToken = {};
186
- }
187
- if (m_previousSiblingAutomationProviderRequestedToken) {
188
- m_childSiteLink.PreviousSiblingAutomationProviderRequested(m_previousSiblingAutomationProviderRequestedToken);
189
- m_previousSiblingAutomationProviderRequestedToken = {};
190
- }
191
- }
192
209
  if (m_islandToConnect) {
193
210
  m_islandToConnect.Close();
194
211
  }
@@ -230,56 +247,13 @@ void ContentIslandComponentView::prepareForRecycle() noexcept {
230
247
  }
231
248
 
232
249
  void ContentIslandComponentView::ConfigureChildSiteLinkAutomation() noexcept {
233
- // This automation mode must be set before connecting the child ContentIsland.
234
- // It puts the child content into a mode where it won't own its own framework root. Instead, the child island's
235
- // automation peers will use the same framework root as the automation peer of this ContentIslandComponentView.
236
- m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FragmentBased);
237
-
238
- // These events are raised in response to the child ContentIsland asking for providers.
239
- // For example, the ContentIsland.FragmentRootAutomationProvider property will return
240
- // the provider we provide here in FragmentRootAutomationProviderRequested.
241
-
242
- // We capture "this" as a raw pointer because ContentIslandComponentView doesn't currently support weak ptrs.
243
- // It's safe because we disconnect these events in the destructor.
244
-
245
- m_fragmentRootAutomationProviderRequestedToken = m_childSiteLink.FragmentRootAutomationProviderRequested(
246
- [this](
247
- const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
248
- const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
249
- // The child island's fragment tree doesn't have its own fragment root.
250
- // Here's how we can provide the correct fragment root to the child's UIA logic.
251
- winrt::com_ptr<IRawElementProviderFragmentRoot> fragmentRoot{nullptr};
252
- auto uiaProvider = this->EnsureUiaProvider();
253
- uiaProvider.as<IRawElementProviderFragment>()->get_FragmentRoot(fragmentRoot.put());
254
- args.AutomationProvider(fragmentRoot.as<IInspectable>());
255
- args.Handled(true);
256
- });
257
-
258
- m_parentAutomationProviderRequestedToken = m_childSiteLink.ParentAutomationProviderRequested(
259
- [this](
260
- const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
261
- const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
262
- auto uiaProvider = this->EnsureUiaProvider();
263
- args.AutomationProvider(uiaProvider);
264
- args.Handled(true);
265
- });
266
-
267
- m_nextSiblingAutomationProviderRequestedToken = m_childSiteLink.NextSiblingAutomationProviderRequested(
268
- [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
269
- const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
270
- // The ContentIsland will always be the one and only child of this node, so it won't have siblings.
271
- args.AutomationProvider(nullptr);
272
- args.Handled(true);
273
- });
274
-
275
- m_previousSiblingAutomationProviderRequestedToken = m_childSiteLink.PreviousSiblingAutomationProviderRequested(
276
- [](const winrt::Microsoft::UI::Content::IContentSiteAutomation &,
277
- const winrt::Microsoft::UI::Content::ContentSiteAutomationProviderRequestedEventArgs &args) {
278
- // The ContentIsland will always be the one and only child of this node, so it won't have siblings.
279
- args.AutomationProvider(nullptr);
280
- args.Handled(true);
281
- });
250
+ // Use FrameworkBased to let the XamlIsland manage its own framework-level accessibility tree
251
+ // and raise focus events naturally. This tells the system that the child island has its own
252
+ // framework (WinUI/XAML) that manages automation.
253
+ m_childSiteLink.AutomationOption(winrt::Microsoft::UI::Content::ContentAutomationOptions::FrameworkBased);
282
254
 
255
+ // When using FrameworkBased mode, we don't register automation callbacks - let the XamlIsland handle its own UIA
256
+ // tree.
283
257
  if (m_innerAutomationProvider) {
284
258
  m_innerAutomationProvider->SetChildSiteLink(m_childSiteLink);
285
259
  }
@@ -68,12 +68,15 @@ 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
- winrt::event_token m_fragmentRootAutomationProviderRequestedToken{};
74
- winrt::event_token m_parentAutomationProviderRequestedToken{};
75
- winrt::event_token m_nextSiblingAutomationProviderRequestedToken{};
76
- winrt::event_token m_previousSiblingAutomationProviderRequestedToken{};
77
80
  };
78
81
 
79
82
  } // namespace winrt::Microsoft::ReactNative::Composition::implementation
@@ -107,6 +107,10 @@ void ParagraphComponentView::updateProps(
107
107
  m_requireRedraw = true;
108
108
  }
109
109
 
110
+ if (oldViewProps.selectionColor != newViewProps.selectionColor) {
111
+ m_requireRedraw = true;
112
+ }
113
+
110
114
  Super::updateProps(props, oldProps);
111
115
  }
112
116
 
@@ -167,6 +171,26 @@ void ParagraphComponentView::updateTextAlignment(
167
171
  m_textLayout = nullptr;
168
172
  }
169
173
 
174
+ facebook::react::Tag ParagraphComponentView::hitTest(
175
+ facebook::react::Point pt,
176
+ facebook::react::Point &localPt,
177
+ bool ignorePointerEvents) const noexcept {
178
+ facebook::react::Point ptLocal{pt.x - m_layoutMetrics.frame.origin.x, pt.y - m_layoutMetrics.frame.origin.y};
179
+ const auto &props = paragraphProps();
180
+ const auto &vProps = *viewProps();
181
+
182
+ if (props.isSelectable && ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
183
+ ptLocal.y <= m_layoutMetrics.frame.size.height) {
184
+ // claims if pointer events are enabled for this component
185
+ if (ignorePointerEvents || vProps.pointerEvents == facebook::react::PointerEventsMode::Auto ||
186
+ vProps.pointerEvents == facebook::react::PointerEventsMode::BoxOnly) {
187
+ localPt = ptLocal;
188
+ return Tag();
189
+ }
190
+ }
191
+ return Super::hitTest(pt, localPt, ignorePointerEvents);
192
+ }
193
+
170
194
  bool ParagraphComponentView::IsTextSelectableAtPoint(facebook::react::Point pt) noexcept {
171
195
  // paragraph-level selectable prop is enabled
172
196
  const auto &props = paragraphProps();
@@ -454,9 +478,14 @@ void ParagraphComponentView::DrawSelectionHighlight(
454
478
  return;
455
479
  }
456
480
 
457
- // TODO: use prop selectionColor if provided
458
481
  winrt::com_ptr<ID2D1SolidColorBrush> selectionBrush;
459
- const D2D1_COLOR_F selectionColor = theme()->D2DPlatformColor("Highlight@40");
482
+ D2D1_COLOR_F selectionColor;
483
+ const auto &props = paragraphProps();
484
+ if (props.selectionColor) {
485
+ selectionColor = theme()->D2DColor(**props.selectionColor);
486
+ } else {
487
+ selectionColor = theme()->D2DPlatformColor("Highlight@40");
488
+ }
460
489
  hr = renderTarget.CreateSolidColorBrush(selectionColor, selectionBrush.put());
461
490
 
462
491
  if (FAILED(hr)) {
@@ -519,6 +548,7 @@ void ParagraphComponentView::ClearSelection() noexcept {
519
548
  m_selectionStart = std::nullopt;
520
549
  m_selectionEnd = std::nullopt;
521
550
  m_isSelecting = false;
551
+ m_isWordSelecting = false;
522
552
  if (hadSelection) {
523
553
  // Clears selection highlight
524
554
  DrawText();
@@ -534,7 +564,8 @@ void ParagraphComponentView::OnPointerPressed(
534
564
  return;
535
565
  }
536
566
 
537
- auto pp = args.GetCurrentPoint(-1);
567
+ // Use Tag() to get coordinates in component's local space
568
+ auto pp = args.GetCurrentPoint(static_cast<int32_t>(Tag()));
538
569
 
539
570
  // Ignores right-click
540
571
  if (pp.Properties().PointerUpdateKind() ==
@@ -545,8 +576,8 @@ void ParagraphComponentView::OnPointerPressed(
545
576
 
546
577
  auto position = pp.Position();
547
578
 
548
- facebook::react::Point localPt{
549
- position.X - m_layoutMetrics.frame.origin.x, position.Y - m_layoutMetrics.frame.origin.y};
579
+ // GetCurrentPoint(Tag()) returns position relative to component origin
580
+ facebook::react::Point localPt{position.X, position.Y};
550
581
 
551
582
  std::optional<int32_t> charPosition = GetTextPositionAtPoint(localPt);
552
583
 
@@ -568,7 +599,13 @@ void ParagraphComponentView::OnPointerPressed(
568
599
 
569
600
  if (isDoubleClick) {
570
601
  SelectWordAtPosition(*charPosition);
571
- m_isSelecting = false;
602
+ if (m_selectionStart && m_selectionEnd) {
603
+ m_isWordSelecting = true;
604
+ m_wordAnchorStart = *m_selectionStart;
605
+ m_wordAnchorEnd = *m_selectionEnd;
606
+ m_isSelecting = true;
607
+ CapturePointer(args.Pointer());
608
+ }
572
609
  } else {
573
610
  // Single-click: start drag selection
574
611
  m_selectionStart = charPosition;
@@ -610,17 +647,35 @@ void ParagraphComponentView::OnPointerMoved(
610
647
  facebook::react::Point localPt{position.X, position.Y};
611
648
  std::optional<int32_t> charPosition = GetClampedTextPosition(localPt);
612
649
 
613
- if (charPosition && charPosition != m_selectionEnd) {
614
- m_selectionEnd = charPosition;
615
- DrawText();
616
- args.Handled(true);
650
+ if (charPosition) {
651
+ if (m_isWordSelecting) {
652
+ // Extend selection by whole words
653
+ auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(*charPosition);
654
+
655
+ if (*charPosition < m_wordAnchorStart) {
656
+ m_selectionStart = wordStart;
657
+ m_selectionEnd = m_wordAnchorEnd;
658
+ } else if (*charPosition >= m_wordAnchorEnd) {
659
+ m_selectionStart = m_wordAnchorStart;
660
+ m_selectionEnd = wordEnd;
661
+ } else {
662
+ m_selectionStart = m_wordAnchorStart;
663
+ m_selectionEnd = m_wordAnchorEnd;
664
+ }
665
+ DrawText();
666
+ args.Handled(true);
667
+ } else if (charPosition != m_selectionEnd) {
668
+ m_selectionEnd = charPosition;
669
+ DrawText();
670
+ args.Handled(true);
671
+ }
617
672
  }
618
673
  }
619
674
 
620
675
  void ParagraphComponentView::OnPointerReleased(
621
676
  const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept {
622
677
  // Check for right-click to show context menu
623
- auto pp = args.GetCurrentPoint(-1);
678
+ auto pp = args.GetCurrentPoint(static_cast<int32_t>(Tag()));
624
679
  if (pp.Properties().PointerUpdateKind() ==
625
680
  winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::RightButtonReleased) {
626
681
  const auto &props = paragraphProps();
@@ -637,6 +692,7 @@ void ParagraphComponentView::OnPointerReleased(
637
692
  }
638
693
 
639
694
  m_isSelecting = false;
695
+ m_isWordSelecting = false;
640
696
 
641
697
  ReleasePointerCapture(args.Pointer());
642
698
 
@@ -661,6 +717,7 @@ void ParagraphComponentView::OnPointerCaptureLost() noexcept {
661
717
  // Pointer capture was lost stop any active selection drag
662
718
  if (m_isSelecting) {
663
719
  m_isSelecting = false;
720
+ m_isWordSelecting = false;
664
721
 
665
722
  if (!m_selectionStart || !m_selectionEnd || *m_selectionStart == *m_selectionEnd) {
666
723
  m_selectionStart = std::nullopt;
@@ -711,12 +768,17 @@ void ParagraphComponentView::CopySelectionToClipboard() noexcept {
711
768
  winrt::Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(dataPackage);
712
769
  }
713
770
 
714
- void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
771
+ std::pair<int32_t, int32_t> ParagraphComponentView::GetWordBoundariesAtPosition(int32_t charPosition) noexcept {
715
772
  const std::wstring utf16Text{facebook::react::WindowsTextLayoutManager::GetTransformedText(m_attributedStringBox)};
716
773
  const int32_t textLength = static_cast<int32_t>(utf16Text.length());
717
774
 
718
- if (utf16Text.empty() || charPosition < 0 || charPosition >= textLength) {
719
- return;
775
+ if (utf16Text.empty() || charPosition < 0) {
776
+ return {0, 0};
777
+ }
778
+
779
+ charPosition = std::min(charPosition, textLength - 1);
780
+ if (charPosition < 0) {
781
+ return {0, 0};
720
782
  }
721
783
 
722
784
  int32_t wordStart = charPosition;
@@ -749,6 +811,12 @@ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept
749
811
  }
750
812
  }
751
813
 
814
+ return {wordStart, wordEnd};
815
+ }
816
+
817
+ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
818
+ auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(charPosition);
819
+
752
820
  if (wordEnd > wordStart) {
753
821
  SetSelection(wordStart, wordEnd);
754
822
  DrawText();
@@ -49,6 +49,11 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
49
49
  static facebook::react::SharedViewProps defaultProps() noexcept;
50
50
  const facebook::react::ParagraphProps &paragraphProps() const noexcept;
51
51
 
52
+ facebook::react::Tag hitTest(
53
+ facebook::react::Point pt,
54
+ facebook::react::Point &localPt,
55
+ bool ignorePointerEvents = false) const noexcept override;
56
+
52
57
  // Returns true when text is selectable
53
58
  bool focusable() const noexcept override;
54
59
 
@@ -90,16 +95,13 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
90
95
  std::optional<int32_t> GetClampedTextPosition(facebook::react::Point pt) noexcept;
91
96
  std::string GetSelectedText() const noexcept;
92
97
 
93
- // Copies currently selected text to the system clipboard
94
98
  void CopySelectionToClipboard() noexcept;
95
99
 
96
- // Selects the word at the given character position
97
100
  void SelectWordAtPosition(int32_t charPosition) noexcept;
101
+ std::pair<int32_t, int32_t> GetWordBoundariesAtPosition(int32_t charPosition) noexcept;
98
102
 
99
- // Shows a context menu with Copy/Select All options on right-click
100
103
  void ShowContextMenu() noexcept;
101
104
 
102
- // m_selectionStart <= m_selectionEnd
103
105
  void SetSelection(int32_t start, int32_t end) noexcept;
104
106
 
105
107
  winrt::com_ptr<::IDWriteTextLayout> m_textLayout;
@@ -113,6 +115,11 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
113
115
  std::optional<int32_t> m_selectionEnd;
114
116
  bool m_isSelecting{false};
115
117
 
118
+ // Double click + drag selection
119
+ bool m_isWordSelecting{false};
120
+ int32_t m_wordAnchorStart{0};
121
+ int32_t m_wordAnchorEnd{0};
122
+
116
123
  // Double-click detection
117
124
  std::chrono::steady_clock::time_point m_lastClickTime{};
118
125
  std::optional<int32_t> m_lastClickPosition;
@@ -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