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.
- package/README.md +1 -0
- package/lib/commonjs/components/ReorderableListCell.js +4 -2
- package/lib/commonjs/components/ReorderableListCell.js.map +1 -1
- package/lib/commonjs/components/ReorderableListCore.js +61 -4
- package/lib/commonjs/components/ReorderableListCore.js.map +1 -1
- package/lib/commonjs/contexts/ReorderableListContext.js.map +1 -1
- package/lib/commonjs/hooks/index.js +11 -0
- package/lib/commonjs/hooks/index.js.map +1 -1
- package/lib/commonjs/hooks/useStableCallback.js +21 -0
- package/lib/commonjs/hooks/useStableCallback.js.map +1 -0
- package/lib/commonjs/types/misc.js.map +1 -1
- package/lib/module/components/ReorderableListCell.js +4 -2
- package/lib/module/components/ReorderableListCell.js.map +1 -1
- package/lib/module/components/ReorderableListCore.js +63 -6
- package/lib/module/components/ReorderableListCore.js.map +1 -1
- package/lib/module/contexts/ReorderableListContext.js.map +1 -1
- package/lib/module/hooks/index.js +1 -0
- package/lib/module/hooks/index.js.map +1 -1
- package/lib/module/hooks/useStableCallback.js +15 -0
- package/lib/module/hooks/useStableCallback.js.map +1 -0
- package/lib/module/types/misc.js.map +1 -1
- package/lib/typescript/components/ReorderableListCell.d.ts.map +1 -1
- package/lib/typescript/components/ReorderableListCore.d.ts.map +1 -1
- package/lib/typescript/contexts/ReorderableListContext.d.ts +2 -1
- package/lib/typescript/contexts/ReorderableListContext.d.ts.map +1 -1
- package/lib/typescript/hooks/index.d.ts +1 -0
- package/lib/typescript/hooks/index.d.ts.map +1 -1
- package/lib/typescript/hooks/useStableCallback.d.ts +4 -0
- package/lib/typescript/hooks/useStableCallback.d.ts.map +1 -0
- package/lib/typescript/types/misc.d.ts +3 -1
- package/lib/typescript/types/misc.d.ts.map +1 -1
- package/lib/typescript/types/props.d.ts +5 -1
- package/lib/typescript/types/props.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/components/ReorderableListCell.tsx +12 -3
- package/src/components/ReorderableListCore.tsx +71 -11
- package/src/contexts/ReorderableListContext.ts +2 -1
- package/src/hooks/index.ts +1 -0
- package/src/hooks/useStableCallback.ts +16 -0
- package/src/types/misc.ts +8 -1
- 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 =
|
|
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={
|
|
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
|
|
package/src/hooks/index.ts
CHANGED
|
@@ -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 {
|
|
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
|
};
|
package/src/types/props.ts
CHANGED
|
@@ -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
|
*/
|