@sdcx/bottom-sheet 0.9.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 +8 -1
- package/ios/BottomSheet/RNBottomSheet.h +8 -3
- package/ios/BottomSheet/RNBottomSheet.m +94 -83
- package/ios/BottomSheet/RNBottomSheetManager.m +8 -3
- package/ios/BottomSheet/RNBottomSheetOffsetChangedEvent.h +11 -0
- package/ios/BottomSheet/RNBottomSheetOffsetChangedEvent.m +58 -0
- package/ios/BottomSheet/RNBottomSheetState.h +17 -0
- package/ios/BottomSheet/RNBottomSheetState.m +38 -0
- package/ios/BottomSheet/RNBottomSheetStateChangedEvent.h +13 -0
- package/ios/BottomSheet/RNBottomSheetStateChangedEvent.m +50 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +7 -16
- package/package.json +1 -1
- package/src/index.tsx +24 -29
package/README.md
CHANGED
|
@@ -43,7 +43,12 @@ const App = () => {
|
|
|
43
43
|
}
|
|
44
44
|
```
|
|
45
45
|
|
|
46
|
-
> :
|
|
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/
|
|
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 :
|
|
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,
|
|
14
|
+
@property(nonatomic, assign) RNBottomSheetState state;
|
|
15
|
+
|
|
16
|
+
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher;
|
|
12
17
|
|
|
13
18
|
@end
|
|
14
19
|
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
#import "RNBottomSheet.h"
|
|
2
|
+
#import "RNBottomSheetStateChangedEvent.h"
|
|
3
|
+
#import "RNBottomSheetOffsetChangedEvent.h"
|
|
2
4
|
|
|
3
5
|
#import <React/UIView+React.h>
|
|
4
6
|
#import <React/RCTRootContentView.h>
|
|
@@ -7,6 +9,8 @@
|
|
|
7
9
|
|
|
8
10
|
@interface RNBottomSheet () <UIGestureRecognizerDelegate>
|
|
9
11
|
|
|
12
|
+
@property(nonatomic, strong) UIView *contentView;
|
|
13
|
+
|
|
10
14
|
@property(nonatomic, strong) UIScrollView *target;
|
|
11
15
|
@property(nonatomic, strong) UIPanGestureRecognizer *panGestureRecognizer;
|
|
12
16
|
|
|
@@ -14,26 +18,41 @@
|
|
|
14
18
|
@property(nonatomic, assign) CGFloat maxY;
|
|
15
19
|
|
|
16
20
|
@property(nonatomic, assign) BOOL nextReturn;
|
|
17
|
-
@property(nonatomic, assign) CGFloat lastDragDistance;
|
|
18
21
|
|
|
19
22
|
@property(nonatomic, strong) CADisplayLink *displayLink;
|
|
23
|
+
@property(nonatomic, strong) RCTEventDispatcher *eventDispatcher;
|
|
20
24
|
|
|
21
25
|
@end
|
|
22
26
|
|
|
23
27
|
@implementation RNBottomSheet {
|
|
24
|
-
__weak RCTRootContentView *
|
|
28
|
+
__weak RCTRootContentView *_rootView;
|
|
25
29
|
}
|
|
26
30
|
|
|
27
|
-
- (instancetype)
|
|
31
|
+
- (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher {
|
|
28
32
|
if (self = [super init]) {
|
|
29
33
|
_panGestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePan:)];
|
|
30
34
|
_panGestureRecognizer.delegate = self;
|
|
31
|
-
_state =
|
|
32
|
-
|
|
35
|
+
_state = RNBottomSheetStateCollapsed;
|
|
36
|
+
_eventDispatcher = eventDispatcher;
|
|
33
37
|
}
|
|
34
38
|
return self;
|
|
35
39
|
}
|
|
36
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
|
+
|
|
37
56
|
- (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer {
|
|
38
57
|
if (gestureRecognizer == self.panGestureRecognizer && otherGestureRecognizer == self.target.panGestureRecognizer) {
|
|
39
58
|
return YES;
|
|
@@ -100,20 +119,20 @@
|
|
|
100
119
|
- (void)didMoveToWindow {
|
|
101
120
|
[super didMoveToWindow];
|
|
102
121
|
if (self.window) {
|
|
103
|
-
[self
|
|
122
|
+
[self cacheRootView];
|
|
104
123
|
}
|
|
105
124
|
}
|
|
106
125
|
|
|
107
|
-
- (void)
|
|
126
|
+
- (void)cacheRootView {
|
|
108
127
|
UIView *rootView = self;
|
|
109
128
|
while (rootView.superview && ![rootView isReactRootView]) {
|
|
110
129
|
rootView = rootView.superview;
|
|
111
130
|
}
|
|
112
|
-
|
|
131
|
+
_rootView = rootView;
|
|
113
132
|
}
|
|
114
133
|
|
|
115
134
|
- (void)cancelRootViewTouches {
|
|
116
|
-
RCTRootContentView *rootView = (RCTRootContentView *)
|
|
135
|
+
RCTRootContentView *rootView = (RCTRootContentView *)_rootView;
|
|
117
136
|
[rootView.touchHandler cancel];
|
|
118
137
|
}
|
|
119
138
|
|
|
@@ -124,39 +143,42 @@
|
|
|
124
143
|
|
|
125
144
|
- (void)reactSetFrame:(CGRect)frame {
|
|
126
145
|
[super reactSetFrame:frame];
|
|
127
|
-
|
|
128
|
-
[self calculateOffset];
|
|
129
|
-
if ([self.state isEqualToString:@"collapsed"]) {
|
|
130
|
-
self.frame = CGRectOffset(self.frame, 0, self.frame.size.height - self.peekHeight);
|
|
131
|
-
[self dispatchOnSlide:self.frame.origin.y];
|
|
132
|
-
} else if ([self.state isEqualToString:@"hidden"]) {
|
|
133
|
-
self.frame = CGRectOffset(self.frame, 0, self.frame.size.height);
|
|
134
|
-
[self dispatchOnSlide:self.frame.origin.y];
|
|
135
|
-
}
|
|
136
|
-
}
|
|
146
|
+
|
|
137
147
|
}
|
|
138
148
|
|
|
139
149
|
- (void)layoutSubviews {
|
|
140
150
|
[super layoutSubviews];
|
|
141
|
-
|
|
142
|
-
|
|
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
|
+
}
|
|
143
166
|
}
|
|
144
167
|
|
|
145
168
|
- (void)calculateOffset {
|
|
146
|
-
CGFloat parentHeight = self.
|
|
147
|
-
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);
|
|
148
171
|
self.maxY = fmax(self.minY, parentHeight - self.peekHeight);
|
|
149
172
|
}
|
|
150
173
|
|
|
151
174
|
- (void)handlePan:(UIPanGestureRecognizer *)pan {
|
|
175
|
+
CGFloat translationY = [pan translationInView:self.contentView].y;
|
|
176
|
+
[pan setTranslation:CGPointZero inView:self.contentView];
|
|
152
177
|
|
|
153
|
-
CGFloat
|
|
154
|
-
[pan setTranslation:CGPointZero inView:self];
|
|
155
|
-
|
|
156
|
-
CGFloat top = self.frame.origin.y;
|
|
178
|
+
CGFloat top = self.contentView.frame.origin.y;
|
|
157
179
|
|
|
158
180
|
if (pan.state == UIGestureRecognizerStateChanged) {
|
|
159
|
-
[self setStateInternal
|
|
181
|
+
[self setStateInternal:RNBottomSheetStateDragging];
|
|
160
182
|
}
|
|
161
183
|
|
|
162
184
|
// 如果有嵌套滚动
|
|
@@ -164,15 +186,15 @@
|
|
|
164
186
|
if(translationY > 0 && top < self.maxY && self.target.contentOffset.y <= 0) {
|
|
165
187
|
//向下拖
|
|
166
188
|
CGFloat y = fmin(top + translationY, self.maxY);
|
|
167
|
-
self.frame = CGRectOffset(self.frame, 0, y - top);
|
|
168
|
-
[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];
|
|
169
191
|
}
|
|
170
192
|
|
|
171
193
|
if (translationY < 0 && top > self.minY) {
|
|
172
194
|
//向上拖
|
|
173
195
|
CGFloat y = fmax(top + translationY, self.minY);
|
|
174
|
-
self.frame = CGRectOffset(self.frame, 0, y - top);
|
|
175
|
-
[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];
|
|
176
198
|
}
|
|
177
199
|
}
|
|
178
200
|
|
|
@@ -181,43 +203,43 @@
|
|
|
181
203
|
if(translationY > 0 && top < self.maxY) {
|
|
182
204
|
//向下拖
|
|
183
205
|
CGFloat y = fmin(top + translationY, self.maxY);
|
|
184
|
-
self.frame = CGRectOffset(self.frame, 0, y - top);
|
|
185
|
-
[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];
|
|
186
208
|
}
|
|
187
209
|
|
|
188
210
|
if (translationY < 0 && top > self.minY) {
|
|
189
211
|
//向上拖
|
|
190
212
|
CGFloat y = fmax(top + translationY, self.minY);
|
|
191
|
-
self.frame = CGRectOffset(self.frame, 0, y - top);
|
|
192
|
-
[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];
|
|
193
215
|
}
|
|
194
216
|
}
|
|
195
217
|
|
|
196
218
|
if (pan.state == UIGestureRecognizerStateEnded || pan.state == UIGestureRecognizerStateCancelled) {
|
|
197
|
-
|
|
219
|
+
// RCTLogInfo(@"velocity:%f", [pan velocityInView:self.contentView].y);
|
|
220
|
+
CGFloat velocity = [pan velocityInView:self.contentView].y;
|
|
221
|
+
if (velocity > 400) {
|
|
198
222
|
if (self.target && self.target.contentOffset.y <= 0) {
|
|
199
223
|
//如果是类似轻扫的那种
|
|
200
|
-
[self settleToState
|
|
224
|
+
[self settleToState:RNBottomSheetStateCollapsed];
|
|
201
225
|
}
|
|
202
226
|
|
|
203
227
|
if (!self.target) {
|
|
204
228
|
//如果是类似轻扫的那种
|
|
205
|
-
[self settleToState
|
|
229
|
+
[self settleToState:RNBottomSheetStateCollapsed];
|
|
206
230
|
}
|
|
207
|
-
} else if (
|
|
231
|
+
} else if (velocity < -400) {
|
|
208
232
|
//如果是类似轻扫的那种
|
|
209
|
-
[self settleToState
|
|
233
|
+
[self settleToState:RNBottomSheetStateExpanded];
|
|
210
234
|
} else {
|
|
211
235
|
//如果是普通拖拽
|
|
212
|
-
if(fabs(self.frame.origin.y - self.minY) > fabs(self.frame.origin.y - self.maxY)) {
|
|
213
|
-
[self settleToState
|
|
236
|
+
if(fabs(self.contentView.frame.origin.y - self.minY) > fabs(self.contentView.frame.origin.y - self.maxY)) {
|
|
237
|
+
[self settleToState:RNBottomSheetStateCollapsed];
|
|
214
238
|
} else {
|
|
215
|
-
[self settleToState
|
|
239
|
+
[self settleToState:RNBottomSheetStateExpanded];
|
|
216
240
|
}
|
|
217
241
|
}
|
|
218
242
|
}
|
|
219
|
-
|
|
220
|
-
self.lastDragDistance = translationY;
|
|
221
243
|
}
|
|
222
244
|
|
|
223
245
|
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(UIScrollView *)target change:(NSDictionary<NSKeyValueChangeKey,id> *)change context:(void *)context{
|
|
@@ -249,7 +271,7 @@
|
|
|
249
271
|
|
|
250
272
|
if (dy < 0) {
|
|
251
273
|
//向上
|
|
252
|
-
if (self.frame.origin.y > self.minY) {
|
|
274
|
+
if (self.contentView.frame.origin.y > self.minY) {
|
|
253
275
|
_nextReturn = true;
|
|
254
276
|
target.contentOffset = CGPointMake(0, old);
|
|
255
277
|
}
|
|
@@ -258,20 +280,20 @@
|
|
|
258
280
|
|
|
259
281
|
- (void)setPeekHeight:(CGFloat)peekHeight {
|
|
260
282
|
_peekHeight = peekHeight;
|
|
261
|
-
if (!CGRectEqualToRect(self.frame, CGRectZero)) {
|
|
283
|
+
if (!CGRectEqualToRect(self.contentView.frame, CGRectZero)) {
|
|
262
284
|
[self calculateOffset];
|
|
263
|
-
if (
|
|
264
|
-
[self settleToState
|
|
285
|
+
if (self.state == RNBottomSheetStateCollapsed) {
|
|
286
|
+
[self settleToState:RNBottomSheetStateCollapsed];
|
|
265
287
|
}
|
|
266
288
|
}
|
|
267
289
|
}
|
|
268
290
|
|
|
269
|
-
- (void)setState:(
|
|
270
|
-
if (
|
|
291
|
+
- (void)setState:(RNBottomSheetState)state {
|
|
292
|
+
if (_state == state) {
|
|
271
293
|
return;
|
|
272
294
|
}
|
|
273
295
|
|
|
274
|
-
if (CGRectEqualToRect(self.frame, CGRectZero)) {
|
|
296
|
+
if (CGRectEqualToRect(self.contentView.frame, CGRectZero)) {
|
|
275
297
|
[self setStateInternal:state];
|
|
276
298
|
return;
|
|
277
299
|
}
|
|
@@ -279,24 +301,24 @@
|
|
|
279
301
|
[self settleToState:state];
|
|
280
302
|
}
|
|
281
303
|
|
|
282
|
-
- (void)settleToState:(
|
|
283
|
-
if (
|
|
304
|
+
- (void)settleToState:(RNBottomSheetState)state {
|
|
305
|
+
if (state == RNBottomSheetStateCollapsed) {
|
|
284
306
|
[self startSettlingToState:state top:self.maxY];
|
|
285
|
-
} else if (
|
|
307
|
+
} else if (state == RNBottomSheetStateExpanded) {
|
|
286
308
|
[self startSettlingToState:state top:self.minY];
|
|
287
|
-
} else if (
|
|
288
|
-
[self startSettlingToState
|
|
309
|
+
} else if (state == RNBottomSheetStateHidden) {
|
|
310
|
+
[self startSettlingToState:state top:self.frame.size.height];
|
|
289
311
|
}
|
|
290
312
|
}
|
|
291
313
|
|
|
292
|
-
- (void)startSettlingToState:(
|
|
314
|
+
- (void)startSettlingToState:(RNBottomSheetState)state top:(CGFloat)top {
|
|
293
315
|
self.target.pagingEnabled = YES;
|
|
294
|
-
[self setStateInternal
|
|
316
|
+
[self setStateInternal:RNBottomSheetStateSettling];
|
|
295
317
|
[self startWatchBottomSheetTransition];
|
|
296
318
|
[self.layer removeAllAnimations];
|
|
297
|
-
CGFloat duration = fmin(fabs(self.frame.origin.y - top) / (self.maxY - self.minY) * 0.3, 0.3);
|
|
298
|
-
[UIView animateWithDuration:
|
|
299
|
-
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);
|
|
300
322
|
} completion:^(BOOL finished) {
|
|
301
323
|
self.target.pagingEnabled = NO;
|
|
302
324
|
[self stopWatchBottomSheetTransition];
|
|
@@ -304,18 +326,14 @@
|
|
|
304
326
|
}];
|
|
305
327
|
}
|
|
306
328
|
|
|
307
|
-
- (void)setStateInternal:(
|
|
308
|
-
if (
|
|
329
|
+
- (void)setStateInternal:(RNBottomSheetState)state {
|
|
330
|
+
if (_state == state) {
|
|
309
331
|
return;
|
|
310
332
|
}
|
|
311
333
|
_state = state;
|
|
312
334
|
|
|
313
|
-
if (
|
|
314
|
-
|
|
315
|
-
self.onStateChanged(@{
|
|
316
|
-
@"state": state,
|
|
317
|
-
});
|
|
318
|
-
}
|
|
335
|
+
if (state == RNBottomSheetStateCollapsed || state == RNBottomSheetStateExpanded || state == RNBottomSheetStateHidden) {
|
|
336
|
+
[self.eventDispatcher sendEvent:[[RNBottomSheetStateChangedEvent alloc] initWithViewTag:self.reactTag state:state]];
|
|
319
337
|
}
|
|
320
338
|
}
|
|
321
339
|
|
|
@@ -323,15 +341,8 @@
|
|
|
323
341
|
if (top < 0 || self.maxY == 0) {
|
|
324
342
|
return;
|
|
325
343
|
}
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
self.onSlide(@{
|
|
329
|
-
@"progress": @(progress),
|
|
330
|
-
@"offset": @(top),
|
|
331
|
-
@"collapsedOffset": @(self.maxY),
|
|
332
|
-
@"expandedOffset": @(self.minY)
|
|
333
|
-
});
|
|
334
|
-
}
|
|
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]];
|
|
335
346
|
}
|
|
336
347
|
|
|
337
348
|
- (void)startWatchBottomSheetTransition {
|
|
@@ -342,9 +353,9 @@
|
|
|
342
353
|
}
|
|
343
354
|
|
|
344
355
|
- (void)stopWatchBottomSheetTransition {
|
|
345
|
-
if (
|
|
356
|
+
if (self.state == RNBottomSheetStateCollapsed) {
|
|
346
357
|
[self dispatchOnSlide:self.maxY];
|
|
347
|
-
} else if (
|
|
358
|
+
} else if (self.state == RNBottomSheetStateExpanded) {
|
|
348
359
|
[self dispatchOnSlide:self.minY];
|
|
349
360
|
}
|
|
350
361
|
if(_displayLink){
|
|
@@ -354,7 +365,7 @@
|
|
|
354
365
|
}
|
|
355
366
|
|
|
356
367
|
- (void)watchBottomSheetTransition {
|
|
357
|
-
CGFloat top = [self.layer presentationLayer].frame.origin.y;
|
|
368
|
+
CGFloat top = [self.contentView.layer presentationLayer].frame.origin.y;
|
|
358
369
|
[self dispatchOnSlide:top];
|
|
359
370
|
}
|
|
360
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
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
package/src/index.tsx
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import React from 'react'
|
|
2
|
-
import { NativeSyntheticEvent,
|
|
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 {
|
|
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
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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({
|