@@ -12,6 +12,11 @@ import type {
12
12
ReactResponderContext ,
13
13
} from 'shared/ReactTypes' ;
14
14
import { REACT_EVENT_COMPONENT_TYPE } from 'shared/ReactSymbols' ;
15
+ import {
16
+ getEventPointerType ,
17
+ getEventCurrentTarget ,
18
+ isEventPositionWithinTouchHitTarget ,
19
+ } from './utils' ;
15
20
16
21
const CAPTURE_PHASE = 2 ;
17
22
@@ -31,11 +36,11 @@ type HoverState = {
31
36
hoverTarget : null | Element | Document ,
32
37
isActiveHovered : boolean ,
33
38
isHovered : boolean ,
34
- isInHitSlop : boolean ,
39
+ isOverTouchHitTarget : boolean ,
35
40
isTouched : boolean ,
36
41
hoverStartTimeout : null | Symbol ,
37
42
hoverEndTimeout : null | Symbol ,
38
- skipMouseAfterPointer : boolean ,
43
+ ignoreEmulatedMouseEvents : boolean ,
39
44
} ;
40
45
41
46
type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove' ;
@@ -181,9 +186,9 @@ function dispatchHoverEndEvents(
181
186
dispatchHoverChangeEvent ( context , props , state ) ;
182
187
}
183
188
184
- state . isInHitSlop = false ;
189
+ state . isOverTouchHitTarget = false ;
185
190
state . hoverTarget = null ;
186
- state . skipMouseAfterPointer = false ;
191
+ state . ignoreEmulatedMouseEvents = false ;
187
192
state . isTouched = false ;
188
193
} ;
189
194
@@ -219,17 +224,25 @@ function unmountResponder(
219
224
}
220
225
}
221
226
227
+ function isEmulatedMouseEvent ( event , state ) {
228
+ const { type} = event ;
229
+ return (
230
+ state . ignoreEmulatedMouseEvents &&
231
+ ( type === 'mousemove' || type === 'mouseover' || type === 'mouseout' )
232
+ ) ;
233
+ }
234
+
222
235
const HoverResponder = {
223
236
targetEventTypes,
224
237
createInitialState ( ) {
225
238
return {
226
239
isActiveHovered : false ,
227
240
isHovered : false ,
228
- isInHitSlop : false ,
241
+ isOverTouchHitTarget : false ,
229
242
isTouched : false ,
230
243
hoverStartTimeout : null ,
231
244
hoverEndTimeout : null ,
232
- skipMouseAfterPointer : false ,
245
+ ignoreEmulatedMouseEvents : false ,
233
246
} ;
234
247
} ,
235
248
onEvent (
@@ -238,112 +251,92 @@ const HoverResponder = {
238
251
props : HoverProps ,
239
252
state : HoverState ,
240
253
) : boolean {
241
- const { type, phase, target} = event ;
242
- const nativeEvent : any = event . nativeEvent ;
254
+ const { type} = event ;
243
255
244
256
// Hover doesn't handle capture target events at this point
245
- if ( phase === CAPTURE_PHASE ) {
257
+ if ( event . phase === CAPTURE_PHASE ) {
246
258
return false ;
247
259
}
248
- switch ( type ) {
249
- /**
250
- * Prevent hover events when touch is being used.
251
- */
252
- case 'touchstart ': {
253
- if ( ! state . isTouched ) {
254
- state . isTouched = true ;
255
- }
256
- break ;
257
- }
258
- case 'touchcancel' :
259
- case 'touchend ': {
260
- if ( state . isTouched ) {
261
- state . isTouched = false ;
262
- }
263
- break ;
264
- }
265
260
261
+ const pointerType = getEventPointerType ( event ) ;
262
+
263
+ switch ( type ) {
264
+ // START
266
265
case 'pointerover ':
267
- case 'mouseover ': {
268
- if ( ! state . isHovered && ! state . isTouched ) {
269
- if ( nativeEvent . pointerType === 'touch' ) {
266
+ case 'mouseover ':
267
+ case 'touchstart ': {
268
+ if ( ! state . isHovered ) {
269
+ // Prevent hover events for touch
270
+ if ( state . isTouched || pointerType === 'touch' ) {
270
271
state . isTouched = true ;
271
272
return false ;
272
273
}
273
- if ( type === 'pointerover' ) {
274
- state . skipMouseAfterPointer = true ;
274
+
275
+ // Prevent hover events for emulated events
276
+ if ( isEmulatedMouseEvent ( event , state ) ) {
277
+ return false ;
275
278
}
276
- if (
277
- context . isPositionWithinTouchHitTarget (
278
- target . ownerDocument ,
279
- nativeEvent . x ,
280
- nativeEvent . y ,
281
- )
282
- ) {
283
- state . isInHitSlop = true ;
279
+
280
+ if ( isEventPositionWithinTouchHitTarget ( event , context ) ) {
281
+ state . isOverTouchHitTarget = true ;
284
282
return false ;
285
283
}
286
- state . hoverTarget = target ;
284
+ state . hoverTarget = getEventCurrentTarget ( event , context ) ;
285
+ state . ignoreEmulatedMouseEvents = true ;
287
286
dispatchHoverStartEvents ( event , context , props , state ) ;
288
287
}
289
- break ;
290
- }
291
- case 'pointerout' :
292
- case 'mouseout ': {
293
- if ( state . isHovered && ! state . isTouched ) {
294
- dispatchHoverEndEvents ( event , context , props , state ) ;
295
- }
296
- break ;
288
+ return false ;
297
289
}
298
290
291
+ // MOVE
299
292
case 'pointermove' :
300
293
case 'mousemove ': {
301
- if ( type === 'mousemove' && state . skipMouseAfterPointer === true ) {
302
- return false ;
303
- }
304
-
305
- if ( state . isHovered && ! state . isTouched ) {
306
- if ( state . isInHitSlop ) {
307
- if (
308
- ! context . isPositionWithinTouchHitTarget (
309
- target . ownerDocument ,
310
- nativeEvent . x ,
311
- nativeEvent . y ,
312
- )
313
- ) {
314
- dispatchHoverStartEvents ( event , context , props , state ) ;
315
- state . isInHitSlop = false ;
316
- }
317
- } else if ( state . isHovered ) {
318
- if (
319
- context . isPositionWithinTouchHitTarget (
320
- target . ownerDocument ,
321
- nativeEvent . x ,
322
- nativeEvent . y ,
323
- )
324
- ) {
325
- dispatchHoverEndEvents ( event , context , props , state ) ;
326
- state . isInHitSlop = true ;
294
+ if ( state . isHovered && ! isEmulatedMouseEvent ( event , state ) ) {
295
+ if ( state . isHovered ) {
296
+ if ( state . isOverTouchHitTarget ) {
297
+ // If we were moving over the TouchHitTarget and have now moved
298
+ // over the Responder target
299
+ if ( ! isEventPositionWithinTouchHitTarget ( event , context ) ) {
300
+ dispatchHoverStartEvents ( event , context , props , state ) ;
301
+ state . isOverTouchHitTarget = false ;
302
+ }
327
303
} else {
328
- if ( props . onHoverMove ) {
329
- const syntheticEvent = createHoverEvent ( 'hovermove' , target ) ;
330
- context . dispatchEvent ( syntheticEvent , props . onHoverMove , {
331
- discrete : false ,
332
- } ) ;
304
+ // If we were moving over the Responder target and have now moved
305
+ // over the TouchHitTarget
306
+ if ( isEventPositionWithinTouchHitTarget ( event , context ) ) {
307
+ dispatchHoverEndEvents ( event , context , props , state ) ;
308
+ state . isOverTouchHitTarget = true ;
309
+ } else {
310
+ if ( props . onHoverMove && state . hoverTarget !== null ) {
311
+ const syntheticEvent = createHoverEvent (
312
+ 'hovermove' ,
313
+ state . hoverTarget ,
314
+ ) ;
315
+ context . dispatchEvent ( syntheticEvent , props . onHoverMove , {
316
+ discrete : false ,
317
+ } ) ;
318
+ }
333
319
}
334
320
}
335
321
}
336
322
}
337
- break ;
323
+ return false ;
338
324
}
339
325
340
- case 'pointercancel ': {
341
- if ( state . isHovered && ! state . isTouched ) {
326
+ // END
327
+ case 'pointerout ':
328
+ case 'pointercancel ':
329
+ case 'mouseout ':
330
+ case 'touchcancel ':
331
+ case 'touchend ': {
332
+ if ( state . isHovered ) {
342
333
dispatchHoverEndEvents ( event , context , props , state ) ;
343
- state . hoverTarget = null ;
334
+ state . ignoreEmulatedMouseEvents = false ;
335
+ }
336
+ if ( state . isTouched ) {
344
337
state . isTouched = false ;
345
338
}
346
- break ;
339
+ return false ;
347
340
}
348
341
}
349
342
return false ;
0 commit comments