[Flare] Account for fixed elements in getAbsoluteBoundingClientRect (#15707)
Dominic Gannaway committed
May 22, 2019 at 19:54 UTC
b5dff62faf4c8fd104362f48e3651332b377f28a
2 files changed
+147
-7
packages/react-events/src/Press.js
+26
-4
@@ -436,10 +436,32 @@ function getAbsoluteBoundingClientRect(
436
let offsetY = 0;
437
438
// Traverse through all offset nodes
439
- while (node != null && node.nodeType !== Node.DOCUMENT_NODE) {
440
- offsetX += (node: any).scrollLeft;
441
- offsetY += (node: any).scrollTop;
442
- node = node.parentNode;
439
+ while (node != null) {
440
+ const parent = node.parentNode;
441
+ const scrollTop = node.scrollTop;
442
+ const scrollLeft = node.scrollLeft;
443
+ const isParentDocumentNode =
444
+ parent !== null && parent.nodeType === Node.DOCUMENT_NODE;
445
+
446
+ // Check if the current node is fixed position, by using
447
+ // offsetParent node for a fast-path. Then we need to
448
+ // check if it's a scrollable container by checking if
449
+ // either scrollLeft or scrollTop are not 0. If it is
450
+ // a scrollable container and the parent node is not
451
+ // the document, then we can stop traversing the tree.
452
+ if (
453
+ !isParentDocumentNode &&
454
+ node.offsetParent === null &&
455
+ (scrollLeft !== 0 || scrollTop !== 0)
456
+ ) {
457
+ break;
458
+ }
459
+ offsetX += scrollLeft;
460
+ offsetY += scrollTop;
461
+ if (isParentDocumentNode) {
462
+ break;
463
+ }
464
+ node = parent;
465
}
466
return {
467
left: left + offsetX,
packages/react-events/src/__tests__/Press-test.internal.js
+121
-3
@@ -1109,8 +1109,8 @@ describe('Event responder: Press', () => {
1109
ReactDOM.render(element, container);
1110
1111
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1112
- // Emulate the element being offset
1113
- document.body.scrollTop = 1000;
1112
+ // Emulate the <html> element being offset with scroll
1113
+ document.firstElementChild.scrollTop = 1000;
1114
const updatedCoordinatesInside = {
1115
pageX: coordinatesInside.pageX,
1116
pageY: coordinatesInside.pageY + 1000,
@@ -1125,7 +1125,125 @@ describe('Event responder: Press', () => {
1125
createEvent('pointerup', updatedCoordinatesInside),
1126
);
1127
jest.runAllTimers();
1128
- document.body.scrollTop = 0;
1128
+ document.firstElementChild.scrollTop = 0;
1129
+
1130
+ expect(events).toEqual([
1131
+ 'onPressStart',
1132
+ 'onPressChange',
1133
+ 'onPressMove',
1134
+ 'onPressEnd',
1135
+ 'onPressChange',
1136
+ 'onPress',
1137
+ ]);
1138
+ });
1139
+
1140
+ it('"onPress" is called on release inside a fixed container', () => {
1141
+ let events = [];
1142
+ const ref = React.createRef();
1143
+ const fixedContainerRef = React.createRef();
1144
+ const createEventHandler = msg => () => {
1145
+ events.push(msg);
1146
+ };
1147
+
1148
+ const element = (
1149
+ <div ref={fixedContainerRef}>
1150
+ <Press
1151
+ onPress={createEventHandler('onPress')}
1152
+ onPressChange={createEventHandler('onPressChange')}
1153
+ onPressMove={createEventHandler('onPressMove')}
1154
+ onPressStart={createEventHandler('onPressStart')}
1155
+ onPressEnd={createEventHandler('onPressEnd')}>
1156
+ <div ref={ref} />
1157
+ </Press>
1158
+ </div>
1159
+ );
1160
+
1161
+ ReactDOM.render(element, container);
1162
+
1163
+ const fixedDiv = fixedContainerRef.current;
1164
+ Object.defineProperty(fixedDiv, 'offsetParent', {
1165
+ value: null,
1166
+ });
1167
+
1168
+ // The fixed container is not scrolled
1169
+ fixedDiv.scrollTop = 0;
1170
+ ref.current.getBoundingClientRect = getBoundingClientRectMock;
1171
+ // Emulate the <html> element being offset with scroll
1172
+ document.firstElementChild.scrollTop = 1000;
1173
+ const updatedCoordinatesInside = {
1174
+ pageX: coordinatesInside.pageX,
1175
+ pageY: coordinatesInside.pageY + 1000,
1176
+ };
1177
+ ref.current.dispatchEvent(
1178
+ createEvent('pointerdown', updatedCoordinatesInside),
1179
+ );
1180
+ container.dispatchEvent(
1181
+ createEvent('pointermove', updatedCoordinatesInside),
1182
+ );
1183
+ container.dispatchEvent(
1184
+ createEvent('pointerup', updatedCoordinatesInside),
1185
+ );
1186
+ jest.runAllTimers();
1187
+ document.firstElementChild.scrollTop = 0;
1188
+
1189
+ expect(events).toEqual([
1190
+ 'onPressStart',
1191
+ 'onPressChange',
1192
+ 'onPressMove',
1193
+ 'onPressEnd',
1194
+ 'onPressChange',
1195
+ 'onPress',
1196
+ ]);
1197
+ });
1198
+
1199
+ it('"onPress" is called on release inside a fixed scrolled container', () => {
1200
+ let events = [];
1201
+ const ref = React.createRef();
1202
+ const fixedContainerRef = React.createRef();
1203
+ const createEventHandler = msg => () => {
1204
+ events.push(msg);
1205
+ };
1206
+
1207
+ const element = (
1208
+ <div ref={fixedContainerRef}>
1209
+ <Press
1210
+ onPress={createEventHandler('onPress')}
1211
+ onPressChange={createEventHandler('onPressChange')}
1212
+ onPressMove={createEventHandler('onPressMove')}
1213
+ onPressStart={createEventHandler('onPressStart')}
1214
+ onPressEnd={createEventHandler('onPressEnd')}>
1215
+ <div ref={ref} />
1216
+ </Press>
1217
+ </div>
1218
+ );
1219
+
1220
+ ReactDOM.render(element, container);
1221
+
1222
+ const fixedDiv = fixedContainerRef.current;
1223
+ Object.defineProperty(fixedDiv, 'offsetParent', {
1224
+ value: null,
1225
+ });
1226
+
1227
+ // The fixed container is scrolled
1228
+ fixedDiv.scrollTop = 100;
1229
+ ref.current.getBoundingClientRect = getBoundingClientRectMock;
1230
+ // Emulate the <html> element being offset with scroll
1231
+ document.firstElementChild.scrollTop = 1000;
1232
+ const updatedCoordinatesInside = {
1233
+ pageX: coordinatesInside.pageX,
1234
+ pageY: coordinatesInside.pageY + 100,
1235
+ };
1236
+ ref.current.dispatchEvent(
1237
+ createEvent('pointerdown', updatedCoordinatesInside),
1238
+ );
1239
+ container.dispatchEvent(
1240
+ createEvent('pointermove', updatedCoordinatesInside),
1241
+ );
1242
+ container.dispatchEvent(
1243
+ createEvent('pointerup', updatedCoordinatesInside),
1244
+ );
1245
+ jest.runAllTimers();
1246
+ document.firstElementChild.scrollTop = 0;
1247
1248
expect(events).toEqual([
1249
'onPressStart',