react-native-reorderable-list 0.15.0 → 0.16.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 (41) hide show
  1. package/README.md +1 -0
  2. package/lib/commonjs/components/ReorderableListCell.js +4 -2
  3. package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
  4. package/lib/commonjs/components/ReorderableListCore.js +61 -4
  5. package/lib/commonjs/components/ReorderableListCore.js.map +1 -1
  6. package/lib/commonjs/contexts/ReorderableListContext.js.map +1 -1
  7. package/lib/commonjs/hooks/index.js +11 -0
  8. package/lib/commonjs/hooks/index.js.map +1 -1
  9. package/lib/commonjs/hooks/useStableCallback.js +21 -0
  10. package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
  11. package/lib/commonjs/types/misc.js.map +1 -1
  12. package/lib/module/components/ReorderableListCell.js +4 -2
  13. package/lib/module/components/ReorderableListCell.js.map +1 -1
  14. package/lib/module/components/ReorderableListCore.js +63 -6
  15. package/lib/module/components/ReorderableListCore.js.map +1 -1
  16. package/lib/module/contexts/ReorderableListContext.js.map +1 -1
  17. package/lib/module/hooks/index.js +1 -0
  18. package/lib/module/hooks/index.js.map +1 -1
  19. package/lib/module/hooks/useStableCallback.js +15 -0
  20. package/lib/module/hooks/useStableCallback.js.map +1 -0
  21. package/lib/module/types/misc.js.map +1 -1
  22. package/lib/typescript/components/ReorderableListCell.d.ts.map +1 -1
  23. package/lib/typescript/components/ReorderableListCore.d.ts.map +1 -1
  24. package/lib/typescript/contexts/ReorderableListContext.d.ts +2 -1
  25. package/lib/typescript/contexts/ReorderableListContext.d.ts.map +1 -1
  26. package/lib/typescript/hooks/index.d.ts +1 -0
  27. package/lib/typescript/hooks/index.d.ts.map +1 -1
  28. package/lib/typescript/hooks/useStableCallback.d.ts +4 -0
  29. package/lib/typescript/hooks/useStableCallback.d.ts.map +1 -0
  30. package/lib/typescript/types/misc.d.ts +3 -1
  31. package/lib/typescript/types/misc.d.ts.map +1 -1
  32. package/lib/typescript/types/props.d.ts +5 -1
  33. package/lib/typescript/types/props.d.ts.map +1 -1
  34. package/package.json +1 -1
  35. package/src/components/ReorderableListCell.tsx +12 -3
  36. package/src/components/ReorderableListCore.tsx +71 -11
  37. package/src/contexts/ReorderableListContext.ts +2 -1
  38. package/src/hooks/index.ts +1 -0
  39. package/src/hooks/useStableCallback.ts +16 -0
  40. package/src/types/misc.ts +8 -1
  41. package/src/types/props.ts +5 -1
