diff --git a/packages/react-native-web/src/hooks/useResponderEvents/ResponderSystem.js b/packages/react-native-web/src/hooks/useResponderEvents/ResponderSystem.js index 7493f299..a61d0449 100644 --- a/packages/react-native-web/src/hooks/useResponderEvents/ResponderSystem.js +++ b/packages/react-native-web/src/hooks/useResponderEvents/ResponderSystem.js @@ -329,21 +329,28 @@ function eventListener(domEvent: any) { if (currentResponderIdPath != null && eventIdPath != null) { const lowestCommonAncestor = getLowestCommonAncestor(currentResponderIdPath, eventIdPath); - const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor); - // Skip the current responder so it doesn't receive unexpected "shouldSet" events. - const index = - indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0); - eventPaths = { - idPath: eventIdPath.slice(index), - nodePath: eventPaths.nodePath.slice(index) - }; + if (lowestCommonAncestor != null) { + const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor); + // Skip the current responder so it doesn't receive unexpected "shouldSet" events. + const index = + indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0); + eventPaths = { + idPath: eventIdPath.slice(index), + nodePath: eventPaths.nodePath.slice(index) + }; + } else { + eventPaths = null; + } } - // If a node wants to become the responder, attempt to transfer. - wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent); - if (wantsResponder != null) { - // Sets responder if none exists, or negotates with existing responder. - attemptTransfer(responderEvent, wantsResponder); - wasNegotiated = true; + + if (eventPaths != null) { + // If a node wants to become the responder, attempt to transfer. + wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent); + if (wantsResponder != null) { + // Sets responder if none exists, or negotates with existing responder. + attemptTransfer(responderEvent, wantsResponder); + wasNegotiated = true; + } } } diff --git a/packages/react-native-web/src/hooks/useResponderEvents/__tests__/index-test.js b/packages/react-native-web/src/hooks/useResponderEvents/__tests__/index-test.js index 9c29f45d..ebc9afac 100644 --- a/packages/react-native-web/src/hooks/useResponderEvents/__tests__/index-test.js +++ b/packages/react-native-web/src/hooks/useResponderEvents/__tests__/index-test.js @@ -2154,6 +2154,106 @@ describe('useResponderEvents', () => { ]); }); + /** + * If siblings are connected to the responder system but have no ancestors + * connected, there should be no negotiation between siblings after one + * becomes the active responder. + */ + test('no negotation between siblings with no responder ancestors', () => { + const pointerType = 'mouse'; + const eventLog = []; + + const targetConfig = { + onStartShouldSetResponderCapture(e) { + eventLog.push('target: onStartShouldSetResponderCapture'); + return false; + }, + onStartShouldSetResponder(e) { + eventLog.push('target: onStartShouldSetResponder'); + return true; + }, + onMoveShouldSetResponderCapture(e) { + eventLog.push('target: onMoveShouldSetResponderCapture'); + return false; + }, + onMoveShouldSetResponder(e) { + eventLog.push('target: onMoveShouldSetResponder'); + return false; + }, + onResponderGrant(e) { + eventLog.push('target: onResponderGrant'); + }, + onResponderStart(e) { + eventLog.push('target: onResponderStart'); + }, + onResponderMove(e) { + eventLog.push('target: onResponderMove'); + } + }; + const siblingConfig = { + onStartShouldSetResponderCapture(e) { + eventLog.push('sibling: onStartShouldSetResponderCapture'); + return true; + }, + onStartShouldSetResponder(e) { + eventLog.push('sibling: onStartShouldSetResponder'); + return true; + }, + onMoveShouldSetResponderCapture(e) { + eventLog.push('sibling: onMoveShouldSetResponderCapture'); + return true; + }, + onMoveShouldSetResponder(e) { + eventLog.push('sibling: onMoveShouldSetResponder'); + return true; + }, + onResponderGrant(e) { + eventLog.push('sibling: onResponderGrant'); + }, + onResponderStart(e) { + eventLog.push('sibling: onResponderStart'); + }, + onResponderMove(e) { + eventLog.push('sibling: onResponderMove'); + } + }; + + const Component = () => { + useResponderEvents(targetRef, targetConfig); + useResponderEvents(siblingRef, siblingConfig); + return ( +