react-native-windows 0.81.3 → 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 (71) 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/ABIViewManager.cpp +12 -1
  12. package/Microsoft.ReactNative/ComponentView.idl +2 -0
  13. package/Microsoft.ReactNative/Composition.Input.idl +7 -0
  14. package/Microsoft.ReactNative/CompositionComponentView.idl +5 -0
  15. package/Microsoft.ReactNative/CompositionHwndHost.idl +1 -0
  16. package/Microsoft.ReactNative/CompositionSwitcher.idl +16 -9
  17. package/Microsoft.ReactNative/Fabric/ComponentView.cpp +19 -1
  18. package/Microsoft.ReactNative/Fabric/ComponentView.h +10 -1
  19. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.cpp +12 -0
  20. package/Microsoft.ReactNative/Fabric/Composition/Composition.Input.h +15 -0
  21. package/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +15 -0
  22. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +75 -0
  23. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +1 -0
  24. package/Microsoft.ReactNative/Fabric/Composition/CompositionHwndHost.cpp +10 -45
  25. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +86 -19
  26. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +4 -0
  27. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.cpp +95 -22
  28. package/Microsoft.ReactNative/Fabric/Composition/ContentIslandComponentView.h +15 -0
  29. package/Microsoft.ReactNative/Fabric/Composition/Modal/WindowsModalHostViewComponentView.cpp +61 -74
  30. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +71 -12
  31. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.h +11 -0
  32. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.cpp +4 -3
  33. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeIsland.h +2 -1
  34. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.cpp +245 -0
  35. package/Microsoft.ReactNative/Fabric/Composition/ReactNativeWindow.h +80 -0
  36. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +33 -1
  37. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.h +17 -0
  38. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +47 -23
  39. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +3 -0
  40. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputShadowNode.cpp +3 -1
  41. package/Microsoft.ReactNative/Fabric/Composition/UiaHelpers.cpp +8 -4
  42. package/Microsoft.ReactNative/Modules/ImageViewManagerModule.cpp +41 -15
  43. package/Microsoft.ReactNative/Modules/LogBoxModule.cpp +20 -95
  44. package/Microsoft.ReactNative/Modules/LogBoxModule.h +1 -1
  45. package/Microsoft.ReactNative/ReactNativeAppBuilder.cpp +0 -41
  46. package/Microsoft.ReactNative/ReactNativeAppBuilder.idl +0 -11
  47. package/Microsoft.ReactNative/ReactNativeIsland.idl +2 -3
  48. package/Microsoft.ReactNative/ReactNativeWin32App.cpp +31 -101
  49. package/Microsoft.ReactNative/ReactNativeWin32App.h +2 -13
  50. package/Microsoft.ReactNative/ReactNativeWindow.idl +44 -0
  51. package/Mso/src/dispatchQueue/queueService.cpp +3 -1
  52. package/Mso/src/dispatchQueue/uiScheduler_winrt.cpp +2 -1
  53. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  54. package/Shared/Networking/OriginPolicyHttpFilter.cpp +2 -1
  55. package/Shared/Shared.vcxitems +7 -0
  56. package/Shared/Shared.vcxitems.filters +6 -0
  57. package/codegen/react/components/rnwcore/ActivityIndicatorView.g.h +2 -1
  58. package/codegen/react/components/rnwcore/AndroidDrawerLayout.g.h +42 -25
  59. package/codegen/react/components/rnwcore/AndroidHorizontalScrollContentView.g.h +2 -1
  60. package/codegen/react/components/rnwcore/AndroidProgressBar.g.h +2 -1
  61. package/codegen/react/components/rnwcore/AndroidSwipeRefreshLayout.g.h +11 -6
  62. package/codegen/react/components/rnwcore/AndroidSwitch.g.h +11 -6
  63. package/codegen/react/components/rnwcore/DebuggingOverlay.g.h +1 -0
  64. package/codegen/react/components/rnwcore/InputAccessory.g.h +2 -1
  65. package/codegen/react/components/rnwcore/ModalHostView.g.h +40 -23
  66. package/codegen/react/components/rnwcore/PullToRefreshView.g.h +11 -6
  67. package/codegen/react/components/rnwcore/SafeAreaView.g.h +1 -0
  68. package/codegen/react/components/rnwcore/Switch.g.h +11 -6
  69. package/codegen/react/components/rnwcore/UnimplementedNativeView.g.h +2 -1
  70. package/codegen/react/components/rnwcore/VirtualView.g.h +41 -8
  71. package/package.json +21 -21
