Skip to content

Instantly share code, notes, and snippets.

@nandorojo
Created February 2, 2023 16:17
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save nandorojo/c70912bf293e8a84f8e5f296227b4af0 to your computer and use it in GitHub Desktop.
Save nandorojo/c70912bf293e8a84f8e5f296227b4af0 to your computer and use it in GitHub Desktop.
Hoverable
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