react-modal-sheet 4.4.0 → 5.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,12 +1,125 @@
1
+ import { motion, useMotionValue, useTransform, useReducedMotion, animate } from 'motion/react';
2
+ import React4, { createContext, forwardRef, useRef, useState, useImperativeHandle, useContext, useCallback, useEffect, useLayoutEffect } from 'react';
1
3
  import { createPortal } from 'react-dom';
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 useMeasure from 'react-use-measure';
4
5
  import { transform } from 'motion';
5
6
 
6
- // src/sheet.tsx
7
+ // src/SheetBackdrop.tsx
8
+ var SheetContext = createContext(
9
+ void 0
10
+ );
11
+ function useSheetContext() {
12
+ const context = useContext(SheetContext);
13
+ if (!context) throw new Error("Sheet context error");
14
+ return context;
15
+ }
16
+
17
+ // src/styles.ts
18
+ var styles = {
19
+ root: {
20
+ base: {
21
+ position: "fixed",
22
+ top: 0,
23
+ bottom: 0,
24
+ left: 0,
25
+ right: 0,
26
+ overflow: "hidden",
27
+ pointerEvents: "none"
28
+ },
29
+ decorative: {}
30
+ },
31
+ backdrop: {
32
+ base: {
33
+ zIndex: 1,
34
+ position: "fixed",
35
+ top: 0,
36
+ left: 0,
37
+ width: "100%",
38
+ height: "100%",
39
+ touchAction: "none",
40
+ userSelect: "none"
41
+ },
42
+ decorative: {
43
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
44
+ border: "none",
45
+ WebkitTapHighlightColor: "transparent"
46
+ }
47
+ },
48
+ container: {
49
+ base: {
50
+ zIndex: 2,
51
+ position: "absolute",
52
+ left: 0,
53
+ bottom: 0,
54
+ width: "100%",
55
+ pointerEvents: "auto",
56
+ display: "flex",
57
+ flexDirection: "column"
58
+ },
59
+ decorative: {
60
+ backgroundColor: "#fff",
61
+ borderTopRightRadius: "8px",
62
+ borderTopLeftRadius: "8px",
63
+ boxShadow: "0px -2px 16px rgba(0, 0, 0, 0.3)"
64
+ }
65
+ },
66
+ headerWrapper: {
67
+ base: {
68
+ width: "100%"
69
+ },
70
+ decorative: {}
71
+ },
72
+ header: {
73
+ base: {
74
+ width: "100%",
75
+ position: "relative"
76
+ },
77
+ decorative: {
78
+ height: "40px",
79
+ display: "flex",
80
+ alignItems: "center",
81
+ justifyContent: "center"
82
+ }
83
+ },
84
+ indicatorWrapper: {
85
+ base: {
86
+ display: "flex"
87
+ },
88
+ decorative: {}
89
+ },
90
+ indicator: {
91
+ base: {
92
+ display: "inline-block"
93
+ },
94
+ decorative: {
95
+ width: "18px",
96
+ height: "4px",
97
+ borderRadius: "99px",
98
+ backgroundColor: "#ddd"
99
+ }
100
+ },
101
+ content: {
102
+ base: {
103
+ minHeight: "0px",
104
+ position: "relative",
105
+ flexGrow: 1,
106
+ display: "flex",
107
+ flexDirection: "column"
108
+ },
109
+ decorative: {}
110
+ },
111
+ scroller: {
112
+ base: {
113
+ height: "100%",
114
+ overflowY: "auto",
115
+ overscrollBehaviorY: "none"
116
+ },
117
+ decorative: {}
118
+ }
119
+ };
7
120
 
8
121
  // src/constants.ts
9
- var MAX_HEIGHT = "calc(100% - env(safe-area-inset-top) - 34px)";
122
+ var DEFAULT_HEIGHT = "calc(100% - env(safe-area-inset-top) - 34px)";
10
123
  var IS_SSR = typeof window === "undefined";
