@lodev09/react-native-true-sheet 3.11.10 → 4.0.0-beta.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.
Files changed (73) hide show
  1. package/README.md +6 -5
  2. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContainerView.kt +23 -2
  3. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentView.kt +116 -26
  4. package/android/src/main/java/com/lodev09/truesheet/TrueSheetContentViewManager.kt +7 -0
  5. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterView.kt +48 -0
  6. package/android/src/main/java/com/lodev09/truesheet/TrueSheetFooterViewManager.kt +7 -0
  7. package/android/src/main/java/com/lodev09/truesheet/TrueSheetStateUpdater.kt +45 -0
  8. package/android/src/main/java/com/lodev09/truesheet/TrueSheetView.kt +28 -12
  9. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewController.kt +99 -34
  10. package/android/src/main/java/com/lodev09/truesheet/TrueSheetViewManager.kt +9 -5
  11. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetCoordinatorLayout.kt +12 -3
  12. package/android/src/main/java/com/lodev09/truesheet/core/TrueSheetDetentCalculator.kt +33 -4
  13. package/android/src/main/jni/TrueSheetSpec.h +2 -0
  14. package/android/src/main/jni/TrueSheetStateUpdater.cpp +96 -0
  15. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewComponentDescriptor.h +24 -0
  16. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.cpp +43 -0
  17. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewShadowNode.h +28 -0
  18. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.cpp +11 -0
  19. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetContentViewState.h +37 -0
  20. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewComponentDescriptor.h +24 -0
  21. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.cpp +56 -0
  22. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h +28 -0
  23. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.cpp +11 -0
  24. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h +41 -0
  25. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.cpp +18 -0
  26. package/common/cpp/react/renderer/components/TrueSheetSpec/TrueSheetInsets.h +16 -0
  27. package/ios/TrueSheetContainerView.h +12 -10
  28. package/ios/TrueSheetContainerView.mm +11 -29
  29. package/ios/TrueSheetContentView.h +22 -7
  30. package/ios/TrueSheetContentView.mm +143 -41
  31. package/ios/TrueSheetFooterView.h +12 -2
  32. package/ios/TrueSheetFooterView.mm +74 -71
  33. package/ios/TrueSheetHeaderView.mm +0 -1
  34. package/ios/TrueSheetView.mm +60 -27
  35. package/ios/TrueSheetViewController.h +9 -0
  36. package/ios/TrueSheetViewController.mm +288 -122
  37. package/ios/core/TrueSheetDetentCalculator.h +9 -0
  38. package/ios/core/TrueSheetDetentCalculator.mm +13 -4
  39. package/ios/tvos/TrueSheetStubs.mm +2 -0
  40. package/lib/module/TrueSheet.js +32 -13
  41. package/lib/module/TrueSheet.js.map +1 -1
  42. package/lib/module/TrueSheet.web.js +262 -38
  43. package/lib/module/TrueSheet.web.js.map +1 -1
  44. package/lib/module/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
  45. package/lib/module/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
  46. package/lib/module/fabric/TrueSheetViewNativeComponent.ts +8 -1
  47. package/lib/module/web/vaul/index.js +17 -6
  48. package/lib/module/web/vaul/index.js.map +1 -1
  49. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  50. package/lib/typescript/src/TrueSheet.types.d.ts +35 -11
  51. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  52. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  53. package/lib/typescript/src/fabric/TrueSheetContentViewNativeComponent.d.ts.map +1 -1
  54. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts +2 -0
  55. package/lib/typescript/src/fabric/TrueSheetFooterViewNativeComponent.d.ts.map +1 -1
  56. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts +5 -1
  57. package/lib/typescript/src/fabric/TrueSheetViewNativeComponent.d.ts.map +1 -1
  58. package/lib/typescript/src/navigation/types.d.ts +1 -1
  59. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  60. package/lib/typescript/src/web/vaul/index.d.ts +8 -1
  61. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -1
  62. package/package.json +10 -10
  63. package/react-native.config.js +5 -1
  64. package/src/TrueSheet.tsx +45 -14
  65. package/src/TrueSheet.types.ts +38 -12
  66. package/src/TrueSheet.web.tsx +314 -44
  67. package/src/fabric/TrueSheetContentViewNativeComponent.ts +5 -1
  68. package/src/fabric/TrueSheetFooterViewNativeComponent.ts +10 -2
  69. package/src/fabric/TrueSheetViewNativeComponent.ts +8 -1
  70. package/src/navigation/types.ts +3 -1
  71. package/src/web/vaul/index.tsx +23 -5
  72. package/ios/utils/LayoutUtil.h +0 -77
  73. package/ios/utils/LayoutUtil.mm +0 -106
