@lodev09/react-native-true-sheet 3.10.1 → 3.11.0-beta.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.
Files changed (108) hide show
  1. package/lib/module/TrueSheet.js +3 -0
  2. package/lib/module/TrueSheet.js.map +1 -1
  3. package/lib/module/TrueSheet.web.js +604 -376
  4. package/lib/module/TrueSheet.web.js.map +1 -1
  5. package/lib/module/TrueSheetProvider.js.map +1 -1
  6. package/lib/module/TrueSheetProvider.web.js +141 -100
  7. package/lib/module/TrueSheetProvider.web.js.map +1 -1
  8. package/lib/module/mocks/index.js.map +1 -1
  9. package/lib/module/reanimated/ReanimatedTrueSheet.web.js.map +1 -1
  10. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js +2 -3
  11. package/lib/module/reanimated/useReanimatedPositionChangeHandler.web.js.map +1 -1
  12. package/lib/module/web/constants.js +16 -0
  13. package/lib/module/web/constants.js.map +1 -0
  14. package/lib/module/web/vaul/README.md +9 -0
  15. package/lib/module/web/vaul/browser.js +30 -0
  16. package/lib/module/web/vaul/browser.js.map +1 -0
  17. package/lib/module/web/vaul/constants.js +14 -0
  18. package/lib/module/web/vaul/constants.js.map +1 -0
  19. package/lib/module/web/vaul/context.js +57 -0
  20. package/lib/module/web/vaul/context.js.map +1 -0
  21. package/lib/module/web/vaul/helpers.js +93 -0
  22. package/lib/module/web/vaul/helpers.js.map +1 -0
  23. package/lib/module/web/vaul/index.js +1206 -0
  24. package/lib/module/web/vaul/index.js.map +1 -0
  25. package/lib/module/web/vaul/style.css +264 -0
  26. package/lib/module/web/vaul/types.js +2 -0
  27. package/lib/module/web/vaul/types.js.map +1 -0
  28. package/lib/module/web/vaul/use-composed-refs.js +34 -0
  29. package/lib/module/web/vaul/use-composed-refs.js.map +1 -0
  30. package/lib/module/web/vaul/use-controllable-state.js +54 -0
  31. package/lib/module/web/vaul/use-controllable-state.js.map +1 -0
  32. package/lib/module/web/vaul/use-position-fixed.js +123 -0
  33. package/lib/module/web/vaul/use-position-fixed.js.map +1 -0
  34. package/lib/module/web/vaul/use-prevent-scroll.js +258 -0
  35. package/lib/module/web/vaul/use-prevent-scroll.js.map +1 -0
  36. package/lib/module/web/vaul/use-scale-background.js +57 -0
  37. package/lib/module/web/vaul/use-scale-background.js.map +1 -0
  38. package/lib/module/web/vaul/use-snap-points.js +294 -0
  39. package/lib/module/web/vaul/use-snap-points.js.map +1 -0
  40. package/lib/typescript/src/TrueSheet.d.ts +2 -2
  41. package/lib/typescript/src/TrueSheet.d.ts.map +1 -1
  42. package/lib/typescript/src/TrueSheet.types.d.ts +79 -36
  43. package/lib/typescript/src/TrueSheet.types.d.ts.map +1 -1
  44. package/lib/typescript/src/TrueSheet.web.d.ts +3 -11
  45. package/lib/typescript/src/TrueSheet.web.d.ts.map +1 -1
  46. package/lib/typescript/src/TrueSheetProvider.d.ts +2 -2
  47. package/lib/typescript/src/TrueSheetProvider.d.ts.map +1 -1
  48. package/lib/typescript/src/TrueSheetProvider.web.d.ts +17 -14
  49. package/lib/typescript/src/TrueSheetProvider.web.d.ts.map +1 -1
  50. package/lib/typescript/src/mocks/index.d.ts +1 -2
  51. package/lib/typescript/src/mocks/index.d.ts.map +1 -1
  52. package/lib/typescript/src/navigation/types.d.ts +1 -1
  53. package/lib/typescript/src/navigation/types.d.ts.map +1 -1
  54. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts +0 -1
  55. package/lib/typescript/src/reanimated/ReanimatedTrueSheet.web.d.ts.map +1 -1
  56. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts +2 -3
  57. package/lib/typescript/src/reanimated/useReanimatedPositionChangeHandler.web.d.ts.map +1 -1
  58. package/lib/typescript/src/web/constants.d.ts +14 -0
  59. package/lib/typescript/src/web/constants.d.ts.map +1 -0
  60. package/lib/typescript/src/web/vaul/browser.d.ts +8 -0
  61. package/lib/typescript/src/web/vaul/browser.d.ts.map +1 -0
  62. package/lib/typescript/src/web/vaul/constants.d.ts +12 -0
  63. package/lib/typescript/src/web/vaul/constants.d.ts.map +1 -0
  64. package/lib/typescript/src/web/vaul/context.d.ts +45 -0
  65. package/lib/typescript/src/web/vaul/context.d.ts.map +1 -0
  66. package/lib/typescript/src/web/vaul/helpers.d.ts +17 -0
  67. package/lib/typescript/src/web/vaul/helpers.d.ts.map +1 -0
  68. package/lib/typescript/src/web/vaul/index.d.ts +210 -0
  69. package/lib/typescript/src/web/vaul/index.d.ts.map +1 -0
  70. package/lib/typescript/src/web/vaul/types.d.ts +7 -0
  71. package/lib/typescript/src/web/vaul/types.d.ts.map +1 -0
  72. package/lib/typescript/src/web/vaul/use-composed-refs.d.ts +14 -0
  73. package/lib/typescript/src/web/vaul/use-composed-refs.d.ts.map +1 -0
  74. package/lib/typescript/src/web/vaul/use-controllable-state.d.ts +9 -0
  75. package/lib/typescript/src/web/vaul/use-controllable-state.d.ts.map +1 -0
  76. package/lib/typescript/src/web/vaul/use-position-fixed.d.ts +19 -0
  77. package/lib/typescript/src/web/vaul/use-position-fixed.d.ts.map +1 -0
  78. package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts +18 -0
  79. package/lib/typescript/src/web/vaul/use-prevent-scroll.d.ts.map +1 -0
  80. package/lib/typescript/src/web/vaul/use-scale-background.d.ts +2 -0
  81. package/lib/typescript/src/web/vaul/use-scale-background.d.ts.map +1 -0
  82. package/lib/typescript/src/web/vaul/use-snap-points.d.ts +37 -0
  83. package/lib/typescript/src/web/vaul/use-snap-points.d.ts.map +1 -0
  84. package/package.json +4 -4
  85. package/src/TrueSheet.tsx +9 -1
  86. package/src/TrueSheet.types.ts +88 -38
  87. package/src/TrueSheet.web.tsx +675 -450
  88. package/src/TrueSheetProvider.tsx +2 -4
  89. package/src/TrueSheetProvider.web.tsx +193 -125
  90. package/src/mocks/index.ts +1 -2
  91. package/src/navigation/types.ts +0 -1
  92. package/src/reanimated/ReanimatedTrueSheet.web.tsx +0 -1
  93. package/src/reanimated/useReanimatedPositionChangeHandler.web.ts +2 -3
  94. package/src/web/constants.ts +13 -0
  95. package/src/web/vaul/README.md +9 -0
  96. package/src/web/vaul/browser.ts +38 -0
  97. package/src/web/vaul/constants.ts +18 -0
  98. package/src/web/vaul/context.ts +87 -0
  99. package/src/web/vaul/helpers.ts +124 -0
  100. package/src/web/vaul/index.tsx +1590 -0
  101. package/src/web/vaul/style.css +264 -0
  102. package/src/web/vaul/types.ts +7 -0
  103. package/src/web/vaul/use-composed-refs.ts +35 -0
  104. package/src/web/vaul/use-controllable-state.ts +66 -0
  105. package/src/web/vaul/use-position-fixed.ts +147 -0
  106. package/src/web/vaul/use-prevent-scroll.ts +309 -0
  107. package/src/web/vaul/use-scale-background.ts +64 -0
  108. package/src/web/vaul/use-snap-points.ts +433 -0
