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 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 | 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`. |
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
- var useSheetContext = () => {
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
- var useSheetScrollerContext = () => {
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((_, { delta }) => {
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(y.get() + delta.y, 0));
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 snapInverse = snapPoints.map(
694
- (p) => sheetHeight - Math.min(p, sheetHeight)
695
- );
696
- if (detent === "content-height" && !snapInverse.includes(0)) {
697
- snapInverse.unshift(0);
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 ySnap = getSnapY({ sheetHeight, snapPoints, snapIndex });
729
- if (ySnap !== null) {
730
- yTo = ySnap;
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 ySnap = getSnapY({ sheetHeight, snapPoints, snapIndex });
745
- if (ySnap !== null) {
746
- react.animate(y, ySnap, animationOptions);
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 (ySnap + 1 >= sheetHeight) {
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
- ({ draggableAt = "top", children, style, className = "", ...rest }, ref) => {
970
- const sheetScrollerContext = useSheetScrollerContext();
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
- sheetScrollerContext.setDragEnabled();
1013
+ setDragEnabled();
980
1014
  } else {
981
- sheetScrollerContext.setDragDisabled();
1015
+ setDragDisabled();
982
1016
  }
983
1017
  }
984
- function onScroll(e) {
985
- determineDragState(e.currentTarget);
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(e) {
991
- determineDragState(e.currentTarget);
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: { ...styles.scroller, ...style },
1033
+ style: {
1034
+ ...styles.scroller,
1035
+ ...style,
1036
+ overflowY: disableScroll ? "hidden" : "auto"
1037
+ },
1004
1038
  ...scrollProps
1005
1039
  },
1006
1040
  children