Swiping across the board to scroll horizontally was intermittent when the gesture started on a task card. Cards render with native HTML5 `draggable` for desktop drag-to-move, but native DnD does not function via touch — the attribute only arms the browser's touch-drag heuristic, which non- deterministically hijacks horizontal swipes meant to pan the board. Disable native drag on touch-primary devices (`(hover: none) and (pointer: coarse)`) via a new `useCoarsePointer` hook, folded into the card's `isDraggable`. Mouse/desktop and hybrid laptops keep drag-to-move; touch loses nothing (DnD never worked there) and panning is now reliable. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
33 lines
1.3 KiB
TypeScript
33 lines
1.3 KiB
TypeScript
import { useEffect, useState } from "react";
|
|
|
|
// Touch-primary devices (phones/tablets, Android WebViews). A hybrid laptop
|
|
// with a trackpad/mouse reports `(hover: hover)` and is intentionally excluded
|
|
// so mouse drag-to-move keeps working there.
|
|
export const COARSE_POINTER_MEDIA_QUERY = "(hover: none) and (pointer: coarse)";
|
|
|
|
export function isCoarsePointer(): boolean {
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return false;
|
|
return window.matchMedia(COARSE_POINTER_MEDIA_QUERY).matches;
|
|
}
|
|
|
|
// Whether the primary pointer is coarse (touch). Native HTML5 drag-and-drop is
|
|
// non-functional via touch, yet a `draggable` element still arms the browser's
|
|
// touch-drag heuristic and can hijack a horizontal swipe meant to scroll the
|
|
// board. Components use this to drop `draggable` on touch so panning is reliable.
|
|
export function useCoarsePointer(): boolean {
|
|
const [coarse, setCoarse] = useState<boolean>(isCoarsePointer);
|
|
|
|
useEffect(() => {
|
|
if (typeof window === "undefined" || typeof window.matchMedia !== "function") return;
|
|
|
|
const query = window.matchMedia(COARSE_POINTER_MEDIA_QUERY);
|
|
const update = () => setCoarse(query.matches);
|
|
|
|
update();
|
|
query.addEventListener("change", update);
|
|
return () => query.removeEventListener("change", update);
|
|
}, []);
|
|
|
|
return coarse;
|
|
}
|