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

This commit is contained in:
Dominic Gannaway
2019-05-03 18:32:50 +01:00
committed by GitHub
parent ec6691a687
commit 339366c461
2 changed files with 211 additions and 7 deletions
+24 -7
View File
@@ -68,6 +68,7 @@ type PressState = {
top: number,
|}>,
ignoreEmulatedMouseEvents: boolean,
allowPressReentry: boolean,
};
type PressEventType =
@@ -301,7 +302,6 @@ function dispatchPressEndEvents(
deactivate(context, props, state);
}
}
removeRootEventTypes(context, state);
}
function isAnchorTagElement(eventTarget: EventTarget): boolean {
@@ -395,6 +395,7 @@ function unmountResponder(
state: PressState,
): void {
if (state.isPressed) {
removeRootEventTypes(context, state);
dispatchPressEndEvents(context, props, state);
}
}
@@ -411,8 +412,11 @@ function dispatchCancel(
(nativeEvent: any).preventDefault();
} else {
state.ignoreEmulatedMouseEvents = false;
removeRootEventTypes(context, state);
dispatchPressEndEvents(context, props, state);
}
} else if (state.allowPressReentry) {
removeRootEventTypes(context, state);
}
}
@@ -432,6 +436,7 @@ function removeRootEventTypes(
): void {
if (state.addedRootEvents) {
state.addedRootEvents = false;
state.allowPressReentry = false;
context.removeRootEventTypes(rootEventTypes);
}
}
@@ -455,6 +460,7 @@ const PressResponder = {
responderRegionOnActivation: null,
responderRegionOnDeactivation: null,
ignoreEmulatedMouseEvents: false,
allowPressReentry: false,
};
},
stopLocalPropagation: true,
@@ -467,6 +473,7 @@ const PressResponder = {
const {target, type} = event;
if (props.disabled) {
removeRootEventTypes(context, state);
dispatchPressEndEvents(context, props, state);
state.ignoreEmulatedMouseEvents = false;
return;
@@ -512,6 +519,7 @@ const PressResponder = {
return;
}
state.allowPressReentry = true;
state.pointerType = pointerType;
state.pressTarget = getEventCurrentTarget(event, context);
state.responderRegionOnActivation = calculateResponderRegion(
@@ -565,7 +573,7 @@ const PressResponder = {
case 'pointermove':
case 'mousemove':
case 'touchmove': {
if (state.isPressed) {
if (state.isPressed || state.allowPressReentry) {
// Ignore emulated events (pointermove will dispatch touch and mouse events)
// Ignore pointermove events during a keyboard press.
if (state.pointerType !== pointerType) {
@@ -589,18 +597,24 @@ const PressResponder = {
);
if (state.isPressWithinResponderRegion) {
if (props.onPressMove) {
dispatchEvent(context, state, 'pressmove', props.onPressMove, {
discrete: false,
});
if (state.isPressed) {
if (props.onPressMove) {
dispatchEvent(context, state, 'pressmove', props.onPressMove, {
discrete: false,
});
}
} else {
dispatchPressStartEvents(context, props, state);
}
} else {
if (!state.allowPressReentry) {
removeRootEventTypes(context, state);
}
dispatchPressEndEvents(context, props, state);
}
}
break;
}
// END
case 'pointerup':
case 'keyup':
@@ -635,6 +649,7 @@ const PressResponder = {
}
const wasLongPressed = state.isLongPressed;
removeRootEventTypes(context, state);
dispatchPressEndEvents(context, props, state);
if (state.pressTarget !== null && props.onPress) {
@@ -652,6 +667,8 @@ const PressResponder = {
}
} else if (type === 'mouseup' && state.ignoreEmulatedMouseEvents) {
state.ignoreEmulatedMouseEvents = false;
} else if (state.allowPressReentry) {
removeRootEventTypes(context, state);
}
break;
}
@@ -1148,6 +1148,193 @@ describe('Event responder: Press', () => {
expect(events).toEqual([]);
});
});
it('"onPress" is not called on release with mouse', () => {
let events = [];
const ref = React.createRef();
const createEventHandler = msg => () => {
events.push(msg);
};
const element = (
<Press
onPress={createEventHandler('onPress')}
onPressChange={createEventHandler('onPressChange')}
onPressMove={createEventHandler('onPressMove')}
onPressStart={createEventHandler('onPressStart')}
onPressEnd={createEventHandler('onPressEnd')}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.getBoundingClientRect = getBoundingClientRectMock;
ref.current.dispatchEvent(
createPointerEvent('pointerdown', {
pointerType: 'mouse',
}),
);
ref.current.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesInside,
pointerType: 'mouse',
}),
);
container.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesOutside,
pointerType: 'mouse',
}),
);
container.dispatchEvent(
createPointerEvent('pointerup', {
...coordinatesOutside,
pointerType: 'mouse',
}),
);
jest.runAllTimers();
expect(events).toEqual([
'onPressStart',
'onPressChange',
'onPressMove',
'onPressEnd',
'onPressChange',
]);
});
it('"onPress" is called on re-entry to hit rect for mouse', () => {
let events = [];
const ref = React.createRef();
const createEventHandler = msg => () => {
events.push(msg);
};
const element = (
<Press
onPress={createEventHandler('onPress')}
onPressChange={createEventHandler('onPressChange')}
onPressMove={createEventHandler('onPressMove')}
onPressStart={createEventHandler('onPressStart')}
onPressEnd={createEventHandler('onPressEnd')}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.getBoundingClientRect = getBoundingClientRectMock;
ref.current.dispatchEvent(
createPointerEvent('pointerdown', {
pointerType: 'mouse',
}),
);
ref.current.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesInside,
pointerType: 'mouse',
}),
);
container.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesOutside,
pointerType: 'mouse',
}),
);
container.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesInside,
pointerType: 'mouse',
}),
);
container.dispatchEvent(
createPointerEvent('pointerup', {
...coordinatesInside,
pointerType: 'mouse',
}),
);
jest.runAllTimers();
expect(events).toEqual([
'onPressStart',
'onPressChange',
'onPressMove',
'onPressEnd',
'onPressChange',
'onPressStart',
'onPressChange',
'onPressEnd',
'onPressChange',
'onPress',
]);
});
it('"onPress" is called on re-entry to hit rect for touch', () => {
let events = [];
const ref = React.createRef();
const createEventHandler = msg => () => {
events.push(msg);
};
const element = (
<Press
onPress={createEventHandler('onPress')}
onPressChange={createEventHandler('onPressChange')}
onPressMove={createEventHandler('onPressMove')}
onPressStart={createEventHandler('onPressStart')}
onPressEnd={createEventHandler('onPressEnd')}>
<div ref={ref} />
</Press>
);
ReactDOM.render(element, container);
ref.current.getBoundingClientRect = getBoundingClientRectMock;
ref.current.dispatchEvent(
createPointerEvent('pointerdown', {
pointerType: 'touch',
}),
);
ref.current.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesInside,
pointerType: 'touch',
}),
);
container.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesOutside,
pointerType: 'touch',
}),
);
container.dispatchEvent(
createPointerEvent('pointermove', {
...coordinatesInside,
pointerType: 'touch',
}),
);
container.dispatchEvent(
createPointerEvent('pointerup', {
...coordinatesInside,
pointerType: 'touch',
}),
);
jest.runAllTimers();
expect(events).toEqual([
'onPressStart',
'onPressChange',
'onPressMove',
'onPressEnd',
'onPressChange',
'onPressStart',
'onPressChange',
'onPressEnd',
'onPressChange',
'onPress',
]);
});
});
describe('delayed and multiple events', () => {