react-native-swipeable-actions 0.1.4 → 0.1.6

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.
@@ -20,6 +20,10 @@ class SwipeableModule : Module() {
20
20
  SwipeableView.closeAll(animated ?: true)
21
21
  }
22
22
 
23
+ Function("isOpenByKey") { key: String ->
24
+ SwipeableView.isOpenByKey(key)
25
+ }
26
+
23
27
  View(SwipeableView::class) {
24
28
  Prop("actionsWidth") { view: SwipeableView, width: Float ->
25
29
  view.actionsWidth = width
@@ -89,6 +89,9 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
89
89
  fun clearCache() {
90
90
  openStateCache.evictAll()
91
91
  }
92
+
93
+ /** Check if a recyclingKey is cached as open (for JS-side state initialization) */
94
+ fun isOpenByKey(key: String): Boolean = getOpenState(key)
92
95
  }
93
96
 
94
97
  private val density = context.resources.displayMetrics.density
@@ -182,7 +185,11 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
182
185
 
183
186
  close(animated = false)
184
187
 
185
- // Apply using local shouldOpen (pendingShouldOpen was cleared by close)
188
+ // Apply using local shouldOpen (pendingShouldOpen was cleared by close).
189
+ // Set recycleSnapPending so onViewAdded can position actions immediately
190
+ // when React renders them (isOpenByKey initializes hasActionsRendered=true
191
+ // before snapToOpen runs on the next frame).
192
+ recycleSnapPending = shouldOpen
186
193
  if (shouldOpen) {
187
194
  post { snapToOpen() }
188
195
  }
@@ -197,6 +204,9 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
197
204
  private var isGestureActivated: Boolean = false
198
205
  private var lastEmittedProgress: Float = -1f
199
206
  private var pendingShouldOpen: Boolean = false
207
+ /** Tracks pending snapToOpen from recyclingKey setter. Unlike pendingShouldOpen,
208
+ * this survives close(animated:false) so onViewAdded can position actions correctly. */
209
+ private var recycleSnapPending: Boolean = false
200
210
  private var lastEmittedWillEndState: String? = null
201
211
 
202
212
  // Track if open animation was explicitly interrupted by close() for autoClose
@@ -235,12 +245,55 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
235
245
  // Auto-close runnable for tracking and cancellation
236
246
  private var autoCloseRunnable: Runnable? = null
237
247
 
248
+ // Track when a child (e.g. RNGH gesture handler) claims the touch
249
+ private var childRequestedDisallowIntercept: Boolean = false
250
+
251
+ // Layout guard: re-applies translations for a few frames after a reorder.
252
+ // Fabric may reset child translationX during prop reconciliation after a
253
+ // recyclingKey change. This guard runs for a bounded number of frames to
254
+ // detect and correct mismatches, then stops automatically.
255
+ private var layoutGuardCallback: Choreographer.FrameCallback? = null
256
+ private var layoutGuardFramesRemaining: Int = 0
257
+
258
+ private fun startLayoutGuard(frames: Int = 5) {
259
+ stopLayoutGuard()
260
+ layoutGuardFramesRemaining = frames
261
+ layoutGuardCallback = Choreographer.FrameCallback {
262
+ layoutGuardFramesRemaining--
263
+ if (isOpen && currentTranslation != 0f && !isDragging && !isAnimating) {
264
+ val contentTx = contentView?.translationX ?: 0f
265
+ if (contentTx != currentTranslation) {
266
+ contentView?.translationX = currentTranslation
267
+ updateActionsTransform()
268
+ actionsView?.visibility = View.VISIBLE
269
+ }
270
+ }
271
+ if (layoutGuardFramesRemaining > 0) {
272
+ layoutGuardCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
273
+ } else {
274
+ layoutGuardCallback = null
275
+ }
276
+ }
277
+ layoutGuardCallback?.let { Choreographer.getInstance().postFrameCallback(it) }
278
+ }
279
+
280
+ private fun stopLayoutGuard() {
281
+ layoutGuardCallback?.let { Choreographer.getInstance().removeFrameCallback(it) }
282
+ layoutGuardCallback = null
283
+ layoutGuardFramesRemaining = 0
284
+ }
285
+
238
286
  init {
239
287
  clipChildren = false
240
288
  clipToPadding = false
241
289
  setWillNotDraw(false)
242
290
  }
243
291
 
292
+ override fun requestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {
293
+ childRequestedDisallowIntercept = disallowIntercept
294
+ super.requestDisallowInterceptTouchEvent(disallowIntercept)
295
+ }
296
+
244
297
  override fun setClipChildren(clipChildren: Boolean) {
245
298
  super.setClipChildren(false)
246
299
  }
@@ -275,6 +328,12 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
275
328
  }
276
329
 
277
330
  if (wasActionsNil && actionsView != null) {
331
+ // Prevent child clipping: Yoga lays out children for absoluteFill (full parent width)
332
+ // but our onLayout overrides the actionsView bounds to actionsWidth. Without this,
333
+ // children positioned by Yoga's flex-end would be clipped beyond the narrower bounds.
334
+ (actionsView as? ViewGroup)?.clipChildren = false
335
+ (actionsView as? ViewGroup)?.clipToPadding = false
336
+
278
337
  if (isOpen || isAnimating) {
279
338
  actionsView?.visibility = View.VISIBLE
280
339
  actionsView?.translationX = 0f
@@ -287,6 +346,13 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
287
346
  pendingShouldOpen = false
288
347
  post { snapToOpen() }
289
348
  }
349
+ else if (recycleSnapPending) {
350
+ // Actions were rendered by React (via isOpenByKey) before native snapToOpen ran.
351
+ // Position them immediately so they're visible when UIAutomator checks.
352
+ recycleSnapPending = false
353
+ actionsView?.visibility = View.VISIBLE
354
+ actionsView?.translationX = 0f
355
+ }
290
356
  }
