Created
February 2, 2023 16:17
-
-
Save nandorojo/c70912bf293e8a84f8e5f296227b4af0 to your computer and use it in GitHub Desktop.
Hoverable
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import * as React from 'react'; | |
import { View, StyleSheet } from 'react-native'; | |
let isEnabled = false; | |
// the following logic comes from the creator of react-native-web | |
// https://gist.github.com/necolas/1c494e44e23eb7f8c5864a2fac66299a | |
// it's also used by MotiPressable's hover interactions | |
// https://github.com/nandorojo/moti/blob/master/packages/interactions/src/pressable/hoverable.tsx | |
if (typeof window != 'undefined') { | |
/** | |
* Web browsers emulate mouse events (and hover states) after touch events. | |
* This code infers when the currently-in-use modality supports hover | |
* (including for multi-modality devices) and considers "hover" to be enabled | |
* if a mouse movement occurs more than 1 second after the last touch event. | |
* This threshold is long enough to account for longer delays between the | |
* browser firing touch and mouse events on low-powered devices. | |
*/ | |
const HOVER_THRESHOLD_MS = 1000; | |
let lastTouchTimestamp = 0; | |
function enableHover() { | |
if (isEnabled || Date.now() - lastTouchTimestamp < HOVER_THRESHOLD_MS) { | |
return; | |
} | |
isEnabled = true; | |
} | |
function disableHover() { | |
lastTouchTimestamp = Date.now(); | |
if (isEnabled) { | |
isEnabled = false; | |
} | |
} | |
document.addEventListener('touchstart', disableHover, true); | |
document.addEventListener('touchmove', disableHover, true); | |
document.addEventListener('mousemove', enableHover, true); | |
} | |
function isHoverEnabled(): boolean { | |
return isEnabled; | |
} | |
export const HoverTrapElement = () => { | |
const localRef = useRef(null) | |
const onMouseEnter = React.useCallback( | |
({ x }) => { | |
// Gesture.Begin() | |
if (isHoverEnabled()) { | |
Gesture.Start() | |
} else { | |
Gesture.Cancel() | |
} | |
}, | |
[] | |
); | |
const onMouseMove = React.useCallback( | |
({ x, y }) => { | |
if (isHoverEnabled()) { | |
Gesture.Update() | |
} else { | |
Gesture.Cancel() | |
} | |
}, | |
[] | |
); | |
const onMouseLeave = React.useCallback(() => { | |
Gesture.End() | |
}, [currentIndex, isActive]); | |
useEffect( | |
function disableHoverOnClickOutside() { | |
// https://gist.github.com/necolas/1c494e44e23eb7f8c5864a2fac66299a#gistcomment-3629646 | |
const listener = (event: MouseEvent) => { | |
if ( | |
localRef.current && | |
event.target instanceof HTMLElement && | |
!localRef.current.contains(event.target) | |
) { | |
Gesture.Cancel() | |
} | |
} | |
document.addEventListener('mousedown', listener) | |
return () => { | |
document.removeEventListener('mousedown', listener) | |
} | |
}, | |
[isHovered] | |
) | |
return ( | |
<div | |
ref={localRef} | |
onMouseEnter={React.useCallback( | |
(e) => { | |
let rect = e.currentTarget.getBoundingClientRect(); | |
let x = e.clientX - rect.left; // x position within the element. | |
let y = e.clientY - rect.top | |
onMouseEnter({ x, y }); | |
}, | |
[onMouseMove] | |
)} | |
onMouseMove={React.useCallback( | |
(e) => { | |
let rect = e.currentTarget.getBoundingClientRect(); | |
let x = e.clientX - rect.left; // x position within the element. | |
let y = e.clientY - rect.top | |
onMouseMove({ x, y }); | |
}, | |
[onMouseMove] | |
)} | |
onMouseLeave={onMouseLeave} | |
> | |
{children} | |
</div> | |
); | |
}; |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment