@lodev09/react-native-true-sheet 4.0.0-beta.19 → 4.0.0-beta.20

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.
Files changed (45) hide show
  1. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +6 -2
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +43 -0
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +17 -10
  4. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetBottomSheetView.kt +8 -1
  5. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +16 -0
  6. package/ios/TrueSheetView.mm +20 -7
  7. package/ios/TrueSheetViewController.h +1 -3
  8. package/ios/TrueSheetViewController.mm +98 -22
  9. package/ios/core/TrueSheetBlurView.h +0 -2
  10. package/ios/core/TrueSheetBlurView.mm +3 -36
  11. package/ios/core/TrueSheetDetentCalculator.h +1 -0
  12. package/ios/core/TrueSheetDetentCalculator.mm +18 -0
  13. package/lib/module/TrueSheet.js +16 -5
  14. package/lib/module/TrueSheet.js.map +1 -1
  15. package/lib/module/TrueSheet.web.js +39 -6
  16. package/lib/module/TrueSheet.web.js.map +1 -1
  17. package/lib/module/TrueSheetBackground.js +15 -0
  18. package/lib/module/TrueSheetBackground.js.map +1 -0
  19. package/lib/module/detentBackgrounds.js +21 -0
  20. package/lib/module/detentBackgrounds.js.map +1 -0
  21. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +6 -9
  22. package/lib/module/mocks/index.js +8 -2
  23. package/lib/module/mocks/index.js.map +1 -1
  24. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  25. package/lib/typescript/src/TrueSheet.types.d.ts +30 -39
  26. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  27. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  28. package/lib/typescript/src/TrueSheetBackground.d.ts +3 -0
  29. package/lib/typescript/src/TrueSheetBackground.d.ts.map +1 -0
  30. package/lib/typescript/src/detentBackgrounds.d.ts +10 -0
  31. package/lib/typescript/src/detentBackgrounds.d.ts.map +1 -0
  32. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +4 -6
  33. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  34. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  35. package/lib/typescript/src/navigation/types.d.ts +1 -1
  36. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  37. package/package.json +1 -1
  38. package/src/TrueSheet.tsx +13 -4
  39. package/src/TrueSheet.types.ts +30 -41
  40. package/src/TrueSheet.web.tsx +42 -6
  41. package/src/TrueSheetBackground.tsx +17 -0
  42. package/src/detentBackgrounds.ts +24 -0
  43. package/src/fabric/TrueSheetViewNativeComponent.ts +6 -9
  44. package/src/mocks/index.ts +10 -2
  45. package/src/navigation/types.ts +3 -2
@@ -204,6 +204,7 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
204
204
  setupScrollable()
205
205
 
206
206
  if (viewController.isPresented) {
207
+ viewController.setupBackground()
207
208
  viewController.sheetView?.setupBackground()
208
209
  viewController.sheetView?.setupGrabber()
209
210
  viewController.sheetView?.updateGravity()
@@ -260,9 +261,12 @@ class TrueSheetView(private val reactContext: ThemedReactContext) :
260
261
  viewController.sheetCornerRadius = radius
261
262
  }
262
263
 
264
+ fun setDetentBackgrounds(colors: List<Int?>) {
265
+ viewController.detentBackgrounds = colors
266
+ }
267
+
263
268
  fun setSheetBackgroundColor(color: Int?) {
264
- if (viewController.sheetBackgroundColor == color) return
265
- viewController.sheetBackgroundColor = color
269
+ viewController.backgroundColor = color
266
270
  }
267
271
 
