[react-interactions] Tap cancels on second pointerdown (#16936)

This patch causes onTapCancel to be called whenever a second pointer interacts
with the responder target.
This commit is contained in:
Nicolas Gallagher
2019-10-01 15:13:31 -07:00
committed by GitHub
parent 34457729a6
commit f6efb224b0
4 changed files with 143 additions and 59 deletions
+28 -18
View File
@@ -344,7 +344,7 @@ function isActivePointer(
const touch = getTouchById(nativeEvent, activePointerId);
return touch != null;
} else {
// accept all events that don't have ids
// accept all events that don't have pointer ids
return true;
}
}
@@ -496,26 +496,29 @@ const responderImpl = {
case 'pointerdown':
case 'mousedown':
case 'touchstart': {
if (hasPointerEvents) {
const pointerId = nativeEvent.pointerId;
state.activePointerId = pointerId;
// Make mouse and touch pointers consistent.
// Flow bug: https://github.com/facebook/flow/issues/8055
// $FlowExpectedError
eventTarget.releasePointerCapture(pointerId);
} else {
if (eventType === 'touchstart') {
const targetTouches = nativeEvent.targetTouches;
if (targetTouches.length > 0) {
state.activePointerId = targetTouches[0].identifier;
}
}
if (eventType === 'mousedown' && state.ignoreEmulatedEvents) {
return;
}
if (eventType === 'mousedown' && state.ignoreEmulatedEvents) {
return;
}
if (!state.isActive) {
if (hasPointerEvents) {
const pointerId = nativeEvent.pointerId;
state.activePointerId = pointerId;
// Make mouse and touch pointers consistent.
// Flow bug: https://github.com/facebook/flow/issues/8055
// $FlowExpectedError
eventTarget.releasePointerCapture(pointerId);
} else {
if (eventType === 'touchstart') {
const targetTouches = nativeEvent.targetTouches;
if (targetTouches.length === 1) {
state.activePointerId = targetTouches[0].identifier;
} else {
return;
}
}
}
const activate = shouldActivate(event);
const activateAuxiliary = isAuxiliary(nativeEvent.buttons, event);
@@ -547,6 +550,13 @@ const responderImpl = {
state.initialPosition.y = gestureState.y;
dispatchStart(context, props, state);
}
} else if (
!isActivePointer(event, state) ||
(eventType === 'touchstart' && nativeEvent.targetTouches.length > 1)
) {
// Cancel the gesture if a second pointer becomes active on the target.
state.isActive = false;
dispatchCancel(context, props, state);
}
break;
}
@@ -212,7 +212,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
const buttons = buttonsType.auxiliary;
const target = createEventTarget(ref.current);
target.pointerdown({buttons, pointerType});
target.pointerup({pointerType});
target.pointerup({buttons, pointerType});
expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
});
@@ -221,7 +221,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
const buttons = buttonsType.primary;
const target = createEventTarget(ref.current);
target.pointerdown({buttons, pointerType});
target.pointerup({metaKey: true, pointerType});
target.pointerup({buttons, metaKey: true, pointerType});
expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
});
});
@@ -284,7 +284,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
);
});
test('second pointer down', () => {
test('second pointer on target', () => {
const pointerType = 'touch';
const target = createEventTarget(ref.current);
const buttons = buttonsType.primary;
@@ -294,10 +294,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
target.pointerdown({buttons, pointerId: 2, pointerType});
} else {
// TouchEvents
target.pointerdown([
{pointerId: 1, pointerType},
{pointerId: 2, pointerType},
]);
target.pointerdown([{pointerId: 1}, {pointerId: 2}]);
}
expect(onTapStart).toHaveBeenCalledTimes(1);
});
@@ -349,8 +346,10 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
testWithPointerType('pointer up', pointerType => {
const target = createEventTarget(ref.current);
target.pointerdown({buttons: buttonsType.primary, pointerType});
const buttons = buttonsType.primary;
target.pointerdown({buttons, pointerType});
target.pointerup({
buttons,
pageX: 10,
pageY: 10,
pointerType,
@@ -420,18 +419,40 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapEnd).not.toBeCalled();
});
if (hasPointerEvents) {
test('second pointer up off target', () => {
const pointerType = 'touch';
const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container);
const buttons = buttonsType.primary;
target.pointerdown({buttons, pointerId: 1, pointerType});
offTarget.pointerdown({buttons, pointerId: 2, pointerType});
offTarget.pointerup({
buttons,
pageX: 10,
pageY: 10,
pointerId: 2,
pointerType,
x: 10,
y: 10,
});
expect(onTapEnd).toHaveBeenCalledTimes(0);
});
}
test('ignored buttons and modifiers', () => {
const target = createEventTarget(ref.current);
const primary = buttonsType.primary;
// right-click
target.pointerdown({buttons: buttonsType.secondary});
target.pointerup();
target.pointerup({buttons: buttonsType.secondary});
// middle-click
target.pointerdown({buttons: buttonsType.auxiliary});
target.pointerup();
target.pointerup({buttons: buttonsType.auxiliary});
// pen eraser
target.pointerdown({buttons: buttonsType.eraser});
target.pointerup();
target.pointerup({buttons: buttonsType.eraser});
// alt-click
target.pointerdown({buttons: primary});
target.pointerup({altKey: true});
@@ -533,6 +554,21 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
// No extra 'onTapUpdate' calls when the pointer is outside the target
expect(onTapUpdate).toHaveBeenCalledTimes(1);
});
if (hasPointerEvents) {
test('second pointer off target', () => {
const pointerType = 'touch';
const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container);
const buttons = buttonsType.primary;
target.pointerdown({buttons, pointerId: 1, pointerType});
offTarget.pointerdown({buttons, pointerId: 2, pointerType});
target.pointermove({pointerId: 1, pointerType, x: 10, y: 10});
expect(onTapUpdate).toHaveBeenCalledTimes(1);
offTarget.pointermove({pointerId: 2, pointerType, x: 10, y: 10});
expect(onTapUpdate).toHaveBeenCalledTimes(1);
});
}
});
describe('onTapChange', () => {
@@ -652,6 +688,32 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
expect(onTapUpdate).not.toBeCalled();
});
test('second pointer on target', () => {
const pointerType = 'touch';
const target = createEventTarget(ref.current);
const buttons = buttonsType.primary;
target.pointerdown({buttons, pointerId: 1, pointerType});
if (hasPointerEvents) {
target.pointerdown({buttons, pointerId: 2, pointerType});
} else {
// TouchEvents
target.pointerdown([{pointerId: 1}, {pointerId: 2}]);
}
expect(onTapCancel).toHaveBeenCalledTimes(1);
});
if (hasPointerEvents) {
test('second pointer off target', () => {
const pointerType = 'touch';
const target = createEventTarget(ref.current);
const offTarget = createEventTarget(container);
const buttons = buttonsType.primary;
target.pointerdown({buttons, pointerId: 1, pointerType});
offTarget.pointerdown({buttons, pointerId: 2, pointerType});
expect(onTapCancel).toHaveBeenCalledTimes(0);
});
}
testWithPointerType('pointer move outside target', pointerType => {
const downTarget = createEventTarget(ref.current);
const upTarget = createEventTarget(container);
@@ -18,6 +18,8 @@ function getPointerType(payload) {
let pointerType = 'mouse';
if (payload != null && payload.pointerType != null) {
pointerType = payload.pointerType;
} else if (Array.isArray(payload)) {
pointerType = 'touch';
}
return pointerType;
}
@@ -77,31 +79,36 @@ export function pointercancel(target, payload) {
export function pointerdown(target, defaultPayload) {
const dispatch = arg => target.dispatchEvent(arg);
const pointerType = getPointerType(defaultPayload);
const payload = {buttons: buttonsType.primary, ...defaultPayload};
if (pointerType === 'mouse') {
if (hasPointerEvent()) {
dispatch(domEvents.pointerover(payload));
dispatch(domEvents.pointerenter(payload));
}
dispatch(domEvents.mouseover(payload));
dispatch(domEvents.mouseenter(payload));
if (hasPointerEvent()) {
dispatch(domEvents.pointerdown(payload));
}
dispatch(domEvents.mousedown(payload));
if (document.activeElement !== target) {
dispatch(domEvents.focus());
}
if (Array.isArray(defaultPayload)) {
// Arrays are for multi-touch only
dispatch(domEvents.touchstart(defaultPayload));
} else {
if (hasPointerEvent()) {
dispatch(domEvents.pointerover(payload));
dispatch(domEvents.pointerenter(payload));
dispatch(domEvents.pointerdown(payload));
}
dispatch(domEvents.touchstart(payload));
if (hasPointerEvent()) {
dispatch(domEvents.gotpointercapture(payload));
const payload = {buttons: buttonsType.primary, ...defaultPayload};
if (pointerType === 'mouse') {
if (hasPointerEvent()) {
dispatch(domEvents.pointerover(payload));
dispatch(domEvents.pointerenter(payload));
}
dispatch(domEvents.mouseover(payload));
dispatch(domEvents.mouseenter(payload));
if (hasPointerEvent()) {
dispatch(domEvents.pointerdown(payload));
}
dispatch(domEvents.mousedown(payload));
if (document.activeElement !== target) {
dispatch(domEvents.focus());
}
} else {
if (hasPointerEvent()) {
dispatch(domEvents.pointerover(payload));
dispatch(domEvents.pointerenter(payload));
dispatch(domEvents.pointerdown(payload));
}
dispatch(domEvents.touchstart(payload));
if (hasPointerEvent()) {
dispatch(domEvents.gotpointercapture(payload));
}
}
}
}
@@ -153,13 +160,14 @@ export function pointermove(target, payload) {
}
}
export function pointerup(target, defaultPayload = {}) {
export function pointerup(target, payload) {
const dispatch = arg => target.dispatchEvent(arg);
const pointerType = getPointerType(defaultPayload);
// eslint-disable-next-line no-unused-vars
const {buttons, ...payload} = defaultPayload;
const pointerType = getPointerType(payload);
if (pointerType === 'mouse') {
if (Array.isArray(payload)) {
// Arrays are for multi-touch only
dispatch(domEvents.touchend(payload));
} else if (pointerType === 'mouse') {
if (hasPointerEvent()) {
dispatch(domEvents.pointerup(payload));
}
@@ -175,7 +183,6 @@ export function pointerup(target, defaultPayload = {}) {
dispatch(domEvents.touchend(payload));
dispatch(domEvents.mouseover(payload));
dispatch(domEvents.mousemove(payload));
// NOTE: the value of 'buttons' for 'mousedown' must not be 0
dispatch(domEvents.mousedown(payload));
if (document.activeElement !== target) {
dispatch(domEvents.focus());
@@ -422,9 +422,14 @@ export function pointerup(payload) {
*/
export function mousedown(payload) {
// The value of 'buttons' for 'mousedown' must not be 0
const buttons =
payload == null || payload.buttons === 0
? buttonsType.primary
: payload.buttons;
return createMouseEvent('mousedown', {
buttons: buttonsType.primary,
...payload,
buttons,
});
}