react-native-swappable-grid 1.0.7 → 1.0.9

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
@@ -13,6 +13,7 @@ A powerful React Native component for creating draggable, swappable grid layouts
13
13
  - 🎨 **Smooth Animations**: Optional wiggle animation during drag mode
14
14
  - 🗑️ **Delete Support**: Built-in hold-still-to-delete or custom delete component (trashcan)
15
15
  - ➕ **Trailing Components**: Support for additional components (e.g., "Add" button)
16
+ - 👋 **Haptic Feedback**: Optional haptic feedback on deletion with Vibration API, or optionally (better) expo-haptics
16
17
  - 📜 **Auto-scroll**: Automatic scrolling when dragging near edges
17
18
  - 🔄 **Order Tracking**: Callbacks for order changes and drag end events
18
19
  - ⚡ **Performance**: Built with React Native Reanimated and Gesture Handler for 60fps animations
@@ -98,6 +99,16 @@ npm install react react-native react-native-gesture-handler react-native-reanima
98
99
  yarn add react react-native react-native-gesture-handler react-native-reanimated
99
100
  ```
100
101
 
102
+ **Optional**: For better haptic feedback (especially on iOS), install `expo-haptics`:
103
+
104
+ ```bash
105
+ npm install expo-haptics
106
+ # or
107
+ yarn add expo-haptics
108
+ ```
109
+
110
+ > **Note**: `expo-haptics` works in both Expo and bare React Native projects. If not installed, the library will fall back to React Native's Vibration API (which has limited control on iOS and will give harsher vibrations).
111
+
101
112
  **Important**: Make sure to follow the setup instructions for:
102
113
 
103
114
  - [react-native-gesture-handler](https://docs.swmansion.com/react-native-gesture-handler/docs/fundamentals/installation/)
@@ -187,6 +198,7 @@ const MyComponent = () => {
187
198
  | `wiggle` | `{ duration: number; degrees: number }` | Optional | - | Wiggle animation configuration when items are in drag mode |
188
199
  | `wiggleDeleteMode` | `{ duration: number; degrees: number }` | Optional | - | Wiggle animation configuration specifically for delete mode. If not provided, uses 2x degrees and 0.7x duration of `wiggle` prop |
189
200
  | `holdStillToDeleteMs` | `number` | Optional | `1000` | Duration in milliseconds to hold an item still before entering delete mode |
201
+ | `hapticFeedback` | `boolean` | Optional | `false` | Enable haptic feedback when entering delete mode. Uses `expo-haptics` if available (recommended for iOS), otherwise falls back to Vibration API. |
190
202
  | `onDragEnd` | `(ordered: ChildNode[]) => void` | Optional | - | Callback fired when drag ends, providing the ordered array of child nodes |
191
203
  | `onOrderChange` | `(keys: string[]) => void` | Optional | - | Callback fired when the order changes, providing an array of keys in the new order |
192
204
  | `onDelete` | `(key: string) => void` | Optional | - | Callback fired when an item is deleted, providing the key of the deleted item |
@@ -8,6 +8,13 @@ var _react = _interopRequireWildcard(require("react"));
8
8
  var _reactNative = require("react-native");
9
9
  var _reactNativeReanimated = _interopRequireWildcard(require("react-native-reanimated"));
10
10
  function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function (e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, default: e }; if (null === e || "object" != typeof e && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (const t in e) "default" !== t && {}.hasOwnProperty.call(e, t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, t)) && (i.get || i.set) ? o(f, t, i) : f[t] = e[t]); return f; })(e, t); }
11
+ // Try to import expo-haptics (optional dependency)
12
+ let Haptics = null;
13
+ try {
14
+ Haptics = require("expo-haptics");
15
+ } catch (e) {
16
+ // expo-haptics not available, will fall back to Vibration API
17
+ }
11
18
  function ChildWrapper({
12
19
  position,
13
20
  itemWidth,
@@ -21,7 +28,8 @@ function ChildWrapper({
21
28
  holdStillToDeleteMs = 1000,
22
29
  dragSizeIncreaseFactor,
23
30
  onDelete,
24
- disableHoldToDelete = false
31
+ disableHoldToDelete = false,
32
+ hapticFeedback = false
25
33
  }) {
26
34
  const rotation = (0, _reactNativeReanimated.useSharedValue)(0);
27
35
  const currentWiggleMode = (0, _reactNativeReanimated.useSharedValue)("none");
@@ -34,6 +42,36 @@ function ChildWrapper({
34
42
  const frameCounter = (0, _reactNativeReanimated.useSharedValue)(0);
35
43
  const wasReleasedAfterDeleteMode = (0, _reactNativeReanimated.useSharedValue)(false); // Track if item was released after entering delete mode
36
44
 
45
+ // Function to trigger haptic feedback (called from worklet via runOnJS)
46
+ const triggerHapticFeedback = () => {
47
+ try {
48
+ // Platform-specific haptic feedback
49
+ if (_reactNative.Platform.OS === "ios") {
50
+ // iOS: Prefer expo-haptics for better control (subtle feedback)
51
+ if (Haptics && Haptics.impactAsync) {
52
+ // Use light impact for subtle feedback (similar to iOS system haptics)
53
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
54
+ return;
55
+ }
56
+ // Fallback to Vibration API if expo-haptics not available
57
+ // Note: This will be a stronger vibration than desired on iOS
58
+ if (_reactNative.Vibration && typeof _reactNative.Vibration.vibrate === "function") {
59
+ _reactNative.Vibration.vibrate(1);
60
+ }
61
+ } else {
62
+ // Android: Use Vibration API (works well and is more reliable than expo-haptics)
63
+ if (_reactNative.Vibration && typeof _reactNative.Vibration.vibrate === "function") {
64
+ // Android requires a pattern array: [delay, duration]
65
+ // [0, 20] means: start immediately (0ms delay), vibrate for 20ms
66
+ _reactNative.Vibration.vibrate([0, 20]);
67
+ }
68
+ }
69
+ } catch (error) {
70
+ // Silently fail if haptic feedback is not available or fails
71
+ // This allows the library to work in environments where haptics are not supported
72
+ }
73
+ };
74
+
37
75
  // Timer logic that runs every frame via useDerivedValue
38
76
  (0, _reactNativeReanimated.useDerivedValue)(() => {
39
77
  "worklet";
@@ -128,11 +166,16 @@ function ChildWrapper({
128
166
  stillTimer.value += 16;
129
167
 
130
168
  // Enter delete mode after holdStillToDeleteMs of being held still
131
- if (stillTimer.value >= holdStillToDeleteMs) {
169
+ if (stillTimer.value >= holdStillToDeleteMs && !deleteModeActive.value) {
132
170
  deleteModeActive.value = true;
133
171
  anyItemInDeleteMode.value = true; // Set global delete mode
134
172
  showDelete.value = true;
135
173
  wasReleasedAfterDeleteMode.value = false; // Reset on entry
174
+
175
+ // Trigger haptic feedback when entering delete mode
176
+ if (hapticFeedback) {
177
+ (0, _reactNativeReanimated.runOnJS)(triggerHapticFeedback)();
178
+ }
136
179
  }
137
180
  });
138
181
  const deleteButtonStyle = (0, _reactNativeReanimated.useAnimatedStyle)(() => {
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeReanimated","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","ChildWrapper","position","itemWidth","itemHeight","dragMode","anyItemInDeleteMode","isPressingDeleteItem","children","wiggle","wiggleDeleteMode","holdStillToDeleteMs","dragSizeIncreaseFactor","onDelete","disableHoldToDelete","rotation","useSharedValue","currentWiggleMode","previousDragMode","showDelete","deleteModeActive","stillTimer","lastX","x","value","lastY","y","frameCounter","wasReleasedAfterDeleteMode","useDerivedValue","isDragging","isActive","active","dragModeJustEnded","moved","Math","abs","deleteButtonStyle","useAnimatedStyle","shouldShow","opacity","pointerEvents","transform","scale","withTiming","duration","useAnimatedReaction","current","previous","isEditMode","inDeleteMode","anyInDeleteMode","targetMode","cancelAnimation","previousMode","deleteWiggleDegrees","degrees","deleteWiggleDuration","currentRot","scaleFactor","withRepeat","withSequence","easing","Easing","linear","animatedStyle","width","height","translateX","translateY","rotate","zIndex","isInDeleteMode","setIsInDeleteMode","useState","runOnJS","handleDelete","createElement","View","style","Pressable","onPressIn","onPressOut","setTimeout","onPress","top","left","right","bottom","borderRadius","justifyContent","alignItems","Text","fontSize","color","fontWeight"],"sources":["ChildWrapper.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { Text, View, Pressable } from \"react-native\";\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withRepeat,\n withSequence,\n withTiming,\n SharedValue,\n useDerivedValue,\n cancelAnimation,\n runOnJS,\n} from \"react-native-reanimated\";\n\ntype Props = {\n position: {\n x: SharedValue<number>;\n y: SharedValue<number>;\n active: SharedValue<number>;\n };\n itemWidth: number;\n itemHeight: number;\n dragMode: SharedValue<boolean>;\n anyItemInDeleteMode: SharedValue<boolean>;\n isPressingDeleteItem: SharedValue<boolean>;\n children: React.ReactNode;\n wiggle?: { duration: number; degrees: number };\n wiggleDeleteMode?: { duration: number; degrees: number };\n holdStillToDeleteMs?: number;\n dragSizeIncreaseFactor: number;\n onDelete?: () => void;\n disableHoldToDelete?: boolean; // If true, disable the hold-to-delete feature\n};\n\nexport default function ChildWrapper({\n position,\n itemWidth,\n itemHeight,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n children,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n dragSizeIncreaseFactor,\n onDelete,\n disableHoldToDelete = false,\n}: Props) {\n const rotation = useSharedValue(0);\n const currentWiggleMode = useSharedValue<\"none\" | \"normal\" | \"delete\">(\n \"none\"\n );\n const previousDragMode = useSharedValue(false);\n\n const showDelete = useSharedValue(false);\n const deleteModeActive = useSharedValue(false); // Persistent delete mode state\n const stillTimer = useSharedValue(0);\n const lastX = useSharedValue(position.x.value);\n const lastY = useSharedValue(position.y.value);\n const frameCounter = useSharedValue(0);\n const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode\n\n // Timer logic that runs every frame via useDerivedValue\n useDerivedValue(() => {\n \"worklet\";\n frameCounter.value = frameCounter.value + 1;\n\n // If hold-to-delete is disabled, skip all delete mode logic\n if (disableHoldToDelete) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n anyItemInDeleteMode.value = false;\n return;\n }\n\n const isDragging = dragMode.value;\n const isActive = position.active.value > 0.5;\n const x = position.x.value;\n const y = position.y.value;\n\n // Track dragMode changes for detecting touches outside\n const dragModeJustEnded = previousDragMode.value && !isDragging;\n previousDragMode.value = isDragging;\n\n // If delete mode is active, keep it active unless:\n // 1. Another item becomes active (dragMode true but this item not active)\n // 2. This item becomes active again AFTER it was released (user starts dragging it again)\n // 3. User touches outside (dragMode becomes false and no item is active)\n if (deleteModeActive.value) {\n // Check if item was released (became inactive)\n if (!isActive && !wasReleasedAfterDeleteMode.value) {\n wasReleasedAfterDeleteMode.value = true;\n }\n\n if (isDragging && !isActive) {\n // Another item is being dragged, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (isActive && wasReleasedAfterDeleteMode.value) {\n // This item became active again AFTER it was released, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (!isDragging && !isActive) {\n // Keep delete mode active (waiting for user interaction)\n // The tap gesture handler in SwappableGrid will cancel it when user taps outside\n showDelete.value = true;\n } else {\n // Keep delete mode active (item can still be held or released)\n showDelete.value = true;\n }\n return;\n }\n\n // Reset release tracking when not in delete mode\n wasReleasedAfterDeleteMode.value = false;\n\n // Timer runs when item is active (being held)\n // Note: isActive (position.active.value) is set when gesture activates after long press\n // isDragging (dragMode.value) is also set at that time, but we primarily check isActive\n // to allow timer to work even in edge cases\n if (!isActive) {\n stillTimer.value = 0;\n return;\n }\n\n // Item is active - timer can run (dragMode should also be true at this point,\n // but we don't require it to allow timer to work in all cases)\n\n // Item is active (being held down) - check if it's still\n // Check if position has changed significantly (more than 10px threshold)\n const moved =\n Math.abs(x - lastX.value) > 10 || Math.abs(y - lastY.value) > 10;\n\n if (moved) {\n // Reset timer if item moved while being held\n stillTimer.value = 0;\n lastX.value = x;\n lastY.value = y;\n return;\n }\n\n // Initialize last position on first frame when active\n if (stillTimer.value === 0) {\n lastX.value = x;\n lastY.value = y;\n }\n\n // If the tile hasn't moved significantly while being held → increment timer\n // Increment by ~16ms per frame (assuming 60fps)\n stillTimer.value += 16;\n\n // Enter delete mode after holdStillToDeleteMs of being held still\n if (stillTimer.value >= holdStillToDeleteMs) {\n deleteModeActive.value = true;\n anyItemInDeleteMode.value = true; // Set global delete mode\n showDelete.value = true;\n wasReleasedAfterDeleteMode.value = false; // Reset on entry\n }\n });\n\n const deleteButtonStyle = useAnimatedStyle(() => {\n // Show delete button when delete mode is active (persists after release)\n const shouldShow = showDelete.value;\n return {\n opacity: shouldShow ? 1 : 0,\n pointerEvents: shouldShow ? \"auto\" : \"none\",\n transform: [\n { scale: withTiming(shouldShow ? 1 : 0.6, { duration: 120 }) },\n ],\n };\n });\n\n // Watch for when global delete mode is cancelled (user tapped outside)\n useAnimatedReaction(\n () => anyItemInDeleteMode.value,\n (current, previous) => {\n \"worklet\";\n // If delete mode was cancelled globally (user tapped outside)\n if (previous && !current && deleteModeActive.value) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n }\n }\n );\n\n // Wiggle animation — triggers on editMode/active changes and delete mode\n useAnimatedReaction(\n () => ({\n isEditMode: dragMode.value,\n isActive: position.active.value > 0.5,\n inDeleteMode: deleteModeActive.value,\n anyInDeleteMode: anyItemInDeleteMode.value,\n }),\n ({ isEditMode, isActive, inDeleteMode, anyInDeleteMode }) => {\n // Determine the target wiggle mode\n let targetMode: \"none\" | \"normal\" | \"delete\" = \"none\";\n if (inDeleteMode && (wiggleDeleteMode || wiggle)) {\n targetMode = \"delete\";\n } else if (anyInDeleteMode && !isActive && wiggle) {\n targetMode = \"normal\";\n } else if (isEditMode && !isActive && wiggle) {\n targetMode = \"normal\";\n }\n\n // If no wiggle is configured at all, stop animation\n if (!wiggle && !wiggleDeleteMode) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If in delete mode but no wiggleDeleteMode and no wiggle, stop animation\n if (targetMode === \"delete\" && !wiggleDeleteMode && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If normal mode but no wiggle, stop animation\n if (targetMode === \"normal\" && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // Only restart animation if mode changed\n if (currentWiggleMode.value === targetMode) {\n return; // Already in the correct mode, don't restart\n }\n\n const previousMode = currentWiggleMode.value;\n currentWiggleMode.value = targetMode;\n\n // Cancel current animation\n cancelAnimation(rotation);\n\n // If this item is in delete mode, use wiggleDeleteMode if provided, otherwise use 2x degrees and 0.7x duration\n if (targetMode === \"delete\") {\n const deleteWiggleDegrees = wiggleDeleteMode\n ? wiggleDeleteMode.degrees\n : (wiggle?.degrees ?? 0) * 2;\n const deleteWiggleDuration = wiggleDeleteMode\n ? wiggleDeleteMode.duration\n : (wiggle?.duration ?? 200) * 0.7; // Faster wiggle\n\n // If transitioning from normal wiggle, preserve the phase by scaling\n if (previousMode === \"normal\" && wiggle) {\n const currentRot = rotation.value;\n const scaleFactor = deleteWiggleDegrees / wiggle.degrees;\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n }),\n withTiming(-deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Normal wiggle (when dragging but not this item, or any item in delete mode)\n else if (targetMode === \"normal\") {\n // If transitioning from delete wiggle, preserve the phase by scaling\n if (previousMode === \"delete\") {\n const currentRot = rotation.value;\n const scaleFactor = wiggle.degrees / (wiggle.degrees * 2);\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n }),\n withTiming(-wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Stop wiggling\n else {\n rotation.value = withTiming(0, { duration: 150 });\n }\n },\n [\n dragMode,\n position.active,\n deleteModeActive,\n anyItemInDeleteMode,\n wiggle,\n wiggleDeleteMode,\n ]\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n const scale = position.active.value\n ? withTiming(dragSizeIncreaseFactor, { duration: 120 })\n : withTiming(1, { duration: 120 });\n\n return {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n transform: [\n { translateX: position.x.value as any },\n { translateY: position.y.value as any },\n { scale: scale as any },\n { rotate: `${rotation.value}deg` as any },\n ],\n zIndex: position.active.value ? 2 : 0,\n } as any;\n });\n\n // Track delete mode on JS thread for conditional rendering\n const [isInDeleteMode, setIsInDeleteMode] = useState(false);\n\n useAnimatedReaction(\n () => deleteModeActive.value,\n (current) => {\n runOnJS(setIsInDeleteMode)(current);\n }\n );\n\n const handleDelete = () => {\n // Exit delete mode when delete button is pressed\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n if (onDelete) {\n onDelete();\n }\n };\n\n return (\n <Animated.View style={animatedStyle} pointerEvents=\"box-none\">\n {/* Full-item Pressable for delete - only active when in delete mode */}\n {isInDeleteMode && (\n <Pressable\n onPressIn={() => {\n // Mark that we're pressing an item to prevent ScrollView from canceling delete mode\n isPressingDeleteItem.value = true;\n }}\n onPressOut={() => {\n // Clear the flag after a short delay to allow onPress to fire\n setTimeout(() => {\n isPressingDeleteItem.value = false;\n }, 50);\n }}\n onPress={handleDelete}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: itemWidth,\n height: itemHeight,\n zIndex: 2,\n }}\n />\n )}\n\n {/* Delete button (×) - visual indicator only */}\n <Animated.View\n style={[\n {\n position: \"absolute\",\n top: itemHeight * 0.01,\n right: itemWidth * 0.04,\n width: itemWidth * 0.2,\n height: itemHeight * 0.2,\n borderRadius: 12,\n justifyContent: \"center\",\n alignItems: \"center\",\n zIndex: 3,\n },\n deleteButtonStyle,\n ]}\n pointerEvents=\"none\"\n >\n <Text\n style={{\n fontSize: itemWidth * 0.2,\n color: \"black\",\n fontWeight: 500,\n }}\n >\n ×\n </Text>\n </Animated.View>\n\n {children}\n </Animated.View>\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeReanimated","e","t","WeakMap","r","n","__esModule","o","i","f","__proto__","default","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","Haptics","ChildWrapper","position","itemWidth","itemHeight","dragMode","anyItemInDeleteMode","isPressingDeleteItem","children","wiggle","wiggleDeleteMode","holdStillToDeleteMs","dragSizeIncreaseFactor","onDelete","disableHoldToDelete","hapticFeedback","rotation","useSharedValue","currentWiggleMode","previousDragMode","showDelete","deleteModeActive","stillTimer","lastX","x","value","lastY","y","frameCounter","wasReleasedAfterDeleteMode","triggerHapticFeedback","Platform","OS","impactAsync","ImpactFeedbackStyle","Light","Vibration","vibrate","error","useDerivedValue","isDragging","isActive","active","dragModeJustEnded","moved","Math","abs","runOnJS","deleteButtonStyle","useAnimatedStyle","shouldShow","opacity","pointerEvents","transform","scale","withTiming","duration","useAnimatedReaction","current","previous","isEditMode","inDeleteMode","anyInDeleteMode","targetMode","cancelAnimation","previousMode","deleteWiggleDegrees","degrees","deleteWiggleDuration","currentRot","scaleFactor","withRepeat","withSequence","easing","Easing","linear","animatedStyle","width","height","translateX","translateY","rotate","zIndex","isInDeleteMode","setIsInDeleteMode","useState","handleDelete","createElement","View","style","Pressable","onPressIn","onPressOut","setTimeout","onPress","top","left","right","bottom","borderRadius","justifyContent","alignItems","Text","fontSize","color","fontWeight"],"sources":["ChildWrapper.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { Text, View, Pressable, Vibration, Platform } from \"react-native\";\n\n// Try to import expo-haptics (optional dependency)\nlet Haptics: any = null;\ntry {\n Haptics = require(\"expo-haptics\");\n} catch (e) {\n // expo-haptics not available, will fall back to Vibration API\n}\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withRepeat,\n withSequence,\n withTiming,\n SharedValue,\n useDerivedValue,\n cancelAnimation,\n runOnJS,\n} from \"react-native-reanimated\";\n\ntype Props = {\n position: {\n x: SharedValue<number>;\n y: SharedValue<number>;\n active: SharedValue<number>;\n };\n itemWidth: number;\n itemHeight: number;\n dragMode: SharedValue<boolean>;\n anyItemInDeleteMode: SharedValue<boolean>;\n isPressingDeleteItem: SharedValue<boolean>;\n children: React.ReactNode;\n wiggle?: { duration: number; degrees: number };\n wiggleDeleteMode?: { duration: number; degrees: number };\n holdStillToDeleteMs?: number;\n dragSizeIncreaseFactor: number;\n onDelete?: () => void;\n disableHoldToDelete?: boolean; // If true, disable the hold-to-delete feature\n hapticFeedback?: boolean; // If true, enable haptic feedback when entering delete mode\n};\n\nexport default function ChildWrapper({\n position,\n itemWidth,\n itemHeight,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n children,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n dragSizeIncreaseFactor,\n onDelete,\n disableHoldToDelete = false,\n hapticFeedback = false,\n}: Props) {\n const rotation = useSharedValue(0);\n const currentWiggleMode = useSharedValue<\"none\" | \"normal\" | \"delete\">(\n \"none\"\n );\n const previousDragMode = useSharedValue(false);\n\n const showDelete = useSharedValue(false);\n const deleteModeActive = useSharedValue(false); // Persistent delete mode state\n const stillTimer = useSharedValue(0);\n const lastX = useSharedValue(position.x.value);\n const lastY = useSharedValue(position.y.value);\n const frameCounter = useSharedValue(0);\n const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode\n\n // Function to trigger haptic feedback (called from worklet via runOnJS)\n const triggerHapticFeedback = () => {\n try {\n // Platform-specific haptic feedback\n if (Platform.OS === \"ios\") {\n // iOS: Prefer expo-haptics for better control (subtle feedback)\n if (Haptics && Haptics.impactAsync) {\n // Use light impact for subtle feedback (similar to iOS system haptics)\n Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);\n return;\n }\n // Fallback to Vibration API if expo-haptics not available\n // Note: This will be a stronger vibration than desired on iOS\n if (Vibration && typeof Vibration.vibrate === \"function\") {\n Vibration.vibrate(1);\n }\n } else {\n // Android: Use Vibration API (works well and is more reliable than expo-haptics)\n if (Vibration && typeof Vibration.vibrate === \"function\") {\n // Android requires a pattern array: [delay, duration]\n // [0, 20] means: start immediately (0ms delay), vibrate for 20ms\n Vibration.vibrate([0, 20]);\n }\n }\n } catch (error) {\n // Silently fail if haptic feedback is not available or fails\n // This allows the library to work in environments where haptics are not supported\n }\n };\n\n // Timer logic that runs every frame via useDerivedValue\n useDerivedValue(() => {\n \"worklet\";\n frameCounter.value = frameCounter.value + 1;\n\n // If hold-to-delete is disabled, skip all delete mode logic\n if (disableHoldToDelete) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n anyItemInDeleteMode.value = false;\n return;\n }\n\n const isDragging = dragMode.value;\n const isActive = position.active.value > 0.5;\n const x = position.x.value;\n const y = position.y.value;\n\n // Track dragMode changes for detecting touches outside\n const dragModeJustEnded = previousDragMode.value && !isDragging;\n previousDragMode.value = isDragging;\n\n // If delete mode is active, keep it active unless:\n // 1. Another item becomes active (dragMode true but this item not active)\n // 2. This item becomes active again AFTER it was released (user starts dragging it again)\n // 3. User touches outside (dragMode becomes false and no item is active)\n if (deleteModeActive.value) {\n // Check if item was released (became inactive)\n if (!isActive && !wasReleasedAfterDeleteMode.value) {\n wasReleasedAfterDeleteMode.value = true;\n }\n\n if (isDragging && !isActive) {\n // Another item is being dragged, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (isActive && wasReleasedAfterDeleteMode.value) {\n // This item became active again AFTER it was released, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (!isDragging && !isActive) {\n // Keep delete mode active (waiting for user interaction)\n // The tap gesture handler in SwappableGrid will cancel it when user taps outside\n showDelete.value = true;\n } else {\n // Keep delete mode active (item can still be held or released)\n showDelete.value = true;\n }\n return;\n }\n\n // Reset release tracking when not in delete mode\n wasReleasedAfterDeleteMode.value = false;\n\n // Timer runs when item is active (being held)\n // Note: isActive (position.active.value) is set when gesture activates after long press\n // isDragging (dragMode.value) is also set at that time, but we primarily check isActive\n // to allow timer to work even in edge cases\n if (!isActive) {\n stillTimer.value = 0;\n return;\n }\n\n // Item is active - timer can run (dragMode should also be true at this point,\n // but we don't require it to allow timer to work in all cases)\n\n // Item is active (being held down) - check if it's still\n // Check if position has changed significantly (more than 10px threshold)\n const moved =\n Math.abs(x - lastX.value) > 10 || Math.abs(y - lastY.value) > 10;\n\n if (moved) {\n // Reset timer if item moved while being held\n stillTimer.value = 0;\n lastX.value = x;\n lastY.value = y;\n return;\n }\n\n // Initialize last position on first frame when active\n if (stillTimer.value === 0) {\n lastX.value = x;\n lastY.value = y;\n }\n\n // If the tile hasn't moved significantly while being held → increment timer\n // Increment by ~16ms per frame (assuming 60fps)\n stillTimer.value += 16;\n\n // Enter delete mode after holdStillToDeleteMs of being held still\n if (stillTimer.value >= holdStillToDeleteMs && !deleteModeActive.value) {\n deleteModeActive.value = true;\n anyItemInDeleteMode.value = true; // Set global delete mode\n showDelete.value = true;\n wasReleasedAfterDeleteMode.value = false; // Reset on entry\n\n // Trigger haptic feedback when entering delete mode\n if (hapticFeedback) {\n runOnJS(triggerHapticFeedback)();\n }\n }\n });\n\n const deleteButtonStyle = useAnimatedStyle(() => {\n // Show delete button when delete mode is active (persists after release)\n const shouldShow = showDelete.value;\n return {\n opacity: shouldShow ? 1 : 0,\n pointerEvents: shouldShow ? \"auto\" : \"none\",\n transform: [\n { scale: withTiming(shouldShow ? 1 : 0.6, { duration: 120 }) },\n ],\n };\n });\n\n // Watch for when global delete mode is cancelled (user tapped outside)\n useAnimatedReaction(\n () => anyItemInDeleteMode.value,\n (current, previous) => {\n \"worklet\";\n // If delete mode was cancelled globally (user tapped outside)\n if (previous && !current && deleteModeActive.value) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n }\n }\n );\n\n // Wiggle animation — triggers on editMode/active changes and delete mode\n useAnimatedReaction(\n () => ({\n isEditMode: dragMode.value,\n isActive: position.active.value > 0.5,\n inDeleteMode: deleteModeActive.value,\n anyInDeleteMode: anyItemInDeleteMode.value,\n }),\n ({ isEditMode, isActive, inDeleteMode, anyInDeleteMode }) => {\n // Determine the target wiggle mode\n let targetMode: \"none\" | \"normal\" | \"delete\" = \"none\";\n if (inDeleteMode && (wiggleDeleteMode || wiggle)) {\n targetMode = \"delete\";\n } else if (anyInDeleteMode && !isActive && wiggle) {\n targetMode = \"normal\";\n } else if (isEditMode && !isActive && wiggle) {\n targetMode = \"normal\";\n }\n\n // If no wiggle is configured at all, stop animation\n if (!wiggle && !wiggleDeleteMode) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If in delete mode but no wiggleDeleteMode and no wiggle, stop animation\n if (targetMode === \"delete\" && !wiggleDeleteMode && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If normal mode but no wiggle, stop animation\n if (targetMode === \"normal\" && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // Only restart animation if mode changed\n if (currentWiggleMode.value === targetMode) {\n return; // Already in the correct mode, don't restart\n }\n\n const previousMode = currentWiggleMode.value;\n currentWiggleMode.value = targetMode;\n\n // Cancel current animation\n cancelAnimation(rotation);\n\n // If this item is in delete mode, use wiggleDeleteMode if provided, otherwise use 2x degrees and 0.7x duration\n if (targetMode === \"delete\") {\n const deleteWiggleDegrees = wiggleDeleteMode\n ? wiggleDeleteMode.degrees\n : (wiggle?.degrees ?? 0) * 2;\n const deleteWiggleDuration = wiggleDeleteMode\n ? wiggleDeleteMode.duration\n : (wiggle?.duration ?? 200) * 0.7; // Faster wiggle\n\n // If transitioning from normal wiggle, preserve the phase by scaling\n if (previousMode === \"normal\" && wiggle) {\n const currentRot = rotation.value;\n const scaleFactor = deleteWiggleDegrees / wiggle.degrees;\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n }),\n withTiming(-deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Normal wiggle (when dragging but not this item, or any item in delete mode)\n else if (targetMode === \"normal\") {\n // If transitioning from delete wiggle, preserve the phase by scaling\n if (previousMode === \"delete\") {\n const currentRot = rotation.value;\n const scaleFactor = wiggle.degrees / (wiggle.degrees * 2);\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n }),\n withTiming(-wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Stop wiggling\n else {\n rotation.value = withTiming(0, { duration: 150 });\n }\n },\n [\n dragMode,\n position.active,\n deleteModeActive,\n anyItemInDeleteMode,\n wiggle,\n wiggleDeleteMode,\n ]\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n const scale = position.active.value\n ? withTiming(dragSizeIncreaseFactor, { duration: 120 })\n : withTiming(1, { duration: 120 });\n\n return {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n transform: [\n { translateX: position.x.value as any },\n { translateY: position.y.value as any },\n { scale: scale as any },\n { rotate: `${rotation.value}deg` as any },\n ],\n zIndex: position.active.value ? 2 : 0,\n } as any;\n });\n\n // Track delete mode on JS thread for conditional rendering\n const [isInDeleteMode, setIsInDeleteMode] = useState(false);\n\n useAnimatedReaction(\n () => deleteModeActive.value,\n (current) => {\n runOnJS(setIsInDeleteMode)(current);\n }\n );\n\n const handleDelete = () => {\n // Exit delete mode when delete button is pressed\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n if (onDelete) {\n onDelete();\n }\n };\n\n return (\n <Animated.View style={animatedStyle} pointerEvents=\"box-none\">\n {/* Full-item Pressable for delete - only active when in delete mode */}\n {isInDeleteMode && (\n <Pressable\n onPressIn={() => {\n // Mark that we're pressing an item to prevent ScrollView from canceling delete mode\n isPressingDeleteItem.value = true;\n }}\n onPressOut={() => {\n // Clear the flag after a short delay to allow onPress to fire\n setTimeout(() => {\n isPressingDeleteItem.value = false;\n }, 50);\n }}\n onPress={handleDelete}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: itemWidth,\n height: itemHeight,\n zIndex: 2,\n }}\n />\n )}\n\n {/* Delete button (×) - visual indicator only */}\n <Animated.View\n style={[\n {\n position: \"absolute\",\n top: itemHeight * 0.01,\n right: itemWidth * 0.04,\n width: itemWidth * 0.2,\n height: itemHeight * 0.2,\n borderRadius: 12,\n justifyContent: \"center\",\n alignItems: \"center\",\n zIndex: 3,\n },\n deleteButtonStyle,\n ]}\n pointerEvents=\"none\"\n >\n <Text\n style={{\n fontSize: itemWidth * 0.2,\n color: \"black\",\n fontWeight: 500,\n }}\n >\n ×\n </Text>\n </Animated.View>\n\n {children}\n </Animated.View>\n );\n}\n"],"mappings":"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","ignoreList":[]}
@@ -65,6 +65,7 @@ const SwappableGrid = /*#__PURE__*/(0, _react.forwardRef)(({
65
65
  wiggle,
66
66
  wiggleDeleteMode,
67
67
  holdStillToDeleteMs = 1000,
68
+ hapticFeedback = false,
68
69
  style,
69
70
  dragSizeIncreaseFactor = 1.06,
70
71
  scrollThreshold = 100,
@@ -323,6 +324,7 @@ const SwappableGrid = /*#__PURE__*/(0, _react.forwardRef)(({
323
324
  wiggle: wiggle,
324
325
  wiggleDeleteMode: wiggleDeleteMode,
325
326
  holdStillToDeleteMs: holdStillToDeleteMs,
327
+ hapticFeedback: hapticFeedback,
326
328
  dragSizeIncreaseFactor: dragSizeIncreaseFactor,
327
329
  disableHoldToDelete: !onDelete || !!deleteComponent,
328
330
  onDelete: () => {
@@ -1 +1 @@
1
- {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeReanimated","_reactNativeGestureHandler","_computerMinHeight","_interopRequireDefault","_useGridLayout","_ChildWrapper","_indexCalculations","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","AnimatedScrollView","Animated","createAnimatedComponent","ScrollView","normalizeKey","k","String","replace","SwappableGrid","forwardRef","children","itemWidth","itemHeight","gap","containerPadding","holdToDragMs","numColumns","onDragEnd","onOrderChange","onDelete","wiggle","wiggleDeleteMode","holdStillToDeleteMs","style","dragSizeIncreaseFactor","scrollThreshold","scrollSpeed","trailingComponent","deleteComponent","deleteComponentStyle","reverse","ref","scrollViewRef","useAnimatedRef","showTrailingComponent","setShowTrailingComponent","useState","showDeleteComponent","setShowDeleteComponent","isDragging","setIsDragging","currentNumColumns","setCurrentNumColumns","touchEndTimeoutRef","React","useRef","deleteComponentRef","useEffect","setTimeout","handleOnOrderChange","order","map","key","paddingBottom","useMemo","styleObj","StyleSheet","flatten","orderState","composed","dynamicNumColumns","onLayoutContent","originalOnLayoutContent","onLayoutScrollView","onScroll","deleteItem","childArray","positions","dragMode","anyItemInDeleteMode","isPressingDeleteItem","deleteComponentPosition","useGridLayout","undefined","contentPaddingBottom","possibleCols","Math","floor","nativeEvent","layout","width","max","useAnimatedReaction","value","isDraggingValue","runOnJS","useImperativeHandle","cancelDeleteMode","trailingX","useDerivedValue","x","indexToXY","index","length","trailingY","y","trailingStyle","useAnimatedStyle","left","top","deleteComponentX","cols","totalWidth","deleteComponentY","rows","ceil","baseY","deleteComponentStyleAnimated","baseStyle","position","height","showTrailing","showDelete","itemsCountForHeight","baseHeight","computeMinHeight","calculatedHeight","totalItems","deleteComponentBottom","createElement","onLayout","scrollEventThrottle","contentContainerStyle","onTouchEnd","current","clearTimeout","View","styles","container","padding","GestureDetector","gesture","pointerEvents","absoluteFill","child","find","c","disableHoldToDelete","collapsable","flex","create","_default","exports"],"sources":["SwappableGrid.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useState,\n useImperativeHandle,\n forwardRef,\n} from \"react\";\nimport {\n View,\n StyleSheet,\n ScrollView,\n StyleProp,\n ViewStyle,\n LayoutChangeEvent,\n} from \"react-native\";\nimport Animated, {\n useAnimatedRef,\n useAnimatedStyle,\n useDerivedValue,\n useAnimatedReaction,\n runOnJS,\n} from \"react-native-reanimated\";\nimport { GestureDetector } from \"react-native-gesture-handler\";\nimport computeMinHeight from \"./utils/helpers/computerMinHeight\";\nimport { useGridLayout } from \"./utils/useGridLayout\";\nimport ChildWrapper from \"./ChildWrapper\";\nimport { indexToXY } from \"./utils/helpers/indexCalculations\";\n\nconst AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);\n\nconst normalizeKey = (k: React.Key) => String(k).replace(/^\\.\\$/, \"\");\n\n/**\n * Props for the SwappableGrid component\n */\ntype SwappableGridProps = {\n /** The child components to render in the grid. Each child should have a unique key. */\n children: ReactNode;\n /** Width of each grid item in pixels */\n itemWidth: number;\n /** Height of each grid item in pixels */\n itemHeight: number;\n /** Gap between grid items in pixels. Defaults to 8. */\n gap?: number;\n /** Padding around the container in pixels. Defaults to 8. */\n containerPadding?: number;\n /** Duration in milliseconds to hold before drag starts. Defaults to 300. */\n holdToDragMs?: number;\n /** Number of columns in the grid. If not provided, will be calculated automatically based on container width. */\n numColumns?: number;\n /** Wiggle animation configuration when items are in drag mode or delete mode */\n wiggle?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Wiggle animation configuration specifically for delete mode. If not provided, uses 2x degrees and 0.7x duration of wiggle prop. */\n wiggleDeleteMode?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */\n holdStillToDeleteMs?: number;\n /** Callback fired when drag ends, providing the ordered array of child nodes */\n onDragEnd?: (ordered: ChildNode[]) => void;\n /** Callback fired when the order changes, providing an array of keys in the new order */\n onOrderChange?: (keys: string[]) => void;\n /** Callback fired when an item is deleted, providing the key of the deleted item */\n onDelete?: (key: string) => void;\n /** Factor by which the dragged item scales up. Defaults to 1.06. */\n dragSizeIncreaseFactor?: number;\n /** Speed of auto-scrolling when dragging near edges. Defaults to 10. */\n scrollSpeed?: number;\n /** Distance from edge in pixels that triggers auto-scroll. Defaults to 100. */\n scrollThreshold?: number;\n /** Custom style for the ScrollView container */\n style?: StyleProp<ViewStyle>;\n /** Component to render after all grid items (e.g., an \"Add\" button) */\n trailingComponent?: ReactNode;\n /** Component to render as a delete target (shown when dragging). If provided, disables hold-to-delete feature. */\n deleteComponent?: ReactNode;\n /** Custom style for the delete component. If provided, allows custom positioning. */\n deleteComponentStyle?: StyleProp<ViewStyle>;\n /** If true, reverses the order of items (right-to-left, bottom-to-top). Defaults to false. */\n reverse?: boolean;\n};\n\n/**\n * Ref methods for SwappableGrid component\n */\nexport interface SwappableGridRef {\n /** Cancels the delete mode if any item is currently in delete mode */\n cancelDeleteMode: () => void;\n}\n\n/**\n * SwappableGrid - A React Native component for creating a draggable, swappable grid layout.\n *\n * Features:\n * - Drag and drop to reorder items\n * - Long press to enter drag mode\n * - Auto-scroll when dragging near edges\n * - Optional wiggle animation during drag mode\n * - Optional delete functionality with hold-to-delete or delete component\n * - Support for trailing components (e.g., \"Add\" button)\n * - Automatic column calculation based on container width\n *\n * @example\n * ```tsx\n * <SwappableGrid\n * itemWidth={100}\n * itemHeight={100}\n * numColumns={3}\n * onOrderChange={(keys) => console.log('New order:', keys)}\n * >\n * {items.map(item => (\n * <View key={item.id}>{item.content}</View>\n * ))}\n * </SwappableGrid>\n * ```\n */\nconst SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(\n (\n {\n children,\n itemWidth,\n itemHeight,\n gap = 8,\n containerPadding = 8,\n holdToDragMs = 300,\n numColumns,\n onDragEnd,\n onOrderChange,\n onDelete,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n style,\n dragSizeIncreaseFactor = 1.06,\n scrollThreshold = 100,\n scrollSpeed = 10,\n trailingComponent,\n deleteComponent,\n deleteComponentStyle,\n reverse = false,\n },\n ref\n ) => {\n // MUST be Animated ref for scrollTo\n const scrollViewRef = useAnimatedRef<Animated.ScrollView>();\n const [showTrailingComponent, setShowTrailingComponent] =\n useState<boolean>(false);\n const [showDeleteComponent, setShowDeleteComponent] =\n useState<boolean>(false);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [currentNumColumns, setCurrentNumColumns] = useState<number>(\n numColumns || 1\n );\n const touchEndTimeoutRef = React.useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n const deleteComponentRef = React.useRef<View>(null);\n\n useEffect(() => {\n setTimeout(() => {\n setShowTrailingComponent(true);\n setShowDeleteComponent(true);\n }, 50); // This is kinda a hack that makes the trailing component render in the correct position because it lets the other components render first to make the position calculation correct.\n }, []);\n\n const handleOnOrderChange = (order: string[]) => {\n if (onOrderChange) onOrderChange(order.map((key) => normalizeKey(key)));\n };\n\n // Extract paddingBottom from style prop to allow dragging into padding area\n const paddingBottom = React.useMemo(() => {\n if (!style) return 0;\n const styleObj = StyleSheet.flatten(style);\n return (styleObj.paddingBottom as number) || 0;\n }, [style]);\n\n const {\n orderState,\n composed,\n dynamicNumColumns,\n onLayoutContent: originalOnLayoutContent, // layout of the inner content view (for width/cols)\n onLayoutScrollView, // layout of the scroll viewport (for height)\n onScroll,\n deleteItem,\n childArray,\n positions,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n order,\n deleteComponentPosition,\n } = useGridLayout({\n reverse,\n children,\n holdToDragMs,\n itemWidth,\n itemHeight,\n gap,\n containerPadding,\n numColumns,\n onDragEnd,\n onOrderChange: handleOnOrderChange,\n onDelete: onDelete ? (key) => onDelete(normalizeKey(key)) : undefined,\n scrollViewRef,\n scrollSpeed,\n scrollThreshold,\n contentPaddingBottom: paddingBottom,\n });\n\n // Track numColumns changes for height calculation\n const onLayoutContent = (e: LayoutChangeEvent) => {\n originalOnLayoutContent(e);\n // Update currentNumColumns for height calculation\n if (numColumns) {\n setCurrentNumColumns(numColumns);\n } else {\n const possibleCols = Math.floor(\n (e.nativeEvent.layout.width - containerPadding * 2 + gap) /\n (itemWidth + gap)\n );\n setCurrentNumColumns(Math.max(1, possibleCols));\n }\n };\n\n // Track drag state to show/hide delete component\n useAnimatedReaction(\n () => dragMode.value,\n (isDraggingValue) => {\n runOnJS(setIsDragging)(isDraggingValue);\n }\n );\n\n // Expose cancel delete mode function to parent\n useImperativeHandle(\n ref,\n () => ({\n cancelDeleteMode: () => {\n if (anyItemInDeleteMode.value) {\n anyItemInDeleteMode.value = false;\n }\n },\n }),\n [anyItemInDeleteMode]\n );\n\n const trailingX = useDerivedValue(() => {\n const { x } = indexToXY({\n index: order.value.length, // AFTER last swappable\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return x;\n });\n\n const trailingY = useDerivedValue(() => {\n const { y } = indexToXY({\n index: order.value.length,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return y;\n });\n\n const trailingStyle = useAnimatedStyle(() => ({\n left: trailingX.value,\n top: trailingY.value,\n }));\n\n // Calculate default delete component position (bottom center)\n const deleteComponentX = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: center horizontally\n const cols = dynamicNumColumns.value;\n const totalWidth =\n cols * itemWidth + (cols - 1) * gap + containerPadding * 2;\n return (totalWidth - itemWidth) / 2;\n });\n\n const deleteComponentY = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: bottom of grid (after all items)\n // Account for trailing component if it exists\n const rows = Math.ceil(order.value.length / dynamicNumColumns.value);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n if (trailingComponent && showTrailingComponent) {\n return baseY + (itemHeight + gap);\n }\n\n return baseY + gap;\n });\n\n const deleteComponentStyleAnimated = useAnimatedStyle(() => {\n const baseStyle: any = {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n };\n\n // Use custom position if provided, otherwise use default\n if (deleteComponentStyle) {\n return baseStyle;\n }\n\n return {\n ...baseStyle,\n left: deleteComponentX.value,\n top: deleteComponentY.value,\n };\n });\n\n // Update delete component position for drop detection when default position changes\n useDerivedValue(() => {\n if (deleteComponent && deleteComponentPosition && !deleteComponentStyle) {\n // Only update if using default position (custom style uses onLayout)\n deleteComponentPosition.value = {\n x: deleteComponentX.value,\n y: deleteComponentY.value,\n width: itemWidth,\n height: itemHeight,\n };\n }\n });\n\n const showTrailing = !!(trailingComponent && showTrailingComponent);\n const showDelete = !!(deleteComponent && showDeleteComponent);\n // Make sure we calculate room for both trailing and delete components\n // Trailing component is part of the grid, delete component is positioned below\n const itemsCountForHeight = orderState.length + (showTrailing ? 1 : 0);\n\n // Calculate minimum height needed (on JS thread since computeMinHeight is not a worklet)\n const baseHeight = computeMinHeight(\n itemsCountForHeight,\n currentNumColumns,\n itemHeight + gap,\n containerPadding\n );\n\n // If delete component is shown and using default position, add extra space for it\n let calculatedHeight = baseHeight;\n if (showDelete && !deleteComponentStyle) {\n // Account for trailing component when calculating rows (same logic as deleteComponentY)\n const totalItems = orderState.length + (showTrailing ? 1 : 0);\n const rows = Math.ceil(totalItems / currentNumColumns);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n let deleteComponentY = baseY;\n if (showTrailing) {\n deleteComponentY = baseY + (itemHeight + gap);\n } else {\n deleteComponentY = baseY + gap;\n }\n\n const deleteComponentBottom = deleteComponentY + itemHeight;\n // Ensure container is tall enough to show the delete component\n calculatedHeight = Math.max(\n baseHeight,\n deleteComponentBottom + containerPadding\n );\n }\n\n return (\n <AnimatedScrollView\n ref={scrollViewRef}\n onScroll={onScroll}\n onLayout={onLayoutScrollView} // viewport height comes from the SCROLLVIEW\n scrollEventThrottle={16}\n contentContainerStyle={[style]}\n onTouchEnd={() => {\n // Cancel delete mode when user touches outside items\n // Add a small delay to avoid canceling when user taps on items\n // (items might briefly activate, which would prevent cancellation)\n if (touchEndTimeoutRef.current) {\n clearTimeout(touchEndTimeoutRef.current);\n }\n touchEndTimeoutRef.current = setTimeout(() => {\n // Only cancel if still in delete mode and not dragging\n // Don't cancel if user is currently pressing an item (they might be deleting it)\n // This ensures we don't cancel when user is interacting with items\n if (\n anyItemInDeleteMode.value &&\n !dragMode.value &&\n !isPressingDeleteItem.value\n ) {\n anyItemInDeleteMode.value = false;\n }\n }, 100); // Small delay to let item interactions complete\n }}\n >\n <View\n style={[\n styles.container,\n {\n padding: containerPadding,\n height: calculatedHeight,\n },\n ]}\n onLayout={onLayoutContent}\n >\n <GestureDetector gesture={composed}>\n <View pointerEvents=\"box-none\" style={StyleSheet.absoluteFill}>\n {orderState.map((key) => {\n const child = childArray.find(\n (c) => c.key && normalizeKey(c.key) === normalizeKey(key)\n );\n if (!child) return null;\n return (\n <ChildWrapper\n key={key}\n position={positions[key]}\n itemWidth={itemWidth}\n itemHeight={itemHeight}\n dragMode={dragMode}\n anyItemInDeleteMode={anyItemInDeleteMode}\n isPressingDeleteItem={isPressingDeleteItem}\n wiggle={wiggle}\n wiggleDeleteMode={wiggleDeleteMode}\n holdStillToDeleteMs={holdStillToDeleteMs}\n dragSizeIncreaseFactor={dragSizeIncreaseFactor}\n disableHoldToDelete={!onDelete || !!deleteComponent}\n onDelete={() => {\n deleteItem(key);\n if (onDelete) {\n onDelete(normalizeKey(key));\n }\n }}\n >\n {child}\n </ChildWrapper>\n );\n })}\n </View>\n </GestureDetector>\n\n {/* Trailing rendered OUTSIDE the GestureDetector */}\n {trailingComponent && showTrailingComponent && (\n <Animated.View\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n },\n trailingStyle, // 👈 left/top from UI thread\n ]}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {trailingComponent}\n </View>\n </Animated.View>\n )}\n\n {/* Delete component rendered OUTSIDE the GestureDetector - only show when dragging */}\n {deleteComponent && showDeleteComponent && isDragging && (\n <Animated.View\n ref={deleteComponentRef}\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n deleteComponentStyleAnimated,\n deleteComponentStyle, // User can override position with custom style\n ]}\n onLayout={(e) => {\n // Update position for drop detection\n const { x, y, width, height } = e.nativeEvent.layout;\n if (deleteComponentPosition) {\n deleteComponentPosition.value = { x, y, width, height };\n }\n }}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {deleteComponent}\n </View>\n </Animated.View>\n )}\n </View>\n </AnimatedScrollView>\n );\n }\n);\n\nconst styles = StyleSheet.create({\n container: {\n width: \"100%\",\n },\n});\n\nexport default SwappableGrid;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["_react","_interopRequireWildcard","require","_reactNative","_reactNativeReanimated","_reactNativeGestureHandler","_computerMinHeight","_interopRequireDefault","_useGridLayout","_ChildWrapper","_indexCalculations","e","__esModule","default","t","WeakMap","r","n","o","i","f","__proto__","has","get","set","hasOwnProperty","call","Object","defineProperty","getOwnPropertyDescriptor","AnimatedScrollView","Animated","createAnimatedComponent","ScrollView","normalizeKey","k","String","replace","SwappableGrid","forwardRef","children","itemWidth","itemHeight","gap","containerPadding","holdToDragMs","numColumns","onDragEnd","onOrderChange","onDelete","wiggle","wiggleDeleteMode","holdStillToDeleteMs","hapticFeedback","style","dragSizeIncreaseFactor","scrollThreshold","scrollSpeed","trailingComponent","deleteComponent","deleteComponentStyle","reverse","ref","scrollViewRef","useAnimatedRef","showTrailingComponent","setShowTrailingComponent","useState","showDeleteComponent","setShowDeleteComponent","isDragging","setIsDragging","currentNumColumns","setCurrentNumColumns","touchEndTimeoutRef","React","useRef","deleteComponentRef","useEffect","setTimeout","handleOnOrderChange","order","map","key","paddingBottom","useMemo","styleObj","StyleSheet","flatten","orderState","composed","dynamicNumColumns","onLayoutContent","originalOnLayoutContent","onLayoutScrollView","onScroll","deleteItem","childArray","positions","dragMode","anyItemInDeleteMode","isPressingDeleteItem","deleteComponentPosition","useGridLayout","undefined","contentPaddingBottom","possibleCols","Math","floor","nativeEvent","layout","width","max","useAnimatedReaction","value","isDraggingValue","runOnJS","useImperativeHandle","cancelDeleteMode","trailingX","useDerivedValue","x","indexToXY","index","length","trailingY","y","trailingStyle","useAnimatedStyle","left","top","deleteComponentX","cols","totalWidth","deleteComponentY","rows","ceil","baseY","deleteComponentStyleAnimated","baseStyle","position","height","showTrailing","showDelete","itemsCountForHeight","baseHeight","computeMinHeight","calculatedHeight","totalItems","deleteComponentBottom","createElement","onLayout","scrollEventThrottle","contentContainerStyle","onTouchEnd","current","clearTimeout","View","styles","container","padding","GestureDetector","gesture","pointerEvents","absoluteFill","child","find","c","disableHoldToDelete","collapsable","flex","create","_default","exports"],"sources":["SwappableGrid.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useState,\n useImperativeHandle,\n forwardRef,\n} from \"react\";\nimport {\n View,\n StyleSheet,\n ScrollView,\n StyleProp,\n ViewStyle,\n LayoutChangeEvent,\n} from \"react-native\";\nimport Animated, {\n useAnimatedRef,\n useAnimatedStyle,\n useDerivedValue,\n useAnimatedReaction,\n runOnJS,\n} from \"react-native-reanimated\";\nimport { GestureDetector } from \"react-native-gesture-handler\";\nimport computeMinHeight from \"./utils/helpers/computerMinHeight\";\nimport { useGridLayout } from \"./utils/useGridLayout\";\nimport ChildWrapper from \"./ChildWrapper\";\nimport { indexToXY } from \"./utils/helpers/indexCalculations\";\n\nconst AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);\n\nconst normalizeKey = (k: React.Key) => String(k).replace(/^\\.\\$/, \"\");\n\n/**\n * Props for the SwappableGrid component\n */\ntype SwappableGridProps = {\n /** The child components to render in the grid. Each child should have a unique key. */\n children: ReactNode;\n /** Width of each grid item in pixels */\n itemWidth: number;\n /** Height of each grid item in pixels */\n itemHeight: number;\n /** Gap between grid items in pixels. Defaults to 8. */\n gap?: number;\n /** Padding around the container in pixels. Defaults to 8. */\n containerPadding?: number;\n /** Duration in milliseconds to hold before drag starts. Defaults to 300. */\n holdToDragMs?: number;\n /** Number of columns in the grid. If not provided, will be calculated automatically based on container width. */\n numColumns?: number;\n /** Wiggle animation configuration when items are in drag mode or delete mode */\n wiggle?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Wiggle animation configuration specifically for delete mode. If not provided, uses 2x degrees and 0.7x duration of wiggle prop. */\n wiggleDeleteMode?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */\n holdStillToDeleteMs?: number;\n /** Enable haptic feedback (vibration) when entering delete mode. Defaults to false. */\n hapticFeedback?: boolean;\n /** Callback fired when drag ends, providing the ordered array of child nodes */\n onDragEnd?: (ordered: ChildNode[]) => void;\n /** Callback fired when the order changes, providing an array of keys in the new order */\n onOrderChange?: (keys: string[]) => void;\n /** Callback fired when an item is deleted, providing the key of the deleted item */\n onDelete?: (key: string) => void;\n /** Factor by which the dragged item scales up. Defaults to 1.06. */\n dragSizeIncreaseFactor?: number;\n /** Speed of auto-scrolling when dragging near edges. Defaults to 10. */\n scrollSpeed?: number;\n /** Distance from edge in pixels that triggers auto-scroll. Defaults to 100. */\n scrollThreshold?: number;\n /** Custom style for the ScrollView container */\n style?: StyleProp<ViewStyle>;\n /** Component to render after all grid items (e.g., an \"Add\" button) */\n trailingComponent?: ReactNode;\n /** Component to render as a delete target (shown when dragging). If provided, disables hold-to-delete feature. */\n deleteComponent?: ReactNode;\n /** Custom style for the delete component. If provided, allows custom positioning. */\n deleteComponentStyle?: StyleProp<ViewStyle>;\n /** If true, reverses the order of items (right-to-left, bottom-to-top). Defaults to false. */\n reverse?: boolean;\n};\n\n/**\n * Ref methods for SwappableGrid component\n */\nexport interface SwappableGridRef {\n /** Cancels the delete mode if any item is currently in delete mode */\n cancelDeleteMode: () => void;\n}\n\n/**\n * SwappableGrid - A React Native component for creating a draggable, swappable grid layout.\n *\n * Features:\n * - Drag and drop to reorder items\n * - Long press to enter drag mode\n * - Auto-scroll when dragging near edges\n * - Optional wiggle animation during drag mode\n * - Optional delete functionality with hold-to-delete or delete component\n * - Support for trailing components (e.g., \"Add\" button)\n * - Automatic column calculation based on container width\n *\n * @example\n * ```tsx\n * <SwappableGrid\n * itemWidth={100}\n * itemHeight={100}\n * numColumns={3}\n * onOrderChange={(keys) => console.log('New order:', keys)}\n * >\n * {items.map(item => (\n * <View key={item.id}>{item.content}</View>\n * ))}\n * </SwappableGrid>\n * ```\n */\nconst SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(\n (\n {\n children,\n itemWidth,\n itemHeight,\n gap = 8,\n containerPadding = 8,\n holdToDragMs = 300,\n numColumns,\n onDragEnd,\n onOrderChange,\n onDelete,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n hapticFeedback = false,\n style,\n dragSizeIncreaseFactor = 1.06,\n scrollThreshold = 100,\n scrollSpeed = 10,\n trailingComponent,\n deleteComponent,\n deleteComponentStyle,\n reverse = false,\n },\n ref\n ) => {\n // MUST be Animated ref for scrollTo\n const scrollViewRef = useAnimatedRef<Animated.ScrollView>();\n const [showTrailingComponent, setShowTrailingComponent] =\n useState<boolean>(false);\n const [showDeleteComponent, setShowDeleteComponent] =\n useState<boolean>(false);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [currentNumColumns, setCurrentNumColumns] = useState<number>(\n numColumns || 1\n );\n const touchEndTimeoutRef = React.useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n const deleteComponentRef = React.useRef<View>(null);\n\n useEffect(() => {\n setTimeout(() => {\n setShowTrailingComponent(true);\n setShowDeleteComponent(true);\n }, 50); // This is kinda a hack that makes the trailing component render in the correct position because it lets the other components render first to make the position calculation correct.\n }, []);\n\n const handleOnOrderChange = (order: string[]) => {\n if (onOrderChange) onOrderChange(order.map((key) => normalizeKey(key)));\n };\n\n // Extract paddingBottom from style prop to allow dragging into padding area\n const paddingBottom = React.useMemo(() => {\n if (!style) return 0;\n const styleObj = StyleSheet.flatten(style);\n return (styleObj.paddingBottom as number) || 0;\n }, [style]);\n\n const {\n orderState,\n composed,\n dynamicNumColumns,\n onLayoutContent: originalOnLayoutContent, // layout of the inner content view (for width/cols)\n onLayoutScrollView, // layout of the scroll viewport (for height)\n onScroll,\n deleteItem,\n childArray,\n positions,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n order,\n deleteComponentPosition,\n } = useGridLayout({\n reverse,\n children,\n holdToDragMs,\n itemWidth,\n itemHeight,\n gap,\n containerPadding,\n numColumns,\n onDragEnd,\n onOrderChange: handleOnOrderChange,\n onDelete: onDelete ? (key) => onDelete(normalizeKey(key)) : undefined,\n scrollViewRef,\n scrollSpeed,\n scrollThreshold,\n contentPaddingBottom: paddingBottom,\n });\n\n // Track numColumns changes for height calculation\n const onLayoutContent = (e: LayoutChangeEvent) => {\n originalOnLayoutContent(e);\n // Update currentNumColumns for height calculation\n if (numColumns) {\n setCurrentNumColumns(numColumns);\n } else {\n const possibleCols = Math.floor(\n (e.nativeEvent.layout.width - containerPadding * 2 + gap) /\n (itemWidth + gap)\n );\n setCurrentNumColumns(Math.max(1, possibleCols));\n }\n };\n\n // Track drag state to show/hide delete component\n useAnimatedReaction(\n () => dragMode.value,\n (isDraggingValue) => {\n runOnJS(setIsDragging)(isDraggingValue);\n }\n );\n\n // Expose cancel delete mode function to parent\n useImperativeHandle(\n ref,\n () => ({\n cancelDeleteMode: () => {\n if (anyItemInDeleteMode.value) {\n anyItemInDeleteMode.value = false;\n }\n },\n }),\n [anyItemInDeleteMode]\n );\n\n const trailingX = useDerivedValue(() => {\n const { x } = indexToXY({\n index: order.value.length, // AFTER last swappable\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return x;\n });\n\n const trailingY = useDerivedValue(() => {\n const { y } = indexToXY({\n index: order.value.length,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return y;\n });\n\n const trailingStyle = useAnimatedStyle(() => ({\n left: trailingX.value,\n top: trailingY.value,\n }));\n\n // Calculate default delete component position (bottom center)\n const deleteComponentX = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: center horizontally\n const cols = dynamicNumColumns.value;\n const totalWidth =\n cols * itemWidth + (cols - 1) * gap + containerPadding * 2;\n return (totalWidth - itemWidth) / 2;\n });\n\n const deleteComponentY = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: bottom of grid (after all items)\n // Account for trailing component if it exists\n const rows = Math.ceil(order.value.length / dynamicNumColumns.value);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n if (trailingComponent && showTrailingComponent) {\n return baseY + (itemHeight + gap);\n }\n\n return baseY + gap;\n });\n\n const deleteComponentStyleAnimated = useAnimatedStyle(() => {\n const baseStyle: any = {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n };\n\n // Use custom position if provided, otherwise use default\n if (deleteComponentStyle) {\n return baseStyle;\n }\n\n return {\n ...baseStyle,\n left: deleteComponentX.value,\n top: deleteComponentY.value,\n };\n });\n\n // Update delete component position for drop detection when default position changes\n useDerivedValue(() => {\n if (deleteComponent && deleteComponentPosition && !deleteComponentStyle) {\n // Only update if using default position (custom style uses onLayout)\n deleteComponentPosition.value = {\n x: deleteComponentX.value,\n y: deleteComponentY.value,\n width: itemWidth,\n height: itemHeight,\n };\n }\n });\n\n const showTrailing = !!(trailingComponent && showTrailingComponent);\n const showDelete = !!(deleteComponent && showDeleteComponent);\n // Make sure we calculate room for both trailing and delete components\n // Trailing component is part of the grid, delete component is positioned below\n const itemsCountForHeight = orderState.length + (showTrailing ? 1 : 0);\n\n // Calculate minimum height needed (on JS thread since computeMinHeight is not a worklet)\n const baseHeight = computeMinHeight(\n itemsCountForHeight,\n currentNumColumns,\n itemHeight + gap,\n containerPadding\n );\n\n // If delete component is shown and using default position, add extra space for it\n let calculatedHeight = baseHeight;\n if (showDelete && !deleteComponentStyle) {\n // Account for trailing component when calculating rows (same logic as deleteComponentY)\n const totalItems = orderState.length + (showTrailing ? 1 : 0);\n const rows = Math.ceil(totalItems / currentNumColumns);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n let deleteComponentY = baseY;\n if (showTrailing) {\n deleteComponentY = baseY + (itemHeight + gap);\n } else {\n deleteComponentY = baseY + gap;\n }\n\n const deleteComponentBottom = deleteComponentY + itemHeight;\n // Ensure container is tall enough to show the delete component\n calculatedHeight = Math.max(\n baseHeight,\n deleteComponentBottom + containerPadding\n );\n }\n\n return (\n <AnimatedScrollView\n ref={scrollViewRef}\n onScroll={onScroll}\n onLayout={onLayoutScrollView} // viewport height comes from the SCROLLVIEW\n scrollEventThrottle={16}\n contentContainerStyle={[style]}\n onTouchEnd={() => {\n // Cancel delete mode when user touches outside items\n // Add a small delay to avoid canceling when user taps on items\n // (items might briefly activate, which would prevent cancellation)\n if (touchEndTimeoutRef.current) {\n clearTimeout(touchEndTimeoutRef.current);\n }\n touchEndTimeoutRef.current = setTimeout(() => {\n // Only cancel if still in delete mode and not dragging\n // Don't cancel if user is currently pressing an item (they might be deleting it)\n // This ensures we don't cancel when user is interacting with items\n if (\n anyItemInDeleteMode.value &&\n !dragMode.value &&\n !isPressingDeleteItem.value\n ) {\n anyItemInDeleteMode.value = false;\n }\n }, 100); // Small delay to let item interactions complete\n }}\n >\n <View\n style={[\n styles.container,\n {\n padding: containerPadding,\n height: calculatedHeight,\n },\n ]}\n onLayout={onLayoutContent}\n >\n <GestureDetector gesture={composed}>\n <View pointerEvents=\"box-none\" style={StyleSheet.absoluteFill}>\n {orderState.map((key) => {\n const child = childArray.find(\n (c) => c.key && normalizeKey(c.key) === normalizeKey(key)\n );\n if (!child) return null;\n return (\n <ChildWrapper\n key={key}\n position={positions[key]}\n itemWidth={itemWidth}\n itemHeight={itemHeight}\n dragMode={dragMode}\n anyItemInDeleteMode={anyItemInDeleteMode}\n isPressingDeleteItem={isPressingDeleteItem}\n wiggle={wiggle}\n wiggleDeleteMode={wiggleDeleteMode}\n holdStillToDeleteMs={holdStillToDeleteMs}\n hapticFeedback={hapticFeedback}\n dragSizeIncreaseFactor={dragSizeIncreaseFactor}\n disableHoldToDelete={!onDelete || !!deleteComponent}\n onDelete={() => {\n deleteItem(key);\n if (onDelete) {\n onDelete(normalizeKey(key));\n }\n }}\n >\n {child}\n </ChildWrapper>\n );\n })}\n </View>\n </GestureDetector>\n\n {/* Trailing rendered OUTSIDE the GestureDetector */}\n {trailingComponent && showTrailingComponent && (\n <Animated.View\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n },\n trailingStyle, // 👈 left/top from UI thread\n ]}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {trailingComponent}\n </View>\n </Animated.View>\n )}\n\n {/* Delete component rendered OUTSIDE the GestureDetector - only show when dragging */}\n {deleteComponent && showDeleteComponent && isDragging && (\n <Animated.View\n ref={deleteComponentRef}\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n deleteComponentStyleAnimated,\n deleteComponentStyle, // User can override position with custom style\n ]}\n onLayout={(e) => {\n // Update position for drop detection\n const { x, y, width, height } = e.nativeEvent.layout;\n if (deleteComponentPosition) {\n deleteComponentPosition.value = { x, y, width, height };\n }\n }}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {deleteComponent}\n </View>\n </Animated.View>\n )}\n </View>\n </AnimatedScrollView>\n );\n }\n);\n\nconst styles = StyleSheet.create({\n container: {\n width: \"100%\",\n },\n});\n\nexport default SwappableGrid;\n"],"mappings":"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","ignoreList":[]}
@@ -1,5 +1,13 @@
1
1
  import React, { useState } from "react";
2
- import { Text, Pressable } from "react-native";
2
+ import { Text, Pressable, Vibration, Platform } from "react-native";
3
+
4
+ // Try to import expo-haptics (optional dependency)
5
+ let Haptics = null;
6
+ try {
7
+ Haptics = require("expo-haptics");
8
+ } catch (e) {
9
+ // expo-haptics not available, will fall back to Vibration API
10
+ }
3
11
  import Animated, { Easing, useAnimatedStyle, useAnimatedReaction, useSharedValue, withRepeat, withSequence, withTiming, useDerivedValue, cancelAnimation, runOnJS } from "react-native-reanimated";
4
12
  export default function ChildWrapper({
5
13
  position,
@@ -14,7 +22,8 @@ export default function ChildWrapper({
14
22
  holdStillToDeleteMs = 1000,
15
23
  dragSizeIncreaseFactor,
16
24
  onDelete,
17
- disableHoldToDelete = false
25
+ disableHoldToDelete = false,
26
+ hapticFeedback = false
18
27
  }) {
19
28
  const rotation = useSharedValue(0);
20
29
  const currentWiggleMode = useSharedValue("none");
@@ -27,6 +36,36 @@ export default function ChildWrapper({
27
36
  const frameCounter = useSharedValue(0);
28
37
  const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode
29
38
 
39
+ // Function to trigger haptic feedback (called from worklet via runOnJS)
40
+ const triggerHapticFeedback = () => {
41
+ try {
42
+ // Platform-specific haptic feedback
43
+ if (Platform.OS === "ios") {
44
+ // iOS: Prefer expo-haptics for better control (subtle feedback)
45
+ if (Haptics && Haptics.impactAsync) {
46
+ // Use light impact for subtle feedback (similar to iOS system haptics)
47
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
48
+ return;
49
+ }
50
+ // Fallback to Vibration API if expo-haptics not available
51
+ // Note: This will be a stronger vibration than desired on iOS
52
+ if (Vibration && typeof Vibration.vibrate === "function") {
53
+ Vibration.vibrate(1);
54
+ }
55
+ } else {
56
+ // Android: Use Vibration API (works well and is more reliable than expo-haptics)
57
+ if (Vibration && typeof Vibration.vibrate === "function") {
58
+ // Android requires a pattern array: [delay, duration]
59
+ // [0, 20] means: start immediately (0ms delay), vibrate for 20ms
60
+ Vibration.vibrate([0, 20]);
61
+ }
62
+ }
63
+ } catch (error) {
64
+ // Silently fail if haptic feedback is not available or fails
65
+ // This allows the library to work in environments where haptics are not supported
66
+ }
67
+ };
68
+
30
69
  // Timer logic that runs every frame via useDerivedValue
31
70
  useDerivedValue(() => {
32
71
  "worklet";
@@ -121,11 +160,16 @@ export default function ChildWrapper({
121
160
  stillTimer.value += 16;
122
161
 
123
162
  // Enter delete mode after holdStillToDeleteMs of being held still
124
- if (stillTimer.value >= holdStillToDeleteMs) {
163
+ if (stillTimer.value >= holdStillToDeleteMs && !deleteModeActive.value) {
125
164
  deleteModeActive.value = true;
126
165
  anyItemInDeleteMode.value = true; // Set global delete mode
127
166
  showDelete.value = true;
128
167
  wasReleasedAfterDeleteMode.value = false; // Reset on entry
168
+
169
+ // Trigger haptic feedback when entering delete mode
170
+ if (hapticFeedback) {
171
+ runOnJS(triggerHapticFeedback)();
172
+ }
129
173
  }
130
174
  });
131
175
  const deleteButtonStyle = useAnimatedStyle(() => {
@@ -1 +1 @@
1
- {"version":3,"names":["React","useState","Text","Pressable","Animated","Easing","useAnimatedStyle","useAnimatedReaction","useSharedValue","withRepeat","withSequence","withTiming","useDerivedValue","cancelAnimation","runOnJS","ChildWrapper","position","itemWidth","itemHeight","dragMode","anyItemInDeleteMode","isPressingDeleteItem","children","wiggle","wiggleDeleteMode","holdStillToDeleteMs","dragSizeIncreaseFactor","onDelete","disableHoldToDelete","rotation","currentWiggleMode","previousDragMode","showDelete","deleteModeActive","stillTimer","lastX","x","value","lastY","y","frameCounter","wasReleasedAfterDeleteMode","isDragging","isActive","active","dragModeJustEnded","moved","Math","abs","deleteButtonStyle","shouldShow","opacity","pointerEvents","transform","scale","duration","current","previous","isEditMode","inDeleteMode","anyInDeleteMode","targetMode","previousMode","deleteWiggleDegrees","degrees","deleteWiggleDuration","currentRot","scaleFactor","easing","linear","animatedStyle","width","height","translateX","translateY","rotate","zIndex","isInDeleteMode","setIsInDeleteMode","handleDelete","createElement","View","style","onPressIn","onPressOut","setTimeout","onPress","top","left","right","bottom","borderRadius","justifyContent","alignItems","fontSize","color","fontWeight"],"sources":["ChildWrapper.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { Text, View, Pressable } from \"react-native\";\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withRepeat,\n withSequence,\n withTiming,\n SharedValue,\n useDerivedValue,\n cancelAnimation,\n runOnJS,\n} from \"react-native-reanimated\";\n\ntype Props = {\n position: {\n x: SharedValue<number>;\n y: SharedValue<number>;\n active: SharedValue<number>;\n };\n itemWidth: number;\n itemHeight: number;\n dragMode: SharedValue<boolean>;\n anyItemInDeleteMode: SharedValue<boolean>;\n isPressingDeleteItem: SharedValue<boolean>;\n children: React.ReactNode;\n wiggle?: { duration: number; degrees: number };\n wiggleDeleteMode?: { duration: number; degrees: number };\n holdStillToDeleteMs?: number;\n dragSizeIncreaseFactor: number;\n onDelete?: () => void;\n disableHoldToDelete?: boolean; // If true, disable the hold-to-delete feature\n};\n\nexport default function ChildWrapper({\n position,\n itemWidth,\n itemHeight,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n children,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n dragSizeIncreaseFactor,\n onDelete,\n disableHoldToDelete = false,\n}: Props) {\n const rotation = useSharedValue(0);\n const currentWiggleMode = useSharedValue<\"none\" | \"normal\" | \"delete\">(\n \"none\"\n );\n const previousDragMode = useSharedValue(false);\n\n const showDelete = useSharedValue(false);\n const deleteModeActive = useSharedValue(false); // Persistent delete mode state\n const stillTimer = useSharedValue(0);\n const lastX = useSharedValue(position.x.value);\n const lastY = useSharedValue(position.y.value);\n const frameCounter = useSharedValue(0);\n const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode\n\n // Timer logic that runs every frame via useDerivedValue\n useDerivedValue(() => {\n \"worklet\";\n frameCounter.value = frameCounter.value + 1;\n\n // If hold-to-delete is disabled, skip all delete mode logic\n if (disableHoldToDelete) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n anyItemInDeleteMode.value = false;\n return;\n }\n\n const isDragging = dragMode.value;\n const isActive = position.active.value > 0.5;\n const x = position.x.value;\n const y = position.y.value;\n\n // Track dragMode changes for detecting touches outside\n const dragModeJustEnded = previousDragMode.value && !isDragging;\n previousDragMode.value = isDragging;\n\n // If delete mode is active, keep it active unless:\n // 1. Another item becomes active (dragMode true but this item not active)\n // 2. This item becomes active again AFTER it was released (user starts dragging it again)\n // 3. User touches outside (dragMode becomes false and no item is active)\n if (deleteModeActive.value) {\n // Check if item was released (became inactive)\n if (!isActive && !wasReleasedAfterDeleteMode.value) {\n wasReleasedAfterDeleteMode.value = true;\n }\n\n if (isDragging && !isActive) {\n // Another item is being dragged, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (isActive && wasReleasedAfterDeleteMode.value) {\n // This item became active again AFTER it was released, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (!isDragging && !isActive) {\n // Keep delete mode active (waiting for user interaction)\n // The tap gesture handler in SwappableGrid will cancel it when user taps outside\n showDelete.value = true;\n } else {\n // Keep delete mode active (item can still be held or released)\n showDelete.value = true;\n }\n return;\n }\n\n // Reset release tracking when not in delete mode\n wasReleasedAfterDeleteMode.value = false;\n\n // Timer runs when item is active (being held)\n // Note: isActive (position.active.value) is set when gesture activates after long press\n // isDragging (dragMode.value) is also set at that time, but we primarily check isActive\n // to allow timer to work even in edge cases\n if (!isActive) {\n stillTimer.value = 0;\n return;\n }\n\n // Item is active - timer can run (dragMode should also be true at this point,\n // but we don't require it to allow timer to work in all cases)\n\n // Item is active (being held down) - check if it's still\n // Check if position has changed significantly (more than 10px threshold)\n const moved =\n Math.abs(x - lastX.value) > 10 || Math.abs(y - lastY.value) > 10;\n\n if (moved) {\n // Reset timer if item moved while being held\n stillTimer.value = 0;\n lastX.value = x;\n lastY.value = y;\n return;\n }\n\n // Initialize last position on first frame when active\n if (stillTimer.value === 0) {\n lastX.value = x;\n lastY.value = y;\n }\n\n // If the tile hasn't moved significantly while being held → increment timer\n // Increment by ~16ms per frame (assuming 60fps)\n stillTimer.value += 16;\n\n // Enter delete mode after holdStillToDeleteMs of being held still\n if (stillTimer.value >= holdStillToDeleteMs) {\n deleteModeActive.value = true;\n anyItemInDeleteMode.value = true; // Set global delete mode\n showDelete.value = true;\n wasReleasedAfterDeleteMode.value = false; // Reset on entry\n }\n });\n\n const deleteButtonStyle = useAnimatedStyle(() => {\n // Show delete button when delete mode is active (persists after release)\n const shouldShow = showDelete.value;\n return {\n opacity: shouldShow ? 1 : 0,\n pointerEvents: shouldShow ? \"auto\" : \"none\",\n transform: [\n { scale: withTiming(shouldShow ? 1 : 0.6, { duration: 120 }) },\n ],\n };\n });\n\n // Watch for when global delete mode is cancelled (user tapped outside)\n useAnimatedReaction(\n () => anyItemInDeleteMode.value,\n (current, previous) => {\n \"worklet\";\n // If delete mode was cancelled globally (user tapped outside)\n if (previous && !current && deleteModeActive.value) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n }\n }\n );\n\n // Wiggle animation — triggers on editMode/active changes and delete mode\n useAnimatedReaction(\n () => ({\n isEditMode: dragMode.value,\n isActive: position.active.value > 0.5,\n inDeleteMode: deleteModeActive.value,\n anyInDeleteMode: anyItemInDeleteMode.value,\n }),\n ({ isEditMode, isActive, inDeleteMode, anyInDeleteMode }) => {\n // Determine the target wiggle mode\n let targetMode: \"none\" | \"normal\" | \"delete\" = \"none\";\n if (inDeleteMode && (wiggleDeleteMode || wiggle)) {\n targetMode = \"delete\";\n } else if (anyInDeleteMode && !isActive && wiggle) {\n targetMode = \"normal\";\n } else if (isEditMode && !isActive && wiggle) {\n targetMode = \"normal\";\n }\n\n // If no wiggle is configured at all, stop animation\n if (!wiggle && !wiggleDeleteMode) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If in delete mode but no wiggleDeleteMode and no wiggle, stop animation\n if (targetMode === \"delete\" && !wiggleDeleteMode && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If normal mode but no wiggle, stop animation\n if (targetMode === \"normal\" && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // Only restart animation if mode changed\n if (currentWiggleMode.value === targetMode) {\n return; // Already in the correct mode, don't restart\n }\n\n const previousMode = currentWiggleMode.value;\n currentWiggleMode.value = targetMode;\n\n // Cancel current animation\n cancelAnimation(rotation);\n\n // If this item is in delete mode, use wiggleDeleteMode if provided, otherwise use 2x degrees and 0.7x duration\n if (targetMode === \"delete\") {\n const deleteWiggleDegrees = wiggleDeleteMode\n ? wiggleDeleteMode.degrees\n : (wiggle?.degrees ?? 0) * 2;\n const deleteWiggleDuration = wiggleDeleteMode\n ? wiggleDeleteMode.duration\n : (wiggle?.duration ?? 200) * 0.7; // Faster wiggle\n\n // If transitioning from normal wiggle, preserve the phase by scaling\n if (previousMode === \"normal\" && wiggle) {\n const currentRot = rotation.value;\n const scaleFactor = deleteWiggleDegrees / wiggle.degrees;\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n }),\n withTiming(-deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Normal wiggle (when dragging but not this item, or any item in delete mode)\n else if (targetMode === \"normal\") {\n // If transitioning from delete wiggle, preserve the phase by scaling\n if (previousMode === \"delete\") {\n const currentRot = rotation.value;\n const scaleFactor = wiggle.degrees / (wiggle.degrees * 2);\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n }),\n withTiming(-wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Stop wiggling\n else {\n rotation.value = withTiming(0, { duration: 150 });\n }\n },\n [\n dragMode,\n position.active,\n deleteModeActive,\n anyItemInDeleteMode,\n wiggle,\n wiggleDeleteMode,\n ]\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n const scale = position.active.value\n ? withTiming(dragSizeIncreaseFactor, { duration: 120 })\n : withTiming(1, { duration: 120 });\n\n return {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n transform: [\n { translateX: position.x.value as any },\n { translateY: position.y.value as any },\n { scale: scale as any },\n { rotate: `${rotation.value}deg` as any },\n ],\n zIndex: position.active.value ? 2 : 0,\n } as any;\n });\n\n // Track delete mode on JS thread for conditional rendering\n const [isInDeleteMode, setIsInDeleteMode] = useState(false);\n\n useAnimatedReaction(\n () => deleteModeActive.value,\n (current) => {\n runOnJS(setIsInDeleteMode)(current);\n }\n );\n\n const handleDelete = () => {\n // Exit delete mode when delete button is pressed\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n if (onDelete) {\n onDelete();\n }\n };\n\n return (\n <Animated.View style={animatedStyle} pointerEvents=\"box-none\">\n {/* Full-item Pressable for delete - only active when in delete mode */}\n {isInDeleteMode && (\n <Pressable\n onPressIn={() => {\n // Mark that we're pressing an item to prevent ScrollView from canceling delete mode\n isPressingDeleteItem.value = true;\n }}\n onPressOut={() => {\n // Clear the flag after a short delay to allow onPress to fire\n setTimeout(() => {\n isPressingDeleteItem.value = false;\n }, 50);\n }}\n onPress={handleDelete}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: itemWidth,\n height: itemHeight,\n zIndex: 2,\n }}\n />\n )}\n\n {/* Delete button (×) - visual indicator only */}\n <Animated.View\n style={[\n {\n position: \"absolute\",\n top: itemHeight * 0.01,\n right: itemWidth * 0.04,\n width: itemWidth * 0.2,\n height: itemHeight * 0.2,\n borderRadius: 12,\n justifyContent: \"center\",\n alignItems: \"center\",\n zIndex: 3,\n },\n deleteButtonStyle,\n ]}\n pointerEvents=\"none\"\n >\n <Text\n style={{\n fontSize: itemWidth * 0.2,\n color: \"black\",\n fontWeight: 500,\n }}\n >\n ×\n </Text>\n </Animated.View>\n\n {children}\n </Animated.View>\n );\n}\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["React","useState","Text","Pressable","Vibration","Platform","Haptics","require","e","Animated","Easing","useAnimatedStyle","useAnimatedReaction","useSharedValue","withRepeat","withSequence","withTiming","useDerivedValue","cancelAnimation","runOnJS","ChildWrapper","position","itemWidth","itemHeight","dragMode","anyItemInDeleteMode","isPressingDeleteItem","children","wiggle","wiggleDeleteMode","holdStillToDeleteMs","dragSizeIncreaseFactor","onDelete","disableHoldToDelete","hapticFeedback","rotation","currentWiggleMode","previousDragMode","showDelete","deleteModeActive","stillTimer","lastX","x","value","lastY","y","frameCounter","wasReleasedAfterDeleteMode","triggerHapticFeedback","OS","impactAsync","ImpactFeedbackStyle","Light","vibrate","error","isDragging","isActive","active","dragModeJustEnded","moved","Math","abs","deleteButtonStyle","shouldShow","opacity","pointerEvents","transform","scale","duration","current","previous","isEditMode","inDeleteMode","anyInDeleteMode","targetMode","previousMode","deleteWiggleDegrees","degrees","deleteWiggleDuration","currentRot","scaleFactor","easing","linear","animatedStyle","width","height","translateX","translateY","rotate","zIndex","isInDeleteMode","setIsInDeleteMode","handleDelete","createElement","View","style","onPressIn","onPressOut","setTimeout","onPress","top","left","right","bottom","borderRadius","justifyContent","alignItems","fontSize","color","fontWeight"],"sources":["ChildWrapper.tsx"],"sourcesContent":["import React, { useEffect, useState } from \"react\";\nimport { Text, View, Pressable, Vibration, Platform } from \"react-native\";\n\n// Try to import expo-haptics (optional dependency)\nlet Haptics: any = null;\ntry {\n Haptics = require(\"expo-haptics\");\n} catch (e) {\n // expo-haptics not available, will fall back to Vibration API\n}\nimport Animated, {\n Easing,\n useAnimatedStyle,\n useAnimatedReaction,\n useSharedValue,\n withRepeat,\n withSequence,\n withTiming,\n SharedValue,\n useDerivedValue,\n cancelAnimation,\n runOnJS,\n} from \"react-native-reanimated\";\n\ntype Props = {\n position: {\n x: SharedValue<number>;\n y: SharedValue<number>;\n active: SharedValue<number>;\n };\n itemWidth: number;\n itemHeight: number;\n dragMode: SharedValue<boolean>;\n anyItemInDeleteMode: SharedValue<boolean>;\n isPressingDeleteItem: SharedValue<boolean>;\n children: React.ReactNode;\n wiggle?: { duration: number; degrees: number };\n wiggleDeleteMode?: { duration: number; degrees: number };\n holdStillToDeleteMs?: number;\n dragSizeIncreaseFactor: number;\n onDelete?: () => void;\n disableHoldToDelete?: boolean; // If true, disable the hold-to-delete feature\n hapticFeedback?: boolean; // If true, enable haptic feedback when entering delete mode\n};\n\nexport default function ChildWrapper({\n position,\n itemWidth,\n itemHeight,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n children,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n dragSizeIncreaseFactor,\n onDelete,\n disableHoldToDelete = false,\n hapticFeedback = false,\n}: Props) {\n const rotation = useSharedValue(0);\n const currentWiggleMode = useSharedValue<\"none\" | \"normal\" | \"delete\">(\n \"none\"\n );\n const previousDragMode = useSharedValue(false);\n\n const showDelete = useSharedValue(false);\n const deleteModeActive = useSharedValue(false); // Persistent delete mode state\n const stillTimer = useSharedValue(0);\n const lastX = useSharedValue(position.x.value);\n const lastY = useSharedValue(position.y.value);\n const frameCounter = useSharedValue(0);\n const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode\n\n // Function to trigger haptic feedback (called from worklet via runOnJS)\n const triggerHapticFeedback = () => {\n try {\n // Platform-specific haptic feedback\n if (Platform.OS === \"ios\") {\n // iOS: Prefer expo-haptics for better control (subtle feedback)\n if (Haptics && Haptics.impactAsync) {\n // Use light impact for subtle feedback (similar to iOS system haptics)\n Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);\n return;\n }\n // Fallback to Vibration API if expo-haptics not available\n // Note: This will be a stronger vibration than desired on iOS\n if (Vibration && typeof Vibration.vibrate === \"function\") {\n Vibration.vibrate(1);\n }\n } else {\n // Android: Use Vibration API (works well and is more reliable than expo-haptics)\n if (Vibration && typeof Vibration.vibrate === \"function\") {\n // Android requires a pattern array: [delay, duration]\n // [0, 20] means: start immediately (0ms delay), vibrate for 20ms\n Vibration.vibrate([0, 20]);\n }\n }\n } catch (error) {\n // Silently fail if haptic feedback is not available or fails\n // This allows the library to work in environments where haptics are not supported\n }\n };\n\n // Timer logic that runs every frame via useDerivedValue\n useDerivedValue(() => {\n \"worklet\";\n frameCounter.value = frameCounter.value + 1;\n\n // If hold-to-delete is disabled, skip all delete mode logic\n if (disableHoldToDelete) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n anyItemInDeleteMode.value = false;\n return;\n }\n\n const isDragging = dragMode.value;\n const isActive = position.active.value > 0.5;\n const x = position.x.value;\n const y = position.y.value;\n\n // Track dragMode changes for detecting touches outside\n const dragModeJustEnded = previousDragMode.value && !isDragging;\n previousDragMode.value = isDragging;\n\n // If delete mode is active, keep it active unless:\n // 1. Another item becomes active (dragMode true but this item not active)\n // 2. This item becomes active again AFTER it was released (user starts dragging it again)\n // 3. User touches outside (dragMode becomes false and no item is active)\n if (deleteModeActive.value) {\n // Check if item was released (became inactive)\n if (!isActive && !wasReleasedAfterDeleteMode.value) {\n wasReleasedAfterDeleteMode.value = true;\n }\n\n if (isDragging && !isActive) {\n // Another item is being dragged, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (isActive && wasReleasedAfterDeleteMode.value) {\n // This item became active again AFTER it was released, exit delete mode\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n } else if (!isDragging && !isActive) {\n // Keep delete mode active (waiting for user interaction)\n // The tap gesture handler in SwappableGrid will cancel it when user taps outside\n showDelete.value = true;\n } else {\n // Keep delete mode active (item can still be held or released)\n showDelete.value = true;\n }\n return;\n }\n\n // Reset release tracking when not in delete mode\n wasReleasedAfterDeleteMode.value = false;\n\n // Timer runs when item is active (being held)\n // Note: isActive (position.active.value) is set when gesture activates after long press\n // isDragging (dragMode.value) is also set at that time, but we primarily check isActive\n // to allow timer to work even in edge cases\n if (!isActive) {\n stillTimer.value = 0;\n return;\n }\n\n // Item is active - timer can run (dragMode should also be true at this point,\n // but we don't require it to allow timer to work in all cases)\n\n // Item is active (being held down) - check if it's still\n // Check if position has changed significantly (more than 10px threshold)\n const moved =\n Math.abs(x - lastX.value) > 10 || Math.abs(y - lastY.value) > 10;\n\n if (moved) {\n // Reset timer if item moved while being held\n stillTimer.value = 0;\n lastX.value = x;\n lastY.value = y;\n return;\n }\n\n // Initialize last position on first frame when active\n if (stillTimer.value === 0) {\n lastX.value = x;\n lastY.value = y;\n }\n\n // If the tile hasn't moved significantly while being held → increment timer\n // Increment by ~16ms per frame (assuming 60fps)\n stillTimer.value += 16;\n\n // Enter delete mode after holdStillToDeleteMs of being held still\n if (stillTimer.value >= holdStillToDeleteMs && !deleteModeActive.value) {\n deleteModeActive.value = true;\n anyItemInDeleteMode.value = true; // Set global delete mode\n showDelete.value = true;\n wasReleasedAfterDeleteMode.value = false; // Reset on entry\n\n // Trigger haptic feedback when entering delete mode\n if (hapticFeedback) {\n runOnJS(triggerHapticFeedback)();\n }\n }\n });\n\n const deleteButtonStyle = useAnimatedStyle(() => {\n // Show delete button when delete mode is active (persists after release)\n const shouldShow = showDelete.value;\n return {\n opacity: shouldShow ? 1 : 0,\n pointerEvents: shouldShow ? \"auto\" : \"none\",\n transform: [\n { scale: withTiming(shouldShow ? 1 : 0.6, { duration: 120 }) },\n ],\n };\n });\n\n // Watch for when global delete mode is cancelled (user tapped outside)\n useAnimatedReaction(\n () => anyItemInDeleteMode.value,\n (current, previous) => {\n \"worklet\";\n // If delete mode was cancelled globally (user tapped outside)\n if (previous && !current && deleteModeActive.value) {\n deleteModeActive.value = false;\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n }\n }\n );\n\n // Wiggle animation — triggers on editMode/active changes and delete mode\n useAnimatedReaction(\n () => ({\n isEditMode: dragMode.value,\n isActive: position.active.value > 0.5,\n inDeleteMode: deleteModeActive.value,\n anyInDeleteMode: anyItemInDeleteMode.value,\n }),\n ({ isEditMode, isActive, inDeleteMode, anyInDeleteMode }) => {\n // Determine the target wiggle mode\n let targetMode: \"none\" | \"normal\" | \"delete\" = \"none\";\n if (inDeleteMode && (wiggleDeleteMode || wiggle)) {\n targetMode = \"delete\";\n } else if (anyInDeleteMode && !isActive && wiggle) {\n targetMode = \"normal\";\n } else if (isEditMode && !isActive && wiggle) {\n targetMode = \"normal\";\n }\n\n // If no wiggle is configured at all, stop animation\n if (!wiggle && !wiggleDeleteMode) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If in delete mode but no wiggleDeleteMode and no wiggle, stop animation\n if (targetMode === \"delete\" && !wiggleDeleteMode && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // If normal mode but no wiggle, stop animation\n if (targetMode === \"normal\" && !wiggle) {\n if (currentWiggleMode.value !== \"none\") {\n cancelAnimation(rotation);\n currentWiggleMode.value = \"none\";\n }\n rotation.value = withTiming(0, { duration: 150 });\n return;\n }\n\n // Only restart animation if mode changed\n if (currentWiggleMode.value === targetMode) {\n return; // Already in the correct mode, don't restart\n }\n\n const previousMode = currentWiggleMode.value;\n currentWiggleMode.value = targetMode;\n\n // Cancel current animation\n cancelAnimation(rotation);\n\n // If this item is in delete mode, use wiggleDeleteMode if provided, otherwise use 2x degrees and 0.7x duration\n if (targetMode === \"delete\") {\n const deleteWiggleDegrees = wiggleDeleteMode\n ? wiggleDeleteMode.degrees\n : (wiggle?.degrees ?? 0) * 2;\n const deleteWiggleDuration = wiggleDeleteMode\n ? wiggleDeleteMode.duration\n : (wiggle?.duration ?? 200) * 0.7; // Faster wiggle\n\n // If transitioning from normal wiggle, preserve the phase by scaling\n if (previousMode === \"normal\" && wiggle) {\n const currentRot = rotation.value;\n const scaleFactor = deleteWiggleDegrees / wiggle.degrees;\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n }),\n withTiming(-deleteWiggleDegrees, {\n duration: deleteWiggleDuration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Normal wiggle (when dragging but not this item, or any item in delete mode)\n else if (targetMode === \"normal\") {\n // If transitioning from delete wiggle, preserve the phase by scaling\n if (previousMode === \"delete\") {\n const currentRot = rotation.value;\n const scaleFactor = wiggle.degrees / (wiggle.degrees * 2);\n rotation.value = currentRot * scaleFactor;\n }\n\n rotation.value = withRepeat(\n withSequence(\n withTiming(wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n }),\n withTiming(-wiggle.degrees, {\n duration: wiggle.duration,\n easing: Easing.linear,\n })\n ),\n -1, // infinite\n true\n );\n }\n // Stop wiggling\n else {\n rotation.value = withTiming(0, { duration: 150 });\n }\n },\n [\n dragMode,\n position.active,\n deleteModeActive,\n anyItemInDeleteMode,\n wiggle,\n wiggleDeleteMode,\n ]\n );\n\n const animatedStyle = useAnimatedStyle(() => {\n const scale = position.active.value\n ? withTiming(dragSizeIncreaseFactor, { duration: 120 })\n : withTiming(1, { duration: 120 });\n\n return {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n transform: [\n { translateX: position.x.value as any },\n { translateY: position.y.value as any },\n { scale: scale as any },\n { rotate: `${rotation.value}deg` as any },\n ],\n zIndex: position.active.value ? 2 : 0,\n } as any;\n });\n\n // Track delete mode on JS thread for conditional rendering\n const [isInDeleteMode, setIsInDeleteMode] = useState(false);\n\n useAnimatedReaction(\n () => deleteModeActive.value,\n (current) => {\n runOnJS(setIsInDeleteMode)(current);\n }\n );\n\n const handleDelete = () => {\n // Exit delete mode when delete button is pressed\n deleteModeActive.value = false;\n anyItemInDeleteMode.value = false; // Clear global delete mode\n showDelete.value = false;\n stillTimer.value = 0;\n wasReleasedAfterDeleteMode.value = false;\n if (onDelete) {\n onDelete();\n }\n };\n\n return (\n <Animated.View style={animatedStyle} pointerEvents=\"box-none\">\n {/* Full-item Pressable for delete - only active when in delete mode */}\n {isInDeleteMode && (\n <Pressable\n onPressIn={() => {\n // Mark that we're pressing an item to prevent ScrollView from canceling delete mode\n isPressingDeleteItem.value = true;\n }}\n onPressOut={() => {\n // Clear the flag after a short delay to allow onPress to fire\n setTimeout(() => {\n isPressingDeleteItem.value = false;\n }, 50);\n }}\n onPress={handleDelete}\n style={{\n position: \"absolute\",\n top: 0,\n left: 0,\n right: 0,\n bottom: 0,\n width: itemWidth,\n height: itemHeight,\n zIndex: 2,\n }}\n />\n )}\n\n {/* Delete button (×) - visual indicator only */}\n <Animated.View\n style={[\n {\n position: \"absolute\",\n top: itemHeight * 0.01,\n right: itemWidth * 0.04,\n width: itemWidth * 0.2,\n height: itemHeight * 0.2,\n borderRadius: 12,\n justifyContent: \"center\",\n alignItems: \"center\",\n zIndex: 3,\n },\n deleteButtonStyle,\n ]}\n pointerEvents=\"none\"\n >\n <Text\n style={{\n fontSize: itemWidth * 0.2,\n color: \"black\",\n fontWeight: 500,\n }}\n >\n ×\n </Text>\n </Animated.View>\n\n {children}\n </Animated.View>\n );\n}\n"],"mappings":"AAAA,OAAOA,KAAK,IAAeC,QAAQ,QAAQ,OAAO;AAClD,SAASC,IAAI,EAAQC,SAAS,EAAEC,SAAS,EAAEC,QAAQ,QAAQ,cAAc;;AAEzE;AACA,IAAIC,OAAY,GAAG,IAAI;AACvB,IAAI;EACFA,OAAO,GAAGC,OAAO,CAAC,cAAc,CAAC;AACnC,CAAC,CAAC,OAAOC,CAAC,EAAE;EACV;AAAA;AAEF,OAAOC,QAAQ,IACbC,MAAM,EACNC,gBAAgB,EAChBC,mBAAmB,EACnBC,cAAc,EACdC,UAAU,EACVC,YAAY,EACZC,UAAU,EAEVC,eAAe,EACfC,eAAe,EACfC,OAAO,QACF,yBAAyB;AAuBhC,eAAe,SAASC,YAAYA,CAAC;EACnCC,QAAQ;EACRC,SAAS;EACTC,UAAU;EACVC,QAAQ;EACRC,mBAAmB;EACnBC,oBAAoB;EACpBC,QAAQ;EACRC,MAAM;EACNC,gBAAgB;EAChBC,mBAAmB,GAAG,IAAI;EAC1BC,sBAAsB;EACtBC,QAAQ;EACRC,mBAAmB,GAAG,KAAK;EAC3BC,cAAc,GAAG;AACZ,CAAC,EAAE;EACR,MAAMC,QAAQ,GAAGtB,cAAc,CAAC,CAAC,CAAC;EAClC,MAAMuB,iBAAiB,GAAGvB,cAAc,CACtC,MACF,CAAC;EACD,MAAMwB,gBAAgB,GAAGxB,cAAc,CAAC,KAAK,CAAC;EAE9C,MAAMyB,UAAU,GAAGzB,cAAc,CAAC,KAAK,CAAC;EACxC,MAAM0B,gBAAgB,GAAG1B,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;EAChD,MAAM2B,UAAU,GAAG3B,cAAc,CAAC,CAAC,CAAC;EACpC,MAAM4B,KAAK,GAAG5B,cAAc,CAACQ,QAAQ,CAACqB,CAAC,CAACC,KAAK,CAAC;EAC9C,MAAMC,KAAK,GAAG/B,cAAc,CAACQ,QAAQ,CAACwB,CAAC,CAACF,KAAK,CAAC;EAC9C,MAAMG,YAAY,GAAGjC,cAAc,CAAC,CAAC,CAAC;EACtC,MAAMkC,0BAA0B,GAAGlC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;;EAE1D;EACA,MAAMmC,qBAAqB,GAAGA,CAAA,KAAM;IAClC,IAAI;MACF;MACA,IAAI3C,QAAQ,CAAC4C,EAAE,KAAK,KAAK,EAAE;QACzB;QACA,IAAI3C,OAAO,IAAIA,OAAO,CAAC4C,WAAW,EAAE;UAClC;UACA5C,OAAO,CAAC4C,WAAW,CAAC5C,OAAO,CAAC6C,mBAAmB,CAACC,KAAK,CAAC;UACtD;QACF;QACA;QACA;QACA,IAAIhD,SAAS,IAAI,OAAOA,SAAS,CAACiD,OAAO,KAAK,UAAU,EAAE;UACxDjD,SAAS,CAACiD,OAAO,CAAC,CAAC,CAAC;QACtB;MACF,CAAC,MAAM;QACL;QACA,IAAIjD,SAAS,IAAI,OAAOA,SAAS,CAACiD,OAAO,KAAK,UAAU,EAAE;UACxD;UACA;UACAjD,SAAS,CAACiD,OAAO,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;QAC5B;MACF;IACF,CAAC,CAAC,OAAOC,KAAK,EAAE;MACd;MACA;IAAA;EAEJ,CAAC;;EAED;EACArC,eAAe,CAAC,MAAM;IACpB,SAAS;;IACT6B,YAAY,CAACH,KAAK,GAAGG,YAAY,CAACH,KAAK,GAAG,CAAC;;IAE3C;IACA,IAAIV,mBAAmB,EAAE;MACvBM,gBAAgB,CAACI,KAAK,GAAG,KAAK;MAC9BL,UAAU,CAACK,KAAK,GAAG,KAAK;MACxBH,UAAU,CAACG,KAAK,GAAG,CAAC;MACpBlB,mBAAmB,CAACkB,KAAK,GAAG,KAAK;MACjC;IACF;IAEA,MAAMY,UAAU,GAAG/B,QAAQ,CAACmB,KAAK;IACjC,MAAMa,QAAQ,GAAGnC,QAAQ,CAACoC,MAAM,CAACd,KAAK,GAAG,GAAG;IAC5C,MAAMD,CAAC,GAAGrB,QAAQ,CAACqB,CAAC,CAACC,KAAK;IAC1B,MAAME,CAAC,GAAGxB,QAAQ,CAACwB,CAAC,CAACF,KAAK;;IAE1B;IACA,MAAMe,iBAAiB,GAAGrB,gBAAgB,CAACM,KAAK,IAAI,CAACY,UAAU;IAC/DlB,gBAAgB,CAACM,KAAK,GAAGY,UAAU;;IAEnC;IACA;IACA;IACA;IACA,IAAIhB,gBAAgB,CAACI,KAAK,EAAE;MAC1B;MACA,IAAI,CAACa,QAAQ,IAAI,CAACT,0BAA0B,CAACJ,KAAK,EAAE;QAClDI,0BAA0B,CAACJ,KAAK,GAAG,IAAI;MACzC;MAEA,IAAIY,UAAU,IAAI,CAACC,QAAQ,EAAE;QAC3B;QACAjB,gBAAgB,CAACI,KAAK,GAAG,KAAK;QAC9BlB,mBAAmB,CAACkB,KAAK,GAAG,KAAK,CAAC,CAAC;QACnCL,UAAU,CAACK,KAAK,GAAG,KAAK;QACxBH,UAAU,CAACG,KAAK,GAAG,CAAC;QACpBI,0BAA0B,CAACJ,KAAK,GAAG,KAAK;MAC1C,CAAC,MAAM,IAAIa,QAAQ,IAAIT,0BAA0B,CAACJ,KAAK,EAAE;QACvD;QACAJ,gBAAgB,CAACI,KAAK,GAAG,KAAK;QAC9BlB,mBAAmB,CAACkB,KAAK,GAAG,KAAK,CAAC,CAAC;QACnCL,UAAU,CAACK,KAAK,GAAG,KAAK;QACxBH,UAAU,CAACG,KAAK,GAAG,CAAC;QACpBI,0BAA0B,CAACJ,KAAK,GAAG,KAAK;MAC1C,CAAC,MAAM,IAAI,CAACY,UAAU,IAAI,CAACC,QAAQ,EAAE;QACnC;QACA;QACAlB,UAAU,CAACK,KAAK,GAAG,IAAI;MACzB,CAAC,MAAM;QACL;QACAL,UAAU,CAACK,KAAK,GAAG,IAAI;MACzB;MACA;IACF;;IAEA;IACAI,0BAA0B,CAACJ,KAAK,GAAG,KAAK;;IAExC;IACA;IACA;IACA;IACA,IAAI,CAACa,QAAQ,EAAE;MACbhB,UAAU,CAACG,KAAK,GAAG,CAAC;MACpB;IACF;;IAEA;IACA;;IAEA;IACA;IACA,MAAMgB,KAAK,GACTC,IAAI,CAACC,GAAG,CAACnB,CAAC,GAAGD,KAAK,CAACE,KAAK,CAAC,GAAG,EAAE,IAAIiB,IAAI,CAACC,GAAG,CAAChB,CAAC,GAAGD,KAAK,CAACD,KAAK,CAAC,GAAG,EAAE;IAElE,IAAIgB,KAAK,EAAE;MACT;MACAnB,UAAU,CAACG,KAAK,GAAG,CAAC;MACpBF,KAAK,CAACE,KAAK,GAAGD,CAAC;MACfE,KAAK,CAACD,KAAK,GAAGE,CAAC;MACf;IACF;;IAEA;IACA,IAAIL,UAAU,CAACG,KAAK,KAAK,CAAC,EAAE;MAC1BF,KAAK,CAACE,KAAK,GAAGD,CAAC;MACfE,KAAK,CAACD,KAAK,GAAGE,CAAC;IACjB;;IAEA;IACA;IACAL,UAAU,CAACG,KAAK,IAAI,EAAE;;IAEtB;IACA,IAAIH,UAAU,CAACG,KAAK,IAAIb,mBAAmB,IAAI,CAACS,gBAAgB,CAACI,KAAK,EAAE;MACtEJ,gBAAgB,CAACI,KAAK,GAAG,IAAI;MAC7BlB,mBAAmB,CAACkB,KAAK,GAAG,IAAI,CAAC,CAAC;MAClCL,UAAU,CAACK,KAAK,GAAG,IAAI;MACvBI,0BAA0B,CAACJ,KAAK,GAAG,KAAK,CAAC,CAAC;;MAE1C;MACA,IAAIT,cAAc,EAAE;QAClBf,OAAO,CAAC6B,qBAAqB,CAAC,CAAC,CAAC;MAClC;IACF;EACF,CAAC,CAAC;EAEF,MAAMc,iBAAiB,GAAGnD,gBAAgB,CAAC,MAAM;IAC/C;IACA,MAAMoD,UAAU,GAAGzB,UAAU,CAACK,KAAK;IACnC,OAAO;MACLqB,OAAO,EAAED,UAAU,GAAG,CAAC,GAAG,CAAC;MAC3BE,aAAa,EAAEF,UAAU,GAAG,MAAM,GAAG,MAAM;MAC3CG,SAAS,EAAE,CACT;QAAEC,KAAK,EAAEnD,UAAU,CAAC+C,UAAU,GAAG,CAAC,GAAG,GAAG,EAAE;UAAEK,QAAQ,EAAE;QAAI,CAAC;MAAE,CAAC;IAElE,CAAC;EACH,CAAC,CAAC;;EAEF;EACAxD,mBAAmB,CACjB,MAAMa,mBAAmB,CAACkB,KAAK,EAC/B,CAAC0B,OAAO,EAAEC,QAAQ,KAAK;IACrB,SAAS;;IACT;IACA,IAAIA,QAAQ,IAAI,CAACD,OAAO,IAAI9B,gBAAgB,CAACI,KAAK,EAAE;MAClDJ,gBAAgB,CAACI,KAAK,GAAG,KAAK;MAC9BL,UAAU,CAACK,KAAK,GAAG,KAAK;MACxBH,UAAU,CAACG,KAAK,GAAG,CAAC;MACpBI,0BAA0B,CAACJ,KAAK,GAAG,KAAK;IAC1C;EACF,CACF,CAAC;;EAED;EACA/B,mBAAmB,CACjB,OAAO;IACL2D,UAAU,EAAE/C,QAAQ,CAACmB,KAAK;IAC1Ba,QAAQ,EAAEnC,QAAQ,CAACoC,MAAM,CAACd,KAAK,GAAG,GAAG;IACrC6B,YAAY,EAAEjC,gBAAgB,CAACI,KAAK;IACpC8B,eAAe,EAAEhD,mBAAmB,CAACkB;EACvC,CAAC,CAAC,EACF,CAAC;IAAE4B,UAAU;IAAEf,QAAQ;IAAEgB,YAAY;IAAEC;EAAgB,CAAC,KAAK;IAC3D;IACA,IAAIC,UAAwC,GAAG,MAAM;IACrD,IAAIF,YAAY,KAAK3C,gBAAgB,IAAID,MAAM,CAAC,EAAE;MAChD8C,UAAU,GAAG,QAAQ;IACvB,CAAC,MAAM,IAAID,eAAe,IAAI,CAACjB,QAAQ,IAAI5B,MAAM,EAAE;MACjD8C,UAAU,GAAG,QAAQ;IACvB,CAAC,MAAM,IAAIH,UAAU,IAAI,CAACf,QAAQ,IAAI5B,MAAM,EAAE;MAC5C8C,UAAU,GAAG,QAAQ;IACvB;;IAEA;IACA,IAAI,CAAC9C,MAAM,IAAI,CAACC,gBAAgB,EAAE;MAChC,IAAIO,iBAAiB,CAACO,KAAK,KAAK,MAAM,EAAE;QACtCzB,eAAe,CAACiB,QAAQ,CAAC;QACzBC,iBAAiB,CAACO,KAAK,GAAG,MAAM;MAClC;MACAR,QAAQ,CAACQ,KAAK,GAAG3B,UAAU,CAAC,CAAC,EAAE;QAAEoD,QAAQ,EAAE;MAAI,CAAC,CAAC;MACjD;IACF;;IAEA;IACA,IAAIM,UAAU,KAAK,QAAQ,IAAI,CAAC7C,gBAAgB,IAAI,CAACD,MAAM,EAAE;MAC3D,IAAIQ,iBAAiB,CAACO,KAAK,KAAK,MAAM,EAAE;QACtCzB,eAAe,CAACiB,QAAQ,CAAC;QACzBC,iBAAiB,CAACO,KAAK,GAAG,MAAM;MAClC;MACAR,QAAQ,CAACQ,KAAK,GAAG3B,UAAU,CAAC,CAAC,EAAE;QAAEoD,QAAQ,EAAE;MAAI,CAAC,CAAC;MACjD;IACF;;IAEA;IACA,IAAIM,UAAU,KAAK,QAAQ,IAAI,CAAC9C,MAAM,EAAE;MACtC,IAAIQ,iBAAiB,CAACO,KAAK,KAAK,MAAM,EAAE;QACtCzB,eAAe,CAACiB,QAAQ,CAAC;QACzBC,iBAAiB,CAACO,KAAK,GAAG,MAAM;MAClC;MACAR,QAAQ,CAACQ,KAAK,GAAG3B,UAAU,CAAC,CAAC,EAAE;QAAEoD,QAAQ,EAAE;MAAI,CAAC,CAAC;MACjD;IACF;;IAEA;IACA,IAAIhC,iBAAiB,CAACO,KAAK,KAAK+B,UAAU,EAAE;MAC1C,OAAO,CAAC;IACV;IAEA,MAAMC,YAAY,GAAGvC,iBAAiB,CAACO,KAAK;IAC5CP,iBAAiB,CAACO,KAAK,GAAG+B,UAAU;;IAEpC;IACAxD,eAAe,CAACiB,QAAQ,CAAC;;IAEzB;IACA,IAAIuC,UAAU,KAAK,QAAQ,EAAE;MAC3B,MAAME,mBAAmB,GAAG/C,gBAAgB,GACxCA,gBAAgB,CAACgD,OAAO,GACxB,CAAC,CAAAjD,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEiD,OAAO,KAAI,CAAC,IAAI,CAAC;MAC9B,MAAMC,oBAAoB,GAAGjD,gBAAgB,GACzCA,gBAAgB,CAACuC,QAAQ,GACzB,CAAC,CAAAxC,MAAM,aAANA,MAAM,uBAANA,MAAM,CAAEwC,QAAQ,KAAI,GAAG,IAAI,GAAG,CAAC,CAAC;;MAErC;MACA,IAAIO,YAAY,KAAK,QAAQ,IAAI/C,MAAM,EAAE;QACvC,MAAMmD,UAAU,GAAG5C,QAAQ,CAACQ,KAAK;QACjC,MAAMqC,WAAW,GAAGJ,mBAAmB,GAAGhD,MAAM,CAACiD,OAAO;QACxD1C,QAAQ,CAACQ,KAAK,GAAGoC,UAAU,GAAGC,WAAW;MAC3C;MAEA7C,QAAQ,CAACQ,KAAK,GAAG7B,UAAU,CACzBC,YAAY,CACVC,UAAU,CAAC4D,mBAAmB,EAAE;QAC9BR,QAAQ,EAAEU,oBAAoB;QAC9BG,MAAM,EAAEvE,MAAM,CAACwE;MACjB,CAAC,CAAC,EACFlE,UAAU,CAAC,CAAC4D,mBAAmB,EAAE;QAC/BR,QAAQ,EAAEU,oBAAoB;QAC9BG,MAAM,EAAEvE,MAAM,CAACwE;MACjB,CAAC,CACH,CAAC,EACD,CAAC,CAAC;MAAE;MACJ,IACF,CAAC;IACH;IACA;IAAA,KACK,IAAIR,UAAU,KAAK,QAAQ,EAAE;MAChC;MACA,IAAIC,YAAY,KAAK,QAAQ,EAAE;QAC7B,MAAMI,UAAU,GAAG5C,QAAQ,CAACQ,KAAK;QACjC,MAAMqC,WAAW,GAAGpD,MAAM,CAACiD,OAAO,IAAIjD,MAAM,CAACiD,OAAO,GAAG,CAAC,CAAC;QACzD1C,QAAQ,CAACQ,KAAK,GAAGoC,UAAU,GAAGC,WAAW;MAC3C;MAEA7C,QAAQ,CAACQ,KAAK,GAAG7B,UAAU,CACzBC,YAAY,CACVC,UAAU,CAACY,MAAM,CAACiD,OAAO,EAAE;QACzBT,QAAQ,EAAExC,MAAM,CAACwC,QAAQ;QACzBa,MAAM,EAAEvE,MAAM,CAACwE;MACjB,CAAC,CAAC,EACFlE,UAAU,CAAC,CAACY,MAAM,CAACiD,OAAO,EAAE;QAC1BT,QAAQ,EAAExC,MAAM,CAACwC,QAAQ;QACzBa,MAAM,EAAEvE,MAAM,CAACwE;MACjB,CAAC,CACH,CAAC,EACD,CAAC,CAAC;MAAE;MACJ,IACF,CAAC;IACH;IACA;IAAA,KACK;MACH/C,QAAQ,CAACQ,KAAK,GAAG3B,UAAU,CAAC,CAAC,EAAE;QAAEoD,QAAQ,EAAE;MAAI,CAAC,CAAC;IACnD;EACF,CAAC,EACD,CACE5C,QAAQ,EACRH,QAAQ,CAACoC,MAAM,EACflB,gBAAgB,EAChBd,mBAAmB,EACnBG,MAAM,EACNC,gBAAgB,CAEpB,CAAC;EAED,MAAMsD,aAAa,GAAGxE,gBAAgB,CAAC,MAAM;IAC3C,MAAMwD,KAAK,GAAG9C,QAAQ,CAACoC,MAAM,CAACd,KAAK,GAC/B3B,UAAU,CAACe,sBAAsB,EAAE;MAAEqC,QAAQ,EAAE;IAAI,CAAC,CAAC,GACrDpD,UAAU,CAAC,CAAC,EAAE;MAAEoD,QAAQ,EAAE;IAAI,CAAC,CAAC;IAEpC,OAAO;MACL/C,QAAQ,EAAE,UAAU;MACpB+D,KAAK,EAAE9D,SAAS;MAChB+D,MAAM,EAAE9D,UAAU;MAClB2C,SAAS,EAAE,CACT;QAAEoB,UAAU,EAAEjE,QAAQ,CAACqB,CAAC,CAACC;MAAa,CAAC,EACvC;QAAE4C,UAAU,EAAElE,QAAQ,CAACwB,CAAC,CAACF;MAAa,CAAC,EACvC;QAAEwB,KAAK,EAAEA;MAAa,CAAC,EACvB;QAAEqB,MAAM,EAAE,GAAGrD,QAAQ,CAACQ,KAAK;MAAa,CAAC,CAC1C;MACD8C,MAAM,EAAEpE,QAAQ,CAACoC,MAAM,CAACd,KAAK,GAAG,CAAC,GAAG;IACtC,CAAC;EACH,CAAC,CAAC;;EAEF;EACA,MAAM,CAAC+C,cAAc,EAAEC,iBAAiB,CAAC,GAAG1F,QAAQ,CAAC,KAAK,CAAC;EAE3DW,mBAAmB,CACjB,MAAM2B,gBAAgB,CAACI,KAAK,EAC3B0B,OAAO,IAAK;IACXlD,OAAO,CAACwE,iBAAiB,CAAC,CAACtB,OAAO,CAAC;EACrC,CACF,CAAC;EAED,MAAMuB,YAAY,GAAGA,CAAA,KAAM;IACzB;IACArD,gBAAgB,CAACI,KAAK,GAAG,KAAK;IAC9BlB,mBAAmB,CAACkB,KAAK,GAAG,KAAK,CAAC,CAAC;IACnCL,UAAU,CAACK,KAAK,GAAG,KAAK;IACxBH,UAAU,CAACG,KAAK,GAAG,CAAC;IACpBI,0BAA0B,CAACJ,KAAK,GAAG,KAAK;IACxC,IAAIX,QAAQ,EAAE;MACZA,QAAQ,CAAC,CAAC;IACZ;EACF,CAAC;EAED,oBACEhC,KAAA,CAAA6F,aAAA,CAACpF,QAAQ,CAACqF,IAAI;IAACC,KAAK,EAAEZ,aAAc;IAAClB,aAAa,EAAC;EAAU,GAE1DyB,cAAc,iBACb1F,KAAA,CAAA6F,aAAA,CAAC1F,SAAS;IACR6F,SAAS,EAAEA,CAAA,KAAM;MACf;MACAtE,oBAAoB,CAACiB,KAAK,GAAG,IAAI;IACnC,CAAE;IACFsD,UAAU,EAAEA,CAAA,KAAM;MAChB;MACAC,UAAU,CAAC,MAAM;QACfxE,oBAAoB,CAACiB,KAAK,GAAG,KAAK;MACpC,CAAC,EAAE,EAAE,CAAC;IACR,CAAE;IACFwD,OAAO,EAAEP,YAAa;IACtBG,KAAK,EAAE;MACL1E,QAAQ,EAAE,UAAU;MACpB+E,GAAG,EAAE,CAAC;MACNC,IAAI,EAAE,CAAC;MACPC,KAAK,EAAE,CAAC;MACRC,MAAM,EAAE,CAAC;MACTnB,KAAK,EAAE9D,SAAS;MAChB+D,MAAM,EAAE9D,UAAU;MAClBkE,MAAM,EAAE;IACV;EAAE,CACH,CACF,eAGDzF,KAAA,CAAA6F,aAAA,CAACpF,QAAQ,CAACqF,IAAI;IACZC,KAAK,EAAE,CACL;MACE1E,QAAQ,EAAE,UAAU;MACpB+E,GAAG,EAAE7E,UAAU,GAAG,IAAI;MACtB+E,KAAK,EAAEhF,SAAS,GAAG,IAAI;MACvB8D,KAAK,EAAE9D,SAAS,GAAG,GAAG;MACtB+D,MAAM,EAAE9D,UAAU,GAAG,GAAG;MACxBiF,YAAY,EAAE,EAAE;MAChBC,cAAc,EAAE,QAAQ;MACxBC,UAAU,EAAE,QAAQ;MACpBjB,MAAM,EAAE;IACV,CAAC,EACD3B,iBAAiB,CACjB;IACFG,aAAa,EAAC;EAAM,gBAEpBjE,KAAA,CAAA6F,aAAA,CAAC3F,IAAI;IACH6F,KAAK,EAAE;MACLY,QAAQ,EAAErF,SAAS,GAAG,GAAG;MACzBsF,KAAK,EAAE,OAAO;MACdC,UAAU,EAAE;IACd;EAAE,GACH,MAEK,CACO,CAAC,EAEflF,QACY,CAAC;AAEpB","ignoreList":[]}
@@ -57,6 +57,7 @@ const SwappableGrid = /*#__PURE__*/forwardRef(({
57
57
  wiggle,
58
58
  wiggleDeleteMode,
59
59
  holdStillToDeleteMs = 1000,
60
+ hapticFeedback = false,
60
61
  style,
61
62
  dragSizeIncreaseFactor = 1.06,
62
63
  scrollThreshold = 100,
@@ -315,6 +316,7 @@ const SwappableGrid = /*#__PURE__*/forwardRef(({
315
316
  wiggle: wiggle,
316
317
  wiggleDeleteMode: wiggleDeleteMode,
317
318
  holdStillToDeleteMs: holdStillToDeleteMs,
319
+ hapticFeedback: hapticFeedback,
318
320
  dragSizeIncreaseFactor: dragSizeIncreaseFactor,
319
321
  disableHoldToDelete: !onDelete || !!deleteComponent,
320
322
  onDelete: () => {
@@ -1 +1 @@
1
- {"version":3,"names":["React","useEffect","useState","useImperativeHandle","forwardRef","View","StyleSheet","ScrollView","Animated","useAnimatedRef","useAnimatedStyle","useDerivedValue","useAnimatedReaction","runOnJS","GestureDetector","computeMinHeight","useGridLayout","ChildWrapper","indexToXY","AnimatedScrollView","createAnimatedComponent","normalizeKey","k","String","replace","SwappableGrid","children","itemWidth","itemHeight","gap","containerPadding","holdToDragMs","numColumns","onDragEnd","onOrderChange","onDelete","wiggle","wiggleDeleteMode","holdStillToDeleteMs","style","dragSizeIncreaseFactor","scrollThreshold","scrollSpeed","trailingComponent","deleteComponent","deleteComponentStyle","reverse","ref","scrollViewRef","showTrailingComponent","setShowTrailingComponent","showDeleteComponent","setShowDeleteComponent","isDragging","setIsDragging","currentNumColumns","setCurrentNumColumns","touchEndTimeoutRef","useRef","deleteComponentRef","setTimeout","handleOnOrderChange","order","map","key","paddingBottom","useMemo","styleObj","flatten","orderState","composed","dynamicNumColumns","onLayoutContent","originalOnLayoutContent","onLayoutScrollView","onScroll","deleteItem","childArray","positions","dragMode","anyItemInDeleteMode","isPressingDeleteItem","deleteComponentPosition","undefined","contentPaddingBottom","e","possibleCols","Math","floor","nativeEvent","layout","width","max","value","isDraggingValue","cancelDeleteMode","trailingX","x","index","length","trailingY","y","trailingStyle","left","top","deleteComponentX","cols","totalWidth","deleteComponentY","rows","ceil","baseY","deleteComponentStyleAnimated","baseStyle","position","height","showTrailing","showDelete","itemsCountForHeight","baseHeight","calculatedHeight","totalItems","deleteComponentBottom","createElement","onLayout","scrollEventThrottle","contentContainerStyle","onTouchEnd","current","clearTimeout","styles","container","padding","gesture","pointerEvents","absoluteFill","child","find","c","disableHoldToDelete","collapsable","flex","create"],"sources":["SwappableGrid.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useState,\n useImperativeHandle,\n forwardRef,\n} from \"react\";\nimport {\n View,\n StyleSheet,\n ScrollView,\n StyleProp,\n ViewStyle,\n LayoutChangeEvent,\n} from \"react-native\";\nimport Animated, {\n useAnimatedRef,\n useAnimatedStyle,\n useDerivedValue,\n useAnimatedReaction,\n runOnJS,\n} from \"react-native-reanimated\";\nimport { GestureDetector } from \"react-native-gesture-handler\";\nimport computeMinHeight from \"./utils/helpers/computerMinHeight\";\nimport { useGridLayout } from \"./utils/useGridLayout\";\nimport ChildWrapper from \"./ChildWrapper\";\nimport { indexToXY } from \"./utils/helpers/indexCalculations\";\n\nconst AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);\n\nconst normalizeKey = (k: React.Key) => String(k).replace(/^\\.\\$/, \"\");\n\n/**\n * Props for the SwappableGrid component\n */\ntype SwappableGridProps = {\n /** The child components to render in the grid. Each child should have a unique key. */\n children: ReactNode;\n /** Width of each grid item in pixels */\n itemWidth: number;\n /** Height of each grid item in pixels */\n itemHeight: number;\n /** Gap between grid items in pixels. Defaults to 8. */\n gap?: number;\n /** Padding around the container in pixels. Defaults to 8. */\n containerPadding?: number;\n /** Duration in milliseconds to hold before drag starts. Defaults to 300. */\n holdToDragMs?: number;\n /** Number of columns in the grid. If not provided, will be calculated automatically based on container width. */\n numColumns?: number;\n /** Wiggle animation configuration when items are in drag mode or delete mode */\n wiggle?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Wiggle animation configuration specifically for delete mode. If not provided, uses 2x degrees and 0.7x duration of wiggle prop. */\n wiggleDeleteMode?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */\n holdStillToDeleteMs?: number;\n /** Callback fired when drag ends, providing the ordered array of child nodes */\n onDragEnd?: (ordered: ChildNode[]) => void;\n /** Callback fired when the order changes, providing an array of keys in the new order */\n onOrderChange?: (keys: string[]) => void;\n /** Callback fired when an item is deleted, providing the key of the deleted item */\n onDelete?: (key: string) => void;\n /** Factor by which the dragged item scales up. Defaults to 1.06. */\n dragSizeIncreaseFactor?: number;\n /** Speed of auto-scrolling when dragging near edges. Defaults to 10. */\n scrollSpeed?: number;\n /** Distance from edge in pixels that triggers auto-scroll. Defaults to 100. */\n scrollThreshold?: number;\n /** Custom style for the ScrollView container */\n style?: StyleProp<ViewStyle>;\n /** Component to render after all grid items (e.g., an \"Add\" button) */\n trailingComponent?: ReactNode;\n /** Component to render as a delete target (shown when dragging). If provided, disables hold-to-delete feature. */\n deleteComponent?: ReactNode;\n /** Custom style for the delete component. If provided, allows custom positioning. */\n deleteComponentStyle?: StyleProp<ViewStyle>;\n /** If true, reverses the order of items (right-to-left, bottom-to-top). Defaults to false. */\n reverse?: boolean;\n};\n\n/**\n * Ref methods for SwappableGrid component\n */\nexport interface SwappableGridRef {\n /** Cancels the delete mode if any item is currently in delete mode */\n cancelDeleteMode: () => void;\n}\n\n/**\n * SwappableGrid - A React Native component for creating a draggable, swappable grid layout.\n *\n * Features:\n * - Drag and drop to reorder items\n * - Long press to enter drag mode\n * - Auto-scroll when dragging near edges\n * - Optional wiggle animation during drag mode\n * - Optional delete functionality with hold-to-delete or delete component\n * - Support for trailing components (e.g., \"Add\" button)\n * - Automatic column calculation based on container width\n *\n * @example\n * ```tsx\n * <SwappableGrid\n * itemWidth={100}\n * itemHeight={100}\n * numColumns={3}\n * onOrderChange={(keys) => console.log('New order:', keys)}\n * >\n * {items.map(item => (\n * <View key={item.id}>{item.content}</View>\n * ))}\n * </SwappableGrid>\n * ```\n */\nconst SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(\n (\n {\n children,\n itemWidth,\n itemHeight,\n gap = 8,\n containerPadding = 8,\n holdToDragMs = 300,\n numColumns,\n onDragEnd,\n onOrderChange,\n onDelete,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n style,\n dragSizeIncreaseFactor = 1.06,\n scrollThreshold = 100,\n scrollSpeed = 10,\n trailingComponent,\n deleteComponent,\n deleteComponentStyle,\n reverse = false,\n },\n ref\n ) => {\n // MUST be Animated ref for scrollTo\n const scrollViewRef = useAnimatedRef<Animated.ScrollView>();\n const [showTrailingComponent, setShowTrailingComponent] =\n useState<boolean>(false);\n const [showDeleteComponent, setShowDeleteComponent] =\n useState<boolean>(false);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [currentNumColumns, setCurrentNumColumns] = useState<number>(\n numColumns || 1\n );\n const touchEndTimeoutRef = React.useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n const deleteComponentRef = React.useRef<View>(null);\n\n useEffect(() => {\n setTimeout(() => {\n setShowTrailingComponent(true);\n setShowDeleteComponent(true);\n }, 50); // This is kinda a hack that makes the trailing component render in the correct position because it lets the other components render first to make the position calculation correct.\n }, []);\n\n const handleOnOrderChange = (order: string[]) => {\n if (onOrderChange) onOrderChange(order.map((key) => normalizeKey(key)));\n };\n\n // Extract paddingBottom from style prop to allow dragging into padding area\n const paddingBottom = React.useMemo(() => {\n if (!style) return 0;\n const styleObj = StyleSheet.flatten(style);\n return (styleObj.paddingBottom as number) || 0;\n }, [style]);\n\n const {\n orderState,\n composed,\n dynamicNumColumns,\n onLayoutContent: originalOnLayoutContent, // layout of the inner content view (for width/cols)\n onLayoutScrollView, // layout of the scroll viewport (for height)\n onScroll,\n deleteItem,\n childArray,\n positions,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n order,\n deleteComponentPosition,\n } = useGridLayout({\n reverse,\n children,\n holdToDragMs,\n itemWidth,\n itemHeight,\n gap,\n containerPadding,\n numColumns,\n onDragEnd,\n onOrderChange: handleOnOrderChange,\n onDelete: onDelete ? (key) => onDelete(normalizeKey(key)) : undefined,\n scrollViewRef,\n scrollSpeed,\n scrollThreshold,\n contentPaddingBottom: paddingBottom,\n });\n\n // Track numColumns changes for height calculation\n const onLayoutContent = (e: LayoutChangeEvent) => {\n originalOnLayoutContent(e);\n // Update currentNumColumns for height calculation\n if (numColumns) {\n setCurrentNumColumns(numColumns);\n } else {\n const possibleCols = Math.floor(\n (e.nativeEvent.layout.width - containerPadding * 2 + gap) /\n (itemWidth + gap)\n );\n setCurrentNumColumns(Math.max(1, possibleCols));\n }\n };\n\n // Track drag state to show/hide delete component\n useAnimatedReaction(\n () => dragMode.value,\n (isDraggingValue) => {\n runOnJS(setIsDragging)(isDraggingValue);\n }\n );\n\n // Expose cancel delete mode function to parent\n useImperativeHandle(\n ref,\n () => ({\n cancelDeleteMode: () => {\n if (anyItemInDeleteMode.value) {\n anyItemInDeleteMode.value = false;\n }\n },\n }),\n [anyItemInDeleteMode]\n );\n\n const trailingX = useDerivedValue(() => {\n const { x } = indexToXY({\n index: order.value.length, // AFTER last swappable\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return x;\n });\n\n const trailingY = useDerivedValue(() => {\n const { y } = indexToXY({\n index: order.value.length,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return y;\n });\n\n const trailingStyle = useAnimatedStyle(() => ({\n left: trailingX.value,\n top: trailingY.value,\n }));\n\n // Calculate default delete component position (bottom center)\n const deleteComponentX = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: center horizontally\n const cols = dynamicNumColumns.value;\n const totalWidth =\n cols * itemWidth + (cols - 1) * gap + containerPadding * 2;\n return (totalWidth - itemWidth) / 2;\n });\n\n const deleteComponentY = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: bottom of grid (after all items)\n // Account for trailing component if it exists\n const rows = Math.ceil(order.value.length / dynamicNumColumns.value);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n if (trailingComponent && showTrailingComponent) {\n return baseY + (itemHeight + gap);\n }\n\n return baseY + gap;\n });\n\n const deleteComponentStyleAnimated = useAnimatedStyle(() => {\n const baseStyle: any = {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n };\n\n // Use custom position if provided, otherwise use default\n if (deleteComponentStyle) {\n return baseStyle;\n }\n\n return {\n ...baseStyle,\n left: deleteComponentX.value,\n top: deleteComponentY.value,\n };\n });\n\n // Update delete component position for drop detection when default position changes\n useDerivedValue(() => {\n if (deleteComponent && deleteComponentPosition && !deleteComponentStyle) {\n // Only update if using default position (custom style uses onLayout)\n deleteComponentPosition.value = {\n x: deleteComponentX.value,\n y: deleteComponentY.value,\n width: itemWidth,\n height: itemHeight,\n };\n }\n });\n\n const showTrailing = !!(trailingComponent && showTrailingComponent);\n const showDelete = !!(deleteComponent && showDeleteComponent);\n // Make sure we calculate room for both trailing and delete components\n // Trailing component is part of the grid, delete component is positioned below\n const itemsCountForHeight = orderState.length + (showTrailing ? 1 : 0);\n\n // Calculate minimum height needed (on JS thread since computeMinHeight is not a worklet)\n const baseHeight = computeMinHeight(\n itemsCountForHeight,\n currentNumColumns,\n itemHeight + gap,\n containerPadding\n );\n\n // If delete component is shown and using default position, add extra space for it\n let calculatedHeight = baseHeight;\n if (showDelete && !deleteComponentStyle) {\n // Account for trailing component when calculating rows (same logic as deleteComponentY)\n const totalItems = orderState.length + (showTrailing ? 1 : 0);\n const rows = Math.ceil(totalItems / currentNumColumns);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n let deleteComponentY = baseY;\n if (showTrailing) {\n deleteComponentY = baseY + (itemHeight + gap);\n } else {\n deleteComponentY = baseY + gap;\n }\n\n const deleteComponentBottom = deleteComponentY + itemHeight;\n // Ensure container is tall enough to show the delete component\n calculatedHeight = Math.max(\n baseHeight,\n deleteComponentBottom + containerPadding\n );\n }\n\n return (\n <AnimatedScrollView\n ref={scrollViewRef}\n onScroll={onScroll}\n onLayout={onLayoutScrollView} // viewport height comes from the SCROLLVIEW\n scrollEventThrottle={16}\n contentContainerStyle={[style]}\n onTouchEnd={() => {\n // Cancel delete mode when user touches outside items\n // Add a small delay to avoid canceling when user taps on items\n // (items might briefly activate, which would prevent cancellation)\n if (touchEndTimeoutRef.current) {\n clearTimeout(touchEndTimeoutRef.current);\n }\n touchEndTimeoutRef.current = setTimeout(() => {\n // Only cancel if still in delete mode and not dragging\n // Don't cancel if user is currently pressing an item (they might be deleting it)\n // This ensures we don't cancel when user is interacting with items\n if (\n anyItemInDeleteMode.value &&\n !dragMode.value &&\n !isPressingDeleteItem.value\n ) {\n anyItemInDeleteMode.value = false;\n }\n }, 100); // Small delay to let item interactions complete\n }}\n >\n <View\n style={[\n styles.container,\n {\n padding: containerPadding,\n height: calculatedHeight,\n },\n ]}\n onLayout={onLayoutContent}\n >\n <GestureDetector gesture={composed}>\n <View pointerEvents=\"box-none\" style={StyleSheet.absoluteFill}>\n {orderState.map((key) => {\n const child = childArray.find(\n (c) => c.key && normalizeKey(c.key) === normalizeKey(key)\n );\n if (!child) return null;\n return (\n <ChildWrapper\n key={key}\n position={positions[key]}\n itemWidth={itemWidth}\n itemHeight={itemHeight}\n dragMode={dragMode}\n anyItemInDeleteMode={anyItemInDeleteMode}\n isPressingDeleteItem={isPressingDeleteItem}\n wiggle={wiggle}\n wiggleDeleteMode={wiggleDeleteMode}\n holdStillToDeleteMs={holdStillToDeleteMs}\n dragSizeIncreaseFactor={dragSizeIncreaseFactor}\n disableHoldToDelete={!onDelete || !!deleteComponent}\n onDelete={() => {\n deleteItem(key);\n if (onDelete) {\n onDelete(normalizeKey(key));\n }\n }}\n >\n {child}\n </ChildWrapper>\n );\n })}\n </View>\n </GestureDetector>\n\n {/* Trailing rendered OUTSIDE the GestureDetector */}\n {trailingComponent && showTrailingComponent && (\n <Animated.View\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n },\n trailingStyle, // 👈 left/top from UI thread\n ]}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {trailingComponent}\n </View>\n </Animated.View>\n )}\n\n {/* Delete component rendered OUTSIDE the GestureDetector - only show when dragging */}\n {deleteComponent && showDeleteComponent && isDragging && (\n <Animated.View\n ref={deleteComponentRef}\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n deleteComponentStyleAnimated,\n deleteComponentStyle, // User can override position with custom style\n ]}\n onLayout={(e) => {\n // Update position for drop detection\n const { x, y, width, height } = e.nativeEvent.layout;\n if (deleteComponentPosition) {\n deleteComponentPosition.value = { x, y, width, height };\n }\n }}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {deleteComponent}\n </View>\n </Animated.View>\n )}\n </View>\n </AnimatedScrollView>\n );\n }\n);\n\nconst styles = StyleSheet.create({\n container: {\n width: \"100%\",\n },\n});\n\nexport default SwappableGrid;\n"],"mappings":"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","ignoreList":[]}
1
+ {"version":3,"names":["React","useEffect","useState","useImperativeHandle","forwardRef","View","StyleSheet","ScrollView","Animated","useAnimatedRef","useAnimatedStyle","useDerivedValue","useAnimatedReaction","runOnJS","GestureDetector","computeMinHeight","useGridLayout","ChildWrapper","indexToXY","AnimatedScrollView","createAnimatedComponent","normalizeKey","k","String","replace","SwappableGrid","children","itemWidth","itemHeight","gap","containerPadding","holdToDragMs","numColumns","onDragEnd","onOrderChange","onDelete","wiggle","wiggleDeleteMode","holdStillToDeleteMs","hapticFeedback","style","dragSizeIncreaseFactor","scrollThreshold","scrollSpeed","trailingComponent","deleteComponent","deleteComponentStyle","reverse","ref","scrollViewRef","showTrailingComponent","setShowTrailingComponent","showDeleteComponent","setShowDeleteComponent","isDragging","setIsDragging","currentNumColumns","setCurrentNumColumns","touchEndTimeoutRef","useRef","deleteComponentRef","setTimeout","handleOnOrderChange","order","map","key","paddingBottom","useMemo","styleObj","flatten","orderState","composed","dynamicNumColumns","onLayoutContent","originalOnLayoutContent","onLayoutScrollView","onScroll","deleteItem","childArray","positions","dragMode","anyItemInDeleteMode","isPressingDeleteItem","deleteComponentPosition","undefined","contentPaddingBottom","e","possibleCols","Math","floor","nativeEvent","layout","width","max","value","isDraggingValue","cancelDeleteMode","trailingX","x","index","length","trailingY","y","trailingStyle","left","top","deleteComponentX","cols","totalWidth","deleteComponentY","rows","ceil","baseY","deleteComponentStyleAnimated","baseStyle","position","height","showTrailing","showDelete","itemsCountForHeight","baseHeight","calculatedHeight","totalItems","deleteComponentBottom","createElement","onLayout","scrollEventThrottle","contentContainerStyle","onTouchEnd","current","clearTimeout","styles","container","padding","gesture","pointerEvents","absoluteFill","child","find","c","disableHoldToDelete","collapsable","flex","create"],"sources":["SwappableGrid.tsx"],"sourcesContent":["import React, {\n ReactNode,\n useEffect,\n useState,\n useImperativeHandle,\n forwardRef,\n} from \"react\";\nimport {\n View,\n StyleSheet,\n ScrollView,\n StyleProp,\n ViewStyle,\n LayoutChangeEvent,\n} from \"react-native\";\nimport Animated, {\n useAnimatedRef,\n useAnimatedStyle,\n useDerivedValue,\n useAnimatedReaction,\n runOnJS,\n} from \"react-native-reanimated\";\nimport { GestureDetector } from \"react-native-gesture-handler\";\nimport computeMinHeight from \"./utils/helpers/computerMinHeight\";\nimport { useGridLayout } from \"./utils/useGridLayout\";\nimport ChildWrapper from \"./ChildWrapper\";\nimport { indexToXY } from \"./utils/helpers/indexCalculations\";\n\nconst AnimatedScrollView = Animated.createAnimatedComponent(ScrollView);\n\nconst normalizeKey = (k: React.Key) => String(k).replace(/^\\.\\$/, \"\");\n\n/**\n * Props for the SwappableGrid component\n */\ntype SwappableGridProps = {\n /** The child components to render in the grid. Each child should have a unique key. */\n children: ReactNode;\n /** Width of each grid item in pixels */\n itemWidth: number;\n /** Height of each grid item in pixels */\n itemHeight: number;\n /** Gap between grid items in pixels. Defaults to 8. */\n gap?: number;\n /** Padding around the container in pixels. Defaults to 8. */\n containerPadding?: number;\n /** Duration in milliseconds to hold before drag starts. Defaults to 300. */\n holdToDragMs?: number;\n /** Number of columns in the grid. If not provided, will be calculated automatically based on container width. */\n numColumns?: number;\n /** Wiggle animation configuration when items are in drag mode or delete mode */\n wiggle?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Wiggle animation configuration specifically for delete mode. If not provided, uses 2x degrees and 0.7x duration of wiggle prop. */\n wiggleDeleteMode?: {\n /** Duration of one wiggle cycle in milliseconds */\n duration: number;\n /** Rotation degrees for the wiggle animation */\n degrees: number;\n };\n /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */\n holdStillToDeleteMs?: number;\n /** Enable haptic feedback (vibration) when entering delete mode. Defaults to false. */\n hapticFeedback?: boolean;\n /** Callback fired when drag ends, providing the ordered array of child nodes */\n onDragEnd?: (ordered: ChildNode[]) => void;\n /** Callback fired when the order changes, providing an array of keys in the new order */\n onOrderChange?: (keys: string[]) => void;\n /** Callback fired when an item is deleted, providing the key of the deleted item */\n onDelete?: (key: string) => void;\n /** Factor by which the dragged item scales up. Defaults to 1.06. */\n dragSizeIncreaseFactor?: number;\n /** Speed of auto-scrolling when dragging near edges. Defaults to 10. */\n scrollSpeed?: number;\n /** Distance from edge in pixels that triggers auto-scroll. Defaults to 100. */\n scrollThreshold?: number;\n /** Custom style for the ScrollView container */\n style?: StyleProp<ViewStyle>;\n /** Component to render after all grid items (e.g., an \"Add\" button) */\n trailingComponent?: ReactNode;\n /** Component to render as a delete target (shown when dragging). If provided, disables hold-to-delete feature. */\n deleteComponent?: ReactNode;\n /** Custom style for the delete component. If provided, allows custom positioning. */\n deleteComponentStyle?: StyleProp<ViewStyle>;\n /** If true, reverses the order of items (right-to-left, bottom-to-top). Defaults to false. */\n reverse?: boolean;\n};\n\n/**\n * Ref methods for SwappableGrid component\n */\nexport interface SwappableGridRef {\n /** Cancels the delete mode if any item is currently in delete mode */\n cancelDeleteMode: () => void;\n}\n\n/**\n * SwappableGrid - A React Native component for creating a draggable, swappable grid layout.\n *\n * Features:\n * - Drag and drop to reorder items\n * - Long press to enter drag mode\n * - Auto-scroll when dragging near edges\n * - Optional wiggle animation during drag mode\n * - Optional delete functionality with hold-to-delete or delete component\n * - Support for trailing components (e.g., \"Add\" button)\n * - Automatic column calculation based on container width\n *\n * @example\n * ```tsx\n * <SwappableGrid\n * itemWidth={100}\n * itemHeight={100}\n * numColumns={3}\n * onOrderChange={(keys) => console.log('New order:', keys)}\n * >\n * {items.map(item => (\n * <View key={item.id}>{item.content}</View>\n * ))}\n * </SwappableGrid>\n * ```\n */\nconst SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(\n (\n {\n children,\n itemWidth,\n itemHeight,\n gap = 8,\n containerPadding = 8,\n holdToDragMs = 300,\n numColumns,\n onDragEnd,\n onOrderChange,\n onDelete,\n wiggle,\n wiggleDeleteMode,\n holdStillToDeleteMs = 1000,\n hapticFeedback = false,\n style,\n dragSizeIncreaseFactor = 1.06,\n scrollThreshold = 100,\n scrollSpeed = 10,\n trailingComponent,\n deleteComponent,\n deleteComponentStyle,\n reverse = false,\n },\n ref\n ) => {\n // MUST be Animated ref for scrollTo\n const scrollViewRef = useAnimatedRef<Animated.ScrollView>();\n const [showTrailingComponent, setShowTrailingComponent] =\n useState<boolean>(false);\n const [showDeleteComponent, setShowDeleteComponent] =\n useState<boolean>(false);\n const [isDragging, setIsDragging] = useState<boolean>(false);\n const [currentNumColumns, setCurrentNumColumns] = useState<number>(\n numColumns || 1\n );\n const touchEndTimeoutRef = React.useRef<ReturnType<\n typeof setTimeout\n > | null>(null);\n const deleteComponentRef = React.useRef<View>(null);\n\n useEffect(() => {\n setTimeout(() => {\n setShowTrailingComponent(true);\n setShowDeleteComponent(true);\n }, 50); // This is kinda a hack that makes the trailing component render in the correct position because it lets the other components render first to make the position calculation correct.\n }, []);\n\n const handleOnOrderChange = (order: string[]) => {\n if (onOrderChange) onOrderChange(order.map((key) => normalizeKey(key)));\n };\n\n // Extract paddingBottom from style prop to allow dragging into padding area\n const paddingBottom = React.useMemo(() => {\n if (!style) return 0;\n const styleObj = StyleSheet.flatten(style);\n return (styleObj.paddingBottom as number) || 0;\n }, [style]);\n\n const {\n orderState,\n composed,\n dynamicNumColumns,\n onLayoutContent: originalOnLayoutContent, // layout of the inner content view (for width/cols)\n onLayoutScrollView, // layout of the scroll viewport (for height)\n onScroll,\n deleteItem,\n childArray,\n positions,\n dragMode,\n anyItemInDeleteMode,\n isPressingDeleteItem,\n order,\n deleteComponentPosition,\n } = useGridLayout({\n reverse,\n children,\n holdToDragMs,\n itemWidth,\n itemHeight,\n gap,\n containerPadding,\n numColumns,\n onDragEnd,\n onOrderChange: handleOnOrderChange,\n onDelete: onDelete ? (key) => onDelete(normalizeKey(key)) : undefined,\n scrollViewRef,\n scrollSpeed,\n scrollThreshold,\n contentPaddingBottom: paddingBottom,\n });\n\n // Track numColumns changes for height calculation\n const onLayoutContent = (e: LayoutChangeEvent) => {\n originalOnLayoutContent(e);\n // Update currentNumColumns for height calculation\n if (numColumns) {\n setCurrentNumColumns(numColumns);\n } else {\n const possibleCols = Math.floor(\n (e.nativeEvent.layout.width - containerPadding * 2 + gap) /\n (itemWidth + gap)\n );\n setCurrentNumColumns(Math.max(1, possibleCols));\n }\n };\n\n // Track drag state to show/hide delete component\n useAnimatedReaction(\n () => dragMode.value,\n (isDraggingValue) => {\n runOnJS(setIsDragging)(isDraggingValue);\n }\n );\n\n // Expose cancel delete mode function to parent\n useImperativeHandle(\n ref,\n () => ({\n cancelDeleteMode: () => {\n if (anyItemInDeleteMode.value) {\n anyItemInDeleteMode.value = false;\n }\n },\n }),\n [anyItemInDeleteMode]\n );\n\n const trailingX = useDerivedValue(() => {\n const { x } = indexToXY({\n index: order.value.length, // AFTER last swappable\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return x;\n });\n\n const trailingY = useDerivedValue(() => {\n const { y } = indexToXY({\n index: order.value.length,\n itemWidth,\n itemHeight,\n dynamicNumColumns,\n containerPadding,\n gap,\n });\n return y;\n });\n\n const trailingStyle = useAnimatedStyle(() => ({\n left: trailingX.value,\n top: trailingY.value,\n }));\n\n // Calculate default delete component position (bottom center)\n const deleteComponentX = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: center horizontally\n const cols = dynamicNumColumns.value;\n const totalWidth =\n cols * itemWidth + (cols - 1) * gap + containerPadding * 2;\n return (totalWidth - itemWidth) / 2;\n });\n\n const deleteComponentY = useDerivedValue(() => {\n if (deleteComponentStyle) {\n // If custom style provided, position will be set by user\n return 0;\n }\n // Default: bottom of grid (after all items)\n // Account for trailing component if it exists\n const rows = Math.ceil(order.value.length / dynamicNumColumns.value);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n if (trailingComponent && showTrailingComponent) {\n return baseY + (itemHeight + gap);\n }\n\n return baseY + gap;\n });\n\n const deleteComponentStyleAnimated = useAnimatedStyle(() => {\n const baseStyle: any = {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n };\n\n // Use custom position if provided, otherwise use default\n if (deleteComponentStyle) {\n return baseStyle;\n }\n\n return {\n ...baseStyle,\n left: deleteComponentX.value,\n top: deleteComponentY.value,\n };\n });\n\n // Update delete component position for drop detection when default position changes\n useDerivedValue(() => {\n if (deleteComponent && deleteComponentPosition && !deleteComponentStyle) {\n // Only update if using default position (custom style uses onLayout)\n deleteComponentPosition.value = {\n x: deleteComponentX.value,\n y: deleteComponentY.value,\n width: itemWidth,\n height: itemHeight,\n };\n }\n });\n\n const showTrailing = !!(trailingComponent && showTrailingComponent);\n const showDelete = !!(deleteComponent && showDeleteComponent);\n // Make sure we calculate room for both trailing and delete components\n // Trailing component is part of the grid, delete component is positioned below\n const itemsCountForHeight = orderState.length + (showTrailing ? 1 : 0);\n\n // Calculate minimum height needed (on JS thread since computeMinHeight is not a worklet)\n const baseHeight = computeMinHeight(\n itemsCountForHeight,\n currentNumColumns,\n itemHeight + gap,\n containerPadding\n );\n\n // If delete component is shown and using default position, add extra space for it\n let calculatedHeight = baseHeight;\n if (showDelete && !deleteComponentStyle) {\n // Account for trailing component when calculating rows (same logic as deleteComponentY)\n const totalItems = orderState.length + (showTrailing ? 1 : 0);\n const rows = Math.ceil(totalItems / currentNumColumns);\n const baseY = containerPadding + rows * (itemHeight + gap);\n\n // If trailing component exists, add extra space so delete component appears below it\n let deleteComponentY = baseY;\n if (showTrailing) {\n deleteComponentY = baseY + (itemHeight + gap);\n } else {\n deleteComponentY = baseY + gap;\n }\n\n const deleteComponentBottom = deleteComponentY + itemHeight;\n // Ensure container is tall enough to show the delete component\n calculatedHeight = Math.max(\n baseHeight,\n deleteComponentBottom + containerPadding\n );\n }\n\n return (\n <AnimatedScrollView\n ref={scrollViewRef}\n onScroll={onScroll}\n onLayout={onLayoutScrollView} // viewport height comes from the SCROLLVIEW\n scrollEventThrottle={16}\n contentContainerStyle={[style]}\n onTouchEnd={() => {\n // Cancel delete mode when user touches outside items\n // Add a small delay to avoid canceling when user taps on items\n // (items might briefly activate, which would prevent cancellation)\n if (touchEndTimeoutRef.current) {\n clearTimeout(touchEndTimeoutRef.current);\n }\n touchEndTimeoutRef.current = setTimeout(() => {\n // Only cancel if still in delete mode and not dragging\n // Don't cancel if user is currently pressing an item (they might be deleting it)\n // This ensures we don't cancel when user is interacting with items\n if (\n anyItemInDeleteMode.value &&\n !dragMode.value &&\n !isPressingDeleteItem.value\n ) {\n anyItemInDeleteMode.value = false;\n }\n }, 100); // Small delay to let item interactions complete\n }}\n >\n <View\n style={[\n styles.container,\n {\n padding: containerPadding,\n height: calculatedHeight,\n },\n ]}\n onLayout={onLayoutContent}\n >\n <GestureDetector gesture={composed}>\n <View pointerEvents=\"box-none\" style={StyleSheet.absoluteFill}>\n {orderState.map((key) => {\n const child = childArray.find(\n (c) => c.key && normalizeKey(c.key) === normalizeKey(key)\n );\n if (!child) return null;\n return (\n <ChildWrapper\n key={key}\n position={positions[key]}\n itemWidth={itemWidth}\n itemHeight={itemHeight}\n dragMode={dragMode}\n anyItemInDeleteMode={anyItemInDeleteMode}\n isPressingDeleteItem={isPressingDeleteItem}\n wiggle={wiggle}\n wiggleDeleteMode={wiggleDeleteMode}\n holdStillToDeleteMs={holdStillToDeleteMs}\n hapticFeedback={hapticFeedback}\n dragSizeIncreaseFactor={dragSizeIncreaseFactor}\n disableHoldToDelete={!onDelete || !!deleteComponent}\n onDelete={() => {\n deleteItem(key);\n if (onDelete) {\n onDelete(normalizeKey(key));\n }\n }}\n >\n {child}\n </ChildWrapper>\n );\n })}\n </View>\n </GestureDetector>\n\n {/* Trailing rendered OUTSIDE the GestureDetector */}\n {trailingComponent && showTrailingComponent && (\n <Animated.View\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n {\n position: \"absolute\",\n width: itemWidth,\n height: itemHeight,\n },\n trailingStyle, // 👈 left/top from UI thread\n ]}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {trailingComponent}\n </View>\n </Animated.View>\n )}\n\n {/* Delete component rendered OUTSIDE the GestureDetector - only show when dragging */}\n {deleteComponent && showDeleteComponent && isDragging && (\n <Animated.View\n ref={deleteComponentRef}\n pointerEvents=\"box-none\"\n collapsable={false}\n style={[\n deleteComponentStyleAnimated,\n deleteComponentStyle, // User can override position with custom style\n ]}\n onLayout={(e) => {\n // Update position for drop detection\n const { x, y, width, height } = e.nativeEvent.layout;\n if (deleteComponentPosition) {\n deleteComponentPosition.value = { x, y, width, height };\n }\n }}\n >\n <View pointerEvents=\"auto\" style={{ flex: 1 }}>\n {deleteComponent}\n </View>\n </Animated.View>\n )}\n </View>\n </AnimatedScrollView>\n );\n }\n);\n\nconst styles = StyleSheet.create({\n container: {\n width: \"100%\",\n },\n});\n\nexport default SwappableGrid;\n"],"mappings":"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","ignoreList":[]}
@@ -24,6 +24,7 @@ type Props = {
24
24
  dragSizeIncreaseFactor: number;
25
25
  onDelete?: () => void;
26
26
  disableHoldToDelete?: boolean;
27
+ hapticFeedback?: boolean;
27
28
  };
28
- export default function ChildWrapper({ position, itemWidth, itemHeight, dragMode, anyItemInDeleteMode, isPressingDeleteItem, children, wiggle, wiggleDeleteMode, holdStillToDeleteMs, dragSizeIncreaseFactor, onDelete, disableHoldToDelete, }: Props): React.JSX.Element;
29
+ export default function ChildWrapper({ position, itemWidth, itemHeight, dragMode, anyItemInDeleteMode, isPressingDeleteItem, children, wiggle, wiggleDeleteMode, holdStillToDeleteMs, dragSizeIncreaseFactor, onDelete, disableHoldToDelete, hapticFeedback, }: Props): React.JSX.Element;
29
30
  export {};
@@ -34,6 +34,8 @@ type SwappableGridProps = {
34
34
  };
35
35
  /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */
36
36
  holdStillToDeleteMs?: number;
37
+ /** Enable haptic feedback (vibration) when entering delete mode. Defaults to false. */
38
+ hapticFeedback?: boolean;
37
39
  /** Callback fired when drag ends, providing the ordered array of child nodes */
38
40
  onDragEnd?: (ordered: ChildNode[]) => void;
39
41
  /** Callback fired when the order changes, providing an array of keys in the new order */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-swappable-grid",
3
- "version": "1.0.7",
3
+ "version": "1.0.9",
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",
@@ -41,6 +41,11 @@
41
41
  "react-native-gesture-handler": "*",
42
42
  "react-native-reanimated": "*"
43
43
  },
44
+ "peerDependenciesMeta": {
45
+ "expo-haptics": {
46
+ "optional": true
47
+ }
48
+ },
44
49
  "devDependencies": {
45
50
  "@types/react": "^19.2.7",
46
51
  "@types/react-native": "^0.73.0",
@@ -1,5 +1,13 @@
1
1
  import React, { useEffect, useState } from "react";
2
- import { Text, View, Pressable } from "react-native";
2
+ import { Text, View, Pressable, Vibration, Platform } from "react-native";
3
+
4
+ // Try to import expo-haptics (optional dependency)
5
+ let Haptics: any = null;
6
+ try {
7
+ Haptics = require("expo-haptics");
8
+ } catch (e) {
9
+ // expo-haptics not available, will fall back to Vibration API
10
+ }
3
11
  import Animated, {
4
12
  Easing,
5
13
  useAnimatedStyle,
@@ -32,6 +40,7 @@ type Props = {
32
40
  dragSizeIncreaseFactor: number;
33
41
  onDelete?: () => void;
34
42
  disableHoldToDelete?: boolean; // If true, disable the hold-to-delete feature
43
+ hapticFeedback?: boolean; // If true, enable haptic feedback when entering delete mode
35
44
  };
36
45
 
37
46
  export default function ChildWrapper({
@@ -48,6 +57,7 @@ export default function ChildWrapper({
48
57
  dragSizeIncreaseFactor,
49
58
  onDelete,
50
59
  disableHoldToDelete = false,
60
+ hapticFeedback = false,
51
61
  }: Props) {
52
62
  const rotation = useSharedValue(0);
53
63
  const currentWiggleMode = useSharedValue<"none" | "normal" | "delete">(
@@ -63,6 +73,36 @@ export default function ChildWrapper({
63
73
  const frameCounter = useSharedValue(0);
64
74
  const wasReleasedAfterDeleteMode = useSharedValue(false); // Track if item was released after entering delete mode
65
75
 
76
+ // Function to trigger haptic feedback (called from worklet via runOnJS)
77
+ const triggerHapticFeedback = () => {
78
+ try {
79
+ // Platform-specific haptic feedback
80
+ if (Platform.OS === "ios") {
81
+ // iOS: Prefer expo-haptics for better control (subtle feedback)
82
+ if (Haptics && Haptics.impactAsync) {
83
+ // Use light impact for subtle feedback (similar to iOS system haptics)
84
+ Haptics.impactAsync(Haptics.ImpactFeedbackStyle.Light);
85
+ return;
86
+ }
87
+ // Fallback to Vibration API if expo-haptics not available
88
+ // Note: This will be a stronger vibration than desired on iOS
89
+ if (Vibration && typeof Vibration.vibrate === "function") {
90
+ Vibration.vibrate(1);
91
+ }
92
+ } else {
93
+ // Android: Use Vibration API (works well and is more reliable than expo-haptics)
94
+ if (Vibration && typeof Vibration.vibrate === "function") {
95
+ // Android requires a pattern array: [delay, duration]
96
+ // [0, 20] means: start immediately (0ms delay), vibrate for 20ms
97
+ Vibration.vibrate([0, 20]);
98
+ }
99
+ }
100
+ } catch (error) {
101
+ // Silently fail if haptic feedback is not available or fails
102
+ // This allows the library to work in environments where haptics are not supported
103
+ }
104
+ };
105
+
66
106
  // Timer logic that runs every frame via useDerivedValue
67
107
  useDerivedValue(() => {
68
108
  "worklet";
@@ -160,11 +200,16 @@ export default function ChildWrapper({
160
200
  stillTimer.value += 16;
161
201
 
162
202
  // Enter delete mode after holdStillToDeleteMs of being held still
163
- if (stillTimer.value >= holdStillToDeleteMs) {
203
+ if (stillTimer.value >= holdStillToDeleteMs && !deleteModeActive.value) {
164
204
  deleteModeActive.value = true;
165
205
  anyItemInDeleteMode.value = true; // Set global delete mode
166
206
  showDelete.value = true;
167
207
  wasReleasedAfterDeleteMode.value = false; // Reset on entry
208
+
209
+ // Trigger haptic feedback when entering delete mode
210
+ if (hapticFeedback) {
211
+ runOnJS(triggerHapticFeedback)();
212
+ }
168
213
  }
169
214
  });
170
215
 
@@ -64,6 +64,8 @@ type SwappableGridProps = {
64
64
  };
65
65
  /** Duration in milliseconds to hold an item still before entering delete mode. Defaults to 1000. */
66
66
  holdStillToDeleteMs?: number;
67
+ /** Enable haptic feedback (vibration) when entering delete mode. Defaults to false. */
68
+ hapticFeedback?: boolean;
67
69
  /** Callback fired when drag ends, providing the ordered array of child nodes */
68
70
  onDragEnd?: (ordered: ChildNode[]) => void;
69
71
  /** Callback fired when the order changes, providing an array of keys in the new order */
@@ -138,6 +140,7 @@ const SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(
138
140
  wiggle,
139
141
  wiggleDeleteMode,
140
142
  holdStillToDeleteMs = 1000,
143
+ hapticFeedback = false,
141
144
  style,
142
145
  dragSizeIncreaseFactor = 1.06,
143
146
  scrollThreshold = 100,
@@ -438,6 +441,7 @@ const SwappableGrid = forwardRef<SwappableGridRef, SwappableGridProps>(
438
441
  wiggle={wiggle}
439
442
  wiggleDeleteMode={wiggleDeleteMode}
440
443
  holdStillToDeleteMs={holdStillToDeleteMs}
444
+ hapticFeedback={hapticFeedback}
441
445
  dragSizeIncreaseFactor={dragSizeIncreaseFactor}
442
446
  disableHoldToDelete={!onDelete || !!deleteComponent}
443
447
  onDelete={() => {