react-native-swipeable-actions 0.1.9 → 0.1.10
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.
|
@@ -105,8 +105,12 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
|
|
|
105
105
|
var actionsWidth: Float = 0f
|
|
106
106
|
set(value) {
|
|
107
107
|
val validated = maxOf(0f, value)
|
|
108
|
-
|
|
108
|
+
val previous = field
|
|
109
|
+
if (previous == validated) return
|
|
109
110
|
field = validated
|
|
111
|
+
if (shouldSyncOpenLayoutForPropChange(previous, validated)) {
|
|
112
|
+
syncOpenStateToCurrentWidth()
|
|
113
|
+
}
|
|
110
114
|
requestLayout()
|
|
111
115
|
}
|
|
112
116
|
|
|
@@ -766,6 +770,28 @@ class SwipeableView(context: Context, appContext: AppContext) : ExpoView(context
|
|
|
766
770
|
actions.translationX = actionsTranslateX
|
|
767
771
|
}
|
|
768
772
|
|
|
773
|
+
private fun shouldSyncOpenLayoutForPropChange(oldValue: Float, newValue: Float): Boolean {
|
|
774
|
+
if (oldValue == newValue) return false
|
|
775
|
+
if (!isOpen || isDragging || isAnimating || isGestureActivated) return false
|
|
776
|
+
return newValue > 0f
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
private fun syncOpenStateToCurrentWidth() {
|
|
780
|
+
val targetX = if (isLeading) actionsWidthPx else -actionsWidthPx
|
|
781
|
+
currentTranslation = targetX
|
|
782
|
+
contentView?.translationX = targetX
|
|
783
|
+
|
|
784
|
+
if (actionsView != null) {
|
|
785
|
+
actionsView?.visibility = View.VISIBLE
|
|
786
|
+
actionsView?.translationX = 0f
|
|
787
|
+
} else {
|
|
788
|
+
onSwipeStart(emptyMap())
|
|
789
|
+
}
|
|
790
|
+
|
|
791
|
+
startLayoutGuard()
|
|
792
|
+
emitFinalProgress(1f, targetX)
|
|
793
|
+
}
|
|
794
|
+
|
|
769
795
|
fun open(velocity: Float = 0f) {
|
|
770
796
|
isOpen = true
|
|
771
797
|
isAnimating = true
|
package/ios/SwipeableView.swift
CHANGED
|
@@ -115,6 +115,11 @@ public class SwipeableView: ExpoView {
|
|
|
115
115
|
// MARK: - State
|
|
116
116
|
private var actionsView: UIView?
|
|
117
117
|
private var contentView: UIView?
|
|
118
|
+
// Track React-managed children explicitly via Fabric's mount hooks.
|
|
119
|
+
// Using UIView.subviews is unsafe because UIKit/Expo/RN may insert helper
|
|
120
|
+
// views (clipping wrappers, focus indicators, etc.) that shift indices and
|
|
121
|
+
// cause us to transform the wrong UIView.
|
|
122
|
+
private var reactChildren: [UIView] = []
|
|
118
123
|
private var currentTranslation: CGFloat = 0
|
|
119
124
|
private var isOpen = false
|
|
120
125
|
private var startOffset: CGFloat = 0
|
|
@@ -135,6 +140,9 @@ public class SwipeableView: ExpoView {
|
|
|
135
140
|
actionsWidth = validated
|
|
136
141
|
return
|
|
137
142
|
}
|
|
143
|
+
if shouldSyncOpenLayoutForPropChange(oldValue: oldValue, newValue: validated) {
|
|
144
|
+
syncOpenStateToCurrentWidth()
|
|
145
|
+
}
|
|
138
146
|
setNeedsLayout()
|
|
139
147
|
}
|
|
140
148
|
}
|
|
@@ -312,6 +320,18 @@ public class SwipeableView: ExpoView {
|
|
|
312
320
|
currentAnimator = nil
|
|
313
321
|
isAnimating = false
|
|
314
322
|
|
|
323
|
+
// Reset internal gesture/translation state so a re-attached view
|
|
324
|
+
// (FlashList recycle) does not re-apply a stale currentTranslation
|
|
325
|
+
// in the next layoutSubviews pass.
|
|
326
|
+
currentTranslation = 0
|
|
327
|
+
isOpen = false
|
|
328
|
+
pendingShouldOpen = false
|
|
329
|
+
startOffset = 0
|
|
330
|
+
lastEmittedProgress = -1
|
|
331
|
+
isDragging = false
|
|
332
|
+
isGestureActivated = false
|
|
333
|
+
gestureStartTranslation = 0
|
|
334
|
+
|
|
315
335
|
// Reset layer properties to prevent corruption
|
|
316
336
|
contentView?.transform = .identity
|
|
317
337
|
contentView?.layer.zPosition = 0
|
|
@@ -469,38 +489,83 @@ public class SwipeableView: ExpoView {
|
|
|
469
489
|
actionsView.transform = CGAffineTransform(translationX: actionsTranslateX, y: 0)
|
|
470
490
|
}
|
|
471
491
|
|
|
492
|
+
private func shouldSyncOpenLayoutForPropChange(oldValue: CGFloat, newValue: CGFloat) -> Bool {
|
|
493
|
+
guard oldValue != newValue else { return false }
|
|
494
|
+
guard isOpen, !isDragging, !isAnimating, !isGestureActivated else { return false }
|
|
495
|
+
return newValue > 0
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
private func syncOpenStateToCurrentWidth() {
|
|
499
|
+
let targetX = isLeading ? actionsWidth : -actionsWidth
|
|
500
|
+
currentTranslation = targetX
|
|
501
|
+
contentView?.transform = CGAffineTransform(translationX: targetX, y: 0)
|
|
502
|
+
|
|
503
|
+
if actionsView != nil {
|
|
504
|
+
actionsView?.transform = .identity
|
|
505
|
+
} else {
|
|
506
|
+
onSwipeStart([:])
|
|
507
|
+
}
|
|
508
|
+
|
|
509
|
+
onSwipeProgress(["progress": 1.0, "translationX": targetX])
|
|
510
|
+
}
|
|
511
|
+
|
|
472
512
|
// MARK: - View Lifecycle
|
|
473
513
|
|
|
474
|
-
|
|
475
|
-
|
|
514
|
+
// Track React-managed children only. Fabric guarantees this fires solely
|
|
515
|
+
// for views Fabric mounts (the JSX children) — never for UIKit helper
|
|
516
|
+
// views that UIView.subviews would otherwise include.
|
|
517
|
+
public override func mountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
518
|
+
super.mountChildComponentView(childComponentView, index: index)
|
|
519
|
+
let clampedIndex = min(max(index, 0), reactChildren.count)
|
|
520
|
+
reactChildren.insert(childComponentView, at: clampedIndex)
|
|
521
|
+
refreshChildAssignments()
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
public override func unmountChildComponentView(_ childComponentView: UIView, index: Int) {
|
|
525
|
+
// Fabric recycles UIView instances. Before handing this child back, undo any
|
|
526
|
+
// transform / zPosition we may have applied — otherwise the next React
|
|
527
|
+
// component that receives this recycled UIView inherits our stale state and
|
|
528
|
+
// appears translated.
|
|
529
|
+
childComponentView.transform = .identity
|
|
530
|
+
childComponentView.layer.zPosition = 0
|
|
531
|
+
|
|
532
|
+
if index >= 0 && index < reactChildren.count && reactChildren[index] === childComponentView {
|
|
533
|
+
reactChildren.remove(at: index)
|
|
534
|
+
} else if let found = reactChildren.firstIndex(where: { $0 === childComponentView }) {
|
|
535
|
+
reactChildren.remove(at: found)
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
if childComponentView === contentView { contentView = nil }
|
|
539
|
+
if childComponentView === actionsView { actionsView = nil }
|
|
540
|
+
|
|
541
|
+
super.unmountChildComponentView(childComponentView, index: index)
|
|
542
|
+
refreshChildAssignments()
|
|
543
|
+
}
|
|
476
544
|
|
|
477
|
-
|
|
545
|
+
private func refreshChildAssignments() {
|
|
478
546
|
let wasActionsNil = actionsView == nil
|
|
479
547
|
let wasContentNil = contentView == nil
|
|
480
548
|
|
|
481
|
-
|
|
482
|
-
switch reactSubviews.count {
|
|
549
|
+
switch reactChildren.count {
|
|
483
550
|
case 0:
|
|
484
|
-
// Should not happen - we just added a subview
|
|
485
551
|
contentView = nil
|
|
486
552
|
actionsView = nil
|
|
487
553
|
case 1:
|
|
488
|
-
contentView =
|
|
554
|
+
contentView = reactChildren[0]
|
|
489
555
|
contentView?.layer.zPosition = 1
|
|
490
556
|
actionsView = nil
|
|
491
557
|
case 2:
|
|
492
|
-
actionsView =
|
|
558
|
+
actionsView = reactChildren[0]
|
|
493
559
|
actionsView?.layer.zPosition = -1
|
|
494
|
-
contentView =
|
|
560
|
+
contentView = reactChildren[1]
|
|
495
561
|
contentView?.layer.zPosition = 1
|
|
496
562
|
default:
|
|
497
|
-
// More than 2 children - log warning but use first two
|
|
498
563
|
#if DEBUG
|
|
499
|
-
print("[SwipeableView] Warning: Expected 1-2
|
|
564
|
+
print("[SwipeableView] Warning: Expected 1-2 React children, got \(reactChildren.count)")
|
|
500
565
|
#endif
|
|
501
|
-
actionsView =
|
|
566
|
+
actionsView = reactChildren[0]
|
|
502
567
|
actionsView?.layer.zPosition = -1
|
|
503
|
-
contentView =
|
|
568
|
+
contentView = reactChildren[1]
|
|
504
569
|
contentView?.layer.zPosition = 1
|
|
505
570
|
}
|
|
506
571
|
|
package/package.json
CHANGED