@souscheflabs/reanimated-flashlist 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +282 -0
- package/lib/AnimatedFlashList.d.ts +6 -0
- package/lib/AnimatedFlashList.d.ts.map +1 -0
- package/lib/AnimatedFlashList.js +207 -0
- package/lib/AnimatedFlashListItem.d.ts +33 -0
- package/lib/AnimatedFlashListItem.d.ts.map +1 -0
- package/lib/AnimatedFlashListItem.js +155 -0
- package/lib/__tests__/utils/test-utils.d.ts +82 -0
- package/lib/__tests__/utils/test-utils.d.ts.map +1 -0
- package/lib/__tests__/utils/test-utils.js +115 -0
- package/lib/constants/animations.d.ts +39 -0
- package/lib/constants/animations.d.ts.map +1 -0
- package/lib/constants/animations.js +100 -0
- package/lib/constants/drag.d.ts +11 -0
- package/lib/constants/drag.d.ts.map +1 -0
- package/lib/constants/drag.js +47 -0
- package/lib/constants/index.d.ts +3 -0
- package/lib/constants/index.d.ts.map +1 -0
- package/lib/constants/index.js +18 -0
- package/lib/contexts/DragStateContext.d.ts +73 -0
- package/lib/contexts/DragStateContext.d.ts.map +1 -0
- package/lib/contexts/DragStateContext.js +148 -0
- package/lib/contexts/ListAnimationContext.d.ts +104 -0
- package/lib/contexts/ListAnimationContext.d.ts.map +1 -0
- package/lib/contexts/ListAnimationContext.js +184 -0
- package/lib/contexts/index.d.ts +5 -0
- package/lib/contexts/index.d.ts.map +1 -0
- package/lib/contexts/index.js +10 -0
- package/lib/hooks/animations/index.d.ts +9 -0
- package/lib/hooks/animations/index.d.ts.map +1 -0
- package/lib/hooks/animations/index.js +13 -0
- package/lib/hooks/animations/useListEntryAnimation.d.ts +38 -0
- package/lib/hooks/animations/useListEntryAnimation.d.ts.map +1 -0
- package/lib/hooks/animations/useListEntryAnimation.js +90 -0
- package/lib/hooks/animations/useListExitAnimation.d.ts +67 -0
- package/lib/hooks/animations/useListExitAnimation.d.ts.map +1 -0
- package/lib/hooks/animations/useListExitAnimation.js +146 -0
- package/lib/hooks/drag/index.d.ts +20 -0
- package/lib/hooks/drag/index.d.ts.map +1 -0
- package/lib/hooks/drag/index.js +26 -0
- package/lib/hooks/drag/useDragAnimatedStyle.d.ts +33 -0
- package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -0
- package/lib/hooks/drag/useDragAnimatedStyle.js +61 -0
- package/lib/hooks/drag/useDragGesture.d.ts +30 -0
- package/lib/hooks/drag/useDragGesture.d.ts.map +1 -0
- package/lib/hooks/drag/useDragGesture.js +189 -0
- package/lib/hooks/drag/useDragShift.d.ts +21 -0
- package/lib/hooks/drag/useDragShift.d.ts.map +1 -0
- package/lib/hooks/drag/useDragShift.js +85 -0
- package/lib/hooks/drag/useDropCompensation.d.ts +27 -0
- package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -0
- package/lib/hooks/drag/useDropCompensation.js +90 -0
- package/lib/hooks/index.d.ts +8 -0
- package/lib/hooks/index.d.ts.map +1 -0
- package/lib/hooks/index.js +18 -0
- package/lib/index.d.ts +42 -0
- package/lib/index.d.ts.map +1 -0
- package/lib/index.js +69 -0
- package/lib/types/animations.d.ts +71 -0
- package/lib/types/animations.d.ts.map +1 -0
- package/lib/types/animations.js +2 -0
- package/lib/types/drag.d.ts +94 -0
- package/lib/types/drag.d.ts.map +1 -0
- package/lib/types/drag.js +2 -0
- package/lib/types/index.d.ts +4 -0
- package/lib/types/index.d.ts.map +1 -0
- package/lib/types/index.js +19 -0
- package/lib/types/list.d.ts +136 -0
- package/lib/types/list.d.ts.map +1 -0
- package/lib/types/list.js +2 -0
- package/package.json +73 -0
- package/src/AnimatedFlashList.tsx +411 -0
- package/src/AnimatedFlashListItem.tsx +212 -0
- package/src/__tests__/components/AnimatedFlashList.test.tsx +365 -0
- package/src/__tests__/components/AnimatedFlashListItem.test.tsx +371 -0
- package/src/__tests__/contexts/DragStateContext.test.tsx +169 -0
- package/src/__tests__/contexts/ListAnimationContext.test.tsx +324 -0
- package/src/__tests__/hooks/useDragAnimatedStyle.test.tsx +118 -0
- package/src/__tests__/hooks/useDragGesture.test.tsx +169 -0
- package/src/__tests__/hooks/useDragShift.test.tsx +94 -0
- package/src/__tests__/hooks/useDropCompensation.test.tsx +182 -0
- package/src/__tests__/hooks/useListEntryAnimation.test.tsx +135 -0
- package/src/__tests__/hooks/useListExitAnimation.test.tsx +175 -0
- package/src/__tests__/utils/test-utils.tsx +159 -0
- package/src/constants/animations.ts +107 -0
- package/src/constants/drag.ts +51 -0
- package/src/constants/index.ts +2 -0
- package/src/contexts/DragStateContext.tsx +197 -0
- package/src/contexts/ListAnimationContext.tsx +302 -0
- package/src/contexts/index.ts +9 -0
- package/src/hooks/animations/index.ts +9 -0
- package/src/hooks/animations/useListEntryAnimation.ts +108 -0
- package/src/hooks/animations/useListExitAnimation.ts +197 -0
- package/src/hooks/drag/index.ts +20 -0
- package/src/hooks/drag/useDragAnimatedStyle.ts +80 -0
- package/src/hooks/drag/useDragGesture.ts +267 -0
- package/src/hooks/drag/useDragShift.ts +119 -0
- package/src/hooks/drag/useDropCompensation.ts +120 -0
- package/src/hooks/index.ts +16 -0
- package/src/index.ts +105 -0
- package/src/types/animations.ts +76 -0
- package/src/types/drag.ts +101 -0
- package/src/types/index.ts +3 -0
- package/src/types/list.ts +178 -0
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import type { AnimationDirection, ExitAnimationPreset } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Configuration for exit animation with layout compensation
|
|
4
|
+
*/
|
|
5
|
+
interface UseListExitAnimationConfig {
|
|
6
|
+
/** Item index in the list (for layout compensation) */
|
|
7
|
+
index?: number;
|
|
8
|
+
/** Measured item height (for layout compensation) */
|
|
9
|
+
measuredHeight?: number;
|
|
10
|
+
/** Callback when exit animation starts (for registering with context) */
|
|
11
|
+
onExitStart?: (index: number, height: number) => void;
|
|
12
|
+
/** Callback when exit animation completes (for unregistering) */
|
|
13
|
+
onExitComplete?: () => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Hook for managing list item exit animations
|
|
17
|
+
*
|
|
18
|
+
* Provides coordinated slide, fade, and scale animations for items
|
|
19
|
+
* exiting a list (e.g., moving between sections, being deleted).
|
|
20
|
+
*
|
|
21
|
+
* PERFORMANCE: Uses eager shared value creation but defers animation calculations.
|
|
22
|
+
* The animated style returns static values when exitDirection === 0 (no animation),
|
|
23
|
+
* avoiding expensive animation calculations until the user actually triggers one.
|
|
24
|
+
*
|
|
25
|
+
* IMPORTANT: This hook handles FlashList view recycling by accepting an itemId parameter.
|
|
26
|
+
* When the itemId changes (view recycled for a different item), all animation state is reset.
|
|
27
|
+
*
|
|
28
|
+
* @param itemId - Unique identifier for the list item (used to detect view recycling)
|
|
29
|
+
* @param config - Optional configuration for layout compensation callbacks
|
|
30
|
+
* @returns Animation styles and trigger functions
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```tsx
|
|
34
|
+
* const { exitAnimatedStyle, triggerExit, resetAnimation } = useListExitAnimation(item.id, {
|
|
35
|
+
* index,
|
|
36
|
+
* measuredHeight: 80,
|
|
37
|
+
* onExitStart: (idx, height) => registerExitingItem(itemId, idx, height),
|
|
38
|
+
* onExitComplete: () => unregisterExitingItem(itemId),
|
|
39
|
+
* });
|
|
40
|
+
*
|
|
41
|
+
* // Trigger exit animation
|
|
42
|
+
* triggerExit(1, () => {
|
|
43
|
+
* onItemRemoved(item.id);
|
|
44
|
+
* });
|
|
45
|
+
*
|
|
46
|
+
* // Apply styles
|
|
47
|
+
* <Animated.View style={exitAnimatedStyle}>
|
|
48
|
+
* {content}
|
|
49
|
+
* </Animated.View>
|
|
50
|
+
* ```
|
|
51
|
+
*/
|
|
52
|
+
export declare const useListExitAnimation: (itemId: string, config?: UseListExitAnimationConfig) => {
|
|
53
|
+
exitAnimatedStyle: {
|
|
54
|
+
opacity: number;
|
|
55
|
+
transform: ({
|
|
56
|
+
translateX: number;
|
|
57
|
+
scale?: undefined;
|
|
58
|
+
} | {
|
|
59
|
+
scale: number;
|
|
60
|
+
translateX?: undefined;
|
|
61
|
+
})[];
|
|
62
|
+
};
|
|
63
|
+
triggerExit: (direction: AnimationDirection, onComplete: () => void, preset?: ExitAnimationPreset) => void;
|
|
64
|
+
resetAnimation: () => void;
|
|
65
|
+
};
|
|
66
|
+
export {};
|
|
67
|
+
//# sourceMappingURL=useListExitAnimation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListExitAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListExitAnimation.ts"],"names":[],"mappings":"AAYA,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;;;;;;;;;;;6BAyEpB,kBAAkB,cACjB,MAAM,IAAI,WACd,mBAAmB;;CAgDhC,CAAC"}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useListExitAnimation = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
6
|
+
const react_native_worklets_1 = require("react-native-worklets");
|
|
7
|
+
const animations_1 = require("../../constants/animations");
|
|
8
|
+
// Animation configs by preset
|
|
9
|
+
const animationConfigs = {
|
|
10
|
+
default: animations_1.DEFAULT_EXIT_ANIMATION,
|
|
11
|
+
fast: animations_1.FAST_EXIT_ANIMATION,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Hook for managing list item exit animations
|
|
15
|
+
*
|
|
16
|
+
* Provides coordinated slide, fade, and scale animations for items
|
|
17
|
+
* exiting a list (e.g., moving between sections, being deleted).
|
|
18
|
+
*
|
|
19
|
+
* PERFORMANCE: Uses eager shared value creation but defers animation calculations.
|
|
20
|
+
* The animated style returns static values when exitDirection === 0 (no animation),
|
|
21
|
+
* avoiding expensive animation calculations until the user actually triggers one.
|
|
22
|
+
*
|
|
23
|
+
* IMPORTANT: This hook handles FlashList view recycling by accepting an itemId parameter.
|
|
24
|
+
* When the itemId changes (view recycled for a different item), all animation state is reset.
|
|
25
|
+
*
|
|
26
|
+
* @param itemId - Unique identifier for the list item (used to detect view recycling)
|
|
27
|
+
* @param config - Optional configuration for layout compensation callbacks
|
|
28
|
+
* @returns Animation styles and trigger functions
|
|
29
|
+
*
|
|
30
|
+
* @example
|
|
31
|
+
* ```tsx
|
|
32
|
+
* const { exitAnimatedStyle, triggerExit, resetAnimation } = useListExitAnimation(item.id, {
|
|
33
|
+
* index,
|
|
34
|
+
* measuredHeight: 80,
|
|
35
|
+
* onExitStart: (idx, height) => registerExitingItem(itemId, idx, height),
|
|
36
|
+
* onExitComplete: () => unregisterExitingItem(itemId),
|
|
37
|
+
* });
|
|
38
|
+
*
|
|
39
|
+
* // Trigger exit animation
|
|
40
|
+
* triggerExit(1, () => {
|
|
41
|
+
* onItemRemoved(item.id);
|
|
42
|
+
* });
|
|
43
|
+
*
|
|
44
|
+
* // Apply styles
|
|
45
|
+
* <Animated.View style={exitAnimatedStyle}>
|
|
46
|
+
* {content}
|
|
47
|
+
* </Animated.View>
|
|
48
|
+
* ```
|
|
49
|
+
*/
|
|
50
|
+
const useListExitAnimation = (itemId, config) => {
|
|
51
|
+
// Shared value for exit direction - created eagerly but idle until animation triggers
|
|
52
|
+
// Value: 0 = no animation, 1 = animating right, -1 = animating left
|
|
53
|
+
const exitDirection = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
54
|
+
// Track current animation config on UI thread for smooth animation
|
|
55
|
+
const slideDistance = (0, react_native_reanimated_1.useSharedValue)(animations_1.FAST_EXIT_ANIMATION.slide.distance);
|
|
56
|
+
const scaleToValue = (0, react_native_reanimated_1.useSharedValue)(animations_1.FAST_EXIT_ANIMATION.scale.toValue);
|
|
57
|
+
// Use useRef instead of useSharedValue for isAnimating flag
|
|
58
|
+
const isAnimatingRef = (0, react_1.useRef)(false);
|
|
59
|
+
// Refs for callback management and unmount safety
|
|
60
|
+
const onCompleteRef = (0, react_1.useRef)(null);
|
|
61
|
+
const isMountedRef = (0, react_1.useRef)(true);
|
|
62
|
+
// Cleanup on unmount
|
|
63
|
+
(0, react_1.useEffect)(() => {
|
|
64
|
+
return () => {
|
|
65
|
+
isMountedRef.current = false;
|
|
66
|
+
onCompleteRef.current = null;
|
|
67
|
+
};
|
|
68
|
+
}, []);
|
|
69
|
+
// Reset animation state when view is recycled (item ID changes)
|
|
70
|
+
(0, react_1.useEffect)(() => {
|
|
71
|
+
exitDirection.value = 0;
|
|
72
|
+
slideDistance.value = animations_1.FAST_EXIT_ANIMATION.slide.distance;
|
|
73
|
+
scaleToValue.value = animations_1.FAST_EXIT_ANIMATION.scale.toValue;
|
|
74
|
+
isAnimatingRef.current = false;
|
|
75
|
+
}, [itemId, exitDirection, slideDistance, scaleToValue]);
|
|
76
|
+
// Exit animated style (slide, fade, scale)
|
|
77
|
+
const exitAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
|
|
78
|
+
// Fast path: no animation active, return static values
|
|
79
|
+
if (exitDirection.value === 0) {
|
|
80
|
+
return {
|
|
81
|
+
opacity: 1,
|
|
82
|
+
transform: [{ translateX: 0 }, { scale: 1 }],
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
const progress = Math.abs(exitDirection.value);
|
|
86
|
+
return {
|
|
87
|
+
opacity: 1 - progress,
|
|
88
|
+
transform: [
|
|
89
|
+
{ translateX: exitDirection.value * slideDistance.value },
|
|
90
|
+
{ scale: 1 - progress * (1 - scaleToValue.value) },
|
|
91
|
+
],
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
// Helper function to safely call the completion callback
|
|
95
|
+
const safeCallComplete = (0, react_1.useCallback)(() => {
|
|
96
|
+
if (isMountedRef.current && onCompleteRef.current) {
|
|
97
|
+
onCompleteRef.current();
|
|
98
|
+
onCompleteRef.current = null;
|
|
99
|
+
}
|
|
100
|
+
// Unregister exiting item (layout compensation cleanup)
|
|
101
|
+
config?.onExitComplete?.();
|
|
102
|
+
isAnimatingRef.current = false;
|
|
103
|
+
}, [config]);
|
|
104
|
+
/**
|
|
105
|
+
* Trigger exit animation with direction, completion callback, and optional preset
|
|
106
|
+
* @param direction - 1 for forward/right, -1 for backward/left
|
|
107
|
+
* @param onComplete - Callback fired when animation completes
|
|
108
|
+
* @param preset - Animation preset: 'default' (300ms) or 'fast' (200ms)
|
|
109
|
+
*/
|
|
110
|
+
const triggerExit = (0, react_1.useCallback)((direction, onComplete, preset = 'fast') => {
|
|
111
|
+
// Guard against rapid toggling
|
|
112
|
+
if (isAnimatingRef.current)
|
|
113
|
+
return;
|
|
114
|
+
isAnimatingRef.current = true;
|
|
115
|
+
onCompleteRef.current = onComplete;
|
|
116
|
+
const animConfig = animationConfigs[preset];
|
|
117
|
+
// Register exiting item for layout compensation (if configured)
|
|
118
|
+
if (config?.onExitStart && config.index !== undefined) {
|
|
119
|
+
const height = config.measuredHeight ?? animConfig.slide.distance;
|
|
120
|
+
config.onExitStart(config.index, height);
|
|
121
|
+
}
|
|
122
|
+
// Set animation config SharedValues BEFORE starting animation
|
|
123
|
+
slideDistance.value = animConfig.slide.distance;
|
|
124
|
+
scaleToValue.value = animConfig.scale.toValue;
|
|
125
|
+
// Animate exitDirection.value from 0 to 1 (or -1)
|
|
126
|
+
exitDirection.value = (0, react_native_reanimated_1.withTiming)(direction, { duration: animConfig.slide.duration, easing: animations_1.standardEasing }, finished => {
|
|
127
|
+
'worklet';
|
|
128
|
+
if (finished) {
|
|
129
|
+
(0, react_native_worklets_1.scheduleOnRN)(safeCallComplete);
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}, [exitDirection, slideDistance, scaleToValue, safeCallComplete, config]);
|
|
133
|
+
/**
|
|
134
|
+
* Reset animation state (for reuse or error recovery)
|
|
135
|
+
*/
|
|
136
|
+
const resetAnimation = (0, react_1.useCallback)(() => {
|
|
137
|
+
isAnimatingRef.current = false;
|
|
138
|
+
exitDirection.value = 0;
|
|
139
|
+
}, [exitDirection]);
|
|
140
|
+
return {
|
|
141
|
+
exitAnimatedStyle,
|
|
142
|
+
triggerExit,
|
|
143
|
+
resetAnimation,
|
|
144
|
+
};
|
|
145
|
+
};
|
|
146
|
+
exports.useListExitAnimation = useListExitAnimation;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Drag-to-reorder hooks
|
|
3
|
+
*
|
|
4
|
+
* These hooks encapsulate the complex logic for implementing drag-to-reorder
|
|
5
|
+
* functionality in a FlashList. They work together with DragStateContext
|
|
6
|
+
* to coordinate animations across all items.
|
|
7
|
+
*
|
|
8
|
+
* Usage:
|
|
9
|
+
* ```tsx
|
|
10
|
+
* const { panGesture, isDragging, translateY } = useDragGesture(config, callbacks);
|
|
11
|
+
* const { shiftY } = useDragShift({ itemId, index });
|
|
12
|
+
* useDropCompensation({ itemId, index, translateY, shiftY });
|
|
13
|
+
* const { dragAnimatedStyle } = useDragAnimatedStyle(itemId, isDragging, translateY, shiftY);
|
|
14
|
+
* ```
|
|
15
|
+
*/
|
|
16
|
+
export { useDragGesture } from './useDragGesture';
|
|
17
|
+
export { useDragShift } from './useDragShift';
|
|
18
|
+
export { useDropCompensation } from './useDropCompensation';
|
|
19
|
+
export { useDragAnimatedStyle } from './useDragAnimatedStyle';
|
|
20
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAClD,OAAO,EAAE,YAAY,EAAE,MAAM,gBAAgB,CAAC;AAC9C,OAAO,EAAE,mBAAmB,EAAE,MAAM,uBAAuB,CAAC;AAC5D,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Drag-to-reorder hooks
|
|
4
|
+
*
|
|
5
|
+
* These hooks encapsulate the complex logic for implementing drag-to-reorder
|
|
6
|
+
* functionality in a FlashList. They work together with DragStateContext
|
|
7
|
+
* to coordinate animations across all items.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* ```tsx
|
|
11
|
+
* const { panGesture, isDragging, translateY } = useDragGesture(config, callbacks);
|
|
12
|
+
* const { shiftY } = useDragShift({ itemId, index });
|
|
13
|
+
* useDropCompensation({ itemId, index, translateY, shiftY });
|
|
14
|
+
* const { dragAnimatedStyle } = useDragAnimatedStyle(itemId, isDragging, translateY, shiftY);
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
18
|
+
exports.useDragAnimatedStyle = exports.useDropCompensation = exports.useDragShift = exports.useDragGesture = void 0;
|
|
19
|
+
var useDragGesture_1 = require("./useDragGesture");
|
|
20
|
+
Object.defineProperty(exports, "useDragGesture", { enumerable: true, get: function () { return useDragGesture_1.useDragGesture; } });
|
|
21
|
+
var useDragShift_1 = require("./useDragShift");
|
|
22
|
+
Object.defineProperty(exports, "useDragShift", { enumerable: true, get: function () { return useDragShift_1.useDragShift; } });
|
|
23
|
+
var useDropCompensation_1 = require("./useDropCompensation");
|
|
24
|
+
Object.defineProperty(exports, "useDropCompensation", { enumerable: true, get: function () { return useDropCompensation_1.useDropCompensation; } });
|
|
25
|
+
var useDragAnimatedStyle_1 = require("./useDragAnimatedStyle");
|
|
26
|
+
Object.defineProperty(exports, "useDragAnimatedStyle", { enumerable: true, get: function () { return useDragAnimatedStyle_1.useDragAnimatedStyle; } });
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
+
import type { UseDragAnimatedStyleResult } from '../../types';
|
|
3
|
+
/**
|
|
4
|
+
* Hook that creates animated styles for drag operations.
|
|
5
|
+
*
|
|
6
|
+
* Handles both dragged item and non-dragged item styles:
|
|
7
|
+
* - Dragged item: Uses translateY for position + scale from context
|
|
8
|
+
* - Non-dragged items: Uses shiftY for displacement animation
|
|
9
|
+
*
|
|
10
|
+
* CRITICAL: This hook merges transforms into a single style because React Native
|
|
11
|
+
* doesn't merge transform arrays (when multiple styles have transforms, the last one wins).
|
|
12
|
+
*
|
|
13
|
+
* Also handles:
|
|
14
|
+
* - zIndex elevation for dragged item
|
|
15
|
+
* - Shadow opacity animation
|
|
16
|
+
* - Elevation for Android
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { dragAnimatedStyle } = useDragAnimatedStyle(
|
|
21
|
+
* item.id,
|
|
22
|
+
* isDragging,
|
|
23
|
+
* translateY,
|
|
24
|
+
* shiftY
|
|
25
|
+
* );
|
|
26
|
+
*
|
|
27
|
+
* <Animated.View style={[styles.container, isDragEnabled && dragAnimatedStyle]}>
|
|
28
|
+
* ...
|
|
29
|
+
* </Animated.View>
|
|
30
|
+
* ```
|
|
31
|
+
*/
|
|
32
|
+
export declare function useDragAnimatedStyle(itemId: string, isDragging: SharedValue<boolean>, translateY: SharedValue<number>, shiftY: SharedValue<number>): UseDragAnimatedStyleResult;
|
|
33
|
+
//# sourceMappingURL=useDragAnimatedStyle.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDragAnimatedStyle.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragAnimatedStyle.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAG3D,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,CAuC5B"}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useDragAnimatedStyle = useDragAnimatedStyle;
|
|
4
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
5
|
+
const DragStateContext_1 = require("../../contexts/DragStateContext");
|
|
6
|
+
/**
|
|
7
|
+
* Hook that creates animated styles for drag operations.
|
|
8
|
+
*
|
|
9
|
+
* Handles both dragged item and non-dragged item styles:
|
|
10
|
+
* - Dragged item: Uses translateY for position + scale from context
|
|
11
|
+
* - Non-dragged items: Uses shiftY for displacement animation
|
|
12
|
+
*
|
|
13
|
+
* CRITICAL: This hook merges transforms into a single style because React Native
|
|
14
|
+
* doesn't merge transform arrays (when multiple styles have transforms, the last one wins).
|
|
15
|
+
*
|
|
16
|
+
* Also handles:
|
|
17
|
+
* - zIndex elevation for dragged item
|
|
18
|
+
* - Shadow opacity animation
|
|
19
|
+
* - Elevation for Android
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const { dragAnimatedStyle } = useDragAnimatedStyle(
|
|
24
|
+
* item.id,
|
|
25
|
+
* isDragging,
|
|
26
|
+
* translateY,
|
|
27
|
+
* shiftY
|
|
28
|
+
* );
|
|
29
|
+
*
|
|
30
|
+
* <Animated.View style={[styles.container, isDragEnabled && dragAnimatedStyle]}>
|
|
31
|
+
* ...
|
|
32
|
+
* </Animated.View>
|
|
33
|
+
* ```
|
|
34
|
+
*/
|
|
35
|
+
function useDragAnimatedStyle(itemId, isDragging, translateY, shiftY) {
|
|
36
|
+
// Global drag state for scale, identity check, and scroll compensation
|
|
37
|
+
const { draggedItemId, draggedScale, config, scrollOffset, dragStartScrollOffset } = (0, DragStateContext_1.useDragState)();
|
|
38
|
+
// Animated style for drag offset with scale and shadow
|
|
39
|
+
const dragAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
|
|
40
|
+
const isThisItemDragged = draggedItemId.value === itemId;
|
|
41
|
+
// Keep elevated if: actively dragging OR has offset (animating back)
|
|
42
|
+
const shouldBeElevated = isDragging.value || Math.abs(translateY.value) > 1;
|
|
43
|
+
const shadowOpacity = (0, react_native_reanimated_1.interpolate)(draggedScale.value, [1, config.dragScale], [0.1, config.dragShadowOpacity]);
|
|
44
|
+
// Calculate scroll delta for position compensation during autoscroll
|
|
45
|
+
const scrollDelta = scrollOffset.value - dragStartScrollOffset.value;
|
|
46
|
+
// Use drag translateY + scroll compensation for dragged item, shiftY for others
|
|
47
|
+
const yOffset = isThisItemDragged
|
|
48
|
+
? translateY.value + scrollDelta
|
|
49
|
+
: shiftY.value;
|
|
50
|
+
return {
|
|
51
|
+
transform: [
|
|
52
|
+
{ translateY: yOffset },
|
|
53
|
+
{ scale: isThisItemDragged ? draggedScale.value : 1 },
|
|
54
|
+
],
|
|
55
|
+
zIndex: shouldBeElevated ? 999 : 0,
|
|
56
|
+
shadowOpacity: isThisItemDragged ? shadowOpacity : 0.1,
|
|
57
|
+
elevation: isDragging.value ? 12 : 4,
|
|
58
|
+
};
|
|
59
|
+
});
|
|
60
|
+
return { dragAnimatedStyle };
|
|
61
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { UseDragGestureConfig, UseDragGestureCallbacks, UseDragGestureResult } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook that encapsulates all drag gesture logic.
|
|
4
|
+
*
|
|
5
|
+
* Handles:
|
|
6
|
+
* - Pan gesture with long press activation
|
|
7
|
+
* - Autoscroll when dragging near viewport edges
|
|
8
|
+
* - Optional haptic feedback on drag start and drop
|
|
9
|
+
* - Scale animation for visual feedback
|
|
10
|
+
* - Drop position calculation based on drag offset
|
|
11
|
+
*
|
|
12
|
+
* Uses DragStateContext for global coordination across all items.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const { panGesture, isDragging, translateY } = useDragGesture(
|
|
17
|
+
* { itemId: item.id, index, totalItems, enabled: true, containerRef },
|
|
18
|
+
* { onReorderByDelta: handleReorder, onHapticFeedback: triggerHaptic }
|
|
19
|
+
* );
|
|
20
|
+
*
|
|
21
|
+
* // Attach to drag handle
|
|
22
|
+
* <GestureDetector gesture={panGesture}>
|
|
23
|
+
* <Animated.View>
|
|
24
|
+
* <DragHandleIcon />
|
|
25
|
+
* </Animated.View>
|
|
26
|
+
* </GestureDetector>
|
|
27
|
+
* ```
|
|
28
|
+
*/
|
|
29
|
+
export declare function useDragGesture(config: UseDragGestureConfig, callbacks: UseDragGestureCallbacks): UseDragGestureResult;
|
|
30
|
+
//# sourceMappingURL=useDragGesture.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDragGesture.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragGesture.ts"],"names":[],"mappings":"AAYA,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,CA0NtB"}
|
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useDragGesture = useDragGesture;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
6
|
+
const react_native_gesture_handler_1 = require("react-native-gesture-handler");
|
|
7
|
+
const react_native_worklets_1 = require("react-native-worklets");
|
|
8
|
+
const DragStateContext_1 = require("../../contexts/DragStateContext");
|
|
9
|
+
/**
|
|
10
|
+
* Hook that encapsulates all drag gesture logic.
|
|
11
|
+
*
|
|
12
|
+
* Handles:
|
|
13
|
+
* - Pan gesture with long press activation
|
|
14
|
+
* - Autoscroll when dragging near viewport edges
|
|
15
|
+
* - Optional haptic feedback on drag start and drop
|
|
16
|
+
* - Scale animation for visual feedback
|
|
17
|
+
* - Drop position calculation based on drag offset
|
|
18
|
+
*
|
|
19
|
+
* Uses DragStateContext for global coordination across all items.
|
|
20
|
+
*
|
|
21
|
+
* @example
|
|
22
|
+
* ```tsx
|
|
23
|
+
* const { panGesture, isDragging, translateY } = useDragGesture(
|
|
24
|
+
* { itemId: item.id, index, totalItems, enabled: true, containerRef },
|
|
25
|
+
* { onReorderByDelta: handleReorder, onHapticFeedback: triggerHaptic }
|
|
26
|
+
* );
|
|
27
|
+
*
|
|
28
|
+
* // Attach to drag handle
|
|
29
|
+
* <GestureDetector gesture={panGesture}>
|
|
30
|
+
* <Animated.View>
|
|
31
|
+
* <DragHandleIcon />
|
|
32
|
+
* </Animated.View>
|
|
33
|
+
* </GestureDetector>
|
|
34
|
+
* ```
|
|
35
|
+
*/
|
|
36
|
+
function useDragGesture(config, callbacks) {
|
|
37
|
+
const { itemId, index, totalItems, enabled, containerRef } = config;
|
|
38
|
+
const { onReorderByDelta, onHapticFeedback } = callbacks;
|
|
39
|
+
// Local drag state for this item's animation
|
|
40
|
+
const isDragging = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
41
|
+
const translateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
42
|
+
// Global drag state for coordinating animations across all items
|
|
43
|
+
const { isDragging: globalIsDragging, draggedIndex, draggedItemId, currentTranslateY, draggedScale, scrollOffset, dragStartScrollOffset, contentHeight, visibleHeight, listTopY, dragUpdateTrigger, measuredItemHeight, isDropping, scrollToOffset, config: dragConfig, } = (0, DragStateContext_1.useDragState)();
|
|
44
|
+
// Store current values in refs for stable gesture callbacks
|
|
45
|
+
const dragContextRef = (0, react_1.useRef)({
|
|
46
|
+
index,
|
|
47
|
+
totalItems,
|
|
48
|
+
itemId,
|
|
49
|
+
onReorderByDelta,
|
|
50
|
+
});
|
|
51
|
+
// Keep ref in sync with current values
|
|
52
|
+
dragContextRef.current = {
|
|
53
|
+
index,
|
|
54
|
+
totalItems,
|
|
55
|
+
itemId,
|
|
56
|
+
onReorderByDelta,
|
|
57
|
+
};
|
|
58
|
+
// Calculate new position and call reorder callback
|
|
59
|
+
const handleDragEnd = (0, react_1.useCallback)((finalTranslateY) => {
|
|
60
|
+
const { index: currentIndex, totalItems: total, itemId: currentItemId, onReorderByDelta: reorder, } = dragContextRef.current;
|
|
61
|
+
// Use dynamically measured height + margins, or fall back to config
|
|
62
|
+
const itemHeight = measuredItemHeight.value > 0
|
|
63
|
+
? measuredItemHeight.value + dragConfig.itemVerticalMargin
|
|
64
|
+
: dragConfig.itemHeight;
|
|
65
|
+
// Calculate how many positions to move based on drag offset
|
|
66
|
+
const positionDelta = Math.round(finalTranslateY / itemHeight);
|
|
67
|
+
// Calculate if position actually changes
|
|
68
|
+
const newIndex = Math.max(0, Math.min(total - 1, currentIndex + positionDelta));
|
|
69
|
+
const positionChanged = reorder && positionDelta !== 0 && newIndex !== currentIndex;
|
|
70
|
+
if (positionChanged) {
|
|
71
|
+
// Position changes - call reorder, let useDropCompensation handle animation
|
|
72
|
+
isDropping.value = true;
|
|
73
|
+
onHapticFeedback?.('medium');
|
|
74
|
+
reorder(currentItemId, positionDelta);
|
|
75
|
+
// Reset translate values
|
|
76
|
+
currentTranslateY.value = 0;
|
|
77
|
+
dragStartScrollOffset.value = 0;
|
|
78
|
+
}
|
|
79
|
+
else {
|
|
80
|
+
// Same position - animate back and reset state
|
|
81
|
+
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 => {
|
|
82
|
+
'worklet';
|
|
83
|
+
if (finished) {
|
|
84
|
+
globalIsDragging.value = false;
|
|
85
|
+
draggedIndex.value = -1;
|
|
86
|
+
draggedItemId.value = '';
|
|
87
|
+
currentTranslateY.value = 0;
|
|
88
|
+
dragStartScrollOffset.value = 0;
|
|
89
|
+
measuredItemHeight.value = 0;
|
|
90
|
+
dragUpdateTrigger.value = dragUpdateTrigger.value + 1;
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}, [
|
|
95
|
+
measuredItemHeight,
|
|
96
|
+
dragConfig,
|
|
97
|
+
translateY,
|
|
98
|
+
globalIsDragging,
|
|
99
|
+
draggedIndex,
|
|
100
|
+
draggedItemId,
|
|
101
|
+
currentTranslateY,
|
|
102
|
+
dragStartScrollOffset,
|
|
103
|
+
dragUpdateTrigger,
|
|
104
|
+
isDropping,
|
|
105
|
+
onHapticFeedback,
|
|
106
|
+
]);
|
|
107
|
+
// Stable haptic callback for drag start
|
|
108
|
+
const triggerLightHaptic = (0, react_1.useCallback)(() => {
|
|
109
|
+
onHapticFeedback?.('light');
|
|
110
|
+
}, [onHapticFeedback]);
|
|
111
|
+
// Pan gesture for drag-to-reorder
|
|
112
|
+
const panGesture = (0, react_1.useMemo)(() => react_native_gesture_handler_1.Gesture.Pan()
|
|
113
|
+
.activateAfterLongPress(dragConfig.longPressDuration)
|
|
114
|
+
.enabled(enabled)
|
|
115
|
+
.onStart(() => {
|
|
116
|
+
'worklet';
|
|
117
|
+
// Measure actual item height for accurate drag calculations
|
|
118
|
+
const measured = (0, react_native_reanimated_1.measure)(containerRef);
|
|
119
|
+
if (measured) {
|
|
120
|
+
measuredItemHeight.value = measured.height;
|
|
121
|
+
}
|
|
122
|
+
// Local drag state
|
|
123
|
+
isDragging.value = true;
|
|
124
|
+
// Global drag state for shift animations
|
|
125
|
+
globalIsDragging.value = true;
|
|
126
|
+
draggedIndex.value = index;
|
|
127
|
+
draggedItemId.value = itemId;
|
|
128
|
+
dragStartScrollOffset.value = scrollOffset.value;
|
|
129
|
+
currentTranslateY.value = 0;
|
|
130
|
+
draggedScale.value = (0, react_native_reanimated_1.withSpring)(dragConfig.dragScale, {
|
|
131
|
+
damping: 15,
|
|
132
|
+
stiffness: 400,
|
|
133
|
+
});
|
|
134
|
+
dragUpdateTrigger.value = dragUpdateTrigger.value + 1;
|
|
135
|
+
(0, react_native_worklets_1.scheduleOnRN)(triggerLightHaptic);
|
|
136
|
+
})
|
|
137
|
+
.onUpdate(event => {
|
|
138
|
+
'worklet';
|
|
139
|
+
translateY.value = event.translationY;
|
|
140
|
+
currentTranslateY.value = event.translationY;
|
|
141
|
+
dragUpdateTrigger.value = dragUpdateTrigger.value + 1;
|
|
142
|
+
// Autoscroll when dragging near edges
|
|
143
|
+
const fingerInList = event.absoluteY - listTopY.value;
|
|
144
|
+
const topEdge = dragConfig.edgeThreshold;
|
|
145
|
+
const bottomEdge = visibleHeight.value - dragConfig.edgeThreshold;
|
|
146
|
+
if (fingerInList < topEdge && scrollOffset.value > 0) {
|
|
147
|
+
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [0, topEdge], [dragConfig.maxScrollSpeed, 0], 'clamp');
|
|
148
|
+
const newOffset = Math.max(0, scrollOffset.value - speed);
|
|
149
|
+
scrollOffset.value = newOffset;
|
|
150
|
+
(0, react_native_worklets_1.scheduleOnRN)(scrollToOffset, newOffset);
|
|
151
|
+
}
|
|
152
|
+
else if (fingerInList > bottomEdge) {
|
|
153
|
+
const maxOffset = Math.max(0, contentHeight.value - visibleHeight.value);
|
|
154
|
+
if (scrollOffset.value < maxOffset) {
|
|
155
|
+
const speed = (0, react_native_reanimated_1.interpolate)(fingerInList, [bottomEdge, visibleHeight.value], [0, dragConfig.maxScrollSpeed], 'clamp');
|
|
156
|
+
const newOffset = Math.min(maxOffset, scrollOffset.value + speed);
|
|
157
|
+
scrollOffset.value = newOffset;
|
|
158
|
+
(0, react_native_worklets_1.scheduleOnRN)(scrollToOffset, newOffset);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
})
|
|
162
|
+
.onEnd(event => {
|
|
163
|
+
'worklet';
|
|
164
|
+
isDragging.value = false;
|
|
165
|
+
// Include scroll compensation in final position calculation
|
|
166
|
+
const scrollDelta = scrollOffset.value - dragStartScrollOffset.value;
|
|
167
|
+
const finalY = event.translationY + scrollDelta;
|
|
168
|
+
draggedScale.value = (0, react_native_reanimated_1.withSpring)(1, { damping: 15, stiffness: 400 });
|
|
169
|
+
(0, react_native_worklets_1.scheduleOnRN)(handleDragEnd, finalY);
|
|
170
|
+
})
|
|
171
|
+
.onFinalize((_event, success) => {
|
|
172
|
+
'worklet';
|
|
173
|
+
if (!success) {
|
|
174
|
+
isDragging.value = false;
|
|
175
|
+
translateY.value = (0, react_native_reanimated_1.withTiming)(0, { duration: 150 });
|
|
176
|
+
isDropping.value = false;
|
|
177
|
+
globalIsDragging.value = false;
|
|
178
|
+
draggedIndex.value = -1;
|
|
179
|
+
draggedItemId.value = '';
|
|
180
|
+
currentTranslateY.value = 0;
|
|
181
|
+
draggedScale.value = 1;
|
|
182
|
+
dragStartScrollOffset.value = 0;
|
|
183
|
+
measuredItemHeight.value = 0;
|
|
184
|
+
}
|
|
185
|
+
}),
|
|
186
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
187
|
+
[enabled, containerRef, triggerLightHaptic, handleDragEnd, index, itemId]);
|
|
188
|
+
return { panGesture, isDragging, translateY };
|
|
189
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { UseDragShiftConfig, UseDragShiftResult } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook that calculates shift animation for non-dragged items.
|
|
4
|
+
*
|
|
5
|
+
* When an item is being dragged, other items need to shift up or down
|
|
6
|
+
* to make room for the dragged item at its new position. This hook
|
|
7
|
+
* calculates the target shift based on:
|
|
8
|
+
* - The current drag position (from context)
|
|
9
|
+
* - The scroll delta (accounting for autoscroll)
|
|
10
|
+
* - The item's index relative to the dragged item
|
|
11
|
+
*
|
|
12
|
+
* The shift is animated with timing for smooth transitions.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```tsx
|
|
16
|
+
* const { shiftY } = useDragShift({ itemId: item.id, index });
|
|
17
|
+
* // Use shiftY.value in animated style
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function useDragShift(config: UseDragShiftConfig): UseDragShiftResult;
|
|
21
|
+
//# sourceMappingURL=useDragShift.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useDragShift.d.ts","sourceRoot":"","sources":["../../../src/hooks/drag/useDragShift.ts"],"names":[],"mappings":"AAQA,OAAO,KAAK,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AAE1E;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,kBAAkB,GAAG,kBAAkB,CA0F3E"}
|