react-native-nitro-modal 0.3.0-rc → 0.3.1-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.
- package/README.md +5 -1
- package/android/src/main/java/com/margelo/nitro/nitromodal/BottomSheetPresenter.kt +73 -9
- package/android/src/main/java/com/margelo/nitro/nitromodal/HybridNitroModal.kt +4 -0
- package/android/src/main/java/com/margelo/nitro/nitromodal/ModalConfig.kt +4 -0
- package/ios/HybridNitroModal.swift +5 -1
- package/ios/ModalConfig.swift +4 -0
- package/ios/SheetView.swift +82 -10
- package/lib/module/NitroModal.js +16 -1
- package/lib/module/NitroModal.js.map +1 -1
- package/lib/module/NitroModalView.web.js +32 -2
- package/lib/module/NitroModalView.web.js.map +1 -1
- package/lib/module/web/ModalConfig.js +3 -1
- package/lib/module/web/ModalConfig.js.map +1 -1
- package/lib/module/web/ModalPresenter.js.map +1 -1
- package/lib/module/web/SheetPresenter.js +66 -7
- package/lib/module/web/SheetPresenter.js.map +1 -1
- package/lib/module/web/dom.js +3 -0
- package/lib/module/web/dom.js.map +1 -1
- package/lib/typescript/src/NitroModal.d.ts +10 -1
- package/lib/typescript/src/NitroModal.d.ts.map +1 -1
- package/lib/typescript/src/NitroModal.nitro.d.ts +8 -0
- package/lib/typescript/src/NitroModal.nitro.d.ts.map +1 -1
- package/lib/typescript/src/NitroModalView.web.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalConfig.d.ts +4 -0
- package/lib/typescript/src/web/ModalConfig.d.ts.map +1 -1
- package/lib/typescript/src/web/ModalPresenter.d.ts +4 -0
- package/lib/typescript/src/web/ModalPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts +12 -1
- package/lib/typescript/src/web/SheetPresenter.d.ts.map +1 -1
- package/lib/typescript/src/web/dom.d.ts +2 -0
- package/lib/typescript/src/web/dom.d.ts.map +1 -1
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.cpp +18 -0
- package/nitrogen/generated/android/c++/JHybridNitroModalSpec.hpp +4 -0
- package/nitrogen/generated/android/c++/views/JHybridNitroModalStateUpdater.cpp +10 -0
- package/nitrogen/generated/android/kotlin/com/margelo/nitro/nitromodal/HybridNitroModalSpec.kt +12 -0
- package/nitrogen/generated/ios/NitroModal-Swift-Cxx-Bridge.hpp +15 -0
- package/nitrogen/generated/ios/c++/HybridNitroModalSpecSwift.hpp +14 -0
- package/nitrogen/generated/ios/c++/views/HybridNitroModalComponent.mm +12 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec.swift +2 -0
- package/nitrogen/generated/ios/swift/HybridNitroModalSpec_cxx.swift +48 -0
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.cpp +4 -0
- package/nitrogen/generated/shared/c++/HybridNitroModalSpec.hpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.cpp +4 -0
- package/nitrogen/generated/shared/c++/views/HybridNitroModalComponent.hpp +6 -0
- package/nitrogen/generated/shared/json/NitroModalConfig.json +2 -0
- package/package.json +1 -1
- package/src/NitroModal.nitro.ts +8 -0
- package/src/NitroModal.tsx +25 -0
- package/src/NitroModalView.web.tsx +26 -1
- package/src/web/ModalConfig.ts +6 -0
- package/src/web/ModalPresenter.ts +4 -0
- package/src/web/SheetPresenter.ts +72 -5
- package/src/web/dom.ts +3 -0
|
@@ -6,7 +6,9 @@ import {
|
|
|
6
6
|
findVerticalScroller,
|
|
7
7
|
isTextInput,
|
|
8
8
|
prefersReducedMotion,
|
|
9
|
+
REFRESH_INDICATOR_SIZE,
|
|
9
10
|
suppressNextClick,
|
|
11
|
+
toCSSColor,
|
|
10
12
|
type Insets,
|
|
11
13
|
type Size,
|
|
12
14
|
} from './dom';
|
|
@@ -15,7 +17,10 @@ import { ModalPresenter, type PresenterListener } from './ModalPresenter';
|
|
|
15
17
|
|
|
16
18
|
/** Gap kept above a modal sheet at its tallest. */
|
|
17
19
|
const TOP_GAP = 10;
|
|
18
|
-
/**
|
|
20
|
+
/**
|
|
21
|
+
* How far below its lowest detent the sheet must be pulled to refresh, and
|
|
22
|
+
* where it rests while refreshing.
|
|
23
|
+
*/
|
|
19
24
|
const REFRESH_THRESHOLD = 56;
|
|
20
25
|
/**
|
|
21
26
|
* Extra sheet height below the viewport so it stays attached to the bottom
|
|
@@ -147,6 +152,9 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
147
152
|
private drag: Drag | null = null;
|
|
148
153
|
/** Vertical velocity of the swipe that requested the dismissal. */
|
|
149
154
|
private releaseVelocity = 0;
|
|
155
|
+
/** The sheet rests below its lowest detent, the indicator spinning above it. */
|
|
156
|
+
private refreshing = false;
|
|
157
|
+
private refreshSpin: Animation | null = null;
|
|
150
158
|
/** The sheet's top edge in the viewport. */
|
|
151
159
|
private readonly top = new AnimatedValue(0, 0.5, (value) =>
|
|
152
160
|
this.render(value)
|
|
@@ -158,6 +166,7 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
158
166
|
Math.max(config.initialDetentIndex, 0),
|
|
159
167
|
this.detents.length - 1
|
|
160
168
|
);
|
|
169
|
+
this.refreshing = config.refreshing ?? false;
|
|
161
170
|
}
|
|
162
171
|
|
|
163
172
|
snapToDetent(index: number) {
|
|
@@ -199,7 +208,10 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
199
208
|
});
|
|
200
209
|
}
|
|
201
210
|
|
|
202
|
-
protected configDidChange() {
|
|
211
|
+
protected configDidChange(previous: ModalConfig) {
|
|
212
|
+
if (previous.refreshing !== this.config.refreshing) {
|
|
213
|
+
this.refreshing = this.config.refreshing ?? false;
|
|
214
|
+
}
|
|
203
215
|
this.selectedIndex = Math.min(this.selectedIndex, this.detents.length - 1);
|
|
204
216
|
this.layout();
|
|
205
217
|
this.animateToRest();
|
|
@@ -223,6 +235,8 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
223
235
|
protected teardown() {
|
|
224
236
|
super.teardown();
|
|
225
237
|
this.cancelDrag();
|
|
238
|
+
this.refreshSpin?.cancel();
|
|
239
|
+
this.refreshSpin = null;
|
|
226
240
|
const surface = this.elements?.surface;
|
|
227
241
|
surface?.removeEventListener('touchstart', this.handleTouchStart);
|
|
228
242
|
surface?.removeEventListener('touchmove', this.handleTouchMove);
|
|
@@ -281,6 +295,18 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
281
295
|
return this.detents.map((_, index) => this.restingTop(index));
|
|
282
296
|
}
|
|
283
297
|
|
|
298
|
+
/** Where the sheet settles on `detents[index]`: held below the lowest detent while refreshing. */
|
|
299
|
+
private settledTop(index: number): number {
|
|
300
|
+
const top = this.restingTop(index);
|
|
301
|
+
if (!this.refreshing || Math.abs(top - Math.max(...this.detentTops)) >= 1)
|
|
302
|
+
return top;
|
|
303
|
+
return top + REFRESH_THRESHOLD;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private get settledTops(): number[] {
|
|
307
|
+
return this.detents.map((_, index) => this.settledTop(index));
|
|
308
|
+
}
|
|
309
|
+
|
|
284
310
|
private layout() {
|
|
285
311
|
const { surface, content, grabber } = this.elements!;
|
|
286
312
|
const width = sheetWidth(this.viewport.width, this.config.isInline);
|
|
@@ -298,6 +324,10 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
298
324
|
content.style.width = `${width}px`;
|
|
299
325
|
content.style.height = `${this.maximumDetentValue}px`;
|
|
300
326
|
grabber.style.display = this.config.grabberVisible ? '' : 'none';
|
|
327
|
+
this.elements!.refreshSpinner.style.color =
|
|
328
|
+
this.config.refreshIndicatorColor != null
|
|
329
|
+
? toCSSColor(this.config.refreshIndicatorColor)
|
|
330
|
+
: 'GrayText';
|
|
301
331
|
|
|
302
332
|
let height = contentHeight(this.config.detents, this.maximumDetentValue);
|
|
303
333
|
if (this.config.keyboardBehavior === 'resize') {
|
|
@@ -311,6 +341,38 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
311
341
|
if (!this.elements) return;
|
|
312
342
|
this.elements.surface.style.transform = `translate3d(0, ${top}px, 0)`;
|
|
313
343
|
this.elements.backdrop.style.opacity = String(this.backdropAlpha(top));
|
|
344
|
+
this.renderRefreshIndicator(top);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
/**
|
|
348
|
+
* Centered in the gap a pull uncovers below the lowest detent, fading in
|
|
349
|
+
* as the pull nears the threshold.
|
|
350
|
+
*/
|
|
351
|
+
private renderRefreshIndicator(top: number) {
|
|
352
|
+
const { refreshIndicator, refreshSpinner } = this.elements!;
|
|
353
|
+
const visible =
|
|
354
|
+
this.config.refreshing != null &&
|
|
355
|
+
!this.entering &&
|
|
356
|
+
!this.isDismissing &&
|
|
357
|
+
!this.isOffscreen;
|
|
358
|
+
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
|
+
refreshIndicator.style.transform = `translate3d(${x}px, ${y}px, 0)`;
|
|
365
|
+
refreshIndicator.style.opacity = String(progress);
|
|
366
|
+
if (progress > 0 && !this.refreshSpin) {
|
|
367
|
+
this.refreshSpin =
|
|
368
|
+
refreshSpinner.animate?.(
|
|
369
|
+
[{ transform: 'rotate(0deg)' }, { transform: 'rotate(360deg)' }],
|
|
370
|
+
{ duration: 800, iterations: Infinity }
|
|
371
|
+
) ?? null;
|
|
372
|
+
} else if (progress === 0 && this.refreshSpin) {
|
|
373
|
+
this.refreshSpin.cancel();
|
|
374
|
+
this.refreshSpin = null;
|
|
375
|
+
}
|
|
314
376
|
}
|
|
315
377
|
|
|
316
378
|
/** Fully dimmed at every detent; fades out below the lowest one. */
|
|
@@ -327,7 +389,7 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
327
389
|
|
|
328
390
|
private settle(motion: Motion, velocity?: number) {
|
|
329
391
|
this.top.animateTo(
|
|
330
|
-
this.
|
|
392
|
+
this.settledTop(this.selectedIndex),
|
|
331
393
|
this.motion(motion),
|
|
332
394
|
{
|
|
333
395
|
velocity,
|
|
@@ -401,6 +463,7 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
401
463
|
drag.canRefresh =
|
|
402
464
|
this.config.pullToRefreshEnabled &&
|
|
403
465
|
!this.config.dismissOnSwipe &&
|
|
466
|
+
!this.refreshing &&
|
|
404
467
|
isSettled &&
|
|
405
468
|
Math.abs(drag.rawTop - Math.max(...this.detentTops)) < 1;
|
|
406
469
|
const { surface } = this.elements!;
|
|
@@ -468,7 +531,11 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
468
531
|
|
|
469
532
|
const velocity = drag.sheetDrove ? this.velocity(drag) : 0;
|
|
470
533
|
const top = this.top.value;
|
|
471
|
-
|
|
534
|
+
if (drag.refreshArmed && this.config.refreshing != null) {
|
|
535
|
+
// Settles on the refreshing position; React confirms or ends it.
|
|
536
|
+
this.refreshing = true;
|
|
537
|
+
}
|
|
538
|
+
const tops = this.settledTops;
|
|
472
539
|
const lowest = Math.max(...tops);
|
|
473
540
|
// Where a fling of this velocity would come to rest.
|
|
474
541
|
const projected = top + velocity * 0.2;
|
|
@@ -529,7 +596,7 @@ export class SheetPresenter extends ModalPresenter {
|
|
|
529
596
|
|
|
530
597
|
/** Rubber-bands above the tallest detent, and below the lowest one when swiping can't dismiss. */
|
|
531
598
|
private constrained(top: number): number {
|
|
532
|
-
const tops = this.
|
|
599
|
+
const tops = this.settledTops;
|
|
533
600
|
const minTop = Math.min(...tops);
|
|
534
601
|
const maxTop = Math.max(...tops);
|
|
535
602
|
const dimension = Math.max(this.viewport.height, 1);
|
package/src/web/dom.ts
CHANGED
|
@@ -17,6 +17,9 @@ export const ZERO_INSETS: Insets = { top: 0, right: 0, bottom: 0, left: 0 };
|
|
|
17
17
|
export const canUseDOM =
|
|
18
18
|
typeof window !== 'undefined' && typeof document !== 'undefined';
|
|
19
19
|
|
|
20
|
+
/** Width and height of the pull-to-refresh indicator. */
|
|
21
|
+
export const REFRESH_INDICATOR_SIZE = 24;
|
|
22
|
+
|
|
20
23
|
/** Converts a color from `processColor` (0xAARRGGBB) to CSS, scaling its alpha by `opacity`. */
|
|
21
24
|
export function toCSSColor(argb: number, opacity = 1): string {
|
|
22
25
|
// As an unsigned 32-bit value, whatever the sign it was passed with.
|