react-modal-sheet 4.1.0 → 4.3.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 +37 -3
- package/dist/index.d.mts +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +81 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +81 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/SheetContainer.tsx +4 -7
- package/src/SheetScroller.tsx +34 -15
- package/src/context.tsx +4 -4
- package/src/sheet.tsx +33 -27
- package/src/styles.ts +2 -0
- package/src/types.tsx +1 -0
- package/src/utils.ts +34 -0
package/README.md
CHANGED
|
@@ -280,9 +280,10 @@ Sheet scroller can be used to make the whole sheet content or parts of it scroll
|
|
|
280
280
|
|
|
281
281
|
#### Scroller props
|
|
282
282
|
|
|
283
|
-
| Name
|
|
284
|
-
|
|
|
285
|
-
| `draggableAt`
|
|
283
|
+
| Name | Required | Default | Description |
|
|
284
|
+
| --------------- | -------- | ------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
285
|
+
| `draggableAt` | no | `"top"` | Should the drag be enabled when the element is scrolled either to the top, bottom, or both. Available values: `top`, `bottom`, `both`. |
|
|
286
|
+
| `disableScroll` | no | false | Disable scrolling. This can be combined with snap points to make the sheet scroller only scrollable at a given snap point (usually the top position). |
|
|
286
287
|
|
|
287
288
|
### `Sheet.Backdrop`
|
|
288
289
|
|
|
@@ -340,6 +341,39 @@ function ScrollableExample() {
|
|
|
340
341
|
}
|
|
341
342
|
```
|
|
342
343
|
|
|
344
|
+
#### Disabling scroll manually
|
|
345
|
+
|
|
346
|
+
You can also disable scrolling manually by passing `disableScroll` prop to the `Sheet.Scroller` component. This can be useful if you want to have a scrollable sheet but only at a certain snap point.
|
|
347
|
+
|
|
348
|
+
For example, if you have a snap point at the top of the sheet and you want to make the content scrollable only when the sheet is at that snap point, you can do the following:
|
|
349
|
+
|
|
350
|
+
```tsx
|
|
351
|
+
const snapPoints = [1, 0.5, 0];
|
|
352
|
+
const initialSnap = 1;
|
|
353
|
+
|
|
354
|
+
function DisableScrollExample() {
|
|
355
|
+
const [currentSnap, setCurrentSnap] = useState(initialSnap);
|
|
356
|
+
|
|
357
|
+
return (
|
|
358
|
+
<Sheet
|
|
359
|
+
initialSnap={initialSnap}
|
|
360
|
+
snapPoints={snapPoints}
|
|
361
|
+
onSnap={setCurrentSnap}
|
|
362
|
+
>
|
|
363
|
+
<Sheet.Container>
|
|
364
|
+
<Sheet.Header />
|
|
365
|
+
<Sheet.Content>
|
|
366
|
+
<Sheet.Scroller draggableAt="top" disableScroll={currentSnap !== 0}>
|
|
367
|
+
{/*...*/}
|
|
368
|
+
</Sheet.Scroller>
|
|
369
|
+
</Sheet.Content>
|
|
370
|
+
</Sheet.Container>
|
|
371
|
+
<Sheet.Backdrop />
|
|
372
|
+
</Sheet>
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
```
|
|
376
|
+
|
|
343
377
|
### 🪟 iOS Modal View effect
|
|
344
378
|
|
|
345
379
|
In addition to the `Sheet.Backdrop` it's possible to apply a scaling effect to the main app element to highlight the modality of the bottom sheet. This effect mimics the [iOS Modal View](https://developer.apple.com/design/human-interface-guidelines/ios/app-architecture/modality/) presentation style to bring more focus to the sheet and add some delight to the user experience.
|
package/dist/index.d.mts
CHANGED
|
@@ -45,6 +45,7 @@ type SheetDraggableProps = Omit<CommonProps, 'drag' | 'dragElastic' | 'dragConst
|
|
|
45
45
|
type SheetBackdropProps = Omit<CommonProps, 'initial' | 'animate' | 'exit'>;
|
|
46
46
|
type SheetScrollerProps = HTMLAttributes<HTMLDivElement> & {
|
|
47
47
|
draggableAt?: 'top' | 'bottom' | 'both';
|
|
48
|
+
disableScroll?: boolean;
|
|
48
49
|
};
|
|
49
50
|
type ContainerComponent = ForwardRefExoticComponent<SheetContainerProps & RefAttributes<any>>;
|
|
50
51
|
type DraggableComponent = ForwardRefExoticComponent<SheetDraggableProps & RefAttributes<any>>;
|
package/dist/index.d.ts
CHANGED
|
@@ -45,6 +45,7 @@ type SheetDraggableProps = Omit<CommonProps, 'drag' | 'dragElastic' | 'dragConst
|
|
|
45
45
|
type SheetBackdropProps = Omit<CommonProps, 'initial' | 'animate' | 'exit'>;
|
|
46
46
|
type SheetScrollerProps = HTMLAttributes<HTMLDivElement> & {
|
|
47
47
|
draggableAt?: 'top' | 'bottom' | 'both';
|
|
48
|
+
disableScroll?: boolean;
|
|
48
49
|
};
|
|
49
50
|
type ContainerComponent = ForwardRefExoticComponent<SheetContainerProps & RefAttributes<any>>;
|
|
50
51
|
type DraggableComponent = ForwardRefExoticComponent<SheetDraggableProps & RefAttributes<any>>;
|
package/dist/index.js
CHANGED
|
@@ -52,6 +52,26 @@ function getSnapPoints({
|
|
|
52
52
|
);
|
|
53
53
|
return snapPointValues;
|
|
54
54
|
}
|
|
55
|
+
function getClosestSnapPoint({
|
|
56
|
+
snapPoints,
|
|
57
|
+
currentY,
|
|
58
|
+
sheetHeight,
|
|
59
|
+
detent
|
|
60
|
+
}) {
|
|
61
|
+
const snapInverse = snapPoints.map(
|
|
62
|
+
(p) => sheetHeight - Math.min(p, sheetHeight)
|
|
63
|
+
);
|
|
64
|
+
if (detent === "content-height" && !snapInverse.includes(0)) {
|
|
65
|
+
snapInverse.unshift(0);
|
|
66
|
+
}
|
|
67
|
+
const snapTo = getClosest(snapInverse, currentY);
|
|
68
|
+
const snapIndex = snapInverse.indexOf(snapTo);
|
|
69
|
+
const snapY = validateSnapTo({
|
|
70
|
+
snapTo: getClosest(snapInverse, currentY),
|
|
71
|
+
sheetHeight
|
|
72
|
+
});
|
|
73
|
+
return { snapY, snapIndex };
|
|
74
|
+
}
|
|
55
75
|
function getClosest(nums, goal) {
|
|
56
76
|
let closest = nums[0];
|
|
57
77
|
let minDifference = Math.abs(nums[0] - goal);
|
|
@@ -142,11 +162,11 @@ var isIOS = cached(function() {
|
|
|
142
162
|
var SheetContext = React2.createContext(
|
|
143
163
|
void 0
|
|
144
164
|
);
|
|
145
|
-
|
|
165
|
+
function useSheetContext() {
|
|
146
166
|
const context = React2.useContext(SheetContext);
|
|
147
167
|
if (!context) throw new Error("Sheet context error");
|
|
148
168
|
return context;
|
|
149
|
-
}
|
|
169
|
+
}
|
|
150
170
|
var SheetScrollerContext = React2.createContext(void 0);
|
|
151
171
|
function SheetScrollerContextProvider({
|
|
152
172
|
children
|
|
@@ -167,11 +187,11 @@ function SheetScrollerContextProvider({
|
|
|
167
187
|
children
|
|
168
188
|
);
|
|
169
189
|
}
|
|
170
|
-
|
|
190
|
+
function useSheetScrollerContext() {
|
|
171
191
|
const context = React2.useContext(SheetScrollerContext);
|
|
172
192
|
if (!context) throw new Error("Sheet scroller context error");
|
|
173
193
|
return context;
|
|
174
|
-
}
|
|
194
|
+
}
|
|
175
195
|
|
|
176
196
|
// src/styles.ts
|
|
177
197
|
var styles = {
|
|
@@ -194,6 +214,7 @@ var styles = {
|
|
|
194
214
|
backgroundColor: "rgba(0, 0, 0, 0.2)",
|
|
195
215
|
touchAction: "none",
|
|
196
216
|
border: "none",
|
|
217
|
+
userSelect: "none",
|
|
197
218
|
WebkitTapHighlightColor: "transparent"
|
|
198
219
|
},
|
|
199
220
|
container: {
|
|
@@ -236,7 +257,8 @@ var styles = {
|
|
|
236
257
|
},
|
|
237
258
|
scroller: {
|
|
238
259
|
height: "100%",
|
|
239
|
-
overflowY: "auto"
|
|
260
|
+
overflowY: "auto",
|
|
261
|
+
overscrollBehaviorY: "none"
|
|
240
262
|
}
|
|
241
263
|
};
|
|
242
264
|
var useIsomorphicLayoutEffect = IS_SSR ? React2.useEffect : React2.useLayoutEffect;
|
|
@@ -667,11 +689,21 @@ var Sheet = React2.forwardRef(
|
|
|
667
689
|
onCloseEnd
|
|
668
690
|
};
|
|
669
691
|
});
|
|
670
|
-
const onDrag = useStableCallback((_,
|
|
692
|
+
const onDrag = useStableCallback((_, info) => {
|
|
693
|
+
const currentY = y.get();
|
|
694
|
+
if (snapPointsProp) {
|
|
695
|
+
const sheetHeight = getSheetHeight(sheetRef);
|
|
696
|
+
const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
|
|
697
|
+
const topSnapPoint = sheetHeight - snapPoints[0];
|
|
698
|
+
if (info.delta.y < 0 && currentY <= topSnapPoint) {
|
|
699
|
+
y.set(topSnapPoint);
|
|
700
|
+
return;
|
|
701
|
+
}
|
|
702
|
+
}
|
|
671
703
|
const velocity = y.getVelocity();
|
|
672
704
|
if (velocity > 0) indicatorRotation.set(10);
|
|
673
705
|
if (velocity < 0) indicatorRotation.set(-10);
|
|
674
|
-
y.set(Math.max(
|
|
706
|
+
y.set(Math.max(currentY + info.delta.y, 0));
|
|
675
707
|
});
|
|
676
708
|
const onDragStart = useStableCallback(() => {
|
|
677
709
|
const focusedElement = document.activeElement;
|
|
@@ -690,18 +722,13 @@ var Sheet = React2.forwardRef(
|
|
|
690
722
|
let yTo = 0;
|
|
691
723
|
const snapPoints = snapPointsProp ? getSnapPoints({ snapPointsProp, sheetHeight }) : void 0;
|
|
692
724
|
if (snapPoints) {
|
|
693
|
-
const
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
}
|
|
699
|
-
const snapTo = getClosest(snapInverse, currentY);
|
|
700
|
-
const snapIndex = snapInverse.indexOf(snapTo);
|
|
701
|
-
yTo = validateSnapTo({
|
|
702
|
-
snapTo: getClosest(snapInverse, currentY),
|
|
703
|
-
sheetHeight
|
|
725
|
+
const { snapY, snapIndex } = getClosestSnapPoint({
|
|
726
|
+
snapPoints,
|
|
727
|
+
currentY,
|
|
728
|
+
sheetHeight,
|
|
729
|
+
detent
|
|
704
730
|
});
|
|
731
|
+
yTo = snapY;
|
|
705
732
|
onSnap == null ? void 0 : onSnap(snapIndex);
|
|
706
733
|
} else if (currentY / sheetHeight > dragCloseThreshold) {
|
|
707
734
|
yTo = sheetHeight;
|
|
@@ -725,9 +752,9 @@ var Sheet = React2.forwardRef(
|
|
|
725
752
|
const snapIndex = initialSnap;
|
|
726
753
|
const sheetHeight = getSheetHeight(sheetRef);
|
|
727
754
|
const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
|
|
728
|
-
const
|
|
729
|
-
if (
|
|
730
|
-
yTo =
|
|
755
|
+
const snapY = getSnapY({ sheetHeight, snapPoints, snapIndex });
|
|
756
|
+
if (snapY !== null) {
|
|
757
|
+
yTo = snapY;
|
|
731
758
|
}
|
|
732
759
|
}
|
|
733
760
|
react.animate(y, yTo, animationOptions);
|
|
@@ -741,11 +768,11 @@ var Sheet = React2.forwardRef(
|
|
|
741
768
|
}
|
|
742
769
|
const sheetHeight = getSheetHeight(sheetRef);
|
|
743
770
|
const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
|
|
744
|
-
const
|
|
745
|
-
if (
|
|
746
|
-
react.animate(y,
|
|
771
|
+
const snapY = getSnapY({ sheetHeight, snapPoints, snapIndex });
|
|
772
|
+
if (snapY !== null) {
|
|
773
|
+
react.animate(y, snapY, animationOptions);
|
|
747
774
|
onSnap == null ? void 0 : onSnap(snapIndex);
|
|
748
|
-
if (
|
|
775
|
+
if (snapY + 1 >= sheetHeight) {
|
|
749
776
|
onClose();
|
|
750
777
|
}
|
|
751
778
|
}
|
|
@@ -853,19 +880,15 @@ var SheetContainer = React2.forwardRef(
|
|
|
853
880
|
animationOptions
|
|
854
881
|
} = useSheetContext();
|
|
855
882
|
const { handleAnimationComplete } = useEventCallbacks(isOpen, callbacks);
|
|
883
|
+
const height = detent === "full-height" ? MAX_HEIGHT : void 0;
|
|
884
|
+
const maxHeight = detent === "content-height" ? MAX_HEIGHT : void 0;
|
|
856
885
|
return /* @__PURE__ */ React2__default.default.createElement(
|
|
857
886
|
react.motion.div,
|
|
858
887
|
{
|
|
859
888
|
...rest,
|
|
860
889
|
ref: mergeRefs([sheetRef, ref]),
|
|
861
890
|
className: `react-modal-sheet-container ${className}`,
|
|
862
|
-
style: {
|
|
863
|
-
...styles.container,
|
|
864
|
-
...style,
|
|
865
|
-
...detent === "full-height" && { height: MAX_HEIGHT },
|
|
866
|
-
...detent === "content-height" && { maxHeight: MAX_HEIGHT },
|
|
867
|
-
y
|
|
868
|
-
},
|
|
891
|
+
style: { ...styles.container, height, maxHeight, ...style, y },
|
|
869
892
|
initial: { y: windowHeight },
|
|
870
893
|
exit: { y: windowHeight, transition: animationOptions },
|
|
871
894
|
onAnimationComplete: handleAnimationComplete
|
|
@@ -966,9 +989,20 @@ var SheetBackdrop = React2.forwardRef(
|
|
|
966
989
|
);
|
|
967
990
|
SheetBackdrop.displayName = "SheetBackdrop";
|
|
968
991
|
var SheetScroller = React2.forwardRef(
|
|
969
|
-
({
|
|
970
|
-
|
|
992
|
+
({
|
|
993
|
+
disableScroll = false,
|
|
994
|
+
draggableAt = "top",
|
|
995
|
+
children,
|
|
996
|
+
style,
|
|
997
|
+
className = "",
|
|
998
|
+
...rest
|
|
999
|
+
}, ref) => {
|
|
1000
|
+
const { setDragEnabled, setDragDisabled } = useSheetScrollerContext();
|
|
971
1001
|
function determineDragState(element) {
|
|
1002
|
+
if (disableScroll) {
|
|
1003
|
+
setDragEnabled();
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
972
1006
|
const { scrollTop, scrollHeight, clientHeight } = element;
|
|
973
1007
|
const isScrollable2 = scrollHeight > clientHeight;
|
|
974
1008
|
if (!isScrollable2) return;
|
|
@@ -976,22 +1010,18 @@ var SheetScroller = React2.forwardRef(
|
|
|
976
1010
|
const isAtBottom = scrollHeight - scrollTop === clientHeight;
|
|
977
1011
|
const shouldEnable = draggableAt === "top" && isAtTop || draggableAt === "bottom" && isAtBottom || draggableAt === "both" && (isAtTop || isAtBottom);
|
|
978
1012
|
if (shouldEnable) {
|
|
979
|
-
|
|
1013
|
+
setDragEnabled();
|
|
980
1014
|
} else {
|
|
981
|
-
|
|
1015
|
+
setDragDisabled();
|
|
982
1016
|
}
|
|
983
1017
|
}
|
|
984
|
-
function onScroll(
|
|
985
|
-
determineDragState(
|
|
986
|
-
if (rest.onScroll)
|
|
987
|
-
rest.onScroll(e);
|
|
988
|
-
}
|
|
1018
|
+
function onScroll(event) {
|
|
1019
|
+
determineDragState(event.currentTarget);
|
|
1020
|
+
if (rest.onScroll) rest.onScroll(event);
|
|
989
1021
|
}
|
|
990
|
-
function onTouchStart(
|
|
991
|
-
determineDragState(
|
|
992
|
-
if (rest.onTouchStart)
|
|
993
|
-
rest.onTouchStart(e);
|
|
994
|
-
}
|
|
1022
|
+
function onTouchStart(event) {
|
|
1023
|
+
determineDragState(event.currentTarget);
|
|
1024
|
+
if (rest.onTouchStart) rest.onTouchStart(event);
|
|
995
1025
|
}
|
|
996
1026
|
const scrollProps = isTouchDevice() ? { onScroll, onTouchStart } : void 0;
|
|
997
1027
|
return /* @__PURE__ */ React2__default.default.createElement(
|
|
@@ -1000,7 +1030,11 @@ var SheetScroller = React2.forwardRef(
|
|
|
1000
1030
|
...rest,
|
|
1001
1031
|
ref,
|
|
1002
1032
|
className: `react-modal-sheet-scroller ${className}`,
|
|
1003
|
-
style: {
|
|
1033
|
+
style: {
|
|
1034
|
+
...styles.scroller,
|
|
1035
|
+
...style,
|
|
1036
|
+
overflowY: disableScroll ? "hidden" : "auto"
|
|
1037
|
+
},
|
|
1004
1038
|
...scrollProps
|
|
1005
1039
|
},
|
|
1006
1040
|
children
|