react-modal-sheet 4.0.2 → 4.1.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/dist/index.mjs CHANGED
@@ -1,6 +1,7 @@
1
- import React2, { createContext, forwardRef, useRef, useEffect, useLayoutEffect, useImperativeHandle, useMemo, Children, cloneElement, useState, useCallback, useContext } from 'react';
2
1
  import { createPortal } from 'react-dom';
3
- import { useMotionValue, useReducedMotion, useTransform, animate, motion, AnimatePresence, transform } from 'motion/react';
2
+ import React2, { createContext, forwardRef, useRef, useEffect, useLayoutEffect, useImperativeHandle, useMemo, Children, cloneElement, useState, useCallback, useContext } from 'react';
3
+ import { useMotionValue, useReducedMotion, useTransform, animate, motion, AnimatePresence } from 'motion/react';
4
+ import { transform } from 'motion';
4
5
 
5
6
  // src/sheet.tsx
6
7
 
@@ -18,182 +19,33 @@ var REDUCED_MOTION_TWEEN_CONFIG = {
18
19
  var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
19
20
  var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
20
21
 
21
- // src/hooks.tsx
22
- var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
23
- function useModalEffect({
24
- y,
25
- rootId,
26
- sheetRef
27
- }) {
28
- const heightRef = useRef(IS_SSR ? 0 : window.innerHeight);
29
- function setup() {
30
- const root = document.querySelector(`#${rootId}`);
31
- const body = document.querySelector("body");
32
- if (!root) return;
33
- body.style.backgroundColor = "#000";
34
- root.style.overflow = "hidden";
35
- root.style.transitionTimingFunction = "cubic-bezier(0.32, 0.72, 0, 1)";
36
- root.style.transitionProperty = "transform, border-radius";
37
- root.style.transitionDuration = "0.5s";
38
- root.style.transformOrigin = "center top";
39
- }
40
- function cleanup() {
41
- const root = document.querySelector(`#${rootId}`);
42
- const body = document.querySelector("body");
43
- if (!root) return;
44
- setTimeout(() => {
45
- body.style.removeProperty("background-color");
46
- root.style.removeProperty("overflow");
47
- root.style.removeProperty("transition-timing-function");
48
- root.style.removeProperty("transition-property");
49
- root.style.removeProperty("transition-duration");
50
- root.style.removeProperty("transform-origin");
51
- root.style.removeProperty("transform");
52
- root.style.removeProperty("border-top-right-radius");
53
- root.style.removeProperty("border-top-left-radius");
54
- }, 100);
22
+ // src/utils.ts
23
+ function getSheetHeight(sheetRef) {
24
+ const sheetEl = sheetRef.current;
25
+ if (!sheetEl) {
26
+ console.warn(
27
+ "Sheet height is not available because the sheet element is not mounted yet."
28
+ );
29
+ return 0;
55
30
  }
56
- useIsomorphicLayoutEffect(() => {
57
- return () => {
58
- if (rootId) cleanup();
59
- };
60
- }, []);
61
- useEffect(() => {
62
- const root = document.querySelector(`#${rootId}`);
63
- if (!root) return;
64
- function onCompleted() {
65
- if (y.get() - 10 >= heightRef.current) cleanup();
66
- }
67
- const removeStartListener = y.on("animationStart", () => {
68
- var _a;
69
- heightRef.current = ((_a = sheetRef.current) == null ? void 0 : _a.offsetHeight) || window.innerHeight;
70
- setup();
71
- });
72
- const removeChangeListener = y.on("change", (value) => {
73
- if (root) {
74
- const progress = Math.max(0, 1 - value / heightRef.current);
75
- const pageWidth = window.innerWidth;
76
- const scale = (pageWidth - 16) / pageWidth;
77
- const ty = transform(progress, [0, 1], [0, 24]);
78
- const s = transform(progress, [0, 1], [1, scale]);
79
- const borderRadius = transform(progress, [0, 1], [0, 10]);
80
- const inset = "env(safe-area-inset-top)";
81
- root.style.transform = `scale(${s}) translate3d(0, calc(${inset} + ${ty}px), 0)`;
82
- root.style.borderTopRightRadius = `${borderRadius}px`;
83
- root.style.borderTopLeftRadius = `${borderRadius}px`;
84
- }
85
- });
86
- const removeCompleteListener = y.on("animationComplete", onCompleted);
87
- const removeCancelListener = y.on("animationCancel", onCompleted);
88
- return () => {
89
- removeStartListener();
90
- removeChangeListener();
91
- removeCompleteListener();
92
- removeCancelListener();
93
- };
94
- }, [y, rootId]);
31
+ return Math.round(sheetEl.getBoundingClientRect().height);
95
32
  }
96
- function useEventCallbacks(isOpen, callbacks) {
97
- const prevOpen = usePrevious(isOpen);
98
- const didOpen = useRef(false);
99
- const handleAnimationComplete = useCallback(() => {
100
- var _a, _b, _c, _d;
101
- if (!didOpen.current) {
102
- (_b = (_a = callbacks.current).onOpenEnd) == null ? void 0 : _b.call(_a);
103
- didOpen.current = true;
104
- } else {
105
- (_d = (_c = callbacks.current).onCloseEnd) == null ? void 0 : _d.call(_c);
106
- didOpen.current = false;
107
- }
108
- }, [isOpen, prevOpen]);
109
- useEffect(() => {
110
- var _a, _b, _c, _d;
111
- if (!prevOpen && isOpen) {
112
- (_b = (_a = callbacks.current).onOpenStart) == null ? void 0 : _b.call(_a);
113
- } else if (!isOpen && prevOpen) {
114
- (_d = (_c = callbacks.current).onCloseStart) == null ? void 0 : _d.call(_c);
33
+ function getSnapPoints({
34
+ snapPointsProp,
35
+ sheetHeight
36
+ }) {
37
+ const snapPointValues = snapPointsProp.map((point) => {
38
+ if (point > 0 && point <= 1) {
39
+ return Math.round(point * sheetHeight);
115
40
  }
116
- }, [isOpen, prevOpen]);
117
- return { handleAnimationComplete };
118
- }
119
- function useDimensions() {
120
- const [dimensions, setDimensions] = useState({
121
- height: !IS_SSR ? window.innerHeight : 0,
122
- width: !IS_SSR ? window.innerWidth : 0
41
+ return point < 0 ? sheetHeight + point : point;
123
42
  });
124
- useIsomorphicLayoutEffect(() => {
125
- function handler() {
126
- setDimensions({
127
- height: window.innerHeight,
128
- width: window.innerWidth
129
- });
130
- }
131
- handler();
132
- window.addEventListener("resize", handler);
133
- return () => {
134
- window.removeEventListener("resize", handler);
135
- };
136
- }, []);
137
- return dimensions;
138
- }
139
- function usePrevious(state) {
140
- const ref = useRef(void 0);
141
- useEffect(() => {
142
- ref.current = state;
143
- });
144
- return ref.current;
145
- }
146
- function useEffectEvent(handler) {
147
- const handlerRef = useRef(void 0);
148
- useIsomorphicLayoutEffect(() => {
149
- handlerRef.current = handler;
150
- });
151
- return useCallback((...args) => {
152
- const fn = handlerRef.current;
153
- return fn == null ? void 0 : fn(...args);
154
- }, []);
155
- }
156
- var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
157
- function useDragConstraints() {
158
- const constraintsRef = useRef(null);
159
- const onMeasureDragConstraints = useCallback(() => constraints, []);
160
- return { constraintsRef, onMeasureDragConstraints };
161
- }
162
- var SheetContext = createContext(
163
- void 0
164
- );
165
- var useSheetContext = () => {
166
- const context = useContext(SheetContext);
167
- if (!context) throw new Error("Sheet context error");
168
- return context;
169
- };
170
- var SheetScrollerContext = createContext(void 0);
171
- function SheetScrollerContextProvider({
172
- children
173
- }) {
174
- const sheetContext = useSheetContext();
175
- const [disableDrag, setDisableDrag] = useState(!!sheetContext.disableDrag);
176
- function setDragEnabled() {
177
- if (!sheetContext.disableDrag) setDisableDrag(false);
178
- }
179
- function setDragDisabled() {
180
- if (!disableDrag) setDisableDrag(true);
181
- }
182
- return /* @__PURE__ */ React2.createElement(
183
- SheetScrollerContext.Provider,
184
- {
185
- value: { disableDrag, setDragEnabled, setDragDisabled }
186
- },
187
- children
43
+ console.assert(
44
+ inDescendingOrder(snapPointValues) || sheetHeight === 0,
45
+ `Snap points need to be in descending order got: [${snapPointsProp.join(", ")}]`
188
46
  );
47
+ return snapPointValues;
189
48
  }
190
- var useSheetScrollerContext = () => {
191
- const context = useContext(SheetScrollerContext);
192
- if (!context) throw new Error("Sheet scroller context error");
193
- return context;
194
- };
195
-
196
- // src/utils.ts
197
49
  function getClosest(nums, goal) {
198
50
  let closest = nums[0];
199
51
  let minDifference = Math.abs(nums[0] - goal);
@@ -212,6 +64,21 @@ function inDescendingOrder(arr) {
212
64
  }
213
65
  return true;
214
66
  }
67
+ function getSnapY({
68
+ sheetHeight,
69
+ snapPoints,
70
+ snapIndex
71
+ }) {
72
+ const snapPoint = snapPoints[snapIndex];
73
+ if (snapPoint === void 0) {
74
+ console.warn(
75
+ `Invalid snap index ${snapIndex}. Snap points are: [${snapPoints.join(", ")}]`
76
+ );
77
+ return null;
78
+ }
79
+ const y = validateSnapTo({ snapTo: sheetHeight - snapPoint, sheetHeight });
80
+ return y;
81
+ }
215
82
  function validateSnapTo({
216
83
  snapTo,
217
84
  sheetHeight
@@ -235,7 +102,7 @@ function mergeRefs(refs) {
235
102
  };
236
103
  }
237
104
  function isTouchDevice() {
238
- if (typeof window === "undefined") return false;
105
+ if (IS_SSR) return false;
239
106
  return "ontouchstart" in window || navigator.maxTouchPoints > 0;
240
107
  }
241
108
  function testPlatform(re) {
@@ -261,14 +128,265 @@ var isIPhone = cached(function() {
261
128
  return testPlatform(/^iPhone/i);
262
129
  });
263
130
  var isIPad = cached(function() {
264
- return testPlatform(/^iPad/i) || // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
265
- isMac() && navigator.maxTouchPoints > 1;
131
+ return testPlatform(/^iPad/i) || isMac() && navigator.maxTouchPoints > 1;
266
132
  });
267
133
  var isIOS = cached(function() {
268
134
  return isIPhone() || isIPad();
269
135
  });
136
+ var SheetContext = createContext(
137
+ void 0
138
+ );
139
+ var useSheetContext = () => {
140
+ const context = useContext(SheetContext);
141
+ if (!context) throw new Error("Sheet context error");
142
+ return context;
143
+ };
144
+ var SheetScrollerContext = createContext(void 0);
145
+ function SheetScrollerContextProvider({
146
+ children
147
+ }) {
148
+ const sheetContext = useSheetContext();
149
+ const [disableDrag, setDisableDrag] = useState(!!sheetContext.disableDrag);
150
+ function setDragEnabled() {
151
+ if (!sheetContext.disableDrag) setDisableDrag(false);
152
+ }
153
+ function setDragDisabled() {
154
+ if (!disableDrag) setDisableDrag(true);
155
+ }
156
+ return /* @__PURE__ */ React2.createElement(
157
+ SheetScrollerContext.Provider,
158
+ {
159
+ value: { disableDrag, setDragEnabled, setDragDisabled }
160
+ },
161
+ children
162
+ );
163
+ }
164
+ var useSheetScrollerContext = () => {
165
+ const context = useContext(SheetScrollerContext);
166
+ if (!context) throw new Error("Sheet scroller context error");
167
+ return context;
168
+ };
169
+
170
+ // src/styles.ts
171
+ var styles = {
172
+ wrapper: {
173
+ position: "fixed",
174
+ top: 0,
175
+ bottom: 0,
176
+ left: 0,
177
+ right: 0,
178
+ overflow: "hidden",
179
+ pointerEvents: "none"
180
+ },
181
+ backdrop: {
182
+ zIndex: 1,
183
+ position: "fixed",
184
+ top: 0,
185
+ left: 0,
186
+ width: "100%",
187
+ height: "100%",
188
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
189
+ touchAction: "none",
190
+ border: "none",
191
+ WebkitTapHighlightColor: "transparent"
192
+ },
193
+ container: {
194
+ zIndex: 2,
195
+ position: "absolute",
196
+ left: 0,
197
+ bottom: 0,
198
+ width: "100%",
199
+ backgroundColor: "#fff",
200
+ borderTopRightRadius: "8px",
201
+ borderTopLeftRadius: "8px",
202
+ boxShadow: "0px -2px 16px rgba(0, 0, 0, 0.3)",
203
+ display: "flex",
204
+ flexDirection: "column",
205
+ pointerEvents: "auto"
206
+ },
207
+ headerWrapper: {
208
+ width: "100%"
209
+ },
210
+ header: {
211
+ height: "40px",
212
+ width: "100%",
213
+ position: "relative",
214
+ display: "flex",
215
+ alignItems: "center",
216
+ justifyContent: "center"
217
+ },
218
+ indicator: {
219
+ width: "18px",
220
+ height: "4px",
221
+ borderRadius: "99px",
222
+ backgroundColor: "#ddd"
223
+ },
224
+ content: {
225
+ flexGrow: 1,
226
+ display: "flex",
227
+ flexDirection: "column",
228
+ minHeight: "0px",
229
+ position: "relative"
230
+ },
231
+ scroller: {
232
+ height: "100%",
233
+ overflowY: "auto"
234
+ }
235
+ };
236
+ var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
237
+ function useSafeAreaInsets() {
238
+ const [insets] = useState(() => {
239
+ const fallback = { top: 0, left: 0, right: 0, bottom: 0 };
240
+ if (IS_SSR) return fallback;
241
+ const root = document.querySelector(":root");
242
+ if (!root) return fallback;
243
+ root.style.setProperty("--rms-sat", "env(safe-area-inset-top)");
244
+ root.style.setProperty("--rms-sal", "env(safe-area-inset-left)");
245
+ root.style.setProperty("--rms-sar", "env(safe-area-inset-right)");
246
+ root.style.setProperty("--rms-sab", "env(safe-area-inset-bottom)");
247
+ const computedStyle = getComputedStyle(root);
248
+ const sat = getComputedValue(computedStyle, "--rms-sat");
249
+ const sal = getComputedValue(computedStyle, "--rms-sal");
250
+ const sar = getComputedValue(computedStyle, "--rms-sar");
251
+ const sab = getComputedValue(computedStyle, "--rms-sab");
252
+ root.style.removeProperty("--rms-sat");
253
+ root.style.removeProperty("--rms-sal");
254
+ root.style.removeProperty("--rms-sar");
255
+ root.style.removeProperty("--rms-sab");
256
+ return { top: sat, left: sal, right: sar, bottom: sab };
257
+ });
258
+ return insets;
259
+ }
260
+ function getComputedValue(computed, property) {
261
+ const strValue = computed.getPropertyValue(property).replace("px", "").trim();
262
+ return parseInt(strValue, 10) || 0;
263
+ }
270
264
 
271
- // src/use-prevent-scroll.ts
265
+ // src/hooks/use-modal-effect.ts
266
+ function useModalEffect({
267
+ y,
268
+ rootId,
269
+ sheetRef,
270
+ snapPointsProp,
271
+ startThreshold
272
+ }) {
273
+ const insetTop = useSafeAreaInsets().top;
274
+ useIsomorphicLayoutEffect(() => {
275
+ return () => {
276
+ if (rootId) cleanupModalEffect(rootId);
277
+ };
278
+ }, []);
279
+ useIsomorphicLayoutEffect(() => {
280
+ if (!rootId) return;
281
+ const root = document.querySelector(`#${rootId}`);
282
+ if (!root) return;
283
+ let sheetHeight = 0;
284
+ const removeStartListener = y.on("animationStart", () => {
285
+ sheetHeight = getSheetHeight(sheetRef);
286
+ setupModalEffect(rootId);
287
+ });
288
+ const removeChangeListener = y.on("change", (yValue) => {
289
+ if (!root) return;
290
+ let progress = Math.max(0, 1 - yValue / sheetHeight);
291
+ const snapPoints = snapPointsProp ? getSnapPoints({ snapPointsProp, sheetHeight }) : void 0;
292
+ const snapThresholdPoint = snapPoints && snapPoints.length > 1 ? snapPoints[1] : void 0;
293
+ if (snapThresholdPoint !== void 0) {
294
+ const snapThresholdValue = sheetHeight - Math.min(snapThresholdPoint, sheetHeight);
295
+ if (yValue <= snapThresholdValue) {
296
+ progress = (snapThresholdValue - yValue) / snapThresholdValue;
297
+ } else {
298
+ progress = 0;
299
+ }
300
+ }
301
+ if (startThreshold !== void 0) {
302
+ const startThresholdValue = sheetHeight - Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
303
+ if (yValue <= startThresholdValue) {
304
+ progress = (startThresholdValue - yValue) / startThresholdValue;
305
+ } else {
306
+ progress = 0;
307
+ }
308
+ }
309
+ progress = Math.max(0, Math.min(1, progress));
310
+ const pageWidth = window.innerWidth;
311
+ const ty = transform(progress, [0, 1], [0, 24 + insetTop]);
312
+ const s = transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]);
313
+ const borderRadius = transform(progress, [0, 1], [0, 10]);
314
+ root.style.transform = `scale(${s}) translate3d(0, ${ty}px, 0)`;
315
+ root.style.borderTopRightRadius = `${borderRadius}px`;
316
+ root.style.borderTopLeftRadius = `${borderRadius}px`;
317
+ });
318
+ function onCompleted() {
319
+ if (y.get() - 5 >= sheetHeight) {
320
+ cleanupModalEffect(rootId);
321
+ }
322
+ }
323
+ const removeCompleteListener = y.on("animationComplete", onCompleted);
324
+ const removeCancelListener = y.on("animationCancel", onCompleted);
325
+ return () => {
326
+ removeStartListener();
327
+ removeChangeListener();
328
+ removeCompleteListener();
329
+ removeCancelListener();
330
+ };
331
+ }, [y, rootId, insetTop, startThreshold, snapPointsProp]);
332
+ }
333
+ function setupModalEffect(rootId) {
334
+ const root = document.querySelector(`#${rootId}`);
335
+ const body = document.querySelector("body");
336
+ if (!root) return;
337
+ body.style.backgroundColor = "#000";
338
+ root.style.overflow = "hidden";
339
+ root.style.transitionTimingFunction = "cubic-bezier(0.32, 0.72, 0, 1)";
340
+ root.style.transitionProperty = "transform, border-radius";
341
+ root.style.transitionDuration = "0.5s";
342
+ root.style.transformOrigin = "center top";
343
+ }
344
+ function cleanupModalEffect(rootId) {
345
+ const root = document.querySelector(`#${rootId}`);
346
+ const body = document.querySelector("body");
347
+ if (!root) return;
348
+ body.style.removeProperty("background-color");
349
+ root.style.removeProperty("overflow");
350
+ root.style.removeProperty("transition-timing-function");
351
+ root.style.removeProperty("transition-property");
352
+ root.style.removeProperty("transition-duration");
353
+ root.style.removeProperty("transform-origin");
354
+ root.style.removeProperty("transform");
355
+ root.style.removeProperty("border-top-right-radius");
356
+ root.style.removeProperty("border-top-left-radius");
357
+ }
358
+ function useDimensions() {
359
+ const [dimensions, setDimensions] = useState(() => ({
360
+ height: !IS_SSR ? window.innerHeight : 0,
361
+ width: !IS_SSR ? window.innerWidth : 0
362
+ }));
363
+ useIsomorphicLayoutEffect(() => {
364
+ function handler() {
365
+ setDimensions({
366
+ height: window.innerHeight,
367
+ width: window.innerWidth
368
+ });
369
+ }
370
+ handler();
371
+ window.addEventListener("resize", handler);
372
+ return () => {
373
+ window.removeEventListener("resize", handler);
374
+ };
375
+ }, []);
376
+ return dimensions;
377
+ }
378
+ function useStableCallback(handler) {
379
+ const handlerRef = useRef(void 0);
380
+ useIsomorphicLayoutEffect(() => {
381
+ handlerRef.current = handler;
382
+ });
383
+ return useCallback((...args) => {
384
+ const fn = handlerRef.current;
385
+ return fn == null ? void 0 : fn(...args);
386
+ }, []);
387
+ }
388
+
389
+ // src/hooks/use-prevent-scroll.ts
272
390
  var KEYBOARD_BUFFER = 24;
273
391
  function chain(...callbacks) {
274
392
  return (...args) => {
@@ -484,74 +602,6 @@ function willOpenKeyboard(target) {
484
602
  return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
485
603
  }
486
604
 
487
- // src/styles.ts
488
- var styles = {
489
- wrapper: {
490
- position: "fixed",
491
- top: 0,
492
- bottom: 0,
493
- left: 0,
494
- right: 0,
495
- overflow: "hidden",
496
- pointerEvents: "none"
497
- },
498
- backdrop: {
499
- zIndex: 1,
500
- position: "fixed",
501
- top: 0,
502
- left: 0,
503
- width: "100%",
504
- height: "100%",
505
- backgroundColor: "rgba(0, 0, 0, 0.2)",
506
- touchAction: "none",
507
- // Disable iOS body scrolling
508
- border: "none",
509
- WebkitTapHighlightColor: "transparent"
510
- },
511
- container: {
512
- zIndex: 2,
513
- position: "absolute",
514
- left: 0,
515
- bottom: 0,
516
- width: "100%",
517
- backgroundColor: "#fff",
518
- borderTopRightRadius: "8px",
519
- borderTopLeftRadius: "8px",
520
- boxShadow: "0px -2px 16px rgba(0, 0, 0, 0.3)",
521
- display: "flex",
522
- flexDirection: "column",
523
- pointerEvents: "auto"
524
- },
525
- headerWrapper: {
526
- width: "100%"
527
- },
528
- header: {
529
- height: "40px",
530
- width: "100%",
531
- position: "relative",
532
- display: "flex",
533
- alignItems: "center",
534
- justifyContent: "center"
535
- },
536
- indicator: {
537
- width: "18px",
538
- height: "4px",
539
- borderRadius: "99px",
540
- backgroundColor: "#ddd"
541
- },
542
- content: {
543
- flexGrow: 1,
544
- display: "flex",
545
- flexDirection: "column",
546
- minHeight: "0px",
547
- position: "relative"
548
- },
549
- scroller: {
550
- height: "100%",
551
- overflowY: "auto"
552
- }
553
- };
554
-
555
605
  // src/sheet.tsx
556
606
  var Sheet = forwardRef(
557
607
  ({
@@ -566,6 +616,7 @@ var Sheet = forwardRef(
566
616
  isOpen,
567
617
  snapPoints: snapPointsProp,
568
618
  rootId,
619
+ modalEffectRootId,
569
620
  mountPoint,
570
621
  style,
571
622
  detent = "full-height",
@@ -575,6 +626,7 @@ var Sheet = forwardRef(
575
626
  tweenConfig = DEFAULT_TWEEN_CONFIG,
576
627
  dragVelocityThreshold = DEFAULT_DRAG_VELOCITY_THRESHOLD,
577
628
  dragCloseThreshold = DEFAULT_DRAG_CLOSE_THRESHOLD,
629
+ modalEffectThreshold,
578
630
  ...rest
579
631
  }, ref) => {
580
632
  const sheetRef = useRef(null);
@@ -609,26 +661,13 @@ var Sheet = forwardRef(
609
661
  onCloseEnd
610
662
  };
611
663
  });
612
- let snapPoints = snapPointsProp;
613
- if (snapPoints) {
614
- snapPoints = snapPoints.map((point) => {
615
- if (point > 0 && point <= 1) return Math.round(point * windowHeight);
616
- return point < 0 ? windowHeight + point : point;
617
- });
618
- console.assert(
619
- inDescendingOrder(snapPoints) || windowHeight === 0,
620
- `Snap points need to be in descending order got: [${snapPoints.join(
621
- ", "
622
- )}]`
623
- );
624
- }
625
- const onDrag = useEffectEvent((_, { delta }) => {
664
+ const onDrag = useStableCallback((_, { delta }) => {
626
665
  const velocity = y.getVelocity();
627
666
  if (velocity > 0) indicatorRotation.set(10);
628
667
  if (velocity < 0) indicatorRotation.set(-10);
629
668
  y.set(Math.max(y.get() + delta.y, 0));
630
669
  });
631
- const onDragStart = useEffectEvent(() => {
670
+ const onDragStart = useStableCallback(() => {
632
671
  const focusedElement = document.activeElement;
633
672
  if (!focusedElement || !sheetRef.current) return;
634
673
  const isInput = focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA";
@@ -636,67 +675,91 @@ var Sheet = forwardRef(
636
675
  focusedElement.blur();
637
676
  }
638
677
  });
639
- const onDragEnd = useEffectEvent((_, { velocity }) => {
678
+ const onDragEnd = useStableCallback((_, { velocity }) => {
640
679
  if (velocity.y > dragVelocityThreshold) {
641
680
  onClose();
642
681
  } else {
643
- const sheetHeight = sheetRef.current.getBoundingClientRect().height;
682
+ const sheetHeight = getSheetHeight(sheetRef);
644
683
  const currentY = y.get();
645
- let snapTo = 0;
684
+ let yTo = 0;
685
+ const snapPoints = snapPointsProp ? getSnapPoints({ snapPointsProp, sheetHeight }) : void 0;
646
686
  if (snapPoints) {
647
- const snapToValues = snapPoints.map(
687
+ const snapInverse = snapPoints.map(
648
688
  (p) => sheetHeight - Math.min(p, sheetHeight)
649
689
  );
650
- if (detent === "content-height" && !snapToValues.includes(0)) {
651
- snapToValues.unshift(0);
690
+ if (detent === "content-height" && !snapInverse.includes(0)) {
691
+ snapInverse.unshift(0);
652
692
  }
653
- snapTo = getClosest(snapToValues, currentY);
693
+ const snapTo = getClosest(snapInverse, currentY);
694
+ const snapIndex = snapInverse.indexOf(snapTo);
695
+ yTo = validateSnapTo({
696
+ snapTo: getClosest(snapInverse, currentY),
697
+ sheetHeight
698
+ });
699
+ onSnap == null ? void 0 : onSnap(snapIndex);
654
700
  } else if (currentY / sheetHeight > dragCloseThreshold) {
655
- snapTo = sheetHeight;
701
+ yTo = sheetHeight;
656
702
  }
657
- snapTo = validateSnapTo({ snapTo, sheetHeight });
658
- animate(y, snapTo, animationOptions);
659
- if (snapPoints && onSnap) {
660
- const snapValue = Math.abs(Math.round(snapPoints[0] - snapTo));
661
- const snapIndex = snapPoints.indexOf(
662
- getClosest(snapPoints, snapValue)
663
- );
664
- onSnap(snapIndex);
703
+ animate(y, yTo, animationOptions);
704
+ if (yTo + 1 >= sheetHeight) {
705
+ onClose();
665
706
  }
666
- const roundedSheetHeight = Math.round(sheetHeight);
667
- const shouldClose = snapTo + 2 >= roundedSheetHeight;
668
- if (shouldClose) onClose();
669
707
  }
670
708
  indicatorRotation.set(0);
671
709
  });
672
710
  useEffect(() => {
673
- if (!snapPoints || !onSnap) return;
674
- const snapIndex = isOpen ? initialSnap : snapPoints.length - 1;
675
- onSnap(snapIndex);
711
+ if (snapPointsProp && onSnap) {
712
+ onSnap(isOpen ? initialSnap : snapPointsProp.length - 1);
713
+ }
714
+ }, [isOpen]);
715
+ useEffect(() => {
716
+ if (!isOpen) return;
717
+ let yTo = 0;
718
+ if (snapPointsProp) {
719
+ const snapIndex = initialSnap;
720
+ const sheetHeight = getSheetHeight(sheetRef);
721
+ const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
722
+ const ySnap = getSnapY({ sheetHeight, snapPoints, snapIndex });
723
+ if (ySnap !== null) {
724
+ yTo = ySnap;
725
+ }
726
+ }
727
+ animate(y, yTo, animationOptions);
676
728
  }, [isOpen]);
677
729
  useImperativeHandle(ref, () => ({
678
730
  y,
679
731
  snapTo: (snapIndex) => {
680
- const sheetEl = sheetRef.current;
681
- if ((snapPoints == null ? void 0 : snapPoints[snapIndex]) !== void 0 && sheetEl) {
682
- const sheetHeight = sheetEl.getBoundingClientRect().height;
683
- const snapPoint = snapPoints[snapIndex];
684
- const snapTo = validateSnapTo({
685
- snapTo: sheetHeight - snapPoint,
686
- sheetHeight
687
- });
688
- animate(y, snapTo, animationOptions);
689
- if (onSnap) onSnap(snapIndex);
690
- if (snapTo >= sheetHeight) onClose();
732
+ if (!snapPointsProp) {
733
+ console.warn("Snapping is not possible without `snapPoints` prop.");
734
+ return;
735
+ }
736
+ const sheetHeight = getSheetHeight(sheetRef);
737
+ const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
738
+ const ySnap = getSnapY({ sheetHeight, snapPoints, snapIndex });
739
+ if (ySnap !== null) {
740
+ animate(y, ySnap, animationOptions);
741
+ onSnap == null ? void 0 : onSnap(snapIndex);
742
+ if (ySnap + 1 >= sheetHeight) {
743
+ onClose();
744
+ }
691
745
  }
692
746
  }
693
747
  }));
748
+ if (rootId) {
749
+ console.warn(
750
+ "The `rootId` prop is deprecated and will be removed in the next major version. Use `modalEffectRootId` instead."
751
+ );
752
+ }
694
753
  useModalEffect({
695
754
  y,
696
- rootId,
697
- sheetRef
755
+ sheetRef,
756
+ snapPointsProp,
757
+ rootId: rootId || modalEffectRootId,
758
+ startThreshold: modalEffectThreshold
759
+ });
760
+ usePreventScroll({
761
+ isDisabled: disableScrollLocking || !isOpen
698
762
  });
699
- usePreventScroll({ isDisabled: disableScrollLocking || !isOpen });
700
763
  const dragProps = useMemo(() => {
701
764
  const dragProps2 = {
702
765
  drag: "y",
@@ -714,7 +777,6 @@ var Sheet = forwardRef(
714
777
  sheetRef,
715
778
  isOpen,
716
779
  initialSnap,
717
- snapPoints,
718
780
  detent,
719
781
  indicatorRotation,
720
782
  callbacks,
@@ -741,23 +803,50 @@ var Sheet = forwardRef(
741
803
  }
742
804
  );
743
805
  Sheet.displayName = "Sheet";
806
+ function useEventCallbacks(isOpen, callbacks) {
807
+ const prevOpen = usePrevious(isOpen);
808
+ const didOpen = useRef(false);
809
+ const handleAnimationComplete = useCallback(() => {
810
+ var _a, _b, _c, _d;
811
+ if (!didOpen.current) {
812
+ (_b = (_a = callbacks.current).onOpenEnd) == null ? void 0 : _b.call(_a);
813
+ didOpen.current = true;
814
+ } else {
815
+ (_d = (_c = callbacks.current).onCloseEnd) == null ? void 0 : _d.call(_c);
816
+ didOpen.current = false;
817
+ }
818
+ }, [isOpen, prevOpen]);
819
+ useEffect(() => {
820
+ var _a, _b, _c, _d;
821
+ if (!prevOpen && isOpen) {
822
+ (_b = (_a = callbacks.current).onOpenStart) == null ? void 0 : _b.call(_a);
823
+ } else if (!isOpen && prevOpen) {
824
+ (_d = (_c = callbacks.current).onCloseStart) == null ? void 0 : _d.call(_c);
825
+ }
826
+ }, [isOpen, prevOpen]);
827
+ return { handleAnimationComplete };
828
+ }
829
+ function usePrevious(state) {
830
+ const ref = useRef(void 0);
831
+ useEffect(() => {
832
+ ref.current = state;
833
+ });
834
+ return ref.current;
835
+ }
836
+
837
+ // src/SheetContainer.tsx
744
838
  var SheetContainer = forwardRef(
745
- ({ children, style = {}, className = "", ...rest }, ref) => {
839
+ ({ children, style, className = "", ...rest }, ref) => {
746
840
  const {
747
841
  y,
748
842
  isOpen,
749
843
  callbacks,
750
- snapPoints,
751
- initialSnap = 0,
752
844
  sheetRef,
753
845
  windowHeight,
754
846
  detent,
755
847
  animationOptions
756
848
  } = useSheetContext();
757
849
  const { handleAnimationComplete } = useEventCallbacks(isOpen, callbacks);
758
- const initialY = snapPoints ? snapPoints[0] - snapPoints[initialSnap] : 0;
759
- const maxSnapHeight = snapPoints ? snapPoints[0] : null;
760
- const height = maxSnapHeight !== null ? `min(${maxSnapHeight}px, ${MAX_HEIGHT})` : MAX_HEIGHT;
761
850
  return /* @__PURE__ */ React2.createElement(
762
851
  motion.div,
763
852
  {
@@ -767,12 +856,11 @@ var SheetContainer = forwardRef(
767
856
  style: {
768
857
  ...styles.container,
769
858
  ...style,
770
- ...detent === "full-height" && { height },
771
- ...detent === "content-height" && { maxHeight: height },
859
+ ...detent === "full-height" && { height: MAX_HEIGHT },
860
+ ...detent === "content-height" && { maxHeight: MAX_HEIGHT },
772
861
  y
773
862
  },
774
863
  initial: { y: windowHeight },
775
- animate: { y: initialY, transition: animationOptions },
776
864
  exit: { y: windowHeight, transition: animationOptions },
777
865
  onAnimationComplete: handleAnimationComplete
778
866
  },
@@ -781,6 +869,14 @@ var SheetContainer = forwardRef(
781
869
  }
782
870
  );
783
871
  SheetContainer.displayName = "SheetContainer";
872
+ var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
873
+ function useDragConstraints() {
874
+ const constraintsRef = useRef(null);
875
+ const onMeasureDragConstraints = useCallback(() => constraints, []);
876
+ return { constraintsRef, onMeasureDragConstraints };
877
+ }
878
+
879
+ // src/SheetContent.tsx
784
880
  var SheetContent = forwardRef(
785
881
  ({ children, style, disableDrag, className = "", ...rest }, ref) => {
786
882
  const sheetContext = useSheetContext();