@souscheflabs/reanimated-flashlist 0.1.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. package/README.md +282 -0
  2. package/lib/AnimatedFlashList.d.ts +6 -0
  3. package/lib/AnimatedFlashList.d.ts.map +1 -0
  4. package/lib/AnimatedFlashList.js +207 -0
  5. package/lib/AnimatedFlashListItem.d.ts +33 -0
  6. package/lib/AnimatedFlashListItem.d.ts.map +1 -0
  7. package/lib/AnimatedFlashListItem.js +155 -0
  8. package/lib/__tests__/utils/test-utils.d.ts +82 -0
  9. package/lib/__tests__/utils/test-utils.d.ts.map +1 -0
  10. package/lib/__tests__/utils/test-utils.js +115 -0
  11. package/lib/constants/animations.d.ts +39 -0
  12. package/lib/constants/animations.d.ts.map +1 -0
  13. package/lib/constants/animations.js +100 -0
  14. package/lib/constants/drag.d.ts +11 -0
  15. package/lib/constants/drag.d.ts.map +1 -0
  16. package/lib/constants/drag.js +47 -0
  17. package/lib/constants/index.d.ts +3 -0
  18. package/lib/constants/index.d.ts.map +1 -0
  19. package/lib/constants/index.js +18 -0
  20. package/lib/contexts/DragStateContext.d.ts +73 -0
  21. package/lib/contexts/DragStateContext.d.ts.map +1 -0
  22. package/lib/contexts/DragStateContext.js +148 -0
  23. package/lib/contexts/ListAnimationContext.d.ts +104 -0
  24. package/lib/contexts/ListAnimationContext.d.ts.map +1 -0
  25. package/lib/contexts/ListAnimationContext.js +184 -0
  26. package/lib/contexts/index.d.ts +5 -0
  27. package/lib/contexts/index.d.ts.map +1 -0
  28. package/lib/contexts/index.js +10 -0
  29. package/lib/hooks/animations/index.d.ts +9 -0
  30. package/lib/hooks/animations/index.d.ts.map +1 -0
  31. package/lib/hooks/animations/index.js +13 -0
  32. package/lib/hooks/animations/useListEntryAnimation.d.ts +38 -0
  33. package/lib/hooks/animations/useListEntryAnimation.d.ts.map +1 -0
  34. package/lib/hooks/animations/useListEntryAnimation.js +90 -0
  35. package/lib/hooks/animations/useListExitAnimation.d.ts +67 -0
  36. package/lib/hooks/animations/useListExitAnimation.d.ts.map +1 -0
  37. package/lib/hooks/animations/useListExitAnimation.js +146 -0
  38. package/lib/hooks/drag/index.d.ts +20 -0
  39. package/lib/hooks/drag/index.d.ts.map +1 -0
  40. package/lib/hooks/drag/index.js +26 -0
  41. package/lib/hooks/drag/useDragAnimatedStyle.d.ts +33 -0
  42. package/lib/hooks/drag/useDragAnimatedStyle.d.ts.map +1 -0
  43. package/lib/hooks/drag/useDragAnimatedStyle.js +61 -0
  44. package/lib/hooks/drag/useDragGesture.d.ts +30 -0
  45. package/lib/hooks/drag/useDragGesture.d.ts.map +1 -0
  46. package/lib/hooks/drag/useDragGesture.js +189 -0
  47. package/lib/hooks/drag/useDragShift.d.ts +21 -0
  48. package/lib/hooks/drag/useDragShift.d.ts.map +1 -0
  49. package/lib/hooks/drag/useDragShift.js +85 -0
  50. package/lib/hooks/drag/useDropCompensation.d.ts +27 -0
  51. package/lib/hooks/drag/useDropCompensation.d.ts.map +1 -0
  52. package/lib/hooks/drag/useDropCompensation.js +90 -0
  53. package/lib/hooks/index.d.ts +8 -0
  54. package/lib/hooks/index.d.ts.map +1 -0
  55. package/lib/hooks/index.js +18 -0
  56. package/lib/index.d.ts +42 -0
  57. package/lib/index.d.ts.map +1 -0
  58. package/lib/index.js +69 -0
  59. package/lib/types/animations.d.ts +71 -0
  60. package/lib/types/animations.d.ts.map +1 -0
  61. package/lib/types/animations.js +2 -0
  62. package/lib/types/drag.d.ts +94 -0
  63. package/lib/types/drag.d.ts.map +1 -0
  64. package/lib/types/drag.js +2 -0
  65. package/lib/types/index.d.ts +4 -0
  66. package/lib/types/index.d.ts.map +1 -0
  67. package/lib/types/index.js +19 -0
  68. package/lib/types/list.d.ts +136 -0
  69. package/lib/types/list.d.ts.map +1 -0
  70. package/lib/types/list.js +2 -0
  71. package/package.json +73 -0
  72. package/src/AnimatedFlashList.tsx +411 -0
  73. package/src/AnimatedFlashListItem.tsx +212 -0
  74. package/src/__tests__/components/AnimatedFlashList.test.tsx +365 -0
  75. package/src/__tests__/components/AnimatedFlashListItem.test.tsx +371 -0
  76. package/src/__tests__/contexts/DragStateContext.test.tsx +169 -0
  77. package/src/__tests__/contexts/ListAnimationContext.test.tsx +324 -0
  78. package/src/__tests__/hooks/useDragAnimatedStyle.test.tsx +118 -0
  79. package/src/__tests__/hooks/useDragGesture.test.tsx +169 -0
  80. package/src/__tests__/hooks/useDragShift.test.tsx +94 -0
  81. package/src/__tests__/hooks/useDropCompensation.test.tsx +182 -0
  82. package/src/__tests__/hooks/useListEntryAnimation.test.tsx +135 -0
  83. package/src/__tests__/hooks/useListExitAnimation.test.tsx +175 -0
  84. package/src/__tests__/utils/test-utils.tsx +159 -0
  85. package/src/constants/animations.ts +107 -0
  86. package/src/constants/drag.ts +51 -0
  87. package/src/constants/index.ts +2 -0
  88. package/src/contexts/DragStateContext.tsx +197 -0
  89. package/src/contexts/ListAnimationContext.tsx +302 -0
  90. package/src/contexts/index.ts +9 -0
  91. package/src/hooks/animations/index.ts +9 -0
  92. package/src/hooks/animations/useListEntryAnimation.ts +108 -0
  93. package/src/hooks/animations/useListExitAnimation.ts +197 -0
  94. package/src/hooks/drag/index.ts +20 -0
  95. package/src/hooks/drag/useDragAnimatedStyle.ts +80 -0
  96. package/src/hooks/drag/useDragGesture.ts +267 -0
  97. package/src/hooks/drag/useDragShift.ts +119 -0
  98. package/src/hooks/drag/useDropCompensation.ts +120 -0
  99. package/src/hooks/index.ts +16 -0
  100. package/src/index.ts +105 -0
  101. package/src/types/animations.ts +76 -0
  102. package/src/types/drag.ts +101 -0
  103. package/src/types/index.ts +3 -0
  104. package/src/types/list.ts +178 -0
@@ -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"}