react-native-nitro-modal 0.3.1-rc → 0.3.4-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 (50) hide show
  1. package/README.md +4 -1
  2. package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +29 -15
  3. package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +5 -0
  4. package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +3 -0
  5. package/android/src/main/java/com/margelo/nitro/nitromodal/PopupPresenter.kt +6 -0
  6. package/ios/HybridNitroModal.swift +4 -0
  7. package/ios/ModalConfig.swift +3 -0
  8. package/ios/PopupViewController.swift +2 -0
  9. package/ios/SheetView.swift +56 -21
  10. package/lib/module/NitroModal.js +4 -0
  11. package/lib/module/NitroModal.js.map +1 -1
  12. package/lib/module/NitroModalView.web.js +24 -8
  13. package/lib/module/NitroModalView.web.js.map +1 -1
  14. package/lib/module/web/ModalConfig.js +2 -0
  15. package/lib/module/web/ModalConfig.js.map +1 -1
  16. package/lib/module/web/ModalPresenter.js +5 -0
  17. package/lib/module/web/ModalPresenter.js.map +1 -1
  18. package/lib/module/web/SheetPresenter.js +50 -11
  19. package/lib/module/web/SheetPresenter.js.map +1 -1
  20. package/lib/typescript/src/NitroModal.d.ts +8 -1
  21. package/lib/typescript/src/NitroModal.d.ts.map +1 -1
  22. package/lib/typescript/src/NitroModal.nitro.d.ts +4 -0
  23. package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -1
  24. package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -1
  25. package/lib/typescript/src/web/ModalConfig.d.ts +4 -0
  26. package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -1
  27. package/lib/typescript/src/web/ModalPresenter.d.ts +3 -1
  28. package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -1
  29. package/lib/typescript/src/web/SheetPresenter.d.ts +14 -2
  30. package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -1
  31. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +18 -0
  32. package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +4 -0
  33. package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +10 -0
  34. package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +12 -0
  35. package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +13 -0
  36. package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +12 -0
  37. package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +2 -0
  38. package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +35 -0
  39. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +4 -0
  40. package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +4 -0
  41. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +4 -0
  42. package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +6 -0
  43. package/nitrogen/generated/shared/json/NitroModalConfig.json +2 -0
  44. package/package.json +1 -1
  45. package/src/NitroModal.nitro.ts +4 -0
  46. package/src/NitroModal.tsx +11 -0
  47. package/src/NitroModalView.web.tsx +16 -3
  48. package/src/web/ModalConfig.ts +9 -0
  49. package/src/web/ModalPresenter.ts +9 -1
  50. package/src/web/SheetPresenter.ts +39 -13
