@souscheflabs/reanimated-flashlist 0.3.3 → 0.3.5

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 (37) hide show
  1. package/lib/AnimatedFlashList.d.ts.map +1 -1
  2. package/lib/AnimatedFlashList.js +56 -14
  3. package/lib/AnimatedFlashListItem.d.ts.map +1 -1
  4. package/lib/AnimatedFlashListItem.js +63 -1
  5. package/lib/constants/drag.d.ts +8 -0
  6. package/lib/constants/drag.d.ts.map +1 -1
  7. package/lib/constants/drag.js +12 -1
  8. package/lib/contexts/DragStateContext.d.ts +4 -5
  9. package/lib/contexts/DragStateContext.d.ts.map +1 -1
  10. package/lib/contexts/DragStateContext.js +32 -18
  11. package/lib/contexts/ListAnimationContext.d.ts.map +1 -1
  12. package/lib/contexts/ListAnimationContext.js +11 -5
  13. package/lib/hooks/animations/useListEntryAnimation.d.ts.map +1 -1
  14. package/lib/hooks/animations/useListEntryAnimation.js +16 -9
  15. package/lib/hooks/animations/useListExitAnimation.d.ts.map +1 -1
  16. package/lib/hooks/animations/useListExitAnimation.js +4 -2
  17. package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -1
  18. package/lib/hooks/drag/useDragAnimatedStyle.js +189 -10
  19. package/lib/hooks/drag/useDragGesture.d.ts.map +1 -1
  20. package/lib/hooks/drag/useDragGesture.js +226 -38
  21. package/lib/hooks/drag/useDragShift.d.ts.map +1 -1
  22. package/lib/hooks/drag/useDragShift.js +98 -30
  23. package/lib/hooks/drag/useDropCompensation.d.ts +6 -4
  24. package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -1
  25. package/lib/hooks/drag/useDropCompensation.js +60 -40
  26. package/package.json +11 -9
  27. package/src/AnimatedFlashList.tsx +111 -32
  28. package/src/AnimatedFlashListItem.tsx +76 -3
  29. package/src/constants/drag.ts +13 -0
  30. package/src/contexts/DragStateContext.tsx +38 -25
  31. package/src/contexts/ListAnimationContext.tsx +12 -5
  32. package/src/hooks/animations/useListEntryAnimation.ts +18 -10
  33. package/src/hooks/animations/useListExitAnimation.ts +4 -2
  34. package/src/hooks/drag/useDragAnimatedStyle.ts +251 -11
  35. package/src/hooks/drag/useDragGesture.ts +270 -40
  36. package/src/hooks/drag/useDragShift.ts +112 -32
  37. package/src/hooks/drag/useDropCompensation.ts +74 -43
@@ -1 +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;AAyYjB,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"}
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;AAwdjB,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"}
@@ -40,11 +40,29 @@ const flash_list_1 = require("@shopify/flash-list");
40
40
  const contexts_1 = require("./contexts");
41
41
  const AnimatedFlashListItem_1 = require("./AnimatedFlashListItem");
42
42
  const constants_1 = require("./constants");
43
+ class AnimationErrorBoundary extends react_1.default.Component {
44
+ state = { hasError: false };
45
+ static getDerivedStateFromError() {
46
+ return { hasError: true };
47
+ }
48
+ componentDidCatch(error) {
49
+ if (__DEV__) {
50
+ console.warn('[AnimatedFlashList] Animation error caught by error boundary. ' +
51
+ 'Falling back to non-animated list.', error);
52
+ }
53
+ }
54
+ render() {
55
+ if (this.state.hasError) {
56
+ return this.props.fallback;
57
+ }
58
+ return this.props.children;
59
+ }
60
+ }
43
61
  const ItemWrapper = react_1.default.memo(function ItemWrapper({ item, index, totalItemsRef, renderItem, canDrag, dragEnabled, onReorderByDelta, onHapticFeedback, }) {
44
62
  const isDragEnabled = dragEnabled && (canDrag ? canDrag(item, index) : true);
45
63
  return (<AnimatedFlashListItem_1.AnimatedFlashListItem item={item} index={index} totalItems={totalItemsRef.current ?? 0} isDragEnabled={isDragEnabled} renderItem={renderItem} onReorderByDelta={onReorderByDelta} onHapticFeedback={onHapticFeedback}/>);
46
64
  });
