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,264 @@
1
+ import type { ReactNode } from 'react';
2
+ import { memo, useEffect, useMemo, useRef } from 'react';
3
+ import type { ViewStyle } from 'react-native';
4
+ import { Platform } from 'react-native';
5
+ import type { SharedValue } from 'react-native-reanimated';
6
+ import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
7
+ import Reanimated, {
8
+ Easing,
9
+ useAnimatedStyle,
10
+ useReducedMotion,
11
+ withSpring,
12
+ withTiming,
13
+ } from 'react-native-reanimated';
14
+
15
+ import { DraxView } from './DraxView';
16
+ import { useDraxContext } from './hooks/useDraxContext';
17
+ import type { SortableItemContextValue } from './SortableItemContext';
18
+ import { SortableItemContext } from './SortableItemContext';
19
+ import type { ResolvedAnimationConfig } from './params';
20
+ import { resolveAnimationConfig } from './params';
21
+ import type {
22
+ DraxViewMeasurementHandler,
23
+ DraxViewProps,
24
+ Position,
25
+ SortableItemMeasurement,
26
+ SortableListHandle,
27
+ } from './types';
28
+
29
+ /**
30
+ * Isolated hook for SortableItem animated style.
31
+ * Kept separate so the worklet closure only contains SharedValues —
32
+ * never React refs from the component scope.
33
+ */
34
+ function useSortableItemStyle(
35
+ hoverReadySV: SharedValue<boolean>,
36
+ draggedIdSV: SharedValue<string>,
37
+ viewIdSV: SharedValue<string>,
38
+ shiftsValidSV: SharedValue<boolean>,
39
+ shiftsRef: SharedValue<Record<string, Position>>,
40
+ instantClearSV: SharedValue<boolean>,
41
+ itemKey: string | undefined,
42
+ animConfig: ResolvedAnimationConfig,
43
+ reducedMotion: boolean,
44
+ inactiveItemStyle?: ViewStyle,
45
+ ) {
46
+ return useAnimatedStyle(() => {
47
+ // Guard: viewIdSV starts as '' before DraxView registers. Without the
48
+ // non-empty check, a newly mounted item would match a cleared draggedIdSV ('')
49
+ // and be hidden (opacity 0) until hoverReadySV clears — visible in cross-container transfers.
50
+ const isDragged = hoverReadySV.value && viewIdSV.value !== '' && draggedIdSV.value === viewIdSV.value;
51
+ const dragActive = draggedIdSV.value !== '';
52
+ const valid = shiftsValidSV.value;
53
+ const shifts = shiftsRef.value;
54
+ const shift = valid && itemKey ? shifts[itemKey] : undefined;
55
+ const instant = instantClearSV.value;
56
+ // When shifts are invalidated (data committing), snap to 0 instantly — no animation.
57
+ // When reduced motion is on, skip all animations.
58
+ const skipAnimation = instant || !valid || reducedMotion;
59
+
60
+ const toX = shift?.x ?? 0;
61
+ const toY = shift?.y ?? 0;
62
+
63
+ let translateX: number;
64
+ let translateY: number;
65
+
66
+ if (skipAnimation) {
67
+ translateX = toX;
68
+ translateY = toY;
69
+ } else if (animConfig.useSpring) {
70
+ const springConfig = {
71
+ damping: animConfig.springDamping,
72
+ stiffness: animConfig.springStiffness,
73
+ mass: animConfig.springMass,
74
+ };
75
+ translateX = withSpring(toX, springConfig);
76
+ translateY = withSpring(toY, springConfig);
77
+ } else {
78
+ const timingConfig = { duration: animConfig.shiftDuration, easing: Easing.linear };
79
+ translateX = withTiming(toX, timingConfig);
80
+ translateY = withTiming(toY, timingConfig);
81
+ }
82
+
83
+ // Apply inactive style to non-dragged items while a drag is active
84
+ const isInactive = dragActive && !isDragged;
85
+
86
+ return {
87
+ opacity: isDragged ? 0 : 1,
88
+ transform: [
89
+ { translateX },
90
+ { translateY },
91
+ ] as const,
92
+ ...(isInactive && inactiveItemStyle ? inactiveItemStyle : {}),
93
+ };
94
+ });
95
+ }
96
+
97
+ export interface SortableItemProps extends DraxViewProps {
98
+ sortable: SortableListHandle<any>;
99
+ index: number;
100
+ /** When true, this item cannot be dragged and stays in its position.
101
+ * Other items will skip over it during reorder. */
102
+ fixed?: boolean;
103
+ children: ReactNode;
104
+ }
105
+
106
+ const SortableItemInner = ({
107
+ sortable,
108
+ index,
109
+ fixed = false,
110
+ children,
111
+ ...draxViewProps
112
+ }: SortableItemProps) => {
113
+ const {
114
+ horizontal,
115
+ lockToMainAxis,
116
+ longPressDelay,
117
+ animationConfig,
118
+ inactiveItemStyle,
119
+ itemEntering,
120
+ itemExiting,
121
+ shiftsRef,
122
+ instantClearSV,
123
+ shiftsValidSV,
124
+ itemMeasurements,
125
+ keyExtractor,
126
+ rawData,
127
+ originalIndexes,
128
+ scrollPosition,
129
+ onItemSnapEnd,
130
+ fixedKeys,
131
+ } = sortable._internal;
132
+
133
+ // Get hoverReadySV and draggedIdSV from DraxContext (provider-level SharedValues)
134
+ const { hoverReadySV, draggedIdSV } = useDraxContext();
135
+
136
+ const originalIndex = originalIndexes[index] ?? index;
137
+ const item = rawData[originalIndex];
138
+ const itemKey = item !== undefined ? keyExtractor(item, index) : undefined;
139
+
140
+ // Register/unregister fixed items so reorder logic can skip them.
141
+ useEffect(() => {
142
+ if (!itemKey) return;
143
+ if (fixed) {
144
+ fixedKeys.current.add(itemKey);
145
+ } else {
146
+ fixedKeys.current.delete(itemKey);
147
+ }
148
+ return () => { fixedKeys.current.delete(itemKey); };
149
+ }, [fixed, itemKey, fixedKeys]);
150
+
151
+ // Store this DraxView's registered ID in a SharedValue so useAnimatedStyle
152
+ // can compare it with draggedIdSV on the UI thread.
153
+ const viewIdSV = useSharedValue('');
154
+
155
+ const measureFnRef = useRef<
156
+ ((handler?: DraxViewMeasurementHandler) => void) | null
157
+ >(null);
158
+
159
+ // Resolve animation config and check reduced motion preference
160
+ const resolvedAnimConfig = useMemo(() => resolveAnimationConfig(animationConfig), [animationConfig]);
161
+ const reducedMotion = useReducedMotion();
162
+
163
+ // Delegated to isolated hook so worklet closure has no refs from this scope.
164
+ const itemStyle = useSortableItemStyle(
165
+ hoverReadySV, draggedIdSV, viewIdSV,
166
+ shiftsValidSV, shiftsRef, instantClearSV, itemKey,
167
+ resolvedAnimConfig, reducedMotion, inactiveItemStyle,
168
+ );
169
+
170
+ // Derive isActive SharedValue for useItemContext consumers
171
+ const isActive = useDerivedValue(() => {
172
+ return viewIdSV.value !== '' && draggedIdSV.value === viewIdSV.value;
173
+ });
174
+
175
+ // Build context value for useItemContext
176
+ const itemContextValue = useMemo<SortableItemContextValue | null>(() => {
177
+ if (!itemKey) return null;
178
+ return {
179
+ itemKey,
180
+ index,
181
+ isActive,
182
+ activeItemId: draggedIdSV,
183
+ };
184
+ }, [itemKey, index, isActive, draggedIdSV]);
185
+
186
+ // Auto-generate accessibility props (can be overridden via draxViewProps)
187
+ const totalItems = rawData.length;
188
+ const defaultA11yLabel = `Item ${index + 1} of ${totalItems}`;
189
+ const defaultA11yHint = 'Long press to drag and reorder';
190
+
191
+ const mergedPayload = useMemo(() => ({
192
+ ...(typeof draxViewProps.payload === 'object' && draxViewProps.payload !== null
193
+ ? draxViewProps.payload : {}),
194
+ index, originalIndex, item,
195
+ }), [draxViewProps.payload, index, originalIndex, item]);
196
+
197
+ return (
198
+ <SortableItemContext value={itemContextValue}>
199
+ <Reanimated.View style={itemStyle} entering={itemEntering} exiting={itemExiting}>
200
+ <DraxView
201
+ longPressDelay={longPressDelay}
202
+ lockDragXPosition={lockToMainAxis && !horizontal}
203
+ lockDragYPosition={lockToMainAxis && horizontal}
204
+ scrollHorizontal={horizontal || undefined}
205
+ draggable={!fixed}
206
+ accessibilityLabel={defaultA11yLabel}
207
+ accessibilityHint={defaultA11yHint}
208
+ accessibilityRole="adjustable"
209
+ {...draxViewProps}
210
+ payload={mergedPayload}
211
+ registration={(reg) => {
212
+ measureFnRef.current = reg?.measure ?? null;
213
+ // Capture the DraxView's registered ID so useAnimatedStyle can match
214
+ // it against draggedIdSV for visibility control.
215
+ viewIdSV.value = reg?.id ?? '';
216
+ }}
217
+ onDragEnd={(event) => {
218
+ draxViewProps.onDragEnd?.(event);
219
+ }}
220
+ onDragDrop={(event) => {
221
+ draxViewProps.onDragDrop?.(event);
222
+ }}
223
+ onSnapEnd={(snapData) => {
224
+ onItemSnapEnd?.();
225
+ draxViewProps.onSnapEnd?.(snapData);
226
+ }}
227
+ onMeasure={(measurements) => {
228
+ draxViewProps.onMeasure?.(measurements);
229
+ if (itemKey && measurements) {
230
+ // Subtract Drax shift transforms when they're included in the
231
+ // measurement. On web: measureLayout always includes transforms.
232
+ // On native: measureLayout ignores transforms, UNLESS DraxView
233
+ // auto-detected transform-based positioning (LegendList) and
234
+ // switched to measure() — flagged via _transformDetected.
235
+ let adjX = measurements.x;
236
+ let adjY = measurements.y;
237
+ if (Platform.OS === 'web' || measurements._transformDetected) {
238
+ const currentShift = shiftsRef.value[itemKey];
239
+ if (currentShift) {
240
+ adjX -= currentShift.x;
241
+ adjY -= currentShift.y;
242
+ }
243
+ }
244
+ const entry: SortableItemMeasurement = {
245
+ x: adjX,
246
+ y: adjY,
247
+ width: measurements.width,
248
+ height: measurements.height,
249
+ key: itemKey,
250
+ index,
251
+ scrollAtMeasure: { x: scrollPosition.value.x, y: scrollPosition.value.y },
252
+ };
253
+ itemMeasurements.current.set(itemKey, entry);
254
+ }
255
+ }}
256
+ >
257
+ {children}
258
+ </DraxView>
259
+ </Reanimated.View>
260
+ </SortableItemContext>
261
+ );
262
+ };
263
+
264
+ export const SortableItem = memo(SortableItemInner) as typeof SortableItemInner;
@@ -0,0 +1,46 @@
1
+ import { createContext, use } from 'react';
2
+ import type { SharedValue } from 'react-native-reanimated';
3
+
4
+ /** Per-item state exposed by useItemContext */
5
+ export interface SortableItemContextValue {
6
+ /** The item's key (from keyExtractor) */
7
+ itemKey: string;
8
+ /** Display index in the current sort order */
9
+ index: number;
10
+ /** SharedValue: true when this item is being dragged */
11
+ isActive: SharedValue<boolean>;
12
+ /** SharedValue: ID of the currently dragged item (empty string if none) */
13
+ activeItemId: SharedValue<string>;
14
+ }
15
+
16
+ export const SortableItemContext = createContext<SortableItemContextValue | null>(null);
17
+
18
+ /**
19
+ * Access per-item state from within a SortableItem's children.
20
+ *
21
+ * Returns SharedValues for reactive animations:
22
+ * - `isActive` — true when THIS item is being dragged
23
+ * - `activeItemId` — ID of the currently dragged item
24
+ * - `itemKey` — this item's key
25
+ * - `index` — this item's display index
26
+ *
27
+ * Must be called within a `<SortableItem>`.
28
+ *
29
+ * @example
30
+ * ```tsx
31
+ * function MyItem() {
32
+ * const { isActive } = useItemContext();
33
+ * const style = useAnimatedStyle(() => ({
34
+ * transform: [{ scale: isActive.value ? 1.1 : 1 }],
35
+ * }));
36
+ * return <Reanimated.View style={style}>...</Reanimated.View>;
37
+ * }
38
+ * ```
39
+ */
40
+ export const useItemContext = (): SortableItemContextValue => {
41
+ const ctx = use(SortableItemContext);
42
+ if (!ctx) {
43
+ throw new Error('useItemContext must be used within a SortableItem');
44
+ }
45
+ return ctx;
46
+ };
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Runtime detection of react-native-gesture-handler version.
3
+ * Returns true if v3 hook API (usePanGesture) is available.
4
+ * Result is cached after first call.
5
+ */
6
+ let _isV3: boolean | null = null;
7
+
8
+ export function isGestureHandlerV3(): boolean {
9
+ if (_isV3 !== null) return _isV3;
10
+ try {
11
+ const rngh = require('react-native-gesture-handler');
12
+ _isV3 = typeof rngh.usePanGesture === 'function';
13
+ } catch {
14
+ _isV3 = false;
15
+ }
16
+ return _isV3;
17
+ }
@@ -0,0 +1,7 @@
1
+ export { useDraxPanGesture } from './useDraxPanGesture';
2
+ export type {
3
+ DraxPanEvent,
4
+ DraxPanGesture,
5
+ DraxPanGestureConfig,
6
+ } from './types';
7
+ export { isGestureHandlerV3 } from './detectVersion';
@@ -0,0 +1,35 @@
1
+ import type { SharedValue } from 'react-native-reanimated';
2
+
3
+ /**
4
+ * Re-export PanGesture from RNGH — resolves to the correct type per installed version:
5
+ * - v3: SingleGesture<PanHandlerData, PanGestureInternalProperties>
6
+ * - v2: PanGesture class extending ContinousBaseGesture
7
+ */
8
+ export type { PanGesture as DraxPanGesture } from 'react-native-gesture-handler';
9
+
10
+ /** Minimal event shape — only the fields Drax uses (present in both v2 and v3). */
11
+ export interface DraxPanEvent {
12
+ x: number;
13
+ y: number;
14
+ absoluteX: number;
15
+ absoluteY: number;
16
+ }
17
+
18
+ /** Config for the version-agnostic pan gesture hook. */
19
+ export interface DraxPanGestureConfig {
20
+ enabledSV: SharedValue<boolean>;
21
+ longPressDelaySV: SharedValue<number>;
22
+ maxPointers: number;
23
+ shouldCancelWhenOutside: boolean;
24
+ /** Web: CSS touch-action for the gesture view. Set to 'pan-y' or 'pan-x'
25
+ * to allow native scrolling before the long-press activates. */
26
+ touchAction?: string;
27
+ /** Fail the gesture if finger moves more than this distance during activation.
28
+ * Prevents accidental drags when the user is trying to scroll. */
29
+ failOffsetX?: number | [number, number];
30
+ failOffsetY?: number | [number, number];
31
+ onActivate: (event: DraxPanEvent) => void;
32
+ onUpdate: (event: DraxPanEvent) => void;
33
+ onDeactivate: (event: DraxPanEvent) => void;
34
+ onFinalize: (event: DraxPanEvent, didSucceed: boolean) => void;
35
+ }
@@ -0,0 +1,112 @@
1
+ import { useMemo, useState } from 'react';
2
+ import { useAnimatedReaction } from 'react-native-reanimated';
3
+ import { runOnJS } from 'react-native-worklets';
4
+
5
+ import { isGestureHandlerV3 } from './detectVersion';
6
+ import type { DraxPanEvent, DraxPanGesture, DraxPanGestureConfig } from './types';
7
+
8
+ // Module-scope require — cached by the bundler, always the same reference.
9
+ // Hoisted out of the hook body so the compiler sees a stable function identity.
10
+ const rngh = require('react-native-gesture-handler');
11
+
12
+ /**
13
+ * v3 path — passes through to RNGH's usePanGesture with SharedValue config.
14
+ * Zero overhead, UI-thread-driven reconfiguration.
15
+ */
16
+ function useDraxPanGestureV3(config: DraxPanGestureConfig): DraxPanGesture {
17
+ // Build config without undefined failOffset keys — RNGH v3 warns on
18
+ // undefined keys that survive transformOffsetProp without deletion.
19
+ // Spread is not allowed here (Reanimated workletizes hook arguments).
20
+ const panConfig: Record<string, unknown> = {
21
+ enabled: config.enabledSV,
22
+ activateAfterLongPress: config.longPressDelaySV,
23
+ maxPointers: config.maxPointers,
24
+ shouldCancelWhenOutside: config.shouldCancelWhenOutside,
25
+ touchAction: config.touchAction,
26
+ onActivate: config.onActivate,
27
+ onUpdate: config.onUpdate,
28
+ onDeactivate: config.onDeactivate,
29
+ onFinalize: config.onFinalize,
30
+ };
31
+ if (config.failOffsetX !== undefined) panConfig.failOffsetX = config.failOffsetX;
32
+ if (config.failOffsetY !== undefined) panConfig.failOffsetY = config.failOffsetY;
33
+ return rngh.usePanGesture(panConfig);
34
+ }
35
+
36
+ /**
37
+ * v2 path — wraps Gesture.Pan() builder pattern.
38
+ * SharedValue config is watched via useAnimatedReaction and triggers gesture
39
+ * recreation on change. This is slower than v3 but functionally correct.
40
+ * In practice, enabled/longPressDelay change very rarely (prop updates only).
41
+ */
42
+ function useDraxPanGestureV2(config: DraxPanGestureConfig): DraxPanGesture {
43
+ const Gesture = rngh.Gesture;
44
+
45
+ // Mirror SharedValues to React state for v2 gesture config
46
+ const [enabled, setEnabled] = useState(config.enabledSV.value);
47
+ const [longPressDelay, setLongPressDelay] = useState(
48
+ config.longPressDelaySV.value
49
+ );
50
+
51
+ // Watch SharedValue changes and sync to JS state
52
+ useAnimatedReaction(
53
+ () => config.enabledSV.value,
54
+ (current, prev) => {
55
+ if (prev !== null && current !== prev) {
56
+ runOnJS(setEnabled)(current);
57
+ }
58
+ }
59
+ );
60
+
61
+ useAnimatedReaction(
62
+ () => config.longPressDelaySV.value,
63
+ (current, prev) => {
64
+ if (prev !== null && current !== prev) {
65
+ runOnJS(setLongPressDelay)(current);
66
+ }
67
+ }
68
+ );
69
+
70
+ // Typed as DraxPanGesture directly — v2 builder returns a legacy type that's structurally
71
+ // different from v3's PanGesture at compile time, but GestureDetector accepts both at runtime.
72
+ // Since Gesture comes from require() (any), the assignment is valid without a cast.
73
+ const gesture: DraxPanGesture = useMemo(() => {
74
+ let g = Gesture.Pan()
75
+ .enabled(enabled)
76
+ .activateAfterLongPress(longPressDelay)
77
+ .maxPointers(config.maxPointers)
78
+ .shouldCancelWhenOutside(config.shouldCancelWhenOutside);
79
+ if (config.failOffsetX !== undefined) g = g.failOffsetX(config.failOffsetX);
80
+ if (config.failOffsetY !== undefined) g = g.failOffsetY(config.failOffsetY);
81
+ return g
82
+ .onStart((event: DraxPanEvent) => {
83
+ 'worklet';
84
+ config.onActivate(event);
85
+ })
86
+ .onUpdate((event: DraxPanEvent) => {
87
+ 'worklet';
88
+ config.onUpdate(event);
89
+ })
90
+ .onEnd((event: DraxPanEvent) => {
91
+ 'worklet';
92
+ config.onDeactivate(event);
93
+ })
94
+ .onFinalize((event: DraxPanEvent, success: boolean) => {
95
+ 'worklet';
96
+ config.onFinalize(event, success);
97
+ });
98
+ }, [Gesture, enabled, longPressDelay, config]);
99
+
100
+ return gesture;
101
+ }
102
+
103
+ /**
104
+ * Version-agnostic pan gesture hook.
105
+ * Delegates to v3's usePanGesture (optimal) or v2's Gesture.Pan() builder (compat).
106
+ * Selected at module load time to avoid conditional hook calls.
107
+ */
108
+ export const useDraxPanGesture: (
109
+ config: DraxPanGestureConfig
110
+ ) => DraxPanGesture = isGestureHandlerV3()
111
+ ? useDraxPanGestureV3
112
+ : useDraxPanGestureV2;
@@ -0,0 +1,2 @@
1
+ export { useDraxContext } from './useDraxContext';
2
+ export { useDraxId } from './useDraxId';