diff --git a/src/components/Player.jsx b/src/components/Player.jsx index ca4e9cc..c3adc54 100644 --- a/src/components/Player.jsx +++ b/src/components/Player.jsx @@ -13,8 +13,20 @@ export default function Player() { return ( <> - {/* Mini bar — always visible above the tab bar while something is loaded */} - - + {expanded && setExpanded(false)} />} diff --git a/src/lib/useLongPress.js b/src/lib/useLongPress.js index fd9e715..fa4bd4f 100644 --- a/src/lib/useLongPress.js +++ b/src/lib/useLongPress.js @@ -6,26 +6,60 @@ import { useRef } from 'react' export function useLongPress(onLongPress, delay = 450) { const timer = useRef(null) const fired = useRef(false) + const startPos = useRef(null) + const touchedAt = useRef(0) + const MOVE_TOLERANCE = 10 // px of finger jitter allowed before we treat it as a scroll - const start = () => { + const clear = () => { + if (timer.current) { + clearTimeout(timer.current) + timer.current = null + } + } + + const begin = (e, isTouch) => { + // Ignore the synthetic mouse events iOS fires right after a touch, so a + // touch long-press isn't followed by a phantom mouse "tap" that resets + // `fired` and lets the tap action slip through. + if (!isTouch && Date.now() - touchedAt.current < 600) return + if (timer.current) return // already holding fired.current = false + const pt = e.touches?.[0] + startPos.current = pt ? { x: pt.clientX, y: pt.clientY } : null timer.current = setTimeout(() => { + timer.current = null fired.current = true onLongPress() }, delay) } - const cancel = () => clearTimeout(timer.current) + + // Cancel only on real movement — a held finger always jitters a few px, and + // the old zero-tolerance onTouchMove:cancel made long-press fire only ~half + // the time. + const move = (e) => { + if (!timer.current || !startPos.current) return + const pt = e.touches?.[0] + if (!pt) return + const dx = pt.clientX - startPos.current.x + const dy = pt.clientY - startPos.current.y + if (dx * dx + dy * dy > MOVE_TOLERANCE * MOVE_TOLERANCE) clear() + } return { handlers: { - onTouchStart: start, - onTouchEnd: cancel, - onTouchMove: cancel, - onMouseDown: start, - onMouseUp: cancel, - onMouseLeave: cancel, + onTouchStart: (e) => begin(e, true), + onTouchEnd: () => { + clear() + touchedAt.current = Date.now() + }, + onTouchMove: move, + onTouchCancel: clear, // a system-interrupted touch must not leave the timer armed + onMouseDown: (e) => begin(e, false), + onMouseUp: clear, + onMouseLeave: clear, onContextMenu: (e) => { e.preventDefault() + clear() fired.current = true onLongPress() },