47
- function InnerFlashList({ data, totalItemsRef, flashListRef, renderItem, keyExtractor, canDrag, dragEnabled, onReorderByDelta, onHapticFeedback, itemHeight, ListFooterComponent, onEndReached, onEndReachedThreshold, onRefresh, refreshing, refreshTintColor, contentContainerStyle, drawDistance = 500, showsVerticalScrollIndicator = true, }) {
65
+ function InnerFlashList({ data, totalItemsRef, flashListRef, renderItem, keyExtractor, canDrag, dragEnabled, onReorderByDelta, onHapticFeedback, itemHeight, ListFooterComponent, ListEmptyComponent, onEndReached, onEndReachedThreshold, onRefresh, refreshing, refreshTintColor, contentContainerStyle, drawDistance = 500, showsVerticalScrollIndicator = true, }) {
48
66
  // Get drag state context for scroll tracking
49
67
  const { scrollOffset, contentHeight, visibleHeight, listTopY, setListRef, totalItems } = (0, contexts_1.useDragState)();
50
68
  // Register FlashList ref with drag context for autoscroll
@@ -96,7 +114,7 @@ function InnerFlashList({ data, totalItemsRef, flashListRef, renderItem, keyExtr
96
114
  // FlashList v2 uses this for span, but we extend for size in drag calculations
97
115
  layout.size = itemHeight;
98
116
  }, [itemHeight]);
99
- 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}/>);
117
+ 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} ListEmptyComponent={ListEmptyComponent ?? undefined} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} refreshControl={onRefresh ? (<react_native_1.RefreshControl refreshing={refreshing ?? false} onRefresh={onRefresh} tintColor={refreshTintColor} colors={refreshTintColor ? [refreshTintColor] : undefined}/>) : undefined}/>);
100
118
  }
101
119
  /**
102
120
  * AnimatedFlashList - High-performance animated list with drag-to-reorder
@@ -128,7 +146,7 @@ function InnerFlashList({ data, totalItemsRef, flashListRef, renderItem, keyExtr
128
146
  * ```
129
147
  */
