diff --git a/change/react-native-windows-f47bdf2a-0807-483f-b63e-3c2c086bee92.json b/change/react-native-windows-f47bdf2a-0807-483f-b63e-3c2c086bee92.json new file mode 100644 index 00000000000..541f74718ee --- /dev/null +++ b/change/react-native-windows-f47bdf2a-0807-483f-b63e-3c2c086bee92.json @@ -0,0 +1,7 @@ +{ + "type": "prerelease", + "comment": "fix: cancel active touches in onPointerCaptureLost to prevent zombie touch state", + "packageName": "react-native-windows", + "email": "gordomacmaster@gmail.com", + "dependentChangeType": "patch" +} \ No newline at end of file diff --git a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp index 928ee52b407..99890b594eb 100644 --- a/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp +++ b/vnext/Microsoft.ReactNative/Fabric/Composition/CompositionEventHandler.cpp @@ -1092,12 +1092,26 @@ void CompositionEventHandler::onPointerCaptureLost( if (SurfaceId() == -1) return; - if (m_pointerCapturingComponentTag) { + if (m_pointerCapturingComponentTag != -1) { // copy array to avoid iterator being invalidated during deletion std::unordered_set capturedPointers = m_capturedPointers; for (auto pointerId : capturedPointers) { releasePointerCapture(pointerId, m_pointerCapturingComponentTag); + + // Cancel any active touch for this pointer so React Native is notified that + // the touch ended. Without this, m_activeTouches retains a zombie entry and + // RN JS is never told the touch is gone — leaving Pressables stuck in a + // pressed state after a system-interrupted gesture (e.g. system back swipe, + // Alt+Tab, another window coming foreground). + auto activeTouch = m_activeTouches.find(pointerId); + if (activeTouch != m_activeTouches.end()) { + ActiveTouch cancelledTouchCopy = std::move(activeTouch->second); + m_activeTouches.erase(activeTouch); + if (cancelledTouchCopy.eventEmitter) { + DispatchSynthesizedTouchCancelForActiveTouch(cancelledTouchCopy, pointerPoint, keyModifiers); + } + } } m_pointerCapturingComponentTag = -1;