268
272
  fun setDismissible(dismissible: Boolean) {
@@ -1,5 +1,6 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
+ import android.animation.ValueAnimator
3
4
  import android.annotation.SuppressLint
4
5
  import android.graphics.Canvas
5
6
  import android.os.Build
@@ -227,7 +228,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
227
228
  override var grabber: Boolean = true
228
229
  override var grabberOptions: GrabberOptions? = null
229
230
  override var accessibilityOptions: AccessibilityOptions? = null
231
+ var backgroundColor: Int? = null
232
+ var detentBackgrounds: List<Int?> = emptyList()
233
+ private var displayedBackgroundIndex = 0
234
+ private var backgroundAnimator: ValueAnimator? = null
235
+ private var targetBackgroundColor: Int? = null
230
236
  override var sheetBackgroundColor: Int? = null
237
+ private set
231
238
  var insetAdjustment: TrueSheetInsetAdjustment = TrueSheetInsetAdjustment.AUTOMATIC
232
239
 
233
240
  var scrollableOptions: ScrollableOptions? = null
@@ -425,6 +432,9 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
425
432
 
426
433
  private fun cleanupSheet() {
427
434
  cleanupKeyboardObserver()
435
+ backgroundAnimator?.cancel()
436
+ backgroundAnimator = null
437
+ targetBackgroundColor = null
428
438
  sheetView?.animate()?.cancel()
429
439
 
430
440
  // Cleanup dim views
@@ -846,6 +856,8 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
846
856
  setupDimmedBackground()
847
857
  setupKeyboardObserver()
848
858
 
859
+ displayedBackgroundIndex = detentIndex
860
+ setupBackground(animated = false)
849
861
  sheet.setupBackground()
850
862
  sheet.setupElevation()
851
863
  sheet.setupGrabber()
@@ -1099,6 +1111,30 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1099
1111
  // MARK: - Dimmed Background
1100
1112
  // =============================================================================
1101
1113
 
1114
+ fun setupBackground(animated: Boolean = true) {
1115
+ val sheet = sheetView ?: return
1116
+ val color = detentBackgrounds.getOrNull(displayedBackgroundIndex)
1117
+ ?: backgroundColor ?: sheet.getDefaultBackgroundColor()
1118
+ if (animated && color == targetBackgroundColor) return
1119
+
1120
+ backgroundAnimator?.cancel()
1121
+ targetBackgroundColor = color
1122
+ val startColor = sheetBackgroundColor ?: color
1123
+ if (!animated || !isPresented || startColor == color) {
1124
+ sheetBackgroundColor = color
1125
+ sheet.updateBackgroundColor(color)
1126
+ return
1127
+ }
1128
+ backgroundAnimator = ValueAnimator.ofArgb(startColor, color).apply {
1129
+ duration = 200L
1130
+ addUpdateListener {
1131
+ sheetBackgroundColor = it.animatedValue as Int
1132
+ sheet.updateBackgroundColor(it.animatedValue as Int)
1133
+ }
1134
+ start()
1135
+ }
1136
+ }
1137
+
1102
1138
  fun setupDimmedBackground() {
1103
1139
  val coordinator = this.coordinatorLayout ?: run {
1104
1140
  RNLog.e(reactContext, "TrueSheet: coordinatorLayout is null in setupDimmedBackground")
@@ -1427,6 +1463,13 @@ class TrueSheetViewController(private val reactContext: ThemedReactContext) :
1427
1463
  }
1428
1464
 
1429
1465
  private fun emitChangePositionDelegate(currentTop: Int, realtime: Boolean = true) {
1466
+ if (isPresented && !isPresentAnimating && !isBeingDismissed) {
1467
+ val index = detentCalculator.getBackgroundIndex(currentTop, displayedBackgroundIndex)
1468
+ if (index != displayedBackgroundIndex) {
1469
+ displayedBackgroundIndex = index
1470
+ setupBackground()
1471
+ }
1472
+ }
1430
1473
  // Dedupe emissions for same position
1431
1474
  if (currentTop == lastEmittedPositionPx) return
1432
1475
 
@@ -1,8 +1,10 @@
1
1
  package com.lodev09.truesheet
2
2
 
3
3
  import android.view.WindowManager
4
+ import com.facebook.react.bridge.ColorPropConverter
4
5
  import com.facebook.react.bridge.ReadableArray
5
6
  import com.facebook.react.bridge.ReadableMap
7
+ import com.facebook.react.bridge.ReadableType
6
8
  import com.facebook.react.module.annotations.ReactModule
7
9
  import com.facebook.react.uimanager.PixelUtil.dpToPx
8
10
  import com.facebook.react.uimanager.PointerEvents
@@ -114,6 +116,21 @@ class TrueSheetViewManager :
114
116
  view.setDetents(detents)
115
117
  }
116
118
 
119
+ @ReactProp(name = "detentBackgrounds")
120
+ override fun setDetentBackgrounds(view: TrueSheetView, value: ReadableArray?) {
121
+ view.setDetentBackgrounds(
122
+ (0 until (value?.size() ?: 0)).map { index ->
123
+ val entry = value?.getMap(index)
124
+ if (entry != null && entry.hasKey("color") && !entry.isNull("color")) {
125
+ val color = if (entry.getType("color") == ReadableType.Map) entry.getMap("color") else entry.getDouble("color")
126
+ ColorPropConverter.getColor(color, view.context)
127
+ } else {
128
+ null
129
+ }
130
+ }
131
+ )
132
+ }
133
+
117
134
  @ReactProp(name = "backgroundColor", customType = "Color")
118
135
  override fun setBackgroundColor(view: TrueSheetView, color: Int?) {
119
136
  view.setSheetBackgroundColor(color)
@@ -228,16 +245,6 @@ class TrueSheetViewManager :
228
245
  // iOS-specific prop - no-op on Android
229
246
  }
230
247
 
231
- @ReactProp(name = "blurOptions")
232
- override fun setBlurOptions(view: TrueSheetView, options: ReadableMap?) {
233
- // iOS-specific prop - no-op on Android
234
- }
235
-
236
- @ReactProp(name = "glass", defaultBoolean = true)
237
- override fun setGlass(view: TrueSheetView, glass: Boolean) {
238
- // iOS-specific prop - no-op on Android
239
- }
240
-
241
248
  @ReactProp(name = "insetAdjustment")
242
249
  override fun setInsetAdjustment(view: TrueSheetView, insetAdjustment: String?) {
243
250
  view.setInsetAdjustment(insetAdjustment ?: "automatic")
@@ -179,7 +179,14 @@ class TrueSheetBottomSheetView(private val reactContext: ThemedReactContext) : F
179
179
  clipToOutline = true
180
180
  }
181
181
 
182
- private fun getDefaultBackgroundColor(): Int {
182
+ fun updateBackgroundColor(color: Int) {
183
+ (background as? ShapeDrawable)?.let {
184
+ it.paint.color = color
185
+ it.invalidateSelf()
186
+ }
187
+ }
188
+
189
+ fun getDefaultBackgroundColor(): Int {
183
190
  val isNight = (
184
191
  reactContext.resources.configuration.uiMode and
185
192
  Configuration.UI_MODE_NIGHT_MASK
@@ -137,6 +137,22 @@ class TrueSheetDetentCalculator(private val reactContext: ThemedReactContext) {
137
137
  return maxContentHeight?.let { minOf(height, it, maxAllowedHeight) } ?: minOf(height, maxAllowedHeight)
138
138
  }
139
139
 
140
+ fun getBackgroundIndex(sheetTop: Int, displayedIndex: Int): Int {
141
+ if (detents.isEmpty()) return -1
142
+
143
+ val height = getVisibleSheetHeight(sheetTop)
144
+ var nextIndex = displayedIndex.coerceIn(0, detents.size - 1)
145
+ var distance = kotlin.math.abs(height - getVisibleSheetHeight(getSheetTopForDetentIndex(nextIndex)))
146
+ for (index in detents.indices) {
147
+ val nextDistance = kotlin.math.abs(height - getVisibleSheetHeight(getSheetTopForDetentIndex(index)))
148
+ if (nextDistance < distance) {
149
+ nextIndex = index
150
+ distance = nextDistance
151
+ }
152
+ }
153
+ return nextIndex
154
+ }
155
+
140
156
  /**
141
157
  * Get the expected sheet top position for a detent index.
142
158
  */
@@ -236,16 +236,29 @@ using namespace facebook::react;
236
236
  // Background color
237
237
  _controller.backgroundColor = RCTUIColorFromSharedColor(newProps.backgroundColor);
238
238
 
239
+ NSMutableArray<NSDictionary *> *detentBackgrounds = [NSMutableArray new];
240
+ ContextContainer contextContainer;
241
+ PropsParserContext parserContext{-1, contextContainer};
242
+ for (const auto &entry : newProps.detentBackgrounds) {
243
+ NSMutableDictionary *background = [NSMutableDictionary new];
244
+ UIColor *color = RCTUIColorFromSharedColor(entry.color);
245
+ if (color) {
246
+ background[@"color"] = color;
247
+ }
248
+ if (!entry.blur.empty()) {
249
+ auto blur = TrueSheetViewBackgroundBlur::None;
250
+ fromRawValue(parserContext, RawValue(entry.blur), blur);
251
+ background[@"blur"] = @(static_cast<int>(blur));
252
+ }
253
+ [detentBackgrounds addObject:background];
254
+ }
255
+ if (![_controller.detentBackgrounds isEqualToArray:detentBackgrounds]) {
256
+ _controller.detentBackgrounds = detentBackgrounds;
257
+ }
258
+
239
259
  // Blur tint
240
260
  _controller.backgroundBlur = newProps.backgroundBlur;
241
261
 
242
- // Blur options
243
- const auto &blurOpts = newProps.blurOptions;
244
- _controller.blurIntensity = blurOpts.intensity >= 0 ? @(blurOpts.intensity) : nil;
245
- _controller.blurInteraction = blurOpts.interaction;
246
-
247
- _controller.glass = newProps.glass;
248
-
249
262
  // Corner radius
250
263
  _controller.cornerRadius = newProps.cornerRadius < 0 ? nil : @(newProps.cornerRadius);
251
264
 
@@ -55,6 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
55
55
 
56
56
  @property (nonatomic, weak, nullable) id<TrueSheetViewControllerDelegate> delegate;
57
57
  @property (nonatomic, strong) NSArray<NSNumber *> *detents;
58
+ @property (nonatomic, copy) NSArray<NSDictionary *> *detentBackgrounds;
58
59
  @property (nonatomic, strong, nullable) NSNumber *maxContentHeight;
59
60
  @property (nonatomic, strong, nullable) NSNumber *maxContentWidth;
60
61
  @property (nonatomic, strong, nullable) NSNumber *contentHeight;
@@ -79,9 +80,6 @@ NS_ASSUME_NONNULL_BEGIN
79
80
  @property (nonatomic, assign) BOOL dimmed;
80
81
  @property (nonatomic, strong, nullable) NSNumber *dimmedDetentIndex;
81
82
  @property (nonatomic, assign) facebook::react::TrueSheetViewBackgroundBlur backgroundBlur;
82
- @property (nonatomic, strong, nullable) NSNumber *blurIntensity;
83
- @property (nonatomic, assign) BOOL blurInteraction;
84
- @property (nonatomic, assign) BOOL glass;
85
83
  @property (nonatomic, assign) facebook::react::TrueSheetViewPresentation presentation;
86
84
  @property (nonatomic, assign) facebook::react::TrueSheetViewPlacement placement;
87
85
  @property (nonatomic, assign) facebook::react::TrueSheetViewInsetAdjustment insetAdjustment;
@@ -74,6 +74,8 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
74
74
  CGFloat _autoDetentHeight;
75
75
  CGFloat _peekDetentHeight;
76
76
  NSInteger _pendingDetentIndex;
77
+ NSInteger _displayedBackgroundIndex;
78
+ BOOL _usesBackgroundEffects;
77
79
 
78
80
  // Present/dismiss tracking: a display link scoped to the transition
79
81
  // coordinator — started alongside the transition, stopped in its completion.
@@ -112,6 +114,8 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
112
114
  - (instancetype)init {
113
115
  if (self = [super initWithNibName:nil bundle:nil]) {
114
116
  _detents = @[ @0.5, @1 ];
117
+ _detentBackgrounds = @[];
118
+ _displayedBackgroundIndex = 0;
115
119
  _contentHeight = @(0);
116
120
  _headerHeight = @(0);
117
121
  _footerHeight = @(0);
@@ -134,7 +138,6 @@ static char TrueSheetAccessibilityWindowPreviousElementsKey;
134
138
  _activeDetentIndex = -1;
135
139
  _pendingDetentIndex = -1;
136
140
 
137
- _blurInteraction = YES;
138
141
  _insetAdjustment = TrueSheetViewInsetAdjustment::Automatic;
139
142
  _detentCalculator = [[TrueSheetDetentCalculator alloc] init];
140
143
  _detentCalculator.delegate = self;
@@ -370,6 +373,9 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
370
373
  }
371
374
 
372
375
  - (BOOL)isDesignCompatibilityMode {
376
+ if (@available(iOS 27.0, *)) {
377
+ return NO;
378
+ }
373
379
  if (@available(iOS 26.0, *)) {
374
380
  NSNumber *value = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UIDesignRequiresCompatibility"];
375
381
  return value.boolValue;
@@ -1119,6 +1125,20 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1119
1125
  }
1120
1126
  }
1121
1127
 
1128
+ if (self.isPresented && !self.isBeingDismissed && !_isWillDismissEmitted && !_usesBackgroundEffects) {
1129
+ NSInteger index = [_detentCalculator backgroundIndexForPosition:position displayedIndex:_displayedBackgroundIndex];
1130
+ if (index != _displayedBackgroundIndex) {
1131
+ _displayedBackgroundIndex = index;
1132
+ [UIView animateWithDuration:0.2
1133
+ delay:0
1134
+ options:UIViewAnimationOptionBeginFromCurrentState | UIViewAnimationOptionAllowUserInteraction
1135
+ animations:^{
1136
+ [self setupBackground];
1137
+ }
1138
+ completion:nil];
1139
+ }
1140
+ }
1141
+
1122
1142
  TrueSheetPositionState state = {
1123
1143
  .position = position,
1124
1144
  .detent = [self interpolatedDetentForPosition:position],
@@ -1227,6 +1247,20 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1227
1247
  }
1228
1248
 
1229
1249
  [_detentCalculator setDetentCount:self.detents.count];
1250
+ _usesBackgroundEffects = [self canUseBackgroundEffects:detents];
1251
+ #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
1252
+ if (@available(iOS 26.1, *)) {
1253
+ if (_usesBackgroundEffects) {
1254
+ for (NSInteger index = 0; index < detents.count; index++) {
1255
+ NSDictionary *entry = [self backgroundForIndex:index];
1256
+ auto blur = entry[@"blur"] ? static_cast<TrueSheetViewBackgroundBlur>([entry[@"blur"] intValue])
1257
+ : TrueSheetViewBackgroundBlur::None;
1258
+ detents[index].backgroundEffect = [self backgroundEffectForColor:entry[@"color"] blur:blur];
1259
+ }
1260
+ }
1261
+ }
1262
+ #endif
1263
+
1230
1264
  sheet.detents = detents;
1231
1265
 
1232
1266
  if (self.dimmed && [self.dimmedDetentIndex integerValue] == 0) {
@@ -1394,6 +1428,8 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1394
1428
 
1395
1429
  - (void)setupActiveDetentWithIndex:(NSInteger)index {
1396
1430
  _activeDetentIndex = index;
1431
+ _displayedBackgroundIndex = index;
1432
+ [self setupBackground];
1397
1433
  [self applyActiveDetent];
1398
1434
  }
1399
1435
 
@@ -1407,34 +1443,72 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1407
1443
  [self applyActiveDetent];
1408
1444
  }
1409
1445
 
1410
- - (void)setupBackground {
1411
- auto effectiveBackgroundBlur = self.backgroundBlur;
1412
- if (@available(iOS 26.0, *)) {
1413
- // iOS 26+ has default liquid glass effect
1414
- } else if (effectiveBackgroundBlur == TrueSheetViewBackgroundBlur::None && !self.backgroundColor) {
1415
- effectiveBackgroundBlur = TrueSheetViewBackgroundBlur::SystemMaterial;
1446
+ - (BOOL)canUseBackgroundEffects:(NSArray<UISheetPresentationControllerDetent *> *)detents {
1447
+ #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
1448
+ if (@available(iOS 26.1, *)) {
1449
+ // Some 26.1 builds omit setters declared in the SDK.
1450
+ if (self.isDesignCompatibilityMode || ![self.sheet respondsToSelector:@selector(setBackgroundEffect:)]) {
1451
+ return NO;
1452
+ }
1453
+ for (UISheetPresentationControllerDetent *detent in detents) {
1454
+ if (![detent respondsToSelector:@selector(setBackgroundEffect:)]) {
1455
+ return NO;
1456
+ }
1457
+ }
1458
+ return YES;
1416
1459
  }
1460
+ #endif
1461
+ return NO;
1462
+ }
1417
1463
 
1418
- BOOL hasBlur = effectiveBackgroundBlur != TrueSheetViewBackgroundBlur::None;
1464
+ - (NSDictionary *)backgroundForIndex:(NSInteger)index {
1465
+ return index >= 0 && index < self.detentBackgrounds.count ? self.detentBackgrounds[index] : nil;
1466
+ }
1419
1467
 
1420
- _blurView.backgroundBlur = hasBlur ? effectiveBackgroundBlur : TrueSheetViewBackgroundBlur::None;
1421
- _blurView.blurIntensity = self.blurIntensity;
1422
- _blurView.blurInteraction = self.blurInteraction;
1423
- [_blurView applyBlurEffect];
1468
+ #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
1469
+ - (UIVisualEffect *)backgroundEffectForColor:(UIColor *)color
1470
+ blur:(TrueSheetViewBackgroundBlur)blur API_AVAILABLE(ios(26.1)) {
1471
+ if (blur != TrueSheetViewBackgroundBlur::None) {
1472
+ return [UIBlurEffect effectWithStyle:[BlurUtil blurEffectStyleFromEnum:blur]];
1473
+ }
1474
+ return color ? [UIColorEffect effectWithColor:color] : nil;
1475
+ }
1476
+ #endif
1424
1477
 
1478
+ - (void)setupBackground {
1425
1479
  #if RNTS_IPHONE_OS_VERSION_AVAILABLE(26_1) && !TARGET_OS_MACCATALYST
1426
1480
  if (@available(iOS 26.1, *)) {
1427
- if (!self.isDesignCompatibilityMode) {
1428
- if (!self.glass || hasBlur) {
1429
- self.sheet.backgroundEffect = [UIColorEffect effectWithColor:[UIColor clearColor]];
1430
- } else {
1431
- self.sheet.backgroundEffect = nil;
1432
- }
1481
+ if (_usesBackgroundEffects) {
1482
+ self.sheet.backgroundEffect = [self backgroundEffectForColor:self.backgroundColor blur:self.backgroundBlur];
1483
+ self.view.backgroundColor = nil;
1484
+ _blurView.hidden = YES;
1485
+ _blurView.backgroundBlur = TrueSheetViewBackgroundBlur::None;
1486
+ [_blurView applyBlurEffect];
1487
+ return;
1433
1488
  }
1434
1489
  }
1435
1490
  #endif
1436
1491
 
1437
- self.view.backgroundColor = self.backgroundColor;
1492
+ NSDictionary *entry = [self backgroundForIndex:_displayedBackgroundIndex];
1493
+ BOOL hasEntry = entry[@"color"] || entry[@"blur"];
1494
+ UIColor *color = hasEntry ? entry[@"color"] : self.backgroundColor;
1495
+ auto effectiveBackgroundBlur =
1496
+ hasEntry ? (entry[@"blur"] ? static_cast<TrueSheetViewBackgroundBlur>([entry[@"blur"] intValue])
1497
+ : TrueSheetViewBackgroundBlur::None)
1498
+ : self.backgroundBlur;
1499
+ if (@available(iOS 26.0, *)) {
1500
+ // iOS 26+ has default liquid glass effect
1501
+ } else if (effectiveBackgroundBlur == TrueSheetViewBackgroundBlur::None && !color) {
1502
+ effectiveBackgroundBlur = TrueSheetViewBackgroundBlur::SystemMaterial;
1503
+ }
1504
+
1505
+ BOOL hasBlur = effectiveBackgroundBlur != TrueSheetViewBackgroundBlur::None;
1506
+
1507
+ _blurView.hidden = NO;
1508
+ _blurView.backgroundBlur = effectiveBackgroundBlur;
1509
+ [_blurView applyBlurEffect];
1510
+
1511
+ self.view.backgroundColor = hasBlur ? nil : color;
1438
1512
  }
1439
1513
 
1440
1514
  - (void)setupGrabber {
@@ -1554,9 +1628,11 @@ static BOOL TrueSheetIsPhoneIdiom(void) {
1554
1628
  #if RNTS_IPHONE_OS_VERSION_AVAILABLE(27_0)
1555
1629
  // iOS 27 exposes native placement. `sourceView` must stay nil since it overrides `preferredPlacement`.
1556
1630
  if (@available(iOS 27.0, *)) {
1557
- self.sheetPresentationController.sourceView = nil;
1558
- self.sheetPresentationController.preferredPlacement = [self preferredPlacement];
1559
- return;
1631
+ if ([self.sheetPresentationController respondsToSelector:@selector(setPreferredPlacement:)]) {
1632
+ self.sheetPresentationController.sourceView = nil;
1633
+ self.sheetPresentationController.preferredPlacement = [self preferredPlacement];
1634
+ return;
1635
+ }
1560
1636
  }
1561
1637
  #endif
1562
1638
 
@@ -14,8 +14,6 @@ NS_ASSUME_NONNULL_BEGIN
14
14
  @interface TrueSheetBlurView : UIVisualEffectView
15
15
 
16
16
  @property (nonatomic, assign) facebook::react::TrueSheetViewBackgroundBlur backgroundBlur;
17
- @property (nonatomic, strong, nullable) NSNumber *blurIntensity;
18
- @property (nonatomic, assign) BOOL blurInteraction;
19
17
 
20
18
  /// Adds the blur view to a parent view with proper constraints
21
19
  - (void)addToView:(UIView *)parentView;
@@ -13,15 +13,13 @@
13
13
 
14
14
  using namespace facebook::react;
15
15
 
16
- @implementation TrueSheetBlurView {
17
- UIViewPropertyAnimator *_blurAnimator;
18
- }
16
+ @implementation TrueSheetBlurView
19
17
 
20
18
  #pragma mark - Initialization
21
19
 
22
20
  - (instancetype)init {
23
21
  if (self = [super init]) {
24
- _blurInteraction = YES;
22
+ self.userInteractionEnabled = NO;
25
23
  }
26
24
  return self;
27
25
  }
@@ -38,45 +36,14 @@ using namespace facebook::react;
38
36
  [parentView insertSubview:self atIndex:0];
39
37
  }
40
38
 
41
- - (void)clearAnimator {
42
- if (_blurAnimator) {
43
- [_blurAnimator stopAnimation:YES];
44
- _blurAnimator = nil;
45
- }
46
- }
47
-
48
39
  - (void)applyBlurEffect {
49
- self.userInteractionEnabled = self.blurInteraction;
50
-
51
40
  if (self.backgroundBlur == TrueSheetViewBackgroundBlur::None) {
52
- [self clearAnimator];
53
41
  self.effect = nil;
54
42
  return;
55
43
  }
56
44
 
57
45
  UIBlurEffectStyle style = [BlurUtil blurEffectStyleFromEnum:self.backgroundBlur];
58
- UIBlurEffect *blurEffect = [UIBlurEffect effectWithStyle:style];
59
-
60
- CGFloat intensity =
61
- (self.blurIntensity && [self.blurIntensity floatValue] >= 0) ? [self.blurIntensity floatValue] / 100.0 : 1.0;
62
-
63
- if (intensity >= 1.0) {
64
- [self clearAnimator];
65
- self.effect = blurEffect;
66
- return;
67
- }
68
-
69
- if (!_blurAnimator) {
70
- __weak __typeof(self) weakSelf = self;
71
- _blurAnimator = [[UIViewPropertyAnimator alloc] initWithDuration:1.0
72
- curve:UIViewAnimationCurveLinear
73
- animations:^{
74
- weakSelf.effect = blurEffect;
75
- }];
76
- }
77
-
78
- _blurAnimator.pausesOnCompletion = YES;
79
- _blurAnimator.fractionComplete = intensity;
46
+ self.effect = [UIBlurEffect effectWithStyle:style];
80
47
  }
81
48
 
82
49
  @end
@@ -46,6 +46,7 @@ NS_ASSUME_NONNULL_BEGIN
46
46
  For peek (-2) detents, calculates based on header + footer height.
47
47
  */
48
48
  - (CGFloat)detentValueForIndex:(NSInteger)index;
49
+ - (NSInteger)backgroundIndexForPosition:(CGFloat)position displayedIndex:(NSInteger)displayedIndex;
49
50
 
50
51
  /**
51
52
  Returns the height for auto (-1) detents: content + header + footer height.
@@ -98,6 +98,24 @@
98
98
  return 0;
99
99
  }
100
100
 
101
+ - (NSInteger)backgroundIndexForPosition:(CGFloat)position displayedIndex:(NSInteger)displayedIndex {
102
+ NSInteger count = self.delegate.detents.count;
103
+ if (count == 0)
104
+ return -1;
105
+
106
+ CGFloat height = self.delegate.screenHeight - position;
107
+ NSInteger nextIndex = MAX(0, MIN(displayedIndex, count - 1));
108
+ CGFloat distance = fabs(height - [self resolvedHeightForIndex:nextIndex]);
109
+ for (NSInteger index = 0; index < count; index++) {
110
+ CGFloat nextDistance = fabs(height - [self resolvedHeightForIndex:index]);
111
+ if (nextDistance < distance) {
112
+ nextIndex = index;
113
+ distance = nextDistance;
114
+ }
115
+ }
116
+ return nextIndex;
117
+ }
118
+
101
119
  - (BOOL)findSegmentForPosition:(CGFloat)position outIndex:(NSInteger *)outIndex outProgress:(CGFloat *)outProgress {
102
120
  NSArray<NSNumber *> *detents = self.delegate.detents;
103
121
  NSInteger count = detents.count;
@@ -7,6 +7,8 @@ import TrueSheetContentViewNativeComponent from './fabric/TrueSheetContentViewNa
7
7
  import TrueSheetHeaderViewNativeComponent from './fabric/TrueSheetHeaderViewNativeComponent';
8
8
  import TrueSheetFooterViewNativeComponent from './fabric/TrueSheetFooterViewNativeComponent';
9
9
  import TrueSheetModule from "./specs/NativeTrueSheetModule.js";
10
+ import { TrueSheetBackground } from "./TrueSheetBackground.js";
11
+ import { normalizeDetentBackground } from "./detentBackgrounds.js";
10
12
  import { Platform, StyleSheet, BackHandler, RootTagContext, findNodeHandle, processColor } from 'react-native';
11
13
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
12
14
  // RN has no public API for hosting the element inspector inside a presented view;
@@ -439,6 +441,9 @@ export class TrueSheet extends PureComponent {
439
441
  const {
440
442
  detents = [0.5, 1],
441
443
  backgroundColor,
444
+ detentBackgrounds,
445
+ background,
446
+ backgroundStyle,
442
447
  dismissible = true,
443
448
  draggable = true,
444
449
  grabber = true,
@@ -449,8 +454,6 @@ export class TrueSheet extends PureComponent {
449
454
  initialDetentAnimated = true,
450
455
  dimmedDetentIndex,
451
456
  backgroundBlur,
452
- blurOptions,
453
- glass = true,
454
457
  cornerRadius,
455
458
  maxContentHeight,
456
459
  maxContentWidth,
@@ -497,7 +500,10 @@ export class TrueSheet extends PureComponent {
497
500
  };
498
501
  }
499
502
  const sheetContent = /*#__PURE__*/_jsxs(_Fragment, {
500
- children: [header && /*#__PURE__*/_jsx(TrueSheetHeaderViewNativeComponent, {
503
+ children: [/*#__PURE__*/_jsx(TrueSheetBackground, {
504
+ background: background,
505
+ backgroundStyle: backgroundStyle
506
+ }), header && /*#__PURE__*/_jsx(TrueSheetHeaderViewNativeComponent, {
501
507
  style: [styles.header, headerOptions?.position === 'absolute' && styles.absoluteHeader, headerStyle],
502
508
  children: /*#__PURE__*/isValidElement(header) ? header : /*#__PURE__*/createElement(header)
503
509
  }), /*#__PURE__*/_jsx(TrueSheetContentViewNativeComponent, {
@@ -517,9 +523,14 @@ export class TrueSheet extends PureComponent {
517
523
  style: styles.sheetView,
518
524
  detents: resolvedDetents,
519
525
  backgroundBlur: backgroundBlur,
520
- blurOptions: blurOptions,
521
- glass: glass,
522
526
  backgroundColor: backgroundColor,
527
+ detentBackgrounds: detentBackgrounds && Array.from(detentBackgrounds.slice(0, 3), entry => {
528
+ const normalized = normalizeDetentBackground(entry);
529
+ return normalized ? {
530
+ ...normalized,
531
+ color: processColor(normalized.color)
532
+ } : {};
533
+ }),
523
534
  cornerRadius: cornerRadius,
524
535
  grabber: grabber,
525
536
  grabberOptions: this.resolvedGrabberOptions,