Skip to content

Commit 3438e5c

Browse files
authored
Experimental Event API: Add Hover onUnmount support (#15394)
1 parent 805e7f8 commit 3438e5c

File tree

2 files changed

+59
-14
lines changed

2 files changed

+59
-14
lines changed

packages/react-events/src/Hover.js

Lines changed: 59 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ type HoverProps = {
2424
};
2525

2626
type HoverState = {
27+
hoverTarget: null | Element | Document,
2728
isActiveHovered: boolean,
2829
isHovered: boolean,
2930
isInHitSlop: boolean,
@@ -66,7 +67,6 @@ function createHoverEvent(
6667
}
6768

6869
function dispatchHoverChangeEvent(
69-
event: ReactResponderEvent,
7070
context: ReactResponderContext,
7171
props: HoverProps,
7272
state: HoverState,
@@ -75,7 +75,10 @@ function dispatchHoverChangeEvent(
7575
const listener = () => {
7676
props.onHoverChange(bool);
7777
};
78-
const syntheticEvent = createHoverEvent('hoverchange', event.target);
78+
const syntheticEvent = createHoverEvent(
79+
'hoverchange',
80+
((state.hoverTarget: any): Element | Document),
81+
);
7982
context.dispatchEvent(syntheticEvent, listener, {discrete: true});
8083
}
8184

@@ -85,9 +88,14 @@ function dispatchHoverStartEvents(
8588
props: HoverProps,
8689
state: HoverState,
8790
): void {
88-
const {nativeEvent, target} = event;
89-
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
90-
return;
91+
const target = state.hoverTarget;
92+
if (event !== null) {
93+
const {nativeEvent} = event;
94+
if (
95+
context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)
96+
) {
97+
return;
98+
}
9199
}
92100

93101
state.isHovered = true;
@@ -101,13 +109,16 @@ function dispatchHoverStartEvents(
101109
state.isActiveHovered = true;
102110

103111
if (props.onHoverStart) {
104-
const syntheticEvent = createHoverEvent('hoverstart', target);
112+
const syntheticEvent = createHoverEvent(
113+
'hoverstart',
114+
((target: any): Element | Document),
115+
);
105116
context.dispatchEvent(syntheticEvent, props.onHoverStart, {
106117
discrete: true,
107118
});
108119
}
109120
if (props.onHoverChange) {
110-
dispatchHoverChangeEvent(event, context, props, state);
121+
dispatchHoverChangeEvent(context, props, state);
111122
}
112123
};
113124

@@ -129,14 +140,19 @@ function dispatchHoverStartEvents(
129140
}
130141

131142
function dispatchHoverEndEvents(
132-
event: ReactResponderEvent,
143+
event: null | ReactResponderEvent,
133144
context: ReactResponderContext,
134145
props: HoverProps,
135146
state: HoverState,
136147
) {
137-
const {nativeEvent, target} = event;
138-
if (context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)) {
139-
return;
148+
const target = state.hoverTarget;
149+
if (event !== null) {
150+
const {nativeEvent} = event;
151+
if (
152+
context.isTargetWithinEventComponent((nativeEvent: any).relatedTarget)
153+
) {
154+
return;
155+
}
140156
}
141157

142158
state.isHovered = false;
@@ -150,11 +166,14 @@ function dispatchHoverEndEvents(
150166
state.isActiveHovered = false;
151167

152168
if (props.onHoverEnd) {
153-
const syntheticEvent = createHoverEvent('hoverend', target);
169+
const syntheticEvent = createHoverEvent(
170+
'hoverend',
171+
((target: any): Element | Document),
172+
);
154173
context.dispatchEvent(syntheticEvent, props.onHoverEnd, {discrete: true});
155174
}
156175
if (props.onHoverChange) {
157-
dispatchHoverChangeEvent(event, context, props, state);
176+
dispatchHoverChangeEvent(context, props, state);
158177
}
159178
};
160179

@@ -179,6 +198,16 @@ function calculateDelayMS(delay: ?number, min = 0, fallback = 0) {
179198
return Math.max(min, maybeNumber != null ? maybeNumber : fallback);
180199
}
181200

201+
function unmountResponder(
202+
context: ReactResponderContext,
203+
props: HoverProps,
204+
state: HoverState,
205+
): void {
206+
if (state.isHovered) {
207+
dispatchHoverEndEvents(null, context, props, state);
208+
}
209+
}
210+
182211
const HoverResponder = {
183212
targetEventTypes,
184213
createInitialState() {
@@ -231,6 +260,7 @@ const HoverResponder = {
231260
state.isInHitSlop = true;
232261
return;
233262
}
263+
state.hoverTarget = target;
234264
dispatchHoverStartEvents(event, context, props, state);
235265
}
236266
break;
@@ -241,6 +271,7 @@ const HoverResponder = {
241271
dispatchHoverEndEvents(event, context, props, state);
242272
}
243273
state.isInHitSlop = false;
274+
state.hoverTarget = null;
244275
state.isTouched = false;
245276
state.skipMouseAfterPointer = false;
246277
break;
@@ -293,12 +324,27 @@ const HoverResponder = {
293324
case 'pointercancel': {
294325
if (state.isHovered && !state.isTouched) {
295326
dispatchHoverEndEvents(event, context, props, state);
327+
state.hoverTarget = null;
296328
state.isTouched = false;
297329
}
298330
break;
299331
}
300332
}
301333
},
334+
onUnmount(
335+
context: ReactResponderContext,
336+
props: HoverProps,
337+
state: HoverState,
338+
) {
339+
unmountResponder(context, props, state);
340+
},
341+
onOwnershipChange(
342+
context: ReactResponderContext,
343+
props: HoverProps,
344+
state: HoverState,
345+
) {
346+
unmountResponder(context, props, state);
347+
},
302348
};
303349

304350
export default {

packages/react-events/src/Press.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -699,7 +699,6 @@ const PressResponder = {
699699
) {
700700
unmountResponder(context, props, state);
701701
},
702-
// TODO This method doesn't work as of yet
703702
onOwnershipChange(
704703
context: ReactResponderContext,
705704
props: PressProps,

0 commit comments

Comments
 (0)