Skip to content

Commit 3233d0f

Browse files
committed
[fix] Responder negotiation between siblings
There should be responder negotiation between siblings if there is no common ancestor connected to the responder system. Instead the current responder should continue to receive events. This was only occuring for mouse events during mousemove, as the target can change during the course of the movement.
1 parent 251cdfb commit 3233d0f

2 files changed

Lines changed: 121 additions & 14 deletions

File tree

packages/react-native-web/src/hooks/useResponderEvents/ResponderSystem.js

Lines changed: 21 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -329,21 +329,28 @@ function eventListener(domEvent: any) {
329329

330330
if (currentResponderIdPath != null && eventIdPath != null) {
331331
const lowestCommonAncestor = getLowestCommonAncestor(currentResponderIdPath, eventIdPath);
332-
const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor);
333-
// Skip the current responder so it doesn't receive unexpected "shouldSet" events.
334-
const index =
335-
indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0);
336-
eventPaths = {
337-
idPath: eventIdPath.slice(index),
338-
nodePath: eventPaths.nodePath.slice(index)
339-
};
332+
if (lowestCommonAncestor != null) {
333+
const indexOfLowestCommonAncestor = eventIdPath.indexOf(lowestCommonAncestor);
334+
// Skip the current responder so it doesn't receive unexpected "shouldSet" events.
335+
const index =
336+
indexOfLowestCommonAncestor + (lowestCommonAncestor === currentResponder.id ? 1 : 0);
337+
eventPaths = {
338+
idPath: eventIdPath.slice(index),
339+
nodePath: eventPaths.nodePath.slice(index)
340+
};
341+
} else {
342+
eventPaths = null;
343+
}
340344
}
341-
// If a node wants to become the responder, attempt to transfer.
342-
wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent);
343-
if (wantsResponder != null) {
344-
// Sets responder if none exists, or negotates with existing responder.
345-
attemptTransfer(responderEvent, wantsResponder);
346-
wasNegotiated = true;
345+
346+
if (eventPaths != null) {
347+
// If a node wants to become the responder, attempt to transfer.
348+
wantsResponder = findWantsResponder(eventPaths, domEvent, responderEvent);
349+
if (wantsResponder != null) {
350+
// Sets responder if none exists, or negotates with existing responder.
351+
attemptTransfer(responderEvent, wantsResponder);
352+
wasNegotiated = true;
353+
}
347354
}
348355
}
349356

packages/react-native-web/src/hooks/useResponderEvents/__tests__/index-test.js

Lines changed: 100 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2154,6 +2154,106 @@ describe('useResponderEvents', () => {
21542154
]);
21552155
});
21562156

2157+
/**
2158+
* If siblings are connected to the responder system but have no ancestors
2159+
* connected, there should be no negotiation between siblings after one
2160+
* becomes the active responder.
2161+
*/
2162+
test('no negotation between siblings with no responder ancestors', () => {
2163+
const pointerType = 'mouse';
2164+
const eventLog = [];
2165+
2166+
const targetConfig = {
2167+
onStartShouldSetResponderCapture(e) {
2168+
eventLog.push('target: onStartShouldSetResponderCapture');
2169+
return false;
2170+
},
2171+
onStartShouldSetResponder(e) {
2172+
eventLog.push('target: onStartShouldSetResponder');
2173+
return true;
2174+
},
2175+
onMoveShouldSetResponderCapture(e) {
2176+
eventLog.push('target: onMoveShouldSetResponderCapture');
2177+
return false;
2178+
},
2179+
onMoveShouldSetResponder(e) {
2180+
eventLog.push('target: onMoveShouldSetResponder');
2181+
return false;
2182+
},
2183+
onResponderGrant(e) {
2184+
eventLog.push('target: onResponderGrant');
2185+
},
2186+
onResponderStart(e) {
2187+
eventLog.push('target: onResponderStart');
2188+
},
2189+
onResponderMove(e) {
2190+
eventLog.push('target: onResponderMove');
2191+
}
2192+
};
2193+
const siblingConfig = {
2194+
onStartShouldSetResponderCapture(e) {
2195+
eventLog.push('sibling: onStartShouldSetResponderCapture');
2196+
return true;
2197+
},
2198+
onStartShouldSetResponder(e) {
2199+
eventLog.push('sibling: onStartShouldSetResponder');
2200+
return true;
2201+
},
2202+
onMoveShouldSetResponderCapture(e) {
2203+
eventLog.push('sibling: onMoveShouldSetResponderCapture');
2204+
return true;
2205+
},
2206+
onMoveShouldSetResponder(e) {
2207+
eventLog.push('sibling: onMoveShouldSetResponder');
2208+
return true;
2209+
},
2210+
onResponderGrant(e) {
2211+
eventLog.push('sibling: onResponderGrant');
2212+
},
2213+
onResponderStart(e) {
2214+
eventLog.push('sibling: onResponderStart');
2215+
},
2216+
onResponderMove(e) {
2217+
eventLog.push('sibling: onResponderMove');
2218+
}
2219+
};
2220+
2221+
const Component = () => {
2222+
useResponderEvents(targetRef, targetConfig);
2223+
useResponderEvents(siblingRef, siblingConfig);
2224+
return (
2225+
<div id="grandParent">
2226+
<div id="parent">
2227+
<div id="target" ref={targetRef} />
2228+
<div id="sibling" ref={siblingRef} />
2229+
</div>
2230+
</div>
2231+
);
2232+
};
2233+
2234+
// render
2235+
act(() => {
2236+
render(<Component />);
2237+
});
2238+
const target = createEventTarget(targetRef.current);
2239+
const sibling = createEventTarget(siblingRef.current);
2240+
// gesture start and move on target
2241+
act(() => {
2242+
target.pointerdown({ pointerType });
2243+
target.pointermove({ pointerType });
2244+
sibling.pointermove({ pointerType });
2245+
});
2246+
// target remains responder, no negotation occurs
2247+
expect(eventLog).toEqual([
2248+
'target: onStartShouldSetResponderCapture',
2249+
'target: onStartShouldSetResponder',
2250+
'target: onResponderGrant',
2251+
'target: onResponderStart',
2252+
'target: onResponderMove',
2253+
'target: onResponderMove'
2254+
]);
2255+
});
2256+
21572257
/**
21582258
* If a node is responder and it rejects a termination request, it
21592259
* should continue to receive responder events.

0 commit comments

Comments
 (0)