mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
[react-ui/events] Tap responder API changes (#16827)
This patch limits the `onTap*` callbacks to the primary pointer button. Auxiliary button and modified primary button interactions call `onAuxiliaryTap`, cancel any active tap, and preserve the native behavior.
This commit is contained in:
+16
-3
@@ -34,7 +34,7 @@ type PressEventType =
|
||||
|
||||
type PressEvent = {|
|
||||
altKey: boolean,
|
||||
buttons: null | 0 | 1 | 4,
|
||||
buttons: null | 1 | 4,
|
||||
ctrlKey: boolean,
|
||||
defaultPrevented: boolean,
|
||||
key: null | string,
|
||||
@@ -53,7 +53,7 @@ type PressEvent = {|
|
||||
function createGestureState(e: any, type: PressEventType): PressEvent {
|
||||
return {
|
||||
altKey: e.altKey,
|
||||
buttons: e.buttons,
|
||||
buttons: e.type === 'tap:auxiliary' ? 4 : 1,
|
||||
ctrlKey: e.ctrlKey,
|
||||
defaultPrevented: e.defaultPrevented,
|
||||
key: e.key,
|
||||
@@ -103,6 +103,19 @@ export function usePress(props: PressProps) {
|
||||
const tap = useTap({
|
||||
disabled: disabled || active === 'keyboard',
|
||||
preventDefault,
|
||||
onAuxiliaryTap(e) {
|
||||
if (onPressStart != null) {
|
||||
onPressStart(createGestureState(e, 'pressstart'));
|
||||
}
|
||||
if (onPressEnd != null) {
|
||||
onPressEnd(createGestureState(e, 'pressend'));
|
||||
}
|
||||
// Here we rely on Tap only calling 'onAuxiliaryTap' with modifiers when
|
||||
// the primary button is pressed
|
||||
if (onPress != null && (e.metaKey || e.shiftKey)) {
|
||||
onPress(createGestureState(e, 'press'));
|
||||
}
|
||||
},
|
||||
onTapStart(e) {
|
||||
if (active == null) {
|
||||
updateActive('tap');
|
||||
@@ -124,7 +137,7 @@ export function usePress(props: PressProps) {
|
||||
if (onPressEnd != null) {
|
||||
onPressEnd(createGestureState(e, 'pressend'));
|
||||
}
|
||||
if (onPress != null && e.buttons !== 4) {
|
||||
if (onPress != null) {
|
||||
onPress(createGestureState(e, 'press'));
|
||||
}
|
||||
updateActive(null);
|
||||
|
||||
+92
-50
@@ -17,18 +17,18 @@ import type {ReactEventResponderListener} from 'shared/ReactTypes';
|
||||
import React from 'react';
|
||||
import {
|
||||
buttonsEnum,
|
||||
hasPointerEvents,
|
||||
isMac,
|
||||
dispatchDiscreteEvent,
|
||||
dispatchUserBlockingEvent,
|
||||
getTouchById,
|
||||
hasModifierKey,
|
||||
hasPointerEvents,
|
||||
} from './shared';
|
||||
|
||||
type TapProps = $ReadOnly<{|
|
||||
disabled?: boolean,
|
||||
maximumDistance?: number,
|
||||
preventDefault?: boolean,
|
||||
onAuxiliaryTap?: (e: TapEvent) => void,
|
||||
onTapCancel?: (e: TapEvent) => void,
|
||||
onTapChange?: boolean => void,
|
||||
onTapEnd?: (e: TapEvent) => void,
|
||||
@@ -38,7 +38,6 @@ type TapProps = $ReadOnly<{|
|
||||
|
||||
type TapGestureState = {|
|
||||
altKey: boolean,
|
||||
buttons: 0 | 1 | 4,
|
||||
ctrlKey: boolean,
|
||||
height: number,
|
||||
metaKey: boolean,
|
||||
@@ -67,13 +66,15 @@ type TapState = {|
|
||||
ignoreEmulatedEvents: boolean,
|
||||
initialPosition: {|x: number, y: number|},
|
||||
isActive: boolean,
|
||||
isAuxiliaryActive: boolean,
|
||||
pointerType: PointerType,
|
||||
responderTarget: null | Element,
|
||||
rootEvents: null | Array<string>,
|
||||
shouldPreventClick: boolean,
|
||||
shouldPreventDefault: boolean,
|
||||
|};
|
||||
|
||||
type TapEventType =
|
||||
| 'tap:auxiliary'
|
||||
| 'tap:cancel'
|
||||
| 'tap:change'
|
||||
| 'tap:end'
|
||||
@@ -125,14 +126,14 @@ function createInitialState(): TapState {
|
||||
buttons: 0,
|
||||
ignoreEmulatedEvents: false,
|
||||
isActive: false,
|
||||
isAuxiliaryActive: false,
|
||||
initialPosition: {x: 0, y: 0},
|
||||
pointerType: '',
|
||||
responderTarget: null,
|
||||
rootEvents: null,
|
||||
shouldPreventClick: true,
|
||||
shouldPreventDefault: true,
|
||||
gestureState: {
|
||||
altKey: false,
|
||||
buttons: 0,
|
||||
ctrlKey: false,
|
||||
height: 1,
|
||||
metaKey: false,
|
||||
@@ -187,7 +188,6 @@ function createPointerEventGestureState(
|
||||
|
||||
return {
|
||||
altKey,
|
||||
buttons: state.buttons,
|
||||
ctrlKey,
|
||||
height,
|
||||
metaKey,
|
||||
@@ -249,7 +249,6 @@ function createFallbackGestureState(
|
||||
|
||||
return {
|
||||
altKey,
|
||||
buttons: state.buttons != null ? state.buttons : 1,
|
||||
ctrlKey,
|
||||
height: !isCancelType && radiusY != null ? radiusY * 2 : 1,
|
||||
metaKey,
|
||||
@@ -351,19 +350,23 @@ function isActivePointer(
|
||||
}
|
||||
}
|
||||
|
||||
function isAuxiliary(buttons: number, nativeEvent: any): boolean {
|
||||
return (
|
||||
// middle-click
|
||||
buttons === buttonsEnum.auxiliary ||
|
||||
// open-in-new-tab
|
||||
(buttons === buttonsEnum.primary && nativeEvent.metaKey) ||
|
||||
// open-in-new-window
|
||||
(buttons === buttonsEnum.primary && nativeEvent.shiftKey)
|
||||
);
|
||||
}
|
||||
|
||||
function shouldActivate(event: ReactDOMResponderEvent): boolean {
|
||||
const nativeEvent: any = event.nativeEvent;
|
||||
const pointerType = event.pointerType;
|
||||
const buttons = nativeEvent.buttons;
|
||||
const isContextMenu = pointerType === 'mouse' && nativeEvent.ctrlKey && isMac;
|
||||
const isValidButton =
|
||||
buttons === buttonsEnum.primary || buttons === buttonsEnum.middle;
|
||||
|
||||
if (pointerType === 'touch' || (isValidButton && !isContextMenu)) {
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
const isValidButton = buttons === buttonsEnum.primary;
|
||||
return pointerType === 'touch' || (isValidButton && !hasModifierKey(event));
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -418,7 +421,7 @@ function dispatchEnd(
|
||||
const onTapEnd = props.onTapEnd;
|
||||
dispatchChange(context, props, state);
|
||||
if (onTapEnd != null) {
|
||||
const defaultPrevented = state.shouldPreventClick === true;
|
||||
const defaultPrevented = state.shouldPreventDefault === true;
|
||||
const payload = context.objectAssign({}, state.gestureState, {
|
||||
defaultPrevented,
|
||||
type,
|
||||
@@ -441,6 +444,22 @@ function dispatchCancel(
|
||||
}
|
||||
}
|
||||
|
||||
function dispatchAuxiliaryTap(
|
||||
context: ReactDOMResponderContext,
|
||||
props: TapProps,
|
||||
state: TapState,
|
||||
): void {
|
||||
const type = 'tap:auxiliary';
|
||||
const onAuxiliaryTap = props.onAuxiliaryTap;
|
||||
if (onAuxiliaryTap != null) {
|
||||
const payload = context.objectAssign({}, state.gestureState, {
|
||||
defaultPrevented: false,
|
||||
type,
|
||||
});
|
||||
dispatchDiscreteEvent(context, payload, onAuxiliaryTap);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Responder implementation
|
||||
*/
|
||||
@@ -493,26 +512,41 @@ const responderImpl = {
|
||||
}
|
||||
}
|
||||
|
||||
if (!state.isActive && shouldActivate(event)) {
|
||||
state.isActive = true;
|
||||
state.buttons = nativeEvent.buttons;
|
||||
state.pointerType = event.pointerType;
|
||||
state.responderTarget = context.getResponderNode();
|
||||
state.shouldPreventClick = props.preventDefault !== false;
|
||||
if (!state.isActive) {
|
||||
const activate = shouldActivate(event);
|
||||
const activateAuxiliary = isAuxiliary(
|
||||
nativeEvent.buttons,
|
||||
nativeEvent,
|
||||
);
|
||||
|
||||
const gestureState = createGestureState(context, props, state, event);
|
||||
state.gestureState = gestureState;
|
||||
state.initialPosition.x = gestureState.x;
|
||||
state.initialPosition.y = gestureState.y;
|
||||
|
||||
dispatchStart(context, props, state);
|
||||
addRootEventTypes(rootEventTypes, context, state);
|
||||
|
||||
if (!hasPointerEvents) {
|
||||
if (eventType === 'touchstart') {
|
||||
state.ignoreEmulatedEvents = true;
|
||||
if (activate || activateAuxiliary) {
|
||||
state.buttons = nativeEvent.buttons;
|
||||
state.pointerType = event.pointerType;
|
||||
state.responderTarget = context.getResponderNode();
|
||||
addRootEventTypes(rootEventTypes, context, state);
|
||||
if (!hasPointerEvents) {
|
||||
if (eventType === 'touchstart') {
|
||||
state.ignoreEmulatedEvents = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (activate) {
|
||||
const gestureState = createGestureState(
|
||||
context,
|
||||
props,
|
||||
state,
|
||||
event,
|
||||
);
|
||||
state.isActive = true;
|
||||
state.shouldPreventDefault = props.preventDefault !== false;
|
||||
state.gestureState = gestureState;
|
||||
state.initialPosition.x = gestureState.x;
|
||||
state.initialPosition.y = gestureState.y;
|
||||
dispatchStart(context, props, state);
|
||||
} else if (activateAuxiliary) {
|
||||
state.isAuxiliaryActive = true;
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -575,24 +609,30 @@ const responderImpl = {
|
||||
case 'mouseup':
|
||||
case 'touchend': {
|
||||
if (state.isActive && isActivePointer(event, state)) {
|
||||
if (state.buttons === buttonsEnum.middle) {
|
||||
// Remove the root events here as no 'click' event is dispatched
|
||||
// when this 'button' is pressed.
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
|
||||
state.isActive = false;
|
||||
if (context.isTargetWithinResponder(hitTarget)) {
|
||||
// Determine whether to call preventDefault on subsequent native events.
|
||||
if (hasModifierKey(event)) {
|
||||
state.shouldPreventClick = false;
|
||||
}
|
||||
dispatchEnd(context, props, state);
|
||||
} else {
|
||||
if (isAuxiliary(state.buttons, nativeEvent)) {
|
||||
dispatchCancel(context, props, state);
|
||||
dispatchAuxiliaryTap(context, props, state);
|
||||
// Remove the root events here as no 'click' event is dispatched
|
||||
removeRootEventTypes(context, state);
|
||||
} else if (
|
||||
!context.isTargetWithinResponder(hitTarget) ||
|
||||
hasModifierKey(event)
|
||||
) {
|
||||
dispatchCancel(context, props, state);
|
||||
} else {
|
||||
dispatchEnd(context, props, state);
|
||||
}
|
||||
} else if (
|
||||
state.isAuxiliaryActive &&
|
||||
isAuxiliary(state.buttons, nativeEvent)
|
||||
) {
|
||||
state.isAuxiliaryActive = false;
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
dispatchAuxiliaryTap(context, props, state);
|
||||
// Remove the root events here as no 'click' event is dispatched
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
|
||||
if (!hasPointerEvents) {
|
||||
@@ -612,6 +652,7 @@ const responderImpl = {
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
state.isActive = false;
|
||||
dispatchCancel(context, props, state);
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -630,12 +671,13 @@ const responderImpl = {
|
||||
state.gestureState = createGestureState(context, props, state, event);
|
||||
state.isActive = false;
|
||||
dispatchCancel(context, props, state);
|
||||
removeRootEventTypes(context, state);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case 'click': {
|
||||
if (state.shouldPreventClick) {
|
||||
if (state.shouldPreventDefault) {
|
||||
nativeEvent.preventDefault();
|
||||
}
|
||||
removeRootEventTypes(context, state);
|
||||
|
||||
@@ -125,11 +125,13 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is called after middle-button pointer down', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
const pointerType = 'mouse';
|
||||
target.pointerdown({buttons: buttonsType.auxiliary, pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
expect(onPressStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.middle,
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressstart',
|
||||
}),
|
||||
@@ -209,12 +211,15 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is called after middle-button pointer up', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.middle,
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressend',
|
||||
}),
|
||||
@@ -350,7 +355,10 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is not called after middle-button press', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPress).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -120,11 +120,14 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is called after middle-button pointer down', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
expect(onPressStart).toHaveBeenCalledTimes(1);
|
||||
expect(onPressStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.middle,
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressstart',
|
||||
}),
|
||||
@@ -135,7 +138,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
const node = ref.current;
|
||||
const target = createEventTarget(node);
|
||||
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
target.pointerhover({x: 110, y: 110});
|
||||
target.pointerhover({x: 50, y: 50});
|
||||
@@ -215,12 +221,15 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is called after middle-button pointer up', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPressEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onPressEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
buttons: buttonsType.middle,
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
type: 'pressend',
|
||||
}),
|
||||
@@ -356,7 +365,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
|
||||
|
||||
it('is not called after middle-button press', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
|
||||
target.pointerdown({
|
||||
buttons: buttonsType.auxiliary,
|
||||
pointerType: 'mouse',
|
||||
});
|
||||
target.pointerup({pointerType: 'mouse'});
|
||||
expect(onPress).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@@ -193,6 +193,39 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('onAuxiliaryTap', () => {
|
||||
let onAuxiliaryTap, ref;
|
||||
|
||||
beforeEach(() => {
|
||||
onAuxiliaryTap = jest.fn();
|
||||
ref = React.createRef();
|
||||
const Component = () => {
|
||||
const listener = useTap({onAuxiliaryTap});
|
||||
return <div ref={ref} listeners={listener} />;
|
||||
};
|
||||
ReactDOM.render(<Component />, container);
|
||||
document.elementFromPoint = () => ref.current;
|
||||
});
|
||||
|
||||
test('auxiliary-button pointer up', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.auxiliary;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('modifier-button pointer up', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.primary;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
target.pointerup({metaKey: true, pointerType});
|
||||
expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onTapStart', () => {
|
||||
let onTapStart, ref;
|
||||
|
||||
@@ -210,6 +243,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
testWithPointerType('pointer down', pointerType => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const nativeEvent = {
|
||||
buttons: buttonsType.primary,
|
||||
pageX: 10,
|
||||
pageY: 10,
|
||||
pointerType,
|
||||
@@ -226,7 +260,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
buttons: 1,
|
||||
ctrlKey: false,
|
||||
height: pointerType === 'mouse' ? 1 : 23,
|
||||
metaKey: false,
|
||||
@@ -269,36 +302,31 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapStart).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('primary-button pointer down', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.primary;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
expect(onTapStart).toHaveBeenCalledTimes(1);
|
||||
expect(onTapStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({buttons, pointerType}),
|
||||
);
|
||||
});
|
||||
|
||||
test('middle-button pointer down', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.middle;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
expect(onTapStart).toHaveBeenCalledTimes(1);
|
||||
expect(onTapStart).toHaveBeenCalledWith(
|
||||
expect.objectContaining({buttons, pointerType}),
|
||||
);
|
||||
});
|
||||
|
||||
test('unsupported buttons', () => {
|
||||
test('ignored buttons and modifiers', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const primary = buttonsType.primary;
|
||||
// right-click
|
||||
target.pointerdown({buttons: buttonsType.secondary});
|
||||
target.pointerup();
|
||||
// middle-click
|
||||
target.pointerdown({buttons: buttonsType.auxiliary});
|
||||
target.pointerup();
|
||||
// pen eraser
|
||||
target.pointerdown({buttons: buttonsType.eraser});
|
||||
target.pointerup();
|
||||
// alt-click
|
||||
target.pointerdown({buttons: primary, altKey: true});
|
||||
target.pointerup();
|
||||
// ctrl-click
|
||||
target.pointerdown({buttons: primary, ctrlKey: true});
|
||||
target.pointerup();
|
||||
// meta-click
|
||||
target.pointerdown({buttons: primary, metaKey: true});
|
||||
target.pointerup();
|
||||
// shift-click
|
||||
target.pointerdown({buttons: primary, shiftKey: true});
|
||||
target.pointerup();
|
||||
|
||||
expect(onTapStart).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
@@ -319,7 +347,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
|
||||
testWithPointerType('pointer up', pointerType => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({pointerType});
|
||||
target.pointerdown({buttons: buttonsType.primary, pointerType});
|
||||
target.pointerup({
|
||||
pageX: 10,
|
||||
pageY: 10,
|
||||
@@ -331,7 +359,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
buttons: 1,
|
||||
ctrlKey: false,
|
||||
height: pointerType === 'mouse' ? 1 : 23,
|
||||
metaKey: false,
|
||||
@@ -356,30 +383,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
test('primary-button pointer up', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.primary;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onTapEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({buttons, pointerType}),
|
||||
);
|
||||
});
|
||||
|
||||
test('middle-button pointer up', () => {
|
||||
const pointerType = 'mouse';
|
||||
const buttons = buttonsType.middle;
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({buttons, pointerType});
|
||||
target.pointerup({pointerType});
|
||||
expect(onTapEnd).toHaveBeenCalledTimes(1);
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({buttons, pointerType}),
|
||||
);
|
||||
});
|
||||
|
||||
testWithPointerType('zero-dimension hit rect', pointerType => {
|
||||
const targetRef = React.createRef();
|
||||
const innerRef = React.createRef();
|
||||
@@ -414,6 +417,34 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
});
|
||||
expect(onTapEnd).not.toBeCalled();
|
||||
});
|
||||
|
||||
test('ignored buttons and modifiers', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const primary = buttonsType.primary;
|
||||
// right-click
|
||||
target.pointerdown({buttons: buttonsType.secondary});
|
||||
target.pointerup();
|
||||
// middle-click
|
||||
target.pointerdown({buttons: buttonsType.auxiliary});
|
||||
target.pointerup();
|
||||
// pen eraser
|
||||
target.pointerdown({buttons: buttonsType.eraser});
|
||||
target.pointerup();
|
||||
// alt-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({altKey: true});
|
||||
// ctrl-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({ctrlKey: true});
|
||||
// meta-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({metaKey: true});
|
||||
// shift-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({shiftKey: true});
|
||||
|
||||
expect(onTapEnd).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
});
|
||||
|
||||
describe('onTapUpdate', () => {
|
||||
@@ -449,7 +480,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapUpdate).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
buttons: 1,
|
||||
ctrlKey: false,
|
||||
height: pointerType === 'mouse' ? 1 : 23,
|
||||
metaKey: false,
|
||||
@@ -594,7 +624,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapCancel).toHaveBeenCalledWith(
|
||||
expect.objectContaining({
|
||||
altKey: false,
|
||||
buttons: buttonsType.primary,
|
||||
ctrlKey: false,
|
||||
height: 1,
|
||||
metaKey: false,
|
||||
@@ -621,6 +650,37 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
expect(onTapUpdate).not.toBeCalled();
|
||||
});
|
||||
|
||||
testWithPointerType('pointer move outside target', pointerType => {
|
||||
const downTarget = createEventTarget(ref.current);
|
||||
const upTarget = createEventTarget(container);
|
||||
tapAndMoveOutside({
|
||||
hasPointerEvents,
|
||||
downTarget,
|
||||
upTarget,
|
||||
pointerType,
|
||||
});
|
||||
expect(onTapCancel).toBeCalled();
|
||||
});
|
||||
|
||||
test('ignored modifiers', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
const primary = buttonsType.primary;
|
||||
// alt-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({altKey: true});
|
||||
// ctrl-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({ctrlKey: true});
|
||||
// meta-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({metaKey: true});
|
||||
// shift-click
|
||||
target.pointerdown({buttons: primary});
|
||||
target.pointerup({shiftKey: true});
|
||||
|
||||
expect(onTapCancel).toHaveBeenCalledTimes(4);
|
||||
});
|
||||
|
||||
test('long press context menu', () => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.contextmenu({}, {pointerType: 'touch'});
|
||||
@@ -659,18 +719,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
containerTarget.scroll();
|
||||
expect(onTapCancel).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
testWithPointerType('pointer move outside target', pointerType => {
|
||||
const downTarget = createEventTarget(ref.current);
|
||||
const upTarget = createEventTarget(container);
|
||||
tapAndMoveOutside({
|
||||
hasPointerEvents,
|
||||
downTarget,
|
||||
upTarget,
|
||||
pointerType,
|
||||
});
|
||||
expect(onTapCancel).toBeCalled();
|
||||
});
|
||||
});
|
||||
|
||||
describe('preventDefault', () => {
|
||||
@@ -718,18 +766,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
|
||||
);
|
||||
});
|
||||
|
||||
test('allows native behaviour by default (modifier keys)', () => {
|
||||
['metaKey', 'ctrlKey', 'shiftKey'].forEach(modifierKey => {
|
||||
const target = createEventTarget(ref.current);
|
||||
target.pointerdown({[modifierKey]: true});
|
||||
target.pointerup({[modifierKey]: true, preventDefault});
|
||||
expect(preventDefault).not.toBeCalled();
|
||||
expect(onTapEnd).toHaveBeenCalledWith(
|
||||
expect.objectContaining({defaultPrevented: false}),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
test('allows native behaviour if false', () => {
|
||||
remount(false);
|
||||
|
||||
|
||||
+1
-1
@@ -27,7 +27,7 @@ export const buttonsEnum = {
|
||||
none: 0,
|
||||
primary: 1,
|
||||
secondary: 2,
|
||||
middle: 4,
|
||||
auxiliary: 4,
|
||||
};
|
||||
|
||||
export function dispatchDiscreteEvent(
|
||||
|
||||
@@ -78,7 +78,7 @@ export const buttonsType = {
|
||||
// pen barrel button
|
||||
secondary: 2,
|
||||
// middle mouse
|
||||
middle: 4,
|
||||
auxiliary: 4,
|
||||
// back mouse
|
||||
back: 8,
|
||||
// forward mouse
|
||||
|
||||
Reference in New Issue
Block a user