Created
July 27, 2022 14:12
-
-
Save sladg/a89058899dbd5de3529106711862e815 to your computer and use it in GitHub Desktop.
Div wrapper with improved functions for mobile and desktop
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 React, { PropsWithChildren, useEffect, useRef, CSSProperties } from 'react' | |
import { logger } from 'utils/logger' | |
export type CustomMouseEvent = React.MouseEvent<HTMLDivElement, MouseEvent> | |
export type CustomTouchEvent = React.TouchEvent<HTMLDivElement> | |
const MOUSE_MOVE_DELTA = 20 | |
const TOUCH_MOVE_DELTA = 30 | |
const HOLD_TRESHOLD = 500 | |
const DUPLICATE_TRESHOLD = 80 | |
const DOUBLE_CLICK_MAX_DIFF = 250 | |
export interface AmazingDivProps { | |
onTap?: (key: string, e: CustomTouchEvent) => void | |
onClick?: (key: string, e: CustomMouseEvent) => void | |
onHold?: (key: string, e: CustomTouchEvent | CustomMouseEvent) => void | |
onDoubleClick?: (key: string, e: CustomMouseEvent) => void | |
} | |
interface Props { | |
onTap?: (e: CustomTouchEvent) => void | |
onClick?: (e: CustomMouseEvent) => void | |
onDoubleClick?: (e: CustomMouseEvent) => void | |
onHold?: (e: CustomTouchEvent | CustomMouseEvent) => void | |
style?: CSSProperties | |
id: string | |
} | |
interface CoordinateType { | |
x: number | |
y: number | |
} | |
const AmazingDiv = ({ children, onClick, onHold, onTap, onDoubleClick, id, style = {} }: PropsWithChildren<Props>) => { | |
const longPressInterval = useRef<NodeJS.Timeout>() | |
const event = useRef<CustomMouseEvent | CustomTouchEvent>() | |
const startTime = useRef<number>() | |
const lastEventTime = useRef<number>() | |
const lastClickTime = useRef<number>() | |
const startCoordinates = useRef<CoordinateType>(null) | |
const startLongPressChecker = () => { | |
longPressInterval.current = setInterval(() => { | |
logger.debug('[longPress]') | |
if (startTime.current && isLongPress()) { | |
logger.debug('[longPress] - activated!') | |
onHold && onHold(event.current) | |
handleClean() | |
} | |
}, 50) | |
} | |
const handleInit = (x: number, y: number, e: CustomMouseEvent | CustomTouchEvent) => { | |
e.persist() | |
startCoordinates.current = { x, y } | |
startTime.current = Date.now() | |
event.current = e | |
startLongPressChecker() | |
} | |
const handleClean = () => { | |
longPressInterval.current && clearInterval(longPressInterval.current) | |
startCoordinates.current = null | |
startTime.current = null | |
event.current = null | |
lastClickTime.current = Date.now() | |
} | |
const isDuplicatedEvent = (e: CustomMouseEvent | CustomTouchEvent) => { | |
const prevTime = lastEventTime.current | |
const nowTime = e.timeStamp | |
return nowTime - prevTime < DUPLICATE_TRESHOLD | |
} | |
const isLongPress = () => { | |
const currentTime = Date.now() | |
const diff = currentTime - startTime.current | |
return diff > HOLD_TRESHOLD | |
} | |
const isSwipe = (e: CustomTouchEvent) => { | |
if (!startCoordinates.current) return false | |
if (!e.touches[0]) return false | |
const xDiff = Math.abs(startCoordinates.current.x - e.touches[0].clientX) | |
const yDiff = Math.abs(startCoordinates.current.y - e.touches[0].clientY) | |
return xDiff > TOUCH_MOVE_DELTA || yDiff > TOUCH_MOVE_DELTA | |
} | |
const isDrag = (e: CustomMouseEvent) => { | |
if (!startCoordinates.current) return false | |
const xDiff = Math.abs(startCoordinates.current.x - e.clientX) | |
const yDiff = Math.abs(startCoordinates.current.y - e.clientY) | |
return xDiff > MOUSE_MOVE_DELTA || yDiff > MOUSE_MOVE_DELTA | |
} | |
const isDoubleClick = (e: CustomMouseEvent) => { | |
logger.debug('[isDoubleClick]', lastClickTime.current) | |
if (!lastClickTime) return false | |
const currentTime = Date.now() | |
const diff = currentTime - lastClickTime.current | |
return diff < DOUBLE_CLICK_MAX_DIFF | |
} | |
const handleTouchStart = (e: CustomTouchEvent) => { | |
logger.debug('[touchStart]') | |
if (isDuplicatedEvent(e)) { | |
handleClean() | |
} else { | |
handleInit(e.touches[0]?.clientX, e.touches[0]?.clientY, e) | |
} | |
} | |
const handleTouchEnd = (e: CustomTouchEvent) => { | |
logger.debug('[touchEnd]') | |
if (isLongPress() || !startTime.current) return | |
onTap && onTap(event.current as CustomTouchEvent) | |
handleClean() | |
lastEventTime.current = e.timeStamp | |
} | |
const handleTouchCancel = (e: CustomTouchEvent) => { | |
logger.debug('[touchCancel]') | |
handleClean() | |
} | |
const handleTouchMove = (e: CustomTouchEvent) => { | |
logger.debug('[touchMove]') | |
if (isSwipe(e)) { | |
handleClean() | |
} | |
} | |
const handleMouseDown = (e: CustomMouseEvent) => { | |
logger.debug('[mouseDown]', onDoubleClick) | |
if (isDoubleClick(e)) { | |
onDoubleClick && onDoubleClick(e) | |
} else if (isDuplicatedEvent(e)) { | |
handleClean() | |
} else { | |
handleInit(e.clientX, e.clientY, e) | |
} | |
} | |
const handleMouseEnd = (e: CustomMouseEvent) => { | |
logger.debug('[mouseEnd]') | |
if (isLongPress() || !startTime.current) return | |
onClick && onClick(event.current as CustomMouseEvent) | |
handleClean() | |
} | |
const handleMouseLeave = (e: CustomMouseEvent) => { | |
logger.debug('[mouseLeave]') | |
handleClean() | |
} | |
const handleMouseMove = (e: CustomMouseEvent) => { | |
if (isDrag(e)) { | |
logger.debug('[mouseMove] - cleaning!') | |
handleClean() | |
} | |
} | |
// Ensures that interval does not hang. | |
useEffect(() => { | |
!event.current && longPressInterval.current && clearInterval(longPressInterval.current) | |
}, [event.current]) | |
return ( | |
<div | |
onTouchStart={handleTouchStart} | |
onTouchCancel={handleTouchCancel} | |
onTouchEnd={handleTouchEnd} | |
onTouchMove={handleTouchMove} | |
onMouseDown={handleMouseDown} | |
onMouseUp={handleMouseEnd} | |
onMouseLeave={handleMouseLeave} | |
onMouseMove={handleMouseMove} | |
style={style} | |
id={id} | |
> | |
{children} | |
</div> | |
) | |
} | |
export default React.memo(AmazingDiv) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment