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,688 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
import { useRef } from 'react';
|
|
4
|
+
import { StyleSheet, View } from 'react-native';
|
|
5
|
+
import { withDelay, withTiming } from 'react-native-reanimated';
|
|
6
|
+
import { runOnJS, runOnUI } from 'react-native-worklets';
|
|
7
|
+
import { computeAbsolutePositionWorklet, getRelativePosition } from "../math.js";
|
|
8
|
+
import { defaultSnapbackDelay, defaultSnapbackDuration } from "../params.js";
|
|
9
|
+
import { DraxSnapbackTargetPreset, DraxViewDragStatus, DraxViewReceiveStatus, isPosition } from "../types.js";
|
|
10
|
+
import { isDraggable } from "./useSpatialIndex.js";
|
|
11
|
+
|
|
12
|
+
/** Style override to strip margins — hover is positioned via translateX/Y */
|
|
13
|
+
/** Styles to strip from the hover content — margins and absolute positioning
|
|
14
|
+
* are not needed since hover is positioned via translateX/Y. */
|
|
15
|
+
import { jsx as _jsx } from "react/jsx-runtime";
|
|
16
|
+
const hoverResetStyle = {
|
|
17
|
+
margin: 0,
|
|
18
|
+
marginHorizontal: 0,
|
|
19
|
+
marginVertical: 0,
|
|
20
|
+
marginTop: 0,
|
|
21
|
+
marginBottom: 0,
|
|
22
|
+
marginLeft: 0,
|
|
23
|
+
marginRight: 0,
|
|
24
|
+
position: 'relative',
|
|
25
|
+
left: 0,
|
|
26
|
+
top: 0,
|
|
27
|
+
right: undefined,
|
|
28
|
+
bottom: undefined
|
|
29
|
+
};
|
|
30
|
+
/**
|
|
31
|
+
* Provides JS-thread callback dispatch functions that are invoked via runOnJS
|
|
32
|
+
* from gesture worklets. These handle ~5 calls per drag (start, receiver changes, end),
|
|
33
|
+
* NOT per frame.
|
|
34
|
+
*/
|
|
35
|
+
export const useCallbackDispatch = deps => {
|
|
36
|
+
const {
|
|
37
|
+
getViewEntry,
|
|
38
|
+
spatialIndexSV,
|
|
39
|
+
scrollOffsetsSV,
|
|
40
|
+
draggedIdSV,
|
|
41
|
+
dragPhaseSV,
|
|
42
|
+
hoverPositionSV,
|
|
43
|
+
grabOffsetSV,
|
|
44
|
+
startPositionSV,
|
|
45
|
+
setHoverContent,
|
|
46
|
+
hoverReadySV,
|
|
47
|
+
onProviderDragStart,
|
|
48
|
+
onProviderDrag,
|
|
49
|
+
onProviderDragEnd,
|
|
50
|
+
droppedItemsRef
|
|
51
|
+
} = deps;
|
|
52
|
+
|
|
53
|
+
// Track current monitor ids for exit events
|
|
54
|
+
const currentMonitorIdsRef = useRef([]);
|
|
55
|
+
|
|
56
|
+
/** Build dragged view event data from current state */
|
|
57
|
+
const buildDraggedViewData = (draggedId, absolutePosition) => {
|
|
58
|
+
const entry = getViewEntry(draggedId);
|
|
59
|
+
if (!entry) return undefined;
|
|
60
|
+
const startPos = startPositionSV.value;
|
|
61
|
+
const grabOffset = grabOffsetSV.value;
|
|
62
|
+
const dragTranslation = {
|
|
63
|
+
x: absolutePosition.x - startPos.x,
|
|
64
|
+
y: absolutePosition.y - startPos.y
|
|
65
|
+
};
|
|
66
|
+
const measurements = entry.measurements;
|
|
67
|
+
// Use || instead of ?? intentionally: zero dimensions would cause division by zero below
|
|
68
|
+
const width = measurements?.width || 1;
|
|
69
|
+
const height = measurements?.height || 1;
|
|
70
|
+
return {
|
|
71
|
+
id: draggedId,
|
|
72
|
+
parentId: entry.parentId,
|
|
73
|
+
payload: entry.props.dragPayload ?? entry.props.payload,
|
|
74
|
+
measurements,
|
|
75
|
+
dragTranslationRatio: {
|
|
76
|
+
x: dragTranslation.x / width,
|
|
77
|
+
y: dragTranslation.y / height
|
|
78
|
+
},
|
|
79
|
+
dragOffset: {
|
|
80
|
+
x: absolutePosition.x - (measurements?.x ?? 0),
|
|
81
|
+
y: absolutePosition.y - (measurements?.y ?? 0)
|
|
82
|
+
},
|
|
83
|
+
grabOffset,
|
|
84
|
+
grabOffsetRatio: {
|
|
85
|
+
x: grabOffset.x / width,
|
|
86
|
+
y: grabOffset.y / height
|
|
87
|
+
},
|
|
88
|
+
hoverPosition: hoverPositionSV.value
|
|
89
|
+
};
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
/** Build receiver view event data */
|
|
93
|
+
const buildReceiverViewData = (receiverId, absolutePosition) => {
|
|
94
|
+
const entry = getViewEntry(receiverId);
|
|
95
|
+
if (!entry?.measurements) return undefined;
|
|
96
|
+
|
|
97
|
+
// Compute absolute measurements of receiver
|
|
98
|
+
const idx = entry.spatialIndex;
|
|
99
|
+
const entries = spatialIndexSV.value;
|
|
100
|
+
const offsets = scrollOffsetsSV.value;
|
|
101
|
+
const absPos = computeAbsolutePositionWorklet(idx, entries, offsets);
|
|
102
|
+
const absMeasurements = {
|
|
103
|
+
...absPos,
|
|
104
|
+
width: entry.measurements.width,
|
|
105
|
+
height: entry.measurements.height,
|
|
106
|
+
_transformDetected: 0
|
|
107
|
+
};
|
|
108
|
+
const {
|
|
109
|
+
relativePosition,
|
|
110
|
+
relativePositionRatio
|
|
111
|
+
} = getRelativePosition(absolutePosition, absMeasurements);
|
|
112
|
+
return {
|
|
113
|
+
id: receiverId,
|
|
114
|
+
parentId: entry.parentId,
|
|
115
|
+
payload: entry.props.receiverPayload ?? entry.props.payload,
|
|
116
|
+
measurements: entry.measurements,
|
|
117
|
+
receiveOffset: relativePosition,
|
|
118
|
+
receiveOffsetRatio: relativePositionRatio
|
|
119
|
+
};
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
/** Called via runOnJS when drag starts */
|
|
123
|
+
const handleDragStart = (draggedId, absolutePosition, _grabOffset) => {
|
|
124
|
+
const draggedEntry = getViewEntry(draggedId);
|
|
125
|
+
if (!draggedEntry) return;
|
|
126
|
+
const dragged = buildDraggedViewData(draggedId, absolutePosition);
|
|
127
|
+
if (!dragged) return;
|
|
128
|
+
const startPos = startPositionSV.value;
|
|
129
|
+
const dragTranslation = {
|
|
130
|
+
x: absolutePosition.x - startPos.x,
|
|
131
|
+
y: absolutePosition.y - startPos.y
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
// Fire onDragStart callback
|
|
135
|
+
draggedEntry.props.onDragStart?.({
|
|
136
|
+
dragAbsolutePosition: absolutePosition,
|
|
137
|
+
dragTranslation,
|
|
138
|
+
dragged
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
// Setup hover styles — set BEFORE setHoverContent so HoverLayer
|
|
142
|
+
// captures them when it re-renders on hoverVersion change.
|
|
143
|
+
deps.hoverStylesRef.current = {
|
|
144
|
+
hoverStyle: flattenOrNull(draggedEntry.props.hoverStyle),
|
|
145
|
+
hoverDraggingStyle: flattenOrNull(draggedEntry.props.hoverDraggingStyle),
|
|
146
|
+
hoverDraggingWithReceiverStyle: flattenOrNull(draggedEntry.props.hoverDraggingWithReceiverStyle),
|
|
147
|
+
hoverDraggingWithoutReceiverStyle: flattenOrNull(draggedEntry.props.hoverDraggingWithoutReceiverStyle),
|
|
148
|
+
hoverDragReleasedStyle: flattenOrNull(draggedEntry.props.hoverDragReleasedStyle)
|
|
149
|
+
};
|
|
150
|
+
|
|
151
|
+
// Setup hover content
|
|
152
|
+
if (isDraggable(draggedEntry.props) && !draggedEntry.props.noHover) {
|
|
153
|
+
const renderFn = draggedEntry.props.renderHoverContent ?? draggedEntry.props.renderContent;
|
|
154
|
+
if (renderFn) {
|
|
155
|
+
const content = renderFn({
|
|
156
|
+
viewState: {
|
|
157
|
+
dragStatus: DraxViewDragStatus.Dragging,
|
|
158
|
+
receiveStatus: DraxViewReceiveStatus.Inactive,
|
|
159
|
+
grabOffset: dragged.grabOffset,
|
|
160
|
+
grabOffsetRatio: dragged.grabOffsetRatio
|
|
161
|
+
},
|
|
162
|
+
trackingStatus: {
|
|
163
|
+
dragging: true,
|
|
164
|
+
receiving: false
|
|
165
|
+
},
|
|
166
|
+
hover: true,
|
|
167
|
+
children: null,
|
|
168
|
+
dimensions: draggedEntry.measurements ? {
|
|
169
|
+
width: draggedEntry.measurements.width,
|
|
170
|
+
height: draggedEntry.measurements.height
|
|
171
|
+
} : undefined
|
|
172
|
+
});
|
|
173
|
+
setHoverContent(content);
|
|
174
|
+
} else {
|
|
175
|
+
// Default hover: wrap children with original view style and dimensions.
|
|
176
|
+
// Strip margins since hover is positioned via translateX/Y.
|
|
177
|
+
const dims = draggedEntry.measurements;
|
|
178
|
+
const viewStyle = draggedEntry.props.style;
|
|
179
|
+
setHoverContent(/*#__PURE__*/_jsx(View, {
|
|
180
|
+
style: [viewStyle, dims && {
|
|
181
|
+
width: dims.width,
|
|
182
|
+
height: dims.height
|
|
183
|
+
}, hoverResetStyle],
|
|
184
|
+
children: draggedEntry.props.children
|
|
185
|
+
}));
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Phase activation is handled by HoverLayer's useLayoutEffect — it fires
|
|
190
|
+
// AFTER React commits the hover content, ensuring both opacity:1 and
|
|
191
|
+
// draggingStyle apply on the same frame. See HoverLayer.tsx.
|
|
192
|
+
|
|
193
|
+
// Fire provider-level onDragStart
|
|
194
|
+
onProviderDragStart?.({
|
|
195
|
+
draggedId,
|
|
196
|
+
position: absolutePosition
|
|
197
|
+
});
|
|
198
|
+
|
|
199
|
+
// Fire monitor onMonitorDragStart callbacks
|
|
200
|
+
currentMonitorIdsRef.current = [];
|
|
201
|
+
};
|
|
202
|
+
|
|
203
|
+
/** Called via runOnJS on every gesture update for callback dispatch.
|
|
204
|
+
* Handles: enter/exit (on receiver change), onDragOver/onReceiveDragOver
|
|
205
|
+
* (continuous, same receiver), onDrag (continuous, no receiver), and monitors. */
|
|
206
|
+
const handleReceiverChange = (oldReceiverId, newReceiverId, absolutePosition, monitorIds) => {
|
|
207
|
+
const draggedId = draggedIdSV.value;
|
|
208
|
+
|
|
209
|
+
// Fast path: receiver unchanged, no monitors (now AND previously),
|
|
210
|
+
// and no continuous callbacks → skip event data construction entirely.
|
|
211
|
+
const newMonitorIds = monitorIds ?? [];
|
|
212
|
+
const prevMonitorIds = currentMonitorIdsRef.current;
|
|
213
|
+
if (oldReceiverId === newReceiverId && newMonitorIds.length === 0 && prevMonitorIds.length === 0) {
|
|
214
|
+
const draggedEntry = getViewEntry(draggedId);
|
|
215
|
+
if (!draggedEntry) return;
|
|
216
|
+
const hasOnDragOver = newReceiverId && draggedEntry.props.onDragOver;
|
|
217
|
+
const receiverEntry = newReceiverId ? getViewEntry(newReceiverId) : undefined;
|
|
218
|
+
const hasOnReceiveDragOver = newReceiverId && receiverEntry?.props.onReceiveDragOver;
|
|
219
|
+
const hasOnDrag = !newReceiverId && (draggedEntry.props.onDrag || onProviderDrag);
|
|
220
|
+
if (!hasOnDragOver && !hasOnReceiveDragOver && !hasOnDrag) return;
|
|
221
|
+
}
|
|
222
|
+
const dragged = buildDraggedViewData(draggedId, absolutePosition);
|
|
223
|
+
if (!dragged) return;
|
|
224
|
+
const draggedEntry = getViewEntry(draggedId);
|
|
225
|
+
const draggedPayload = draggedEntry?.props.dragPayload ?? draggedEntry?.props.payload;
|
|
226
|
+
const startPos = startPositionSV.value;
|
|
227
|
+
const dragTranslation = {
|
|
228
|
+
x: absolutePosition.x - startPos.x,
|
|
229
|
+
y: absolutePosition.y - startPos.y
|
|
230
|
+
};
|
|
231
|
+
const baseEventData = {
|
|
232
|
+
dragAbsolutePosition: absolutePosition,
|
|
233
|
+
dragTranslation,
|
|
234
|
+
dragged
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
// ── Check dynamicReceptiveCallback / acceptsDrag on new receiver ──
|
|
238
|
+
let acceptedReceiverId = newReceiverId;
|
|
239
|
+
if (newReceiverId && oldReceiverId !== newReceiverId) {
|
|
240
|
+
const newReceiverEntry = getViewEntry(newReceiverId);
|
|
241
|
+
if (newReceiverEntry) {
|
|
242
|
+
// Check acceptsDrag first (simpler convenience prop)
|
|
243
|
+
const acceptsDrag = newReceiverEntry.props.acceptsDrag;
|
|
244
|
+
if (acceptsDrag && !acceptsDrag(draggedPayload)) {
|
|
245
|
+
acceptedReceiverId = '';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Check capacity
|
|
249
|
+
const capacity = newReceiverEntry.props.capacity;
|
|
250
|
+
if (acceptedReceiverId && capacity !== undefined) {
|
|
251
|
+
const droppedSet = droppedItemsRef.current.get(newReceiverId);
|
|
252
|
+
const count = droppedSet ? droppedSet.size : 0;
|
|
253
|
+
if (count >= capacity) {
|
|
254
|
+
acceptedReceiverId = '';
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// Check dynamicReceptiveCallback (more detailed)
|
|
259
|
+
const dynamicCallback = newReceiverEntry.props.dynamicReceptiveCallback;
|
|
260
|
+
if (acceptedReceiverId && dynamicCallback && newReceiverEntry.measurements) {
|
|
261
|
+
const accepted = dynamicCallback({
|
|
262
|
+
targetId: newReceiverId,
|
|
263
|
+
targetMeasurements: newReceiverEntry.measurements,
|
|
264
|
+
draggedId,
|
|
265
|
+
draggedPayload
|
|
266
|
+
});
|
|
267
|
+
if (!accepted) {
|
|
268
|
+
acceptedReceiverId = '';
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// If rejected, tell the gesture worklet to skip this receiver on future frames.
|
|
274
|
+
// Also clear receiverIdSV so animated styles don't flash the receiving state.
|
|
275
|
+
if (!acceptedReceiverId) {
|
|
276
|
+
runOnUI((_receiverIdSV, _rejectedReceiverIdSV, _rejectedId) => {
|
|
277
|
+
'worklet';
|
|
278
|
+
|
|
279
|
+
_receiverIdSV.value = '';
|
|
280
|
+
_rejectedReceiverIdSV.value = _rejectedId;
|
|
281
|
+
})(deps.receiverIdSV, deps.rejectedReceiverIdSV, newReceiverId);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// Fire exit on old receiver (only when receiver actually changed)
|
|
286
|
+
if (oldReceiverId && oldReceiverId !== acceptedReceiverId) {
|
|
287
|
+
const oldReceiverEntry = getViewEntry(oldReceiverId);
|
|
288
|
+
const oldReceiverData = buildReceiverViewData(oldReceiverId, absolutePosition);
|
|
289
|
+
if (oldReceiverEntry && oldReceiverData) {
|
|
290
|
+
// Dragged view: onDragExit
|
|
291
|
+
draggedEntry?.props.onDragExit?.({
|
|
292
|
+
...baseEventData,
|
|
293
|
+
receiver: oldReceiverData
|
|
294
|
+
});
|
|
295
|
+
|
|
296
|
+
// Receiver view: onReceiveDragExit
|
|
297
|
+
oldReceiverEntry.props.onReceiveDragExit?.({
|
|
298
|
+
...baseEventData,
|
|
299
|
+
receiver: oldReceiverData,
|
|
300
|
+
cancelled: false
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
// Fire enter on new receiver (only when receiver actually changed)
|
|
306
|
+
if (acceptedReceiverId && oldReceiverId !== acceptedReceiverId) {
|
|
307
|
+
const newReceiverEntry = getViewEntry(acceptedReceiverId);
|
|
308
|
+
const newReceiverData = buildReceiverViewData(acceptedReceiverId, absolutePosition);
|
|
309
|
+
if (newReceiverEntry && newReceiverData) {
|
|
310
|
+
// Dragged view: onDragEnter
|
|
311
|
+
draggedEntry?.props.onDragEnter?.({
|
|
312
|
+
...baseEventData,
|
|
313
|
+
receiver: newReceiverData
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
// Receiver view: onReceiveDragEnter
|
|
317
|
+
newReceiverEntry.props.onReceiveDragEnter?.({
|
|
318
|
+
...baseEventData,
|
|
319
|
+
receiver: newReceiverData
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
// ── Continuous callbacks: onDragOver / onReceiveDragOver / onDrag ──
|
|
325
|
+
if (acceptedReceiverId && oldReceiverId === acceptedReceiverId) {
|
|
326
|
+
// Dragging over the same receiver — fire onDragOver + onReceiveDragOver
|
|
327
|
+
const receiverEntry = getViewEntry(acceptedReceiverId);
|
|
328
|
+
const receiverData = buildReceiverViewData(acceptedReceiverId, absolutePosition);
|
|
329
|
+
if (receiverEntry && receiverData) {
|
|
330
|
+
draggedEntry?.props.onDragOver?.({
|
|
331
|
+
...baseEventData,
|
|
332
|
+
receiver: receiverData
|
|
333
|
+
});
|
|
334
|
+
receiverEntry.props.onReceiveDragOver?.({
|
|
335
|
+
...baseEventData,
|
|
336
|
+
receiver: receiverData
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
} else if (!acceptedReceiverId) {
|
|
340
|
+
// No receiver — fire onDrag (continuous, not over any receiver)
|
|
341
|
+
draggedEntry?.props.onDrag?.(baseEventData);
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ── Dispatch monitor events ──────────────────────────────────────
|
|
345
|
+
const prevWasEmpty = prevMonitorIds.length === 0;
|
|
346
|
+
|
|
347
|
+
// Build receiver data for monitor event payload (use accepted receiver, not raw hit-test)
|
|
348
|
+
const receiverData = acceptedReceiverId ? buildReceiverViewData(acceptedReceiverId, absolutePosition) : undefined;
|
|
349
|
+
|
|
350
|
+
// Fire events on current monitors (start/enter before over)
|
|
351
|
+
for (const monitorId of newMonitorIds) {
|
|
352
|
+
const monitorEntry = getViewEntry(monitorId);
|
|
353
|
+
if (!monitorEntry?.measurements) continue;
|
|
354
|
+
const {
|
|
355
|
+
relativePosition: monitorOffset,
|
|
356
|
+
relativePositionRatio: monitorOffsetRatio
|
|
357
|
+
} = getRelativePosition(absolutePosition, monitorEntry.measurements);
|
|
358
|
+
const monitorEventData = {
|
|
359
|
+
...baseEventData,
|
|
360
|
+
receiver: receiverData,
|
|
361
|
+
monitorOffset,
|
|
362
|
+
monitorOffsetRatio
|
|
363
|
+
};
|
|
364
|
+
const isNew = !prevMonitorIds.includes(monitorId);
|
|
365
|
+
|
|
366
|
+
// First time we see any monitor after drag start → fire onMonitorDragStart
|
|
367
|
+
if (isNew && prevWasEmpty) {
|
|
368
|
+
monitorEntry.props.onMonitorDragStart?.(monitorEventData);
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
// New monitor → fire onMonitorDragEnter
|
|
372
|
+
if (isNew) {
|
|
373
|
+
monitorEntry.props.onMonitorDragEnter?.(monitorEventData);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// All current monitors → fire onMonitorDragOver
|
|
377
|
+
monitorEntry.props.onMonitorDragOver?.(monitorEventData);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Fire exit on monitors that are no longer hit
|
|
381
|
+
for (const prevMonitorId of prevMonitorIds) {
|
|
382
|
+
if (newMonitorIds.includes(prevMonitorId)) continue;
|
|
383
|
+
const monitorEntry = getViewEntry(prevMonitorId);
|
|
384
|
+
if (!monitorEntry?.measurements) continue;
|
|
385
|
+
const {
|
|
386
|
+
relativePosition: monitorOffset,
|
|
387
|
+
relativePositionRatio: monitorOffsetRatio
|
|
388
|
+
} = getRelativePosition(absolutePosition, monitorEntry.measurements);
|
|
389
|
+
monitorEntry.props.onMonitorDragExit?.({
|
|
390
|
+
...baseEventData,
|
|
391
|
+
receiver: receiverData,
|
|
392
|
+
monitorOffset,
|
|
393
|
+
monitorOffsetRatio
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
currentMonitorIdsRef.current = newMonitorIds;
|
|
397
|
+
|
|
398
|
+
// Fire provider-level onDrag (use acceptedReceiverId, not raw newReceiverId)
|
|
399
|
+
onProviderDrag?.({
|
|
400
|
+
draggedId: draggedIdSV.value,
|
|
401
|
+
receiverId: acceptedReceiverId || undefined,
|
|
402
|
+
position: absolutePosition
|
|
403
|
+
});
|
|
404
|
+
};
|
|
405
|
+
|
|
406
|
+
/** Called via runOnJS when drag ends or is cancelled */
|
|
407
|
+
const handleDragEnd = (draggedId, receiverId, cancelled, finalMonitorIds) => {
|
|
408
|
+
// receiverIdSV is already cleared on the UI thread in onDeactivate/onFinalize,
|
|
409
|
+
// so the receiver's animated style resets immediately.
|
|
410
|
+
|
|
411
|
+
const draggedEntry = getViewEntry(draggedId);
|
|
412
|
+
if (!draggedEntry) {
|
|
413
|
+
// Reset drag state atomically on UI thread to avoid one-frame flash
|
|
414
|
+
runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
|
|
415
|
+
'worklet';
|
|
416
|
+
|
|
417
|
+
_hoverReadySV.value = false;
|
|
418
|
+
_dragPhaseSV.value = 'idle';
|
|
419
|
+
_draggedIdSV.value = '';
|
|
420
|
+
_hoverPositionSV.value = {
|
|
421
|
+
x: 0,
|
|
422
|
+
y: 0
|
|
423
|
+
};
|
|
424
|
+
})(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
|
|
425
|
+
setHoverContent(null);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
const absolutePosition = {
|
|
429
|
+
...hoverPositionSV.value
|
|
430
|
+
};
|
|
431
|
+
const dragged = buildDraggedViewData(draggedId, absolutePosition);
|
|
432
|
+
if (!dragged) {
|
|
433
|
+
runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
|
|
434
|
+
'worklet';
|
|
435
|
+
|
|
436
|
+
_hoverReadySV.value = false;
|
|
437
|
+
_dragPhaseSV.value = 'idle';
|
|
438
|
+
_draggedIdSV.value = '';
|
|
439
|
+
_hoverPositionSV.value = {
|
|
440
|
+
x: 0,
|
|
441
|
+
y: 0
|
|
442
|
+
};
|
|
443
|
+
})(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
|
|
444
|
+
setHoverContent(null);
|
|
445
|
+
return;
|
|
446
|
+
}
|
|
447
|
+
const startPos = startPositionSV.value;
|
|
448
|
+
const dragTranslation = {
|
|
449
|
+
x: absolutePosition.x - startPos.x,
|
|
450
|
+
y: absolutePosition.y - startPos.y
|
|
451
|
+
};
|
|
452
|
+
const baseEventData = {
|
|
453
|
+
dragAbsolutePosition: absolutePosition,
|
|
454
|
+
dragTranslation,
|
|
455
|
+
dragged
|
|
456
|
+
};
|
|
457
|
+
let snapTarget = DraxSnapbackTargetPreset.Default;
|
|
458
|
+
if (receiverId && !cancelled) {
|
|
459
|
+
// Successful drop — default snap to receiver position
|
|
460
|
+
const receiverEntry = getViewEntry(receiverId);
|
|
461
|
+
const receiverData = buildReceiverViewData(receiverId, absolutePosition);
|
|
462
|
+
if (receiverData && receiverEntry) {
|
|
463
|
+
// Compute receiver's absolute position and center the dragged item within it
|
|
464
|
+
const receiverAbsPos = computeAbsolutePositionWorklet(receiverEntry.spatialIndex, spatialIndexSV.value, scrollOffsetsSV.value);
|
|
465
|
+
const draggedDims = draggedEntry.measurements;
|
|
466
|
+
const receiverDims = receiverEntry.measurements;
|
|
467
|
+
if (receiverDims && draggedDims) {
|
|
468
|
+
snapTarget = {
|
|
469
|
+
x: receiverAbsPos.x + (receiverDims.width - draggedDims.width) / 2,
|
|
470
|
+
y: receiverAbsPos.y + (receiverDims.height - draggedDims.height) / 2
|
|
471
|
+
};
|
|
472
|
+
} else {
|
|
473
|
+
snapTarget = receiverAbsPos;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
// Fire onDragDrop on dragged (can override snap target)
|
|
477
|
+
const dragDropResponse = draggedEntry.props.onDragDrop?.({
|
|
478
|
+
...baseEventData,
|
|
479
|
+
receiver: receiverData
|
|
480
|
+
});
|
|
481
|
+
if (dragDropResponse !== undefined) snapTarget = dragDropResponse;
|
|
482
|
+
|
|
483
|
+
// Fire onReceiveDragDrop on receiver (can override snap target)
|
|
484
|
+
const receiveDropResponse = receiverEntry.props.onReceiveDragDrop?.({
|
|
485
|
+
...baseEventData,
|
|
486
|
+
receiver: receiverData
|
|
487
|
+
});
|
|
488
|
+
if (receiveDropResponse !== undefined) snapTarget = receiveDropResponse;
|
|
489
|
+
|
|
490
|
+
// Track the drop for capacity enforcement
|
|
491
|
+
if (!droppedItemsRef.current.has(receiverId)) {
|
|
492
|
+
droppedItemsRef.current.set(receiverId, new Set());
|
|
493
|
+
}
|
|
494
|
+
droppedItemsRef.current.get(receiverId).add(draggedId);
|
|
495
|
+
}
|
|
496
|
+
} else {
|
|
497
|
+
// No receiver or cancelled
|
|
498
|
+
const dragEndResponse = draggedEntry.props.onDragEnd?.({
|
|
499
|
+
...baseEventData,
|
|
500
|
+
cancelled
|
|
501
|
+
});
|
|
502
|
+
if (dragEndResponse !== undefined) snapTarget = dragEndResponse;
|
|
503
|
+
}
|
|
504
|
+
|
|
505
|
+
// Fire monitor end events — use final hit-test monitors from onDeactivate
|
|
506
|
+
// if available, falling back to tracked monitors from receiver changes.
|
|
507
|
+
const monitorIdsToUse = finalMonitorIds ?? currentMonitorIdsRef.current;
|
|
508
|
+
for (const monitorId of monitorIdsToUse) {
|
|
509
|
+
const monitorEntry = getViewEntry(monitorId);
|
|
510
|
+
if (!monitorEntry?.measurements) continue;
|
|
511
|
+
const {
|
|
512
|
+
relativePosition: monitorOffset,
|
|
513
|
+
relativePositionRatio: monitorOffsetRatio
|
|
514
|
+
} = getRelativePosition(absolutePosition, monitorEntry.measurements);
|
|
515
|
+
if (receiverId && !cancelled) {
|
|
516
|
+
const receiverData = buildReceiverViewData(receiverId, absolutePosition);
|
|
517
|
+
if (receiverData) {
|
|
518
|
+
const monitorDropResponse = monitorEntry.props.onMonitorDragDrop?.({
|
|
519
|
+
...baseEventData,
|
|
520
|
+
receiver: receiverData,
|
|
521
|
+
monitorOffset,
|
|
522
|
+
monitorOffsetRatio
|
|
523
|
+
});
|
|
524
|
+
if (monitorDropResponse !== undefined) snapTarget = monitorDropResponse;
|
|
525
|
+
}
|
|
526
|
+
} else {
|
|
527
|
+
const monitorEndResponse = monitorEntry.props.onMonitorDragEnd?.({
|
|
528
|
+
...baseEventData,
|
|
529
|
+
monitorOffset,
|
|
530
|
+
monitorOffsetRatio,
|
|
531
|
+
cancelled
|
|
532
|
+
});
|
|
533
|
+
if (monitorEndResponse !== undefined) snapTarget = monitorEndResponse;
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Resolve Default snap target to root-relative visual position.
|
|
538
|
+
// Default triggers when monitors are empty or all callbacks return undefined.
|
|
539
|
+
// draggedEntry.measurements are content-relative (from measureLayout), so we
|
|
540
|
+
// use the spatial index to compute root-relative visual position instead.
|
|
541
|
+
if (snapTarget === DraxSnapbackTargetPreset.Default) {
|
|
542
|
+
const absPos = computeAbsolutePositionWorklet(draggedEntry.spatialIndex, spatialIndexSV.value, scrollOffsetsSV.value);
|
|
543
|
+
snapTarget = absPos;
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
// Handle snap-back animation
|
|
547
|
+
performSnapback(snapTarget, draggedEntry, receiverId ? getViewEntry(receiverId) : undefined, hoverPositionSV, dragPhaseSV, draggedIdSV, hoverReadySV, setHoverContent, deps.hoverClearDeferredRef);
|
|
548
|
+
|
|
549
|
+
// Fire provider-level onDragEnd (use last known hover position)
|
|
550
|
+
onProviderDragEnd?.({
|
|
551
|
+
draggedId,
|
|
552
|
+
receiverId: receiverId || undefined,
|
|
553
|
+
position: hoverPositionSV.value,
|
|
554
|
+
cancelled
|
|
555
|
+
});
|
|
556
|
+
};
|
|
557
|
+
return {
|
|
558
|
+
handleDragStart,
|
|
559
|
+
handleReceiverChange,
|
|
560
|
+
handleDragEnd
|
|
561
|
+
};
|
|
562
|
+
};
|
|
563
|
+
|
|
564
|
+
/**
|
|
565
|
+
* Perform the snap-back animation after drag ends.
|
|
566
|
+
*
|
|
567
|
+
* CRITICAL ORDERING: When the snap animation completes, we must:
|
|
568
|
+
* 1. Fire onSnapEnd callbacks → triggers finalizeDrag → commits reorder / cancels drag
|
|
569
|
+
* 2. THEN clear drag state (hover disappears, item becomes visible)
|
|
570
|
+
*
|
|
571
|
+
* This ordering ensures shifted items are cleaned up BEFORE the hover disappears.
|
|
572
|
+
* Without this, there's a visible gap where shifted items are at shifted positions
|
|
573
|
+
* but the hover is already gone (the "drop blink").
|
|
574
|
+
*/
|
|
575
|
+
function performSnapback(target, draggedEntry, receiverEntry, hoverPositionSV, dragPhaseSV, draggedIdSV, hoverReadySV, setHoverContent, hoverClearDeferredRef) {
|
|
576
|
+
const animateSnap = draggedEntry.props.animateSnap ?? true;
|
|
577
|
+
const snapDelay = draggedEntry.props.snapDelay ?? defaultSnapbackDelay;
|
|
578
|
+
const snapDuration = draggedEntry.props.snapDuration ?? defaultSnapbackDuration;
|
|
579
|
+
const snapAnimator = draggedEntry.props.snapAnimator;
|
|
580
|
+
|
|
581
|
+
// Build snap event data for callbacks
|
|
582
|
+
const snapEventData = {
|
|
583
|
+
dragged: {
|
|
584
|
+
id: draggedEntry.id,
|
|
585
|
+
parentId: draggedEntry.parentId,
|
|
586
|
+
payload: draggedEntry.props.dragPayload ?? draggedEntry.props.payload
|
|
587
|
+
},
|
|
588
|
+
receiver: receiverEntry ? {
|
|
589
|
+
id: receiverEntry.id,
|
|
590
|
+
parentId: receiverEntry.parentId,
|
|
591
|
+
payload: receiverEntry.props.receiverPayload ?? receiverEntry.props.payload
|
|
592
|
+
} : undefined
|
|
593
|
+
};
|
|
594
|
+
|
|
595
|
+
/**
|
|
596
|
+
* Called when snap animation completes. Fires callbacks FIRST (so finalizeDrag
|
|
597
|
+
* can set permanent shifts + clear hover), THEN clears hover & drag state.
|
|
598
|
+
*
|
|
599
|
+
* For REORDER: finalizeDrag sets permanent shifts + clears hover via runOnUI
|
|
600
|
+
* in a single atomic block. No FlatList data change, so no blink.
|
|
601
|
+
*
|
|
602
|
+
* For CANCEL: finalizeDrag → cancelDrag → reverts to committed shifts.
|
|
603
|
+
* Then hover clears on next UI frame. Items at visual positions. No blink.
|
|
604
|
+
*/
|
|
605
|
+
const onSnapComplete = () => {
|
|
606
|
+
// Reset the deferred flag before firing callbacks.
|
|
607
|
+
// finalizeDrag (called via onSnapEnd) may set it to true for reorder.
|
|
608
|
+
hoverClearDeferredRef.current = false;
|
|
609
|
+
|
|
610
|
+
// Step 1: Fire callbacks → finalizeDrag runs synchronously.
|
|
611
|
+
draggedEntry.props.onSnapEnd?.(snapEventData);
|
|
612
|
+
receiverEntry?.props.onReceiveSnapEnd?.(snapEventData);
|
|
613
|
+
|
|
614
|
+
// Step 2: Clear hover if NOT deferred by a sortable reorder.
|
|
615
|
+
if (!hoverClearDeferredRef.current) {
|
|
616
|
+
runOnUI((_hoverReadySV, _dragPhaseSV, _draggedIdSV, _hoverPositionSV) => {
|
|
617
|
+
'worklet';
|
|
618
|
+
|
|
619
|
+
_hoverReadySV.value = false;
|
|
620
|
+
_dragPhaseSV.value = 'idle';
|
|
621
|
+
_draggedIdSV.value = '';
|
|
622
|
+
_hoverPositionSV.value = {
|
|
623
|
+
x: 0,
|
|
624
|
+
y: 0
|
|
625
|
+
};
|
|
626
|
+
})(hoverReadySV, dragPhaseSV, draggedIdSV, hoverPositionSV);
|
|
627
|
+
setHoverContent(null);
|
|
628
|
+
} else {
|
|
629
|
+
// Do NOT call setHoverContent(null) here — the hover must remain visible
|
|
630
|
+
// until the FlatList re-renders. The deferred cleanup in useLayoutEffect
|
|
631
|
+
// will clear SharedValues, and setHoverContent(null) is called there too.
|
|
632
|
+
}
|
|
633
|
+
};
|
|
634
|
+
if (target === DraxSnapbackTargetPreset.None || !animateSnap) {
|
|
635
|
+
// No snap animation — run cleanup immediately
|
|
636
|
+
onSnapComplete();
|
|
637
|
+
return;
|
|
638
|
+
}
|
|
639
|
+
|
|
640
|
+
// Determine snap-to position
|
|
641
|
+
let toValue;
|
|
642
|
+
if (isPosition(target)) {
|
|
643
|
+
toValue = target;
|
|
644
|
+
} else {
|
|
645
|
+
// Default: snap back to original view position
|
|
646
|
+
toValue = draggedEntry.measurements ? {
|
|
647
|
+
x: draggedEntry.measurements.x,
|
|
648
|
+
y: draggedEntry.measurements.y
|
|
649
|
+
} : {
|
|
650
|
+
x: 0,
|
|
651
|
+
y: 0
|
|
652
|
+
};
|
|
653
|
+
}
|
|
654
|
+
if (snapAnimator) {
|
|
655
|
+
// Custom snap animation
|
|
656
|
+
snapAnimator({
|
|
657
|
+
hoverPosition: hoverPositionSV,
|
|
658
|
+
toValue,
|
|
659
|
+
delay: snapDelay,
|
|
660
|
+
duration: snapDuration,
|
|
661
|
+
finishedCallback: finished => {
|
|
662
|
+
if (finished) {
|
|
663
|
+
onSnapComplete();
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
});
|
|
667
|
+
} else {
|
|
668
|
+
// Default withTiming snap animation.
|
|
669
|
+
// When animation finishes, bounce to JS for ordered cleanup.
|
|
670
|
+
hoverPositionSV.value = withDelay(snapDelay, withTiming(toValue, {
|
|
671
|
+
duration: snapDuration
|
|
672
|
+
}, finished => {
|
|
673
|
+
'worklet';
|
|
674
|
+
|
|
675
|
+
if (finished) {
|
|
676
|
+
runOnJS(onSnapComplete)();
|
|
677
|
+
}
|
|
678
|
+
}));
|
|
679
|
+
}
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
// ─── Helpers ───────────────────────────────────────────────────────────────
|
|
683
|
+
|
|
684
|
+
function flattenOrNull(s) {
|
|
685
|
+
if (!s) return null;
|
|
686
|
+
return StyleSheet.flatten(s) ?? null;
|
|
687
|
+
}
|
|
688
|
+
//# sourceMappingURL=useCallbackDispatch.js.map
|