@@ -0,0 +1,433 @@
1
+ // @ts-nocheck — vendored upstream, incompatible with noUncheckedIndexedAccess
2
+ import React from 'react';
3
+ import { set, isVertical } from './helpers';
4
+ import { TRANSITIONS, VELOCITY_THRESHOLD } from './constants';
5
+ import { useControllableState } from './use-controllable-state';
6
+ import type { DrawerDirection } from './types';
7
+
8
+ export function useSnapPoints({
9
+ activeSnapPointProp,
10
+ setActiveSnapPointProp,
11
+ snapPoints,
12
+ drawerRef,
13
+ overlayRef,
14
+ fadeFromIndex,
15
+ onSnapPointChange,
16
+ direction = 'bottom',
17
+ container,
18
+ snapToSequentialPoint,
19
+ isOpen,
20
+ contentHeight,
21
+ detachedOffset = 0,
22
+ maxContentHeight,
23
+ initialAnimated = true,
24
+ }: {
25
+ activeSnapPointProp?: number | string | null;
26
+ setActiveSnapPointProp?(snapPoint: number | null | string): void;
27
+ snapPoints?: (number | string)[];
28
+ fadeFromIndex?: number;
29
+ drawerRef: React.RefObject<HTMLDivElement | null>;
30
+ overlayRef: React.RefObject<HTMLDivElement | null>;
31
+ onSnapPointChange(activeSnapPointIndex: number): void;
32
+ direction?: DrawerDirection;
33
+ container?: HTMLElement | null | undefined;
34
+ snapToSequentialPoint?: boolean;
35
+ isOpen?: boolean;
36
+ contentHeight?: number;
37
+ detachedOffset?: number;
38
+ maxContentHeight?: number;
39
+ initialAnimated?: boolean;
40
+ }) {
41
+ const [activeSnapPoint, setActiveSnapPoint] = useControllableState<string | number | null>({
42
+ prop: activeSnapPointProp,
43
+ defaultProp: snapPoints?.[0],
44
+ onChange: setActiveSnapPointProp,
45
+ });
46
+
47
+ const [windowDimensions, setWindowDimensions] = React.useState(
48
+ typeof window !== 'undefined'
49
+ ? {
50
+ innerWidth: window.innerWidth,
51
+ innerHeight: window.innerHeight,
52
+ }
53
+ : undefined
54
+ );
55
+
56
+ React.useEffect(() => {
57
+ function onResize() {
58
+ setWindowDimensions({
59
+ innerWidth: window.innerWidth,
60
+ innerHeight: window.innerHeight,
61
+ });
62
+ }
63
+ window.addEventListener('resize', onResize);
64
+
65
+ return () => window.removeEventListener('resize', onResize);
66
+ }, []);
67
+
68
+ const isLastSnapPoint = React.useMemo(
69
+ () => activeSnapPoint === snapPoints?.[snapPoints.length - 1] || null,
70
+ [snapPoints, activeSnapPoint]
71
+ );
72
+
73
+ const activeSnapPointIndex = React.useMemo(
74
+ () => snapPoints?.findIndex((snapPoint) => snapPoint === activeSnapPoint) ?? null,
75
+ [snapPoints, activeSnapPoint]
76
+ );
77
+
78
+ const shouldFade =
79
+ (snapPoints &&
80
+ snapPoints.length > 0 &&
81
+ (fadeFromIndex || fadeFromIndex === 0) &&
82
+ !Number.isNaN(fadeFromIndex) &&
83
+ snapPoints[fadeFromIndex] === activeSnapPoint) ||
84
+ !snapPoints;
85
+
86
+ const snapPointsOffset = React.useMemo(() => {
87
+ const containerSize = container
88
+ ? {
89
+ width: container.getBoundingClientRect().width,
90
+ height: container.getBoundingClientRect().height,
91
+ }
92
+ : typeof window !== 'undefined'
93
+ ? { width: window.innerWidth, height: window.innerHeight }
94
+ : { width: 0, height: 0 };
95
+
96
+ // Shrink the effective vertical area by detachedOffset so snap math
97
+ // reserves a fixed gap at the bottom — the floating card stays offset
98
+ // from the viewport edge through drag, snap and resize.
99
+ const effectiveHeight = Math.max(0, containerSize.height - (detachedOffset || 0));
100
+
101
+ return (
102
+ snapPoints?.map((snapPoint) => {
103
+ // 'auto' resolves to measured content height. Falls back to half the
104
+ // container so the initial snap is close to final before ResizeObserver
105
+ // reports a real measurement.
106
+ const resolved =
107
+ snapPoint === 'auto'
108
+ ? `${contentHeight && contentHeight > 0 ? contentHeight : effectiveHeight / 2}px`
109
+ : snapPoint;
110
+ const isPx = typeof resolved === 'string';
111
+ let snapPointAsNumber = 0;
112
+
113
+ if (isPx) {
114
+ snapPointAsNumber = parseInt(resolved, 10);
115
+ }
116
+
117
+ if (isVertical(direction)) {
118
+ const rawHeight = isPx
119
+ ? snapPointAsNumber
120
+ : windowDimensions
121
+ ? resolved * effectiveHeight
122
+ : 0;
123
+ // Cap px/'auto' snap points so a content taller than the viewport
124
+ // doesn't push the drawer past the top edge (offset would go
125
+ // negative). Fractional detents are already ≤ effectiveHeight.
126
+ // `maxContentHeight`, when set, caps the visible sheet further so
127
+ // full/'auto' detents respect the user's ceiling.
128
+ const ceiling =
129
+ maxContentHeight !== undefined
130
+ ? Math.min(effectiveHeight, maxContentHeight)
131
+ : effectiveHeight;
132
+ const height = Math.min(rawHeight, ceiling);
133
+
134
+ if (windowDimensions) {
135
+ return direction === 'bottom' ? effectiveHeight - height : -effectiveHeight + height;
136
+ }
137
+
138
+ return height;
139
+ }
140
+ const rawWidth = isPx
141
+ ? snapPointAsNumber
142
+ : windowDimensions
143
+ ? resolved * containerSize.width
144
+ : 0;
145
+ const width = Math.min(rawWidth, containerSize.width);
146
+
147
+ if (windowDimensions) {
148
+ return direction === 'right' ? containerSize.width - width : -containerSize.width + width;
149
+ }
150
+
151
+ return width;
152
+ }) ?? []
153
+ );
154
+ }, [snapPoints, windowDimensions, container, contentHeight, detachedOffset, maxContentHeight]);
155
+
156
+ const activeSnapPointOffset = React.useMemo(
157
+ () => (activeSnapPointIndex !== null ? snapPointsOffset?.[activeSnapPointIndex] : null),
158
+ [snapPointsOffset, activeSnapPointIndex]
159
+ );
160
+
161
+ // Detached-only: the drawer sits inside a clip wrapper. When drag overshoots
162
+ // below the lowest detent we translate the wrapper by the excess so the
163
+ // whole floating card follows the pointer instead of the drawer sliding
164
+ // behind the wrapper's rounded-bottom clip.
165
+ const setDetachedWrapperTransform = (y: number, animated: boolean) => {
166
+ if (!drawerRef.current || !isVertical(direction)) return;
167
+ const wrapper = drawerRef.current.closest<HTMLElement>('[data-vaul-detached-wrapper]');
168
+ if (!wrapper) return;
169
+ set(wrapper, {
170
+ transition: animated
171
+ ? `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
172
+ : 'none',
173
+ transform: `translate3d(0, ${y}px, 0)`,
174
+ });
175
+ };
176
+
177
+ // Skip the transition on the very first snap when the consumer opts out of
178
+ // the initial presentation animation. All subsequent snaps still animate.
179
+ const hasSnappedRef = React.useRef(false);
180
+ const snapToPoint = React.useCallback(
181
+ (dimension: number) => {
182
+ const newSnapPointIndex =
183
+ snapPointsOffset?.findIndex((snapPointDim) => snapPointDim === dimension) ?? null;
184
+ onSnapPointChange(newSnapPointIndex);
185
+
186
+ const animateThisSnap = hasSnappedRef.current || initialAnimated;
187
+ hasSnappedRef.current = true;
188
+ set(drawerRef.current, {
189
+ transition: animateThisSnap
190
+ ? `transform ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`
191
+ : 'none',
192
+ transform: isVertical(direction)
193
+ ? `translate3d(0, ${dimension}px, 0)`
194
+ : `translate3d(${dimension}px, 0, 0)`,
195
+ });
196
+
197
+ // Snapping implies drag overshoot (if any) should be undone.
198
+ setDetachedWrapperTransform(0, animateThisSnap);
199
+
200
+ if (
201
+ snapPointsOffset &&
202
+ newSnapPointIndex !== snapPointsOffset.length - 1 &&
203
+ fadeFromIndex !== undefined &&
204
+ newSnapPointIndex !== fadeFromIndex &&
205
+ newSnapPointIndex < fadeFromIndex
206
+ ) {
207
+ set(overlayRef.current, {
208
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`,
209
+ opacity: '0',
210
+ pointerEvents: 'none',
211
+ });
212
+ } else {
213
+ set(overlayRef.current, {
214
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`,
215
+ opacity: '1',
216
+ pointerEvents: 'auto',
217
+ });
218
+ }
219
+
220
+ setActiveSnapPoint(snapPoints?.[Math.max(newSnapPointIndex, 0)]);
221
+ },
222
+ [drawerRef.current, snapPoints, snapPointsOffset, fadeFromIndex, overlayRef, setActiveSnapPoint]
223
+ );
224
+
225
+ React.useEffect(() => {
226
+ if (!isOpen) return;
227
+ if (activeSnapPoint || activeSnapPointProp) {
228
+ const newIndex =
229
+ snapPoints?.findIndex(
230
+ (snapPoint) => snapPoint === activeSnapPointProp || snapPoint === activeSnapPoint
231
+ ) ?? -1;
232
+ if (snapPointsOffset && newIndex !== -1 && typeof snapPointsOffset[newIndex] === 'number') {
233
+ snapToPoint(snapPointsOffset[newIndex] as number);
234
+ }
235
+ }
236
+ }, [isOpen, activeSnapPoint, activeSnapPointProp, snapPoints, snapPointsOffset, snapToPoint]);
237
+
238
+ function onRelease({
239
+ draggedDistance,
240
+ closeDrawer,
241
+ velocity,
242
+ dismissible,
243
+ }: {
244
+ draggedDistance: number;
245
+ closeDrawer: () => void;
246
+ velocity: number;
247
+ dismissible: boolean;
248
+ }) {
249
+ if (fadeFromIndex === undefined) return;
250
+
251
+ const currentPosition =
252
+ direction === 'bottom' || direction === 'right'
253
+ ? (activeSnapPointOffset ?? 0) - draggedDistance
254
+ : (activeSnapPointOffset ?? 0) + draggedDistance;
255
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
256
+ const isFirst = activeSnapPointIndex === 0;
257
+ const hasDraggedUp = draggedDistance > 0;
258
+
259
+ if (isOverlaySnapPoint) {
260
+ set(overlayRef.current, {
261
+ transition: `opacity ${TRANSITIONS.DURATION}s cubic-bezier(${TRANSITIONS.EASE.join(',')})`,
262
+ });
263
+ }
264
+
265
+ // Distance-based dismiss: when overshooting past the lowest detent by more
266
+ // than half of its visible height, commit to dismiss regardless of
267
+ // velocity — lets a slow drag-down still close the sheet.
268
+ if (
269
+ dismissible &&
270
+ (direction === 'bottom' || direction === 'right') &&
271
+ currentPosition > snapPointsOffset[0]
272
+ ) {
273
+ const viewportDim = isVertical(direction) ? window.innerHeight : window.innerWidth;
274
+ const effectiveDim = Math.max(0, viewportDim - (detachedOffset || 0));
275
+ const visibleAtLowest = Math.max(0, effectiveDim - snapPointsOffset[0]);
276
+ const overshoot = currentPosition - snapPointsOffset[0];
277
+ if (overshoot > visibleAtLowest * 0.5) {
278
+ closeDrawer();
279
+ return;
280
+ }
281
+ }
282
+
283
+ if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
284
+ if (dismissible) closeDrawer();
285
+ else snapToPoint(snapPointsOffset[0]); // snap to initial point
286
+ return;
287
+ }
288
+
289
+ if (!snapToSequentialPoint && velocity > 2 && hasDraggedUp && snapPointsOffset && snapPoints) {
290
+ snapToPoint(snapPointsOffset[snapPoints.length - 1] as number);
291
+ return;
292
+ }
293
+
294
+ // Find the closest snap point to the current position
295
+ const closestSnapPoint = snapPointsOffset?.reduce((prev, curr) => {
296
+ if (typeof prev !== 'number' || typeof curr !== 'number') return prev;
297
+
298
+ return Math.abs(curr - currentPosition) < Math.abs(prev - currentPosition) ? curr : prev;
299
+ });
300
+
301
+ const dim = isVertical(direction) ? window.innerHeight : window.innerWidth;
302
+ if (velocity > VELOCITY_THRESHOLD && Math.abs(draggedDistance) < dim * 0.4) {
303
+ const dragDirection = hasDraggedUp ? 1 : -1; // 1 = up, -1 = down
304
+
305
+ // Don't do anything if we swipe upwards while being on the last snap point
306
+ if (dragDirection > 0 && isLastSnapPoint && snapPoints) {
307
+ snapToPoint(snapPointsOffset[snapPoints.length - 1]);
308
+ return;
309
+ }
310
+
311
+ if (isFirst && dragDirection < 0 && dismissible) {
312
+ closeDrawer();
313
+ }
314
+
315
+ if (activeSnapPointIndex === null) return;
316
+
317
+ snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
318
+ return;
319
+ }
320
+
321
+ snapToPoint(closestSnapPoint);
322
+ }
323
+
324
+ function onDrag({ draggedDistance }: { draggedDistance: number }) {
325
+ if (activeSnapPointOffset === null) return;
326
+ const newValue =
327
+ direction === 'bottom' || direction === 'right'
328
+ ? activeSnapPointOffset - draggedDistance
329
+ : activeSnapPointOffset + draggedDistance;
330
+
331
+ // Don't do anything if we exceed the last(biggest) snap point
332
+ if (
333
+ (direction === 'bottom' || direction === 'right') &&
334
+ newValue < snapPointsOffset[snapPointsOffset.length - 1]
335
+ ) {
336
+ return;
337
+ }
338
+ if (
339
+ (direction === 'top' || direction === 'left') &&
340
+ newValue > snapPointsOffset[snapPointsOffset.length - 1]
341
+ ) {
342
+ return;
343
+ }
344
+
345
+ // Past the lowest detent: cap the drawer and translate the detached
346
+ // wrapper by the excess so the floating card follows the pointer.
347
+ if ((direction === 'bottom' || direction === 'right') && newValue > snapPointsOffset[0]) {
348
+ const excess = newValue - snapPointsOffset[0];
349
+ set(drawerRef.current, {
350
+ transform: isVertical(direction)
351
+ ? `translate3d(0, ${snapPointsOffset[0]}px, 0)`
352
+ : `translate3d(${snapPointsOffset[0]}px, 0, 0)`,
353
+ });
354
+ setDetachedWrapperTransform(excess, false);
355
+ return;
356
+ }
357
+
358
+ setDetachedWrapperTransform(0, false);
359
+ set(drawerRef.current, {
360
+ transform: isVertical(direction)
361
+ ? `translate3d(0, ${newValue}px, 0)`
362
+ : `translate3d(${newValue}px, 0, 0)`,
363
+ });
364
+ }
365
+
366
+ function getPercentageDragged(absDraggedDistance: number, isDraggingDown: boolean) {
367
+ if (
368
+ !snapPoints ||
369
+ typeof activeSnapPointIndex !== 'number' ||
370
+ !snapPointsOffset ||
371
+ fadeFromIndex === undefined
372
+ )
373
+ return null;
374
+
375
+ // fadeFromIndex === 0 means every snap point is fully dimmed. Only the dismiss drag
376
+ // (toward close from the smallest detent) should fade the overlay out, interpolating
377
+ // opacity by distance from the smallest snap to the fully-dismissed position.
378
+ if (fadeFromIndex === 0) {
379
+ if (activeSnapPointIndex === 0 && !isDraggingDown) {
380
+ const activeOffset = snapPointsOffset[activeSnapPointIndex];
381
+ if (activeOffset === undefined) return 0;
382
+ const windowDim = isVertical(direction) ? window.innerHeight : window.innerWidth;
383
+ const dismissOffset =
384
+ direction === 'bottom' || direction === 'right' ? windowDim : -windowDim;
385
+ const dismissRange = Math.abs(dismissOffset - activeOffset);
386
+ return dismissRange > 0 ? Math.min(1, absDraggedDistance / dismissRange) : 0;
387
+ }
388
+ return 0;
389
+ }
390
+
391
+ // If this is true we are dragging to a snap point that is supposed to have an overlay
392
+ const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
393
+ const isOverlaySnapPointOrHigher = activeSnapPointIndex >= fadeFromIndex;
394
+
395
+ if (isOverlaySnapPointOrHigher && isDraggingDown) {
396
+ return 0;
397
+ }
398
+
399
+ // Don't animate, but still use this one if we are dragging away from the overlaySnapPoint
400
+ if (isOverlaySnapPoint && !isDraggingDown) return 1;
401
+ if (!shouldFade && !isOverlaySnapPoint) return null;
402
+
403
+ // Either fadeFrom index or the one before
404
+ const targetSnapPointIndex = isOverlaySnapPoint
405
+ ? activeSnapPointIndex + 1
406
+ : activeSnapPointIndex - 1;
407
+
408
+ // Get the distance from overlaySnapPoint to the one before or vice-versa to calculate the opacity percentage accordingly
409
+ const snapPointDistance = isOverlaySnapPoint
410
+ ? snapPointsOffset[targetSnapPointIndex] - snapPointsOffset[targetSnapPointIndex - 1]
411
+ : snapPointsOffset[targetSnapPointIndex + 1] - snapPointsOffset[targetSnapPointIndex];
412
+
413
+ const percentageDragged = absDraggedDistance / Math.abs(snapPointDistance);
414
+
415
+ if (isOverlaySnapPoint) {
416
+ return 1 - percentageDragged;
417
+ } else {
418
+ return percentageDragged;
419
+ }
420
+ }
421
+
422
+ return {
423
+ isLastSnapPoint,
424
+ activeSnapPoint,
425
+ shouldFade,
426
+ getPercentageDragged,
427
+ setActiveSnapPoint,
428
+ activeSnapPointIndex,
429
+ onRelease,
430
+ onDrag,
431
+ snapPointsOffset,
432
+ };
433
+ }