useDrag/Drag: isDragging never resets on pointercancel, only pointerup (affects BaseBrush too)
Description
useDrag/<Drag> only expose dragStart/dragMove/dragEnd, wired to onPointerDown/onPointerMove/onPointerUp:
<g
onPointerDown={drag.dragStart}
onPointerMove={drag.dragMove}
onPointerUp={drag.dragEnd}
>There's no dragCancel/onPointerCancel anywhere in packages/visx-drag. isDragging only flips back to false inside dragEnd. Per the Pointer Events spec, pointercancel fires instead of pointerup when the browser can no longer generate events for an in-progress pointer (most commonly on touch, when the browser's own gesture recognition takes the interaction over, e.g. decides a drag is actually a page scroll). When that happens here, dragEnd never runs, so isDragging stays true and the last dx/dy stay stale until the user starts and completes a full new drag.
This affects everything built on <Drag>, including @visx/brush's BaseBrush, which renders <Drag> internally and reads the same dragStart/isDragging/dragMove/dragEnd from its render prop, so a cancelled touch drag on a brush selection leaves it in the same stuck "still dragging" state.
Same bug class as @vueuse/core's useDraggable, which had the identical gap (listened for pointerup but not pointercancel) fixed recently in vueuse/vueuse#5550.
Reproduction
- Use
useDrag(or<Drag>, orBaseBrush) on a touch device. - Start a drag (
pointerdown+ a couplepointermoves). - Interrupt it with a
pointercancelinstead of apointerupfor the same pointer (real-world: the browser's touch-scroll gesture arbitration taking over mid-drag; synthetic: dispatchPointerEvent('pointercancel', { pointerId })instead ofpointerup).
Expected: isDragging returns to false, dx/dy reset per resetOnStart/normal end-of-drag behavior.
Actual: isDragging stays true indefinitely (no onDragEnd callback fires either), until a subsequent full pointerdown→pointerup cycle happens on the same element.
Suggested fix
Add a dragCancel handler to useDrag's returned API (mirroring dragEnd but skipping the "commit" semantics, if any consumer cares about that distinction) and wire it to onPointerCancel in Drag.tsx, so isDragging always gets reset regardless of how the gesture terminates.
Source: airbnb/visx