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.
Files changed (246) hide show
  1. package/LICENSE.md +1 -1
  2. package/README.md +390 -227
  3. package/lib/module/DebugOverlay.js +121 -0
  4. package/lib/module/DebugOverlay.js.map +1 -0
  5. package/lib/module/Drax.js +36 -0
  6. package/lib/module/Drax.js.map +1 -0
  7. package/lib/module/DraxContext.js +6 -0
  8. package/lib/module/DraxContext.js.map +1 -0
  9. package/lib/module/DraxHandle.js +47 -0
  10. package/lib/module/DraxHandle.js.map +1 -0
  11. package/lib/module/DraxHandleContext.js +11 -0
  12. package/lib/module/DraxHandleContext.js.map +1 -0
  13. package/lib/module/DraxList.js +108 -0
  14. package/lib/module/DraxList.js.map +1 -0
  15. package/lib/module/DraxProvider.js +203 -0
  16. package/lib/module/DraxProvider.js.map +1 -0
  17. package/lib/module/DraxScrollView.js +167 -0
  18. package/lib/module/DraxScrollView.js.map +1 -0
  19. package/lib/module/DraxSubprovider.js +21 -0
  20. package/lib/module/DraxSubprovider.js.map +1 -0
  21. package/lib/module/DraxView.js +348 -0
  22. package/lib/module/DraxView.js.map +1 -0
  23. package/lib/module/HoverLayer.js +152 -0
  24. package/lib/module/HoverLayer.js.map +1 -0
  25. package/lib/module/SortableBoardContainer.js +386 -0
  26. package/lib/module/SortableBoardContainer.js.map +1 -0
  27. package/lib/module/SortableBoardContext.js +6 -0
  28. package/lib/module/SortableBoardContext.js.map +1 -0
  29. package/lib/module/SortableContainer.js +571 -0
  30. package/lib/module/SortableContainer.js.map +1 -0
  31. package/lib/module/SortableItem.js +226 -0
  32. package/lib/module/SortableItem.js.map +1 -0
  33. package/lib/module/SortableItemContext.js +38 -0
  34. package/lib/module/SortableItemContext.js.map +1 -0
  35. package/lib/module/compat/detectVersion.js +19 -0
  36. package/lib/module/compat/detectVersion.js.map +1 -0
  37. package/lib/module/compat/index.js +5 -0
  38. package/lib/module/compat/index.js.map +1 -0
  39. package/lib/module/compat/types.js +4 -0
  40. package/lib/module/compat/types.js.map +1 -0
  41. package/lib/module/compat/useDraxPanGesture.js +94 -0
  42. package/lib/module/compat/useDraxPanGesture.js.map +1 -0
  43. package/lib/module/hooks/index.js +5 -0
  44. package/lib/module/hooks/index.js.map +1 -0
  45. package/lib/module/hooks/useCallbackDispatch.js +688 -0
  46. package/lib/module/hooks/useCallbackDispatch.js.map +1 -0
  47. package/lib/module/hooks/useDragGesture.js +240 -0
  48. package/lib/module/hooks/useDragGesture.js.map +1 -0
  49. package/lib/module/hooks/useDraxContext.js +12 -0
  50. package/lib/module/hooks/useDraxContext.js.map +1 -0
  51. package/lib/module/hooks/useDraxId.js +13 -0
  52. package/lib/module/hooks/useDraxId.js.map +1 -0
  53. package/lib/module/hooks/useDraxMethods.js +73 -0
  54. package/lib/module/hooks/useDraxMethods.js.map +1 -0
  55. package/lib/module/hooks/useDraxScrollHandler.js +97 -0
  56. package/lib/module/hooks/useDraxScrollHandler.js.map +1 -0
  57. package/lib/module/hooks/useSortableBoard.js +37 -0
  58. package/lib/module/hooks/useSortableBoard.js.map +1 -0
  59. package/lib/module/hooks/useSortableList.js +988 -0
  60. package/lib/module/hooks/useSortableList.js.map +1 -0
  61. package/lib/module/hooks/useSpatialIndex.js +283 -0
  62. package/lib/module/hooks/useSpatialIndex.js.map +1 -0
  63. package/lib/module/hooks/useViewStyles.js +158 -0
  64. package/lib/module/hooks/useViewStyles.js.map +1 -0
  65. package/lib/module/hooks/useWebScrollFreeze.js +52 -0
  66. package/lib/module/hooks/useWebScrollFreeze.js.map +1 -0
  67. package/lib/module/index.js +37 -0
  68. package/lib/module/index.js.map +1 -0
  69. package/lib/module/math.js +294 -0
  70. package/lib/module/math.js.map +1 -0
  71. package/lib/module/package.json +1 -0
  72. package/lib/module/params.js +88 -0
  73. package/lib/module/params.js.map +1 -0
  74. package/lib/module/types.js +215 -0
  75. package/lib/module/types.js.map +1 -0
  76. package/lib/typescript/package.json +1 -0
  77. package/lib/typescript/src/DebugOverlay.d.ts +17 -0
  78. package/lib/typescript/src/DebugOverlay.d.ts.map +1 -0
  79. package/lib/typescript/src/Drax.d.ts +28 -0
  80. package/lib/typescript/src/Drax.d.ts.map +1 -0
  81. package/lib/typescript/src/DraxContext.d.ts +3 -0
  82. package/lib/typescript/src/DraxContext.d.ts.map +1 -0
  83. package/lib/typescript/src/DraxHandle.d.ts +25 -0
  84. package/lib/typescript/src/DraxHandle.d.ts.map +1 -0
  85. package/lib/typescript/src/DraxHandleContext.d.ts +12 -0
  86. package/lib/typescript/src/DraxHandleContext.d.ts.map +1 -0
  87. package/lib/typescript/src/DraxList.d.ts +66 -0
  88. package/lib/typescript/src/DraxList.d.ts.map +1 -0
  89. package/lib/typescript/src/DraxProvider.d.ts +4 -0
  90. package/lib/typescript/src/DraxProvider.d.ts.map +1 -0
  91. package/lib/typescript/src/DraxScrollView.d.ts +7 -0
  92. package/lib/typescript/src/DraxScrollView.d.ts.map +1 -0
  93. package/lib/typescript/src/DraxSubprovider.d.ts +4 -0
  94. package/lib/typescript/src/DraxSubprovider.d.ts.map +1 -0
  95. package/lib/typescript/src/DraxView.d.ts +4 -0
  96. package/lib/typescript/src/DraxView.d.ts.map +1 -0
  97. package/lib/typescript/src/HoverLayer.d.ts +38 -0
  98. package/lib/typescript/src/HoverLayer.d.ts.map +1 -0
  99. package/lib/typescript/src/SortableBoardContainer.d.ts +11 -0
  100. package/lib/typescript/src/SortableBoardContainer.d.ts.map +1 -0
  101. package/lib/typescript/src/SortableBoardContext.d.ts +4 -0
  102. package/lib/typescript/src/SortableBoardContext.d.ts.map +1 -0
  103. package/lib/typescript/src/SortableContainer.d.ts +13 -0
  104. package/lib/typescript/src/SortableContainer.d.ts.map +1 -0
  105. package/lib/typescript/src/SortableItem.d.ts +14 -0
  106. package/lib/typescript/src/SortableItem.d.ts.map +1 -0
  107. package/lib/typescript/src/SortableItemContext.d.ts +37 -0
  108. package/lib/typescript/src/SortableItemContext.d.ts.map +1 -0
  109. package/lib/typescript/src/compat/detectVersion.d.ts +2 -0
  110. package/lib/typescript/src/compat/detectVersion.d.ts.map +1 -0
  111. package/lib/typescript/src/compat/index.d.ts +4 -0
  112. package/lib/typescript/src/compat/index.d.ts.map +1 -0
  113. package/lib/typescript/src/compat/types.d.ts +33 -0
  114. package/lib/typescript/src/compat/types.d.ts.map +1 -0
  115. package/lib/typescript/src/compat/useDraxPanGesture.d.ts +8 -0
  116. package/lib/typescript/src/compat/useDraxPanGesture.d.ts.map +1 -0
  117. package/lib/typescript/src/hooks/index.d.ts +3 -0
  118. package/lib/typescript/src/hooks/index.d.ts.map +1 -0
  119. package/lib/typescript/src/hooks/useCallbackDispatch.d.ts +40 -0
  120. package/lib/typescript/src/hooks/useCallbackDispatch.d.ts.map +1 -0
  121. package/lib/typescript/src/hooks/useDragGesture.d.ts +17 -0
  122. package/lib/typescript/src/hooks/useDragGesture.d.ts.map +1 -0
  123. package/lib/typescript/src/hooks/useDraxContext.d.ts +2 -0
  124. package/lib/typescript/src/hooks/useDraxContext.d.ts.map +1 -0
  125. package/{build → lib/typescript/src}/hooks/useDraxId.d.ts +1 -0
  126. package/lib/typescript/src/hooks/useDraxId.d.ts.map +1 -0
  127. package/lib/typescript/src/hooks/useDraxMethods.d.ts +13 -0
  128. package/lib/typescript/src/hooks/useDraxMethods.d.ts.map +1 -0
  129. package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts +27 -0
  130. package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts.map +1 -0
  131. package/lib/typescript/src/hooks/useSortableBoard.d.ts +10 -0
  132. package/lib/typescript/src/hooks/useSortableBoard.d.ts.map +1 -0
  133. package/lib/typescript/src/hooks/useSortableList.d.ts +11 -0
  134. package/lib/typescript/src/hooks/useSortableList.d.ts.map +1 -0
  135. package/lib/typescript/src/hooks/useSpatialIndex.d.ts +22 -0
  136. package/lib/typescript/src/hooks/useSpatialIndex.d.ts.map +1 -0
  137. package/lib/typescript/src/hooks/useViewStyles.d.ts +183 -0
  138. package/lib/typescript/src/hooks/useViewStyles.d.ts.map +1 -0
  139. package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts +14 -0
  140. package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts.map +1 -0
  141. package/lib/typescript/src/index.d.ts +25 -0
  142. package/lib/typescript/src/index.d.ts.map +1 -0
  143. package/lib/typescript/src/math.d.ts +76 -0
  144. package/lib/typescript/src/math.d.ts.map +1 -0
  145. package/{build → lib/typescript/src}/params.d.ts +13 -9
  146. package/lib/typescript/src/params.d.ts.map +1 -0
  147. package/lib/typescript/src/types.d.ts +756 -0
  148. package/lib/typescript/src/types.d.ts.map +1 -0
  149. package/package.json +164 -34
  150. package/src/DebugOverlay.tsx +140 -0
  151. package/src/Drax.ts +33 -0
  152. package/src/DraxContext.ts +8 -0
  153. package/src/DraxHandle.tsx +52 -0
  154. package/src/DraxHandleContext.ts +15 -0
  155. package/src/DraxList.tsx +181 -0
  156. package/src/DraxProvider.tsx +224 -0
  157. package/src/DraxScrollView.tsx +180 -0
  158. package/src/DraxSubprovider.tsx +22 -0
  159. package/src/DraxView.tsx +430 -0
  160. package/src/HoverLayer.tsx +167 -0
  161. package/src/SortableBoardContainer.tsx +439 -0
  162. package/src/SortableBoardContext.ts +6 -0
  163. package/src/SortableContainer.tsx +650 -0
  164. package/src/SortableItem.tsx +264 -0
  165. package/src/SortableItemContext.ts +46 -0
  166. package/src/compat/detectVersion.ts +17 -0
  167. package/src/compat/index.ts +7 -0
  168. package/src/compat/types.ts +35 -0
  169. package/src/compat/useDraxPanGesture.ts +112 -0
  170. package/src/hooks/index.ts +2 -0
  171. package/src/hooks/useCallbackDispatch.tsx +830 -0
  172. package/src/hooks/useDragGesture.ts +273 -0
  173. package/src/hooks/useDraxContext.ts +11 -0
  174. package/src/hooks/useDraxId.ts +11 -0
  175. package/src/hooks/useDraxMethods.ts +71 -0
  176. package/src/hooks/useDraxScrollHandler.ts +121 -0
  177. package/src/hooks/useSortableBoard.ts +44 -0
  178. package/src/hooks/useSortableList.ts +1063 -0
  179. package/src/hooks/useSpatialIndex.ts +336 -0
  180. package/src/hooks/useViewStyles.ts +180 -0
  181. package/src/hooks/useWebScrollFreeze.ts +60 -0
  182. package/src/index.ts +111 -0
  183. package/src/math.ts +333 -0
  184. package/src/params.ts +74 -0
  185. package/src/types.ts +933 -0
  186. package/.editorconfig +0 -15
  187. package/.eslintrc.js +0 -4
  188. package/.prettierrc +0 -16
  189. package/CHANGELOG.md +0 -270
  190. package/CODE-OF-CONDUCT.md +0 -85
  191. package/CONTRIBUTING.md +0 -15
  192. package/FUNDING.yml +0 -4
  193. package/build/AllHoverViews.d.ts +0 -0
  194. package/build/AllHoverViews.js +0 -30
  195. package/build/DraxContext.d.ts +0 -2
  196. package/build/DraxContext.js +0 -6
  197. package/build/DraxList.d.ts +0 -8
  198. package/build/DraxList.js +0 -512
  199. package/build/DraxListItem.d.ts +0 -7
  200. package/build/DraxListItem.js +0 -121
  201. package/build/DraxProvider.d.ts +0 -2
  202. package/build/DraxProvider.js +0 -704
  203. package/build/DraxScrollView.d.ts +0 -6
  204. package/build/DraxScrollView.js +0 -136
  205. package/build/DraxSubprovider.d.ts +0 -3
  206. package/build/DraxSubprovider.js +0 -18
  207. package/build/DraxView.d.ts +0 -8
  208. package/build/DraxView.js +0 -93
  209. package/build/HoverView.d.ts +0 -8
  210. package/build/HoverView.js +0 -40
  211. package/build/PanGestureDetector.d.ts +0 -3
  212. package/build/PanGestureDetector.js +0 -49
  213. package/build/hooks/index.d.ts +0 -4
  214. package/build/hooks/index.js +0 -11
  215. package/build/hooks/useContent.d.ts +0 -23
  216. package/build/hooks/useContent.js +0 -212
  217. package/build/hooks/useDraxContext.d.ts +0 -1
  218. package/build/hooks/useDraxContext.js +0 -13
  219. package/build/hooks/useDraxId.js +0 -13
  220. package/build/hooks/useDraxProtocol.d.ts +0 -5
  221. package/build/hooks/useDraxProtocol.js +0 -32
  222. package/build/hooks/useDraxRegistry.d.ts +0 -78
  223. package/build/hooks/useDraxRegistry.js +0 -714
  224. package/build/hooks/useDraxScrollHandler.d.ts +0 -25
  225. package/build/hooks/useDraxScrollHandler.js +0 -89
  226. package/build/hooks/useDraxState.d.ts +0 -10
  227. package/build/hooks/useDraxState.js +0 -132
  228. package/build/hooks/useMeasurements.d.ts +0 -9
  229. package/build/hooks/useMeasurements.js +0 -119
  230. package/build/hooks/useStatus.d.ts +0 -11
  231. package/build/hooks/useStatus.js +0 -96
  232. package/build/index.d.ts +0 -9
  233. package/build/index.js +0 -33
  234. package/build/math.d.ts +0 -22
  235. package/build/math.js +0 -68
  236. package/build/params.js +0 -27
  237. package/build/transform.d.ts +0 -11
  238. package/build/transform.js +0 -59
  239. package/build/types.d.ts +0 -807
  240. package/build/types.js +0 -46
  241. package/docs/concept.md +0 -79
  242. package/docs/images/color-drag-drop.gif +0 -0
  243. package/docs/images/deck-cards.gif +0 -0
  244. package/docs/images/drag-drop-events.jpg +0 -0
  245. package/docs/images/knight-moves.gif +0 -0
  246. 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);