react-modal-sheet 5.5.0 → 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/package.json CHANGED
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "5.5.0",
2
+ "version": "5.6.0",
3
3
  "license": "MIT",
4
4
  "name": "react-modal-sheet",
5
5
  "description": "Flexible bottom sheet component for your React apps",
package/src/context.tsx CHANGED
@@ -1,12 +1,22 @@
1
1
  import { createContext, useContext } from 'react';
2
- import { type SheetContextType } from './types';
2
+ import type { ExposedContextType, InternalContextType } from './types';
3
3
 
4
- export const SheetContext = createContext<SheetContextType | undefined>(
5
- undefined
6
- );
4
+ export const InternalSheetContext = createContext<
5
+ InternalContextType | undefined
6
+ >(undefined);
7
7
 
8
- export function useSheetContext() {
9
- const context = useContext(SheetContext);
8
+ export const ExposedSheetContext = createContext<
9
+ ExposedContextType | undefined
10
+ >(undefined);
11
+
12
+ export function useInternalContext() {
13
+ const context = useContext(InternalSheetContext);
14
+ if (!context) throw new Error('Sheet context error');
15
+ return context;
16
+ }
17
+
18
+ export function useExposedContext() {
19
+ const context = useContext(ExposedSheetContext);
10
20
  if (!context) throw new Error('Sheet context error');
11
21
  return context;
12
22
  }
@@ -1,9 +1,9 @@
1
1
  import { type MotionValue, transform } from 'motion';
2
- import { type RefObject } from 'react';
2
+ import { useEffect } from 'react';
3
3
 
4
4
  import type { SheetDetent, SheetSnapPoint } from '../types';
5
- import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
6
5
  import { useSafeAreaInsets } from './use-safe-area-insets';
6
+ import { useStableCallback } from './use-stable-callback';
7
7
 
8
8
  export function useModalEffect({
9
9
  y,
@@ -29,114 +29,105 @@ export function useModalEffect({
29
29
  rootId = undefined;
30
30
  }
31
31
 
32
- // Cleanup on unmount
33
- useIsomorphicLayoutEffect(() => {
34
- return () => {
35
- if (rootId) cleanupModalEffect(rootId);
36
- };
37
- }, []);
38
-
39
- useIsomorphicLayoutEffect(() => {
32
+ const handleYChange = useStableCallback((yValue: number) => {
40
33
  if (!rootId) return;
41
34
 
42
- const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
43
- if (!root) return;
35
+ let progress = Math.max(0, 1 - yValue / sheetHeight);
44
36
 
45
- const removeStartListener = y.on('animationStart', () => {
46
- // biome-ignore lint/style/noNonNullAssertion: root is always defined here
47
- setupModalEffect(rootId!);
48
- });
37
+ /**
38
+ * Start the effect only if we have dragged over the second snap point
39
+ * to make the effect more natural as the sheet will reach it's final
40
+ * position when the user drags it over the second snap point.
41
+ */
42
+ const snapThresholdPoint =
43
+ snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : undefined;
49
44
 
50
45
  /**
51
- * NOTE: The `y` value gets smaller when the sheet is opened and larger
52
- * when the sheet is being closed.
46
+ * If we have snap points, we need to calculate the progress percentage
47
+ * based on the snap point threshold. Note that the maximum value is also
48
+ * different in this case as the range between the start of the effect
49
+ * and its end is different.
53
50
  */
54
- const removeChangeListener = y.on('change', (yValue) => {
55
- if (!root) return;
56
-
57
- let progress = Math.max(0, 1 - yValue / sheetHeight);
58
-
59
- /**
60
- * Start the effect only if we have dragged over the second snap point
61
- * to make the effect more natural as the sheet will reach it's final
62
- * position when the user drags it over the second snap point.
63
- */
64
- const snapThresholdPoint =
65
- snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : undefined;
66
-
67
- /**
68
- * If we have snap points, we need to calculate the progress percentage
69
- * based on the snap point threshold. Note that the maximum value is also
70
- * different in this case as the range between the start of the effect
71
- * and its end is different.
72
- */
73
- if (snapThresholdPoint !== undefined) {
74
- const snapThresholdValue = snapThresholdPoint.snapValueY;
75
-
76
- if (yValue <= snapThresholdValue) {
77
- progress = (snapThresholdValue - yValue) / snapThresholdValue;
78
- } else {
79
- progress = 0;
80
- }
51
+ if (snapThresholdPoint !== undefined) {
52
+ const snapThresholdValue = snapThresholdPoint.snapValueY;
53
+
54
+ if (yValue <= snapThresholdValue) {
55
+ progress = (snapThresholdValue - yValue) / snapThresholdValue;
56
+ } else {
57
+ progress = 0;
81
58
  }
59
+ }
82
60
 
83
- /**
84
- * If we have a start threshold, we need to calculate the progress
85
- * percentage based on the start threshold (0 to 1). For example,
86
- * if the start threshold is 0.5, the progress will be 0 until the sheet
87
- * is dragged over 50% of the complete drag distance.
88
- */
89
- if (startThreshold !== undefined) {
90
- const startThresholdValue =
91
- sheetHeight -
92
- Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
93
-
94
- if (yValue <= startThresholdValue) {
95
- progress = (startThresholdValue - yValue) / startThresholdValue;
96
- } else {
97
- progress = 0;
98
- }
61
+ /**
62
+ * If we have a start threshold, we need to calculate the progress
63
+ * percentage based on the start threshold (0 to 1). For example,
64
+ * if the start threshold is 0.5, the progress will be 0 until the sheet
65
+ * is dragged over 50% of the complete drag distance.
66
+ */
67
+ if (startThreshold !== undefined) {
68
+ const startThresholdValue =
69
+ sheetHeight -
70
+ Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
71
+
72
+ if (yValue <= startThresholdValue) {
73
+ progress = (startThresholdValue - yValue) / startThresholdValue;
74
+ } else {
75
+ progress = 0;
99
76
  }
77
+ }
100
78
 
101
- // Make sure progress is between 0 and 1
102
- progress = Math.max(0, Math.min(1, progress));
79
+ // Make sure progress is between 0 and 1
80
+ progress = Math.max(0, Math.min(1, progress));
103
81
 
104
- const pageWidth = window.innerWidth;
105
- const ty = transform(progress, [0, 1], [0, 24 + insetTop]);
106
- const s = transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]);
107
- const borderRadius = transform(progress, [0, 1], [0, 10]);
82
+ const pageWidth = window.innerWidth;
108
83
 
109
- root.style.transform = `scale(${s}) translate3d(0, ${ty}px, 0)`;
110
- root.style.borderTopRightRadius = `${borderRadius}px`;
111
- root.style.borderTopLeftRadius = `${borderRadius}px`;
84
+ updateModalEffect(rootId, {
85
+ scale: transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]),
86
+ translateY: transform(progress, [0, 1], [0, 24 + insetTop]),
87
+ borderRadius: transform(progress, [0, 1], [0, 10]),
112
88
  });
89
+ });
113
90
 
114
- function onCompleted() {
115
- // -5 just to take into account some inprecision to ensure the cleanup is done
116
- if (y.get() - 5 >= sheetHeight) {
117
- // biome-ignore lint/style/noNonNullAssertion: root is always defined here
118
- cleanupModalEffect(rootId!);
119
- }
91
+ const handleYComplete = useStableCallback(() => {
92
+ if (!rootId) return;
93
+
94
+ // +5 just to take into account some inprecision to ensure the cleanup is done
95
+ if (y.get() + 5 >= sheetHeight) {
96
+ cleanupModalEffect(rootId);
120
97
  }
98
+ });
121
99
 
122
- const removeCompleteListener = y.on('animationComplete', onCompleted);
123
- const removeCancelListener = y.on('animationCancel', onCompleted);
100
+ useEffect(() => {
101
+ if (!rootId) return;
102
+
103
+ const removeStartListener = y.on('animationStart', () => {
104
+ if (rootId) setupModalEffect(rootId);
105
+ });
106
+
107
+ const removeChangeListener = y.on('change', (yValue) => {
108
+ handleYChange(yValue);
109
+ });
110
+
111
+ const removeCompleteListener = y.on('animationComplete', handleYComplete);
112
+ const removeCancelListener = y.on('animationCancel', handleYComplete);
124
113
 
125
114
  return () => {
115
+ if (rootId) cleanupModalEffect(rootId);
126
116
  removeStartListener();
127
117
  removeChangeListener();
128
118
  removeCompleteListener();
129
119
  removeCancelListener();
130
120
  };
131
- }, [y, rootId, insetTop, startThreshold, sheetHeight]);
121
+ }, [rootId]);
132
122
  }
133
123
 
134
124
  function setupModalEffect(rootId: string) {
135
125
  const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
136
- const body = document.querySelector('body') as HTMLBodyElement;
137
126
  if (!root) return;
138
127
 
139
- body.style.backgroundColor = '#000';
128
+ const body = document.querySelector('body') as HTMLBodyElement;
129
+
130
+ body.style.backgroundColor = '#000000';
140
131
  root.style.overflow = 'hidden';
141
132
  root.style.transitionTimingFunction = 'cubic-bezier(0.32, 0.72, 0, 1)';
142
133
  root.style.transitionProperty = 'transform, border-radius';
@@ -144,11 +135,25 @@ function setupModalEffect(rootId: string) {
144
135
  root.style.transformOrigin = 'center top';
145
136
  }
146
137
 
138
+ function updateModalEffect(
139
+ rootId: string,
140
+ opts: { scale: number; translateY: number; borderRadius: number }
141
+ ) {
142
+ const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
143
+ if (!root) return;
144
+
145
+ const { scale, translateY, borderRadius } = opts;
146
+ root.style.transform = `scale(${scale}) translate3d(0, ${translateY}px, 0)`;
147
+ root.style.borderTopRightRadius = `${borderRadius}px`;
148
+ root.style.borderTopLeftRadius = `${borderRadius}px`;
149
+ }
150
+
147
151
  function cleanupModalEffect(rootId: string) {
148
152
  const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
149
- const body = document.querySelector('body') as HTMLBodyElement;
150
153
  if (!root) return;
151
154
 
155
+ const body = document.querySelector('body') as HTMLBodyElement;
156
+
152
157
  body.style.removeProperty('background-color');
153
158
  root.style.removeProperty('overflow');
154
159
  root.style.removeProperty('transition-timing-function');
package/src/index.tsx CHANGED
@@ -1,19 +1,11 @@
1
- import type { MotionValue } from 'motion/react';
2
-
3
- import { SheetBackdrop } from './SheetBackdrop';
4
- import { SheetContainer } from './SheetContainer';
5
- import { SheetContent } from './SheetContent';
6
- import { SheetDragIndicator } from './SheetDragIndicator';
7
- import { SheetHeader } from './SheetHeader';
1
+ import { useExposedContext } from './context';
8
2
  import { Sheet as SheetBase } from './sheet';
9
- import type { SheetCompound } from './types';
10
-
11
- export interface SheetRef {
12
- y: MotionValue<number>;
13
- yInverted: MotionValue<number>;
14
- height: number;
15
- snapTo: (index: number) => Promise<void>;
16
- }
3
+ import { SheetBackdrop } from './sheet-backdrop';
4
+ import { SheetContainer } from './sheet-container';
5
+ import { SheetContent } from './sheet-content';
6
+ import { SheetDragIndicator } from './sheet-drag-indicator';
7
+ import { SheetHeader } from './sheet-header';
8
+ import type { ExposedContextType, SheetCompound } from './types';
17
9
 
18
10
  export const Sheet: SheetCompound = Object.assign(SheetBase, {
19
11
  Container: SheetContainer,
@@ -21,12 +13,15 @@ export const Sheet: SheetCompound = Object.assign(SheetBase, {
21
13
  DragIndicator: SheetDragIndicator,
22
14
  Content: SheetContent,
23
15
  Backdrop: SheetBackdrop,
16
+ useContext: useExposedContext,
24
17
  });
25
18
 
26
19
  export { useScrollPosition } from './hooks/use-scroll-position';
27
20
  export { useVirtualKeyboard } from './hooks/use-virtual-keyboard';
28
21
 
29
- // Export types
22
+ export interface SheetContext extends ExposedContextType {}
23
+ export interface SheetRef extends ExposedContextType {}
24
+
30
25
  export type {
31
26
  SheetBackdropProps,
32
27
  SheetContainerProps,
@@ -1,7 +1,7 @@
1
1
  import { type MotionStyle, motion } from 'motion/react';
2
2
  import React, { forwardRef } from 'react';
3
3
 
4
- import { useSheetContext } from './context';
4
+ import { useInternalContext } from './context';
5
5
  import { styles } from './styles';
6
6
  import { type SheetBackdropProps } from './types';
7
7
  import { applyStyles } from './utils';
@@ -10,30 +10,21 @@ const isClickable = (props: any) => !!props.onClick || !!props.onTap;
10
10
 
11
11
  export const SheetBackdrop = forwardRef<any, SheetBackdropProps>(
12
12
  ({ style, className = '', unstyled, ...rest }, ref) => {
13
- const sheetContext = useSheetContext();
13
+ const sheetContext = useInternalContext();
14
14
  const clickable = isClickable(rest);
15
15
  const Comp = clickable ? motion.button : motion.div;
16
16
  const pointerEvents = clickable ? 'auto' : 'none';
17
17
  const isUnstyled = unstyled ?? sheetContext.unstyled;
18
18
 
19
19
  const backdropStyle: MotionStyle = {
20
+ opacity: sheetContext.yProgress,
20
21
  ...applyStyles(styles.backdrop, isUnstyled),
21
22
  ...style,
22
23
  pointerEvents,
23
24
  };
24
25
 
25
- const animationProps = sheetContext.prefersReducedMotion
26
- ? {}
27
- : {
28
- initial: { opacity: 0 },
29
- animate: { opacity: 1 },
30
- exit: { opacity: 0 },
31
- transition: { duration: 1 },
32
- };
33
-
34
26
  return (
35
27
  <Comp
36
- {...animationProps}
37
28
  {...(rest as any)}
38
29
  ref={ref}
39
30
  className={`react-modal-sheet-backdrop ${className}`}
@@ -2,14 +2,14 @@ import { type MotionStyle, motion } from 'motion/react';
2
2
  import React, { forwardRef } from 'react';
3
3
 
4
4
  import { DEFAULT_HEIGHT } from './constants';
5
- import { useSheetContext } from './context';
5
+ import { useInternalContext } from './context';
6
6
  import { styles } from './styles';
7
7
  import { type SheetContainerProps } from './types';
8
8
  import { applyStyles, mergeRefs } from './utils';
9
9
 
10
10
  export const SheetContainer = forwardRef<any, SheetContainerProps>(
11
11
  ({ children, style, className = '', unstyled, ...rest }, ref) => {
12
- const sheetContext = useSheetContext();
12
+ const sheetContext = useInternalContext();
13
13
 
14
14
  const isUnstyled = unstyled ?? sheetContext.unstyled;
15
15
 
@@ -1,7 +1,7 @@
1
1
  import { type MotionStyle, motion } from 'motion/react';
2
2
  import React, { forwardRef } from 'react';
3
3
 
4
- import { useSheetContext } from './context';
4
+ import { useInternalContext } from './context';
5
5
  import { useDragConstraints } from './hooks/use-drag-constraints';
6
6
  import { useScrollPosition } from './hooks/use-scroll-position';
7
7
  import { styles } from './styles';
@@ -24,7 +24,7 @@ export const SheetContent = forwardRef<any, SheetContentProps>(
24
24
  },
25
25
  ref
26
26
  ) => {
27
- const sheetContext = useSheetContext();
27
+ const sheetContext = useInternalContext();
28
28
  const dragConstraints = useDragConstraints();
29
29
 
30
30
  const scroll = useScrollPosition({
@@ -84,7 +84,13 @@ export const SheetContent = forwardRef<any, SheetContentProps>(
84
84
  }
85
85
 
86
86
  if (disableScroll) {
87
- scrollStyle.overflowY = 'hidden';
87
+ /**
88
+ * NOTE: use `clip` to avoid glitchy behavior where using `hidden`
89
+ * would actually allow scrolling but just not show the scroll content.
90
+ * Using `hidden` seems to work fine on iOS but on Android it causes
91
+ * the up drag gesture to cancel occasionally causing bad UX.
92
+ */
93
+ scrollStyle.overflowY = 'clip';
88
94
  }
89
95
 
90
96
  if (dragProps !== undefined) {
@@ -1,7 +1,7 @@
1
1
  import { motion, useTransform } from 'motion/react';
2
2
  import React from 'react';
3
3
 
4
- import { useSheetContext } from './context';
4
+ import { useInternalContext } from './context';
5
5
  import { styles } from './styles';
6
6
  import type { SheetDragIndicatorProps } from './types';
7
7
  import { applyStyles } from './utils';
@@ -12,7 +12,7 @@ export function SheetDragIndicator({
12
12
  unstyled,
13
13
  ...rest
14
14
  }: SheetDragIndicatorProps) {
15
- const sheetContext = useSheetContext();
15
+ const sheetContext = useInternalContext();
16
16
 
17
17
  const indicator1Transform = useTransform(
18
18
  sheetContext.indicatorRotation,
@@ -1,9 +1,9 @@
1
1
  import { motion } from 'motion/react';
2
2
  import React, { forwardRef } from 'react';
3
3
 
4
- import { useSheetContext } from './context';
4
+ import { useInternalContext } from './context';
5
5
  import { useDragConstraints } from './hooks/use-drag-constraints';
6
- import { SheetDragIndicator } from './SheetDragIndicator';
6
+ import { SheetDragIndicator } from './sheet-drag-indicator';
7
7
  import { styles } from './styles';
8
8
  import { type SheetHeaderProps } from './types';
9
9
  import { applyStyles, mergeRefs } from './utils';
@@ -13,7 +13,7 @@ export const SheetHeader = forwardRef<any, SheetHeaderProps>(
13
13
  { children, style, disableDrag, unstyled, className = '', ...rest },
14
14
  ref
15
15
  ) => {
16
- const sheetContext = useSheetContext();
16
+ const sheetContext = useInternalContext();
17
17
  const dragConstraints = useDragConstraints();
18
18
  const dragProps =
19
19
  disableDrag || sheetContext.disableDrag
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,7 +24,7 @@ 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';
@@ -32,8 +33,12 @@ import { useSheetState } from './hooks/use-sheet-state';
32
33
  import { useStableCallback } from './hooks/use-stable-callback';
33
34
  import { classifyDragEnd, computeSnapPoints } from './snap';
34
35
  import { styles } from './styles';
35
- import { type SheetContextType, type SheetProps } from './types';
36
- 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';
37
42
 
38
43
  export const Sheet = forwardRef<any, SheetProps>(
39
44
  (
@@ -74,17 +79,12 @@ export const Sheet = forwardRef<any, SheetProps>(
74
79
  const sheetRef = useRef<HTMLDivElement>(null);
75
80
  const sheetHeight = Math.round(sheetBounds.height);
76
81
  const [currentSnap, setCurrentSnap] = useState(initialSnap);
77
- const snapPoints =
78
- snapPointsProp && sheetHeight > 0
79
- ? computeSnapPoints({ sheetHeight, snapPointsProp })
80
- : [];
81
-
82
82
  const { windowHeight } = useDimensions();
83
83
  const closedY = sheetHeight > 0 ? sheetHeight : windowHeight;
84
84
  const y = useMotionValue(closedY);
85
85
  const yInverted = useTransform(y, (val) => Math.max(sheetHeight - val, 0));
86
+ const yProgress = useTransform(y, (val) => clamp(1 - val / closedY, 0, 1));
86
87
  const indicatorRotation = useMotionValue(0);
87
-
88
88
  const shouldReduceMotion = useReducedMotion();
89
89
  const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
90
90
  const animationOptions: Transition = {
@@ -100,6 +100,13 @@ export const Sheet = forwardRef<any, SheetProps>(
100
100
  val + 2 >= closedY ? 'hidden' : 'visible'
101
101
  );
102
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
+
103
110
  const updateSnap = useStableCallback((snapIndex: number) => {
104
111
  setCurrentSnap(snapIndex);
105
112
  onSnap?.(snapIndex);
@@ -268,13 +275,6 @@ export const Sheet = forwardRef<any, SheetProps>(
268
275
  indicatorRotation.set(0);
269
276
  });
270
277
 
271
- useImperativeHandle(ref, () => ({
272
- y,
273
- yInverted,
274
- height: sheetHeight,
275
- snapTo,
276
- }));
277
-
278
278
  useModalEffect({
279
279
  y,
280
280
  detent,
@@ -310,23 +310,17 @@ export const Sheet = forwardRef<any, SheetProps>(
310
310
  const yTo = initialSnapPoint?.snapValueY ?? 0;
311
311
 
312
312
  await animate(y, yTo, animationOptions);
313
-
314
- if (initialSnap !== undefined) {
315
- updateSnap(initialSnap);
316
- }
317
-
313
+ if (initialSnap !== undefined) updateSnap(initialSnap);
318
314
  onOpenEnd?.();
319
315
  },
320
316
  onClosing: async () => {
321
317
  onCloseStart?.();
322
-
323
318
  await animate(y, closedY, animationOptions);
324
-
325
319
  onCloseEnd?.();
326
320
  },
327
321
  });
328
322
 
329
- const dragProps: SheetContextType['dragProps'] = {
323
+ const dragProps: InternalContextType['dragProps'] = {
330
324
  drag: 'y',
331
325
  dragElastic: 0,
332
326
  dragMomentum: false,
@@ -336,7 +330,7 @@ export const Sheet = forwardRef<any, SheetProps>(
336
330
  onDragEnd,
337
331
  };
338
332
 
339
- const context: SheetContextType = {
333
+ const internalContext: InternalContextType = {
340
334
  currentSnap,
341
335
  detent,
342
336
  disableDrag,
@@ -347,11 +341,24 @@ export const Sheet = forwardRef<any, SheetProps>(
347
341
  sheetBoundsRef,
348
342
  sheetRef,
349
343
  unstyled,
344
+ yProgress,
350
345
  y,
351
346
  };
352
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
+
353
360
  const sheet = (
354
- <SheetContext.Provider value={context}>
361
+ <InternalSheetContext.Provider value={internalContext}>
355
362
  <motion.div
356
363
  {...rest}
357
364
  ref={ref}
@@ -364,9 +371,11 @@ export const Sheet = forwardRef<any, SheetProps>(
364
371
  ...style,
365
372
  }}
366
373
  >
367
- {state !== 'closed' ? children : null}
374
+ <ExposedSheetContext.Provider value={exposedContext}>
375
+ {state !== 'closed' ? children : null}
376
+ </ExposedSheetContext.Provider>
368
377
  </motion.div>
369
- </SheetContext.Provider>
378
+ </InternalSheetContext.Provider>
370
379
  );
371
380
 
372
381
  if (IS_SSR) return sheet;
package/src/snap.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import type { PanInfo } from 'motion';
2
2
  import type { SheetSnapPoint } from './types';
3
- import { isAscendingOrder } from './utils';
3
+ import { clamp, isAscendingOrder } from './utils';
4
4
 
5
5
  /**
6
6
  * Convert negative / percentage snap points to absolute values
@@ -108,10 +108,6 @@ export function computeSnapPoints({
108
108
  }));
109
109
  }
110
110
 
111
- function clamp(value: number, min: number, max: number) {
112
- return Math.min(Math.max(value, min), max);
113
- }
114
-
115
111
  function sign(value: number) {
116
112
  if (value > 0) return 1;
117
113
  if (value < 0) return -1;
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
+ }