react-native-reorderable-list 0.13.0 → 0.13.1

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 (38) hide show
  1. package/lib/commonjs/components/NestedReorderableList.js +2 -0
  2. package/lib/commonjs/components/NestedReorderableList.js.map +1 -1
  3. package/lib/commonjs/components/ReorderableList.js +4 -3
  4. package/lib/commonjs/components/ReorderableList.js.map +1 -1
  5. package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js +3 -1
  6. package/lib/commonjs/components/ReorderableListCore/ReorderableListCore.js.map +1 -1
  7. package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js +76 -40
  8. package/lib/commonjs/components/ReorderableListCore/useReorderableListCore.js.map +1 -1
  9. package/lib/commonjs/components/ScrollViewContainer.js +14 -3
  10. package/lib/commonjs/components/ScrollViewContainer.js.map +1 -1
  11. package/lib/commonjs/contexts/ScrollViewContainerContext.js.map +1 -1
  12. package/lib/module/components/NestedReorderableList.js +2 -0
  13. package/lib/module/components/NestedReorderableList.js.map +1 -1
  14. package/lib/module/components/ReorderableList.js +4 -3
  15. package/lib/module/components/ReorderableList.js.map +1 -1
  16. package/lib/module/components/ReorderableListCore/ReorderableListCore.js +3 -1
  17. package/lib/module/components/ReorderableListCore/ReorderableListCore.js.map +1 -1
  18. package/lib/module/components/ReorderableListCore/useReorderableListCore.js +77 -41
  19. package/lib/module/components/ReorderableListCore/useReorderableListCore.js.map +1 -1
  20. package/lib/module/components/ScrollViewContainer.js +16 -5
  21. package/lib/module/components/ScrollViewContainer.js.map +1 -1
  22. package/lib/module/contexts/ScrollViewContainerContext.js.map +1 -1
  23. package/lib/typescript/components/NestedReorderableList.d.ts.map +1 -1
  24. package/lib/typescript/components/ReorderableList.d.ts.map +1 -1
  25. package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts +1 -0
  26. package/lib/typescript/components/ReorderableListCore/ReorderableListCore.d.ts.map +1 -1
  27. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts +4 -4
  28. package/lib/typescript/components/ReorderableListCore/useReorderableListCore.d.ts.map +1 -1
  29. package/lib/typescript/components/ScrollViewContainer.d.ts.map +1 -1
  30. package/lib/typescript/contexts/ScrollViewContainerContext.d.ts +1 -0
  31. package/lib/typescript/contexts/ScrollViewContainerContext.d.ts.map +1 -1
  32. package/package.json +1 -1
  33. package/src/components/NestedReorderableList.tsx +2 -0
  34. package/src/components/ReorderableList.tsx +3 -2
  35. package/src/components/ReorderableListCore/ReorderableListCore.tsx +4 -1
  36. package/src/components/ReorderableListCore/useReorderableListCore.ts +141 -89
  37. package/src/components/ScrollViewContainer.tsx +23 -5
  38. package/src/contexts/ScrollViewContainerContext.ts +1 -0
