@@ -31,55 +31,54 @@ const animationConfig = {
31
31
const SortableListItem = ( props : Props ) => {
32
32
const { children, index} = props ;
33
33
34
- const { data, itemHeight, onItemLayout, itemsOrder, onChange, enableHaptic} = useContext ( SortableListContext ) ;
34
+ const {
35
+ data,
36
+ itemHeight,
37
+ onItemLayout,
38
+ itemsOrder,
39
+ onChange,
40
+ enableHaptic,
41
+ scale : propsScale = 1
42
+ } = useContext ( SortableListContext ) ;
35
43
const { getTranslationByIndexChange, getItemIndexById, getIndexByPosition, getIdByItemIndex} = usePresenter ( ) ;
36
44
const id : string = data [ index ] . id ;
37
45
const initialIndex = useSharedValue < number > ( map ( data , 'id' ) . indexOf ( id ) ) ;
46
+ const currIndex = useSharedValue ( initialIndex . value ) ;
38
47
const translateY = useSharedValue < number > ( 0 ) ;
39
48
40
49
const isDragging = useSharedValue ( false ) ;
41
50
const tempTranslateY = useSharedValue < number > ( 0 ) ;
42
51
const tempItemsOrder = useSharedValue < string [ ] > ( itemsOrder . value ) ;
43
- const dataManuallyChanged = useSharedValue < boolean > ( false ) ;
44
52
45
53
useDidUpdate ( ( ) => {
46
- dataManuallyChanged . value = true ;
47
- initialIndex . value = map ( data , 'id' ) . indexOf ( id ) ;
54
+ const newItemIndex = map ( data , 'id' ) . indexOf ( id ) ;
55
+
56
+ initialIndex . value = newItemIndex ;
57
+ currIndex . value = newItemIndex ;
58
+
59
+ translateY . value = 0 ;
48
60
} , [ data ] ) ;
49
61
50
- useAnimatedReaction ( ( ) => itemsOrder . value ,
51
- ( currItemsOrder , prevItemsOrder ) => {
52
- // Note: Unfortunately itemsOrder sharedValue is being initialized on each render
53
- // Therefore I added this extra check here that compares current and previous values
54
- // See open issue: https://github.com/software-mansion/react-native-reanimated/issues/3224
55
- if ( prevItemsOrder === null || currItemsOrder . join ( ',' ) === prevItemsOrder . join ( ',' ) ) {
62
+ useAnimatedReaction ( ( ) => getItemIndexById ( itemsOrder . value , id ) ,
63
+ ( newIndex , prevIndex ) => {
64
+ if ( prevIndex === null || newIndex === prevIndex ) {
56
65
return ;
57
- } else {
58
- const newIndex = getItemIndexById ( currItemsOrder , id ) ;
59
- const oldIndex = getItemIndexById ( prevItemsOrder , id ) ;
60
-
61
- /* In case the order of the item has returned back to its initial index we reset its position */
62
- if ( newIndex === initialIndex . value ) {
63
- /* Reset without an animation when the change is due to manual data change */
64
- if ( dataManuallyChanged . value ) {
65
- translateY . value = 0 ;
66
- dataManuallyChanged . value = false ;
67
- /* Reset with an animation when the change id due to user reordering */
68
- } else {
69
- translateY . value = withTiming ( 0 , animationConfig ) ;
70
- }
71
- /* Handle an order change, animate item to its new position */
72
- } else if ( newIndex !== oldIndex ) {
73
- const translation = getTranslationByIndexChange ( newIndex , oldIndex , itemHeight . value ) ;
74
- translateY . value = withTiming ( translateY . value + translation , animationConfig ) ;
75
- }
76
66
}
77
- } ) ;
67
+
68
+ currIndex . value = newIndex ;
69
+ if ( ! isDragging . value ) {
70
+ const translation = getTranslationByIndexChange ( currIndex . value , initialIndex . value , itemHeight . value ) ;
71
+
72
+ translateY . value = withTiming ( translation , animationConfig ) ;
73
+ }
74
+ } ,
75
+ [ ] ) ;
78
76
79
77
const dragOnLongPressGesture = Gesture . Pan ( )
80
78
. activateAfterLongPress ( 250 )
81
79
. onStart ( ( ) => {
82
80
isDragging . value = true ;
81
+ translateY . value = getTranslationByIndexChange ( currIndex . value , initialIndex . value , itemHeight . value ) ;
83
82
tempTranslateY . value = translateY . value ;
84
83
tempItemsOrder . value = itemsOrder . value ;
85
84
} )
@@ -92,10 +91,15 @@ const SortableListItem = (props: Props) => {
92
91
translateY . value = tempTranslateY . value + event . translationY ;
93
92
94
93
// Swapping items
95
- const newIndex = getIndexByPosition ( translateY . value , itemHeight . value ) + initialIndex . value ;
94
+ let newIndex = getIndexByPosition ( translateY . value , itemHeight . value ) + initialIndex . value ;
96
95
const oldIndex = getItemIndexById ( itemsOrder . value , id ) ;
97
96
98
97
if ( newIndex !== oldIndex ) {
98
+ // Sometimes getIndexByPosition will give an index that is off by one because of rounding error (floor\ceil does not help)
99
+ if ( Math . abs ( newIndex - oldIndex ) > 1 ) {
100
+ newIndex = Math . sign ( newIndex - oldIndex ) + oldIndex ;
101
+ }
102
+
99
103
const itemIdToSwap = getIdByItemIndex ( itemsOrder . value , newIndex ) ;
100
104
101
105
if ( itemIdToSwap !== undefined ) {
@@ -124,7 +128,7 @@ const SortableListItem = (props: Props) => {
124
128
} ) ;
125
129
126
130
const draggedAnimatedStyle = useAnimatedStyle ( ( ) => {
127
- const scaleY = withSpring ( isDragging . value ? 1.1 : 1 ) ;
131
+ const scale = withSpring ( isDragging . value ? propsScale : 1 ) ;
128
132
const zIndex = isDragging . value ? 100 : withTiming ( 0 , animationConfig ) ;
129
133
const opacity = isDragging . value ? 0.95 : 1 ;
130
134
const shadow = isDragging . value
@@ -140,7 +144,7 @@ const SortableListItem = (props: Props) => {
140
144
return {
141
145
backgroundColor : Colors . $backgroundDefault , // required for elevation to work in Android
142
146
zIndex,
143
- transform : [ { translateY : translateY . value } , { scaleY } ] ,
147
+ transform : [ { translateY : translateY . value } , { scale } ] ,
144
148
opacity,
145
149
...shadow
146
150
} ;
0 commit comments