Skip to content

Commit 339366c

Browse files
authored
Event API: Support press reentry for pointer events (#15560)
1 parent ec6691a commit 339366c

File tree

2 files changed

+211
-7
lines changed

2 files changed

+211
-7
lines changed

packages/react-events/src/Press.js

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,7 @@ type PressState = {
6868
top: number,
6969
|}>,
7070
ignoreEmulatedMouseEvents: boolean,
71+
allowPressReentry: boolean,
7172
};
7273

7374
type PressEventType =
@@ -301,7 +302,6 @@ function dispatchPressEndEvents(
301302
deactivate(context, props, state);
302303
}
303304
}
304-
removeRootEventTypes(context, state);
305305
}
306306

307307
function isAnchorTagElement(eventTarget: EventTarget): boolean {
@@ -395,6 +395,7 @@ function unmountResponder(
395395
state: PressState,
396396
): void {
397397
if (state.isPressed) {
398+
removeRootEventTypes(context, state);
398399
dispatchPressEndEvents(context, props, state);
399400
}
400401
}
@@ -411,8 +412,11 @@ function dispatchCancel(
411412
(nativeEvent: any).preventDefault();
412413
} else {
413414
state.ignoreEmulatedMouseEvents = false;
415+
removeRootEventTypes(context, state);
414416
dispatchPressEndEvents(context, props, state);
415417
}
418+
} else if (state.allowPressReentry) {
419+
removeRootEventTypes(context, state);
416420
}
417421
}
418422

@@ -432,6 +436,7 @@ function removeRootEventTypes(
432436
): void {
433437
if (state.addedRootEvents) {
434438
state.addedRootEvents = false;
439+
state.allowPressReentry = false;
435440
context.removeRootEventTypes(rootEventTypes);
436441
}
437442
}
@@ -455,6 +460,7 @@ const PressResponder = {
455460
responderRegionOnActivation: null,
456461
responderRegionOnDeactivation: null,
457462
ignoreEmulatedMouseEvents: false,
463+
allowPressReentry: false,
458464
};
459465
},
460466
stopLocalPropagation: true,
@@ -467,6 +473,7 @@ const PressResponder = {
467473
const {target, type} = event;
468474

469475
if (props.disabled) {
476+
removeRootEventTypes(context, state);
470477
dispatchPressEndEvents(context, props, state);
471478
state.ignoreEmulatedMouseEvents = false;
472479
return;
@@ -512,6 +519,7 @@ const PressResponder = {
512519
return;
513520
}
514521

522+
state.allowPressReentry = true;
515523
state.pointerType = pointerType;
516524
state.pressTarget = getEventCurrentTarget(event, context);
517525
state.responderRegionOnActivation = calculateResponderRegion(
@@ -565,7 +573,7 @@ const PressResponder = {
565573
case 'pointermove':
566574
case 'mousemove':
567575
case 'touchmove': {
568-
if (state.isPressed) {
576+
if (state.isPressed || state.allowPressReentry) {
569577
// Ignore emulated events (pointermove will dispatch touch and mouse events)
570578
// Ignore pointermove events during a keyboard press.
571579
if (state.pointerType !== pointerType) {
@@ -589,18 +597,24 @@ const PressResponder = {
589597
);
590598

591599
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);
596608
}
597609
} else {
610+
if (!state.allowPressReentry) {
611+
removeRootEventTypes(context, state);
612+
}
598613
dispatchPressEndEvents(context, props, state);
599614
}
600615
}
601616
break;
602617
}
603-
604618
// END
605619
case 'pointerup':
606620
case 'keyup':
@@ -635,6 +649,7 @@ const PressResponder = {
635649
}
636650

637651
const wasLongPressed = state.isLongPressed;
652+
removeRootEventTypes(context, state);
638653
dispatchPressEndEvents(context, props, state);
639654

640655
if (state.pressTarget !== null && props.onPress) {
@@ -652,6 +667,8 @@ const PressResponder = {
652667
}
653668
} else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
654669
state.ignoreEmulatedMouseEvents = false;
670+
} else if (state.allowPressReentry) {
671+
removeRootEventTypes(context, state);
655672
}
656673
break;
657674
}

packages/react-events/src/__tests__/Press-test.internal.js

Lines changed: 187 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1148,6 +1148,193 @@ describe('Event responder: Press', () => {
11481148
expect(events).toEqual([]);
11491149
});
11501150
});
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+
});
11511338
});
11521339

11531340
describe('delayed and multiple events', () => {

0 commit comments

Comments
 (0)