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,439 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { useEffect, useRef } from 'react';
|
|
3
|
+
import type { StyleProp, ViewStyle } from 'react-native';
|
|
4
|
+
import { withDelay, withTiming } from 'react-native-reanimated';
|
|
5
|
+
import { runOnJS, runOnUI } from 'react-native-worklets';
|
|
6
|
+
import { DraxView } from './DraxView';
|
|
7
|
+
import { useDraxContext } from './hooks/useDraxContext';
|
|
8
|
+
import { defaultSnapbackDelay, defaultSnapbackDuration } from './params';
|
|
9
|
+
import { SortableBoardContext } from './SortableBoardContext';
|
|
10
|
+
import type {
|
|
11
|
+
DraxMonitorEndEventData,
|
|
12
|
+
DraxMonitorEventData,
|
|
13
|
+
DraxMonitorDragDropEventData,
|
|
14
|
+
DraxProtocolDragEndResponse,
|
|
15
|
+
DraxViewProps,
|
|
16
|
+
Position,
|
|
17
|
+
SortableBoardContextValue,
|
|
18
|
+
SortableBoardHandle,
|
|
19
|
+
SortableBoardInternal,
|
|
20
|
+
SortableListInternal,
|
|
21
|
+
ViewDimensions,
|
|
22
|
+
} from './types';
|
|
23
|
+
import { isSortableItemPayload } from './types';
|
|
24
|
+
|
|
25
|
+
export interface SortableBoardContainerProps<TItem = unknown> {
|
|
26
|
+
board: SortableBoardHandle<TItem>;
|
|
27
|
+
style?: StyleProp<ViewStyle>;
|
|
28
|
+
children: ReactNode;
|
|
29
|
+
draxViewProps?: Partial<DraxViewProps>;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export const SortableBoardContainer = <TItem,>({
|
|
33
|
+
board,
|
|
34
|
+
style,
|
|
35
|
+
children,
|
|
36
|
+
draxViewProps,
|
|
37
|
+
}: SortableBoardContainerProps<TItem>) => {
|
|
38
|
+
const boardInternal = board._internal;
|
|
39
|
+
const columns = boardInternal.columns;
|
|
40
|
+
const transferStateRef = boardInternal.transferState;
|
|
41
|
+
|
|
42
|
+
const {
|
|
43
|
+
hoverClearDeferredRef,
|
|
44
|
+
draggedIdSV,
|
|
45
|
+
hoverReadySV,
|
|
46
|
+
dragPhaseSV,
|
|
47
|
+
hoverPositionSV,
|
|
48
|
+
hoverDimsSV,
|
|
49
|
+
setHoverContent,
|
|
50
|
+
} = useDraxContext();
|
|
51
|
+
|
|
52
|
+
// Source info recorded on drag start
|
|
53
|
+
const sourceInfoRef = useRef<{
|
|
54
|
+
colId: string;
|
|
55
|
+
originalIndex: number;
|
|
56
|
+
itemKey: string;
|
|
57
|
+
dragStartIndex: number;
|
|
58
|
+
dimensions: ViewDimensions;
|
|
59
|
+
} | undefined>(undefined);
|
|
60
|
+
|
|
61
|
+
// ── Find which column contains the given absolute position ──────────
|
|
62
|
+
const findTargetColumn = (absolutePos: Position): string | undefined => {
|
|
63
|
+
for (const [colId, internal] of columns.entries()) {
|
|
64
|
+
const bounds = internal.containerMeasurementsRef.current;
|
|
65
|
+
if (!bounds) continue;
|
|
66
|
+
if (
|
|
67
|
+
absolutePos.x >= bounds.x &&
|
|
68
|
+
absolutePos.x <= bounds.x + bounds.width &&
|
|
69
|
+
absolutePos.y >= bounds.y &&
|
|
70
|
+
absolutePos.y <= bounds.y + bounds.height
|
|
71
|
+
) {
|
|
72
|
+
return colId;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return undefined;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
// ── Convert absolute position to column content position ────────────
|
|
79
|
+
const toContentPos = (
|
|
80
|
+
absolutePos: Position,
|
|
81
|
+
col: SortableListInternal<unknown>
|
|
82
|
+
): Position => {
|
|
83
|
+
const bounds = col.containerMeasurementsRef.current;
|
|
84
|
+
if (!bounds) return { x: 0, y: 0 };
|
|
85
|
+
return {
|
|
86
|
+
x: absolutePos.x - bounds.x + col.scrollPosition.value.x,
|
|
87
|
+
y: absolutePos.y - bounds.y + col.scrollPosition.value.y,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
|
|
91
|
+
// ── Finalize cross-container transfer ─────────────────────────────────
|
|
92
|
+
// 1. Clear source/target JS-thread drag refs
|
|
93
|
+
// 2. Clear source draggedItem on UI thread
|
|
94
|
+
// 3. rAF → onTransfer → parent setState → useLayoutEffect updates stableData
|
|
95
|
+
// (hover covers any brief re-layout; React 18 flushes setStableData
|
|
96
|
+
// synchronously from useLayoutEffect, so FlatList re-renders before paint)
|
|
97
|
+
// 4. Guarded rAF → rAF → clear hover (skipped if a new drag already started)
|
|
98
|
+
const finalizeTransferRef = useRef<(() => void) | undefined>(undefined);
|
|
99
|
+
/** Generation counter — incremented on each new drag start.
|
|
100
|
+
* Deferred hover cleanup checks this to skip if a new drag has started. */
|
|
101
|
+
const cleanupGenRef = useRef(0);
|
|
102
|
+
|
|
103
|
+
finalizeTransferRef.current = () => {
|
|
104
|
+
const transfer = transferStateRef.current;
|
|
105
|
+
if (!transfer || !transfer.targetId) return;
|
|
106
|
+
|
|
107
|
+
const source = columns.get(transfer.sourceId);
|
|
108
|
+
const target = columns.get(transfer.targetId);
|
|
109
|
+
if (!source || !target) return;
|
|
110
|
+
|
|
111
|
+
const phantomIndex = target.phantomRef.current?.atDisplayIndex ?? transfer.targetSlot ?? 0;
|
|
112
|
+
|
|
113
|
+
const item = source.rawData[transfer.sourceOriginalIndex] as TItem;
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
// 1. Clear source drag refs
|
|
117
|
+
source.draggedDisplayIndexRef.current = undefined;
|
|
118
|
+
source.dragStartIndexRef.current = undefined;
|
|
119
|
+
source.pendingOrderRef.current = [];
|
|
120
|
+
|
|
121
|
+
// Clear target phantom ref (JS-thread only, shifts stay for now).
|
|
122
|
+
target.phantomRef.current = undefined;
|
|
123
|
+
target.pendingOrderRef.current = [];
|
|
124
|
+
|
|
125
|
+
// Keep hover visible during transition
|
|
126
|
+
hoverClearDeferredRef.current = true;
|
|
127
|
+
|
|
128
|
+
// Clear board/transfer state
|
|
129
|
+
transferStateRef.current = undefined;
|
|
130
|
+
sourceInfoRef.current = undefined;
|
|
131
|
+
|
|
132
|
+
// 2. Clear source draggedItem AND draggedIdSV on UI thread.
|
|
133
|
+
const sourceItemKey = transfer.itemKey;
|
|
134
|
+
|
|
135
|
+
runOnUI((
|
|
136
|
+
_srcDraggedItem: typeof source.draggedItem,
|
|
137
|
+
_draggedIdSV: typeof draggedIdSV,
|
|
138
|
+
_shiftsRef: typeof source.shiftsRef,
|
|
139
|
+
_key: string,
|
|
140
|
+
) => {
|
|
141
|
+
'worklet';
|
|
142
|
+
const current = _shiftsRef.value;
|
|
143
|
+
_shiftsRef.value = { ...current, [_key]: { x: -9999, y: -9999 } };
|
|
144
|
+
_srcDraggedItem.value = -1;
|
|
145
|
+
_draggedIdSV.value = '';
|
|
146
|
+
})(source.draggedItem, draggedIdSV, source.shiftsRef, sourceItemKey);
|
|
147
|
+
|
|
148
|
+
// 3. rAF → onTransfer → parent setState
|
|
149
|
+
const gen = ++cleanupGenRef.current;
|
|
150
|
+
|
|
151
|
+
requestAnimationFrame(() => {
|
|
152
|
+
|
|
153
|
+
boardInternal.onTransfer({
|
|
154
|
+
item,
|
|
155
|
+
fromContainerId: transfer.sourceId,
|
|
156
|
+
fromIndex: transfer.dragStartIndex,
|
|
157
|
+
toContainerId: transfer.targetId!,
|
|
158
|
+
toIndex: phantomIndex,
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
// 4. Wait for React render + Fabric commit, then clear hover.
|
|
162
|
+
requestAnimationFrame(() => {
|
|
163
|
+
|
|
164
|
+
requestAnimationFrame(() => {
|
|
165
|
+
|
|
166
|
+
if (cleanupGenRef.current !== gen) {
|
|
167
|
+
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
runOnUI((
|
|
172
|
+
_hoverReadySV: typeof hoverReadySV,
|
|
173
|
+
_dragPhaseSV: typeof dragPhaseSV,
|
|
174
|
+
_draggedIdSV: typeof draggedIdSV,
|
|
175
|
+
_hoverPositionSV: typeof hoverPositionSV,
|
|
176
|
+
_hoverDimsSV: typeof hoverDimsSV,
|
|
177
|
+
_setHoverContent: typeof setHoverContent,
|
|
178
|
+
) => {
|
|
179
|
+
'worklet';
|
|
180
|
+
_hoverReadySV.value = false;
|
|
181
|
+
_dragPhaseSV.value = 'idle';
|
|
182
|
+
_draggedIdSV.value = '';
|
|
183
|
+
_hoverPositionSV.value = { x: 0, y: 0 };
|
|
184
|
+
_hoverDimsSV.value = { x: 0, y: 0 };
|
|
185
|
+
runOnJS(_setHoverContent)(null);
|
|
186
|
+
})(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV, hoverDimsSV, setHoverContent);
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
};
|
|
191
|
+
useEffect(() => {
|
|
192
|
+
boardInternal.finalizeTransfer = () => finalizeTransferRef.current?.();
|
|
193
|
+
});
|
|
194
|
+
|
|
195
|
+
// ── Monitor callbacks ──────────────────────────────────────────────
|
|
196
|
+
|
|
197
|
+
const onMonitorDragStart = (eventData: DraxMonitorEventData) => {
|
|
198
|
+
draxViewProps?.onMonitorDragStart?.(eventData);
|
|
199
|
+
|
|
200
|
+
// Invalidate any pending deferred hover cleanup from a previous transfer
|
|
201
|
+
cleanupGenRef.current++;
|
|
202
|
+
|
|
203
|
+
// Reset hover dimensions from any previous cross-container transfer
|
|
204
|
+
hoverDimsSV.value = { x: 0, y: 0 };
|
|
205
|
+
|
|
206
|
+
const { dragged } = eventData;
|
|
207
|
+
const parentId = dragged.parentId;
|
|
208
|
+
if (!parentId || !columns.has(parentId)) return;
|
|
209
|
+
if (!isSortableItemPayload(dragged.payload)) return;
|
|
210
|
+
|
|
211
|
+
const sourceCol = columns.get(parentId)!;
|
|
212
|
+
const { originalIndex } = dragged.payload;
|
|
213
|
+
const item = sourceCol.rawData[originalIndex];
|
|
214
|
+
if (item === undefined) return;
|
|
215
|
+
|
|
216
|
+
const itemKey = sourceCol.keyExtractor(item, originalIndex);
|
|
217
|
+
const dims = dragged.measurements
|
|
218
|
+
? { width: dragged.measurements.width, height: dragged.measurements.height }
|
|
219
|
+
: { width: 0, height: 0 };
|
|
220
|
+
|
|
221
|
+
// Determine display index (accounting for committed visual order)
|
|
222
|
+
const committed = sourceCol.committedOrderRef.current;
|
|
223
|
+
let displayIndex = dragged.payload.index;
|
|
224
|
+
if (committed.length > 0) {
|
|
225
|
+
const pos = committed.indexOf(originalIndex);
|
|
226
|
+
if (pos >= 0) displayIndex = pos;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Set hover dimensions to source item size for cross-container dimension animation
|
|
230
|
+
if (dims.width > 0 && dims.height > 0) {
|
|
231
|
+
hoverDimsSV.value = { x: dims.width, y: dims.height };
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
sourceInfoRef.current = {
|
|
235
|
+
colId: parentId,
|
|
236
|
+
originalIndex,
|
|
237
|
+
itemKey,
|
|
238
|
+
dragStartIndex: displayIndex,
|
|
239
|
+
dimensions: dims,
|
|
240
|
+
};
|
|
241
|
+
};
|
|
242
|
+
|
|
243
|
+
const onMonitorDragOver = (eventData: DraxMonitorEventData) => {
|
|
244
|
+
draxViewProps?.onMonitorDragOver?.(eventData);
|
|
245
|
+
|
|
246
|
+
const source = sourceInfoRef.current;
|
|
247
|
+
if (!source) return;
|
|
248
|
+
|
|
249
|
+
const targetColId = findTargetColumn(eventData.dragAbsolutePosition);
|
|
250
|
+
const transfer = transferStateRef.current;
|
|
251
|
+
|
|
252
|
+
if (targetColId && targetColId !== source.colId) {
|
|
253
|
+
// ── Drag is over a different column → cross-container ──────
|
|
254
|
+
const targetCol = columns.get(targetColId);
|
|
255
|
+
if (!targetCol) return;
|
|
256
|
+
|
|
257
|
+
// Ensure target column's pending order is initialized for slot computation
|
|
258
|
+
if (targetCol.pendingOrderRef.current.length === 0) {
|
|
259
|
+
const committed = targetCol.committedOrderRef.current;
|
|
260
|
+
targetCol.pendingOrderRef.current = committed.length > 0
|
|
261
|
+
? [...committed]
|
|
262
|
+
: [...targetCol.originalIndexes];
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
const contentPos = toContentPos(eventData.dragAbsolutePosition, targetCol);
|
|
266
|
+
const insertIdx = targetCol.getSlotFromPosition(contentPos);
|
|
267
|
+
|
|
268
|
+
if (!transfer) {
|
|
269
|
+
// First time crossing — eject from source, set phantom in target
|
|
270
|
+
const sourceCol = columns.get(source.colId);
|
|
271
|
+
sourceCol?.ejectDraggedItem();
|
|
272
|
+
|
|
273
|
+
transferStateRef.current = {
|
|
274
|
+
sourceId: source.colId,
|
|
275
|
+
sourceOriginalIndex: source.originalIndex,
|
|
276
|
+
itemKey: source.itemKey,
|
|
277
|
+
itemDimensions: source.dimensions,
|
|
278
|
+
dragStartIndex: source.dragStartIndex,
|
|
279
|
+
targetId: targetColId,
|
|
280
|
+
targetSlot: insertIdx,
|
|
281
|
+
};
|
|
282
|
+
|
|
283
|
+
targetCol.setPhantomSlot(insertIdx, source.dimensions.width, source.dimensions.height);
|
|
284
|
+
} else if (transfer.targetId !== targetColId) {
|
|
285
|
+
// Crossed to a DIFFERENT target column — clear old target, set new
|
|
286
|
+
if (transfer.targetId) {
|
|
287
|
+
const prevTarget = columns.get(transfer.targetId);
|
|
288
|
+
prevTarget?.clearPhantomSlot();
|
|
289
|
+
}
|
|
290
|
+
transferStateRef.current = { ...transfer, targetId: targetColId, targetSlot: insertIdx };
|
|
291
|
+
targetCol.setPhantomSlot(insertIdx, source.dimensions.width, source.dimensions.height);
|
|
292
|
+
} else if (transfer.targetSlot !== insertIdx) {
|
|
293
|
+
// Same target column — update phantom position only if slot changed
|
|
294
|
+
transferStateRef.current = { ...transfer, targetSlot: insertIdx };
|
|
295
|
+
targetCol.setPhantomSlot(insertIdx, source.dimensions.width, source.dimensions.height);
|
|
296
|
+
}
|
|
297
|
+
} else if (targetColId === source.colId && transfer) {
|
|
298
|
+
// ── Drag returned to source column ─────────────────────────
|
|
299
|
+
// Clear target phantom
|
|
300
|
+
if (transfer.targetId) {
|
|
301
|
+
const prevTarget = columns.get(transfer.targetId);
|
|
302
|
+
prevTarget?.clearPhantomSlot();
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Reinject item into source
|
|
306
|
+
const sourceCol = columns.get(source.colId);
|
|
307
|
+
if (sourceCol) {
|
|
308
|
+
const contentPos = toContentPos(eventData.dragAbsolutePosition, sourceCol);
|
|
309
|
+
const insertIdx = sourceCol.pendingOrderRef.current.length > 0
|
|
310
|
+
? sourceCol.getSlotFromPosition(contentPos)
|
|
311
|
+
: source.dragStartIndex;
|
|
312
|
+
sourceCol.reinjectDraggedItem(insertIdx, source.originalIndex);
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
// Clear transfer state — source column's monitor handles from here
|
|
316
|
+
transferStateRef.current = undefined;
|
|
317
|
+
} else if (!targetColId && transfer) {
|
|
318
|
+
// ── Drag is outside all columns but transfer was active ────
|
|
319
|
+
if (transfer.targetId) {
|
|
320
|
+
const prevTarget = columns.get(transfer.targetId);
|
|
321
|
+
prevTarget?.clearPhantomSlot();
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// Reinject into source at original position
|
|
325
|
+
const sourceCol = columns.get(source.colId);
|
|
326
|
+
sourceCol?.reinjectDraggedItem(source.dragStartIndex, source.originalIndex);
|
|
327
|
+
|
|
328
|
+
transferStateRef.current = undefined;
|
|
329
|
+
}
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
const onMonitorDragEnd = (eventData: DraxMonitorEndEventData): DraxProtocolDragEndResponse => {
|
|
333
|
+
const transfer = transferStateRef.current;
|
|
334
|
+
const cancelled = eventData.cancelled;
|
|
335
|
+
|
|
336
|
+
if (transfer && transfer.targetId && !cancelled) {
|
|
337
|
+
// Successful drop during cross-container transfer — snap to phantom position
|
|
338
|
+
const targetCol = columns.get(transfer.targetId);
|
|
339
|
+
if (targetCol) {
|
|
340
|
+
const snapTarget = targetCol.getPhantomSnapTarget();
|
|
341
|
+
// Clear source info to prevent spurious re-triggers during snap animation
|
|
342
|
+
sourceInfoRef.current = undefined;
|
|
343
|
+
|
|
344
|
+
// Animate hover dimensions from source to target size during snap.
|
|
345
|
+
// This causes the hover content to reflow naturally (width/height change,
|
|
346
|
+
// not scale) so the card smoothly transitions to the target column's size.
|
|
347
|
+
const sourceDims = transfer.itemDimensions;
|
|
348
|
+
const firstItemMeas = targetCol.itemMeasurements.current.values().next().value;
|
|
349
|
+
if (sourceDims && firstItemMeas && sourceDims.width > 0 && sourceDims.height > 0) {
|
|
350
|
+
if (Math.abs(firstItemMeas.width - sourceDims.width) > 2 ||
|
|
351
|
+
Math.abs(firstItemMeas.height - sourceDims.height) > 2) {
|
|
352
|
+
const snapDelay = draxViewProps?.snapDelay ?? defaultSnapbackDelay;
|
|
353
|
+
const snapDuration = draxViewProps?.snapDuration ?? defaultSnapbackDuration;
|
|
354
|
+
hoverDimsSV.value = withDelay(snapDelay,
|
|
355
|
+
withTiming(
|
|
356
|
+
{ x: firstItemMeas.width, y: firstItemMeas.height },
|
|
357
|
+
{ duration: snapDuration },
|
|
358
|
+
),
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
draxViewProps?.onMonitorDragEnd?.(eventData);
|
|
364
|
+
return snapTarget;
|
|
365
|
+
}
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (transfer) {
|
|
369
|
+
// Cancelled or no target — clear phantom and reinject
|
|
370
|
+
if (transfer.targetId) {
|
|
371
|
+
const prevTarget = columns.get(transfer.targetId);
|
|
372
|
+
prevTarget?.clearPhantomSlot();
|
|
373
|
+
}
|
|
374
|
+
const sourceCol = columns.get(transfer.sourceId);
|
|
375
|
+
if (sourceCol) {
|
|
376
|
+
sourceCol.reinjectDraggedItem(
|
|
377
|
+
transfer.dragStartIndex,
|
|
378
|
+
transfer.sourceOriginalIndex,
|
|
379
|
+
);
|
|
380
|
+
}
|
|
381
|
+
transferStateRef.current = undefined;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
sourceInfoRef.current = undefined;
|
|
385
|
+
|
|
386
|
+
const provided = draxViewProps?.onMonitorDragEnd?.(eventData);
|
|
387
|
+
return provided;
|
|
388
|
+
};
|
|
389
|
+
|
|
390
|
+
const onMonitorDragDrop = (eventData: DraxMonitorDragDropEventData): DraxProtocolDragEndResponse => {
|
|
391
|
+
// For cross-container, drops go through onMonitorDragEnd (no receiver).
|
|
392
|
+
// This handles the case where a receiver exists.
|
|
393
|
+
const transfer = transferStateRef.current;
|
|
394
|
+
|
|
395
|
+
if (transfer && transfer.targetId) {
|
|
396
|
+
const targetCol = columns.get(transfer.targetId);
|
|
397
|
+
if (targetCol) {
|
|
398
|
+
const snapTarget = targetCol.getPhantomSnapTarget();
|
|
399
|
+
sourceInfoRef.current = undefined;
|
|
400
|
+
draxViewProps?.onMonitorDragDrop?.(eventData);
|
|
401
|
+
return snapTarget;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
sourceInfoRef.current = undefined;
|
|
406
|
+
return draxViewProps?.onMonitorDragDrop?.(eventData);
|
|
407
|
+
};
|
|
408
|
+
|
|
409
|
+
// ── Context value ──────────────────────────────────────────────────
|
|
410
|
+
|
|
411
|
+
// Pass boardInternal directly — NOT a copy. The useEffect above mutates
|
|
412
|
+
// boardInternal.finalizeTransfer after render. A snapshot copy would capture
|
|
413
|
+
// `undefined` and memoization would never update it.
|
|
414
|
+
// Cast needed: SortableBoardInternal<TItem> → SortableBoardInternal<unknown>
|
|
415
|
+
// is safe because consumers only read transferState and finalizeTransfer.
|
|
416
|
+
const contextValue: SortableBoardContextValue = {
|
|
417
|
+
registerColumn: boardInternal.registerColumn,
|
|
418
|
+
unregisterColumn: boardInternal.unregisterColumn,
|
|
419
|
+
boardInternal: boardInternal as SortableBoardInternal<unknown>,
|
|
420
|
+
};
|
|
421
|
+
|
|
422
|
+
return (
|
|
423
|
+
<SortableBoardContext.Provider value={contextValue}>
|
|
424
|
+
<DraxView
|
|
425
|
+
{...draxViewProps}
|
|
426
|
+
style={[draxViewProps?.style, style]}
|
|
427
|
+
draggable={false}
|
|
428
|
+
receptive={false}
|
|
429
|
+
monitoring
|
|
430
|
+
onMonitorDragStart={onMonitorDragStart}
|
|
431
|
+
onMonitorDragOver={onMonitorDragOver}
|
|
432
|
+
onMonitorDragEnd={onMonitorDragEnd}
|
|
433
|
+
onMonitorDragDrop={onMonitorDragDrop}
|
|
434
|
+
>
|
|
435
|
+
{children}
|
|
436
|
+
</DraxView>
|
|
437
|
+
</SortableBoardContext.Provider>
|
|
438
|
+
);
|
|
439
|
+
};
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
2
|
+
import type { SortableBoardContextValue } from './types';
|
|
3
|
+
|
|
4
|
+
export const SortableBoardContext = createContext<SortableBoardContextValue | undefined>(undefined);
|
|
5
|
+
|
|
6
|
+
export const useSortableBoardContext = () => useContext(SortableBoardContext);
|