@swmansion/react-native-bottom-sheet 0.8.0 → 0.8.1

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/README.md CHANGED
@@ -42,7 +42,8 @@ React Native.
42
42
  The library provides two components: `BottomSheet` (inline) and
43
43
  `ModalBottomSheet` (modal). Both render their children as the sheet content
44
44
  (including any background) and are controlled via `detents`, `index`,
45
- and `onIndexChange`.
45
+ and `onIndexChange`. Use `onSettle` for
46
+ post‍-‍snap observability.
46
47
 
47
48
  ### Inline
48
49
 
@@ -108,13 +109,18 @@ its color:
108
109
  ### Detents and index
109
110
 
110
111
  Detents are the points to which the sheet snaps. Each detent is either a number
111
- (a fixed height in pixels) or `'max'` (the sheet’s content height, capped by the
112
- available screen height). The default detents are `[0, 'max']`.
112
+ (a fixed height in pixels) or `'content'` (the sheet’s content height, capped by
113
+ the available screen height). The default detents are `[0, 'content']`.
113
114
 
114
115
  The `index` prop is a zero‍-‍based index into the `detents` array.
115
- `onIndexChange` is called when the sheet snaps to a different detent after
116
- a drag. You can also control the sheet externally by updating the
117
- index state.
116
+ `onIndexChange` and `onSettle` have different responsibilities:
117
+
118
+ - `onIndexChange` is for user‍-‍triggered snaps. Treat it as the signal
119
+ to update your controlled `index` state.
120
+ - `onSettle` fires when the sheet finishes snapping to a detent, regardless of
121
+ whether that snap was user‍-‍triggered or programmatic. Use it for
122
+ observability or side effects (analytics, reacting to collapse, etc.), not for
123
+ updating the controlled `index` state.
118
124
 
119
125
  ```tsx
120
126
  const [index, setIndex] = useState(0);
@@ -122,9 +128,12 @@ const [index, setIndex] = useState(0);
122
128
 
123
129
  ```tsx
124
130
  <BottomSheet // Or `ModalBottomSheet`.
125
- detents={[0, 300, 'max']} // Collapsed, 300 px, content height.
131
+ detents={[0, 300, 'content']} // Collapsed, 300 px, content height.
126
132
  index={index}
127
- onIndexChange={setIndex}
133
+ onIndexChange={setIndex} // Keep controlled state in sync.
134
+ onSettle={(nextIndex) => {
135
+ if (nextIndex === 0) console.log('Sheet collapsed.');
136
+ }}
128
137
  >
129
138
  {/* ... */}
130
139
  </BottomSheet>
@@ -138,9 +147,12 @@ drag snapping but can still be targeted via `index`&nbsp;updates.
138
147
 
