@souscheflabs/reanimated-flashlist 0.1.7

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 (104) hide show
  1. package/README.md +282 -0
  2. package/lib/AnimatedFlashList.d.ts +6 -0
  3. package/lib/AnimatedFlashList.d.ts.map +1 -0
  4. package/lib/AnimatedFlashList.js +207 -0
  5. package/lib/AnimatedFlashListItem.d.ts +33 -0
  6. package/lib/AnimatedFlashListItem.d.ts.map +1 -0
  7. package/lib/AnimatedFlashListItem.js +155 -0
  8. package/lib/__tests__/utils/test-utils.d.ts +82 -0
  9. package/lib/__tests__/utils/test-utils.d.ts.map +1 -0
  10. package/lib/__tests__/utils/test-utils.js +115 -0
  11. package/lib/constants/animations.d.ts +39 -0
  12. package/lib/constants/animations.d.ts.map +1 -0
  13. package/lib/constants/animations.js +100 -0
  14. package/lib/constants/drag.d.ts +11 -0
  15. package/lib/constants/drag.d.ts.map +1 -0
  16. package/lib/constants/drag.js +47 -0
  17. package/lib/constants/index.d.ts +3 -0
  18. package/lib/constants/index.d.ts.map +1 -0
  19. package/lib/constants/index.js +18 -0
  20. package/lib/contexts/DragStateContext.d.ts +73 -0
  21. package/lib/contexts/DragStateContext.d.ts.map +1 -0
  22. package/lib/contexts/DragStateContext.js +148 -0
  23. package/lib/contexts/ListAnimationContext.d.ts +104 -0
  24. package/lib/contexts/ListAnimationContext.d.ts.map +1 -0
  25. package/lib/contexts/ListAnimationContext.js +184 -0
  26. package/lib/contexts/index.d.ts +5 -0
  27. package/lib/contexts/index.d.ts.map +1 -0
  28. package/lib/contexts/index.js +10 -0
  29. package/lib/hooks/animations/index.d.ts +9 -0
  30. package/lib/hooks/animations/index.d.ts.map +1 -0
  31. package/lib/hooks/animations/index.js +13 -0
  32. package/lib/hooks/animations/useListEntryAnimation.d.ts +38 -0
  33. package/lib/hooks/animations/useListEntryAnimation.d.ts.map +1 -0
  34. package/lib/hooks/animations/useListEntryAnimation.js +90 -0
  35. package/lib/hooks/animations/useListExitAnimation.d.ts +67 -0
  36. package/lib/hooks/animations/useListExitAnimation.d.ts.map +1 -0
  37. package/lib/hooks/animations/useListExitAnimation.js +146 -0
  38. package/lib/hooks/drag/index.d.ts +20 -0
  39. package/lib/hooks/drag/index.d.ts.map +1 -0
  40. package/lib/hooks/drag/index.js +26 -0
  41. package/lib/hooks/drag/useDragAnimatedStyle.d.ts +33 -0
  42. package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -0
  43. package/lib/hooks/drag/useDragAnimatedStyle.js +61 -0
  44. package/lib/hooks/drag/useDragGesture.d.ts +30 -0
  45. package/lib/hooks/drag/useDragGesture.d.ts.map +1 -0
  46. package/lib/hooks/drag/useDragGesture.js +189 -0
  47. package/lib/hooks/drag/useDragShift.d.ts +21 -0
  48. package/lib/hooks/drag/useDragShift.d.ts.map +1 -0
  49. package/lib/hooks/drag/useDragShift.js +85 -0
  50. package/lib/hooks/drag/useDropCompensation.d.ts +27 -0
  51. package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -0
  52. package/lib/hooks/drag/useDropCompensation.js +90 -0
  53. package/lib/hooks/index.d.ts +8 -0
  54. package/lib/hooks/index.d.ts.map +1 -0
  55. package/lib/hooks/index.js +18 -0
  56. package/lib/index.d.ts +42 -0
  57. package/lib/index.d.ts.map +1 -0
  58. package/lib/index.js +69 -0
  59. package/lib/types/animations.d.ts +71 -0
  60. package/lib/types/animations.d.ts.map +1 -0
  61. package/lib/types/animations.js +2 -0
  62. package/lib/types/drag.d.ts +94 -0
  63. package/lib/types/drag.d.ts.map +1 -0
  64. package/lib/types/drag.js +2 -0
  65. package/lib/types/index.d.ts +4 -0
  66. package/lib/types/index.d.ts.map +1 -0
  67. package/lib/types/index.js +19 -0
  68. package/lib/types/list.d.ts +136 -0
  69. package/lib/types/list.d.ts.map +1 -0
  70. package/lib/types/list.js +2 -0
  71. package/package.json +73 -0
  72. package/src/AnimatedFlashList.tsx +411 -0
  73. package/src/AnimatedFlashListItem.tsx +212 -0
  74. package/src/__tests__/components/AnimatedFlashList.test.tsx +365 -0
  75. package/src/__tests__/components/AnimatedFlashListItem.test.tsx +371 -0
  76. package/src/__tests__/contexts/DragStateContext.test.tsx +169 -0
  77. package/src/__tests__/contexts/ListAnimationContext.test.tsx +324 -0
  78. package/src/__tests__/hooks/useDragAnimatedStyle.test.tsx +118 -0
  79. package/src/__tests__/hooks/useDragGesture.test.tsx +169 -0
  80. package/src/__tests__/hooks/useDragShift.test.tsx +94 -0
  81. package/src/__tests__/hooks/useDropCompensation.test.tsx +182 -0
  82. package/src/__tests__/hooks/useListEntryAnimation.test.tsx +135 -0
  83. package/src/__tests__/hooks/useListExitAnimation.test.tsx +175 -0
  84. package/src/__tests__/utils/test-utils.tsx +159 -0
  85. package/src/constants/animations.ts +107 -0
  86. package/src/constants/drag.ts +51 -0
  87. package/src/constants/index.ts +2 -0
  88. package/src/contexts/DragStateContext.tsx +197 -0
  89. package/src/contexts/ListAnimationContext.tsx +302 -0
  90. package/src/contexts/index.ts +9 -0
  91. package/src/hooks/animations/index.ts +9 -0
  92. package/src/hooks/animations/useListEntryAnimation.ts +108 -0
  93. package/src/hooks/animations/useListExitAnimation.ts +197 -0
  94. package/src/hooks/drag/index.ts +20 -0
  95. package/src/hooks/drag/useDragAnimatedStyle.ts +80 -0
  96. package/src/hooks/drag/useDragGesture.ts +267 -0
  97. package/src/hooks/drag/useDragShift.ts +119 -0
  98. package/src/hooks/drag/useDropCompensation.ts +120 -0
  99. package/src/hooks/index.ts +16 -0
  100. package/src/index.ts +105 -0
  101. package/src/types/animations.ts +76 -0
  102. package/src/types/drag.ts +101 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/list.ts +178 -0
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # @souschef/reanimated-flashlist
2
+
3
+ A high-performance animated FlashList with drag-to-reorder and entry/exit animations for React Native.
4
+
5
+ ## Features
6
+
7
+ - **Drag-to-reorder** with smooth animations and autoscroll
8
+ - **Entry animations** when items appear in the list
9
+ - **Exit animations** with configurable slide/fade/scale effects
10
+ - **Layout animations** for remaining items when one is removed
11
+ - **60fps performance** via Reanimated UI-thread animations
12
+ - **Full TypeScript support** with generics
13
+
14
+ ## Installation
15
+
16
+ ### From npm (when published)
17
+ ```bash
18
+ npm install @souschef/reanimated-flashlist
19
+ ```
20
+
21
+ ### From GitHub (private repo)
22
+
23
+ Using HTTPS (will prompt for credentials):
24
+ ```bash
25
+ npm install github:SousChefLabs/reanimated-flashlist#v0.1.1
26
+ ```
27
+
28
+ Using SSH (recommended for CI/private repos):
29
+ ```bash
30
+ npm install git+ssh://git@github.com:SousChefLabs/reanimated-flashlist.git#v0.1.1
31
+ ```
32
+
33
+ Or add to `package.json`:
34
+ ```json
35
+ {
36
+ "dependencies": {
37
+ "@souschef/reanimated-flashlist": "github:SousChefLabs/reanimated-flashlist#v0.1.1"
38
+ }
39
+ }
40
+ ```
41
+
42
+ Replace `v0.1.1` with the desired version tag.
43
+
44
+ ### Requirements
45
+
46
+ **React Native New Architecture required.** This library uses TurboModules and JSI for optimal performance.
47
+
48
+ - React Native 0.74.0+ with [New Architecture enabled](https://reactnative.dev/architecture/landing-page)
49
+ - `@shopify/flash-list` v2.0.0+
50
+ - `react-native-reanimated` v4.0.0+
51
+ - `react-native-gesture-handler` v2.14.0+
52
+
53
+ ```bash
54
+ npm install @shopify/flash-list react-native-reanimated react-native-gesture-handler
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ```tsx
60
+ import { AnimatedFlashList, type AnimatedListItem } from '@souschef/reanimated-flashlist';
61
+ import { GestureDetector } from 'react-native-gesture-handler';
62
+
63
+ interface MyItem extends AnimatedListItem {
64
+ title: string;
65
+ }
66
+
67
+ function MyList() {
68
+ const [items, setItems] = useState<MyItem[]>([
69
+ { id: '1', title: 'Item 1' },
70
+ { id: '2', title: 'Item 2' },
71
+ ]);
72
+
73
+ return (
74
+ <AnimatedFlashList<MyItem>
75
+ data={items}
76
+ keyExtractor={(item) => item.id}
77
+ renderItem={({ item, dragHandleProps, triggerExitAnimation }) => (
78
+ <View style={styles.item}>
79
+ <Text>{item.title}</Text>
80
+
81
+ {/* Drag handle */}
82
+ {dragHandleProps && (
83
+ <GestureDetector gesture={dragHandleProps.gesture}>
84
+ <View style={styles.dragHandle}>
85
+ <DragIcon />
86
+ </View>
87
+ </GestureDetector>
88
+ )}
89
+
90
+ {/* Delete button with exit animation */}
91
+ <TouchableOpacity
92
+ onPress={() => {
93
+ triggerExitAnimation(1, () => {
94
+ setItems(prev => prev.filter(i => i.id !== item.id));
95
+ }, 'fast');
96
+ }}
97
+ >
98
+ <DeleteIcon />
99
+ </TouchableOpacity>
100
+ </View>
101
+ )}
102
+ dragEnabled
103
+ onReorder={(itemId, fromIndex, toIndex) => {
104
+ setItems(prev => {
105
+ const newItems = [...prev];
106
+ const [removed] = newItems.splice(fromIndex, 1);
107
+ newItems.splice(toIndex, 0, removed);
108
+ return newItems;
109
+ });
110
+ }}
111
+ />
112
+ );
113
+ }
114
+ ```
115
+
116
+ ## API Reference
117
+
118
+ ### AnimatedFlashListProps
119
+
120
+ | Prop | Type | Default | Description |
121
+ |------|------|---------|-------------|
122
+ | `data` | `T[]` | Required | Array of items (must extend `AnimatedListItem`) |
123
+ | `keyExtractor` | `(item: T, index: number) => string` | Required | Extract unique key for each item |
124
+ | `renderItem` | `(info: AnimatedRenderItemInfo<T>) => ReactElement` | Required | Render function for items |
125
+ | `dragEnabled` | `boolean` | `false` | Enable drag-to-reorder |
126
+ | `onReorder` | `(itemId, fromIndex, toIndex) => void` | - | Called when item is reordered |
127
+ | `onReorderByNeighbors` | `(itemId, afterId, beforeId) => void` | - | Alternative callback with neighbor IDs (for fractional indexing) |
128
+ | `canDrag` | `(item: T, index: number) => boolean` | `() => true` | Control which items can be dragged |
129
+ | `onHapticFeedback` | `(type: HapticFeedbackType) => void` | - | Haptic feedback callback |
130
+ | `config` | `AnimatedFlashListConfig` | - | Configuration overrides |
131
+
132
+ ### AnimatedRenderItemInfo
133
+
134
+ Props passed to your `renderItem` function:
135
+
136
+ | Prop | Type | Description |
137
+ |------|------|-------------|
138
+ | `item` | `T` | The item data |
139
+ | `index` | `number` | Current index |
140
+ | `totalItems` | `number` | Total items in list |
141
+ | `dragHandleProps` | `{ gesture, isDragging } \| null` | Spread onto GestureDetector for drag handle |
142
+ | `isDragEnabled` | `boolean` | Whether drag is enabled for this item |
143
+ | `triggerExitAnimation` | `(direction, onComplete, preset?) => void` | Trigger exit animation |
144
+ | `resetExitAnimation` | `() => void` | Reset exit animation state |
145
+
146
+ ## Recipes
147
+
148
+ ### Checkbox Toggle with Exit Animation
149
+
150
+ When a checkbox is toggled and the item should exit:
151
+
152
+ ```tsx
153
+ renderItem={({ item, triggerExitAnimation }) => (
154
+ <View style={styles.item}>
155
+ <Checkbox
156
+ checked={item.completed}
157
+ onToggle={() => {
158
+ // Trigger exit animation, then remove item
159
+ triggerExitAnimation(1, () => {
160
+ removeItem(item.id);
161
+ }, 'fast'); // 'fast' preset for quick actions
162
+ }}
163
+ />
164
+ <Text>{item.title}</Text>
165
+ </View>
166
+ )}
167
+ ```
168
+
169
+ ### Swipe to Delete
170
+
171
+ ```tsx
172
+ renderItem={({ item, triggerExitAnimation }) => (
173
+ <Swipeable
174
+ onSwipeRight={() => {
175
+ triggerExitAnimation(1, () => deleteItem(item.id));
176
+ }}
177
+ onSwipeLeft={() => {
178
+ triggerExitAnimation(-1, () => deleteItem(item.id));
179
+ }}
180
+ >
181
+ <ItemContent item={item} />
182
+ </Swipeable>
183
+ )}
184
+ ```
185
+
186
+ ### Custom Drag Handle
187
+
188
+ ```tsx
189
+ renderItem={({ item, dragHandleProps }) => (
190
+ <View style={styles.item}>
191
+ <Text>{item.title}</Text>
192
+
193
+ {dragHandleProps && (
194
+ <GestureDetector gesture={dragHandleProps.gesture}>
195
+ <Animated.View
196
+ style={[
197
+ styles.dragHandle,
198
+ // Optional: visual feedback during drag
199
+ useAnimatedStyle(() => ({
200
+ opacity: dragHandleProps.isDragging.value ? 0.5 : 1,
201
+ })),
202
+ ]}
203
+ >
204
+ <GripIcon />
205
+ </Animated.View>
206
+ </GestureDetector>
207
+ )}
208
+ </View>
209
+ )}
210
+ ```
211
+
212
+ ## Configuration
213
+
214
+ ### Drag Configuration
215
+
216
+ ```tsx
217
+ <AnimatedFlashList
218
+ config={{
219
+ drag: {
220
+ itemHeight: 80, // Item height for drag calculations
221
+ dragScale: 1.05, // Scale when dragging
222
+ longPressDuration: 200, // ms to activate drag
223
+ edgeThreshold: 80, // px from edge to trigger autoscroll
224
+ maxScrollSpeed: 10, // Autoscroll speed
225
+ },
226
+ }}
227
+ />
228
+ ```
229
+
230
+ ### Animation Presets
231
+
232
+ Exit animations have two presets:
233
+
234
+ - **`'default'`** (300ms): Standard exit with staggered fade/scale
235
+ - **`'fast'`** (200ms): Quick exit for checkbox toggles
236
+
237
+ ```tsx
238
+ // Use fast preset for quick actions
239
+ triggerExitAnimation(1, onComplete, 'fast');
240
+
241
+ // Use default preset for deliberate actions
242
+ triggerExitAnimation(-1, onComplete, 'default');
243
+ ```
244
+
245
+ ## Layout Animations
246
+
247
+ When an item exits the list, remaining items automatically animate to fill the gap. This uses React Native's `LayoutAnimation` API and is enabled by default.
248
+
249
+ The animation is triggered automatically when `triggerExitAnimation` is called - no additional setup required.
250
+
251
+ ## Advanced Usage
252
+
253
+ ### Using Individual Hooks
254
+
255
+ For custom implementations, you can use the hooks directly:
256
+
257
+ ```tsx
258
+ import {
259
+ useDragGesture,
260
+ useDragShift,
261
+ useDragAnimatedStyle,
262
+ useListExitAnimation,
263
+ useListEntryAnimation,
264
+ } from '@souschef/reanimated-flashlist';
265
+ ```
266
+
267
+ ### Context Providers
268
+
269
+ For building custom list implementations:
270
+
271
+ ```tsx
272
+ import {
273
+ DragStateProvider,
274
+ ListAnimationProvider,
275
+ useDragState,
276
+ useListAnimation,
277
+ } from '@souschef/reanimated-flashlist';
278
+ ```
279
+
280
+ ## License
281
+
282
+ MIT
@@ -0,0 +1,6 @@
1
+ import React from 'react';
2
+ import type { AnimatedListItem, AnimatedFlashListProps, AnimatedFlashListRef } from './types';
3
+ export declare const AnimatedFlashList: <T extends AnimatedListItem>(props: AnimatedFlashListProps<T> & {
4
+ ref?: React.ForwardedRef<AnimatedFlashListRef>;
5
+ }) => React.ReactElement | null;
6
+ //# sourceMappingURL=AnimatedFlashList.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedFlashList.d.ts","sourceRoot":"","sources":["../src/AnimatedFlashList.tsx"],"names":[],"mappings":"AAAA,OAAO,KAON,MAAM,OAAO,CAAC;AAaf,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,oBAAoB,EAErB,MAAM,SAAS,CAAC;AA6XjB,eAAO,MAAM,iBAAiB,EAAyC,CACrE,CAAC,SAAS,gBAAgB,EAE1B,KAAK,EAAE,sBAAsB,CAAC,CAAC,CAAC,GAAG;IAAE,GAAG,CAAC,EAAE,KAAK,CAAC,YAAY,CAAC,oBAAoB,CAAC,CAAA;CAAE,KAClF,KAAK,CAAC,YAAY,GAAG,IAAI,CAAC"}
@@ -0,0 +1,207 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AnimatedFlashList = void 0;
37
+ const react_1 = __importStar(require("react"));
38
+ const react_native_1 = require("react-native");
39
+ const flash_list_1 = require("@shopify/flash-list");
40
+ const contexts_1 = require("./contexts");
41
+ const AnimatedFlashListItem_1 = require("./AnimatedFlashListItem");
42
+ const constants_1 = require("./constants");
43
+ const ItemWrapper = react_1.default.memo(function ItemWrapper({ item, index, totalItemsRef, renderItem, canDrag, dragEnabled, onReorderByDelta, onHapticFeedback, }) {
44
+ const isDragEnabled = dragEnabled && (canDrag ? canDrag(item, index) : true);
45
+ return (<AnimatedFlashListItem_1.AnimatedFlashListItem item={item} index={index} totalItems={totalItemsRef.current ?? 0} isDragEnabled={isDragEnabled} renderItem={renderItem} onReorderByDelta={onReorderByDelta} onHapticFeedback={onHapticFeedback}/>);
46
+ });
47
+ function InnerFlashList({ data, totalItemsRef, flashListRef, renderItem, keyExtractor, canDrag, dragEnabled, onReorderByDelta, onHapticFeedback, itemHeight, ListFooterComponent, onEndReached, onEndReachedThreshold, onRefresh, refreshing, refreshTintColor, contentContainerStyle, drawDistance = 500, showsVerticalScrollIndicator = true, }) {
48
+ // Get drag state context for scroll tracking
49
+ const { scrollOffset, contentHeight, visibleHeight, listTopY, setListRef } = (0, contexts_1.useDragState)();
50
+ // Register FlashList ref with drag context for autoscroll
51
+ (0, react_1.useEffect)(() => {
52
+ // Cast to unknown to satisfy the generic constraint
53
+ setListRef(flashListRef.current);
54
+ return () => setListRef(null);
55
+ }, [setListRef, flashListRef]);
56
+ // Update scroll offset on scroll
57
+ const handleScroll = (0, react_1.useCallback)((event) => {
58
+ scrollOffset.value = event.nativeEvent.contentOffset.y;
59
+ }, [scrollOffset]);
60
+ // Update content height when list content changes
61
+ const handleContentSizeChange = (0, react_1.useCallback)((_width, height) => {
62
+ contentHeight.value = height;
63
+ }, [contentHeight]);
64
+ // Update visible height and list position when layout changes
65
+ const handleLayout = (0, react_1.useCallback)((event) => {
66
+ visibleHeight.value = event.nativeEvent.layout.height;
67
+ const nativeRef = flashListRef.current?.getNativeScrollRef?.();
68
+ if (nativeRef?.measureInWindow) {
69
+ nativeRef.measureInWindow((_x, y) => {
70
+ listTopY.value = y;
71
+ });
72
+ }
73
+ }, [visibleHeight, listTopY, flashListRef]);
74
+ // Render item for FlashList
75
+ const flashListRenderItem = (0, react_1.useCallback)(({ item, index }) => (<ItemWrapper item={item} index={index} totalItemsRef={totalItemsRef} renderItem={renderItem} canDrag={canDrag} dragEnabled={dragEnabled} onReorderByDelta={onReorderByDelta} onHapticFeedback={onHapticFeedback}/>), [
76
+ totalItemsRef,
77
+ renderItem,
78
+ canDrag,
79
+ dragEnabled,
80
+ onReorderByDelta,
81
+ onHapticFeedback,
82
+ ]);
83
+ // getItemType for FlashList recycling optimization
84
+ const getItemType = (0, react_1.useCallback)(() => 'animated-item', []);
85
+ // Override item layout for consistent drag calculations
86
+ // Note: We cast the layout to include size for drag calculations
87
+ const overrideItemLayout = (0, react_1.useCallback)((layout, _item, _index) => {
88
+ // FlashList v2 uses this for span, but we extend for size in drag calculations
89
+ layout.size = itemHeight;
90
+ }, [itemHeight]);
91
+ return (<flash_list_1.FlashList ref={flashListRef} data={data} renderItem={flashListRenderItem} keyExtractor={keyExtractor} getItemType={getItemType} overrideItemLayout={overrideItemLayout} onScroll={handleScroll} onContentSizeChange={handleContentSizeChange} onLayout={handleLayout} scrollEventThrottle={16} drawDistance={drawDistance} maintainVisibleContentPosition={{ disabled: true }} showsVerticalScrollIndicator={showsVerticalScrollIndicator} contentContainerStyle={contentContainerStyle} ListFooterComponent={ListFooterComponent ?? undefined} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} refreshControl={onRefresh ? (<react_native_1.RefreshControl refreshing={refreshing ?? false} onRefresh={onRefresh} tintColor={refreshTintColor} colors={refreshTintColor ? [refreshTintColor] : undefined}/>) : undefined}/>);
92
+ }
93
+ /**
94
+ * AnimatedFlashList - High-performance animated list with drag-to-reorder
95
+ *
96
+ * A wrapper around @shopify/flash-list that provides:
97
+ * - Smooth drag-to-reorder with autoscroll
98
+ * - Entry animations for new items
99
+ * - Exit animations for removed items
100
+ * - Full TypeScript generics support
101
+ *
102
+ * @example
103
+ * ```tsx
104
+ * <AnimatedFlashList<MyItem>
105
+ * data={items}
106
+ * keyExtractor={(item) => item.id}
107
+ * renderItem={({ item, animatedStyle, dragHandleProps }) => (
108
+ * <Animated.View style={animatedStyle}>
109
+ * <MyItem item={item} />
110
+ * {dragHandleProps && (
111
+ * <GestureDetector gesture={dragHandleProps.gesture}>
112
+ * <DragHandle />
113
+ * </GestureDetector>
114
+ * )}
115
+ * </Animated.View>
116
+ * )}
117
+ * dragEnabled
118
+ * onReorder={(itemId, from, to) => reorderItems(itemId, from, to)}
119
+ * />
120
+ * ```
121
+ */
122
+ function AnimatedFlashListInner(props, ref) {
123
+ const { data, keyExtractor, renderItem, dragEnabled = false, onReorder, onReorderByNeighbors, canDrag, onHapticFeedback, config, onPrepareLayoutAnimation, ListFooterComponent, onRefresh, refreshing = false, onEndReached, onEndReachedThreshold = 0.5, contentContainerStyle, ...flashListProps } = props;
124
+ // Merge config with defaults
125
+ const dragConfig = (0, react_1.useMemo)(() => ({
126
+ ...constants_1.DEFAULT_DRAG_CONFIG,
127
+ ...config?.drag,
128
+ }), [config?.drag]);
129
+ // Ref to FlashList
130
+ const flashListRef = (0, react_1.useRef)(null);
131
+ // Expose methods to parent via ref
132
+ (0, react_1.useImperativeHandle)(ref, () => ({
133
+ prepareForLayoutAnimation: () => {
134
+ flashListRef.current?.prepareForLayoutAnimationRender();
135
+ onPrepareLayoutAnimation?.();
136
+ },
137
+ scrollToOffset: (offset, animated = true) => {
138
+ flashListRef.current?.scrollToOffset({ offset, animated });
139
+ },
140
+ scrollToIndex: (index, animated = true) => {
141
+ flashListRef.current?.scrollToIndex({ index, animated });
142
+ },
143
+ }));
144
+ // Keep valid items in ref for reorder callback
145
+ const dataRef = (0, react_1.useRef)([]);
146
+ dataRef.current = data;
147
+ // Handle reorder by index delta - converts to various callback formats
148
+ const handleReorderByDelta = (0, react_1.useCallback)((itemId, indexDelta) => {
149
+ if (indexDelta === 0)
150
+ return;
151
+ const currentItems = dataRef.current;
152
+ const currentIndex = currentItems.findIndex(item => item.id === itemId);
153
+ if (currentIndex === -1)
154
+ return;
155
+ const newIndex = Math.max(0, Math.min(currentItems.length - 1, currentIndex + indexDelta));
156
+ if (newIndex === currentIndex)
157
+ return;
158
+ // Call onReorder if provided
159
+ if (onReorder) {
160
+ onReorder(itemId, currentIndex, newIndex);
161
+ }
162
+ // Call onReorderByNeighbors if provided (for fractional indexing)
163
+ if (onReorderByNeighbors) {
164
+ let afterItemId = null;
165
+ let beforeItemId = null;
166
+ if (indexDelta > 0) {
167
+ afterItemId = currentItems[newIndex]?.id ?? null;
168
+ beforeItemId =
169
+ newIndex < currentItems.length - 1
170
+ ? currentItems[newIndex + 1]?.id ?? null
171
+ : null;
172
+ }
173
+ else {
174
+ afterItemId =
175
+ newIndex > 0 ? currentItems[newIndex - 1]?.id ?? null : null;
176
+ beforeItemId = currentItems[newIndex]?.id ?? null;
177
+ }
178
+ onReorderByNeighbors(itemId, afterItemId, beforeItemId);
179
+ }
180
+ }, [onReorder, onReorderByNeighbors]);
181
+ // Use ref for totalItems to avoid renderItem callback recreation
182
+ const totalItemsRef = (0, react_1.useRef)(data.length);
183
+ totalItemsRef.current = data.length;
184
+ // Early return for empty data
185
+ if (!data || !Array.isArray(data) || data.length === 0) {
186
+ if (ListFooterComponent) {
187
+ return (<react_native_1.View style={containerStyle}>
188
+ {react_1.default.isValidElement(ListFooterComponent)
189
+ ? ListFooterComponent
190
+ : react_1.default.createElement(ListFooterComponent)}
191
+ </react_native_1.View>);
192
+ }
193
+ return null;
194
+ }
195
+ return (<contexts_1.ListAnimationProvider>
196
+ <contexts_1.DragStateProvider config={dragConfig}>
197
+ <react_native_1.View style={containerStyle}>
198
+ <InnerFlashList data={data} totalItemsRef={totalItemsRef} flashListRef={flashListRef} renderItem={renderItem} keyExtractor={keyExtractor} canDrag={canDrag} dragEnabled={dragEnabled} onReorderByDelta={onReorder || onReorderByNeighbors ? handleReorderByDelta : undefined} onHapticFeedback={onHapticFeedback} itemHeight={dragConfig.itemHeight} ListFooterComponent={ListFooterComponent} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} onRefresh={onRefresh} refreshing={refreshing} contentContainerStyle={contentContainerStyle} {...flashListProps}/>
199
+ </react_native_1.View>
200
+ </contexts_1.DragStateProvider>
201
+ </contexts_1.ListAnimationProvider>);
202
+ }
203
+ const containerStyle = {
204
+ flex: 1,
205
+ };
206
+ // Forward ref with generic support
207
+ exports.AnimatedFlashList = (0, react_1.forwardRef)(AnimatedFlashListInner);
@@ -0,0 +1,33 @@
1
+ import React from 'react';
2
+ import type { AnimatedListItem, AnimatedRenderItemInfo, HapticFeedbackType } from './types';
3
+ interface AnimatedFlashListItemProps<T extends AnimatedListItem> {
4
+ /** The item data */
5
+ item: T;
6
+ /** Current index in list */
7
+ index: number;
8
+ /** Total number of items */
9
+ totalItems: number;
10
+ /** Whether drag is enabled for this item */
11
+ isDragEnabled: boolean;
12
+ /** Render function from parent */
13
+ renderItem: (info: AnimatedRenderItemInfo<T>) => React.ReactElement;
14
+ /** Callback when reorder occurs */
15
+ onReorderByDelta?: (itemId: string, delta: number) => void;
16
+ /** Optional haptic feedback callback */
17
+ onHapticFeedback?: (type: HapticFeedbackType) => void;
18
+ }
19
+ /**
20
+ * Internal item wrapper that provides all animation functionality.
21
+ *
22
+ * This component:
23
+ * 1. Sets up drag gesture and shift animations
24
+ * 2. Sets up entry/exit animations
25
+ * 3. Combines all animated styles
26
+ * 4. Passes everything to the consumer's renderItem function
27
+ *
28
+ * @internal
29
+ */
30
+ declare function AnimatedFlashListItemInner<T extends AnimatedListItem>({ item, index, totalItems, isDragEnabled, renderItem, onReorderByDelta, onHapticFeedback, }: AnimatedFlashListItemProps<T>): React.ReactElement | null;
31
+ export declare const AnimatedFlashListItem: typeof AnimatedFlashListItemInner;
32
+ export {};
33
+ //# sourceMappingURL=AnimatedFlashListItem.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"AnimatedFlashListItem.d.ts","sourceRoot":"","sources":["../src/AnimatedFlashListItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAwD,MAAM,OAAO,CAAC;AAY7E,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAEjB,UAAU,0BAA0B,CAAC,CAAC,SAAS,gBAAgB;IAC7D,oBAAoB;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,4BAA4B;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,4BAA4B;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,aAAa,EAAE,OAAO,CAAC;IACvB,kCAAkC;IAClC,UAAU,EAAE,CAAC,IAAI,EAAE,sBAAsB,CAAC,CAAC,CAAC,KAAK,KAAK,CAAC,YAAY,CAAC;IACpE,mCAAmC;IACnC,gBAAgB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IAC3D,wCAAwC;IACxC,gBAAgB,CAAC,EAAE,CAAC,IAAI,EAAE,kBAAkB,KAAK,IAAI,CAAC;CACvD;AAED;;;;;;;;;;GAUG;AACH,iBAAS,0BAA0B,CAAC,CAAC,SAAS,gBAAgB,EAAE,EAC9D,IAAI,EACJ,KAAK,EACL,UAAU,EACV,aAAa,EACb,UAAU,EACV,gBAAgB,EAChB,gBAAgB,GACjB,EAAE,0BAA0B,CAAC,CAAC,CAAC,GAAG,KAAK,CAAC,YAAY,GAAG,IAAI,CAwJ3D;AAGD,eAAO,MAAM,qBAAqB,EAE7B,OAAO,0BAA0B,CAAC"}