130
148
  function AnimatedFlashListInner(props, ref) {
131
- const { data, keyExtractor, renderItem, dragEnabled = false, onReorder, onReorderByNeighbors, canDrag, onHapticFeedback, config, onPrepareLayoutAnimation, ListFooterComponent, onRefresh, refreshing = false, onEndReached, onEndReachedThreshold = 0.5, contentContainerStyle, estimatedItemSize, ...flashListProps } = props;
149
+ const { data, keyExtractor, renderItem, dragEnabled = false, onReorder, onReorderByNeighbors, canDrag, onHapticFeedback, config, onPrepareLayoutAnimation, ListFooterComponent, ListEmptyComponent, onRefresh, refreshing = false, onEndReached, onEndReachedThreshold = 0.5, contentContainerStyle, estimatedItemSize, ...flashListProps } = props;
132
150
  // Merge config with defaults
133
151
  // Use estimatedItemSize as default itemHeight if not explicitly configured
134
152
  const dragConfig = (0, react_1.useMemo)(() => ({
@@ -193,22 +211,46 @@ function AnimatedFlashListInner(props, ref) {
193
211
  totalItemsRef.current = data.length;
194
212
  // Early return for empty data
195
213
  if (!data || !Array.isArray(data) || data.length === 0) {
196
- if (ListFooterComponent) {
214
+ const emptyContent = ListEmptyComponent
215
+ ? react_1.default.isValidElement(ListEmptyComponent)
216
+ ? ListEmptyComponent
217
+ : react_1.default.createElement(ListEmptyComponent)
218
+ : null;
219
+ const footerContent = ListFooterComponent
220
+ ? react_1.default.isValidElement(ListFooterComponent)
221
+ ? ListFooterComponent
222
+ : react_1.default.createElement(ListFooterComponent)
223
+ : null;
224
+ if (emptyContent || footerContent) {
197
225
  return (<react_native_1.View style={containerStyle}>
198
- {react_1.default.isValidElement(ListFooterComponent)
199
- ? ListFooterComponent
200
- : react_1.default.createElement(ListFooterComponent)}
226
+ {emptyContent}
227
+ {footerContent}
201
228
  </react_native_1.View>);
202
229
  }
203
230
  return null;
204
231
  }
205
- return (<contexts_1.ListAnimationProvider>
206
- <contexts_1.DragStateProvider config={dragConfig}>
207
- <react_native_1.View style={containerStyle}>
208
- <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}/>
209
- </react_native_1.View>
210
- </contexts_1.DragStateProvider>
211
- </contexts_1.ListAnimationProvider>);
232
+ const plainFlashListFallback = (<react_native_1.View style={containerStyle}>
233
+ <flash_list_1.FlashList ref={flashListRef} data={data} renderItem={({ item, index }) => renderItem({
234
+ item,
235
+ index,
236
+ totalItems: data.length,
237
+ animatedStyle: {},
238
+ dragHandleProps: null,
239
+ isDragging: false,
240
+ isDragEnabled: false,
241
+ triggerExitAnimation: () => { },
242
+ resetExitAnimation: () => { },
243
+ })} keyExtractor={keyExtractor} contentContainerStyle={contentContainerStyle} ListFooterComponent={ListFooterComponent ?? undefined} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold}/>
244
+ </react_native_1.View>);
245
+ return (<AnimationErrorBoundary fallback={plainFlashListFallback}>
246
+ <contexts_1.ListAnimationProvider>
247
+ <contexts_1.DragStateProvider config={dragConfig}>
248
+ <react_native_1.View style={containerStyle}>
249
+ <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} ListEmptyComponent={ListEmptyComponent} onEndReached={onEndReached} onEndReachedThreshold={onEndReachedThreshold} onRefresh={onRefresh} refreshing={refreshing} contentContainerStyle={contentContainerStyle} {...flashListProps}/>
250
+ </react_native_1.View>
251
+ </contexts_1.DragStateProvider>
252
+ </contexts_1.ListAnimationProvider>
253
+ </AnimationErrorBoundary>);
212
254
  }
213
255
  const containerStyle = {
214
256
  flex: 1,
@@ -1 +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,CA8J3D;AAGD,eAAO,MAAM,qBAAqB,EAE7B,OAAO,0BAA0B,CAAC"}
1
+ {"version":3,"file":"AnimatedFlashListItem.d.ts","sourceRoot":"","sources":["../src/AnimatedFlashListItem.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAmE,MAAM,OAAO,CAAC;AAaxF,OAAO,KAAK,EACV,gBAAgB,EAChB,sBAAsB,EACtB,kBAAkB,EACnB,MAAM,SAAS,CAAC;AAMjB,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,CAkO3D;AAGD,eAAO,MAAM,qBAAqB,EAE7B,OAAO,0BAA0B,CAAC"}
@@ -38,6 +38,10 @@ const react_1 = __importStar(require("react"));
38
38
  const react_native_reanimated_1 = __importStar(require("react-native-reanimated"));
39
39
  const hooks_1 = require("./hooks");
40
40
  const contexts_1 = require("./contexts");
41
+ const constants_1 = require("./constants");
42
+ // Debug: Track component instances to detect recycling.
43
+ // Note: IDs are not stable across hot reloads (counter persists at module scope).
44
+ let instanceCounter = 0;
41
45
  /**
42
46
  * Internal item wrapper that provides all animation functionality.
43
47
  *
@@ -50,10 +54,68 @@ const contexts_1 = require("./contexts");
50
54
  * @internal
51
55
  */
52
56
  function AnimatedFlashListItemInner({ item, index, totalItems, isDragEnabled, renderItem, onReorderByDelta, onHapticFeedback, }) {
57
+ // Debug: Track this component instance (only incremented in DEV)
58
+ const instanceIdRef = (0, react_1.useRef)(null);
59
+ if (__DEV__ && instanceIdRef.current === null) {
60
+ instanceIdRef.current = ++instanceCounter;
61
+ }
62
+ const instanceId = instanceIdRef.current;
63
+ // Track previous itemId to detect recycling
64
+ const prevItemIdRef = (0, react_1.useRef)(item.id);
65
+ // Log every render with instance tracking (verbose only - fires per render per item)
66
+ if (__DEV__ && constants_1.VERBOSE_DRAG_DEBUG) {
67
+ const isRecycled = prevItemIdRef.current !== item.id;
68
+ console.log(`[FlashListItem:inst${instanceId}] RENDER: itemId=${item.id}, index=${index}` +
69
+ (isRecycled ? ` (RECYCLED from ${prevItemIdRef.current})` : ''));
70
+ prevItemIdRef.current = item.id;
71
+ }
72
+ // Track mount/unmount
73
+ (0, react_1.useEffect)(() => {
74
+ if (__DEV__) {
75
+ console.log(`[FlashListItem:inst${instanceId}] MOUNT: itemId=${item.id}, index=${index}`);
76
+ }
77
+ return () => {
78
+ if (__DEV__) {
79
+ console.log(`[FlashListItem:inst${instanceId}] UNMOUNT: was itemId=${item.id}`);
80
+ }
81
+ };
82
+ // eslint-disable-next-line react-hooks/exhaustive-deps
83
+ }, []); // Empty deps - only track actual mount/unmount
84
+ // Create shared value for index (for UI-thread access in animations)
85
+ // This allows worklets to read the current index without closure stale capture
86
+ const indexShared = (0, react_native_reanimated_1.useSharedValue)(index);
53
87
  // Register this item's index in the central registry
54
88
  // This handles FlashList's recycling behavior where components may not re-render
55
89
  // after data changes, leaving their index props stale
56
- const { updateItemIndex } = (0, contexts_1.useDragState)();
90
+ const { updateItemIndex, isDragging: globalIsDragging, isDropping } = (0, contexts_1.useDragState)();
91
+ // Sync index to SharedValue on every render, but skip during active drag/drop
92
+ // This prevents stale React props from corrupting the index during the drop phase
93
+ // when FlashList re-renders with new data order
94
+ (0, react_1.useLayoutEffect)(() => {
95
+ // Don't update during active drag/drop to prevent race conditions
96
+ // The registry handles correct index lookup during these phases
97
+ if (globalIsDragging.value || isDropping.value) {
98
+ return;
99
+ }
100
+ indexShared.value = index;
101
+ }, [index, indexShared, globalIsDragging, isDropping]);
102
+ // Secondary sync: Catch the case where React re-rendered with new index during drop phase.
103
+ // The primary useLayoutEffect bails out during drop (guards=true), and when guards are
104
+ // released, it doesn't re-run because `index` didn't change. This effect forces the sync
105
+ // on every render when guards are down and there's a mismatch.
106
+ //
107
+ // This effect intentionally has NO dependency array. Adding deps would defeat its purpose:
108
+ // it needs to run on EVERY render to catch the window where guards drop but `index` hasn't
109
+ // changed (so the primary useLayoutEffect won't re-fire). The body is lightweight (three
110
+ // SharedValue reads + one conditional write) so the per-render cost is negligible.
111
+ (0, react_1.useEffect)(() => {
112
+ if (globalIsDragging.value || isDropping.value || indexShared.value === index)
113
+ return;
114
+ if (__DEV__ && constants_1.VERBOSE_DRAG_DEBUG) {
115
+ console.log(`[FlashListItem:${item.id}] Secondary sync: indexShared ${indexShared.value} → ${index}`);
116
+ }
117
+ indexShared.value = index;
118
+ });
57
119
  // Update item index in useLayoutEffect to avoid side effects during render
58
120
  // useLayoutEffect runs synchronously after DOM mutations but before paint,
59
121
  // ensuring the registry is up-to-date before any visual updates
@@ -1,4 +1,12 @@
1
1
  import type { DragConfig } from '../types';
2
+ /**
3
+ * Enable verbose per-frame drag debug logging.
4
+ * When false (default), only critical lifecycle logs (drag start/end, errors)
5
+ * are emitted under __DEV__. When true, per-frame and per-render logs are
6
+ * also emitted. These produce ~480 console.log calls/second during drag
7
+ * (8 visible items * 60fps), which can lag Metro debugger.
8
+ */
9
+ export declare const VERBOSE_DRAG_DEBUG = false;
2
10
  /**
3
11
  * Unified animation timing configuration for consistency across all drag hooks.
4
12
  * Using consistent timing prevents visual "mismatches" during complex interactions.
@@ -1 +1 @@
1
- {"version":3,"file":"drag.d.ts","sourceRoot":"","sources":["../../src/constants/drag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAM3C;;;GAGG;AACH,eAAO,MAAM,gBAAgB;IAC3B,wEAAwE;;IAExE,wFAAwF;;IAExF,kDAAkD;;IAElD,4DAA4D;;CAEpD,CAAC;AAMX;;;GAGG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;;OAKG;;IAEH;;;;OAIG;;IAEH;;;OAGG;;CAEK,CAAC;AAMX;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAoCjC,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAG5E"}
1
+ {"version":3,"file":"drag.d.ts","sourceRoot":"","sources":["../../src/constants/drag.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAM3C;;;;;;GAMG;AACH,eAAO,MAAM,kBAAkB,QAAQ,CAAC;AAMxC;;;GAGG;AACH,eAAO,MAAM,gBAAgB;IAC3B,wEAAwE;;IAExE,wFAAwF;;IAExF,kDAAkD;;IAElD,4DAA4D;;CAEpD,CAAC;AAMX;;;GAGG;AACH,eAAO,MAAM,eAAe;IAC1B;;;;;OAKG;;IAEH;;;;OAIG;;IAEH;;;OAGG;;CAEK,CAAC;AAMX;;;GAGG;AACH,eAAO,MAAM,mBAAmB,EAAE,UAoCjC,CAAC;AAEF;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,SAAS,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,GAAG,UAAU,CAG5E"}
@@ -1,8 +1,19 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.DEFAULT_DRAG_CONFIG = exports.DRAG_THRESHOLDS = exports.ANIMATION_TIMING = void 0;
3
+ exports.DEFAULT_DRAG_CONFIG = exports.DRAG_THRESHOLDS = exports.ANIMATION_TIMING = exports.VERBOSE_DRAG_DEBUG = void 0;
4
4
  exports.createDragConfig = createDragConfig;
5
5
  // ============================================================================
6
+ // Debug Configuration
7
+ // ============================================================================
8
+ /**
9
+ * Enable verbose per-frame drag debug logging.
10
+ * When false (default), only critical lifecycle logs (drag start/end, errors)
11
+ * are emitted under __DEV__. When true, per-frame and per-render logs are
12
+ * also emitted. These produce ~480 console.log calls/second during drag
13
+ * (8 visible items * 60fps), which can lag Metro debugger.
14
+ */
15
+ exports.VERBOSE_DRAG_DEBUG = false;
16
+ // ============================================================================
6
17
  // Animation Timing & Easing Constants
7
18
  // ============================================================================
8
19
  /**
@@ -39,6 +39,10 @@ export interface DragStateContextValue {
39
39
  isDropping: SharedValue<boolean>;
40
40
  /** Counter incremented on each drag start, used to force shift recalculation */
41
41
  dragSequence: SharedValue<number>;
42
+ /** Expected index after drop (for handoff detection) */
43
+ expectedDropIndex: SharedValue<number>;
44
+ /** Frozen mismatch offset computed at drop time to prevent double compensation */
45
+ dropMismatchOffset: SharedValue<number>;
42
46
  /** Register the FlashList ref for autoscroll operations */
43
47
  setListRef: (ref: FlashListRef<unknown> | null) => void;
44
48
  /** Scroll the list to a specific offset (for autoscroll during drag) */
@@ -75,11 +79,6 @@ export interface DragStateContextValue {
75
79
  * Call this when reorder completes to keep indices accurate.
76
80
  */
77
81
  applyReorderToRegistry: (fromId: string, fromIndex: number, toIndex: number) => void;
78
- /**
79
- * Mark registry as recently updated to protect against stale overwrites.
80
- * Call this from handleDragEnd after onEnd updates the registry.
81
- */
82
- markRegistryUpdated: () => void;
83
82
  /** Total number of items in the list (synced from data.length) */
84
83
  totalItems: SharedValue<number>;
85
84
  }
@@ -1 +1 @@
1
- {"version":3,"file":"DragStateContext.d.ts","sourceRoot":"","sources":["../../src/contexts/DragStateContext.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"DragStateContext.d.ts","sourceRoot":"","sources":["../../src/contexts/DragStateContext.tsx"],"names":[],"mappings":"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"}
@@ -130,6 +130,12 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
130
130
  const isDropping = (0, react_native_reanimated_1.useSharedValue)(false);
131
131
  // Counter incremented on each drag start, used to force shift recalculation
132
132
  const dragSequence = (0, react_native_reanimated_1.useSharedValue)(0);
133
+ // Expected index after drop (for handoff detection)
134
+ const expectedDropIndex = (0, react_native_reanimated_1.useSharedValue)(-1);
135
+ // Frozen mismatch offset computed at drop time to prevent double compensation
136
+ // This ensures mismatchOffset + currentTranslateY remains constant even if
137
+ // useAnimatedStyle evaluates between registry update and currentTranslateY adjustment
138
+ const dropMismatchOffset = (0, react_native_reanimated_1.useSharedValue)(0);
133
139
  // Index registry for tracking itemId -> index mapping
134
140
  // This handles FlashList's recycling behavior where components may not re-render
135
141
  // after data changes, leaving their index props stale
@@ -138,8 +144,6 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
138
144
  const dragStartIndexSnapshot = (0, react_native_reanimated_1.useSharedValue)({});
139
145
  // Total number of items in the list (synced from data.length by InnerFlashList)
140
146
  const totalItems = (0, react_native_reanimated_1.useSharedValue)(0);
141
- // Timestamp when registry was last updated by onEnd (for protecting against stale overwrites)
142
- const registryUpdateTimestamp = (0, react_1.useRef)(0);
143
147
  // Ref to FlashList for autoscroll operations
144
148
  const listRef = (0, react_1.useRef)(null);
145
149
  // Register the FlashList ref
@@ -157,32 +161,27 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
157
161
  // Update an item's index in the registry
158
162
  // Uses scheduleOnUI to ensure SharedValue modifications are immediately visible
159
163
  // to UI thread worklets (fixing the registry sync bug).
160
- // Also uses timestamp-based guard to prevent stale React props from overwriting
161
- // correct indices set by onEnd. We protect updates for 500ms after onEnd runs.
164
+ // Uses state-based guards (isDragging/isDropping) to prevent stale React props
165
+ // from overwriting correct indices during active drag/drop operations.
162
166
  const updateItemIndex = (0, react_1.useCallback)((itemId, index) => {
163
167
  // Read current value (may be slightly stale from JS thread, but OK for guard logic)
164
168
  const existingIndex = itemIndexRegistry.value[itemId];
165
- const timeSinceUpdate = Date.now() - registryUpdateTimestamp.current;
166
- const isProtected = timeSinceUpdate < 500; // 500ms protection window
167
169
  // Add new items always - schedule on UI thread for immediate visibility (or synchronous fallback in tests)
168
170
  if (existingIndex === undefined) {
169
171
  scheduleOnUIOrImmediate(updateRegistryOnUI, itemIndexRegistry, itemId, index);
170
172
  return;
171
173
  }
172
- // Skip update if within protection window (onEnd just updated the registry)
174
+ // Skip updates during active drag/drop operations
173
175
  // This prevents stale React props from overwriting correct indices
174
- if (isProtected) {
176
+ // State-based guard is more reliable than time-based (works on all devices)
177
+ if (isDragging.value || isDropping.value) {
175
178
  return;
176
179
  }
177
- // Outside protection window, update if value changed - schedule on UI thread
180
+ // Outside active drag/drop, update if value changed - schedule on UI thread
178
181
  if (existingIndex !== index) {
179
182
  scheduleOnUIOrImmediate(updateRegistryOnUI, itemIndexRegistry, itemId, index);
180
183
  }
181
- }, [itemIndexRegistry]);
182
- // Mark registry as recently updated (call this from handleDragEnd)
183
- const markRegistryUpdated = (0, react_1.useCallback)(() => {
184
- registryUpdateTimestamp.current = Date.now();
185
- }, []);
184
+ }, [itemIndexRegistry, isDragging, isDropping]);
186
185
  // Get an item's index from the registry
187
186
  const getItemIndex = (0, react_1.useCallback)((itemId) => {
188
187
  return itemIndexRegistry.value[itemId];
@@ -231,8 +230,20 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
231
230
  dragStartScrollOffset.value = 0;
232
231
  measuredItemHeight.value = 0;
233
232
  isDropping.value = false;
234
- // eslint-disable-next-line react-hooks/exhaustive-deps
235
- }, []);
233
+ expectedDropIndex.value = -1;
234
+ dropMismatchOffset.value = 0;
235
+ }, [
236
+ isDragging,
237
+ draggedIndex,
238
+ draggedItemId,
239
+ currentTranslateY,
240
+ draggedScale,
241
+ dragStartScrollOffset,
242
+ measuredItemHeight,
243
+ isDropping,
244
+ expectedDropIndex,
245
+ dropMismatchOffset,
246
+ ]);
236
247
  // Context value is stable since SharedValue references don't change
237
248
  const value = (0, react_1.useMemo)(() => ({
238
249
  isDragging,
@@ -248,6 +259,8 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
248
259
  measuredItemHeight,
249
260
  isDropping,
250
261
  dragSequence,
262
+ expectedDropIndex,
263
+ dropMismatchOffset,
251
264
  setListRef,
252
265
  scrollToOffset,
253
266
  prepareForLayoutAnimationRender,
@@ -259,7 +272,6 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
259
272
  dragStartIndexSnapshot,
260
273
  snapshotRegistryForDrag,
261
274
  applyReorderToRegistry,
262
- markRegistryUpdated,
263
275
  totalItems,
264
276
  }), [
265
277
  isDragging,
@@ -275,8 +287,11 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
275
287
  measuredItemHeight,
276
288
  isDropping,
277
289
  dragSequence,
290
+ expectedDropIndex,
291
+ dropMismatchOffset,
278
292
  setListRef,
279
293
  scrollToOffset,
294
+ prepareForLayoutAnimationRender,
280
295
  resetDragState,
281
296
  config,
282
297
  itemIndexRegistry,
@@ -285,7 +300,6 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
285
300
  dragStartIndexSnapshot,
286
301
  snapshotRegistryForDrag,
287
302
  applyReorderToRegistry,
288
- markRegistryUpdated,
289
303
  totalItems,
290
304
  ]);
291
305
  return (<DragStateContext.Provider value={value}>
@@ -1 +1 @@
1
- {"version":3,"file":"ListAnimationContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ListAnimationContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EAEtB,MAAM,UAAU,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,wBAAwB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAElF;;;OAGG;IACH,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAErD;;;OAGG;IACH,oBAAoB,EAAE,CACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,MAAM,IAAI,KACnB,OAAO,CAAC;IAEb;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAE7E;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,qBAAqB,GAAG,IAAI,CAAC;IAEtE;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7E;;OAEG;IACH,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhD;;;OAGG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjD;;;OAGG;IACH,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzC;;OAEG;IACH,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAID;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAO,yBAMnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAAO,yBAAyB,GAAG,IAEvE,CAAC;AAEF,UAAU,0BAA0B;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAkMtE,CAAC"}
1
+ {"version":3,"file":"ListAnimationContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ListAnimationContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAOZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EAEtB,MAAM,UAAU,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,wBAAwB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAElF;;;OAGG;IACH,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAErD;;;OAGG;IACH,oBAAoB,EAAE,CACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,MAAM,IAAI,KACnB,OAAO,CAAC;IAEb;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAE7E;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,qBAAqB,GAAG,IAAI,CAAC;IAEtE;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7E;;OAEG;IACH,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhD;;;OAGG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjD;;;OAGG;IACH,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzC;;OAEG;IACH,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAID;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAO,yBAMnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAAO,yBAAyB,GAAG,IAEvE,CAAC;AAEF,UAAU,0BAA0B;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;;;;OAMG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CAyMtE,CAAC"}
@@ -193,14 +193,20 @@ const ListAnimationProvider = ({ children, entryAnimationTimeout = 5000, layoutA
193
193
  ]);
194
194
  // Cleanup all Maps and timers on unmount to prevent memory leaks
195
195
  (0, react_1.useEffect)(() => {
196
+ // Capture ref values at setup time to satisfy react-hooks/exhaustive-deps
197
+ // This ensures cleanup uses the same Map instances that were set up
198
+ const timeouts = timeoutIdsRef.current;
199
+ const triggers = animationTriggersRef.current;
200
+ const pending = pendingEntriesRef.current;
201
+ const exiting = exitingItemsRef.current;
196
202
  return () => {
197
203
  // Clear all pending timeouts
198
- timeoutIdsRef.current.forEach(timeoutId => clearTimeout(timeoutId));
199
- timeoutIdsRef.current.clear();
204
+ timeouts.forEach(timeoutId => clearTimeout(timeoutId));
205
+ timeouts.clear();
200
206
  // Clear all Maps
201
- animationTriggersRef.current.clear();
202
- pendingEntriesRef.current.clear();
203
- exitingItemsRef.current.clear();
207
+ triggers.clear();
208
+ pending.clear();
209
+ exiting.clear();
204
210
  };
205
211
  }, []);
206
212
  return (<ListAnimationContext.Provider value={value}>
@@ -1 +1 @@
1
- {"version":3,"file":"useListEntryAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListEntryAnimation.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EACd,kBAAkB,OAAO,CAAC,oBAAoB,CAAC;;;;;;;;;;CAsEhD,CAAC"}
1
+ {"version":3,"file":"useListEntryAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListEntryAnimation.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EACd,kBAAkB,OAAO,CAAC,oBAAoB,CAAC;;;;;;;;;;CA6EhD,CAAC"}
@@ -3,6 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.useListEntryAnimation = void 0;
4
4
  const react_1 = require("react");
5
5
  const react_native_reanimated_1 = require("react-native-reanimated");
6
+ const flash_list_1 = require("@shopify/flash-list");
6
7
  const animations_1 = require("../../constants/animations");
7
8
  const ListAnimationContext_1 = require("../../contexts/ListAnimationContext");
8
9
  /**
@@ -42,15 +43,21 @@ const useListEntryAnimation = (itemId, configOverrides) => {
42
43
  const opacity = (0, react_native_reanimated_1.useSharedValue)(1);
43
44
  // Track if we've already checked for entry animation for this item
44
45
  const hasCheckedRef = (0, react_1.useRef)(false);
45
- const lastItemIdRef = (0, react_1.useRef)(itemId);
46
- // Reset check flag when item ID changes (view recycled)
47
- if (lastItemIdRef.current !== itemId) {
48
- lastItemIdRef.current = itemId;
49
- hasCheckedRef.current = false;
50
- // Reset animation values for new item
51
- translateX.value = 0;
52
- opacity.value = 1;
53
- }
46
+ // Use FlashList's useRecyclingState to detect when view is recycled
47
+ // This tracks the previous itemId so we can reset state when the view
48
+ // is reused for a different item
49
+ const [prevItemId] = (0, flash_list_1.useRecyclingState)(itemId, [itemId]);
50
+ // Reset animation state when view is recycled (item ID changes)
51
+ // Using useLayoutEffect to run before paint and prevent visual glitches
52
+ // IMPORTANT: SharedValue writes must happen in effects, not during render
53
+ (0, react_1.useLayoutEffect)(() => {
54
+ if (prevItemId !== itemId) {
55
+ hasCheckedRef.current = false;
56
+ // Reset animation values for new item
57
+ translateX.value = 0;
58
+ opacity.value = 1;
59
+ }
60
+ }, [itemId, prevItemId, translateX, opacity]);
54
61
  // Check for pending entry animation on mount
55
62
  (0, react_1.useEffect)(() => {
56
63
  if (!animationContext || hasCheckedRef.current)
@@ -1 +1 @@
1
- {"version":3,"file":"useListExitAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListExitAnimation.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE3E;;GAEG;AACH,UAAU,0BAA0B;IAClC,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,SAAS,0BAA0B;;;;;;;;;;;6BA2EpB,kBAAkB,cACjB,MAAM,IAAI,WACd,mBAAmB;;CAwDhC,CAAC"}
1
+ {"version":3,"file":"useListExitAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListExitAnimation.ts"],"names":[],"mappings":"AAaA,OAAO,KAAK,EAAE,kBAAkB,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAE3E;;GAEG;AACH,UAAU,0BAA0B;IAClC,uDAAuD;IACvD,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,qDAAqD;IACrD,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,yEAAyE;IACzE,WAAW,CAAC,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IACtD,iEAAiE;IACjE,cAAc,CAAC,EAAE,MAAM,IAAI,CAAC;CAC7B;AAQD;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAoCG;AACH,eAAO,MAAM,oBAAoB,GAC/B,QAAQ,MAAM,EACd,SAAS,0BAA0B;;;;;;;;;;;6BA6EpB,kBAAkB,cACjB,MAAM,IAAI,WACd,mBAAmB;;CAwDhC,CAAC"}
@@ -67,14 +67,16 @@ const useListExitAnimation = (itemId, config) => {
67
67
  // Cancel any running animation to prevent callbacks firing after unmount
68
68
  (0, react_native_reanimated_1.cancelAnimation)(exitDirection);
69
69
  };
70
- }, [exitDirection]);
70
+ // eslint-disable-next-line react-hooks/exhaustive-deps
71
+ }, []); // Cleanup effect - SharedValues are stable refs, no deps needed
71
72
  // Reset animation state when view is recycled (item ID changes)
72
73
  (0, react_1.useEffect)(() => {
73
74
  exitDirection.value = 0;
74
75
  slideDistance.value = animations_1.FAST_EXIT_ANIMATION.slide.distance;
75
76
  scaleToValue.value = animations_1.FAST_EXIT_ANIMATION.scale.toValue;
76
77
  isAnimatingRef.current = false;
77
- }, [itemId, exitDirection, slideDistance, scaleToValue]);
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, [itemId]); // Only itemId triggers reset - SharedValues are stable refs
78
80
  // Exit animated style (slide, fade, scale)
79
81
  const exitAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
80
82
  // Fast path: no animation active, return static values
@@ -1 +1 @@
1
- {"version":3,"file":"useDragAnimatedStyle.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragAnimatedStyle.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAI3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAE9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,EAChC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAC/B,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,GAC1B,0BAA0B,CAmF5B"}
1
+ {"version":3,"file":"useDragAnimatedStyle.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragAnimatedStyle.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAI3D,OAAO,KAAK,EAAE,0BAA0B,EAAE,MAAM,aAAa,CAAC;AAO9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,EAChC,UAAU,EAAE,WAAW,CAAC,MAAM,CAAC,EAC/B,MAAM,EAAE,WAAW,CAAC,MAAM,CAAC,GAC1B,0BAA0B,CA4T5B"}