@@ -0,0 +1,24 @@
1
+ #pragma once
2
+
3
+ #include <react/renderer/components/TrueSheetSpec/TrueSheetFooterViewShadowNode.h>
4
+ #include <react/renderer/core/ConcreteComponentDescriptor.h>
5
+
6
+ namespace facebook::react {
7
+
8
+ /*
9
+ * Descriptor for <TrueSheetFooterView> component.
10
+ */
11
+ class TrueSheetFooterViewComponentDescriptor final
12
+ : public ConcreteComponentDescriptor<TrueSheetFooterViewShadowNode> {
13
+ using ConcreteComponentDescriptor::ConcreteComponentDescriptor;
14
+
15
+ void adopt(ShadowNode &shadowNode) const override {
16
+ auto &concreteShadowNode =
17
+ static_cast<TrueSheetFooterViewShadowNode &>(shadowNode);
18
+ concreteShadowNode.adjustLayoutWithState();
19
+
20
+ ConcreteComponentDescriptor::adopt(shadowNode);
21
+ }
22
+ };
23
+
24
+ } // namespace facebook::react
@@ -0,0 +1,56 @@
1
+ #include "TrueSheetFooterViewShadowNode.h"
2
+
3
+ #include <react/renderer/components/view/conversions.h>
4
+
5
+ #include "TrueSheetInsets.h"
6
+
7
+ namespace facebook::react {
8
+
9
+ using namespace yoga;
10
+
11
+ extern const char TrueSheetFooterViewComponentName[] = "TrueSheetFooterView";
12
+
13
+ void TrueSheetFooterViewShadowNode::adjustLayoutWithState() {
14
+ ensureUnsealed();
15
+
16
+ auto state = std::static_pointer_cast<
17
+ const TrueSheetFooterViewShadowNode::ConcreteState>(getState());
18
+ auto stateData = state->getData();
19
+
20
+ auto &props = getConcreteProps();
21
+
22
+ // Until native pushes the real inset (which requires this node to already
23
+ // exist), seed a late-mounted footer's first layout with the precalculated
24
+ // inset — otherwise it lays out unpadded and forces a second sheet resize.
25
+ float bottomInset = stateData.bottomInset;
26
+ if (!stateData.initialized && props.autoBottomInset) {
27
+ bottomInset = TrueSheetInsets::bottomSafeArea();
28
+ }
29
+
30
+ // A footer pinned to the sheet's bottom edge owns the bottom safe-area
31
+ // inset — pad it on top of any style padding so its content clears the
32
+ // home indicator while the background fills the inset.
33
+ auto padding = props.yogaStyle.padding(Edge::Bottom);
34
+ if (bottomInset > 0) {
35
+ // Base bottom padding from the style — Edge::Bottom wins over
36
+ // Vertical/All, mirroring Yoga's own resolution. Points only.
37
+ float base = 0;
38
+ for (auto edge : {Edge::Bottom, Edge::Vertical, Edge::All}) {
39
+ auto length = props.yogaStyle.padding(edge);
40
+ if (length.isDefined()) {
41
+ base = length.isPoints() ? length.value().unwrap() : 0;
42
+ break;
43
+ }
44
+ }
45
+ padding = StyleLength::points(base + bottomInset);
46
+ }
47
+
48
+ if (yogaNode_.style().padding(Edge::Bottom) != padding) {
49
+ yoga::Style adjustedStyle = props.yogaStyle;
50
+ adjustedStyle.setPadding(Edge::Bottom, padding);
51
+ yogaNode_.setStyle(adjustedStyle);
52
+ yogaNode_.setDirty(true);
53
+ }
54
+ }
55
+
56
+ } // namespace facebook::react
@@ -0,0 +1,28 @@
1
+ #pragma once
2
+
3
+ #include <jsi/jsi.h>
4
+ #include <react/renderer/components/TrueSheetSpec/EventEmitters.h>
5
+ #include <react/renderer/components/TrueSheetSpec/Props.h>
6
+ #include <react/renderer/components/TrueSheetSpec/TrueSheetFooterViewState.h>
7
+ #include <react/renderer/components/view/ConcreteViewShadowNode.h>
8
+
9
+ namespace facebook::react {
10
+
11
+ JSI_EXPORT extern const char TrueSheetFooterViewComponentName[];
12
+
13
+ /*
14
+ * `ShadowNode` for <TrueSheetFooterView> component.
15
+ */
16
+ class JSI_EXPORT TrueSheetFooterViewShadowNode final
17
+ : public ConcreteViewShadowNode<
18
+ TrueSheetFooterViewComponentName,
19
+ TrueSheetFooterViewProps,
20
+ TrueSheetFooterViewEventEmitter,
21
+ TrueSheetFooterViewState> {
22
+ using ConcreteViewShadowNode::ConcreteViewShadowNode;
23
+
24
+ public:
25
+ void adjustLayoutWithState();
26
+ };
27
+
28
+ } // namespace facebook::react
@@ -0,0 +1,11 @@
1
+ #include "TrueSheetFooterViewState.h"
2
+
3
+ namespace facebook::react {
4
+
5
+ #ifdef ANDROID
6
+ folly::dynamic TrueSheetFooterViewState::getDynamic() const {
7
+ return folly::dynamic::object("bottomInset", bottomInset);
8
+ }
9
+ #endif
10
+
11
+ } // namespace facebook::react
@@ -0,0 +1,41 @@
1
+ #pragma once
2
+
3
+ #ifdef ANDROID
4
+ #include <folly/dynamic.h>
5
+ #include <react/renderer/mapbuffer/MapBuffer.h>
6
+ #include <react/renderer/mapbuffer/MapBufferBuilder.h>
7
+ #endif
8
+
9
+ namespace facebook::react {
10
+
11
+ /*
12
+ * State for <TrueSheetFooterView> component.
13
+ * Set from native with the sheet's bottom safe-area inset so the shadow node
14
+ * pads the footer's bottom edge (see TrueSheetFooterViewShadowNode).
15
+ */
16
+ class TrueSheetFooterViewState final {
17
+ public:
18
+ TrueSheetFooterViewState() = default;
19
+
20
+ #ifdef ANDROID
21
+ TrueSheetFooterViewState(
22
+ TrueSheetFooterViewState const &previousState,
23
+ folly::dynamic data)
24
+ : bottomInset(static_cast<float>(data["bottomInset"].getDouble())),
25
+ initialized(true) {}
26
+ #endif
27
+
28
+ float bottomInset{0};
29
+ // True once native has pushed a real inset — until then the shadow node
30
+ // seeds the layout from TrueSheetInsets (see TrueSheetFooterViewShadowNode)
31
+ bool initialized{false};
32
+
33
+ #ifdef ANDROID
34
+ folly::dynamic getDynamic() const;
35
+ MapBuffer getMapBuffer() const {
36
+ return MapBufferBuilder::EMPTY();
37
+ }
38
+ #endif
39
+ };
40
+
41
+ } // namespace facebook::react
@@ -0,0 +1,18 @@
1
+ #include "TrueSheetInsets.h"
2
+
3
+ #include <atomic>
4
+
5
+ namespace facebook::react {
6
+
7
+ // Written from the UI thread, read from the layout thread
8
+ static std::atomic<float> gBottomSafeArea{0};
9
+
10
+ void TrueSheetInsets::setBottomSafeArea(float inset) {
11
+ gBottomSafeArea.store(inset, std::memory_order_relaxed);
12
+ }
13
+
14
+ float TrueSheetInsets::bottomSafeArea() {
15
+ return gBottomSafeArea.load(std::memory_order_relaxed);
16
+ }
17
+
18
+ } // namespace facebook::react
@@ -0,0 +1,16 @@
1
+ #pragma once
2
+
3
+ namespace facebook::react {
4
+
5
+ /*
6
+ * Bottom safe-area inset published by the host platform (points/dp) so a
7
+ * late-mounted footer's first layout can be padded before any Fabric state
8
+ * lands (see TrueSheetFooterViewShadowNode).
9
+ */
10
+ class TrueSheetInsets {
11
+ public:
12
+ static void setBottomSafeArea(float inset);
13
+ static float bottomSafeArea();
14
+ };
15
+
16
+ } // namespace facebook::react
@@ -45,11 +45,6 @@ NS_ASSUME_NONNULL_BEGIN
45
45
  */
46
46
  @property (nonatomic, weak, nullable) id<TrueSheetContainerViewDelegate> delegate;
47
47
 
48
- /**
49
- * Enable scrollable content
50
- */
51
- @property (nonatomic, assign) BOOL scrollableEnabled;
52
-
53
48
  /**
54
49
  * Inset adjustment mode for scrollable content
55
50
  */
@@ -60,6 +55,18 @@ NS_ASSUME_NONNULL_BEGIN
60
55
  */
61
56
  @property (nonatomic, strong, nullable) ScrollableOptions *scrollableOptions;
62
57
 
58
+ /**
59
+ * Whether the sheet has an `auto` detent — bounds the pinned scrollable's
60
+ * viewport to the container so the sheet height can derive from its content size
61
+ */
62
+ @property (nonatomic, assign) BOOL hasAutoDetent;
63
+
64
+ /**
65
+ * Bottom safe-area inset the footer absorbs as padding — the footer
66
+ * owns the sheet's bottom edge, so its background fills the inset
67
+ */
68
+ @property (nonatomic, assign) CGFloat footerBottomInset;
69
+
63
70
  /**
64
71
  * Returns the current content height
65
72
  */
@@ -85,11 +92,6 @@ NS_ASSUME_NONNULL_BEGIN
85
92
  */