@@ -32,6 +32,7 @@ const AnimatedFlatList = Animated.createAnimatedComponent(
32
32
  interface ReorderableListCoreProps<T> extends ReorderableListProps<T> {
33
33
  // not optional but undefined to avoid forgetting to pass a prop
34
34
  scrollViewContainerRef: React.RefObject<ScrollView> | undefined;
35
+ scrollViewPageY: SharedValue<number> | undefined;
35
36
  scrollViewHeightY: SharedValue<number> | undefined;
36
37
  scrollViewScrollOffsetY: SharedValue<number> | undefined;
37
38
  scrollViewScrollEnabled: SharedValue<boolean> | undefined;
@@ -56,6 +57,7 @@ const ReorderableListCore = <T,>(
56
57
  onDragEnd,
57
58
  onIndexChange,
58
59
  scrollViewContainerRef,
60
+ scrollViewPageY,
59
61
  scrollViewHeightY,
60
62
  scrollViewScrollOffsetY,
61
63
  scrollViewScrollEnabled,
@@ -96,9 +98,11 @@ const ReorderableListCore = <T,>(
96
98
  onDragEnd,
97
99
  onIndexChange,
98
100
  scrollViewContainerRef,
101
+ scrollViewPageY,
99
102
  scrollViewHeightY,
100
103
  scrollViewScrollOffsetY,
101
104
  scrollViewScrollEnabled,
105
+ scrollable,
102
106
  // flatlist will default to true if we pass explicitly undefined,
103
107
  // but internally we would treat it as false, so we force true
104
108
  initialScrollEnabled:
@@ -107,7 +111,6 @@ const ReorderableListCore = <T,>(
107
111
  typeof initialScrollViewScrollEnabled === 'undefined'
108
112
  ? true
109
113
  : initialScrollViewScrollEnabled,
110
- nestedScrollable: scrollable,
111
114
  cellAnimations,
112
115
  shouldUpdateActiveItem,
113
116
  panEnabled,
@@ -16,12 +16,14 @@ import Animated, {
16
16
  AnimatedRef,
17
17
  Easing,
18
18
  SharedValue,
19
+ measure,
19
20
  runOnJS,
20
21
  runOnUI,
21
22
  scrollTo,
22
23
  useAnimatedReaction,
23
24
  useAnimatedRef,
24
25
  useAnimatedScrollHandler,
26
+ useDerivedValue,
25
27
  useSharedValue,
26
28
  withDelay,
27
29
  withTiming,
@@ -37,9 +39,9 @@ import {
37
39
  ReorderableListDragEndEvent,
38
40
  ReorderableListDragStartEvent,
39
41
  ReorderableListIndexChangeEvent,
42
+ ReorderableListReorderEvent,
40
43
  ReorderableListState,
41
44
  } from '../../types';
42
- import type {ReorderableListReorderEvent} from '../../types';
43
45
 
44
46
  const version = React.version.split('.');
45
47
  const hasAutomaticBatching = version.length
@@ -60,12 +62,13 @@ interface UseReorderableListCoreArgs<T> {
60
62
  onIndexChange?: (event: ReorderableListIndexChangeEvent) => void;
61
63
  onLayout?: (event: LayoutChangeEvent) => void;
62
64
  scrollViewContainerRef: React.RefObject<ScrollView> | undefined;
65
+ scrollViewPageY: SharedValue<number> | undefined;
63
66
  scrollViewHeightY: SharedValue<number> | undefined;
64
67
  scrollViewScrollOffsetY: SharedValue<number> | undefined;
65
68
  scrollViewScrollEnabled: SharedValue<boolean> | undefined;
69
+ scrollable: boolean | undefined;
66
70
  initialScrollEnabled: boolean | undefined;
67
71
  initialScrollViewScrollEnabled: boolean | undefined;
68
- nestedScrollable: boolean | undefined;
69
72
  cellAnimations: ReorderableListCellAnimations | undefined;
70
73
  shouldUpdateActiveItem: boolean | undefined;
71
74
  panEnabled: boolean;
@@ -86,12 +89,13 @@ export const useReorderableListCore = <T>({
86
89
  onLayout,
87
90
  onIndexChange,
88
91
  scrollViewContainerRef,
92
+ scrollViewPageY,
89
93
  scrollViewHeightY,
90
94
  scrollViewScrollOffsetY,
91
95
  scrollViewScrollEnabled,
96
+ scrollable,
92
97
  initialScrollEnabled,
93
98
  initialScrollViewScrollEnabled,
94
- nestedScrollable,
95
99
  cellAnimations,
96
100
  shouldUpdateActiveItem,
97
101
  panActivateAfterLongPress,
@@ -107,7 +111,7 @@ export const useReorderableListCore = <T>({
107
111
  const startItemDragCenterY = useSharedValue<number>(0);
108
112
  const flatListScrollOffsetY = useSharedValue(0);
109
113
  const flatListHeightY = useSharedValue(0);
110
- const nestedFlatListPositionY = useSharedValue(0);
114
+ const flatListPageY = useSharedValue(0);
111
115
  // The scroll y translation of the list since drag start
112
116
  const dragScrollTranslationY = useSharedValue(0);
113
117
  // The initial scroll offset y of the list on drag start
@@ -136,6 +140,11 @@ export const useReorderableListCore = <T>({
136
140
  const lastDragDirectionPivot = useSharedValue<number | null>(null);
137
141
  const autoscrollDelta = useSharedValue(autoscrollActivationDelta);
138
142
 
143
+ // Position of the list relative to the scroll container
144
+ const nestedFlatListPositionY = useDerivedValue(
145
+ () => flatListPageY.value - (scrollViewPageY?.value || 0),
146
+ );
147
+
139
148
  useEffect(() => {
140
149
  duration.value = animationDuration;
141
150
  autoscrollDelta.value = autoscrollActivationDelta;
@@ -543,7 +552,7 @@ export const useReorderableListCore = <T>({
543
552
  },
544
553
  );
545
554
 
546
- const calculateHiddenArea = useCallback(() => {
555
+ const computeHiddenArea = useCallback(() => {
547
556
  'worklet';
548
557
  if (!scrollViewScrollOffsetY || !scrollViewHeightY) {
549
558
  return {top: 0, bottom: 0};
@@ -569,103 +578,121 @@ export const useReorderableListCore = <T>({
569
578
  flatListHeightY,
570
579
  ]);
571
580
 
572
- const calculateThresholdArea = useCallback(
573
- (hiddenArea: {top: number; bottom: number}) => {
574
- 'worklet';
575
- const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
576
- const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
577
- const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
578
- const visibleHeight =
579
- flatListHeightY.value -
580
- (hiddenArea.top + hiddenArea.bottom) -
581
- (offsetTop + offsetBottom);
581
+ const computeThresholdArea = useCallback(() => {
582
+ 'worklet';
582
583
 
583
- const area = visibleHeight * threshold;
584
- const top = area + offsetTop;
585
- const bottom = flatListHeightY.value - area - offsetBottom;
584
+ const hiddenArea = computeHiddenArea();
586
585
 
587
- return {top, bottom};
588
- },
589
- [autoscrollThreshold, autoscrollThresholdOffset, flatListHeightY],
590
- );
586
+ const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
587
+ const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
588
+ const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
589
+ const visibleHeight =
590
+ flatListHeightY.value -
591
+ (hiddenArea.top + hiddenArea.bottom) -
592
+ (offsetTop + offsetBottom);
591
593
 
592
- const calculateThresholdAreaParent = useCallback(
593
- (hiddenArea: {top: number; bottom: number}) => {
594
- 'worklet';
595
- const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
596
- const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
597
- const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
598
-
599
- const area = flatListHeightY.value * threshold;
600
- const top = area + offsetTop;
601
- const bottom = flatListHeightY.value - area - offsetBottom;
602
-
603
- // if the hidden area is 0 then we don't have a threshold area
604
- // we might have floating errors like 0.0001 which we should ignore
605
- return {
606
- top: hiddenArea.top > 0.1 ? top + hiddenArea.top : 0,
607
- bottom: hiddenArea.bottom > 0.1 ? bottom - hiddenArea.bottom : 0,
608
- };
609
- },
610
- [autoscrollThreshold, autoscrollThresholdOffset, flatListHeightY],
611
- );
594
+ const area = visibleHeight * threshold;
595
+ const top = area + offsetTop;
596
+ const bottom = flatListHeightY.value - area - offsetBottom;
612
597
 
613
- const shouldScrollParent = useCallback(
598
+ return {top, bottom};
599
+ }, [
600
+ computeHiddenArea,
601
+ autoscrollThreshold,
602
+ autoscrollThresholdOffset,
603
+ flatListHeightY,
604
+ ]);
605
+
606
+ const computeContainerThresholdArea = useCallback(() => {
607
+ 'worklet';
608
+ if (!scrollViewHeightY) {
609
+ return {top: -Infinity, bottom: Infinity};
610
+ }
611
+
612
+ const offsetTop = Math.max(0, autoscrollThresholdOffset?.top || 0);
613
+ const offsetBottom = Math.max(0, autoscrollThresholdOffset?.bottom || 0);
614
+ const threshold = Math.max(0, Math.min(autoscrollThreshold, 0.4));
615
+ const visibleHeight = scrollViewHeightY.value - (offsetTop + offsetBottom);
616
+
617
+ const area = visibleHeight * threshold;
618
+ const top = area + offsetTop;
619
+ const bottom = visibleHeight - area - offsetBottom;
620
+
621
+ return {top, bottom};
622
+ }, [autoscrollThreshold, autoscrollThresholdOffset, scrollViewHeightY]);
623
+
624
+ const shouldScrollContainer = useCallback(
614
625
  (y: number) => {
615
626
  'worklet';
616
- const hiddenArea = calculateHiddenArea();
617
- const thresholdAreaParent = calculateThresholdAreaParent(hiddenArea);
627
+ const containerThresholdArea = computeContainerThresholdArea();
628
+ const nestedListHiddenArea = computeHiddenArea();
618
629
 
619
- // we might have floating errors like 0.0001 which we should ignore
630
+ // We should scroll the container if there's a hidden part of the nested list.
631
+ // We might have floating errors like 0.0001 which we should ignore.
620
632
  return (
621
- (hiddenArea.top > 0.1 && y <= thresholdAreaParent.top) ||
622
- (hiddenArea.bottom > 0.1 && y >= thresholdAreaParent.bottom)
633
+ (nestedListHiddenArea.top > 0.01 && y <= containerThresholdArea.top) ||
634
+ (nestedListHiddenArea.bottom > 0.01 &&
635
+ y >= containerThresholdArea.bottom)
623
636
  );
624
637
  },
625
- [calculateHiddenArea, calculateThresholdAreaParent],
638
+ [computeHiddenArea, computeContainerThresholdArea],
626
639
  );
627
640
 
628
- const scrollDirection = useCallback(
629
- (y: number) => {
630
- 'worklet';
631
- const hiddenArea = calculateHiddenArea();
641
+ const getRelativeContainerY = useCallback(() => {
642
+ 'worklet';
632
643
 
633
- if (shouldScrollParent(y)) {
634
- const thresholdAreaParent = calculateThresholdAreaParent(hiddenArea);
635
- if (y <= thresholdAreaParent.top) {
636
- return -1;
637
- }
644
+ return (
645
+ currentY.value +
646
+ nestedFlatListPositionY.value -
647
+ scrollViewDragInitialScrollOffsetY.value
648
+ );
649
+ }, [currentY, nestedFlatListPositionY, scrollViewDragInitialScrollOffsetY]);
638
650
 
639
- if (y >= thresholdAreaParent.bottom) {
640
- return 1;
641
- }
651
+ const getRelativeListY = useCallback(() => {
652
+ 'worklet';
642
653
 
643
- return 0;
644
- } else if (nestedScrollable) {
645
- const thresholdArea = calculateThresholdArea(hiddenArea);
646
- if (y <= thresholdArea.top) {
647
- return -1;
648
- }
654
+ return currentY.value + scrollViewDragScrollTranslationY.value;
655
+ }, [currentY, scrollViewDragScrollTranslationY]);
649
656
 
650
- if (y >= thresholdArea.bottom) {
651
- return 1;
652
- }
657
+ const scrollDirection = useCallback(() => {
658
+ 'worklet';
659
+
660
+ const relativeContainerY = getRelativeContainerY();
661
+ if (shouldScrollContainer(relativeContainerY)) {
662
+ const containerThresholdArea = computeContainerThresholdArea();
663
+ if (relativeContainerY <= containerThresholdArea.top) {
664
+ return -1;
653
665
  }
654
666
 
655
- return 0;
656
- },
657
- [
658
- nestedScrollable,
659
- shouldScrollParent,
660
- calculateHiddenArea,
661
- calculateThresholdArea,
662
- calculateThresholdAreaParent,
663
- ],
664
- );
667
+ if (relativeContainerY >= containerThresholdArea.bottom) {
668
+ return 1;
669
+ }
670
+ } else if (scrollable) {
671
+ const relativeListY = getRelativeListY();
672
+ const thresholdArea = computeThresholdArea();
673
+
674
+ if (relativeListY <= thresholdArea.top) {
675
+ return -1;
676
+ }
677
+
678
+ if (relativeListY >= thresholdArea.bottom) {
679
+ return 1;
680
+ }
681
+ }
682
+
683
+ return 0;
684
+ }, [
685
+ shouldScrollContainer,
686
+ computeThresholdArea,
687
+ computeContainerThresholdArea,
688
+ getRelativeContainerY,
689
+ getRelativeListY,
690
+ scrollable,
691
+ ]);
665
692
 
666
693
  useAnimatedReaction(
667
- () => currentY.value + scrollViewDragScrollTranslationY.value,
668
- y => {
694
+ () => currentY.value,
695
+ () => {
669
696
  if (
670
697
  state.value === ReorderableListState.DRAGGED ||
671
698
  state.value === ReorderableListState.AUTOSCROLL
@@ -676,7 +703,7 @@ export const useReorderableListCore = <T>({
676
703
  // 1. Within the threshold area (top or bottom of list)
677
704
  // 2. Have dragged in the same direction as the scroll
678
705
  // 3. Not already in autoscroll mode
679
- if (dragDirection.value === scrollDirection(y)) {
706
+ if (dragDirection.value === scrollDirection()) {
680
707
  // When the first two conditions are met and it's already in autoscroll mode, we let it continue (no-op)
681
708
  if (state.value !== ReorderableListState.AUTOSCROLL) {
682
709
  state.value = ReorderableListState.AUTOSCROLL;
@@ -697,9 +724,8 @@ export const useReorderableListCore = <T>({
697
724
  autoscrollTrigger.value !== lastAutoscrollTrigger.value &&
698
725
  state.value === ReorderableListState.AUTOSCROLL
699
726
  ) {
700
- let y = currentY.value + scrollViewDragScrollTranslationY.value;
701
727
  const autoscrollIncrement =
702
- scrollDirection(y) *
728
+ dragDirection.value *
703
729
  AUTOSCROLL_CONFIG.increment *
704
730
  autoscrollSpeedScale;
705
731
 
@@ -708,7 +734,13 @@ export const useReorderableListCore = <T>({
708
734
  let listRef =
709
735
  flatListRef as unknown as AnimatedRef<Animated.ScrollView>;
710
736
 
711
- if (shouldScrollParent(y) && scrollViewScrollOffsetY) {
737
+ // Checking on every autoscroll whether to scroll the container,
738
+ // this allows to smoothly pass the scroll from the container to the nested list
739
+ // without any gesture input.
740
+ if (
741
+ scrollViewScrollOffsetY &&
742
+ shouldScrollContainer(getRelativeContainerY())
743
+ ) {
712
744
  scrollOffset = scrollViewScrollOffsetY.value;
713
745
  listRef =
714
746
  scrollViewContainerRef as unknown as AnimatedRef<Animated.ScrollView>;
@@ -750,7 +782,7 @@ export const useReorderableListCore = <T>({
750
782
  }
751
783
  });
752
784
 
753
- // parent scroll handler
785
+ // container scroll handler
754
786
  useAnimatedReaction(
755
787
  () => scrollViewScrollOffsetY?.value,
756
788
  value => {
@@ -826,12 +858,32 @@ export const useReorderableListCore = <T>({
826
858
 
827
859
  const handleFlatListLayout = useCallback(
828
860
  (e: LayoutChangeEvent) => {
829
- nestedFlatListPositionY.value = e.nativeEvent.layout.y;
830
861
  flatListHeightY.value = e.nativeEvent.layout.height;
831
862
 
863
+ // If nested in a scroll container.
864
+ if (scrollViewScrollOffsetY) {
865
+ // Timeout fixes a bug where measure returns height 0.
866
+ setTimeout(() => {
867
+ runOnUI(() => {
868
+ const measurement = measure(flatListRef);
869
+ if (!measurement) {
870
+ return;
871
+ }
872
+
873
+ flatListPageY.value = measurement.pageY;
874
+ })();
875
+ }, 100);
876
+ }
877
+
832
878
  onLayout?.(e);
833
879
  },
834
- [nestedFlatListPositionY, flatListHeightY, onLayout],
880
+ [
881
+ flatListRef,
882
+ flatListPageY,
883
+ flatListHeightY,
884
+ scrollViewScrollOffsetY,
885
+ onLayout,
886
+ ],
835
887
  );
836
888
 
837
889
  const handleRef = (value: FlatList<T>) => {
@@ -1,8 +1,10 @@
1
- import React, {useMemo} from 'react';
1
+ import React, {useCallback, useMemo} from 'react';
2
2
  import {LayoutChangeEvent} from 'react-native';
3
3
 
4
4
  import {Gesture, GestureDetector} from 'react-native-gesture-handler';
5
5
  import Animated, {
6
+ measure,
7
+ runOnUI,
6
8
  useAnimatedRef,
7
9
  useAnimatedScrollHandler,
8
10
  useComposedEventHandler,
@@ -21,6 +23,7 @@ export const ScrollViewContainer: React.FC<ScrollViewContainerProps> = ({
21
23
  const scrollViewScrollEnabled = useSharedValue(scrollEnabled);
22
24
  const scrollViewContainerRef = useAnimatedRef<Animated.ScrollView>();
23
25
  const scrollViewScrollOffsetY = useSharedValue(0);
26
+ const scrollViewPageY = useSharedValue(0);
24
27
  const scrollViewHeightY = useSharedValue(0);
25
28
 
26
29
  const outerScrollGesture = useMemo(() => Gesture.Native(), []);
@@ -40,6 +43,7 @@ export const ScrollViewContainer: React.FC<ScrollViewContainerProps> = ({
40
43
  const contextValue = useMemo(
41
44
  () => ({
42
45
  scrollViewContainerRef,
46
+ scrollViewPageY,
43
47
  scrollViewHeightY,
44
48
  scrollViewScrollOffsetY,
45
49
  scrollViewScrollEnabled,
@@ -48,6 +52,7 @@ export const ScrollViewContainer: React.FC<ScrollViewContainerProps> = ({
48
52
  }),
49
53
  [
50
54
  scrollViewContainerRef,
55
+ scrollViewPageY,
51
56
  scrollViewHeightY,
52
57
  scrollViewScrollOffsetY,
53
58
  scrollViewScrollEnabled,
@@ -56,11 +61,24 @@ export const ScrollViewContainer: React.FC<ScrollViewContainerProps> = ({
56
61
  ],
57
62
  );
58
63
 
59
- const handleLayout = (e: LayoutChangeEvent) => {
60
- scrollViewHeightY.value = e.nativeEvent.layout.height;
64
+ const handleLayout = useCallback(
65
+ (e: LayoutChangeEvent) => {
66
+ scrollViewHeightY.value = e.nativeEvent.layout.height;
61
67
 
62
- onLayout?.(e);
63
- };
68
+ // measuring pageY allows wrapping nested lists in other views
69
+ runOnUI(() => {
70
+ const measurement = measure(scrollViewContainerRef);
71
+ if (!measurement) {
72
+ return;
73
+ }
74
+
75
+ scrollViewPageY.value = measurement.pageY;
76
+ })();
77
+
78
+ onLayout?.(e);
79
+ },
80
+ [onLayout, scrollViewContainerRef, scrollViewHeightY, scrollViewPageY],
81
+ );
64
82
 
65
83
  return (
66
84
  <ScrollViewContainerContext.Provider value={contextValue}>
@@ -6,6 +6,7 @@ import {SharedValue} from 'react-native-reanimated';
6
6
 
7
7
  interface ScrollViewContainerContextData {
8
8
  scrollViewContainerRef: React.RefObject<ScrollView>;
9
+ scrollViewPageY: SharedValue<number>;
9
10
  scrollViewHeightY: SharedValue<number>;
10
11
  scrollViewScrollOffsetY: SharedValue<number>;
11
12
  scrollViewScrollEnabled: SharedValue<boolean>;