291
357
  }
292
358
 
@@ -305,22 +371,30 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
305
371
 
306
372
  for (i in 0 until childCount) {
307
373
  val child = getChildAt(i)
308
- if (child === actionsView && actionsWidthPx > 0f) {
309
- val actionsWidthInt = actionsWidthPx.toInt()
310
- if (isLeading) {
311
- child.layout(0, 0, actionsWidthInt, height)
312
- } else {
313
- child.layout(width - actionsWidthInt, 0, width, height)
314
- }
315
- } else {
316
- child.layout(0, 0, width, height)
317
- }
374
+ // Don't override actionsView layout - Yoga positions it via absoluteFill
375
+ // and lays out children (flex-end) based on full parent width. Overriding
376
+ // the bounds to actionsWidth causes children to be positioned outside the
377
+ // narrower bounds, making them invisible during FlatList reorder.
378
+ // Instead, use translationX to hide/show the actions.
379
+ child.layout(0, 0, width, height)
318
380
  }
319
381
 
320
- if (!isDragging && !isAnimating && currentTranslation == 0f && actionsWidthPx > 0f) {
382
+ if (!isDragging && !isAnimating && currentTranslation == 0f && actionsWidthPx > 0f && !recycleSnapPending) {
321
383
  val actionsOffset = if (isLeading) -actionsWidthPx else actionsWidthPx
322
384
  actionsView?.translationX = actionsOffset
323
385
  }
386
+
387
+ // Re-apply translations when open. Fabric may reset child translationX during
388
+ // prop reconciliation after a list reorder. The post {} ensures we run after
389
+ // Fabric finishes applying all mutations in the current frame.
390
+ if (isOpen && currentTranslation != 0f && !isDragging && !isAnimating) {
391
+ post {
392
+ if (isOpen && currentTranslation != 0f && !isDragging && !isAnimating) {
393
+ contentView?.translationX = currentTranslation
394
+ updateActionsTransform()
395
+ }
396
+ }
397
+ }
324
398
  }
325
399
 
