[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.
Nicolas Gallagher committed
Sep 19, 2019 at 09:36 UTC
fd870e6b6a9c35cf4bb77d83dbfe61e07327368f
7 files changed
+254
-143
packages/react-ui/events/src/dom/Press.js
+16
-3
@@ -34,7 +34,7 @@ type PressEventType =
34
35
type PressEvent = {|
36
altKey: boolean,
37
- buttons: null | 0 | 1 | 4,
37
+ buttons: null | 1 | 4,
38
ctrlKey: boolean,
39
defaultPrevented: boolean,
40
key: null | string,
@@ -53,7 +53,7 @@ type PressEvent = {|
53
function createGestureState(e: any, type: PressEventType): PressEvent {
54
return {
55
altKey: e.altKey,
56
- buttons: e.buttons,
56
+ buttons: e.type === 'tap:auxiliary' ? 4 : 1,
57
ctrlKey: e.ctrlKey,
58
defaultPrevented: e.defaultPrevented,
59
key: e.key,
@@ -103,6 +103,19 @@ export function usePress(props: PressProps) {
103
const tap = useTap({
104
disabled: disabled || active === 'keyboard',
105
preventDefault,
106
+ onAuxiliaryTap(e) {
107
+ if (onPressStart != null) {
108
+ onPressStart(createGestureState(e, 'pressstart'));
109
+ }
110
+ if (onPressEnd != null) {
111
+ onPressEnd(createGestureState(e, 'pressend'));
112
+ }
113
+ // Here we rely on Tap only calling 'onAuxiliaryTap' with modifiers when
114
+ // the primary button is pressed
115
+ if (onPress != null && (e.metaKey || e.shiftKey)) {
116
+ onPress(createGestureState(e, 'press'));
117
+ }
118
+ },
119
onTapStart(e) {
120
if (active == null) {
121
updateActive('tap');
@@ -124,7 +137,7 @@ export function usePress(props: PressProps) {
137
if (onPressEnd != null) {
138
onPressEnd(createGestureState(e, 'pressend'));
139
}
127
- if (onPress != null && e.buttons !== 4) {
140
+ if (onPress != null) {
141
onPress(createGestureState(e, 'press'));
142
}
143
updateActive(null);
packages/react-ui/events/src/dom/Tap.js
+93
-51
@@ -17,18 +17,18 @@ import type {ReactEventResponderListener} from 'shared/ReactTypes';
17
import React from 'react';
18
import {
19
buttonsEnum,
20
- hasPointerEvents,
21
- isMac,
20
dispatchDiscreteEvent,
21
dispatchUserBlockingEvent,
22
getTouchById,
23
hasModifierKey,
24
+ hasPointerEvents,
25
} from './shared';
26
27
type TapProps = $ReadOnly<{|
28
disabled?: boolean,
29
maximumDistance?: number,
30
preventDefault?: boolean,
31
+ onAuxiliaryTap?: (e: TapEvent) => void,
32
onTapCancel?: (e: TapEvent) => void,
33
onTapChange?: boolean => void,
34
onTapEnd?: (e: TapEvent) => void,
@@ -38,7 +38,6 @@ type TapProps = $ReadOnly<{|
38
39
type TapGestureState = {|
40
altKey: boolean,
41
- buttons: 0 | 1 | 4,
41
ctrlKey: boolean,
42
height: number,
43
metaKey: boolean,
@@ -67,13 +66,15 @@ type TapState = {|
66
ignoreEmulatedEvents: boolean,
67
initialPosition: {|x: number, y: number|},
68
isActive: boolean,
69
+ isAuxiliaryActive: boolean,
70
pointerType: PointerType,
71
responderTarget: null | Element,
72
rootEvents: null | Array<string>,
73
- shouldPreventClick: boolean,
73
+ shouldPreventDefault: boolean,
74
|};
75
76
type TapEventType =
77
+ | 'tap:auxiliary'
78
| 'tap:cancel'
79
| 'tap:change'
80
| 'tap:end'
@@ -125,14 +126,14 @@ function createInitialState(): TapState {
126
buttons: 0,
127
ignoreEmulatedEvents: false,
128
isActive: false,
129
+ isAuxiliaryActive: false,
130
initialPosition: {x: 0, y: 0},
131
pointerType: '',
132
responderTarget: null,
133
rootEvents: null,
132
- shouldPreventClick: true,
134
+ shouldPreventDefault: true,
135
gestureState: {
136
altKey: false,
135
- buttons: 0,
137
ctrlKey: false,
138
height: 1,
139
metaKey: false,
@@ -187,7 +188,6 @@ function createPointerEventGestureState(
188
189
return {
190
altKey,
190
- buttons: state.buttons,
191
ctrlKey,
192
height,
193
metaKey,
@@ -249,7 +249,6 @@ function createFallbackGestureState(
249
250
return {
251
altKey,
252
- buttons: state.buttons != null ? state.buttons : 1,
252
ctrlKey,
253
height: !isCancelType && radiusY != null ? radiusY * 2 : 1,
254
metaKey,
@@ -351,19 +350,23 @@ function isActivePointer(
350
}
351
}
352
353
+function isAuxiliary(buttons: number, nativeEvent: any): boolean {
354
+ return (
355
+ // middle-click
356
+ buttons === buttonsEnum.auxiliary ||
357
+ // open-in-new-tab
358
+ (buttons === buttonsEnum.primary && nativeEvent.metaKey) ||
359
+ // open-in-new-window
360
+ (buttons === buttonsEnum.primary && nativeEvent.shiftKey)
361
+ );
362
+}
363
+
364
function shouldActivate(event: ReactDOMResponderEvent): boolean {
365
const nativeEvent: any = event.nativeEvent;
366
const pointerType = event.pointerType;
367
const buttons = nativeEvent.buttons;
358
- const isContextMenu = pointerType === 'mouse' && nativeEvent.ctrlKey && isMac;
359
- const isValidButton =
360
- buttons === buttonsEnum.primary || buttons === buttonsEnum.middle;
361
-
362
- if (pointerType === 'touch' || (isValidButton && !isContextMenu)) {
363
- return true;
364
- } else {
365
- return false;
366
- }
368
+ const isValidButton = buttons === buttonsEnum.primary;
369
+ return pointerType === 'touch' || (isValidButton && !hasModifierKey(event));
370
}
371
372
/**
@@ -418,7 +421,7 @@ function dispatchEnd(
421
const onTapEnd = props.onTapEnd;
422
dispatchChange(context, props, state);
423
if (onTapEnd != null) {
421
- const defaultPrevented = state.shouldPreventClick === true;
424
+ const defaultPrevented = state.shouldPreventDefault === true;
425
const payload = context.objectAssign({}, state.gestureState, {
426
defaultPrevented,
427
type,
@@ -441,6 +444,22 @@ function dispatchCancel(
444
}
445
}
446
447
+function dispatchAuxiliaryTap(
448
+ context: ReactDOMResponderContext,
449
+ props: TapProps,
450
+ state: TapState,
451
+): void {
452
+ const type = 'tap:auxiliary';
453
+ const onAuxiliaryTap = props.onAuxiliaryTap;
454
+ if (onAuxiliaryTap != null) {
455
+ const payload = context.objectAssign({}, state.gestureState, {
456
+ defaultPrevented: false,
457
+ type,
458
+ });
459
+ dispatchDiscreteEvent(context, payload, onAuxiliaryTap);
460
+ }
461
+}
462
+
463
/**
464
* Responder implementation
465
*/
@@ -493,26 +512,41 @@ const responderImpl = {
512
}
513
}
514
496
- if (!state.isActive && shouldActivate(event)) {
497
- state.isActive = true;
498
- state.buttons = nativeEvent.buttons;
499
- state.pointerType = event.pointerType;
500
- state.responderTarget = context.getResponderNode();
501
- state.shouldPreventClick = props.preventDefault !== false;
502
-
503
- const gestureState = createGestureState(context, props, state, event);
504
- state.gestureState = gestureState;
505
- state.initialPosition.x = gestureState.x;
506
- state.initialPosition.y = gestureState.y;
507
-
508
- dispatchStart(context, props, state);
509
- addRootEventTypes(rootEventTypes, context, state);
510
-
511
- if (!hasPointerEvents) {
512
- if (eventType === 'touchstart') {
513
- state.ignoreEmulatedEvents = true;
515
+ if (!state.isActive) {
516
+ const activate = shouldActivate(event);
517
+ const activateAuxiliary = isAuxiliary(
518
+ nativeEvent.buttons,
519
+ nativeEvent,
520
+ );
521
+
522
+ if (activate || activateAuxiliary) {
523
+ state.buttons = nativeEvent.buttons;
524
+ state.pointerType = event.pointerType;
525
+ state.responderTarget = context.getResponderNode();
526
+ addRootEventTypes(rootEventTypes, context, state);
527
+ if (!hasPointerEvents) {
528
+ if (eventType === 'touchstart') {
529
+ state.ignoreEmulatedEvents = true;
530
+ }
531
}
532
}
533
+
534
+ if (activate) {
535
+ const gestureState = createGestureState(
536
+ context,
537
+ props,
538
+ state,
539
+ event,
540
+ );
541
+ state.isActive = true;
542
+ state.shouldPreventDefault = props.preventDefault !== false;
543
+ state.gestureState = gestureState;
544
+ state.initialPosition.x = gestureState.x;
545
+ state.initialPosition.y = gestureState.y;
546
+ dispatchStart(context, props, state);
547
+ } else if (activateAuxiliary) {
548
+ state.isAuxiliaryActive = true;
549
+ }
550
}
551
break;
552
}
@@ -575,24 +609,30 @@ const responderImpl = {
609
case 'mouseup':
610
case 'touchend': {
611
if (state.isActive && isActivePointer(event, state)) {
578
- if (state.buttons === buttonsEnum.middle) {
579
- // Remove the root events here as no 'click' event is dispatched
580
- // when this 'button' is pressed.
581
- removeRootEventTypes(context, state);
582
- }
583
-
612
state.gestureState = createGestureState(context, props, state, event);
585
-
613
state.isActive = false;
587
- if (context.isTargetWithinResponder(hitTarget)) {
588
- // Determine whether to call preventDefault on subsequent native events.
589
- if (hasModifierKey(event)) {
590
- state.shouldPreventClick = false;
591
- }
592
- dispatchEnd(context, props, state);
593
- } else {
614
+ if (isAuxiliary(state.buttons, nativeEvent)) {
615
+ dispatchCancel(context, props, state);
616
+ dispatchAuxiliaryTap(context, props, state);
617
+ // Remove the root events here as no 'click' event is dispatched
618
+ removeRootEventTypes(context, state);
619
+ } else if (
620
+ !context.isTargetWithinResponder(hitTarget) ||
621
+ hasModifierKey(event)
622
+ ) {
623
dispatchCancel(context, props, state);
624
+ } else {
625
+ dispatchEnd(context, props, state);
626
}
627
+ } else if (
628
+ state.isAuxiliaryActive &&
629
+ isAuxiliary(state.buttons, nativeEvent)
630
+ ) {
631
+ state.isAuxiliaryActive = false;
632
+ state.gestureState = createGestureState(context, props, state, event);
633
+ dispatchAuxiliaryTap(context, props, state);
634
+ // Remove the root events here as no 'click' event is dispatched
635
+ removeRootEventTypes(context, state);
636
}
637
638
if (!hasPointerEvents) {
@@ -612,6 +652,7 @@ const responderImpl = {
652
state.gestureState = createGestureState(context, props, state, event);
653
state.isActive = false;
654
dispatchCancel(context, props, state);
655
+ removeRootEventTypes(context, state);
656
}
657
break;
658
}
@@ -630,12 +671,13 @@ const responderImpl = {
671
state.gestureState = createGestureState(context, props, state, event);
672
state.isActive = false;
673
dispatchCancel(context, props, state);
674
+ removeRootEventTypes(context, state);
675
}
676
break;
677
}
678
679
case 'click': {
638
- if (state.shouldPreventClick) {
680
+ if (state.shouldPreventDefault) {
681
nativeEvent.preventDefault();
682
}
683
removeRootEventTypes(context, state);
packages/react-ui/events/src/dom/__tests__/Press-test.internal.js
+13
-5
@@ -125,11 +125,13 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
125
126
it('is called after middle-button pointer down', () => {
127
const target = createEventTarget(ref.current);
128
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
128
+ const pointerType = 'mouse';
129
+ target.pointerdown({buttons: buttonsType.auxiliary, pointerType});
130
+ target.pointerup({pointerType});
131
expect(onPressStart).toHaveBeenCalledTimes(1);
132
expect(onPressStart).toHaveBeenCalledWith(
133
expect.objectContaining({
132
- buttons: buttonsType.middle,
134
+ buttons: buttonsType.auxiliary,
135
pointerType: 'mouse',
136
type: 'pressstart',
137
}),
@@ -209,12 +211,15 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
211
212
it('is called after middle-button pointer up', () => {
213
const target = createEventTarget(ref.current);
212
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
214
+ target.pointerdown({
215
+ buttons: buttonsType.auxiliary,
216
+ pointerType: 'mouse',
217
+ });
218
target.pointerup({pointerType: 'mouse'});
219
expect(onPressEnd).toHaveBeenCalledTimes(1);
220
expect(onPressEnd).toHaveBeenCalledWith(
221
expect.objectContaining({
217
- buttons: buttonsType.middle,
222
+ buttons: buttonsType.auxiliary,
223
pointerType: 'mouse',
224
type: 'pressend',
225
}),
@@ -350,7 +355,10 @@ describeWithPointerEvent('Press responder', hasPointerEvents => {
355
356
it('is not called after middle-button press', () => {
357
const target = createEventTarget(ref.current);
353
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
358
+ target.pointerdown({
359
+ buttons: buttonsType.auxiliary,
360
+ pointerType: 'mouse',
361
+ });
362
target.pointerup({pointerType: 'mouse'});
363
expect(onPress).not.toHaveBeenCalled();
364
});
packages/react-ui/events/src/dom/__tests__/PressLegacy-test.internal.js
+18
-6
@@ -120,11 +120,14 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
120
121
it('is called after middle-button pointer down', () => {
122
const target = createEventTarget(ref.current);
123
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
123
+ target.pointerdown({
124
+ buttons: buttonsType.auxiliary,
125
+ pointerType: 'mouse',
126
+ });
127
expect(onPressStart).toHaveBeenCalledTimes(1);
128
expect(onPressStart).toHaveBeenCalledWith(
129
expect.objectContaining({
127
- buttons: buttonsType.middle,
130
+ buttons: buttonsType.auxiliary,
131
pointerType: 'mouse',
132
type: 'pressstart',
133
}),
@@ -135,7 +138,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
138
const node = ref.current;
139
const target = createEventTarget(node);
140
target.setBoundingClientRect({x: 0, y: 0, width: 100, height: 100});
138
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
141
+ target.pointerdown({
142
+ buttons: buttonsType.auxiliary,
143
+ pointerType: 'mouse',
144
+ });
145
target.pointerup({pointerType: 'mouse'});
146
target.pointerhover({x: 110, y: 110});
147
target.pointerhover({x: 50, y: 50});
@@ -215,12 +221,15 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
221
222
it('is called after middle-button pointer up', () => {
223
const target = createEventTarget(ref.current);
218
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
224
+ target.pointerdown({
225
+ buttons: buttonsType.auxiliary,
226
+ pointerType: 'mouse',
227
+ });
228
target.pointerup({pointerType: 'mouse'});
229
expect(onPressEnd).toHaveBeenCalledTimes(1);
230
expect(onPressEnd).toHaveBeenCalledWith(
231
expect.objectContaining({
223
- buttons: buttonsType.middle,
232
+ buttons: buttonsType.auxiliary,
233
pointerType: 'mouse',
234
type: 'pressend',
235
}),
@@ -356,7 +365,10 @@ describe.each(environmentTable)('Press responder', hasPointerEvents => {
365
366
it('is not called after middle-button press', () => {
367
const target = createEventTarget(ref.current);
359
- target.pointerdown({buttons: buttonsType.middle, pointerType: 'mouse'});
368
+ target.pointerdown({
369
+ buttons: buttonsType.auxiliary,
370
+ pointerType: 'mouse',
371
+ });
372
target.pointerup({pointerType: 'mouse'});
373
expect(onPress).not.toHaveBeenCalled();
374
});
packages/react-ui/events/src/dom/__tests__/Tap-test.internal.js
+112
-76
@@ -193,6 +193,39 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
193
});
194
});
195
196
+ describe('onAuxiliaryTap', () => {
197
+ let onAuxiliaryTap, ref;
198
+
199
+ beforeEach(() => {
200
+ onAuxiliaryTap = jest.fn();
201
+ ref = React.createRef();
202
+ const Component = () => {
203
+ const listener = useTap({onAuxiliaryTap});
204
+ return <div ref={ref} listeners={listener} />;
205
+ };
206
+ ReactDOM.render(<Component />, container);
207
+ document.elementFromPoint = () => ref.current;
208
+ });
209
+
210
+ test('auxiliary-button pointer up', () => {
211
+ const pointerType = 'mouse';
212
+ const buttons = buttonsType.auxiliary;
213
+ const target = createEventTarget(ref.current);
214
+ target.pointerdown({buttons, pointerType});
215
+ target.pointerup({pointerType});
216
+ expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
217
+ });
218
+
219
+ test('modifier-button pointer up', () => {
220
+ const pointerType = 'mouse';
221
+ const buttons = buttonsType.primary;
222
+ const target = createEventTarget(ref.current);
223
+ target.pointerdown({buttons, pointerType});
224
+ target.pointerup({metaKey: true, pointerType});
225
+ expect(onAuxiliaryTap).toHaveBeenCalledTimes(1);
226
+ });
227
+ });
228
+
229
describe('onTapStart', () => {
230
let onTapStart, ref;
231
@@ -210,6 +243,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
243
testWithPointerType('pointer down', pointerType => {
244
const target = createEventTarget(ref.current);
245
const nativeEvent = {
246
+ buttons: buttonsType.primary,
247
pageX: 10,
248
pageY: 10,
249
pointerType,
@@ -226,7 +260,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
260
expect(onTapStart).toHaveBeenCalledWith(
261
expect.objectContaining({
262
altKey: false,
229
- buttons: 1,
263
ctrlKey: false,
264
height: pointerType === 'mouse' ? 1 : 23,
265
metaKey: false,
@@ -269,36 +302,31 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
302
expect(onTapStart).toHaveBeenCalledTimes(1);
303
});
304
272
- test('primary-button pointer down', () => {
273
- const pointerType = 'mouse';
274
- const buttons = buttonsType.primary;
275
- const target = createEventTarget(ref.current);
276
- target.pointerdown({buttons, pointerType});
277
- expect(onTapStart).toHaveBeenCalledTimes(1);
278
- expect(onTapStart).toHaveBeenCalledWith(
279
- expect.objectContaining({buttons, pointerType}),
280
- );
281
- });
282
-
283
- test('middle-button pointer down', () => {
284
- const pointerType = 'mouse';
285
- const buttons = buttonsType.middle;
286
- const target = createEventTarget(ref.current);
287
- target.pointerdown({buttons, pointerType});
288
- expect(onTapStart).toHaveBeenCalledTimes(1);
289
- expect(onTapStart).toHaveBeenCalledWith(
290
- expect.objectContaining({buttons, pointerType}),
291
- );
292
- });
293
-
294
- test('unsupported buttons', () => {
305
+ test('ignored buttons and modifiers', () => {
306
const target = createEventTarget(ref.current);
307
+ const primary = buttonsType.primary;
308
// right-click
309
target.pointerdown({buttons: buttonsType.secondary});
310
target.pointerup();
311
+ // middle-click
312
+ target.pointerdown({buttons: buttonsType.auxiliary});
313
+ target.pointerup();
314
// pen eraser
315
target.pointerdown({buttons: buttonsType.eraser});
316
target.pointerup();
317
+ // alt-click
318
+ target.pointerdown({buttons: primary, altKey: true});
319
+ target.pointerup();
320
+ // ctrl-click
321
+ target.pointerdown({buttons: primary, ctrlKey: true});
322
+ target.pointerup();
323
+ // meta-click
324
+ target.pointerdown({buttons: primary, metaKey: true});
325
+ target.pointerup();
326
+ // shift-click
327
+ target.pointerdown({buttons: primary, shiftKey: true});
328
+ target.pointerup();
329
+
330
expect(onTapStart).toHaveBeenCalledTimes(0);
331
});
332
});
@@ -319,7 +347,7 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
347
348
testWithPointerType('pointer up', pointerType => {
349
const target = createEventTarget(ref.current);
322
- target.pointerdown({pointerType});
350
+ target.pointerdown({buttons: buttonsType.primary, pointerType});
351
target.pointerup({
352
pageX: 10,
353
pageY: 10,
@@ -331,7 +359,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
359
expect(onTapEnd).toHaveBeenCalledWith(
360
expect.objectContaining({
361
altKey: false,
334
- buttons: 1,
362
ctrlKey: false,
363
height: pointerType === 'mouse' ? 1 : 23,
364
metaKey: false,
@@ -356,30 +383,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
383
);
384
});
385
359
- test('primary-button pointer up', () => {
360
- const pointerType = 'mouse';
361
- const buttons = buttonsType.primary;
362
- const target = createEventTarget(ref.current);
363
- target.pointerdown({buttons, pointerType});
364
- target.pointerup({pointerType});
365
- expect(onTapEnd).toHaveBeenCalledTimes(1);
366
- expect(onTapEnd).toHaveBeenCalledWith(
367
- expect.objectContaining({buttons, pointerType}),
368
- );
369
- });
370
-
371
- test('middle-button pointer up', () => {
372
- const pointerType = 'mouse';
373
- const buttons = buttonsType.middle;
374
- const target = createEventTarget(ref.current);
375
- target.pointerdown({buttons, pointerType});
376
- target.pointerup({pointerType});
377
- expect(onTapEnd).toHaveBeenCalledTimes(1);
378
- expect(onTapEnd).toHaveBeenCalledWith(
379
- expect.objectContaining({buttons, pointerType}),
380
- );
381
- });
382
-
386
testWithPointerType('zero-dimension hit rect', pointerType => {
387
const targetRef = React.createRef();
388
const innerRef = React.createRef();
@@ -414,6 +417,34 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
417
});
418
expect(onTapEnd).not.toBeCalled();
419
});
420
+
421
+ test('ignored buttons and modifiers', () => {
422
+ const target = createEventTarget(ref.current);
423
+ const primary = buttonsType.primary;
424
+ // right-click
425
+ target.pointerdown({buttons: buttonsType.secondary});
426
+ target.pointerup();
427
+ // middle-click
428
+ target.pointerdown({buttons: buttonsType.auxiliary});
429
+ target.pointerup();
430
+ // pen eraser
431
+ target.pointerdown({buttons: buttonsType.eraser});
432
+ target.pointerup();
433
+ // alt-click
434
+ target.pointerdown({buttons: primary});
435
+ target.pointerup({altKey: true});
436
+ // ctrl-click
437
+ target.pointerdown({buttons: primary});
438
+ target.pointerup({ctrlKey: true});
439
+ // meta-click
440
+ target.pointerdown({buttons: primary});
441
+ target.pointerup({metaKey: true});
442
+ // shift-click
443
+ target.pointerdown({buttons: primary});
444
+ target.pointerup({shiftKey: true});
445
+
446
+ expect(onTapEnd).toHaveBeenCalledTimes(0);
447
+ });
448
});
449
450
describe('onTapUpdate', () => {
@@ -449,7 +480,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
480
expect(onTapUpdate).toHaveBeenCalledWith(
481
expect.objectContaining({
482
altKey: false,
452
- buttons: 1,
483
ctrlKey: false,
484
height: pointerType === 'mouse' ? 1 : 23,
485
metaKey: false,
@@ -594,7 +624,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
624
expect(onTapCancel).toHaveBeenCalledWith(
625
expect.objectContaining({
626
altKey: false,
597
- buttons: buttonsType.primary,
627
ctrlKey: false,
628
height: 1,
629
metaKey: false,
@@ -621,6 +650,37 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
650
expect(onTapUpdate).not.toBeCalled();
651
});
652
653
+ testWithPointerType('pointer move outside target', pointerType => {
654
+ const downTarget = createEventTarget(ref.current);
655
+ const upTarget = createEventTarget(container);
656
+ tapAndMoveOutside({
657
+ hasPointerEvents,
658
+ downTarget,
659
+ upTarget,
660
+ pointerType,
661
+ });
662
+ expect(onTapCancel).toBeCalled();
663
+ });
664
+
665
+ test('ignored modifiers', () => {
666
+ const target = createEventTarget(ref.current);
667
+ const primary = buttonsType.primary;
668
+ // alt-click
669
+ target.pointerdown({buttons: primary});
670
+ target.pointerup({altKey: true});
671
+ // ctrl-click
672
+ target.pointerdown({buttons: primary});
673
+ target.pointerup({ctrlKey: true});
674
+ // meta-click
675
+ target.pointerdown({buttons: primary});
676
+ target.pointerup({metaKey: true});
677
+ // shift-click
678
+ target.pointerdown({buttons: primary});
679
+ target.pointerup({shiftKey: true});
680
+
681
+ expect(onTapCancel).toHaveBeenCalledTimes(4);
682
+ });
683
+
684
test('long press context menu', () => {
685
const target = createEventTarget(ref.current);
686
target.contextmenu({}, {pointerType: 'touch'});
@@ -659,18 +719,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
719
containerTarget.scroll();
720
expect(onTapCancel).toHaveBeenCalledTimes(1);
721
});
662
-
663
- testWithPointerType('pointer move outside target', pointerType => {
664
- const downTarget = createEventTarget(ref.current);
665
- const upTarget = createEventTarget(container);
666
- tapAndMoveOutside({
667
- hasPointerEvents,
668
- downTarget,
669
- upTarget,
670
- pointerType,
671
- });
672
- expect(onTapCancel).toBeCalled();
673
- });
722
});
723
724
describe('preventDefault', () => {
@@ -718,18 +766,6 @@ describeWithPointerEvent('Tap responder', hasPointerEvents => {
766
);
767
});
768
721
- test('allows native behaviour by default (modifier keys)', () => {
722
- ['metaKey', 'ctrlKey', 'shiftKey'].forEach(modifierKey => {
723
- const target = createEventTarget(ref.current);
724
- target.pointerdown({[modifierKey]: true});
725
- target.pointerup({[modifierKey]: true, preventDefault});
726
- expect(preventDefault).not.toBeCalled();
727
- expect(onTapEnd).toHaveBeenCalledWith(
728
- expect.objectContaining({defaultPrevented: false}),
729
- );
730
- });
731
- });
732
-
769
test('allows native behaviour if false', () => {
770
remount(false);
771
packages/react-ui/events/src/dom/shared/index.js
+1
-1
@@ -27,7 +27,7 @@ export const buttonsEnum = {
27
none: 0,
28
primary: 1,
29
secondary: 2,
30
- middle: 4,
30
+ auxiliary: 4,
31
};
32
33
export function dispatchDiscreteEvent(
packages/react-ui/events/src/dom/testing-library/domEnvironment.js
+1
-1
@@ -78,7 +78,7 @@ export const buttonsType = {
78
// pen barrel button
79
secondary: 2,
80
// middle mouse
81
- middle: 4,
81
+ auxiliary: 4,
82
// back mouse
83
back: 8,
84
// forward mouse