@@ -32,6 +32,8 @@ struct ContentIslandComponentView : ContentIslandComponentViewT<ContentIslandCom
32
32
  uint32_t index) noexcept override;
33
33
  void Connect(const winrt::Microsoft::UI::Content::ContentIsland &contentIsland) noexcept;
34
34
 
35
+ winrt::Microsoft::UI::Content::ChildSiteLink ChildSiteLink() noexcept;
36
+
35
37
  void updateLayoutMetrics(
36
38
  facebook::react::LayoutMetrics const &layoutMetrics,
37
39
  facebook::react::LayoutMetrics const &oldLayoutMetrics) noexcept;
@@ -58,15 +60,28 @@ struct ContentIslandComponentView : ContentIslandComponentViewT<ContentIslandCom
58
60
  void OnMounted() noexcept;
59
61
  void OnUnmounted() noexcept;
60
62
  void ParentLayoutChanged() noexcept;
63
+ void ConnectInternal() noexcept;
64
+ winrt::Microsoft::UI::Content::ContentIsland ParentContentIsland() noexcept;
61
65
 
62
66
  bool m_layoutChangePosted{false};
67
+ winrt::Microsoft::UI::Content::ContentIsland m_parentContentIsland{nullptr};
63
68
  winrt::Microsoft::UI::Content::ContentIsland m_islandToConnect{nullptr};
69
+ std::optional<winrt::Microsoft::UI::Input::FocusNavigationReason> m_pendingNavigateFocus;
70
+
64
71
  winrt::event_token m_mountedToken;
65
72
  winrt::event_token m_unmountedToken;
66
73
  std::vector<winrt::Microsoft::ReactNative::ComponentView::LayoutMetricsChanged_revoker> m_layoutMetricChangedRevokers;
67
74
  winrt::Microsoft::UI::Content::ChildSiteLink m_childSiteLink{nullptr};
68
75
  winrt::Microsoft::UI::Input::InputFocusNavigationHost m_navigationHost{nullptr};
69
76
  winrt::event_token m_navigationHostDepartFocusRequestedToken{};
77
+ std::optional<winrt::Microsoft::UI::Input::FocusNavigationReason> m_pendingFocus;
78
+
79
+ // Issue #15557: Store ViewChanged subscriptions to parent ScrollViews for transform updates
80
+ struct ViewChangedSubscription {
81
+ winrt::weak_ref<winrt::Microsoft::ReactNative::Composition::ScrollViewComponentView> scrollView;
82
+ winrt::event_token token;
83
+ };
84
+ std::vector<ViewChangedSubscription> m_viewChangedSubscriptions;
70
85
 
71
86
  // Automation
72
87
  void ConfigureChildSiteLinkAutomation() noexcept;