326
400
  /**
@@ -350,6 +424,7 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
350
424
  dispatchTouchStartX = event.rawX
351
425
  dispatchTouchStartY = event.rawY
352
426
  isBlockingChildEvents = false
427
+ childRequestedDisallowIntercept = false
353
428
 
354
429
  // Initialize tracking
355
430
  touchStartX = event.rawX
@@ -379,6 +454,11 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
379
454
 
380
455
  // Detect horizontal swipe
381
456
  if (dx > touchSlop && dx > dy) {
457
+ // If a child gesture handler (e.g. RNGH) claimed the touch, yield to it
458
+ if (childRequestedDisallowIntercept) {
459
+ return super.dispatchTouchEvent(event)
460
+ }
461
+
382
462
  val horizontalDelta = event.rawX - dispatchTouchStartX
383
463
  val isCorrectDirection = if (isOpen) {
384
464
  true
@@ -714,6 +794,7 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
714
794
  return@addEndListener
715
795
  }
716
796
  isAnimating = false
797
+ startLayoutGuard()
717
798
  emitFinalProgress(1f, targetX)
718
799
  // With autoClose, let close() emit onSwipeEnd("open") instead
719
800
  if (!autoClose) {
@@ -749,6 +830,8 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
749
830
  onSwipeStart(emptyMap())
750
831
  }
751
832
 
833
+ startLayoutGuard()
834
+
752
835
  // Emit final progress
753
836
  emitFinalProgress(1f, targetX)
754
837
  }
@@ -756,6 +839,7 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
756
839
  fun close(animated: Boolean = true, velocity: Float = 0f) {
757
840
  cancelAutoClose()
758
841
  stopProgressUpdates()
842
+ stopLayoutGuard()
759
843
 
760
844
  // Cancel any running animations
761
845
  openAnimationInterruptedByClose = true
@@ -943,16 +1027,28 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
943
1027
 
944
1028
  override fun onAttachedToWindow() {
945
1029
  super.onAttachedToWindow()
946
- // Re-register in the view registry after reattach (e.g. returning from navigation).
947
- // onDetachedFromWindow unregisters the view, but the recyclingKey prop setter
948
- // short-circuits when the value hasn't changed, so the view is never re-registered
949
- // unless we do it here.
950
1030
  recyclingKey?.let { registerView(this, it) }
1031
+
1032
+ if (isOpen && currentTranslation != 0f) {
1033
+ post {
1034
+ if (isOpen && currentTranslation != 0f && !isDragging && !isAnimating) {
1035
+ contentView?.translationX = currentTranslation
1036
+ updateActionsTransform()
1037
+ actionsView?.visibility = View.VISIBLE
1038
+ }
1039
+ }
1040
+
1041
+ // Restart autoClose timer if it was cancelled during detach
1042
+ if (autoClose) {
1043
+ scheduleAutoClose()
1044
+ }
1045
+ }
951
1046
  }
952
1047
 
953
1048
  override fun onDetachedFromWindow() {
954
1049
  super.onDetachedFromWindow()
955
1050
  stopProgressUpdates()
1051
+ stopLayoutGuard()
956
1052
  cancelGestureTimeout()
957
1053
  cancelAutoClose()
958
1054
  contentSpringAnimation?.cancel()
@@ -1 +1 @@
1
- {"version":3,"file":"Swipeable.d.ts","sourceRoot":"","sources":["../src/Swipeable.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,cAAc,EACd,gBAAgB,EAIjB,MAAM,mBAAmB,CAAA;AAe1B,QAAA,MAAM,SAAS,0HA8Hb,CAAA;AAgBF,KAAK,mBAAmB,GAAG,OAAO,SAAS,GAAG;IAC5C,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACzD,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC,CAAA;AAED,QAAA,MAAM,eAAe,EAAgB,mBAAmB,CAAA;AAsBxD,eAAe,eAAe,CAAA"}
1
+ {"version":3,"file":"Swipeable.d.ts","sourceRoot":"","sources":["../src/Swipeable.tsx"],"names":[],"mappings":"AAKA,OAAO,EACL,cAAc,EACd,gBAAgB,EAIjB,MAAM,mBAAmB,CAAA;AAe1B,QAAA,MAAM,SAAS,0HAiJb,CAAA;AAgBF,KAAK,mBAAmB,GAAG,OAAO,SAAS,GAAG;IAC5C,IAAI,EAAE,CAAC,YAAY,EAAE,MAAM,KAAK,IAAI,CAAA;IACpC,KAAK,EAAE,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACzD,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC,CAAA;AAED,QAAA,MAAM,eAAe,EAAgB,mBAAmB,CAAA;AAsBxD,eAAe,eAAe,CAAA"}
@@ -20,13 +20,26 @@ function normalizeRecyclingKey(key) {
20
20
  }
21
21
  const Swipeable = (0, react_1.forwardRef)(function Swipeable({ children, actions, actionsWidth, actionsPosition = constants_1.SWIPE_DEFAULTS.ACTIONS_POSITION, friction = constants_1.SWIPE_DEFAULTS.FRICTION, threshold = constants_1.SWIPE_DEFAULTS.THRESHOLD, gestureEnabled = constants_1.SWIPE_DEFAULTS.GESTURE_ENABLED, dragOffsetFromEdge = constants_1.SWIPE_DEFAULTS.DRAG_OFFSET_FROM_EDGE, style, testID, onSwipeStart, onSwipeStateChange, onSwipeEnd, onProgress, autoClose = false, autoCloseTimeout = 0, recyclingKey }, ref) {
22
22
  const nativeRef = (0, react_1.useRef)(null);
23
- const [hasActionsRendered, setHasActionsRendered] = (0, react_1.useState)(false);
24
- const swipeStartedRef = (0, react_1.useRef)(false);
25
- // Normalize and sync recyclingKey to ref for use in useImperativeHandle
26
- // (set during render to ensure it's always current before callbacks fire)
23
+ // Normalize recyclingKey before hooks that depend on it
27
24
  const normalizedKey = normalizeRecyclingKey(recyclingKey);
28
25
  const recyclingKeyRef = (0, react_1.useRef)(undefined);
29
26
  recyclingKeyRef.current = normalizedKey;
27
+ // Initialize from native cache: if this recyclingKey was open, render actions immediately.
28
+ // This handles FlatList remounting components during data reorder - React state is lost
29
+ // but native cache preserves the open state.
30
+ const [hasActionsRendered, setHasActionsRendered] = (0, react_1.useState)(() => {
31
+ return normalizedKey != null && (0, nativeModuleUtils_1.isOpenByKey)(normalizedKey);
32
+ });
33
+ const swipeStartedRef = (0, react_1.useRef)(false);
34
+ // When recyclingKey changes (FlatList reorder/recycle), sync hasActionsRendered
35
+ // with the native cached open state. FlatList reuses component instances without
36
+ // remounting, so useState initializer doesn't re-run.
37
+ (0, react_1.useEffect)(() => {
38
+ if (!normalizedKey)
39
+ return;
40
+ const cachedOpen = (0, nativeModuleUtils_1.isOpenByKey)(normalizedKey);
41
+ setHasActionsRendered(cachedOpen);
42
+ }, [normalizedKey]);
30
43
  // Development-time validation
31
44
  if (__DEV__) {
32
45
  if (autoClose && recyclingKey == null) {
@@ -76,7 +89,9 @@ const Swipeable = (0, react_1.forwardRef)(function Swipeable({ children, actions
76
89
  onSwipeEnd?.(event.nativeEvent.state);
77
90
  }, [onSwipeEnd]);
78
91
  return (<SwipeableView_1.SwipeableView ref={nativeRef} style={[styles.container, style]} actionsWidth={actionsWidth} actionsPosition={actionsPosition} friction={friction} threshold={threshold} gestureEnabled={gestureEnabled} dragOffsetFromEdge={dragOffsetFromEdge} autoClose={autoClose} autoCloseTimeout={autoCloseTimeout} {...(normalizedKey ? { recyclingKey: normalizedKey } : {})} {...(testID ? { testID } : {})} onSwipeStart={handleSwipeStart} onSwipeProgress={handleSwipeProgress} onSwipeStateChange={handleSwipeStateChange} onSwipeEnd={handleSwipeEnd}>
79
- {hasActionsRendered && (<SwipeableActions_1.SwipeableActions actionsPosition={actionsPosition}>{actions}</SwipeableActions_1.SwipeableActions>)}
92
+ {hasActionsRendered && (<SwipeableActions_1.SwipeableActions actionsPosition={actionsPosition} {...(testID ? { testID: `${testID}-actions` } : {})}>
93
+ {actions}
94
+ </SwipeableActions_1.SwipeableActions>)}
80
95
 
81
96
  <react_native_1.View style={styles.content}>{children}</react_native_1.View>
82
97
  </SwipeableView_1.SwipeableView>);
@@ -1 +1 @@
1
- {"version":3,"file":"Swipeable.js","sourceRoot":"","sources":["../src/Swipeable.tsx"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"Swipeable.js","sourceRoot":"","sources":["../src/Swipeable.tsx"],"names":[],"mappings":"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"}
@@ -7,11 +7,13 @@ export interface SwipeableActionsProps {
7
7
  children: ReactNode;
8
8
  /** Position of actions: 'left' or 'right' */
