mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Event API: Support press reentry for pointer events (#15560)
This commit is contained in:
Vendored
+24
-7
@@ -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', () => {
|
||||
|
||||
Reference in New Issue
Block a user