Skip to content

Commit 937d262

Browse files
authored
React events: keyboard press, types, tests (#15314)
* Add HoverProps type * Add more Hover event module tests * Add more Press event module tests * Change default longPress delay from 1000 to 500 * Rename dispatchPressEvent -> dispatchEvent * Consolidate state updates in Press event module * Add keyboard support for Press events * Add FocusProps type and unit tests
1 parent 7a2dc48 commit 937d262

File tree

6 files changed

+584
-241
lines changed

6 files changed

+584
-241
lines changed

packages/react-events/src/Focus.js

+23-10
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,12 @@
1010
import type {EventResponderContext} from 'events/EventTypes';
1111
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
1212

13-
const targetEventTypes = [
14-
{name: 'focus', passive: true, capture: true},
15-
{name: 'blur', passive: true, capture: true},
16-
];
13+
type FocusProps = {
14+
disabled: boolean,
15+
onBlur: (e: FocusEvent) => void,
16+
onFocus: (e: FocusEvent) => void,
17+
onFocusChange: boolean => void,
18+
};
1719

1820
type FocusState = {
1921
isFocused: boolean,
@@ -27,6 +29,11 @@ type FocusEvent = {|
2729
type: FocusEventType,
2830
|};
2931

32+
const targetEventTypes = [
33+
{name: 'focus', passive: true, capture: true},
34+
{name: 'blur', passive: true, capture: true},
35+
];
36+
3037
function createFocusEvent(
3138
type: FocusEventType,
3239
target: Element | Document,
@@ -39,7 +46,10 @@ function createFocusEvent(
3946
};
4047
}
4148

42-
function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
49+
function dispatchFocusInEvents(
50+
context: EventResponderContext,
51+
props: FocusProps,
52+
) {
4353
const {event, eventTarget} = context;
4454
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
4555
return;
@@ -53,19 +63,22 @@ function dispatchFocusInEvents(context: EventResponderContext, props: Object) {
5363
context.dispatchEvent(syntheticEvent, {discrete: true});
5464
}
5565
if (props.onFocusChange) {
56-
const focusChangeEventListener = () => {
66+
const listener = () => {
5767
props.onFocusChange(true);
5868
};
5969
const syntheticEvent = createFocusEvent(
6070
'focuschange',
6171
eventTarget,
62-
focusChangeEventListener,
72+
listener,
6373
);
6474
context.dispatchEvent(syntheticEvent, {discrete: true});
6575
}
6676
}
6777

68-
function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
78+
function dispatchFocusOutEvents(
79+
context: EventResponderContext,
80+
props: FocusProps,
81+
) {
6982
const {event, eventTarget} = context;
7083
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
7184
return;
@@ -75,13 +88,13 @@ function dispatchFocusOutEvents(context: EventResponderContext, props: Object) {
7588
context.dispatchEvent(syntheticEvent, {discrete: true});
7689
}
7790
if (props.onFocusChange) {
78-
const focusChangeEventListener = () => {
91+
const listener = () => {
7992
props.onFocusChange(false);
8093
};
8194
const syntheticEvent = createFocusEvent(
8295
'focuschange',
8396
eventTarget,
84-
focusChangeEventListener,
97+
listener,
8598
);
8699
context.dispatchEvent(syntheticEvent, {discrete: true});
87100
}

packages/react-events/src/Hover.js

+41-24
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,14 @@
1010
import type {EventResponderContext} from 'events/EventTypes';
1111
import {REACT_EVENT_COMPONENT_TYPE} from 'shared/ReactSymbols';
1212

13-
const targetEventTypes = [
14-
'pointerover',
15-
'pointermove',
16-
'pointerout',
17-
'pointercancel',
18-
];
13+
type HoverProps = {
14+
disabled: boolean,
15+
delayHoverEnd: number,
16+
delayHoverStart: number,
17+
onHoverChange: boolean => void,
18+
onHoverEnd: (e: HoverEvent) => void,
19+
onHoverStart: (e: HoverEvent) => void,
20+
};
1921

2022
type HoverState = {
2123
isHovered: boolean,
@@ -31,6 +33,21 @@ type HoverEvent = {|
3133
type: HoverEventType,
3234
|};
3335

36+
// const DEFAULT_HOVER_END_DELAY_MS = 0;
37+
// const DEFAULT_HOVER_START_DELAY_MS = 0;
38+
39+
const targetEventTypes = [
40+
'pointerover',
41+
'pointermove',
42+
'pointerout',
43+
'pointercancel',
44+
];
45+
46+
// If PointerEvents is not supported (e.g., Safari), also listen to touch and mouse events.
47+
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
48+
targetEventTypes.push('touchstart', 'mouseover', 'mouseout');
49+
}
50+
3451
function createHoverEvent(
3552
type: HoverEventType,
3653
target: Element | Document,
@@ -43,16 +60,9 @@ function createHoverEvent(
4360
};
4461
}
4562

46-
// In the case we don't have PointerEvents (Safari), we listen to touch events
47-
// too
48-
if (typeof window !== 'undefined' && window.PointerEvent === undefined) {
49-
targetEventTypes.push('touchstart', 'mouseover', 'mouseout');
50-
}
51-
5263
function dispatchHoverStartEvents(
5364
context: EventResponderContext,
54-
props: Object,
55-
state: HoverState,
65+
props: HoverProps,
5666
): void {
5767
const {event, eventTarget} = context;
5868
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
@@ -67,19 +77,22 @@ function dispatchHoverStartEvents(
6777
context.dispatchEvent(syntheticEvent, {discrete: true});
6878
}
6979
if (props.onHoverChange) {
70-
const hoverChangeEventListener = () => {
80+
const listener = () => {
7181
props.onHoverChange(true);
7282
};
7383
const syntheticEvent = createHoverEvent(
7484
'hoverchange',
7585
eventTarget,
76-
hoverChangeEventListener,
86+
listener,
7787
);
7888
context.dispatchEvent(syntheticEvent, {discrete: true});
7989
}
8090
}
8191

82-
function dispatchHoverEndEvents(context: EventResponderContext, props: Object) {
92+
function dispatchHoverEndEvents(
93+
context: EventResponderContext,
94+
props: HoverProps,
95+
) {
8396
const {event, eventTarget} = context;
8497
if (context.isTargetWithinEventComponent((event: any).relatedTarget)) {
8598
return;
@@ -93,13 +106,13 @@ function dispatchHoverEndEvents(context: EventResponderContext, props: Object) {
93106
context.dispatchEvent(syntheticEvent, {discrete: true});
94107
}
95108
if (props.onHoverChange) {
96-
const hoverChangeEventListener = () => {
109+
const listener = () => {
97110
props.onHoverChange(false);
98111
};
99112
const syntheticEvent = createHoverEvent(
100113
'hoverchange',
101114
eventTarget,
102-
hoverChangeEventListener,
115+
listener,
103116
);
104117
context.dispatchEvent(syntheticEvent, {discrete: true});
105118
}
@@ -116,18 +129,22 @@ const HoverResponder = {
116129
},
117130
handleEvent(
118131
context: EventResponderContext,
119-
props: Object,
132+
props: HoverProps,
120133
state: HoverState,
121134
): void {
122135
const {eventType, eventTarget, event} = context;
123136

124137
switch (eventType) {
125-
case 'touchstart':
126-
// Touch devices don't have hover support
138+
/**
139+
* Prevent hover events when touch is being used.
140+
*/
141+
case 'touchstart': {
127142
if (!state.isTouched) {
128143
state.isTouched = true;
129144
}
130145
break;
146+
}
147+
131148
case 'pointerover':
132149
case 'mouseover': {
133150
if (
@@ -148,7 +165,7 @@ const HoverResponder = {
148165
state.isInHitSlop = true;
149166
return;
150167
}
151-
dispatchHoverStartEvents(context, props, state);
168+
dispatchHoverStartEvents(context, props);
152169
state.isHovered = true;
153170
}
154171
break;
@@ -172,7 +189,7 @@ const HoverResponder = {
172189
(event: any).y,
173190
)
174191
) {
175-
dispatchHoverStartEvents(context, props, state);
192+
dispatchHoverStartEvents(context, props);
176193
state.isHovered = true;
177194
state.isInHitSlop = false;
178195
}

0 commit comments

Comments
 (0)