9
9
  actionsPosition: 'left' | 'right';
10
+ /** Test ID for e2e testing */
11
+ testID?: string;
10
12
  }
11
13
  /**
12
14
  * SwipeableActions - Internal layout component for swipeable actions
13
15
  *
14
16
  * Handles entrance animation when children mount.
15
17
  */
16
- export declare function SwipeableActions({ children, actionsPosition }: SwipeableActionsProps): React.JSX.Element;
18
+ export declare function SwipeableActions({ children, actionsPosition, testID }: SwipeableActionsProps): React.JSX.Element;
17
19
  //# sourceMappingURL=SwipeableActions.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"SwipeableActions.d.ts","sourceRoot":"","sources":["../src/SwipeableActions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGxC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,kCAAkC;IAClC,QAAQ,EAAE,SAAS,CAAA;IACnB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,GAAG,OAAO,CAAA;CAClC;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,EAAE,qBAAqB,qBASpF"}
1
+ {"version":3,"file":"SwipeableActions.d.ts","sourceRoot":"","sources":["../src/SwipeableActions.tsx"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,OAAO,CAAA;AAGxC;;GAEG;AACH,MAAM,WAAW,qBAAqB;IACpC,kCAAkC;IAClC,QAAQ,EAAE,SAAS,CAAA;IACnB,6CAA6C;IAC7C,eAAe,EAAE,MAAM,GAAG,OAAO,CAAA;IACjC,8BAA8B;IAC9B,MAAM,CAAC,EAAE,MAAM,CAAA;CAChB;AAED;;;;GAIG;AACH,wBAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAE,EAAE,qBAAqB,qBAU5F"}
@@ -11,8 +11,8 @@ const react_native_1 = require("react-native");
11
11
  *
12
12
  * Handles entrance animation when children mount.
13
13
  */
14
- function SwipeableActions({ children, actionsPosition }) {
15
- return (<react_native_1.View style={actionsPosition === 'left' ? styles.actionsLeading : styles.actionsTrailing} pointerEvents={children ? 'auto' : 'none'}>
14
+ function SwipeableActions({ children, actionsPosition, testID }) {
15
+ return (<react_native_1.View style={actionsPosition === 'left' ? styles.actionsLeading : styles.actionsTrailing} pointerEvents={children ? 'auto' : 'none'} testID={testID}>
16
16
  {children}
17
17
  </react_native_1.View>);
18
18
  }
@@ -1 +1 @@
1
- {"version":3,"file":"SwipeableActions.js","sourceRoot":"","sources":["../src/SwipeableActions.tsx"],"names":[],"mappings":";;;;;AAkBA,4CASC;AA3BD,kDAAwC;AACxC,+CAA+C;AAY/C;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAyB;IACnF,OAAO,CACL,CAAC,mBAAI,CACH,KAAK,CAAC,CAAC,eAAe,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CACnF,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAE1C;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,mBAAI,CAAC,CACR,CAAA;AACH,CAAC;AAED,MAAM,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IAC/B,eAAe,EAAE;QACf,GAAG,yBAAU,CAAC,YAAY;QAC1B,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,UAAU;KAC3B;IACD,cAAc,EAAE;QACd,GAAG,yBAAU,CAAC,YAAY;QAC1B,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,YAAY;KAC7B;CACF,CAAC,CAAA"}
1
+ {"version":3,"file":"SwipeableActions.js","sourceRoot":"","sources":["../src/SwipeableActions.tsx"],"names":[],"mappings":";;;;;AAoBA,4CAUC;AA9BD,kDAAwC;AACxC,+CAA+C;AAc/C;;;;GAIG;AACH,SAAgB,gBAAgB,CAAC,EAAE,QAAQ,EAAE,eAAe,EAAE,MAAM,EAAyB;IAC3F,OAAO,CACL,CAAC,mBAAI,CACH,KAAK,CAAC,CAAC,eAAe,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,cAAc,CAAC,CAAC,CAAC,MAAM,CAAC,eAAe,CAAC,CACnF,aAAa,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAC1C,MAAM,CAAC,CAAC,MAAM,CAAC,CAEf;MAAA,CAAC,QAAQ,CACX;IAAA,EAAE,mBAAI,CAAC,CACR,CAAA;AACH,CAAC;AAED,MAAM,MAAM,GAAG,yBAAU,CAAC,MAAM,CAAC;IAC/B,eAAe,EAAE;QACf,GAAG,yBAAU,CAAC,YAAY;QAC1B,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,UAAU;KAC3B;IACD,cAAc,EAAE;QACd,GAAG,yBAAU,CAAC,YAAY;QAC1B,aAAa,EAAE,KAAK;QACpB,cAAc,EAAE,YAAY;KAC7B;CACF,CAAC,CAAA"}
@@ -3,6 +3,7 @@ interface SwipeableNativeModule {
3
3
  openByKey: (key: string) => void;
4
4
  closeByKey: (key: string, animated?: boolean) => void;
5
5
  closeAll: (animated?: boolean) => void;
6
+ isOpenByKey: (key: string) => boolean;
6
7
  }
7
8
  export declare const SwipeableModule: SwipeableNativeModule;
8
9
  export declare const SwipeableView: import("react").ForwardRefExoticComponent<SwipeableViewProps & import("react").RefAttributes<NativeSwipeableRef>>;
@@ -1 +1 @@
1
- {"version":3,"file":"SwipeableView.d.ts","sourceRoot":"","sources":["../src/SwipeableView.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAK1E,UAAU,qBAAqB;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrD,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;CACvC;AAED,eAAO,MAAM,eAAe,uBAA0D,CAAA;AAEtF,eAAO,MAAM,aAAa,mHAIzB,CAAA"}
1
+ {"version":3,"file":"SwipeableView.d.ts","sourceRoot":"","sources":["../src/SwipeableView.tsx"],"names":[],"mappings":"AAEA,OAAO,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAK1E,UAAU,qBAAqB;IAC7B,SAAS,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,CAAA;IAChC,UAAU,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACrD,QAAQ,EAAE,CAAC,QAAQ,CAAC,EAAE,OAAO,KAAK,IAAI,CAAA;IACtC,WAAW,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAA;CACtC;AAED,eAAO,MAAM,eAAe,uBAA0D,CAAA;AAEtF,eAAO,MAAM,aAAa,mHAIzB,CAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"SwipeableView.js","sourceRoot":"","sources":["../src/SwipeableView.tsx"],"names":[],"mappings":";;;AAAA,iCAAiE;AACjE,yDAAiF;AAGjF,MAAM,UAAU,GAAG,IAAA,4CAAwB,EAAqB,WAAW,CAAC,CAAA;AAS/D,QAAA,eAAe,GAAG,IAAA,uCAAmB,EAAwB,WAAW,CAAC,CAAA;AAEzE,QAAA,aAAa,GAAG,IAAA,kBAAU,EACrC,SAAS,aAAa,CAAC,KAAK,EAAE,GAAG;IAC/B,OAAO,IAAA,qBAAa,EAAC,UAAU,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,EAAuC,CAAC,CAAA;AAC1F,CAAC,CACF,CAAA;AAED,qBAAa,CAAC,WAAW,GAAG,eAAe,CAAA"}
1
+ {"version":3,"file":"SwipeableView.js","sourceRoot":"","sources":["../src/SwipeableView.tsx"],"names":[],"mappings":";;;AAAA,iCAAiE;AACjE,yDAAiF;AAGjF,MAAM,UAAU,GAAG,IAAA,4CAAwB,EAAqB,WAAW,CAAC,CAAA;AAU/D,QAAA,eAAe,GAAG,IAAA,uCAAmB,EAAwB,WAAW,CAAC,CAAA;AAEzE,QAAA,aAAa,GAAG,IAAA,kBAAU,EACrC,SAAS,aAAa,CAAC,KAAK,EAAE,GAAG;IAC/B,OAAO,IAAA,qBAAa,EAAC,UAAU,EAAE,EAAE,GAAG,KAAK,EAAE,GAAG,EAAuC,CAAC,CAAA;AAC1F,CAAC,CACF,CAAA;AAED,qBAAa,CAAC,WAAW,GAAG,eAAe,CAAA"}
@@ -8,4 +8,5 @@ export declare function setGlobalErrorHandler(handler: SwipeableErrorHandler | u
8
8
  export declare function openByKey(key: string): void;
9
9
  export declare function closeByKey(key: string, animated?: boolean): void;
10
10
  export declare function closeAll(animated?: boolean): void;
11
+ export declare function isOpenByKey(key: string): boolean;
11
12
  //# sourceMappingURL=nativeModuleUtils.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"nativeModuleUtils.d.ts","sourceRoot":"","sources":["../src/nativeModuleUtils.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,KAAK,EAAE,iBAAiB,EACxB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,KACT,IAAI,CAAA;AAIT,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,CAEtF;AAgBD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAM3C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAMhE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAMjD"}
1
+ {"version":3,"file":"nativeModuleUtils.d.ts","sourceRoot":"","sources":["../src/nativeModuleUtils.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAA;IACZ,OAAO,EAAE,MAAM,CAAA;IACf,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B;AAED,MAAM,MAAM,qBAAqB,GAAG,CAClC,KAAK,EAAE,iBAAiB,EACxB,SAAS,EAAE,MAAM,EACjB,GAAG,CAAC,EAAE,MAAM,KACT,IAAI,CAAA;AAIT,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,qBAAqB,GAAG,SAAS,GAAG,IAAI,CAEtF;AAgBD,wBAAgB,SAAS,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,CAM3C;AAED,wBAAgB,UAAU,CAAC,GAAG,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAMhE;AAED,wBAAgB,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,GAAG,IAAI,CAMjD;AAED,wBAAgB,WAAW,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAMhD"}
@@ -4,6 +4,7 @@ exports.setGlobalErrorHandler = setGlobalErrorHandler;
4
4
  exports.openByKey = openByKey;
5
5
  exports.closeByKey = closeByKey;
6
6
  exports.closeAll = closeAll;
7
+ exports.isOpenByKey = isOpenByKey;
7
8
  const SwipeableView_1 = require("./SwipeableView");
8
9
  let globalErrorHandler;
9
10
  function setGlobalErrorHandler(handler) {
@@ -46,4 +47,12 @@ function closeAll(animated) {
46
47
  handleNativeError(error, 'closeAll');
47
48
  }
48
49
  }
50
+ function isOpenByKey(key) {
51
+ try {
52
+ return SwipeableView_1.SwipeableModule.isOpenByKey(key);
53
+ }
54
+ catch {
55
+ return false;
56
+ }
57
+ }
49
58
  //# sourceMappingURL=nativeModuleUtils.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"nativeModuleUtils.js","sourceRoot":"","sources":["../src/nativeModuleUtils.ts"],"names":[],"mappings":";;AAgBA,sDAEC;AAgBD,8BAMC;AAED,gCAMC;AAED,4BAMC;AAxDD,mDAAiD;AAcjD,IAAI,kBAAqD,CAAA;AAEzD,SAAgB,qBAAqB,CAAC,OAA0C;IAC9E,kBAAkB,GAAG,OAAO,CAAA;AAC9B,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,SAAiB,EAAE,GAAY;IACxE,MAAM,WAAW,GAAsB;QACrC,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/D,GAAG,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpF,CAAA;IAED,IAAI,kBAAkB,EAAE,CAAC;QACvB,kBAAkB,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,OAAO,CAAC,KAAK,CAAC,eAAe,SAAS,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACxE,CAAC;AACH,CAAC;AAED,SAAgB,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,+BAAe,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC,CAAA;IAC5C,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CAAC,GAAW,EAAE,QAAkB;IACxD,IAAI,CAAC;QACH,+BAAe,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC;AAED,SAAgB,QAAQ,CAAC,QAAkB;IACzC,IAAI,CAAC;QACH,+BAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IACtC,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"nativeModuleUtils.js","sourceRoot":"","sources":["../src/nativeModuleUtils.ts"],"names":[],"mappings":";;AAgBA,sDAEC;AAgBD,8BAMC;AAED,gCAMC;AAED,4BAMC;AAED,kCAMC;AAhED,mDAAiD;AAcjD,IAAI,kBAAqD,CAAA;AAEzD,SAAgB,qBAAqB,CAAC,OAA0C;IAC9E,kBAAkB,GAAG,OAAO,CAAA;AAC9B,CAAC;AAED,SAAS,iBAAiB,CAAC,KAAc,EAAE,SAAiB,EAAE,GAAY;IACxE,MAAM,WAAW,GAAsB;QACrC,IAAI,EAAE,wBAAwB;QAC9B,OAAO,EAAE,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC;QAC/D,GAAG,CAAC,KAAK,YAAY,KAAK,IAAI,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,gBAAgB,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;KACpF,CAAA;IAED,IAAI,kBAAkB,EAAE,CAAC;QACvB,kBAAkB,CAAC,WAAW,EAAE,SAAS,EAAE,GAAG,CAAC,CAAA;IACjD,CAAC;SAAM,IAAI,OAAO,EAAE,CAAC;QACnB,OAAO,CAAC,KAAK,CAAC,eAAe,SAAS,UAAU,EAAE,WAAW,CAAC,OAAO,CAAC,CAAA;IACxE,CAAC;AACH,CAAC;AAED,SAAgB,SAAS,CAAC,GAAW;IACnC,IAAI,CAAC;QACH,+BAAe,CAAC,SAAS,CAAC,GAAG,CAAC,CAAA;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,WAAW,EAAE,GAAG,CAAC,CAAA;IAC5C,CAAC;AACH,CAAC;AAED,SAAgB,UAAU,CAAC,GAAW,EAAE,QAAkB;IACxD,IAAI,CAAC;QACH,+BAAe,CAAC,UAAU,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAA;IAC3C,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,YAAY,EAAE,GAAG,CAAC,CAAA;IAC7C,CAAC;AACH,CAAC;AAED,SAAgB,QAAQ,CAAC,QAAkB;IACzC,IAAI,CAAC;QACH,+BAAe,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAA;IACpC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,iBAAiB,CAAC,KAAK,EAAE,UAAU,CAAC,CAAA;IACtC,CAAC;AACH,CAAC;AAED,SAAgB,WAAW,CAAC,GAAW;IACrC,IAAI,CAAC;QACH,OAAO,+BAAe,CAAC,WAAW,CAAC,GAAG,CAAC,CAAA;IACzC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,KAAK,CAAA;IACd,CAAC;AACH,CAAC"}
@@ -17,6 +17,10 @@ public class SwipeableModule: Module {
17
17
  SwipeableView.closeAll(animated: animated ?? true)
18
18
  }
19
19
 
20
+ Function("isOpenByKey") { (key: String) -> Bool in
21
+ SwipeableView.getOpenState(for: key)
22
+ }
23
+
20
24
  View(SwipeableView.self) {
21
25
  // Props
22
26
  Prop("actionsWidth") { (view: SwipeableView, width: CGFloat) in
@@ -31,18 +31,53 @@ public class SwipeableView: ExpoView {
31
31
  return typeName.contains("SurfaceTouchHandler") || typeName.contains("RCTTouchHandler")
32
32
  }
33
33
 
34
+ /// Check if a gesture recognizer belongs to react-native-gesture-handler.
35
+ /// RNGH uses custom recognizer classes prefixed with "RNBetter" (e.g. RNBetterPanGestureRecognizer)
36
+ /// and "RNManualActivation" for manual activation recognizers.
37
+ private static func isRNGHGestureHandler(_ recognizer: UIGestureRecognizer) -> Bool {
38
+ let typeName = String(describing: type(of: recognizer))
39
+ return typeName.hasPrefix("RNBetter") || typeName.hasPrefix("RNManualActivation")
40
+ }
41
+
42
+ /// Walk from a hit-tested view up to (but not including) self, checking for RNGH recognizers.
43
+ /// Used to prevent our pan gesture from beginning when the touch landed on a RNGH-managed view.
44
+ private func hasRNGHGesture(from view: UIView) -> Bool {
45
+ var current: UIView? = view
46
+ while let v = current, v !== self {
47
+ for recognizer in v.gestureRecognizers ?? [] {
48
+ if Self.isRNGHGestureHandler(recognizer) {
49
+ return true
50
+ }
51
+ }
52
+ current = v.superview
53
+ }
54
+ return false
55
+ }
56
+
34
57
  // MARK: - Thread-Safe Static Registry
35
58
 
36
59
  private static let registryQueue = DispatchQueue(label: "com.swipeable.registry", qos: .userInteractive)
60
+ private static let maxCacheSize = 1000
37
61
  private static var _openStateCache: [String: Bool] = [:]
62
+ private static var _openStateCacheOrder: [String] = []
38
63
  private static var _viewRegistry: [String: WeakRef<SwipeableView>] = [:]
39
64
 
40
- private static func getOpenState(for key: String) -> Bool {
65
+ static func getOpenState(for key: String) -> Bool {
41
66
  registryQueue.sync { _openStateCache[key] ?? false }
42
67
  }
43
68
 
44
69
  private static func setOpenState(for key: String, isOpen: Bool) {
45
- registryQueue.async(flags: .barrier) { _openStateCache[key] = isOpen }
70
+ registryQueue.async(flags: .barrier) {
71
+ if _openStateCache[key] == nil {
72
+ _openStateCacheOrder.append(key)
73
+ }
74
+ _openStateCache[key] = isOpen
75
+ // Evict oldest entries when cache exceeds max size
76
+ while _openStateCacheOrder.count > maxCacheSize {
77
+ let oldest = _openStateCacheOrder.removeFirst()
78
+ _openStateCache.removeValue(forKey: oldest)
79
+ }
80
+ }
46
81
  }
47
82
 
48
83
  private static func getView(for key: String) -> SwipeableView? {
@@ -278,17 +313,17 @@ public class SwipeableView: ExpoView {
278
313
  currentAnimator = nil
279
314
  isAnimating = false
280
315
 
281
- // Reset layer properties to prevent corruption
282
- contentView?.transform = .identity
283
- contentView?.layer.zPosition = 0
284
- actionsView?.transform = .identity
285
- actionsView?.layer.zPosition = 0
286
-
287
- // Unregister from static registry and clear cached state
316
+ // Unregister from static registry (re-registered in didMoveToWindow)
288
317
  if let key = recyclingKey {
289
318
  Self.unregisterView(for: key)
290
- Self.clearOpenState(for: key)
291
319
  }
320
+
321
+ // Note: transforms and zPositions are NOT reset here.
322
+ // Fabric may remove and re-insert views during list reorder (DELETE+INSERT),
323
+ // which triggers this callback. Resetting transforms would cause open
324
+ // swipeables to visually close even though native state (isOpen,
325
+ // currentTranslation) remains correct. The recyclingKey setter handles
326
+ // resetting transforms when the view is actually recycled for a new item.
292
327
  }
293
328
  super.willMove(toSuperview: newSuperview)
294
329
  }
@@ -316,6 +351,28 @@ public class SwipeableView: ExpoView {
316
351
  panGesture.view?.removeGestureRecognizer(panGesture)
317
352
  addGestureRecognizer(panGesture)
318
353
  }
354
+
355
+ // Restore visual state after Fabric reorder (DELETE+INSERT cycle).
356
+ // Fabric may reset child transforms during prop reconciliation. Re-apply
357
+ // on next runloop tick to ensure we run after Fabric finishes mutations.
358
+ if isOpen && currentTranslation != 0 {
359
+ DispatchQueue.main.async { [weak self] in
360
+ guard let self = self,
361
+ self.isOpen,
362
+ self.currentTranslation != 0,
363
+ !self.isDragging,
364
+ !self.isAnimating else { return }
365
+ self.contentView?.transform = CGAffineTransform(translationX: self.currentTranslation, y: 0)
366
+ self.contentView?.layer.zPosition = 1
367
+ self.actionsView?.layer.zPosition = -1
368
+ self.updateActionsTransform()
369
+ }
370
+
371
+ // Restart autoClose timer if it was cancelled during detach
372
+ if autoClose {
373
+ scheduleAutoClose()
374
+ }
375
+ }
319
376
  }
320
377
 
321
378
  public override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? {
@@ -506,6 +563,17 @@ public class SwipeableView: ExpoView {
506
563
  if currentTranslation != 0 {
507
564
  contentView?.transform = CGAffineTransform(translationX: currentTranslation, y: 0)
508
565
  updateActionsTransform()
566
+
567
+ // Fabric may reset child transforms during prop reconciliation after a list
568
+ // reorder. The async dispatch ensures we run after Fabric finishes mutations.
569
+ DispatchQueue.main.async { [weak self] in
570
+ guard let self = self,
571
+ self.currentTranslation != 0,
572
+ !self.isDragging,
573
+ !self.isAnimating else { return }
574
+ self.contentView?.transform = CGAffineTransform(translationX: self.currentTranslation, y: 0)
575
+ self.updateActionsTransform()
576
+ }
509
577
  }
510
578
  }
511
579
 
@@ -862,6 +930,13 @@ extension SwipeableView: UIGestureRecognizerDelegate {
862
930
  let shouldBegin = isOpen || (isLeading ? velocity.x > 0 : velocity.x < 0)
863
931
 
864
932
  if shouldBegin {
933
+ // Prevent our pan from starting when the touch is on a RNGH-managed view.
934
+ // Delegate-based conflict resolution is broken with RNGH (it overrides delegates),
935
+ // so we use hit-test prevention instead.
936
+ let touchPoint = pan.location(in: self)
937
+ if let hitView = super.hitTest(touchPoint, with: nil), hasRNGHGesture(from: hitView) {
938
+ return false
939
+ }
865
940
  cancelFabricTouches()
866
941
  }
867
942
 
@@ -892,11 +967,4 @@ extension SwipeableView: UIGestureRecognizerDelegate {
892
967
 
893
968
  return false
894
969
  }
895
-
896
- public func gestureRecognizer(
897
- _ gestureRecognizer: UIGestureRecognizer,
898
- shouldRequireFailureOf otherGestureRecognizer: UIGestureRecognizer
899
- ) -> Bool {
900
- false
901
- }
902
970
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-swipeable-actions",
3
- "version": "0.1.4",
3
+ "version": "0.1.6",
4
4
  "description": "High-performance native swipeable row component for React Native using Expo Modules",
5
5
  "main": "src/index.ts",
6
6
  "types": "build/index.d.ts",
package/src/Swipeable.tsx CHANGED
@@ -1,4 +1,4 @@
1
- import { forwardRef, useImperativeHandle, useRef, useCallback, useState } from 'react'
1
+ import { forwardRef, useImperativeHandle, useRef, useCallback, useState, useEffect } from 'react'
2
2
  import { StyleSheet, View, Platform } from 'react-native'
3
3
 
4
4
  import { SwipeableView } from './SwipeableView'
@@ -11,7 +11,7 @@ import {
11
11
  NativeSwipeableRef
12
12
  } from './Swipeable.types'
13
13
  import { SWIPE_DEFAULTS } from './constants'
14
- import { openByKey, closeByKey, closeAll } from './nativeModuleUtils'
14
+ import { openByKey, closeByKey, closeAll, isOpenByKey } from './nativeModuleUtils'
15
15
 
16
16
  function normalizeRecyclingKey(key: string | number | undefined): string | undefined {
17
17
  if (key == null) return undefined
@@ -47,15 +47,29 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(function Swipeabl
47
47
  ref
48
48
  ) {
49
49
  const nativeRef = useRef<NativeSwipeableRef>(null)
50
- const [hasActionsRendered, setHasActionsRendered] = useState(false)
51
- const swipeStartedRef = useRef(false)
52
50
 
53
- // Normalize and sync recyclingKey to ref for use in useImperativeHandle
54
- // (set during render to ensure it's always current before callbacks fire)
51
+ // Normalize recyclingKey before hooks that depend on it
55
52
  const normalizedKey = normalizeRecyclingKey(recyclingKey)
56
53
  const recyclingKeyRef = useRef<string | undefined>(undefined)
57
54
  recyclingKeyRef.current = normalizedKey
58
55
 
56
+ // Initialize from native cache: if this recyclingKey was open, render actions immediately.
57
+ // This handles FlatList remounting components during data reorder - React state is lost
58
+ // but native cache preserves the open state.
59
+ const [hasActionsRendered, setHasActionsRendered] = useState(() => {
60
+ return normalizedKey != null && isOpenByKey(normalizedKey)
61
+ })
62
+ const swipeStartedRef = useRef(false)
63
+
64
+ // When recyclingKey changes (FlatList reorder/recycle), sync hasActionsRendered
65
+ // with the native cached open state. FlatList reuses component instances without
66
+ // remounting, so useState initializer doesn't re-run.
67
+ useEffect(() => {
68
+ if (!normalizedKey) return
69
+ const cachedOpen = isOpenByKey(normalizedKey)
70
+ setHasActionsRendered(cachedOpen)
71
+ }, [normalizedKey])
72
+
59
73
  // Development-time validation
60
74
  if (__DEV__) {
61
75
  if (autoClose && recyclingKey == null) {
@@ -144,7 +158,12 @@ const Swipeable = forwardRef<SwipeableMethods, SwipeableProps>(function Swipeabl
144
158
  onSwipeEnd={handleSwipeEnd}
145
159
  >
146
160
  {hasActionsRendered && (
147
- <SwipeableActions actionsPosition={actionsPosition}>{actions}</SwipeableActions>
161
+ <SwipeableActions
162
+ actionsPosition={actionsPosition}
163
+ {...(testID ? { testID: `${testID}-actions` } : {})}
164
+ >
165
+ {actions}
166
+ </SwipeableActions>
148
167
  )}
149
168
 
150
169
  <View style={styles.content}>{children}</View>
@@ -9,6 +9,8 @@ export interface SwipeableActionsProps {
9
9
  children: ReactNode
10
10
  /** Position of actions: 'left' or 'right' */
11
11
  actionsPosition: 'left' | 'right'
12
+ /** Test ID for e2e testing */
13
+ testID?: string
12
14
  }
13
15
 
14
16
  /**
@@ -16,11 +18,12 @@ export interface SwipeableActionsProps {
16
18
  *
17
19
  * Handles entrance animation when children mount.
18
20
  */
19
- export function SwipeableActions({ children, actionsPosition }: SwipeableActionsProps) {
21
+ export function SwipeableActions({ children, actionsPosition, testID }: SwipeableActionsProps) {
20
22
  return (
21
23
  <View
22
24
  style={actionsPosition === 'left' ? styles.actionsLeading : styles.actionsTrailing}
23
25
  pointerEvents={children ? 'auto' : 'none'}
26
+ testID={testID}
24
27
  >
25
28
  {children}
26
29
  </View>
@@ -9,6 +9,7 @@ interface SwipeableNativeModule {
9
9
  openByKey: (key: string) => void
10
10
  closeByKey: (key: string, animated?: boolean) => void
11
11
  closeAll: (animated?: boolean) => void
12
+ isOpenByKey: (key: string) => boolean
12
13
  }
13
14
 
14
15
  export const SwipeableModule = requireNativeModule<SwipeableNativeModule>('Swipeable')
@@ -55,3 +55,11 @@ export function closeAll(animated?: boolean): void {
55
55
  handleNativeError(error, 'closeAll')
56
56
  }
57
57
  }
58
+
59
+ export function isOpenByKey(key: string): boolean {
60
+ try {
61
+ return SwipeableModule.isOpenByKey(key)
62
+ } catch {
63
+ return false
64
+ }
65
+ }