Skip to content

feat(DraxView): add per-axis drag activation offsets - #277

Open
cvburgess wants to merge 1 commit into
nuclearpasta:mainfrom
cvburgess:feat/per-axis-drag-activation
Open

feat(DraxView): add per-axis drag activation offsets#277
cvburgess wants to merge 1 commit into
nuclearpasta:mainfrom
cvburgess:feat/per-axis-drag-activation

Conversation

@cvburgess

Copy link
Copy Markdown

Adds four optional props — dragActivationOffsetX / dragActivationOffsetY (mapping to gesture-handler's activeOffsetX / activeOffsetY) and per-axis dragActivationFailOffsetX / dragActivationFailOffsetY — so a drag can be told apart by direction rather than by a long-press delay.

Happy to move this to an issue for discussion first if you'd prefer; CONTRIBUTING asks for that on non-trivial changes and I wasn't sure which side of that line this falls on.

The problem

longPressDelay maps to activateAfterLongPress, which activates the pan after its delay regardless of movement — so a stationary press starts a drag.

That's fine in isolation, but it conflicts when the draggable view also hosts a long-press context menu. On iOS that's a UIContextMenuInteraction at ~500ms; when the pan activates first it cancels the UIKit touches and the menu never opens. There is no value that satisfies both:

  • below ~500ms → the drag wins and the menu becomes unreachable
  • at or above ~500ms → the menu wins and the drag never activates

We hit this shipping drag-to-reschedule in a planner app, where the same card is both a drag source and the host of a context menu carrying most of its actions. It presented as an intermittent menu, which took a while to pin down: the menu only appeared when the drag happened to fail first via dragActivationFailOffset.

dragActivationFailOffset can't express the alternative, because it applies one value to both failOffsetX and failOffsetY. Separating the gestures needs "activate on horizontal, fail on vertical" — which requires per-axis control.

The fix

When every meaningful drop is on one axis, the offsets separate the three gestures cleanly, with no timer involved:

<DraxView
  longPressDelay={0}                    // a stationary press never starts a drag
  dragActivationOffsetX={[-15, 15]}     // only a sideways pull picks the view up
  dragActivationFailOffsetY={[-15, 15]} // vertical travel returns the gesture to the list
  payload={item}
/>

A press that doesn't move leaves the context menu to open, a vertical drag scrolls the list, and a horizontal drag starts the drag. It also removes a footgun in the existing API: with longPressDelay: 0, a symmetric dragActivationFailOffset is the only rule the pan has left, so setting one prevents the drag from ever activating.

Compatibility

Fully additive:

  • every new prop is optional
  • dragActivationFailOffset keeps its exact current meaning as the symmetric shorthand; the per-axis values take precedence only on their own axis, and only when set
  • no behavior changes for any view that doesn't set the new props

Changes

  • src/types.ts — the four new DraxViewProps
  • src/DraxView.tsx — added to DRAX_PROP_KEYS (so they don't reach the underlying Reanimated.View) and forwarded to useDragGesture
  • src/hooks/useDragGesture.ts — one new optional trailing argument; per-axis values resolve over the symmetric shorthand
  • src/compat/types.ts, src/compat/useDraxPanGesture.tsactiveOffsetX / activeOffsetY applied on both the RNGH v2 builder and v3 usePanGesture paths
  • docs-site/docs/api/components/drax-view.mdx — props table rows plus a short "Activating a drag by direction" section

I left CHANGELOG.md alone since release-it looks like it's part of your release flow — glad to add an entry if you'd rather it came with the PR.

Verification

yarn typecheck, yarn lint, yarn test and yarn prepare all pass, as do the lefthook pre-commit hooks.

Beyond that it's been running in a production React Native app (iOS/iPad, Android, and web) via patch-package, on RNGH 2.32 and Reanimated 4.5, across roughly a dozen simultaneous drop targets in a scrollable container. Verified by hand that the context menu and the drag now coexist on iPad, which is what motivated the change.

I don't have a repro in example/ — let me know if you'd like one added and I'll put it together.

Adds dragActivationOffsetX/Y (activeOffsetX/Y) and the per-axis
dragActivationFailOffsetX/Y, so a drag can be told apart by direction
rather than by a long-press delay.

longPressDelay maps to activateAfterLongPress, which activates the pan
after its delay regardless of movement — a stationary press starts a
drag. That is a problem when the draggable view also hosts a long-press
context menu: any delay short enough to feel responsive cancels the menu,
and any delay long enough to avoid it loses the drag. There is no value
that satisfies both.

dragActivationFailOffset can't express the alternative, because it
applies the same value to failOffsetX and failOffsetY. Separating the
gestures needs 'activate on horizontal, fail on vertical', which takes
per-axis control:

  longPressDelay={0}
  dragActivationOffsetX={[-15, 15]}
  dragActivationFailOffsetY={[-15, 15]}

A press that doesn't move leaves the context menu to open, a vertical
drag scrolls the list, and a horizontal drag starts the drag.

Fully additive: every new prop is optional, and dragActivationFailOffset
keeps its existing meaning as the symmetric shorthand — the per-axis
values only take precedence on their own axis when set. Applied to both
the RNGH v2 builder and v3 usePanGesture paths.
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

@cvburgess is attempting to deploy a commit to the Nuclear Pasta Team on Vercel.

A member of the Team first needs to authorize it.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant