react-native-windows 0.81.13 → 0.81.18

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 (49) hide show
  1. package/Common/Common.vcxproj +1 -1
  2. package/Folly/Folly.vcxproj +1 -1
  3. package/Libraries/Modal/Modal.windows.js +1 -7
  4. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.cpp +73 -97
  5. package/Microsoft.ReactNative/Fabric/Composition/BorderPrimitive.h +4 -1
  6. package/Microsoft.ReactNative/Fabric/Composition/CompositionContextHelper.cpp +58 -21
  7. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +253 -58
  8. package/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.h +18 -4
  9. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.cpp +149 -27
  10. package/Microsoft.ReactNative/Fabric/Composition/CompositionViewComponentView.h +10 -0
  11. package/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.cpp +5 -0
  12. package/Microsoft.ReactNative/Fabric/Composition/ImageComponentView.h +2 -0
  13. package/Microsoft.ReactNative/Fabric/Composition/ParagraphComponentView.cpp +3 -1
  14. package/Microsoft.ReactNative/Fabric/Composition/ScrollViewComponentView.cpp +4 -6
  15. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.cpp +101 -44
  16. package/Microsoft.ReactNative/Fabric/Composition/TextInput/WindowsTextInputComponentView.h +4 -2
  17. package/Microsoft.ReactNative/Fabric/WindowsComponentDescriptorRegistry.cpp +3 -3
  18. package/Microsoft.ReactNative/Fabric/WindowsComponentDescriptorRegistry.h +3 -1
  19. package/Microsoft.ReactNative/Fabric/WindowsImageManager.cpp +0 -1
  20. package/Microsoft.ReactNative/Fabric/platform/react/renderer/graphics/HostPlatformColor.h +3 -0
  21. package/Microsoft.ReactNative/Fabric/platform/react/renderer/textlayoutmanager/WindowsTextLayoutManager.cpp +2 -7
  22. package/Microsoft.ReactNative/IReactPackageBuilder.idl +5 -0
  23. package/Microsoft.ReactNative/Microsoft.ReactNative.vcxproj +2 -2
  24. package/Microsoft.ReactNative/Modules/Animated/AnimatedNode.cpp +3 -3
  25. package/Microsoft.ReactNative/Modules/Animated/AnimatedNode.h +3 -2
  26. package/Microsoft.ReactNative/Modules/Timing.h +2 -1
  27. package/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +2 -0
  28. package/Microsoft.ReactNative/ReactPackageBuilder.cpp +7 -0
  29. package/Microsoft.ReactNative/ReactPackageBuilder.h +1 -0
  30. package/Microsoft.ReactNative/TurboModulesProvider.cpp +5 -10
  31. package/Microsoft.ReactNative/TurboModulesProvider.h +2 -0
  32. package/Microsoft.ReactNative.Cxx/ModuleRegistration.cpp +8 -2
  33. package/Microsoft.ReactNative.Cxx/ModuleRegistration.h +17 -4
  34. package/Microsoft.ReactNative.Cxx/NativeModules.h +5 -0
  35. package/PropertySheets/External/Microsoft.ReactNative.Composition.CppApp.targets +2 -2
  36. package/PropertySheets/External/Microsoft.ReactNative.Composition.CppLib.targets +2 -2
  37. package/PropertySheets/External/Microsoft.ReactNative.Uwp.CSharpApp.targets +1 -1
  38. package/PropertySheets/External/Microsoft.ReactNative.Uwp.CppApp.targets +1 -1
  39. package/PropertySheets/Generated/PackageVersion.g.props +3 -3
  40. package/PropertySheets/JSEngine.props +2 -1
  41. package/PropertySheets/WinUI.props +3 -3
  42. package/ReactCommon/ReactCommon.vcxproj +1 -1
  43. package/Scripts/OfficeReact.Win32.nuspec +44 -44
  44. package/Scripts/Tfs/Invoke-WebRequestWithRetry.ps1 +40 -0
  45. package/Scripts/Tfs/Layout-Desktop-Headers.ps1 +1 -15
  46. package/Shared/Shared.vcxitems.filters +2 -2
  47. package/package.json +1 -1
  48. package/Scripts/OpenSSL.nuspec +0 -39
  49. package/Scripts/OpenSSL.targets +0 -36