86
93
  - (void)attachPeekView:(TrueSheetPeekView *)peekView;
87
94
 
88
- /**
89
- * Updates footer layout constraints if needed
90
- */
91
- - (void)layoutFooter;
92
-
93
95
  /**
94
96
  * Re-applies the footer's keyboard slide to reflect a live `keyboardOffset` change.
95
97
  */
@@ -26,7 +26,6 @@
26
26
 
27
27
  #import <React/RCTConversions.h>
28
28
  #import <React/RCTLog.h>
29
- #import <react/renderer/core/LayoutMetrics.h>
30
29
 
31
30
  using namespace facebook::react;
32
31
 
@@ -56,7 +55,6 @@ using namespace facebook::react;
56
55
  TrueSheetFooterView *_footerView;
57
56
  TrueSheetPeekView *__weak _peekView;
58
57
  TrueSheetKeyboardObserver *_keyboardObserver;
59
- BOOL _scrollableSet;
60
58
  }
61
59
 
62
60
  #pragma mark - Initialization
@@ -74,7 +72,6 @@ using namespace facebook::react;
74
72
  _contentView = nil;
75
73
  _headerView = nil;
76
74
  _footerView = nil;
77
- _scrollableSet = NO;
78
75
  self.isAccessibilityElement = NO;
79
76
  }
80
77
  return self;
@@ -109,13 +106,8 @@ using namespace facebook::react;
109
106
 
110
107
  #pragma mark - Layout
111
108
 
112
- - (void)layoutSubviews {
113
- [super layoutSubviews];
114
- [_contentView updateScrollViewHeight];
115
- }
116
-
117
109
  - (CGFloat)contentHeight {
118
- return _contentView ? _contentView.frame.size.height : 0;
110
+ return _contentView ? _contentView.naturalHeight : 0;
119
111
  }
120
112
 
121
113
  - (CGFloat)headerHeight {
@@ -141,22 +133,13 @@ using namespace facebook::react;
141
133
  return CGRectGetMaxY([_peekView convertRect:_peekView.bounds toView:_contentView]);
142
134
  }
143
135
 
144
- - (void)layoutFooter {
145
- if (_footerView) {
146
- CGFloat height = _footerView.frame.size.height;
147
- if (height > 0) {
148
- [_footerView setupConstraintsWithHeight:height];
149
- }
150
- }
151
- }
152
-
153
136
  - (void)updateFooterKeyboardOffset {
154
137
  [_footerView applyKeyboardOffset];
155
138
  }
156
139
 
