@samitouri / QOS-React-2 / commits / 339366c461

Event API: Support press reentry for pointer events (#15560)

Dominic Gannaway committed May 3, 2019 at 18:32 UTC 339366c461acca41d12ce15264181bc94aaa0eb4
2 files changed +211 -7
packages/react-events/src/Press.js
+24 -7
@@ -68,6 +68,7 @@ type PressState = {
68 top: number,
69 |}>,
70 ignoreEmulatedMouseEvents: boolean,
71 + allowPressReentry: boolean,
72 };
73
74 type PressEventType =
@@ -301,7 +302,6 @@ function dispatchPressEndEvents(
302 deactivate(context, props, state);
303 }
304 }
304 - removeRootEventTypes(context, state);
305 }
306
307 function isAnchorTagElement(eventTarget: EventTarget): boolean {
@@ -395,6 +395,7 @@ function unmountResponder(
395 state: PressState,
396 ): void {
397 if (state.isPressed) {
398 + removeRootEventTypes(context, state);
399 dispatchPressEndEvents(context, props, state);
400 }
401 }
@@ -411,8 +412,11 @@ function dispatchCancel(
412 (nativeEvent: any).preventDefault();
413 } else {
414 state.ignoreEmulatedMouseEvents = false;
415 + removeRootEventTypes(context, state);
416 dispatchPressEndEvents(context, props, state);
417 }
418 + } else if (state.allowPressReentry) {
419 + removeRootEventTypes(context, state);
420 }
421 }
422
@@ -432,6 +436,7 @@ function removeRootEventTypes(
436 ): void {
437 if (state.addedRootEvents) {
438 state.addedRootEvents = false;
439 + state.allowPressReentry = false;
440 context.removeRootEventTypes(rootEventTypes);
441 }
442 }
@@ -455,6 +460,7 @@ const PressResponder = {
460 responderRegionOnActivation: null,
461 responderRegionOnDeactivation: null,
462 ignoreEmulatedMouseEvents: false,
463 + allowPressReentry: false,
464 };
465 },
466 stopLocalPropagation: true,
@@ -467,6 +473,7 @@ const PressResponder = {
473 const {target, type} = event;
474
475 if (props.disabled) {
476 + removeRootEventTypes(context, state);
477 dispatchPressEndEvents(context, props, state);
478 state.ignoreEmulatedMouseEvents = false;
479 return;
@@ -512,6 +519,7 @@ const PressResponder = {
519 return;
520 }
521
522 + state.allowPressReentry = true;
523 state.pointerType = pointerType;
524 state.pressTarget = getEventCurrentTarget(event, context);
525 state.responderRegionOnActivation = calculateResponderRegion(
@@ -565,7 +573,7 @@ const PressResponder = {
573 case 'pointermove':
574 case 'mousemove':
575 case 'touchmove': {
568 - if (state.isPressed) {
576 + if (state.isPressed || state.allowPressReentry) {
577 // Ignore emulated events (pointermove will dispatch touch and mouse events)
578 // Ignore pointermove events during a keyboard press.
579 if (state.pointerType !== pointerType) {
@@ -589,18 +597,24 @@ const PressResponder = {
597 );
598
599 if (state.isPressWithinResponderRegion) {
592 - if (props.onPressMove) {
593 - dispatchEvent(context, state, 'pressmove', props.onPressMove, {
594 - discrete: false,
595 - });
600 + if (state.isPressed) {
601 + if (props.onPressMove) {
602 + dispatchEvent(context, state, 'pressmove', props.onPressMove, {
603 + discrete: false,
604 + });
605 + }
606 + } else {
607 + dispatchPressStartEvents(context, props, state);
608 }
609 } else {
610 + if (!state.allowPressReentry) {
611 + removeRootEventTypes(context, state);
612 + }
613 dispatchPressEndEvents(context, props, state);
614 }
615 }
616 break;
617 }
603 -
618 // END
619 case 'pointerup':
620 case 'keyup':
@@ -635,6 +649,7 @@ const PressResponder = {
649 }
650
651 const wasLongPressed = state.isLongPressed;
652 + removeRootEventTypes(context, state);
653 dispatchPressEndEvents(context, props, state);
654
655 if (state.pressTarget !== null && props.onPress) {
@@ -652,6 +667,8 @@ const PressResponder = {
667 }
668 } else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
669 state.ignoreEmulatedMouseEvents = false;
670 + } else if (state.allowPressReentry) {
671 + removeRootEventTypes(context, state);
672 }
673 break;
674 }
packages/react-events/src/__tests__/Press-test.internal.js
+187
@@ -1148,6 +1148,193 @@ describe('Event responder: Press', () => {
1148 expect(events).toEqual([]);
1149 });
1150 });
1151 +
1152 + it('"onPress" is not called on release with mouse', () => {
1153 + let events = [];
1154 + const ref = React.createRef();
1155 + const createEventHandler = msg => () => {
1156 + events.push(msg);
1157 + };
1158 +
1159 + const element = (
1160 + <Press
1161 + onPress={createEventHandler('onPress')}
1162 + onPressChange={createEventHandler('onPressChange')}
1163 + onPressMove={createEventHandler('onPressMove')}
1164 + onPressStart={createEventHandler('onPressStart')}
1165 + onPressEnd={createEventHandler('onPressEnd')}>
1166 + <div ref={ref} />
1167 + </Press>
1168 + );
1169 +
1170 + ReactDOM.render(element, container);
1171 +
1172 + ref.current.getBoundingClientRect = getBoundingClientRectMock;
1173 + ref.current.dispatchEvent(
1174 + createPointerEvent('pointerdown', {
1175 + pointerType: 'mouse',
1176 + }),
1177 + );
1178 + ref.current.dispatchEvent(
1179 + createPointerEvent('pointermove', {
1180 + ...coordinatesInside,
1181 + pointerType: 'mouse',
1182 + }),
1183 + );
1184 + container.dispatchEvent(
1185 + createPointerEvent('pointermove', {
1186 + ...coordinatesOutside,
1187 + pointerType: 'mouse',
1188 + }),
1189 + );
1190 + container.dispatchEvent(
1191 + createPointerEvent('pointerup', {
1192 + ...coordinatesOutside,
1193 + pointerType: 'mouse',
1194 + }),
1195 + );
1196 + jest.runAllTimers();
1197 +
1198 + expect(events).toEqual([
1199 + 'onPressStart',
1200 + 'onPressChange',
1201 + 'onPressMove',
1202 + 'onPressEnd',
1203 + 'onPressChange',
1204 + ]);
1205 + });
1206 +
1207 + it('"onPress" is called on re-entry to hit rect for mouse', () => {
1208 + let events = [];
1209 + const ref = React.createRef();
1210 + const createEventHandler = msg => () => {
1211 + events.push(msg);
1212 + };
1213 +
1214 + const element = (
1215 + <Press
1216 + onPress={createEventHandler('onPress')}
1217 + onPressChange={createEventHandler('onPressChange')}
1218 + onPressMove={createEventHandler('onPressMove')}
1219 + onPressStart={createEventHandler('onPressStart')}
1220 + onPressEnd={createEventHandler('onPressEnd')}>
1221 + <div ref={ref} />
1222 + </Press>
1223 + );
1224 +
1225 + ReactDOM.render(element, container);
1226 +
1227 + ref.current.getBoundingClientRect = getBoundingClientRectMock;
1228 + ref.current.dispatchEvent(
1229 + createPointerEvent('pointerdown', {
1230 + pointerType: 'mouse',
1231 + }),
1232 + );
1233 + ref.current.dispatchEvent(
1234 + createPointerEvent('pointermove', {
1235 + ...coordinatesInside,
1236 + pointerType: 'mouse',
1237 + }),
1238 + );
1239 + container.dispatchEvent(
1240 + createPointerEvent('pointermove', {
1241 + ...coordinatesOutside,
1242 + pointerType: 'mouse',
1243 + }),
1244 + );
1245 + container.dispatchEvent(
1246 + createPointerEvent('pointermove', {
1247 + ...coordinatesInside,
1248 + pointerType: 'mouse',
1249 + }),
1250 + );
1251 + container.dispatchEvent(
1252 + createPointerEvent('pointerup', {
1253 + ...coordinatesInside,
1254 + pointerType: 'mouse',
1255 + }),
1256 + );
1257 + jest.runAllTimers();
1258 +
1259 + expect(events).toEqual([
1260 + 'onPressStart',
1261 + 'onPressChange',
1262 + 'onPressMove',
1263 + 'onPressEnd',
1264 + 'onPressChange',
1265 + 'onPressStart',
1266 + 'onPressChange',
1267 + 'onPressEnd',
1268 + 'onPressChange',
1269 + 'onPress',
1270 + ]);
1271 + });
1272 +
1273 + it('"onPress" is called on re-entry to hit rect for touch', () => {
1274 + let events = [];
1275 + const ref = React.createRef();
1276 + const createEventHandler = msg => () => {
1277 + events.push(msg);
1278 + };
1279 +
1280 + const element = (
1281 + <Press
1282 + onPress={createEventHandler('onPress')}
1283 + onPressChange={createEventHandler('onPressChange')}
1284 + onPressMove={createEventHandler('onPressMove')}
1285 + onPressStart={createEventHandler('onPressStart')}
1286 + onPressEnd={createEventHandler('onPressEnd')}>
1287 + <div ref={ref} />
1288 + </Press>
1289 + );
1290 +
1291 + ReactDOM.render(element, container);
1292 +
1293 + ref.current.getBoundingClientRect = getBoundingClientRectMock;
1294 + ref.current.dispatchEvent(
1295 + createPointerEvent('pointerdown', {
1296 + pointerType: 'touch',
1297 + }),
1298 + );
1299 + ref.current.dispatchEvent(
1300 + createPointerEvent('pointermove', {
1301 + ...coordinatesInside,
1302 + pointerType: 'touch',
1303 + }),
1304 + );
1305 + container.dispatchEvent(
1306 + createPointerEvent('pointermove', {
1307 + ...coordinatesOutside,
1308 + pointerType: 'touch',
1309 + }),
1310 + );
1311 + container.dispatchEvent(
1312 + createPointerEvent('pointermove', {
1313 + ...coordinatesInside,
1314 + pointerType: 'touch',
1315 + }),
1316 + );
1317 + container.dispatchEvent(
1318 + createPointerEvent('pointerup', {
1319 + ...coordinatesInside,
1320 + pointerType: 'touch',
1321 + }),
1322 + );
1323 + jest.runAllTimers();
1324 +
1325 + expect(events).toEqual([
1326 + 'onPressStart',
1327 + 'onPressChange',
1328 + 'onPressMove',
1329 + 'onPressEnd',
1330 + 'onPressChange',
1331 + 'onPressStart',
1332 + 'onPressChange',
1333 + 'onPressEnd',
1334 + 'onPressChange',
1335 + 'onPress',
1336 + ]);
1337 + });
1338 });
1339
1340 describe('delayed and multiple events', () => {