[Flare] Refactor of Press to fix various issues (#15878)
Dominic Gannaway committed
Jun 14, 2019 at 22:52 UTC
a146c1f9ed2f13bb885a5f49623d45f03c0bb424
9 files changed
+649
-310
packages/react-dom/src/events/DOMEventResponderSystem.js
+19
-22
@@ -394,26 +394,6 @@ const eventResponderContext: ReactResponderContext = {
394
},
395
getActiveDocument,
396
objectAssign: Object.assign,
397
- getEventPointerType(
398
- event: ReactResponderEvent,
399
- ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch' {
400
- validateResponderContext();
401
- const nativeEvent: any = event.nativeEvent;
402
- const {type, pointerType} = nativeEvent;
403
- if (pointerType != null) {
404
- return pointerType;
405
- }
406
- if (type.indexOf('mouse') === 0) {
407
- return 'mouse';
408
- }
409
- if (type.indexOf('touch') === 0) {
410
- return 'touch';
411
- }
412
- if (type.indexOf('key') === 0) {
413
- return 'keyboard';
414
- }
415
- return '';
416
- },
397
getEventCurrentTarget(event: ReactResponderEvent): Element {
398
validateResponderContext();
399
const target = event.target;
@@ -583,12 +563,29 @@ function createResponderEvent(
563
passive: boolean,
564
passiveSupported: boolean,
565
): ReactResponderEvent {
566
+ const {pointerType} = (nativeEvent: any);
567
+ let eventPointerType = '';
568
+ let pointerId = null;
569
+
570
+ if (pointerType !== undefined) {
571
+ eventPointerType = pointerType;
572
+ pointerId = (nativeEvent: any).pointerId;
573
+ } else if (nativeEvent.key !== undefined) {
574
+ eventPointerType = 'keyboard';
575
+ } else if (nativeEvent.button !== undefined) {
576
+ eventPointerType = 'mouse';
577
+ } else if ((nativeEvent: any).changedTouches !== undefined) {
578
+ eventPointerType = 'touch';
579
+ }
580
+
581
const responderEvent = {
582
nativeEvent: nativeEvent,
588
- target: nativeEventTarget,
589
- type: topLevelType,
583
passive,
584
passiveSupported,
585
+ pointerId,
586
+ pointerType: eventPointerType,
587
+ target: nativeEventTarget,
588
+ type: topLevelType,
589
};
590
if (__DEV__) {
591
Object.freeze(responderEvent);
packages/react-events/src/Focus.js
+1
-1
@@ -8,6 +8,7 @@
8
*/
9
10
import type {
11
+ PointerType,
12
ReactResponderEvent,
13
ReactResponderContext,
14
} from 'shared/ReactTypes';
@@ -30,7 +31,6 @@ type FocusState = {
31
pointerType: PointerType,
32
};
33
33
-type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
34
type FocusEventType = 'focus' | 'blur' | 'focuschange' | 'focusvisiblechange';
35
36
type FocusEvent = {|
packages/react-events/src/Hover.js
+1
-2
@@ -294,7 +294,7 @@ const HoverResponder = {
294
props: HoverProps,
295
state: HoverState,
296
): void {
297
- const {type} = event;
297
+ const {pointerType, type} = event;
298
299
if (props.disabled) {
300
if (state.isHovered) {
@@ -306,7 +306,6 @@ const HoverResponder = {
306
}
307
return;
308
}
309
- const pointerType = context.getEventPointerType(event);
309
310
switch (type) {
311
// START
packages/react-events/src/Press.js
+174
-131
@@ -8,6 +8,7 @@
8
*/
9
10
import type {
11
+ PointerType,
12
ReactResponderEvent,
13
ReactResponderContext,
14
} from 'shared/ReactTypes';
@@ -40,8 +41,6 @@ type PressProps = {
41
stopPropagation: boolean,
42
};
43
43
-type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
44
-
44
type PressState = {
45
activationPosition: null | $ReadOnly<{|
46
x: number,
@@ -71,7 +70,7 @@ type PressState = {
70
top: number,
71
|}>,
72
ignoreEmulatedMouseEvents: boolean,
74
- allowPressReentry: boolean,
73
+ activePointerId: null | number,
74
};
75
76
type PressEventType =
@@ -176,7 +175,7 @@ function createPressEvent(
175
let eventObject;
176
if (nativeEvent.clientX !== undefined) {
177
eventObject = (nativeEvent: any);
179
- } else if (isTouchEvent(nativeEvent)) {
178
+ } else if (isNativeTouchEvent(nativeEvent)) {
179
eventObject = getTouchFromPressEvent(nativeEvent);
180
}
181
if (eventObject) {
@@ -482,7 +481,7 @@ function calculateResponderRegion(
481
};
482
}
483
485
-function isTouchEvent(nativeEvent: Event): boolean {
484
+function isNativeTouchEvent(nativeEvent: Event): boolean {
485
const changedTouches = ((nativeEvent: any): TouchEvent).changedTouches;
486
return changedTouches && typeof changedTouches.length === 'number';
487
}
@@ -500,7 +499,7 @@ function getEventViewportCoords(
499
nativeEvent: Event,
500
): {x: null | number, y: null | number} {
501
let eventObject = (nativeEvent: any);
503
- if (isTouchEvent(eventObject)) {
502
+ if (isNativeTouchEvent(eventObject)) {
503
eventObject = getTouchFromPressEvent(eventObject);
504
}
505
const x = eventObject.clientX;
@@ -511,39 +510,6 @@ function getEventViewportCoords(
510
};
511
}
512
514
-function isPressWithinResponderRegion(
515
- nativeEvent: $PropertyType<ReactResponderEvent, 'nativeEvent'>,
516
- state: PressState,
517
-): boolean {
518
- const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
519
- let left, top, right, bottom;
520
-
521
- if (responderRegionOnActivation != null) {
522
- left = responderRegionOnActivation.left;
523
- top = responderRegionOnActivation.top;
524
- right = responderRegionOnActivation.right;
525
- bottom = responderRegionOnActivation.bottom;
526
-
527
- if (responderRegionOnDeactivation != null) {
528
- left = Math.min(left, responderRegionOnDeactivation.left);
529
- top = Math.min(top, responderRegionOnDeactivation.top);
530
- right = Math.max(right, responderRegionOnDeactivation.right);
531
- bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
532
- }
533
- }
534
- const {x, y} = getEventViewportCoords(((nativeEvent: any): Event));
535
-
536
- return (
537
- left != null &&
538
- right != null &&
539
- top != null &&
540
- bottom != null &&
541
- x !== null &&
542
- y !== null &&
543
- (x >= left && x <= right && y >= top && y <= bottom)
544
- );
545
-}
546
-
513
function unmountResponder(
514
context: ReactResponderContext,
515
props: PressProps,
@@ -571,11 +537,78 @@ function removeRootEventTypes(
537
): void {
538
if (state.addedRootEvents) {
539
state.addedRootEvents = false;
574
- state.allowPressReentry = false;
540
context.removeRootEventTypes(rootEventTypes);
541
}
542
}
543
544
+function getTouchById(
545
+ nativeEvent: TouchEvent,
546
+ pointerId: null | number,
547
+): null | Touch {
548
+ const changedTouches = nativeEvent.changedTouches;
549
+ for (let i = 0; i < changedTouches.length; i++) {
550
+ const touch = changedTouches[i];
551
+ if (touch.identifier === pointerId) {
552
+ return touch;
553
+ }
554
+ }
555
+ return null;
556
+}
557
+
558
+function getTouchTarget(context: ReactResponderContext, touchEvent: Touch) {
559
+ const doc = context.getActiveDocument();
560
+ return doc.elementFromPoint(touchEvent.clientX, touchEvent.clientY);
561
+}
562
+
563
+function updateIsPressWithinResponderRegion(
564
+ target: Element | Document,
565
+ nativeEventOrTouchEvent: Event | Touch,
566
+ context: ReactResponderContext,
567
+ props: PressProps,
568
+ state: PressState,
569
+): void {
570
+ if (
571
+ state.pressTarget != null &&
572
+ !context.isTargetWithinElement(target, state.pressTarget)
573
+ ) {
574
+ // Calculate the responder region we use for deactivation if not
575
+ // already done during move event.
576
+ if (state.responderRegionOnDeactivation == null) {
577
+ state.responderRegionOnDeactivation = calculateResponderRegion(
578
+ context,
579
+ state.pressTarget,
580
+ props,
581
+ );
582
+ }
583
+ const {responderRegionOnActivation, responderRegionOnDeactivation} = state;
584
+ let left, top, right, bottom;
585
+
586
+ if (responderRegionOnActivation != null) {
587
+ left = responderRegionOnActivation.left;
588
+ top = responderRegionOnActivation.top;
589
+ right = responderRegionOnActivation.right;
590
+ bottom = responderRegionOnActivation.bottom;
591
+
592
+ if (responderRegionOnDeactivation != null) {
593
+ left = Math.min(left, responderRegionOnDeactivation.left);
594
+ top = Math.min(top, responderRegionOnDeactivation.top);
595
+ right = Math.max(right, responderRegionOnDeactivation.right);
596
+ bottom = Math.max(bottom, responderRegionOnDeactivation.bottom);
597
+ }
598
+ }
599
+ const {clientX: x, clientY: y} = (nativeEventOrTouchEvent: any);
600
+
601
+ state.isPressWithinResponderRegion =
602
+ left != null &&
603
+ right != null &&
604
+ top != null &&
605
+ bottom != null &&
606
+ x !== null &&
607
+ y !== null &&
608
+ (x >= left && x <= right && y >= top && y <= bottom);
609
+ }
610
+}
611
+
612
const PressResponder = {
613
targetEventTypes,
614
createInitialState(): PressState {
@@ -596,7 +629,7 @@ const PressResponder = {
629
responderRegionOnActivation: null,
630
responderRegionOnDeactivation: null,
631
ignoreEmulatedMouseEvents: false,
599
- allowPressReentry: false,
632
+ activePointerId: null,
633
};
634
},
635
allowMultipleHostChildren: false,
@@ -607,7 +640,7 @@ const PressResponder = {
640
props: PressProps,
641
state: PressState,
642
): void {
610
- const {type} = event;
643
+ const {pointerId, pointerType, type} = event;
644
645
if (props.disabled) {
646
removeRootEventTypes(context, state);
@@ -616,7 +649,7 @@ const PressResponder = {
649
return;
650
}
651
const nativeEvent: any = event.nativeEvent;
619
- const pointerType = context.getEventPointerType(event);
652
+ const isPressed = state.isPressed;
653
654
if (props.stopPropagation === true) {
655
nativeEvent.stopPropagation();
@@ -627,26 +660,27 @@ const PressResponder = {
660
case 'keydown':
661
case 'mousedown':
662
case 'touchstart': {
630
- if (!state.isPressed) {
631
- if (type === 'pointerdown' || type === 'touchstart') {
663
+ if (!isPressed) {
664
+ const isTouchEvent = type === 'touchstart';
665
+ const isPointerEvent = type === 'pointerdown';
666
+ const isKeyboardEvent = pointerType === 'keyboard';
667
+ const isMouseEvent = pointerType === 'mouse';
668
+ const isPenEvent = pointerType === 'pen';
669
+
670
+ if (isPointerEvent || isTouchEvent) {
671
state.ignoreEmulatedMouseEvents = true;
633
- }
634
-
635
- // Ignore unrelated key events
636
- if (pointerType === 'keyboard') {
672
+ } else if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
673
+ // Ignore emulated mouse events
674
+ return;
675
+ } else if (isKeyboardEvent) {
676
+ // Ignore unrelated key events
677
if (!isValidKeyboardEvent(nativeEvent)) {
678
return;
679
}
680
}
641
-
642
- // Ignore emulated mouse events
643
- if (type === 'mousedown' && state.ignoreEmulatedMouseEvents) {
644
- return;
645
- }
681
// Ignore mouse/pen pressing on touch hit target area
647
- const isMouseType = pointerType === 'mouse';
682
if (
649
- (isMouseType || pointerType === 'pen') &&
683
+ (isMouseEvent || isPenEvent) &&
684
context.isEventWithinTouchHitTarget(event)
685
) {
686
// We need to prevent the native event to block the focus
@@ -659,18 +693,23 @@ const PressResponder = {
693
// data around for handling of the context menu
694
state.pointerType = pointerType;
695
state.pressTarget = context.getEventCurrentTarget(event);
696
+ if (isPointerEvent) {
697
+ state.activePointerId = pointerId;
698
+ } else if (isTouchEvent) {
699
+ const touchEvent = getTouchFromPressEvent(nativeEvent);
700
+ state.activePointerId = touchEvent.identifier;
701
+ }
702
703
// Ignore any device buttons except left-mouse and touch/pen contact.
704
// Additionally we ignore left-mouse + ctrl-key with Macs as that
705
// acts like right-click and opens the contextmenu.
706
if (
707
nativeEvent.button > 0 ||
668
- (isMac && isMouseType && nativeEvent.ctrlKey)
708
+ (isMac && isMouseEvent && nativeEvent.ctrlKey)
709
) {
710
return;
711
}
712
673
- state.allowPressReentry = true;
713
state.responderRegionOnActivation = calculateResponderRegion(
714
context,
715
state.pressTarget,
@@ -689,13 +728,13 @@ const PressResponder = {
728
}
729
730
case 'contextmenu': {
692
- if (state.isPressed) {
693
- dispatchCancel(event, context, props, state);
731
+ if (isPressed) {
732
if (props.preventDefault !== false) {
733
// Skip dispatching of onContextMenu below
734
nativeEvent.preventDefault();
735
return;
736
}
737
+ dispatchCancel(event, context, props, state);
738
}
739
if (props.onContextMenu) {
740
dispatchEvent(
@@ -719,10 +758,11 @@ const PressResponder = {
758
props: PressProps,
759
state: PressState,
760
): void {
722
- const {target, type} = event;
761
+ let {pointerId, pointerType, target, type} = event;
762
763
const nativeEvent: any = event.nativeEvent;
725
- const pointerType = context.getEventPointerType(event);
764
+ const isPressed = state.isPressed;
765
+ const activePointerId = state.activePointerId;
766
767
if (props.stopPropagation === true) {
768
nativeEvent.stopPropagation();
@@ -732,61 +772,62 @@ const PressResponder = {
772
case 'pointermove':
773
case 'mousemove':
774
case 'touchmove': {
735
- if (state.isPressed || state.allowPressReentry) {
736
- // Ignore emulated events (pointermove will dispatch touch and mouse events)
737
- // Ignore pointermove events during a keyboard press.
738
- if (state.pointerType !== pointerType) {
775
+ let touchEvent;
776
+ // Ignore emulated events (pointermove will dispatch touch and mouse events)
777
+ // Ignore pointermove events during a keyboard press.
778
+ if (state.pointerType !== pointerType) {
779
+ return;
780
+ }
781
+ if (type === 'pointermove' && activePointerId !== pointerId) {
782
+ return;
783
+ } else if (type === 'touchmove') {
784
+ touchEvent = getTouchById(nativeEvent, activePointerId);
785
+ if (touchEvent === null) {
786
return;
787
}
788
+ target = getTouchTarget(context, touchEvent);
789
+ }
790
742
- // Calculate the responder region we use for deactivation, as the
743
- // element dimensions may have changed since activation.
744
- if (
745
- state.pressTarget !== null &&
746
- state.responderRegionOnDeactivation == null
747
- ) {
748
- state.responderRegionOnDeactivation = calculateResponderRegion(
749
- context,
750
- state.pressTarget,
751
- props,
752
- );
753
- }
754
- state.isPressWithinResponderRegion = isPressWithinResponderRegion(
755
- nativeEvent,
756
- state,
757
- );
758
-
759
- if (state.isPressWithinResponderRegion) {
760
- if (state.isPressed) {
761
- if (props.onPressMove) {
762
- dispatchEvent(
763
- event,
764
- context,
765
- state,
766
- 'pressmove',
767
- props.onPressMove,
768
- UserBlockingEvent,
769
- );
770
- }
791
+ // Calculate the responder region we use for deactivation, as the
792
+ // element dimensions may have changed since activation.
793
+ updateIsPressWithinResponderRegion(
794
+ target,
795
+ touchEvent || nativeEvent,
796
+ context,
797
+ props,
798
+ state,
799
+ );
800
+
801
+ if (state.isPressWithinResponderRegion) {
802
+ if (isPressed) {
803
+ if (props.onPressMove) {
804
+ dispatchEvent(
805
+ event,
806
+ context,
807
+ state,
808
+ 'pressmove',
809
+ props.onPressMove,
810
+ UserBlockingEvent,
811
+ );
812
+ }
813
+ if (
814
+ state.activationPosition != null &&
815
+ state.longPressTimeout != null
816
+ ) {
817
+ const deltaX = state.activationPosition.x - nativeEvent.clientX;
818
+ const deltaY = state.activationPosition.y - nativeEvent.clientY;
819
if (
772
- state.activationPosition != null &&
820
+ Math.hypot(deltaX, deltaY) > 10 &&
821
state.longPressTimeout != null
822
) {
775
- const deltaX = state.activationPosition.x - nativeEvent.clientX;
776
- const deltaY = state.activationPosition.y - nativeEvent.clientY;
777
- if (
778
- Math.hypot(deltaX, deltaY) > 10 &&
779
- state.longPressTimeout != null
780
- ) {
781
- context.clearTimeout(state.longPressTimeout);
782
- }
823
+ context.clearTimeout(state.longPressTimeout);
824
}
784
- } else {
785
- dispatchPressStartEvents(event, context, props, state);
825
}
826
} else {
788
- dispatchPressEndEvents(event, context, props, state);
827
+ dispatchPressStartEvents(event, context, props, state);
828
}
829
+ } else {
830
+ dispatchPressEndEvents(event, context, props, state);
831
}
832
break;
833
}
@@ -796,39 +837,41 @@ const PressResponder = {
837
case 'keyup':
838
case 'mouseup':
839
case 'touchend': {
799
- if (state.isPressed) {
800
- // Ignore unrelated keyboard events and verify press is within
801
- // responder region for non-keyboard events.
802
- if (pointerType === 'keyboard') {
803
- if (!isValidKeyboardEvent(nativeEvent)) {
840
+ if (isPressed) {
841
+ let isKeyboardEvent = false;
842
+ let touchEvent;
843
+ if (type === 'pointerup' && activePointerId !== pointerId) {
844
+ return;
845
+ } else if (type === 'touchend') {
846
+ touchEvent = getTouchById(nativeEvent, activePointerId);
847
+ if (touchEvent === null) {
848
return;
849
}
806
- // If the event target isn't within the press target, check if we're still
807
- // within the responder region. The region may have changed if the
808
- // element's layout was modified after activation.
809
- } else if (
810
- state.pressTarget != null &&
811
- !context.isTargetWithinElement(target, state.pressTarget)
812
- ) {
813
- // Calculate the responder region we use for deactivation if not
814
- // already done during move event.
815
- if (state.responderRegionOnDeactivation == null) {
816
- state.responderRegionOnDeactivation = calculateResponderRegion(
817
- context,
818
- state.pressTarget,
819
- props,
820
- );
850
+ target = getTouchTarget(context, touchEvent);
851
+ } else if (type === 'keyup') {
852
+ // Ignore unrelated keyboard events
853
+ if (!isValidKeyboardEvent(nativeEvent)) {
854
+ return;
855
}
822
- state.isPressWithinResponderRegion = isPressWithinResponderRegion(
823
- nativeEvent,
824
- state,
825
- );
856
+ isKeyboardEvent = true;
857
}
858
859
const wasLongPressed = state.isLongPressed;
860
dispatchPressEndEvents(event, context, props, state);
861
862
if (state.pressTarget !== null && props.onPress) {
863
+ if (!isKeyboardEvent) {
864
+ // If the event target isn't within the press target, check if we're still
865
+ // within the responder region. The region may have changed if the
866
+ // element's layout was modified after activation.
867
+ updateIsPressWithinResponderRegion(
868
+ target,
869
+ touchEvent || nativeEvent,
870
+ context,
871
+ props,
872
+ state,
873
+ );
874
+ }
875
if (state.isPressWithinResponderRegion) {
876
if (
877
!(
packages/react-events/src/Scroll.js
+3
-6
@@ -8,6 +8,7 @@
8
*/
9
10
import type {
11
+ PointerType,
12
ReactResponderEvent,
13
ReactResponderContext,
14
} from 'shared/ReactTypes';
@@ -38,8 +39,6 @@ type ScrollEventType =
39
| 'scrollmomentumstart'
40
| 'scrollmomentumend';
41
41
-type PointerType = '' | 'mouse' | 'keyboard' | 'pen' | 'touch';
42
-
42
type ScrollDirection = '' | 'up' | 'down' | 'left' | 'right';
43
44
type ScrollEvent = {|
@@ -134,7 +133,7 @@ const ScrollResponder = {
133
props: ScrollProps,
134
state: ScrollState,
135
): void {
137
- const {target, type} = event;
136
+ const {pointerType, target, type} = event;
137
138
if (props.disabled) {
139
if (state.isPointerDown) {
@@ -144,7 +143,6 @@ const ScrollResponder = {
143
}
144
return;
145
}
147
- const pointerType = context.getEventPointerType(event);
146
147
switch (type) {
148
case 'scroll': {
@@ -181,8 +179,7 @@ const ScrollResponder = {
179
props: ScrollProps,
180
state: ScrollState,
181
) {
184
- const {type} = event;
185
- const pointerType = context.getEventPointerType(event);
182
+ const {pointerType, type} = event;
183
184
switch (type) {
185
case 'pointercancel':
packages/react-events/src/__tests__/Hover-test.internal.js
+110
-70
@@ -16,7 +16,7 @@ let TestUtils;
16
let Scheduler;
17
let Hover;
18
19
-const createPointerEvent = (type, data) => {
19
+const createEvent = (type, data) => {
20
const event = document.createEvent('CustomEvent');
21
event.initCustomEvent(type, true, true);
22
if (data != null) {
@@ -27,6 +27,17 @@ const createPointerEvent = (type, data) => {
27
return event;
28
};
29
30
+function createTouchEvent(type, id, data) {
31
+ return createEvent(type, {
32
+ changedTouches: [
33
+ {
34
+ ...data,
35
+ identifier: id,
36
+ },
37
+ ],
38
+ });
39
+}
40
+
41
describe('Hover event responder', () => {
42
let container;
43
@@ -67,8 +78,8 @@ describe('Hover event responder', () => {
78
});
79
80
it('prevents custom events being dispatched', () => {
70
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
71
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
81
+ ref.current.dispatchEvent(createEvent('pointerover'));
82
+ ref.current.dispatchEvent(createEvent('pointerout'));
83
expect(onHoverStart).not.toBeCalled();
84
expect(onHoverEnd).not.toBeCalled();
85
});
@@ -89,44 +100,65 @@ describe('Hover event responder', () => {
100
});
101
102
it('is called after "pointerover" event', () => {
92
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
103
+ ref.current.dispatchEvent(createEvent('pointerover'));
104
expect(onHoverStart).toHaveBeenCalledTimes(1);
105
});
106
107
it('is not called if "pointerover" pointerType is touch', () => {
97
- const event = createPointerEvent('pointerover', {pointerType: 'touch'});
108
+ const event = createEvent('pointerover', {pointerType: 'touch'});
109
ref.current.dispatchEvent(event);
110
expect(onHoverStart).not.toBeCalled();
111
});
112
113
it('is called if valid "pointerover" follows touch', () => {
114
ref.current.dispatchEvent(
104
- createPointerEvent('pointerover', {pointerType: 'touch'}),
115
+ createEvent('pointerover', {pointerType: 'touch'}),
116
);
117
ref.current.dispatchEvent(
107
- createPointerEvent('pointerout', {pointerType: 'touch'}),
118
+ createEvent('pointerout', {pointerType: 'touch'}),
119
);
120
ref.current.dispatchEvent(
110
- createPointerEvent('pointerover', {pointerType: 'mouse'}),
121
+ createEvent('pointerover', {pointerType: 'mouse'}),
122
);
123
expect(onHoverStart).toHaveBeenCalledTimes(1);
124
});
125
126
it('ignores browser emulated "mouseover" event', () => {
116
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
117
- ref.current.dispatchEvent(createPointerEvent('mouseover'));
127
+ ref.current.dispatchEvent(createEvent('pointerover'));
128
+ ref.current.dispatchEvent(
129
+ createEvent('mouseover', {
130
+ button: 0,
131
+ }),
132
+ );
133
expect(onHoverStart).toHaveBeenCalledTimes(1);
134
});
135
136
// No PointerEvent fallbacks
137
it('is called after "mouseover" event', () => {
123
- ref.current.dispatchEvent(createPointerEvent('mouseover'));
138
+ ref.current.dispatchEvent(
139
+ createEvent('mouseover', {
140
+ button: 0,
141
+ }),
142
+ );
143
expect(onHoverStart).toHaveBeenCalledTimes(1);
144
});
145
+
146
it('is not called after "touchstart"', () => {
127
- ref.current.dispatchEvent(createPointerEvent('touchstart'));
128
- ref.current.dispatchEvent(createPointerEvent('touchend'));
129
- ref.current.dispatchEvent(createPointerEvent('mouseover'));
147
+ ref.current.dispatchEvent(
148
+ createTouchEvent('touchstart', 0, {
149
+ target: ref.current,
150
+ }),
151
+ );
152
+ ref.current.dispatchEvent(
153
+ createTouchEvent('touchend', 0, {
154
+ target: ref.current,
155
+ }),
156
+ );
157
+ ref.current.dispatchEvent(
158
+ createEvent('mouseover', {
159
+ button: 0,
160
+ }),
161
+ );
162
expect(onHoverStart).not.toBeCalled();
163
});
164
@@ -139,7 +171,7 @@ describe('Hover event responder', () => {
171
);
172
ReactDOM.render(element, container);
173
142
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
174
+ ref.current.dispatchEvent(createEvent('pointerover'));
175
jest.advanceTimersByTime(1999);
176
expect(onHoverStart).not.toBeCalled();
177
jest.advanceTimersByTime(1);
@@ -154,12 +186,12 @@ describe('Hover event responder', () => {
186
);
187
ReactDOM.render(element, container);
188
157
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
189
+ ref.current.dispatchEvent(createEvent('pointerover'));
190
jest.advanceTimersByTime(499);
159
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
191
+ ref.current.dispatchEvent(createEvent('pointerout'));
192
jest.advanceTimersByTime(1);
193
expect(onHoverStart).not.toBeCalled();
162
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
194
+ ref.current.dispatchEvent(createEvent('pointerover'));
195
jest.runAllTimers();
196
expect(onHoverStart).toHaveBeenCalledTimes(1);
197
});
@@ -172,7 +204,7 @@ describe('Hover event responder', () => {
204
);
205
ReactDOM.render(element, container);
206
175
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
207
+ ref.current.dispatchEvent(createEvent('pointerover'));
208
expect(onHoverStart).toHaveBeenCalledTimes(1);
209
});
210
@@ -187,12 +219,12 @@ describe('Hover event responder', () => {
219
);
220
ReactDOM.render(element, container);
221
190
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
222
+ ref.current.dispatchEvent(createEvent('pointerover'));
223
jest.advanceTimersByTime(500);
224
expect(onHoverStart).toHaveBeenCalledTimes(1);
193
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
225
+ ref.current.dispatchEvent(createEvent('pointerout'));
226
jest.advanceTimersByTime(10);
195
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
227
+ ref.current.dispatchEvent(createEvent('pointerover'));
228
jest.runAllTimers();
229
expect(onHoverStart).toHaveBeenCalledTimes(1);
230
});
@@ -214,20 +246,20 @@ describe('Hover event responder', () => {
246
});
247
248
it('is called after "pointerover" and "pointerout" events', () => {
217
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
249
+ ref.current.dispatchEvent(createEvent('pointerover'));
250
expect(onHoverChange).toHaveBeenCalledTimes(1);
251
expect(onHoverChange).toHaveBeenCalledWith(true);
220
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
252
+ ref.current.dispatchEvent(createEvent('pointerout'));
253
expect(onHoverChange).toHaveBeenCalledTimes(2);
254
expect(onHoverChange).toHaveBeenCalledWith(false);
255
});
256
257
// No PointerEvent fallbacks
258
it('is called after "mouseover" and "mouseout" events', () => {
227
- ref.current.dispatchEvent(createPointerEvent('mouseover'));
259
+ ref.current.dispatchEvent(createEvent('mouseover'));
260
expect(onHoverChange).toHaveBeenCalledTimes(1);
261
expect(onHoverChange).toHaveBeenCalledWith(true);
230
- ref.current.dispatchEvent(createPointerEvent('mouseout'));
262
+ ref.current.dispatchEvent(createEvent('mouseout'));
263
expect(onHoverChange).toHaveBeenCalledTimes(2);
264
expect(onHoverChange).toHaveBeenCalledWith(false);
265
});
@@ -266,7 +298,7 @@ describe('Hover event responder', () => {
298
expect(newContainer.textContent).toEqual('not hovered');
299
300
await act(async () => {
269
- target.current.dispatchEvent(createPointerEvent('mouseover'));
301
+ target.current.dispatchEvent(createEvent('mouseover'));
302
303
// 3s should be enough to expire the updates
304
Scheduler.advanceTime(3000);
@@ -290,49 +322,57 @@ describe('Hover event responder', () => {
322
});
323
324
it('is called after "pointerout" event', () => {
293
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
294
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
325
+ ref.current.dispatchEvent(createEvent('pointerover'));
326
+ ref.current.dispatchEvent(createEvent('pointerout'));
327
expect(onHoverEnd).toHaveBeenCalledTimes(1);
328
});
329
330
it('is not called if "pointerover" pointerType is touch', () => {
299
- const event = createPointerEvent('pointerover');
331
+ const event = createEvent('pointerover');
332
event.pointerType = 'touch';
333
ref.current.dispatchEvent(event);
302
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
334
+ ref.current.dispatchEvent(createEvent('pointerout'));
335
expect(onHoverEnd).not.toBeCalled();
336
});
337
338
it('ignores browser emulated "mouseout" event', () => {
307
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
308
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
309
- ref.current.dispatchEvent(createPointerEvent('mouseout'));
339
+ ref.current.dispatchEvent(createEvent('pointerover'));
340
+ ref.current.dispatchEvent(createEvent('pointerout'));
341
+ ref.current.dispatchEvent(createEvent('mouseout'));
342
expect(onHoverEnd).toHaveBeenCalledTimes(1);
343
});
344
345
it('is called after "pointercancel" event', () => {
314
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
315
- ref.current.dispatchEvent(createPointerEvent('pointercancel'));
346
+ ref.current.dispatchEvent(createEvent('pointerover'));
347
+ ref.current.dispatchEvent(createEvent('pointercancel'));
348
expect(onHoverEnd).toHaveBeenCalledTimes(1);
349
});
350
351
it('is not called again after "pointercancel" event if it follows "pointerout"', () => {
320
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
321
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
322
- ref.current.dispatchEvent(createPointerEvent('pointercancel'));
352
+ ref.current.dispatchEvent(createEvent('pointerover'));
353
+ ref.current.dispatchEvent(createEvent('pointerout'));
354
+ ref.current.dispatchEvent(createEvent('pointercancel'));
355
expect(onHoverEnd).toHaveBeenCalledTimes(1);
356
});
357
358
// No PointerEvent fallbacks
359
it('is called after "mouseout" event', () => {
328
- ref.current.dispatchEvent(createPointerEvent('mouseover'));
329
- ref.current.dispatchEvent(createPointerEvent('mouseout'));
360
+ ref.current.dispatchEvent(createEvent('mouseover'));
361
+ ref.current.dispatchEvent(createEvent('mouseout'));
362
expect(onHoverEnd).toHaveBeenCalledTimes(1);
363
});
364
it('is not called after "touchend"', () => {
333
- ref.current.dispatchEvent(createPointerEvent('touchstart'));
334
- ref.current.dispatchEvent(createPointerEvent('touchend'));
335
- ref.current.dispatchEvent(createPointerEvent('mouseout'));
365
+ ref.current.dispatchEvent(
366
+ createTouchEvent('touchstart', 0, {
367
+ target: ref.current,
368
+ }),
369
+ );
370
+ ref.current.dispatchEvent(
371
+ createTouchEvent('touchend', 0, {
372
+ target: ref.current,
373
+ }),
374
+ );
375
+ ref.current.dispatchEvent(createEvent('mouseout'));
376
expect(onHoverEnd).not.toBeCalled();
377
});
378
@@ -345,8 +385,8 @@ describe('Hover event responder', () => {
385
);
386
ReactDOM.render(element, container);
387
348
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
349
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
388
+ ref.current.dispatchEvent(createEvent('pointerover'));
389
+ ref.current.dispatchEvent(createEvent('pointerout'));
390
jest.advanceTimersByTime(1999);
391
expect(onHoverEnd).not.toBeCalled();
392
jest.advanceTimersByTime(1);
@@ -361,8 +401,8 @@ describe('Hover event responder', () => {
401
);
402
ReactDOM.render(element, container);
403
364
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
365
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
404
+ ref.current.dispatchEvent(createEvent('pointerover'));
405
+ ref.current.dispatchEvent(createEvent('pointerout'));
406
expect(onHoverEnd).toHaveBeenCalledTimes(1);
407
});
408
@@ -374,12 +414,12 @@ describe('Hover event responder', () => {
414
);
415
ReactDOM.render(element, container);
416
377
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
378
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
417
+ ref.current.dispatchEvent(createEvent('pointerover'));
418
+ ref.current.dispatchEvent(createEvent('pointerout'));
419
jest.advanceTimersByTime(499);
380
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
420
+ ref.current.dispatchEvent(createEvent('pointerover'));
421
jest.advanceTimersByTime(100);
382
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
422
+ ref.current.dispatchEvent(createEvent('pointerout'));
423
jest.runAllTimers();
424
expect(onHoverEnd).toHaveBeenCalledTimes(1);
425
});
@@ -392,10 +432,10 @@ describe('Hover event responder', () => {
432
);
433
ReactDOM.render(element, container);
434
395
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
396
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
435
+ ref.current.dispatchEvent(createEvent('pointerover'));
436
+ ref.current.dispatchEvent(createEvent('pointerout'));
437
jest.advanceTimersByTime(499);
398
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
438
+ ref.current.dispatchEvent(createEvent('pointerover'));
439
jest.advanceTimersByTime(1);
440
expect(onHoverEnd).not.toBeCalled();
441
});
@@ -411,8 +451,8 @@ describe('Hover event responder', () => {
451
);
452
ReactDOM.render(element, container);
453
414
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
415
- ref.current.dispatchEvent(createPointerEvent('pointerout'));
454
+ ref.current.dispatchEvent(createEvent('pointerover'));
455
+ ref.current.dispatchEvent(createEvent('pointerout'));
456
jest.runAllTimers();
457
expect(onHoverEnd).not.toBeCalled();
458
});
@@ -436,12 +476,12 @@ describe('Hover event responder', () => {
476
bottom: 500,
477
right: 500,
478
});
439
- ref.current.dispatchEvent(createPointerEvent('pointerover'));
479
+ ref.current.dispatchEvent(createEvent('pointerover'));
480
ref.current.dispatchEvent(
441
- createPointerEvent('pointermove', {pointerType: 'mouse'}),
481
+ createEvent('pointermove', {pointerType: 'mouse'}),
482
);
443
- ref.current.dispatchEvent(createPointerEvent('touchmove'));
444
- ref.current.dispatchEvent(createPointerEvent('mousemove'));
483
+ ref.current.dispatchEvent(createEvent('touchmove'));
484
+ ref.current.dispatchEvent(createEvent('mousemove'));
485
expect(onHoverMove).toHaveBeenCalledTimes(1);
486
expect(onHoverMove).toHaveBeenCalledWith(
487
expect.objectContaining({type: 'hovermove'}),
@@ -476,18 +516,18 @@ describe('Hover event responder', () => {
516
517
ReactDOM.render(element, container);
518
479
- outerRef.current.dispatchEvent(createPointerEvent('pointerover'));
519
+ outerRef.current.dispatchEvent(createEvent('pointerover'));
520
outerRef.current.dispatchEvent(
481
- createPointerEvent('pointerout', {relatedTarget: innerRef.current}),
521
+ createEvent('pointerout', {relatedTarget: innerRef.current}),
522
);
483
- innerRef.current.dispatchEvent(createPointerEvent('pointerover'));
523
+ innerRef.current.dispatchEvent(createEvent('pointerover'));
524
innerRef.current.dispatchEvent(
485
- createPointerEvent('pointerout', {relatedTarget: outerRef.current}),
525
+ createEvent('pointerout', {relatedTarget: outerRef.current}),
526
);
527
outerRef.current.dispatchEvent(
488
- createPointerEvent('pointerover', {relatedTarget: innerRef.current}),
528
+ createEvent('pointerover', {relatedTarget: innerRef.current}),
529
);
490
- outerRef.current.dispatchEvent(createPointerEvent('pointerout'));
530
+ outerRef.current.dispatchEvent(createEvent('pointerout'));
531
expect(events).toEqual([
532
'outer: onHoverStart',
533
'outer: onHoverChange',
@@ -547,7 +587,7 @@ describe('Hover event responder', () => {
587
});
588
589
ref.current.dispatchEvent(
550
- createPointerEvent('pointerover', {
590
+ createEvent('pointerover', {
591
pointerType: 'mouse',
592
pageX: 15,
593
pageY: 16,
@@ -558,7 +598,7 @@ describe('Hover event responder', () => {
598
}),
599
);
600
ref.current.dispatchEvent(
561
- createPointerEvent('pointermove', {
601
+ createEvent('pointermove', {
602
pointerType: 'mouse',
603
pageX: 16,
604
pageY: 17,
@@ -569,7 +609,7 @@ describe('Hover event responder', () => {
609
}),
610
);
611
ref.current.dispatchEvent(
572
- createPointerEvent('pointerout', {
612
+ createEvent('pointerout', {
613
pointerType: 'mouse',
614
pageX: 17,
615
pageY: 18,
packages/react-events/src/__tests__/Press-test.internal.js
+319
-71
@@ -28,6 +28,17 @@ const createEvent = (type, data) => {
28
return event;
29
};
30
31
+function createTouchEvent(type, id, data) {
32
+ return createEvent(type, {
33
+ changedTouches: [
34
+ {
35
+ ...data,
36
+ identifier: id,
37
+ },
38
+ ],
39
+ });
40
+}
41
+
42
const createKeyboardEvent = (type, data) => {
43
return new KeyboardEvent(type, {
44
bubbles: true,
@@ -108,6 +119,11 @@ describe('Event responder: Press', () => {
119
ref.current.dispatchEvent(
120
createEvent('pointerdown', {pointerType: 'pen'}),
121
);
122
+ ref.current.dispatchEvent(
123
+ createTouchEvent('touchstart', 0, {
124
+ target: ref.current,
125
+ }),
126
+ );
127
expect(onPressStart).toHaveBeenCalledTimes(1);
128
expect(onPressStart).toHaveBeenCalledWith(
129
expect.objectContaining({pointerType: 'pen', type: 'pressstart'}),
@@ -156,14 +172,23 @@ describe('Event responder: Press', () => {
172
173
// No PointerEvent fallbacks
174
it('is called after "mousedown" event', () => {
159
- ref.current.dispatchEvent(createEvent('mousedown'));
175
+ ref.current.dispatchEvent(
176
+ createEvent('mousedown', {
177
+ button: 0,
178
+ }),
179
+ );
180
expect(onPressStart).toHaveBeenCalledTimes(1);
181
expect(onPressStart).toHaveBeenCalledWith(
182
expect.objectContaining({pointerType: 'mouse', type: 'pressstart'}),
183
);
184
});
185
+
186
it('is called after "touchstart" event', () => {
166
- ref.current.dispatchEvent(createEvent('touchstart'));
187
+ ref.current.dispatchEvent(
188
+ createTouchEvent('touchstart', 0, {
189
+ target: ref.current,
190
+ }),
191
+ );
192
expect(onPressStart).toHaveBeenCalledTimes(1);
193
expect(onPressStart).toHaveBeenCalledWith(
194
expect.objectContaining({pointerType: 'touch', type: 'pressstart'}),
@@ -266,7 +291,17 @@ describe('Event responder: Press', () => {
291
ref.current.dispatchEvent(
292
createEvent('pointerdown', {pointerType: 'pen'}),
293
);
269
- ref.current.dispatchEvent(createEvent('pointerup'));
294
+ ref.current.dispatchEvent(
295
+ createTouchEvent('touchstart', 0, {
296
+ target: ref.current,
297
+ }),
298
+ );
299
+ ref.current.dispatchEvent(
300
+ createTouchEvent('touchend', 0, {
301
+ target: ref.current,
302
+ }),
303
+ );
304
+ ref.current.dispatchEvent(createEvent('pointerup', {pointerType: 'pen'}));
305
expect(onPressEnd).toHaveBeenCalledTimes(1);
306
expect(onPressEnd).toHaveBeenCalledWith(
307
expect.objectContaining({pointerType: 'pen', type: 'pressend'}),
@@ -277,10 +312,20 @@ describe('Event responder: Press', () => {
312
ref.current.dispatchEvent(
313
createEvent('pointerdown', {pointerType: 'touch'}),
314
);
280
- ref.current.dispatchEvent(createEvent('touchstart'));
281
- ref.current.dispatchEvent(createEvent('pointerup'));
282
- ref.current.dispatchEvent(createEvent('touchend'));
315
+ ref.current.dispatchEvent(
316
+ createTouchEvent('touchstart', 0, {
317
+ target: ref.current,
318
+ }),
319
+ );
320
ref.current.dispatchEvent(createEvent('mousedown'));
321
+ ref.current.dispatchEvent(
322
+ createEvent('pointerup', {pointerType: 'touch'}),
323
+ );
324
+ ref.current.dispatchEvent(
325
+ createTouchEvent('touchend', 0, {
326
+ target: ref.current,
327
+ }),
328
+ );
329
ref.current.dispatchEvent(createEvent('mouseup'));
330
expect(onPressEnd).toHaveBeenCalledTimes(1);
331
expect(onPressEnd).toHaveBeenCalledWith(
@@ -337,16 +382,33 @@ describe('Event responder: Press', () => {
382
383
// No PointerEvent fallbacks
384
it('is called after "mouseup" event', () => {
340
- ref.current.dispatchEvent(createEvent('mousedown'));
341
- ref.current.dispatchEvent(createEvent('mouseup'));
385
+ ref.current.dispatchEvent(
386
+ createEvent('mousedown', {
387
+ button: 0,
388
+ }),
389
+ );
390
+ ref.current.dispatchEvent(
391
+ createEvent('mouseup', {
392
+ button: 0,
393
+ }),
394
+ );
395
expect(onPressEnd).toHaveBeenCalledTimes(1);
396
expect(onPressEnd).toHaveBeenCalledWith(
397
expect.objectContaining({pointerType: 'mouse', type: 'pressend'}),
398
);
399
});
400
it('is called after "touchend" event', () => {
348
- ref.current.dispatchEvent(createEvent('touchstart'));
349
- ref.current.dispatchEvent(createEvent('touchend'));
401
+ document.elementFromPoint = () => ref.current;
402
+ ref.current.dispatchEvent(
403
+ createTouchEvent('touchstart', 0, {
404
+ target: ref.current,
405
+ }),
406
+ );
407
+ ref.current.dispatchEvent(
408
+ createTouchEvent('touchend', 0, {
409
+ target: ref.current,
410
+ }),
411
+ );
412
expect(onPressEnd).toHaveBeenCalledTimes(1);
413
expect(onPressEnd).toHaveBeenCalledWith(
414
expect.objectContaining({pointerType: 'touch', type: 'pressend'}),
@@ -510,10 +572,18 @@ describe('Event responder: Press', () => {
572
expect(onPressChange).toHaveBeenCalledWith(false);
573
});
574
it('is called after "touchstart" and "touchend" events', () => {
513
- ref.current.dispatchEvent(createEvent('touchstart'));
575
+ ref.current.dispatchEvent(
576
+ createTouchEvent('touchstart', 0, {
577
+ target: ref.current,
578
+ }),
579
+ );
580
expect(onPressChange).toHaveBeenCalledTimes(1);
581
expect(onPressChange).toHaveBeenCalledWith(true);
516
- ref.current.dispatchEvent(createEvent('touchend'));
582
+ ref.current.dispatchEvent(
583
+ createTouchEvent('touchend', 0, {
584
+ target: ref.current,
585
+ }),
586
+ );
587
expect(onPressChange).toHaveBeenCalledTimes(2);
588
expect(onPressChange).toHaveBeenCalledWith(false);
589
});
@@ -544,8 +614,16 @@ describe('Event responder: Press', () => {
614
createEvent('pointerdown', {pointerType: 'pen'}),
615
);
616
ref.current.dispatchEvent(
547
- createEvent('pointerup', {clientX: 10, clientY: 10}),
617
+ createTouchEvent('touchstart', 0, {
618
+ target: ref.current,
619
+ }),
620
);
621
+ ref.current.dispatchEvent(
622
+ createTouchEvent('touchend', 0, {
623
+ target: ref.current,
624
+ }),
625
+ );
626
+ ref.current.dispatchEvent(createEvent('pointerup', {pointerType: 'pen'}));
627
expect(onPress).toHaveBeenCalledTimes(1);
628
expect(onPress).toHaveBeenCalledWith(
629
expect.objectContaining({pointerType: 'pen', type: 'press'}),
@@ -624,6 +702,11 @@ describe('Event responder: Press', () => {
702
ref.current.dispatchEvent(
703
createEvent('pointerdown', {pointerType: 'pen'}),
704
);
705
+ ref.current.dispatchEvent(
706
+ createTouchEvent('touchstart', 0, {
707
+ target: ref.current,
708
+ }),
709
+ );
710
jest.advanceTimersByTime(DEFAULT_LONG_PRESS_DELAY - 1);
711
expect(onLongPress).not.toBeCalled();
712
jest.advanceTimersByTime(1);
@@ -815,18 +898,18 @@ describe('Event responder: Press', () => {
898
right: 100,
899
});
900
ref.current.dispatchEvent(
818
- createEvent('pointerdown', {pointerType: 'touch'}),
901
+ createEvent('pointerdown', {pointerType: 'mouse'}),
902
);
903
ref.current.dispatchEvent(
904
createEvent('pointermove', {
822
- pointerType: 'touch',
905
+ pointerType: 'mouse',
906
clientX: 10,
907
clientY: 10,
908
}),
909
);
910
expect(onPressMove).toHaveBeenCalledTimes(1);
911
expect(onPressMove).toHaveBeenCalledWith(
829
- expect.objectContaining({pointerType: 'touch', type: 'pressmove'}),
912
+ expect.objectContaining({pointerType: 'mouse', type: 'pressmove'}),
913
);
914
});
915
@@ -876,7 +959,11 @@ describe('Event responder: Press', () => {
959
ref.current.dispatchEvent(
960
createEvent('pointerdown', {pointerType: 'touch'}),
961
);
879
- ref.current.dispatchEvent(createEvent('touchstart'));
962
+ ref.current.dispatchEvent(
963
+ createTouchEvent('touchstart', 0, {
964
+ target: ref.current,
965
+ }),
966
+ );
967
ref.current.dispatchEvent(
968
createEvent('pointermove', {
969
pointerType: 'touch',
@@ -884,7 +971,13 @@ describe('Event responder: Press', () => {
971
clientY: 10,
972
}),
973
);
887
- ref.current.dispatchEvent(createEvent('touchmove'));
974
+ ref.current.dispatchEvent(
975
+ createTouchEvent('touchmove', 0, {
976
+ target: ref.current,
977
+ clientX: 10,
978
+ clientY: 10,
979
+ }),
980
+ );
981
ref.current.dispatchEvent(createEvent('mousemove'));
982
expect(onPressMove).toHaveBeenCalledTimes(1);
983
});
@@ -1344,24 +1437,53 @@ describe('Event responder: Press', () => {
1437
pointerType: 'touch',
1438
}),
1439
);
1440
+ ref.current.dispatchEvent(
1441
+ createTouchEvent('touchstart', 0, {
1442
+ target: ref.current,
1443
+ }),
1444
+ );
1445
ref.current.dispatchEvent(
1446
createEvent('pointermove', {
1447
...coordinatesInside,
1448
pointerType: 'touch',
1449
}),
1450
);
1451
+ ref.current.dispatchEvent(
1452
+ createTouchEvent('touchmove', 0, {
1453
+ ...coordinatesInside,
1454
+ target: ref.current,
1455
+ }),
1456
+ );
1457
container.dispatchEvent(
1458
createEvent('pointermove', {
1459
...coordinatesOutside,
1460
pointerType: 'touch',
1461
}),
1462
);
1463
+ ref.current.dispatchEvent(
1464
+ createTouchEvent('touchmove', 0, {
1465
+ ...coordinatesOutside,
1466
+ target: ref.current,
1467
+ }),
1468
+ );
1469
container.dispatchEvent(
1470
createEvent('pointermove', {
1471
...coordinatesInside,
1472
pointerType: 'touch',
1473
}),
1474
);
1475
+ ref.current.dispatchEvent(
1476
+ createTouchEvent('touchmove', 0, {
1477
+ ...coordinatesInside,
1478
+ target: ref.current,
1479
+ }),
1480
+ );
1481
+ ref.current.dispatchEvent(
1482
+ createTouchEvent('touchend', 0, {
1483
+ ...coordinatesInside,
1484
+ target: ref.current,
1485
+ }),
1486
+ );
1487
container.dispatchEvent(
1488
createEvent('pointerup', {
1489
...coordinatesInside,
@@ -1397,20 +1519,12 @@ describe('Event responder: Press', () => {
1519
const pressRectOffset = 20;
1520
const getBoundingClientRectMock = () => rectMock;
1521
const coordinatesInside = {
1400
- changedTouches: [
1401
- {
1402
- clientX: rectMock.left - pressRectOffset,
1403
- clientY: rectMock.top - pressRectOffset,
1404
- },
1405
- ],
1522
+ clientX: rectMock.left - pressRectOffset,
1523
+ clientY: rectMock.top - pressRectOffset,
1524
};
1525
const coordinatesOutside = {
1408
- changedTouches: [
1409
- {
1410
- clientX: rectMock.left - pressRectOffset - 1,
1411
- clientY: rectMock.top - pressRectOffset - 1,
1412
- },
1413
- ],
1526
+ clientX: rectMock.left - pressRectOffset - 1,
1527
+ clientY: rectMock.top - pressRectOffset - 1,
1528
};
1529
1530
describe('within bounds of hit rect', () => {
@@ -1441,10 +1555,25 @@ describe('Event responder: Press', () => {
1555
1556
ReactDOM.render(element, container);
1557
1558
+ document.elementFromPoint = () => ref.current;
1559
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1445
- ref.current.dispatchEvent(createEvent('touchstart'));
1446
- ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1447
- ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1560
+ ref.current.dispatchEvent(
1561
+ createTouchEvent('touchstart', 0, {
1562
+ target: ref.current,
1563
+ }),
1564
+ );
1565
+ ref.current.dispatchEvent(
1566
+ createTouchEvent('touchmove', 0, {
1567
+ ...coordinatesInside,
1568
+ target: ref.current,
1569
+ }),
1570
+ );
1571
+ ref.current.dispatchEvent(
1572
+ createTouchEvent('touchend', 0, {
1573
+ ...coordinatesInside,
1574
+ target: ref.current,
1575
+ }),
1576
+ );
1577
jest.runAllTimers();
1578
1579
expect(events).toEqual([
@@ -1478,9 +1607,19 @@ describe('Event responder: Press', () => {
1607
1608
ReactDOM.render(element, container);
1609
1610
+ document.elementFromPoint = () => ref.current;
1611
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1482
- ref.current.dispatchEvent(createEvent('touchstart'));
1483
- ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1612
+ ref.current.dispatchEvent(
1613
+ createTouchEvent('touchstart', 0, {
1614
+ target: ref.current,
1615
+ }),
1616
+ );
1617
+ ref.current.dispatchEvent(
1618
+ createTouchEvent('touchmove', 0, {
1619
+ ...coordinatesInside,
1620
+ target: ref.current,
1621
+ }),
1622
+ );
1623
jest.advanceTimersByTime(499);
1624
expect(events).toEqual(['onPressMove']);
1625
events = [];
@@ -1489,7 +1628,12 @@ describe('Event responder: Press', () => {
1628
expect(events).toEqual(['onPressStart', 'onPressChange']);
1629
events = [];
1630
1492
- ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1631
+ ref.current.dispatchEvent(
1632
+ createTouchEvent('touchend', 0, {
1633
+ ...coordinatesInside,
1634
+ target: ref.current,
1635
+ }),
1636
+ );
1637
expect(events).toEqual(['onPressEnd', 'onPressChange', 'onPress']);
1638
});
1639
@@ -1514,15 +1658,27 @@ describe('Event responder: Press', () => {
1658
);
1659
1660
ReactDOM.render(element, container);
1661
+
1662
+ document.elementFromPoint = () => ref.current;
1663
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1518
- ref.current.dispatchEvent(createEvent('touchstart'));
1664
ref.current.dispatchEvent(
1520
- createEvent('touchmove', {
1665
+ createTouchEvent('touchstart', 0, {
1666
+ target: ref.current,
1667
+ }),
1668
+ );
1669
+ ref.current.dispatchEvent(
1670
+ createTouchEvent('touchmove', 0, {
1671
clientX: rectMock.left - pressRetentionOffset.left,
1672
clientY: rectMock.top - pressRetentionOffset.top,
1673
+ target: ref.current,
1674
+ }),
1675
+ );
1676
+ ref.current.dispatchEvent(
1677
+ createTouchEvent('touchend', 0, {
1678
+ ...coordinatesInside,
1679
+ target: ref.current,
1680
}),
1681
);
1525
- ref.current.dispatchEvent(createEvent('touchend', coordinatesInside));
1682
expect(events).toEqual([
1683
'onPressStart',
1684
'onPressChange',
@@ -1550,8 +1706,14 @@ describe('Event responder: Press', () => {
1706
);
1707
1708
ReactDOM.render(element, container);
1709
+
1710
+ document.elementFromPoint = () => ref.current;
1711
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1554
- ref.current.dispatchEvent(createEvent('touchstart'));
1712
+ ref.current.dispatchEvent(
1713
+ createTouchEvent('touchstart', 0, {
1714
+ target: ref.current,
1715
+ }),
1716
+ );
1717
// emulate smaller dimensions change on activation
1718
ref.current.getBoundingClientRect = () => ({
1719
width: 80,
@@ -1566,8 +1728,18 @@ describe('Event responder: Press', () => {
1728
clientY: rectMock.top,
1729
};
1730
// move to an area within the pre-activation region
1569
- ref.current.dispatchEvent(createEvent('touchmove', coordinates));
1570
- ref.current.dispatchEvent(createEvent('touchend', coordinates));
1731
+ ref.current.dispatchEvent(
1732
+ createTouchEvent('touchmove', 0, {
1733
+ ...coordinates,
1734
+ target: ref.current,
1735
+ }),
1736
+ );
1737
+ ref.current.dispatchEvent(
1738
+ createTouchEvent('touchend', 0, {
1739
+ ...coordinates,
1740
+ target: ref.current,
1741
+ }),
1742
+ );
1743
expect(events).toEqual(['onPressStart', 'onPressEnd', 'onPress']);
1744
});
1745
@@ -1588,8 +1760,14 @@ describe('Event responder: Press', () => {
1760
);
1761
1762
ReactDOM.render(element, container);
1763
+
1764
+ document.elementFromPoint = () => ref.current;
1765
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1592
- ref.current.dispatchEvent(createEvent('touchstart'));
1766
+ ref.current.dispatchEvent(
1767
+ createTouchEvent('touchstart', 0, {
1768
+ target: ref.current,
1769
+ }),
1770
+ );
1771
// emulate larger dimensions change on activation
1772
ref.current.getBoundingClientRect = () => ({
1773
width: 200,
@@ -1604,8 +1782,18 @@ describe('Event responder: Press', () => {
1782
clientY: rectMock.top - 50,
1783
};
1784
// move to an area within the post-activation region
1607
- ref.current.dispatchEvent(createEvent('touchmove', coordinates));
1608
- ref.current.dispatchEvent(createEvent('touchend', coordinates));
1785
+ ref.current.dispatchEvent(
1786
+ createTouchEvent('touchmove', 0, {
1787
+ ...coordinates,
1788
+ target: ref.current,
1789
+ }),
1790
+ );
1791
+ ref.current.dispatchEvent(
1792
+ createTouchEvent('touchend', 0, {
1793
+ ...coordinates,
1794
+ target: ref.current,
1795
+ }),
1796
+ );
1797
expect(events).toEqual(['onPressStart', 'onPressEnd', 'onPress']);
1798
});
1799
});
@@ -1640,11 +1828,32 @@ describe('Event responder: Press', () => {
1828
1829
ReactDOM.render(element, container);
1830
1831
+ document.elementFromPoint = () => ref.current;
1832
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1644
- ref.current.dispatchEvent(createEvent('touchstart'));
1645
- ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1646
- container.dispatchEvent(createEvent('touchmove', coordinatesOutside));
1647
- container.dispatchEvent(createEvent('touchend', coordinatesOutside));
1833
+ ref.current.dispatchEvent(
1834
+ createTouchEvent('touchstart', 0, {
1835
+ target: ref.current,
1836
+ }),
1837
+ );
1838
+ ref.current.dispatchEvent(
1839
+ createTouchEvent('touchmove', 0, {
1840
+ ...coordinatesInside,
1841
+ target: ref.current,
1842
+ }),
1843
+ );
1844
+ document.elementFromPoint = () => container;
1845
+ ref.current.dispatchEvent(
1846
+ createTouchEvent('touchmove', 0, {
1847
+ ...coordinatesOutside,
1848
+ target: ref.current,
1849
+ }),
1850
+ );
1851
+ ref.current.dispatchEvent(
1852
+ createTouchEvent('touchend', 0, {
1853
+ ...coordinatesOutside,
1854
+ target: ref.current,
1855
+ }),
1856
+ );
1857
jest.runAllTimers();
1858
1859
expect(events).toEqual([
@@ -1679,14 +1888,35 @@ describe('Event responder: Press', () => {
1888
1889
ReactDOM.render(element, container);
1890
1891
+ document.elementFromPoint = () => ref.current;
1892
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1683
- ref.current.dispatchEvent(createEvent('touchstart'));
1684
- ref.current.dispatchEvent(createEvent('touchmove', coordinatesInside));
1685
- container.dispatchEvent(createEvent('touchmove', coordinatesOutside));
1893
+ ref.current.dispatchEvent(
1894
+ createTouchEvent('touchstart', 0, {
1895
+ target: ref.current,
1896
+ }),
1897
+ );
1898
+ ref.current.dispatchEvent(
1899
+ createTouchEvent('touchmove', 0, {
1900
+ ...coordinatesInside,
1901
+ target: ref.current,
1902
+ }),
1903
+ );
1904
+ document.elementFromPoint = () => container;
1905
+ container.dispatchEvent(
1906
+ createTouchEvent('touchmove', 0, {
1907
+ ...coordinatesOutside,
1908
+ target: container,
1909
+ }),
1910
+ );
1911
jest.runAllTimers();
1912
expect(events).toEqual(['onPressMove']);
1913
events = [];
1689
- container.dispatchEvent(createEvent('touchend', coordinatesOutside));
1914
+ ref.current.dispatchEvent(
1915
+ createTouchEvent('touchend', 0, {
1916
+ ...coordinatesOutside,
1917
+ target: ref.current,
1918
+ }),
1919
+ );
1920
jest.runAllTimers();
1921
expect(events).toEqual([]);
1922
});
@@ -1712,34 +1942,36 @@ describe('Event responder: Press', () => {
1942
1943
ReactDOM.render(element, container);
1944
1945
+ document.elementFromPoint = () => ref.current;
1946
ref.current.getBoundingClientRect = getBoundingClientRectMock;
1947
ref.current.dispatchEvent(
1717
- createEvent('touchstart', {
1718
- pointerType: 'touch',
1948
+ createTouchEvent('touchstart', 0, {
1949
+ target: ref.current,
1950
}),
1951
);
1952
ref.current.dispatchEvent(
1722
- createEvent('touchmove', {
1953
+ createTouchEvent('touchmove', 0, {
1954
...coordinatesInside,
1724
- pointerType: 'touch',
1955
+ target: ref.current,
1956
}),
1957
);
1727
- container.dispatchEvent(
1728
- createEvent('touchmove', {
1958
+ document.elementFromPoint = () => container;
1959
+ ref.current.dispatchEvent(
1960
+ createTouchEvent('touchmove', 0, {
1961
...coordinatesOutside,
1730
- pointerType: 'touch',
1962
+ target: ref.current,
1963
}),
1964
);
1733
- container.dispatchEvent(
1734
- createEvent('touchmove', {
1965
+ ref.current.dispatchEvent(
1966
+ createTouchEvent('touchmove', 0, {
1967
...coordinatesInside,
1736
- pointerType: 'touch',
1968
+ target: ref.current,
1969
}),
1970
);
1739
- container.dispatchEvent(
1740
- createEvent('touchend', {
1741
- ...coordinatesInside,
1742
- pointerType: 'touch',
1971
+ document.elementFromPoint = () => ref.current;
1972
+ ref.current.dispatchEvent(
1973
+ createTouchEvent('touchend', 0, {
1974
+ target: ref.current,
1975
}),
1976
);
1977
jest.runAllTimers();
@@ -2094,8 +2326,16 @@ describe('Event responder: Press', () => {
2326
onPressEnd.mockReset();
2327
2328
// When pointer events are supported
2097
- ref.current.dispatchEvent(createEvent('pointerdown'));
2098
- ref.current.dispatchEvent(createEvent('pointercancel'));
2329
+ ref.current.dispatchEvent(
2330
+ createEvent('pointerdown', {
2331
+ pointerType: 'mouse',
2332
+ }),
2333
+ );
2334
+ ref.current.dispatchEvent(
2335
+ createEvent('pointercancel', {
2336
+ pointerType: 'mouse',
2337
+ }),
2338
+ );
2339
expect(onPressEnd).toHaveBeenCalledTimes(1);
2340
jest.runAllTimers();
2341
expect(onLongPress).not.toBeCalled();
@@ -2104,8 +2344,16 @@ describe('Event responder: Press', () => {
2344
onPressEnd.mockReset();
2345
2346
// Touch fallback
2107
- ref.current.dispatchEvent(createEvent('touchstart'));
2108
- ref.current.dispatchEvent(createEvent('touchcancel'));
2347
+ ref.current.dispatchEvent(
2348
+ createTouchEvent('touchstart', 0, {
2349
+ target: ref.current,
2350
+ }),
2351
+ );
2352
+ ref.current.dispatchEvent(
2353
+ createTouchEvent('touchcancel', 0, {
2354
+ target: ref.current,
2355
+ }),
2356
+ );
2357
expect(onPressEnd).toHaveBeenCalledTimes(1);
2358
jest.runAllTimers();
2359
expect(onLongPress).not.toBeCalled();
packages/react-events/src/__tests__/Scroll-test.internal.js
+10
-2
@@ -121,8 +121,16 @@ describe('Scroll event responder', () => {
121
});
122
123
it('with a keyboard pointerType', () => {
124
- ref.current.dispatchEvent(createEvent('keydown'));
125
- ref.current.dispatchEvent(createEvent('keyup'));
124
+ ref.current.dispatchEvent(
125
+ createEvent('keydown', {
126
+ key: 'A',
127
+ }),
128
+ );
129
+ ref.current.dispatchEvent(
130
+ createEvent('keyup', {
131
+ key: 'A',
132
+ }),
133
+ );
134
ref.current.dispatchEvent(createEvent('scroll'));
135
expect(onScroll).toHaveBeenCalledTimes(1);
136
expect(onScroll).toHaveBeenCalledWith(
packages/shared/ReactTypes.js
+12
-5
@@ -150,12 +150,22 @@ export type ReactEventTarget = {|
150
151
type AnyNativeEvent = Event | KeyboardEvent | MouseEvent | Touch;
152
153
+export type PointerType =
154
+ | ''
155
+ | 'mouse'
156
+ | 'keyboard'
157
+ | 'pen'
158
+ | 'touch'
159
+ | 'trackpad';
160
+
161
export type ReactResponderEvent = {
162
nativeEvent: AnyNativeEvent,
155
- target: Element | Document,
156
- type: string,
163
passive: boolean,
164
passiveSupported: boolean,
165
+ pointerId: null | number,
166
+ pointerType: PointerType,
167
+ target: Element | Document,
168
+ type: string,
169
};
170
171
export opaque type EventPriority = 0 | 1 | 2;
@@ -192,9 +202,6 @@ export type ReactResponderContext = {
202
getFocusableElementsInScope(): Array<HTMLElement>,
203
getActiveDocument(): Document,
204
objectAssign: Function,
195
- getEventPointerType(
196
- event: ReactResponderEvent,
197
- ): '' | 'mouse' | 'keyboard' | 'pen' | 'touch',
205
getEventCurrentTarget(event: ReactResponderEvent): Element,
206
getTimeStamp: () => number,
207
isTargetWithinHostComponent: (