react-native-drax 0.10.3 → 0.11.0-alpha.2

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 (47) hide show
  1. package/.eslintrc.js +2 -73
  2. package/.prettierrc +16 -0
  3. package/README.md +81 -1
  4. package/build/AllHoverViews.d.ts +0 -0
  5. package/build/AllHoverViews.js +30 -0
  6. package/build/DraxContext.d.ts +0 -1
  7. package/build/DraxList.d.ts +3 -3
  8. package/build/DraxList.js +231 -216
  9. package/build/DraxListItem.d.ts +7 -0
  10. package/build/DraxListItem.js +121 -0
  11. package/build/DraxProvider.d.ts +1 -3
  12. package/build/DraxProvider.js +322 -259
  13. package/build/DraxScrollView.d.ts +1 -1
  14. package/build/DraxScrollView.js +29 -90
  15. package/build/DraxSubprovider.d.ts +2 -2
  16. package/build/DraxSubprovider.js +1 -1
  17. package/build/DraxView.d.ts +7 -2
  18. package/build/DraxView.js +60 -383
  19. package/build/HoverView.d.ts +8 -0
  20. package/build/HoverView.js +40 -0
  21. package/build/PanGestureDetector.d.ts +3 -0
  22. package/build/PanGestureDetector.js +49 -0
  23. package/build/hooks/useContent.d.ts +23 -0
  24. package/build/hooks/useContent.js +212 -0
  25. package/build/hooks/useDraxProtocol.d.ts +5 -0
  26. package/build/hooks/useDraxProtocol.js +32 -0
  27. package/build/hooks/useDraxRegistry.d.ts +21 -20
  28. package/build/hooks/useDraxRegistry.js +195 -182
  29. package/build/hooks/useDraxScrollHandler.d.ts +25 -0
  30. package/build/hooks/useDraxScrollHandler.js +89 -0
  31. package/build/hooks/useDraxState.d.ts +1 -1
  32. package/build/hooks/useDraxState.js +9 -6
  33. package/build/hooks/useMeasurements.d.ts +9 -0
  34. package/build/hooks/useMeasurements.js +119 -0
  35. package/build/hooks/useStatus.d.ts +11 -0
  36. package/build/hooks/useStatus.js +96 -0
  37. package/build/index.d.ts +2 -0
  38. package/build/index.js +4 -1
  39. package/build/math.d.ts +2 -2
  40. package/build/math.js +10 -6
  41. package/build/params.d.ts +9 -0
  42. package/build/params.js +7 -1
  43. package/build/transform.d.ts +11 -4
  44. package/build/transform.js +50 -8
  45. package/build/types.d.ts +238 -87
  46. package/build/types.js +10 -7
  47. package/package.json +43 -45
package/build/DraxList.js CHANGED
@@ -22,51 +22,40 @@ var __importStar = (this && this.__importStar) || function (mod) {
22
22
  __setModuleDefault(result, mod);
23
23
  return result;
24
24
  };
25
+ var __importDefault = (this && this.__importDefault) || function (mod) {
26
+ return (mod && mod.__esModule) ? mod : { "default": mod };
27
+ };
25
28
  Object.defineProperty(exports, "__esModule", { value: true });
26
29
  exports.DraxList = void 0;
30
+ const lodash_throttle_1 = __importDefault(require("lodash.throttle"));
27
31
  const react_1 = __importStar(require("react"));
28
32
  const react_native_1 = require("react-native");
29
- const DraxView_1 = require("./DraxView");
33
+ const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
30
34
  const DraxSubprovider_1 = require("./DraxSubprovider");
31
- const hooks_1 = require("./hooks");
32
- const types_1 = require("./types");
35
+ const DraxView_1 = require("./DraxView");
36
+ const useDraxScrollHandler_1 = require("./hooks/useDraxScrollHandler");
33
37
  const params_1 = require("./params");
