Skip to content
Open
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
27 changes: 27 additions & 0 deletions docs-site/docs/api/components/drax-view.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,36 @@ import { DraxView } from 'react-native-drax';
| `lockDragXPosition` | `boolean` | `false` | Lock horizontal drag movement |
| `lockDragYPosition` | `boolean` | `false` | Lock vertical drag movement |
| `dragActivationFailOffset` | `number` | — | Cancel drag if finger moves more than this (px) during long press |
| `dragActivationOffsetX` | `number \| [number, number]` | — | Minimum horizontal travel (px) before the drag activates (`activeOffsetX`) |
| `dragActivationOffsetY` | `number \| [number, number]` | — | Vertical counterpart of `dragActivationOffsetX` (`activeOffsetY`) |
| `dragActivationFailOffsetX` | `number \| [number, number]` | — | Per-axis `dragActivationFailOffset`; takes precedence on its own axis |
| `dragActivationFailOffsetY` | `number \| [number, number]` | — | See `dragActivationFailOffsetX` |
| `capacity` | `number` | — | Max items this view can receive (auto-rejects when full) |
| `rejectOwnChildren` | `boolean` | `false` | Don't receive drags from own children |

### Activating a drag by direction

`longPressDelay` activates the pan after its delay **regardless of movement**, so
a stationary press starts a drag. That is a problem when the draggable view also
hosts a long-press context menu, or sits in a scrollable list: any delay short
enough to feel responsive cancels the menu, and any delay long enough to avoid it
loses the drag.

When every meaningful drop is on one axis, the offsets can separate the gestures
instead of a timer:

```jsx
<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.

## Drag Callbacks

| Callback | Type | Description |
Expand Down
12 changes: 11 additions & 1 deletion src/DraxView.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,10 @@ const DRAX_PROP_KEYS: ReadonlySet<string> = new Set([
'otherDraggingWithoutReceiverStyle',
'dragHandle',
'dragActivationFailOffset',
'dragActivationOffsetX',
'dragActivationOffsetY',
'dragActivationFailOffsetX',
'dragActivationFailOffsetY',
'collisionAlgorithm',
'scrollHorizontal',
]);
Expand Down Expand Up @@ -355,7 +359,13 @@ export const DraxView = memo((props: DraxViewProps): ReactNode => {
lockDragYPosition,
dragBoundsSV,
props.dragActivationFailOffset,
scrollHorizontal
scrollHorizontal,
{
offsetX: props.dragActivationOffsetX,
offsetY: props.dragActivationOffsetY,
failOffsetX: props.dragActivationFailOffsetX,
failOffsetY: props.dragActivationFailOffsetY,
}
);

// ── Animated styles ────────────────────────────────────────────────
Expand Down
5 changes: 5 additions & 0 deletions src/compat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ export interface DraxPanGestureConfig {
* Prevents accidental drags when the user is trying to scroll. */
failOffsetX?: number | [number, number];
failOffsetY?: number | [number, number];
/** Minimum travel on an axis before the gesture activates. Lets a drag be
* told apart from a stationary press or a scroll by direction rather than
* by a long-press delay. */
activeOffsetX?: number | [number, number];
activeOffsetY?: number | [number, number];
onActivate: (event: DraxPanEvent) => void;
onUpdate: (event: DraxPanEvent) => void;
onDeactivate: (event: DraxPanEvent) => void;
Expand Down
4 changes: 4 additions & 0 deletions src/compat/useDraxPanGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ function useDraxPanGestureV3(config: DraxPanGestureConfig): DraxPanGesture {
};
if (config.failOffsetX !== undefined) panConfig.failOffsetX = config.failOffsetX;
if (config.failOffsetY !== undefined) panConfig.failOffsetY = config.failOffsetY;
if (config.activeOffsetX !== undefined) panConfig.activeOffsetX = config.activeOffsetX;
if (config.activeOffsetY !== undefined) panConfig.activeOffsetY = config.activeOffsetY;
return rngh.usePanGesture(panConfig);
}

Expand Down Expand Up @@ -78,6 +80,8 @@ function useDraxPanGestureV2(config: DraxPanGestureConfig): DraxPanGesture {
.shouldCancelWhenOutside(config.shouldCancelWhenOutside);
if (config.failOffsetX !== undefined) g = g.failOffsetX(config.failOffsetX);
if (config.failOffsetY !== undefined) g = g.failOffsetY(config.failOffsetY);
if (config.activeOffsetX !== undefined) g = g.activeOffsetX(config.activeOffsetX);
if (config.activeOffsetY !== undefined) g = g.activeOffsetY(config.activeOffsetY);
return g
.onStart((event: DraxPanEvent) => {
'worklet';
Expand Down
21 changes: 18 additions & 3 deletions src/hooks/useDragGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,15 @@ export const useDragGesture = (
lockDragYPosition?: boolean,
dragBoundsSV?: SharedValue<{ x: number; y: number; width: number; height: number } | null>,
dragActivationFailOffset?: number,
scrollHorizontal?: boolean
scrollHorizontal?: boolean,
/** Per-axis activation, for telling a drag apart by direction rather than
* by time. See `dragActivationOffsetX` in types.ts. */
dragActivationOffsets?: {
offsetX?: number | [number, number];
offsetY?: number | [number, number];
failOffsetX?: number | [number, number];
failOffsetY?: number | [number, number];
}
) => {
const {
draggedIdSV,
Expand Down Expand Up @@ -58,14 +66,21 @@ export const useDragGesture = (
? [-dragActivationFailOffset, dragActivationFailOffset] as [number, number]
: undefined;

// Per-axis values win over the symmetric shorthand on their own axis, so a
// caller can fail the drag on one axis while activating it on the other.
const failOffsetX = dragActivationOffsets?.failOffsetX ?? failOffset;
const failOffsetY = dragActivationOffsets?.failOffsetY ?? failOffset;

const gesture = useDraxPanGesture({
enabledSV,
longPressDelaySV,
maxPointers: 1,
shouldCancelWhenOutside: false,
touchAction,
failOffsetX: failOffset,
failOffsetY: failOffset,
activeOffsetX: dragActivationOffsets?.offsetX,
activeOffsetY: dragActivationOffsets?.offsetY,
failOffsetX,
failOffsetY,
onActivate: (event) => {
'worklet';

Expand Down
18 changes: 18 additions & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -318,6 +318,24 @@ export interface DraxViewProps
* Prevents accidental drags when the user is trying to scroll.
* Can be a number (symmetric) or [min, max] tuple per axis. */
dragActivationFailOffset?: number;
/** Minimum horizontal travel (px) before the drag activates. Maps to
* gesture-handler's `activeOffsetX`.
*
* Use this to tell a drag apart by *direction* rather than by time. With
* `longPressDelay: 0` and this set, a stationary press never starts a
* drag — so a long-press context menu underneath the view still opens —
* and pairing it with `dragActivationFailOffsetY` leaves vertical
* scrolling to the list the view sits in.
*
* Can be a number (symmetric) or [min, max] tuple. */
dragActivationOffsetX?: number | [number, number];
/** Vertical counterpart of `dragActivationOffsetX` (`activeOffsetY`). */
dragActivationOffsetY?: number | [number, number];
/** Per-axis `dragActivationFailOffset`. Takes precedence over it on its
* own axis, so one axis can fail the drag while the other activates it. */
dragActivationFailOffsetX?: number | [number, number];
/** See `dragActivationFailOffsetX`. */
dragActivationFailOffsetY?: number | [number, number];

/** Hint that this view is inside a horizontal scroll container.
* On mobile web, sets `touch-action: pan-x` so the browser allows
Expand Down