11import {
22 animate ,
3+ Axis ,
34 type DragHandler ,
45 motion ,
56 type Transition ,
@@ -19,6 +20,7 @@ import useMeasure from 'react-use-measure';
1920import {
2021 DEFAULT_DRAG_CLOSE_THRESHOLD ,
2122 DEFAULT_DRAG_VELOCITY_THRESHOLD ,
23+ DEFAULT_TOP_CONSTRAINT ,
2224 DEFAULT_TWEEN_CONFIG ,
2325 IS_SSR ,
2426 REDUCED_MOTION_TWEEN_CONFIG ,
@@ -37,7 +39,7 @@ import {
3739} from './snap' ;
3840import { styles } from './styles' ;
3941import { type SheetContextType , type SheetProps } from './types' ;
40- import { applyStyles , waitForElement } from './utils' ;
42+ import { applyConstraints , applyStyles , waitForElement } from './utils' ;
4143
4244export const Sheet = forwardRef < any , SheetProps > (
4345 (
@@ -61,6 +63,7 @@ export const Sheet = forwardRef<any, SheetProps>(
6163 style,
6264 tweenConfig = DEFAULT_TWEEN_CONFIG ,
6365 unstyled = false ,
66+ dragConstraints : dragConstraintsProp ,
6467 onOpenStart,
6568 onOpenEnd,
6669 onClose,
@@ -83,9 +86,27 @@ export const Sheet = forwardRef<any, SheetProps>(
8386 ? computeSnapPoints ( { sheetHeight, snapPointsProp } )
8487 : [ ] ;
8588
89+ // for default & content detents, the sheet height is constrained instead of the drag
90+ const sheetHeightConstraint =
91+ detent === 'full'
92+ ? 0
93+ : ( dragConstraintsProp ?. min ?? DEFAULT_TOP_CONSTRAINT ) ;
94+
95+ const dragBottomConstraint =
96+ ( dragConstraintsProp ?. max ?? Infinity ) - sheetHeightConstraint ;
97+
98+ const dragConstraints : Axis = {
99+ min : 0 , // top constraint (applied through sheet height instead)
100+ max : dragBottomConstraint , // bottom constraint
101+ } ;
102+
86103 const { windowHeight } = useDimensions ( ) ;
87104 const closedY = sheetHeight > 0 ? sheetHeight : windowHeight ;
88105 const y = useMotionValue ( closedY ) ;
106+ const yUnconstrainedRef = useRef < number | undefined > ( undefined ) ;
107+ // y is below 0 when the sheet is overextended
108+ // this happens because the sheet is elastic and can be dragged beyond the full open position
109+ const yOverflow = useTransform ( y , ( val ) => ( val < 0 ? Math . abs ( val ) : 0 ) ) ;
89110 const yInverted = useTransform ( y , ( val ) => Math . max ( sheetHeight - val , 0 ) ) ;
90111 const indicatorRotation = useMotionValue ( 0 ) ;
91112
@@ -104,12 +125,12 @@ export const Sheet = forwardRef<any, SheetProps>(
104125 // Disable drag if the keyboard is open to avoid weird behavior
105126 const disableDrag = keyboard . isKeyboardOpen || disableDragProp ;
106127
107- // +2 for tolerance in case the animated value is slightly off
128+ // -10 for tolerance in case the animated value is slightly off
108129 const zIndex = useTransform ( y , ( val ) =>
109- val + 2 >= closedY ? - 1 : ( style ?. zIndex ?? 9999 )
130+ val - 10 >= closedY ? - 1 : ( style ?. zIndex ?? 9999 )
110131 ) ;
111132 const visibility = useTransform ( y , ( val ) =>
112- val + 2 >= closedY ? 'hidden' : 'visible'
133+ val - 10 >= closedY ? 'hidden' : 'visible'
113134 ) ;
114135
115136 const updateSnap = useStableCallback ( ( snapIndex : number ) => {
@@ -173,27 +194,37 @@ export const Sheet = forwardRef<any, SheetProps>(
173194 } ) ;
174195
175196 const onDrag = useStableCallback < DragHandler > ( ( event , info ) => {
176- onDragProp ?. ( event , info ) ;
197+ if ( yUnconstrainedRef . current === undefined ) return ;
177198
178- const currentY = y . get ( ) ;
199+ onDragProp ?.( event , info ) ;
200+ if ( event . defaultPrevented ) return ;
179201
180202 // Update drag indicator rotation based on drag velocity
181203 const velocity = y . getVelocity ( ) ;
182204 if ( velocity > 0 ) indicatorRotation . set ( 10 ) ;
183205 if ( velocity < 0 ) indicatorRotation . set ( - 10 ) ;
184206
185- // Make sure user cannot drag beyond the top of the sheet
186- y . set ( Math . max ( currentY + info . delta . y , 0 ) ) ;
207+ const currentY = yUnconstrainedRef . current ;
208+ const nextY = currentY + info . delta . y ;
209+ yUnconstrainedRef . current = nextY ;
210+ const constrainedY = applyConstraints ( nextY , dragConstraints , {
211+ min : 0.1 ,
212+ max : 0.1 ,
213+ } ) ;
214+ y . set ( constrainedY ) ;
187215 } ) ;
188216
189217 const onDragStart = useStableCallback < DragHandler > ( ( event , info ) => {
190- blurActiveInput ( ) ;
218+ yUnconstrainedRef . current = y . get ( ) ;
191219 onDragStartProp ?.( event , info ) ;
220+ if ( event . defaultPrevented ) return ;
221+ blurActiveInput ( ) ;
192222 } ) ;
193223
194224 const onDragEnd = useStableCallback < DragHandler > ( ( event , info ) => {
195- blurActiveInput ( ) ;
196225 onDragEndProp ?.( event , info ) ;
226+ if ( event . defaultPrevented ) return ;
227+ blurActiveInput ( ) ;
197228
198229 const currentY = y . get ( ) ;
199230
@@ -258,6 +289,7 @@ export const Sheet = forwardRef<any, SheetProps>(
258289
259290 // Update the spring value so that the sheet is animated to the snap point
260291 animate ( y , yTo , animationOptions ) ;
292+ yUnconstrainedRef . current = undefined ;
261293
262294 // +1px for imprecision tolerance
263295 // Only call onClose if disableDismiss is false or if we're actually closing
@@ -274,6 +306,9 @@ export const Sheet = forwardRef<any, SheetProps>(
274306 yInverted,
275307 height : sheetHeight ,
276308 snapTo,
309+ getSnapPoint,
310+ snapPoints,
311+ currentSnap,
277312 } ) ) ;
278313
279314 useModalEffect ( {
@@ -348,6 +383,9 @@ export const Sheet = forwardRef<any, SheetProps>(
348383 sheetRef,
349384 unstyled,
350385 y,
386+ yOverflow,
387+ sheetHeight,
388+ sheetHeightConstraint,
351389 } ;
352390
353391 const sheet = (
0 commit comments