mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[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:
+28
-18
@@ -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);
|
||||
|
||||
+36
-29
@@ -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,
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user