[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.
This commit is contained in:
Nicolas Gallagher
2020-08-10 13:26:13 -07:00
parent 251cdfb220
commit 3233d0ffe9
2 changed files with 121 additions and 14 deletions
@@ -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;
}
}
}
@@ -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 (
<div id="grandParent">
<div id="parent">
<div id="target" ref={targetRef} />
<div id="sibling" ref={siblingRef} />
</div>
</div>
);
};
// render
act(() => {
render(<Component />);
});
const target = createEventTarget(targetRef.current);
const sibling = createEventTarget(siblingRef.current);
// gesture start and move on target
act(() => {
target.pointerdown({ pointerType });
target.pointermove({ pointerType });
sibling.pointermove({ pointerType });
});
// target remains responder, no negotation occurs
expect(eventLog).toEqual([
'target: onStartShouldSetResponderCapture',
'target: onStartShouldSetResponder',
'target: onResponderGrant',
'target: onResponderStart',
'target: onResponderMove',
'target: onResponderMove'
]);
});
/**
* If a node is responder and it rejects a termination request, it
* should continue to receive responder events.