Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 15 additions & 3 deletions src/components/Player.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,20 @@ export default function Player() {

return (
<>
{/* Mini bar — always visible above the tab bar while something is loaded */}
<button className="mini" onClick={() => setExpanded(true)}>
{/* Mini bar — a button-like card, but a DIV so the inner play/next
<button>s aren't invalidly nested (which broke their taps on iOS). */}
<div
className="mini"
role="button"
tabIndex={0}
onClick={() => setExpanded(true)}
onKeyDown={(e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault()
setExpanded(true)
}
}}
>
<Artwork track={p.current} size={40} />
<span className="mini__meta">
<span className="mini__title">{p.current.title}</span>
Expand All @@ -29,7 +41,7 @@ export default function Player() {
<button className="iconbtn" onClick={p.next} aria-label="Next"><NextIcon /></button>
</span>
<span className="mini__progress" style={{ width: `${(p.progress / (p.duration || 1)) * 100}%` }} />
</button>
</div>

{expanded && <NowPlaying p={p} onClose={() => setExpanded(false)} />}
</>
Expand Down
50 changes: 42 additions & 8 deletions src/lib/useLongPress.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()
},
Expand Down
Loading