@samitouri / QOS-React-2 / commits / b5dff62faf

[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',