react-modal-sheet 5.5.0 → 5.7.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.7.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,10 @@
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';
5
+ import { clamp } from '../utils';
6
6
  import { useSafeAreaInsets } from './use-safe-area-insets';
7
+ import { useStableCallback } from './use-stable-callback';
7
8
 
8
9
  export function useModalEffect({
9
10
  y,
@@ -29,114 +30,108 @@ export function useModalEffect({
29
30
  rootId = undefined;
30
31
  }
31
32
 
32
- // Cleanup on unmount
33
- useIsomorphicLayoutEffect(() => {
34
- return () => {
35
- if (rootId) cleanupModalEffect(rootId);
36
- };
37
- }, []);
38
-
39
- useIsomorphicLayoutEffect(() => {
33
+ const handleYChange = useStableCallback((yValue: number) => {
40
34
  if (!rootId) return;
41
35
 
42
- const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
43
- if (!root) return;
36
+ let progress = Math.max(0, 1 - yValue / sheetHeight);
44
37
 
45
- const removeStartListener = y.on('animationStart', () => {
46
- // biome-ignore lint/style/noNonNullAssertion: root is always defined here
47
- setupModalEffect(rootId!);
48
- });
38
+ /**
39
+ * Start the effect only if we have dragged over the second snap point
40
+ * to make the effect more natural as the sheet will reach it's final
41
+ * position when the user drags it over the second snap point.
42
+ */
43
+ const snapThresholdPoint =
44
+ snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : undefined;
49
45
 
50
46
  /**
51
- * NOTE: The `y` value gets smaller when the sheet is opened and larger
52
- * when the sheet is being closed.
47
+ * If we have snap points, we need to calculate the progress percentage
48
+ * based on the snap point threshold. Note that the maximum value is also
49
+ * different in this case as the range between the start of the effect
50
+ * and its end is different.
53
51
  */
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
- }
52
+ if (snapThresholdPoint !== undefined) {
53
+ const snapThresholdValue = snapThresholdPoint.snapValueY;
54
+
55
+ if (yValue <= snapThresholdValue) {
56
+ progress = (snapThresholdValue - yValue) / snapThresholdValue;
57
+ } else {
58
+ progress = 0;
81
59
  }
60
+ }
82
61
 
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
- }
62
+ /**
63
+ * If we have a start threshold, we need to calculate the progress
64
+ * percentage based on the start threshold (0 to 1). For example,
65
+ * if the start threshold is 0.5, the progress will be 0 until the sheet
66
+ * is dragged over 50% of the complete drag distance.
67
+ */
68
+ if (startThreshold !== undefined) {
69
+ const startThresholdValue =
70
+ sheetHeight -
71
+ Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
72
+
73
+ if (yValue <= startThresholdValue) {
74
+ progress = (startThresholdValue - yValue) / startThresholdValue;
75
+ } else {
76
+ progress = 0;
99
77
  }
78
+ }
100
79
 
101
- // Make sure progress is between 0 and 1
102
- progress = Math.max(0, Math.min(1, progress));
80
+ // Make sure progress is between 0 and 1
81
+ progress = Math.max(0, Math.min(1, progress));
103
82
 
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]);
83
+ updateModalEffect(rootId, { progress, insetTop });
84
+ });
108
85
 
109
- root.style.transform = `scale(${s}) translate3d(0, ${ty}px, 0)`;
110
- root.style.borderTopRightRadius = `${borderRadius}px`;
111
- root.style.borderTopLeftRadius = `${borderRadius}px`;
112
- });
86
+ const handleYComplete = useStableCallback(() => {
87
+ if (!rootId) return;
113
88
 
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
- }
89
+ // +5 just to take into account some inprecision to ensure the cleanup is done
90
+ if (y.get() + 5 >= sheetHeight) {
91
+ cleanupModalEffect(rootId);
120
92
  }
93
+ });
94
+
95
+ useEffect(() => {
96
+ if (!rootId) return;
97
+
98
+ const removeStartListener = y.on('animationStart', () => {
99
+ if (rootId) setupModalEffect(rootId);
100
+ });
101
+
102
+ const removeChangeListener = y.on('change', (yValue) => {
103
+ handleYChange(yValue);
104
+ });
121
105
 
122
- const removeCompleteListener = y.on('animationComplete', onCompleted);
123
- const removeCancelListener = y.on('animationCancel', onCompleted);
106
+ const removeCompleteListener = y.on('animationComplete', handleYComplete);
107
+ const removeCancelListener = y.on('animationCancel', handleYComplete);
124
108
 
125
109
  return () => {
110
+ if (rootId) cleanupModalEffect(rootId);
126
111
  removeStartListener();
127
112
  removeChangeListener();
128
113
  removeCompleteListener();
129
114
  removeCancelListener();
130
115
  };
131
- }, [y, rootId, insetTop, startThreshold, sheetHeight]);
116
+ }, [rootId]);
132
117
  }
