react-modal-sheet 5.4.1 → 5.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,122 +1,22 @@
1
- import { motion, useMotionValue, useTransform, useReducedMotion, animate } from 'motion/react';
2
- import React4, { createContext, forwardRef, useRef, useState, useImperativeHandle, useContext, useCallback, useMemo, useEffect, useLayoutEffect } from 'react';
1
+ import React, { createContext, forwardRef, useRef, useState, useMemo, useImperativeHandle, useEffect, useLayoutEffect, useCallback, useContext } from 'react';
2
+ import { useMotionValue, useTransform, useReducedMotion, motion, animate } from 'motion/react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import useMeasure from 'react-use-measure';
5
5
  import { transform } from 'motion';
6
6
 
7
- // src/SheetBackdrop.tsx
8
- var SheetContext = createContext(
9
- void 0
10
- );
11
- function useSheetContext() {
12
- const context = useContext(SheetContext);
7
+ // src/context.tsx
8
+ var InternalSheetContext = createContext(void 0);
9
+ var ExposedSheetContext = createContext(void 0);
10
+ function useInternalContext() {
11
+ const context = useContext(InternalSheetContext);
12
+ if (!context) throw new Error("Sheet context error");
13
+ return context;
14
+ }
15
+ function useExposedContext() {
16
+ const context = useContext(ExposedSheetContext);
13
17
  if (!context) throw new Error("Sheet context error");
14
18
  return context;
15
19
  }
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
- };
120
20
 
121
21
  // src/constants.ts
122
22
  var DEFAULT_HEIGHT = "calc(100% - env(safe-area-inset-top) - 34px)";
@@ -130,7 +30,30 @@ var REDUCED_MOTION_TWEEN_CONFIG = {
130
30
  duration: 0.01
131
31
  };
132
32
  var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
133
- var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
33
+ var DEFAULT_DRAG_VELOCITY_THRESHOLD = 1200;
34
+ var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
35
+
36
+ // src/hooks/use-dimensions.ts
37
+ function useDimensions() {
38
+ const [dimensions, setDimensions] = useState(() => ({
39
+ windowHeight: !IS_SSR ? window.innerHeight : 0,
40
+ windowWidth: !IS_SSR ? window.innerWidth : 0
41
+ }));
42
+ useIsomorphicLayoutEffect(() => {
43
+ function handler() {
44
+ setDimensions({
45
+ windowHeight: window.innerHeight,
46
+ windowWidth: window.innerWidth
47
+ });
48
+ }
49
+ handler();
50
+ window.addEventListener("resize", handler);
51
+ return () => {
52
+ window.removeEventListener("resize", handler);
53
+ };
54
+ }, []);
55
+ return dimensions;
56
+ }
134
57
 
135
58
  // src/utils.ts