139
148
  ```tsx
140
149
  <BottomSheet
141
- detents={[0, programmatic(300), 'max']}
150
+ detents={[0, programmatic(300), 'content']}
142
151
  index={index}
143
152
  onIndexChange={setIndex}
153
+ onSettle={(nextIndex) => {
154
+ console.log(`Settled at ${nextIndex}.`);
155
+ }}
144
156
  >
145
157
  {/* ... */}
146
158
  </BottomSheet>
@@ -56,6 +56,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
56
56
  private val sheetContainer = FrameLayout(context)
57
57
  private val scrimPaint = Paint(Paint.ANTI_ALIAS_FLAG)
58
58
  private var activeAnimation: SpringAnimation? = null
59
+ private var activeAnimationEmitsSettle = false
59
60
  private var velocityTracker: VelocityTracker? = null
60
61
  private var choreographerCallback: Choreographer.FrameCallback? = null
61
62
  private val density = context.resources.displayMetrics.density
@@ -188,13 +189,15 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
188
189
  targetIndex = targetIndex.coerceIn(0, detentSpecs.size - 1)
189
190
  if (activeAnimation != null) {
190
191
  val currentTy = sheetContainer.translationY
192
+ val shouldEmitSettle = activeAnimationEmitsSettle
191
193
  activeAnimation?.cancel()
192
194
  activeAnimation = null
195
+ activeAnimationEmitsSettle = false
193
196
  stopChoreographer()
194
197
  sheetContainer.translationY =
195
198
  currentTy.coerceIn(0f, detentSpecs.lastOrNull()?.height ?: currentTy)
196
199
  emitPosition()
197
- snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = false)
200
+ snapToIndex(targetIndex, 0f, emitIndexChange = false, emitSettle = shouldEmitSettle)
198
201
  } else {
199
202
  sheetContainer.translationY = translationY(targetIndex)
200
203
  emitPosition()
@@ -308,6 +311,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
308
311
  targetIndex = index
309
312
 
310
313
  val targetTy = translationY(index)
314
+ activeAnimationEmitsSettle = emitSettle
311
315
  activeAnimation?.cancel()
312
316
 
313
317
  val spring = SpringAnimation(sheetContainer, DynamicAnimation.TRANSLATION_Y, targetTy).apply {
@@ -323,6 +327,7 @@ class BottomSheetView(context: Context) : ReactViewGroup(context) {
323
327
  stopChoreographer()
324
328
  emitPosition()
325
329
  activeAnimation = null
330
+ activeAnimationEmitsSettle = false
326
331
  updateInteractionState()
327
332
  if (emitIndexChange) listener?.onIndexChange(index)
328
333
  if (emitSettle) listener?.onSettle(index)
@@ -35,6 +35,7 @@ public final class RNSBottomSheetHostingView: UIView {
35
35
  private let scrimView = UIControl()
36
36
  private var panGesture: UIPanGestureRecognizer!
37
37
  private var activeAnimator: UIViewPropertyAnimator?
38
+ private var activeAnimatorEmitsSettle = false
38
39
  private var displayLink: CADisplayLink?
39
40
  private var pendingIndex: Int?
40
41
  private var hasLaidOut = false
@@ -173,14 +174,16 @@ public final class RNSBottomSheetHostingView: UIView {
173
174
  if let animator = activeAnimator {
174
175
  stopDisplayLink()
175
176
  let visualTy = sheetContainer.layer.presentation()?.affineTransform().ty ?? sheetContainer.transform.ty
177
+ let shouldEmitSettle = activeAnimatorEmitsSettle
176
178
  animator.stopAnimation(true)
177
179
  activeAnimator = nil
180
+ activeAnimatorEmitsSettle = false
178
181
  sheetContainer.transform = CGAffineTransform(
179
182
  translationX: 0,
180
183
  y: min(max(visualTy, 0), detentSpecs.last?.height ?? visualTy)
181
184
  )
182
185
  emitPosition()
183
- snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: false)
186
+ snapToIndex(targetIndex, velocity: 0, emitIndexChange: false, emitSettle: shouldEmitSettle)
184
187
  } else {
185
188
  sheetContainer.transform = CGAffineTransform(translationX: 0, y: translationY(for: targetIndex))
186
189
  emitPosition()
@@ -337,6 +340,7 @@ public final class RNSBottomSheetHostingView: UIView {
337
340
  let clampedRatio = min(max(velocityRatio, -5), 5)
338
341
  let initialVelocity = CGVector(dx: 0, dy: clampedRatio)
339
342
 
343
+ activeAnimatorEmitsSettle = emitSettle
340
344
  activeAnimator?.stopAnimation(true)
341
345
 
342
346
  let spring = UISpringTimingParameters(dampingRatio: 1.0, initialVelocity: initialVelocity)
@@ -350,6 +354,7 @@ public final class RNSBottomSheetHostingView: UIView {
350
354
  self.stopDisplayLink()
351
355
  self.emitPosition()
352
356
  self.activeAnimator = nil
357
+ self.activeAnimatorEmitsSettle = false
353
358
  self.setContentInteractionEnabled(true)
354
359
  self.updateInteractionState()
355
360
  if emitIndexChange {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@swmansion/react-native-bottom-sheet",
3
- "version": "0.8.0",
3
+ "version": "0.8.1",
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",