133
118
 
134
119
  function setupModalEffect(rootId: string) {
135
- const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
120
+ const target = getModalEffectTarget(rootId);
121
+ if (!target) return;
122
+
123
+ if (target.type === 'sheet') {
124
+ const sheet = target.element;
125
+
126
+ // No transition as the sheet follows the `y` of the stacked sheet
127
+ sheet.style.transformOrigin = 'center top';
128
+ return;
129
+ }
130
+
131
+ const root = target.element;
136
132
  const body = document.querySelector('body') as HTMLBodyElement;
137
- if (!root) return;
138
133
 
139
- body.style.backgroundColor = '#000';
134
+ body.style.backgroundColor = '#000000';
140
135
  root.style.overflow = 'hidden';
141
136
  root.style.transitionTimingFunction = 'cubic-bezier(0.32, 0.72, 0, 1)';
142
137
  root.style.transitionProperty = 'transform, border-radius';
@@ -144,10 +139,58 @@ function setupModalEffect(rootId: string) {
144
139
  root.style.transformOrigin = 'center top';
145
140
  }
146
141
 
142
+ function updateModalEffect(
143
+ rootId: string,
144
+ opts: { progress: number; insetTop: number }
145
+ ) {
146
+ const target = getModalEffectTarget(rootId);
147
+ if (!target) return;
148
+
149
+ const { progress, insetTop } = opts;
150
+
151
+ if (target.type === 'sheet') {
152
+ const sheet = target.element;
153
+
154
+ const sheetWidth = sheet.offsetWidth;
155
+ const sheetTop = sheet.offsetTop;
156
+ const scaleEnd = (sheetWidth - 16) / sheetWidth;
157
+ const scale = transform(progress, [0, 1], [1, scaleEnd]);
158
+ // Match the scaled app root's top edge, but cap the final lift for short sheets.
159
+ const translateYEnd = clamp(scaleEnd * (24 + insetTop) - sheetTop, -10, 0);
160
+ const translateY = transform(progress, [0, 1], [0, translateYEnd]);
161
+
162
+ sheet.style.scale = `${scale}`;
163
+ sheet.style.translate = `0 ${translateY}px`;
164
+ return;
165
+ }
166
+
167
+ const root = target.element;
168
+ const pageWidth = window.innerWidth;
169
+ const scaleEnd = (pageWidth - 16) / pageWidth;
170
+ const scale = transform(progress, [0, 1], [1, scaleEnd]);
171
+ const translateY = transform(progress, [0, 1], [0, 24 + insetTop]);
172
+ const borderRadius = transform(progress, [0, 1], [0, 10]);
173
+
174
+ root.style.transform = `scale(${scale}) translate3d(0, ${translateY}px, 0)`;
175
+ root.style.borderTopRightRadius = `${borderRadius}px`;
176
+ root.style.borderTopLeftRadius = `${borderRadius}px`;
177
+ }
178
+
147
179
  function cleanupModalEffect(rootId: string) {
148
- const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
180
+ const target = getModalEffectTarget(rootId);
181
+ if (!target) return;
182
+
183
+ if (target.type === 'sheet') {
184
+ const sheet = target.element;
185
+
186
+ sheet.style.removeProperty('transform-origin');
187
+ sheet.style.removeProperty('scale');
188
+ sheet.style.removeProperty('translate');
189
+ return;
190
+ }
191
+
192
+ const root = target.element;
149
193
  const body = document.querySelector('body') as HTMLBodyElement;
150
- if (!root) return;
151
194
 
152
195
  body.style.removeProperty('background-color');
153
196
  root.style.removeProperty('overflow');
@@ -159,3 +202,22 @@ function cleanupModalEffect(rootId: string) {
159
202
  root.style.removeProperty('border-top-right-radius');
160
203
  root.style.removeProperty('border-top-left-radius');
161
204
  }
205
+
206
+ function getModalEffectTarget(rootId: string) {
207
+ const root = document.getElementById(rootId) as HTMLDivElement;
208
+ if (!root) return null;
209
+
210
+ // When stacking sheets the effect is applied to the container of the sheet
211
+ const sheetRoot = root.closest('.react-modal-sheet-root');
212
+
213
+ if (sheetRoot) {
214
+ const container = sheetRoot.querySelector(
215
+ '.react-modal-sheet-container'
216
+ ) as HTMLDivElement;
217
+ if (!container) return null;
218
+
219
+ return { type: 'sheet', element: container };
220
+ }
221
+
222
+ return { type: 'root', element: root };
223
+ }
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
  (
@@ -70,21 +75,16 @@ export const Sheet = forwardRef<any, SheetProps>(
70
75
  },
71
76
  ref
72
77
  ) => {
73
- const [sheetBoundsRef, sheetBounds] = useMeasure();
78
+ const [sheetBoundsRef, sheetBounds] = useMeasure({ offsetSize: true });
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;