@swmansion/react-native-bottom-sheet 0.10.0-next.1 → 0.10.0-next.2

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.
@@ -292,12 +292,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
292
292
  return
293
293
  }
294
294
 
295
+ val previousMaxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
295
296
  detentSpecs = resolvedDetents
296
297
  if (width > 0 && height > 0 && detentSpecs.isNotEmpty()) {
297
298
  layoutSheetContainer(width, height)
298
299
 
299
300
  if (hasLaidOut && !isPanning) {
300
301
  targetIndex = targetIndex.coerceIn(0, detentSpecs.size - 1)
302
+ val newMaxHeight = detentSpecs.maxOfOrNull { it.height } ?: resolvedMaxDetentHeight()
303
+ val targetTy = translationY(targetIndex)
301
304
  if (activeAnimation != null && isTargetingClosedDetent) {
302
305
  suppressScrimForClosingTarget = true
303
306
  hideScrim()
@@ -309,17 +312,26 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
309
312
  activeAnimation = null
310
313
  activeAnimationEmitsSettle = false
311
314
  stopChoreographer()
312
- sheetContainer.translationY =
313
- currentTy.coerceIn(0f, detentSpecs.maxOfOrNull { it.height } ?: currentTy)
315
+ // Re-anchor the in-flight position to the new container height so the
316
+ // sheet surface keeps the same on-screen height across the resize.
317
+ val visibleHeight = previousMaxHeight - currentTy
318
+ sheetContainer.translationY = (newMaxHeight - visibleHeight).coerceIn(0f, newMaxHeight)
314
319
  emitPosition()
315
320
  snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = shouldEmitSettle)
316
321
  } else {
317
- val targetTy = translationY(targetIndex)
318
- val currentTy = sheetContainer.translationY
319
- if (abs(targetTy - currentTy) <= 0.5f) {
322
+ val currentVisibleHeight = previousMaxHeight - sheetContainer.translationY
323
+ val targetHeight = detentSpecs.getOrNull(targetIndex)?.height ?: 0f
324
+ if (targetHeight <= currentVisibleHeight + 0.5f) {
325
+ // Content shrank (or is unchanged): snap immediately. Animating here
326
+ // would expose blank space below the shrunken content.
320
327
  sheetContainer.translationY = targetTy
321
328
  emitPosition()
322
329
  } else {
330
+ // Content grew: re-anchor at the current visible height, then animate
331
+ // up to the taller detent.
332
+ sheetContainer.translationY =
333
+ (newMaxHeight - currentVisibleHeight).coerceIn(0f, newMaxHeight)
334
+ emitPosition()
323
335
  snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = false)
324
336
  }
325
337
  }
@@ -60,7 +60,8 @@ public final class RNSBottomSheetHostingView: UIView {
60
60
  private var hasLaidOut = false
61
61
  private var isPanning = false
62
62
  private var isContentInteractionDisabled = false
63
- private weak var contentHeightMarker: UIView?
63
+ private var contentHeightMarker: UIView?
64
+ private static var markerObservationContext = 0
64
65
 
65
66
  override public init(frame: CGRect) {
66
67
  super.init(frame: frame)
@@ -237,7 +238,7 @@ public final class RNSBottomSheetHostingView: UIView {
237
238
  hasLaidOut = false
238
239
  isPanning = false
239
240
  setContentInteractionEnabled(true)
240
- contentHeightMarker = nil
241
+ stopObservingContentHeightMarker()
241
242
  sheetContainer.transform = .identity
242
243
  scrimView.alpha = 0
243
244
  scrimView.isHidden = true
@@ -576,6 +577,7 @@ public final class RNSBottomSheetHostingView: UIView {
576
577
  return
577
578
  }
578
579
 
580
+ let previousMaxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
579
581
  detentSpecs = resolvedDetents
580
582
 
581
583
  guard bounds.width > 0, bounds.height > 0, !detentSpecs.isEmpty else {
@@ -584,6 +586,8 @@ public final class RNSBottomSheetHostingView: UIView {
584
586
 
585
587
  if hasLaidOut, !isPanning {
586
588
  targetIndex = max(0, min(detentSpecs.count - 1, targetIndex))
589
+ let newMaxHeight = maximumResolvedDetentHeight ?? resolvedMaxDetentHeight
590
+ let targetTy = translationY(for: targetIndex)
587
591
 
588
592
  if let animator = activeAnimator {
589
593
  stopDisplayLink()
@@ -592,19 +596,27 @@ public final class RNSBottomSheetHostingView: UIView {
592
596
  animator.stopAnimation(true)
593
597
  activeAnimator = nil
594
598
  activeAnimatorEmitsSettle = false
595
- sheetContainer.transform = CGAffineTransform(
596
- translationX: 0,
597
- y: min(max(visualTy, 0), maximumResolvedDetentHeight ?? visualTy)
598
- )
599
+ // Re-anchor the in-flight position to the new container height so the
600
+ // sheet surface keeps the same on-screen height across the resize.
601
+ let visibleHeight = previousMaxHeight - visualTy
602
+ let reanchoredTy = min(max(newMaxHeight - visibleHeight, 0), newMaxHeight)
603
+ sheetContainer.transform = CGAffineTransform(translationX: 0, y: reanchoredTy)
599
604
  emitPosition()
600
605
  snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
601
606
  } else {
602
- let targetTy = translationY(for: targetIndex)
603
- let currentTy = currentTranslationY
604
- if abs(targetTy - currentTy) <= 0.5 {
607
+ let currentVisibleHeight = previousMaxHeight - currentTranslationY
608
+ let targetHeight = detent(at: targetIndex).height
609
+ if targetHeight <= currentVisibleHeight + 0.5 {
610
+ // Content shrank (or is unchanged): snap immediately. Animating here
611
+ // would expose blank space below the shrunken content.
605
612
  sheetContainer.transform = CGAffineTransform(translationX: 0, y: targetTy)
606
613
  emitPosition()
607
614
  } else {
615
+ // Content grew: re-anchor at the current visible height, then animate
616
+ // up to the taller detent.
617
+ let startTy = min(max(newMaxHeight - currentVisibleHeight, 0), newMaxHeight)
618
+ sheetContainer.transform = CGAffineTransform(translationX: 0, y: startTy)
619
+ emitPosition()
608
620
  snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: false)
609
621
  }
610
622
  }
@@ -612,7 +624,50 @@ public final class RNSBottomSheetHostingView: UIView {
612
624
  }
613
625
 
614
626
  private func refreshContentHeightMarker() {
615
- contentHeightMarker = findContentHeightMarker()
627
+ let marker = findContentHeightMarker()
628
+ guard marker !== contentHeightMarker else { return }
629
+ stopObservingContentHeightMarker()
630
+ contentHeightMarker = marker
631
+ if let marker {
632
+ // The marker's frame is updated by React Native when content above it
633
+ // resizes; observe its layer so we can re-resolve detents immediately
634
+ // instead of waiting for an unrelated layout pass. This is the iOS
635
+ // counterpart to Android's OnLayoutChangeListener on the marker.
636
+ marker.layer.addObserver(
637
+ self, forKeyPath: "position", options: [], context: &Self.markerObservationContext
638
+ )
639
+ marker.layer.addObserver(
640
+ self, forKeyPath: "bounds", options: [], context: &Self.markerObservationContext
641
+ )
642
+ }
643
+ }
644
+
645
+ private func stopObservingContentHeightMarker() {
646
+ guard let marker = contentHeightMarker else { return }
647
+ marker.layer.removeObserver(
648
+ self, forKeyPath: "position", context: &Self.markerObservationContext
649
+ )
650
+ marker.layer.removeObserver(
651
+ self, forKeyPath: "bounds", context: &Self.markerObservationContext
652
+ )
653
+ contentHeightMarker = nil
654
+ }
655
+
656
+ override public func observeValue(
657
+ forKeyPath keyPath: String?,
658
+ of object: Any?,
659
+ change: [NSKeyValueChangeKey: Any]?,
660
+ context: UnsafeMutableRawPointer?
661
+ ) {
662
+ if context == &Self.markerObservationContext {
663
+ refreshDetentsFromLayout()
664
+ } else {
665
+ super.observeValue(forKeyPath: keyPath, of: object, change: change, context: context)
666
+ }
667
+ }
668
+
669
+ deinit {
670
+ stopObservingContentHeightMarker()
616
671
  }
617
672
 
618
673
  private func findContentHeightMarker() -> UIView? {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/react-native-bottom-sheet",
3
- "version": "0.10.0-next.1",
3
+ "version": "0.10.0-next.2",
4
4
  "description": "Provides bottom-sheet components for React Native.",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",