react-native-drax 0.11.0-alpha.2 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +1 -1
- package/README.md +390 -227
- package/lib/module/DebugOverlay.js +121 -0
- package/lib/module/DebugOverlay.js.map +1 -0
- package/lib/module/Drax.js +36 -0
- package/lib/module/Drax.js.map +1 -0
- package/lib/module/DraxContext.js +6 -0
- package/lib/module/DraxContext.js.map +1 -0
- package/lib/module/DraxHandle.js +47 -0
- package/lib/module/DraxHandle.js.map +1 -0
- package/lib/module/DraxHandleContext.js +11 -0
- package/lib/module/DraxHandleContext.js.map +1 -0
- package/lib/module/DraxList.js +108 -0
- package/lib/module/DraxList.js.map +1 -0
- package/lib/module/DraxProvider.js +203 -0
- package/lib/module/DraxProvider.js.map +1 -0
- package/lib/module/DraxScrollView.js +167 -0
- package/lib/module/DraxScrollView.js.map +1 -0
- package/lib/module/DraxSubprovider.js +21 -0
- package/lib/module/DraxSubprovider.js.map +1 -0
- package/lib/module/DraxView.js +348 -0
- package/lib/module/DraxView.js.map +1 -0
- package/lib/module/HoverLayer.js +152 -0
- package/lib/module/HoverLayer.js.map +1 -0
- package/lib/module/SortableBoardContainer.js +386 -0
- package/lib/module/SortableBoardContainer.js.map +1 -0
- package/lib/module/SortableBoardContext.js +6 -0
- package/lib/module/SortableBoardContext.js.map +1 -0
- package/lib/module/SortableContainer.js +571 -0
- package/lib/module/SortableContainer.js.map +1 -0
- package/lib/module/SortableItem.js +226 -0
- package/lib/module/SortableItem.js.map +1 -0
- package/lib/module/SortableItemContext.js +38 -0
- package/lib/module/SortableItemContext.js.map +1 -0
- package/lib/module/compat/detectVersion.js +19 -0
- package/lib/module/compat/detectVersion.js.map +1 -0
- package/lib/module/compat/index.js +5 -0
- package/lib/module/compat/index.js.map +1 -0
- package/lib/module/compat/types.js +4 -0
- package/lib/module/compat/types.js.map +1 -0
- package/lib/module/compat/useDraxPanGesture.js +94 -0
- package/lib/module/compat/useDraxPanGesture.js.map +1 -0
- package/lib/module/hooks/index.js +5 -0
- package/lib/module/hooks/index.js.map +1 -0
- package/lib/module/hooks/useCallbackDispatch.js +688 -0
- package/lib/module/hooks/useCallbackDispatch.js.map +1 -0
- package/lib/module/hooks/useDragGesture.js +240 -0
- package/lib/module/hooks/useDragGesture.js.map +1 -0
- package/lib/module/hooks/useDraxContext.js +12 -0
- package/lib/module/hooks/useDraxContext.js.map +1 -0
- package/lib/module/hooks/useDraxId.js +13 -0
- package/lib/module/hooks/useDraxId.js.map +1 -0
- package/lib/module/hooks/useDraxMethods.js +73 -0
- package/lib/module/hooks/useDraxMethods.js.map +1 -0
- package/lib/module/hooks/useDraxScrollHandler.js +97 -0
- package/lib/module/hooks/useDraxScrollHandler.js.map +1 -0
- package/lib/module/hooks/useSortableBoard.js +37 -0
- package/lib/module/hooks/useSortableBoard.js.map +1 -0
- package/lib/module/hooks/useSortableList.js +988 -0
- package/lib/module/hooks/useSortableList.js.map +1 -0
- package/lib/module/hooks/useSpatialIndex.js +283 -0
- package/lib/module/hooks/useSpatialIndex.js.map +1 -0
- package/lib/module/hooks/useViewStyles.js +158 -0
- package/lib/module/hooks/useViewStyles.js.map +1 -0
- package/lib/module/hooks/useWebScrollFreeze.js +52 -0
- package/lib/module/hooks/useWebScrollFreeze.js.map +1 -0
- package/lib/module/index.js +37 -0
- package/lib/module/index.js.map +1 -0
- package/lib/module/math.js +294 -0
- package/lib/module/math.js.map +1 -0
- package/lib/module/package.json +1 -0
- package/lib/module/params.js +88 -0
- package/lib/module/params.js.map +1 -0
- package/lib/module/types.js +215 -0
- package/lib/module/types.js.map +1 -0
- package/lib/typescript/package.json +1 -0
- package/lib/typescript/src/DebugOverlay.d.ts +17 -0
- package/lib/typescript/src/DebugOverlay.d.ts.map +1 -0
- package/lib/typescript/src/Drax.d.ts +28 -0
- package/lib/typescript/src/Drax.d.ts.map +1 -0
- package/lib/typescript/src/DraxContext.d.ts +3 -0
- package/lib/typescript/src/DraxContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandle.d.ts +25 -0
- package/lib/typescript/src/DraxHandle.d.ts.map +1 -0
- package/lib/typescript/src/DraxHandleContext.d.ts +12 -0
- package/lib/typescript/src/DraxHandleContext.d.ts.map +1 -0
- package/lib/typescript/src/DraxList.d.ts +66 -0
- package/lib/typescript/src/DraxList.d.ts.map +1 -0
- package/lib/typescript/src/DraxProvider.d.ts +4 -0
- package/lib/typescript/src/DraxProvider.d.ts.map +1 -0
- package/lib/typescript/src/DraxScrollView.d.ts +7 -0
- package/lib/typescript/src/DraxScrollView.d.ts.map +1 -0
- package/lib/typescript/src/DraxSubprovider.d.ts +4 -0
- package/lib/typescript/src/DraxSubprovider.d.ts.map +1 -0
- package/lib/typescript/src/DraxView.d.ts +4 -0
- package/lib/typescript/src/DraxView.d.ts.map +1 -0
- package/lib/typescript/src/HoverLayer.d.ts +38 -0
- package/lib/typescript/src/HoverLayer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts +11 -0
- package/lib/typescript/src/SortableBoardContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableBoardContext.d.ts +4 -0
- package/lib/typescript/src/SortableBoardContext.d.ts.map +1 -0
- package/lib/typescript/src/SortableContainer.d.ts +13 -0
- package/lib/typescript/src/SortableContainer.d.ts.map +1 -0
- package/lib/typescript/src/SortableItem.d.ts +14 -0
- package/lib/typescript/src/SortableItem.d.ts.map +1 -0
- package/lib/typescript/src/SortableItemContext.d.ts +37 -0
- package/lib/typescript/src/SortableItemContext.d.ts.map +1 -0
- package/lib/typescript/src/compat/detectVersion.d.ts +2 -0
- package/lib/typescript/src/compat/detectVersion.d.ts.map +1 -0
- package/lib/typescript/src/compat/index.d.ts +4 -0
- package/lib/typescript/src/compat/index.d.ts.map +1 -0
- package/lib/typescript/src/compat/types.d.ts +33 -0
- package/lib/typescript/src/compat/types.d.ts.map +1 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts +8 -0
- package/lib/typescript/src/compat/useDraxPanGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/index.d.ts +3 -0
- package/lib/typescript/src/hooks/index.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts +40 -0
- package/lib/typescript/src/hooks/useCallbackDispatch.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts +17 -0
- package/lib/typescript/src/hooks/useDragGesture.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts +2 -0
- package/lib/typescript/src/hooks/useDraxContext.d.ts.map +1 -0
- package/{build → lib/typescript/src}/hooks/useDraxId.d.ts +1 -0
- package/lib/typescript/src/hooks/useDraxId.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts +13 -0
- package/lib/typescript/src/hooks/useDraxMethods.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts +27 -0
- package/lib/typescript/src/hooks/useDraxScrollHandler.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts +10 -0
- package/lib/typescript/src/hooks/useSortableBoard.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts +11 -0
- package/lib/typescript/src/hooks/useSortableList.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts +22 -0
- package/lib/typescript/src/hooks/useSpatialIndex.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts +183 -0
- package/lib/typescript/src/hooks/useViewStyles.d.ts.map +1 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts +14 -0
- package/lib/typescript/src/hooks/useWebScrollFreeze.d.ts.map +1 -0
- package/lib/typescript/src/index.d.ts +25 -0
- package/lib/typescript/src/index.d.ts.map +1 -0
- package/lib/typescript/src/math.d.ts +76 -0
- package/lib/typescript/src/math.d.ts.map +1 -0
- package/{build → lib/typescript/src}/params.d.ts +13 -9
- package/lib/typescript/src/params.d.ts.map +1 -0
- package/lib/typescript/src/types.d.ts +756 -0
- package/lib/typescript/src/types.d.ts.map +1 -0
- package/package.json +164 -34
- package/src/DebugOverlay.tsx +140 -0
- package/src/Drax.ts +33 -0
- package/src/DraxContext.ts +8 -0
- package/src/DraxHandle.tsx +52 -0
- package/src/DraxHandleContext.ts +15 -0
- package/src/DraxList.tsx +181 -0
- package/src/DraxProvider.tsx +224 -0
- package/src/DraxScrollView.tsx +180 -0
- package/src/DraxSubprovider.tsx +22 -0
- package/src/DraxView.tsx +430 -0
- package/src/HoverLayer.tsx +167 -0
- package/src/SortableBoardContainer.tsx +439 -0
- package/src/SortableBoardContext.ts +6 -0
- package/src/SortableContainer.tsx +650 -0
- package/src/SortableItem.tsx +264 -0
- package/src/SortableItemContext.ts +46 -0
- package/src/compat/detectVersion.ts +17 -0
- package/src/compat/index.ts +7 -0
- package/src/compat/types.ts +35 -0
- package/src/compat/useDraxPanGesture.ts +112 -0
- package/src/hooks/index.ts +2 -0
- package/src/hooks/useCallbackDispatch.tsx +830 -0
- package/src/hooks/useDragGesture.ts +273 -0
- package/src/hooks/useDraxContext.ts +11 -0
- package/src/hooks/useDraxId.ts +11 -0
- package/src/hooks/useDraxMethods.ts +71 -0
- package/src/hooks/useDraxScrollHandler.ts +121 -0
- package/src/hooks/useSortableBoard.ts +44 -0
- package/src/hooks/useSortableList.ts +1063 -0
- package/src/hooks/useSpatialIndex.ts +336 -0
- package/src/hooks/useViewStyles.ts +180 -0
- package/src/hooks/useWebScrollFreeze.ts +60 -0
- package/src/index.ts +111 -0
- package/src/math.ts +333 -0
- package/src/params.ts +74 -0
- package/src/types.ts +933 -0
- package/.editorconfig +0 -15
- package/.eslintrc.js +0 -4
- package/.prettierrc +0 -16
- package/CHANGELOG.md +0 -270
- package/CODE-OF-CONDUCT.md +0 -85
- package/CONTRIBUTING.md +0 -15
- package/FUNDING.yml +0 -4
- package/build/AllHoverViews.d.ts +0 -0
- package/build/AllHoverViews.js +0 -30
- package/build/DraxContext.d.ts +0 -2
- package/build/DraxContext.js +0 -6
- package/build/DraxList.d.ts +0 -8
- package/build/DraxList.js +0 -512
- package/build/DraxListItem.d.ts +0 -7
- package/build/DraxListItem.js +0 -121
- package/build/DraxProvider.d.ts +0 -2
- package/build/DraxProvider.js +0 -704
- package/build/DraxScrollView.d.ts +0 -6
- package/build/DraxScrollView.js +0 -136
- package/build/DraxSubprovider.d.ts +0 -3
- package/build/DraxSubprovider.js +0 -18
- package/build/DraxView.d.ts +0 -8
- package/build/DraxView.js +0 -93
- package/build/HoverView.d.ts +0 -8
- package/build/HoverView.js +0 -40
- package/build/PanGestureDetector.d.ts +0 -3
- package/build/PanGestureDetector.js +0 -49
- package/build/hooks/index.d.ts +0 -4
- package/build/hooks/index.js +0 -11
- package/build/hooks/useContent.d.ts +0 -23
- package/build/hooks/useContent.js +0 -212
- package/build/hooks/useDraxContext.d.ts +0 -1
- package/build/hooks/useDraxContext.js +0 -13
- package/build/hooks/useDraxId.js +0 -13
- package/build/hooks/useDraxProtocol.d.ts +0 -5
- package/build/hooks/useDraxProtocol.js +0 -32
- package/build/hooks/useDraxRegistry.d.ts +0 -78
- package/build/hooks/useDraxRegistry.js +0 -714
- package/build/hooks/useDraxScrollHandler.d.ts +0 -25
- package/build/hooks/useDraxScrollHandler.js +0 -89
- package/build/hooks/useDraxState.d.ts +0 -10
- package/build/hooks/useDraxState.js +0 -132
- package/build/hooks/useMeasurements.d.ts +0 -9
- package/build/hooks/useMeasurements.js +0 -119
- package/build/hooks/useStatus.d.ts +0 -11
- package/build/hooks/useStatus.js +0 -96
- package/build/index.d.ts +0 -9
- package/build/index.js +0 -33
- package/build/math.d.ts +0 -22
- package/build/math.js +0 -68
- package/build/params.js +0 -27
- package/build/transform.d.ts +0 -11
- package/build/transform.js +0 -59
- package/build/types.d.ts +0 -807
- package/build/types.js +0 -46
- package/docs/concept.md +0 -79
- package/docs/images/color-drag-drop.gif +0 -0
- package/docs/images/deck-cards.gif +0 -0
- package/docs/images/drag-drop-events.jpg +0 -0
- package/docs/images/knight-moves.gif +0 -0
- package/docs/images/reorderable-list.gif +0 -0
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import { memo, useEffect, useMemo, useRef } from 'react';
|
|
3
|
+
import type { ViewStyle } from 'react-native';
|
|
4
|
+
import { Platform } from 'react-native';
|
|
5
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
6
|
+
import { useDerivedValue, useSharedValue } from 'react-native-reanimated';
|
|
7
|
+
import Reanimated, {
|
|
8
|
+
Easing,
|
|
9
|
+
useAnimatedStyle,
|
|
10
|
+
useReducedMotion,
|
|
11
|
+
withSpring,
|
|
12
|
+
withTiming,
|
|
13
|
+
} from 'react-native-reanimated';
|
|
14
|
+
|
|
15
|
+
import { DraxView } from './DraxView';
|
|
16
|
+
import { useDraxContext } from './hooks/useDraxContext';
|
|
17
|
+
import type { SortableItemContextValue } from './SortableItemContext';
|
|
18
|
+
import { SortableItemContext } from './SortableItemContext';
|
|
19
|
+
import type { ResolvedAnimationConfig } from './params';
|
|
20
|
+
import { resolveAnimationConfig } from './params';
|
|
21
|
+
import type {
|
|
22
|
+
DraxViewMeasurementHandler,
|
|
23
|
+
DraxViewProps,
|
|
24
|
+
Position,
|
|
25
|
+
SortableItemMeasurement,
|
|
26
|
+
SortableListHandle,
|
|
27
|
+
} from './types';
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Isolated hook for SortableItem animated style.
|
|
31
|
+
* Kept separate so the worklet closure only contains SharedValues —
|
|
32
|
+
* never React refs from the component scope.
|
|
33
|
+
*/
|
|
34
|
+
function useSortableItemStyle(
|
|
35
|
+
hoverReadySV: SharedValue<boolean>,
|
|
36
|
+
draggedIdSV: SharedValue<string>,
|
|
37
|
+
viewIdSV: SharedValue<string>,
|
|
38
|
+
shiftsValidSV: SharedValue<boolean>,
|
|
39
|
+
shiftsRef: SharedValue<Record<string, Position>>,
|
|
40
|
+
instantClearSV: SharedValue<boolean>,
|
|
41
|
+
itemKey: string | undefined,
|
|
42
|
+
animConfig: ResolvedAnimationConfig,
|
|
43
|
+
reducedMotion: boolean,
|
|
44
|
+
inactiveItemStyle?: ViewStyle,
|
|
45
|
+
) {
|
|
46
|
+
return useAnimatedStyle(() => {
|
|
47
|
+
// Guard: viewIdSV starts as '' before DraxView registers. Without the
|
|
48
|
+
// non-empty check, a newly mounted item would match a cleared draggedIdSV ('')
|
|
49
|
+
// and be hidden (opacity 0) until hoverReadySV clears — visible in cross-container transfers.
|
|
50
|
+
const isDragged = hoverReadySV.value && viewIdSV.value !== '' && draggedIdSV.value === viewIdSV.value;
|
|
51
|
+
const dragActive = draggedIdSV.value !== '';
|
|
52
|
+
const valid = shiftsValidSV.value;
|
|
53
|
+
const shifts = shiftsRef.value;
|
|
54
|
+
const shift = valid && itemKey ? shifts[itemKey] : undefined;
|
|
55
|
+
const instant = instantClearSV.value;
|
|
56
|
+
// When shifts are invalidated (data committing), snap to 0 instantly — no animation.
|
|
57
|
+
// When reduced motion is on, skip all animations.
|
|
58
|
+
const skipAnimation = instant || !valid || reducedMotion;
|
|
59
|
+
|
|
60
|
+
const toX = shift?.x ?? 0;
|
|
61
|
+
const toY = shift?.y ?? 0;
|
|
62
|
+
|
|
63
|
+
let translateX: number;
|
|
64
|
+
let translateY: number;
|
|
65
|
+
|
|
66
|
+
if (skipAnimation) {
|
|
67
|
+
translateX = toX;
|
|
68
|
+
translateY = toY;
|
|
69
|
+
} else if (animConfig.useSpring) {
|
|
70
|
+
const springConfig = {
|
|
71
|
+
damping: animConfig.springDamping,
|
|
72
|
+
stiffness: animConfig.springStiffness,
|
|
73
|
+
mass: animConfig.springMass,
|
|
74
|
+
};
|
|
75
|
+
translateX = withSpring(toX, springConfig);
|
|
76
|
+
translateY = withSpring(toY, springConfig);
|
|
77
|
+
} else {
|
|
78
|
+
const timingConfig = { duration: animConfig.shiftDuration, easing: Easing.linear };
|
|
79
|
+
translateX = withTiming(toX, timingConfig);
|
|
80
|
+
translateY = withTiming(toY, timingConfig);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Apply inactive style to non-dragged items while a drag is active
|
|
84
|
+
const isInactive = dragActive && !isDragged;
|
|
85
|
+
|
|
86
|
+
return {
|
|
87
|
+
opacity: isDragged ? 0 : 1,
|
|
88
|
+
transform: [
|
|
89
|
+
{ translateX },
|
|
90
|
+
{ translateY },
|
|
91
|
+
] as const,
|
|
92
|
+
...(isInactive && inactiveItemStyle ? inactiveItemStyle : {}),
|
|
93
|
+
};
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export interface SortableItemProps extends DraxViewProps {
|
|
98
|
+
sortable: SortableListHandle<any>;
|
|
99
|
+
index: number;
|
|
100
|
+
/** When true, this item cannot be dragged and stays in its position.
|
|
101
|
+
* Other items will skip over it during reorder. */
|
|
102
|
+
fixed?: boolean;
|
|
103
|
+
children: ReactNode;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const SortableItemInner = ({
|
|
107
|
+
sortable,
|
|
108
|
+
index,
|
|
109
|
+
fixed = false,
|
|
110
|
+
children,
|
|
111
|
+
...draxViewProps
|
|
112
|
+
}: SortableItemProps) => {
|
|
113
|
+
const {
|
|
114
|
+
horizontal,
|
|
115
|
+
lockToMainAxis,
|
|
116
|
+
longPressDelay,
|
|
117
|
+
animationConfig,
|
|
118
|
+
inactiveItemStyle,
|
|
119
|
+
itemEntering,
|
|
120
|
+
itemExiting,
|
|
121
|
+
shiftsRef,
|
|
122
|
+
instantClearSV,
|
|
123
|
+
shiftsValidSV,
|
|
124
|
+
itemMeasurements,
|
|
125
|
+
keyExtractor,
|
|
126
|
+
rawData,
|
|
127
|
+
originalIndexes,
|
|
128
|
+
scrollPosition,
|
|
129
|
+
onItemSnapEnd,
|
|
130
|
+
fixedKeys,
|
|
131
|
+
} = sortable._internal;
|
|
132
|
+
|
|
133
|
+
// Get hoverReadySV and draggedIdSV from DraxContext (provider-level SharedValues)
|
|
134
|
+
const { hoverReadySV, draggedIdSV } = useDraxContext();
|
|
135
|
+
|
|
136
|
+
const originalIndex = originalIndexes[index] ?? index;
|
|
137
|
+
const item = rawData[originalIndex];
|
|
138
|
+
const itemKey = item !== undefined ? keyExtractor(item, index) : undefined;
|
|
139
|
+
|
|
140
|
+
// Register/unregister fixed items so reorder logic can skip them.
|
|
141
|
+
useEffect(() => {
|
|
142
|
+
if (!itemKey) return;
|
|
143
|
+
if (fixed) {
|
|
144
|
+
fixedKeys.current.add(itemKey);
|
|
145
|
+
} else {
|
|
146
|
+
fixedKeys.current.delete(itemKey);
|
|
147
|
+
}
|
|
148
|
+
return () => { fixedKeys.current.delete(itemKey); };
|
|
149
|
+
}, [fixed, itemKey, fixedKeys]);
|
|
150
|
+
|
|
151
|
+
// Store this DraxView's registered ID in a SharedValue so useAnimatedStyle
|
|
152
|
+
// can compare it with draggedIdSV on the UI thread.
|
|
153
|
+
const viewIdSV = useSharedValue('');
|
|
154
|
+
|
|
155
|
+
const measureFnRef = useRef<
|
|
156
|
+
((handler?: DraxViewMeasurementHandler) => void) | null
|
|
157
|
+
>(null);
|
|
158
|
+
|
|
159
|
+
// Resolve animation config and check reduced motion preference
|
|
160
|
+
const resolvedAnimConfig = useMemo(() => resolveAnimationConfig(animationConfig), [animationConfig]);
|
|
161
|
+
const reducedMotion = useReducedMotion();
|
|
162
|
+
|
|
163
|
+
// Delegated to isolated hook so worklet closure has no refs from this scope.
|
|
164
|
+
const itemStyle = useSortableItemStyle(
|
|
165
|
+
hoverReadySV, draggedIdSV, viewIdSV,
|
|
166
|
+
shiftsValidSV, shiftsRef, instantClearSV, itemKey,
|
|
167
|
+
resolvedAnimConfig, reducedMotion, inactiveItemStyle,
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// Derive isActive SharedValue for useItemContext consumers
|
|
171
|
+
const isActive = useDerivedValue(() => {
|
|
172
|
+
return viewIdSV.value !== '' && draggedIdSV.value === viewIdSV.value;
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
// Build context value for useItemContext
|
|
176
|
+
const itemContextValue = useMemo<SortableItemContextValue | null>(() => {
|
|
177
|
+
if (!itemKey) return null;
|
|
178
|
+
return {
|
|
179
|
+
itemKey,
|
|
180
|
+
index,
|
|
181
|
+
isActive,
|
|
182
|
+
activeItemId: draggedIdSV,
|
|
183
|
+
};
|
|
184
|
+
}, [itemKey, index, isActive, draggedIdSV]);
|
|
185
|
+
|
|
186
|
+
// Auto-generate accessibility props (can be overridden via draxViewProps)
|
|
187
|
+
const totalItems = rawData.length;
|
|
188
|
+
const defaultA11yLabel = `Item ${index + 1} of ${totalItems}`;
|
|
189
|
+
const defaultA11yHint = 'Long press to drag and reorder';
|
|
190
|
+
|
|
191
|
+
const mergedPayload = useMemo(() => ({
|
|
192
|
+
...(typeof draxViewProps.payload === 'object' && draxViewProps.payload !== null
|
|
193
|
+
? draxViewProps.payload : {}),
|
|
194
|
+
index, originalIndex, item,
|
|
195
|
+
}), [draxViewProps.payload, index, originalIndex, item]);
|
|
196
|
+
|
|
197
|
+
return (
|
|
198
|
+
<SortableItemContext value={itemContextValue}>
|
|
199
|
+
<Reanimated.View style={itemStyle} entering={itemEntering} exiting={itemExiting}>
|
|
200
|
+
<DraxView
|
|
201
|
+
longPressDelay={longPressDelay}
|
|
202
|
+
lockDragXPosition={lockToMainAxis && !horizontal}
|
|
203
|
+
lockDragYPosition={lockToMainAxis && horizontal}
|
|
204
|
+
scrollHorizontal={horizontal || undefined}
|
|
205
|
+
draggable={!fixed}
|
|
206
|
+
accessibilityLabel={defaultA11yLabel}
|
|
207
|
+
accessibilityHint={defaultA11yHint}
|
|
208
|
+
accessibilityRole="adjustable"
|
|
209
|
+
{...draxViewProps}
|
|
210
|
+
payload={mergedPayload}
|
|
211
|
+
registration={(reg) => {
|
|
212
|
+
measureFnRef.current = reg?.measure ?? null;
|
|
213
|
+
// Capture the DraxView's registered ID so useAnimatedStyle can match
|
|
214
|
+
// it against draggedIdSV for visibility control.
|
|
215
|
+
viewIdSV.value = reg?.id ?? '';
|
|
216
|
+
}}
|
|
217
|
+
onDragEnd={(event) => {
|
|
218
|
+
draxViewProps.onDragEnd?.(event);
|
|
219
|
+
}}
|
|
220
|
+
onDragDrop={(event) => {
|
|
221
|
+
draxViewProps.onDragDrop?.(event);
|
|
222
|
+
}}
|
|
223
|
+
onSnapEnd={(snapData) => {
|
|
224
|
+
onItemSnapEnd?.();
|
|
225
|
+
draxViewProps.onSnapEnd?.(snapData);
|
|
226
|
+
}}
|
|
227
|
+
onMeasure={(measurements) => {
|
|
228
|
+
draxViewProps.onMeasure?.(measurements);
|
|
229
|
+
if (itemKey && measurements) {
|
|
230
|
+
// Subtract Drax shift transforms when they're included in the
|
|
231
|
+
// measurement. On web: measureLayout always includes transforms.
|
|
232
|
+
// On native: measureLayout ignores transforms, UNLESS DraxView
|
|
233
|
+
// auto-detected transform-based positioning (LegendList) and
|
|
234
|
+
// switched to measure() — flagged via _transformDetected.
|
|
235
|
+
let adjX = measurements.x;
|
|
236
|
+
let adjY = measurements.y;
|
|
237
|
+
if (Platform.OS === 'web' || measurements._transformDetected) {
|
|
238
|
+
const currentShift = shiftsRef.value[itemKey];
|
|
239
|
+
if (currentShift) {
|
|
240
|
+
adjX -= currentShift.x;
|
|
241
|
+
adjY -= currentShift.y;
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
const entry: SortableItemMeasurement = {
|
|
245
|
+
x: adjX,
|
|
246
|
+
y: adjY,
|
|
247
|
+
width: measurements.width,
|
|
248
|
+
height: measurements.height,
|
|
249
|
+
key: itemKey,
|
|
250
|
+
index,
|
|
251
|
+
scrollAtMeasure: { x: scrollPosition.value.x, y: scrollPosition.value.y },
|
|
252
|
+
};
|
|
253
|
+
itemMeasurements.current.set(itemKey, entry);
|
|
254
|
+
}
|
|
255
|
+
}}
|
|
256
|
+
>
|
|
257
|
+
{children}
|
|
258
|
+
</DraxView>
|
|
259
|
+
</Reanimated.View>
|
|
260
|
+
</SortableItemContext>
|
|
261
|
+
);
|
|
262
|
+
};
|
|
263
|
+
|
|
264
|
+
export const SortableItem = memo(SortableItemInner) as typeof SortableItemInner;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { createContext, use } from 'react';
|
|
2
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
3
|
+
|
|
4
|
+
/** Per-item state exposed by useItemContext */
|
|
5
|
+
export interface SortableItemContextValue {
|
|
6
|
+
/** The item's key (from keyExtractor) */
|
|
7
|
+
itemKey: string;
|
|
8
|
+
/** Display index in the current sort order */
|
|
9
|
+
index: number;
|
|
10
|
+
/** SharedValue: true when this item is being dragged */
|
|
11
|
+
isActive: SharedValue<boolean>;
|
|
12
|
+
/** SharedValue: ID of the currently dragged item (empty string if none) */
|
|
13
|
+
activeItemId: SharedValue<string>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export const SortableItemContext = createContext<SortableItemContextValue | null>(null);
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Access per-item state from within a SortableItem's children.
|
|
20
|
+
*
|
|
21
|
+
* Returns SharedValues for reactive animations:
|
|
22
|
+
* - `isActive` — true when THIS item is being dragged
|
|
23
|
+
* - `activeItemId` — ID of the currently dragged item
|
|
24
|
+
* - `itemKey` — this item's key
|
|
25
|
+
* - `index` — this item's display index
|
|
26
|
+
*
|
|
27
|
+
* Must be called within a `<SortableItem>`.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```tsx
|
|
31
|
+
* function MyItem() {
|
|
32
|
+
* const { isActive } = useItemContext();
|
|
33
|
+
* const style = useAnimatedStyle(() => ({
|
|
34
|
+
* transform: [{ scale: isActive.value ? 1.1 : 1 }],
|
|
35
|
+
* }));
|
|
36
|
+
* return <Reanimated.View style={style}>...</Reanimated.View>;
|
|
37
|
+
* }
|
|
38
|
+
* ```
|
|
39
|
+
*/
|
|
40
|
+
export const useItemContext = (): SortableItemContextValue => {
|
|
41
|
+
const ctx = use(SortableItemContext);
|
|
42
|
+
if (!ctx) {
|
|
43
|
+
throw new Error('useItemContext must be used within a SortableItem');
|
|
44
|
+
}
|
|
45
|
+
return ctx;
|
|
46
|
+
};
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Runtime detection of react-native-gesture-handler version.
|
|
3
|
+
* Returns true if v3 hook API (usePanGesture) is available.
|
|
4
|
+
* Result is cached after first call.
|
|
5
|
+
*/
|
|
6
|
+
let _isV3: boolean | null = null;
|
|
7
|
+
|
|
8
|
+
export function isGestureHandlerV3(): boolean {
|
|
9
|
+
if (_isV3 !== null) return _isV3;
|
|
10
|
+
try {
|
|
11
|
+
const rngh = require('react-native-gesture-handler');
|
|
12
|
+
_isV3 = typeof rngh.usePanGesture === 'function';
|
|
13
|
+
} catch {
|
|
14
|
+
_isV3 = false;
|
|
15
|
+
}
|
|
16
|
+
return _isV3;
|
|
17
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { SharedValue } from 'react-native-reanimated';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Re-export PanGesture from RNGH — resolves to the correct type per installed version:
|
|
5
|
+
* - v3: SingleGesture<PanHandlerData, PanGestureInternalProperties>
|
|
6
|
+
* - v2: PanGesture class extending ContinousBaseGesture
|
|
7
|
+
*/
|
|
8
|
+
export type { PanGesture as DraxPanGesture } from 'react-native-gesture-handler';
|
|
9
|
+
|
|
10
|
+
/** Minimal event shape — only the fields Drax uses (present in both v2 and v3). */
|
|
11
|
+
export interface DraxPanEvent {
|
|
12
|
+
x: number;
|
|
13
|
+
y: number;
|
|
14
|
+
absoluteX: number;
|
|
15
|
+
absoluteY: number;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Config for the version-agnostic pan gesture hook. */
|
|
19
|
+
export interface DraxPanGestureConfig {
|
|
20
|
+
enabledSV: SharedValue<boolean>;
|
|
21
|
+
longPressDelaySV: SharedValue<number>;
|
|
22
|
+
maxPointers: number;
|
|
23
|
+
shouldCancelWhenOutside: boolean;
|
|
24
|
+
/** Web: CSS touch-action for the gesture view. Set to 'pan-y' or 'pan-x'
|
|
25
|
+
* to allow native scrolling before the long-press activates. */
|
|
26
|
+
touchAction?: string;
|
|
27
|
+
/** Fail the gesture if finger moves more than this distance during activation.
|
|
28
|
+
* Prevents accidental drags when the user is trying to scroll. */
|
|
29
|
+
failOffsetX?: number | [number, number];
|
|
30
|
+
failOffsetY?: number | [number, number];
|
|
31
|
+
onActivate: (event: DraxPanEvent) => void;
|
|
32
|
+
onUpdate: (event: DraxPanEvent) => void;
|
|
33
|
+
onDeactivate: (event: DraxPanEvent) => void;
|
|
34
|
+
onFinalize: (event: DraxPanEvent, didSucceed: boolean) => void;
|
|
35
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
import { useMemo, useState } from 'react';
|
|
2
|
+
import { useAnimatedReaction } from 'react-native-reanimated';
|
|
3
|
+
import { runOnJS } from 'react-native-worklets';
|
|
4
|
+
|
|
5
|
+
import { isGestureHandlerV3 } from './detectVersion';
|
|
6
|
+
import type { DraxPanEvent, DraxPanGesture, DraxPanGestureConfig } from './types';
|
|
7
|
+
|
|
8
|
+
// Module-scope require — cached by the bundler, always the same reference.
|
|
9
|
+
// Hoisted out of the hook body so the compiler sees a stable function identity.
|
|
10
|
+
const rngh = require('react-native-gesture-handler');
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* v3 path — passes through to RNGH's usePanGesture with SharedValue config.
|
|
14
|
+
* Zero overhead, UI-thread-driven reconfiguration.
|
|
15
|
+
*/
|
|
16
|
+
function useDraxPanGestureV3(config: DraxPanGestureConfig): DraxPanGesture {
|
|
17
|
+
// Build config without undefined failOffset keys — RNGH v3 warns on
|
|
18
|
+
// undefined keys that survive transformOffsetProp without deletion.
|
|
19
|
+
// Spread is not allowed here (Reanimated workletizes hook arguments).
|
|
20
|
+
const panConfig: Record<string, unknown> = {
|
|
21
|
+
enabled: config.enabledSV,
|
|
22
|
+
activateAfterLongPress: config.longPressDelaySV,
|
|
23
|
+
maxPointers: config.maxPointers,
|
|
24
|
+
shouldCancelWhenOutside: config.shouldCancelWhenOutside,
|
|
25
|
+
touchAction: config.touchAction,
|
|
26
|
+
onActivate: config.onActivate,
|
|
27
|
+
onUpdate: config.onUpdate,
|
|
28
|
+
onDeactivate: config.onDeactivate,
|
|
29
|
+
onFinalize: config.onFinalize,
|
|
30
|
+
};
|
|
31
|
+
if (config.failOffsetX !== undefined) panConfig.failOffsetX = config.failOffsetX;
|
|
32
|
+
if (config.failOffsetY !== undefined) panConfig.failOffsetY = config.failOffsetY;
|
|
33
|
+
return rngh.usePanGesture(panConfig);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* v2 path — wraps Gesture.Pan() builder pattern.
|
|
38
|
+
* SharedValue config is watched via useAnimatedReaction and triggers gesture
|
|
39
|
+
* recreation on change. This is slower than v3 but functionally correct.
|
|
40
|
+
* In practice, enabled/longPressDelay change very rarely (prop updates only).
|
|
41
|
+
*/
|
|
42
|
+
function useDraxPanGestureV2(config: DraxPanGestureConfig): DraxPanGesture {
|
|
43
|
+
const Gesture = rngh.Gesture;
|
|
44
|
+
|
|
45
|
+
// Mirror SharedValues to React state for v2 gesture config
|
|
46
|
+
const [enabled, setEnabled] = useState(config.enabledSV.value);
|
|
47
|
+
const [longPressDelay, setLongPressDelay] = useState(
|
|
48
|
+
config.longPressDelaySV.value
|
|
49
|
+
);
|
|
50
|
+
|
|
51
|
+
// Watch SharedValue changes and sync to JS state
|
|
52
|
+
useAnimatedReaction(
|
|
53
|
+
() => config.enabledSV.value,
|
|
54
|
+
(current, prev) => {
|
|
55
|
+
if (prev !== null && current !== prev) {
|
|
56
|
+
runOnJS(setEnabled)(current);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
);
|
|
60
|
+
|
|
61
|
+
useAnimatedReaction(
|
|
62
|
+
() => config.longPressDelaySV.value,
|
|
63
|
+
(current, prev) => {
|
|
64
|
+
if (prev !== null && current !== prev) {
|
|
65
|
+
runOnJS(setLongPressDelay)(current);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
);
|
|
69
|
+
|
|
70
|
+
// Typed as DraxPanGesture directly — v2 builder returns a legacy type that's structurally
|
|
71
|
+
// different from v3's PanGesture at compile time, but GestureDetector accepts both at runtime.
|
|
72
|
+
// Since Gesture comes from require() (any), the assignment is valid without a cast.
|
|
73
|
+
const gesture: DraxPanGesture = useMemo(() => {
|
|
74
|
+
let g = Gesture.Pan()
|
|
75
|
+
.enabled(enabled)
|
|
76
|
+
.activateAfterLongPress(longPressDelay)
|
|
77
|
+
.maxPointers(config.maxPointers)
|
|
78
|
+
.shouldCancelWhenOutside(config.shouldCancelWhenOutside);
|
|
79
|
+
if (config.failOffsetX !== undefined) g = g.failOffsetX(config.failOffsetX);
|
|
80
|
+
if (config.failOffsetY !== undefined) g = g.failOffsetY(config.failOffsetY);
|
|
81
|
+
return g
|
|
82
|
+
.onStart((event: DraxPanEvent) => {
|
|
83
|
+
'worklet';
|
|
84
|
+
config.onActivate(event);
|
|
85
|
+
})
|
|
86
|
+
.onUpdate((event: DraxPanEvent) => {
|
|
87
|
+
'worklet';
|
|
88
|
+
config.onUpdate(event);
|
|
89
|
+
})
|
|
90
|
+
.onEnd((event: DraxPanEvent) => {
|
|
91
|
+
'worklet';
|
|
92
|
+
config.onDeactivate(event);
|
|
93
|
+
})
|
|
94
|
+
.onFinalize((event: DraxPanEvent, success: boolean) => {
|
|
95
|
+
'worklet';
|
|
96
|
+
config.onFinalize(event, success);
|
|
97
|
+
});
|
|
98
|
+
}, [Gesture, enabled, longPressDelay, config]);
|
|
99
|
+
|
|
100
|
+
return gesture;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
/**
|
|
104
|
+
* Version-agnostic pan gesture hook.
|
|
105
|
+
* Delegates to v3's usePanGesture (optimal) or v2's Gesture.Pan() builder (compat).
|
|
106
|
+
* Selected at module load time to avoid conditional hook calls.
|
|
107
|
+
*/
|
|
108
|
+
export const useDraxPanGesture: (
|
|
109
|
+
config: DraxPanGestureConfig
|
|
110
|
+
) => DraxPanGesture = isGestureHandlerV3()
|
|
111
|
+
? useDraxPanGestureV3
|
|
112
|
+
: useDraxPanGestureV2;
|