Event API: ensure calculateResponderRegion accounts for page offset (#15671)
Dominic Gannaway committed
May 16, 2019 at 17:09 UTC
aad5a264d27bba113d322613587997d2987ba2d3
2 files changed
+76
-6
packages/react-events/src/Press.js
+25
-6
@@ -428,6 +428,29 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
428
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
429
}
430
431
+function getAbsoluteBoundingClientRect(
432
+ target: Element,
433
+): {left: number, right: number, bottom: number, top: number} {
434
+ const clientRect = target.getBoundingClientRect();
435
+ let {left, right, bottom, top} = clientRect;
436
+ let node = target.parentNode;
437
+ let offsetX = 0;
438
+ let offsetY = 0;
439
+
440
+ // Traverse through all offset nodes
441
+ while (node != null && node.nodeType !== Node.DOCUMENT_NODE) {
442
+ offsetX += (node: any).scrollLeft;
443
+ offsetY += (node: any).scrollTop;
444
+ node = node.parentNode;
445
+ }
446
+ return {
447
+ left: left + offsetX,
448
+ right: right + offsetX,
449
+ bottom: bottom + offsetY,
450
+ top: top + offsetY,
451
+ };
452
+}
453
+
454
// TODO: account for touch hit slop
455
function calculateResponderRegion(
456
context: ReactResponderContext,
@@ -440,12 +463,8 @@ function calculateResponderRegion(
463
props.pressRetentionOffset,
464
);
465
443
- const clientRect = target.getBoundingClientRect();
444
-
445
- let bottom = clientRect.bottom;
446
- let left = clientRect.left;
447
- let right = clientRect.right;
448
- let top = clientRect.top;
466
+ const clientRect = getAbsoluteBoundingClientRect(target);
467
+ let {left, right, bottom, top} = clientRect;
468
469
if (pressRetentionOffset) {
470
if (pressRetentionOffset.bottom != null) {
packages/react-events/src/__tests__/Press-test.internal.js
+51
@@ -1085,6 +1085,57 @@ describe('Event responder: Press', () => {
1085
});
1086
});
1087
1088
+ describe('the page offset changes', () => {
1089
+ it('"onPress" is called on release', () => {
1090
+ let events = [];
1091
+ const ref = React.createRef();
1092
+ const createEventHandler = msg => () => {
1093
+ events.push(msg);
1094
+ };
1095
+
1096
+ const element = (
1097
+ <Press
1098
+ onPress={createEventHandler('onPress')}
1099
+ onPressChange={createEventHandler('onPressChange')}
1100
+ onPressMove={createEventHandler('onPressMove')}
1101
+ onPressStart={createEventHandler('onPressStart')}
1102
+ onPressEnd={createEventHandler('onPressEnd')}>
1103
+ <div ref={ref} />
1104
+ </Press>
1105
+ );
1106
+
1107
+ ReactDOM.render(element, container);
1108
+
1109
+ ref.current.getBoundingClientRect = getBoundingClientRectMock;
1110
+ // Emulate the element being offset
1111
+ document.body.scrollTop = 1000;
1112
+ const updatedCoordinatesInside = {
1113
+ pageX: coordinatesInside.pageX,
1114
+ pageY: coordinatesInside.pageY + 1000,
1115
+ };
1116
+ ref.current.dispatchEvent(
1117
+ createEvent('pointerdown', updatedCoordinatesInside),
1118
+ );
1119
+ container.dispatchEvent(
1120
+ createEvent('pointermove', updatedCoordinatesInside),
1121
+ );
1122
+ container.dispatchEvent(
1123
+ createEvent('pointerup', updatedCoordinatesInside),
1124
+ );
1125
+ jest.runAllTimers();
1126
+ document.body.scrollTop = 0;
1127
+
1128
+ expect(events).toEqual([
1129
+ 'onPressStart',
1130
+ 'onPressChange',
1131
+ 'onPressMove',
1132
+ 'onPressEnd',
1133
+ 'onPressChange',
1134
+ 'onPress',
1135
+ ]);
1136
+ });
1137
+ });
1138
+
1139
describe('beyond bounds of hit rect', () => {
1140
/** ┌──────────────────┐
1141
* │ ┌────────────┐ │