@@ -12,6 +12,11 @@ import type {
1212 ReactResponderContext ,
1313} from 'shared/ReactTypes' ;
1414import { REACT_EVENT_COMPONENT_TYPE } from 'shared/ReactSymbols' ;
15+ import {
16+ getEventPointerType ,
17+ getEventCurrentTarget ,
18+ isEventPositionWithinTouchHitTarget ,
19+ } from './utils' ;
1520
1621const CAPTURE_PHASE = 2 ;
1722
@@ -31,11 +36,11 @@ type HoverState = {
3136 hoverTarget : null | Element | Document ,
3237 isActiveHovered : boolean ,
3338 isHovered : boolean ,
34- isInHitSlop : boolean ,
39+ isOverTouchHitTarget : boolean ,
3540 isTouched : boolean ,
3641 hoverStartTimeout : null | Symbol ,
3742 hoverEndTimeout : null | Symbol ,
38- skipMouseAfterPointer : boolean ,
43+ ignoreEmulatedMouseEvents : boolean ,
3944} ;
4045
4146type HoverEventType = 'hoverstart' | 'hoverend' | 'hoverchange' | 'hovermove' ;
@@ -181,9 +186,9 @@ function dispatchHoverEndEvents(
181186 dispatchHoverChangeEvent ( context , props , state ) ;
182187 }
183188
184- state . isInHitSlop = false ;
189+ state . isOverTouchHitTarget = false ;
185190 state . hoverTarget = null ;
186- state . skipMouseAfterPointer = false ;
191+ state . ignoreEmulatedMouseEvents = false ;
187192 state . isTouched = false ;
188193 } ;
189194
@@ -219,17 +224,25 @@ function unmountResponder(
219224 }
220225}
221226
227+ function isEmulatedMouseEvent ( event , state ) {
228+ const { type} = event ;
229+ return (
230+ state . ignoreEmulatedMouseEvents &&
231+ ( type === 'mousemove' || type === 'mouseover' || type === 'mouseout' )
232+ ) ;
233+ }
234+
222235const HoverResponder = {
223236 targetEventTypes,
224237 createInitialState ( ) {
225238 return {
226239 isActiveHovered : false ,
227240 isHovered : false ,
228- isInHitSlop : false ,
241+ isOverTouchHitTarget : false ,
229242 isTouched : false ,
230243 hoverStartTimeout : null ,
231244 hoverEndTimeout : null ,
232- skipMouseAfterPointer : false ,
245+ ignoreEmulatedMouseEvents : false ,
233246 } ;
234247 } ,
235248 onEvent (
@@ -238,112 +251,92 @@ const HoverResponder = {
238251 props : HoverProps ,
239252 state : HoverState ,
240253 ) : boolean {
241- const { type, phase, target} = event ;
242- const nativeEvent : any = event . nativeEvent ;
254+ const { type} = event ;
243255
244256 // Hover doesn't handle capture target events at this point
245- if ( phase === CAPTURE_PHASE ) {
257+ if ( event . phase === CAPTURE_PHASE ) {
246258 return false ;
247259 }
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- }
265260
261+ const pointerType = getEventPointerType ( event ) ;
262+
263+ switch ( type ) {
264+ // START
266265 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' ) {
270271 state . isTouched = true ;
271272 return false ;
272273 }
273- if ( type === 'pointerover' ) {
274- state . skipMouseAfterPointer = true ;
274+
275+ // Prevent hover events for emulated events
276+ if ( isEmulatedMouseEvent ( event , state ) ) {
277+ return false ;
275278 }
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 ;
284282 return false ;
285283 }
286- state . hoverTarget = target ;
284+ state . hoverTarget = getEventCurrentTarget ( event , context ) ;
285+ state . ignoreEmulatedMouseEvents = true ;
287286 dispatchHoverStartEvents ( event , context , props , state ) ;
288287 }
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 ;
297289 }
298290
291+ // MOVE
299292 case 'pointermove' :
300293 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+ }
327303 } 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+ }
333319 }
334320 }
335321 }
336322 }
337- break ;
323+ return false ;
338324 }
339325
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 ) {
342333 dispatchHoverEndEvents ( event , context , props , state ) ;
343- state . hoverTarget = null ;
334+ state . ignoreEmulatedMouseEvents = false ;
335+ }
336+ if ( state . isTouched ) {
344337 state . isTouched = false ;
345338 }
346- break ;
339+ return false ;
347340 }
348341 }
349342 return false ;
0 commit comments