react-native-drax 0.11.0-alpha.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +390 -227
- package/lib/module/DebugOverlay.js +121 -0
- package/lib/module/DebugOverlay.js.map +1 -0
- package/lib/module/Drax.js +36 -0
- package/lib/module/Drax.js.map +1 -0
- package/lib/module/DraxContext.js +6 -0
- package/lib/module/DraxContext.js.map +1 -0
- package/lib/module/DraxHandle.js +47 -0
- package/lib/module/DraxHandle.js.map +1 -0
- package/lib/module/DraxHandleContext.js +11 -0
- package/lib/module/DraxHandleContext.js.map +1 -0
- package/lib/module/DraxList.js +108 -0
- package/lib/module/DraxList.js.map +1 -0
- package/lib/module/DraxProvider.js +203 -0
- package/lib/module/DraxProvider.js.map +1 -0
- package/lib/module/DraxScrollView.js +167 -0
- package/lib/module/DraxScrollView.js.map +1 -0
- package/lib/module/DraxSubprovider.js +21 -0
- package/lib/module/DraxSubprovider.js.map +1 -0
- package/lib/module/DraxView.js +348 -0
- package/lib/module/DraxView.js.map +1 -0
- package/lib/module/HoverLayer.js +152 -0
- package/lib/module/HoverLayer.js.map +1 -0
- package/lib/module/SortableBoardContainer.js +386 -0
- package/lib/module/SortableBoardContainer.js.map +1 -0
- package/lib/module/SortableBoardContext.js +6 -0
- package/lib/module/SortableBoardContext.js.map +1 -0
- package/lib/module/SortableContainer.js +571 -0
- package/lib/module/SortableContainer.js.map +1 -0
- package/lib/module/SortableItem.js +226 -0
- package/lib/module/SortableItem.js.map +1 -0
- package/lib/module/SortableItemContext.js +38 -0
- package/lib/module/SortableItemContext.js.map +1 -0
- package/lib/module/compat/detectVersion.js +19 -0
- package/lib/module/compat/detectVersion.js.map +1 -0
- package/lib/module/compat/index.js +5 -0
- package/lib/module/compat/index.js.map +1 -0
- package/lib/module/compat/types.js +4 -0
- package/lib/module/compat/types.js.map +1 -0
- package/lib/module/compat/useDraxPanGesture.js +94 -0
- package/lib/module/compat/useDraxPanGesture.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useCallbackDispatch.js +688 -0
- package/lib/module/hooks/useCallbackDispatch.js.map +1 -0
- package/lib/module/hooks/useDragGesture.js +240 -0
- package/lib/module/hooks/useDragGesture.js.map +1 -0
- package/lib/module/hooks/useDraxContext.js +12 -0
- package/lib/module/hooks/useDraxContext.js.map +1 -0
- package/lib/module/hooks/useDraxId.js +13 -0
- package/lib/module/hooks/useDraxId.js.map +1 -0
- package/lib/module/hooks/useDraxMethods.js +73 -0
- package/lib/module/hooks/useDraxMethods.js.map +1 -0
- package/lib/module/hooks/useDraxScrollHandler.js +97 -0
- package/lib/module/hooks/useDraxScrollHandler.js.map +1 -0
- package/lib/module/hooks/useSortableBoard.js +37 -0
- package/lib/module/hooks/useSortableBoard.js.map +1 -0
- package/lib/module/hooks/useSortableList.js +988 -0
- package/lib/module/hooks/useSortableList.js.map +1 -0
- package/lib/module/hooks/useSpatialIndex.js +283 -0
- package/lib/module/hooks/useSpatialIndex.js.map +1 -0
- package/lib/module/hooks/useViewStyles.js +158 -0
- package/lib/module/hooks/useViewStyles.js.map +1 -0
- package/lib/module/hooks/useWebScrollFreeze.js +52 -0
- package/lib/module/hooks/useWebScrollFreeze.js.map +1 -0
- package/lib/module/index.js +37 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/math.js +294 -0
- package/lib/module/math.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/params.js +88 -0
- package/lib/module/params.js.map +1 -0
- package/lib/module/types.js +215 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/DebugOverlay.d.ts +17 -0
- package/lib/typescript/src/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Drax.d.ts +28 -0
- package/lib/typescript/src/Drax.d.ts.map +1 -0
- package/lib/typescript/src/DraxContext.d.ts +3 -0
- package/lib/typescript/src/DraxContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandle.d.ts +25 -0
- package/lib/typescript/src/DraxHandle.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandleContext.d.ts +12 -0
- package/lib/typescript/src/DraxHandleContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxList.d.ts +66 -0
- package/lib/typescript/src/DraxList.d.ts.map +1 -0
- package/lib/typescript/src/DraxProvider.d.ts +4 -0
- package/lib/typescript/src/DraxProvider.d.ts.map +1 -0
- package/lib/typescript/src/DraxScrollView.d.ts +7 -0
- package/lib/typescript/src/DraxScrollView.d.ts.map +1 -0
- package/lib/typescript/src/DraxSubprovider.d.ts +4 -0
- package/lib/typescript/src/DraxSubprovider.d.ts.map +1 -0
- package/lib/typescript/src/DraxView.d.ts +4 -0
- package/lib/typescript/src/DraxView.d.ts.map +1 -0
- package/lib/typescript/src/HoverLayer.d.ts +38 -0
- package/lib/typescript/src/HoverLayer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts +11 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContext.d.ts +4 -0
- package/lib/typescript/src/SortableBoardContext.d.ts.map +1 -0
- package/lib/typescript/src/SortableContainer.d.ts +13 -0
- package/lib/typescript/src/SortableContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableItem.d.ts +14 -0
- package/lib/typescript/src/SortableItem.d.ts.map +1 -0
- package/lib/typescript/src/SortableItemContext.d.ts +37 -0
- package/lib/typescript/src/SortableItemContext.d.ts.map +1 -0
- package/lib/typescript/src/compat/detectVersion.d.ts +2 -0
- package/lib/typescript/src/compat/detectVersion.d.ts.map +1 -0
- package/lib/typescript/src/compat/index.d.ts +4 -0
- package/lib/typescript/src/compat/index.d.ts.map +1 -0
- package/lib/typescript/src/compat/types.d.ts +33 -0
- package/lib/typescript/src/compat/types.d.ts.map +1 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts +8 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts +40 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts +17 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts +2 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts.map +1 -0
- package/{build → lib/typescript/src}/hooks/useDraxId.d.ts +1 -0
- package/lib/typescript/src/hooks/useDraxId.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts +13 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts +27 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts +10 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts +11 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts +22 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts +183 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts +14 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/math.d.ts +76 -0
- package/lib/typescript/src/math.d.ts.map +1 -0
- package/{build → lib/typescript/src}/params.d.ts +13 -9
- package/lib/typescript/src/params.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +756 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +164 -34
- package/src/DebugOverlay.tsx +140 -0
- package/src/Drax.ts +33 -0
- package/src/DraxContext.ts +8 -0
- package/src/DraxHandle.tsx +52 -0
- package/src/DraxHandleContext.ts +15 -0
- package/src/DraxList.tsx +181 -0
- package/src/DraxProvider.tsx +224 -0
- package/src/DraxScrollView.tsx +180 -0
- package/src/DraxSubprovider.tsx +22 -0
- package/src/DraxView.tsx +430 -0
- package/src/HoverLayer.tsx +167 -0
- package/src/SortableBoardContainer.tsx +439 -0
- package/src/SortableBoardContext.ts +6 -0
- package/src/SortableContainer.tsx +650 -0
- package/src/SortableItem.tsx +264 -0
- package/src/SortableItemContext.ts +46 -0
- package/src/compat/detectVersion.ts +17 -0
- package/src/compat/index.ts +7 -0
- package/src/compat/types.ts +35 -0
- package/src/compat/useDraxPanGesture.ts +112 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCallbackDispatch.tsx +830 -0
- package/src/hooks/useDragGesture.ts +273 -0
- package/src/hooks/useDraxContext.ts +11 -0
- package/src/hooks/useDraxId.ts +11 -0
- package/src/hooks/useDraxMethods.ts +71 -0
- package/src/hooks/useDraxScrollHandler.ts +121 -0
- package/src/hooks/useSortableBoard.ts +44 -0
- package/src/hooks/useSortableList.ts +1063 -0
- package/src/hooks/useSpatialIndex.ts +336 -0
- package/src/hooks/useViewStyles.ts +180 -0
- package/src/hooks/useWebScrollFreeze.ts +60 -0
- package/src/index.ts +111 -0
- package/src/math.ts +333 -0
- package/src/params.ts +74 -0
- package/src/types.ts +933 -0
- package/.editorconfig +0 -15
- package/.eslintrc.js +0 -4
- package/.prettierrc +0 -16
- package/CHANGELOG.md +0 -270
- package/CODE-OF-CONDUCT.md +0 -85
- package/CONTRIBUTING.md +0 -15
- package/FUNDING.yml +0 -4
- package/build/AllHoverViews.d.ts +0 -0
- package/build/AllHoverViews.js +0 -30
- package/build/DraxContext.d.ts +0 -2
- package/build/DraxContext.js +0 -6
- package/build/DraxList.d.ts +0 -8
- package/build/DraxList.js +0 -512
- package/build/DraxListItem.d.ts +0 -7
- package/build/DraxListItem.js +0 -121
- package/build/DraxProvider.d.ts +0 -2
- package/build/DraxProvider.js +0 -704
- package/build/DraxScrollView.d.ts +0 -6
- package/build/DraxScrollView.js +0 -136
- package/build/DraxSubprovider.d.ts +0 -3
- package/build/DraxSubprovider.js +0 -18
- package/build/DraxView.d.ts +0 -8
- package/build/DraxView.js +0 -93
- package/build/HoverView.d.ts +0 -8
- package/build/HoverView.js +0 -40
- package/build/PanGestureDetector.d.ts +0 -3
- package/build/PanGestureDetector.js +0 -49
- package/build/hooks/index.d.ts +0 -4
- package/build/hooks/index.js +0 -11
- package/build/hooks/useContent.d.ts +0 -23
- package/build/hooks/useContent.js +0 -212
- package/build/hooks/useDraxContext.d.ts +0 -1
- package/build/hooks/useDraxContext.js +0 -13
- package/build/hooks/useDraxId.js +0 -13
- package/build/hooks/useDraxProtocol.d.ts +0 -5
- package/build/hooks/useDraxProtocol.js +0 -32
- package/build/hooks/useDraxRegistry.d.ts +0 -78
- package/build/hooks/useDraxRegistry.js +0 -714
- package/build/hooks/useDraxScrollHandler.d.ts +0 -25
- package/build/hooks/useDraxScrollHandler.js +0 -89
- package/build/hooks/useDraxState.d.ts +0 -10
- package/build/hooks/useDraxState.js +0 -132
- package/build/hooks/useMeasurements.d.ts +0 -9
- package/build/hooks/useMeasurements.js +0 -119
- package/build/hooks/useStatus.d.ts +0 -11
- package/build/hooks/useStatus.js +0 -96
- package/build/index.d.ts +0 -9
- package/build/index.js +0 -33
- package/build/math.d.ts +0 -22
- package/build/math.js +0 -68
- package/build/params.js +0 -27
- package/build/transform.d.ts +0 -11
- package/build/transform.js +0 -59
- package/build/types.d.ts +0 -807
- package/build/types.js +0 -46
- package/docs/concept.md +0 -79
- package/docs/images/color-drag-drop.gif +0 -0
- package/docs/images/deck-cards.gif +0 -0
- package/docs/images/drag-drop-events.jpg +0 -0
- package/docs/images/knight-moves.gif +0 -0
- package/docs/images/reorderable-list.gif +0 -0
|
@@ -0,0 +1,650 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { useEffect, useLayoutEffect, useRef } from 'react';
|
|
3
|
+
import { Platform, StyleSheet } from 'react-native';
|
|
4
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
5
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
6
|
+
import Reanimated, { useAnimatedStyle, withTiming } from 'react-native-reanimated';
|
|
7
|
+
import { runOnJS, runOnUI } from 'react-native-worklets';
|
|
8
|
+
import { DraxView } from './DraxView';
|
|
9
|
+
import { useDraxContext } from './hooks/useDraxContext';
|
|
10
|
+
import { useWebScrollFreeze } from './hooks/useWebScrollFreeze';
|
|
11
|
+
import { defaultAutoScrollIntervalLength, ITEM_SHIFT_ANIMATION_DURATION } from './params';
|
|
12
|
+
import { useSortableBoardContext } from './SortableBoardContext';
|
|
13
|
+
import type {
|
|
14
|
+
DropIndicatorProps,
|
|
15
|
+
Position,
|
|
16
|
+
DraxDragEventData,
|
|
17
|
+
DraxMonitorDragDropEventData,
|
|
18
|
+
DraxMonitorEndEventData,
|
|
19
|
+
DraxMonitorEventData,
|
|
20
|
+
DraxProtocolDragEndResponse,
|
|
21
|
+
DraxViewProps,
|
|
22
|
+
SortableListHandle,
|
|
23
|
+
} from './types';
|
|
24
|
+
import {
|
|
25
|
+
AutoScrollDirection,
|
|
26
|
+
isSortableItemPayload,
|
|
27
|
+
isWithCancelledFlag,
|
|
28
|
+
} from './types';
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Touch sensor jitter threshold in pixels.
|
|
32
|
+
* Computes actual finger displacement from drag start and ignores
|
|
33
|
+
* reorder when the finger hasn't meaningfully moved.
|
|
34
|
+
*/
|
|
35
|
+
const FINGER_JITTER_THRESHOLD = 5;
|
|
36
|
+
|
|
37
|
+
function computeFingerDisplacement(eventData: DraxDragEventData): number {
|
|
38
|
+
const { grabOffset, measurements } = eventData.dragged;
|
|
39
|
+
if (!measurements) return Infinity;
|
|
40
|
+
const dx =
|
|
41
|
+
grabOffset.x - measurements.width / 2 + eventData.dragTranslation.x;
|
|
42
|
+
const dy =
|
|
43
|
+
grabOffset.y - measurements.height / 2 + eventData.dragTranslation.y;
|
|
44
|
+
return Math.abs(dx) + Math.abs(dy);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export interface SortableContainerProps {
|
|
48
|
+
sortable: SortableListHandle<any>;
|
|
49
|
+
scrollRef: RefObject<any>;
|
|
50
|
+
style?: StyleProp<ViewStyle>;
|
|
51
|
+
children: ReactNode;
|
|
52
|
+
draxViewProps?: Partial<DraxViewProps>;
|
|
53
|
+
renderDropIndicator?: (props: DropIndicatorProps) => ReactNode;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export const SortableContainer = ({
|
|
57
|
+
sortable,
|
|
58
|
+
scrollRef,
|
|
59
|
+
style,
|
|
60
|
+
children,
|
|
61
|
+
draxViewProps,
|
|
62
|
+
renderDropIndicator,
|
|
63
|
+
}: SortableContainerProps) => {
|
|
64
|
+
const {
|
|
65
|
+
id,
|
|
66
|
+
horizontal,
|
|
67
|
+
draggedItem,
|
|
68
|
+
rawData,
|
|
69
|
+
moveDraggedItem,
|
|
70
|
+
getSnapbackTarget,
|
|
71
|
+
setDraggedItem,
|
|
72
|
+
resetDraggedItem,
|
|
73
|
+
scrollPosition,
|
|
74
|
+
containerMeasurementsRef,
|
|
75
|
+
contentSizeRef,
|
|
76
|
+
autoScrollJumpRatio,
|
|
77
|
+
autoScrollBackThreshold,
|
|
78
|
+
autoScrollForwardThreshold,
|
|
79
|
+
onDragStart: onDragStartCallback,
|
|
80
|
+
onDragPositionChange: onDragPositionChangeCallback,
|
|
81
|
+
onDragEnd: onDragEndCallback,
|
|
82
|
+
onReorder,
|
|
83
|
+
getMeasurementByOriginalIndex,
|
|
84
|
+
dropTargetPositionSV,
|
|
85
|
+
dropTargetVisibleSV,
|
|
86
|
+
draggedDisplayIndexRef,
|
|
87
|
+
dragStartIndexRef,
|
|
88
|
+
initPendingOrder,
|
|
89
|
+
commitVisualOrder,
|
|
90
|
+
computeShiftsForOrder,
|
|
91
|
+
pendingOrderRef,
|
|
92
|
+
committedOrderRef,
|
|
93
|
+
cancelDrag,
|
|
94
|
+
shiftsRef,
|
|
95
|
+
instantClearSV,
|
|
96
|
+
shiftsValidSV,
|
|
97
|
+
getSlotFromPosition,
|
|
98
|
+
} = sortable._internal;
|
|
99
|
+
|
|
100
|
+
// Access hover SharedValues from DraxContext for deferred clearing.
|
|
101
|
+
const {
|
|
102
|
+
hoverReadySV,
|
|
103
|
+
dragPhaseSV,
|
|
104
|
+
draggedIdSV,
|
|
105
|
+
hoverPositionSV,
|
|
106
|
+
hoverClearDeferredRef,
|
|
107
|
+
setHoverContent,
|
|
108
|
+
} = useDraxContext();
|
|
109
|
+
|
|
110
|
+
const boardContext = useSortableBoardContext();
|
|
111
|
+
|
|
112
|
+
useEffect(() => {
|
|
113
|
+
if (!boardContext) return;
|
|
114
|
+
boardContext.registerColumn(id, sortable._internal);
|
|
115
|
+
return () => boardContext.unregisterColumn(id);
|
|
116
|
+
}, [boardContext, id, sortable._internal]);
|
|
117
|
+
|
|
118
|
+
const itemCount = rawData.length;
|
|
119
|
+
const scrollStateRef = useRef(AutoScrollDirection.None);
|
|
120
|
+
const scrollIntervalRef = useRef<ReturnType<typeof setInterval> | undefined>(
|
|
121
|
+
undefined
|
|
122
|
+
);
|
|
123
|
+
const draggedToIndex = useRef<number | undefined>(undefined);
|
|
124
|
+
const jitterExceededRef = useRef(false);
|
|
125
|
+
// Track last receiver that triggered a reorder to prevent oscillation.
|
|
126
|
+
// After moveDraggedItem inserts at position R, the receiver shifts to R-1.
|
|
127
|
+
// Next frame, same receiver at R-1 would move backward — skip it.
|
|
128
|
+
const lastMoveReceiverRef = useRef<number | undefined>(undefined);
|
|
129
|
+
const lastMoveDirectionRef = useRef<number>(0); // +1 forward, -1 backward
|
|
130
|
+
|
|
131
|
+
|
|
132
|
+
const { freeze: freezeScroll, unfreeze: unfreezeScroll } = useWebScrollFreeze(scrollRef);
|
|
133
|
+
|
|
134
|
+
// ── Finalize drag (called after snap animation completes) ──────────
|
|
135
|
+
|
|
136
|
+
const finalizeDrag = () => {
|
|
137
|
+
unfreezeScroll();
|
|
138
|
+
|
|
139
|
+
if (boardContext?.boardInternal.transferState.current?.targetId) {
|
|
140
|
+
boardContext.boardInternal.finalizeTransfer?.();
|
|
141
|
+
return;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
const startIdx = dragStartIndexRef.current;
|
|
145
|
+
const endIdx = draggedDisplayIndexRef.current;
|
|
146
|
+
|
|
147
|
+
const pending = pendingOrderRef.current;
|
|
148
|
+
const didReorder = startIdx !== undefined && endIdx !== undefined
|
|
149
|
+
&& startIdx !== endIdx && pending.length > 0;
|
|
150
|
+
|
|
151
|
+
if (didReorder) {
|
|
152
|
+
// Build final data BEFORE clearing refs
|
|
153
|
+
const finalData = pending
|
|
154
|
+
.map((idx) => rawData[idx])
|
|
155
|
+
.filter((item): item is any => item !== undefined);
|
|
156
|
+
const draggedOrigIdx = pending[endIdx];
|
|
157
|
+
const displacedOrigIdx = pending[startIdx];
|
|
158
|
+
|
|
159
|
+
const reorderEvent = {
|
|
160
|
+
data: finalData,
|
|
161
|
+
fromIndex: startIdx,
|
|
162
|
+
toIndex: endIdx,
|
|
163
|
+
fromItem: draggedOrigIdx !== undefined ? rawData[draggedOrigIdx] as any : undefined as any,
|
|
164
|
+
toItem: displacedOrigIdx !== undefined ? rawData[displacedOrigIdx] as any : undefined as any,
|
|
165
|
+
isExternalDrag: false,
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
{
|
|
169
|
+
// ── PERMANENT SHIFTS: blink-free for all contexts ──
|
|
170
|
+
const finalShifts = computeShiftsForOrder(pending) ?? {};
|
|
171
|
+
commitVisualOrder();
|
|
172
|
+
|
|
173
|
+
// Clear JS-thread refs BEFORE the runOnUI block.
|
|
174
|
+
draggedDisplayIndexRef.current = undefined;
|
|
175
|
+
dragStartIndexRef.current = undefined;
|
|
176
|
+
pendingOrderRef.current = [];
|
|
177
|
+
|
|
178
|
+
hoverClearDeferredRef.current = true;
|
|
179
|
+
runOnUI((_shifts: Record<string, Position>) => {
|
|
180
|
+
'worklet';
|
|
181
|
+
instantClearSV.value = true;
|
|
182
|
+
shiftsValidSV.value = true;
|
|
183
|
+
shiftsRef.value = _shifts;
|
|
184
|
+
draggedItem.value = -1;
|
|
185
|
+
hoverReadySV.value = false;
|
|
186
|
+
dragPhaseSV.value = 'idle';
|
|
187
|
+
draggedIdSV.value = '';
|
|
188
|
+
hoverPositionSV.value = { x: 0, y: 0 };
|
|
189
|
+
runOnJS(setHoverContent)(null);
|
|
190
|
+
})(finalShifts);
|
|
191
|
+
|
|
192
|
+
requestAnimationFrame(() => {
|
|
193
|
+
onReorder(reorderEvent);
|
|
194
|
+
if (boardContext) {
|
|
195
|
+
setTimeout(() => {
|
|
196
|
+
sortable._internal.flushVisualOrder();
|
|
197
|
+
}, 300);
|
|
198
|
+
} else if (Platform.OS === 'web') {
|
|
199
|
+
// On web, flush synchronously after onReorder so FlatList cells
|
|
200
|
+
// move to correct positions immediately. The delayed flush caused
|
|
201
|
+
// races when the user grabbed another item before it fired.
|
|
202
|
+
sortable._internal.flushVisualOrder();
|
|
203
|
+
}
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
} else {
|
|
207
|
+
// No reorder — cancel drag: revert to committed shifts + make item visible.
|
|
208
|
+
cancelDrag();
|
|
209
|
+
resetDraggedItem();
|
|
210
|
+
}
|
|
211
|
+
};
|
|
212
|
+
|
|
213
|
+
// Register finalizeDrag via the stable ref so SortableItem always
|
|
214
|
+
// calls the latest version, even if it has a stale _internal reference
|
|
215
|
+
// (e.g., after MATCH path skips FlatList re-render).
|
|
216
|
+
useLayoutEffect(() => {
|
|
217
|
+
sortable._internal.onItemSnapEnd = finalizeDrag;
|
|
218
|
+
}, [sortable._internal, finalizeDrag]);
|
|
219
|
+
|
|
220
|
+
// ── Auto-scroll ─────────────────────────────────────────────────────
|
|
221
|
+
|
|
222
|
+
const doScroll = () => {
|
|
223
|
+
const containerMeasurements = containerMeasurementsRef.current;
|
|
224
|
+
const contentSize = contentSizeRef.current;
|
|
225
|
+
if (!scrollRef.current || !containerMeasurements || !contentSize) return;
|
|
226
|
+
|
|
227
|
+
let containerLength: number;
|
|
228
|
+
let contentLength: number;
|
|
229
|
+
let prevOffset: number;
|
|
230
|
+
if (horizontal) {
|
|
231
|
+
containerLength = containerMeasurements.width;
|
|
232
|
+
contentLength = contentSize.x;
|
|
233
|
+
prevOffset = scrollPosition.value.x;
|
|
234
|
+
} else {
|
|
235
|
+
containerLength = containerMeasurements.height;
|
|
236
|
+
contentLength = contentSize.y;
|
|
237
|
+
prevOffset = scrollPosition.value.y;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const jumpLength = containerLength * autoScrollJumpRatio;
|
|
241
|
+
let offset: number | undefined;
|
|
242
|
+
if (scrollStateRef.current === AutoScrollDirection.Forward) {
|
|
243
|
+
const maxOffset = contentLength - containerLength;
|
|
244
|
+
if (prevOffset < maxOffset) {
|
|
245
|
+
offset = Math.min(prevOffset + jumpLength, maxOffset);
|
|
246
|
+
}
|
|
247
|
+
} else if (scrollStateRef.current === AutoScrollDirection.Back) {
|
|
248
|
+
if (prevOffset > 0) {
|
|
249
|
+
offset = Math.max(prevOffset - jumpLength, 0);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
if (offset !== undefined) {
|
|
254
|
+
if (scrollRef.current.scrollToOffset) {
|
|
255
|
+
// FlatList / FlashList / LegendList
|
|
256
|
+
scrollRef.current.scrollToOffset({ offset });
|
|
257
|
+
} else if (scrollRef.current.scrollTo) {
|
|
258
|
+
// ScrollView
|
|
259
|
+
scrollRef.current.scrollTo(
|
|
260
|
+
horizontal ? { x: offset } : { y: offset },
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
if (scrollRef.current.flashScrollIndicators) {
|
|
264
|
+
scrollRef.current.flashScrollIndicators();
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
};
|
|
268
|
+
|
|
269
|
+
const startScroll = () => {
|
|
270
|
+
if (scrollIntervalRef.current) return;
|
|
271
|
+
doScroll();
|
|
272
|
+
scrollIntervalRef.current = setInterval(
|
|
273
|
+
doScroll,
|
|
274
|
+
defaultAutoScrollIntervalLength
|
|
275
|
+
);
|
|
276
|
+
};
|
|
277
|
+
|
|
278
|
+
const stopScroll = () => {
|
|
279
|
+
if (scrollIntervalRef.current) {
|
|
280
|
+
clearInterval(scrollIntervalRef.current);
|
|
281
|
+
scrollIntervalRef.current = undefined;
|
|
282
|
+
}
|
|
283
|
+
};
|
|
284
|
+
|
|
285
|
+
// ── Internal drag end handler ───────────────────────────────────────
|
|
286
|
+
|
|
287
|
+
const handleInternalDragEnd = (
|
|
288
|
+
eventData:
|
|
289
|
+
| DraxMonitorEventData
|
|
290
|
+
| DraxMonitorEndEventData
|
|
291
|
+
| DraxMonitorDragDropEventData,
|
|
292
|
+
totalDragEnd: boolean
|
|
293
|
+
): DraxProtocolDragEndResponse => {
|
|
294
|
+
scrollStateRef.current = AutoScrollDirection.None;
|
|
295
|
+
stopScroll();
|
|
296
|
+
unfreezeScroll();
|
|
297
|
+
dropTargetVisibleSV.value = false;
|
|
298
|
+
|
|
299
|
+
const { dragged, receiver } = eventData;
|
|
300
|
+
const draggedPayload = isSortableItemPayload(dragged.payload)
|
|
301
|
+
? dragged.payload
|
|
302
|
+
: undefined;
|
|
303
|
+
const externalDrag = dragged.parentId !== id || !draggedPayload;
|
|
304
|
+
|
|
305
|
+
const fromIndex = dragStartIndexRef.current ?? draggedPayload?.index ?? 0;
|
|
306
|
+
const fromOriginalIndex = draggedPayload?.originalIndex ?? fromIndex;
|
|
307
|
+
const fromItem = externalDrag ? undefined : rawData[fromOriginalIndex];
|
|
308
|
+
|
|
309
|
+
const receiverPayload = isSortableItemPayload(receiver?.payload)
|
|
310
|
+
? receiver?.payload
|
|
311
|
+
: undefined;
|
|
312
|
+
const toPayload =
|
|
313
|
+
receiver?.parentId === id ? receiverPayload : undefined;
|
|
314
|
+
|
|
315
|
+
if (totalDragEnd) {
|
|
316
|
+
onDragEndCallback?.({
|
|
317
|
+
index: fromIndex,
|
|
318
|
+
item: fromItem as any,
|
|
319
|
+
toIndex: draggedDisplayIndexRef.current,
|
|
320
|
+
cancelled: isWithCancelledFlag(eventData)
|
|
321
|
+
? eventData.cancelled
|
|
322
|
+
: false,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// Reset drag position tracking
|
|
327
|
+
if (draggedToIndex.current !== undefined) {
|
|
328
|
+
if (!totalDragEnd) {
|
|
329
|
+
onDragPositionChangeCallback?.({
|
|
330
|
+
index: fromIndex,
|
|
331
|
+
item: fromItem as any,
|
|
332
|
+
toIndex: undefined,
|
|
333
|
+
previousIndex: draggedToIndex.current,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
draggedToIndex.current = undefined;
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// User hasn't moved — skip reorder, snap back to origin.
|
|
340
|
+
// Don't clean up here — finalizeDrag handles it after snap completes.
|
|
341
|
+
if (
|
|
342
|
+
toPayload !== undefined &&
|
|
343
|
+
computeFingerDisplacement(eventData) < FINGER_JITTER_THRESHOLD
|
|
344
|
+
) {
|
|
345
|
+
return undefined;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Reorder happened — return snap target. Don't commit yet;
|
|
349
|
+
// finalizeDrag commits after the snap animation completes so
|
|
350
|
+
// the hover covers any FlatList re-render.
|
|
351
|
+
if (totalDragEnd && draggedDisplayIndexRef.current !== undefined) {
|
|
352
|
+
// Shifts stay active during snap animation — items remain at their
|
|
353
|
+
// shifted positions. finalizeDrag will set permanent shifts after snap.
|
|
354
|
+
const snapbackTarget = getSnapbackTarget();
|
|
355
|
+
return snapbackTarget;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// Dropped on a receiver outside this sortable list
|
|
359
|
+
if (receiver && receiver.parentId !== id) {
|
|
360
|
+
return undefined;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// External drag (item from another container) with no reorder — snap back
|
|
364
|
+
// to original position. Without this, getMeasurementByOriginalIndex would
|
|
365
|
+
// look up the wrong item in this container's data by the source index.
|
|
366
|
+
if (externalDrag) {
|
|
367
|
+
return undefined;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// No receiver — snap back to the dragged item's current position
|
|
371
|
+
const containerMeasurements = containerMeasurementsRef.current;
|
|
372
|
+
const fromMeas = getMeasurementByOriginalIndex(fromOriginalIndex);
|
|
373
|
+
if (fromMeas && containerMeasurements) {
|
|
374
|
+
return {
|
|
375
|
+
x: containerMeasurements.x + fromMeas.x - scrollPosition.value.x,
|
|
376
|
+
y: containerMeasurements.y + fromMeas.y - scrollPosition.value.y,
|
|
377
|
+
};
|
|
378
|
+
}
|
|
379
|
+
return undefined;
|
|
380
|
+
};
|
|
381
|
+
|
|
382
|
+
// ── Monitor callbacks ───────────────────────────────────────────────
|
|
383
|
+
|
|
384
|
+
const onMonitorDragStart = (eventData: DraxMonitorEventData) => {
|
|
385
|
+
draxViewProps?.onMonitorDragStart?.(eventData);
|
|
386
|
+
jitterExceededRef.current = false;
|
|
387
|
+
lastMoveReceiverRef.current = undefined;
|
|
388
|
+
lastMoveDirectionRef.current = 0;
|
|
389
|
+
// Clear any stale freeze from a previous drag that failed to unfreeze
|
|
390
|
+
// (e.g., fast cross-container gesture where onMonitorDragExit was skipped).
|
|
391
|
+
unfreezeScroll();
|
|
392
|
+
freezeScroll();
|
|
393
|
+
|
|
394
|
+
const { dragged } = eventData;
|
|
395
|
+
|
|
396
|
+
// No guard on draggedItem.value — Reanimated 4 doesn't reliably sync
|
|
397
|
+
// SharedValue writes from runOnUI worklets, so the value may be stale.
|
|
398
|
+
// onMonitorDragStart only fires at the start of a new gesture, so
|
|
399
|
+
// setDraggedItem + initPendingOrder safely overwrite any stale state.
|
|
400
|
+
|
|
401
|
+
if (
|
|
402
|
+
dragged.parentId === id &&
|
|
403
|
+
isSortableItemPayload(dragged.payload)
|
|
404
|
+
) {
|
|
405
|
+
const { index, originalIndex } = dragged.payload;
|
|
406
|
+
setDraggedItem(originalIndex);
|
|
407
|
+
// Initialize pending order BEFORE setting display index.
|
|
408
|
+
// initPendingOrder copies the committed visual order into pendingOrderRef.
|
|
409
|
+
initPendingOrder();
|
|
410
|
+
// Map FlatList index to committed visual order position.
|
|
411
|
+
// With stableData, FlatList renders original data and permanent shifts
|
|
412
|
+
// handle visual order. The dragged item at FlatList cell `index`
|
|
413
|
+
// may be at a different visual position in the committed order.
|
|
414
|
+
const committed = committedOrderRef.current;
|
|
415
|
+
let displayIndex = index;
|
|
416
|
+
if (committed.length > 0) {
|
|
417
|
+
const pos = committed.indexOf(originalIndex);
|
|
418
|
+
if (pos >= 0) displayIndex = pos;
|
|
419
|
+
}
|
|
420
|
+
draggedDisplayIndexRef.current = displayIndex;
|
|
421
|
+
dragStartIndexRef.current = displayIndex;
|
|
422
|
+
// Item visibility is controlled by hoverReadySV from DraxContext.
|
|
423
|
+
onDragStartCallback?.({
|
|
424
|
+
index: displayIndex,
|
|
425
|
+
item: rawData[originalIndex] as any,
|
|
426
|
+
});
|
|
427
|
+
}
|
|
428
|
+
};
|
|
429
|
+
|
|
430
|
+
const onMonitorDragOver = (eventData: DraxMonitorEventData) => {
|
|
431
|
+
const displacement = computeFingerDisplacement(eventData);
|
|
432
|
+
if (!jitterExceededRef.current) {
|
|
433
|
+
if (displacement < FINGER_JITTER_THRESHOLD) {
|
|
434
|
+
draxViewProps?.onMonitorDragOver?.(eventData);
|
|
435
|
+
return;
|
|
436
|
+
}
|
|
437
|
+
jitterExceededRef.current = true;
|
|
438
|
+
// Item visibility is now controlled by hoverReadySV from DraxContext —
|
|
439
|
+
// SortableItem hides when hoverReadySV && draggedIdSV match.
|
|
440
|
+
// No need for setDraggedKey here.
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
draxViewProps?.onMonitorDragOver?.(eventData);
|
|
444
|
+
|
|
445
|
+
const { dragged, monitorOffset, monitorOffsetRatio } = eventData;
|
|
446
|
+
const draggedPayload = isSortableItemPayload(dragged.payload)
|
|
447
|
+
? dragged.payload
|
|
448
|
+
: undefined;
|
|
449
|
+
const externalDrag = dragged.parentId !== id || !draggedPayload;
|
|
450
|
+
const fromIndex = dragStartIndexRef.current ?? draggedPayload?.index ?? 0;
|
|
451
|
+
const fromItem = externalDrag
|
|
452
|
+
? undefined
|
|
453
|
+
: rawData[draggedPayload?.originalIndex ?? fromIndex];
|
|
454
|
+
|
|
455
|
+
if (typeof draggedItem.value !== 'number' || draggedItem.value < 0) {
|
|
456
|
+
setDraggedItem(itemCount);
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// ── Position-based slot detection ──────────────────────────────────
|
|
460
|
+
// Use the hover center's content position. Slot boundaries are based
|
|
461
|
+
// on original layout positions (stable, never shift during drag).
|
|
462
|
+
const contentPos = {
|
|
463
|
+
x: monitorOffset.x + scrollPosition.value.x,
|
|
464
|
+
y: monitorOffset.y + scrollPosition.value.y,
|
|
465
|
+
};
|
|
466
|
+
const targetSlot = getSlotFromPosition(contentPos);
|
|
467
|
+
|
|
468
|
+
// Track drag position changes (log only on slot change to avoid per-frame noise)
|
|
469
|
+
if (targetSlot !== draggedToIndex.current) {
|
|
470
|
+
onDragPositionChangeCallback?.({
|
|
471
|
+
toIndex: targetSlot,
|
|
472
|
+
index: fromIndex,
|
|
473
|
+
item: fromItem as any,
|
|
474
|
+
previousIndex: draggedToIndex.current,
|
|
475
|
+
});
|
|
476
|
+
draggedToIndex.current = targetSlot;
|
|
477
|
+
|
|
478
|
+
// Update drop indicator
|
|
479
|
+
if (renderDropIndicator) {
|
|
480
|
+
const pending = pendingOrderRef.current;
|
|
481
|
+
const slotOrigIdx = pending.length > targetSlot ? pending[targetSlot] : undefined;
|
|
482
|
+
const toMeas = slotOrigIdx !== undefined
|
|
483
|
+
? getMeasurementByOriginalIndex(slotOrigIdx)
|
|
484
|
+
: undefined;
|
|
485
|
+
if (toMeas) {
|
|
486
|
+
const currentDragIdx = draggedDisplayIndexRef.current ?? fromIndex;
|
|
487
|
+
const isForward = currentDragIdx < targetSlot;
|
|
488
|
+
if (horizontal) {
|
|
489
|
+
dropTargetPositionSV.value = {
|
|
490
|
+
x: isForward ? toMeas.x + toMeas.width : toMeas.x,
|
|
491
|
+
y: toMeas.y,
|
|
492
|
+
};
|
|
493
|
+
} else {
|
|
494
|
+
dropTargetPositionSV.value = {
|
|
495
|
+
x: toMeas.x,
|
|
496
|
+
y: isForward ? toMeas.y + toMeas.height : toMeas.y,
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
dropTargetVisibleSV.value = true;
|
|
500
|
+
}
|
|
501
|
+
} else {
|
|
502
|
+
dropTargetVisibleSV.value = false;
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Reorder via position-based slot (not receiver-based).
|
|
507
|
+
// Receiver detection uses the spatial index which stores FlatList layout
|
|
508
|
+
// positions. With stableData, these become stale after the first reorder
|
|
509
|
+
// because shifts move items visually but don't update the spatial index.
|
|
510
|
+
const currentDragIdx = draggedDisplayIndexRef.current;
|
|
511
|
+
if (currentDragIdx !== undefined && targetSlot !== currentDragIdx) {
|
|
512
|
+
const direction = Math.sign(targetSlot - currentDragIdx);
|
|
513
|
+
const sameTarget = lastMoveReceiverRef.current === targetSlot;
|
|
514
|
+
const wouldReverse = sameTarget && direction !== 0
|
|
515
|
+
&& direction !== lastMoveDirectionRef.current;
|
|
516
|
+
|
|
517
|
+
if (!wouldReverse) {
|
|
518
|
+
if (direction !== 0) {
|
|
519
|
+
lastMoveReceiverRef.current = targetSlot;
|
|
520
|
+
lastMoveDirectionRef.current = direction;
|
|
521
|
+
}
|
|
522
|
+
moveDraggedItem(targetSlot);
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Auto-scroll
|
|
527
|
+
const ratio = horizontal ? monitorOffsetRatio.x : monitorOffsetRatio.y;
|
|
528
|
+
if (ratio > autoScrollBackThreshold && ratio < autoScrollForwardThreshold) {
|
|
529
|
+
scrollStateRef.current = AutoScrollDirection.None;
|
|
530
|
+
stopScroll();
|
|
531
|
+
} else {
|
|
532
|
+
if (ratio >= autoScrollForwardThreshold) {
|
|
533
|
+
scrollStateRef.current = AutoScrollDirection.Forward;
|
|
534
|
+
} else if (ratio <= autoScrollBackThreshold) {
|
|
535
|
+
scrollStateRef.current = AutoScrollDirection.Back;
|
|
536
|
+
}
|
|
537
|
+
startScroll();
|
|
538
|
+
}
|
|
539
|
+
};
|
|
540
|
+
|
|
541
|
+
const onMonitorDragExit = (eventData: DraxMonitorEventData) => {
|
|
542
|
+
stopScroll();
|
|
543
|
+
if (scrollIntervalRef.current) {
|
|
544
|
+
draxViewProps?.onMonitorDragExit?.(eventData);
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
handleInternalDragEnd(eventData, false);
|
|
548
|
+
draxViewProps?.onMonitorDragExit?.(eventData);
|
|
549
|
+
};
|
|
550
|
+
|
|
551
|
+
const onMonitorDragEnd = (eventData: DraxMonitorEndEventData) => {
|
|
552
|
+
if (boardContext?.boardInternal.transferState.current?.targetId) {
|
|
553
|
+
unfreezeScroll();
|
|
554
|
+
draxViewProps?.onMonitorDragEnd?.(eventData);
|
|
555
|
+
return undefined;
|
|
556
|
+
}
|
|
557
|
+
const defaultSnapbackTarget = handleInternalDragEnd(eventData, true);
|
|
558
|
+
const providedSnapTarget =
|
|
559
|
+
draxViewProps?.onMonitorDragEnd?.(eventData);
|
|
560
|
+
|
|
561
|
+
return providedSnapTarget ?? defaultSnapbackTarget;
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
const onMonitorDragDrop = (eventData: DraxMonitorDragDropEventData) => {
|
|
565
|
+
if (boardContext?.boardInternal.transferState.current?.targetId) {
|
|
566
|
+
unfreezeScroll();
|
|
567
|
+
draxViewProps?.onMonitorDragDrop?.(eventData);
|
|
568
|
+
return undefined;
|
|
569
|
+
}
|
|
570
|
+
const defaultSnapbackTarget = handleInternalDragEnd(eventData, true);
|
|
571
|
+
const providedSnapTarget =
|
|
572
|
+
draxViewProps?.onMonitorDragDrop?.(eventData);
|
|
573
|
+
|
|
574
|
+
return providedSnapTarget ?? defaultSnapbackTarget;
|
|
575
|
+
};
|
|
576
|
+
|
|
577
|
+
const handleMeasure = (event: any) => {
|
|
578
|
+
draxViewProps?.onMeasure?.(event);
|
|
579
|
+
containerMeasurementsRef.current = event;
|
|
580
|
+
};
|
|
581
|
+
|
|
582
|
+
return (
|
|
583
|
+
<DraxView
|
|
584
|
+
{...draxViewProps}
|
|
585
|
+
style={[draxViewProps?.style, style]}
|
|
586
|
+
id={id}
|
|
587
|
+
isParent
|
|
588
|
+
scrollPosition={scrollPosition}
|
|
589
|
+
monitoring
|
|
590
|
+
onMeasure={handleMeasure}
|
|
591
|
+
onMonitorDragStart={onMonitorDragStart}
|
|
592
|
+
onMonitorDragOver={onMonitorDragOver}
|
|
593
|
+
onMonitorDragExit={onMonitorDragExit}
|
|
594
|
+
onMonitorDragEnd={onMonitorDragEnd}
|
|
595
|
+
onMonitorDragDrop={onMonitorDragDrop}
|
|
596
|
+
>
|
|
597
|
+
{children}
|
|
598
|
+
{renderDropIndicator && (
|
|
599
|
+
<DropIndicatorOverlay
|
|
600
|
+
dropTargetPositionSV={dropTargetPositionSV}
|
|
601
|
+
dropTargetVisibleSV={dropTargetVisibleSV}
|
|
602
|
+
horizontal={horizontal}
|
|
603
|
+
renderDropIndicator={renderDropIndicator}
|
|
604
|
+
/>
|
|
605
|
+
)}
|
|
606
|
+
</DraxView>
|
|
607
|
+
);
|
|
608
|
+
};
|
|
609
|
+
|
|
610
|
+
/** Extracted so useAnimatedStyle is always called when the component mounts. */
|
|
611
|
+
const DropIndicatorOverlay = ({
|
|
612
|
+
dropTargetPositionSV,
|
|
613
|
+
dropTargetVisibleSV,
|
|
614
|
+
horizontal,
|
|
615
|
+
renderDropIndicator,
|
|
616
|
+
}: {
|
|
617
|
+
dropTargetPositionSV: SharedValue<Position>;
|
|
618
|
+
dropTargetVisibleSV: SharedValue<boolean>;
|
|
619
|
+
horizontal: boolean;
|
|
620
|
+
renderDropIndicator: (props: DropIndicatorProps) => ReactNode;
|
|
621
|
+
}) => {
|
|
622
|
+
const indicatorStyle = useAnimatedStyle(() => {
|
|
623
|
+
const pos = dropTargetPositionSV.value;
|
|
624
|
+
const visible = dropTargetVisibleSV.value;
|
|
625
|
+
return {
|
|
626
|
+
opacity: visible ? 1 : 0,
|
|
627
|
+
transform: [
|
|
628
|
+
{ translateX: withTiming(pos.x, { duration: ITEM_SHIFT_ANIMATION_DURATION }) },
|
|
629
|
+
{ translateY: withTiming(pos.y, { duration: ITEM_SHIFT_ANIMATION_DURATION }) },
|
|
630
|
+
] as const,
|
|
631
|
+
};
|
|
632
|
+
});
|
|
633
|
+
|
|
634
|
+
return (
|
|
635
|
+
<Reanimated.View
|
|
636
|
+
style={[dropIndicatorStyles.container, indicatorStyle]}
|
|
637
|
+
pointerEvents="none"
|
|
638
|
+
>
|
|
639
|
+
{renderDropIndicator({ visible: true, horizontal })}
|
|
640
|
+
</Reanimated.View>
|
|
641
|
+
);
|
|
642
|
+
};
|
|
643
|
+
|
|
644
|
+
const dropIndicatorStyles = StyleSheet.create({
|
|
645
|
+
container: {
|
|
646
|
+
position: 'absolute',
|
|
647
|
+
top: 0,
|
|
648
|
+
left: 0,
|
|
649
|
+
},
|
|
650
|
+
});
|