157
- - (void)setScrollableEnabled:(BOOL)scrollableEnabled {
158
- _scrollableEnabled = scrollableEnabled;
159
- _scrollableSet = YES;
140
+ - (void)setFooterBottomInset:(CGFloat)footerBottomInset {
141
+ _footerBottomInset = footerBottomInset;
142
+ [_footerView setBottomInset:footerBottomInset];
160
143
  }
161
144
 
162
145
  - (void)setScrollableOptions:(ScrollableOptions *)scrollableOptions {
@@ -165,12 +148,13 @@ using namespace facebook::react;
165
148
  }
166
149
 
167
150
  - (void)setupScrollable {
168
- if (_scrollableSet && _contentView) {
151
+ if (_contentView) {
169
152
  CGFloat bottomInset = 0;
170
153
  if (_insetAdjustment == TrueSheetViewInsetAdjustment::Automatic) {
171
154
  bottomInset = [WindowUtil keyWindow].safeAreaInsets.bottom;
172
155
  }
173
- [_contentView setupScrollable:_scrollableEnabled bottomInset:bottomInset];
156
+ _contentView.hasAutoDetent = _hasAutoDetent;
157
+ [_contentView setupScrollableWithBottomInset:bottomInset];
174
158
  [_contentView applyScrollEdgeEffects:_scrollableOptions];
175
159
  if (@available(iOS 26.0, *)) {
176
160
  [self setupEdgeInteractions];
@@ -214,6 +198,7 @@ using namespace facebook::react;
214
198
  }
215
199
  _contentView = (TrueSheetContentView *)childComponentView;
216
200
  _contentView.delegate = self;
201
+ _contentView.footerView = _footerView;
217
202
 
218
203
  // Children mount bottom-up, so the content subtree is complete here.
219
204
  // Late-mounted peek views attach themselves instead (see TrueSheetPeekView).
@@ -240,6 +225,8 @@ using namespace facebook::react;
240
225
  }
241
226
  _footerView = (TrueSheetFooterView *)childComponentView;
242
227
  _footerView.delegate = self;
228
+ _contentView.footerView = _footerView;
229
+ [_footerView setBottomInset:_footerBottomInset];
243
230
  [self footerViewDidChangeSize:_footerView.frame.size];
244
231
  }
245
232
  }
@@ -259,6 +246,7 @@ using namespace facebook::react;
259
246
  if ([childComponentView isKindOfClass:[TrueSheetFooterView class]]) {
260
247
  _footerView.delegate = nil;
261
248
  _footerView = nil;
249
+ _contentView.footerView = nil;
262
250
  [self footerViewDidChangeSize:CGSizeZero];
263
251
  }
264
252
 
@@ -269,12 +257,6 @@ using namespace facebook::react;
269
257
  [super updateProps:props oldProps:oldProps];
270
258
  }
271
259
 
272
- #pragma clang diagnostic ignored "-Wobjc-missing-super-calls"
273
- - (void)updateLayoutMetrics:(const LayoutMetrics &)layoutMetrics
274
- oldLayoutMetrics:(const LayoutMetrics &)oldLayoutMetrics {
275
- // Intentionally skip super - AutoLayout handles container's frame, not Yoga
276
- }
277
-
278
260
  #pragma mark - TrueSheetContentViewDelegate
279
261
 
280
262
  - (void)contentViewDidChangeSize:(CGSize)newSize {
@@ -15,6 +15,7 @@
15
15
  #import "core/TrueSheetKeyboardObserver.h"
16
16
 
17
17
  @class TrueSheetViewController;
18
+ @class TrueSheetFooterView;
18
19
  @class RCTScrollViewComponentView;
19
20
  @class ScrollableOptions;
20
21
 
@@ -33,19 +34,33 @@ NS_ASSUME_NONNULL_BEGIN
33
34
  @property (nonatomic, assign) CGFloat keyboardScrollOffset;
34
35
  @property (nonatomic, weak, nullable) TrueSheetKeyboardObserver *keyboardObserver;
35
36
 
36
- - (RCTScrollViewComponentView *_Nullable)findScrollView;
37
+ /**
38
+ * Sibling footer view — an absolute footer rises above the keyboard and
39
+ * occludes the content's bottom edge, so it counts toward the keyboard inset.
40
+ */
41
+ @property (nonatomic, weak, nullable) TrueSheetFooterView *footerView;
37
42
 
38
43
  /**
39
- * Setup scrollable content
40
- * @param enabled Whether scrollable is enabled
41
- * @param bottomInset Bottom content inset for the scroll view
44
+ * Whether the sheet has an `auto` detent. Deriving the sheet height from the
45
+ * scroll content is circular with natural layout, so the pinned ScrollView's
46
+ * viewport is force-bounded to the container only in this case.
47
+ */
48
+ @property (nonatomic, assign) BOOL hasAutoDetent;
49
+
50
+ /**
51
+ * Content height with the pinned ScrollView's viewport replaced by its content
52
+ * size — the height the content wants regardless of container bounds.
53
+ * Falls back to the frame height when no ScrollView is pinned.
42
54
  */
43
- - (void)setupScrollable:(BOOL)enabled bottomInset:(CGFloat)bottomInset;
55
+ @property (nonatomic, readonly) CGFloat naturalHeight;
56
+
57
+ - (RCTScrollViewComponentView *_Nullable)findScrollView;
44
58
 
45
59
  /**
46
- * Update the pinned scroll view's height to fill the container
60
+ * Pin the first ScrollView found in the content, wiring insets and keyboard handling
61
+ * @param bottomInset Bottom content inset for the scroll view
47
62
  */
48
- - (void)updateScrollViewHeight;
63
+ - (void)setupScrollableWithBottomInset:(CGFloat)bottomInset;
49
64
 
50
65
  /**
51
66
  * Apply scroll edge effects to the pinned scroll view (iOS 26+)