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 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
- const maxScroll = contentH.value - viewportH.value;
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
- // Allow dragging into padding area (paddingBottom from style prop)
59
- const minY = 0;
60
- // Add paddingBottom to maxY to allow dragging into the padding area
61
- const maxY = contentH.value - itemHeight + contentPaddingBottom;
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
- // Auto-scroll (unchanged)
126
- const pointerYInViewport = p.y.value - scrollOffset.value;
127
- if (pointerYInViewport > viewportH.value - scrollThreshold) {
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 (pointerYInViewport < scrollThreshold) {
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
- const maxScroll = contentH.value - viewportH.value;
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
- // Allow dragging into padding area (paddingBottom from style prop)
53
- const minY = 0;
54
- // Add paddingBottom to maxY to allow dragging into the padding area
55
- const maxY = contentH.value - itemHeight + contentPaddingBottom;
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
- // Auto-scroll (unchanged)
120
- const pointerYInViewport = p.y.value - scrollOffset.value;
121
- if (pointerYInViewport > viewportH.value - scrollThreshold) {
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 (pointerYInViewport < scrollThreshold) {
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.12",
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
- const maxScroll = contentH.value - viewportH.value;
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
- // Allow dragging into padding area (paddingBottom from style prop)
115
- const minY = 0;
116
- // Add paddingBottom to maxY to allow dragging into the padding area
117
- const maxY = contentH.value - itemHeight + contentPaddingBottom;
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
- // Auto-scroll (unchanged)
180
- const pointerYInViewport = p.y.value - scrollOffset.value;
181
- if (pointerYInViewport > viewportH.value - scrollThreshold) {
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 (pointerYInViewport < scrollThreshold) {
235
+ } else if (nearTop) {
184
236
  scrollDir.value = -1;
185
237
  } else {
186
238
  scrollDir.value = 0;