react-native-nitro-modal 0.2.0 → 0.3.0-rc

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 (83) hide show
  1. package/README.md +48 -4
  2. package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +335 -34
  3. package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +63 -22
  4. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +31 -1
  5. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalContentRoot.kt +48 -5
  6. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalPresenter.kt +63 -4
  7. package/android/src/main/java/com/margelo/nitro/nitromodal/NitroModalHostView.kt +68 -8
  8. package/ios/HybridNitroModal.swift +111 -20
  9. package/ios/ModalConfig.swift +44 -2
  10. package/ios/NitroModalHostView.swift +73 -8
  11. package/ios/SheetView.swift +575 -0
  12. package/ios/SheetViewController.swift +19 -450
  13. package/lib/module/NitroModal.js +38 -5
  14. package/lib/module/NitroModal.js.map +1 -1
  15. package/lib/module/NitroModalView.web.js +55 -25
  16. package/lib/module/NitroModalView.web.js.map +1 -1
  17. package/lib/module/index.js.map +1 -1
  18. package/lib/module/web/AnimatedValue.js +3 -0
  19. package/lib/module/web/AnimatedValue.js.map +1 -1
  20. package/lib/module/web/ModalConfig.js +8 -4
  21. package/lib/module/web/ModalConfig.js.map +1 -1
  22. package/lib/module/web/ModalController.js +29 -2
  23. package/lib/module/web/ModalController.js.map +1 -1
  24. package/lib/module/web/ModalPresenter.js +47 -12
  25. package/lib/module/web/ModalPresenter.js.map +1 -1
  26. package/lib/module/web/SheetPresenter.js +54 -29
  27. package/lib/module/web/SheetPresenter.js.map +1 -1
  28. package/lib/module/web/dom.js +13 -0
  29. package/lib/module/web/dom.js.map +1 -1
  30. package/lib/typescript/src/NitroModal.d.ts +33 -2
  31. package/lib/typescript/src/NitroModal.d.ts.map +1 -1
  32. package/lib/typescript/src/NitroModal.nitro.d.ts +29 -2
  33. package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -1
  34. package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -1
  35. package/lib/typescript/src/index.d.ts +2 -2
  36. package/lib/typescript/src/index.d.ts.map +1 -1
  37. package/lib/typescript/src/web/AnimatedValue.d.ts +1 -0
  38. package/lib/typescript/src/web/AnimatedValue.d.ts.map +1 -1
  39. package/lib/typescript/src/web/ModalConfig.d.ts +6 -0
  40. package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -1
  41. package/lib/typescript/src/web/ModalController.d.ts +5 -0
  42. package/lib/typescript/src/web/ModalController.d.ts.map +1 -1
  43. package/lib/typescript/src/web/ModalPresenter.d.ts +9 -0
  44. package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -1
  45. package/lib/typescript/src/web/SheetPresenter.d.ts +1 -1
  46. package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -1
  47. package/lib/typescript/src/web/dom.d.ts +2 -0
  48. package/lib/typescript/src/web/dom.d.ts.map +1 -1
  49. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +60 -6
  50. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +10 -2
  51. package/nitrogen/generated/android/c++/JNamedSheetDetent.hpp +64 -0
  52. package/nitrogen/generated/android/c++/JSheetDetent.cpp +26 -0
  53. package/nitrogen/generated/android/c++/JSheetDetent.hpp +42 -37
  54. package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +20 -0
  55. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +40 -0
  56. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/NamedSheetDetent.kt +25 -0
  57. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/SheetDetent.kt +47 -10
  58. package/nitrogen/generated/android/nitromodal+autolinking.cmake +1 -0
  59. package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.hpp +53 -8
  60. package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Umbrella.hpp +4 -3
  61. package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +32 -5
  62. package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +24 -0
  63. package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +4 -0
  64. package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +110 -5
  65. package/nitrogen/generated/ios/swift/NamedSheetDetent.swift +48 -0
  66. package/nitrogen/generated/ios/swift/SheetDetent.swift +15 -33
  67. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +8 -0
  68. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +14 -5
  69. package/nitrogen/generated/shared/c++/{SheetDetent.hpp → NamedSheetDetent.hpp} +17 -17
  70. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +9 -1
  71. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +15 -2
  72. package/nitrogen/generated/shared/json/NitroModalConfig.json +4 -0
  73. package/package.json +1 -1
  74. package/src/NitroModal.nitro.ts +30 -2
  75. package/src/NitroModal.tsx +68 -4
  76. package/src/NitroModalView.web.tsx +54 -12
  77. package/src/index.tsx +2 -0
  78. package/src/web/AnimatedValue.ts +4 -0
  79. package/src/web/ModalConfig.ts +13 -3
  80. package/src/web/ModalController.ts +35 -2
  81. package/src/web/ModalPresenter.ts +46 -11
  82. package/src/web/SheetPresenter.ts +98 -33
  83. package/src/web/dom.ts +17 -0
@@ -1,5 +1,5 @@
1
1
  ///
2
- /// SheetDetent.hpp
2
+ /// NamedSheetDetent.hpp
3
3
  /// This file was generated by nitrogen. DO NOT MODIFY THIS FILE.