34
- const defaultStyles = react_native_1.StyleSheet.create({
35
- draggingStyle: { opacity: 0 },
36
- dragReleasedStyle: { opacity: 0.5 },
37
- });
38
+ const types_1 = require("./types");
38
39
  const DraxListUnforwarded = (props, forwardedRef) => {
39
- const { data, style, flatListStyle, itemStyles, renderItemContent, renderItemHoverContent, onItemDragStart, onItemDragPositionChange, onItemDragEnd, onItemReorder, viewPropsExtractor, id: idProp, reorderable: reorderableProp, onScroll: onScrollProp, itemsDraggable = true, lockItemDragsToMainAxis = false, longPressDelay = params_1.defaultListItemLongPressDelay, ...flatListProps } = props;
40
+ const { data, style, onItemDragStart, onItemDragPositionChange, onItemDragEnd, onItemReorder, reorderable: reorderableProp, onScroll: onScrollProp, lockItemDragsToMainAxis = false, longPressDelay = params_1.defaultListItemLongPressDelay, parentDraxViewProps, monitoringExternalDragStyle, ...flatListProps } = props;
40
41
  // Copy the value of the horizontal property for internal use.
41
42
  const horizontal = flatListProps.horizontal ?? false;
42
43
  // Get the item count for internal use.
43
44
  const itemCount = data?.length ?? 0;
44
45
  // Set a sensible default for reorderable prop.
45
- const reorderable = reorderableProp ?? (onItemReorder !== undefined);
46
- // The unique identifer for this list's Drax view.
47
- const id = (0, hooks_1.useDraxId)(idProp);
48
- // FlatList, used for scrolling.
49
- const flatListRef = (0, react_1.useRef)(null);
50
- // FlatList node handle, used for measuring children.
51
- const nodeHandleRef = (0, react_1.useRef)(null);
52
- // Container view measurements, for scrolling by percentage.
53
- const containerMeasurementsRef = (0, react_1.useRef)(undefined);
54
- // Content size, for scrolling by percentage.
55
- const contentSizeRef = (0, react_1.useRef)(undefined);
56
- // Scroll position, for Drax bounds checking and auto-scrolling.
57
- const scrollPositionRef = (0, react_1.useRef)({ x: 0, y: 0 });
46
+ const reorderable = reorderableProp ?? onItemReorder !== undefined;
58
47
  // Original index of the currently dragged list item, if any.
59
- const draggedItemRef = (0, react_1.useRef)(undefined);
48
+ const draggedItem = (0, react_native_reanimated_1.useSharedValue)(undefined);
60
49
  // Auto-scrolling state.
61
50
  const scrollStateRef = (0, react_1.useRef)(types_1.AutoScrollDirection.None);
62
- // Auto-scrolling interval.
63
- const scrollIntervalRef = (0, react_1.useRef)(undefined);
64
51
  // List item measurements, for determining shift.
65
52
  const itemMeasurementsRef = (0, react_1.useRef)([]);
53
+ const prevItemMeasurementsRef = (0, react_1.useRef)([]);
66
54
  // Drax view registrations, for remeasuring after reorder.
67
55
  const registrationsRef = (0, react_1.useRef)([]);
68
56
  // Shift offsets.
69
- const shiftsRef = (0, react_1.useRef)([]);
57
+ const shiftsRef = (0, react_native_reanimated_1.useSharedValue)([]);
58
+ const previousShiftsRef = (0, react_native_reanimated_1.useSharedValue)([]);
70
59
  // Maintain cache of reordered list indexes until data updates.
71
60
  const [originalIndexes, setOriginalIndexes] = (0, react_1.useState)([]);
72
61
  // Maintain the index the item is currently dragged to.
@@ -75,7 +64,7 @@ const DraxListUnforwarded = (props, forwardedRef) => {
75
64
  (0, react_1.useEffect)(() => {
76
65
  const itemMeasurements = itemMeasurementsRef.current;
77
66
  const registrations = registrationsRef.current;
78
- const shifts = shiftsRef.current;
67
+ const shifts = shiftsRef.value;
79
68
  if (itemMeasurements.length > itemCount) {
80
69
  itemMeasurements.splice(itemCount - itemMeasurements.length);
81
70
  }
@@ -97,109 +86,21 @@ const DraxListUnforwarded = (props, forwardedRef) => {
97
86
  }
98
87
  else {
99
88
  while (shifts.length < itemCount) {
100
- shifts.push({
101
- targetValue: 0,
102
- animatedValue: new react_native_1.Animated.Value(0),
103
- });
89
+ shifts.push({ x: 0, y: 0 });
104
90
  }
105
91
  }
92
+ shiftsRef.value = shifts;
106
93
  }, [itemCount]);
107
94
  // Clear reorders when data changes.
108
95
  (0, react_1.useLayoutEffect)(() => {
109
96
  // console.log('clear reorders');
110
97
  setOriginalIndexes(data ? [...Array(data.length).keys()] : []);
111
98
  }, [data]);
112
- // Apply the reorder cache to the data.
113
- const reorderedData = (0, react_1.useMemo)(() => {
114
- // console.log('refresh sorted data');
115
- if (!id || !data) {
116
- return null;
117
- }
118
- if (data.length !== originalIndexes.length) {
119
- return data;
120
- }
121
- return originalIndexes.map((index) => data[index]);
122
- }, [id, data, originalIndexes]);
123
- // Get shift transform for list item at index.
124
- const getShiftTransform = (0, react_1.useCallback)((index) => {
125
- const shift = shiftsRef.current[index]?.animatedValue ?? 0;
126
- return horizontal
127
- ? [{ translateX: shift }]
128
- : [{ translateY: shift }];
129
- }, [horizontal]);
130
- // Set the currently dragged list item.
131
- const setDraggedItem = (0, react_1.useCallback)((originalIndex) => {
132
- draggedItemRef.current = originalIndex;
133
- }, []);
134
- // Clear the currently dragged list item.
135
- const resetDraggedItem = (0, react_1.useCallback)(() => {
136
- draggedItemRef.current = undefined;
137
- }, []);
138
- // Drax view renderItem wrapper.
139
- const renderItem = (0, react_1.useCallback)((info) => {
140
- const { index, item } = info;
141
- const originalIndex = originalIndexes[index];
142
- const { style: itemStyle, draggingStyle = defaultStyles.draggingStyle, dragReleasedStyle = defaultStyles.dragReleasedStyle, ...otherStyleProps } = itemStyles ?? {};
143
- return (react_1.default.createElement(DraxView_1.DraxView, { style: [itemStyle, { transform: getShiftTransform(originalIndex) }], draggingStyle: draggingStyle, dragReleasedStyle: dragReleasedStyle, ...otherStyleProps, longPressDelay: longPressDelay, lockDragXPosition: lockItemDragsToMainAxis && !horizontal, lockDragYPosition: lockItemDragsToMainAxis && horizontal, draggable: itemsDraggable, payload: { index, originalIndex }, ...(viewPropsExtractor?.(item) ?? {}), onDragEnd: resetDraggedItem, onDragDrop: resetDraggedItem, onMeasure: (measurements) => {
144
- if (originalIndex !== undefined) {
145
- // console.log(`measuring [${index}, ${originalIndex}]: (${measurements?.x}, ${measurements?.y})`);
146
- itemMeasurementsRef.current[originalIndex] = measurements;
147
- }
148
- }, registration: (registration) => {
149
- if (registration && originalIndex !== undefined) {
150
- // console.log(`registering [${index}, ${originalIndex}], ${registration.id}`);
151
- registrationsRef.current[originalIndex] = registration;
152
- registration.measure();
153
- }
154
- }, renderContent: (contentProps) => renderItemContent(info, contentProps), renderHoverContent: renderItemHoverContent
155
- && ((hoverContentProps) => renderItemHoverContent(info, hoverContentProps)) }));
156
- }, [
157
- originalIndexes,
158
- itemStyles,
159
- viewPropsExtractor,
160
- getShiftTransform,
161
- resetDraggedItem,
162
- itemsDraggable,
163
- renderItemContent,
164
- renderItemHoverContent,
165
- longPressDelay,
166
- lockItemDragsToMainAxis,
167
- horizontal,
168
- ]);
169
- // Track the size of the container view.
170
- const onMeasureContainer = (0, react_1.useCallback)((measurements) => {
171
- containerMeasurementsRef.current = measurements;
172
- }, []);
173
- // Track the size of the content.
174
- const onContentSizeChange = (0, react_1.useCallback)((width, height) => {
175
- contentSizeRef.current = { x: width, y: height };
176
- }, []);
177
- // Set FlatList and node handle refs.
178
- const setFlatListRefs = (0, react_1.useCallback)((ref) => {
179
- flatListRef.current = ref;
180
- nodeHandleRef.current = ref && (0, react_native_1.findNodeHandle)(ref);
181
- if (forwardedRef) {
182
- if (typeof forwardedRef === 'function') {
183
- forwardedRef(ref);
184
- }
185
- else {
186
- // eslint-disable-next-line no-param-reassign
187
- forwardedRef.current = ref;
188
- }
189
- }
190
- }, [forwardedRef]);
191
- // Update tracked scroll position when list is scrolled.
192
- const onScroll = (0, react_1.useCallback)((event) => {
193
- const { nativeEvent: { contentOffset } } = event;
194
- scrollPositionRef.current = { ...contentOffset };
195
- onScrollProp?.(event);
196
- }, [onScrollProp]);
197
99
  // Handle auto-scrolling on interval.
198
100
  const doScroll = (0, react_1.useCallback)(() => {
199
- const flatList = flatListRef.current;
200
101
  const containerMeasurements = containerMeasurementsRef.current;
201
102
  const contentSize = contentSizeRef.current;
202
- if (!flatList || !containerMeasurements || !contentSize) {
103
+ if (!flatListRef.current || !containerMeasurements || !contentSize) {
203
104
  return;
204
105
  }
205
106
  let containerLength;
@@ -208,12 +109,12 @@ const DraxListUnforwarded = (props, forwardedRef) => {
208
109
  if (horizontal) {
209
110
  containerLength = containerMeasurements.width;
210
111
  contentLength = contentSize.x;
211
- prevOffset = scrollPositionRef.current.x;
112
+ prevOffset = scrollPosition.value.x;
212
113
  }
213
114
  else {
214
115
  containerLength = containerMeasurements.height;
215
116
  contentLength = contentSize.y;
216
- prevOffset = scrollPositionRef.current.y;
117
+ prevOffset = scrollPosition.value.y;
217
118
  }
218
119
  const jumpLength = containerLength * 0.2;
219
120
  let offset;
@@ -229,63 +130,126 @@ const DraxListUnforwarded = (props, forwardedRef) => {
229
130
  }
230
131
  }
231
132
  if (offset !== undefined) {
232
- flatList.scrollToOffset({ offset });
233
- flatList.flashScrollIndicators();
133
+ flatListRef.current.scrollToOffset({ offset });
134
+ flatListRef.current.flashScrollIndicators();
234
135
  }
235
136
  }, [horizontal]);
236
- // Start the auto-scrolling interval.
237
- const startScroll = (0, react_1.useCallback)(() => {
238
- if (scrollIntervalRef.current) {
239
- return;
137
+ const { id, containerMeasurementsRef, contentSizeRef, onContentSizeChange, onMeasureContainer, onScroll, scrollRef: flatListRef, scrollPosition, setScrollRefs, startScroll, stopScroll, } = (0, useDraxScrollHandler_1.useDraxScrollHandler)({
138
+ idProp: parentDraxViewProps?.id,
139
+ onContentSizeChangeProp: props.onContentSizeChange,
140
+ onScrollProp,
141
+ forwardedRef,
142
+ doScroll,
143
+ });
144
+ // Apply the reorder cache to the data.
145
+ const reorderedData = (0, react_1.useMemo)(() => {
146
+ // console.log('refresh sorted data');
147
+ if (!id || !data) {
148
+ return null;
240
149
  }
241
- doScroll();
242
- scrollIntervalRef.current = setInterval(doScroll, 250);
243
- }, [doScroll]);
244
- // Stop the auto-scrolling interval.
245
- const stopScroll = (0, react_1.useCallback)(() => {
246
- if (scrollIntervalRef.current) {
247
- clearInterval(scrollIntervalRef.current);
248
- scrollIntervalRef.current = undefined;
150
+ if (data.length !== originalIndexes.length) {
151
+ return data;
249
152
  }
153
+ return originalIndexes.map(index => data[index]).filter(Boolean);
154
+ }, [id, data, originalIndexes]);
155
+ // Set the currently dragged list item.
156
+ const setDraggedItem = (0, react_1.useCallback)((originalIndex) => {
157
+ draggedItem.value = originalIndex;
250
158
  }, []);
251
- // If startScroll changes, refresh our interval.
252
- (0, react_1.useEffect)(() => {
253
- if (scrollIntervalRef.current) {
254
- stopScroll();
255
- startScroll();
256
- }
257
- }, [stopScroll, startScroll]);
159
+ // Clear the currently dragged list item.
160
+ const resetDraggedItem = (0, react_1.useCallback)(() => {
161
+ draggedItem.value = undefined;
162
+ }, []);
163
+ // Drax view renderItem wrapper.
164
+ const renderItem = (0, react_1.useCallback)((info) => {
165
+ const { index, item } = info;
166
+ const originalIndex = originalIndexes[index];
167
+ const itemProps = {
168
+ index,
169
+ item,
170
+ originalIndex,
171
+ horizontal,
172
+ longPressDelay,
173
+ lockItemDragsToMainAxis,
174
+ draggedItem,
175
+ shiftsRef,
176
+ itemMeasurementsRef,
177
+ prevItemMeasurementsRef,
178
+ resetDraggedItem,
179
+ keyExtractor: props?.keyExtractor,
180
+ previousShiftsRef,
181
+ registrationsRef,
182
+ info,
183
+ data,
184
+ };
185
+ return props?.renderItem?.(info, itemProps);
186
+ }, [
187
+ originalIndexes,
188
+ resetDraggedItem,
189
+ longPressDelay,
190
+ lockItemDragsToMainAxis,
191
+ horizontal,
192
+ draggedItem,
193
+ shiftsRef,
194
+ itemMeasurementsRef,
195
+ prevItemMeasurementsRef,
196
+ previousShiftsRef,
197
+ registrationsRef,
198
+ props?.keyExtractor,
199
+ data,
200
+ ]);
258
201
  // Reset all shift values.
259
202
  const resetShifts = (0, react_1.useCallback)(() => {
260
- shiftsRef.current.forEach((shift) => {
261
- // eslint-disable-next-line no-param-reassign
262
- shift.targetValue = 0;
263
- shift.animatedValue.setValue(0);
264
- });
203
+ previousShiftsRef.value = shiftsRef.value;
204
+ prevItemMeasurementsRef.current = [...itemMeasurementsRef.current];
205
+ shiftsRef.value = shiftsRef.value.map(() => ({ x: 0, y: 0 }));
265
206
  }, []);
266
207
  // Update shift values in response to a drag.
267
- const updateShifts = (0, react_1.useCallback)(({ index: fromIndex, originalIndex: fromOriginalIndex }, { index: toIndex }) => {
268
- const { width = 50, height = 50 } = itemMeasurementsRef.current[fromOriginalIndex] ?? {};
269
- const offset = horizontal ? width : height;
270
- originalIndexes.forEach((originalIndex, index) => {
271
- const shift = shiftsRef.current[originalIndex];
272
- let newTargetValue = 0;
273
- if (index > fromIndex && index <= toIndex) {
274
- newTargetValue = -offset;
208
+ const updateShifts = (0, react_1.useCallback)(({ index: fromIndex, originalIndex: fromOriginalIndex }, { index: toIndex }, dragged) => {
209
+ const isForward = fromIndex < toIndex;
210
+ shiftsRef.value = originalIndexes.map(index => {
211
+ // Don't shift the dragged item
212
+ if (index === fromIndex)
213
+ return { x: 0, y: 0 };
214
+ // Reset shift if item is no longer in the affected range
215
+ const shouldShift = isForward
216
+ ? index > fromIndex && index <= toIndex
217
+ : index >= toIndex && index < fromIndex;
218
+ if (!shouldShift)
219
+ return { x: 0, y: 0 };
220
+ // Get measurements for current item and the item we're shifting to
221
+ const currentMeasurements = itemMeasurementsRef.current[index];
222
+ const draggedMeasurements = itemMeasurementsRef.current[fromOriginalIndex] ||
223
+ /** If no measurements, it must be an external dragged item */
224
+ dragged.data.hoverMeasurements;
225
+ const targetIndex = isForward ? index - 1 : index + 1;
226
+ const targetMeasurements = itemMeasurementsRef.current[targetIndex] ||
227
+ /** If no measurements, it must be last item. Fallback to list contentSize */
228
+ contentSizeRef.current;
229
+ if (!currentMeasurements || !draggedMeasurements || !targetMeasurements) {
230
+ return { x: 0, y: 0 };
231
+ }
232
+ // Calculate gaps between items in both directions
233
+ const xGap = isForward
234
+ ? currentMeasurements.x - (targetMeasurements.x + targetMeasurements.width)
235
+ : targetMeasurements.x - (currentMeasurements.x + currentMeasurements.width);
236
+ const yGap = isForward
237
+ ? currentMeasurements.y - (targetMeasurements.y + targetMeasurements.height)
238
+ : targetMeasurements.y - (currentMeasurements.y + currentMeasurements.height);
239
+ // Calculated new shifts
240
+ const x = isForward ? -(draggedMeasurements.width + xGap) : draggedMeasurements.width + xGap;
241
+ const y = isForward ? -(draggedMeasurements.height + yGap) : draggedMeasurements.height + yGap;
242
+ if ((props?.numColumns || 1) > 1) {
243
+ return { x, y };
275
244
  }
276
- else if (index < fromIndex && index >= toIndex) {
277
- newTargetValue = offset;
245
+ if (horizontal) {
246
+ return { x, y: 0 };
278
247
  }
279
- if (shift.targetValue !== newTargetValue) {
280
- shift.targetValue = newTargetValue;
281
- react_native_1.Animated.timing(shift.animatedValue, {
282
- duration: 200,
283
- toValue: newTargetValue,
284
- useNativeDriver: true,
285
- }).start();
248
+ else {
249
+ return { x: 0, y };
286
250
  }
287
251
  });
288
- }, [originalIndexes, horizontal]);
252
+ }, [originalIndexes]);
289
253
  // Calculate absolute position of list item for snapback.
290
254
  const calculateSnapbackTarget = (0, react_1.useCallback)(({ index: fromIndex, originalIndex: fromOriginalIndex }, { index: toIndex, originalIndex: toOriginalIndex }) => {
291
255
  const containerMeasurements = containerMeasurementsRef.current;
@@ -300,37 +264,51 @@ const DraxListUnforwarded = (props, forwardedRef) => {
300
264
  // toIndex + 1 is in the list. We can measure the position of the next item.
301
265
  const nextMeasurements = itemMeasurements[originalIndexes[nextIndex]];
302
266
  if (nextMeasurements) {
303
- nextPos = { x: nextMeasurements.x, y: nextMeasurements.y };
267
+ nextPos = {
268
+ x: nextMeasurements.x,
269
+ y: nextMeasurements.y,
270
+ };
304
271
  }
305
272
  }
306
273
  else {
307
274
  // toIndex is the last item of the list. We can use the list content size.
308
275
  const contentSize = contentSizeRef.current;
309
276
  if (contentSize) {
310
- nextPos = horizontal
311
- ? { x: contentSize.x, y: 0 }
312
- : { x: 0, y: contentSize.y };
277
+ nextPos = horizontal ? { x: contentSize.x, y: 0 } : { x: 0, y: contentSize.y };
313
278
  }
314
279
  }
315
280
  const fromMeasurements = itemMeasurements[fromOriginalIndex];
316
281
  if (nextPos && fromMeasurements) {
282
+ const flattenedStyles = react_native_1.StyleSheet.flatten(flatListProps.contentContainerStyle) || {};
283
+ //@ts-ignore
284
+ const rowGap = flattenedStyles.rowGap ?? flattenedStyles.gap ?? 0;
285
+ //@ts-ignore
286
+ const columnGap = flattenedStyles.columnGap ?? flattenedStyles.gap ?? 0;
317
287
  targetPos = horizontal
318
- ? { x: nextPos.x - fromMeasurements.width, y: nextPos.y }
319
- : { x: nextPos.x, y: nextPos.y - fromMeasurements.height };
288
+ ? {
289
+ x: nextPos.x - fromMeasurements.width - rowGap,
290
+ y: nextPos.y,
291
+ }
292
+ : {
293
+ x: nextPos.x,
294
+ y: nextPos.y - fromMeasurements.height - columnGap,
295
+ };
320
296
  }
321
297
  }
322
298
  else {
323
299
  // Target pos(toIndex)
324
300
  const toMeasurements = itemMeasurements[toOriginalIndex];
325
301
  if (toMeasurements) {
326
- targetPos = { x: toMeasurements.x, y: toMeasurements.y };
302
+ targetPos = {
303
+ x: toMeasurements.x,
304
+ y: toMeasurements.y,
305
+ };
327
306
  }
328
307
  }
329
308
  if (targetPos) {
330
- const scrollPosition = scrollPositionRef.current;
331
309
  return {
332
- x: containerMeasurements.x - scrollPosition.x + targetPos.x,
333
- y: containerMeasurements.y - scrollPosition.y + targetPos.y,
310
+ x: containerMeasurements.x - scrollPosition.value.x + targetPos.x,
311
+ y: containerMeasurements.y - scrollPosition.value.y + targetPos.y,
334
312
  };
335
313
  }
336
314
  }
@@ -342,16 +320,24 @@ const DraxListUnforwarded = (props, forwardedRef) => {
342
320
  scrollStateRef.current = types_1.AutoScrollDirection.None;
343
321
  stopScroll();
344
322
  const { dragged, receiver } = eventData;
345
- // Check if we need to handle this drag end.
346
- if (reorderable && dragged.parentId === id) {
347
- // Determine list indexes of dragged/received items, if any.
348
- const fromPayload = dragged.payload;
349
- const toPayload = receiver?.parentId === id
350
- ? receiver.payload
351
- : undefined;
323
+ const isExternalDrag = dragged.parentId !== id || typeof dragged.payload.originalIndex !== 'number';
324
+ // First, check if we need to shift items.
325
+ if (reorderable) {
326
+ const fromPayload = {
327
+ /**
328
+ * Indexing should start from zero and stop at `itemCount - 1`, but
329
+ * we're also handling for external drag by adding a fake item index,
330
+ * resulting to `itemCount`.
331
+ */
332
+ index: !isExternalDrag ? dragged.payload.index : itemCount,
333
+ originalIndex: !isExternalDrag ? dragged.payload.originalIndex : itemCount,
334
+ };
335
+ const toPayload = receiver?.parentId === id ? receiver.payload : undefined;
352
336
  const { index: fromIndex, originalIndex: fromOriginalIndex } = fromPayload;
353
337
  const { index: toIndex, originalIndex: toOriginalIndex } = toPayload ?? {};
354
- const toItem = (toOriginalIndex !== undefined) ? data?.[toOriginalIndex] : undefined;
338
+ const toItem = toOriginalIndex !== undefined ? data?.[toOriginalIndex] : undefined;
339
+ const fromItem = data?.[fromOriginalIndex] || dragged.payload;
340
+ throttledSetIsExternalDrag(false);
355
341
  // Reset all shifts and call callback, regardless of whether toPayload exists.
356
342
  resetShifts();
357
343
  if (totalDragEnd) {
@@ -361,7 +347,8 @@ const DraxListUnforwarded = (props, forwardedRef) => {
361
347
  toItem,
362
348
  cancelled: (0, types_1.isWithCancelledFlag)(eventData) ? eventData.cancelled : false,
363
349
  index: fromIndex,
364
- item: data?.[fromOriginalIndex],
350
+ item: fromItem,
351
+ isExternalDrag,
365
352
  });
366
353
  }
367
354
  // Reset currently dragged over position index to undefined.
@@ -370,9 +357,10 @@ const DraxListUnforwarded = (props, forwardedRef) => {
370
357
  onItemDragPositionChange?.({
371
358
  ...eventData,
372
359
  index: fromIndex,
373
- item: data?.[fromOriginalIndex],
360
+ item: fromItem,
374
361
  toIndex: undefined,
375
362
  previousIndex: draggedToIndex.current,
363
+ isExternalDrag,
376
364
  });
377
365
  }
378
366
  draggedToIndex.current = undefined;
@@ -387,9 +375,10 @@ const DraxListUnforwarded = (props, forwardedRef) => {
387
375
  setOriginalIndexes(newOriginalIndexes);
388
376
  onItemReorder?.({
389
377
  fromIndex,
390
- fromItem: data[fromOriginalIndex],
378
+ fromItem,
391
379
  toIndex: toIndex,
392
- toItem: data[toOriginalIndex],
380
+ toItem,
381
+ isExternalDrag,
393
382
  });
394
383
  }
395
384
  return snapbackTarget;
@@ -410,6 +399,7 @@ const DraxListUnforwarded = (props, forwardedRef) => {
410
399
  ]);
411
400
  // Monitor drag starts to handle callbacks.
412
401
  const onMonitorDragStart = (0, react_1.useCallback)((eventData) => {
402
+ parentDraxViewProps?.onMonitorDragStart?.(eventData);
413
403
  const { dragged } = eventData;
414
404
  // First, check if we need to do anything.
415
405
  if (reorderable && dragged.parentId === id) {
@@ -420,26 +410,34 @@ const DraxListUnforwarded = (props, forwardedRef) => {
420
410
  ...eventData,
421
411
  index,
422
412
  item: data?.[originalIndex],
413
+ isExternalDrag: false,
423
414
  });
424
415
  }
425
- }, [
426
- id,
427
- reorderable,
428
- data,
429
- setDraggedItem,
430
- onItemDragStart,
431
- ]);
416
+ }, [id, reorderable, data, setDraggedItem, onItemDragStart]);
432
417
  // Monitor drags to react with item shifts and auto-scrolling.
433
418
  const onMonitorDragOver = (0, react_1.useCallback)((eventData) => {
419
+ parentDraxViewProps?.onMonitorDragOver?.(eventData);
434
420
  const { dragged, receiver, monitorOffsetRatio } = eventData;
421
+ const isExternalDrag = dragged.parentId !== id || typeof dragged.payload.originalIndex !== 'number';
435
422
  // First, check if we need to shift items.
436
- if (reorderable && dragged.parentId === id) {
437
- // One of our list items is being dragged.
438
- const fromPayload = dragged.payload;
423
+ if (reorderable) {
424
+ const fromPayload = {
425
+ /**
426
+ * Indexing should start from zero and stop at `itemCount - 1`, but
427
+ * we're also handling for external drag by adding a fake item index,
428
+ * resulting to `itemCount`.
429
+ */
430
+ index: !isExternalDrag ? dragged.payload.index : itemCount,
431
+ originalIndex: !isExternalDrag ? dragged.payload.originalIndex : itemCount,
432
+ };
433
+ if (typeof draggedItem.value !== 'number') {
434
+ /** DraxList is receiving external drag */
435
+ setDraggedItem(itemCount);
436
+ }
437
+ monitoringExternalDragStyle && draggedItem.value === itemCount && throttledSetIsExternalDrag(true);
438
+ const fromItem = data?.[fromPayload.originalIndex] || dragged.payload;
439
439
  // Find its current position index in the list, if any.
440
- const toPayload = receiver?.parentId === id
441
- ? receiver.payload
442
- : undefined;
440
+ const toPayload = receiver?.parentId === id ? receiver.payload : undefined;
443
441
  // Check and update currently dragged over position index.
444
442
  const toIndex = toPayload?.index;
445
443
  if (toIndex !== draggedToIndex.current) {
@@ -447,13 +445,14 @@ const DraxListUnforwarded = (props, forwardedRef) => {
447
445
  ...eventData,
448
446
  toIndex,
449
447
  index: fromPayload.index,
450
- item: data?.[fromPayload.originalIndex],
448
+ item: fromItem,
451
449
  previousIndex: draggedToIndex.current,
450
+ isExternalDrag,
452
451
  });
453
452
  draggedToIndex.current = toIndex;
454
453
  }
455
454
  // Update shift transforms for items in the list.
456
- updateShifts(fromPayload, toPayload ?? fromPayload);
455
+ updateShifts(fromPayload, toPayload ?? fromPayload, dragged);
457
456
  }
458
457
  // Next, see if we need to auto-scroll.
459
458
  const ratio = horizontal ? monitorOffsetRatio.x : monitorOffsetRatio.y;
@@ -470,28 +469,44 @@ const DraxListUnforwarded = (props, forwardedRef) => {
470
469
  }
471
470
  startScroll();
472
471
  }
473
- }, [
474
- id,
475
- reorderable,
476
- data,
477
- updateShifts,
478
- horizontal,
479
- stopScroll,
480
- startScroll,
481
- onItemDragPositionChange,
482
- ]);
472
+ }, [id, reorderable, data, updateShifts, horizontal, stopScroll, startScroll, onItemDragPositionChange]);
483
473
  // Monitor drag exits to stop scrolling, update shifts, and update draggedToIndex.
484
- const onMonitorDragExit = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, false), [handleInternalDragEnd]);
474
+ const onMonitorDragExit = (0, react_1.useCallback)((eventData) => {
475
+ handleInternalDragEnd(eventData, false);
476
+ parentDraxViewProps?.onMonitorDragExit?.(eventData);
477
+ }, [handleInternalDragEnd]);
485
478
  /*
486
479
  * Monitor drag ends to stop scrolling, update shifts, and possibly reorder.
487
480
  * This addresses the Android case where if we drag a list item and auto-scroll
488
481
  * too far, the drag gets cancelled.
489
482
  */
490
- const onMonitorDragEnd = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, true), [handleInternalDragEnd]);
483
+ const onMonitorDragEnd = (0, react_1.useCallback)((eventData) => {
484
+ const defaultSnapbackTarget = handleInternalDragEnd(eventData, true);
485
+ const providedSnapTarget = parentDraxViewProps?.onMonitorDragEnd?.(eventData);
486
+ resetDraggedItem();
487
+ return providedSnapTarget ?? defaultSnapbackTarget;
488
+ }, [handleInternalDragEnd]);
491
489
  // Monitor drag drops to stop scrolling, update shifts, and possibly reorder.
492
- const onMonitorDragDrop = (0, react_1.useCallback)((eventData) => handleInternalDragEnd(eventData, true), [handleInternalDragEnd]);
493
- return (react_1.default.createElement(DraxView_1.DraxView, { id: id, style: style, scrollPositionRef: scrollPositionRef, onMeasure: onMeasureContainer, onMonitorDragStart: onMonitorDragStart, onMonitorDragOver: onMonitorDragOver, onMonitorDragExit: onMonitorDragExit, onMonitorDragEnd: onMonitorDragEnd, onMonitorDragDrop: onMonitorDragDrop },
494
- react_1.default.createElement(DraxSubprovider_1.DraxSubprovider, { parent: { id, nodeHandleRef } },
495
- react_1.default.createElement(react_native_1.FlatList, { ...flatListProps, style: flatListStyle, ref: setFlatListRefs, renderItem: renderItem, onScroll: onScroll, onContentSizeChange: onContentSizeChange, data: reorderedData }))));
490
+ const onMonitorDragDrop = (0, react_1.useCallback)((eventData) => {
491
+ const defaultSnapbackTarget = handleInternalDragEnd(eventData, true);
492
+ const providedSnapTarget = parentDraxViewProps?.onMonitorDragDrop?.(eventData);
493
+ draggedItem.value === itemCount && resetDraggedItem();
494
+ return providedSnapTarget ?? defaultSnapbackTarget;
495
+ }, [handleInternalDragEnd]);
496
+ const [isExternalDrag, setIsExternalDrag] = (0, react_1.useState)(false);
497
+ const throttledSetIsExternalDrag = (0, react_1.useMemo)(() => (0, lodash_throttle_1.default)((position) => {
498
+ setIsExternalDrag(position);
499
+ }, 300), []);
500
+ return (react_1.default.createElement(DraxView_1.DraxView, { ...parentDraxViewProps, style: [parentDraxViewProps?.style, isExternalDrag && monitoringExternalDragStyle], id: id, scrollPosition: scrollPosition, onMeasure: event => {
501
+ parentDraxViewProps?.onMeasure?.(event);
502
+ return onMeasureContainer(event);
503
+ }, onMonitorDragStart: onMonitorDragStart, onMonitorDragOver: onMonitorDragOver, onMonitorDragExit: onMonitorDragExit, onMonitorDragEnd: onMonitorDragEnd, onMonitorDragDrop: onMonitorDragDrop },
504
+ react_1.default.createElement(DraxSubprovider_1.DraxSubprovider, { parent: {
505
+ id,
506
+ viewRef: {
507
+ current: flatListRef?.current?.getNativeScrollRef?.(),
508
+ },
509
+ } },
510
+ react_1.default.createElement(react_native_reanimated_1.default.FlatList, { ...flatListProps, style: style, ref: setScrollRefs, renderItem: renderItem, onScroll: onScroll, onContentSizeChange: onContentSizeChange, data: reorderedData }))));
496
511
  };
497
512
  exports.DraxList = (0, react_1.forwardRef)(DraxListUnforwarded);
@@ -0,0 +1,7 @@
1
+ import React from 'react';
2
+ import { DraxListItemProps, DraxViewProps } from './types';
3
+ declare const RenderItemComponent: <T extends unknown>({ itemProps: { index, item, originalIndex, horizontal, lockItemDragsToMainAxis, draggedItem, shiftsRef, itemMeasurementsRef, prevItemMeasurementsRef, resetDraggedItem, keyExtractor, previousShiftsRef, registrationsRef, data, }, ...draxViewProps }: {
4
+ itemProps: DraxListItemProps<T>;
5
+ } & DraxViewProps) => React.JSX.Element;
6
+ export declare const DraxListItem: typeof RenderItemComponent;
7
+ export {};