diff --git a/frontend/app/element/iconbutton.tsx b/frontend/app/element/iconbutton.tsx index 85986314e..dfabc895b 100644 --- a/frontend/app/element/iconbutton.tsx +++ b/frontend/app/element/iconbutton.tsx @@ -10,7 +10,7 @@ import "./iconbutton.scss"; type IconButtonProps = { decl: IconButtonDecl; className?: string }; export const IconButton = memo( forwardRef(({ decl, className }, ref) => { - ref = ref ?? useRef(null); + const localRef = ref ?? useRef(null); const spin = decl.iconSpin ?? false; useLongClick(ref, decl.click, decl.longClick, decl.disabled); return ( diff --git a/frontend/app/hook/useLongClick.tsx b/frontend/app/hook/useLongClick.tsx index 5b71563fd..9a8a5fb80 100644 --- a/frontend/app/hook/useLongClick.tsx +++ b/frontend/app/hook/useLongClick.tsx @@ -1,40 +1,45 @@ // Copyright 2024, Command Line Inc. // SPDX-License-Identifier: Apache-2.0 -import { useCallback, useEffect, useRef, useState } from "react"; +import { useCallback, useEffect, useRef } from "react"; -export const useLongClick = (ref, onClick, onLongClick, disabled = false, ms = 300) => { - const timerRef = useRef(null); - const [longClickTriggered, setLongClickTriggered] = useState(false); +export function useLongClick( + ref: React.MutableRefObject, + onClick?: React.PointerEventHandler, + onLongClick?: React.PointerEventHandler, + disabled = false, + ms = 300 +) { + const clickStartRef = useRef(0); const startPress = useCallback( - (e: React.MouseEvent) => { - if (onLongClick == null) { - return; + (e: React.PointerEvent) => { + if (!onLongClick) { + onClick?.(e); + } else { + clickStartRef.current = Date.now(); } - setLongClickTriggered(false); - timerRef.current = setTimeout(() => { - setLongClickTriggered(true); - onLongClick?.(e); - }, ms); }, - [onLongClick, ms] + [onLongClick, onClick] ); - const stopPress = useCallback(() => { - clearTimeout(timerRef.current); - }, []); - - const handleClick = useCallback( - (e: React.MouseEvent) => { - if (longClickTriggered) { - e.preventDefault(); - e.stopPropagation(); - return; + const stopPress = useCallback( + (e: React.PointerEvent) => { + const clickStart = clickStartRef.current; + clickStartRef.current = 0; + if (clickStart !== 0) { + const now = Date.now(); + const longClickTriggered = now - clickStart > ms; + if (longClickTriggered && onLongClick) { + onLongClick?.(e); + } else { + onClick?.(e); + } + } else { + onClick?.(e); } - onClick?.(e); }, - [longClickTriggered, onClick] + [ms, onClick, onLongClick] ); useEffect(() => { @@ -42,18 +47,19 @@ export const useLongClick = (ref, onClick, onLongClick, disabled = false, ms = 3 if (!element || disabled) return; - element.addEventListener("mousedown", startPress); - element.addEventListener("mouseup", stopPress); - element.addEventListener("mouseleave", stopPress); - element.addEventListener("click", handleClick); + const startPressBound = startPress.bind(element); + const stopPressBound = stopPress.bind(element); + + element.addEventListener("pointerdown", startPressBound); + element.addEventListener("pointerup", stopPressBound); + element.addEventListener("pointerleave", stopPressBound); return () => { - element.removeEventListener("mousedown", startPress); - element.removeEventListener("mouseup", stopPress); - element.removeEventListener("mouseleave", stopPress); - element.removeEventListener("click", handleClick); + element.removeEventListener("pointerdown", startPressBound); + element.removeEventListener("pointerup", stopPressBound); + element.removeEventListener("pointerleave", stopPressBound); }; - }, [ref.current, startPress, stopPress, handleClick]); + }, [ref.current, startPress, stopPress]); return ref; -}; +}