@sdcx/bottom-sheet 0.8.0 → 0.10.0

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
@@ -1,6 +1,6 @@
1
1
  # BottomSheet
2
2
 
3
- `BottomSheet` 是一个 React Native 原生 UI 组件。
3
+ [BottomSheet](https://github.com/sdcxtech/react-native-troika/blob/master/packages/bottom-sheet/README.md) 是一个类似于 Android 原生的 [BottomSheetBehavior](https://developer.android.com/reference/com/google/android/material/bottomsheet/BottomSheetBehavior) 组件,我们在 API 设计上也尽量和 Android 原生保持一致。
4
4
 
5
5
  它位于屏幕底部,可拖拽,支持嵌套滚动,可以和可滚动视图(`FlatList`, `FlashList`, `WebView` 等等)一起使用。
6
6
 
@@ -43,7 +43,12 @@ const App = () => {
43
43
  }
44
44
  ```
45
45
 
46
- > :white_check_mark: 当和可滚动视图一起使用时,需要设置 `nestedScrollEnabled` 属性。
46
+ > :exclamation: :exclamation: :exclamation:
47
+ > Android 是基于 [NestedScrolling API](https://developer.android.com/reference/androidx/core/view/NestedScrollingChild) 实现的。
48
+ >
49
+ > <h3>请记得为你的列表开启 `nestedScrollEnabled` 属性。</h3>
50
+ >
51
+ > :exclamation: :exclamation: :exclamation:
47
52
 
48
53
  ## 基本概念和 API
49
54
 
@@ -67,6 +72,8 @@ const App = () => {
67
72
 
68
73
  - `fitToContents`,是指 BottomSheet 在展开时,是否适应内容的高度,默认是 `false`。当和可滚动列表,譬如 ScrollView 一起使用时,请保持默认值。
69
74
 
75
+ - `contentContainerStyle`,用来设置内层视图的样式。
76
+
70
77
  ### 回调
71
78
 
72
79
  - `onStateChanged`, 是指 BottomSheet 状态变化时的回调,它和 `state` 属性是一对,用来实现受控模式。
@@ -1,14 +1,19 @@
1
1
  #import <UIKit/UIKit.h>
2
- #import <React/RCTComponent.h>
2
+ #import <React/RCTView.h>
3
+ #import <React/RCTEventDispatcher.h>
4
+
5
+ #import "RNBottomSheetState.h"
3
6
 
4
7
  NS_ASSUME_NONNULL_BEGIN
5
8
 
6
- @interface RNBottomSheet : UIView
9
+ @interface RNBottomSheet : RCTView
7
10
 
8
11
  @property(nonatomic, copy) RCTDirectEventBlock onSlide;
9
12
  @property(nonatomic, copy) RCTDirectEventBlock onStateChanged;
10
13
  @property(nonatomic, assign) CGFloat peekHeight;
11
- @property(nonatomic, copy) NSString *state;
14
+ @property(nonatomic, assign) RNBottomSheetState state;
15
+
16
+ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
12
17
 
13
18
  @end
14
19
 
@@ -1,10 +1,16 @@
1
1
  #import "RNBottomSheet.h"
2
+ #import "RNBottomSheetStateChangedEvent.h"
3
+ #import "RNBottomSheetOffsetChangedEvent.h"
2
4
 
3
5
  #import <React/UIView+React.h>
6
+ #import <React/RCTRootContentView.h>
7
+ #import <React/RCTTouchHandler.h>
4
8
  #import <React/RCTLog.h>
5
9
 
6
10
  @interface RNBottomSheet () <UIGestureRecognizerDelegate>
7
11
 
12
+ @property(nonatomic, strong) UIView *contentView;
13
+
8
14
  @property(nonatomic, strong) UIScrollView *target;
9
15
  @property(nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
10
16
 
@@ -12,24 +18,41 @@
12
18
  @property(nonatomic, assign) CGFloat maxY;
13
19
 
14
20
  @property(nonatomic, assign) BOOL nextReturn;
15
- @property(nonatomic, assign) CGFloat lastDragDistance;
16
21
 
17
22
  @property(nonatomic, strong) CADisplayLink *displayLink;
23
+ @property(nonatomic, strong) RCTEventDispatcher *eventDispatcher;
18
24
 
19
25
  @end
20
26
 
21
- @implementation RNBottomSheet
27
+ @implementation RNBottomSheet {
28
+ __weak RCTRootContentView *_rootView;
29
+ }
22
30
 
23
- - (instancetype)init {
31
+ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
24
32
  if (self = [super init]) {
25
33
  _panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
26
34
  _panGestureRecognizer.delegate = self;
27
- _state = @"collapsed";
28
- [self addGestureRecognizer:_panGestureRecognizer];
35
+ _state = RNBottomSheetStateCollapsed;
36
+ _eventDispatcher = eventDispatcher;
29
37
  }
30
38
  return self;
31
39
  }
32
40
 
41
+ - (void)insertReactSubview:(UIView *)subview atIndex:(NSInteger)atIndex {
42
+ [super insertReactSubview:subview atIndex:atIndex];
43
+ if (atIndex == 0) {
44
+ self.contentView = subview;
45
+ [subview addGestureRecognizer:_panGestureRecognizer];
46
+ }
47
+ }
48
+
49
+ - (void)removeReactSubview:(UIView *)subview {
50
+ [super removeReactSubview:subview];
51
+ if (self.contentView == subview) {
52
+ [subview removeGestureRecognizer:_panGestureRecognizer];
53
+ }
54
+ }
55
+
33
56
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
34
57
  if (gestureRecognizer == self.panGestureRecognizer && otherGestureRecognizer == self.target.panGestureRecognizer) {
35
58
  return YES;
@@ -37,6 +60,17 @@
37
60
  return NO;
38
61
  }
39
62
 
63
+ - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer {
64
+ if (gestureRecognizer == self.panGestureRecognizer) {
65
+ if ([super gestureRecognizerShouldBegin:gestureRecognizer]) {
66
+ [self cancelRootViewTouches];
67
+ return YES;
68
+ }
69
+ return NO;
70
+ }
71
+ return [super gestureRecognizerShouldBegin:gestureRecognizer];
72
+ }
73
+
40
74
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch {
41
75
  if (gestureRecognizer != self.panGestureRecognizer) {
42
76
  return YES;
@@ -82,6 +116,26 @@
82
116
  }
83
117
  }
84
118
 
119
+ - (void)didMoveToWindow {
120
+ [super didMoveToWindow];
121
+ if (self.window) {
122
+ [self cacheRootView];
123
+ }
124
+ }
125
+
126
+ - (void)cacheRootView {
127
+ UIView *rootView = self;
128
+ while (rootView.superview && ![rootView isReactRootView]) {
129
+ rootView = rootView.superview;
130
+ }
131
+ _rootView = rootView;
132
+ }
133
+
134
+ - (void)cancelRootViewTouches {
135
+ RCTRootContentView *rootView = (RCTRootContentView *)_rootView;
136
+ [rootView.touchHandler cancel];
137
+ }
138
+
85
139
  - (BOOL)isHorizontal:(UIScrollView *)scrollView {
86
140
  return scrollView.contentSize.width > self.frame.size.width;
87
141
  }
@@ -89,39 +143,42 @@
89
143
 
90
144
  - (void)reactSetFrame:(CGRect)frame {
91
145
  [super reactSetFrame:frame];
92
- if (!CGRectEqualToRect(self.frame, CGRectZero)) {
93
- [self calculateOffset];
94
- if ([self.state isEqualToString:@"collapsed"]) {
95
- self.frame = CGRectOffset(self.frame, 0, self.frame.size.height - self.peekHeight);
96
- [self dispatchOnSlide:self.frame.origin.y];
97
- } else if ([self.state isEqualToString:@"hidden"]) {
98
- self.frame = CGRectOffset(self.frame, 0, self.frame.size.height);
99
- [self dispatchOnSlide:self.frame.origin.y];
100
- }
101
- }
146
+
102
147
  }
103
148
 
104
149
  - (void)layoutSubviews {
105
150
  [super layoutSubviews];
106
- [self calculateOffset];
107
- [self dispatchOnSlide:self.frame.origin.y];
151
+ if (!self.contentView) {
152
+ return;
153
+ }
154
+
155
+ if (!CGRectEqualToRect(self.contentView.frame, CGRectZero)) {
156
+ [self calculateOffset];
157
+ if (self.state == RNBottomSheetStateCollapsed) {
158
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, self.maxY - self.contentView.frame.origin.y);
159
+ } else if (self.state == RNBottomSheetStateExpanded) {
160
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, self.minY - self.contentView.frame.origin.y);
161
+ } else if (self.state == RNBottomSheetStateHidden) {
162
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, self.frame.size.height - self.contentView.frame.origin.y);
163
+ }
164
+ [self dispatchOnSlide:self.contentView.frame.origin.y];
165
+ }
108
166
  }
109
167
 
110
168
  - (void)calculateOffset {
111
- CGFloat parentHeight = self.superview.frame.size.height;
112
- self.minY = fmax(0, parentHeight - self.frame.size.height);
169
+ CGFloat parentHeight = self.frame.size.height;
170
+ self.minY = fmax(0, parentHeight - self.contentView.frame.size.height);
113
171
  self.maxY = fmax(self.minY, parentHeight - self.peekHeight);
114
172
  }
115
173
 
116
174
  - (void)handlePan:(UIPanGestureRecognizer *)pan {
175
+ CGFloat translationY = [pan translationInView:self.contentView].y;
176
+ [pan setTranslation:CGPointZero inView:self.contentView];
117
177
 
118
- CGFloat translationY = [pan translationInView:self].y;
119
- [pan setTranslation:CGPointZero inView:self];
120
-
121
- CGFloat top = self.frame.origin.y;
178
+ CGFloat top = self.contentView.frame.origin.y;
122
179
 
123
180
  if (pan.state == UIGestureRecognizerStateChanged) {
124
- [self setStateInternal:@"dragging"];
181
+ [self setStateInternal:RNBottomSheetStateDragging];
125
182
  }
126
183
 
127
184
  // 如果有嵌套滚动
@@ -129,15 +186,15 @@
129
186
  if(translationY > 0 && top < self.maxY && self.target.contentOffset.y <= 0) {
130
187
  //向下拖
131
188
  CGFloat y = fmin(top + translationY, self.maxY);
132
- self.frame = CGRectOffset(self.frame, 0, y - top);
133
- [self dispatchOnSlide:self.frame.origin.y];
189
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, y - top);
190
+ [self dispatchOnSlide:self.contentView.frame.origin.y];
134
191
  }
135
192
 
136
193
  if (translationY < 0 && top > self.minY) {
137
194
  //向上拖
138
195
  CGFloat y = fmax(top + translationY, self.minY);
139
- self.frame = CGRectOffset(self.frame, 0, y - top);
140
- [self dispatchOnSlide:self.frame.origin.y];
196
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, y - top);
197
+ [self dispatchOnSlide:self.contentView.frame.origin.y];
141
198
  }
142
199
  }
143
200
 
@@ -146,43 +203,43 @@
146
203
  if(translationY > 0 && top < self.maxY) {
147
204
  //向下拖
148
205
  CGFloat y = fmin(top + translationY, self.maxY);
149
- self.frame = CGRectOffset(self.frame, 0, y - top);
150
- [self dispatchOnSlide:self.frame.origin.y];
206
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, y - top);
207
+ [self dispatchOnSlide:self.contentView.frame.origin.y];
151
208
  }
152
209
 
153
210
  if (translationY < 0 && top > self.minY) {
154
211
  //向上拖
155
212
  CGFloat y = fmax(top + translationY, self.minY);
156
- self.frame = CGRectOffset(self.frame, 0, y - top);
157
- [self dispatchOnSlide:self.frame.origin.y];
213
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, y - top);
214
+ [self dispatchOnSlide:self.contentView.frame.origin.y];
158
215
  }
159
216
  }
160
217
 
161
218
  if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
162
- if (self.lastDragDistance > 0) {
219
+ // RCTLogInfo(@"velocity:%f", [pan velocityInView:self.contentView].y);
220
+ CGFloat velocity = [pan velocityInView:self.contentView].y;
221
+ if (velocity > 400) {
163
222
  if (self.target && self.target.contentOffset.y <= 0) {
164
223
  //如果是类似轻扫的那种
165
- [self settleToState:@"collapsed"];
224
+ [self settleToState:RNBottomSheetStateCollapsed];
166
225
  }
167
226
 
168
227
  if (!self.target) {
169
228
  //如果是类似轻扫的那种
170
- [self settleToState:@"collapsed"];
229
+ [self settleToState:RNBottomSheetStateCollapsed];
171
230
  }
172
- } else if (self.lastDragDistance < 0) {
231
+ } else if (velocity < -400) {
173
232
  //如果是类似轻扫的那种
174
- [self settleToState:@"expanded"];
233
+ [self settleToState:RNBottomSheetStateExpanded];
175
234
  } else {
176
235
  //如果是普通拖拽
177
- if(fabs(self.frame.origin.y - self.minY) > fabs(self.frame.origin.y - self.maxY)) {
178
- [self settleToState:@"collapsed"];
236
+ if(fabs(self.contentView.frame.origin.y - self.minY) > fabs(self.contentView.frame.origin.y - self.maxY)) {
237
+ [self settleToState:RNBottomSheetStateCollapsed];
179
238
  } else {
180
- [self settleToState:@"expanded"];
239
+ [self settleToState:RNBottomSheetStateExpanded];
181
240
  }
182
241
  }
183
242
  }
184
-
185
- self.lastDragDistance = translationY;
186
243
  }
187
244
 
188
245
  - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(UIScrollView *)target change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
@@ -214,7 +271,7 @@
214
271
 
215
272
  if (dy < 0) {
216
273
  //向上
217
- if (self.frame.origin.y > self.minY) {
274
+ if (self.contentView.frame.origin.y > self.minY) {
218
275
  _nextReturn = true;
219
276
  target.contentOffset = CGPointMake(0, old);
220
277
  }
@@ -223,20 +280,20 @@
223
280
 
224
281
  - (void)setPeekHeight:(CGFloat)peekHeight {
225
282
  _peekHeight = peekHeight;
226
- if (!CGRectEqualToRect(self.frame, CGRectZero)) {
283
+ if (!CGRectEqualToRect(self.contentView.frame, CGRectZero)) {
227
284
  [self calculateOffset];
228
- if ([self.state isEqualToString:@"collapsed"]) {
229
- [self settleToState:@"collapsed"];
285
+ if (self.state == RNBottomSheetStateCollapsed) {
286
+ [self settleToState:RNBottomSheetStateCollapsed];
230
287
  }
231
288
  }
232
289
  }
233
290
 
234
- - (void)setState:(NSString *)state {
235
- if ([_state isEqualToString:state]) {
291
+ - (void)setState:(RNBottomSheetState)state {
292
+ if (_state == state) {
236
293
  return;
237
294
  }
238
295
 
239
- if (CGRectEqualToRect(self.frame, CGRectZero)) {
296
+ if (CGRectEqualToRect(self.contentView.frame, CGRectZero)) {
240
297
  [self setStateInternal:state];
241
298
  return;
242
299
  }
@@ -244,24 +301,24 @@
244
301
  [self settleToState:state];
245
302
  }
246
303
 
247
- - (void)settleToState:(NSString *)state {
248
- if ([state isEqualToString:@"collapsed"]) {
304
+ - (void)settleToState:(RNBottomSheetState)state {
305
+ if (state == RNBottomSheetStateCollapsed) {
249
306
  [self startSettlingToState:state top:self.maxY];
250
- } else if ([state isEqualToString:@"expanded"]) {
307
+ } else if (state == RNBottomSheetStateExpanded) {
251
308
  [self startSettlingToState:state top:self.minY];
252
- } else if ([state isEqualToString:@"hidden"]) {
253
- [self startSettlingToState:@"hidden" top:self.superview.frame.size.height];
309
+ } else if (state == RNBottomSheetStateHidden) {
310
+ [self startSettlingToState:state top:self.frame.size.height];
254
311
  }
255
312
  }
256
313
 
257
- - (void)startSettlingToState:(NSString *)state top:(CGFloat)top {
314
+ - (void)startSettlingToState:(RNBottomSheetState)state top:(CGFloat)top {
258
315
  self.target.pagingEnabled = YES;
259
- [self setStateInternal:@"settling"];
316
+ [self setStateInternal:RNBottomSheetStateSettling];
260
317
  [self startWatchBottomSheetTransition];
261
318
  [self.layer removeAllAnimations];
262
- CGFloat duration = fmin(fabs(self.frame.origin.y - top) / (self.maxY - self.minY) * 0.3, 0.3);
263
- [UIView animateWithDuration:duration delay:0 options:UIViewAnimationOptionCurveEaseOut|UIViewAnimationOptionBeginFromCurrentState animations:^{
264
- self.frame = CGRectOffset(self.frame, 0, top - self.frame.origin.y);
319
+ // CGFloat duration = fmin(fabs(self.contentView.frame.origin.y - top) / (self.maxY - self.minY) * 0.3, 0.3);
320
+ [UIView animateWithDuration:0.25 delay:0 usingSpringWithDamping:1 initialSpringVelocity:0.5 options:NULL animations:^{
321
+ self.contentView.frame = CGRectOffset(self.contentView.frame, 0, top - self.contentView.frame.origin.y);
265
322
  } completion:^(BOOL finished) {
266
323
  self.target.pagingEnabled = NO;
267
324
  [self stopWatchBottomSheetTransition];
@@ -269,18 +326,14 @@
269
326
  }];
270
327
  }
271
328
 
272
- - (void)setStateInternal:(NSString *)state {
273
- if ([_state isEqualToString:state]) {
329
+ - (void)setStateInternal:(RNBottomSheetState)state {
330
+ if (_state == state) {
274
331
  return;
275
332
  }
276
333
  _state = state;
277
334
 
278
- if (self.onStateChanged) {
279
- if ([state isEqualToString:@"collapsed"] || [state isEqualToString:@"expanded"] || [state isEqualToString:@"hidden"]) {
280
- self.onStateChanged(@{
281
- @"state": state,
282
- });
283
- }
335
+ if (state == RNBottomSheetStateCollapsed || state == RNBottomSheetStateExpanded || state == RNBottomSheetStateHidden) {
336
+ [self.eventDispatcher sendEvent:[[RNBottomSheetStateChangedEvent alloc] initWithViewTag:self.reactTag state:state]];
284
337
  }
285
338
  }
286
339
 
@@ -288,15 +341,8 @@
288
341
  if (top < 0 || self.maxY == 0) {
289
342
  return;
290
343
  }
291
- if (self.onSlide) {
292
- CGFloat progress = fmin((top - self.minY) * 1.0f / (self.maxY - self.minY), 1);
293
- self.onSlide(@{
294
- @"progress": @(progress),
295
- @"offset": @(top),
296
- @"collapsedOffset": @(self.maxY),
297
- @"expandedOffset": @(self.minY)
298
- });
299
- }
344
+ CGFloat progress = fmin((top - self.minY) * 1.0f / (self.maxY - self.minY), 1);
345
+ [self.eventDispatcher sendEvent:[[RNBottomSheetOffsetChangedEvent alloc] initWithViewTag:self.reactTag progress:progress offset:top minY:self.minY maxY:self.maxY]];
300
346
  }
301
347
 
302
348
  - (void)startWatchBottomSheetTransition {
@@ -307,9 +353,9 @@
307
353
  }
308
354
 
309
355
  - (void)stopWatchBottomSheetTransition {
310
- if ([self.state isEqualToString:@"collapsed"]) {
356
+ if (self.state == RNBottomSheetStateCollapsed) {
311
357
  [self dispatchOnSlide:self.maxY];
312
- } else if ([self.state isEqualToString:@"expanded"]) {
358
+ } else if (self.state == RNBottomSheetStateExpanded) {
313
359
  [self dispatchOnSlide:self.minY];
314
360
  }
315
361
  if(_displayLink){
@@ -319,7 +365,7 @@
319
365
  }
320
366
 
321
367
  - (void)watchBottomSheetTransition {
322
- CGFloat top = [self.layer presentationLayer].frame.origin.y;
368
+ CGFloat top = [self.contentView.layer presentationLayer].frame.origin.y;
323
369
  [self dispatchOnSlide:top];
324
370
  }
325
371
 
@@ -1,18 +1,23 @@
1
1
  #import "RNBottomSheetManager.h"
2
2
  #import "RNBottomSheet.h"
3
+ #import "RNBottomSheetState.h"
3
4
 
4
5
  @implementation RNBottomSheetManager
5
6
 
6
7
  RCT_EXPORT_MODULE(BottomSheet)
7
8
 
8
9
  - (UIView *)view {
9
- return [RNBottomSheet new];
10
+ RNBottomSheet *bottomSheet = [[RNBottomSheet alloc] initWithEventDispatcher:self.bridge.eventDispatcher];
11
+ bottomSheet.pointerEvents = RCTPointerEventsBoxNone;
12
+ return bottomSheet;
10
13
  }
11
14
 
12
15
  RCT_EXPORT_VIEW_PROPERTY(onSlide, RCTDirectEventBlock)
13
16
  RCT_EXPORT_VIEW_PROPERTY(onStateChanged, RCTDirectEventBlock)
14
-
15
17
  RCT_EXPORT_VIEW_PROPERTY(peekHeight, CGFloat)
16
- RCT_EXPORT_VIEW_PROPERTY(state, NSString)
18
+
19
+ RCT_CUSTOM_VIEW_PROPERTY(state, NSString, RNBottomSheet) {
20
+ view.state = RNBottomSheetStateFromString([RCTConvert NSString:json]);
21
+ }
17
22
 
18
23
  @end
@@ -0,0 +1,11 @@
1
+ #import <React/RCTEventDispatcherProtocol.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ @interface RNBottomSheetOffsetChangedEvent : NSObject <RCTEvent>
6
+
7
+ - (instancetype)initWithViewTag:(NSNumber *)viewTag progress:(CGFloat)progress offset:(CGFloat)offset minY:(CGFloat)minY maxY:(CGFloat)maxY;
8
+
9
+ @end
10
+
11
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,58 @@
1
+ #import "RNBottomSheetOffsetChangedEvent.h"
2
+
3
+ @interface RNBottomSheetOffsetChangedEvent ()
4
+
5
+ @property (nonatomic, assign) CGFloat progress;
6
+ @property (nonatomic, assign) CGFloat offest;
7
+ @property (nonatomic, assign) CGFloat minY;
8
+ @property (nonatomic, assign) CGFloat maxY;
9
+
10
+ @end
11
+
12
+ @implementation RNBottomSheetOffsetChangedEvent
13
+
14
+ @synthesize viewTag = _viewTag;
15
+
16
+ + (NSString *)moduleDotMethod {
17
+ return @"RCTEventEmitter.receiveEvent";
18
+ }
19
+
20
+ RCT_NOT_IMPLEMENTED(- (instancetype)init)
21
+ - (instancetype)initWithViewTag:(NSNumber *)viewTag progress:(CGFloat)progress offset:(CGFloat)offset minY:(CGFloat)minY maxY:(CGFloat)maxY {
22
+ if (self = [super init]) {
23
+ _viewTag = viewTag;
24
+ _progress = progress;
25
+ _offest = offset;
26
+ _minY = minY;
27
+ _maxY = maxY;
28
+ }
29
+ return self;
30
+ }
31
+
32
+ - (NSString *)eventName {
33
+ return @"onSlide";
34
+ }
35
+
36
+ - (BOOL)canCoalesce {
37
+ return YES;
38
+ }
39
+
40
+ - (uint16_t)coalescingKey {
41
+ return 0;
42
+ }
43
+
44
+ - (NSArray *)arguments {
45
+ return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), @{
46
+ @"progress": @(self.progress),
47
+ @"offset": @(self.offest),
48
+ @"expandedOffset": @(self.minY),
49
+ @"collapsedOffset": @(self.maxY)
50
+ }];
51
+ }
52
+
53
+ - (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent {
54
+ return newEvent;
55
+ }
56
+
57
+
58
+ @end
@@ -0,0 +1,17 @@
1
+ #import <React/RCTUtils.h>
2
+
3
+ NS_ASSUME_NONNULL_BEGIN
4
+
5
+ typedef NS_ENUM(NSUInteger, RNBottomSheetState) {
6
+ RNBottomSheetStateCollapsed = 0,
7
+ RNBottomSheetStateExpanded,
8
+ RNBottomSheetStateHidden,
9
+ RNBottomSheetStateDragging,
10
+ RNBottomSheetStateSettling,
11
+ };
12
+
13
+ RCT_EXTERN RNBottomSheetState RNBottomSheetStateFromString(NSString *state);
14
+
15
+ RCT_EXTERN NSString* RNBottomSheetStateToString(RNBottomSheetState state);
16
+
17
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,38 @@
1
+ #import "RNBottomSheetState.h"
2
+
3
+ RNBottomSheetState RNBottomSheetStateFromString(NSString *state) {
4
+ if ([state isEqualToString:@"collapsed"]) {
5
+ return RNBottomSheetStateCollapsed;
6
+ }
7
+ if ([state isEqualToString:@"expanded"]) {
8
+ return RNBottomSheetStateExpanded;
9
+ }
10
+ if ([state isEqualToString:@"settling"]) {
11
+ return RNBottomSheetStateSettling;
12
+ }
13
+ if ([state isEqualToString:@"dragging"]) {
14
+ return RNBottomSheetStateDragging;
15
+ }
16
+ return RNBottomSheetStateHidden;
17
+ }
18
+
19
+
20
+ NSString* RNBottomSheetStateToString(RNBottomSheetState state) {
21
+ if (state == RNBottomSheetStateCollapsed) {
22
+ return @"collapsed";
23
+ }
24
+
25
+ if (state == RNBottomSheetStateExpanded) {
26
+ return @"expanded";
27
+ }
28
+
29
+ if (state == RNBottomSheetStateSettling) {
30
+ return @"settling";
31
+ }
32
+
33
+ if (state == RNBottomSheetStateDragging) {
34
+ return @"dragging";
35
+ }
36
+
37
+ return @"hidden";
38
+ }
@@ -0,0 +1,13 @@
1
+ #import <React/RCTEventDispatcherProtocol.h>
2
+
3
+ #import "RNBottomSheetState.h"
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ @interface RNBottomSheetStateChangedEvent : NSObject <RCTEvent>
8
+
9
+ - (instancetype)initWithViewTag:(NSNumber *)viewTag state:(RNBottomSheetState)state;
10
+
11
+ @end
12
+
13
+ NS_ASSUME_NONNULL_END
@@ -0,0 +1,50 @@
1
+ #import "RNBottomSheetStateChangedEvent.h"
2
+
3
+ static uint16_t _coalescingKey = 0;
4
+
5
+ @interface RNBottomSheetStateChangedEvent ()
6
+
7
+ @property (nonatomic, assign) RNBottomSheetState state;
8
+
9
+ @end
10
+
11
+ @implementation RNBottomSheetStateChangedEvent
12
+
13
+ @synthesize viewTag = _viewTag;
14
+
15
+ + (NSString *)moduleDotMethod {
16
+ return @"RCTEventEmitter.receiveEvent";
17
+ }
18
+
19
+ RCT_NOT_IMPLEMENTED(- (instancetype)init)
20
+ - (instancetype)initWithViewTag:(NSNumber *)viewTag state:(RNBottomSheetState)state {
21
+ if (self = [super init]) {
22
+ _viewTag = viewTag;
23
+ _state = state;
24
+ }
25
+ return self;
26
+ }
27
+
28
+ - (NSString *)eventName {
29
+ return @"onStateChanged";
30
+ }
31
+
32
+ - (BOOL)canCoalesce {
33
+ return NO;
34
+ }
35
+
36
+ - (uint16_t)coalescingKey {
37
+ return _coalescingKey++;
38
+ }
39
+
40
+ - (NSArray *)arguments {
41
+ return @[self.viewTag, RCTNormalizeInputEventName(self.eventName), @{
42
+ @"state": RNBottomSheetStateToString(self.state)
43
+ }];
44
+ }
45
+
46
+ - (id<RCTEvent>)coalesceWithEvent:(id<RCTEvent>)newEvent {
47
+ return newEvent;
48
+ }
49
+
50
+ @end
package/lib/index.d.ts CHANGED
@@ -15,6 +15,7 @@ interface NativeBottomSheetProps extends ViewProps {
15
15
  onStateChanged?: (event: NativeSyntheticEvent<StateChangedEventData>) => void;
16
16
  peekHeight?: number;
17
17
  state?: BottomSheetState;
18
+ contentContainerStyle?: ViewProps['style'];
18
19
  }
19
20
  declare const BottomSheet: React.ForwardRefExoticComponent<NativeBottomSheetProps & {
20
21
  fitToContents?: boolean | undefined;
package/lib/index.js CHANGED
@@ -1,24 +1,15 @@
1
1
  import React from 'react';
2
- import { Platform, requireNativeComponent, StyleSheet, View } from 'react-native';
2
+ import { requireNativeComponent, StyleSheet, View } from 'react-native';
3
3
  import splitLayoutProps from './splitLayoutProps';
4
4
  const NativeBottomSheet = requireNativeComponent('BottomSheet');
5
5
  const BottomSheet = React.forwardRef((props, ref) => {
6
- const { style, children, peekHeight = 200, state = 'collapsed', fitToContents, ...rest } = props;
6
+ const { style, contentContainerStyle, children, peekHeight = 200, state = 'collapsed', fitToContents, ...rest } = props;
7
7
  const { outer, inner } = splitLayoutProps(StyleSheet.flatten(style));
8
- if (Platform.OS === 'android') {
9
- return (<NativeBottomSheet style={[StyleSheet.absoluteFill, outer]} peekHeight={peekHeight} state={state} {...rest} ref={ref}>
10
- <View style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner]} collapsable={false}>
11
- {children}
12
- </View>
13
- </NativeBottomSheet>);
14
- }
15
- else {
16
- return (<View style={[StyleSheet.absoluteFill, outer]} pointerEvents="box-none">
17
- <NativeBottomSheet style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner]} peekHeight={peekHeight} state={state} {...rest} ref={ref}>
18
- {children}
19
- </NativeBottomSheet>
20
- </View>);
21
- }
8
+ return (<NativeBottomSheet style={[StyleSheet.absoluteFill, outer]} peekHeight={peekHeight} state={state} {...rest} ref={ref}>
9
+ <View style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner, contentContainerStyle]} collapsable={false}>
10
+ {children}
11
+ </View>
12
+ </NativeBottomSheet>);
22
13
  });
23
14
  const styles = StyleSheet.create({
24
15
  fitToContents: {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@sdcx/bottom-sheet",
3
3
  "description": "A react-native BottomSheet component.",
4
- "version": "0.8.0",
4
+ "version": "0.10.0",
5
5
  "main": "./lib/index.js",
6
6
  "typings": "./lib/index.d.ts",
7
7
  "react-native": "src/index",
package/src/index.tsx CHANGED
@@ -1,5 +1,5 @@
1
1
  import React from 'react'
2
- import { NativeSyntheticEvent, Platform, requireNativeComponent, StyleSheet, View, ViewProps } from 'react-native'
2
+ import { NativeSyntheticEvent, requireNativeComponent, StyleSheet, View, ViewProps } from 'react-native'
3
3
  import splitLayoutProps from './splitLayoutProps'
4
4
 
5
5
  export interface OffsetChangedEventData {
@@ -20,6 +20,7 @@ interface NativeBottomSheetProps extends ViewProps {
20
20
  onStateChanged?: (event: NativeSyntheticEvent<StateChangedEventData>) => void
21
21
  peekHeight?: number
22
22
  state?: BottomSheetState
23
+ contentContainerStyle?: ViewProps['style']
23
24
  }
24
25
 
25
26
  type BottomSheetProps = NativeBottomSheetProps & {
@@ -31,36 +32,30 @@ const NativeBottomSheet = requireNativeComponent<NativeBottomSheetProps>('Bottom
31
32
  type NativeBottomSheetInstance = InstanceType<typeof NativeBottomSheet>
32
33
 
33
34
  const BottomSheet = React.forwardRef<NativeBottomSheetInstance, BottomSheetProps>((props, ref) => {
34
- const { style, children, peekHeight = 200, state = 'collapsed', fitToContents, ...rest } = props
35
+ const {
36
+ style,
37
+ contentContainerStyle,
38
+ children,
39
+ peekHeight = 200,
40
+ state = 'collapsed',
41
+ fitToContents,
42
+ ...rest
43
+ } = props
35
44
  const { outer, inner } = splitLayoutProps(StyleSheet.flatten(style))
36
-
37
- if (Platform.OS === 'android') {
38
- return (
39
- <NativeBottomSheet
40
- style={[StyleSheet.absoluteFill, outer]}
41
- peekHeight={peekHeight}
42
- state={state}
43
- {...rest}
44
- ref={ref}>
45
- <View style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner]} collapsable={false}>
46
- {children}
47
- </View>
48
- </NativeBottomSheet>
49
- )
50
- } else {
51
- return (
52
- <View style={[StyleSheet.absoluteFill, outer]} pointerEvents="box-none">
53
- <NativeBottomSheet
54
- style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner]}
55
- peekHeight={peekHeight}
56
- state={state}
57
- {...rest}
58
- ref={ref}>
59
- {children}
60
- </NativeBottomSheet>
45
+ return (
46
+ <NativeBottomSheet
47
+ style={[StyleSheet.absoluteFill, outer]}
48
+ peekHeight={peekHeight}
49
+ state={state}
50
+ {...rest}
51
+ ref={ref}>
52
+ <View
53
+ style={[fitToContents ? styles.fitToContents : StyleSheet.absoluteFill, inner, contentContainerStyle]}
54
+ collapsable={false}>
55
+ {children}
61
56
  </View>
62
- )
63
- }
57
+ </NativeBottomSheet>
58
+ )
64
59
  })
65
60
 
66
61
  const styles = StyleSheet.create({