@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,148 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.DragStateProvider = exports.useDragState = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
39
|
+
const constants_1 = require("../constants");
|
|
40
|
+
const DragStateContext = (0, react_1.createContext)(null);
|
|
41
|
+
/**
|
|
42
|
+
* Hook to access shared drag state from context.
|
|
43
|
+
* Must be used within DragStateProvider.
|
|
44
|
+
*/
|
|
45
|
+
const useDragState = () => {
|
|
46
|
+
const context = (0, react_1.useContext)(DragStateContext);
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error('useDragState must be used within DragStateProvider');
|
|
49
|
+
}
|
|
50
|
+
return context;
|
|
51
|
+
};
|
|
52
|
+
exports.useDragState = useDragState;
|
|
53
|
+
/**
|
|
54
|
+
* Provider that creates shared Reanimated values for drag state.
|
|
55
|
+
*
|
|
56
|
+
* These values are shared across all list items:
|
|
57
|
+
* - The dragged item writes to them during drag gestures
|
|
58
|
+
* - Non-dragged items read them to calculate their shift offset
|
|
59
|
+
* - Scroll state enables viewport-aware hover calculations
|
|
60
|
+
*
|
|
61
|
+
* Using SharedValues ensures animations run on the UI thread at 60fps.
|
|
62
|
+
*/
|
|
63
|
+
const DragStateProvider = ({ children, config: configOverrides, }) => {
|
|
64
|
+
// Merge config with defaults
|
|
65
|
+
const config = (0, react_1.useMemo)(() => ({ ...constants_1.DEFAULT_DRAG_CONFIG, ...configOverrides }), [configOverrides]);
|
|
66
|
+
// Shared values are created once and persist for the lifetime of the provider
|
|
67
|
+
const isDragging = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
68
|
+
const draggedIndex = (0, react_native_reanimated_1.useSharedValue)(-1);
|
|
69
|
+
const draggedItemId = (0, react_native_reanimated_1.useSharedValue)('');
|
|
70
|
+
const currentTranslateY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
71
|
+
const draggedScale = (0, react_native_reanimated_1.useSharedValue)(1);
|
|
72
|
+
// Scroll state for viewport-aware calculations and autoscroll
|
|
73
|
+
const scrollOffset = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
74
|
+
const dragStartScrollOffset = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
75
|
+
const contentHeight = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
76
|
+
const visibleHeight = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
77
|
+
const listTopY = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
78
|
+
// Counter to force useDerivedValue re-evaluation on every drag state change
|
|
79
|
+
const dragUpdateTrigger = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
80
|
+
// Measured height of dragged item (0 = use fallback from config)
|
|
81
|
+
const measuredItemHeight = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
82
|
+
// Flag to freeze shift values during drop transition
|
|
83
|
+
const isDropping = (0, react_native_reanimated_1.useSharedValue)(false);
|
|
84
|
+
// Ref to FlashList for autoscroll operations
|
|
85
|
+
const listRef = (0, react_1.useRef)(null);
|
|
86
|
+
// Register the FlashList ref
|
|
87
|
+
const setListRef = (0, react_1.useCallback)((ref) => {
|
|
88
|
+
listRef.current = ref;
|
|
89
|
+
}, []);
|
|
90
|
+
// Scroll to offset (called via scheduleOnRN from worklet for autoscroll)
|
|
91
|
+
const scrollToOffset = (0, react_1.useCallback)((offset, animated = false) => {
|
|
92
|
+
listRef.current?.scrollToOffset({ offset, animated });
|
|
93
|
+
}, []);
|
|
94
|
+
// Reset drag state after drop
|
|
95
|
+
const resetDragState = (0, react_1.useCallback)(() => {
|
|
96
|
+
isDragging.value = false;
|
|
97
|
+
draggedIndex.value = -1;
|
|
98
|
+
draggedItemId.value = '';
|
|
99
|
+
currentTranslateY.value = 0;
|
|
100
|
+
draggedScale.value = 1;
|
|
101
|
+
dragStartScrollOffset.value = 0;
|
|
102
|
+
measuredItemHeight.value = 0;
|
|
103
|
+
isDropping.value = false;
|
|
104
|
+
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
105
|
+
}, []);
|
|
106
|
+
// Context value is stable since SharedValue references don't change
|
|
107
|
+
const value = (0, react_1.useMemo)(() => ({
|
|
108
|
+
isDragging,
|
|
109
|
+
draggedIndex,
|
|
110
|
+
draggedItemId,
|
|
111
|
+
currentTranslateY,
|
|
112
|
+
draggedScale,
|
|
113
|
+
scrollOffset,
|
|
114
|
+
dragStartScrollOffset,
|
|
115
|
+
contentHeight,
|
|
116
|
+
visibleHeight,
|
|
117
|
+
listTopY,
|
|
118
|
+
dragUpdateTrigger,
|
|
119
|
+
measuredItemHeight,
|
|
120
|
+
isDropping,
|
|
121
|
+
setListRef,
|
|
122
|
+
scrollToOffset,
|
|
123
|
+
resetDragState,
|
|
124
|
+
config,
|
|
125
|
+
}), [
|
|
126
|
+
isDragging,
|
|
127
|
+
draggedIndex,
|
|
128
|
+
draggedItemId,
|
|
129
|
+
currentTranslateY,
|
|
130
|
+
draggedScale,
|
|
131
|
+
scrollOffset,
|
|
132
|
+
dragStartScrollOffset,
|
|
133
|
+
contentHeight,
|
|
134
|
+
visibleHeight,
|
|
135
|
+
listTopY,
|
|
136
|
+
dragUpdateTrigger,
|
|
137
|
+
measuredItemHeight,
|
|
138
|
+
isDropping,
|
|
139
|
+
setListRef,
|
|
140
|
+
scrollToOffset,
|
|
141
|
+
resetDragState,
|
|
142
|
+
config,
|
|
143
|
+
]);
|
|
144
|
+
return (<DragStateContext.Provider value={value}>
|
|
145
|
+
{children}
|
|
146
|
+
</DragStateContext.Provider>);
|
|
147
|
+
};
|
|
148
|
+
exports.DragStateProvider = DragStateProvider;
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import React, { type ReactNode } from 'react';
|
|
2
|
+
import { type SharedValue } from 'react-native-reanimated';
|
|
3
|
+
import type { AnimationDirection, ExitAnimationTrigger, PendingEntryAnimation } from '../types';
|
|
4
|
+
/**
|
|
5
|
+
* Context value for coordinating entry/exit animations across list items.
|
|
6
|
+
*
|
|
7
|
+
* This enables:
|
|
8
|
+
* 1. Direct O(1) animation triggers from subscription handlers
|
|
9
|
+
* 2. Entry animation coordination when items appear in new locations
|
|
10
|
+
* 3. Layout animation coordination when items are removed
|
|
11
|
+
* 4. Decoupled animation state from React render cycle
|
|
12
|
+
*/
|
|
13
|
+
export interface ListAnimationContextValue {
|
|
14
|
+
/**
|
|
15
|
+
* Register an exit animation trigger for an item.
|
|
16
|
+
* Called by useListExitAnimation on mount.
|
|
17
|
+
*/
|
|
18
|
+
registerAnimationTrigger: (itemId: string, trigger: ExitAnimationTrigger) => void;
|
|
19
|
+
/**
|
|
20
|
+
* Unregister an exit animation trigger.
|
|
21
|
+
* Called by useListExitAnimation on unmount.
|
|
22
|
+
*/
|
|
23
|
+
unregisterAnimationTrigger: (itemId: string) => void;
|
|
24
|
+
/**
|
|
25
|
+
* Trigger exit animation for a specific item.
|
|
26
|
+
* Returns true if animation was triggered, false if item not found.
|
|
27
|
+
*/
|
|
28
|
+
triggerExitAnimation: (itemId: string, direction: AnimationDirection, onComplete: () => void) => boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Queue an entry animation for an item that's about to appear.
|
|
31
|
+
* Call this before the item is added to the list.
|
|
32
|
+
*/
|
|
33
|
+
queueEntryAnimation: (itemId: string, direction: AnimationDirection) => void;
|
|
34
|
+
/**
|
|
35
|
+
* Check and claim a pending entry animation for an item.
|
|
36
|
+
* Returns the animation info if found, null otherwise.
|
|
37
|
+
* Calling this consumes the pending animation.
|
|
38
|
+
*/
|
|
39
|
+
claimEntryAnimation: (itemId: string) => PendingEntryAnimation | null;
|
|
40
|
+
/**
|
|
41
|
+
* Register an item that is currently exiting (for layout compensation).
|
|
42
|
+
* Called when exit animation starts.
|
|
43
|
+
*/
|
|
44
|
+
registerExitingItem: (itemId: string, index: number, height: number) => void;
|
|
45
|
+
/**
|
|
46
|
+
* Unregister an exiting item (called when removal is complete).
|
|
47
|
+
*/
|
|
48
|
+
unregisterExitingItem: (itemId: string) => void;
|
|
49
|
+
/**
|
|
50
|
+
* Get info about items currently exiting above a given index.
|
|
51
|
+
* Returns the total height of exiting items above.
|
|
52
|
+
*/
|
|
53
|
+
getExitingHeightAbove: (index: number) => number;
|
|
54
|
+
/**
|
|
55
|
+
* SharedValue that increments when exiting items change.
|
|
56
|
+
* Used to trigger useDerivedValue re-evaluation.
|
|
57
|
+
*/
|
|
58
|
+
exitingItemsVersion: SharedValue<number>;
|
|
59
|
+
/**
|
|
60
|
+
* Duration for layout animations (ms).
|
|
61
|
+
*/
|
|
62
|
+
layoutAnimationDuration: number;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Hook to access list animation context.
|
|
66
|
+
* Throws if used outside ListAnimationProvider.
|
|
67
|
+
*/
|
|
68
|
+
export declare const useListAnimation: () => ListAnimationContextValue;
|
|
69
|
+
/**
|
|
70
|
+
* Hook to optionally access list animation context.
|
|
71
|
+
* Returns null if used outside ListAnimationProvider.
|
|
72
|
+
* Useful for components that can work with or without animations.
|
|
73
|
+
*/
|
|
74
|
+
export declare const useListAnimationOptional: () => ListAnimationContextValue | null;
|
|
75
|
+
interface ListAnimationProviderProps {
|
|
76
|
+
children: ReactNode;
|
|
77
|
+
/**
|
|
78
|
+
* How long pending entry animations are valid (ms)
|
|
79
|
+
* @default 5000
|
|
80
|
+
*/
|
|
81
|
+
entryAnimationTimeout?: number;
|
|
82
|
+
/**
|
|
83
|
+
* Duration for layout animations when items are removed (ms)
|
|
84
|
+
* @default 200
|
|
85
|
+
*/
|
|
86
|
+
layoutAnimationDuration?: number;
|
|
87
|
+
/**
|
|
88
|
+
* Whether to enable native LayoutAnimation for remaining items
|
|
89
|
+
* @default true
|
|
90
|
+
*/
|
|
91
|
+
enableLayoutAnimation?: boolean;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Provider for coordinating entry/exit animations across list items.
|
|
95
|
+
*
|
|
96
|
+
* Features:
|
|
97
|
+
* - O(1) exit animation triggers via Map lookup
|
|
98
|
+
* - Entry animation queuing with automatic expiration
|
|
99
|
+
* - Layout animation coordination when items are removed
|
|
100
|
+
* - Decoupled from React render cycle for performance
|
|
101
|
+
*/
|
|
102
|
+
export declare const ListAnimationProvider: React.FC<ListAnimationProviderProps>;
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=ListAnimationContext.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ListAnimationContext.d.ts","sourceRoot":"","sources":["../../src/contexts/ListAnimationContext.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAMZ,KAAK,SAAS,EACf,MAAM,OAAO,CAAC;AAEf,OAAO,EAAkB,KAAK,WAAW,EAAE,MAAM,yBAAyB,CAAC;AAC3E,OAAO,KAAK,EACV,kBAAkB,EAClB,oBAAoB,EACpB,qBAAqB,EAEtB,MAAM,UAAU,CAAC;AAElB;;;;;;;;GAQG;AACH,MAAM,WAAW,yBAAyB;IACxC;;;OAGG;IACH,wBAAwB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,KAAK,IAAI,CAAC;IAElF;;;OAGG;IACH,0BAA0B,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAErD;;;OAGG;IACH,oBAAoB,EAAE,CACpB,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,kBAAkB,EAC7B,UAAU,EAAE,MAAM,IAAI,KACnB,OAAO,CAAC;IAEb;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,SAAS,EAAE,kBAAkB,KAAK,IAAI,CAAC;IAE7E;;;;OAIG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,qBAAqB,GAAG,IAAI,CAAC;IAEtE;;;OAGG;IACH,mBAAmB,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAE7E;;OAEG;IACH,qBAAqB,EAAE,CAAC,MAAM,EAAE,MAAM,KAAK,IAAI,CAAC;IAEhD;;;OAGG;IACH,qBAAqB,EAAE,CAAC,KAAK,EAAE,MAAM,KAAK,MAAM,CAAC;IAEjD;;;OAGG;IACH,mBAAmB,EAAE,WAAW,CAAC,MAAM,CAAC,CAAC;IAEzC;;OAEG;IACH,uBAAuB,EAAE,MAAM,CAAC;CACjC;AAID;;;GAGG;AACH,eAAO,MAAM,gBAAgB,QAAO,yBAMnC,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,wBAAwB,QAAO,yBAAyB,GAAG,IAEvE,CAAC;AAEF,UAAU,0BAA0B;IAClC,QAAQ,EAAE,SAAS,CAAC;IACpB;;;OAGG;IACH,qBAAqB,CAAC,EAAE,MAAM,CAAC;IAC/B;;;OAGG;IACH,uBAAuB,CAAC,EAAE,MAAM,CAAC;IACjC;;;OAGG;IACH,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED;;;;;;;;GAQG;AACH,eAAO,MAAM,qBAAqB,EAAE,KAAK,CAAC,EAAE,CAAC,0BAA0B,CA+JtE,CAAC"}
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.ListAnimationProvider = exports.useListAnimationOptional = exports.useListAnimation = void 0;
|
|
37
|
+
const react_1 = __importStar(require("react"));
|
|
38
|
+
const react_native_1 = require("react-native");
|
|
39
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
40
|
+
const ListAnimationContext = (0, react_1.createContext)(null);
|
|
41
|
+
/**
|
|
42
|
+
* Hook to access list animation context.
|
|
43
|
+
* Throws if used outside ListAnimationProvider.
|
|
44
|
+
*/
|
|
45
|
+
const useListAnimation = () => {
|
|
46
|
+
const context = (0, react_1.useContext)(ListAnimationContext);
|
|
47
|
+
if (!context) {
|
|
48
|
+
throw new Error('useListAnimation must be used within ListAnimationProvider');
|
|
49
|
+
}
|
|
50
|
+
return context;
|
|
51
|
+
};
|
|
52
|
+
exports.useListAnimation = useListAnimation;
|
|
53
|
+
/**
|
|
54
|
+
* Hook to optionally access list animation context.
|
|
55
|
+
* Returns null if used outside ListAnimationProvider.
|
|
56
|
+
* Useful for components that can work with or without animations.
|
|
57
|
+
*/
|
|
58
|
+
const useListAnimationOptional = () => {
|
|
59
|
+
return (0, react_1.useContext)(ListAnimationContext);
|
|
60
|
+
};
|
|
61
|
+
exports.useListAnimationOptional = useListAnimationOptional;
|
|
62
|
+
/**
|
|
63
|
+
* Provider for coordinating entry/exit animations across list items.
|
|
64
|
+
*
|
|
65
|
+
* Features:
|
|
66
|
+
* - O(1) exit animation triggers via Map lookup
|
|
67
|
+
* - Entry animation queuing with automatic expiration
|
|
68
|
+
* - Layout animation coordination when items are removed
|
|
69
|
+
* - Decoupled from React render cycle for performance
|
|
70
|
+
*/
|
|
71
|
+
const ListAnimationProvider = ({ children, entryAnimationTimeout = 5000, layoutAnimationDuration = 200, enableLayoutAnimation = true, }) => {
|
|
72
|
+
// Map of itemId -> exit animation trigger function
|
|
73
|
+
const animationTriggersRef = (0, react_1.useRef)(new Map());
|
|
74
|
+
// Map of itemId -> pending entry animation
|
|
75
|
+
const pendingEntriesRef = (0, react_1.useRef)(new Map());
|
|
76
|
+
// Map of itemId -> exiting item info (for layout compensation)
|
|
77
|
+
const exitingItemsRef = (0, react_1.useRef)(new Map());
|
|
78
|
+
// SharedValue to trigger re-evaluation when exiting items change
|
|
79
|
+
const exitingItemsVersion = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
80
|
+
const registerAnimationTrigger = (0, react_1.useCallback)((itemId, trigger) => {
|
|
81
|
+
animationTriggersRef.current.set(itemId, trigger);
|
|
82
|
+
}, []);
|
|
83
|
+
const unregisterAnimationTrigger = (0, react_1.useCallback)((itemId) => {
|
|
84
|
+
animationTriggersRef.current.delete(itemId);
|
|
85
|
+
}, []);
|
|
86
|
+
const triggerExitAnimation = (0, react_1.useCallback)((itemId, direction, onComplete) => {
|
|
87
|
+
const trigger = animationTriggersRef.current.get(itemId);
|
|
88
|
+
if (trigger) {
|
|
89
|
+
trigger(direction, onComplete);
|
|
90
|
+
return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}, []);
|
|
94
|
+
const queueEntryAnimation = (0, react_1.useCallback)((itemId, direction) => {
|
|
95
|
+
const entry = {
|
|
96
|
+
itemId,
|
|
97
|
+
direction,
|
|
98
|
+
timestamp: Date.now(),
|
|
99
|
+
};
|
|
100
|
+
pendingEntriesRef.current.set(itemId, entry);
|
|
101
|
+
// Auto-expire after timeout
|
|
102
|
+
setTimeout(() => {
|
|
103
|
+
const current = pendingEntriesRef.current.get(itemId);
|
|
104
|
+
if (current && current.timestamp === entry.timestamp) {
|
|
105
|
+
pendingEntriesRef.current.delete(itemId);
|
|
106
|
+
}
|
|
107
|
+
}, entryAnimationTimeout);
|
|
108
|
+
}, [entryAnimationTimeout]);
|
|
109
|
+
const claimEntryAnimation = (0, react_1.useCallback)((itemId) => {
|
|
110
|
+
const entry = pendingEntriesRef.current.get(itemId);
|
|
111
|
+
if (entry) {
|
|
112
|
+
pendingEntriesRef.current.delete(itemId);
|
|
113
|
+
// Check if still valid (not expired)
|
|
114
|
+
if (Date.now() - entry.timestamp < entryAnimationTimeout) {
|
|
115
|
+
return entry;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
return null;
|
|
119
|
+
}, [entryAnimationTimeout]);
|
|
120
|
+
// Register an exiting item for layout compensation
|
|
121
|
+
const registerExitingItem = (0, react_1.useCallback)((itemId, index, height) => {
|
|
122
|
+
const info = {
|
|
123
|
+
itemId,
|
|
124
|
+
index,
|
|
125
|
+
height,
|
|
126
|
+
timestamp: Date.now(),
|
|
127
|
+
};
|
|
128
|
+
exitingItemsRef.current.set(itemId, info);
|
|
129
|
+
exitingItemsVersion.value = exitingItemsVersion.value + 1;
|
|
130
|
+
// Configure native LayoutAnimation for remaining items
|
|
131
|
+
if (enableLayoutAnimation) {
|
|
132
|
+
// Use custom config for smoother animation
|
|
133
|
+
react_native_1.LayoutAnimation.configureNext({
|
|
134
|
+
duration: layoutAnimationDuration,
|
|
135
|
+
update: {
|
|
136
|
+
type: react_native_1.LayoutAnimation.Types.easeInEaseOut,
|
|
137
|
+
property: react_native_1.LayoutAnimation.Properties.scaleY,
|
|
138
|
+
},
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}, [enableLayoutAnimation, layoutAnimationDuration, exitingItemsVersion]);
|
|
142
|
+
// Unregister an exiting item
|
|
143
|
+
const unregisterExitingItem = (0, react_1.useCallback)((itemId) => {
|
|
144
|
+
exitingItemsRef.current.delete(itemId);
|
|
145
|
+
exitingItemsVersion.value = exitingItemsVersion.value + 1;
|
|
146
|
+
}, [exitingItemsVersion]);
|
|
147
|
+
// Get total height of exiting items above a given index
|
|
148
|
+
const getExitingHeightAbove = (0, react_1.useCallback)((index) => {
|
|
149
|
+
let totalHeight = 0;
|
|
150
|
+
exitingItemsRef.current.forEach(info => {
|
|
151
|
+
if (info.index < index) {
|
|
152
|
+
totalHeight += info.height;
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
return totalHeight;
|
|
156
|
+
}, []);
|
|
157
|
+
const value = (0, react_1.useMemo)(() => ({
|
|
158
|
+
registerAnimationTrigger,
|
|
159
|
+
unregisterAnimationTrigger,
|
|
160
|
+
triggerExitAnimation,
|
|
161
|
+
queueEntryAnimation,
|
|
162
|
+
claimEntryAnimation,
|
|
163
|
+
registerExitingItem,
|
|
164
|
+
unregisterExitingItem,
|
|
165
|
+
getExitingHeightAbove,
|
|
166
|
+
exitingItemsVersion,
|
|
167
|
+
layoutAnimationDuration,
|
|
168
|
+
}), [
|
|
169
|
+
registerAnimationTrigger,
|
|
170
|
+
unregisterAnimationTrigger,
|
|
171
|
+
triggerExitAnimation,
|
|
172
|
+
queueEntryAnimation,
|
|
173
|
+
claimEntryAnimation,
|
|
174
|
+
registerExitingItem,
|
|
175
|
+
unregisterExitingItem,
|
|
176
|
+
getExitingHeightAbove,
|
|
177
|
+
exitingItemsVersion,
|
|
178
|
+
layoutAnimationDuration,
|
|
179
|
+
]);
|
|
180
|
+
return (<ListAnimationContext.Provider value={value}>
|
|
181
|
+
{children}
|
|
182
|
+
</ListAnimationContext.Provider>);
|
|
183
|
+
};
|
|
184
|
+
exports.ListAnimationProvider = ListAnimationProvider;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export { DragStateProvider, useDragState } from './DragStateContext';
|
|
2
|
+
export type { DragStateContextValue } from './DragStateContext';
|
|
3
|
+
export { ListAnimationProvider, useListAnimation, useListAnimationOptional, } from './ListAnimationContext';
|
|
4
|
+
export type { ListAnimationContextValue } from './ListAnimationContext';
|
|
5
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/contexts/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,oBAAoB,CAAC;AACrE,YAAY,EAAE,qBAAqB,EAAE,MAAM,oBAAoB,CAAC;AAEhE,OAAO,EACL,qBAAqB,EACrB,gBAAgB,EAChB,wBAAwB,GACzB,MAAM,wBAAwB,CAAC;AAChC,YAAY,EAAE,yBAAyB,EAAE,MAAM,wBAAwB,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useListAnimationOptional = exports.useListAnimation = exports.ListAnimationProvider = exports.useDragState = exports.DragStateProvider = void 0;
|
|
4
|
+
var DragStateContext_1 = require("./DragStateContext");
|
|
5
|
+
Object.defineProperty(exports, "DragStateProvider", { enumerable: true, get: function () { return DragStateContext_1.DragStateProvider; } });
|
|
6
|
+
Object.defineProperty(exports, "useDragState", { enumerable: true, get: function () { return DragStateContext_1.useDragState; } });
|
|
7
|
+
var ListAnimationContext_1 = require("./ListAnimationContext");
|
|
8
|
+
Object.defineProperty(exports, "ListAnimationProvider", { enumerable: true, get: function () { return ListAnimationContext_1.ListAnimationProvider; } });
|
|
9
|
+
Object.defineProperty(exports, "useListAnimation", { enumerable: true, get: function () { return ListAnimationContext_1.useListAnimation; } });
|
|
10
|
+
Object.defineProperty(exports, "useListAnimationOptional", { enumerable: true, get: function () { return ListAnimationContext_1.useListAnimationOptional; } });
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Animation hooks for list items
|
|
3
|
+
*
|
|
4
|
+
* These hooks provide entry and exit animations for items
|
|
5
|
+
* being added to or removed from the list.
|
|
6
|
+
*/
|
|
7
|
+
export { useListExitAnimation } from './useListExitAnimation';
|
|
8
|
+
export { useListEntryAnimation } from './useListEntryAnimation';
|
|
9
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/index.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,wBAAwB,CAAC;AAC9D,OAAO,EAAE,qBAAqB,EAAE,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Animation hooks for list items
|
|
4
|
+
*
|
|
5
|
+
* These hooks provide entry and exit animations for items
|
|
6
|
+
* being added to or removed from the list.
|
|
7
|
+
*/
|
|
8
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
9
|
+
exports.useListEntryAnimation = exports.useListExitAnimation = void 0;
|
|
10
|
+
var useListExitAnimation_1 = require("./useListExitAnimation");
|
|
11
|
+
Object.defineProperty(exports, "useListExitAnimation", { enumerable: true, get: function () { return useListExitAnimation_1.useListExitAnimation; } });
|
|
12
|
+
var useListEntryAnimation_1 = require("./useListEntryAnimation");
|
|
13
|
+
Object.defineProperty(exports, "useListEntryAnimation", { enumerable: true, get: function () { return useListEntryAnimation_1.useListEntryAnimation; } });
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type { EntryAnimationConfig } from '../../types';
|
|
2
|
+
/**
|
|
3
|
+
* Hook for managing list item entry animations
|
|
4
|
+
*
|
|
5
|
+
* Provides coordinated slide and fade animations for items appearing
|
|
6
|
+
* in a new list (e.g., after being moved via subscription update).
|
|
7
|
+
*
|
|
8
|
+
* PERFORMANCE: Uses shared values with static defaults.
|
|
9
|
+
* Animation only triggers when an entry animation is claimed.
|
|
10
|
+
*
|
|
11
|
+
* IMPORTANT: This hook handles FlashList view recycling by accepting an itemId parameter.
|
|
12
|
+
* When the itemId changes (view recycled for a different item), animation state is reset.
|
|
13
|
+
*
|
|
14
|
+
* @param itemId - Unique identifier for the list item (used to detect view recycling)
|
|
15
|
+
* @param configOverrides - Optional animation configuration overrides
|
|
16
|
+
* @returns Animation styles for entry effect
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```tsx
|
|
20
|
+
* const { entryAnimatedStyle } = useListEntryAnimation(item.id);
|
|
21
|
+
*
|
|
22
|
+
* <Animated.View style={[styles.container, entryAnimatedStyle]}>
|
|
23
|
+
* {content}
|
|
24
|
+
* </Animated.View>
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
export declare const useListEntryAnimation: (itemId: string, configOverrides?: Partial<EntryAnimationConfig>) => {
|
|
28
|
+
entryAnimatedStyle: {
|
|
29
|
+
opacity?: undefined;
|
|
30
|
+
transform?: undefined;
|
|
31
|
+
} | {
|
|
32
|
+
opacity: number;
|
|
33
|
+
transform: {
|
|
34
|
+
translateX: number;
|
|
35
|
+
}[];
|
|
36
|
+
};
|
|
37
|
+
};
|
|
38
|
+
//# sourceMappingURL=useListEntryAnimation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useListEntryAnimation.d.ts","sourceRoot":"","sources":["../../../src/hooks/animations/useListEntryAnimation.ts"],"names":[],"mappings":"AAWA,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,aAAa,CAAC;AAExD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,eAAO,MAAM,qBAAqB,GAChC,QAAQ,MAAM,EACd,kBAAkB,OAAO,CAAC,oBAAoB,CAAC;;;;;;;;;;CAmEhD,CAAC"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.useListEntryAnimation = void 0;
|
|
4
|
+
const react_1 = require("react");
|
|
5
|
+
const react_native_reanimated_1 = require("react-native-reanimated");
|
|
6
|
+
const animations_1 = require("../../constants/animations");
|
|
7
|
+
const ListAnimationContext_1 = require("../../contexts/ListAnimationContext");
|
|
8
|
+
/**
|
|
9
|
+
* Hook for managing list item entry animations
|
|
10
|
+
*
|
|
11
|
+
* Provides coordinated slide and fade animations for items appearing
|
|
12
|
+
* in a new list (e.g., after being moved via subscription update).
|
|
13
|
+
*
|
|
14
|
+
* PERFORMANCE: Uses shared values with static defaults.
|
|
15
|
+
* Animation only triggers when an entry animation is claimed.
|
|
16
|
+
*
|
|
17
|
+
* IMPORTANT: This hook handles FlashList view recycling by accepting an itemId parameter.
|
|
18
|
+
* When the itemId changes (view recycled for a different item), animation state is reset.
|
|
19
|
+
*
|
|
20
|
+
* @param itemId - Unique identifier for the list item (used to detect view recycling)
|
|
21
|
+
* @param configOverrides - Optional animation configuration overrides
|
|
22
|
+
* @returns Animation styles for entry effect
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```tsx
|
|
26
|
+
* const { entryAnimatedStyle } = useListEntryAnimation(item.id);
|
|
27
|
+
*
|
|
28
|
+
* <Animated.View style={[styles.container, entryAnimatedStyle]}>
|
|
29
|
+
* {content}
|
|
30
|
+
* </Animated.View>
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
const useListEntryAnimation = (itemId, configOverrides) => {
|
|
34
|
+
const animationContext = (0, ListAnimationContext_1.useListAnimationOptional)();
|
|
35
|
+
// Merge config with defaults
|
|
36
|
+
const config = {
|
|
37
|
+
fade: { ...animations_1.DEFAULT_ENTRY_ANIMATION.fade, ...configOverrides?.fade },
|
|
38
|
+
slide: { ...animations_1.DEFAULT_ENTRY_ANIMATION.slide, ...configOverrides?.slide },
|
|
39
|
+
};
|
|
40
|
+
// Shared values for entry animation - start at final position (no animation by default)
|
|
41
|
+
const translateX = (0, react_native_reanimated_1.useSharedValue)(0);
|
|
42
|
+
const opacity = (0, react_native_reanimated_1.useSharedValue)(1);
|
|
43
|
+
// Track if we've already checked for entry animation for this item
|
|
44
|
+
const hasCheckedRef = (0, react_1.useRef)(false);
|
|
45
|
+
const lastItemIdRef = (0, react_1.useRef)(itemId);
|
|
46
|
+
// Reset check flag when item ID changes (view recycled)
|
|
47
|
+
if (lastItemIdRef.current !== itemId) {
|
|
48
|
+
lastItemIdRef.current = itemId;
|
|
49
|
+
hasCheckedRef.current = false;
|
|
50
|
+
// Reset animation values for new item
|
|
51
|
+
translateX.value = 0;
|
|
52
|
+
opacity.value = 1;
|
|
53
|
+
}
|
|
54
|
+
// Check for pending entry animation on mount
|
|
55
|
+
(0, react_1.useEffect)(() => {
|
|
56
|
+
if (!animationContext || hasCheckedRef.current)
|
|
57
|
+
return;
|
|
58
|
+
hasCheckedRef.current = true;
|
|
59
|
+
const entry = animationContext.claimEntryAnimation(itemId);
|
|
60
|
+
if (entry) {
|
|
61
|
+
// Start from offset position (slide in from direction)
|
|
62
|
+
translateX.value = entry.direction * config.slide.distance;
|
|
63
|
+
opacity.value = 0;
|
|
64
|
+
// Animate to final position
|
|
65
|
+
translateX.value = (0, react_native_reanimated_1.withTiming)(0, {
|
|
66
|
+
duration: config.slide.duration,
|
|
67
|
+
easing: animations_1.standardEasing,
|
|
68
|
+
});
|
|
69
|
+
opacity.value = (0, react_native_reanimated_1.withTiming)(1, {
|
|
70
|
+
duration: config.fade.duration,
|
|
71
|
+
easing: animations_1.standardEasing,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
}, [itemId, animationContext, translateX, opacity, config.slide, config.fade]);
|
|
75
|
+
// Animated style for entry effect
|
|
76
|
+
const entryAnimatedStyle = (0, react_native_reanimated_1.useAnimatedStyle)(() => {
|
|
77
|
+
// Fast path: no animation active (default state)
|
|
78
|
+
if (translateX.value === 0 && opacity.value === 1) {
|
|
79
|
+
return {};
|
|
80
|
+
}
|
|
81
|
+
return {
|
|
82
|
+
opacity: opacity.value,
|
|
83
|
+
transform: [{ translateX: translateX.value }],
|
|
84
|
+
};
|
|
85
|
+
});
|
|
86
|
+
return {
|
|
87
|
+
entryAnimatedStyle,
|
|
88
|
+
};
|
|
89
|
+
};
|
|
90
|
+
exports.useListEntryAnimation = useListEntryAnimation;
|