@@ -253,7 +253,10 @@ void CompositionEventHandler::Initialize() noexcept {
253
253
  if (strongThis->SurfaceId() == -1)
254
254
  return;
255
255
 
256
- auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
256
+ auto *rootView = strongThis->RootComponentView();
257
+ if (!rootView)
258
+ return;
259
+ auto focusedComponent = rootView->GetFocusedComponent();
257
260
  auto keyboardSource = winrt::make<CompositionInputKeyboardSource>(source);
258
261
  auto keyArgs =
259
262
  winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::KeyRoutedEventArgs>(
@@ -279,7 +282,10 @@ void CompositionEventHandler::Initialize() noexcept {
279
282
  if (strongThis->SurfaceId() == -1)
280
283
  return;
281
284
 
282
- auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
285
+ auto *rootView = strongThis->RootComponentView();
286
+ if (!rootView)
287
+ return;
288
+ auto focusedComponent = rootView->GetFocusedComponent();
283
289
  auto keyboardSource = winrt::make<CompositionInputKeyboardSource>(source);
284
290
  auto keyArgs =
285
291
  winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::KeyRoutedEventArgs>(
@@ -306,7 +312,10 @@ void CompositionEventHandler::Initialize() noexcept {
306
312
  if (strongThis->SurfaceId() == -1)
307
313
  return;
308
314
 
309
- auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
315
+ auto *rootView = strongThis->RootComponentView();
316
+ if (!rootView)
317
+ return;
318
+ auto focusedComponent = rootView->GetFocusedComponent();
310
319
  auto keyboardSource = winrt::make<CompositionInputKeyboardSource>(source);
311
320
  auto charArgs = winrt::make<
312
321
  winrt::Microsoft::ReactNative::Composition::Input::implementation::CharacterReceivedRoutedEventArgs>(
@@ -333,7 +342,10 @@ void CompositionEventHandler::Initialize() noexcept {
333
342
  if (strongThis->SurfaceId() == -1)
334
343
  return;
335
344
 
336
- auto focusedComponent = strongThis->RootComponentView().GetFocusedComponent();
345
+ auto *rootView = strongThis->RootComponentView();
346
+ if (!rootView)
347
+ return;
348
+ auto focusedComponent = rootView->GetFocusedComponent();
337
349
  if (focusedComponent) {
338
350
  auto tag =
339
351
  winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
@@ -363,6 +375,7 @@ CompositionEventHandler::~CompositionEventHandler() {
363
375
  pointerSource.PointerMoved(m_pointerMovedToken);
364
376
  pointerSource.PointerCaptureLost(m_pointerCaptureLostToken);
365
377
  pointerSource.PointerWheelChanged(m_pointerWheelChangedToken);
378
+ pointerSource.PointerExited(m_pointerExitedToken);
366
379
  auto keyboardSource = winrt::Microsoft::UI::Input::InputKeyboardSource::GetForIsland(island);
367
380
  keyboardSource.KeyDown(m_keyDownToken);
368
381
  keyboardSource.KeyUp(m_keyUpToken);
@@ -386,10 +399,15 @@ facebook::react::SurfaceId CompositionEventHandler::SurfaceId() const noexcept {
386
399
  return -1;
387
400
  }
388
401
 
389
- winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView &
402
+ winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *
390
403
  CompositionEventHandler::RootComponentView() const noexcept {
391
404
  auto island = m_wkRootView.get();
392
- return *winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(island)->GetComponentView();
405
+ if (!island) {
406
+ return nullptr;
407
+ }
408
+ return winrt::get_self<winrt::Microsoft::ReactNative::implementation::ReactNativeIsland>(island)
409
+ ->GetComponentView()
410
+ .get();
393
411
  }
394
412
 
395
413
  void CompositionEventHandler::onPointerWheelChanged(
@@ -404,8 +422,11 @@ void CompositionEventHandler::onPointerWheelChanged(
404
422
 
405
423
  // In the case of a sub rootview, we may have a non-zero origin. hitTest takes a pt in the parent coords, so we
406
424
  // need to apply the current origin
407
- ptScaled += RootComponentView().layoutMetrics().frame.origin;
408
- auto tag = RootComponentView().hitTest(ptScaled, ptLocal);
425
+ auto *rootView = RootComponentView();
426
+ if (!rootView)
427
+ return;
428
+ ptScaled += rootView->layoutMetrics().frame.origin;
429
+ auto tag = rootView->hitTest(ptScaled, ptLocal);
409
430
 
410
431
  if (tag == -1)
411
432
  return;
@@ -559,7 +580,10 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
559
580
  case WM_CHAR:
560
581
  case WM_SYSCHAR: {
561
582
  if (auto strongRootView = m_wkRootView.get()) {
562
- auto focusedComponent = RootComponentView().GetFocusedComponent();
583
+ auto *rootView = RootComponentView();
584
+ if (!rootView)
585
+ break;
586
+ auto focusedComponent = rootView->GetFocusedComponent();
563
587
  auto keyboardSource = winrt::make<CompositionKeyboardSource>(this);
564
588
  auto args = winrt::make<
565
589
  winrt::Microsoft::ReactNative::Composition::Input::implementation::CharacterReceivedRoutedEventArgs>(
@@ -582,7 +606,10 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
582
606
  case WM_SYSKEYDOWN:
583
607
  case WM_SYSKEYUP: {
584
608
  if (auto strongRootView = m_wkRootView.get()) {
585
- auto focusedComponent = RootComponentView().GetFocusedComponent();
609
+ auto *rootView = RootComponentView();
610
+ if (!rootView)
611
+ break;
612
+ auto focusedComponent = rootView->GetFocusedComponent();
586
613
  auto keyboardSource = winrt::make<CompositionKeyboardSource>(this);
587
614
  auto args = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::KeyRoutedEventArgs>(
588
615
  focusedComponent
@@ -614,9 +641,12 @@ int64_t CompositionEventHandler::SendMessage(HWND hwnd, uint32_t msg, uint64_t w
614
641
 
615
642
  void CompositionEventHandler::onKeyDown(
616
643
  const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
617
- RootComponentView().UseKeyboardForProgrammaticFocus(true);
644
+ auto *rootView = RootComponentView();
645
+ if (!rootView)
646
+ return;
647
+ rootView->UseKeyboardForProgrammaticFocus(true);
618
648
 
619
- if (auto focusedComponent = RootComponentView().GetFocusedComponent()) {
649
+ if (auto focusedComponent = rootView->GetFocusedComponent()) {
620
650
  winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)->OnKeyDown(args);
621
651
 
622
652
  if (args.Handled())
@@ -639,7 +669,7 @@ void CompositionEventHandler::onKeyDown(
639
669
  }
640
670
 
641
671
  if (!fCtrl && args.Key() == winrt::Windows::System::VirtualKey::Tab) {
642
- if (RootComponentView().TryMoveFocus(!fShift, winrt::Microsoft::ReactNative::FocusState::Keyboard)) {
672
+ if (rootView->TryMoveFocus(!fShift, winrt::Microsoft::ReactNative::FocusState::Keyboard)) {
643
673
  args.Handled(true);
644
674
  }
645
675
 
@@ -649,9 +679,12 @@ void CompositionEventHandler::onKeyDown(
649
679
 
650
680
  void CompositionEventHandler::onKeyUp(
651
681
  const winrt::Microsoft::ReactNative::Composition::Input::KeyRoutedEventArgs &args) noexcept {
652
- RootComponentView().UseKeyboardForProgrammaticFocus(true);
682
+ auto *rootView = RootComponentView();
683
+ if (!rootView)
684
+ return;
685
+ rootView->UseKeyboardForProgrammaticFocus(true);
653
686
 
654
- if (auto focusedComponent = RootComponentView().GetFocusedComponent()) {
687
+ if (auto focusedComponent = rootView->GetFocusedComponent()) {
655
688
  winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)->OnKeyUp(args);
656
689
 
657
690
  if (args.Handled())
@@ -661,7 +694,10 @@ void CompositionEventHandler::onKeyUp(
661
694
 
662
695
  void CompositionEventHandler::onCharacterReceived(
663
696
  const winrt::Microsoft::ReactNative::Composition::Input::CharacterReceivedRoutedEventArgs &args) noexcept {
664
- if (auto focusedComponent = RootComponentView().GetFocusedComponent()) {
697
+ auto *rootView = RootComponentView();
698
+ if (!rootView)
699
+ return;
700
+ if (auto focusedComponent = rootView->GetFocusedComponent()) {
665
701
  winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(focusedComponent)
666
702
  ->OnCharacterReceived(args);
667
703
 
@@ -670,7 +706,7 @@ void CompositionEventHandler::onCharacterReceived(
670
706
  }
671
707
  }
672
708
 
673
- std::vector<winrt::Microsoft::ReactNative::ComponentView> GetTouchableViewsInPathToRoot(
709
+ std::vector<winrt::Microsoft::ReactNative::ComponentView> CompositionEventHandler::GetTouchableViewsInPathToRoot(
674
710
  const winrt::Microsoft::ReactNative::ComponentView &componentView) {
675
711
  std::vector<winrt::Microsoft::ReactNative::ComponentView> results;
676
712
  auto view = componentView;
@@ -680,6 +716,7 @@ std::vector<winrt::Microsoft::ReactNative::ComponentView> GetTouchableViewsInPat
680
716
  }
681
717
  view = view.Parent();
682
718
  }
719
+
683
720
  return results;
684
721
  }
685
722
 
@@ -980,8 +1017,8 @@ void CompositionEventHandler::UpdateActiveTouch(
980
1017
  // activeTouch.touch.isEraser = false;
981
1018
  activeTouch.touch.pagePoint.x = ptScaled.x;
982
1019
  activeTouch.touch.pagePoint.y = ptScaled.y;
983
- activeTouch.touch.screenPoint.x = ptLocal.x;
984
- activeTouch.touch.screenPoint.y = ptLocal.y;
1020
+ activeTouch.touch.screenPoint.x = ptScaled.x;
1021
+ activeTouch.touch.screenPoint.y = ptScaled.y;
985
1022
  activeTouch.touch.offsetPoint.x = ptLocal.x;
986
1023
  activeTouch.touch.offsetPoint.y = ptLocal.y;
987
1024
  activeTouch.touch.timestamp = static_cast<facebook::react::Float>(
@@ -1034,9 +1071,12 @@ void CompositionEventHandler::getTargetPointerArgs(
1034
1071
 
1035
1072
  // In the case of a sub rootview, we may have a non-zero origin. hitTest takes a pt in the parent coords, so we need
1036
1073
  // to apply the current origin
1037
- ptScaled += RootComponentView().layoutMetrics().frame.origin;
1074
+ auto *rootView = RootComponentView();
1075
+ if (!rootView)
1076
+ return;
1077
+ ptScaled += rootView->layoutMetrics().frame.origin;
1038
1078
 
1039
- if (std::find(m_capturedPointers.begin(), m_capturedPointers.end(), pointerId) != m_capturedPointers.end()) {
1079
+ if (m_capturedPointers.count(pointerId)) {
1040
1080
  assert(m_pointerCapturingComponentTag != -1);
1041
1081
  tag = m_pointerCapturingComponentTag;
1042
1082
 
@@ -1048,7 +1088,7 @@ void CompositionEventHandler::getTargetPointerArgs(
1048
1088
  ptLocal.y = ptScaled.y - (clientRect.top / strongRootView.ScaleFactor());
1049
1089
  }
1050
1090
  } else {
1051
- tag = RootComponentView().hitTest(ptScaled, ptLocal);
1091
+ tag = rootView->hitTest(ptScaled, ptLocal);
1052
1092
  }
1053
1093
  }
1054
1094
 
@@ -1058,12 +1098,26 @@ void CompositionEventHandler::onPointerCaptureLost(
1058
1098
  if (SurfaceId() == -1)
1059
1099
  return;
1060
1100
 
1061
- if (m_pointerCapturingComponentTag) {
1101
+ if (m_pointerCapturingComponentTag != -1) {
1062
1102
  // copy array to avoid iterator being invalidated during deletion
1063
- std::vector<PointerId> capturedPointers = m_capturedPointers;
1103
+ std::unordered_set<PointerId> capturedPointers = m_capturedPointers;
1064
1104
 
1065
1105
  for (auto pointerId : capturedPointers) {
1066
1106
  releasePointerCapture(pointerId, m_pointerCapturingComponentTag);
1107
+
1108
+ // Cancel any active touch for this pointer so React Native is notified that
1109
+ // the touch ended. Without this, m_activeTouches retains a zombie entry and
1110
+ // RN JS is never told the touch is gone — leaving Pressables stuck in a
1111
+ // pressed state after a system-interrupted gesture (e.g. system back swipe,
1112
+ // Alt+Tab, another window coming foreground).
1113
+ auto activeTouch = m_activeTouches.find(pointerId);
1114
+ if (activeTouch != m_activeTouches.end()) {
1115
+ ActiveTouch cancelledTouchCopy = std::move(activeTouch->second);
1116
+ m_activeTouches.erase(activeTouch);
1117
+ if (cancelledTouchCopy.eventEmitter) {
1118
+ DispatchSynthesizedTouchCancelForActiveTouch(cancelledTouchCopy, pointerPoint, keyModifiers);
1119
+ }
1120
+ }
1067
1121
  }
1068
1122
 
1069
1123
  m_pointerCapturingComponentTag = -1;
@@ -1107,10 +1161,11 @@ void CompositionEventHandler::onPointerMoved(
1107
1161
 
1108
1162
  auto handler = [&, targetView, pointerEvent, isActiveTouch](
1109
1163
  std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
1164
+ auto *rootViewForEmitter = RootComponentView();
1110
1165
  const auto eventEmitter = targetView
1111
1166
  ? winrt::get_self<winrt::Microsoft::ReactNative::implementation::ComponentView>(targetView)
1112
1167
  ->eventEmitterAtPoint(pointerEvent.offsetPoint)
1113
- : RootComponentView().eventEmitterAtPoint(pointerEvent.offsetPoint);
1168
+ : (rootViewForEmitter ? rootViewForEmitter->eventEmitterAtPoint(pointerEvent.offsetPoint) : nullptr);
1114
1169
 
1115
1170
  if (eventEmitter != nullptr) {
1116
1171
  eventEmitter->onPointerMove(pointerEvent);
@@ -1136,7 +1191,10 @@ void CompositionEventHandler::ClearAllHoveredForPointer(const facebook::react::P
1136
1191
  // events. If we get null for the targetView, that means that the mouse is no over any components, so we have no
1137
1192
  // element to send the move event to. However we need to send something so that any previously hovered elements
1138
1193
  // are no longer hovered.
1139
- auto children = RootComponentView().Children();
1194
+ auto *rootView = RootComponentView();
1195
+ if (!rootView)
1196
+ return;
1197
+ auto children = rootView->Children();
1140
1198
  if (auto size = children.Size()) {
1141
1199
  auto firstChild = children.GetAt(0);
1142
1200
  if (auto childEventEmitter =
@@ -1176,28 +1234,67 @@ void CompositionEventHandler::onPointerExited(
1176
1234
  }
1177
1235
  }
1178
1236
 
1237
+ // Windows touch pointer IDs can be arbitrarily large (e.g. 2233). React Native's JS
1238
+ // touch handler uses identifiers as array indices and warns/misbehaves for values > 20.
1239
+ // This function maps each live Windows pointer to a small identifier in [0, 19] by
1240
+ // scanning m_activeTouches for in-use slots and cycling from the last assigned index.
1241
+ // Identifier MOUSE_POINTER_ID (1) is permanently reserved for mouse and never returned here.
1242
+ int CompositionEventHandler::AllocateTouchIdentifier() noexcept {
1243
+ constexpr int kMaxTouchIdentifier = 20;
1244
+ for (int i = 0; i < kMaxTouchIdentifier; i++) {
1245
+ int candidate = (m_touchId + i) % kMaxTouchIdentifier;
1246
+ if (candidate == static_cast<int>(MOUSE_POINTER_ID)) {
1247
+ continue; // reserved for mouse
1248
+ }
1249
+ bool inUse = std::any_of(m_activeTouches.begin(), m_activeTouches.end(), [candidate](const auto &pair) {
1250
+ return pair.second.touch.identifier == candidate;
1251
+ });
1252
+ if (!inUse) {
1253
+ m_touchId = (candidate + 1) % kMaxTouchIdentifier;
1254
+ return candidate;
1255
+ }
1256
+ }
1257
+ // All non-mouse slots occupied (> 19 simultaneous touch/pen points) — wrap anyway,
1258
+ // skipping the mouse-reserved slot.
1259
+ int fallback = m_touchId;
1260
+ m_touchId = (m_touchId + 1) % kMaxTouchIdentifier;
1261
+ if (fallback == static_cast<int>(MOUSE_POINTER_ID)) {
1262
+ fallback = m_touchId;
1263
+ m_touchId = (m_touchId + 1) % kMaxTouchIdentifier;
1264
+ }
1265
+ return fallback;
1266
+ }
1267
+
1179
1268
  void CompositionEventHandler::onPointerPressed(
1180
1269
  const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
1181
1270
  winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
1182
1271
  namespace Composition = winrt::Microsoft::ReactNative::Composition;
1183
1272
 
1184
- RootComponentView().UseKeyboardForProgrammaticFocus(false);
1273
+ auto *rootView = RootComponentView();
1274
+ if (!rootView)
1275
+ return;
1276
+ rootView->UseKeyboardForProgrammaticFocus(false);
1185
1277
 
1186
1278
  // Clears any active text selection when left pointer is pressed
1187
1279
  if (pointerPoint.Properties().PointerUpdateKind() != Composition::Input::PointerUpdateKind::RightButtonPressed) {
1188
- RootComponentView().ClearCurrentTextSelection();
1280
+ rootView->ClearCurrentTextSelection();
1189
1281
  }
1190
1282
 
1191
1283
  PointerId pointerId = pointerPoint.PointerId();
1192
1284
 
1193
- auto staleTouch = std::find_if(m_activeTouches.begin(), m_activeTouches.end(), [pointerId](const auto &pair) {
1194
- return pair.second.touch.identifier == pointerId;
1195
- });
1285
+ auto staleTouch = m_activeTouches.find(pointerId);
1196
1286
 
1197
1287
  if (staleTouch != m_activeTouches.end()) {
1198
- // A pointer with this ID already exists - Should we fire a button cancel or something?
1199
- // assert(false);
1200
- return;
1288
+ // A previous pointer with this ID was never properly released (e.g., app lost focus,
1289
+ // pointer left window). Cancel the stale touch and clean it up so the new press can proceed.
1290
+ // Copy and erase before dispatching to avoid holding a reference into m_activeTouches
1291
+ // across DispatchSynthesizedTouchCancelForActiveTouch, which calls HandleIncomingPointerEvent
1292
+ // and iterates m_activeTouches internally.
1293
+ ActiveTouch staleTouchCopy = std::move(staleTouch->second);
1294
+ m_activeTouches.erase(staleTouch);
1295
+ if (staleTouchCopy.eventEmitter) {
1296
+ DispatchSynthesizedTouchCancelForActiveTouch(staleTouchCopy, pointerPoint, keyModifiers);
1297
+ }
1201
1298
  }
1202
1299
 
1203
1300
  const auto eventType = TouchEventType::Start;
@@ -1218,7 +1315,18 @@ void CompositionEventHandler::onPointerPressed(
1218
1315
  ->OnPointerPressed(args);
1219
1316
 
1220
1317
  ActiveTouch activeTouch{0};
1221
- activeTouch.touchType = UITouchType::Mouse;
1318
+ switch (pointerPoint.PointerDeviceType()) {
1319
+ case Composition::Input::PointerDeviceType::Touch:
1320
+ activeTouch.touchType = UITouchType::Touch;
1321
+ break;
1322
+ case Composition::Input::PointerDeviceType::Pen:
1323
+ activeTouch.touchType = UITouchType::Pen;
1324
+ break;
1325
+ case Composition::Input::PointerDeviceType::Mouse:
1326
+ default:
1327
+ activeTouch.touchType = UITouchType::Mouse;
1328
+ break;
1329
+ }
1222
1330
 
1223
1331
  // Map PointerUpdateKind to W3C button value
1224
1332
  // https://developer.mozilla.org/docs/Web/API/MouseEvent/button
@@ -1256,14 +1364,27 @@ void CompositionEventHandler::onPointerPressed(
1256
1364
  targetComponentView = targetComponentView.Parent();
1257
1365
  }
1258
1366
 
1367
+ // Don't register the touch if no eventEmitter was found — inserting a null-emitter entry
1368
+ // into m_activeTouches would block future presses with the same pointer ID.
1369
+ if (!activeTouch.eventEmitter) {
1370
+ return;
1371
+ }
1372
+
1259
1373
  UpdateActiveTouch(activeTouch, ptScaled, ptLocal);
1260
1374
 
1261
1375
  activeTouch.isPrimary = pointerId == 1;
1262
- activeTouch.touch.identifier = pointerId;
1376
+ // Map the Windows pointer ID to a small identifier (0–19) safe for use as a JS array index.
1377
+ // Windows touch IDs can be arbitrarily large (e.g. 2233), which causes React Native to warn
1378
+ // and corrupts touch state, leaving Pressables stuck after a scroll.
1379
+ // Mouse pointer ID is always 1 (MOUSE_POINTER_ID), which is already within the safe range —
1380
+ // use it directly to preserve stable, predictable identifier assignment for mouse input.
1381
+ activeTouch.touch.identifier = (pointerPoint.PointerDeviceType() == Composition::Input::PointerDeviceType::Mouse)
1382
+ ? static_cast<int>(MOUSE_POINTER_ID)
1383
+ : AllocateTouchIdentifier();
1263
1384
 
1264
1385
  // If the pointer has not been marked as hovering over views before the touch started, we register
1265
1386
  // that the activeTouch should not maintain its hovered state once the pointer has been lifted.
1266
- auto currentlyHoveredTags = m_currentlyHoveredViewsPerPointer.find(activeTouch.touch.identifier);
1387
+ auto currentlyHoveredTags = m_currentlyHoveredViewsPerPointer.find(pointerId);
1267
1388
  if (currentlyHoveredTags == m_currentlyHoveredViewsPerPointer.end() || currentlyHoveredTags->second.empty()) {
1268
1389
  activeTouch.shouldLeaveWhenReleased = true;
1269
1390
  }
@@ -1279,11 +1400,12 @@ void CompositionEventHandler::onPointerReleased(
1279
1400
  winrt::Windows::System::VirtualKeyModifiers keyModifiers) noexcept {
1280
1401
  int pointerId = pointerPoint.PointerId();
1281
1402
 
1282
- RootComponentView().UseKeyboardForProgrammaticFocus(false);
1403
+ auto *rootView = RootComponentView();
1404
+ if (!rootView)
1405
+ return;
1406
+ rootView->UseKeyboardForProgrammaticFocus(false);
1283
1407
 
1284
- auto activeTouch = std::find_if(m_activeTouches.begin(), m_activeTouches.end(), [pointerId](const auto &pair) {
1285
- return pair.second.touch.identifier == pointerId;
1286
- });
1408
+ auto activeTouch = m_activeTouches.find(pointerId);
1287
1409
 
1288
1410
  if (activeTouch == m_activeTouches.end()) {
1289
1411
  return;
@@ -1295,8 +1417,13 @@ void CompositionEventHandler::onPointerReleased(
1295
1417
  facebook::react::Point ptLocal, ptScaled;
1296
1418
  getTargetPointerArgs(fabricuiManager, pointerPoint, tag, ptScaled, ptLocal);
1297
1419
 
1298
- if (tag == -1)
1420
+ if (tag == -1) {
1421
+ if (activeTouch->second.eventEmitter) {
1422
+ DispatchSynthesizedTouchCancelForActiveTouch(activeTouch->second, pointerPoint, keyModifiers);
1423
+ }
1424
+ m_activeTouches.erase(pointerId);
1299
1425
  return;
1426
+ }
1300
1427
 
1301
1428
  auto targetComponentView = fabricuiManager->GetViewRegistry().componentViewDescriptorWithTag(tag).view;
1302
1429
  auto args = winrt::make<winrt::Microsoft::ReactNative::Composition::Input::implementation::PointerRoutedEventArgs>(
@@ -1328,7 +1455,7 @@ bool CompositionEventHandler::CapturePointer(
1328
1455
  }
1329
1456
 
1330
1457
  m_pointerCapturingComponentTag = tag;
1331
- m_capturedPointers.push_back(pointer.PointerId());
1458
+ m_capturedPointers.insert(pointer.PointerId());
1332
1459
  return true;
1333
1460
  }
1334
1461
 
@@ -1343,11 +1470,9 @@ bool CompositionEventHandler::releasePointerCapture(PointerId pointerId, faceboo
1343
1470
  bool result = false;
1344
1471
 
1345
1472
  if (m_pointerCapturingComponentTag == tag) {
1346
- auto it = std::find(m_capturedPointers.begin(), m_capturedPointers.end(), pointerId);
1347
- if (it == m_capturedPointers.end()) {
1473
+ if (m_capturedPointers.erase(pointerId) == 0) {
1348
1474
  return false;
1349
1475
  }
1350
- m_capturedPointers.erase(it);
1351
1476
 
1352
1477
  if (std::shared_ptr<FabricUIManager> fabricuiManager =
1353
1478
  ::Microsoft::ReactNative::FabricUIManager::FromProperties(m_context.Properties())) {
@@ -1358,7 +1483,7 @@ bool CompositionEventHandler::releasePointerCapture(PointerId pointerId, faceboo
1358
1483
  ->OnPointerCaptureLost();
1359
1484
  }
1360
1485
 
1361
- if (m_capturedPointers.size() == 0) {
1486
+ if (m_capturedPointers.empty()) {
1362
1487
  m_pointerCapturingComponentTag = -1;
1363
1488
  return true;
1364
1489
  }
@@ -1473,16 +1598,83 @@ bool CompositionEventHandler::IsPointerWithinInitialTree(const ActiveTouch &acti
1473
1598
  if (!initialComponentView)
1474
1599
  return false;
1475
1600
 
1476
- auto initialViewSet = GetTouchableViewsInPathToRoot(initialComponentView);
1601
+ auto *rootView = RootComponentView();
1602
+ if (!rootView)
1603
+ return false;
1604
+
1605
+ facebook::react::Point ptLocal;
1606
+ auto currentTag = rootView->hitTest(activeTouch.touch.pagePoint, ptLocal);
1607
+ if (currentTag == -1)
1608
+ return false;
1609
+
1610
+ auto fabricuiManager = ::Microsoft::ReactNative::FabricUIManager::FromProperties(m_context.Properties());
1611
+ if (!fabricuiManager)
1612
+ return false;
1477
1613
 
1478
- for (const auto &view : initialViewSet) {
1479
- if (view.Tag() == activeTouch.touch.target)
1614
+ auto initialTag = initialComponentView.Tag();
1615
+ auto &viewRegistry = fabricuiManager->GetViewRegistry();
1616
+ auto currentView = viewRegistry.componentViewDescriptorWithTag(currentTag).view;
1617
+ while (currentView) {
1618
+ if (currentView.Tag() == initialTag)
1480
1619
  return true;
1620
+ currentView = currentView.Parent();
1621
+ }
1622
+
1623
+ // Fallback: if the pointer drifted spatially but the original target
1624
+ // is still structurally within the initial tree, honor the tap.
1625
+ // This provides touch-device tolerance for finger drift.
1626
+ auto targetView = viewRegistry.componentViewDescriptorWithTag(activeTouch.touch.target).view;
1627
+ while (targetView) {
1628
+ if (targetView.Tag() == initialTag)
1629
+ return true;
1630
+ targetView = targetView.Parent();
1481
1631
  }
1482
1632
 
1483
1633
  return false;
1484
1634
  }
1485
1635
 
1636
+ void CompositionEventHandler::DispatchSynthesizedTouchCancelForActiveTouch(
1637
+ const ActiveTouch &cancelledTouch,
1638
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
1639
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers) {
1640
+ if (!cancelledTouch.eventEmitter) {
1641
+ return;
1642
+ }
1643
+
1644
+ facebook::react::PointerEvent pointerEvent =
1645
+ CreatePointerEventFromActiveTouch(cancelledTouch, TouchEventType::Cancel);
1646
+ winrt::Microsoft::ReactNative::ComponentView targetView{nullptr};
1647
+ facebook::react::SharedTouchEventEmitter emitter = cancelledTouch.eventEmitter;
1648
+ auto pointerHandler = [emitter, pointerEvent](std::vector<winrt::Microsoft::ReactNative::ComponentView> &) {
1649
+ emitter->onPointerCancel(pointerEvent);
1650
+ };
1651
+ HandleIncomingPointerEvent(pointerEvent, targetView, pointerPoint, keyModifiers, pointerHandler);
1652
+
1653
+ facebook::react::TouchEvent touchEvent;
1654
+ touchEvent.changedTouches.insert(cancelledTouch.touch);
1655
+
1656
+ for (const auto &pair : m_activeTouches) {
1657
+ if (!pair.second.eventEmitter) {
1658
+ continue;
1659
+ }
1660
+
1661
+ if (touchEvent.changedTouches.find(pair.second.touch) != touchEvent.changedTouches.end()) {
1662
+ continue;
1663
+ }
1664
+
1665
+ touchEvent.touches.insert(pair.second.touch);
1666
+ }
1667
+
1668
+ for (const auto &pair : m_activeTouches) {
1669
+ if (pair.second.eventEmitter == cancelledTouch.eventEmitter &&
1670
+ touchEvent.changedTouches.find(pair.second.touch) == touchEvent.changedTouches.end()) {
1671
+ touchEvent.targetTouches.insert(pair.second.touch);
1672
+ }
1673
+ }
1674
+
1675
+ cancelledTouch.eventEmitter->onTouchCancel(touchEvent);
1676
+ }
1677
+
1486
1678
  // If we have events that include multiple pointer updates, we should change arg from pointerId to vector<pointerId>
1487
1679
  void CompositionEventHandler::DispatchTouchEvent(
1488
1680
  TouchEventType eventType,
@@ -1507,7 +1699,7 @@ void CompositionEventHandler::DispatchTouchEvent(
1507
1699
  continue;
1508
1700
  }
1509
1701
 
1510
- if (activeTouch.touch.identifier == pointerId) {
1702
+ if (pair.first == pointerId) {
1511
1703
  event.changedTouches.insert(activeTouch.touch);
1512
1704
  }
1513
1705
  uniqueEventEmitters.insert(activeTouch.eventEmitter);
@@ -1518,16 +1710,19 @@ void CompositionEventHandler::DispatchTouchEvent(
1518
1710
  bool shouldLeave = (eventType == TouchEventType::End && activeTouch.shouldLeaveWhenReleased) ||
1519
1711
  eventType == TouchEventType::Cancel;
1520
1712
  if (!shouldLeave) {
1521
- const auto &viewRegistry = fabricuiManager->GetViewRegistry();
1522
- facebook::react::Point ptLocal;
1523
- auto targetTag = RootComponentView().hitTest(pointerEvent.clientPoint, ptLocal);
1524
- if (targetTag != -1) {
1525
- auto targetComponentViewDescriptor = viewRegistry.componentViewDescriptorWithTag(targetTag);
1526
- targetView = FindClosestFabricManagedTouchableView(targetComponentViewDescriptor.view);
1713
+ auto *rootViewForHit = RootComponentView();
1714
+ if (rootViewForHit) {
1715
+ const auto &viewRegistry = fabricuiManager->GetViewRegistry();
1716
+ facebook::react::Point ptLocal;
1717
+ auto targetTag = rootViewForHit->hitTest(pointerEvent.clientPoint, ptLocal);
1718
+ if (targetTag != -1) {
1719
+ auto targetComponentViewDescriptor = viewRegistry.componentViewDescriptorWithTag(targetTag);
1720
+ targetView = FindClosestFabricManagedTouchableView(targetComponentViewDescriptor.view);
1721
+ }
1527
1722
  }
1528
1723
  }
1529
1724
 
1530
- auto handler = [&activeTouch, eventType, &pointerEvent](
1725
+ auto handler = [this, &activeTouch, eventType, &pointerEvent](
1531
1726
  std::vector<winrt::Microsoft::ReactNative::ComponentView> &eventPathViews) {
1532
1727
  switch (eventType) {
1533
1728
  case TouchEventType::Start:
@@ -14,6 +14,7 @@
14
14
  #include <winrt/Windows.Devices.Input.h>
15
15
  #include <optional>
16
16
  #include <set>
17
+ #include <unordered_set>
17
18
 
18
19
  namespace winrt {
19
20
  using namespace Windows::UI;
@@ -79,7 +80,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
79
80
  bool releasePointerCapture(PointerId pointerId, facebook::react::Tag tag) noexcept;
80
81
 
81
82
  facebook::react::SurfaceId SurfaceId() const noexcept;
82
- winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView &RootComponentView() const noexcept;
83
+ winrt::Microsoft::ReactNative::Composition::implementation::RootComponentView *RootComponentView() const noexcept;
83
84
 
84
85
  enum class UITouchType {
85
86
  Mouse,
@@ -141,7 +142,7 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
141
142
  ReactTaggedView initialComponentView{nullptr};
142
143
  };
143
144
 
144
- static bool IsPointerWithinInitialTree(const ActiveTouch &activeTouch) noexcept;
145
+ bool IsPointerWithinInitialTree(const ActiveTouch &activeTouch) noexcept;
145
146
  static bool IsEndishEventType(TouchEventType eventType) noexcept;
146
147
  static const char *PointerTypeCStringFromUITouchType(UITouchType type) noexcept;
147
148
  static facebook::react::PointerEvent CreatePointerEventFromActiveTouch(
@@ -150,18 +151,31 @@ class CompositionEventHandler : public std::enable_shared_from_this<CompositionE
150
151
  static void
151
152
  UpdateActiveTouch(ActiveTouch &activeTouch, facebook::react::Point ptScaled, facebook::react::Point ptLocal) noexcept;
152
153
 
154
+ void DispatchSynthesizedTouchCancelForActiveTouch(
155
+ const ActiveTouch &cancelledTouch,
156
+ const winrt::Microsoft::ReactNative::Composition::Input::PointerPoint &pointerPoint,
157
+ winrt::Windows::System::VirtualKeyModifiers keyModifiers);
158
+
159
+ std::vector<winrt::Microsoft::ReactNative::ComponentView> GetTouchableViewsInPathToRoot(
160
+ const winrt::Microsoft::ReactNative::ComponentView &componentView);
161
+
153
162
  void UpdateCursor() noexcept;
154
163
  void SetCursor(facebook::react::Cursor cursor, HCURSOR hcur) noexcept;
155
164
 
165
+ // Allocates a small touch identifier (0–19) that is safe to use as a JS array index.
166
+ // Windows pointer IDs can be arbitrarily large, which causes React Native to warn and
167
+ // back-fill huge arrays, corrupting touch state after scrolling.
168
+ int AllocateTouchIdentifier() noexcept;
169
+
156
170
  std::map<PointerId, ActiveTouch> m_activeTouches; // iOS is map of touch event args to ActiveTouch..?
157
- PointerId m_touchId = 0;
171
+ int m_touchId = 0; // cycling base used by AllocateTouchIdentifier
158
172
 
159
173
  std::map<PointerId, std::vector<ReactTaggedView>> m_currentlyHoveredViewsPerPointer;
160
174
  winrt::weak_ref<winrt::Microsoft::ReactNative::ReactNativeIsland> m_wkRootView;
161
175
  winrt::Microsoft::ReactNative::ReactContext m_context;
162
176
 
163
177
  facebook::react::Tag m_pointerCapturingComponentTag{-1}; // Component that has captured input
164
- std::vector<PointerId> m_capturedPointers;
178
+ std::unordered_set<PointerId> m_capturedPointers;
165
179
  HCURSOR m_hcursor{nullptr};
166
180
  bool m_hcursorOwned{false}; // If we create the cursor, so we need to destroy it
167
181
  facebook::react::Cursor m_currentCursor{facebook::react::Cursor::Auto};