@@ -37,9 +37,9 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
37
37
  ~ModalHostView() {
38
38
  if (m_popUp) {
39
39
  // Unregister closing event handler
40
- if (m_appWindow && m_appWindowClosingToken) {
41
- m_appWindow.Closing(m_appWindowClosingToken);
42
- m_appWindowClosingToken.value = 0;
40
+ if (m_appWindowClosingToken) {
41
+ m_rnWindow.AppWindow().Closing(m_appWindowClosingToken);
42
+ m_appWindowClosingToken = {};
43
43
  }
44
44
 
45
45
  // Reset topWindowID before destroying
@@ -49,21 +49,15 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
49
49
  m_prevWindowID = 0;
50
50
  }
51
51
 
52
- // Close island
53
- if (m_reactNativeIsland) {
54
- m_reactNativeIsland.Island().Close();
55
- m_reactNativeIsland = nullptr;
56
- }
57
-
58
52
  // Hide popup
59
53
  if (m_popUp.IsVisible()) {
60
54
  m_popUp.Hide();
61
55
  }
62
56
 
63
57
  // Destroy AppWindow this automatically resumes parent window to receive inputs
64
- if (m_appWindow) {
65
- m_appWindow.Destroy();
66
- m_appWindow = nullptr;
58
+ if (m_rnWindow.AppWindow()) {
59
+ m_rnWindow.Close();
60
+ m_rnWindow = nullptr;
67
61
  }
68
62
 
69
63
  // Bring parent window to foreground
@@ -111,11 +105,11 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
111
105
  }
112
106
 
113
107
  // Update Title if changed and AppWindow exists
114
- if (m_appWindow && (!oldProps || newViewProps.title != oldViewProps.title)) {
108
+ if (m_rnWindow && (!oldProps || newViewProps.title != oldViewProps.title)) {
115
109
  // Use empty string if title is not set
116
110
  winrt::hstring titleValue =
117
111
  newViewProps.title.has_value() ? winrt::to_hstring(newViewProps.title.value()) : winrt::hstring();
118
- m_appWindow.Title(titleValue);
112
+ m_rnWindow.AppWindow().Title(titleValue);
119
113
  }
120
114
 
121
115
  ::Microsoft::ReactNativeSpecs::BaseModalHostView<ModalHostView>::UpdateProps(view, newProps, oldProps);
@@ -181,7 +175,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
181
175
  }
182
176
 
183
177
  void AdjustWindowSize(const winrt::Microsoft::ReactNative::LayoutMetrics &layoutMetrics) noexcept {
184
- if (!m_appWindow) {
178
+ if (!m_rnWindow) {
185
179
  return;
186
180
  }
187
181
 
@@ -198,17 +192,17 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
198
192
  clientHeightPx = std::max(100, clientHeightPx);
199
193
 
200
194
  // Size the client area directly
201
- m_appWindow.ResizeClient({clientWidthPx, clientHeightPx});
195
+ m_rnWindow.AppWindow().ResizeClient({clientWidthPx, clientHeightPx});
202
196
 
203
197
  // Center the window on its parent
204
198
  RECT parentRC;
205
199
  GetWindowRect(m_parentHwnd, &parentRC);
206
- auto outerSize = m_appWindow.Size();
200
+ auto outerSize = m_rnWindow.AppWindow().Size();
207
201
 
208
202
  int32_t xCor = parentRC.left + (parentRC.right - parentRC.left - outerSize.Width) / 2;
209
203
  int32_t yCor = parentRC.top + (parentRC.bottom - parentRC.top - outerSize.Height) / 2;
210
204
 
211
- m_appWindow.Move({xCor, yCor});
205
+ m_rnWindow.AppWindow().Move({xCor, yCor});
212
206
  };
213
207
 
214
208
  void ShowOnUIThread(const winrt::Microsoft::ReactNative::ComponentView &view) {
@@ -231,8 +225,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
231
225
 
232
226
  // dispatch onShow event
233
227
  if (auto eventEmitter = EventEmitter()) {
234
- ::Microsoft::ReactNativeSpecs::ModalHostViewEventEmitter::OnShow eventArgs;
235
- eventEmitter->onShow(eventArgs);
228
+ eventEmitter->onShow({});
236
229
  }
237
230
  }
238
231
  }
@@ -261,8 +254,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
261
254
 
262
255
  // Dispatch onDismiss event
263
256
  if (auto eventEmitter = EventEmitter()) {
264
- ::Microsoft::ReactNativeSpecs::ModalHostViewEventEmitter::OnDismiss eventArgs;
265
- eventEmitter->onDismiss(eventArgs);
257
+ eventEmitter->onDismiss({});
266
258
  }
267
259
  }
268
260
 
@@ -279,59 +271,55 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
279
271
  view.as<::Microsoft::ReactNative::Composition::Experimental::IComponentViewInterop>()->GetHwndForParenting();
280
272
 
281
273
  auto portal = view.as<winrt::Microsoft::ReactNative::Composition::PortalComponentView>();
282
- m_reactNativeIsland = winrt::Microsoft::ReactNative::ReactNativeIsland::CreatePortal(portal);
283
- auto contentIsland = m_reactNativeIsland.Island();
284
-
285
274
  m_popUp = winrt::Microsoft::UI::Content::DesktopPopupSiteBridge::Create(
286
275
  portal.Parent()
287
276
  .as<winrt::Microsoft::ReactNative::Composition::ComponentView>()
288
277
  .Root()
289
278
  .ReactNativeIsland()
290
279
  .Island());
291
- m_popUp.Connect(contentIsland);
292
280
 
293
281
  // Get AppWindow and configure presenter
294
- m_appWindow = winrt::Microsoft::UI::Windowing::AppWindow::GetFromWindowId(m_popUp.WindowId());
295
- if (m_appWindow) {
296
- auto overlappedPresenter = winrt::Microsoft::UI::Windowing::OverlappedPresenter::Create();
297
-
298
- // Configure presenter for modal behavior
299
- overlappedPresenter.IsModal(true);
300
- overlappedPresenter.SetBorderAndTitleBar(true, true);
301
-
302
- // modal should only have close button
303
- overlappedPresenter.IsMinimizable(false);
304
- overlappedPresenter.IsMaximizable(false);
305
-
306
- // Apply the presenter to the window
307
- m_appWindow.SetPresenter(overlappedPresenter);
308
-
309
- // Hide the title bar icon
310
- m_appWindow.TitleBar().IconShowOptions(winrt::Microsoft::UI::Windowing::IconShowOptions::HideIconAndSystemMenu);
311
-
312
- // Set initial title using the stored local props
313
- if (m_localProps && m_localProps->title.has_value()) {
314
- winrt::hstring titleValue = winrt::to_hstring(m_localProps->title.value());
315
- m_appWindow.Title(titleValue);
316
- } else {
317
- m_appWindow.Title(L""); // Empty title if not provided
318
- }
282
+ m_rnWindow = winrt::Microsoft::ReactNative::ReactNativeWindow::CreateFromContentSiteBridgeAndIsland(
283
+ m_popUp, winrt::Microsoft::ReactNative::ReactNativeIsland::CreatePortal(portal));
284
+ m_rnWindow.ResizePolicy(winrt::Microsoft::ReactNative::ContentSizePolicy::None);
285
+ auto overlappedPresenter = winrt::Microsoft::UI::Windowing::OverlappedPresenter::Create();
286
+
287
+ // Configure presenter for modal behavior
288
+ overlappedPresenter.IsModal(true);
289
+ overlappedPresenter.SetBorderAndTitleBar(true, true);
290
+
291
+ // modal should only have close button
292
+ overlappedPresenter.IsMinimizable(false);
293
+ overlappedPresenter.IsMaximizable(false);
294
+
295
+ // Apply the presenter to the window
296
+ m_rnWindow.AppWindow().SetPresenter(overlappedPresenter);
297
+
298
+ // Hide the title bar icon
299
+ m_rnWindow.AppWindow().TitleBar().IconShowOptions(
300
+ winrt::Microsoft::UI::Windowing::IconShowOptions::HideIconAndSystemMenu);
301
+
302
+ // Set initial title using the stored local props
303
+ if (m_localProps && m_localProps->title.has_value()) {
304
+ winrt::hstring titleValue = winrt::to_hstring(m_localProps->title.value());
305
+ m_rnWindow.AppWindow().Title(titleValue);
306
+ } else {
307
+ m_rnWindow.AppWindow().Title(L""); // Empty title if not provided
308
+ }
319
309
 
320
- // Handle close request ('X' button)
321
- m_appWindowClosingToken =
322
- m_appWindow.Closing([wkThis = get_weak()](
323
- const winrt::Microsoft::UI::Windowing::AppWindow & /*sender*/,
324
- const winrt::Microsoft::UI::Windowing::AppWindowClosingEventArgs &args) {
325
- args.Cancel(true); // Prevent default close
326
- if (auto strongThis = wkThis.get()) {
327
- // Dispatch onRequestClose event
328
- if (auto eventEmitter = strongThis->EventEmitter()) {
329
- ::Microsoft::ReactNativeSpecs::ModalHostViewEventEmitter::OnRequestClose eventArgs;
330
- eventEmitter->onRequestClose(eventArgs);
331
- }
310
+ // Handle close request ('X' button)
311
+ m_appWindowClosingToken =
312
+ m_rnWindow.AppWindow().Closing([wkThis = get_weak()](
313
+ const winrt::Microsoft::UI::Windowing::AppWindow & /*sender*/,
314
+ const winrt::Microsoft::UI::Windowing::AppWindowClosingEventArgs &args) {
315
+ args.Cancel(true); // Prevent default close
316
+ if (auto strongThis = wkThis.get()) {
317
+ // Dispatch onRequestClose event
318
+ if (auto eventEmitter = strongThis->EventEmitter()) {
319
+ eventEmitter->onRequestClose({});
332
320
  }
333
- });
334
- }
321
+ }
322
+ });
335
323
 
336
324
  // set the top-level windows as the new hwnd
337
325
  winrt::Microsoft::ReactNative::ReactCoreInjection::SetTopLevelWindowId(
@@ -352,10 +340,10 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
352
340
  });
353
341
  */
354
342
 
355
- m_islandStateChangedToken =
356
- contentIsland.StateChanged([weakThis = get_weak()](
357
- winrt::Microsoft::UI::Content::ContentIsland const &island,
358
- winrt::Microsoft::UI::Content::ContentIslandStateChangedEventArgs const &args) {
343
+ m_islandStateChangedToken = m_rnWindow.ReactNativeIsland().Island().StateChanged(
344
+ [weakThis = get_weak()](
345
+ winrt::Microsoft::UI::Content::ContentIsland const &island,
346
+ winrt::Microsoft::UI::Content::ContentIslandStateChangedEventArgs const &args) {
359
347
  if (auto pThis = weakThis.get()) {
360
348
  if (args.DidRasterizationScaleChange() || args.DidLayoutDirectionChange()) {
361
349
  pThis->UpdateConstraints();
@@ -375,7 +363,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
375
363
  m_popUp.SiteView().EnvironmentView().DisplayId());
376
364
  auto workArea = displayArea.WorkArea();
377
365
 
378
- float scale = m_reactNativeIsland.Island().RasterizationScale();
366
+ float scale = m_rnWindow.ReactNativeIsland().Island().RasterizationScale();
379
367
 
380
368
  winrt::Microsoft::ReactNative::LayoutConstraints constraints;
381
369
  constraints.MinimumSize = {0, 0};
@@ -384,7 +372,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
384
372
  static_cast<float>((workArea.Width / scale) * 0.9), static_cast<float>((workArea.Height / scale) * 0.9)};
385
373
  constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::Undefined;
386
374
 
387
- auto layoutDirection = m_reactNativeIsland.Island().LayoutDirection();
375
+ auto layoutDirection = m_rnWindow.ReactNativeIsland().Island().LayoutDirection();
388
376
  if (layoutDirection == winrt::Microsoft::UI::Content::ContentLayoutDirection::LeftToRight)
389
377
  constraints.LayoutDirection = winrt::Microsoft::ReactNative::LayoutDirection::LeftToRight;
390
378
  else if (layoutDirection == winrt::Microsoft::UI::Content::ContentLayoutDirection::RightToLeft)
@@ -394,7 +382,7 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
394
382
  // When no constraint is set (maxSize is std::numeric_limits<Float>::infinity()), yoga will layout the content to a
395
383
  // desired size If we provide a specific max size, then contents with a flex:1 will expand to fill that size. We
396
384
  // might want to provide a windows specific property to control this behavior.
397
- m_state.UpdateState(winrt::make<ModalHostState>(constraints, m_reactNativeIsland.Island().RasterizationScale()));
385
+ m_state.UpdateState(winrt::make<ModalHostState>(constraints, scale));
398
386
  }
399
387
 
400
388
  static void TrySetFocus(const winrt::Microsoft::ReactNative::ComponentView &view) {
@@ -414,9 +402,8 @@ struct ModalHostView : public winrt::implements<ModalHostView, winrt::Windows::F
414
402
  winrt::event_token m_departFocusToken;
415
403
  winrt::event_token m_childLayoutMetricsToken;
416
404
  winrt::Microsoft::ReactNative::IComponentState m_state{nullptr};
417
- winrt::Microsoft::ReactNative::ReactNativeIsland m_reactNativeIsland{nullptr};
405
+ winrt::Microsoft::ReactNative::ReactNativeWindow m_rnWindow{nullptr};
418
406
  winrt::Microsoft::UI::Content::DesktopPopupSiteBridge m_popUp{nullptr};
419
- winrt::Microsoft::UI::Windowing::AppWindow m_appWindow{nullptr};
420
407
  winrt::event_token m_appWindowClosingToken;
421
408
  winrt::com_ptr<::Microsoft::ReactNativeSpecs::ModalHostViewProps> m_localProps{nullptr};
422
409
  };
@@ -167,6 +167,26 @@ void ParagraphComponentView::updateTextAlignment(
167
167
  m_textLayout = nullptr;
168
168
  }
169
169
 
170
+ facebook::react::Tag ParagraphComponentView::hitTest(
171
+ facebook::react::Point pt,
172
+ facebook::react::Point &localPt,
173
+ bool ignorePointerEvents) const noexcept {
174
+ facebook::react::Point ptLocal{pt.x - m_layoutMetrics.frame.origin.x, pt.y - m_layoutMetrics.frame.origin.y};
175
+ const auto &props = paragraphProps();
176
+ const auto &vProps = *viewProps();
177
+
178
+ if (props.isSelectable && ptLocal.x >= 0 && ptLocal.x <= m_layoutMetrics.frame.size.width && ptLocal.y >= 0 &&
179
+ ptLocal.y <= m_layoutMetrics.frame.size.height) {
180
+ // claims if pointer events are enabled for this component
181
+ if (ignorePointerEvents || vProps.pointerEvents == facebook::react::PointerEventsMode::Auto ||
182
+ vProps.pointerEvents == facebook::react::PointerEventsMode::BoxOnly) {
183
+ localPt = ptLocal;
184
+ return Tag();
185
+ }
186
+ }
187
+ return Super::hitTest(pt, localPt, ignorePointerEvents);
188
+ }
189
+
170
190
  bool ParagraphComponentView::IsTextSelectableAtPoint(facebook::react::Point pt) noexcept {
171
191
  // paragraph-level selectable prop is enabled
172
192
  const auto &props = paragraphProps();
@@ -519,6 +539,7 @@ void ParagraphComponentView::ClearSelection() noexcept {
519
539
  m_selectionStart = std::nullopt;
520
540
  m_selectionEnd = std::nullopt;
521
541
  m_isSelecting = false;
542
+ m_isWordSelecting = false;
522
543
  if (hadSelection) {
523
544
  // Clears selection highlight
524
545
  DrawText();
@@ -534,7 +555,8 @@ void ParagraphComponentView::OnPointerPressed(
534
555
  return;
535
556
  }
536
557
 
537
- auto pp = args.GetCurrentPoint(-1);
558
+ // Use Tag() to get coordinates in component's local space
559
+ auto pp = args.GetCurrentPoint(static_cast<int32_t>(Tag()));
538
560
 
539
561
  // Ignores right-click
540
562
  if (pp.Properties().PointerUpdateKind() ==
@@ -545,8 +567,8 @@ void ParagraphComponentView::OnPointerPressed(
545
567
 
546
568
  auto position = pp.Position();
547
569
 
548
- facebook::react::Point localPt{
549
- position.X - m_layoutMetrics.frame.origin.x, position.Y - m_layoutMetrics.frame.origin.y};
570
+ // GetCurrentPoint(Tag()) returns position relative to component origin
571
+ facebook::react::Point localPt{position.X, position.Y};
550
572
 
551
573
  std::optional<int32_t> charPosition = GetTextPositionAtPoint(localPt);
552
574
 
@@ -568,7 +590,13 @@ void ParagraphComponentView::OnPointerPressed(
568
590
 
569
591
  if (isDoubleClick) {
570
592
  SelectWordAtPosition(*charPosition);
571
- m_isSelecting = false;
593
+ if (m_selectionStart && m_selectionEnd) {
594
+ m_isWordSelecting = true;
595
+ m_wordAnchorStart = *m_selectionStart;
596
+ m_wordAnchorEnd = *m_selectionEnd;
597
+ m_isSelecting = true;
598
+ CapturePointer(args.Pointer());
599
+ }
572
600
  } else {
573
601
  // Single-click: start drag selection
574
602
  m_selectionStart = charPosition;
@@ -610,17 +638,35 @@ void ParagraphComponentView::OnPointerMoved(
610
638
  facebook::react::Point localPt{position.X, position.Y};
611
639
  std::optional<int32_t> charPosition = GetClampedTextPosition(localPt);
612
640
 
613
- if (charPosition && charPosition != m_selectionEnd) {
614
- m_selectionEnd = charPosition;
615
- DrawText();
616
- args.Handled(true);
641
+ if (charPosition) {
642
+ if (m_isWordSelecting) {
643
+ // Extend selection by whole words
644
+ auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(*charPosition);
645
+
646
+ if (*charPosition < m_wordAnchorStart) {
647
+ m_selectionStart = wordStart;
648
+ m_selectionEnd = m_wordAnchorEnd;
649
+ } else if (*charPosition >= m_wordAnchorEnd) {
650
+ m_selectionStart = m_wordAnchorStart;
651
+ m_selectionEnd = wordEnd;
652
+ } else {
653
+ m_selectionStart = m_wordAnchorStart;
654
+ m_selectionEnd = m_wordAnchorEnd;
655
+ }
656
+ DrawText();
657
+ args.Handled(true);
658
+ } else if (charPosition != m_selectionEnd) {
659
+ m_selectionEnd = charPosition;
660
+ DrawText();
661
+ args.Handled(true);
662
+ }
617
663
  }
618
664
  }
619
665
 
620
666
  void ParagraphComponentView::OnPointerReleased(
621
667
  const winrt::Microsoft::ReactNative::Composition::Input::PointerRoutedEventArgs &args) noexcept {
622
668
  // Check for right-click to show context menu
623
- auto pp = args.GetCurrentPoint(-1);
669
+ auto pp = args.GetCurrentPoint(static_cast<int32_t>(Tag()));
624
670
  if (pp.Properties().PointerUpdateKind() ==
625
671
  winrt::Microsoft::ReactNative::Composition::Input::PointerUpdateKind::RightButtonReleased) {
626
672
  const auto &props = paragraphProps();
@@ -637,6 +683,7 @@ void ParagraphComponentView::OnPointerReleased(
637
683
  }
638
684
 
639
685
  m_isSelecting = false;
686
+ m_isWordSelecting = false;
640
687
 
641
688
  ReleasePointerCapture(args.Pointer());
642
689
 
@@ -661,6 +708,7 @@ void ParagraphComponentView::OnPointerCaptureLost() noexcept {
661
708
  // Pointer capture was lost stop any active selection drag
662
709
  if (m_isSelecting) {
663
710
  m_isSelecting = false;
711
+ m_isWordSelecting = false;
664
712
 
665
713
  if (!m_selectionStart || !m_selectionEnd || *m_selectionStart == *m_selectionEnd) {
666
714
  m_selectionStart = std::nullopt;
@@ -711,12 +759,17 @@ void ParagraphComponentView::CopySelectionToClipboard() noexcept {
711
759
  winrt::Windows::ApplicationModel::DataTransfer::Clipboard::SetContent(dataPackage);
712
760
  }
713
761
 
714
- void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
762
+ std::pair<int32_t, int32_t> ParagraphComponentView::GetWordBoundariesAtPosition(int32_t charPosition) noexcept {
715
763
  const std::wstring utf16Text{facebook::react::WindowsTextLayoutManager::GetTransformedText(m_attributedStringBox)};
716
764
  const int32_t textLength = static_cast<int32_t>(utf16Text.length());
717
765
 
718
- if (utf16Text.empty() || charPosition < 0 || charPosition >= textLength) {
719
- return;
766
+ if (utf16Text.empty() || charPosition < 0) {
767
+ return {0, 0};
768
+ }
769
+
770
+ charPosition = std::min(charPosition, textLength - 1);
771
+ if (charPosition < 0) {
772
+ return {0, 0};
720
773
  }
721
774
 
722
775
  int32_t wordStart = charPosition;
@@ -749,6 +802,12 @@ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept
749
802
  }
750
803
  }
751
804
 
805
+ return {wordStart, wordEnd};
806
+ }
807
+
808
+ void ParagraphComponentView::SelectWordAtPosition(int32_t charPosition) noexcept {
809
+ auto [wordStart, wordEnd] = GetWordBoundariesAtPosition(charPosition);
810
+
752
811
  if (wordEnd > wordStart) {
753
812
  SetSelection(wordStart, wordEnd);
754
813
  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
 
@@ -95,6 +100,7 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
95
100
 
96
101
  // Selects the word at the given character position
97
102
  void SelectWordAtPosition(int32_t charPosition) noexcept;
103
+ std::pair<int32_t, int32_t> GetWordBoundariesAtPosition(int32_t charPosition) noexcept;
98
104
 
99
105
  // Shows a context menu with Copy/Select All options on right-click
100
106
  void ShowContextMenu() noexcept;
@@ -113,6 +119,11 @@ struct ParagraphComponentView : ParagraphComponentViewT<ParagraphComponentView,
113
119
  std::optional<int32_t> m_selectionEnd;
114
120
  bool m_isSelecting{false};
115
121
 
122
+ // Double click + drag selection
123
+ bool m_isWordSelecting{false};
124
+ int32_t m_wordAnchorStart{0};
125
+ int32_t m_wordAnchorEnd{0};
126
+
116
127
  // Double-click detection
117
128
  std::chrono::steady_clock::time_point m_lastClickTime{};
118
129
  std::optional<int32_t> m_lastClickPosition;
@@ -168,9 +168,6 @@ winrt::Microsoft::ReactNative::ReactNativeIsland ReactNativeIsland::CreatePortal
168
168
  return winrt::make<ReactNativeIsland>(portal);
169
169
  }
170
170
 
171
- ReactNativeIsland::ReactNativeIsland() noexcept
172
- : ReactNativeIsland(winrt::Microsoft::UI::Composition::Compositor{nullptr}) {}
173
-
174
171
  ReactNativeIsland::~ReactNativeIsland() noexcept {
175
172
  #ifdef USE_WINUI3
176
173
  if (m_island) {
@@ -220,6 +217,10 @@ void ReactNativeIsland::ReactViewHost(winrt::Microsoft::ReactNative::IReactViewH
220
217
  }
221
218
  }
222
219
 
220
+ winrt::Microsoft::UI::Composition::Compositor ReactNativeIsland::Compositor() noexcept {
221
+ return m_compositor;
222
+ }
223
+
223
224
  winrt::Microsoft::UI::Composition::Visual ReactNativeIsland::RootVisual() noexcept {
224
225
  return winrt::Microsoft::ReactNative::Composition::Experimental::MicrosoftCompositionContextHelper::InnerVisual(
225
226
  m_rootVisual);
@@ -48,7 +48,6 @@ struct FocusNavigationResult : FocusNavigationResultT<FocusNavigationResult> {
48
48
 
49
49
  struct ReactNativeIsland
50
50
  : ReactNativeIslandT<ReactNativeIsland, Composition::Experimental::IInternalCompositionRootView> {
51
- ReactNativeIsland() noexcept;
52
51
  ~ReactNativeIsland() noexcept;
53
52
 
54
53
  ReactNativeIsland(const winrt::Microsoft::UI::Composition::Compositor &compositor) noexcept;
@@ -65,6 +64,8 @@ struct ReactNativeIsland
65
64
  ReactNative::IReactViewHost ReactViewHost() noexcept;
66
65
  void ReactViewHost(ReactNative::IReactViewHost const &value);
67
66
 
67
+ winrt::Microsoft::UI::Composition::Compositor Compositor() noexcept;
68
+
68
69
  winrt::Microsoft::UI::Composition::Visual RootVisual() noexcept;
69
70
 
70
71
  // property RootVisual