@@ -3,6 +3,7 @@ import {
3
3
  CellRendererProps,
4
4
  FlatList,
5
5
  FlatListProps,
6
+ InteractionManager,
6
7
  LayoutChangeEvent,
7
8
  Platform,
8
9
  ScrollView,
@@ -42,7 +43,7 @@ import {
42
43
  SCALE_ANIMATION_CONFIG_DEFAULT,
43
44
  } from './constants';
44
45
  import {ReorderableListCell} from './ReorderableListCell';
45
- import {usePropAsSharedValue} from '../hooks';
46
+ import {usePropAsSharedValue, useStableCallback} from '../hooks';
46
47
 
47
48
  const AnimatedFlatList = Animated.createAnimatedComponent(
48
49
  FlatList,
@@ -87,10 +88,12 @@ const ReorderableListCore = <T,>(
87
88
  cellAnimations,
88
89
  dragEnabled = true,
89
90
  shouldUpdateActiveItem,
91
+ itemLayoutAnimation,
90
92
  panGesture,
91
93
  panEnabled = true,
92
94
  panActivateAfterLongPress,
93
95
  data,
96
+ keyExtractor,
94
97
  ...rest
95
98
  }: ReorderableListCoreProps<T>,
96
99
  ref: React.ForwardedRef<FlatList<T>>,
@@ -98,6 +101,7 @@ const ReorderableListCore = <T,>(
98
101
  const scrollEnabled = rest.scrollEnabled ?? true;
99
102
 
100
103
  const flatListRef = useAnimatedRef<FlatList>();
104
+ const markedCellsRef = useRef<Map<string, 1>>();
101
105
  const [activeIndex, setActiveIndex] = useState(-1);
102
106
  const prevItemCount = useRef(data.length);
103
107
 
@@ -139,6 +143,12 @@ const ReorderableListCore = <T,>(
139
143
  const dragDirection = useSharedValue(0);
140
144
  const lastDragDirectionPivot = useSharedValue<number | null>(null);
141
145
 
146
+ const itemLayoutAnimationPropRef = useRef(itemLayoutAnimation);
147
+ itemLayoutAnimationPropRef.current = itemLayoutAnimation;
148
+
149
+ const keyExtractorPropRef = useRef(keyExtractor);
150
+ keyExtractorPropRef.current = keyExtractor;
151
+
142
152
  const scrollEnabledProp = usePropAsSharedValue(scrollEnabled);
143
153
  const animationDurationProp = usePropAsSharedValue(animationDuration);
144
154
  const autoscrollActivationDeltaProp = usePropAsSharedValue(
@@ -169,6 +179,42 @@ const ReorderableListCore = <T,>(
169
179
  prevItemCount.current = data.length;
170
180
  }, [data.length, itemHeight, itemOffset, itemCount]);
171
181
 
182
+ useEffect(() => {
183
+ if (
184
+ !markedCellsRef.current ||
185
+ // Clean keys once they surpass by 10% the size of the list itself.
186
+ markedCellsRef.current.size <= data.length + Math.ceil(data.length * 0.1)
187
+ ) {
188
+ return;
189
+ }
190
+
191
+ // Can be heavy to loop through all items, defer the task to run after interactions.
192
+ const task = InteractionManager.runAfterInteractions(() => {
193
+ if (!markedCellsRef.current) {
194
+ return;
195
+ }
196
+
197
+ const map = new Map<string, 1>();
198
+ for (let i = 0; i < data.length; i++) {
199
+ const key = keyExtractorPropRef.current?.(data[i], i) || i.toString();
200
+ if (markedCellsRef.current.has(key)) {
201
+ map.set(key, markedCellsRef.current.get(key)!);
202
+ }
203
+ }
204
+
205
+ markedCellsRef.current = map;
206
+ });
207
+
208
+ return () => {
209
+ task.cancel();
210
+ };
211
+ }, [data]);
212
+
213
+ const createCellKey = useCallback((cellKey: string) => {
214
+ const mark = markedCellsRef.current?.get(cellKey) || 0;
215
+ return `${cellKey}#${mark}`;
216
+ }, []);
217
+
172
218
  const listContextValue = useMemo(
173
219
  () => ({
174
220
  draggedHeight,
@@ -176,6 +222,7 @@ const ReorderableListCore = <T,>(
176
222
  draggedIndex,
177
223
  dragEndHandlers,
178
224
  activeIndex,
225
+ itemLayoutAnimation: itemLayoutAnimationPropRef,
179
226
  cellAnimations: {
180
227
  ...cellAnimations,
181
228
  transform:
@@ -195,6 +242,7 @@ const ReorderableListCore = <T,>(
195
242
  dragEndHandlers,
196
243
  activeIndex,
197
244
  cellAnimations,
245
+ itemLayoutAnimationPropRef,
198
246
  scaleDefault,
199
247
  opacityDefault,
200
248
  ],
@@ -394,9 +442,28 @@ const ReorderableListCore = <T,>(
394
442
  setTimeout(runOnUI(resetSharedValues), animationDurationProp.value);
395
443
  }, [resetSharedValues, animationDurationProp]);
396
444
 
445
+ const markCells = (fromIndex: number, toIndex: number) => {
446
+ if (!markedCellsRef.current) {
447
+ markedCellsRef.current = new Map();
448
+ }
449
+
450
+ const start = Math.min(fromIndex, toIndex);
451
+ const end = Math.max(fromIndex, toIndex);
452
+ for (let i = start; i <= end; i++) {
453
+ const cellKey = keyExtractorPropRef.current?.(data[i], i) || i.toString();
454
+ if (!markedCellsRef.current.has(cellKey)) {
455
+ markedCellsRef.current.set(cellKey, 1);
456
+ } else {
457
+ markedCellsRef.current.delete(cellKey);
458
+ }
459
+ }
460
+ };
461
+
397
462
  const reorder = (fromIndex: number, toIndex: number) => {
398
463
  runOnUI(resetSharedValues)();
399
464
 
465
+ markCells(fromIndex, toIndex);
466
+
400
467
  if (fromIndex !== toIndex) {
401
468
  onReorder({from: fromIndex, to: toIndex});
402
469
  }
@@ -950,12 +1017,12 @@ const ReorderableListCore = <T,>(
950
1017
  onScroll || null,
951
1018
  ]);
952
1019
 
953
- const renderAnimatedCell = useCallback(
1020
+ const renderAnimatedCell = useStableCallback(
954
1021
  ({cellKey, ...props}: CellRendererProps<T>) => (
955
1022
  <ReorderableListCell
956
1023
  {...props}
957
1024
  // forces remount with key change on reorder
958
- key={`${cellKey}+${props.index}`}
1025
+ key={createCellKey(cellKey)}
959
1026
  itemOffset={itemOffset}
960
1027
  itemHeight={itemHeight}
961
1028
  dragY={dragY}
@@ -964,14 +1031,6 @@ const ReorderableListCore = <T,>(
964
1031
  startDrag={startDrag}
965
1032
  />
966
1033
  ),
967
- [
968
- itemOffset,
969
- itemHeight,
970
- dragY,
971
- draggedIndex,
972
- animationDurationProp,
973
- startDrag,
974
- ],
975
1034
  );
976
1035
 
977
1036
  return (
@@ -981,6 +1040,7 @@ const ReorderableListCore = <T,>(
981
1040
  {...rest}
982
1041
  ref={handleRef}
983
1042
  data={data}
1043
+ keyExtractor={keyExtractor}
984
1044
  CellRendererComponent={renderAnimatedCell}
985
1045
  onLayout={handleFlatListLayout}
986
1046
  onScroll={composedScrollHandler}
@@ -2,13 +2,14 @@ import React from 'react';
2
2
 
3
3
  import type {SharedValue} from 'react-native-reanimated';
4
4
 
5
- import {ReorderableListCellAnimations} from '../types';
5
+ import {ItemLayoutAnimation, ReorderableListCellAnimations} from '../types';
6
6
 
7
7
  interface ReorderableListContextData {
8
8
  currentIndex: SharedValue<number>;
9
9
  draggedHeight: SharedValue<number>;
10
10
  dragEndHandlers: SharedValue<((from: number, to: number) => void)[][]>;
11
11
  activeIndex: number;
12
+ itemLayoutAnimation: React.MutableRefObject<ItemLayoutAnimation | undefined>;
12
13
  cellAnimations: ReorderableListCellAnimations;
13
14
  }
14
15
 
@@ -4,3 +4,4 @@ export * from './useIsActive';
4
4
  export * from './useReorderableDrag';
5
5
  export * from './useReorderableDragEnd';
6
6
  export * from './useReorderableDragStart';
7
+ export * from './useStableCallback';
@@ -0,0 +1,16 @@
1
+ import {useCallback, useLayoutEffect, useRef} from 'react';
2
+
3
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
4
+ type Fn = (...args: any[]) => any;
5
+
6
+ export const useStableCallback = <T extends Fn>(value: T) => {
7
+ const callback = useRef<T>(value);
8
+
9
+ useLayoutEffect(() => {
10
+ callback.current = value;
11
+ });
12
+
13
+ return useCallback((...args: Parameters<T>): ReturnType<T> => {
14
+ return callback.current?.(...args);
15
+ }, []);
16
+ };
package/src/types/misc.ts CHANGED
@@ -1,4 +1,6 @@
1
- import {SharedValue} from 'react-native-reanimated';
1
+ import {ComponentProps} from 'react';
2
+
3
+ import Animated, {SharedValue} from 'react-native-reanimated';
2
4
 
3
5
  export enum ReorderableListState {
4
6
  IDLE = 0,
@@ -7,6 +9,11 @@ export enum ReorderableListState {
7
9
  AUTOSCROLL,
8
10
  }
9
11
 
12
+ export type ItemLayoutAnimation = Exclude<
13
+ ComponentProps<typeof Animated.View>['layout'],
14
+ undefined
15
+ >;
16
+
10
17
  export type SharedValueOrType<T> = {
11
18
  [TKey in keyof T]?: SharedValue<T[TKey]> | T[TKey];
12
19
  };
@@ -20,7 +20,7 @@ import type {
20
20
  import {PanGesture} from 'react-native-gesture-handler';
21
21
  import {SharedValue, useAnimatedScrollHandler} from 'react-native-reanimated';
22
22
 
23
- import {MaximumOneOf, SharedValueOrType} from './misc';
23
+ import {ItemLayoutAnimation, MaximumOneOf, SharedValueOrType} from './misc';
24
24
 
25
25
  export interface ReorderableListReorderEvent {
26
26
  /**
@@ -131,6 +131,10 @@ export interface ReorderableListProps<T>
131
131
  * @deprecated In favor of `panGesture` prop.
132
132
  */
133
133
  panActivateAfterLongPress?: number;
134
+ /**
135
+ * Layout animation when the item is added to and/or removed from the view hierarchy. To skip entering or exiting animations use the LayoutAnimationConfig component from [Reanimated](https://docs.swmansion.com/react-native-reanimated).
136
+ */
137
+ itemLayoutAnimation?: ItemLayoutAnimation;
134
138
  /**
135
139
  * Event fired after an item is released and the list is reordered.
136
140
  */