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
11 changes: 11 additions & 0 deletions src/SortableBoardContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -365,6 +365,17 @@ export const SortableBoardContainer = <TItem,>({
}
}

// A cancelled drag-end arriving after the success path above has already
// run (source info cleared, transfer still pending for finalizeTransfer)
// is stale — reinjecting would revert the completed transfer. Return the
// phantom snap target again so the in-flight snap stays aimed at the
// target column instead of resolving to the default origin target.
if (cancelled && transfer?.targetId && !sourceInfoRef.current) {
draxViewProps?.onMonitorDragEnd?.(eventData);
const staleTarget = columns.get(transfer.targetId);
return staleTarget ? staleTarget.getPhantomSnapTarget() : undefined;
}

if (transfer) {
// Cancelled or no target — clear phantom and reinject
if (transfer.targetId) {
Expand Down
16 changes: 16 additions & 0 deletions src/compat/finalizeCanceled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { DraxPanEvent } from './types';

/**
* Whether a finalized pan gesture was cancelled, across gesture-handler
* versions. Released v3 reports it as `event.canceled` (PR #3887); v2 and
* v3 betas predating that change pass a legacy `success` second parameter
* instead. Reading only the parameter makes every normal end look cancelled
* on released v3.
*/
export function isFinalizeCanceled(
event: DraxPanEvent,
didSucceed?: boolean
): boolean {
'worklet';
return event.canceled ?? didSucceed === false;
}
1 change: 1 addition & 0 deletions src/compat/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
export { useDraxPanGesture } from './useDraxPanGesture';
export { isFinalizeCanceled } from './finalizeCanceled';
export type {
DraxPanEvent,
DraxPanGesture,
Expand Down
8 changes: 7 additions & 1 deletion src/compat/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,10 @@ export interface DraxPanEvent {
y: number;
absoluteX: number;
absoluteY: number;
/** Released v3 (gesture-handler PR #3887) moved the end flag into the
* event, inverted, replacing the legacy `success` second parameter.
* Absent on v2 and on v3 betas before the change. */
canceled?: boolean;
}

/** Config for the version-agnostic pan gesture hook. */
Expand All @@ -31,5 +35,7 @@ export interface DraxPanGestureConfig {
onActivate: (event: DraxPanEvent) => void;
onUpdate: (event: DraxPanEvent) => void;
onDeactivate: (event: DraxPanEvent) => void;
onFinalize: (event: DraxPanEvent, didSucceed: boolean) => void;
/** `didSucceed` is only passed by v2 and by v3 betas predating
* gesture-handler PR #3887; released v3 sends `event.canceled` instead. */
onFinalize: (event: DraxPanEvent, didSucceed?: boolean) => void;
}
6 changes: 3 additions & 3 deletions src/hooks/useDragGesture.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { Platform } from 'react-native';
import type { SharedValue } from 'react-native-reanimated';
import { runOnJS } from 'react-native-worklets';

import { useDraxPanGesture } from '../compat';
import { isFinalizeCanceled, useDraxPanGesture } from '../compat';
import { computeAbsolutePositionWorklet, hitTestWorklet } from '../math';
import type { Position } from '../types';
import { useDraxContext } from './useDraxContext';
Expand Down Expand Up @@ -239,13 +239,13 @@ export const useDragGesture = (
// Bounce to JS for end callbacks + snap animation
runOnJS(handleDragEnd)(currentDraggedId, currentReceiverId, false, finalHitResult.monitorIds);
},
onFinalize: (_event, didSucceed) => {
onFinalize: (event, didSucceed) => {
'worklet';

// If gesture was cancelled (not ended normally).
// Check draggedIdSV (set in onActivate) instead of dragPhaseSV
// because phase is now set later in handleDragStart via runOnUI.
if (!didSucceed && draggedIdSV.value !== '') {
if (isFinalizeCanceled(event, didSucceed) && draggedIdSV.value !== '') {
const currentDraggedId = draggedIdSV.value;
const currentReceiverId = receiverIdSV.value;

Expand Down