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', () => {