4
4
  /// https://github.com/mrousavy/nitro
5
5
  /// Copyright © Marc Rousavy @ Margelo
@@ -26,9 +26,9 @@
26
26
  namespace margelo::nitro::nitromodal {
27
27
 
28
28
  /**
29
- * An enum which can be represented as a JavaScript union (SheetDetent).
29
+ * An enum which can be represented as a JavaScript union (NamedSheetDetent).
30
30
  */
31
- enum class SheetDetent {
31
+ enum class NamedSheetDetent {
32
32
  SMALL SWIFT_NAME(small) = 0,
33
33
  MEDIUM SWIFT_NAME(medium) = 1,
34
34
  LARGE SWIFT_NAME(large) = 2,
@@ -39,28 +39,28 @@ namespace margelo::nitro::nitromodal {
39
39
 
40
40
  namespace margelo::nitro {
41
41
 
42
- // C++ SheetDetent <> JS SheetDetent (union)
42
+ // C++ NamedSheetDetent <> JS NamedSheetDetent (union)
43
43
  template <>
44
- struct JSIConverter<margelo::nitro::nitromodal::SheetDetent> final {
45
- static inline margelo::nitro::nitromodal::SheetDetent fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
44
+ struct JSIConverter<margelo::nitro::nitromodal::NamedSheetDetent> final {
45
+ static inline margelo::nitro::nitromodal::NamedSheetDetent fromJSI(jsi::Runtime& runtime, const jsi::Value& arg) {
46
46
  std::string unionValue = JSIConverter<std::string>::fromJSI(runtime, arg);
47
47
  switch (hashString(unionValue.c_str(), unionValue.size())) {
48
- case hashString("small"): return margelo::nitro::nitromodal::SheetDetent::SMALL;
49
- case hashString("medium"): return margelo::nitro::nitromodal::SheetDetent::MEDIUM;
50
- case hashString("large"): return margelo::nitro::nitromodal::SheetDetent::LARGE;
51
- case hashString("fitContent"): return margelo::nitro::nitromodal::SheetDetent::FITCONTENT;
48
+ case hashString("small"): return margelo::nitro::nitromodal::NamedSheetDetent::SMALL;
49
+ case hashString("medium"): return margelo::nitro::nitromodal::NamedSheetDetent::MEDIUM;
50
+ case hashString("large"): return margelo::nitro::nitromodal::NamedSheetDetent::LARGE;
51
+ case hashString("fitContent"): return margelo::nitro::nitromodal::NamedSheetDetent::FITCONTENT;
52
52
  default: [[unlikely]]
53
- throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum SheetDetent - invalid value!");
53
+ throw std::invalid_argument("Cannot convert \"" + unionValue + "\" to enum NamedSheetDetent - invalid value!");
54
54
  }
55
55
  }
56
- static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::nitromodal::SheetDetent arg) {
56
+ static inline jsi::Value toJSI(jsi::Runtime& runtime, margelo::nitro::nitromodal::NamedSheetDetent arg) {
57
57
  switch (arg) {
58
- case margelo::nitro::nitromodal::SheetDetent::SMALL: return JSIConverter<std::string>::toJSI(runtime, "small");
59
- case margelo::nitro::nitromodal::SheetDetent::MEDIUM: return JSIConverter<std::string>::toJSI(runtime, "medium");
60
- case margelo::nitro::nitromodal::SheetDetent::LARGE: return JSIConverter<std::string>::toJSI(runtime, "large");
61
- case margelo::nitro::nitromodal::SheetDetent::FITCONTENT: return JSIConverter<std::string>::toJSI(runtime, "fitContent");
58
+ case margelo::nitro::nitromodal::NamedSheetDetent::SMALL: return JSIConverter<std::string>::toJSI(runtime, "small");
59
+ case margelo::nitro::nitromodal::NamedSheetDetent::MEDIUM: return JSIConverter<std::string>::toJSI(runtime, "medium");
60
+ case margelo::nitro::nitromodal::NamedSheetDetent::LARGE: return JSIConverter<std::string>::toJSI(runtime, "large");
61
+ case margelo::nitro::nitromodal::NamedSheetDetent::FITCONTENT: return JSIConverter<std::string>::toJSI(runtime, "fitContent");
62
62
  default: [[unlikely]]
63
- throw std::invalid_argument("Cannot convert SheetDetent to JS - invalid value: "
63
+ throw std::invalid_argument("Cannot convert NamedSheetDetent to JS - invalid value: "
64
64
  + std::to_string(static_cast<int>(arg)) + "!");
65
65
  }
66
66
  }
@@ -22,7 +22,8 @@ namespace margelo::nitro::nitromodal::views {
22
22
  react::ViewProps(context, sourceProps, rawProps, filterObjectKeys),
23
23
  isOpen(nitro::ReactProp<bool>::fromRawValue("NitroModal", "isOpen", rawProps, sourceProps.isOpen)),
24
24
  mode(nitro::ReactProp<ModalMode>::fromRawValue("NitroModal", "mode", rawProps, sourceProps.mode)),
25
- detents(nitro::ReactProp<std::vector<SheetDetent>>::fromRawValue("NitroModal", "detents", rawProps, sourceProps.detents)),
25
+ isInline(nitro::ReactProp<bool>::fromRawValue("NitroModal", "isInline", rawProps, sourceProps.isInline)),
26
+ detents(nitro::ReactProp<std::vector<std::variant<NamedSheetDetent, double>>>::fromRawValue("NitroModal", "detents", rawProps, sourceProps.detents)),
26
27
  initialDetentIndex(nitro::ReactProp<double>::fromRawValue("NitroModal", "initialDetentIndex", rawProps, sourceProps.initialDetentIndex)),
27
28
  backdropColor(nitro::ReactProp<double>::fromRawValue("NitroModal", "backdropColor", rawProps, sourceProps.backdropColor)),
28
29
  backdropOpacity(nitro::ReactProp<double>::fromRawValue("NitroModal", "backdropOpacity", rawProps, sourceProps.backdropOpacity)),
@@ -35,18 +36,22 @@ namespace margelo::nitro::nitromodal::views {
35
36
  contentBackgroundColor(nitro::ReactProp<std::optional<double>>::fromRawValue("NitroModal", "contentBackgroundColor", rawProps, sourceProps.contentBackgroundColor)),
36
37
  keyboardBehavior(nitro::ReactProp<KeyboardBehavior>::fromRawValue("NitroModal", "keyboardBehavior", rawProps, sourceProps.keyboardBehavior)),
37
38
  popupAnimation(nitro::ReactProp<PopupAnimation>::fromRawValue("NitroModal", "popupAnimation", rawProps, sourceProps.popupAnimation)),
39
+ pullToRefreshEnabled(nitro::ReactProp<bool>::fromRawValue("NitroModal", "pullToRefreshEnabled", rawProps, sourceProps.pullToRefreshEnabled)),
38
40
  onPresent(nitro::ReactProp<std::optional<std::function<void()>>>::fromRawValue("NitroModal", "onPresent", rawProps, sourceProps.onPresent)),
39
41
  onDismiss(nitro::ReactProp<std::optional<std::function<void(DismissReason /* reason */)>>>::fromRawValue("NitroModal", "onDismiss", rawProps, sourceProps.onDismiss)),
40
42
  onDetentChange(nitro::ReactProp<std::optional<std::function<void(double /* index */)>>>::fromRawValue("NitroModal", "onDetentChange", rawProps, sourceProps.onDetentChange)),
41
43
  onBackdropPress(nitro::ReactProp<std::optional<std::function<void()>>>::fromRawValue("NitroModal", "onBackdropPress", rawProps, sourceProps.onBackdropPress)),
42
44
  onBackButtonPress(nitro::ReactProp<std::optional<std::function<void()>>>::fromRawValue("NitroModal", "onBackButtonPress", rawProps, sourceProps.onBackButtonPress)),
43
45
  onContentAreaChange(nitro::ReactProp<std::optional<std::function<void(const ModalContentArea& /* area */)>>>::fromRawValue("NitroModal", "onContentAreaChange", rawProps, sourceProps.onContentAreaChange)),
46
+ onPullToRefresh(nitro::ReactProp<std::optional<std::function<void()>>>::fromRawValue("NitroModal", "onPullToRefresh", rawProps, sourceProps.onPullToRefresh)),
47
+ onRestingTopChange(nitro::ReactProp<std::optional<std::function<void(double /* top */)>>>::fromRawValue("NitroModal", "onRestingTopChange", rawProps, sourceProps.onRestingTopChange)),
44
48
  hybridRef(nitro::ReactProp<std::optional<std::function<void(const std::shared_ptr<HybridNitroModalSpec>& /* ref */)>>>::fromRawValue("NitroModal", "hybridRef", rawProps, sourceProps.hybridRef)) { }
45
49
 
46
50
  bool HybridNitroModalProps::filterObjectKeys(const std::string& propName) {
47
51
  switch (hashString(propName)) {
48
52
  case hashString("isOpen"): return true;
49
53
  case hashString("mode"): return true;
54
+ case hashString("isInline"): return true;
50
55
  case hashString("detents"): return true;
51
56
  case hashString("initialDetentIndex"): return true;
52
57
  case hashString("backdropColor"): return true;
@@ -60,12 +65,15 @@ namespace margelo::nitro::nitromodal::views {
60
65
  case hashString("contentBackgroundColor"): return true;
61
66
  case hashString("keyboardBehavior"): return true;
62
67
  case hashString("popupAnimation"): return true;
68
+ case hashString("pullToRefreshEnabled"): return true;
63
69
  case hashString("onPresent"): return true;
64
70
  case hashString("onDismiss"): return true;
65
71
  case hashString("onDetentChange"): return true;
66
72
  case hashString("onBackdropPress"): return true;
67
73
  case hashString("onBackButtonPress"): return true;
68
74
  case hashString("onContentAreaChange"): return true;
75
+ case hashString("onPullToRefresh"): return true;
76
+ case hashString("onRestingTopChange"): return true;
69
77
  case hashString("hybridRef"): return true;
70
78
  default: return false;
71
79
  }
@@ -18,7 +18,8 @@
18
18
  #include <string>
19
19
 
20
20
  #include "ModalMode.hpp"
21
- #include "SheetDetent.hpp"
21
+ #include "NamedSheetDetent.hpp"
22
+ #include <variant>
22
23
  #include <vector>
23
24
  #include <optional>
24
25
  #include "KeyboardBehavior.hpp"
@@ -51,7 +52,8 @@ namespace margelo::nitro::nitromodal::views {
51
52
  public:
52
53
  nitro::ReactProp<bool> isOpen;
53
54
  nitro::ReactProp<ModalMode> mode;
54
- nitro::ReactProp<std::vector<SheetDetent>> detents;
55
+ nitro::ReactProp<bool> isInline;
56
+ nitro::ReactProp<std::vector<std::variant<NamedSheetDetent, double>>> detents;
55
57
  nitro::ReactProp<double> initialDetentIndex;
56
58
  nitro::ReactProp<double> backdropColor;
57
59
  nitro::ReactProp<double> backdropOpacity;
@@ -64,18 +66,22 @@ namespace margelo::nitro::nitromodal::views {
64
66
  nitro::ReactProp<std::optional<double>> contentBackgroundColor;
65
67
  nitro::ReactProp<KeyboardBehavior> keyboardBehavior;
66
68
  nitro::ReactProp<PopupAnimation> popupAnimation;
69
+ nitro::ReactProp<bool> pullToRefreshEnabled;
67
70
  nitro::ReactProp<std::optional<std::function<void()>>> onPresent;
68
71
  nitro::ReactProp<std::optional<std::function<void(DismissReason /* reason */)>>> onDismiss;
69
72
  nitro::ReactProp<std::optional<std::function<void(double /* index */)>>> onDetentChange;
70
73
  nitro::ReactProp<std::optional<std::function<void()>>> onBackdropPress;
71
74
  nitro::ReactProp<std::optional<std::function<void()>>> onBackButtonPress;
72
75
  nitro::ReactProp<std::optional<std::function<void(const ModalContentArea& /* area */)>>> onContentAreaChange;
76
+ nitro::ReactProp<std::optional<std::function<void()>>> onPullToRefresh;
77
+ nitro::ReactProp<std::optional<std::function<void(double /* top */)>>> onRestingTopChange;
73
78
  nitro::ReactProp<std::optional<std::function<void(const std::shared_ptr<HybridNitroModalSpec>& /* ref */)>>> hybridRef;
74
79
 
75
80
  [[nodiscard]]
76
81
  bool hasSameProps(const HybridNitroModalProps& other) const noexcept {
77
82
  return isOpen.hasSameValue(other.isOpen) &&
78
83
  mode.hasSameValue(other.mode) &&
84
+ isInline.hasSameValue(other.isInline) &&
79
85
  detents.hasSameValue(other.detents) &&
80
86
  initialDetentIndex.hasSameValue(other.initialDetentIndex) &&
81
87
  backdropColor.hasSameValue(other.backdropColor) &&
@@ -89,12 +95,15 @@ namespace margelo::nitro::nitromodal::views {
89
95
  contentBackgroundColor.hasSameValue(other.contentBackgroundColor) &&
90
96
  keyboardBehavior.hasSameValue(other.keyboardBehavior) &&
91
97
  popupAnimation.hasSameValue(other.popupAnimation) &&
98
+ pullToRefreshEnabled.hasSameValue(other.pullToRefreshEnabled) &&
92
99
  onPresent.hasSameValue(other.onPresent) &&
93
100
  onDismiss.hasSameValue(other.onDismiss) &&
94
101
  onDetentChange.hasSameValue(other.onDetentChange) &&
95
102
  onBackdropPress.hasSameValue(other.onBackdropPress) &&
96
103
  onBackButtonPress.hasSameValue(other.onBackButtonPress) &&
97
104
  onContentAreaChange.hasSameValue(other.onContentAreaChange) &&
105
+ onPullToRefresh.hasSameValue(other.onPullToRefresh) &&
106
+ onRestingTopChange.hasSameValue(other.onRestingTopChange) &&
98
107
  hybridRef.hasSameValue(other.hybridRef);
99
108
  }
100
109
 
@@ -102,6 +111,7 @@ namespace margelo::nitro::nitromodal::views {
102
111
  bool hasAnyProvidedProps() const noexcept {
103
112
  return isOpen.isProvided() ||
104
113
  mode.isProvided() ||
114
+ isInline.isProvided() ||
105
115
  detents.isProvided() ||
106
116
  initialDetentIndex.isProvided() ||
107
117
  backdropColor.isProvided() ||
@@ -115,12 +125,15 @@ namespace margelo::nitro::nitromodal::views {
115
125
  contentBackgroundColor.isProvided() ||
116
126
  keyboardBehavior.isProvided() ||
117
127
  popupAnimation.isProvided() ||
128
+ pullToRefreshEnabled.isProvided() ||
118
129
  onPresent.isProvided() ||
119
130
  onDismiss.isProvided() ||
120
131
  onDetentChange.isProvided() ||
121
132
  onBackdropPress.isProvided() ||
122
133
  onBackButtonPress.isProvided() ||
123
134
  onContentAreaChange.isProvided() ||
135
+ onPullToRefresh.isProvided() ||
136
+ onRestingTopChange.isProvided() ||
124
137
  hybridRef.isProvided();
125
138
  }
126
139
 
@@ -6,6 +6,7 @@
6
6
  "validAttributes": {
7
7
  "isOpen": true,
8
8
  "mode": true,
9
+ "isInline": true,
9
10
  "detents": true,
10
11
  "initialDetentIndex": true,
11
12
  "backdropColor": true,
@@ -19,12 +20,15 @@
19
20
  "contentBackgroundColor": true,
20
21
  "keyboardBehavior": true,
21
22
  "popupAnimation": true,
23
+ "pullToRefreshEnabled": true,
22
24
  "onPresent": true,
23
25
  "onDismiss": true,
24
26
  "onDetentChange": true,
25
27
  "onBackdropPress": true,
26
28
  "onBackButtonPress": true,
27
29
  "onContentAreaChange": true,
30
+ "onPullToRefresh": true,
31
+ "onRestingTopChange": true,
28
32
  "hybridRef": true
29
33
  }
30
34
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-modal",
3
- "version": "0.2.0",
3
+ "version": "0.3.0-rc",
4
4
  "description": "react native native modal",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -13,13 +13,20 @@ import type {
13
13
  export type ModalMode = 'bottomSheet' | 'popup';
14
14
 
15
15
  /**
16
- * Resting heights of a `bottomSheet`.
16
+ * Named resting heights of a `bottomSheet`.
17
17
  * - `small`: ~25% of the available height.
18
18
  * - `medium`: ~50% of the available height.
19
19
  * - `large`: the full available height.
20
20
  * - `fitContent`: the measured height of the React content.
21
21
  */
22
- export type SheetDetent = 'small' | 'medium' | 'large' | 'fitContent';
22
+ export type NamedSheetDetent = 'small' | 'medium' | 'large' | 'fitContent';
23
+
24
+ /**
25
+ * A resting height of a `bottomSheet`: a named height, or the height
26
+ * (dp/pt) of the content above the bottom safe area, clamped to the
27
+ * available height.
28
+ */
29
+ export type SheetDetent = NamedSheetDetent | number;
23
30
 
24
31
  /**
25
32
  * How the modal reacts to the software keyboard.
@@ -48,6 +55,12 @@ export interface NitroModalProps extends HybridViewProps {
48
55
  /** Whether the modal should be presented. */
49
56
  isOpen: boolean;
50
57
  mode: ModalMode;
58
+ /**
59
+ * A `bottomSheet` that lives inside the host view's own bounds instead of
60
+ * being presented over the app: no backdrop, touches outside the sheet
61
+ * reach the views behind it, and the user can't dismiss it.
62
+ */
63
+ isInline: boolean;
51
64
  /** Detents of a `bottomSheet`, smallest first. At most 3 are used on Android. */
52
65
  detents: SheetDetent[];
53
66
  /** Index into `detents` that the sheet opens at. */
@@ -68,6 +81,11 @@ export interface NitroModalProps extends HybridViewProps {
68
81
  contentBackgroundColor?: number;
69
82
  keyboardBehavior: KeyboardBehavior;
70
83
  popupAnimation: PopupAnimation;
84
+ /**
85
+ * Pulling a `bottomSheet` down past its lowest detent calls
86
+ * `onPullToRefresh`. Needs `dismissOnSwipe` off (always the case inline).
87
+ */
88
+ pullToRefreshEnabled: boolean;
71
89
 
72
90
  /** The present transition finished. */
73
91
  onPresent?: () => void;
@@ -80,6 +98,16 @@ export interface NitroModalProps extends HybridViewProps {
80
98
  onBackButtonPress?: () => void;
81
99
  /** The area available to the content changed (rotation, keyboard, sheet sizing). */
82
100
  onContentAreaChange?: (area: ModalContentArea) => void;
101
+ /**
102
+ * The user pulled the sheet down past its lowest detent and let go. The
103
+ * gesture must start with the sheet resting on that detent.
104
+ */
105
+ onPullToRefresh?: () => void;
106
+ /**
107
+ * Inline sheets: where the sheet's top rests within the host view (or
108
+ * will, once it settles). Lets React lay the content out where it is shown.
109
+ */
110
+ onRestingTopChange?: (top: number) => void;
83
111
  }
84
112
 
85
113
  export interface NitroModalMethods extends HybridViewMethods {
@@ -21,12 +21,22 @@ import type {
21
21
  KeyboardBehavior,
22
22
  ModalContentArea,
23
23
  ModalMode,
24
+ NamedSheetDetent,
24
25
  NitroModal as NitroModalHybridView,
25
26
  PopupAnimation,
26
27
  SheetDetent,
27
28
  } from './NitroModal.nitro';
28
29
  import { callback, NitroModalView } from './NitroModalView';
29
30
 
31
+ /**
32
+ * Where a `bottomSheet` lives.
33
+ * - `modal`: presented over the whole app, with a backdrop.
34
+ * - `inline`: inside the component's own frame (`style`), as part of your
35
+ * screen. No backdrop, the views behind it stay interactive, and the user
36
+ * can't dismiss it.
37
+ */
38
+ export type ModalPresentation = 'modal' | 'inline';
39
+
30
40
  export interface NitroModalRef {
31
41
  /** Opens the modal. Only for uncontrolled usage (no `isOpen` prop). */
32
42
  present(): void;
@@ -45,7 +55,23 @@ export interface NitroModalProps {
45
55
  isOpen?: boolean;
46
56
  /** @default 'bottomSheet' */
47
57
  mode?: ModalMode;
48
- /** Sheet heights, smallest first. Android uses at most three. @default ['fitContent'] */
58
+ /**
59
+ * `inline` keeps a `bottomSheet` inside this component's frame, like a
60
+ * persistent sheet that is part of the screen. Popups are always modal.
61
+ * @default 'modal'
62
+ */
63
+ presentation?: ModalPresentation;
64
+ /**
65
+ * Inline only: the frame the sheet lives in, laid out like any view.
66
+ * The sheet rests against its bottom edge and its largest detent reaches its
67
+ * top edge. @default StyleSheet.absoluteFill
68
+ */
69
+ style?: StyleProp<ViewStyle>;
70
+ /**
71
+ * Sheet heights, smallest first: named sizes, or the content height in
72
+ * dp/pt above the bottom safe area. Android uses at most three.
73
+ * @default ['fitContent']
74
+ */
49
75
  detents?: SheetDetent[];
50
76
  /** @default 0 */
51
77
  initialDetentIndex?: number;
@@ -78,6 +104,13 @@ export interface NitroModalProps {
78
104
  onDetentChange?: (index: number) => void;
79
105
  onBackdropPress?: () => void;
80
106
  onBackButtonPress?: () => void;
107
+ /**
108
+ * Pull-to-refresh: the user dragged the sheet down past its lowest detent
109
+ * and let go. Only for sheets that can't be swiped away (inline, or
110
+ * `dismissOnSwipe={false}`), and only from a gesture that starts with the
111
+ * sheet resting on its lowest detent.
112
+ */
113
+ onPullToRefresh?: () => void;
81
114
  children?: ReactNode;
82
115
  testID?: string;
83
116
  ref?: Ref<NitroModalRef>;
@@ -86,6 +119,13 @@ export interface NitroModalProps {
86
119
  const DEFAULT_DETENTS: SheetDetent[] = ['fitContent'];
87
120
  const BLACK = 0xff000000;
88
121
 
122
+ function parseDetent(value: string): SheetDetent {
123
+ const points = Number(value);
124
+ return value !== '' && Number.isFinite(points)
125
+ ? points
126
+ : (value as NamedSheetDetent);
127
+ }
128
+
89
129
  function toNativeColor(color: ColorValue | undefined): number | undefined {
90
130
  if (color == null) return undefined;
91
131
  const processed = processColor(color);
@@ -96,6 +136,8 @@ function toNativeColor(color: ColorValue | undefined): number | undefined {
96
136
  export function NitroModal({
97
137
  isOpen,
98
138
  mode = 'bottomSheet',
139
+ presentation = 'modal',
140
+ style,
99
141
  detents = DEFAULT_DETENTS,
100
142
  initialDetentIndex = 0,
101
143
  backdropColor = 'black',
@@ -115,11 +157,13 @@ export function NitroModal({
115
157
  onDetentChange,
116
158
  onBackdropPress,
117
159
  onBackButtonPress,
160
+ onPullToRefresh,
118
161
  children,
119
162
  testID,
120
163
  ref,
121
164
  }: NitroModalProps) {
122
165
  const isControlled = isOpen !== undefined;
166
+ const isInline = presentation === 'inline' && mode === 'bottomSheet';
123
167
  const [internalOpen, setInternalOpen] = useState(false);
124
168
  const open = isControlled ? isOpen : internalOpen;
125
169
 
@@ -134,6 +178,10 @@ export function NitroModal({
134
178
  const renderContent = open || sessionActive;
135
179
 
136
180
  const [area, setArea] = useState<ModalContentArea | null>(null);
181
+ // Inline (iOS/Android): where the sheet's top rests in the host. The content
182
+ // is laid out there so `measure()`, which presses rely on, matches the
183
+ // screen; native cancels the offset out on screen.
184
+ const [restingTop, setRestingTop] = useState(0);
137
185
  const hybridRef = useRef<NitroModalHybridView | null>(null);
138
186
 
139
187
  // Native callbacks are created once (stable props, no native updates on
@@ -145,6 +193,7 @@ export function NitroModal({
145
193
  onDetentChange,
146
194
  onBackdropPress,
147
195
  onBackButtonPress,
196
+ onPullToRefresh,
148
197
  });
149
198
  useLayoutEffect(() => {
150
199
  latest.current = {
@@ -154,6 +203,7 @@ export function NitroModal({
154
203
  onDetentChange,
155
204
  onBackdropPress,
156
205
  onBackButtonPress,
206
+ onPullToRefresh,
157
207
  };
158
208
  });
159
209
 
@@ -180,6 +230,12 @@ export function NitroModal({
180
230
  onBackButtonPress: callback(() => {
181
231
  latest.current.onBackButtonPress?.();
182
232
  }),
233
+ onPullToRefresh: callback(() => {
234
+ latest.current.onPullToRefresh?.();
235
+ }),
236
+ onRestingTopChange: callback((top: number) => {
237
+ setRestingTop(top);
238
+ }),
183
239
  onContentAreaChange: callback((next: ModalContentArea) => {
184
240
  setArea((current) =>
185
241
  current?.width === next.width && current.height === next.height
@@ -220,7 +276,7 @@ export function NitroModal({
220
276
  // Arrays are diffed by identity, so keep one instance per distinct value.
221
277
  const detentsKey = detents.join(',');
222
278
  const nativeDetents = useMemo(
223
- () => [...new Set(detentsKey.split(','))] as SheetDetent[],
279
+ () => [...new Set(detentsKey.split(','))].map(parseDetent),
224
280
  [detentsKey]
225
281
  );
226
282
 
@@ -240,9 +296,11 @@ export function NitroModal({
240
296
 
241
297
  return (
242
298
  <NitroModalView
243
- style={styles.host}
299
+ style={isInline ? [StyleSheet.absoluteFill, style] : styles.host}
300
+ pointerEvents={isInline ? 'box-none' : undefined}
244
301
  isOpen={open}
245
302
  mode={mode}
303
+ isInline={isInline}
246
304
  detents={nativeDetents}
247
305
  initialDetentIndex={initialDetentIndex}
248
306
  backdropColor={toNativeColor(backdropColor) ?? BLACK}
@@ -256,13 +314,19 @@ export function NitroModal({
256
314
  contentBackgroundColor={toNativeColor(backgroundColor)}
257
315
  keyboardBehavior={keyboardBehavior}
258
316
  popupAnimation={popupAnimation}
317
+ pullToRefreshEnabled={onPullToRefresh != null}
259
318
  {...nativeCallbacks}
260
319
  >
261
320
  {renderContent ? (
262
321
  <View
263
322
  collapsable={false}
264
323
  testID={testID}
265
- style={[styles.container, containerStyle, contentContainerStyle]}
324
+ style={[
325
+ styles.container,
326
+ containerStyle,
327
+ contentContainerStyle,
328
+ isInline && restingTop > 0 ? { top: restingTop } : null,
329
+ ]}
266
330
  >
267
331
  {children}
268
332
  </View>
@@ -1,19 +1,23 @@
1
1
  /// <reference lib="dom" />
2
2
 
3
3
  import {
4
+ useCallback,
4
5
  useLayoutEffect,
5
6
  useRef,
6
7
  useState,
7
8
  type CSSProperties,
8
9
  type ReactNode,
9
10
  } from 'react';
10
- import { Modal } from 'react-native';
11
+ import { Modal, View, type StyleProp, type ViewStyle } from 'react-native';
11
12
  import type { ReactNativeView } from 'react-native-nitro-modules';
12
13
  import type { NitroModalMethods, NitroModalProps } from './NitroModal.nitro';
14
+ import { clipOverflow } from './web/dom';
13
15
  import { ModalController } from './web/ModalController';
14
16
 
15
17
  interface WebNitroModalViewProps extends NitroModalProps {
16
18
  hybridRef?: (instance: NitroModalMethods) => void;
19
+ /** Inline: the frame the sheet fills. */
20
+ style?: StyleProp<ViewStyle>;
17
21
  children?: ReactNode;
18
22
  }
19
23
 
@@ -27,9 +31,13 @@ export function callback<T>(func: T): T {
27
31
  * Native Web's `Modal` (a portal with focus trapping, Escape handling and
28
32
  * stacking with other modals) and is laid out and animated by the presenters
29
33
  * in `./web`. In place, it renders nothing.
34
+ *
35
+ * An inline sheet renders in place instead: a view laid out with `style`,
36
+ * holding the sheet's elements.
30
37
  */
31
38
  function WebNitroModalView(props: WebNitroModalViewProps) {
32
39
  const { hybridRef, children } = props;
40
+ const isInline = props.isInline && props.mode === 'bottomSheet';
33
41
  const [mounted, setMounted] = useState(false);
34
42
  const [controller] = useState(() => new ModalController(setMounted));
35
43
  const overlay = useRef<HTMLDivElement>(null);
@@ -69,10 +77,46 @@ function WebNitroModalView(props: WebNitroModalViewProps) {
69
77
  }
70
78
  });
71
79
 
72
- if (!mounted) return null;
80
+ const hostRef = useCallback(
81
+ (node: unknown) => {
82
+ controller.hostDidChange(node instanceof HTMLElement ? node : null);
83
+ },
84
+ [controller]
85
+ );
73
86
 
74
87
  // Styles set here never change; the presenters write sizes, positions,
75
88
  // colors and animated values straight to the elements.
89
+ const layer = mounted ? (
90
+ // Focusable so the modal's focus trap lands here first rather than on the first input.
91
+ <div
92
+ ref={overlay}
93
+ tabIndex={-1}
94
+ style={
95
+ isInline
96
+ ? { ...styles.inlineOverlay, overflow: clipOverflow() }
97
+ : styles.overlay
98
+ }
99
+ >
100
+ <div ref={backdrop} aria-hidden style={styles.backdrop} />
101
+ <div ref={surface} style={styles.surface}>
102
+ <div ref={content} style={styles.content}>
103
+ {children}
104
+ </div>
105
+ <div ref={grabber} aria-hidden style={styles.grabber} />
106
+ </div>
107
+ </div>
108
+ ) : null;
109
+
110
+ if (isInline) {
111
+ return (
112
+ <View ref={hostRef} style={props.style} pointerEvents="box-none">
113
+ {layer}
114
+ </View>
115
+ );
116
+ }
117
+
118
+ if (!layer) return null;
119
+
76
120
  return (
77
121
  <Modal
78
122
  visible
@@ -80,16 +124,7 @@ function WebNitroModalView(props: WebNitroModalViewProps) {
80
124
  animationType="none"
81
125
  onRequestClose={controller.handleBackPress}
82
126
  >
83
- {/* Focusable so the modal's focus trap lands here first rather than on the first input. */}
84
- <div ref={overlay} tabIndex={-1} style={styles.overlay}>
85
- <div ref={backdrop} aria-hidden style={styles.backdrop} />
86
- <div ref={surface} style={styles.surface}>
87
- <div ref={content} style={styles.content}>
88
- {children}
89
- </div>
90
- <div ref={grabber} aria-hidden style={styles.grabber} />
91
- </div>
92
- </div>
127
+ {layer}
93
128
  </Modal>
94
129
  );
95
130
  }
@@ -100,6 +135,13 @@ const styles = {
100
135
  inset: 0,
101
136
  outline: 'none',
102
137
  },
138
+ // Fills the host view; the sheet hides below its bottom edge.
139
+ inlineOverlay: {
140
+ position: 'absolute',
141
+ inset: 0,
142
+ outline: 'none',
143
+ pointerEvents: 'none',
144
+ },
103
145
  backdrop: {
104
146
  position: 'absolute',
105
147
  inset: 0,
package/src/index.tsx CHANGED
@@ -1,5 +1,6 @@
1
1
  export {
2
2
  NitroModal,
3
+ type ModalPresentation,
3
4
  type NitroModalProps,
4
5
  type NitroModalRef,
5
6
  } from './NitroModal';
@@ -7,6 +8,7 @@ export type {
7
8
  DismissReason,
8
9
  KeyboardBehavior,
9
10
  ModalMode,
11
+ NamedSheetDetent,
10
12
  PopupAnimation,
11
13
  SheetDetent,
12
14
  } from './NitroModal.nitro';
@@ -71,6 +71,10 @@ export class AnimatedValue {
71
71
  return this.current;
72
72
  }
73
73
 
74
+ get isAnimating(): boolean {
75
+ return this.run != null;
76
+ }
77
+
74
78
  /** Jumps to `value`, cancelling any animation. */
75
79
  set(value: number) {
76
80
  this.stop();
@@ -9,6 +9,11 @@ import type {
9
9
  /** Snapshot of the props that shape a presentation. */
10
10
  export interface ModalConfig {
11
11
  mode: ModalMode;
12
+ /**
13
+ * A `bottomSheet` rendered in place, filling the host view: no backdrop,
14
+ * the page behind stays interactive, and the user can't dismiss it.
15
+ */
16
+ isInline: boolean;
12
17
  detents: SheetDetent[];
13
18
  initialDetentIndex: number;
14
19
  /** 0xAARRGGBB, from `processColor`. */
@@ -25,11 +30,15 @@ export interface ModalConfig {
25
30
  contentBackgroundColor: number | null;
26
31
  keyboardBehavior: KeyboardBehavior;
27
32
  popupAnimation: PopupAnimation;
33
+ pullToRefreshEnabled: boolean;
28
34
  }
29
35
 
30
36
  export function makeConfig(props: NitroModalProps): ModalConfig {
37
+ // An inline sheet is part of the page; the user can't dismiss it.
38
+ const isInline = props.isInline && props.mode === 'bottomSheet';
31
39
  return {
32
40
  mode: props.mode,
41
+ isInline,
33
42
  detents: props.detents,
34
43
  initialDetentIndex: Number.isFinite(props.initialDetentIndex)
35
44
  ? Math.trunc(props.initialDetentIndex)
@@ -37,14 +46,15 @@ export function makeConfig(props: NitroModalProps): ModalConfig {
37
46
  backdropColor: props.backdropColor,
38
47
  backdropOpacity: props.backdropOpacity,
39
48
  backdropBlurRadius: props.backdropBlurRadius,
40
- dismissOnBackdropPress: props.dismissOnBackdropPress,
41
- dismissOnSwipe: props.dismissOnSwipe,
42
- dismissOnBackButton: props.dismissOnBackButton,
49
+ dismissOnBackdropPress: props.dismissOnBackdropPress && !isInline,
50
+ dismissOnSwipe: props.dismissOnSwipe && !isInline,
51
+ dismissOnBackButton: props.dismissOnBackButton && !isInline,
43
52
  grabberVisible: props.grabberVisible,
44
53
  cornerRadius: props.cornerRadius >= 0 ? props.cornerRadius : null,
45
54
  contentBackgroundColor: props.contentBackgroundColor ?? null,
46
55
  keyboardBehavior: props.keyboardBehavior,
47
56
  popupAnimation: props.popupAnimation,
57
+ pullToRefreshEnabled: props.pullToRefreshEnabled,
48
58
  };
49
59
  }
50
60