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.
- package/android/src/main/java/expo/modules/swipeable/SwipeableModule.kt +4 -0
- package/android/src/main/java/expo/modules/swipeable/SwipeableView.kt +112 -16
- package/build/Swipeable.d.ts.map +1 -1
- package/build/Swipeable.js +20 -5
- package/build/Swipeable.js.map +1 -1
- package/build/SwipeableActions.d.ts +3 -1
- package/build/SwipeableActions.d.ts.map +1 -1
- package/build/SwipeableActions.js +2 -2
- package/build/SwipeableActions.js.map +1 -1
- package/build/SwipeableView.d.ts +1 -0
- package/build/SwipeableView.d.ts.map +1 -1
- package/build/SwipeableView.js.map +1 -1
- package/build/nativeModuleUtils.d.ts +1 -0
- package/build/nativeModuleUtils.d.ts.map +1 -1
- package/build/nativeModuleUtils.js +9 -0
- package/build/nativeModuleUtils.js.map +1 -1
- package/ios/SwipeableModule.swift +4 -0
- package/ios/SwipeableView.swift +85 -17
- package/package.json +1 -1
- package/src/Swipeable.tsx +26 -7
- package/src/SwipeableActions.tsx +4 -1
- package/src/SwipeableView.tsx +1 -0
- package/src/nativeModuleUtils.ts +8 -0
|
@@ -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
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
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()
|
package/build/Swipeable.d.ts.map
CHANGED
|
@@ -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,
|
|
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"}
|
package/build/Swipeable.js
CHANGED
|
@@ -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
|
-
|
|
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}
|
|
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>);
|
package/build/Swipeable.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"Swipeable.js","sourceRoot":"","sources":["../src/Swipeable.tsx"],"names":[],"mappings":";;AAAA,
|
|
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;
|
|
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":";;;;;
|
|
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"}
|
package/build/SwipeableView.d.ts
CHANGED
|
@@ -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;
|
|
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;
|
|
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;
|
|
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
|
package/ios/SwipeableView.swift
CHANGED
|
@@ -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
|
-
|
|
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) {
|
|
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
|
-
//
|
|
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
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
|
|
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
|
|
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>
|
package/src/SwipeableActions.tsx
CHANGED
|
@@ -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>
|
package/src/SwipeableView.tsx
CHANGED
|
@@ -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')
|
package/src/nativeModuleUtils.ts
CHANGED