react-native-swappable-grid 1.0.12 → 1.0.13
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +11 -0
- package/lib/commonjs/utils/helpers/gestures/PanWithLongPress.js +48 -12
- package/lib/commonjs/utils/helpers/gestures/PanWithLongPress.js.map +1 -1
- package/lib/module/utils/helpers/gestures/PanWithLongPress.js +48 -12
- package/lib/module/utils/helpers/gestures/PanWithLongPress.js.map +1 -1
- package/package.json +1 -1
- package/src/utils/helpers/gestures/PanWithLongPress.ts +64 -12
package/README.md
CHANGED
|
@@ -31,6 +31,17 @@ A powerful React Native component for creating draggable, swappable grid layouts
|
|
|
31
31
|
</br>
|
|
32
32
|
</br>
|
|
33
33
|
|
|
34
|
+
<h3 align="center">
|
|
35
|
+
Autoscroll near edges
|
|
36
|
+
</h3>
|
|
37
|
+
|
|
38
|
+
<div align="center">
|
|
39
|
+
<img src="./assets/autoscroll-near-edges.gif" width="300" height="auto" />
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
</br>
|
|
43
|
+
</br>
|
|
44
|
+
|
|
34
45
|
<h3 align="center">
|
|
35
46
|
Add new items
|
|
36
47
|
</h3>
|
|
@@ -48,18 +48,30 @@ const PanWithLongPress = props => {
|
|
|
48
48
|
if (!p) return;
|
|
49
49
|
|
|
50
50
|
// 1. Clamp scroll offset
|
|
51
|
-
|
|
51
|
+
// Account for contentPaddingBottom in max scroll calculation
|
|
52
|
+
// The ScrollView's contentContainerStyle paddingBottom adds to scrollable content
|
|
53
|
+
const maxScroll = Math.max(0, contentH.value + contentPaddingBottom - viewportH.value);
|
|
52
54
|
const newScroll = Math.max(0, Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll));
|
|
53
55
|
(0, _reactNativeReanimated.scrollTo)(scrollViewRef, 0, newScroll, false);
|
|
54
56
|
const scrollDelta = newScroll - initialScrollOffset.value;
|
|
55
57
|
scrollOffset.value = newScroll;
|
|
56
58
|
|
|
57
|
-
// 2. Clamp item position
|
|
58
|
-
//
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
const
|
|
59
|
+
// 2. Clamp item position to stay within visible viewport and content bounds
|
|
60
|
+
// This runs every frame for auto-scroll adjustments
|
|
61
|
+
// Use the same clamping logic as onUpdate for consistency
|
|
62
|
+
const minY = scrollOffset.value;
|
|
63
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
64
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
65
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
66
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
67
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
68
|
+
const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);
|
|
69
|
+
const contentMaxY = contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
70
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
71
|
+
|
|
72
|
+
// Calculate position accounting for scroll delta (for auto-scroll)
|
|
62
73
|
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
74
|
+
// Clamp the position
|
|
63
75
|
p.y.value = Math.max(minY, Math.min(proposedY, maxY));
|
|
64
76
|
|
|
65
77
|
// X stays normal
|
|
@@ -119,14 +131,38 @@ const PanWithLongPress = props => {
|
|
|
119
131
|
// Update active (top-left)
|
|
120
132
|
offsetX.value = translationX;
|
|
121
133
|
offsetY.value = translationY;
|
|
122
|
-
p.x.value = startX.value + offsetX.value;
|
|
123
|
-
p.y.value = startY.value + offsetY.value + scrollDelta;
|
|
124
134
|
|
|
125
|
-
//
|
|
126
|
-
const
|
|
127
|
-
|
|
135
|
+
// Calculate proposed position
|
|
136
|
+
const proposedX = startX.value + offsetX.value;
|
|
137
|
+
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
138
|
+
|
|
139
|
+
// Clamp Y position immediately to prevent visual glitch
|
|
140
|
+
const minY = scrollOffset.value;
|
|
141
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
142
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
143
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
144
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
145
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
146
|
+
const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);
|
|
147
|
+
const contentMaxY = contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
148
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
149
|
+
const clampedY = Math.max(minY, Math.min(proposedY, maxY));
|
|
150
|
+
p.x.value = proposedX;
|
|
151
|
+
p.y.value = clampedY;
|
|
152
|
+
|
|
153
|
+
// Auto-scroll: Use item's center (or bottom edge if very large) to detect proximity to edges
|
|
154
|
+
// This ensures scrolling works even for very large items
|
|
155
|
+
const itemTopInViewport = p.y.value - scrollOffset.value;
|
|
156
|
+
const itemBottomInViewport = itemTopInViewport + itemHeight;
|
|
157
|
+
const itemCenterInViewport = itemTopInViewport + itemHeight / 2;
|
|
158
|
+
|
|
159
|
+
// For large items, check if any part is near the edge
|
|
160
|
+
// For small items, use center for more intuitive behavior
|
|
161
|
+
const nearBottom = itemHeight > viewportH.value * 0.5 ? itemBottomInViewport > viewportH.value - scrollThreshold : itemCenterInViewport > viewportH.value - scrollThreshold;
|
|
162
|
+
const nearTop = itemHeight > viewportH.value * 0.5 ? itemTopInViewport < scrollThreshold : itemCenterInViewport < scrollThreshold;
|
|
163
|
+
if (nearBottom) {
|
|
128
164
|
scrollDir.value = 1;
|
|
129
|
-
} else if (
|
|
165
|
+
} else if (nearTop) {
|
|
130
166
|
scrollDir.value = -1;
|
|
131
167
|
} else {
|
|
132
168
|
scrollDir.value = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["_reactNativeGestureHandler","require","_reactNativeReanimated","_indexCalculations","PanWithLongPress","props","order","dynamicNumColumns","activeKey","offsetX","offsetY","startX","startY","dragMode","positions","itemsByKey","itemWidth","itemHeight","containerPadding","gap","setOrderState","onDragEnd","onOrderChange","scrollSpeed","scrollThreshold","scrollViewRef","scrollOffset","viewportH","holdToDragMs","contentH","reverse","deleteComponentPosition","deleteItem","contentPaddingBottom","scrollDir","useSharedValue","initialScrollOffset","useDerivedValue","value","key","p","maxScroll","newScroll","Math","max","min","scrollTo","scrollDelta","minY","maxY","proposedY","y","x","requestAnimationFrame","getIndexOfKey","findIndex","Gesture","Pan","minDistance","activateAfterLongPress","onStart","bestKey","bestDist","Number","MAX_VALUE","forEach","cx","cy","dx","dy","dist2","active","withTiming","duration","onUpdate","translationX","translationY","pointerYInViewport","centerY","fromIndex","toIndex","toIndex1ColFromLiveMidlines","centerX","xyToIndex","length","next","splice","onEnd","deletePos","tolerance","expandedDeleteX","expandedDeleteY","expandedDeleteWidth","width","expandedDeleteHeight","height","itemLeft","itemRight","itemTop","itemBottom","overlaps","runOnJS","idx","indexToXY","index","scale","damping","stiffness","mass","withSpring","map","exports"],"sources":["PanWithLongPress.ts"],"sourcesContent":["import { Gesture } from \"react-native-gesture-handler\";\nimport {\n runOnJS,\n SharedValue,\n withSpring,\n withTiming,\n scrollTo,\n AnimatedRef,\n useSharedValue,\n useDerivedValue,\n} from \"react-native-reanimated\";\nimport {\n indexToXY,\n toIndex1ColFromLiveMidlines,\n xyToIndex,\n} from \"../indexCalculations\";\n\ninterface PanProps {\n order: SharedValue<string[]>;\n dynamicNumColumns: SharedValue<number>;\n activeKey: SharedValue<string | null>;\n offsetX: SharedValue<number>;\n offsetY: SharedValue<number>;\n startX: SharedValue<number>;\n startY: SharedValue<number>;\n dragMode: SharedValue<boolean>;\n positions: any;\n itemsByKey: any;\n itemWidth: number;\n itemHeight: number;\n containerPadding: number;\n gap: number;\n setOrderState: React.Dispatch<React.SetStateAction<string[]>>;\n onDragEnd?: (ordered: ChildNode[]) => void;\n onOrderChange?: (keys: string[]) => void;\n\n // scrolling\n scrollSpeed: number;\n scrollThreshold: number;\n scrollViewRef: AnimatedRef<any>;\n scrollOffset: SharedValue<number>;\n viewportH: SharedValue<number>;\n holdToDragMs: number;\n contentH: SharedValue<number>;\n reverse?: boolean;\n deleteComponentPosition?: SharedValue<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>;\n deleteItem?: (key: string) => void;\n contentPaddingBottom?: number; // Padding bottom from style prop to allow dragging into padding area\n}\n\nexport const PanWithLongPress = (\n props: PanProps & { holdToDragMs: number }\n) => {\n const {\n order,\n dynamicNumColumns,\n activeKey,\n offsetX,\n offsetY,\n startX,\n startY,\n dragMode,\n positions,\n itemsByKey,\n itemWidth,\n itemHeight,\n containerPadding,\n gap,\n setOrderState,\n onDragEnd,\n onOrderChange,\n scrollSpeed,\n scrollThreshold,\n scrollViewRef,\n scrollOffset,\n viewportH,\n holdToDragMs,\n contentH,\n reverse = false,\n deleteComponentPosition,\n deleteItem,\n contentPaddingBottom = 0,\n } = props;\n\n const scrollDir = useSharedValue(0); // -1 = up, 1 = down, 0 = none\n const initialScrollOffset = useSharedValue(0);\n\n useDerivedValue(() => {\n if (!dragMode.value || !activeKey.value) return;\n\n if (viewportH.value <= 0 || contentH.value <= 0) return;\n\n const key = activeKey.value;\n const p = positions[key];\n if (!p) return;\n\n // 1. Clamp scroll offset\n const maxScroll = contentH.value - viewportH.value;\n const newScroll = Math.max(\n 0,\n Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll)\n );\n\n scrollTo(scrollViewRef, 0, newScroll, false);\n const scrollDelta = newScroll - initialScrollOffset.value;\n scrollOffset.value = newScroll;\n\n // 2. Clamp item position\n // Allow dragging into padding area (paddingBottom from style prop)\n const minY = 0;\n // Add paddingBottom to maxY to allow dragging into the padding area\n const maxY = contentH.value - itemHeight + contentPaddingBottom;\n const proposedY = startY.value + offsetY.value + scrollDelta;\n p.y.value = Math.max(minY, Math.min(proposedY, maxY));\n\n // X stays normal\n p.x.value = startX.value + offsetX.value;\n\n // Keep loop alive\n requestAnimationFrame(() => {\n scrollDir.value = scrollDir.value;\n });\n });\n\n const getIndexOfKey = (key: string) => {\n \"worklet\";\n return order.value.findIndex((x) => x === key);\n };\n\n return Gesture.Pan()\n .minDistance(10)\n .activateAfterLongPress(holdToDragMs)\n .onStart(({ x, y }) => {\n initialScrollOffset.value = scrollOffset.value;\n dragMode.value = true;\n let bestKey: string | null = null;\n let bestDist = Number.MAX_VALUE;\n order.value.forEach((key) => {\n const p = positions[key];\n if (!p) return;\n const cx = p.x.value + itemWidth / 2;\n const cy = p.y.value + itemHeight / 2;\n const dx = cx - x;\n const dy = cy - y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 < bestDist) {\n bestDist = dist2;\n bestKey = key;\n }\n });\n if (!bestKey) return;\n activeKey.value = bestKey;\n const p = positions[bestKey]!;\n p.active.value = withTiming(1, { duration: 120 });\n startX.value = p.x.value;\n startY.value = p.y.value;\n offsetX.value = 0;\n offsetY.value = 0;\n })\n .onUpdate(({ translationX, translationY }) => {\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) return;\n\n const p = positions[key]!;\n const scrollDelta = scrollOffset.value - initialScrollOffset.value;\n\n // Update active (top-left)\n offsetX.value = translationX;\n offsetY.value = translationY;\n p.x.value = startX.value + offsetX.value;\n p.y.value = startY.value + offsetY.value + scrollDelta;\n\n // Auto-scroll (unchanged)\n const pointerYInViewport = p.y.value - scrollOffset.value;\n if (pointerYInViewport > viewportH.value - scrollThreshold) {\n scrollDir.value = 1;\n } else if (pointerYInViewport < scrollThreshold) {\n scrollDir.value = -1;\n } else {\n scrollDir.value = 0;\n }\n\n // Compute target index from the active tile's **center**\n const centerY = p.y.value + itemHeight / 2;\n const fromIndex = getIndexOfKey(key);\n\n let toIndex: number;\n if (dynamicNumColumns.value === 1) {\n toIndex = toIndex1ColFromLiveMidlines(\n order,\n positions,\n activeKey,\n itemHeight,\n centerY,\n reverse // ← pass your prop\n );\n } else {\n // unchanged multi-column path\n const centerX = p.x.value + itemWidth / 2;\n toIndex = xyToIndex({\n order,\n x: centerX,\n y: centerY,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n }\n\n if (\n toIndex !== fromIndex &&\n toIndex >= 0 &&\n toIndex <= order.value.length - 1\n ) {\n const next = [...order.value];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, key);\n order.value = next;\n }\n })\n .onEnd(() => {\n scrollDir.value = 0; // stop auto-scroll\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) {\n dragMode.value = false;\n return;\n }\n const p = positions[key]!;\n\n // Check if item was dropped into delete component\n if (deleteComponentPosition?.value && deleteItem) {\n const deletePos = deleteComponentPosition.value;\n\n // Add tolerance/padding to make it easier to hit (20% of item size)\n const tolerance = Math.min(itemWidth, itemHeight) * 0.2;\n const expandedDeleteX = deletePos.x - tolerance;\n const expandedDeleteY = deletePos.y - tolerance;\n const expandedDeleteWidth = deletePos.width + tolerance * 2;\n const expandedDeleteHeight = deletePos.height + tolerance * 2;\n\n // Check if item bounding box overlaps with expanded delete component bounds\n // This is more forgiving than checking just the center point\n const itemLeft = p.x.value;\n const itemRight = p.x.value + itemWidth;\n const itemTop = p.y.value;\n const itemBottom = p.y.value + itemHeight;\n\n // Bounding box intersection check\n const overlaps =\n itemLeft < expandedDeleteX + expandedDeleteWidth &&\n itemRight > expandedDeleteX &&\n itemTop < expandedDeleteY + expandedDeleteHeight &&\n itemBottom > expandedDeleteY;\n\n if (overlaps) {\n // Item was dropped into delete component - delete it\n runOnJS(deleteItem)(key);\n // Note: deleteItem will handle calling onDelete callback if provided\n p.active.value = withTiming(0, { duration: 120 });\n activeKey.value = null;\n dragMode.value = false;\n return;\n }\n }\n\n // Normal drop - return to grid position\n const idx = getIndexOfKey(key);\n const { x, y } = indexToXY({\n index: idx,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n const scale = Math.min(itemWidth, itemHeight) / 200; // 100px baseline\n\n const damping = 18 * scale;\n const stiffness = 240 * scale;\n const mass = Math.max(0.05, scale); // helps stability for tiny items\n\n p.x.value = withSpring(x, { damping, stiffness, mass });\n p.y.value = withSpring(y, { damping, stiffness, mass });\n\n p.active.value = withTiming(0, { duration: 120 });\n\n runOnJS(setOrderState)(order.value);\n if (onDragEnd) {\n runOnJS(onDragEnd)(order.value.map((key) => itemsByKey[key]));\n }\n if (onOrderChange) {\n runOnJS(onOrderChange)([...order.value]);\n }\n activeKey.value = null;\n dragMode.value = false;\n });\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["_reactNativeGestureHandler","require","_reactNativeReanimated","_indexCalculations","PanWithLongPress","props","order","dynamicNumColumns","activeKey","offsetX","offsetY","startX","startY","dragMode","positions","itemsByKey","itemWidth","itemHeight","containerPadding","gap","setOrderState","onDragEnd","onOrderChange","scrollSpeed","scrollThreshold","scrollViewRef","scrollOffset","viewportH","holdToDragMs","contentH","reverse","deleteComponentPosition","deleteItem","contentPaddingBottom","scrollDir","useSharedValue","initialScrollOffset","useDerivedValue","value","key","p","maxScroll","Math","max","newScroll","min","scrollTo","scrollDelta","minY","visibleMaxY","paddingAllowance","contentMaxY","maxY","proposedY","y","x","requestAnimationFrame","getIndexOfKey","findIndex","Gesture","Pan","minDistance","activateAfterLongPress","onStart","bestKey","bestDist","Number","MAX_VALUE","forEach","cx","cy","dx","dy","dist2","active","withTiming","duration","onUpdate","translationX","translationY","proposedX","clampedY","itemTopInViewport","itemBottomInViewport","itemCenterInViewport","nearBottom","nearTop","centerY","fromIndex","toIndex","toIndex1ColFromLiveMidlines","centerX","xyToIndex","length","next","splice","onEnd","deletePos","tolerance","expandedDeleteX","expandedDeleteY","expandedDeleteWidth","width","expandedDeleteHeight","height","itemLeft","itemRight","itemTop","itemBottom","overlaps","runOnJS","idx","indexToXY","index","scale","damping","stiffness","mass","withSpring","map","exports"],"sources":["PanWithLongPress.ts"],"sourcesContent":["import { Gesture } from \"react-native-gesture-handler\";\nimport {\n runOnJS,\n SharedValue,\n withSpring,\n withTiming,\n scrollTo,\n AnimatedRef,\n useSharedValue,\n useDerivedValue,\n} from \"react-native-reanimated\";\nimport {\n indexToXY,\n toIndex1ColFromLiveMidlines,\n xyToIndex,\n} from \"../indexCalculations\";\n\ninterface PanProps {\n order: SharedValue<string[]>;\n dynamicNumColumns: SharedValue<number>;\n activeKey: SharedValue<string | null>;\n offsetX: SharedValue<number>;\n offsetY: SharedValue<number>;\n startX: SharedValue<number>;\n startY: SharedValue<number>;\n dragMode: SharedValue<boolean>;\n positions: any;\n itemsByKey: any;\n itemWidth: number;\n itemHeight: number;\n containerPadding: number;\n gap: number;\n setOrderState: React.Dispatch<React.SetStateAction<string[]>>;\n onDragEnd?: (ordered: ChildNode[]) => void;\n onOrderChange?: (keys: string[]) => void;\n\n // scrolling\n scrollSpeed: number;\n scrollThreshold: number;\n scrollViewRef: AnimatedRef<any>;\n scrollOffset: SharedValue<number>;\n viewportH: SharedValue<number>;\n holdToDragMs: number;\n contentH: SharedValue<number>;\n reverse?: boolean;\n deleteComponentPosition?: SharedValue<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>;\n deleteItem?: (key: string) => void;\n contentPaddingBottom?: number; // Padding bottom from style prop to allow dragging into padding area\n}\n\nexport const PanWithLongPress = (\n props: PanProps & { holdToDragMs: number }\n) => {\n const {\n order,\n dynamicNumColumns,\n activeKey,\n offsetX,\n offsetY,\n startX,\n startY,\n dragMode,\n positions,\n itemsByKey,\n itemWidth,\n itemHeight,\n containerPadding,\n gap,\n setOrderState,\n onDragEnd,\n onOrderChange,\n scrollSpeed,\n scrollThreshold,\n scrollViewRef,\n scrollOffset,\n viewportH,\n holdToDragMs,\n contentH,\n reverse = false,\n deleteComponentPosition,\n deleteItem,\n contentPaddingBottom = 0,\n } = props;\n\n const scrollDir = useSharedValue(0); // -1 = up, 1 = down, 0 = none\n const initialScrollOffset = useSharedValue(0);\n\n useDerivedValue(() => {\n if (!dragMode.value || !activeKey.value) return;\n\n if (viewportH.value <= 0 || contentH.value <= 0) return;\n\n const key = activeKey.value;\n const p = positions[key];\n if (!p) return;\n\n // 1. Clamp scroll offset\n // Account for contentPaddingBottom in max scroll calculation\n // The ScrollView's contentContainerStyle paddingBottom adds to scrollable content\n const maxScroll = Math.max(\n 0,\n contentH.value + contentPaddingBottom - viewportH.value\n );\n const newScroll = Math.max(\n 0,\n Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll)\n );\n\n scrollTo(scrollViewRef, 0, newScroll, false);\n const scrollDelta = newScroll - initialScrollOffset.value;\n scrollOffset.value = newScroll;\n\n // 2. Clamp item position to stay within visible viewport and content bounds\n // This runs every frame for auto-scroll adjustments\n // Use the same clamping logic as onUpdate for consistency\n const minY = scrollOffset.value;\n const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;\n // Items are positioned starting at containerPadding, so the last item's bottom\n // should be at contentH - containerPadding. But we also need to account for\n // the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.\n // Allow items to extend slightly into the padding area for better UX.\n const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);\n const contentMaxY =\n contentH.value - containerPadding - itemHeight + paddingAllowance;\n const maxY = Math.min(visibleMaxY, contentMaxY);\n\n // Calculate position accounting for scroll delta (for auto-scroll)\n const proposedY = startY.value + offsetY.value + scrollDelta;\n // Clamp the position\n p.y.value = Math.max(minY, Math.min(proposedY, maxY));\n\n // X stays normal\n p.x.value = startX.value + offsetX.value;\n\n // Keep loop alive\n requestAnimationFrame(() => {\n scrollDir.value = scrollDir.value;\n });\n });\n\n const getIndexOfKey = (key: string) => {\n \"worklet\";\n return order.value.findIndex((x) => x === key);\n };\n\n return Gesture.Pan()\n .minDistance(10)\n .activateAfterLongPress(holdToDragMs)\n .onStart(({ x, y }) => {\n initialScrollOffset.value = scrollOffset.value;\n dragMode.value = true;\n let bestKey: string | null = null;\n let bestDist = Number.MAX_VALUE;\n order.value.forEach((key) => {\n const p = positions[key];\n if (!p) return;\n const cx = p.x.value + itemWidth / 2;\n const cy = p.y.value + itemHeight / 2;\n const dx = cx - x;\n const dy = cy - y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 < bestDist) {\n bestDist = dist2;\n bestKey = key;\n }\n });\n if (!bestKey) return;\n activeKey.value = bestKey;\n const p = positions[bestKey]!;\n p.active.value = withTiming(1, { duration: 120 });\n startX.value = p.x.value;\n startY.value = p.y.value;\n offsetX.value = 0;\n offsetY.value = 0;\n })\n .onUpdate(({ translationX, translationY }) => {\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) return;\n\n const p = positions[key]!;\n const scrollDelta = scrollOffset.value - initialScrollOffset.value;\n\n // Update active (top-left)\n offsetX.value = translationX;\n offsetY.value = translationY;\n\n // Calculate proposed position\n const proposedX = startX.value + offsetX.value;\n const proposedY = startY.value + offsetY.value + scrollDelta;\n\n // Clamp Y position immediately to prevent visual glitch\n const minY = scrollOffset.value;\n const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;\n // Items are positioned starting at containerPadding, so the last item's bottom\n // should be at contentH - containerPadding. But we also need to account for\n // the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.\n // Allow items to extend slightly into the padding area for better UX.\n const paddingAllowance = Math.min(\n contentPaddingBottom,\n itemHeight * 0.75\n );\n const contentMaxY =\n contentH.value - containerPadding - itemHeight + paddingAllowance;\n const maxY = Math.min(visibleMaxY, contentMaxY);\n const clampedY = Math.max(minY, Math.min(proposedY, maxY));\n\n p.x.value = proposedX;\n p.y.value = clampedY;\n\n // Auto-scroll: Use item's center (or bottom edge if very large) to detect proximity to edges\n // This ensures scrolling works even for very large items\n const itemTopInViewport = p.y.value - scrollOffset.value;\n const itemBottomInViewport = itemTopInViewport + itemHeight;\n const itemCenterInViewport = itemTopInViewport + itemHeight / 2;\n\n // For large items, check if any part is near the edge\n // For small items, use center for more intuitive behavior\n const nearBottom =\n itemHeight > viewportH.value * 0.5\n ? itemBottomInViewport > viewportH.value - scrollThreshold\n : itemCenterInViewport > viewportH.value - scrollThreshold;\n const nearTop =\n itemHeight > viewportH.value * 0.5\n ? itemTopInViewport < scrollThreshold\n : itemCenterInViewport < scrollThreshold;\n\n if (nearBottom) {\n scrollDir.value = 1;\n } else if (nearTop) {\n scrollDir.value = -1;\n } else {\n scrollDir.value = 0;\n }\n\n // Compute target index from the active tile's **center**\n const centerY = p.y.value + itemHeight / 2;\n const fromIndex = getIndexOfKey(key);\n\n let toIndex: number;\n if (dynamicNumColumns.value === 1) {\n toIndex = toIndex1ColFromLiveMidlines(\n order,\n positions,\n activeKey,\n itemHeight,\n centerY,\n reverse // ← pass your prop\n );\n } else {\n // unchanged multi-column path\n const centerX = p.x.value + itemWidth / 2;\n toIndex = xyToIndex({\n order,\n x: centerX,\n y: centerY,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n }\n\n if (\n toIndex !== fromIndex &&\n toIndex >= 0 &&\n toIndex <= order.value.length - 1\n ) {\n const next = [...order.value];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, key);\n order.value = next;\n }\n })\n .onEnd(() => {\n scrollDir.value = 0; // stop auto-scroll\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) {\n dragMode.value = false;\n return;\n }\n const p = positions[key]!;\n\n // Check if item was dropped into delete component\n if (deleteComponentPosition?.value && deleteItem) {\n const deletePos = deleteComponentPosition.value;\n\n // Add tolerance/padding to make it easier to hit (20% of item size)\n const tolerance = Math.min(itemWidth, itemHeight) * 0.2;\n const expandedDeleteX = deletePos.x - tolerance;\n const expandedDeleteY = deletePos.y - tolerance;\n const expandedDeleteWidth = deletePos.width + tolerance * 2;\n const expandedDeleteHeight = deletePos.height + tolerance * 2;\n\n // Check if item bounding box overlaps with expanded delete component bounds\n // This is more forgiving than checking just the center point\n const itemLeft = p.x.value;\n const itemRight = p.x.value + itemWidth;\n const itemTop = p.y.value;\n const itemBottom = p.y.value + itemHeight;\n\n // Bounding box intersection check\n const overlaps =\n itemLeft < expandedDeleteX + expandedDeleteWidth &&\n itemRight > expandedDeleteX &&\n itemTop < expandedDeleteY + expandedDeleteHeight &&\n itemBottom > expandedDeleteY;\n\n if (overlaps) {\n // Item was dropped into delete component - delete it\n runOnJS(deleteItem)(key);\n // Note: deleteItem will handle calling onDelete callback if provided\n p.active.value = withTiming(0, { duration: 120 });\n activeKey.value = null;\n dragMode.value = false;\n return;\n }\n }\n\n // Normal drop - return to grid position\n const idx = getIndexOfKey(key);\n const { x, y } = indexToXY({\n index: idx,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n const scale = Math.min(itemWidth, itemHeight) / 200; // 100px baseline\n\n const damping = 18 * scale;\n const stiffness = 240 * scale;\n const mass = Math.max(0.05, scale); // helps stability for tiny items\n\n p.x.value = withSpring(x, { damping, stiffness, mass });\n p.y.value = withSpring(y, { damping, stiffness, mass });\n\n p.active.value = withTiming(0, { duration: 120 });\n\n runOnJS(setOrderState)(order.value);\n if (onDragEnd) {\n runOnJS(onDragEnd)(order.value.map((key) => itemsByKey[key]));\n }\n if (onOrderChange) {\n runOnJS(onOrderChange)([...order.value]);\n }\n activeKey.value = null;\n dragMode.value = false;\n });\n};\n"],"mappings":"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","ignoreList":[]}
|
|
@@ -42,18 +42,30 @@ export const PanWithLongPress = props => {
|
|
|
42
42
|
if (!p) return;
|
|
43
43
|
|
|
44
44
|
// 1. Clamp scroll offset
|
|
45
|
-
|
|
45
|
+
// Account for contentPaddingBottom in max scroll calculation
|
|
46
|
+
// The ScrollView's contentContainerStyle paddingBottom adds to scrollable content
|
|
47
|
+
const maxScroll = Math.max(0, contentH.value + contentPaddingBottom - viewportH.value);
|
|
46
48
|
const newScroll = Math.max(0, Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll));
|
|
47
49
|
scrollTo(scrollViewRef, 0, newScroll, false);
|
|
48
50
|
const scrollDelta = newScroll - initialScrollOffset.value;
|
|
49
51
|
scrollOffset.value = newScroll;
|
|
50
52
|
|
|
51
|
-
// 2. Clamp item position
|
|
52
|
-
//
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
const
|
|
53
|
+
// 2. Clamp item position to stay within visible viewport and content bounds
|
|
54
|
+
// This runs every frame for auto-scroll adjustments
|
|
55
|
+
// Use the same clamping logic as onUpdate for consistency
|
|
56
|
+
const minY = scrollOffset.value;
|
|
57
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
58
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
59
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
60
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
61
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
62
|
+
const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);
|
|
63
|
+
const contentMaxY = contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
64
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
65
|
+
|
|
66
|
+
// Calculate position accounting for scroll delta (for auto-scroll)
|
|
56
67
|
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
68
|
+
// Clamp the position
|
|
57
69
|
p.y.value = Math.max(minY, Math.min(proposedY, maxY));
|
|
58
70
|
|
|
59
71
|
// X stays normal
|
|
@@ -113,14 +125,38 @@ export const PanWithLongPress = props => {
|
|
|
113
125
|
// Update active (top-left)
|
|
114
126
|
offsetX.value = translationX;
|
|
115
127
|
offsetY.value = translationY;
|
|
116
|
-
p.x.value = startX.value + offsetX.value;
|
|
117
|
-
p.y.value = startY.value + offsetY.value + scrollDelta;
|
|
118
128
|
|
|
119
|
-
//
|
|
120
|
-
const
|
|
121
|
-
|
|
129
|
+
// Calculate proposed position
|
|
130
|
+
const proposedX = startX.value + offsetX.value;
|
|
131
|
+
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
132
|
+
|
|
133
|
+
// Clamp Y position immediately to prevent visual glitch
|
|
134
|
+
const minY = scrollOffset.value;
|
|
135
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
136
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
137
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
138
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
139
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
140
|
+
const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);
|
|
141
|
+
const contentMaxY = contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
142
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
143
|
+
const clampedY = Math.max(minY, Math.min(proposedY, maxY));
|
|
144
|
+
p.x.value = proposedX;
|
|
145
|
+
p.y.value = clampedY;
|
|
146
|
+
|
|
147
|
+
// Auto-scroll: Use item's center (or bottom edge if very large) to detect proximity to edges
|
|
148
|
+
// This ensures scrolling works even for very large items
|
|
149
|
+
const itemTopInViewport = p.y.value - scrollOffset.value;
|
|
150
|
+
const itemBottomInViewport = itemTopInViewport + itemHeight;
|
|
151
|
+
const itemCenterInViewport = itemTopInViewport + itemHeight / 2;
|
|
152
|
+
|
|
153
|
+
// For large items, check if any part is near the edge
|
|
154
|
+
// For small items, use center for more intuitive behavior
|
|
155
|
+
const nearBottom = itemHeight > viewportH.value * 0.5 ? itemBottomInViewport > viewportH.value - scrollThreshold : itemCenterInViewport > viewportH.value - scrollThreshold;
|
|
156
|
+
const nearTop = itemHeight > viewportH.value * 0.5 ? itemTopInViewport < scrollThreshold : itemCenterInViewport < scrollThreshold;
|
|
157
|
+
if (nearBottom) {
|
|
122
158
|
scrollDir.value = 1;
|
|
123
|
-
} else if (
|
|
159
|
+
} else if (nearTop) {
|
|
124
160
|
scrollDir.value = -1;
|
|
125
161
|
} else {
|
|
126
162
|
scrollDir.value = 0;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"names":["Gesture","runOnJS","withSpring","withTiming","scrollTo","useSharedValue","useDerivedValue","indexToXY","toIndex1ColFromLiveMidlines","xyToIndex","PanWithLongPress","props","order","dynamicNumColumns","activeKey","offsetX","offsetY","startX","startY","dragMode","positions","itemsByKey","itemWidth","itemHeight","containerPadding","gap","setOrderState","onDragEnd","onOrderChange","scrollSpeed","scrollThreshold","scrollViewRef","scrollOffset","viewportH","holdToDragMs","contentH","reverse","deleteComponentPosition","deleteItem","contentPaddingBottom","scrollDir","initialScrollOffset","value","key","p","maxScroll","newScroll","Math","max","min","scrollDelta","minY","maxY","proposedY","y","x","requestAnimationFrame","getIndexOfKey","findIndex","Pan","minDistance","activateAfterLongPress","onStart","bestKey","bestDist","Number","MAX_VALUE","forEach","cx","cy","dx","dy","dist2","active","duration","onUpdate","translationX","translationY","pointerYInViewport","centerY","fromIndex","toIndex","centerX","length","next","splice","onEnd","deletePos","tolerance","expandedDeleteX","expandedDeleteY","expandedDeleteWidth","width","expandedDeleteHeight","height","itemLeft","itemRight","itemTop","itemBottom","overlaps","idx","index","scale","damping","stiffness","mass","map"],"sources":["PanWithLongPress.ts"],"sourcesContent":["import { Gesture } from \"react-native-gesture-handler\";\nimport {\n runOnJS,\n SharedValue,\n withSpring,\n withTiming,\n scrollTo,\n AnimatedRef,\n useSharedValue,\n useDerivedValue,\n} from \"react-native-reanimated\";\nimport {\n indexToXY,\n toIndex1ColFromLiveMidlines,\n xyToIndex,\n} from \"../indexCalculations\";\n\ninterface PanProps {\n order: SharedValue<string[]>;\n dynamicNumColumns: SharedValue<number>;\n activeKey: SharedValue<string | null>;\n offsetX: SharedValue<number>;\n offsetY: SharedValue<number>;\n startX: SharedValue<number>;\n startY: SharedValue<number>;\n dragMode: SharedValue<boolean>;\n positions: any;\n itemsByKey: any;\n itemWidth: number;\n itemHeight: number;\n containerPadding: number;\n gap: number;\n setOrderState: React.Dispatch<React.SetStateAction<string[]>>;\n onDragEnd?: (ordered: ChildNode[]) => void;\n onOrderChange?: (keys: string[]) => void;\n\n // scrolling\n scrollSpeed: number;\n scrollThreshold: number;\n scrollViewRef: AnimatedRef<any>;\n scrollOffset: SharedValue<number>;\n viewportH: SharedValue<number>;\n holdToDragMs: number;\n contentH: SharedValue<number>;\n reverse?: boolean;\n deleteComponentPosition?: SharedValue<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>;\n deleteItem?: (key: string) => void;\n contentPaddingBottom?: number; // Padding bottom from style prop to allow dragging into padding area\n}\n\nexport const PanWithLongPress = (\n props: PanProps & { holdToDragMs: number }\n) => {\n const {\n order,\n dynamicNumColumns,\n activeKey,\n offsetX,\n offsetY,\n startX,\n startY,\n dragMode,\n positions,\n itemsByKey,\n itemWidth,\n itemHeight,\n containerPadding,\n gap,\n setOrderState,\n onDragEnd,\n onOrderChange,\n scrollSpeed,\n scrollThreshold,\n scrollViewRef,\n scrollOffset,\n viewportH,\n holdToDragMs,\n contentH,\n reverse = false,\n deleteComponentPosition,\n deleteItem,\n contentPaddingBottom = 0,\n } = props;\n\n const scrollDir = useSharedValue(0); // -1 = up, 1 = down, 0 = none\n const initialScrollOffset = useSharedValue(0);\n\n useDerivedValue(() => {\n if (!dragMode.value || !activeKey.value) return;\n\n if (viewportH.value <= 0 || contentH.value <= 0) return;\n\n const key = activeKey.value;\n const p = positions[key];\n if (!p) return;\n\n // 1. Clamp scroll offset\n const maxScroll = contentH.value - viewportH.value;\n const newScroll = Math.max(\n 0,\n Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll)\n );\n\n scrollTo(scrollViewRef, 0, newScroll, false);\n const scrollDelta = newScroll - initialScrollOffset.value;\n scrollOffset.value = newScroll;\n\n // 2. Clamp item position\n // Allow dragging into padding area (paddingBottom from style prop)\n const minY = 0;\n // Add paddingBottom to maxY to allow dragging into the padding area\n const maxY = contentH.value - itemHeight + contentPaddingBottom;\n const proposedY = startY.value + offsetY.value + scrollDelta;\n p.y.value = Math.max(minY, Math.min(proposedY, maxY));\n\n // X stays normal\n p.x.value = startX.value + offsetX.value;\n\n // Keep loop alive\n requestAnimationFrame(() => {\n scrollDir.value = scrollDir.value;\n });\n });\n\n const getIndexOfKey = (key: string) => {\n \"worklet\";\n return order.value.findIndex((x) => x === key);\n };\n\n return Gesture.Pan()\n .minDistance(10)\n .activateAfterLongPress(holdToDragMs)\n .onStart(({ x, y }) => {\n initialScrollOffset.value = scrollOffset.value;\n dragMode.value = true;\n let bestKey: string | null = null;\n let bestDist = Number.MAX_VALUE;\n order.value.forEach((key) => {\n const p = positions[key];\n if (!p) return;\n const cx = p.x.value + itemWidth / 2;\n const cy = p.y.value + itemHeight / 2;\n const dx = cx - x;\n const dy = cy - y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 < bestDist) {\n bestDist = dist2;\n bestKey = key;\n }\n });\n if (!bestKey) return;\n activeKey.value = bestKey;\n const p = positions[bestKey]!;\n p.active.value = withTiming(1, { duration: 120 });\n startX.value = p.x.value;\n startY.value = p.y.value;\n offsetX.value = 0;\n offsetY.value = 0;\n })\n .onUpdate(({ translationX, translationY }) => {\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) return;\n\n const p = positions[key]!;\n const scrollDelta = scrollOffset.value - initialScrollOffset.value;\n\n // Update active (top-left)\n offsetX.value = translationX;\n offsetY.value = translationY;\n p.x.value = startX.value + offsetX.value;\n p.y.value = startY.value + offsetY.value + scrollDelta;\n\n // Auto-scroll (unchanged)\n const pointerYInViewport = p.y.value - scrollOffset.value;\n if (pointerYInViewport > viewportH.value - scrollThreshold) {\n scrollDir.value = 1;\n } else if (pointerYInViewport < scrollThreshold) {\n scrollDir.value = -1;\n } else {\n scrollDir.value = 0;\n }\n\n // Compute target index from the active tile's **center**\n const centerY = p.y.value + itemHeight / 2;\n const fromIndex = getIndexOfKey(key);\n\n let toIndex: number;\n if (dynamicNumColumns.value === 1) {\n toIndex = toIndex1ColFromLiveMidlines(\n order,\n positions,\n activeKey,\n itemHeight,\n centerY,\n reverse // ← pass your prop\n );\n } else {\n // unchanged multi-column path\n const centerX = p.x.value + itemWidth / 2;\n toIndex = xyToIndex({\n order,\n x: centerX,\n y: centerY,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n }\n\n if (\n toIndex !== fromIndex &&\n toIndex >= 0 &&\n toIndex <= order.value.length - 1\n ) {\n const next = [...order.value];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, key);\n order.value = next;\n }\n })\n .onEnd(() => {\n scrollDir.value = 0; // stop auto-scroll\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) {\n dragMode.value = false;\n return;\n }\n const p = positions[key]!;\n\n // Check if item was dropped into delete component\n if (deleteComponentPosition?.value && deleteItem) {\n const deletePos = deleteComponentPosition.value;\n\n // Add tolerance/padding to make it easier to hit (20% of item size)\n const tolerance = Math.min(itemWidth, itemHeight) * 0.2;\n const expandedDeleteX = deletePos.x - tolerance;\n const expandedDeleteY = deletePos.y - tolerance;\n const expandedDeleteWidth = deletePos.width + tolerance * 2;\n const expandedDeleteHeight = deletePos.height + tolerance * 2;\n\n // Check if item bounding box overlaps with expanded delete component bounds\n // This is more forgiving than checking just the center point\n const itemLeft = p.x.value;\n const itemRight = p.x.value + itemWidth;\n const itemTop = p.y.value;\n const itemBottom = p.y.value + itemHeight;\n\n // Bounding box intersection check\n const overlaps =\n itemLeft < expandedDeleteX + expandedDeleteWidth &&\n itemRight > expandedDeleteX &&\n itemTop < expandedDeleteY + expandedDeleteHeight &&\n itemBottom > expandedDeleteY;\n\n if (overlaps) {\n // Item was dropped into delete component - delete it\n runOnJS(deleteItem)(key);\n // Note: deleteItem will handle calling onDelete callback if provided\n p.active.value = withTiming(0, { duration: 120 });\n activeKey.value = null;\n dragMode.value = false;\n return;\n }\n }\n\n // Normal drop - return to grid position\n const idx = getIndexOfKey(key);\n const { x, y } = indexToXY({\n index: idx,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n const scale = Math.min(itemWidth, itemHeight) / 200; // 100px baseline\n\n const damping = 18 * scale;\n const stiffness = 240 * scale;\n const mass = Math.max(0.05, scale); // helps stability for tiny items\n\n p.x.value = withSpring(x, { damping, stiffness, mass });\n p.y.value = withSpring(y, { damping, stiffness, mass });\n\n p.active.value = withTiming(0, { duration: 120 });\n\n runOnJS(setOrderState)(order.value);\n if (onDragEnd) {\n runOnJS(onDragEnd)(order.value.map((key) => itemsByKey[key]));\n }\n if (onOrderChange) {\n runOnJS(onOrderChange)([...order.value]);\n }\n activeKey.value = null;\n dragMode.value = false;\n });\n};\n"],"mappings":"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","ignoreList":[]}
|
|
1
|
+
{"version":3,"names":["Gesture","runOnJS","withSpring","withTiming","scrollTo","useSharedValue","useDerivedValue","indexToXY","toIndex1ColFromLiveMidlines","xyToIndex","PanWithLongPress","props","order","dynamicNumColumns","activeKey","offsetX","offsetY","startX","startY","dragMode","positions","itemsByKey","itemWidth","itemHeight","containerPadding","gap","setOrderState","onDragEnd","onOrderChange","scrollSpeed","scrollThreshold","scrollViewRef","scrollOffset","viewportH","holdToDragMs","contentH","reverse","deleteComponentPosition","deleteItem","contentPaddingBottom","scrollDir","initialScrollOffset","value","key","p","maxScroll","Math","max","newScroll","min","scrollDelta","minY","visibleMaxY","paddingAllowance","contentMaxY","maxY","proposedY","y","x","requestAnimationFrame","getIndexOfKey","findIndex","Pan","minDistance","activateAfterLongPress","onStart","bestKey","bestDist","Number","MAX_VALUE","forEach","cx","cy","dx","dy","dist2","active","duration","onUpdate","translationX","translationY","proposedX","clampedY","itemTopInViewport","itemBottomInViewport","itemCenterInViewport","nearBottom","nearTop","centerY","fromIndex","toIndex","centerX","length","next","splice","onEnd","deletePos","tolerance","expandedDeleteX","expandedDeleteY","expandedDeleteWidth","width","expandedDeleteHeight","height","itemLeft","itemRight","itemTop","itemBottom","overlaps","idx","index","scale","damping","stiffness","mass","map"],"sources":["PanWithLongPress.ts"],"sourcesContent":["import { Gesture } from \"react-native-gesture-handler\";\nimport {\n runOnJS,\n SharedValue,\n withSpring,\n withTiming,\n scrollTo,\n AnimatedRef,\n useSharedValue,\n useDerivedValue,\n} from \"react-native-reanimated\";\nimport {\n indexToXY,\n toIndex1ColFromLiveMidlines,\n xyToIndex,\n} from \"../indexCalculations\";\n\ninterface PanProps {\n order: SharedValue<string[]>;\n dynamicNumColumns: SharedValue<number>;\n activeKey: SharedValue<string | null>;\n offsetX: SharedValue<number>;\n offsetY: SharedValue<number>;\n startX: SharedValue<number>;\n startY: SharedValue<number>;\n dragMode: SharedValue<boolean>;\n positions: any;\n itemsByKey: any;\n itemWidth: number;\n itemHeight: number;\n containerPadding: number;\n gap: number;\n setOrderState: React.Dispatch<React.SetStateAction<string[]>>;\n onDragEnd?: (ordered: ChildNode[]) => void;\n onOrderChange?: (keys: string[]) => void;\n\n // scrolling\n scrollSpeed: number;\n scrollThreshold: number;\n scrollViewRef: AnimatedRef<any>;\n scrollOffset: SharedValue<number>;\n viewportH: SharedValue<number>;\n holdToDragMs: number;\n contentH: SharedValue<number>;\n reverse?: boolean;\n deleteComponentPosition?: SharedValue<{\n x: number;\n y: number;\n width: number;\n height: number;\n } | null>;\n deleteItem?: (key: string) => void;\n contentPaddingBottom?: number; // Padding bottom from style prop to allow dragging into padding area\n}\n\nexport const PanWithLongPress = (\n props: PanProps & { holdToDragMs: number }\n) => {\n const {\n order,\n dynamicNumColumns,\n activeKey,\n offsetX,\n offsetY,\n startX,\n startY,\n dragMode,\n positions,\n itemsByKey,\n itemWidth,\n itemHeight,\n containerPadding,\n gap,\n setOrderState,\n onDragEnd,\n onOrderChange,\n scrollSpeed,\n scrollThreshold,\n scrollViewRef,\n scrollOffset,\n viewportH,\n holdToDragMs,\n contentH,\n reverse = false,\n deleteComponentPosition,\n deleteItem,\n contentPaddingBottom = 0,\n } = props;\n\n const scrollDir = useSharedValue(0); // -1 = up, 1 = down, 0 = none\n const initialScrollOffset = useSharedValue(0);\n\n useDerivedValue(() => {\n if (!dragMode.value || !activeKey.value) return;\n\n if (viewportH.value <= 0 || contentH.value <= 0) return;\n\n const key = activeKey.value;\n const p = positions[key];\n if (!p) return;\n\n // 1. Clamp scroll offset\n // Account for contentPaddingBottom in max scroll calculation\n // The ScrollView's contentContainerStyle paddingBottom adds to scrollable content\n const maxScroll = Math.max(\n 0,\n contentH.value + contentPaddingBottom - viewportH.value\n );\n const newScroll = Math.max(\n 0,\n Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll)\n );\n\n scrollTo(scrollViewRef, 0, newScroll, false);\n const scrollDelta = newScroll - initialScrollOffset.value;\n scrollOffset.value = newScroll;\n\n // 2. Clamp item position to stay within visible viewport and content bounds\n // This runs every frame for auto-scroll adjustments\n // Use the same clamping logic as onUpdate for consistency\n const minY = scrollOffset.value;\n const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;\n // Items are positioned starting at containerPadding, so the last item's bottom\n // should be at contentH - containerPadding. But we also need to account for\n // the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.\n // Allow items to extend slightly into the padding area for better UX.\n const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);\n const contentMaxY =\n contentH.value - containerPadding - itemHeight + paddingAllowance;\n const maxY = Math.min(visibleMaxY, contentMaxY);\n\n // Calculate position accounting for scroll delta (for auto-scroll)\n const proposedY = startY.value + offsetY.value + scrollDelta;\n // Clamp the position\n p.y.value = Math.max(minY, Math.min(proposedY, maxY));\n\n // X stays normal\n p.x.value = startX.value + offsetX.value;\n\n // Keep loop alive\n requestAnimationFrame(() => {\n scrollDir.value = scrollDir.value;\n });\n });\n\n const getIndexOfKey = (key: string) => {\n \"worklet\";\n return order.value.findIndex((x) => x === key);\n };\n\n return Gesture.Pan()\n .minDistance(10)\n .activateAfterLongPress(holdToDragMs)\n .onStart(({ x, y }) => {\n initialScrollOffset.value = scrollOffset.value;\n dragMode.value = true;\n let bestKey: string | null = null;\n let bestDist = Number.MAX_VALUE;\n order.value.forEach((key) => {\n const p = positions[key];\n if (!p) return;\n const cx = p.x.value + itemWidth / 2;\n const cy = p.y.value + itemHeight / 2;\n const dx = cx - x;\n const dy = cy - y;\n const dist2 = dx * dx + dy * dy;\n if (dist2 < bestDist) {\n bestDist = dist2;\n bestKey = key;\n }\n });\n if (!bestKey) return;\n activeKey.value = bestKey;\n const p = positions[bestKey]!;\n p.active.value = withTiming(1, { duration: 120 });\n startX.value = p.x.value;\n startY.value = p.y.value;\n offsetX.value = 0;\n offsetY.value = 0;\n })\n .onUpdate(({ translationX, translationY }) => {\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) return;\n\n const p = positions[key]!;\n const scrollDelta = scrollOffset.value - initialScrollOffset.value;\n\n // Update active (top-left)\n offsetX.value = translationX;\n offsetY.value = translationY;\n\n // Calculate proposed position\n const proposedX = startX.value + offsetX.value;\n const proposedY = startY.value + offsetY.value + scrollDelta;\n\n // Clamp Y position immediately to prevent visual glitch\n const minY = scrollOffset.value;\n const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;\n // Items are positioned starting at containerPadding, so the last item's bottom\n // should be at contentH - containerPadding. But we also need to account for\n // the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.\n // Allow items to extend slightly into the padding area for better UX.\n const paddingAllowance = Math.min(\n contentPaddingBottom,\n itemHeight * 0.75\n );\n const contentMaxY =\n contentH.value - containerPadding - itemHeight + paddingAllowance;\n const maxY = Math.min(visibleMaxY, contentMaxY);\n const clampedY = Math.max(minY, Math.min(proposedY, maxY));\n\n p.x.value = proposedX;\n p.y.value = clampedY;\n\n // Auto-scroll: Use item's center (or bottom edge if very large) to detect proximity to edges\n // This ensures scrolling works even for very large items\n const itemTopInViewport = p.y.value - scrollOffset.value;\n const itemBottomInViewport = itemTopInViewport + itemHeight;\n const itemCenterInViewport = itemTopInViewport + itemHeight / 2;\n\n // For large items, check if any part is near the edge\n // For small items, use center for more intuitive behavior\n const nearBottom =\n itemHeight > viewportH.value * 0.5\n ? itemBottomInViewport > viewportH.value - scrollThreshold\n : itemCenterInViewport > viewportH.value - scrollThreshold;\n const nearTop =\n itemHeight > viewportH.value * 0.5\n ? itemTopInViewport < scrollThreshold\n : itemCenterInViewport < scrollThreshold;\n\n if (nearBottom) {\n scrollDir.value = 1;\n } else if (nearTop) {\n scrollDir.value = -1;\n } else {\n scrollDir.value = 0;\n }\n\n // Compute target index from the active tile's **center**\n const centerY = p.y.value + itemHeight / 2;\n const fromIndex = getIndexOfKey(key);\n\n let toIndex: number;\n if (dynamicNumColumns.value === 1) {\n toIndex = toIndex1ColFromLiveMidlines(\n order,\n positions,\n activeKey,\n itemHeight,\n centerY,\n reverse // ← pass your prop\n );\n } else {\n // unchanged multi-column path\n const centerX = p.x.value + itemWidth / 2;\n toIndex = xyToIndex({\n order,\n x: centerX,\n y: centerY,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n }\n\n if (\n toIndex !== fromIndex &&\n toIndex >= 0 &&\n toIndex <= order.value.length - 1\n ) {\n const next = [...order.value];\n next.splice(fromIndex, 1);\n next.splice(toIndex, 0, key);\n order.value = next;\n }\n })\n .onEnd(() => {\n scrollDir.value = 0; // stop auto-scroll\n if (!dragMode.value) return;\n const key = activeKey.value;\n if (!key) {\n dragMode.value = false;\n return;\n }\n const p = positions[key]!;\n\n // Check if item was dropped into delete component\n if (deleteComponentPosition?.value && deleteItem) {\n const deletePos = deleteComponentPosition.value;\n\n // Add tolerance/padding to make it easier to hit (20% of item size)\n const tolerance = Math.min(itemWidth, itemHeight) * 0.2;\n const expandedDeleteX = deletePos.x - tolerance;\n const expandedDeleteY = deletePos.y - tolerance;\n const expandedDeleteWidth = deletePos.width + tolerance * 2;\n const expandedDeleteHeight = deletePos.height + tolerance * 2;\n\n // Check if item bounding box overlaps with expanded delete component bounds\n // This is more forgiving than checking just the center point\n const itemLeft = p.x.value;\n const itemRight = p.x.value + itemWidth;\n const itemTop = p.y.value;\n const itemBottom = p.y.value + itemHeight;\n\n // Bounding box intersection check\n const overlaps =\n itemLeft < expandedDeleteX + expandedDeleteWidth &&\n itemRight > expandedDeleteX &&\n itemTop < expandedDeleteY + expandedDeleteHeight &&\n itemBottom > expandedDeleteY;\n\n if (overlaps) {\n // Item was dropped into delete component - delete it\n runOnJS(deleteItem)(key);\n // Note: deleteItem will handle calling onDelete callback if provided\n p.active.value = withTiming(0, { duration: 120 });\n activeKey.value = null;\n dragMode.value = false;\n return;\n }\n }\n\n // Normal drop - return to grid position\n const idx = getIndexOfKey(key);\n const { x, y } = indexToXY({\n index: idx,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n const scale = Math.min(itemWidth, itemHeight) / 200; // 100px baseline\n\n const damping = 18 * scale;\n const stiffness = 240 * scale;\n const mass = Math.max(0.05, scale); // helps stability for tiny items\n\n p.x.value = withSpring(x, { damping, stiffness, mass });\n p.y.value = withSpring(y, { damping, stiffness, mass });\n\n p.active.value = withTiming(0, { duration: 120 });\n\n runOnJS(setOrderState)(order.value);\n if (onDragEnd) {\n runOnJS(onDragEnd)(order.value.map((key) => itemsByKey[key]));\n }\n if (onOrderChange) {\n runOnJS(onOrderChange)([...order.value]);\n }\n activeKey.value = null;\n dragMode.value = false;\n });\n};\n"],"mappings":"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","ignoreList":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "react-native-swappable-grid",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.13",
|
|
4
4
|
"description": "A React Native component for creating draggable, swappable grid layouts with reordering, delete functionality, and smooth animations",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"react-native",
|
|
@@ -100,7 +100,12 @@ export const PanWithLongPress = (
|
|
|
100
100
|
if (!p) return;
|
|
101
101
|
|
|
102
102
|
// 1. Clamp scroll offset
|
|
103
|
-
|
|
103
|
+
// Account for contentPaddingBottom in max scroll calculation
|
|
104
|
+
// The ScrollView's contentContainerStyle paddingBottom adds to scrollable content
|
|
105
|
+
const maxScroll = Math.max(
|
|
106
|
+
0,
|
|
107
|
+
contentH.value + contentPaddingBottom - viewportH.value
|
|
108
|
+
);
|
|
104
109
|
const newScroll = Math.max(
|
|
105
110
|
0,
|
|
106
111
|
Math.min(scrollOffset.value + scrollDir.value * scrollSpeed, maxScroll)
|
|
@@ -110,12 +115,23 @@ export const PanWithLongPress = (
|
|
|
110
115
|
const scrollDelta = newScroll - initialScrollOffset.value;
|
|
111
116
|
scrollOffset.value = newScroll;
|
|
112
117
|
|
|
113
|
-
// 2. Clamp item position
|
|
114
|
-
//
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
const
|
|
118
|
+
// 2. Clamp item position to stay within visible viewport and content bounds
|
|
119
|
+
// This runs every frame for auto-scroll adjustments
|
|
120
|
+
// Use the same clamping logic as onUpdate for consistency
|
|
121
|
+
const minY = scrollOffset.value;
|
|
122
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
123
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
124
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
125
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
126
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
127
|
+
const paddingAllowance = Math.min(contentPaddingBottom, itemHeight * 0.75);
|
|
128
|
+
const contentMaxY =
|
|
129
|
+
contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
130
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
131
|
+
|
|
132
|
+
// Calculate position accounting for scroll delta (for auto-scroll)
|
|
118
133
|
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
134
|
+
// Clamp the position
|
|
119
135
|
p.y.value = Math.max(minY, Math.min(proposedY, maxY));
|
|
120
136
|
|
|
121
137
|
// X stays normal
|
|
@@ -173,14 +189,50 @@ export const PanWithLongPress = (
|
|
|
173
189
|
// Update active (top-left)
|
|
174
190
|
offsetX.value = translationX;
|
|
175
191
|
offsetY.value = translationY;
|
|
176
|
-
p.x.value = startX.value + offsetX.value;
|
|
177
|
-
p.y.value = startY.value + offsetY.value + scrollDelta;
|
|
178
192
|
|
|
179
|
-
//
|
|
180
|
-
const
|
|
181
|
-
|
|
193
|
+
// Calculate proposed position
|
|
194
|
+
const proposedX = startX.value + offsetX.value;
|
|
195
|
+
const proposedY = startY.value + offsetY.value + scrollDelta;
|
|
196
|
+
|
|
197
|
+
// Clamp Y position immediately to prevent visual glitch
|
|
198
|
+
const minY = scrollOffset.value;
|
|
199
|
+
const visibleMaxY = scrollOffset.value + viewportH.value - itemHeight;
|
|
200
|
+
// Items are positioned starting at containerPadding, so the last item's bottom
|
|
201
|
+
// should be at contentH - containerPadding. But we also need to account for
|
|
202
|
+
// the ScrollView's contentContainerStyle paddingBottom which extends beyond contentH.
|
|
203
|
+
// Allow items to extend slightly into the padding area for better UX.
|
|
204
|
+
const paddingAllowance = Math.min(
|
|
205
|
+
contentPaddingBottom,
|
|
206
|
+
itemHeight * 0.75
|
|
207
|
+
);
|
|
208
|
+
const contentMaxY =
|
|
209
|
+
contentH.value - containerPadding - itemHeight + paddingAllowance;
|
|
210
|
+
const maxY = Math.min(visibleMaxY, contentMaxY);
|
|
211
|
+
const clampedY = Math.max(minY, Math.min(proposedY, maxY));
|
|
212
|
+
|
|
213
|
+
p.x.value = proposedX;
|
|
214
|
+
p.y.value = clampedY;
|
|
215
|
+
|
|
216
|
+
// Auto-scroll: Use item's center (or bottom edge if very large) to detect proximity to edges
|
|
217
|
+
// This ensures scrolling works even for very large items
|
|
218
|
+
const itemTopInViewport = p.y.value - scrollOffset.value;
|
|
219
|
+
const itemBottomInViewport = itemTopInViewport + itemHeight;
|
|
220
|
+
const itemCenterInViewport = itemTopInViewport + itemHeight / 2;
|
|
221
|
+
|
|
222
|
+
// For large items, check if any part is near the edge
|
|
223
|
+
// For small items, use center for more intuitive behavior
|
|
224
|
+
const nearBottom =
|
|
225
|
+
itemHeight > viewportH.value * 0.5
|
|
226
|
+
? itemBottomInViewport > viewportH.value - scrollThreshold
|
|
227
|
+
: itemCenterInViewport > viewportH.value - scrollThreshold;
|
|
228
|
+
const nearTop =
|
|
229
|
+
itemHeight > viewportH.value * 0.5
|
|
230
|
+
? itemTopInViewport < scrollThreshold
|
|
231
|
+
: itemCenterInViewport < scrollThreshold;
|
|
232
|
+
|
|
233
|
+
if (nearBottom) {
|
|
182
234
|
scrollDir.value = 1;
|
|
183
|
-
} else if (
|
|
235
|
+
} else if (nearTop) {
|
|
184
236
|
scrollDir.value = -1;
|
|
185
237
|
} else {
|
|
186
238
|
scrollDir.value = 0;
|