@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.
- package/lib/AnimatedFlashList.d.ts.map +1 -1
- package/lib/AnimatedFlashList.js +56 -14
- package/lib/AnimatedFlashListItem.d.ts.map +1 -1
- package/lib/AnimatedFlashListItem.js +63 -1
- package/lib/constants/drag.d.ts +8 -0
- package/lib/constants/drag.d.ts.map +1 -1
- package/lib/constants/drag.js +12 -1
- package/lib/contexts/DragStateContext.d.ts +4 -5
- package/lib/contexts/DragStateContext.d.ts.map +1 -1
- package/lib/contexts/DragStateContext.js +32 -18
- package/lib/contexts/ListAnimationContext.d.ts.map +1 -1
- package/lib/contexts/ListAnimationContext.js +11 -5
- package/lib/hooks/animations/useListEntryAnimation.d.ts.map +1 -1
- package/lib/hooks/animations/useListEntryAnimation.js +16 -9
- package/lib/hooks/animations/useListExitAnimation.d.ts.map +1 -1
- package/lib/hooks/animations/useListExitAnimation.js +4 -2
- package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -1
- package/lib/hooks/drag/useDragAnimatedStyle.js +189 -10
- package/lib/hooks/drag/useDragGesture.d.ts.map +1 -1
- package/lib/hooks/drag/useDragGesture.js +226 -38
- package/lib/hooks/drag/useDragShift.d.ts.map +1 -1
- package/lib/hooks/drag/useDragShift.js +98 -30
- package/lib/hooks/drag/useDropCompensation.d.ts +6 -4
- package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -1
- package/lib/hooks/drag/useDropCompensation.js +60 -40
- package/package.json +11 -9
- package/src/AnimatedFlashList.tsx +111 -32
- package/src/AnimatedFlashListItem.tsx +76 -3
- package/src/constants/drag.ts +13 -0
- package/src/contexts/DragStateContext.tsx +38 -25
- package/src/contexts/ListAnimationContext.tsx +12 -5
- package/src/hooks/animations/useListEntryAnimation.ts +18 -10
- package/src/hooks/animations/useListExitAnimation.ts +4 -2
- package/src/hooks/drag/useDragAnimatedStyle.ts +251 -11
- package/src/hooks/drag/useDragGesture.ts +270 -40
- package/src/hooks/drag/useDragShift.ts +112 -32
- 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;
|
|
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"}
|
package/lib/AnimatedFlashList.js
CHANGED
|
@@ -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
|
-
|
|
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
|
-
{
|
|
199
|
-
|
|
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
|
-
|
|
206
|
-
<
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
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,
|
|
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
|
package/lib/constants/drag.d.ts
CHANGED
|
@@ -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"}
|
package/lib/constants/drag.js
CHANGED
|
@@ -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":"AAAA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AACf,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAE3E,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;AAgC3C;;;;;;;;;GASG;AACH,MAAM,WAAW,qBAAqB;IACpC,2CAA2C;IAC3C,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,oEAAoE;IACpE,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,oFAAoF;IACpF,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,iFAAiF;IACjF,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACvC,sEAAsE;IACtE,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,+DAA+D;IAC/D,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,uFAAuF;IACvF,qBAAqB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC3C,yEAAyE;IACzE,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,qDAAqD;IACrD,aAAa,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACnC,mFAAmF;IACnF,QAAQ,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAC9B,4EAA4E;IAC5E,kBAAkB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IACxC,yDAAyD;IACzD,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAC;IACjC,gFAAgF;IAChF,YAAY,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAClC,2DAA2D;IAC3D,UAAU,EAAE,CAAC,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,IAAI,KAAK,IAAI,CAAC;IACxD,wEAAwE;IACxE,cAAc,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAC;IAC7D,6EAA6E;IAC7E,+BAA+B,EAAE,MAAM,IAAI,CAAC;IAC5C,sDAAsD;IACtD,cAAc,EAAE,MAAM,IAAI,CAAC;IAC3B,iCAAiC;IACjC,MAAM,EAAE,UAAU,CAAC;IACnB;;;;OAIG;IACH,iBAAiB,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACvD,mEAAmE;IACnE,eAAe,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,KAAK,IAAI,CAAC;IACzD,4CAA4C;IAC5C,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,MAAM,GAAG,SAAS,CAAC;IACrD;;;;OAIG;IACH,sBAAsB,EAAE,WAAW,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IAC5D;;;OAGG;IACH,uBAAuB,EAAE,MAAM,IAAI,CAAC;IACpC;;;OAGG;IACH,sBAAsB,EAAE,CACtB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,OAAO,EAAE,MAAM,KACZ,IAAI,CAAC;IACV
|
|
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
|
-
//
|
|
161
|
-
// correct indices
|
|
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
|
|
174
|
+
// Skip updates during active drag/drop operations
|
|
173
175
|
// This prevents stale React props from overwriting correct indices
|
|
174
|
-
|
|
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
|
|
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
|
-
|
|
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,
|
|
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
|
-
|
|
199
|
-
|
|
204
|
+
timeouts.forEach(timeoutId => clearTimeout(timeoutId));
|
|
205
|
+
timeouts.clear();
|
|
200
206
|
// Clear all Maps
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
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":"
|
|
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
|
-
|
|
46
|
-
//
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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;;;;;;;;;;;
|
|
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
|
-
|
|
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
|
-
|
|
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":"
|
|
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"}
|