react-modal-sheet 5.4.1 → 5.6.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/src/sheet.tsx CHANGED
@@ -10,6 +10,7 @@ import {
10
10
  import React, {
11
11
  forwardRef,
12
12
  useImperativeHandle,
13
+ useMemo,
13
14
  useRef,
14
15
  useState,
15
16
  } from 'react';
@@ -23,21 +24,21 @@ import {
23
24
  IS_SSR,
24
25
  REDUCED_MOTION_TWEEN_CONFIG,
25
26
  } from './constants';
26
- import { SheetContext } from './context';
27
+ import { ExposedSheetContext, InternalSheetContext } from './context';
27
28
  import { useDimensions } from './hooks/use-dimensions';
28
29
  import { useKeyboardAvoidance } from './hooks/use-keyboard-avoidance';
29
30
  import { useModalEffect } from './hooks/use-modal-effect';
30
31
  import { usePreventScroll } from './hooks/use-prevent-scroll';
31
32
  import { useSheetState } from './hooks/use-sheet-state';
32
33
  import { useStableCallback } from './hooks/use-stable-callback';
33
- import {
34
- computeSnapPoints,
35
- handleHighVelocityDrag,
36
- handleLowVelocityDrag,
37
- } from './snap';
34
+ import { classifyDragEnd, computeSnapPoints } from './snap';
38
35
  import { styles } from './styles';
39
- import { type SheetContextType, type SheetProps } from './types';
40
- import { applyStyles, waitForElement, willOpenKeyboard } from './utils';
36
+ import type {
37
+ ExposedContextType,
38
+ InternalContextType,
39
+ SheetProps,
40
+ } from './types';
41
+ import { applyStyles, clamp, waitForElement, willOpenKeyboard } from './utils';
41
42
 
42
43
  export const Sheet = forwardRef<any, SheetProps>(
43
44
  (
@@ -78,17 +79,12 @@ export const Sheet = forwardRef<any, SheetProps>(
78
79
  const sheetRef = useRef<HTMLDivElement>(null);
79
80
  const sheetHeight = Math.round(sheetBounds.height);
80
81
  const [currentSnap, setCurrentSnap] = useState(initialSnap);
81
- const snapPoints =
82
- snapPointsProp && sheetHeight > 0
83
- ? computeSnapPoints({ sheetHeight, snapPointsProp })
84
- : [];
85
-
86
82
  const { windowHeight } = useDimensions();
87
83
  const closedY = sheetHeight > 0 ? sheetHeight : windowHeight;
88
84
  const y = useMotionValue(closedY);
89
85
  const yInverted = useTransform(y, (val) => Math.max(sheetHeight - val, 0));
86
+ const yProgress = useTransform(y, (val) => clamp(1 - val / closedY, 0, 1));
90
87
  const indicatorRotation = useMotionValue(0);
91
-
92
88
  const shouldReduceMotion = useReducedMotion();
93
89
  const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
94
90
  const animationOptions: Transition = {
@@ -104,6 +100,13 @@ export const Sheet = forwardRef<any, SheetProps>(
104
100
  val + 2 >= closedY ? 'hidden' : 'visible'
105
101
  );
106
102
 
103
+ // Memo snap points as we expose them in context so we want them to be stable
104
+ const snapPoints = useMemo(() => {
105
+ return snapPointsProp && sheetHeight > 0
106
+ ? computeSnapPoints({ sheetHeight, snapPointsProp })
107
+ : [];
108
+ }, [sheetHeight, snapPointsProp?.join('')]);
109
+
107
110
  const updateSnap = useStableCallback((snapIndex: number) => {
108
111
  setCurrentSnap(snapIndex);
109
112
  onSnap?.(snapIndex);
@@ -232,63 +235,31 @@ export const Sheet = forwardRef<any, SheetProps>(
232
235
 
233
236
  const currentY = y.get();
234
237
 
235
- let yTo = 0;
236
-
237
- const currentSnapPoint =
238
- currentSnap !== undefined ? getSnapPoint(currentSnap) : null;
239
-
240
- if (currentSnapPoint) {
241
- const dragOffsetDirection = info.offset.y > 0 ? 'down' : 'up';
242
- const dragVelocityDirection = info.velocity.y > 0 ? 'down' : 'up';
243
- const isHighVelocity =
244
- Math.abs(info.velocity.y) > dragVelocityThreshold;
238
+ const result = classifyDragEnd({
239
+ y: currentY,
240
+ info,
241
+ sheetHeight,
242
+ dragCloseThreshold,
243
+ snapPoints,
244
+ dragVelocityThreshold,
245
+ });
245
246
 
246
- let result: { yTo: number; snapIndex: number | undefined };
247
+ let yTo = result.yTo;
247
248
 
248
- if (isHighVelocity) {
249
- result = handleHighVelocityDrag({
250
- snapPoints,
251
- dragDirection: dragVelocityDirection,
252
- });
253
- } else {
254
- result = handleLowVelocityDrag({
255
- currentSnapPoint,
256
- currentY,
257
- dragDirection: dragOffsetDirection,
258
- snapPoints,
259
- velocity: info.velocity.y,
260
- });
261
- }
249
+ // If disableDismiss is true, prevent closing via gesture
250
+ if (disableDismiss && yTo + 1 >= sheetHeight) {
251
+ // Use the bottom-most open snap point
252
+ const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
262
253
 
263
- yTo = result.yTo;
264
-
265
- // If disableDismiss is true, prevent closing via gesture
266
- if (disableDismiss && yTo + 1 >= sheetHeight) {
267
- // Use the bottom-most open snap point
268
- const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
269
-
270
- if (bottomSnapPoint) {
271
- yTo = bottomSnapPoint.snapValueY;
272
- updateSnap(bottomSnapPoint.snapIndex);
273
- } else {
274
- // If no open snap points available, stay at current position
275
- yTo = currentY;
276
- }
277
- } else if (result.snapIndex !== undefined) {
278
- updateSnap(result.snapIndex);
279
- }
280
- } else if (
281
- info.velocity.y > dragVelocityThreshold ||
282
- currentY > sheetHeight * dragCloseThreshold
283
- ) {
284
- // Close the sheet if dragged past the threshold or if the velocity is high enough
285
- // But only if disableDismiss is false
286
- if (disableDismiss) {
287
- // If disableDismiss, snap back to the open position
288
- yTo = 0;
254
+ if (bottomSnapPoint) {
255
+ yTo = bottomSnapPoint.snapValueY;
256
+ updateSnap(bottomSnapPoint.snapIndex);
289
257
  } else {
290
- yTo = closedY;
258
+ // If no open snap points available, stay at current position
259
+ yTo = currentY;
291
260
  }
261
+ } else if (result.snapIndex !== undefined) {
262
+ updateSnap(result.snapIndex);
292
263
  }
293
264
 
294
265
  // Update the spring value so that the sheet is animated to the snap point
@@ -304,13 +275,6 @@ export const Sheet = forwardRef<any, SheetProps>(
304
275
  indicatorRotation.set(0);
305
276
  });
306
277
 
307
- useImperativeHandle(ref, () => ({
308
- y,
309
- yInverted,
310
- height: sheetHeight,
311
- snapTo,
312
- }));
313
-
314
278
  useModalEffect({
315
279
  y,
316
280
  detent,
@@ -346,23 +310,17 @@ export const Sheet = forwardRef<any, SheetProps>(
346
310
  const yTo = initialSnapPoint?.snapValueY ?? 0;
347
311
 
348
312
  await animate(y, yTo, animationOptions);
349
-
350
- if (initialSnap !== undefined) {
351
- updateSnap(initialSnap);
352
- }
353
-
313
+ if (initialSnap !== undefined) updateSnap(initialSnap);
354
314
  onOpenEnd?.();
355
315
  },
356
316
  onClosing: async () => {
357
317
  onCloseStart?.();
358
-
359
318
  await animate(y, closedY, animationOptions);
360
-
361
319
  onCloseEnd?.();
362
320
  },
363
321
  });
364
322
 
365
- const dragProps: SheetContextType['dragProps'] = {
323
+ const dragProps: InternalContextType['dragProps'] = {
366
324
  drag: 'y',
367
325
  dragElastic: 0,
368
326
  dragMomentum: false,
@@ -372,7 +330,7 @@ export const Sheet = forwardRef<any, SheetProps>(
372
330
  onDragEnd,
373
331
  };
374
332
 
375
- const context: SheetContextType = {
333
+ const internalContext: InternalContextType = {
376
334
  currentSnap,
377
335
  detent,
378
336
  disableDrag,
@@ -383,11 +341,24 @@ export const Sheet = forwardRef<any, SheetProps>(
383
341
  sheetBoundsRef,
384
342
  sheetRef,
385
343
  unstyled,
344
+ yProgress,
386
345
  y,
387
346
  };
388
347
 
348
+ const exposedContext: ExposedContextType = {
349
+ height: sheetHeight,
350
+ currentSnap,
351
+ snapPoints,
352
+ snapTo,
353
+ y,
354
+ yInverted,
355
+ yProgress,
356
+ };
357
+
358
+ useImperativeHandle(ref, () => exposedContext);
359
+
389
360
  const sheet = (
390
- <SheetContext.Provider value={context}>
361
+ <InternalSheetContext.Provider value={internalContext}>
391
362
  <motion.div
392
363
  {...rest}
393
364
  ref={ref}
@@ -400,9 +371,11 @@ export const Sheet = forwardRef<any, SheetProps>(
400
371
  ...style,
401
372
  }}
402
373
  >
403
- {state !== 'closed' ? children : null}
374
+ <ExposedSheetContext.Provider value={exposedContext}>
375
+ {state !== 'closed' ? children : null}
376
+ </ExposedSheetContext.Provider>
404
377
  </motion.div>
405
- </SheetContext.Provider>
378
+ </InternalSheetContext.Provider>
406
379
  );
407
380
 
408
381
  if (IS_SSR) return sheet;
package/src/snap.ts CHANGED
@@ -1,5 +1,6 @@
1
+ import type { PanInfo } from 'motion';
1
2
  import type { SheetSnapPoint } from './types';
2
- import { isAscendingOrder } from './utils';
3
+ import { clamp, isAscendingOrder } from './utils';
3
4
 
4
5
  /**
5
6
  * Convert negative / percentage snap points to absolute values
@@ -107,136 +108,234 @@ export function computeSnapPoints({
107
108
  }));
108
109
  }
109
110
 
110
- function findClosestSnapPoint({
111
- snapPoints,
112
- currentY,
113
- }: {
114
- snapPoints: SheetSnapPoint[];
115
- currentY: number;
116
- }) {
111
+ function sign(value: number) {
112
+ if (value > 0) return 1;
113
+ if (value < 0) return -1;
114
+ return 0;
115
+ }
116
+
117
+ function toAscendingYSnaps(snapPoints: SheetSnapPoint[]) {
118
+ return snapPoints.slice().sort((a, b) => a.snapValueY - b.snapValueY);
119
+ }
120
+
121
+ function findNearestSnapByY(snapPoints: SheetSnapPoint[], y: number) {
117
122
  return snapPoints.reduce((closest, snap) =>
118
- Math.abs(snap.snapValueY - currentY) <
119
- Math.abs(closest.snapValueY - currentY)
123
+ Math.abs(snap.snapValueY - y) < Math.abs(closest.snapValueY - y)
120
124
  ? snap
121
125
  : closest
122
126
  );
123
127
  }
124
128
 
125
- function findNextSnapPointInDirection({
126
- y,
127
- snapPoints,
128
- dragDirection,
129
- }: {
130
- y: number;
131
- snapPoints: SheetSnapPoint[];
132
- dragDirection: 'up' | 'down';
133
- }) {
134
- // NOTE: lower Y means higher in the sheet position!
135
- if (dragDirection === 'down') {
136
- /**
137
- * Example:
138
- *
139
- * [
140
- * { snapIndex: 0, snapValueY: 810 },
141
- * { snapIndex: 1, snapValueY: 640 },
142
- * { snapIndex: 2, snapValueY: 405 }, <-- next down
143
- * ------------- Y = 60 ------------
144
- * { snapIndex: 3, snapValueY: 50 },
145
- * { snapIndex: 4, snapValueY: 0 },
146
- * ]
147
- */
148
- return snapPoints
149
- .slice()
150
- .reverse()
151
- .find((s) => s.snapValueY > y);
152
- } else {
153
- /**
154
- * Example:
155
- * [
156
- * { snapIndex: 0, snapValueY: 810 },
157
- * { snapIndex: 1, snapValueY: 640 },
158
- * { snapIndex: 2, snapValueY: 405 },
159
- * ------------- Y = 60 ------------
160
- * { snapIndex: 3, snapValueY: 50 }, <-- next up
161
- * { snapIndex: 4, snapValueY: 0 },
162
- * ]
163
- */
164
- return snapPoints.find((s) => s.snapValueY < y);
129
+ function findNearestSnapAboveY(snapPoints: SheetSnapPoint[], y: number) {
130
+ const snaps = toAscendingYSnaps(snapPoints);
131
+
132
+ for (let i = snaps.length - 1; i >= 0; i--) {
133
+ if (snaps[i].snapValueY < y) {
134
+ return snaps[i];
135
+ }
165
136
  }
137
+
138
+ return null;
166
139
  }
167
- export function handleHighVelocityDrag({
168
- dragDirection,
169
- snapPoints,
140
+
141
+ function findNearestSnapBelowY(snapPoints: SheetSnapPoint[], y: number) {
142
+ const snaps = toAscendingYSnaps(snapPoints);
143
+
144
+ for (let i = 0; i < snaps.length; i++) {
145
+ if (snaps[i].snapValueY > y) {
146
+ return snaps[i];
147
+ }
148
+ }
149
+
150
+ return null;
151
+ }
152
+
153
+ function resolveDirection({
154
+ offsetY,
155
+ velocityY,
156
+ deltaY,
170
157
  }: {
171
- dragDirection: 'up' | 'down';
172
- snapPoints: SheetSnapPoint[];
158
+ offsetY: number;
159
+ velocityY: number;
160
+ deltaY: number;
173
161
  }) {
174
- // Go to either the last or the first snap point depending on the direction
175
- const bottomSnapPoint = snapPoints[0];
176
- const topSnapPoint = snapPoints[snapPoints.length - 1];
162
+ if (Math.abs(velocityY) > 50) return sign(velocityY);
177
163
 
178
- if (dragDirection === 'down') {
179
- return {
180
- yTo: bottomSnapPoint.snapValueY,
181
- snapIndex: bottomSnapPoint.snapIndex,
182
- };
183
- }
184
- return {
185
- yTo: topSnapPoint.snapValueY,
186
- snapIndex: topSnapPoint.snapIndex,
187
- };
164
+ const offsetDirection = sign(offsetY);
165
+ if (offsetDirection !== 0) return offsetDirection;
166
+
167
+ return sign(deltaY);
188
168
  }
189
169
 
190
- export function handleLowVelocityDrag({
191
- currentSnapPoint,
192
- currentY,
193
- dragDirection,
170
+ /**
171
+ * Classifies the final sheet position when a drag gesture ends.
172
+ *
173
+ * Decision flow (high level):
174
+ * 1) Predict momentum endpoint from current position and velocity.
175
+ * 2) Resolve drag direction (prefer velocity, fallback to offset/delta).
176
+ * 3) Detect flick tiers:
177
+ * - strong flick -> jump fully open/closed
178
+ * - flick -> move to next snap in flick direction
179
+ * 4) For slower drags:
180
+ * - tiny drag or near-snap stickiness -> snap back to nearest snap
181
+ * - otherwise, use progress between surrounding snaps and commit threshold
182
+ * 5) Apply velocity-assisted bias to nearest predicted snap for natural feel.
183
+ * 6) Fallback to nearest snap.
184
+ *
185
+ * Special case with no snap points:
186
+ * - flick: open/close by direction
187
+ * - otherwise: open/close by predicted position vs close threshold midpoint
188
+ */
189
+ export function classifyDragEnd({
190
+ y,
191
+ info,
192
+ sheetHeight,
194
193
  snapPoints,
195
- velocity,
194
+ dragVelocityThreshold,
195
+ dragCloseThreshold,
196
196
  }: {
197
- currentSnapPoint: SheetSnapPoint;
198
- currentY: number;
199
- dragDirection: 'up' | 'down';
197
+ y: number;
198
+ info: PanInfo;
199
+ sheetHeight: number;
200
200
  snapPoints: SheetSnapPoint[];
201
- velocity: number;
201
+ dragVelocityThreshold: number;
202
+ dragCloseThreshold: number;
202
203
  }) {
203
- const closestSnapRelativeToCurrentY = findClosestSnapPoint({
204
- snapPoints,
205
- currentY,
206
- });
207
-
208
204
  /**
209
- * If velocity is very low the user has stopped the sheet to a specific
210
- * position and we should snap to the closest snap point as there is no
211
- * "momentum" that would push the sheet further to the given direction
205
+ * Thresholds and configuration for drag classification.
206
+ * Tweak these to adjust the feel of the sheet.
212
207
  */
213
- if (Math.abs(velocity) < 20) {
208
+ // Velocity above this is considered a flick gesture.
209
+ const flickVelocity = dragVelocityThreshold;
210
+ // Velocity above this is treated as a strong flick and jumps to full open/closed.
211
+ const strongFlickVelocity = Math.max(2000, flickVelocity);
212
+ // In slow drags, crossing this portion of the current snap region commits to the next snap.
213
+ const distanceCommitRatio = 0.35;
214
+ // Time horizon (in seconds) used to estimate momentum-based end position.
215
+ const predictionTime = 0.2;
216
+ // Very small drags below this distance snap back instead of changing snap state.
217
+ const minDragDistance = 20;
218
+ // If drag ends within this many pixels from a snap, stick to that snap to reduce jitter.
219
+ const snapStickiness = 24;
220
+
221
+ const minY = 0;
222
+ const maxY = sheetHeight;
223
+ const offsetY = info.offset.y;
224
+ const deltaY = info.delta.y;
225
+ const velocityY = info.velocity.y;
226
+
227
+ // Step 1 — Compute predicted end position from momentum.
228
+ const predictedY = clamp(y + velocityY * predictionTime, minY, maxY);
229
+ const absVelocity = Math.abs(velocityY);
230
+
231
+ // Step 2 + 3 — Flick detection and gesture direction resolution.
232
+ const direction = resolveDirection({ offsetY, velocityY, deltaY });
233
+
234
+ const isStrongFlick = absVelocity > strongFlickVelocity;
235
+ const isFlick = absVelocity > flickVelocity;
236
+
237
+ // Step 4 — No snap points: decide between open/close only.
238
+ if (snapPoints.length === 0) {
239
+ if (isFlick) {
240
+ return {
241
+ yTo: direction < 0 ? minY : maxY,
242
+ snapIndex: undefined,
243
+ };
244
+ }
245
+
246
+ const midpoint = minY + (maxY - minY) * dragCloseThreshold;
247
+
214
248
  return {
215
- yTo: closestSnapRelativeToCurrentY.snapValueY,
216
- snapIndex: closestSnapRelativeToCurrentY.snapIndex,
249
+ yTo: predictedY < midpoint ? minY : maxY,
250
+ snapIndex: undefined,
217
251
  };
218
252
  }
219
253
 
220
- /**
221
- * If the dragging has a bit more velocity, we instead want to go to
222
- * the next snap point in the given direction if it exists
223
- */
224
- const nextSnapInDirectionRelativeToCurrentY = findNextSnapPointInDirection({
225
- y: currentY,
226
- snapPoints,
227
- dragDirection,
228
- });
254
+ const nearestSnap = findNearestSnapByY(snapPoints, y);
255
+
256
+ // Slow-drag guard — tiny movement: snap back to nearest.
257
+ if (!isFlick && Math.abs(offsetY) < minDragDistance) {
258
+ return {
259
+ yTo: nearestSnap.snapValueY,
260
+ snapIndex: nearestSnap.snapIndex,
261
+ };
262
+ }
263
+
264
+ // Hysteresis (stickiness) — avoid jitter if already close to a snap.
265
+ if (!isFlick && Math.abs(y - nearestSnap.snapValueY) < snapStickiness) {
266
+ return {
267
+ yTo: nearestSnap.snapValueY,
268
+ snapIndex: nearestSnap.snapIndex,
269
+ };
270
+ }
229
271
 
230
- if (nextSnapInDirectionRelativeToCurrentY) {
272
+ // Step 5 — Strong flick overrides everything and goes fully open/closed.
273
+ if (isStrongFlick) {
274
+ const target = direction < 0 ? minY : maxY;
275
+ const targetSnap = findNearestSnapByY(snapPoints, target);
276
+
277
+ return {
278
+ yTo: target,
279
+ snapIndex: targetSnap.snapIndex,
280
+ };
281
+ }
282
+
283
+ // Step 5 — Medium flick moves to the next snap in flick direction.
284
+ if (isFlick) {
285
+ const nextSnap =
286
+ direction < 0
287
+ ? findNearestSnapAboveY(snapPoints, y)
288
+ : findNearestSnapBelowY(snapPoints, y);
289
+
290
+ const fallbackY = direction < 0 ? minY : maxY;
291
+ const fallbackSnap = findNearestSnapByY(snapPoints, fallbackY);
292
+ const targetSnap = nextSnap ?? fallbackSnap;
293
+
294
+ return {
295
+ yTo: targetSnap.snapValueY,
296
+ snapIndex: targetSnap.snapIndex,
297
+ };
298
+ }
299
+
300
+ const firstSnap = toAscendingYSnaps(snapPoints)[0];
301
+ const lastSnap = toAscendingYSnaps(snapPoints)[snapPoints.length - 1];
302
+ const prevSnap = findNearestSnapAboveY(snapPoints, y) ?? firstSnap;
303
+ const nextSnap = findNearestSnapBelowY(snapPoints, y) ?? lastSnap;
304
+
305
+ if (prevSnap.snapIndex === nextSnap.snapIndex) {
231
306
  return {
232
- yTo: nextSnapInDirectionRelativeToCurrentY.snapValueY,
233
- snapIndex: nextSnapInDirectionRelativeToCurrentY.snapIndex,
307
+ yTo: prevSnap.snapValueY,
308
+ snapIndex: prevSnap.snapIndex,
234
309
  };
235
310
  }
236
311
 
237
- // No snap point down, stay at current
312
+ const range = nextSnap.snapValueY - prevSnap.snapValueY;
313
+ const progress = range > 0 ? (y - prevSnap.snapValueY) / range : 0;
314
+
315
+ // Step 6 — Slow drag commit based on progress within the current snap region.
316
+ let selectedSnap = nearestSnap;
317
+
318
+ if (direction > 0) {
319
+ selectedSnap = progress > distanceCommitRatio ? nextSnap : prevSnap;
320
+ } else if (direction < 0) {
321
+ selectedSnap = progress < 1 - distanceCommitRatio ? prevSnap : nextSnap;
322
+ }
323
+
324
+ // Step 7 — Velocity-assisted bias toward predicted-position snap.
325
+ if (absVelocity > 200) {
326
+ const predictedSnap = findNearestSnapByY(snapPoints, predictedY);
327
+
328
+ if (
329
+ Math.abs(predictedSnap.snapValueY - y) <
330
+ Math.abs(selectedSnap.snapValueY - y)
331
+ ) {
332
+ selectedSnap = predictedSnap;
333
+ }
334
+ }
335
+
336
+ // Step 8 — Final fallback.
238
337
  return {
239
- yTo: currentSnapPoint.snapValueY,
240
- snapIndex: currentSnapPoint.snapIndex,
338
+ yTo: selectedSnap.snapValueY,
339
+ snapIndex: selectedSnap.snapIndex,
241
340
  };
242
341
  }
package/src/types.tsx CHANGED
@@ -103,7 +103,7 @@ export type SheetSnapPoint = {
103
103
  snapValueY: number; // Y value is inverted as `y = 0` means sheet is at the top
104
104
  };
105
105
 
106
- export interface SheetContextType {
106
+ export interface InternalContextType {
107
107
  currentSnap?: number;
108
108
  detent: SheetDetent;
109
109
  disableDrag: boolean;
@@ -114,9 +114,20 @@ export interface SheetContextType {
114
114
  sheetBoundsRef: (node: HTMLDivElement | null) => void;
115
115
  sheetRef: RefObject<any>;
116
116
  unstyled: boolean;
117
+ yProgress: MotionValue<number>;
117
118
  y: MotionValue<any>;
118
119
  }
119
120
 
121
+ export interface ExposedContextType {
122
+ currentSnap?: number;
123
+ height: number;
124
+ snapPoints: SheetSnapPoint[];
125
+ snapTo: (index: number) => void;
126
+ y: MotionValue<number>;
127
+ yInverted: MotionValue<number>;
128
+ yProgress: MotionValue<number>;
129
+ }
130
+
120
131
  export interface SheetScrollerContextType {
121
132
  disableDrag: boolean;
122
133
  setDragDisabled: () => void;
@@ -149,6 +160,7 @@ interface SheetCompoundComponent {
149
160
  DragIndicator: FunctionComponent<SheetDragIndicatorProps>;
150
161
  Content: ContentComponent;
151
162
  Backdrop: BackdropComponent;
163
+ useContext: () => ExposedContextType;
152
164
  }
153
165
 
154
166
  export type SheetCompound = SheetComponent & SheetCompoundComponent;
package/src/utils.ts CHANGED
@@ -114,3 +114,7 @@ export function willOpenKeyboard(target: Element) {
114
114
  export function isHTTPS() {
115
115
  return typeof window !== 'undefined' && window.isSecureContext;
116
116
  }
117
+
118
+ export function clamp(value: number, min: number, max: number) {
119
+ return Math.min(Math.max(value, min), max);
120
+ }