@souscheflabs/reanimated-flashlist 0.2.7 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/contexts/DragStateContext.d.ts +16 -4
- package/lib/contexts/DragStateContext.d.ts.map +1 -1
- package/lib/contexts/DragStateContext.js +85 -15
- package/lib/hooks/drag/useDragAnimatedStyle.d.ts +2 -2
- package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -1
- package/lib/hooks/drag/useDragAnimatedStyle.js +7 -15
- package/lib/hooks/drag/useDragGesture.d.ts +1 -1
- package/lib/hooks/drag/useDragGesture.d.ts.map +1 -1
- package/lib/hooks/drag/useDragGesture.js +126 -71
- package/lib/hooks/drag/useDragShift.d.ts +1 -1
- package/lib/hooks/drag/useDragShift.d.ts.map +1 -1
- package/lib/hooks/drag/useDragShift.js +86 -23
- package/lib/hooks/drag/useDropCompensation.d.ts +1 -1
- package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -1
- package/lib/hooks/drag/useDropCompensation.js +84 -33
- package/package.json +1 -1
- package/src/contexts/DragStateContext.tsx +122 -20
- package/src/hooks/drag/useDragAnimatedStyle.ts +21 -24
- package/src/hooks/drag/useDragGesture.ts +154 -87
- package/src/hooks/drag/useDragShift.ts +111 -30
- package/src/hooks/drag/useDropCompensation.ts +106 -46
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import React, { type ReactNode } from
|
|
2
|
-
import { type SharedValue } from
|
|
3
|
-
import type { FlashListRef } from
|
|
4
|
-
import type { DragConfig } from
|
|
1
|
+
import React, { type ReactNode } from "react";
|
|
2
|
+
import { type SharedValue } from "react-native-reanimated";
|
|
3
|
+
import type { FlashListRef } from "@shopify/flash-list";
|
|
4
|
+
import type { DragConfig } from "../types";
|
|
5
5
|
/**
|
|
6
6
|
* Centralized drag state for coordinating animations across list items.
|
|
7
7
|
* All animation values are Reanimated SharedValues for 60fps UI thread performance.
|
|
@@ -43,6 +43,8 @@ export interface DragStateContextValue {
|
|
|
43
43
|
setListRef: (ref: FlashListRef<unknown> | null) => void;
|
|
44
44
|
/** Scroll the list to a specific offset (for autoscroll during drag) */
|
|
45
45
|
scrollToOffset: (offset: number, animated?: boolean) => void;
|
|
46
|
+
/** Ask FlashList to prepare cells for layout animation on the next render */
|
|
47
|
+
prepareForLayoutAnimationRender: () => void;
|
|
46
48
|
/** Reset drag state after drop animation completes */
|
|
47
49
|
resetDragState: () => void;
|
|
48
50
|
/** Current drag configuration */
|
|
@@ -68,6 +70,16 @@ export interface DragStateContextValue {
|
|
|
68
70
|
* Call this at the start of a drag operation.
|
|
69
71
|
*/
|
|
70
72
|
snapshotRegistryForDrag: () => void;
|
|
73
|
+
/**
|
|
74
|
+
* Update registry after reorder to handle FlashList not re-rendering.
|
|
75
|
+
* Call this when reorder completes to keep indices accurate.
|
|
76
|
+
*/
|
|
77
|
+
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;
|
|
71
83
|
}
|
|
72
84
|
/**
|
|
73
85
|
* Hook to access shared drag state from context.
|
|
@@ -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;
|
|
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;AAoB3C;;;;;;;;;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;;;OAGG;IACH,mBAAmB,EAAE,MAAM,IAAI,CAAC;CACjC;AAID;;;GAGG;AACH,eAAO,MAAM,YAAY,QAAO,qBAM/B,CAAC;AAEF,UAAU,sBAAsB;IAC9B,QAAQ,EAAE,SAAS,CAAC;IACpB,4CAA4C;IAC5C,MAAM,CAAC,EAAE,OAAO,CAAC,UAAU,CAAC,CAAC;CAC9B;AAmDD,eAAO,MAAM,iBAAiB,EAAE,KAAK,CAAC,EAAE,CAAC,sBAAsB,CA+N9D,CAAC"}
|
|
@@ -36,7 +36,20 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.DragStateProvider = exports.useDragState = void 0;
|
|
37
37
|
const react_1 = __importStar(require("react"));
|
|
38
38
|
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
39
|
+
const react_native_worklets_1 = require("react-native-worklets");
|
|
39
40
|
const constants_1 = require("../constants");
|
|
41
|
+
/**
|
|
42
|
+
* Worklet that updates the item index registry on the UI thread.
|
|
43
|
+
* Using scheduleOnUI ensures the SharedValue modification is immediately
|
|
44
|
+
* visible to other worklets (like onStart in useDragGesture).
|
|
45
|
+
*/
|
|
46
|
+
const updateRegistryOnUI = (registry, itemId, index) => {
|
|
47
|
+
"worklet";
|
|
48
|
+
const existingIndex = registry.value[itemId];
|
|
49
|
+
if (existingIndex === undefined || existingIndex !== index) {
|
|
50
|
+
registry.value = { ...registry.value, [itemId]: index };
|
|
51
|
+
}
|
|
52
|
+
};
|
|
40
53
|
const DragStateContext = (0, react_1.createContext)(null);
|
|
41
54
|
/**
|
|
42
55
|
* Hook to access shared drag state from context.
|
|
@@ -45,7 +58,7 @@ const DragStateContext = (0, react_1.createContext)(null);
|
|
|
45
58
|
const useDragState = () => {
|
|
46
59
|
const context = (0, react_1.useContext)(DragStateContext);
|
|
47
60
|
if (!context) {
|
|
48
|
-
throw new Error(
|
|
61
|
+
throw new Error("useDragState must be used within DragStateProvider");
|
|
49
62
|
}
|
|
50
63
|
return context;
|
|
51
64
|
};
|
|
@@ -67,19 +80,19 @@ exports.useDragState = useDragState;
|
|
|
67
80
|
function validateConfig(config) {
|
|
68
81
|
if (__DEV__) {
|
|
69
82
|
if (config.itemHeight <= 0) {
|
|
70
|
-
console.warn(
|
|
83
|
+
console.warn("[AnimatedFlashList] Invalid itemHeight: must be positive. Got:", config.itemHeight);
|
|
71
84
|
}
|
|
72
85
|
if (config.longPressDuration <= 0) {
|
|
73
|
-
console.warn(
|
|
86
|
+
console.warn("[AnimatedFlashList] Invalid longPressDuration: must be positive. Got:", config.longPressDuration);
|
|
74
87
|
}
|
|
75
88
|
if (config.dragScale <= 0) {
|
|
76
|
-
console.warn(
|
|
89
|
+
console.warn("[AnimatedFlashList] Invalid dragScale: must be positive. Got:", config.dragScale);
|
|
77
90
|
}
|
|
78
91
|
if (config.edgeThreshold < 0) {
|
|
79
|
-
console.warn(
|
|
92
|
+
console.warn("[AnimatedFlashList] Invalid edgeThreshold: must be non-negative. Got:", config.edgeThreshold);
|
|
80
93
|
}
|
|
81
94
|
if (config.maxScrollSpeed <= 0) {
|
|
82
|
-
console.warn(
|
|
95
|
+
console.warn("[AnimatedFlashList] Invalid maxScrollSpeed: must be positive. Got:", config.maxScrollSpeed);
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
98
|
}
|
|
@@ -93,7 +106,7 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
93
106
|
// Shared values are created once and persist for the lifetime of the provider
|
|
94
107
|
const isDragging = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
95
108
|
const draggedIndex = (0, react_native_reanimated_1.useSharedValue)(-1);
|
|
96
|
-
const draggedItemId = (0, react_native_reanimated_1.useSharedValue)(
|
|
109
|
+
const draggedItemId = (0, react_native_reanimated_1.useSharedValue)("");
|
|
97
110
|
const currentTranslateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
98
111
|
const draggedScale = (0, react_native_reanimated_1.useSharedValue)(1);
|
|
99
112
|
// Scroll state for viewport-aware calculations and autoscroll
|
|
@@ -114,6 +127,8 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
114
127
|
const itemIndexRegistry = (0, react_native_reanimated_1.useSharedValue)({});
|
|
115
128
|
// Snapshot of registry taken at drag start for consistent index lookup during drag
|
|
116
129
|
const dragStartIndexSnapshot = (0, react_native_reanimated_1.useSharedValue)({});
|
|
130
|
+
// Timestamp when registry was last updated by onEnd (for protecting against stale overwrites)
|
|
131
|
+
const registryUpdateTimestamp = (0, react_1.useRef)(0);
|
|
117
132
|
// Ref to FlashList for autoscroll operations
|
|
118
133
|
const listRef = (0, react_1.useRef)(null);
|
|
119
134
|
// Register the FlashList ref
|
|
@@ -124,17 +139,39 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
124
139
|
const scrollToOffset = (0, react_1.useCallback)((offset, animated = false) => {
|
|
125
140
|
listRef.current?.scrollToOffset({ offset, animated });
|
|
126
141
|
}, []);
|
|
142
|
+
// Request FlashList to prepare cells for layout animation on next render
|
|
143
|
+
const prepareForLayoutAnimationRender = (0, react_1.useCallback)(() => {
|
|
144
|
+
listRef.current?.prepareForLayoutAnimationRender?.();
|
|
145
|
+
}, []);
|
|
127
146
|
// Update an item's index in the registry
|
|
128
|
-
//
|
|
147
|
+
// Uses scheduleOnUI to ensure SharedValue modifications are immediately visible
|
|
148
|
+
// to UI thread worklets (fixing the registry sync bug).
|
|
149
|
+
// Also uses timestamp-based guard to prevent stale React props from overwriting
|
|
150
|
+
// correct indices set by onEnd. We protect updates for 500ms after onEnd runs.
|
|
129
151
|
const updateItemIndex = (0, react_1.useCallback)((itemId, index) => {
|
|
130
|
-
//
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
152
|
+
// Read current value (may be slightly stale from JS thread, but OK for guard logic)
|
|
153
|
+
const existingIndex = itemIndexRegistry.value[itemId];
|
|
154
|
+
const timeSinceUpdate = Date.now() - registryUpdateTimestamp.current;
|
|
155
|
+
const isProtected = timeSinceUpdate < 500; // 500ms protection window
|
|
156
|
+
// Add new items always - schedule on UI thread for immediate visibility
|
|
157
|
+
if (existingIndex === undefined) {
|
|
158
|
+
(0, react_native_worklets_1.scheduleOnUI)(updateRegistryOnUI, itemIndexRegistry, itemId, index);
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
// Skip update if within protection window (onEnd just updated the registry)
|
|
162
|
+
// This prevents stale React props from overwriting correct indices
|
|
163
|
+
if (isProtected) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
// Outside protection window, update if value changed - schedule on UI thread
|
|
167
|
+
if (existingIndex !== index) {
|
|
168
|
+
(0, react_native_worklets_1.scheduleOnUI)(updateRegistryOnUI, itemIndexRegistry, itemId, index);
|
|
136
169
|
}
|
|
137
170
|
}, [itemIndexRegistry]);
|
|
171
|
+
// Mark registry as recently updated (call this from handleDragEnd)
|
|
172
|
+
const markRegistryUpdated = (0, react_1.useCallback)(() => {
|
|
173
|
+
registryUpdateTimestamp.current = Date.now();
|
|
174
|
+
}, []);
|
|
138
175
|
// Get an item's index from the registry
|
|
139
176
|
const getItemIndex = (0, react_1.useCallback)((itemId) => {
|
|
140
177
|
return itemIndexRegistry.value[itemId];
|
|
@@ -145,11 +182,39 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
145
182
|
const snapshotRegistryForDrag = (0, react_1.useCallback)(() => {
|
|
146
183
|
dragStartIndexSnapshot.value = { ...itemIndexRegistry.value };
|
|
147
184
|
}, [dragStartIndexSnapshot, itemIndexRegistry]);
|
|
185
|
+
// Update registry after reorder to handle FlashList not re-rendering
|
|
186
|
+
const applyReorderToRegistry = (0, react_1.useCallback)((fromId, fromIndex, toIndex) => {
|
|
187
|
+
const registry = itemIndexRegistry.value;
|
|
188
|
+
const newRegistry = { ...registry };
|
|
189
|
+
if (toIndex > fromIndex) {
|
|
190
|
+
// Moving down: items between from and to shift UP (index decreases)
|
|
191
|
+
for (const [id, idx] of Object.entries(registry)) {
|
|
192
|
+
if (idx > fromIndex && idx <= toIndex) {
|
|
193
|
+
newRegistry[id] = idx - 1;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
else if (toIndex < fromIndex) {
|
|
198
|
+
// Moving up: items between to and from shift DOWN (index increases)
|
|
199
|
+
for (const [id, idx] of Object.entries(registry)) {
|
|
200
|
+
if (idx >= toIndex && idx < fromIndex) {
|
|
201
|
+
newRegistry[id] = idx + 1;
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
newRegistry[fromId] = toIndex;
|
|
206
|
+
itemIndexRegistry.value = newRegistry;
|
|
207
|
+
}, [itemIndexRegistry]);
|
|
148
208
|
// Reset drag state after drop
|
|
209
|
+
// Note: We intentionally do NOT clear itemIndexRegistry here.
|
|
210
|
+
// The registry persists across drags and is updated by:
|
|
211
|
+
// 1. applyReorderToRegistry() in useDragGesture.onEnd after each reorder
|
|
212
|
+
// 2. updateItemIndex() when items render
|
|
213
|
+
// Clearing it would cause subsequent drags to fall back to stale React props.
|
|
149
214
|
const resetDragState = (0, react_1.useCallback)(() => {
|
|
150
215
|
isDragging.value = false;
|
|
151
216
|
draggedIndex.value = -1;
|
|
152
|
-
draggedItemId.value =
|
|
217
|
+
draggedItemId.value = "";
|
|
153
218
|
currentTranslateY.value = 0;
|
|
154
219
|
draggedScale.value = 1;
|
|
155
220
|
dragStartScrollOffset.value = 0;
|
|
@@ -174,6 +239,7 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
174
239
|
dragSequence,
|
|
175
240
|
setListRef,
|
|
176
241
|
scrollToOffset,
|
|
242
|
+
prepareForLayoutAnimationRender,
|
|
177
243
|
resetDragState,
|
|
178
244
|
config,
|
|
179
245
|
itemIndexRegistry,
|
|
@@ -181,6 +247,8 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
181
247
|
getItemIndex,
|
|
182
248
|
dragStartIndexSnapshot,
|
|
183
249
|
snapshotRegistryForDrag,
|
|
250
|
+
applyReorderToRegistry,
|
|
251
|
+
markRegistryUpdated,
|
|
184
252
|
}), [
|
|
185
253
|
isDragging,
|
|
186
254
|
draggedIndex,
|
|
@@ -204,6 +272,8 @@ const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
|
204
272
|
getItemIndex,
|
|
205
273
|
dragStartIndexSnapshot,
|
|
206
274
|
snapshotRegistryForDrag,
|
|
275
|
+
applyReorderToRegistry,
|
|
276
|
+
markRegistryUpdated,
|
|
207
277
|
]);
|
|
208
278
|
return (<DragStateContext.Provider value={value}>
|
|
209
279
|
{children}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import type { SharedValue } from
|
|
2
|
-
import type { UseDragAnimatedStyleResult } from
|
|
1
|
+
import type { SharedValue } from "react-native-reanimated";
|
|
2
|
+
import type { UseDragAnimatedStyleResult } from "../../types";
|
|
3
3
|
/**
|
|
4
4
|
* Hook that creates animated styles for drag operations.
|
|
5
5
|
*
|
|
@@ -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,
|
|
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"}
|
|
@@ -35,7 +35,7 @@ const constants_1 = require("../../constants");
|
|
|
35
35
|
*/
|
|
36
36
|
function useDragAnimatedStyle(itemId, isDragging, translateY, shiftY) {
|
|
37
37
|
// Global drag state for scale, identity check, and scroll compensation
|
|
38
|
-
const { draggedItemId, draggedScale, config, scrollOffset, dragStartScrollOffset } = (0, DragStateContext_1.useDragState)();
|
|
38
|
+
const { draggedItemId, draggedScale, config, scrollOffset, dragStartScrollOffset, } = (0, DragStateContext_1.useDragState)();
|
|
39
39
|
// Issue 2 Fix: Track shadow opacity with smooth transitions to prevent "flash"
|
|
40
40
|
// When drag ends, shadow opacity should fade smoothly instead of snapping
|
|
41
41
|
const animatedShadowOpacity = (0, react_native_reanimated_1.useSharedValue)(0.1);
|
|
@@ -44,7 +44,7 @@ function useDragAnimatedStyle(itemId, isDragging, translateY, shiftY) {
|
|
|
44
44
|
isThisItemDragged: draggedItemId.value === itemId,
|
|
45
45
|
scale: draggedScale.value,
|
|
46
46
|
}), (current, previous) => {
|
|
47
|
-
|
|
47
|
+
"worklet";
|
|
48
48
|
if (current.isThisItemDragged) {
|
|
49
49
|
// When dragged, interpolate shadow opacity based on scale
|
|
50
50
|
const targetOpacity = (0, react_native_reanimated_1.interpolate)(current.scale, [1, config.dragScale], [0.1, config.dragShadowOpacity]);
|
|
@@ -67,14 +67,9 @@ function useDragAnimatedStyle(itemId, isDragging, translateY, shiftY) {
|
|
|
67
67
|
const hasSignificantTranslateY = Math.abs(translateY.value) > constants_1.DRAG_THRESHOLDS.SHIFT_SIGNIFICANCE_THRESHOLD;
|
|
68
68
|
// Calculate scroll delta for position compensation during autoscroll
|
|
69
69
|
const scrollDelta = scrollOffset.value - dragStartScrollOffset.value;
|
|
70
|
-
//
|
|
71
|
-
//
|
|
72
|
-
|
|
73
|
-
//
|
|
74
|
-
// Case 2 handles the FALLBACK scenario where FlashList doesn't re-render
|
|
75
|
-
// after reorder. The item uses translateY to stay at its new position
|
|
76
|
-
// until FlashList eventually re-renders and useDropCompensation runs.
|
|
77
|
-
const shouldUseDragOffset = (isThisItemDragged && isDragging.value) || hasSignificantTranslateY;
|
|
70
|
+
// Use translateY only while actively dragging. Once drop begins we rely on
|
|
71
|
+
// the new base layout (shiftY/FlashList) to avoid any post-drop bounce.
|
|
72
|
+
const shouldUseDragOffset = isThisItemDragged && isDragging.value;
|
|
78
73
|
// Use drag translateY + scroll compensation for dragged/settling item,
|
|
79
74
|
// shiftY for others or for items at rest
|
|
80
75
|
const yOffset = shouldUseDragOffset
|
|
@@ -83,11 +78,8 @@ function useDragAnimatedStyle(itemId, isDragging, translateY, shiftY) {
|
|
|
83
78
|
// Only apply visual effects (scale, elevation, zIndex) when ACTIVELY dragging
|
|
84
79
|
// Not when just settling at new position after drop
|
|
85
80
|
const isActivelyDragging = isThisItemDragged && isDragging.value;
|
|
86
|
-
// Keep elevated zIndex
|
|
87
|
-
|
|
88
|
-
// Note: We use hasSignificantTranslateY alone (not isThisItemDragged) because
|
|
89
|
-
// after FALLBACK completes, draggedItemId is reset to '' but translateY remains
|
|
90
|
-
const shouldBeElevated = isActivelyDragging || hasSignificantTranslateY;
|
|
81
|
+
// Keep elevated zIndex only while actively dragging
|
|
82
|
+
const shouldBeElevated = isActivelyDragging;
|
|
91
83
|
return {
|
|
92
84
|
transform: [
|
|
93
85
|
{ translateY: yOffset },
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDragGesture.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragGesture.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"useDragGesture.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragGesture.ts"],"names":[],"mappings":"AAqBA,OAAO,KAAK,EACV,oBAAoB,EACpB,uBAAuB,EACvB,oBAAoB,EACrB,MAAM,aAAa,CAAC;AAErB;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,cAAc,CAC5B,MAAM,EAAE,oBAAoB,EAC5B,SAAS,EAAE,uBAAuB,GACjC,oBAAoB,CA2atB"}
|
|
@@ -41,7 +41,7 @@ function useDragGesture(config, callbacks) {
|
|
|
41
41
|
const isDragging = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
42
42
|
const translateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
43
43
|
// Global drag state for coordinating animations across all items
|
|
44
|
-
const { isDragging: globalIsDragging, draggedIndex, draggedItemId, currentTranslateY, draggedScale, scrollOffset, dragStartScrollOffset, contentHeight, visibleHeight, listTopY, measuredItemHeight, isDropping, scrollToOffset, config: dragConfig, itemIndexRegistry,
|
|
44
|
+
const { isDragging: globalIsDragging, draggedIndex, draggedItemId, currentTranslateY, draggedScale, scrollOffset, dragStartScrollOffset, contentHeight, visibleHeight, listTopY, measuredItemHeight, isDropping, scrollToOffset, prepareForLayoutAnimationRender, config: dragConfig, itemIndexRegistry, dragStartIndexSnapshot, dragSequence, markRegistryUpdated, } = (0, DragStateContext_1.useDragState)();
|
|
45
45
|
// Performance optimization: Track last significant Y position and scroll time
|
|
46
46
|
// to avoid updating on every pixel movement
|
|
47
47
|
const lastSignificantY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
@@ -66,7 +66,15 @@ function useDragGesture(config, callbacks) {
|
|
|
66
66
|
currentIndex.value = index;
|
|
67
67
|
setPrevItemId(itemId);
|
|
68
68
|
}
|
|
69
|
-
}, [
|
|
69
|
+
}, [
|
|
70
|
+
itemId,
|
|
71
|
+
prevItemId,
|
|
72
|
+
setPrevItemId,
|
|
73
|
+
translateY,
|
|
74
|
+
isDragging,
|
|
75
|
+
currentIndex,
|
|
76
|
+
index,
|
|
77
|
+
]);
|
|
70
78
|
// Sync index to SharedValue in useEffect (not during render per Reanimated docs)
|
|
71
79
|
(0, react_1.useEffect)(() => {
|
|
72
80
|
currentIndex.value = index;
|
|
@@ -93,8 +101,11 @@ function useDragGesture(config, callbacks) {
|
|
|
93
101
|
// Drag was cancelled or reset
|
|
94
102
|
return;
|
|
95
103
|
}
|
|
96
|
-
// Use
|
|
97
|
-
|
|
104
|
+
// Use measured item height for accurate position calculations
|
|
105
|
+
// Fall back to config height if measurement failed or not yet available
|
|
106
|
+
const itemHeight = measuredItemHeight.value > 0
|
|
107
|
+
? measuredItemHeight.value
|
|
108
|
+
: dragConfig.itemHeight;
|
|
98
109
|
// Calculate how many positions to move based on drag offset
|
|
99
110
|
const positionDelta = Math.round(finalTranslateY / itemHeight);
|
|
100
111
|
// Calculate if position actually changes
|
|
@@ -106,73 +117,47 @@ function useDragGesture(config, callbacks) {
|
|
|
106
117
|
// but we also animate here as a fallback since FlashList's virtualization
|
|
107
118
|
// may not trigger re-renders when data changes.
|
|
108
119
|
isDropping.value = true;
|
|
109
|
-
onHapticFeedback?.(
|
|
120
|
+
onHapticFeedback?.("medium");
|
|
121
|
+
// Ask FlashList to prepare cells for layout animation on next render
|
|
122
|
+
prepareForLayoutAnimationRender();
|
|
110
123
|
reorder(currentItemId, positionDelta);
|
|
111
|
-
//
|
|
112
|
-
// The
|
|
113
|
-
//
|
|
114
|
-
|
|
124
|
+
// Mark registry as recently updated to protect against stale React prop overwrites
|
|
125
|
+
// The onEnd callback already updated the registry on the UI thread - this timestamp
|
|
126
|
+
// prevents items re-rendering with stale indices from corrupting those updates.
|
|
127
|
+
markRegistryUpdated();
|
|
128
|
+
// Note: Registry is now updated in onEnd on UI thread to prevent race conditions
|
|
129
|
+
// where a new drag starts before this JS callback runs.
|
|
115
130
|
// Reset currentTranslateY to stop shift calculations for other items
|
|
131
|
+
// This triggers all shifted items to animate their shiftY back to 0
|
|
116
132
|
currentTranslateY.value = 0;
|
|
117
|
-
//
|
|
118
|
-
// This
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// old index. To make it appear at the new index, we calculate the target translateY
|
|
130
|
-
// that positions the item correctly.
|
|
131
|
-
//
|
|
132
|
-
// When FlashList eventually re-renders (on scroll or interaction), useDropCompensation
|
|
133
|
-
// will see the index change and animate translateY back to 0.
|
|
134
|
-
(0, react_native_reanimated_1.cancelAnimation)(translateY);
|
|
135
|
-
// Calculate the target translateY to position item at its new index
|
|
136
|
-
// Since FlashList hasn't updated base position, we need:
|
|
137
|
-
// targetTranslateY = (newIndex - currentIndex) * itemHeight
|
|
138
|
-
const targetTranslateY = (newIndex - currentIndex) * itemHeight;
|
|
139
|
-
// Use a delayed animation to give useDropCompensation a chance to run first
|
|
140
|
-
translateY.value = (0, react_native_reanimated_1.withDelay)(150, // Delay to let React re-render and useDropCompensation run first
|
|
141
|
-
(0, react_native_reanimated_1.withTiming)(targetTranslateY, // Animate to the new position
|
|
142
|
-
{ duration: 150, easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.ease) }, finished => {
|
|
143
|
-
'worklet';
|
|
144
|
-
// Only reset state if still in dropping state (useDropCompensation didn't handle it)
|
|
145
|
-
if (finished && isDropping.value) {
|
|
146
|
-
// Reset all drag state - the item is now visually at its new position
|
|
147
|
-
// via translateY offset. When FlashList re-renders, useDropCompensation
|
|
148
|
-
// will compensate and animate to 0.
|
|
149
|
-
globalIsDragging.value = false;
|
|
150
|
-
isDropping.value = false;
|
|
151
|
-
draggedIndex.value = -1;
|
|
152
|
-
draggedItemId.value = '';
|
|
153
|
-
measuredItemHeight.value = 0;
|
|
154
|
-
dragStartScrollOffset.value = 0;
|
|
155
|
-
// FlashList doesn't re-render visible items after data changes.
|
|
156
|
-
// Since useDropCompensation won't run, we need to animate translateY
|
|
157
|
-
// back to 0 ourselves. This causes a visual "snap" but prevents the
|
|
158
|
-
// item from being stuck floating forever.
|
|
159
|
-
translateY.value = (0, react_native_reanimated_1.withTiming)(0, {
|
|
160
|
-
duration: 200,
|
|
161
|
-
easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.ease),
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
}));
|
|
133
|
+
// CRITICAL: Store the current translateY value before any animations start
|
|
134
|
+
// This represents where the item visually is at the moment of drop
|
|
135
|
+
const dropTranslateY = translateY.value;
|
|
136
|
+
// Freeze the visual position at the drop point; do not animate toward 0 here
|
|
137
|
+
// because FlashList has not yet updated base layouts. Animating now would
|
|
138
|
+
// create a visible bounce (the issue you are seeing).
|
|
139
|
+
translateY.value = dropTranslateY;
|
|
140
|
+
console.log("[DROP] Captured translateY at drop:", {
|
|
141
|
+
dropTranslateY,
|
|
142
|
+
currentIndex,
|
|
143
|
+
newIndex,
|
|
144
|
+
});
|
|
165
145
|
}
|
|
166
146
|
else {
|
|
167
147
|
// Same position - animate back and reset state
|
|
148
|
+
// Capture current sequence to guard callback against race conditions
|
|
149
|
+
const currentSequence = dragSequence.value;
|
|
168
150
|
// Cancel any existing animation before starting the return animation
|
|
169
151
|
(0, react_native_reanimated_1.cancelAnimation)(translateY);
|
|
170
|
-
translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 150, easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.ease) }, finished => {
|
|
171
|
-
|
|
152
|
+
translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 150, easing: react_native_reanimated_1.Easing.out(react_native_reanimated_1.Easing.ease) }, (finished) => {
|
|
153
|
+
"worklet";
|
|
154
|
+
// Guard: Only reset if this is still the same drag operation
|
|
155
|
+
if (dragSequence.value !== currentSequence)
|
|
156
|
+
return;
|
|
172
157
|
if (finished) {
|
|
173
158
|
globalIsDragging.value = false;
|
|
174
159
|
draggedIndex.value = -1;
|
|
175
|
-
draggedItemId.value =
|
|
160
|
+
draggedItemId.value = "";
|
|
176
161
|
currentTranslateY.value = 0;
|
|
177
162
|
dragStartScrollOffset.value = 0;
|
|
178
163
|
measuredItemHeight.value = 0;
|
|
@@ -190,17 +175,19 @@ function useDragGesture(config, callbacks) {
|
|
|
190
175
|
dragStartScrollOffset,
|
|
191
176
|
isDropping,
|
|
192
177
|
onHapticFeedback,
|
|
178
|
+
dragSequence,
|
|
179
|
+
markRegistryUpdated,
|
|
193
180
|
]);
|
|
194
181
|
// Stable haptic callback for drag start
|
|
195
182
|
const triggerLightHaptic = (0, react_1.useCallback)(() => {
|
|
196
|
-
onHapticFeedback?.(
|
|
183
|
+
onHapticFeedback?.("light");
|
|
197
184
|
}, [onHapticFeedback]);
|
|
198
185
|
// Pan gesture for drag-to-reorder
|
|
199
186
|
const panGesture = (0, react_1.useMemo)(() => react_native_gesture_handler_1.Gesture.Pan()
|
|
200
187
|
.activateAfterLongPress(dragConfig.longPressDuration)
|
|
201
188
|
.enabled(enabled)
|
|
202
189
|
.onStart(() => {
|
|
203
|
-
|
|
190
|
+
"worklet";
|
|
204
191
|
// Reset any pending drop state from previous drag
|
|
205
192
|
// This is critical: if a new drag starts before the previous drop's
|
|
206
193
|
// fallback completed, isDropping would still be true, causing shift
|
|
@@ -208,6 +195,17 @@ function useDragGesture(config, callbacks) {
|
|
|
208
195
|
if (isDropping.value) {
|
|
209
196
|
isDropping.value = false;
|
|
210
197
|
}
|
|
198
|
+
// Cancel any in-flight animations from previous drag to prevent
|
|
199
|
+
// their callbacks from corrupting our state
|
|
200
|
+
(0, react_native_reanimated_1.cancelAnimation)(translateY);
|
|
201
|
+
(0, react_native_reanimated_1.cancelAnimation)(draggedScale);
|
|
202
|
+
// Reset translateY to prevent inheriting values from previous drag
|
|
203
|
+
// This is critical when the same item is dragged consecutively or
|
|
204
|
+
// when FlashList recycled a view that had a non-zero translateY
|
|
205
|
+
translateY.value = 0;
|
|
206
|
+
// Take snapshot SYNCHRONOUSLY in worklet for consistent index lookup
|
|
207
|
+
// This must happen before any shift calculations read the snapshot
|
|
208
|
+
dragStartIndexSnapshot.value = { ...itemIndexRegistry.value };
|
|
211
209
|
// Increment drag sequence to force all items to recalculate their shifts
|
|
212
210
|
// This handles the case where items had non-zero shiftY from a previous
|
|
213
211
|
// drag that didn't complete its animations
|
|
@@ -240,8 +238,8 @@ function useDragGesture(config, callbacks) {
|
|
|
240
238
|
});
|
|
241
239
|
(0, react_native_worklets_1.scheduleOnRN)(triggerLightHaptic);
|
|
242
240
|
})
|
|
243
|
-
.onUpdate(event => {
|
|
244
|
-
|
|
241
|
+
.onUpdate((event) => {
|
|
242
|
+
"worklet";
|
|
245
243
|
// Always update translateY for smooth visual feedback
|
|
246
244
|
translateY.value = event.translationY;
|
|
247
245
|
// Performance optimization: Only update currentTranslateY (which triggers
|
|
@@ -261,7 +259,7 @@ function useDragGesture(config, callbacks) {
|
|
|
261
259
|
const topEdge = dragConfig.edgeThreshold;
|
|
262
260
|
const bottomEdge = visibleHeight.value - dragConfig.edgeThreshold;
|
|
263
261
|
if (fingerInList < topEdge && scrollOffset.value > 0) {
|
|
264
|
-
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [0, topEdge], [dragConfig.maxScrollSpeed, 0],
|
|
262
|
+
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [0, topEdge], [dragConfig.maxScrollSpeed, 0], "clamp");
|
|
265
263
|
const newOffset = Math.max(0, scrollOffset.value - speed);
|
|
266
264
|
scrollOffset.value = newOffset;
|
|
267
265
|
// Throttle actual scroll calls to reduce JS thread pressure
|
|
@@ -273,7 +271,7 @@ function useDragGesture(config, callbacks) {
|
|
|
273
271
|
else if (fingerInList > bottomEdge) {
|
|
274
272
|
const maxOffset = Math.max(0, contentHeight.value - visibleHeight.value);
|
|
275
273
|
if (scrollOffset.value < maxOffset) {
|
|
276
|
-
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [bottomEdge, visibleHeight.value], [0, dragConfig.maxScrollSpeed],
|
|
274
|
+
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [bottomEdge, visibleHeight.value], [0, dragConfig.maxScrollSpeed], "clamp");
|
|
277
275
|
const newOffset = Math.min(maxOffset, scrollOffset.value + speed);
|
|
278
276
|
scrollOffset.value = newOffset;
|
|
279
277
|
// Throttle actual scroll calls to reduce JS thread pressure
|
|
@@ -284,24 +282,72 @@ function useDragGesture(config, callbacks) {
|
|
|
284
282
|
}
|
|
285
283
|
}
|
|
286
284
|
})
|
|
287
|
-
.onEnd(event => {
|
|
288
|
-
|
|
285
|
+
.onEnd((event) => {
|
|
286
|
+
"worklet";
|
|
289
287
|
isDragging.value = false;
|
|
290
288
|
// Include scroll compensation in final position calculation
|
|
291
289
|
const scrollDelta = scrollOffset.value - dragStartScrollOffset.value;
|
|
292
290
|
const finalY = event.translationY + scrollDelta;
|
|
293
291
|
draggedScale.value = (0, react_native_reanimated_1.withSpring)(1, { damping: 15, stiffness: 400 });
|
|
292
|
+
// CRITICAL: Update registry on UI thread BEFORE scheduling JS callback
|
|
293
|
+
// This prevents race conditions where a new drag starts before handleDragEnd
|
|
294
|
+
// runs on the JS thread, which would cause the new drag's snapshot to have
|
|
295
|
+
// stale indices.
|
|
296
|
+
const currentIdx = draggedIndex.value;
|
|
297
|
+
const total = dragContextRef.current.totalItems;
|
|
298
|
+
// Use measured item height for accurate position calculations
|
|
299
|
+
// Fall back to config height if measurement failed or not yet available
|
|
300
|
+
const itemHeight = measuredItemHeight.value > 0
|
|
301
|
+
? measuredItemHeight.value
|
|
302
|
+
: dragConfig.itemHeight;
|
|
303
|
+
const positionDelta = Math.round(finalY / itemHeight);
|
|
304
|
+
const newIdx = Math.max(0, Math.min(total - 1, currentIdx + positionDelta));
|
|
305
|
+
if (positionDelta !== 0 && newIdx !== currentIdx) {
|
|
306
|
+
// Mark drop phase immediately on UI thread to prevent non-dragged items
|
|
307
|
+
// from briefly animating back to 0 before the JS drop handler runs.
|
|
308
|
+
isDropping.value = true;
|
|
309
|
+
// Update registry directly on UI thread
|
|
310
|
+
const registry = itemIndexRegistry.value;
|
|
311
|
+
const newRegistry = {};
|
|
312
|
+
// Copy existing entries and adjust indices
|
|
313
|
+
for (const id of Object.keys(registry)) {
|
|
314
|
+
const idx = registry[id];
|
|
315
|
+
if (idx === undefined)
|
|
316
|
+
continue;
|
|
317
|
+
if (newIdx > currentIdx) {
|
|
318
|
+
// Moving down: items between currentIdx and newIdx shift UP
|
|
319
|
+
if (idx > currentIdx && idx <= newIdx) {
|
|
320
|
+
newRegistry[id] = idx - 1;
|
|
321
|
+
}
|
|
322
|
+
else {
|
|
323
|
+
newRegistry[id] = idx;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
else {
|
|
327
|
+
// Moving up: items between newIdx and currentIdx shift DOWN
|
|
328
|
+
if (idx >= newIdx && idx < currentIdx) {
|
|
329
|
+
newRegistry[id] = idx + 1;
|
|
330
|
+
}
|
|
331
|
+
else {
|
|
332
|
+
newRegistry[id] = idx;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
// Set the dragged item's new index
|
|
337
|
+
newRegistry[draggedItemId.value] = newIdx;
|
|
338
|
+
itemIndexRegistry.value = newRegistry;
|
|
339
|
+
}
|
|
294
340
|
(0, react_native_worklets_1.scheduleOnRN)(handleDragEnd, finalY);
|
|
295
341
|
})
|
|
296
342
|
.onFinalize((_event, success) => {
|
|
297
|
-
|
|
343
|
+
"worklet";
|
|
298
344
|
if (!success) {
|
|
299
345
|
isDragging.value = false;
|
|
300
346
|
translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 150 });
|
|
301
347
|
isDropping.value = false;
|
|
302
348
|
globalIsDragging.value = false;
|
|
303
349
|
draggedIndex.value = -1;
|
|
304
|
-
draggedItemId.value =
|
|
350
|
+
draggedItemId.value = "";
|
|
305
351
|
currentTranslateY.value = 0;
|
|
306
352
|
draggedScale.value = 1;
|
|
307
353
|
dragStartScrollOffset.value = 0;
|
|
@@ -309,6 +355,15 @@ function useDragGesture(config, callbacks) {
|
|
|
309
355
|
}
|
|
310
356
|
}),
|
|
311
357
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
312
|
-
[
|
|
358
|
+
[
|
|
359
|
+
enabled,
|
|
360
|
+
containerRef,
|
|
361
|
+
triggerLightHaptic,
|
|
362
|
+
handleDragEnd,
|
|
363
|
+
index,
|
|
364
|
+
itemId,
|
|
365
|
+
dragStartIndexSnapshot,
|
|
366
|
+
itemIndexRegistry,
|
|
367
|
+
]);
|
|
313
368
|
return { panGesture, isDragging, translateY };
|
|
314
369
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useDragShift.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragShift.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,
|
|
1
|
+
{"version":3,"file":"useDragShift.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragShift.ts"],"names":[],"mappings":"AAYA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CAmP3E"}
|