react-native-reorderable-list 0.12.0 → 0.13.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 (23) hide show
  1. package/README.md +0 -1
  2. package/lib/commonjs/components/ReorderableListCell.js +2 -2
  3. package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
  4. package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js +0 -2
  5. package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +1 -1
  6. package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js +84 -59
  7. package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js.map +1 -1
  8. package/lib/module/components/ReorderableListCell.js +2 -2
  9. package/lib/module/components/ReorderableListCell.js.map +1 -1
  10. package/lib/module/components/ReorderableListCore/ReorderableListCore.js +0 -2
  11. package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +1 -1
  12. package/lib/module/components/ReorderableListCore/useReorderableListCore.js +84 -59
  13. package/lib/module/components/ReorderableListCore/useReorderableListCore.js.map +1 -1
  14. package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts.map +1 -1
  15. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts +1 -2
  16. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts.map +1 -1
  17. package/lib/typescript/types/props.d.ts +0 -5
  18. package/lib/typescript/types/props.d.ts.map +1 -1
  19. package/package.json +1 -1
  20. package/src/components/ReorderableListCell.tsx +3 -3
  21. package/src/components/ReorderableListCore/ReorderableListCore.tsx +0 -2
  22. package/src/components/ReorderableListCore/useReorderableListCore.ts +136 -95
  23. package/src/types/props.ts +0 -5
