react-native-drax 0.11.0-alpha.1 → 1.0.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 +385 -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 +561 -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 +681 -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 +824 -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 +222 -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 +213 -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 +52 -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 +743 -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 +642 -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 +823 -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 +868 -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 +110 -0
- package/src/math.ts +251 -0
- package/src/params.ts +74 -0
- package/src/types.ts +919 -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 -8
- package/build/hooks/useMeasurements.js +0 -118
- 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
package/src/DraxList.tsx
ADDED
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import type { ComponentType, ReactNode, Ref } from 'react';
|
|
2
|
+
import { useRef } from 'react';
|
|
3
|
+
import type {
|
|
4
|
+
FlatList,
|
|
5
|
+
ListRenderItemInfo,
|
|
6
|
+
StyleProp,
|
|
7
|
+
ViewStyle,
|
|
8
|
+
} from 'react-native';
|
|
9
|
+
|
|
10
|
+
import { useSortableList } from './hooks/useSortableList';
|
|
11
|
+
import { SortableContainer } from './SortableContainer';
|
|
12
|
+
import { SortableItem } from './SortableItem';
|
|
13
|
+
import type { EntryOrExitLayoutType } from 'react-native-reanimated';
|
|
14
|
+
import type {
|
|
15
|
+
DraxViewProps,
|
|
16
|
+
SortableAnimationConfig,
|
|
17
|
+
SortableDragEndEvent,
|
|
18
|
+
SortableDragPositionChangeEvent,
|
|
19
|
+
SortableDragStartEvent,
|
|
20
|
+
SortableReorderEvent,
|
|
21
|
+
SortableReorderStrategy,
|
|
22
|
+
} from './types';
|
|
23
|
+
|
|
24
|
+
export interface DraxListProps<T> {
|
|
25
|
+
/**
|
|
26
|
+
* List component to render. Any component that accepts `data`, `renderItem`,
|
|
27
|
+
* and `keyExtractor` props (FlatList, FlashList, LegendList, etc.).
|
|
28
|
+
* @default FlatList
|
|
29
|
+
*/
|
|
30
|
+
component?: ComponentType<any>;
|
|
31
|
+
ref?: Ref<any>;
|
|
32
|
+
/** Optional explicit DraxView id for the container */
|
|
33
|
+
id?: string;
|
|
34
|
+
data: T[];
|
|
35
|
+
keyExtractor: (item: T, index: number) => string;
|
|
36
|
+
onReorder: (event: SortableReorderEvent<T>) => void;
|
|
37
|
+
renderItem: (info: ListRenderItemInfo<T>) => ReactNode;
|
|
38
|
+
/** Reorder strategy. @default 'insert' */
|
|
39
|
+
reorderStrategy?: SortableReorderStrategy;
|
|
40
|
+
/** Long press delay before drag starts in ms. @default 250 */
|
|
41
|
+
longPressDelay?: number;
|
|
42
|
+
/** Lock item drags to the list's main axis. @default false */
|
|
43
|
+
lockToMainAxis?: boolean;
|
|
44
|
+
/** Animation config for item shift animations. @default 'default' */
|
|
45
|
+
animationConfig?: SortableAnimationConfig;
|
|
46
|
+
/** Style applied to all non-dragged items while a drag is active.
|
|
47
|
+
* Use for dimming/scaling inactive items (e.g., `{ opacity: 0.5 }`). */
|
|
48
|
+
inactiveItemStyle?: ViewStyle;
|
|
49
|
+
/** Reanimated layout animation for items entering the list (e.g., `FadeIn`). */
|
|
50
|
+
itemEntering?: EntryOrExitLayoutType;
|
|
51
|
+
/** Reanimated layout animation for items exiting the list (e.g., `FadeOut`). */
|
|
52
|
+
itemExiting?: EntryOrExitLayoutType;
|
|
53
|
+
/** DraxView props to apply to each item */
|
|
54
|
+
itemDraxViewProps?: Partial<DraxViewProps>;
|
|
55
|
+
/** DraxView props for the container */
|
|
56
|
+
containerDraxViewProps?: Partial<DraxViewProps>;
|
|
57
|
+
/** Style for the container */
|
|
58
|
+
containerStyle?: StyleProp<ViewStyle>;
|
|
59
|
+
/** Style for the list component */
|
|
60
|
+
style?: StyleProp<ViewStyle>;
|
|
61
|
+
/** Horizontal list layout. @default false */
|
|
62
|
+
horizontal?: boolean;
|
|
63
|
+
/** Number of columns (grid layout). @default 1 */
|
|
64
|
+
numColumns?: number;
|
|
65
|
+
/** Callback when drag starts */
|
|
66
|
+
onDragStart?: (event: SortableDragStartEvent<T>) => void;
|
|
67
|
+
/** Callback when drag position changes */
|
|
68
|
+
onDragPositionChange?: (event: SortableDragPositionChangeEvent<T>) => void;
|
|
69
|
+
/** Callback when drag ends */
|
|
70
|
+
onDragEnd?: (event: SortableDragEndEvent<T>) => void;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* List-agnostic sortable list component.
|
|
75
|
+
*
|
|
76
|
+
* Wraps any list component (FlatList, FlashList, LegendList, etc.) with
|
|
77
|
+
* drag-and-drop reordering powered by `useSortableList` + `SortableContainer` + `SortableItem`.
|
|
78
|
+
*
|
|
79
|
+
* For full control, use the composable API directly:
|
|
80
|
+
* `useSortableList` + `SortableContainer` + `SortableItem`.
|
|
81
|
+
*
|
|
82
|
+
* Any extra props beyond the ones defined in `DraxListProps` are forwarded
|
|
83
|
+
* to the underlying list component (e.g. `estimatedItemSize` for FlashList).
|
|
84
|
+
*/
|
|
85
|
+
export const DraxList = <T,>({
|
|
86
|
+
component: ListComponent,
|
|
87
|
+
ref,
|
|
88
|
+
id,
|
|
89
|
+
data,
|
|
90
|
+
keyExtractor,
|
|
91
|
+
onReorder,
|
|
92
|
+
renderItem,
|
|
93
|
+
reorderStrategy,
|
|
94
|
+
longPressDelay,
|
|
95
|
+
lockToMainAxis,
|
|
96
|
+
animationConfig,
|
|
97
|
+
inactiveItemStyle,
|
|
98
|
+
itemEntering,
|
|
99
|
+
itemExiting,
|
|
100
|
+
itemDraxViewProps,
|
|
101
|
+
containerDraxViewProps,
|
|
102
|
+
containerStyle,
|
|
103
|
+
onDragStart,
|
|
104
|
+
onDragPositionChange,
|
|
105
|
+
onDragEnd,
|
|
106
|
+
style,
|
|
107
|
+
horizontal,
|
|
108
|
+
numColumns,
|
|
109
|
+
...listProps
|
|
110
|
+
}: DraxListProps<T> & Record<string, any>): ReactNode => {
|
|
111
|
+
const isHorizontal = horizontal ?? false;
|
|
112
|
+
const cols = numColumns ?? 1;
|
|
113
|
+
|
|
114
|
+
const sortable = useSortableList({
|
|
115
|
+
id,
|
|
116
|
+
data,
|
|
117
|
+
keyExtractor,
|
|
118
|
+
onReorder,
|
|
119
|
+
horizontal: isHorizontal,
|
|
120
|
+
numColumns: cols,
|
|
121
|
+
reorderStrategy,
|
|
122
|
+
longPressDelay,
|
|
123
|
+
lockToMainAxis,
|
|
124
|
+
animationConfig,
|
|
125
|
+
inactiveItemStyle,
|
|
126
|
+
itemEntering,
|
|
127
|
+
itemExiting,
|
|
128
|
+
onDragStart,
|
|
129
|
+
onDragPositionChange,
|
|
130
|
+
onDragEnd,
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
const listRef = useRef<FlatList<T>>(null);
|
|
134
|
+
|
|
135
|
+
// Forward external ref
|
|
136
|
+
const setRef = (instance: FlatList<T> | null) => {
|
|
137
|
+
(listRef as any).current = instance;
|
|
138
|
+
if (ref) {
|
|
139
|
+
if (typeof ref === 'function') {
|
|
140
|
+
ref(instance);
|
|
141
|
+
} else {
|
|
142
|
+
(ref as any).current = instance;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|
|
146
|
+
|
|
147
|
+
// Lazy-load FlatList only when no component is provided.
|
|
148
|
+
// This avoids importing react-native's FlatList at module scope
|
|
149
|
+
// when the consumer uses a different list component.
|
|
150
|
+
const ResolvedList = ListComponent ?? require('react-native').FlatList;
|
|
151
|
+
|
|
152
|
+
return (
|
|
153
|
+
<SortableContainer
|
|
154
|
+
sortable={sortable}
|
|
155
|
+
scrollRef={listRef}
|
|
156
|
+
style={containerStyle}
|
|
157
|
+
draxViewProps={containerDraxViewProps}
|
|
158
|
+
>
|
|
159
|
+
<ResolvedList
|
|
160
|
+
{...listProps}
|
|
161
|
+
style={style}
|
|
162
|
+
ref={setRef}
|
|
163
|
+
horizontal={isHorizontal}
|
|
164
|
+
keyExtractor={sortable.stableKeyExtractor}
|
|
165
|
+
numColumns={cols}
|
|
166
|
+
data={sortable.data}
|
|
167
|
+
onScroll={sortable.onScroll}
|
|
168
|
+
onContentSizeChange={sortable.onContentSizeChange}
|
|
169
|
+
renderItem={(info: ListRenderItemInfo<T>) => (
|
|
170
|
+
<SortableItem
|
|
171
|
+
sortable={sortable}
|
|
172
|
+
index={info.index}
|
|
173
|
+
{...itemDraxViewProps}
|
|
174
|
+
>
|
|
175
|
+
{renderItem(info)}
|
|
176
|
+
</SortableItem>
|
|
177
|
+
)}
|
|
178
|
+
/>
|
|
179
|
+
</SortableContainer>
|
|
180
|
+
);
|
|
181
|
+
};
|
|
@@ -0,0 +1,224 @@
|
|
|
1
|
+
import type { ReactNode, RefObject } from 'react';
|
|
2
|
+
import { useCallback, useMemo, useRef, useState } from 'react';
|
|
3
|
+
import type { HostInstance } from 'react-native';
|
|
4
|
+
import { StyleSheet, View } from 'react-native';
|
|
5
|
+
import { useSharedValue } from 'react-native-reanimated';
|
|
6
|
+
|
|
7
|
+
import { DebugOverlay } from './DebugOverlay';
|
|
8
|
+
import { DraxContext } from './DraxContext';
|
|
9
|
+
import type { FlattenedHoverStyles } from './HoverLayer';
|
|
10
|
+
import { HoverLayer } from './HoverLayer';
|
|
11
|
+
import { useCallbackDispatch } from './hooks/useCallbackDispatch';
|
|
12
|
+
import { useSpatialIndex } from './hooks/useSpatialIndex';
|
|
13
|
+
import type {
|
|
14
|
+
DragPhase,
|
|
15
|
+
DraxContextValue,
|
|
16
|
+
DraxProviderProps,
|
|
17
|
+
Position,
|
|
18
|
+
} from './types';
|
|
19
|
+
|
|
20
|
+
export const DraxProvider = ({
|
|
21
|
+
style = styles.provider,
|
|
22
|
+
debug = false,
|
|
23
|
+
onDragStart: onProviderDragStart,
|
|
24
|
+
onDrag: onProviderDrag,
|
|
25
|
+
onDragEnd: onProviderDragEnd,
|
|
26
|
+
children,
|
|
27
|
+
}: DraxProviderProps): ReactNode => {
|
|
28
|
+
// ── Split SharedValues (by update frequency) ───────────────────────
|
|
29
|
+
// Changes ~2x per drag. Read by all DraxView useAnimatedStyle.
|
|
30
|
+
const draggedIdSV = useSharedValue<string>('');
|
|
31
|
+
// Changes ~3-5x per drag. Read by all DraxView useAnimatedStyle.
|
|
32
|
+
const receiverIdSV = useSharedValue<string>('');
|
|
33
|
+
// Changes ~3x per drag. Read by all DraxView useAnimatedStyle.
|
|
34
|
+
const dragPhaseSV = useSharedValue<DragPhase>('idle');
|
|
35
|
+
// Changes every frame during drag. Read ONLY by HoverLayer (1 component).
|
|
36
|
+
const hoverPositionSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
37
|
+
// Changes every frame during drag. Used by gesture worklet for hit-testing.
|
|
38
|
+
// NOT read by any useAnimatedStyle.
|
|
39
|
+
const dragAbsolutePositionSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
40
|
+
// ID of the most recently rejected receiver. Read by gesture worklet to skip
|
|
41
|
+
// re-detecting the same rejected receiver. Cleared when drag leaves its bounds.
|
|
42
|
+
const rejectedReceiverIdSV = useSharedValue<string>('');
|
|
43
|
+
// Set once per drag start.
|
|
44
|
+
const grabOffsetSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
45
|
+
const startPositionSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
46
|
+
// Screen offset of the root view (measured on layout).
|
|
47
|
+
const rootOffsetSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
48
|
+
// True after hover content is committed to DOM. False after snap completes.
|
|
49
|
+
const hoverReadySV = useSharedValue(false);
|
|
50
|
+
// Set by SortableContainer.finalizeDrag to defer hover clearing to useLayoutEffect.
|
|
51
|
+
const hoverClearDeferredRef = useRef(false);
|
|
52
|
+
// Animated dimensions for hover content during cross-container transfer.
|
|
53
|
+
// x = width, y = height. {0,0} = no constraint (natural size).
|
|
54
|
+
const hoverDimsSV = useSharedValue<Position>({ x: 0, y: 0 });
|
|
55
|
+
|
|
56
|
+
// ── Dropped items tracking ─────────────────────────────────────────
|
|
57
|
+
const droppedItemsRef = useRef<Map<string, Set<string>>>(new Map());
|
|
58
|
+
|
|
59
|
+
// ── Spatial index + registry ───────────────────────────────────────
|
|
60
|
+
const {
|
|
61
|
+
spatialIndexSV,
|
|
62
|
+
scrollOffsetsSV,
|
|
63
|
+
registerView,
|
|
64
|
+
unregisterView,
|
|
65
|
+
updateMeasurements,
|
|
66
|
+
updateScrollOffset,
|
|
67
|
+
updateViewProps,
|
|
68
|
+
getViewEntry,
|
|
69
|
+
} = useSpatialIndex();
|
|
70
|
+
|
|
71
|
+
// ── Hover content (ref-based to avoid provider re-renders) ─────────
|
|
72
|
+
// Store content in a ref so changing it doesn't re-render the entire tree.
|
|
73
|
+
// Only HoverLayer re-renders via the version counter.
|
|
74
|
+
const hoverContentRef: RefObject<ReactNode> = useRef<ReactNode>(null);
|
|
75
|
+
const hoverStylesRef: RefObject<FlattenedHoverStyles | null> = useRef<FlattenedHoverStyles | null>(null);
|
|
76
|
+
const [hoverVersion, setHoverVersion] = useState(0);
|
|
77
|
+
const setHoverContent = useCallback((content: ReactNode | null) => {
|
|
78
|
+
hoverContentRef.current = content;
|
|
79
|
+
if (content === null) {
|
|
80
|
+
hoverStylesRef.current = null;
|
|
81
|
+
}
|
|
82
|
+
setHoverVersion((v) => v + 1);
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
// ── Callback dispatch ──────────────────────────────────────────────
|
|
86
|
+
const { handleDragStart, handleReceiverChange, handleDragEnd } =
|
|
87
|
+
useCallbackDispatch({
|
|
88
|
+
getViewEntry,
|
|
89
|
+
spatialIndexSV,
|
|
90
|
+
scrollOffsetsSV,
|
|
91
|
+
draggedIdSV,
|
|
92
|
+
receiverIdSV,
|
|
93
|
+
rejectedReceiverIdSV,
|
|
94
|
+
dragPhaseSV,
|
|
95
|
+
hoverPositionSV,
|
|
96
|
+
grabOffsetSV,
|
|
97
|
+
startPositionSV,
|
|
98
|
+
setHoverContent,
|
|
99
|
+
hoverReadySV,
|
|
100
|
+
hoverClearDeferredRef,
|
|
101
|
+
hoverStylesRef,
|
|
102
|
+
onProviderDragStart,
|
|
103
|
+
onProviderDrag,
|
|
104
|
+
onProviderDragEnd,
|
|
105
|
+
droppedItemsRef,
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
// ── Root view ref ──────────────────────────────────────────────────
|
|
109
|
+
const rootViewRef = useRef<HostInstance>(null);
|
|
110
|
+
const setRootViewRef = (ref: HostInstance | null) => {
|
|
111
|
+
rootViewRef.current = ref;
|
|
112
|
+
};
|
|
113
|
+
|
|
114
|
+
// Measure root view's screen position on layout
|
|
115
|
+
const handleRootLayout = useCallback(() => {
|
|
116
|
+
const view = rootViewRef.current;
|
|
117
|
+
if (view) {
|
|
118
|
+
(view as unknown as { measure: (cb: (...args: number[]) => void) => void })
|
|
119
|
+
.measure((_x, _y, _w, _h, pageX, pageY) => {
|
|
120
|
+
rootOffsetSV.value = { x: pageX, y: pageY };
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
}, [rootOffsetSV]);
|
|
124
|
+
|
|
125
|
+
// ── Stable context value ───────────────────────────────────────────
|
|
126
|
+
const contextValue = useMemo<DraxContextValue>(
|
|
127
|
+
() => ({
|
|
128
|
+
// SharedValues
|
|
129
|
+
draggedIdSV,
|
|
130
|
+
receiverIdSV,
|
|
131
|
+
dragPhaseSV,
|
|
132
|
+
hoverPositionSV,
|
|
133
|
+
dragAbsolutePositionSV,
|
|
134
|
+
rejectedReceiverIdSV,
|
|
135
|
+
spatialIndexSV,
|
|
136
|
+
scrollOffsetsSV,
|
|
137
|
+
grabOffsetSV,
|
|
138
|
+
startPositionSV,
|
|
139
|
+
rootOffsetSV,
|
|
140
|
+
hoverReadySV,
|
|
141
|
+
hoverClearDeferredRef,
|
|
142
|
+
hoverDimsSV,
|
|
143
|
+
|
|
144
|
+
// Registry methods
|
|
145
|
+
registerView,
|
|
146
|
+
unregisterView,
|
|
147
|
+
updateMeasurements,
|
|
148
|
+
updateScrollOffset,
|
|
149
|
+
updateViewProps,
|
|
150
|
+
getViewEntry,
|
|
151
|
+
|
|
152
|
+
// Callback dispatch
|
|
153
|
+
handleDragStart,
|
|
154
|
+
handleReceiverChange,
|
|
155
|
+
handleDragEnd,
|
|
156
|
+
|
|
157
|
+
// Hover content
|
|
158
|
+
setHoverContent,
|
|
159
|
+
|
|
160
|
+
// Dropped items
|
|
161
|
+
droppedItemsRef,
|
|
162
|
+
|
|
163
|
+
// Refs
|
|
164
|
+
rootViewRef,
|
|
165
|
+
}),
|
|
166
|
+
[
|
|
167
|
+
draggedIdSV,
|
|
168
|
+
receiverIdSV,
|
|
169
|
+
dragPhaseSV,
|
|
170
|
+
hoverPositionSV,
|
|
171
|
+
dragAbsolutePositionSV,
|
|
172
|
+
rejectedReceiverIdSV,
|
|
173
|
+
spatialIndexSV,
|
|
174
|
+
scrollOffsetsSV,
|
|
175
|
+
grabOffsetSV,
|
|
176
|
+
startPositionSV,
|
|
177
|
+
rootOffsetSV,
|
|
178
|
+
hoverReadySV,
|
|
179
|
+
hoverClearDeferredRef,
|
|
180
|
+
hoverDimsSV,
|
|
181
|
+
registerView,
|
|
182
|
+
unregisterView,
|
|
183
|
+
updateMeasurements,
|
|
184
|
+
updateScrollOffset,
|
|
185
|
+
updateViewProps,
|
|
186
|
+
getViewEntry,
|
|
187
|
+
handleDragStart,
|
|
188
|
+
handleReceiverChange,
|
|
189
|
+
handleDragEnd,
|
|
190
|
+
setHoverContent,
|
|
191
|
+
droppedItemsRef,
|
|
192
|
+
]
|
|
193
|
+
);
|
|
194
|
+
|
|
195
|
+
return (
|
|
196
|
+
<DraxContext value={contextValue}>
|
|
197
|
+
<View style={style} ref={setRootViewRef} onLayout={handleRootLayout} collapsable={false}>
|
|
198
|
+
{children}
|
|
199
|
+
{debug && (
|
|
200
|
+
<DebugOverlay
|
|
201
|
+
spatialIndexSV={spatialIndexSV}
|
|
202
|
+
scrollOffsetsSV={scrollOffsetsSV}
|
|
203
|
+
/>
|
|
204
|
+
)}
|
|
205
|
+
<HoverLayer
|
|
206
|
+
hoverContentRef={hoverContentRef}
|
|
207
|
+
hoverVersion={hoverVersion}
|
|
208
|
+
hoverPositionSV={hoverPositionSV}
|
|
209
|
+
dragPhaseSV={dragPhaseSV}
|
|
210
|
+
receiverIdSV={receiverIdSV}
|
|
211
|
+
hoverReadySV={hoverReadySV}
|
|
212
|
+
hoverDimsSV={hoverDimsSV}
|
|
213
|
+
hoverStylesRef={hoverStylesRef}
|
|
214
|
+
/>
|
|
215
|
+
</View>
|
|
216
|
+
</DraxContext>
|
|
217
|
+
);
|
|
218
|
+
};
|
|
219
|
+
|
|
220
|
+
const styles = StyleSheet.create({
|
|
221
|
+
provider: {
|
|
222
|
+
flex: 1,
|
|
223
|
+
},
|
|
224
|
+
});
|
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
import type { PropsWithChildren, Ref } from 'react';
|
|
2
|
+
import { useRef, type RefObject } from 'react';
|
|
3
|
+
import { ScrollView } from 'react-native';
|
|
4
|
+
import Reanimated from 'react-native-reanimated';
|
|
5
|
+
|
|
6
|
+
import { DraxSubprovider } from './DraxSubprovider';
|
|
7
|
+
import { DraxView } from './DraxView';
|
|
8
|
+
import { useDraxScrollHandler } from './hooks/useDraxScrollHandler';
|
|
9
|
+
import { useWebScrollFreeze } from './hooks/useWebScrollFreeze';
|
|
10
|
+
import {
|
|
11
|
+
defaultAutoScrollBackThreshold,
|
|
12
|
+
defaultAutoScrollForwardThreshold,
|
|
13
|
+
defaultAutoScrollJumpRatio,
|
|
14
|
+
defaultScrollEventThrottle,
|
|
15
|
+
} from './params';
|
|
16
|
+
import type {
|
|
17
|
+
AutoScrollState,
|
|
18
|
+
DraxMonitorEventData,
|
|
19
|
+
DraxScrollViewProps,
|
|
20
|
+
} from './types';
|
|
21
|
+
import { AutoScrollDirection } from './types';
|
|
22
|
+
|
|
23
|
+
export const DraxScrollView = (
|
|
24
|
+
props: PropsWithChildren<DraxScrollViewProps> & { ref?: Ref<ScrollView> }
|
|
25
|
+
) => {
|
|
26
|
+
const {
|
|
27
|
+
ref,
|
|
28
|
+
children,
|
|
29
|
+
style,
|
|
30
|
+
onContentSizeChange: onContentSizeChangeProp,
|
|
31
|
+
scrollEventThrottle = defaultScrollEventThrottle,
|
|
32
|
+
autoScrollJumpRatio = defaultAutoScrollJumpRatio,
|
|
33
|
+
autoScrollBackThreshold = defaultAutoScrollBackThreshold,
|
|
34
|
+
autoScrollForwardThreshold = defaultAutoScrollForwardThreshold,
|
|
35
|
+
id: idProp,
|
|
36
|
+
...scrollViewProps
|
|
37
|
+
} = props;
|
|
38
|
+
|
|
39
|
+
// Auto-scroll state.
|
|
40
|
+
const autoScrollStateRef = useRef<AutoScrollState>({
|
|
41
|
+
x: AutoScrollDirection.None,
|
|
42
|
+
y: AutoScrollDirection.None,
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
// Handle auto-scrolling on interval (ref-based to avoid circular deps with useDraxScrollHandler).
|
|
46
|
+
const doScrollRef: RefObject<() => void> = useRef(() => {});
|
|
47
|
+
|
|
48
|
+
const {
|
|
49
|
+
id,
|
|
50
|
+
containerMeasurementsRef,
|
|
51
|
+
contentSizeRef,
|
|
52
|
+
onContentSizeChange,
|
|
53
|
+
onMeasureContainer,
|
|
54
|
+
onScroll,
|
|
55
|
+
scrollRef,
|
|
56
|
+
scrollPosition,
|
|
57
|
+
setScrollRefs,
|
|
58
|
+
startScroll,
|
|
59
|
+
stopScroll,
|
|
60
|
+
} = useDraxScrollHandler<ScrollView>({
|
|
61
|
+
idProp,
|
|
62
|
+
onContentSizeChangeProp,
|
|
63
|
+
onScrollProp: props?.onScroll,
|
|
64
|
+
externalRef: ref,
|
|
65
|
+
doScroll: doScrollRef,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// Assign doScroll implementation now that we have scrollRef, measurements, etc.
|
|
69
|
+
doScrollRef.current = () => {
|
|
70
|
+
const scroll = scrollRef.current;
|
|
71
|
+
const containerMeasurements = containerMeasurementsRef.current;
|
|
72
|
+
const contentSize = contentSizeRef.current;
|
|
73
|
+
if (!scroll || !containerMeasurements || !contentSize) return;
|
|
74
|
+
const autoScrollState = autoScrollStateRef.current;
|
|
75
|
+
const jump = {
|
|
76
|
+
x: containerMeasurements.width * autoScrollJumpRatio,
|
|
77
|
+
y: containerMeasurements.height * autoScrollJumpRatio,
|
|
78
|
+
};
|
|
79
|
+
let xNew: number | undefined;
|
|
80
|
+
let yNew: number | undefined;
|
|
81
|
+
if (autoScrollState.x === AutoScrollDirection.Forward) {
|
|
82
|
+
const xMax = contentSize.x - containerMeasurements.width;
|
|
83
|
+
if (scrollPosition.value.x < xMax) {
|
|
84
|
+
xNew = Math.min(scrollPosition.value.x + jump.x, xMax);
|
|
85
|
+
}
|
|
86
|
+
} else if (autoScrollState.x === AutoScrollDirection.Back) {
|
|
87
|
+
if (scrollPosition.value.x > 0) {
|
|
88
|
+
xNew = Math.max(scrollPosition.value.x - jump.x, 0);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
if (autoScrollState.y === AutoScrollDirection.Forward) {
|
|
92
|
+
const yMax = contentSize.y - containerMeasurements.height;
|
|
93
|
+
if (scrollPosition.value.y < yMax) {
|
|
94
|
+
yNew = Math.min(scrollPosition.value.y + jump.y, yMax);
|
|
95
|
+
}
|
|
96
|
+
} else if (autoScrollState.y === AutoScrollDirection.Back) {
|
|
97
|
+
if (scrollPosition.value.y > 0) {
|
|
98
|
+
yNew = Math.max(scrollPosition.value.y - jump.y, 0);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (xNew !== undefined || yNew !== undefined) {
|
|
102
|
+
// @ts-expect-error Reanimated's type augmentation hides scrollTo, but it exists at runtime
|
|
103
|
+
scroll.scrollTo({
|
|
104
|
+
x: xNew ?? scrollPosition.value.x,
|
|
105
|
+
y: yNew ?? scrollPosition.value.y,
|
|
106
|
+
});
|
|
107
|
+
if (
|
|
108
|
+
'flashScrollIndicators' in scroll &&
|
|
109
|
+
typeof scroll.flashScrollIndicators === 'function'
|
|
110
|
+
) {
|
|
111
|
+
scroll.flashScrollIndicators();
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
const { freeze: freezeScroll, unfreeze: unfreezeScroll } = useWebScrollFreeze(scrollRef);
|
|
117
|
+
|
|
118
|
+
// Clear auto-scroll direction and stop the auto-scrolling interval.
|
|
119
|
+
const resetScroll = () => {
|
|
120
|
+
const autoScrollState = autoScrollStateRef.current;
|
|
121
|
+
autoScrollState.x = AutoScrollDirection.None;
|
|
122
|
+
autoScrollState.y = AutoScrollDirection.None;
|
|
123
|
+
stopScroll();
|
|
124
|
+
unfreezeScroll();
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
// Monitor drag-over events to react with auto-scrolling.
|
|
128
|
+
const onMonitorDragOver = (event: DraxMonitorEventData) => {
|
|
129
|
+
const { monitorOffsetRatio } = event;
|
|
130
|
+
const autoScrollState = autoScrollStateRef.current;
|
|
131
|
+
if (monitorOffsetRatio.x >= autoScrollForwardThreshold) {
|
|
132
|
+
autoScrollState.x = AutoScrollDirection.Forward;
|
|
133
|
+
} else if (monitorOffsetRatio.x <= autoScrollBackThreshold) {
|
|
134
|
+
autoScrollState.x = AutoScrollDirection.Back;
|
|
135
|
+
} else {
|
|
136
|
+
autoScrollState.x = AutoScrollDirection.None;
|
|
137
|
+
}
|
|
138
|
+
if (monitorOffsetRatio.y >= autoScrollForwardThreshold) {
|
|
139
|
+
autoScrollState.y = AutoScrollDirection.Forward;
|
|
140
|
+
} else if (monitorOffsetRatio.y <= autoScrollBackThreshold) {
|
|
141
|
+
autoScrollState.y = AutoScrollDirection.Back;
|
|
142
|
+
} else {
|
|
143
|
+
autoScrollState.y = AutoScrollDirection.None;
|
|
144
|
+
}
|
|
145
|
+
if (autoScrollState.x === AutoScrollDirection.None && autoScrollState.y === AutoScrollDirection.None) {
|
|
146
|
+
stopScroll();
|
|
147
|
+
} else {
|
|
148
|
+
startScroll();
|
|
149
|
+
}
|
|
150
|
+
};
|
|
151
|
+
|
|
152
|
+
const scrollViewParent = { id, viewRef: scrollRef, isScrollContainer: true };
|
|
153
|
+
|
|
154
|
+
return (
|
|
155
|
+
<DraxView
|
|
156
|
+
id={id}
|
|
157
|
+
style={style}
|
|
158
|
+
scrollPosition={scrollPosition}
|
|
159
|
+
onMeasure={onMeasureContainer}
|
|
160
|
+
onMonitorDragStart={freezeScroll}
|
|
161
|
+
onMonitorDragOver={onMonitorDragOver}
|
|
162
|
+
onMonitorDragExit={resetScroll}
|
|
163
|
+
onMonitorDragEnd={resetScroll}
|
|
164
|
+
onMonitorDragDrop={resetScroll}
|
|
165
|
+
>
|
|
166
|
+
<DraxSubprovider parent={scrollViewParent}>
|
|
167
|
+
<Reanimated.ScrollView
|
|
168
|
+
{...scrollViewProps}
|
|
169
|
+
// @ts-expect-error — callback ref bridges AnimatedRef + external ref; runtime-compatible
|
|
170
|
+
ref={setScrollRefs}
|
|
171
|
+
onContentSizeChange={onContentSizeChange}
|
|
172
|
+
scrollEventThrottle={scrollEventThrottle}
|
|
173
|
+
onScroll={onScroll}
|
|
174
|
+
>
|
|
175
|
+
{children}
|
|
176
|
+
</Reanimated.ScrollView>
|
|
177
|
+
</DraxSubprovider>
|
|
178
|
+
</DraxView>
|
|
179
|
+
);
|
|
180
|
+
};
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { PropsWithChildren } from 'react';
|
|
2
|
+
import { useMemo } from 'react';
|
|
3
|
+
|
|
4
|
+
import { DraxContext } from './DraxContext';
|
|
5
|
+
import { useDraxContext } from './hooks';
|
|
6
|
+
import type { DraxSubproviderProps } from './types';
|
|
7
|
+
|
|
8
|
+
export const DraxSubprovider = ({
|
|
9
|
+
parent,
|
|
10
|
+
children,
|
|
11
|
+
}: PropsWithChildren<DraxSubproviderProps>) => {
|
|
12
|
+
const contextValue = useDraxContext();
|
|
13
|
+
const subContextValue = useMemo(
|
|
14
|
+
() => ({ ...contextValue, parent }),
|
|
15
|
+
[contextValue, parent]
|
|
16
|
+
);
|
|
17
|
+
return (
|
|
18
|
+
<DraxContext value={subContextValue}>
|
|
19
|
+
{children}
|
|
20
|
+
</DraxContext>
|
|
21
|
+
);
|
|
22
|
+
};
|