@@ -54,11 +54,11 @@ const initialState: InteractiveState = {
5454
5555// event listeners set by RI
5656const eventMap : Record < string , string > = {
57- mouseenter : 'onMouseEnter ' ,
57+ mouseover : 'onMouseOver ' ,
5858 mouseleave : 'onMouseLeave' ,
5959 mousedown : 'onMouseDown' ,
6060 mouseup : 'onMouseUp' ,
61- pointerenter : 'onPointerEnter ' ,
61+ pointerover : 'onPointerOver ' ,
6262 pointerleave : 'onPointerLeave' ,
6363 pointerdown : 'onPointerDown' ,
6464 pointerup : 'onPointerUp' ,
@@ -367,7 +367,7 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
367367 ////////////////////////////////////
368368
369369 // handleEvent handles all events that change the interactive state of the component
370- // for example <As onMouseEnter ={handleEvent} onPointerEnter ={handleEvent} etc...>
370+ // for example <As onMouseOver ={handleEvent} onPointerOver ={handleEvent} etc...>
371371
372372 // always set all pointer/mouse/touch event listeners
373373 // instead of just pointer event listeners (when supported by the browser)
@@ -376,7 +376,7 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
376376 // - 1. the pointer events implementation is buggy on some devices
377377 // so pointer events on its own is not a good option
378378 // for example, on iPadOS pointer events from mouse will cease to fire for the entire page
379- // after using mouse and touch input at the same time on the same element (note that mouse events are unaffected)
379+ // after using mouse and touch input at the same time (note that mouse events are unaffected)
380380 // - 2. the pointercancel event is useful for syncing the touchActive state with browser generated click events
381381 // as it fires as soon as the browser uses the touch interaction for another purpose (e.g. scrolling)
382382 // and this can't be replicated with touch events (touchcancel behaves differently)
@@ -388,6 +388,13 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
388388 // and bail on updating state in setIState if the state hasn't changed to prevent unnecessary renders
389389 // also note that setting listeners for events not supported by the browser has no effect
390390
391+ // note: use onMouseOver and onPointerOver instead of onMouseEnter and onPointerEnter because
392+ // of a react bug where enter events are not dispatched on an element when the element above it is removed,
393+ // this also effects when navigating around a single page app where the user clicks on a link
394+ // and the element that's rendered on the new page under the mouse doesn't receive enter events
395+ // see: https://github.com/facebook/react/issues/13956
396+ // note that since stateChange is idempotent the extra mouse/pointer over events will have no effect
397+
391398 // useCallback so event handlers passed to <As> are referentially equivalent between renders
392399 const handleEvent = React . useCallback (
393400 (
@@ -411,14 +418,14 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
411418 // default switch on eventFrom(e)
412419 // eventFrom mouse
413420 // switch on e.type
414- // mouse/pointer enter -> hover: enter true
421+ // mouse/pointer over -> hover: enter true
415422 // mouse/pointer leave, pointercancel -> hover: enter false, active: exit mouseActive
416423 // mouse/pointer down -> active: enter mouseActive
417424 // mouse/pointer up -> active: exit mouseActive
418425 // eventFrom touch
419426 // switch on e.type
420427 // touchstart/pointerdown -> active: enter touchActive
421- // touchend/pointerup/touchcancel/pointercancel/mouseenter -> active: exit touchActive
428+ // touchend/pointerup/touchcancel/pointercancel/mouseover -> active: exit touchActive
422429 // mouseleave -> hover: enter false, active: exit mouseActive
423430 switch ( e . type ) {
424431 case 'focus' :
@@ -487,8 +494,8 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
487494 switch ( eventFrom ( e ) ) {
488495 case 'mouse' :
489496 switch ( e . type ) {
490- case 'mouseenter ' :
491- case 'pointerenter ' :
497+ case 'mouseover ' :
498+ case 'pointerover ' :
492499 stateChange ( {
493500 iStateKey : 'hover' ,
494501 state : true ,
@@ -545,11 +552,11 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
545552 case 'touchcancel' :
546553 case 'pointerup' :
547554 case 'pointercancel' :
548- // exit touchActive on mouseenter eventFrom touch
555+ // exit touchActive on mouseover eventFrom touch
549556 // because on android, mouse events (and focus and context menu) fire ~500ms after touch start
550557 // once they fire, click will never be fired, so exit touchActive
551558 // eslint-disable-next-line no-fallthrough
552- case 'mouseenter ' :
559+ case 'mouseover ' :
553560 // if useExtendedTouchActive then only exit touchActive on touchend and touchcancel events
554561 // which won't fire until the touch point is removed from the screen
555562 if (
@@ -570,7 +577,7 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
570577 // but RI is now stuck in the hover state
571578 // so RI needs to exit the hover state on a mouseleave eventFrom touch,
572579 // note that if no scrolling is involved this is not too bad because
573- // when the mouse is used again it will be over the RI element (and will fire mouseenter event),
580+ // when the mouse is used again it will be over the RI element (and will fire mouseover event),
574581 // but if the user scrolled with the touch interaction then RI will be stuck in the hover state
575582 // until the user re-hovers and exits the RI element with their mouse, which is bad,
576583 // also note that on touch only devices this stateChange call will have no effect because RI is never in the hover or mouseActive states
@@ -600,7 +607,7 @@ const InteractiveNotMemoized: PolymorphicForwardRefExoticComponent<
600607 }
601608 } ,
602609 // handleEvent is dependent on event handler props that are also in eventMap
603- // for example, restProps.onMouseEnter , restProps.onTouchStart, etc
610+ // for example, restProps.onMouseOver , restProps.onTouchStart, etc
604611 // this generates an array of event handler props that are also in eventMap
605612 // handleEvent is also dependent on stateChange, but this will always be referentially equivalent
606613 // eslint-disable-next-line react-hooks/exhaustive-deps
0 commit comments