@@ -54,7 +54,6 @@ interface UseReorderableListCoreArgs<T> {
54
54
  autoscrollDelay: number;
55
55
  autoscrollActivationDelta: number;
56
56
  animationDuration: number;
57
- dragReorderThreshold: number;
58
57
  onReorder: (event: ReorderableListReorderEvent) => void;
59
58
  onDragStart?: (event: ReorderableListDragStartEvent) => void;
60
59
  onDragEnd?: (event: ReorderableListDragEndEvent) => void;
@@ -81,7 +80,6 @@ export const useReorderableListCore = <T>({
81
80
  autoscrollDelay,
82
81
  autoscrollActivationDelta,
83
82
  animationDuration,
84
- dragReorderThreshold,
85
83
  onReorder,
86
84
  onDragStart,
87
85
  onDragEnd,
@@ -105,22 +103,25 @@ export const useReorderableListCore = <T>({
105
103
  const gestureState = useSharedValue<State>(State.UNDETERMINED);
106
104
  const currentY = useSharedValue(0);
107
105
  const currentTranslationY = useSharedValue(0);
106
+ const currentItemDragCenterY = useSharedValue<number | null>(null);
107
+ const startItemDragCenterY = useSharedValue<number>(0);
108
108
  const flatListScrollOffsetY = useSharedValue(0);
109
109
  const flatListHeightY = useSharedValue(0);
110
110
  const nestedFlatListPositionY = useSharedValue(0);
111
+ // The scroll y translation of the list since drag start
111
112
  const dragScrollTranslationY = useSharedValue(0);
113
+ // The initial scroll offset y of the list on drag start
112
114
  const dragInitialScrollOffsetY = useSharedValue(0);
115
+ // The scroll y translation of the ScrollViewContainer since drag start
113
116
  const scrollViewDragScrollTranslationY = useSharedValue(0);
117
+ // The initial scroll offset y of the ScrollViewContainer on drag start
114
118
  const scrollViewDragInitialScrollOffsetY = useSharedValue(0);
115
119
  const draggedHeight = useSharedValue(0);
116
120
  const itemOffset = useSharedValue<number[]>([]);
117
121
  const itemHeight = useSharedValue<number[]>([]);
118
122
  const autoscrollTrigger = useSharedValue(-1);
119
123
  const lastAutoscrollTrigger = useSharedValue(-1);
120
- const previousY = useSharedValue(0);
121
124
  const dragY = useSharedValue(0);
122
- const previousDirection = useSharedValue(0);
123
- const previousIndex = useSharedValue(-1);
124
125
  const currentIndex = useSharedValue(-1);
125
126
  const draggedIndex = useSharedValue(-1);
126
127
  const state = useSharedValue<ReorderableListState>(ReorderableListState.IDLE);
@@ -175,6 +176,8 @@ export const useReorderableListCore = <T>({
175
176
  * Decides the intended drag direction of the user.
176
177
  * This is used to to determine if the user intends to autoscroll
177
178
  * when within the threshold area.
179
+ *
180
+ * @param e - The payload of the pan gesture update event.
178
181
  */
179
182
  const setDragDirection = useCallback(
180
183
  (e: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
@@ -196,6 +199,39 @@ export const useReorderableListCore = <T>({
196
199
  [dragDirection, lastDragDirectionPivot, autoscrollDelta],
197
200
  );
198
201
 
202
+ const setCurrentItemDragCenterY = useCallback(
203
+ (e: GestureUpdateEvent<PanGestureHandlerEventPayload>) => {
204
+ 'worklet';
205
+
206
+ if (currentItemDragCenterY.value === null) {
207
+ if (currentIndex.value >= 0) {
208
+ const itemCenter = itemHeight.value[currentIndex.value] * 0.5;
209
+ // the y coordinate of the item relative to the list
210
+ const itemY =
211
+ itemOffset.value[currentIndex.value] -
212
+ (flatListScrollOffsetY.value +
213
+ scrollViewDragScrollTranslationY.value);
214
+
215
+ const value = itemY + itemCenter + e.translationY;
216
+ startItemDragCenterY.value = value;
217
+ currentItemDragCenterY.value = value;
218
+ }
219
+ } else {
220
+ currentItemDragCenterY.value =
221
+ startItemDragCenterY.value + e.translationY;
222
+ }
223
+ },
224
+ [
225
+ currentItemDragCenterY,
226
+ currentIndex,
227
+ startItemDragCenterY,
228
+ itemOffset,
229
+ itemHeight,
230
+ flatListScrollOffsetY,
231
+ scrollViewDragScrollTranslationY,
232
+ ],
233
+ );
234
+
199
235
  const panGestureHandler = useMemo(
200
236
  () =>
201
237
  Gesture.Pan()
@@ -215,6 +251,8 @@ export const useReorderableListCore = <T>({
215
251
  }
216
252
 
217
253
  if (state.value !== ReorderableListState.RELEASED) {
254
+ setCurrentItemDragCenterY(e);
255
+
218
256
  currentY.value = startY.value + e.translationY;
219
257
  currentTranslationY.value = e.translationY;
220
258
  dragY.value =
@@ -233,9 +271,10 @@ export const useReorderableListCore = <T>({
233
271
  currentTranslationY,
234
272
  dragY,
235
273
  gestureState,
236
- setDragDirection,
237
274
  dragScrollTranslationY,
238
275
  scrollViewDragScrollTranslationY,
276
+ setDragDirection,
277
+ setCurrentItemDragCenterY,
239
278
  ],
240
279
  );
241
280
 
@@ -295,6 +334,7 @@ export const useReorderableListCore = <T>({
295
334
  scrollViewDragScrollTranslationY.value = 0;
296
335
  dragDirection.value = 0;
297
336
  lastDragDirectionPivot.value = null;
337
+ currentItemDragCenterY.value = null;
298
338
  }, [
299
339
  state,
300
340
  draggedIndex,
@@ -303,6 +343,7 @@ export const useReorderableListCore = <T>({
303
343
  scrollViewDragScrollTranslationY,
304
344
  dragDirection,
305
345
  lastDragDirectionPivot,
346
+ currentItemDragCenterY,
306
347
  ]);
307
348
 
308
349
  const resetSharedValuesAfterAnimations = useCallback(() => {
@@ -325,102 +366,106 @@ export const useReorderableListCore = <T>({
325
366
  }
326
367
  };
327
368
 
328
- const getIndexFromY = useCallback(
329
- (y: number) => {
369
+ const recomputeLayout = useCallback(
370
+ (from: number, to: number) => {
330
371
  'worklet';
331
372
 
332
- const relativeY = flatListScrollOffsetY.value + y;
333
- const count = itemOffset.value.length;
373
+ const itemDirection = to > from;
374
+ const index1 = itemDirection ? from : to;
375
+ const index2 = itemDirection ? to : from;
376
+
377
+ const newOffset1 = itemOffset.value[index1];
378
+ const newHeight1 = itemHeight.value[index2];
379
+ const newOffset2 =
380
+ itemOffset.value[index2] +
381
+ itemHeight.value[index2] -
382
+ itemHeight.value[index1];
383
+ const newHeight2 = itemHeight.value[index1];
384
+
385
+ itemOffset.value[index1] = newOffset1;
386
+ itemHeight.value[index1] = newHeight1;
387
+ itemOffset.value[index2] = newOffset2;
388
+ itemHeight.value[index2] = newHeight2;
389
+ },
390
+ [itemOffset, itemHeight],
391
+ );
334
392
 
335
- for (let i = 0; i < count; i++) {
336
- if (currentIndex.value === i) {
337
- continue;
338
- }
393
+ /**
394
+ * Computes a potential new drop container for the current dragged item and evaluates
395
+ * whether the dragged item center is nearer to the center of the current container or the new one.
396
+ *
397
+ * @returns The new index if the center of the dragged item is closer to the center of
398
+ * the new drop container or the current index if closer to the current drop container.
399
+ */
400
+ const computeCurrentIndex = useCallback(() => {
401
+ 'worklet';
339
402
 
340
- const direction = i > currentIndex.value ? 1 : -1;
341
- const threshold = Math.max(0, Math.min(1, dragReorderThreshold));
342
- const height = itemHeight.value[i];
343
- const offset = itemOffset.value[i] + height * threshold * direction;
403
+ if (currentItemDragCenterY.value === null) {
404
+ return currentIndex.value;
405
+ }
344
406
 
345
- if (
346
- (i === 0 && relativeY <= offset) ||
347
- (i === count - 1 && relativeY >= offset + height) ||
348
- (relativeY >= offset && relativeY <= offset + height)
349
- ) {
350
- return {index: i, direction};
351
- }
407
+ // apply scroll offset and scroll container translation
408
+ const relativeDragCenterY =
409
+ flatListScrollOffsetY.value +
410
+ scrollViewDragScrollTranslationY.value +
411
+ currentItemDragCenterY.value;
412
+
413
+ const currentOffset = itemOffset.value[currentIndex.value];
414
+ const currentHeight = itemHeight.value[currentIndex.value];
415
+ const currentCenter = currentOffset + currentHeight * 0.5;
416
+
417
+ const max = itemOffset.value.length;
418
+ const possibleIndex =
419
+ relativeDragCenterY < currentCenter
420
+ ? Math.max(0, currentIndex.value - 1)
421
+ : Math.min(max - 1, currentIndex.value + 1);
422
+
423
+ if (currentIndex.value !== possibleIndex) {
424
+ let possibleOffset = itemOffset.value[possibleIndex];
425
+ if (possibleIndex > currentIndex.value) {
426
+ possibleOffset += itemHeight.value[possibleIndex] - currentHeight;
352
427
  }
353
428
 
354
- return {
355
- index: currentIndex.value,
356
- direction: previousDirection.value,
357
- };
358
- },
359
- [
360
- dragReorderThreshold,
361
- currentIndex,
362
- flatListScrollOffsetY,
363
- previousDirection,
364
- itemOffset,
365
- itemHeight,
366
- ],
367
- );
429
+ const possibleCenter = possibleOffset + currentHeight * 0.5;
430
+ const distanceFromCurrent = Math.abs(relativeDragCenterY - currentCenter);
431
+ const distanceFromPossible = Math.abs(
432
+ relativeDragCenterY - possibleCenter,
433
+ );
368
434
 
369
- const setCurrentIndex = useCallback(
370
- (y: number) => {
371
- 'worklet';
435
+ return distanceFromCurrent <= distanceFromPossible
436
+ ? currentIndex.value
437
+ : possibleIndex;
438
+ }
372
439
 
373
- const {index: newIndex, direction: newDirection} = getIndexFromY(y);
374
- const delta = Math.abs(previousY.value - y);
440
+ return currentIndex.value;
441
+ }, [
442
+ currentIndex,
443
+ currentItemDragCenterY,
444
+ itemOffset,
445
+ itemHeight,
446
+ flatListScrollOffsetY,
447
+ scrollViewDragScrollTranslationY,
448
+ ]);
375
449
 
376
- if (
377
- currentIndex.value !== newIndex &&
378
- // if the same two items re-swap index check delta and direction to avoid swap flickering
379
- (previousIndex.value !== newIndex ||
380
- (previousDirection.value !== newDirection && delta >= 5))
381
- ) {
382
- const itemDirection = newIndex > currentIndex.value;
383
- const index1 = itemDirection ? currentIndex.value : newIndex;
384
- const index2 = itemDirection ? newIndex : currentIndex.value;
385
-
386
- const newOffset1 = itemOffset.value[index1];
387
- const newHeight1 = itemHeight.value[index2];
388
- const newOffset2 =
389
- itemOffset.value[index2] +
390
- (itemHeight.value[index2] - itemHeight.value[index1]);
391
- const newHeight2 = itemHeight.value[index1];
392
-
393
- itemOffset.value[index1] = newOffset1;
394
- itemHeight.value[index1] = newHeight1;
395
- itemOffset.value[index2] = newOffset2;
396
- itemHeight.value[index2] = newHeight2;
397
-
398
- previousY.value = y;
399
- previousDirection.value = newDirection;
400
- previousIndex.value = currentIndex.value;
401
- currentIndex.value = newIndex;
402
-
403
- onIndexChange?.({index: newIndex});
404
- }
405
- },
406
- [
407
- currentIndex,
408
- previousIndex,
409
- previousDirection,
410
- previousY,
411
- itemOffset,
412
- itemHeight,
413
- getIndexFromY,
414
- onIndexChange,
415
- ],
416
- );
450
+ const setCurrentIndex = useCallback(() => {
451
+ 'worklet';
452
+
453
+ const newIndex = computeCurrentIndex();
454
+
455
+ if (currentIndex.value !== newIndex) {
456
+ recomputeLayout(currentIndex.value, newIndex);
457
+ currentIndex.value = newIndex;
458
+
459
+ onIndexChange?.({index: newIndex});
460
+ }
461
+ }, [currentIndex, computeCurrentIndex, recomputeLayout, onIndexChange]);
417
462
 
418
463
  const runDefaultDragAnimations = useCallback(
419
464
  (type: 'start' | 'end') => {
420
465
  'worklet';
421
466
 
422
467
  // if no custom scale run default
423
- if (!(cellAnimations && 'transformtra' in cellAnimations)) {
468
+ if (!(cellAnimations && 'transform' in cellAnimations)) {
424
469
  const scaleConfig = SCALE_ANIMATION_CONFIG_DEFAULT[type];
425
470
  scaleDefault.value = withTiming(scaleConfig.toValue, scaleConfig);
426
471
  }
@@ -625,7 +670,7 @@ export const useReorderableListCore = <T>({
625
670
  state.value === ReorderableListState.DRAGGED ||
626
671
  state.value === ReorderableListState.AUTOSCROLL
627
672
  ) {
628
- setCurrentIndex(y);
673
+ setCurrentIndex();
629
674
 
630
675
  // Trigger autoscroll when:
631
676
  // 1. Within the threshold area (top or bottom of list)
@@ -674,7 +719,7 @@ export const useReorderableListCore = <T>({
674
719
 
675
720
  // when autoscrolling user may not be moving his finger so we need
676
721
  // to update the current position of the dragged item here
677
- setCurrentIndex(y);
722
+ setCurrentIndex();
678
723
  }
679
724
  },
680
725
  );
@@ -711,8 +756,7 @@ export const useReorderableListCore = <T>({
711
756
  value => {
712
757
  if (value && scrollViewScrollEnabled) {
713
758
  // checking if the list is not scrollable instead of the scrolling state
714
- // fixes a bug on iOS where the item is shifted after autoscrolling and then
715
- // moving await from autoscroll area
759
+ // fixes a bug on iOS where the item is shifted after autoscrolling and moving away from the area
716
760
  if (!scrollViewScrollEnabled.value) {
717
761
  scrollViewDragScrollTranslationY.value =
718
762
  value - scrollViewDragInitialScrollOffsetY.value;
@@ -747,13 +791,11 @@ export const useReorderableListCore = <T>({
747
791
  }
748
792
 
749
793
  dragInitialScrollOffsetY.value = flatListScrollOffsetY.value;
750
- scrollViewDragInitialScrollOffsetY.value = scrollViewScrollOffsetY
751
- ? scrollViewScrollOffsetY.value
752
- : 0;
794
+ scrollViewDragInitialScrollOffsetY.value =
795
+ scrollViewScrollOffsetY?.value || 0;
753
796
 
754
797
  draggedHeight.value = itemHeight.value[index];
755
798
  draggedIndex.value = index;
756
- previousIndex.value = -1;
757
799
  currentIndex.value = index;
758
800
  state.value = ReorderableListState.DRAGGED;
759
801
 
@@ -772,7 +814,6 @@ export const useReorderableListCore = <T>({
772
814
  scrollViewDragInitialScrollOffsetY,
773
815
  setScrollEnabled,
774
816
  currentIndex,
775
- previousIndex,
776
817
  draggedHeight,
777
818
  draggedIndex,
778
819
  state,
@@ -95,11 +95,6 @@ export interface ReorderableListProps<T>
95
95
  * Default: `5`.
96
96
  */
97
97
  autoscrollActivationDelta?: number;
98
- /**
99
- * Specifies the fraction of an item's size at which it will shift when a dragged item crosses over it.
100
- * For example, a value of 0.2 means the item shifts when the dragged item passes 20% of its height (vertical list). Default is `0.2`.
101
- */
102
- dragReorderThreshold?: number;
103
98
  /**
104
99
  * Duration of the animations in milliseconds.
105
100
  * Be aware that users won't be able to drag a new item until the dragged item is released and