@@ -90,6 +90,10 @@ namespace margelo::nitro::nitromodal {
90
90
  virtual void setGrabberVisible(bool grabberVisible) = 0;
91
91
  virtual double getCornerRadius() = 0;
92
92
  virtual void setCornerRadius(double cornerRadius) = 0;
93
+ virtual double getBorderWidth() = 0;
94
+ virtual void setBorderWidth(double borderWidth) = 0;
95
+ virtual std::optional<double> getBorderColor() = 0;
96
+ virtual void setBorderColor(std::optional<double> borderColor) = 0;
93
97
  virtual std::optional<double> getContentBackgroundColor() = 0;
94
98
  virtual void setContentBackgroundColor(std::optional<double> contentBackgroundColor) = 0;
95
99
  virtual KeyboardBehavior getKeyboardBehavior() = 0;
@@ -33,6 +33,8 @@ namespace margelo::nitro::nitromodal::views {
33
33
  dismissOnBackButton(nitro::ReactProp<bool>::fromRawValue("NitroModal", "dismissOnBackButton", rawProps, sourceProps.dismissOnBackButton)),
34
34
  grabberVisible(nitro::ReactProp<bool>::fromRawValue("NitroModal", "grabberVisible", rawProps, sourceProps.grabberVisible)),
35
35
  cornerRadius(nitro::ReactProp<double>::fromRawValue("NitroModal", "cornerRadius", rawProps, sourceProps.cornerRadius)),
36
+ borderWidth(nitro::ReactProp<double>::fromRawValue("NitroModal", "borderWidth", rawProps, sourceProps.borderWidth)),
37
+ borderColor(nitro::ReactProp<std::optional<double>>::fromRawValue("NitroModal", "borderColor", rawProps, sourceProps.borderColor)),
36
38
  contentBackgroundColor(nitro::ReactProp<std::optional<double>>::fromRawValue("NitroModal", "contentBackgroundColor", rawProps, sourceProps.contentBackgroundColor)),
37
39
  keyboardBehavior(nitro::ReactProp<KeyboardBehavior>::fromRawValue("NitroModal", "keyboardBehavior", rawProps, sourceProps.keyboardBehavior)),
38
40
  popupAnimation(nitro::ReactProp<PopupAnimation>::fromRawValue("NitroModal", "popupAnimation", rawProps, sourceProps.popupAnimation)),
@@ -64,6 +66,8 @@ namespace margelo::nitro::nitromodal::views {
64
66
  case hashString("dismissOnBackButton"): return true;
65
67
  case hashString("grabberVisible"): return true;
66
68
  case hashString("cornerRadius"): return true;
69
+ case hashString("borderWidth"): return true;
70
+ case hashString("borderColor"): return true;
67
71
  case hashString("contentBackgroundColor"): return true;
68
72
  case hashString("keyboardBehavior"): return true;
69
73
  case hashString("popupAnimation"): return true;
@@ -63,6 +63,8 @@ namespace margelo::nitro::nitromodal::views {
63
63
  nitro::ReactProp<bool> dismissOnBackButton;
64
64
  nitro::ReactProp<bool> grabberVisible;
65
65
  nitro::ReactProp<double> cornerRadius;
66
+ nitro::ReactProp<double> borderWidth;
67
+ nitro::ReactProp<std::optional<double>> borderColor;
66
68
  nitro::ReactProp<std::optional<double>> contentBackgroundColor;
67
69
  nitro::ReactProp<KeyboardBehavior> keyboardBehavior;
68
70
  nitro::ReactProp<PopupAnimation> popupAnimation;
@@ -94,6 +96,8 @@ namespace margelo::nitro::nitromodal::views {
94
96
  dismissOnBackButton.hasSameValue(other.dismissOnBackButton) &&
95
97
  grabberVisible.hasSameValue(other.grabberVisible) &&
96
98
  cornerRadius.hasSameValue(other.cornerRadius) &&
99
+ borderWidth.hasSameValue(other.borderWidth) &&
100
+ borderColor.hasSameValue(other.borderColor) &&
97
101
  contentBackgroundColor.hasSameValue(other.contentBackgroundColor) &&
98
102
  keyboardBehavior.hasSameValue(other.keyboardBehavior) &&
99
103
  popupAnimation.hasSameValue(other.popupAnimation) &&
@@ -126,6 +130,8 @@ namespace margelo::nitro::nitromodal::views {
126
130
  dismissOnBackButton.isProvided() ||
127
131
  grabberVisible.isProvided() ||
128
132
  cornerRadius.isProvided() ||
133
+ borderWidth.isProvided() ||
134
+ borderColor.isProvided() ||
129
135
  contentBackgroundColor.isProvided() ||
130
136
  keyboardBehavior.isProvided() ||
131
137
  popupAnimation.isProvided() ||
@@ -17,6 +17,8 @@
17
17
  "dismissOnBackButton": true,
18
18
  "grabberVisible": true,
19
19
  "cornerRadius": true,
20
+ "borderWidth": true,
21
+ "borderColor": true,
20
22
  "contentBackgroundColor": true,
21
23
  "keyboardBehavior": true,
22
24
  "popupAnimation": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "react-native-nitro-modal",
3
- "version": "0.3.1-rc",
3
+ "version": "0.3.4-rc",
4
4
  "description": "react native native modal",
5
5
  "main": "./lib/module/index.js",
6
6
  "types": "./lib/typescript/src/index.d.ts",
@@ -77,6 +77,10 @@ export interface NitroModalProps extends HybridViewProps {
77
77
  grabberVisible: boolean;
78
78
  /** Corner radius of the sheet/card. A negative value uses the platform default. */
79
79
  cornerRadius: number;
80
+ /** Width (dp/pt) of the border drawn around the sheet/card, above the content. 0 draws none. */
81
+ borderWidth: number;
82
+ /** Border color as a processed ARGB color. */
83
+ borderColor?: number;
80
84
  /** Sheet/card background as a processed ARGB color. Omit for the system background. */
81
85
  contentBackgroundColor?: number;
82
86
  keyboardBehavior: KeyboardBehavior;
@@ -92,6 +92,13 @@ export interface NitroModalProps {
92
92
  showGrabber?: boolean;
93
93
  /** Corner radius of the sheet/card. Defaults to the platform style. */
94
94
  cornerRadius?: number;
95
+ /**
96
+ * Width (dp/pt/px) of a border around the sheet/card, drawn above the
97
+ * content and following its corners. @default 0
98
+ */
99
+ borderWidth?: number;
100
+ /** Color of the border. @default 'transparent' */
101
+ borderColor?: ColorValue;
95
102
  /** Sheet/card background. Defaults to the system surface color. */
96
103
  backgroundColor?: ColorValue;
97
104
  /** @default 'pan' */
@@ -158,6 +165,8 @@ export function NitroModal({
158
165
  dismissOnBackButton = true,
159
166
  showGrabber = false,
160
167
  cornerRadius,
168
+ borderWidth = 0,
169
+ borderColor,
161
170
  backgroundColor,
162
171
  keyboardBehavior = 'pan',
163
172
  popupAnimation = 'scale',
@@ -334,6 +343,8 @@ export function NitroModal({
334
343
  dismissOnBackButton={dismissOnBackButton}
335
344
  grabberVisible={showGrabber}
336
345
  cornerRadius={cornerRadius ?? -1}
346
+ borderWidth={borderWidth}
347
+ borderColor={toNativeColor(borderColor)}
337
348
  contentBackgroundColor={toNativeColor(backgroundColor)}
338
349
  keyboardBehavior={keyboardBehavior}
339
350
  popupAnimation={popupAnimation}
@@ -45,6 +45,7 @@ function WebNitroModalView(props: WebNitroModalViewProps) {
45
45
  const surface = useRef<HTMLDivElement>(null);
46
46
  const content = useRef<HTMLDivElement>(null);
47
47
  const grabber = useRef<HTMLDivElement>(null);
48
+ const border = useRef<HTMLDivElement>(null);
48
49
  const refreshIndicator = useRef<HTMLDivElement>(null);
49
50
  const refreshSpinner = useRef<HTMLDivElement>(null);
50
51
 
@@ -69,6 +70,7 @@ function WebNitroModalView(props: WebNitroModalViewProps) {
69
70
  surface: surface.current!,
70
71
  content: content.current!,
71
72
  grabber: grabber.current!,
73
+ border: border.current!,
72
74
  refreshIndicator: refreshIndicator.current!,
73
75
  refreshSpinner: refreshSpinner.current!,
74
76
  });
@@ -102,14 +104,15 @@ function WebNitroModalView(props: WebNitroModalViewProps) {
102
104
  }
103
105
  >
104
106
  <div ref={backdrop} aria-hidden style={styles.backdrop} />
105
- <div ref={refreshIndicator} aria-hidden style={styles.refreshIndicator}>
106
- <div ref={refreshSpinner} style={styles.refreshSpinner} />
107
- </div>
108
107
  <div ref={surface} style={styles.surface}>
108
+ <div ref={refreshIndicator} aria-hidden style={styles.refreshIndicator}>
109
+ <div ref={refreshSpinner} style={styles.refreshSpinner} />
110
+ </div>
109
111
  <div ref={content} style={styles.content}>
110
112
  {children}
111
113
  </div>
112
114
  <div ref={grabber} aria-hidden style={styles.grabber} />
115
+ <div ref={border} aria-hidden style={styles.border} />
113
116
  </div>
114
117
  </div>
115
118
  ) : null;
@@ -179,6 +182,16 @@ const styles = {
179
182
  backgroundColor: 'rgba(127, 127, 127, 0.4)',
180
183
  pointerEvents: 'none',
181
184
  },
185
+ // Sized by the presenter's border width; follows the surface's corners.
186
+ border: {
187
+ position: 'absolute',
188
+ inset: 0,
189
+ boxSizing: 'border-box',
190
+ borderStyle: 'solid',
191
+ borderWidth: 0,
192
+ borderRadius: 'inherit',
193
+ pointerEvents: 'none',
194
+ },
182
195
  refreshIndicator: {
183
196
  position: 'absolute',
184
197
  left: 0,
@@ -26,6 +26,10 @@ export interface ModalConfig {
26
26
  grabberVisible: boolean;
27
27
  /** `null` uses the default. */
28
28
  cornerRadius: number | null;
29
+ /** Border around the surface, drawn above the content. */
30
+ borderWidth: number;
31
+ /** 0xAARRGGBB; `null` draws no border. */
32
+ borderColor: number | null;
29
33
  /** 0xAARRGGBB; `null` uses the system background. */
30
34
  contentBackgroundColor: number | null;
31
35
  keyboardBehavior: KeyboardBehavior;
@@ -55,6 +59,11 @@ export function makeConfig(props: NitroModalProps): ModalConfig {
55
59
  dismissOnBackButton: props.dismissOnBackButton && !isInline,
56
60
  grabberVisible: props.grabberVisible,
57
61
  cornerRadius: props.cornerRadius >= 0 ? props.cornerRadius : null,
62
+ borderWidth:
63
+ Number.isFinite(props.borderWidth) && props.borderWidth > 0
64
+ ? props.borderWidth
65
+ : 0,
66
+ borderColor: props.borderColor ?? null,
58
67
  contentBackgroundColor: props.contentBackgroundColor ?? null,
59
68
  keyboardBehavior: props.keyboardBehavior,
60
69
  popupAnimation: props.popupAnimation,
@@ -23,7 +23,9 @@ export interface ModalElements {
23
23
  /** Hosts the React content. */
24
24
  content: HTMLElement;
25
25
  grabber: HTMLElement;
26
- /** Behind the sheet, uncovered as it's pulled down to refresh. */
26
+ /** Above the content, following the surface's corners. */
27
+ border: HTMLElement;
28
+ /** Inside the sheet behind the content, uncovered as it's pulled down to refresh. */
27
29
  refreshIndicator: HTMLElement;
28
30
  /** The spinning ring inside `refreshIndicator`. */
29
31
  refreshSpinner: HTMLElement;
@@ -246,6 +248,12 @@ export abstract class ModalPresenter {
246
248
  config.contentBackgroundColor != null
247
249
  ? toCSSColor(config.contentBackgroundColor)
248
250
  : 'Canvas';
251
+ const { border } = this.elements!;
252
+ border.style.borderWidth = `${config.borderWidth}px`;
253
+ border.style.borderColor =
254
+ config.borderColor != null
255
+ ? toCSSColor(config.borderColor)
256
+ : 'transparent';
249
257
  }
250
258
 
251
259
  /** Inline, only the surface takes input; the rest reaches the page. */
@@ -339,28 +339,42 @@ export class SheetPresenter extends ModalPresenter {
339
339
 
340
340
  private render(top: number) {
341
341
  if (!this.elements) return;
342
- this.elements.surface.style.transform = `translate3d(0, ${top}px, 0)`;
343
- this.elements.backdrop.style.opacity = String(this.backdropAlpha(top));
344
- this.renderRefreshIndicator(top);
342
+ const { surface, content, backdrop } = this.elements;
343
+ const { sheetTop, pull } = this.split(top);
344
+ surface.style.transform = `translate3d(0, ${sheetTop}px, 0)`;
345
+ content.style.transform = pull > 0 ? `translate3d(0, ${pull}px, 0)` : '';
346
+ backdrop.style.opacity = String(this.backdropAlpha(sheetTop));
347
+ this.renderRefreshIndicator(pull);
345
348
  }
346
349
 
347
350
  /**
348
- * Centered in the gap a pull uncovers below the lowest detent, fading in
349
- * as the pull nears the threshold.
351
+ * With a refresh indicator, a pull below the lowest detent keeps the sheet
352
+ * there and moves the content down inside it instead, uncovering the
353
+ * indicator at the top of the sheet.
350
354
  */
351
- private renderRefreshIndicator(top: number) {
352
- const { refreshIndicator, refreshSpinner } = this.elements!;
353
- const visible =
355
+ private split(top: number): { sheetTop: number; pull: number } {
356
+ const pullsContent =
354
357
  this.config.refreshing != null &&
358
+ this.config.pullToRefreshEnabled &&
359
+ !this.config.dismissOnSwipe &&
355
360
  !this.entering &&
356
361
  !this.isDismissing &&
357
362
  !this.isOffscreen;
358
363
  const lowest = Math.max(...this.detentTops);
359
- const progress = visible
360
- ? Math.min(Math.max((top - lowest) / REFRESH_THRESHOLD, 0), 1)
361
- : 0;
362
- const x = (this.viewport.width - REFRESH_INDICATOR_SIZE) / 2;
363
- const y = top - (REFRESH_THRESHOLD + REFRESH_INDICATOR_SIZE) / 2;
364
+ if (!pullsContent || !(top > lowest)) return { sheetTop: top, pull: 0 };
365
+ return { sheetTop: lowest, pull: top - lowest };
366
+ }
367
+
368
+ /**
369
+ * Centered in the gap a pull uncovers above the content, fading in as the
370
+ * pull nears the threshold.
371
+ */
372
+ private renderRefreshIndicator(pull: number) {
373
+ const { refreshIndicator, refreshSpinner } = this.elements!;
374
+ const progress = Math.min(Math.max(pull / REFRESH_THRESHOLD, 0), 1);
375
+ const width = sheetWidth(this.viewport.width, this.config.isInline);
376
+ const x = (width - REFRESH_INDICATOR_SIZE) / 2;
377
+ const y = (pull - REFRESH_INDICATOR_SIZE) / 2;
364
378
  refreshIndicator.style.transform = `translate3d(${x}px, ${y}px, 0)`;
365
379
  refreshIndicator.style.opacity = String(progress);
366
380
  if (progress > 0 && !this.refreshSpin) {
@@ -513,6 +527,9 @@ export class SheetPresenter extends ModalPresenter {
513
527
  drag.rawTop += delta;
514
528
  drag.sheetDrove = true;
515
529
  }
530
+ if (!this.canDragBelowLowestDetent) {
531
+ drag.rawTop = Math.min(drag.rawTop, Math.max(...this.settledTops));
532
+ }
516
533
  const top = this.constrained(drag.rawTop);
517
534
  if (drag.canRefresh) {
518
535
  drag.refreshArmed =
@@ -594,6 +611,15 @@ export class SheetPresenter extends ModalPresenter {
594
611
  return ((last.y - first.y) / (last.time - first.time)) * 1000;
595
612
  }
596
613
 
614
+ /**
615
+ * Whether a drag can take the sheet below its lowest detent: to dismiss it
616
+ * or to pull to refresh. Otherwise it stops there, like Android's
617
+ * `BottomSheetBehavior`.
618
+ */
619
+ private get canDragBelowLowestDetent(): boolean {
620
+ return this.config.dismissOnSwipe || this.config.pullToRefreshEnabled;
621
+ }
622
+
597
623
  /** Rubber-bands above the tallest detent, and below the lowest one when swiping can't dismiss. */
598
624
  private constrained(top: number): number {
599
625
  const tops = this.settledTops;