11
124
  var DEFAULT_TWEEN_CONFIG = {
12
125
  ease: "easeOut",
@@ -20,96 +133,15 @@ var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
20
133
  var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
21
134
 
22
135
  // 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;
30
- }
31
- return Math.round(sheetEl.getBoundingClientRect().height);
32
- }
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);
40
- }
41
- return point < 0 ? sheetHeight + point : point;
42
- });
43
- console.assert(
44
- inDescendingOrder(snapPointValues) || sheetHeight === 0,
45
- `Snap points need to be in descending order got: [${snapPointsProp.join(", ")}]`
46
- );
47
- return snapPointValues;
48
- }
49
- function getClosestSnapPoint({
50
- snapPoints,
51
- currentY,
52
- sheetHeight,
53
- detent
54
- }) {
55
- const snapInverse = snapPoints.map(
56
- (p) => sheetHeight - Math.min(p, sheetHeight)
57
- );
58
- if (detent === "content-height" && !snapInverse.includes(0)) {
59
- snapInverse.unshift(0);
60
- }
61
- const snapTo = getClosest(snapInverse, currentY);
62
- const snapIndex = snapInverse.indexOf(snapTo);
63
- const snapY = validateSnapTo({
64
- snapTo: getClosest(snapInverse, currentY),
65
- sheetHeight
66
- });
67
- return { snapY, snapIndex };
68
- }
69
- function getClosest(nums, goal) {
70
- let closest = nums[0];
71
- let minDifference = Math.abs(nums[0] - goal);
72
- for (let i = 1; i < nums.length; i++) {
73
- const difference = Math.abs(nums[i] - goal);
74
- if (difference < minDifference) {
75
- closest = nums[i];
76
- minDifference = difference;
77
- }
78
- }
79
- return closest;
136
+ function applyStyles(styles2, unstyled) {
137
+ return unstyled ? styles2.base : { ...styles2.base, ...styles2.decorative };
80
138
  }
81
- function inDescendingOrder(arr) {
139
+ function isAscendingOrder(arr) {
82
140
  for (let i = 0; i < arr.length; i++) {
83
- if (arr[i + 1] > arr[i]) return false;
141
+ if (arr[i + 1] < arr[i]) return false;
84
142
  }
85
143
  return true;
86
144
  }
87
- function getSnapY({
88
- sheetHeight,
89
- snapPoints,
90
- snapIndex
91
- }) {
92
- const snapPoint = snapPoints[snapIndex];
93
- if (snapPoint === void 0) {
94
- console.warn(
95
- `Invalid snap index ${snapIndex}. Snap points are: [${snapPoints.join(", ")}]`
96
- );
97
- return null;
98
- }
99
- const y = validateSnapTo({ snapTo: sheetHeight - snapPoint, sheetHeight });
100
- return y;
101
- }
102
- function validateSnapTo({
103
- snapTo,
104
- sheetHeight
105
- }) {
106
- if (snapTo < 0) {
107
- console.warn(
108
- `Snap point is out of bounds. Sheet height is ${sheetHeight} but snap point is ${sheetHeight + Math.abs(snapTo)}.`
109
- );
110
- }
111
- return Math.max(Math.round(snapTo), 0);
112
- }
113
145
  function mergeRefs(refs) {
114
146
  return (value) => {
115
147
  refs.forEach((ref) => {
@@ -121,10 +153,6 @@ function mergeRefs(refs) {
121
153
  });
122
154
  };
123
155
  }
124
- function isTouchDevice() {
125
- if (IS_SSR) return false;
126
- return "ontouchstart" in window || navigator.maxTouchPoints > 0;
127
- }
128
156
  function testPlatform(re) {
129
157
  var _a;
130
158
  return typeof window !== "undefined" && window.navigator != null ? re.test(
@@ -153,109 +181,287 @@ var isIPad = cached(function() {
153
181
  var isIOS = cached(function() {
154
182
  return isIPhone() || isIPad();
155
183
  });
156
- var SheetContext = createContext(
157
- void 0
184
+
185
+ // src/SheetBackdrop.tsx
186
+ var isClickable = (props) => !!props.onClick || !!props.onTap;
187
+ var SheetBackdrop = forwardRef(
188
+ ({ style, className = "", unstyled, ...rest }, ref) => {
189
+ const sheetContext = useSheetContext();
190
+ const clickable = isClickable(rest);
191
+ const Comp = clickable ? motion.button : motion.div;
192
+ const pointerEvents = clickable ? "auto" : "none";
193
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
194
+ const backdropStyle = {
195
+ ...applyStyles(styles.backdrop, isUnstyled),
196
+ ...style,
197
+ pointerEvents
198
+ };
199
+ return /* @__PURE__ */ React4.createElement(
200
+ Comp,
201
+ {
202
+ ...rest,
203
+ ref,
204
+ className: `react-modal-sheet-backdrop ${className}`,
205
+ style: backdropStyle,
206
+ initial: { opacity: 0 },
207
+ animate: { opacity: 1 },
208
+ exit: { opacity: 0 },
209
+ transition: { duration: 1 }
210
+ }
211
+ );
212
+ }
158
213
  );
159
- function useSheetContext() {
160
- const context = useContext(SheetContext);
161
- if (!context) throw new Error("Sheet context error");
162
- return context;
214
+ SheetBackdrop.displayName = "SheetBackdrop";
215
+ var SheetContainer = forwardRef(
216
+ ({ children, style, className = "", unstyled, ...rest }, ref) => {
217
+ const sheetContext = useSheetContext();
218
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
219
+ const containerStyle = {
220
+ ...applyStyles(styles.container, isUnstyled),
221
+ ...style,
222
+ y: sheetContext.y
223
+ };
224
+ if (sheetContext.detent === "default") {
225
+ containerStyle.height = DEFAULT_HEIGHT;
226
+ }
227
+ if (sheetContext.detent === "full") {
228
+ containerStyle.height = "100%";
229
+ containerStyle.maxHeight = "100%";
230
+ }
231
+ if (sheetContext.detent === "content") {
232
+ containerStyle.height = "auto";
233
+ containerStyle.maxHeight = DEFAULT_HEIGHT;
234
+ }
235
+ return /* @__PURE__ */ React4.createElement(
236
+ motion.div,
237
+ {
238
+ ...rest,
239
+ ref: mergeRefs([
240
+ ref,
241
+ sheetContext.sheetRef,
242
+ sheetContext.sheetBoundsRef
243
+ ]),
244
+ className: `react-modal-sheet-container ${className}`,
245
+ style: containerStyle
246
+ },
247
+ children
248
+ );
249
+ }
250
+ );
251
+ SheetContainer.displayName = "SheetContainer";
252
+ var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
253
+ function useDragConstraints() {
254
+ const ref = useRef(null);
255
+ const onMeasure = useCallback(() => constraints, []);
256
+ return { ref, onMeasure };
257
+ }
258
+ function useScrollPosition() {
259
+ const ref = useRef(null);
260
+ const [scrollPosition, setScrollPosition] = useState(void 0);
261
+ useEffect(() => {
262
+ const element = ref.current;
263
+ if (!element) return;
264
+ let scrollTimeout = null;
265
+ function determineScrollPosition(element2) {
266
+ const { scrollTop, scrollHeight, clientHeight } = element2;
267
+ const isScrollable2 = scrollHeight > clientHeight;
268
+ if (!isScrollable2) {
269
+ if (scrollPosition) setScrollPosition(void 0);
270
+ return;
271
+ }
272
+ const isAtTop = scrollTop <= 0;
273
+ const isAtBottom = Math.ceil(scrollHeight) - Math.ceil(scrollTop) === Math.ceil(clientHeight);
274
+ let position;
275
+ if (isAtTop) {
276
+ position = "top";
277
+ } else if (isAtBottom) {
278
+ position = "bottom";
279
+ } else {
280
+ position = "middle";
281
+ }
282
+ if (position === scrollPosition) return;
283
+ setScrollPosition(position);
284
+ }
285
+ function onScroll(event) {
286
+ if (event.currentTarget instanceof HTMLDivElement) {
287
+ const el = event.currentTarget;
288
+ if (scrollTimeout) clearTimeout(scrollTimeout);
289
+ scrollTimeout = setTimeout(() => determineScrollPosition(el), 32);
290
+ }
291
+ }
292
+ function onTouchStart(event) {
293
+ if (event.currentTarget instanceof HTMLDivElement) {
294
+ determineScrollPosition(event.currentTarget);
295
+ }
296
+ }
297
+ determineScrollPosition(element);
298
+ element.addEventListener("scroll", onScroll);
299
+ element.addEventListener("touchstart", onTouchStart);
300
+ return () => {
301
+ if (scrollTimeout) clearTimeout(scrollTimeout);
302
+ element.removeEventListener("scroll", onScroll);
303
+ element.removeEventListener("touchstart", onTouchStart);
304
+ };
305
+ }, []);
306
+ return { ref, scrollPosition };
163
307
  }
164
- var SheetScrollerContext = createContext(void 0);
165
- function SheetScrollerContextProvider({
166
- children
308
+
309
+ // src/SheetContent.tsx
310
+ var SheetContent = forwardRef(
311
+ ({
312
+ disableScroll: disableScrollProp,
313
+ disableDrag: disableDragProp,
314
+ children,
315
+ style: styleProp,
316
+ className = "",
317
+ scrollRef: scrollRefProp = null,
318
+ unstyled,
319
+ ...rest
320
+ }, ref) => {
321
+ const sheetContext = useSheetContext();
322
+ const dragConstraints = useDragConstraints();
323
+ const scroll = useScrollPosition();
324
+ const disableScroll = typeof disableScrollProp === "function" ? disableScrollProp({
325
+ scrollPosition: scroll.scrollPosition,
326
+ currentSnap: sheetContext.currentSnap
327
+ }) : Boolean(disableScrollProp);
328
+ const disableDragDueToScroll = !disableScroll && scroll.scrollPosition && scroll.scrollPosition !== "top";
329
+ const disableDragDueToProp = typeof disableDragProp === "function" ? disableDragProp({
330
+ scrollPosition: scroll.scrollPosition,
331
+ currentSnap: sheetContext.currentSnap
332
+ }) : Boolean(disableDragProp);
333
+ const disableDrag = disableDragDueToProp || disableDragDueToScroll || sheetContext.disableDrag;
334
+ const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
335
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
336
+ const contentStyle = {
337
+ ...applyStyles(styles.content, isUnstyled),
338
+ ...styleProp
339
+ };
340
+ const scrollStyle = applyStyles(styles.scroller, isUnstyled);
341
+ if (sheetContext.avoidKeyboard) {
342
+ scrollStyle.paddingBottom = "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))";
343
+ }
344
+ if (disableScroll) {
345
+ scrollStyle.overflowY = "hidden";
346
+ }
347
+ return /* @__PURE__ */ React4.createElement(
348
+ motion.div,
349
+ {
350
+ ...rest,
351
+ ref: mergeRefs([ref, dragConstraints.ref]),
352
+ className: `react-modal-sheet-content ${className}`,
353
+ style: contentStyle,
354
+ ...dragProps,
355
+ dragConstraints: dragConstraints.ref,
356
+ onMeasureDragConstraints: dragConstraints.onMeasure
357
+ },
358
+ /* @__PURE__ */ React4.createElement(
359
+ motion.div,
360
+ {
361
+ ref: mergeRefs([scroll.ref, scrollRefProp]),
362
+ style: scrollStyle,
363
+ className: "react-modal-sheet-content-scroller"
364
+ },
365
+ children
366
+ )
367
+ );
368
+ }
369
+ );
370
+ SheetContent.displayName = "SheetContent";
371
+ function SheetDragIndicator({
372
+ style,
373
+ className = "",
374
+ unstyled,
375
+ ...rest
167
376
  }) {
168
377
  const sheetContext = useSheetContext();
169
- const [disableDrag, setDisableDrag] = useState(!!sheetContext.disableDrag);
170
- function setDragEnabled() {
171
- if (!sheetContext.disableDrag) setDisableDrag(false);
172
- }
173
- function setDragDisabled() {
174
- if (!disableDrag) setDisableDrag(true);
175
- }
176
- return /* @__PURE__ */ React2.createElement(
177
- SheetScrollerContext.Provider,
378
+ const indicator1Transform = useTransform(
379
+ sheetContext.indicatorRotation,
380
+ (r) => `translateX(2px) rotate(${r}deg)`
381
+ );
382
+ const indicator2Transform = useTransform(
383
+ sheetContext.indicatorRotation,
384
+ (r) => `translateX(-2px) rotate(${ -1 * r}deg)`
385
+ );
386
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
387
+ const indicatorWrapperStyle = {
388
+ ...applyStyles(styles.indicatorWrapper, isUnstyled),
389
+ ...style
390
+ };
391
+ const indicatorStyle = applyStyles(styles.indicator, isUnstyled);
392
+ return /* @__PURE__ */ React4.createElement(
393
+ "div",
178
394
  {
179
- value: { disableDrag, setDragEnabled, setDragDisabled }
395
+ className: `react-modal-sheet-drag-indicator-container ${className}`,
396
+ style: indicatorWrapperStyle,
397
+ ...rest
180
398
  },
181
- children
399
+ /* @__PURE__ */ React4.createElement(
400
+ motion.span,
401
+ {
402
+ className: "react-modal-sheet-drag-indicator",
403
+ style: { ...indicatorStyle, transform: indicator1Transform }
404
+ }
405
+ ),
406
+ /* @__PURE__ */ React4.createElement(
407
+ motion.span,
408
+ {
409
+ className: "react-modal-sheet-drag-indicator",
410
+ style: { ...indicatorStyle, transform: indicator2Transform }
411
+ }
412
+ )
182
413
  );
183
414
  }
184
- function useSheetScrollerContext() {
185
- const context = useContext(SheetScrollerContext);
186
- if (!context) throw new Error("Sheet scroller context error");
187
- return context;
188
- }
189
-
190
- // src/styles.ts
191
- var styles = {
192
- wrapper: {
193
- position: "fixed",
194
- top: 0,
195
- bottom: 0,
196
- left: 0,
197
- right: 0,
198
- overflow: "hidden",
199
- pointerEvents: "none"
200
- },
201
- backdrop: {
202
- zIndex: 1,
203
- position: "fixed",
204
- top: 0,
205
- left: 0,
206
- width: "100%",
207
- height: "100%",
208
- backgroundColor: "rgba(0, 0, 0, 0.2)",
209
- touchAction: "none",
210
- border: "none",
211
- userSelect: "none",
212
- WebkitTapHighlightColor: "transparent"
213
- },
214
- container: {
215
- zIndex: 2,
216
- position: "absolute",
217
- left: 0,
218
- bottom: 0,
219
- width: "100%",
220
- backgroundColor: "#fff",
221
- borderTopRightRadius: "8px",
222
- borderTopLeftRadius: "8px",
223
- boxShadow: "0px -2px 16px rgba(0, 0, 0, 0.3)",
224
- display: "flex",
225
- flexDirection: "column",
226
- pointerEvents: "auto"
227
- },
228
- headerWrapper: {
229
- width: "100%"
230
- },
231
- header: {
232
- height: "40px",
233
- width: "100%",
234
- position: "relative",
235
- display: "flex",
236
- alignItems: "center",
237
- justifyContent: "center"
238
- },
239
- indicator: {
240
- width: "18px",
241
- height: "4px",
242
- borderRadius: "99px",
243
- backgroundColor: "#ddd"
244
- },
245
- content: {
246
- flexGrow: 1,
247
- display: "flex",
248
- flexDirection: "column",
249
- minHeight: "0px",
250
- position: "relative"
251
- },
252
- scroller: {
253
- height: "100%",
254
- overflowY: "auto",
255
- overscrollBehaviorY: "none"
415
+ var SheetHeader = forwardRef(
416
+ ({ children, style, disableDrag, unstyled, className = "", ...rest }, ref) => {
417
+ const sheetContext = useSheetContext();
418
+ const dragConstraints = useDragConstraints();
419
+ const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
420
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
421
+ const headerWrapperStyle = {
422
+ ...applyStyles(styles.headerWrapper, isUnstyled),
423
+ ...style
424
+ };
425
+ const headerStyle = applyStyles(styles.header, isUnstyled);
426
+ return /* @__PURE__ */ React4.createElement(
427
+ motion.div,
428
+ {
429
+ ...rest,
430
+ ref: mergeRefs([ref, dragConstraints.ref]),
431
+ style: headerWrapperStyle,
432
+ className: `react-modal-sheet-header-container ${className}`,
433
+ ...dragProps,
434
+ dragConstraints: dragConstraints.ref,
435
+ onMeasureDragConstraints: dragConstraints.onMeasure
436
+ },
437
+ children || /* @__PURE__ */ React4.createElement("div", { className: "react-modal-sheet-header", style: headerStyle }, /* @__PURE__ */ React4.createElement(SheetDragIndicator, null))
438
+ );
256
439
  }
257
- };
440
+ );
441
+ SheetHeader.displayName = "SheetHeader";
258
442
  var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
443
+
444
+ // src/hooks/use-dimensions.ts
445
+ function useDimensions() {
446
+ const [dimensions, setDimensions] = useState(() => ({
447
+ windowHeight: !IS_SSR ? window.innerHeight : 0,
448
+ windowWidth: !IS_SSR ? window.innerWidth : 0
449
+ }));
450
+ useIsomorphicLayoutEffect(() => {
451
+ function handler() {
452
+ setDimensions({
453
+ windowHeight: window.innerHeight,
454
+ windowWidth: window.innerWidth
455
+ });
456
+ }
457
+ handler();
458
+ window.addEventListener("resize", handler);
459
+ return () => {
460
+ window.removeEventListener("resize", handler);
461
+ };
462
+ }, []);
463
+ return dimensions;
464
+ }
259
465
  function useSafeAreaInsets() {
260
466
  const [insets] = useState(() => {
261
467
  const fallback = { top: 0, left: 0, right: 0, bottom: 0 };
@@ -287,12 +493,18 @@ function getComputedValue(computed, property) {
287
493
  // src/hooks/use-modal-effect.ts
288
494
  function useModalEffect({
289
495
  y,
290
- rootId,
291
- sheetRef,
292
- snapPointsProp,
496
+ detent,
497
+ rootId: _rootId,
498
+ sheetHeight,
499
+ snapPoints,
293
500
  startThreshold
294
501
  }) {
295
502
  const insetTop = useSafeAreaInsets().top;
503
+ let rootId = _rootId;
504
+ if (rootId && detent === "full") {
505
+ console.warn('Using "full" detent with modal effect is not supported.');
506
+ rootId = void 0;
507
+ }
296
508
  useIsomorphicLayoutEffect(() => {
297
509
  return () => {
298
510
  if (rootId) cleanupModalEffect(rootId);
@@ -302,18 +514,15 @@ function useModalEffect({
302
514
  if (!rootId) return;
303
515
  const root = document.querySelector(`#${rootId}`);
304
516
  if (!root) return;
305
- let sheetHeight = 0;
306
517
  const removeStartListener = y.on("animationStart", () => {
307
- sheetHeight = getSheetHeight(sheetRef);
308
518
  setupModalEffect(rootId);
309
519
  });
310
520
  const removeChangeListener = y.on("change", (yValue) => {
311
521
  if (!root) return;
312
522
  let progress = Math.max(0, 1 - yValue / sheetHeight);
313
- const snapPoints = snapPointsProp ? getSnapPoints({ snapPointsProp, sheetHeight }) : void 0;
314
- const snapThresholdPoint = snapPoints && snapPoints.length > 1 ? snapPoints[1] : void 0;
523
+ const snapThresholdPoint = snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : void 0;
315
524
  if (snapThresholdPoint !== void 0) {
316
- const snapThresholdValue = sheetHeight - Math.min(snapThresholdPoint, sheetHeight);
525
+ const snapThresholdValue = snapThresholdPoint.snapValueY;
317
526
  if (yValue <= snapThresholdValue) {
318
527
  progress = (snapThresholdValue - yValue) / snapThresholdValue;
319
528
  } else {
@@ -350,7 +559,7 @@ function useModalEffect({
350
559
  removeCompleteListener();
351
560
  removeCancelListener();
352
561
  };
353
- }, [y, rootId, insetTop, startThreshold, snapPointsProp]);
562
+ }, [y, rootId, insetTop, startThreshold, sheetHeight]);
354
563
  }
355
564
  function setupModalEffect(rootId) {
356
565
  const root = document.querySelector(`#${rootId}`);
@@ -377,36 +586,6 @@ function cleanupModalEffect(rootId) {
377
586
  root.style.removeProperty("border-top-right-radius");
378
587
  root.style.removeProperty("border-top-left-radius");
379
588
  }
380
- function useDimensions() {
381
- const [dimensions, setDimensions] = useState(() => ({
382
- height: !IS_SSR ? window.innerHeight : 0,
383
- width: !IS_SSR ? window.innerWidth : 0
384
- }));
385
- useIsomorphicLayoutEffect(() => {
386
- function handler() {
387
- setDimensions({
388
- height: window.innerHeight,
389
- width: window.innerWidth
390
- });
391
- }
392
- handler();
393
- window.addEventListener("resize", handler);
394
- return () => {
395
- window.removeEventListener("resize", handler);
396
- };
397
- }, []);
398
- return dimensions;
399
- }
400
- function useStableCallback(handler) {
401
- const handlerRef = useRef(void 0);
402
- useIsomorphicLayoutEffect(() => {
403
- handlerRef.current = handler;
404
- });
405
- return useCallback((...args) => {
406
- const fn = handlerRef.current;
407
- return fn == null ? void 0 : fn(...args);
408
- }, []);
409
- }
410
589
 
411
590
  // src/hooks/use-prevent-scroll.ts
412
591
  var KEYBOARD_BUFFER = 24;
@@ -623,83 +802,370 @@ function scrollIntoView(target) {
623
802
  function willOpenKeyboard(target) {
624
803
  return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
625
804
  }
626
-
627
- // src/sheet.tsx
805
+ function useStableCallback(handler) {
806
+ const handlerRef = useRef(void 0);
807
+ useIsomorphicLayoutEffect(() => {
808
+ handlerRef.current = handler;
809
+ });
810
+ return useCallback((...args) => {
811
+ const fn = handlerRef.current;
812
+ return fn == null ? void 0 : fn(...args);
813
+ }, []);
814
+ }
815
+
816
+ // src/hooks/use-sheet-state.ts
817
+ function useSheetState({
818
+ isOpen,
819
+ onClosed: _onClosed,
820
+ onOpening: _onOpening,
821
+ onOpen: _onOpen,
822
+ onClosing: _onClosing
823
+ }) {
824
+ const [state, setState] = useState(isOpen ? "opening" : "closed");
825
+ const onClosed = useStableCallback(() => _onClosed == null ? void 0 : _onClosed());
826
+ const onOpening = useStableCallback(() => _onOpening == null ? void 0 : _onOpening());
827
+ const onOpen = useStableCallback(() => _onOpen == null ? void 0 : _onOpen());
828
+ const onClosing = useStableCallback(() => _onClosing == null ? void 0 : _onClosing());
829
+ useEffect(() => {
830
+ if (isOpen && state === "closed") {
831
+ setState("opening");
832
+ } else if (!isOpen && (state === "open" || state === "opening")) {
833
+ setState("closing");
834
+ }
835
+ }, [isOpen, state]);
836
+ useEffect(() => {
837
+ async function handle() {
838
+ switch (state) {
839
+ case "closed":
840
+ await (onClosed == null ? void 0 : onClosed());
841
+ break;
842
+ case "opening":
843
+ await (onOpening == null ? void 0 : onOpening());
844
+ setState("open");
845
+ break;
846
+ case "open":
847
+ await (onOpen == null ? void 0 : onOpen());
848
+ break;
849
+ case "closing":
850
+ await (onClosing == null ? void 0 : onClosing());
851
+ setState("closed");
852
+ break;
853
+ }
854
+ }
855
+ handle().catch((error) => {
856
+ console.error("Internal sheet state error:", error);
857
+ });
858
+ }, [state]);
859
+ return state;
860
+ }
861
+ function useVirtualKeyboard({
862
+ containerRef,
863
+ isEnabled = true,
864
+ debounceDelay = 100,
865
+ includeContentEditable = true,
866
+ visualViewportThreshold = 100
867
+ }) {
868
+ const [state, setState] = useState({
869
+ isVisible: false,
870
+ height: 0
871
+ });
872
+ const focusedElementRef = useRef(null);
873
+ const debounceTimer = useRef(null);
874
+ const isTextInput = useStableCallback((el) => {
875
+ return (el == null ? void 0 : el.tagName) === "INPUT" || (el == null ? void 0 : el.tagName) === "TEXTAREA" || includeContentEditable && el instanceof HTMLElement && el.isContentEditable;
876
+ });
877
+ useEffect(() => {
878
+ if (!isEnabled) return;
879
+ const vv = window.visualViewport;
880
+ const vk = navigator.virtualKeyboard;
881
+ function setKeyboardInsetHeightEnv(height) {
882
+ var _a;
883
+ (_a = containerRef.current) == null ? void 0 : _a.style.setProperty(
884
+ "--keyboard-inset-height",
885
+ `${height}px`
886
+ );
887
+ }
888
+ function handleFocusIn(e) {
889
+ if (e.target instanceof HTMLElement && isTextInput(e.target)) {
890
+ focusedElementRef.current = e.target;
891
+ updateKeyboardState();
892
+ }
893
+ }
894
+ function handleFocusOut() {
895
+ focusedElementRef.current = null;
896
+ updateKeyboardState();
897
+ }
898
+ function updateKeyboardState() {
899
+ if (debounceTimer.current) {
900
+ clearTimeout(debounceTimer.current);
901
+ }
902
+ debounceTimer.current = setTimeout(() => {
903
+ const active = focusedElementRef.current;
904
+ const inputIsFocused = isTextInput(active);
905
+ if (!inputIsFocused) {
906
+ setKeyboardInsetHeightEnv(0);
907
+ setState({ isVisible: false, height: 0 });
908
+ return;
909
+ }
910
+ if (vv) {
911
+ const heightDiff = window.innerHeight - vv.height;
912
+ if (heightDiff > visualViewportThreshold) {
913
+ setKeyboardInsetHeightEnv(heightDiff);
914
+ setState({ isVisible: true, height: heightDiff });
915
+ } else {
916
+ setKeyboardInsetHeightEnv(0);
917
+ setState({ isVisible: false, height: 0 });
918
+ }
919
+ }
920
+ }, debounceDelay);
921
+ }
922
+ window.addEventListener("focusin", handleFocusIn);
923
+ window.addEventListener("focusout", handleFocusOut);
924
+ if (vv) {
925
+ vv.addEventListener("resize", updateKeyboardState);
926
+ vv.addEventListener("scroll", updateKeyboardState);
927
+ }
928
+ let currentOverlaysContent = false;
929
+ if (vk) {
930
+ currentOverlaysContent = vk.overlaysContent;
931
+ vk.overlaysContent = true;
932
+ }
933
+ return () => {
934
+ window.removeEventListener("focusin", handleFocusIn);
935
+ window.removeEventListener("focusout", handleFocusOut);
936
+ if (vv) {
937
+ vv.removeEventListener("resize", updateKeyboardState);
938
+ vv.removeEventListener("scroll", updateKeyboardState);
939
+ }
940
+ if (vk) {
941
+ vk.overlaysContent = currentOverlaysContent;
942
+ }
943
+ if (debounceTimer.current) {
944
+ clearTimeout(debounceTimer.current);
945
+ }
946
+ };
947
+ }, [
948
+ debounceDelay,
949
+ includeContentEditable,
950
+ isEnabled,
951
+ visualViewportThreshold
952
+ ]);
953
+ return {
954
+ keyboardHeight: state.height,
955
+ isKeyboardOpen: state.isVisible
956
+ };
957
+ }
958
+
959
+ // src/snap.ts
960
+ function computeSnapPoints({
961
+ snapPointsProp,
962
+ sheetHeight
963
+ }) {
964
+ if (sheetHeight <= 0) {
965
+ console.warn(
966
+ `Sheet height is ${sheetHeight}, cannot compute snap points. Make sure the sheet is mounted and has a valid height.`
967
+ );
968
+ return [];
969
+ }
970
+ const snapPointValues = snapPointsProp.map((point) => {
971
+ if (point > 0 && point <= 1) {
972
+ return Math.round(point * sheetHeight);
973
+ }
974
+ return point < 0 ? sheetHeight + point : point;
975
+ });
976
+ console.assert(
977
+ isAscendingOrder(snapPointValues),
978
+ `Snap points need to be in ascending order got: [${snapPointsProp.join(", ")}]`
979
+ );
980
+ snapPointValues.forEach((snap) => {
981
+ if (snap < 0 || snap > sheetHeight) {
982
+ console.warn(
983
+ `Snap point ${snap} is outside of the sheet height ${sheetHeight}. This can cause unexpected behavior. Consider adjusting your snap points.`
984
+ );
985
+ }
986
+ });
987
+ if (!snapPointValues.includes(sheetHeight)) {
988
+ console.warn(
989
+ "Snap points do not include the sheet height.Please include `1` as the last snap point or it will be included automatically.This is to ensure the sheet can be fully opened."
990
+ );
991
+ snapPointValues.push(sheetHeight);
992
+ }
993
+ return snapPointValues.map((snap, index) => ({
994
+ snapIndex: index,
995
+ snapValue: snap,
996
+ // Absolute value from the bottom of the sheet
997
+ snapValueY: sheetHeight - snap
998
+ // Y value is inverted as `y = 0` means sheet is at the top
999
+ }));
1000
+ }
1001
+ function findClosestSnapPoint({
1002
+ snapPoints,
1003
+ currentY
1004
+ }) {
1005
+ return snapPoints.reduce(
1006
+ (closest, snap) => Math.abs(snap.snapValueY - currentY) < Math.abs(closest.snapValueY - currentY) ? snap : closest
1007
+ );
1008
+ }
1009
+ function findNextSnapPointInDirection({
1010
+ y,
1011
+ snapPoints,
1012
+ dragDirection
1013
+ }) {
1014
+ if (dragDirection === "down") {
1015
+ return snapPoints.slice().reverse().find((s) => s.snapValueY > y);
1016
+ } else {
1017
+ return snapPoints.find((s) => s.snapValueY < y);
1018
+ }
1019
+ }
1020
+ function handleHighVelocityDrag({
1021
+ dragDirection,
1022
+ snapPoints
1023
+ }) {
1024
+ const bottomSnapPoint = snapPoints[0];
1025
+ const topSnapPoint = snapPoints[snapPoints.length - 1];
1026
+ if (dragDirection === "down") {
1027
+ return {
1028
+ yTo: bottomSnapPoint.snapValueY,
1029
+ snapIndex: bottomSnapPoint.snapIndex
1030
+ };
1031
+ }
1032
+ return {
1033
+ yTo: topSnapPoint.snapValueY,
1034
+ snapIndex: topSnapPoint.snapIndex
1035
+ };
1036
+ }
1037
+ function handleLowVelocityDrag({
1038
+ currentSnapPoint,
1039
+ currentY,
1040
+ dragDirection,
1041
+ snapPoints,
1042
+ velocity
1043
+ }) {
1044
+ const closestSnapRelativeToCurrentY = findClosestSnapPoint({
1045
+ snapPoints,
1046
+ currentY
1047
+ });
1048
+ if (Math.abs(velocity) < 20) {
1049
+ return {
1050
+ yTo: closestSnapRelativeToCurrentY.snapValueY,
1051
+ snapIndex: closestSnapRelativeToCurrentY.snapIndex
1052
+ };
1053
+ }
1054
+ const nextSnapInDirectionRelativeToCurrentY = findNextSnapPointInDirection({
1055
+ y: currentY,
1056
+ snapPoints,
1057
+ dragDirection
1058
+ });
1059
+ if (nextSnapInDirectionRelativeToCurrentY) {
1060
+ return {
1061
+ yTo: nextSnapInDirectionRelativeToCurrentY.snapValueY,
1062
+ snapIndex: nextSnapInDirectionRelativeToCurrentY.snapIndex
1063
+ };
1064
+ }
1065
+ return {
1066
+ yTo: currentSnapPoint.snapValueY,
1067
+ snapIndex: currentSnapPoint.snapIndex
1068
+ };
1069
+ }
1070
+
1071
+ // src/sheet.tsx
628
1072
  var Sheet = forwardRef(
629
1073
  ({
630
- onOpenStart,
631
- onOpenEnd,
632
- onClose,
633
- onCloseStart,
634
- onCloseEnd,
635
- onSnap,
1074
+ avoidKeyboard = true,
636
1075
  children,
1076
+ className = "",
1077
+ detent = "default",
1078
+ disableDismiss = false,
1079
+ disableDrag: disableDragProp = false,
637
1080
  disableScrollLocking = false,
1081
+ dragCloseThreshold = DEFAULT_DRAG_CLOSE_THRESHOLD,
1082
+ dragVelocityThreshold = DEFAULT_DRAG_VELOCITY_THRESHOLD,
1083
+ initialSnap,
638
1084
  isOpen,
639
- snapPoints: snapPointsProp,
640
- rootId,
641
1085
  modalEffectRootId,
1086
+ modalEffectThreshold,
642
1087
  mountPoint,
643
- style,
644
- detent = "full-height",
645
- initialSnap = 0,
646
- disableDrag = false,
647
1088
  prefersReducedMotion = false,
1089
+ snapPoints: snapPointsProp,
1090
+ style,
648
1091
  tweenConfig = DEFAULT_TWEEN_CONFIG,
649
- dragVelocityThreshold = DEFAULT_DRAG_VELOCITY_THRESHOLD,
650
- dragCloseThreshold = DEFAULT_DRAG_CLOSE_THRESHOLD,
651
- modalEffectThreshold,
1092
+ unstyled = false,
1093
+ onOpenStart,
1094
+ onOpenEnd,
1095
+ onClose,
1096
+ onCloseStart,
1097
+ onCloseEnd,
1098
+ onSnap,
1099
+ onDrag: onDragProp,
1100
+ onDragStart: onDragStartProp,
1101
+ onDragEnd: onDragEndProp,
652
1102
  ...rest
653
1103
  }, ref) => {
1104
+ const [sheetBoundsRef, sheetBounds] = useMeasure();
654
1105
  const sheetRef = useRef(null);
1106
+ const sheetHeight = Math.round(sheetBounds.height);
1107
+ const [currentSnap, setCurrentSnap] = useState(initialSnap);
1108
+ const snapPoints = snapPointsProp && sheetHeight > 0 ? computeSnapPoints({ sheetHeight, snapPointsProp }) : [];
1109
+ const { windowHeight } = useDimensions();
1110
+ const closedY = sheetHeight > 0 ? sheetHeight : windowHeight;
1111
+ const y = useMotionValue(closedY);
1112
+ const yInverted = useTransform(y, (val) => Math.max(sheetHeight - val, 0));
655
1113
  const indicatorRotation = useMotionValue(0);
656
- const { height: windowHeight } = useDimensions();
657
1114
  const shouldReduceMotion = useReducedMotion();
658
1115
  const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
659
1116
  const animationOptions = {
660
1117
  type: "tween",
661
1118
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
662
1119
  };
663
- const y = useMotionValue(windowHeight);
1120
+ const keyboard = useVirtualKeyboard({
1121
+ isEnabled: isOpen && avoidKeyboard,
1122
+ containerRef: sheetRef
1123
+ });
1124
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
664
1125
  const zIndex = useTransform(
665
1126
  y,
666
- (value) => value + 2 >= windowHeight ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
1127
+ (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
667
1128
  );
668
1129
  const visibility = useTransform(
669
1130
  y,
670
- (value) => value + 2 >= windowHeight ? "hidden" : "visible"
1131
+ (val) => val + 2 >= closedY ? "hidden" : "visible"
671
1132
  );
672
- const callbacks = useRef({
673
- onOpenStart,
674
- onOpenEnd,
675
- onCloseStart,
676
- onCloseEnd
677
- });
678
- useIsomorphicLayoutEffect(() => {
679
- callbacks.current = {
680
- onOpenStart,
681
- onOpenEnd,
682
- onCloseStart,
683
- onCloseEnd
684
- };
1133
+ const updateSnap = useStableCallback((snapIndex) => {
1134
+ setCurrentSnap(snapIndex);
1135
+ onSnap == null ? void 0 : onSnap(snapIndex);
685
1136
  });
686
- const onDrag = useStableCallback((_, info) => {
687
- const currentY = y.get();
688
- if (snapPointsProp) {
689
- const sheetHeight = getSheetHeight(sheetRef);
690
- const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
691
- const topSnapPoint = sheetHeight - snapPoints[0];
692
- if (info.delta.y < 0 && currentY <= topSnapPoint) {
693
- y.set(topSnapPoint);
694
- return;
1137
+ const getSnapPoint = useStableCallback((snapIndex) => {
1138
+ if (snapPointsProp && snapPoints) {
1139
+ if (snapIndex < 0 || snapIndex >= snapPoints.length) {
1140
+ console.warn(
1141
+ `Invalid snap index ${snapIndex}. Snap points are: [${snapPointsProp.join(", ")}] and their computed values are: [${snapPoints.map((point) => point.snapValue).join(", ")}]`
1142
+ );
1143
+ return null;
695
1144
  }
1145
+ return snapPoints[snapIndex];
696
1146
  }
697
- const velocity = y.getVelocity();
698
- if (velocity > 0) indicatorRotation.set(10);
699
- if (velocity < 0) indicatorRotation.set(-10);
700
- y.set(Math.max(currentY + info.delta.y, 0));
1147
+ return null;
701
1148
  });
702
- const onDragStart = useStableCallback(() => {
1149
+ const snapTo = useStableCallback(async (snapIndex) => {
1150
+ if (!snapPointsProp) {
1151
+ console.warn("Snapping is not possible without `snapPoints` prop.");
1152
+ return;
1153
+ }
1154
+ const snapPoint = getSnapPoint(snapIndex);
1155
+ if (snapPoint === null) {
1156
+ console.warn(`Invalid snap index ${snapIndex}.`);
1157
+ return;
1158
+ }
1159
+ if (snapIndex === 0) {
1160
+ onClose();
1161
+ return;
1162
+ }
1163
+ await animate(y, snapPoint.snapValueY, {
1164
+ ...animationOptions,
1165
+ onComplete: () => updateSnap(snapIndex)
1166
+ });
1167
+ });
1168
+ const blurActiveInput = useStableCallback(() => {
703
1169
  const focusedElement = document.activeElement;
704
1170
  if (!focusedElement || !sheetRef.current) return;
705
1171
  const isInput = focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA";
@@ -707,351 +1173,153 @@ var Sheet = forwardRef(
707
1173
  focusedElement.blur();
708
1174
  }
709
1175
  });
710
- const onDragEnd = useStableCallback((_, { velocity }) => {
711
- if (velocity.y > dragVelocityThreshold) {
712
- onClose();
713
- } else {
714
- const sheetHeight = getSheetHeight(sheetRef);
715
- const currentY = y.get();
716
- let yTo = 0;
717
- const snapPoints = snapPointsProp ? getSnapPoints({ snapPointsProp, sheetHeight }) : void 0;
718
- if (snapPoints) {
719
- const { snapY, snapIndex } = getClosestSnapPoint({
1176
+ const onDrag = useStableCallback((event, info) => {
1177
+ onDragProp == null ? void 0 : onDragProp(event, info);
1178
+ const currentY = y.get();
1179
+ const velocity = y.getVelocity();
1180
+ if (velocity > 0) indicatorRotation.set(10);
1181
+ if (velocity < 0) indicatorRotation.set(-10);
1182
+ y.set(Math.max(currentY + info.delta.y, 0));
1183
+ });
1184
+ const onDragStart = useStableCallback((event, info) => {
1185
+ blurActiveInput();
1186
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1187
+ });
1188
+ const onDragEnd = useStableCallback((event, info) => {
1189
+ blurActiveInput();
1190
+ onDragEndProp == null ? void 0 : onDragEndProp(event, info);
1191
+ const currentY = y.get();
1192
+ let yTo = 0;
1193
+ const currentSnapPoint = currentSnap !== void 0 ? getSnapPoint(currentSnap) : null;
1194
+ if (currentSnapPoint) {
1195
+ const dragOffsetDirection = info.offset.y > 0 ? "down" : "up";
1196
+ const dragVelocityDirection = info.velocity.y > 0 ? "down" : "up";
1197
+ const isHighVelocity = Math.abs(info.velocity.y) > dragVelocityThreshold;
1198
+ let result;
1199
+ if (isHighVelocity) {
1200
+ result = handleHighVelocityDrag({
720
1201
  snapPoints,
1202
+ dragDirection: dragVelocityDirection
1203
+ });
1204
+ } else {
1205
+ result = handleLowVelocityDrag({
1206
+ currentSnapPoint,
721
1207
  currentY,
722
- sheetHeight,
723
- detent
1208
+ dragDirection: dragOffsetDirection,
1209
+ snapPoints,
1210
+ velocity: info.velocity.y
724
1211
  });
725
- yTo = snapY;
726
- onSnap == null ? void 0 : onSnap(snapIndex);
727
- } else if (currentY / sheetHeight > dragCloseThreshold) {
728
- yTo = sheetHeight;
729
1212
  }
730
- animate(y, yTo, animationOptions);
731
- if (yTo + 1 >= sheetHeight) {
732
- onClose();
1213
+ yTo = result.yTo;
1214
+ if (disableDismiss && yTo + 1 >= sheetHeight) {
1215
+ const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1216
+ if (bottomSnapPoint) {
1217
+ yTo = bottomSnapPoint.snapValueY;
1218
+ updateSnap(bottomSnapPoint.snapIndex);
1219
+ } else {
1220
+ yTo = currentY;
1221
+ }
1222
+ } else if (result.snapIndex !== void 0) {
1223
+ updateSnap(result.snapIndex);
733
1224
  }
734
- }
735
- indicatorRotation.set(0);
736
- });
737
- useEffect(() => {
738
- if (snapPointsProp && onSnap) {
739
- onSnap(isOpen ? initialSnap : snapPointsProp.length - 1);
740
- }
741
- }, [isOpen]);
742
- useEffect(() => {
743
- if (!isOpen) return;
744
- let yTo = 0;
745
- if (snapPointsProp) {
746
- const snapIndex = initialSnap;
747
- const sheetHeight = getSheetHeight(sheetRef);
748
- const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
749
- const snapY = getSnapY({ sheetHeight, snapPoints, snapIndex });
750
- if (snapY !== null) {
751
- yTo = snapY;
1225
+ } else if (info.velocity.y > dragVelocityThreshold || currentY > sheetHeight * dragCloseThreshold) {
1226
+ if (disableDismiss) {
1227
+ yTo = 0;
1228
+ } else {
1229
+ yTo = closedY;
752
1230
  }
753
1231
  }
754
1232
  animate(y, yTo, animationOptions);
755
- }, [isOpen]);
1233
+ if (yTo + 1 >= sheetHeight && !disableDismiss) {
1234
+ onClose();
1235
+ }
1236
+ indicatorRotation.set(0);
1237
+ });
756
1238
  useImperativeHandle(ref, () => ({
757
1239
  y,
758
- snapTo: (snapIndex) => {
759
- if (!snapPointsProp) {
760
- console.warn("Snapping is not possible without `snapPoints` prop.");
761
- return;
762
- }
763
- const sheetHeight = getSheetHeight(sheetRef);
764
- const snapPoints = getSnapPoints({ snapPointsProp, sheetHeight });
765
- const snapY = getSnapY({ sheetHeight, snapPoints, snapIndex });
766
- if (snapY !== null) {
767
- animate(y, snapY, animationOptions);
768
- onSnap == null ? void 0 : onSnap(snapIndex);
769
- if (snapY + 1 >= sheetHeight) {
770
- onClose();
771
- }
772
- }
773
- }
1240
+ yInverted,
1241
+ height: sheetHeight,
1242
+ snapTo
774
1243
  }));
775
- if (rootId) {
776
- console.warn(
777
- "The `rootId` prop is deprecated and will be removed in the next major version. Use `modalEffectRootId` instead."
778
- );
779
- }
780
1244
  useModalEffect({
781
1245
  y,
782
- sheetRef,
783
- snapPointsProp,
784
- rootId: rootId || modalEffectRootId,
1246
+ detent,
1247
+ sheetHeight,
1248
+ snapPoints,
1249
+ rootId: modalEffectRootId,
785
1250
  startThreshold: modalEffectThreshold
786
1251
  });
787
1252
  usePreventScroll({
788
1253
  isDisabled: disableScrollLocking || !isOpen
789
1254
  });
790
- const dragProps = useMemo(() => {
791
- const dragProps2 = {
792
- drag: "y",
793
- dragElastic: 0,
794
- dragMomentum: false,
795
- dragPropagation: false,
796
- onDrag,
797
- onDragStart,
798
- onDragEnd
799
- };
800
- return disableDrag ? void 0 : dragProps2;
801
- }, [disableDrag, windowHeight]);
802
- const context = {
803
- y,
804
- sheetRef,
1255
+ const state = useSheetState({
805
1256
  isOpen,
806
- initialSnap,
1257
+ onOpen: async () => {
1258
+ onOpenStart == null ? void 0 : onOpenStart();
1259
+ const initialSnapPoint = initialSnap !== void 0 ? getSnapPoint(initialSnap) : null;
1260
+ const yTo = (initialSnapPoint == null ? void 0 : initialSnapPoint.snapValueY) ?? 0;
1261
+ await animate(y, yTo, animationOptions);
1262
+ if (initialSnap !== void 0) {
1263
+ updateSnap(initialSnap);
1264
+ }
1265
+ onOpenEnd == null ? void 0 : onOpenEnd();
1266
+ },
1267
+ onClosing: async () => {
1268
+ onCloseStart == null ? void 0 : onCloseStart();
1269
+ await animate(y, closedY, animationOptions);
1270
+ onCloseEnd == null ? void 0 : onCloseEnd();
1271
+ }
1272
+ });
1273
+ const dragProps = {
1274
+ drag: "y",
1275
+ dragElastic: 0,
1276
+ dragMomentum: false,
1277
+ dragPropagation: false,
1278
+ onDrag,
1279
+ onDragStart,
1280
+ onDragEnd
1281
+ };
1282
+ const context = {
1283
+ currentSnap,
807
1284
  detent,
808
- indicatorRotation,
809
- callbacks,
1285
+ disableDrag,
810
1286
  dragProps,
811
- windowHeight,
812
- animationOptions,
813
- reduceMotion,
814
- disableDrag
1287
+ indicatorRotation,
1288
+ avoidKeyboard,
1289
+ sheetBoundsRef,
1290
+ sheetRef,
1291
+ unstyled,
1292
+ y
815
1293
  };
816
- const sheet = /* @__PURE__ */ React2.createElement(SheetContext.Provider, { value: context }, /* @__PURE__ */ React2.createElement(
1294
+ const sheet = /* @__PURE__ */ React4.createElement(SheetContext.Provider, { value: context }, /* @__PURE__ */ React4.createElement(
817
1295
  motion.div,
818
1296
  {
819
1297
  ...rest,
820
1298
  ref,
821
- style: { ...styles.wrapper, zIndex, visibility, ...style }
1299
+ "data-sheet-state": state,
1300
+ className: `react-modal-sheet-root ${className}`,
1301
+ style: {
1302
+ ...applyStyles(styles.root, unstyled),
1303
+ zIndex,
1304
+ visibility,
1305
+ ...style
1306
+ }
822
1307
  },
823
- /* @__PURE__ */ React2.createElement(AnimatePresence, null, isOpen ? /* @__PURE__ */ React2.createElement(SheetScrollerContextProvider, null, Children.map(
824
- children,
825
- (child, i) => cloneElement(child, { key: `sheet-child-${i}` })
826
- )) : null)
1308
+ state !== "closed" ? children : null
827
1309
  ));
828
1310
  if (IS_SSR) return sheet;
829
1311
  return createPortal(sheet, mountPoint ?? document.body);
830
1312
  }
831
1313
  );
832
1314
  Sheet.displayName = "Sheet";
833
- function useEventCallbacks(isOpen, callbacks) {
834
- const prevOpen = usePrevious(isOpen);
835
- const didOpen = useRef(false);
836
- const handleAnimationComplete = useCallback(() => {
837
- var _a, _b, _c, _d;
838
- if (!didOpen.current) {
839
- (_b = (_a = callbacks.current).onOpenEnd) == null ? void 0 : _b.call(_a);
840
- didOpen.current = true;
841
- } else {
842
- (_d = (_c = callbacks.current).onCloseEnd) == null ? void 0 : _d.call(_c);
843
- didOpen.current = false;
844
- }
845
- }, [isOpen, prevOpen]);
846
- useEffect(() => {
847
- var _a, _b, _c, _d;
848
- if (!prevOpen && isOpen) {
849
- (_b = (_a = callbacks.current).onOpenStart) == null ? void 0 : _b.call(_a);
850
- } else if (!isOpen && prevOpen) {
851
- (_d = (_c = callbacks.current).onCloseStart) == null ? void 0 : _d.call(_c);
852
- }
853
- }, [isOpen, prevOpen]);
854
- return { handleAnimationComplete };
855
- }
856
- function usePrevious(state) {
857
- const ref = useRef(void 0);
858
- useEffect(() => {
859
- ref.current = state;
860
- });
861
- return ref.current;
862
- }
863
-
864
- // src/SheetContainer.tsx
865
- var SheetContainer = forwardRef(
866
- ({ children, style, className = "", ...rest }, ref) => {
867
- const {
868
- y,
869
- isOpen,
870
- callbacks,
871
- sheetRef,
872
- windowHeight,
873
- detent,
874
- animationOptions
875
- } = useSheetContext();
876
- const { handleAnimationComplete } = useEventCallbacks(isOpen, callbacks);
877
- const height = detent === "full-height" ? MAX_HEIGHT : void 0;
878
- const maxHeight = detent === "content-height" ? MAX_HEIGHT : void 0;
879
- return /* @__PURE__ */ React2.createElement(
880
- motion.div,
881
- {
882
- ...rest,
883
- ref: mergeRefs([sheetRef, ref]),
884
- className: `react-modal-sheet-container ${className}`,
885
- style: { ...styles.container, height, maxHeight, ...style, y },
886
- initial: { y: windowHeight },
887
- exit: { y: windowHeight, transition: animationOptions },
888
- onAnimationComplete: handleAnimationComplete
889
- },
890
- children
891
- );
892
- }
893
- );
894
- SheetContainer.displayName = "SheetContainer";
895
- var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
896
- function useDragConstraints() {
897
- const constraintsRef = useRef(null);
898
- const onMeasureDragConstraints = useCallback(() => constraints, []);
899
- return { constraintsRef, onMeasureDragConstraints };
900
- }
901
-
902
- // src/SheetContent.tsx
903
- var SheetContent = forwardRef(
904
- ({ children, style, disableDrag, className = "", ...rest }, ref) => {
905
- const sheetContext = useSheetContext();
906
- const sheetScrollerContext = useSheetScrollerContext();
907
- const { constraintsRef, onMeasureDragConstraints } = useDragConstraints();
908
- const dragProps = disableDrag || sheetScrollerContext.disableDrag ? void 0 : sheetContext.dragProps;
909
- return /* @__PURE__ */ React2.createElement(
910
- motion.div,
911
- {
912
- ...rest,
913
- ref: mergeRefs([ref, constraintsRef]),
914
- className: `react-modal-sheet-content ${className}`,
915
- style: { ...styles.content, ...style },
916
- ...dragProps,
917
- dragConstraints: constraintsRef,
918
- onMeasureDragConstraints
919
- },
920
- children
921
- );
922
- }
923
- );
924
- SheetContent.displayName = "SheetContent";
925
- var SheetHeader = forwardRef(
926
- ({ children, style, disableDrag, ...rest }, ref) => {
927
- const { indicatorRotation, dragProps } = useSheetContext();
928
- const { constraintsRef, onMeasureDragConstraints } = useDragConstraints();
929
- const _dragProps = disableDrag ? void 0 : dragProps;
930
- const indicator1Transform = useTransform(
931
- indicatorRotation,
932
- (r) => `translateX(2px) rotate(${r}deg)`
933
- );
934
- const indicator2Transform = useTransform(
935
- indicatorRotation,
936
- (r) => `translateX(-2px) rotate(${ -1 * r}deg)`
937
- );
938
- return /* @__PURE__ */ React2.createElement(
939
- motion.div,
940
- {
941
- ...rest,
942
- ref: mergeRefs([ref, constraintsRef]),
943
- style: { ...styles.headerWrapper, ...style },
944
- ..._dragProps,
945
- dragConstraints: constraintsRef,
946
- onMeasureDragConstraints
947
- },
948
- children || /* @__PURE__ */ React2.createElement("div", { className: "react-modal-sheet-header", style: styles.header }, /* @__PURE__ */ React2.createElement(
949
- motion.span,
950
- {
951
- className: "react-modal-sheet-drag-indicator",
952
- style: { ...styles.indicator, transform: indicator1Transform }
953
- }
954
- ), /* @__PURE__ */ React2.createElement(
955
- motion.span,
956
- {
957
- className: "react-modal-sheet-drag-indicator",
958
- style: { ...styles.indicator, transform: indicator2Transform }
959
- }
960
- ))
961
- );
962
- }
963
- );
964
- SheetHeader.displayName = "SheetHeader";
965
- var isClickable = (props) => !!props.onClick || !!props.onTap;
966
- var SheetBackdrop = forwardRef(
967
- ({ style = {}, className = "", ...rest }, ref) => {
968
- const Comp = isClickable(rest) ? motion.button : motion.div;
969
- const pointerEvents = isClickable(rest) ? "auto" : "none";
970
- return /* @__PURE__ */ React2.createElement(
971
- Comp,
972
- {
973
- ...rest,
974
- ref,
975
- className: `react-modal-sheet-backdrop ${className}`,
976
- style: { ...styles.backdrop, ...style, pointerEvents },
977
- initial: { opacity: 0 },
978
- animate: { opacity: 1 },
979
- exit: { opacity: 0 }
980
- }
981
- );
982
- }
983
- );
984
- SheetBackdrop.displayName = "SheetBackdrop";
985
- var SheetScroller = forwardRef(
986
- ({
987
- autoPadding = true,
988
- disableScroll = false,
989
- draggableAt = "top",
990
- children,
991
- style: styleProp,
992
- className = "",
993
- ...rest
994
- }, ref) => {
995
- const { y } = useSheetContext();
996
- const { setDragEnabled, setDragDisabled } = useSheetScrollerContext();
997
- function determineDragState(element) {
998
- if (disableScroll) {
999
- setDragEnabled();
1000
- return;
1001
- }
1002
- const { scrollTop, scrollHeight, clientHeight } = element;
1003
- const isScrollable2 = scrollHeight > clientHeight;
1004
- if (!isScrollable2) return;
1005
- const isAtTop = scrollTop <= 0;
1006
- const isAtBottom = scrollHeight - scrollTop === clientHeight;
1007
- const shouldEnable = draggableAt === "top" && isAtTop || draggableAt === "bottom" && isAtBottom || draggableAt === "both" && (isAtTop || isAtBottom);
1008
- if (shouldEnable) {
1009
- setDragEnabled();
1010
- } else {
1011
- setDragDisabled();
1012
- }
1013
- }
1014
- function onScroll(event) {
1015
- determineDragState(event.currentTarget);
1016
- if (rest.onScroll) rest.onScroll(event);
1017
- }
1018
- function onTouchStart(event) {
1019
- determineDragState(event.currentTarget);
1020
- if (rest.onTouchStart) rest.onTouchStart(event);
1021
- }
1022
- const scrollProps = isTouchDevice() ? { onScroll, onTouchStart } : void 0;
1023
- const style = {
1024
- ...styles.scroller,
1025
- ...styleProp
1026
- };
1027
- if (autoPadding) {
1028
- style.paddingBottom = y;
1029
- }
1030
- if (disableScroll) {
1031
- style.overflowY = "hidden";
1032
- }
1033
- return /* @__PURE__ */ React2.createElement(
1034
- motion.div,
1035
- {
1036
- ...rest,
1037
- ref,
1038
- className: `react-modal-sheet-scroller ${className}`,
1039
- style,
1040
- ...scrollProps
1041
- },
1042
- children
1043
- );
1044
- }
1045
- );
1046
- SheetScroller.displayName = "SheetScroller";
1047
1315
 
1048
1316
  // src/index.tsx
1049
1317
  var Sheet2 = Object.assign(Sheet, {
1050
1318
  Container: SheetContainer,
1051
1319
  Header: SheetHeader,
1320
+ DragIndicator: SheetDragIndicator,
1052
1321
  Content: SheetContent,
1053
- Backdrop: SheetBackdrop,
1054
- Scroller: SheetScroller
1322
+ Backdrop: SheetBackdrop
1055
1323
  });
1056
1324
 
1057
1325
  export { Sheet2 as Sheet };