react-native-drax 0.11.0-alpha.2 → 1.0.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 +385 -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 +561 -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 +681 -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 +824 -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 +222 -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 +213 -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 +52 -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 +743 -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 +642 -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 +823 -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 +868 -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 +110 -0
  183. package/src/math.ts +251 -0
  184. package/src/params.ts +74 -0
  185. package/src/types.ts +919 -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,681 @@
1
+ "use strict";
2
+
3
+ import { useRef } from 'react';
4
+ import { StyleSheet, View } from 'react-native';
5
+ import { withDelay, withTiming } from 'react-native-reanimated';
6
+ import { runOnJS, runOnUI } from 'react-native-worklets';
7
+ import { computeAbsolutePositionWorklet, getRelativePosition } from "../math.js";
8
+ import { defaultSnapbackDelay, defaultSnapbackDuration } from "../params.js";
9
+ import { DraxSnapbackTargetPreset, DraxViewDragStatus, DraxViewReceiveStatus, isPosition } from "../types.js";
10
+ import { isDraggable } from "./useSpatialIndex.js";
11
+
12
+ /** Style override to strip margins — hover is positioned via translateX/Y */
13
+ import { jsx as _jsx } from "react/jsx-runtime";
14
+ const noMargin = {
15
+ margin: 0,
16
+ marginHorizontal: 0,
17
+ marginVertical: 0,
18
+ marginTop: 0,
19
+ marginBottom: 0,
20
+ marginLeft: 0,
21
+ marginRight: 0
22
+ };
23
+ /**
24
+ * Provides JS-thread callback dispatch functions that are invoked via runOnJS
25
+ * from gesture worklets. These handle ~5 calls per drag (start, receiver changes, end),
26
+ * NOT per frame.
27
+ */
28
+ export const useCallbackDispatch = deps => {
29
+ const {
30
+ getViewEntry,
31
+ spatialIndexSV,
32
+ scrollOffsetsSV,
33
+ draggedIdSV,
34
+ dragPhaseSV,
35
+ hoverPositionSV,
36
+ grabOffsetSV,
37
+ startPositionSV,
38
+ setHoverContent,
39
+ hoverReadySV,
40
+ onProviderDragStart,
41
+ onProviderDrag,
42
+ onProviderDragEnd,
43
+ droppedItemsRef
44
+ } = deps;
45
+
46
+ // Track current monitor ids for exit events
47
+ const currentMonitorIdsRef = useRef([]);
48
+
49
+ /** Build dragged view event data from current state */
50
+ const buildDraggedViewData = (draggedId, absolutePosition) => {
51
+ const entry = getViewEntry(draggedId);
52
+ if (!entry) return undefined;
53
+ const startPos = startPositionSV.value;
54
+ const grabOffset = grabOffsetSV.value;
55
+ const dragTranslation = {
56
+ x: absolutePosition.x - startPos.x,
57
+ y: absolutePosition.y - startPos.y
58
+ };
59
+ const measurements = entry.measurements;
60
+ // Use || instead of ?? intentionally: zero dimensions would cause division by zero below
61
+ const width = measurements?.width || 1;
62
+ const height = measurements?.height || 1;
63
+ return {
64
+ id: draggedId,
65
+ parentId: entry.parentId,
66
+ payload: entry.props.dragPayload ?? entry.props.payload,
67
+ measurements,
68
+ dragTranslationRatio: {
69
+ x: dragTranslation.x / width,
70
+ y: dragTranslation.y / height
71
+ },
72
+ dragOffset: {
73
+ x: absolutePosition.x - (measurements?.x ?? 0),
74
+ y: absolutePosition.y - (measurements?.y ?? 0)
75
+ },
76
+ grabOffset,
77
+ grabOffsetRatio: {
78
+ x: grabOffset.x / width,
79
+ y: grabOffset.y / height
80
+ },
81
+ hoverPosition: hoverPositionSV.value
82
+ };
83
+ };
84
+
85
+ /** Build receiver view event data */
86
+ const buildReceiverViewData = (receiverId, absolutePosition) => {
87
+ const entry = getViewEntry(receiverId);
88
+ if (!entry?.measurements) return undefined;
89
+
90
+ // Compute absolute measurements of receiver
91
+ const idx = entry.spatialIndex;
92
+ const entries = spatialIndexSV.value;
93
+ const offsets = scrollOffsetsSV.value;
94
+ const absPos = computeAbsolutePositionWorklet(idx, entries, offsets);
95
+ const absMeasurements = {
96
+ ...absPos,
97
+ width: entry.measurements.width,
98
+ height: entry.measurements.height,
99
+ _transformDetected: 0
100
+ };
101
+ const {
102
+ relativePosition,
103
+ relativePositionRatio
104
+ } = getRelativePosition(absolutePosition, absMeasurements);
105
+ return {
106
+ id: receiverId,
107
+ parentId: entry.parentId,
108
+ payload: entry.props.receiverPayload ?? entry.props.payload,
109
+ measurements: entry.measurements,
110
+ receiveOffset: relativePosition,
111
+ receiveOffsetRatio: relativePositionRatio
112
+ };
113
+ };
114
+
115
+ /** Called via runOnJS when drag starts */
116
+ const handleDragStart = (draggedId, absolutePosition, _grabOffset) => {
117
+ const draggedEntry = getViewEntry(draggedId);
118
+ if (!draggedEntry) return;
119
+ const dragged = buildDraggedViewData(draggedId, absolutePosition);
120
+ if (!dragged) return;
121
+ const startPos = startPositionSV.value;
122
+ const dragTranslation = {
123
+ x: absolutePosition.x - startPos.x,
124
+ y: absolutePosition.y - startPos.y
125
+ };
126
+
127
+ // Fire onDragStart callback
128
+ draggedEntry.props.onDragStart?.({
129
+ dragAbsolutePosition: absolutePosition,
130
+ dragTranslation,
131
+ dragged
132
+ });
133
+
134
+ // Setup hover styles — set BEFORE setHoverContent so HoverLayer
135
+ // captures them when it re-renders on hoverVersion change.
136
+ deps.hoverStylesRef.current = {
137
+ hoverStyle: flattenOrNull(draggedEntry.props.hoverStyle),
138
+ hoverDraggingStyle: flattenOrNull(draggedEntry.props.hoverDraggingStyle),
139
+ hoverDraggingWithReceiverStyle: flattenOrNull(draggedEntry.props.hoverDraggingWithReceiverStyle),
140
+ hoverDraggingWithoutReceiverStyle: flattenOrNull(draggedEntry.props.hoverDraggingWithoutReceiverStyle),
141
+ hoverDragReleasedStyle: flattenOrNull(draggedEntry.props.hoverDragReleasedStyle)
142
+ };
143
+
144
+ // Setup hover content
145
+ if (isDraggable(draggedEntry.props) && !draggedEntry.props.noHover) {
146
+ const renderFn = draggedEntry.props.renderHoverContent ?? draggedEntry.props.renderContent;
147
+ if (renderFn) {
148
+ const content = renderFn({
149
+ viewState: {
150
+ dragStatus: DraxViewDragStatus.Dragging,
151
+ receiveStatus: DraxViewReceiveStatus.Inactive,
152
+ grabOffset: dragged.grabOffset,
153
+ grabOffsetRatio: dragged.grabOffsetRatio
154
+ },
155
+ trackingStatus: {
156
+ dragging: true,
157
+ receiving: false
158
+ },
159
+ hover: true,
160
+ children: null,
161
+ dimensions: draggedEntry.measurements ? {
162
+ width: draggedEntry.measurements.width,
163
+ height: draggedEntry.measurements.height
164
+ } : undefined
165
+ });
166
+ setHoverContent(content);
167
+ } else {
168
+ // Default hover: wrap children with original view style and dimensions.
169
+ // Strip margins since hover is positioned via translateX/Y.
170
+ const dims = draggedEntry.measurements;
171
+ const viewStyle = draggedEntry.props.style;
172
+ setHoverContent(/*#__PURE__*/_jsx(View, {
173
+ style: [viewStyle, dims && {
174
+ width: dims.width,
175
+ height: dims.height
176
+ }, noMargin],
177
+ children: draggedEntry.props.children
178
+ }));
179
+ }
180
+ }
181
+
182
+ // Phase activation is handled by HoverLayer's useLayoutEffect — it fires
183
+ // AFTER React commits the hover content, ensuring both opacity:1 and
184
+ // draggingStyle apply on the same frame. See HoverLayer.tsx.
185
+
186
+ // Fire provider-level onDragStart
187
+ onProviderDragStart?.({
188
+ draggedId,
189
+ position: absolutePosition
190
+ });
191
+
192
+ // Fire monitor onMonitorDragStart callbacks
193
+ currentMonitorIdsRef.current = [];
194
+ };
195
+
196
+ /** Called via runOnJS on every gesture update for callback dispatch.
197
+ * Handles: enter/exit (on receiver change), onDragOver/onReceiveDragOver
198
+ * (continuous, same receiver), onDrag (continuous, no receiver), and monitors. */
199
+ const handleReceiverChange = (oldReceiverId, newReceiverId, absolutePosition, monitorIds) => {
200
+ const draggedId = draggedIdSV.value;
201
+
202
+ // Fast path: receiver unchanged, no monitors (now AND previously),
203
+ // and no continuous callbacks → skip event data construction entirely.
204
+ const newMonitorIds = monitorIds ?? [];
205
+ const prevMonitorIds = currentMonitorIdsRef.current;
206
+ if (oldReceiverId === newReceiverId && newMonitorIds.length === 0 && prevMonitorIds.length === 0) {
207
+ const draggedEntry = getViewEntry(draggedId);
208
+ if (!draggedEntry) return;
209
+ const hasOnDragOver = newReceiverId && draggedEntry.props.onDragOver;
210
+ const receiverEntry = newReceiverId ? getViewEntry(newReceiverId) : undefined;
211
+ const hasOnReceiveDragOver = newReceiverId && receiverEntry?.props.onReceiveDragOver;
212
+ const hasOnDrag = !newReceiverId && (draggedEntry.props.onDrag || onProviderDrag);
213
+ if (!hasOnDragOver && !hasOnReceiveDragOver && !hasOnDrag) return;
214
+ }
215
+ const dragged = buildDraggedViewData(draggedId, absolutePosition);
216
+ if (!dragged) return;
217
+ const draggedEntry = getViewEntry(draggedId);
218
+ const draggedPayload = draggedEntry?.props.dragPayload ?? draggedEntry?.props.payload;
219
+ const startPos = startPositionSV.value;
220
+ const dragTranslation = {
221
+ x: absolutePosition.x - startPos.x,
222
+ y: absolutePosition.y - startPos.y
223
+ };
224
+ const baseEventData = {
225
+ dragAbsolutePosition: absolutePosition,
226
+ dragTranslation,
227
+ dragged
228
+ };
229
+
230
+ // ── Check dynamicReceptiveCallback / acceptsDrag on new receiver ──
231
+ let acceptedReceiverId = newReceiverId;
232
+ if (newReceiverId && oldReceiverId !== newReceiverId) {
233
+ const newReceiverEntry = getViewEntry(newReceiverId);
234
+ if (newReceiverEntry) {
235
+ // Check acceptsDrag first (simpler convenience prop)
236
+ const acceptsDrag = newReceiverEntry.props.acceptsDrag;
237
+ if (acceptsDrag && !acceptsDrag(draggedPayload)) {
238
+ acceptedReceiverId = '';
239
+ }
240
+
241
+ // Check capacity
242
+ const capacity = newReceiverEntry.props.capacity;
243
+ if (acceptedReceiverId && capacity !== undefined) {
244
+ const droppedSet = droppedItemsRef.current.get(newReceiverId);
245
+ const count = droppedSet ? droppedSet.size : 0;
246
+ if (count >= capacity) {
247
+ acceptedReceiverId = '';
248
+ }
249
+ }
250
+
251
+ // Check dynamicReceptiveCallback (more detailed)
252
+ const dynamicCallback = newReceiverEntry.props.dynamicReceptiveCallback;
253
+ if (acceptedReceiverId && dynamicCallback && newReceiverEntry.measurements) {
254
+ const accepted = dynamicCallback({
255
+ targetId: newReceiverId,
256
+ targetMeasurements: newReceiverEntry.measurements,
257
+ draggedId,
258
+ draggedPayload
259
+ });
260
+ if (!accepted) {
261
+ acceptedReceiverId = '';
262
+ }
263
+ }
264
+ }
265
+
266
+ // If rejected, tell the gesture worklet to skip this receiver on future frames.
267
+ // Also clear receiverIdSV so animated styles don't flash the receiving state.
268
+ if (!acceptedReceiverId) {
269
+ runOnUI((_receiverIdSV, _rejectedReceiverIdSV, _rejectedId) => {
270
+ 'worklet';
271
+
272
+ _receiverIdSV.value = '';
273
+ _rejectedReceiverIdSV.value = _rejectedId;
274
+ })(deps.receiverIdSV, deps.rejectedReceiverIdSV, newReceiverId);
275
+ }
276
+ }
277
+
278
+ // Fire exit on old receiver (only when receiver actually changed)
279
+ if (oldReceiverId && oldReceiverId !== acceptedReceiverId) {
280
+ const oldReceiverEntry = getViewEntry(oldReceiverId);
281
+ const oldReceiverData = buildReceiverViewData(oldReceiverId, absolutePosition);
282
+ if (oldReceiverEntry && oldReceiverData) {
283
+ // Dragged view: onDragExit
284
+ draggedEntry?.props.onDragExit?.({
285
+ ...baseEventData,
286
+ receiver: oldReceiverData
287
+ });
288
+
289
+ // Receiver view: onReceiveDragExit
290
+ oldReceiverEntry.props.onReceiveDragExit?.({
291
+ ...baseEventData,
292
+ receiver: oldReceiverData,
293
+ cancelled: false
294
+ });
295
+ }
296
+ }
297
+
298
+ // Fire enter on new receiver (only when receiver actually changed)
299
+ if (acceptedReceiverId && oldReceiverId !== acceptedReceiverId) {
300
+ const newReceiverEntry = getViewEntry(acceptedReceiverId);
301
+ const newReceiverData = buildReceiverViewData(acceptedReceiverId, absolutePosition);
302
+ if (newReceiverEntry && newReceiverData) {
303
+ // Dragged view: onDragEnter
304
+ draggedEntry?.props.onDragEnter?.({
305
+ ...baseEventData,
306
+ receiver: newReceiverData
307
+ });
308
+
309
+ // Receiver view: onReceiveDragEnter
310
+ newReceiverEntry.props.onReceiveDragEnter?.({
311
+ ...baseEventData,
312
+ receiver: newReceiverData
313
+ });
314
+ }
315
+ }
316
+
317
+ // ── Continuous callbacks: onDragOver / onReceiveDragOver / onDrag ──
318
+ if (acceptedReceiverId && oldReceiverId === acceptedReceiverId) {
319
+ // Dragging over the same receiver — fire onDragOver + onReceiveDragOver
320
+ const receiverEntry = getViewEntry(acceptedReceiverId);
321
+ const receiverData = buildReceiverViewData(acceptedReceiverId, absolutePosition);
322
+ if (receiverEntry && receiverData) {
323
+ draggedEntry?.props.onDragOver?.({
324
+ ...baseEventData,
325
+ receiver: receiverData
326
+ });
327
+ receiverEntry.props.onReceiveDragOver?.({
328
+ ...baseEventData,
329
+ receiver: receiverData
330
+ });
331
+ }
332
+ } else if (!acceptedReceiverId) {
333
+ // No receiver — fire onDrag (continuous, not over any receiver)
334
+ draggedEntry?.props.onDrag?.(baseEventData);
335
+ }
336
+
337
+ // ── Dispatch monitor events ──────────────────────────────────────
338
+ const prevWasEmpty = prevMonitorIds.length === 0;
339
+
340
+ // Build receiver data for monitor event payload (use accepted receiver, not raw hit-test)
341
+ const receiverData = acceptedReceiverId ? buildReceiverViewData(acceptedReceiverId, absolutePosition) : undefined;
342
+
343
+ // Fire events on current monitors (start/enter before over)
344
+ for (const monitorId of newMonitorIds) {
345
+ const monitorEntry = getViewEntry(monitorId);
346
+ if (!monitorEntry?.measurements) continue;
347
+ const {
348
+ relativePosition: monitorOffset,
349
+ relativePositionRatio: monitorOffsetRatio
350
+ } = getRelativePosition(absolutePosition, monitorEntry.measurements);
351
+ const monitorEventData = {
352
+ ...baseEventData,
353
+ receiver: receiverData,
354
+ monitorOffset,
355
+ monitorOffsetRatio
356
+ };
357
+ const isNew = !prevMonitorIds.includes(monitorId);
358
+
359
+ // First time we see any monitor after drag start → fire onMonitorDragStart
360
+ if (isNew && prevWasEmpty) {
361
+ monitorEntry.props.onMonitorDragStart?.(monitorEventData);
362
+ }
363
+
364
+ // New monitor → fire onMonitorDragEnter
365
+ if (isNew) {
366
+ monitorEntry.props.onMonitorDragEnter?.(monitorEventData);
367
+ }
368
+
369
+ // All current monitors → fire onMonitorDragOver
370
+ monitorEntry.props.onMonitorDragOver?.(monitorEventData);
371
+ }
372
+
373
+ // Fire exit on monitors that are no longer hit
374
+ for (const prevMonitorId of prevMonitorIds) {
375
+ if (newMonitorIds.includes(prevMonitorId)) continue;
376
+ const monitorEntry = getViewEntry(prevMonitorId);
377
+ if (!monitorEntry?.measurements) continue;
378
+ const {
379
+ relativePosition: monitorOffset,
380
+ relativePositionRatio: monitorOffsetRatio
381
+ } = getRelativePosition(absolutePosition, monitorEntry.measurements);
382
+ monitorEntry.props.onMonitorDragExit?.({
383
+ ...baseEventData,
384
+ receiver: receiverData,
385
+ monitorOffset,
386
+ monitorOffsetRatio
387
+ });
388
+ }
389
+ currentMonitorIdsRef.current = newMonitorIds;
390
+
391
+ // Fire provider-level onDrag (use acceptedReceiverId, not raw newReceiverId)
392
+ onProviderDrag?.({
393
+ draggedId: draggedIdSV.value,
394
+ receiverId: acceptedReceiverId || undefined,
395
+ position: absolutePosition
396
+ });
397
+ };
398
+
399
+ /** Called via runOnJS when drag ends or is cancelled */
400
+ const handleDragEnd = (draggedId, receiverId, cancelled, finalMonitorIds) => {
401
+ // receiverIdSV is already cleared on the UI thread in onDeactivate/onFinalize,
402
+ // so the receiver's animated style resets immediately.
403
+
404
+ const draggedEntry = getViewEntry(draggedId);
405
+ if (!draggedEntry) {
406
+ // Reset drag state atomically on UI thread to avoid one-frame flash
407
+ runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
408
+ 'worklet';
409
+
410
+ _hoverReadySV.value = false;
411
+ _dragPhaseSV.value = 'idle';
412
+ _draggedIdSV.value = '';
413
+ _hoverPositionSV.value = {
414
+ x: 0,
415
+ y: 0
416
+ };
417
+ })(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
418
+ setHoverContent(null);
419
+ return;
420
+ }
421
+ const absolutePosition = {
422
+ ...hoverPositionSV.value
423
+ };
424
+ const dragged = buildDraggedViewData(draggedId, absolutePosition);
425
+ if (!dragged) {
426
+ runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
427
+ 'worklet';
428
+
429
+ _hoverReadySV.value = false;
430
+ _dragPhaseSV.value = 'idle';
431
+ _draggedIdSV.value = '';
432
+ _hoverPositionSV.value = {
433
+ x: 0,
434
+ y: 0
435
+ };
436
+ })(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
437
+ setHoverContent(null);
438
+ return;
439
+ }
440
+ const startPos = startPositionSV.value;
441
+ const dragTranslation = {
442
+ x: absolutePosition.x - startPos.x,
443
+ y: absolutePosition.y - startPos.y
444
+ };
445
+ const baseEventData = {
446
+ dragAbsolutePosition: absolutePosition,
447
+ dragTranslation,
448
+ dragged
449
+ };
450
+ let snapTarget = DraxSnapbackTargetPreset.Default;
451
+ if (receiverId && !cancelled) {
452
+ // Successful drop — default snap to receiver position
453
+ const receiverEntry = getViewEntry(receiverId);
454
+ const receiverData = buildReceiverViewData(receiverId, absolutePosition);
455
+ if (receiverData && receiverEntry) {
456
+ // Compute receiver's absolute position and center the dragged item within it
457
+ const receiverAbsPos = computeAbsolutePositionWorklet(receiverEntry.spatialIndex, spatialIndexSV.value, scrollOffsetsSV.value);
458
+ const draggedDims = draggedEntry.measurements;
459
+ const receiverDims = receiverEntry.measurements;
460
+ if (receiverDims && draggedDims) {
461
+ snapTarget = {
462
+ x: receiverAbsPos.x + (receiverDims.width - draggedDims.width) / 2,
463
+ y: receiverAbsPos.y + (receiverDims.height - draggedDims.height) / 2
464
+ };
465
+ } else {
466
+ snapTarget = receiverAbsPos;
467
+ }
468
+
469
+ // Fire onDragDrop on dragged (can override snap target)
470
+ const dragDropResponse = draggedEntry.props.onDragDrop?.({
471
+ ...baseEventData,
472
+ receiver: receiverData
473
+ });
474
+ if (dragDropResponse !== undefined) snapTarget = dragDropResponse;
475
+
476
+ // Fire onReceiveDragDrop on receiver (can override snap target)
477
+ const receiveDropResponse = receiverEntry.props.onReceiveDragDrop?.({
478
+ ...baseEventData,
479
+ receiver: receiverData
480
+ });
481
+ if (receiveDropResponse !== undefined) snapTarget = receiveDropResponse;
482
+
483
+ // Track the drop for capacity enforcement
484
+ if (!droppedItemsRef.current.has(receiverId)) {
485
+ droppedItemsRef.current.set(receiverId, new Set());
486
+ }
487
+ droppedItemsRef.current.get(receiverId).add(draggedId);
488
+ }
489
+ } else {
490
+ // No receiver or cancelled
491
+ const dragEndResponse = draggedEntry.props.onDragEnd?.({
492
+ ...baseEventData,
493
+ cancelled
494
+ });
495
+ if (dragEndResponse !== undefined) snapTarget = dragEndResponse;
496
+ }
497
+
498
+ // Fire monitor end events — use final hit-test monitors from onDeactivate
499
+ // if available, falling back to tracked monitors from receiver changes.
500
+ const monitorIdsToUse = finalMonitorIds ?? currentMonitorIdsRef.current;
501
+ for (const monitorId of monitorIdsToUse) {
502
+ const monitorEntry = getViewEntry(monitorId);
503
+ if (!monitorEntry?.measurements) continue;
504
+ const {
505
+ relativePosition: monitorOffset,
506
+ relativePositionRatio: monitorOffsetRatio
507
+ } = getRelativePosition(absolutePosition, monitorEntry.measurements);
508
+ if (receiverId && !cancelled) {
509
+ const receiverData = buildReceiverViewData(receiverId, absolutePosition);
510
+ if (receiverData) {
511
+ const monitorDropResponse = monitorEntry.props.onMonitorDragDrop?.({
512
+ ...baseEventData,
513
+ receiver: receiverData,
514
+ monitorOffset,
515
+ monitorOffsetRatio
516
+ });
517
+ if (monitorDropResponse !== undefined) snapTarget = monitorDropResponse;
518
+ }
519
+ } else {
520
+ const monitorEndResponse = monitorEntry.props.onMonitorDragEnd?.({
521
+ ...baseEventData,
522
+ monitorOffset,
523
+ monitorOffsetRatio,
524
+ cancelled
525
+ });
526
+ if (monitorEndResponse !== undefined) snapTarget = monitorEndResponse;
527
+ }
528
+ }
529
+
530
+ // Resolve Default snap target to root-relative visual position.
531
+ // Default triggers when monitors are empty or all callbacks return undefined.
532
+ // draggedEntry.measurements are content-relative (from measureLayout), so we
533
+ // use the spatial index to compute root-relative visual position instead.
534
+ if (snapTarget === DraxSnapbackTargetPreset.Default) {
535
+ const absPos = computeAbsolutePositionWorklet(draggedEntry.spatialIndex, spatialIndexSV.value, scrollOffsetsSV.value);
536
+ snapTarget = absPos;
537
+ }
538
+
539
+ // Handle snap-back animation
540
+ performSnapback(snapTarget, draggedEntry, receiverId ? getViewEntry(receiverId) : undefined, hoverPositionSV, dragPhaseSV, draggedIdSV, hoverReadySV, setHoverContent, deps.hoverClearDeferredRef);
541
+
542
+ // Fire provider-level onDragEnd (use last known hover position)
543
+ onProviderDragEnd?.({
544
+ draggedId,
545
+ receiverId: receiverId || undefined,
546
+ position: hoverPositionSV.value,
547
+ cancelled
548
+ });
549
+ };
550
+ return {
551
+ handleDragStart,
552
+ handleReceiverChange,
553
+ handleDragEnd
554
+ };
555
+ };
556
+
557
+ /**
558
+ * Perform the snap-back animation after drag ends.
559
+ *
560
+ * CRITICAL ORDERING: When the snap animation completes, we must:
561
+ * 1. Fire onSnapEnd callbacks → triggers finalizeDrag → commits reorder / cancels drag
562
+ * 2. THEN clear drag state (hover disappears, item becomes visible)
563
+ *
564
+ * This ordering ensures shifted items are cleaned up BEFORE the hover disappears.
565
+ * Without this, there's a visible gap where shifted items are at shifted positions
566
+ * but the hover is already gone (the "drop blink").
567
+ */
568
+ function performSnapback(target, draggedEntry, receiverEntry, hoverPositionSV, dragPhaseSV, draggedIdSV, hoverReadySV, setHoverContent, hoverClearDeferredRef) {
569
+ const animateSnap = draggedEntry.props.animateSnap ?? true;
570
+ const snapDelay = draggedEntry.props.snapDelay ?? defaultSnapbackDelay;
571
+ const snapDuration = draggedEntry.props.snapDuration ?? defaultSnapbackDuration;
572
+ const snapAnimator = draggedEntry.props.snapAnimator;
573
+
574
+ // Build snap event data for callbacks
575
+ const snapEventData = {
576
+ dragged: {
577
+ id: draggedEntry.id,
578
+ parentId: draggedEntry.parentId,
579
+ payload: draggedEntry.props.dragPayload ?? draggedEntry.props.payload
580
+ },
581
+ receiver: receiverEntry ? {
582
+ id: receiverEntry.id,
583
+ parentId: receiverEntry.parentId,
584
+ payload: receiverEntry.props.receiverPayload ?? receiverEntry.props.payload
585
+ } : undefined
586
+ };
587
+
588
+ /**
589
+ * Called when snap animation completes. Fires callbacks FIRST (so finalizeDrag
590
+ * can set permanent shifts + clear hover), THEN clears hover & drag state.
591
+ *
592
+ * For REORDER: finalizeDrag sets permanent shifts + clears hover via runOnUI
593
+ * in a single atomic block. No FlatList data change, so no blink.
594
+ *
595
+ * For CANCEL: finalizeDrag → cancelDrag → reverts to committed shifts.
596
+ * Then hover clears on next UI frame. Items at visual positions. No blink.
597
+ */
598
+ const onSnapComplete = () => {
599
+ // Reset the deferred flag before firing callbacks.
600
+ // finalizeDrag (called via onSnapEnd) may set it to true for reorder.
601
+ hoverClearDeferredRef.current = false;
602
+
603
+ // Step 1: Fire callbacks → finalizeDrag runs synchronously.
604
+ draggedEntry.props.onSnapEnd?.(snapEventData);
605
+ receiverEntry?.props.onReceiveSnapEnd?.(snapEventData);
606
+
607
+ // Step 2: Clear hover if NOT deferred by a sortable reorder.
608
+ if (!hoverClearDeferredRef.current) {
609
+ runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
610
+ 'worklet';
611
+
612
+ _hoverReadySV.value = false;
613
+ _dragPhaseSV.value = 'idle';
614
+ _draggedIdSV.value = '';
615
+ _hoverPositionSV.value = {
616
+ x: 0,
617
+ y: 0
618
+ };
619
+ })(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
620
+ setHoverContent(null);
621
+ } else {
622
+ // Do NOT call setHoverContent(null) here — the hover must remain visible
623
+ // until the FlatList re-renders. The deferred cleanup in useLayoutEffect
624
+ // will clear SharedValues, and setHoverContent(null) is called there too.
625
+ }
626
+ };
627
+ if (target === DraxSnapbackTargetPreset.None || !animateSnap) {
628
+ // No snap animation — run cleanup immediately
629
+ onSnapComplete();
630
+ return;
631
+ }
632
+
633
+ // Determine snap-to position
634
+ let toValue;
635
+ if (isPosition(target)) {
636
+ toValue = target;
637
+ } else {
638
+ // Default: snap back to original view position
639
+ toValue = draggedEntry.measurements ? {
640
+ x: draggedEntry.measurements.x,
641
+ y: draggedEntry.measurements.y
642
+ } : {
643
+ x: 0,
644
+ y: 0
645
+ };
646
+ }
647
+ if (snapAnimator) {
648
+ // Custom snap animation
649
+ snapAnimator({
650
+ hoverPosition: hoverPositionSV,
651
+ toValue,
652
+ delay: snapDelay,
653
+ duration: snapDuration,
654
+ finishedCallback: finished => {
655
+ if (finished) {
656
+ onSnapComplete();
657
+ }
658
+ }
659
+ });
660
+ } else {
661
+ // Default withTiming snap animation.
662
+ // When animation finishes, bounce to JS for ordered cleanup.
663
+ hoverPositionSV.value = withDelay(snapDelay, withTiming(toValue, {
664
+ duration: snapDuration
665
+ }, finished => {
666
+ 'worklet';
667
+
668
+ if (finished) {
669
+ runOnJS(onSnapComplete)();
670
+ }
671
+ }));
672
+ }
673
+ }
674
+
675
+ // ─── Helpers ───────────────────────────────────────────────────────────────
676
+
677
+ function flattenOrNull(s) {
678
+ if (!s) return null;
679
+ return StyleSheet.flatten(s) ?? null;
680
+ }
681
+ //# sourceMappingURL=useCallbackDispatch.js.map