feat(reader): swipe left/right to turn pages in single mode

Wire horizontal swipe into the mobile tap-zone overlay (single mode only,
so continuous mode keeps native vertical scroll). A leftward swipe past the
threshold turns to the next page, rightward to the previous; a mostly-
vertical drag is ignored so panning a tall page never turns it. The gesture
suppresses the synthesized zone tap so it doesn't double-fire. Swipe
classification is a pure, unit-tested helper; e2e drives real touch pointer
events for horizontal (both directions) and vertical cases.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
MechaCat02
2026-07-04 21:23:34 +02:00
parent c212deb7b0
commit 3f1a5e9c41
8 changed files with 148 additions and 7 deletions

23
frontend/src/lib/swipe.ts Normal file
View File

@@ -0,0 +1,23 @@
// Pure gesture helper for the reader's single-page swipe navigation. Kept
// UI-free so the "is this a page-turn swipe, and which way" decision is
// unit-testable without dispatching pointer events.
export const SWIPE_THRESHOLD_PX = 45;
/**
* Classifies a pointer displacement as a page-turn swipe. Returns `'next'`
* for a leftward swipe, `'prev'` for a rightward one, or `null` when the
* gesture is too short or too vertical to be a deliberate horizontal swipe
* (so vertical panning never turns the page).
*/
export function swipeDirection(
dx: number,
dy: number,
threshold: number = SWIPE_THRESHOLD_PX
): 'next' | 'prev' | null {
if (Math.abs(dx) < threshold) return null;
// Must be predominantly horizontal — a diagonal or vertical drag is not
// a page turn.
if (Math.abs(dx) <= Math.abs(dy)) return null;
return dx < 0 ? 'next' : 'prev';
}