136
59
  function applyStyles(styles2, unstyled) {
@@ -213,86 +136,9 @@ function willOpenKeyboard(target) {
213
136
  function isHTTPS() {
214
137
  return typeof window !== "undefined" && window.isSecureContext;
215
138
  }
216
-
217
- // src/SheetBackdrop.tsx
218
- var isClickable = (props) => !!props.onClick || !!props.onTap;
219
- var SheetBackdrop = forwardRef(
220
- ({ style, className = "", unstyled, ...rest }, ref) => {
221
- const sheetContext = useSheetContext();
222
- const clickable = isClickable(rest);
223
- const Comp = clickable ? motion.button : motion.div;
224
- const pointerEvents = clickable ? "auto" : "none";
225
- const isUnstyled = unstyled ?? sheetContext.unstyled;
226
- const backdropStyle = {
227
- ...applyStyles(styles.backdrop, isUnstyled),
228
- ...style,
229
- pointerEvents
230
- };
231
- const animationProps = sheetContext.prefersReducedMotion ? {} : {
232
- initial: { opacity: 0 },
233
- animate: { opacity: 1 },
234
- exit: { opacity: 0 },
235
- transition: { duration: 1 }
236
- };
237
- return /* @__PURE__ */ React4.createElement(
238
- Comp,
239
- {
240
- ...animationProps,
241
- ...rest,
242
- ref,
243
- className: `react-modal-sheet-backdrop ${className}`,
244
- style: backdropStyle
245
- }
246
- );
247
- }
248
- );
249
- SheetBackdrop.displayName = "SheetBackdrop";
250
- var SheetContainer = forwardRef(
251
- ({ children, style, className = "", unstyled, ...rest }, ref) => {
252
- const sheetContext = useSheetContext();
253
- const isUnstyled = unstyled ?? sheetContext.unstyled;
254
- const containerStyle = {
255
- ...applyStyles(styles.container, isUnstyled),
256
- ...style,
257
- y: sheetContext.y
258
- };
259
- if (sheetContext.detent === "default") {
260
- containerStyle.height = DEFAULT_HEIGHT;
261
- }
262
- if (sheetContext.detent === "full") {
263
- containerStyle.height = "100%";
264
- containerStyle.maxHeight = "100%";
265
- }
266
- if (sheetContext.detent === "content") {
267
- containerStyle.height = "auto";
268
- containerStyle.maxHeight = DEFAULT_HEIGHT;
269
- }
270
- return /* @__PURE__ */ React4.createElement(
271
- motion.div,
272
- {
273
- ...rest,
274
- ref: mergeRefs([
275
- ref,
276
- sheetContext.sheetRef,
277
- sheetContext.sheetBoundsRef
278
- ]),
279
- className: `react-modal-sheet-container ${className}`,
280
- style: containerStyle
281
- },
282
- children
283
- );
284
- }
285
- );
286
- SheetContainer.displayName = "SheetContainer";
287
- var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
288
- function useDragConstraints() {
289
- const ref = useRef(null);
290
- const onMeasure = useCallback(() => constraints, []);
291
- return { ref, onMeasure };
139
+ function clamp(value, min, max) {
140
+ return Math.min(Math.max(value, min), max);
292
141
  }
293
- var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
294
-
295
- // src/hooks/use-stable-callback.ts
296
142
  function useStableCallback(handler) {
297
143
  const handlerRef = useRef(void 0);
298
144
  useIsomorphicLayoutEffect(() => {
@@ -303,272 +149,39 @@ function useStableCallback(handler) {
303
149
  return fn == null ? void 0 : fn(...args);
304
150
  }, []);
305
151
  }
306
-
307
- // src/hooks/use-scroll-position.ts
308
- function useScrollPosition(options = {}) {
309
- const { debounceDelay = 32, isEnabled = true } = options;
310
- const scrollTimeoutRef = useRef(null);
311
- const [element, setElement] = useState(null);
312
- const [scrollPosition, setScrollPosition] = useState(void 0);
313
- const scrollRef = useMemo(
314
- () => (element2) => {
315
- setElement(element2);
316
- },
317
- []
318
- );
319
- const determineScrollPosition = useStableCallback((element2) => {
320
- const { scrollTop, scrollHeight, clientHeight } = element2;
321
- const isScrollable2 = scrollHeight > clientHeight;
322
- if (!isScrollable2) {
323
- if (scrollPosition) setScrollPosition(void 0);
324
- return;
152
+ var virtualKeyboardOverlayUsers = 0;
153
+ var initialVirtualKeyboardOverlaysContent = null;
154
+ function useVirtualKeyboard(options = {}) {
155
+ const {
156
+ containerRef,
157
+ isEnabled = true,
158
+ debounceDelay = 100,
159
+ visualViewportThreshold = 100
160
+ } = options;
161
+ const [state, setState] = useState({
162
+ isVisible: false,
163
+ height: 0
164
+ });
165
+ const focusedElementRef = useRef(null);
166
+ const debounceTimer = useRef(null);
167
+ useEffect(() => {
168
+ const vv = window.visualViewport;
169
+ const vk = getVirtualKeyboardApi();
170
+ function setKeyboardInsetHeightEnv(height) {
171
+ const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
172
+ if (vk) {
173
+ element.style.setProperty(
174
+ "--keyboard-inset-height",
175
+ `env(keyboard-inset-height, ${height}px)`
176
+ );
177
+ } else {
178
+ element.style.setProperty("--keyboard-inset-height", `${height}px`);
179
+ }
325
180
  }
326
- const isAtTop = scrollTop <= 0;
327
- const isAtBottom = Math.ceil(scrollHeight) - Math.ceil(scrollTop) === Math.ceil(clientHeight);
328
- let position;
329
- if (isAtTop) {
330
- position = "top";
331
- } else if (isAtBottom) {
332
- position = "bottom";
333
- } else {
334
- position = "middle";
335
- }
336
- element2.style.touchAction = isAtTop ? "pan-down" : "";
337
- if (position === scrollPosition) return;
338
- setScrollPosition(position);
339
- });
340
- const onScroll = useStableCallback((event) => {
341
- if (event.currentTarget instanceof HTMLElement) {
342
- const el = event.currentTarget;
343
- if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
344
- if (debounceDelay === 0) {
345
- determineScrollPosition(el);
346
- } else {
347
- scrollTimeoutRef.current = setTimeout(
348
- () => determineScrollPosition(el),
349
- debounceDelay
350
- );
351
- }
352
- }
353
- });
354
- const onTouchStart = useStableCallback((event) => {
355
- if (event.currentTarget instanceof HTMLElement) {
356
- const element2 = event.currentTarget;
357
- requestAnimationFrame(() => {
358
- determineScrollPosition(element2);
359
- });
360
- }
361
- });
362
- useEffect(() => {
363
- if (!element || !isEnabled) return;
364
- determineScrollPosition(element);
365
- element.addEventListener("scroll", onScroll);
366
- element.addEventListener("touchstart", onTouchStart);
367
- return () => {
368
- if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
369
- element.removeEventListener("scroll", onScroll);
370
- element.removeEventListener("touchstart", onTouchStart);
371
- };
372
- }, [element, isEnabled]);
373
- return {
374
- scrollRef,
375
- scrollPosition
376
- };
377
- }
378
-
379
- // src/SheetContent.tsx
380
- var SheetContent = forwardRef(
381
- ({
382
- disableScroll: disableScrollProp,
383
- disableDrag: disableDragProp,
384
- children,
385
- style: styleProp,
386
- className = "",
387
- scrollClassName = "",
388
- scrollStyle: scrollStyleProp,
389
- scrollRef: scrollRefProp = null,
390
- unstyled,
391
- ...rest
392
- }, ref) => {
393
- const sheetContext = useSheetContext();
394
- const dragConstraints = useDragConstraints();
395
- const scroll = useScrollPosition({
396
- isEnabled: typeof disableScrollProp === "function" ? true : !disableScrollProp
397
- });
398
- const disableScroll = typeof disableScrollProp === "function" ? disableScrollProp({
399
- scrollPosition: scroll.scrollPosition,
400
- currentSnap: sheetContext.currentSnap
401
- }) : Boolean(disableScrollProp);
402
- const disableDragDueToScroll = !disableScroll && scroll.scrollPosition && scroll.scrollPosition !== "top";
403
- const disableDragDueToProp = typeof disableDragProp === "function" ? disableDragProp({
404
- scrollPosition: scroll.scrollPosition,
405
- currentSnap: sheetContext.currentSnap
406
- }) : Boolean(disableDragProp);
407
- const disableDrag = disableDragDueToProp || disableDragDueToScroll || sheetContext.disableDrag;
408
- const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
409
- const isUnstyled = unstyled ?? sheetContext.unstyled;
410
- const contentStyle = {
411
- ...applyStyles(styles.content, isUnstyled),
412
- ...styleProp
413
- };
414
- const scrollStyle = applyStyles(styles.scroller, isUnstyled);
415
- if (sheetContext.avoidKeyboard) {
416
- scrollStyle.paddingBottom = isHTTPS() ? "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))" : "var(--keyboard-inset-height, 0px)";
417
- }
418
- if (disableScroll) {
419
- scrollStyle.overflowY = "hidden";
420
- }
421
- if (dragProps !== void 0) {
422
- scrollStyle.touchAction = "pan-down";
423
- }
424
- return /* @__PURE__ */ React4.createElement(
425
- motion.div,
426
- {
427
- ...rest,
428
- ref: mergeRefs([ref, dragConstraints.ref]),
429
- className: `react-modal-sheet-content ${className}`,
430
- style: contentStyle,
431
- ...dragProps,
432
- dragConstraints: dragConstraints.ref,
433
- onMeasureDragConstraints: dragConstraints.onMeasure
434
- },
435
- /* @__PURE__ */ React4.createElement(
436
- motion.div,
437
- {
438
- ref: mergeRefs([scroll.scrollRef, scrollRefProp]),
439
- style: { ...scrollStyle, ...scrollStyleProp },
440
- className: `react-modal-sheet-content-scroller ${scrollClassName}`
441
- },
442
- children
443
- )
444
- );
445
- }
446
- );
447
- SheetContent.displayName = "SheetContent";
448
- function SheetDragIndicator({
449
- style,
450
- className = "",
451
- unstyled,
452
- ...rest
453
- }) {
454
- const sheetContext = useSheetContext();
455
- const indicator1Transform = useTransform(
456
- sheetContext.indicatorRotation,
457
- (r) => `translateX(2px) rotate(${r}deg)`
458
- );
459
- const indicator2Transform = useTransform(
460
- sheetContext.indicatorRotation,
461
- (r) => `translateX(-2px) rotate(${ -1 * r}deg)`
462
- );
463
- const isUnstyled = unstyled ?? sheetContext.unstyled;
464
- const indicatorWrapperStyle = {
465
- ...applyStyles(styles.indicatorWrapper, isUnstyled),
466
- ...style
467
- };
468
- const indicatorStyle = applyStyles(styles.indicator, isUnstyled);
469
- return /* @__PURE__ */ React4.createElement(
470
- "div",
471
- {
472
- className: `react-modal-sheet-drag-indicator-container ${className}`,
473
- style: indicatorWrapperStyle,
474
- ...rest
475
- },
476
- /* @__PURE__ */ React4.createElement(
477
- motion.span,
478
- {
479
- className: "react-modal-sheet-drag-indicator",
480
- style: { ...indicatorStyle, transform: indicator1Transform }
481
- }
482
- ),
483
- /* @__PURE__ */ React4.createElement(
484
- motion.span,
485
- {
486
- className: "react-modal-sheet-drag-indicator",
487
- style: { ...indicatorStyle, transform: indicator2Transform }
488
- }
489
- )
490
- );
491
- }
492
- var SheetHeader = forwardRef(
493
- ({ children, style, disableDrag, unstyled, className = "", ...rest }, ref) => {
494
- const sheetContext = useSheetContext();
495
- const dragConstraints = useDragConstraints();
496
- const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
497
- const isUnstyled = unstyled ?? sheetContext.unstyled;
498
- const headerWrapperStyle = {
499
- ...applyStyles(styles.headerWrapper, isUnstyled),
500
- ...style
501
- };
502
- const headerStyle = applyStyles(styles.header, isUnstyled);
503
- return /* @__PURE__ */ React4.createElement(
504
- motion.div,
505
- {
506
- ...rest,
507
- ref: mergeRefs([ref, dragConstraints.ref]),
508
- style: headerWrapperStyle,
509
- className: `react-modal-sheet-header-container ${className}`,
510
- ...dragProps,
511
- dragConstraints: dragConstraints.ref,
512
- onMeasureDragConstraints: dragConstraints.onMeasure
513
- },
514
- children || /* @__PURE__ */ React4.createElement("div", { className: "react-modal-sheet-header", style: headerStyle }, /* @__PURE__ */ React4.createElement(SheetDragIndicator, null))
515
- );
516
- }
517
- );
518
- SheetHeader.displayName = "SheetHeader";
519
- function useDimensions() {
520
- const [dimensions, setDimensions] = useState(() => ({
521
- windowHeight: !IS_SSR ? window.innerHeight : 0,
522
- windowWidth: !IS_SSR ? window.innerWidth : 0
523
- }));
524
- useIsomorphicLayoutEffect(() => {
525
- function handler() {
526
- setDimensions({
527
- windowHeight: window.innerHeight,
528
- windowWidth: window.innerWidth
529
- });
530
- }
531
- handler();
532
- window.addEventListener("resize", handler);
533
- return () => {
534
- window.removeEventListener("resize", handler);
535
- };
536
- }, []);
537
- return dimensions;
538
- }
539
- var virtualKeyboardOverlayUsers = 0;
540
- var initialVirtualKeyboardOverlaysContent = null;
541
- function useVirtualKeyboard(options = {}) {
542
- const {
543
- containerRef,
544
- isEnabled = true,
545
- debounceDelay = 100,
546
- visualViewportThreshold = 100
547
- } = options;
548
- const [state, setState] = useState({
549
- isVisible: false,
550
- height: 0
551
- });
552
- const focusedElementRef = useRef(null);
553
- const debounceTimer = useRef(null);
554
- useEffect(() => {
555
- const vv = window.visualViewport;
556
- const vk = getVirtualKeyboardApi();
557
- function setKeyboardInsetHeightEnv(height) {
558
- const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
559
- if (vk) {
560
- element.style.setProperty(
561
- "--keyboard-inset-height",
562
- `env(keyboard-inset-height, ${height}px)`
563
- );
564
- } else {
565
- element.style.setProperty("--keyboard-inset-height", `${height}px`);
566
- }
567
- }
568
- function setKeyboardState(nextState) {
569
- setState(
570
- (prevState) => prevState.isVisible === nextState.isVisible && prevState.height === nextState.height ? prevState : nextState
571
- );
181
+ function setKeyboardState(nextState) {
182
+ setState(
183
+ (prevState) => prevState.isVisible === nextState.isVisible && prevState.height === nextState.height ? prevState : nextState
184
+ );
572
185
  }
573
186
  function resetKeyboardState() {
574
187
  focusedElementRef.current = null;
@@ -768,77 +381,82 @@ function useModalEffect({
768
381
  console.warn('Using "full" detent with modal effect is not supported.');
769
382
  rootId = void 0;
770
383
  }
771
- useIsomorphicLayoutEffect(() => {
772
- return () => {
773
- if (rootId) cleanupModalEffect(rootId);
774
- };
775
- }, []);
776
- useIsomorphicLayoutEffect(() => {
384
+ const handleYChange = useStableCallback((yValue) => {
777
385
  if (!rootId) return;
778
- const root = document.querySelector(`#${rootId}`);
779
- if (!root) return;
780
- const removeStartListener = y.on("animationStart", () => {
781
- setupModalEffect(rootId);
782
- });
783
- const removeChangeListener = y.on("change", (yValue) => {
784
- if (!root) return;
785
- let progress = Math.max(0, 1 - yValue / sheetHeight);
786
- const snapThresholdPoint = snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : void 0;
787
- if (snapThresholdPoint !== void 0) {
788
- const snapThresholdValue = snapThresholdPoint.snapValueY;
789
- if (yValue <= snapThresholdValue) {
790
- progress = (snapThresholdValue - yValue) / snapThresholdValue;
791
- } else {
792
- progress = 0;
793
- }
386
+ let progress = Math.max(0, 1 - yValue / sheetHeight);
387
+ const snapThresholdPoint = snapPoints.length > 1 ? snapPoints[snapPoints.length - 2] : void 0;
388
+ if (snapThresholdPoint !== void 0) {
389
+ const snapThresholdValue = snapThresholdPoint.snapValueY;
390
+ if (yValue <= snapThresholdValue) {
391
+ progress = (snapThresholdValue - yValue) / snapThresholdValue;
392
+ } else {
393
+ progress = 0;
794
394
  }
795
- if (startThreshold !== void 0) {
796
- const startThresholdValue = sheetHeight - Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
797
- if (yValue <= startThresholdValue) {
798
- progress = (startThresholdValue - yValue) / startThresholdValue;
799
- } else {
800
- progress = 0;
801
- }
395
+ }
396
+ if (startThreshold !== void 0) {
397
+ const startThresholdValue = sheetHeight - Math.min(Math.floor(startThreshold * sheetHeight), sheetHeight);
398
+ if (yValue <= startThresholdValue) {
399
+ progress = (startThresholdValue - yValue) / startThresholdValue;
400
+ } else {
401
+ progress = 0;
802
402
  }
803
- progress = Math.max(0, Math.min(1, progress));
804
- const pageWidth = window.innerWidth;
805
- const ty = transform(progress, [0, 1], [0, 24 + insetTop]);
806
- const s = transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]);
807
- const borderRadius = transform(progress, [0, 1], [0, 10]);
808
- root.style.transform = `scale(${s}) translate3d(0, ${ty}px, 0)`;
809
- root.style.borderTopRightRadius = `${borderRadius}px`;
810
- root.style.borderTopLeftRadius = `${borderRadius}px`;
403
+ }
404
+ progress = Math.max(0, Math.min(1, progress));
405
+ const pageWidth = window.innerWidth;
406
+ updateModalEffect(rootId, {
407
+ scale: transform(progress, [0, 1], [1, (pageWidth - 16) / pageWidth]),
408
+ translateY: transform(progress, [0, 1], [0, 24 + insetTop]),
409
+ borderRadius: transform(progress, [0, 1], [0, 10])
811
410
  });
812
- function onCompleted() {
813
- if (y.get() - 5 >= sheetHeight) {
814
- cleanupModalEffect(rootId);
815
- }
411
+ });
412
+ const handleYComplete = useStableCallback(() => {
413
+ if (!rootId) return;
414
+ if (y.get() + 5 >= sheetHeight) {
415
+ cleanupModalEffect(rootId);
816
416
  }
817
- const removeCompleteListener = y.on("animationComplete", onCompleted);
818
- const removeCancelListener = y.on("animationCancel", onCompleted);
417
+ });
418
+ useEffect(() => {
419
+ if (!rootId) return;
420
+ const removeStartListener = y.on("animationStart", () => {
421
+ if (rootId) setupModalEffect(rootId);
422
+ });
423
+ const removeChangeListener = y.on("change", (yValue) => {
424
+ handleYChange(yValue);
425
+ });
426
+ const removeCompleteListener = y.on("animationComplete", handleYComplete);
427
+ const removeCancelListener = y.on("animationCancel", handleYComplete);
819
428
  return () => {
429
+ if (rootId) cleanupModalEffect(rootId);
820
430
  removeStartListener();
821
431
  removeChangeListener();
822
432
  removeCompleteListener();
823
433
  removeCancelListener();
824
434
  };
825
- }, [y, rootId, insetTop, startThreshold, sheetHeight]);
435
+ }, [rootId]);
826
436
  }
827
437
  function setupModalEffect(rootId) {
828
438
  const root = document.querySelector(`#${rootId}`);
829
- const body = document.querySelector("body");
830
439
  if (!root) return;
831
- body.style.backgroundColor = "#000";
440
+ const body = document.querySelector("body");
441
+ body.style.backgroundColor = "#000000";
832
442
  root.style.overflow = "hidden";
833
443
  root.style.transitionTimingFunction = "cubic-bezier(0.32, 0.72, 0, 1)";
834
444
  root.style.transitionProperty = "transform, border-radius";
835
445
  root.style.transitionDuration = "0.5s";
836
446
  root.style.transformOrigin = "center top";
837
447
  }
838
- function cleanupModalEffect(rootId) {
448
+ function updateModalEffect(rootId, opts) {
839
449
  const root = document.querySelector(`#${rootId}`);
840
- const body = document.querySelector("body");
841
450
  if (!root) return;
451
+ const { scale, translateY, borderRadius } = opts;
452
+ root.style.transform = `scale(${scale}) translate3d(0, ${translateY}px, 0)`;
453
+ root.style.borderTopRightRadius = `${borderRadius}px`;
454
+ root.style.borderTopLeftRadius = `${borderRadius}px`;
455
+ }
456
+ function cleanupModalEffect(rootId) {
457
+ const root = document.querySelector(`#${rootId}`);
458
+ if (!root) return;
459
+ const body = document.querySelector("body");
842
460
  body.style.removeProperty("background-color");
843
461
  root.style.removeProperty("overflow");
844
462
  root.style.removeProperty("transition-timing-function");
@@ -1150,76 +768,249 @@ function computeSnapPoints({
1150
768
  // Y value is inverted as `y = 0` means sheet is at the top
1151
769
  }));
1152
770
  }
1153
- function findClosestSnapPoint({
1154
- snapPoints,
1155
- currentY
1156
- }) {
771
+ function sign(value) {
772
+ if (value > 0) return 1;
773
+ if (value < 0) return -1;
774
+ return 0;
775
+ }
776
+ function toAscendingYSnaps(snapPoints) {
777
+ return snapPoints.slice().sort((a, b) => a.snapValueY - b.snapValueY);
778
+ }
779
+ function findNearestSnapByY(snapPoints, y) {
1157
780
  return snapPoints.reduce(
1158
- (closest, snap) => Math.abs(snap.snapValueY - currentY) < Math.abs(closest.snapValueY - currentY) ? snap : closest
781
+ (closest, snap) => Math.abs(snap.snapValueY - y) < Math.abs(closest.snapValueY - y) ? snap : closest
1159
782
  );
1160
783
  }
1161
- function findNextSnapPointInDirection({
784
+ function findNearestSnapAboveY(snapPoints, y) {
785
+ const snaps = toAscendingYSnaps(snapPoints);
786
+ for (let i = snaps.length - 1; i >= 0; i--) {
787
+ if (snaps[i].snapValueY < y) {
788
+ return snaps[i];
789
+ }
790
+ }
791
+ return null;
792
+ }
793
+ function findNearestSnapBelowY(snapPoints, y) {
794
+ const snaps = toAscendingYSnaps(snapPoints);
795
+ for (let i = 0; i < snaps.length; i++) {
796
+ if (snaps[i].snapValueY > y) {
797
+ return snaps[i];
798
+ }
799
+ }
800
+ return null;
801
+ }
802
+ function resolveDirection({
803
+ offsetY,
804
+ velocityY,
805
+ deltaY
806
+ }) {
807
+ if (Math.abs(velocityY) > 50) return sign(velocityY);
808
+ const offsetDirection = sign(offsetY);
809
+ if (offsetDirection !== 0) return offsetDirection;
810
+ return sign(deltaY);
811
+ }
812
+ function classifyDragEnd({
1162
813
  y,
814
+ info,
815
+ sheetHeight,
1163
816
  snapPoints,
1164
- dragDirection
817
+ dragVelocityThreshold,
818
+ dragCloseThreshold
1165
819
  }) {
1166
- if (dragDirection === "down") {
1167
- return snapPoints.slice().reverse().find((s) => s.snapValueY > y);
1168
- } else {
1169
- return snapPoints.find((s) => s.snapValueY < y);
820
+ const flickVelocity = dragVelocityThreshold;
821
+ const strongFlickVelocity = Math.max(2e3, flickVelocity);
822
+ const distanceCommitRatio = 0.35;
823
+ const predictionTime = 0.2;
824
+ const minDragDistance = 20;
825
+ const snapStickiness = 24;
826
+ const minY = 0;
827
+ const maxY = sheetHeight;
828
+ const offsetY = info.offset.y;
829
+ const deltaY = info.delta.y;
830
+ const velocityY = info.velocity.y;
831
+ const predictedY = clamp(y + velocityY * predictionTime, minY, maxY);
832
+ const absVelocity = Math.abs(velocityY);
833
+ const direction = resolveDirection({ offsetY, velocityY, deltaY });
834
+ const isStrongFlick = absVelocity > strongFlickVelocity;
835
+ const isFlick = absVelocity > flickVelocity;
836
+ if (snapPoints.length === 0) {
837
+ if (isFlick) {
838
+ return {
839
+ yTo: direction < 0 ? minY : maxY,
840
+ snapIndex: void 0
841
+ };
842
+ }
843
+ const midpoint = minY + (maxY - minY) * dragCloseThreshold;
844
+ return {
845
+ yTo: predictedY < midpoint ? minY : maxY,
846
+ snapIndex: void 0
847
+ };
1170
848
  }
1171
- }
1172
- function handleHighVelocityDrag({
1173
- dragDirection,
1174
- snapPoints
1175
- }) {
1176
- const bottomSnapPoint = snapPoints[0];
1177
- const topSnapPoint = snapPoints[snapPoints.length - 1];
1178
- if (dragDirection === "down") {
849
+ const nearestSnap = findNearestSnapByY(snapPoints, y);
850
+ if (!isFlick && Math.abs(offsetY) < minDragDistance) {
1179
851
  return {
1180
- yTo: bottomSnapPoint.snapValueY,
1181
- snapIndex: bottomSnapPoint.snapIndex
852
+ yTo: nearestSnap.snapValueY,
853
+ snapIndex: nearestSnap.snapIndex
1182
854
  };
1183
855
  }
1184
- return {
1185
- yTo: topSnapPoint.snapValueY,
1186
- snapIndex: topSnapPoint.snapIndex
1187
- };
1188
- }
1189
- function handleLowVelocityDrag({
1190
- currentSnapPoint,
1191
- currentY,
1192
- dragDirection,
1193
- snapPoints,
1194
- velocity
1195
- }) {
1196
- const closestSnapRelativeToCurrentY = findClosestSnapPoint({
1197
- snapPoints,
1198
- currentY
1199
- });
1200
- if (Math.abs(velocity) < 20) {
856
+ if (!isFlick && Math.abs(y - nearestSnap.snapValueY) < snapStickiness) {
1201
857
  return {
1202
- yTo: closestSnapRelativeToCurrentY.snapValueY,
1203
- snapIndex: closestSnapRelativeToCurrentY.snapIndex
858
+ yTo: nearestSnap.snapValueY,
859
+ snapIndex: nearestSnap.snapIndex
1204
860
  };
1205
861
  }
1206
- const nextSnapInDirectionRelativeToCurrentY = findNextSnapPointInDirection({
1207
- y: currentY,
1208
- snapPoints,
1209
- dragDirection
1210
- });
1211
- if (nextSnapInDirectionRelativeToCurrentY) {
862
+ if (isStrongFlick) {
863
+ const target = direction < 0 ? minY : maxY;
864
+ const targetSnap = findNearestSnapByY(snapPoints, target);
865
+ return {
866
+ yTo: target,
867
+ snapIndex: targetSnap.snapIndex
868
+ };
869
+ }
870
+ if (isFlick) {
871
+ const nextSnap2 = direction < 0 ? findNearestSnapAboveY(snapPoints, y) : findNearestSnapBelowY(snapPoints, y);
872
+ const fallbackY = direction < 0 ? minY : maxY;
873
+ const fallbackSnap = findNearestSnapByY(snapPoints, fallbackY);
874
+ const targetSnap = nextSnap2 ?? fallbackSnap;
1212
875
  return {
1213
- yTo: nextSnapInDirectionRelativeToCurrentY.snapValueY,
1214
- snapIndex: nextSnapInDirectionRelativeToCurrentY.snapIndex
876
+ yTo: targetSnap.snapValueY,
877
+ snapIndex: targetSnap.snapIndex
1215
878
  };
1216
879
  }
880
+ const firstSnap = toAscendingYSnaps(snapPoints)[0];
881
+ const lastSnap = toAscendingYSnaps(snapPoints)[snapPoints.length - 1];
882
+ const prevSnap = findNearestSnapAboveY(snapPoints, y) ?? firstSnap;
883
+ const nextSnap = findNearestSnapBelowY(snapPoints, y) ?? lastSnap;
884
+ if (prevSnap.snapIndex === nextSnap.snapIndex) {
885
+ return {
886
+ yTo: prevSnap.snapValueY,
887
+ snapIndex: prevSnap.snapIndex
888
+ };
889
+ }
890
+ const range = nextSnap.snapValueY - prevSnap.snapValueY;
891
+ const progress = range > 0 ? (y - prevSnap.snapValueY) / range : 0;
892
+ let selectedSnap = nearestSnap;
893
+ if (direction > 0) {
894
+ selectedSnap = progress > distanceCommitRatio ? nextSnap : prevSnap;
895
+ } else if (direction < 0) {
896
+ selectedSnap = progress < 1 - distanceCommitRatio ? prevSnap : nextSnap;
897
+ }
898
+ if (absVelocity > 200) {
899
+ const predictedSnap = findNearestSnapByY(snapPoints, predictedY);
900
+ if (Math.abs(predictedSnap.snapValueY - y) < Math.abs(selectedSnap.snapValueY - y)) {
901
+ selectedSnap = predictedSnap;
902
+ }
903
+ }
1217
904
  return {
1218
- yTo: currentSnapPoint.snapValueY,
1219
- snapIndex: currentSnapPoint.snapIndex
905
+ yTo: selectedSnap.snapValueY,
906
+ snapIndex: selectedSnap.snapIndex
1220
907
  };
1221
908
  }
1222
909
 
910
+ // src/styles.ts
911
+ var styles = {
912
+ root: {
913
+ base: {
914
+ position: "fixed",
915
+ top: 0,
916
+ bottom: 0,
917
+ left: 0,
918
+ right: 0,
919
+ overflow: "hidden",
920
+ pointerEvents: "none"
921
+ },
922
+ decorative: {}
923
+ },
924
+ backdrop: {
925
+ base: {
926
+ zIndex: 1,
927
+ position: "fixed",
928
+ top: 0,
929
+ left: 0,
930
+ width: "100%",
931
+ height: "100%",
932
+ touchAction: "none",
933
+ userSelect: "none"
934
+ },
935
+ decorative: {
936
+ backgroundColor: "rgba(0, 0, 0, 0.2)",
937
+ border: "none",
938
+ WebkitTapHighlightColor: "transparent"
939
+ }
940
+ },
941
+ container: {
942
+ base: {
943
+ zIndex: 2,
944
+ position: "absolute",
945
+ left: 0,
946
+ bottom: 0,
947
+ width: "100%",
948
+ pointerEvents: "auto",
949
+ display: "flex",
950
+ flexDirection: "column"
951
+ },
952
+ decorative: {
953
+ backgroundColor: "#fff",
954
+ borderTopRightRadius: "8px",
955
+ borderTopLeftRadius: "8px",
956
+ boxShadow: "0px -2px 16px rgba(0, 0, 0, 0.3)"
957
+ }
958
+ },
959
+ headerWrapper: {
960
+ base: {
961
+ width: "100%"
962
+ },
963
+ decorative: {}
964
+ },
965
+ header: {
966
+ base: {
967
+ width: "100%",
968
+ position: "relative"
969
+ },
970
+ decorative: {
971
+ height: "40px",
972
+ display: "flex",
973
+ alignItems: "center",
974
+ justifyContent: "center"
975
+ }
976
+ },
977
+ indicatorWrapper: {
978
+ base: {
979
+ display: "flex"
980
+ },
981
+ decorative: {}
982
+ },
983
+ indicator: {
984
+ base: {
985
+ display: "inline-block"
986
+ },
987
+ decorative: {
988
+ width: "18px",
989
+ height: "4px",
990
+ borderRadius: "99px",
991
+ backgroundColor: "#ddd"
992
+ }
993
+ },
994
+ content: {
995
+ base: {
996
+ minHeight: "0px",
997
+ position: "relative",
998
+ flexGrow: 1,
999
+ display: "flex",
1000
+ flexDirection: "column"
1001
+ },
1002
+ decorative: {}
1003
+ },
1004
+ scroller: {
1005
+ base: {
1006
+ height: "100%",
1007
+ overflowY: "auto",
1008
+ overscrollBehaviorY: "none"
1009
+ },
1010
+ decorative: {}
1011
+ }
1012
+ };
1013
+
1223
1014
  // src/sheet.tsx
1224
1015
  var Sheet = forwardRef(
1225
1016
  ({
@@ -1257,11 +1048,11 @@ var Sheet = forwardRef(
1257
1048
  const sheetRef = useRef(null);
1258
1049
  const sheetHeight = Math.round(sheetBounds.height);
1259
1050
  const [currentSnap, setCurrentSnap] = useState(initialSnap);
1260
- const snapPoints = snapPointsProp && sheetHeight > 0 ? computeSnapPoints({ sheetHeight, snapPointsProp }) : [];
1261
1051
  const { windowHeight } = useDimensions();
1262
1052
  const closedY = sheetHeight > 0 ? sheetHeight : windowHeight;
1263
1053
  const y = useMotionValue(closedY);
1264
1054
  const yInverted = useTransform(y, (val) => Math.max(sheetHeight - val, 0));
1055
+ const yProgress = useTransform(y, (val) => clamp(1 - val / closedY, 0, 1));
1265
1056
  const indicatorRotation = useMotionValue(0);
1266
1057
  const shouldReduceMotion = useReducedMotion();
1267
1058
  const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
@@ -1277,6 +1068,9 @@ var Sheet = forwardRef(
1277
1068
  y,
1278
1069
  (val) => val + 2 >= closedY ? "hidden" : "visible"
1279
1070
  );
1071
+ const snapPoints = useMemo(() => {
1072
+ return snapPointsProp && sheetHeight > 0 ? computeSnapPoints({ sheetHeight, snapPointsProp }) : [];
1073
+ }, [sheetHeight, snapPointsProp == null ? void 0 : snapPointsProp.join("")]);
1280
1074
  const updateSnap = useStableCallback((snapIndex) => {
1281
1075
  setCurrentSnap(snapIndex);
1282
1076
  onSnap == null ? void 0 : onSnap(snapIndex);
@@ -1363,45 +1157,25 @@ var Sheet = forwardRef(
1363
1157
  blurActiveInput();
1364
1158
  onDragEndProp == null ? void 0 : onDragEndProp(event, info);
1365
1159
  const currentY = y.get();
1366
- let yTo = 0;
1367
- const currentSnapPoint = currentSnap !== void 0 ? getSnapPoint(currentSnap) : null;
1368
- if (currentSnapPoint) {
1369
- const dragOffsetDirection = info.offset.y > 0 ? "down" : "up";
1370
- const dragVelocityDirection = info.velocity.y > 0 ? "down" : "up";
1371
- const isHighVelocity = Math.abs(info.velocity.y) > dragVelocityThreshold;
1372
- let result;
1373
- if (isHighVelocity) {
1374
- result = handleHighVelocityDrag({
1375
- snapPoints,
1376
- dragDirection: dragVelocityDirection
1377
- });
1378
- } else {
1379
- result = handleLowVelocityDrag({
1380
- currentSnapPoint,
1381
- currentY,
1382
- dragDirection: dragOffsetDirection,
1383
- snapPoints,
1384
- velocity: info.velocity.y
1385
- });
1386
- }
1387
- yTo = result.yTo;
1388
- if (disableDismiss && yTo + 1 >= sheetHeight) {
1389
- const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1390
- if (bottomSnapPoint) {
1391
- yTo = bottomSnapPoint.snapValueY;
1392
- updateSnap(bottomSnapPoint.snapIndex);
1393
- } else {
1394
- yTo = currentY;
1395
- }
1396
- } else if (result.snapIndex !== void 0) {
1397
- updateSnap(result.snapIndex);
1398
- }
1399
- } else if (info.velocity.y > dragVelocityThreshold || currentY > sheetHeight * dragCloseThreshold) {
1400
- if (disableDismiss) {
1401
- yTo = 0;
1160
+ const result = classifyDragEnd({
1161
+ y: currentY,
1162
+ info,
1163
+ sheetHeight,
1164
+ dragCloseThreshold,
1165
+ snapPoints,
1166
+ dragVelocityThreshold
1167
+ });
1168
+ let yTo = result.yTo;
1169
+ if (disableDismiss && yTo + 1 >= sheetHeight) {
1170
+ const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1171
+ if (bottomSnapPoint) {
1172
+ yTo = bottomSnapPoint.snapValueY;
1173
+ updateSnap(bottomSnapPoint.snapIndex);
1402
1174
  } else {
1403
- yTo = closedY;
1175
+ yTo = currentY;
1404
1176
  }
1177
+ } else if (result.snapIndex !== void 0) {
1178
+ updateSnap(result.snapIndex);
1405
1179
  }
1406
1180
  animate(y, yTo, animationOptions);
1407
1181
  if (yTo + 1 >= sheetHeight && !disableDismiss) {
@@ -1409,12 +1183,6 @@ var Sheet = forwardRef(
1409
1183
  }
1410
1184
  indicatorRotation.set(0);
1411
1185
  });
1412
- useImperativeHandle(ref, () => ({
1413
- y,
1414
- yInverted,
1415
- height: sheetHeight,
1416
- snapTo
1417
- }));
1418
1186
  useModalEffect({
1419
1187
  y,
1420
1188
  detent,
@@ -1434,9 +1202,7 @@ var Sheet = forwardRef(
1434
1202
  const initialSnapPoint = initialSnap !== void 0 ? getSnapPoint(initialSnap) : null;
1435
1203
  const yTo = (initialSnapPoint == null ? void 0 : initialSnapPoint.snapValueY) ?? 0;
1436
1204
  await animate(y, yTo, animationOptions);
1437
- if (initialSnap !== void 0) {
1438
- updateSnap(initialSnap);
1439
- }
1205
+ if (initialSnap !== void 0) updateSnap(initialSnap);
1440
1206
  onOpenEnd == null ? void 0 : onOpenEnd();
1441
1207
  },
1442
1208
  onClosing: async () => {
@@ -1454,7 +1220,7 @@ var Sheet = forwardRef(
1454
1220
  onDragStart,
1455
1221
  onDragEnd
1456
1222
  };
1457
- const context = {
1223
+ const internalContext = {
1458
1224
  currentSnap,
1459
1225
  detent,
1460
1226
  disableDrag,
@@ -1465,9 +1231,20 @@ var Sheet = forwardRef(
1465
1231
  sheetBoundsRef,
1466
1232
  sheetRef,
1467
1233
  unstyled,
1234
+ yProgress,
1468
1235
  y
1469
1236
  };
1470
- const sheet = /* @__PURE__ */ React4.createElement(SheetContext.Provider, { value: context }, /* @__PURE__ */ React4.createElement(
1237
+ const exposedContext = {
1238
+ height: sheetHeight,
1239
+ currentSnap,
1240
+ snapPoints,
1241
+ snapTo,
1242
+ y,
1243
+ yInverted,
1244
+ yProgress
1245
+ };
1246
+ useImperativeHandle(ref, () => exposedContext);
1247
+ const sheet = /* @__PURE__ */ React.createElement(InternalSheetContext.Provider, { value: internalContext }, /* @__PURE__ */ React.createElement(
1471
1248
  motion.div,
1472
1249
  {
1473
1250
  ...rest,
@@ -1481,13 +1258,293 @@ var Sheet = forwardRef(
1481
1258
  ...style
1482
1259
  }
1483
1260
  },
1484
- state !== "closed" ? children : null
1261
+ /* @__PURE__ */ React.createElement(ExposedSheetContext.Provider, { value: exposedContext }, state !== "closed" ? children : null)
1485
1262
  ));
1486
1263
  if (IS_SSR) return sheet;
1487
1264
  return createPortal(sheet, mountPoint ?? document.body);
1488
1265
  }
1489
1266
  );
1490
1267
  Sheet.displayName = "Sheet";
1268
+ var isClickable = (props) => !!props.onClick || !!props.onTap;
1269
+ var SheetBackdrop = forwardRef(
1270
+ ({ style, className = "", unstyled, ...rest }, ref) => {
1271
+ const sheetContext = useInternalContext();
1272
+ const clickable = isClickable(rest);
1273
+ const Comp = clickable ? motion.button : motion.div;
1274
+ const pointerEvents = clickable ? "auto" : "none";
1275
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
1276
+ const backdropStyle = {
1277
+ opacity: sheetContext.yProgress,
1278
+ ...applyStyles(styles.backdrop, isUnstyled),
1279
+ ...style,
1280
+ pointerEvents
1281
+ };
1282
+ return /* @__PURE__ */ React.createElement(
1283
+ Comp,
1284
+ {
1285
+ ...rest,
1286
+ ref,
1287
+ className: `react-modal-sheet-backdrop ${className}`,
1288
+ style: backdropStyle
1289
+ }
1290
+ );
1291
+ }
1292
+ );
1293
+ SheetBackdrop.displayName = "SheetBackdrop";
1294
+ var SheetContainer = forwardRef(
1295
+ ({ children, style, className = "", unstyled, ...rest }, ref) => {
1296
+ const sheetContext = useInternalContext();
1297
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
1298
+ const containerStyle = {
1299
+ ...applyStyles(styles.container, isUnstyled),
1300
+ ...style,
1301
+ y: sheetContext.y
1302
+ };
1303
+ if (sheetContext.detent === "default") {
1304
+ containerStyle.height = DEFAULT_HEIGHT;
1305
+ }
1306
+ if (sheetContext.detent === "full") {
1307
+ containerStyle.height = "100%";
1308
+ containerStyle.maxHeight = "100%";
1309
+ }
1310
+ if (sheetContext.detent === "content") {
1311
+ containerStyle.height = "auto";
1312
+ containerStyle.maxHeight = DEFAULT_HEIGHT;
1313
+ }
1314
+ return /* @__PURE__ */ React.createElement(
1315
+ motion.div,
1316
+ {
1317
+ ...rest,
1318
+ ref: mergeRefs([
1319
+ ref,
1320
+ sheetContext.sheetRef,
1321
+ sheetContext.sheetBoundsRef
1322
+ ]),
1323
+ className: `react-modal-sheet-container ${className}`,
1324
+ style: containerStyle
1325
+ },
1326
+ children
1327
+ );
1328
+ }
1329
+ );
1330
+ SheetContainer.displayName = "SheetContainer";
1331
+ var constraints = { bottom: 0, top: 0, left: 0, right: 0 };
1332
+ function useDragConstraints() {
1333
+ const ref = useRef(null);
1334
+ const onMeasure = useCallback(() => constraints, []);
1335
+ return { ref, onMeasure };
1336
+ }
1337
+ function useScrollPosition(options = {}) {
1338
+ const { debounceDelay = 32, isEnabled = true } = options;
1339
+ const scrollTimeoutRef = useRef(null);
1340
+ const [element, setElement] = useState(null);
1341
+ const [scrollPosition, setScrollPosition] = useState(void 0);
1342
+ const scrollRef = useMemo(
1343
+ () => (element2) => {
1344
+ setElement(element2);
1345
+ },
1346
+ []
1347
+ );
1348
+ const determineScrollPosition = useStableCallback((element2) => {
1349
+ const { scrollTop, scrollHeight, clientHeight } = element2;
1350
+ const isScrollable2 = scrollHeight > clientHeight;
1351
+ if (!isScrollable2) {
1352
+ if (scrollPosition) setScrollPosition(void 0);
1353
+ return;
1354
+ }
1355
+ const isAtTop = scrollTop <= 0;
1356
+ const isAtBottom = Math.ceil(scrollHeight) - Math.ceil(scrollTop) === Math.ceil(clientHeight);
1357
+ let position;
1358
+ if (isAtTop) {
1359
+ position = "top";
1360
+ } else if (isAtBottom) {
1361
+ position = "bottom";
1362
+ } else {
1363
+ position = "middle";
1364
+ }
1365
+ element2.style.touchAction = isAtTop ? "pan-down" : "";
1366
+ if (position === scrollPosition) return;
1367
+ setScrollPosition(position);
1368
+ });
1369
+ const onScroll = useStableCallback((event) => {
1370
+ if (event.currentTarget instanceof HTMLElement) {
1371
+ const el = event.currentTarget;
1372
+ if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
1373
+ if (debounceDelay === 0) {
1374
+ determineScrollPosition(el);
1375
+ } else {
1376
+ scrollTimeoutRef.current = setTimeout(
1377
+ () => determineScrollPosition(el),
1378
+ debounceDelay
1379
+ );
1380
+ }
1381
+ }
1382
+ });
1383
+ const onTouchStart = useStableCallback((event) => {
1384
+ if (event.currentTarget instanceof HTMLElement) {
1385
+ const element2 = event.currentTarget;
1386
+ requestAnimationFrame(() => {
1387
+ determineScrollPosition(element2);
1388
+ });
1389
+ }
1390
+ });
1391
+ useEffect(() => {
1392
+ if (!element || !isEnabled) return;
1393
+ determineScrollPosition(element);
1394
+ element.addEventListener("scroll", onScroll);
1395
+ element.addEventListener("touchstart", onTouchStart);
1396
+ return () => {
1397
+ if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
1398
+ element.removeEventListener("scroll", onScroll);
1399
+ element.removeEventListener("touchstart", onTouchStart);
1400
+ };
1401
+ }, [element, isEnabled]);
1402
+ return {
1403
+ scrollRef,
1404
+ scrollPosition
1405
+ };
1406
+ }
1407
+
1408
+ // src/sheet-content.tsx
1409
+ var SheetContent = forwardRef(
1410
+ ({
1411
+ disableScroll: disableScrollProp,
1412
+ disableDrag: disableDragProp,
1413
+ children,
1414
+ style: styleProp,
1415
+ className = "",
1416
+ scrollClassName = "",
1417
+ scrollStyle: scrollStyleProp,
1418
+ scrollRef: scrollRefProp = null,
1419
+ unstyled,
1420
+ ...rest
1421
+ }, ref) => {
1422
+ const sheetContext = useInternalContext();
1423
+ const dragConstraints = useDragConstraints();
1424
+ const scroll = useScrollPosition({
1425
+ isEnabled: typeof disableScrollProp === "function" ? true : !disableScrollProp
1426
+ });
1427
+ const disableScroll = typeof disableScrollProp === "function" ? disableScrollProp({
1428
+ scrollPosition: scroll.scrollPosition,
1429
+ currentSnap: sheetContext.currentSnap
1430
+ }) : Boolean(disableScrollProp);
1431
+ const disableDragDueToScroll = !disableScroll && scroll.scrollPosition && scroll.scrollPosition !== "top";
1432
+ const disableDragDueToProp = typeof disableDragProp === "function" ? disableDragProp({
1433
+ scrollPosition: scroll.scrollPosition,
1434
+ currentSnap: sheetContext.currentSnap
1435
+ }) : Boolean(disableDragProp);
1436
+ const disableDrag = disableDragDueToProp || disableDragDueToScroll || sheetContext.disableDrag;
1437
+ const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
1438
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
1439
+ const contentStyle = {
1440
+ ...applyStyles(styles.content, isUnstyled),
1441
+ ...styleProp
1442
+ };
1443
+ const scrollStyle = applyStyles(styles.scroller, isUnstyled);
1444
+ if (sheetContext.avoidKeyboard) {
1445
+ scrollStyle.paddingBottom = isHTTPS() ? "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))" : "var(--keyboard-inset-height, 0px)";
1446
+ }
1447
+ if (disableScroll) {
1448
+ scrollStyle.overflowY = "clip";
1449
+ }
1450
+ if (dragProps !== void 0) {
1451
+ scrollStyle.touchAction = "pan-down";
1452
+ }
1453
+ return /* @__PURE__ */ React.createElement(
1454
+ motion.div,
1455
+ {
1456
+ ...rest,
1457
+ ref: mergeRefs([ref, dragConstraints.ref]),
1458
+ className: `react-modal-sheet-content ${className}`,
1459
+ style: contentStyle,
1460
+ ...dragProps,
1461
+ dragConstraints: dragConstraints.ref,
1462
+ onMeasureDragConstraints: dragConstraints.onMeasure
1463
+ },
1464
+ /* @__PURE__ */ React.createElement(
1465
+ motion.div,
1466
+ {
1467
+ ref: mergeRefs([scroll.scrollRef, scrollRefProp]),
1468
+ style: { ...scrollStyle, ...scrollStyleProp },
1469
+ className: `react-modal-sheet-content-scroller ${scrollClassName}`
1470
+ },
1471
+ children
1472
+ )
1473
+ );
1474
+ }
1475
+ );
1476
+ SheetContent.displayName = "SheetContent";
1477
+ function SheetDragIndicator({
1478
+ style,
1479
+ className = "",
1480
+ unstyled,
1481
+ ...rest
1482
+ }) {
1483
+ const sheetContext = useInternalContext();
1484
+ const indicator1Transform = useTransform(
1485
+ sheetContext.indicatorRotation,
1486
+ (r) => `translateX(2px) rotate(${r}deg)`
1487
+ );
1488
+ const indicator2Transform = useTransform(
1489
+ sheetContext.indicatorRotation,
1490
+ (r) => `translateX(-2px) rotate(${ -1 * r}deg)`
1491
+ );
1492
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
1493
+ const indicatorWrapperStyle = {
1494
+ ...applyStyles(styles.indicatorWrapper, isUnstyled),
1495
+ ...style
1496
+ };
1497
+ const indicatorStyle = applyStyles(styles.indicator, isUnstyled);
1498
+ return /* @__PURE__ */ React.createElement(
1499
+ "div",
1500
+ {
1501
+ className: `react-modal-sheet-drag-indicator-container ${className}`,
1502
+ style: indicatorWrapperStyle,
1503
+ ...rest
1504
+ },
1505
+ /* @__PURE__ */ React.createElement(
1506
+ motion.span,
1507
+ {
1508
+ className: "react-modal-sheet-drag-indicator",
1509
+ style: { ...indicatorStyle, transform: indicator1Transform }
1510
+ }
1511
+ ),
1512
+ /* @__PURE__ */ React.createElement(
1513
+ motion.span,
1514
+ {
1515
+ className: "react-modal-sheet-drag-indicator",
1516
+ style: { ...indicatorStyle, transform: indicator2Transform }
1517
+ }
1518
+ )
1519
+ );
1520
+ }
1521
+ var SheetHeader = forwardRef(
1522
+ ({ children, style, disableDrag, unstyled, className = "", ...rest }, ref) => {
1523
+ const sheetContext = useInternalContext();
1524
+ const dragConstraints = useDragConstraints();
1525
+ const dragProps = disableDrag || sheetContext.disableDrag ? void 0 : sheetContext.dragProps;
1526
+ const isUnstyled = unstyled ?? sheetContext.unstyled;
1527
+ const headerWrapperStyle = {
1528
+ ...applyStyles(styles.headerWrapper, isUnstyled),
1529
+ ...style
1530
+ };
1531
+ const headerStyle = applyStyles(styles.header, isUnstyled);
1532
+ return /* @__PURE__ */ React.createElement(
1533
+ motion.div,
1534
+ {
1535
+ ...rest,
1536
+ ref: mergeRefs([ref, dragConstraints.ref]),
1537
+ style: headerWrapperStyle,
1538
+ className: `react-modal-sheet-header-container ${className}`,
1539
+ ...dragProps,
1540
+ dragConstraints: dragConstraints.ref,
1541
+ onMeasureDragConstraints: dragConstraints.onMeasure
1542
+ },
1543
+ children || /* @__PURE__ */ React.createElement("div", { className: "react-modal-sheet-header", style: headerStyle }, /* @__PURE__ */ React.createElement(SheetDragIndicator, null))
1544
+ );
1545
+ }
1546
+ );
1547
+ SheetHeader.displayName = "SheetHeader";
1491
1548
 
1492
1549
  // src/index.tsx
1493
1550
  var Sheet2 = Object.assign(Sheet, {
@@ -1495,7 +1552,8 @@ var Sheet2 = Object.assign(Sheet, {
1495
1552
  Header: SheetHeader,
1496
1553
  DragIndicator: SheetDragIndicator,
1497
1554
  Content: SheetContent,
1498
- Backdrop: SheetBackdrop
1555
+ Backdrop: SheetBackdrop,
1556
+ useContext: useExposedContext
1499
1557
  });
1500
1558
 
1501
1559
  export { Sheet2 as Sheet, useScrollPosition, useVirtualKeyboard };