@simoneggert/react-modal-sheet 5.4.3

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 ADDED
@@ -0,0 +1,1502 @@
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';
3
+ import { createPortal } from 'react-dom';
4
+ import useMeasure from 'react-use-measure';
5
+ import { transform } from 'motion';
6
+
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
+ };
120
+
121
+ // src/constants.ts
122
+ var DEFAULT_HEIGHT = "calc(100% - env(safe-area-inset-top) - 34px)";
123
+ var IS_SSR = typeof window === "undefined";
124
+ var DEFAULT_TWEEN_CONFIG = {
125
+ ease: "easeOut",
126
+ duration: 0.2
127
+ };
128
+ var REDUCED_MOTION_TWEEN_CONFIG = {
129
+ ease: "linear",
130
+ duration: 0.01
131
+ };
132
+ var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
133
+ var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
134
+
135
+ // src/utils.ts
136
+ function applyStyles(styles2, unstyled) {
137
+ return unstyled ? styles2.base : { ...styles2.base, ...styles2.decorative };
138
+ }
139
+ function isAscendingOrder(arr) {
140
+ for (let i = 0; i < arr.length; i++) {
141
+ if (arr[i + 1] < arr[i]) return false;
142
+ }
143
+ return true;
144
+ }
145
+ function mergeRefs(refs) {
146
+ return (value) => {
147
+ refs.forEach((ref) => {
148
+ if (typeof ref === "function") {
149
+ ref(value);
150
+ } else if (ref) {
151
+ ref.current = value;
152
+ }
153
+ });
154
+ };
155
+ }
156
+ function testPlatform(re) {
157
+ var _a;
158
+ return typeof window !== "undefined" && window.navigator != null ? re.test(
159
+ // @ts-expect-error
160
+ ((_a = window.navigator.userAgentData) == null ? void 0 : _a.platform) || window.navigator.platform
161
+ ) : false;
162
+ }
163
+ function cached(fn) {
164
+ let res = null;
165
+ return () => {
166
+ if (res == null) {
167
+ res = fn();
168
+ }
169
+ return res;
170
+ };
171
+ }
172
+ var isMac = cached(function() {
173
+ return testPlatform(/^Mac/i);
174
+ });
175
+ var isIPhone = cached(function() {
176
+ return testPlatform(/^iPhone/i);
177
+ });
178
+ var isIPad = cached(function() {
179
+ return testPlatform(/^iPad/i) || isMac() && navigator.maxTouchPoints > 1;
180
+ });
181
+ var isIOS = cached(function() {
182
+ return isIPhone() || isIPad();
183
+ });
184
+ function waitForElement(className, interval = 50, maxAttempts = 20) {
185
+ return new Promise((resolve) => {
186
+ let attempts = 0;
187
+ const timer = setInterval(() => {
188
+ const element = document.getElementsByClassName(
189
+ className
190
+ )[0];
191
+ attempts++;
192
+ if (element || attempts >= maxAttempts) {
193
+ clearInterval(timer);
194
+ resolve(element);
195
+ }
196
+ }, interval);
197
+ });
198
+ }
199
+ var nonTextInputTypes = /* @__PURE__ */ new Set([
200
+ "checkbox",
201
+ "radio",
202
+ "range",
203
+ "color",
204
+ "file",
205
+ "image",
206
+ "button",
207
+ "submit",
208
+ "reset"
209
+ ]);
210
+ function willOpenKeyboard(target) {
211
+ return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
212
+ }
213
+ function isHTTPS() {
214
+ return typeof window !== "undefined" && window.isSecureContext;
215
+ }
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 };
292
+ }
293
+ var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
294
+
295
+ // src/hooks/use-stable-callback.ts
296
+ function useStableCallback(handler) {
297
+ const handlerRef = useRef(void 0);
298
+ useIsomorphicLayoutEffect(() => {
299
+ handlerRef.current = handler;
300
+ });
301
+ return useCallback((...args) => {
302
+ const fn = handlerRef.current;
303
+ return fn == null ? void 0 : fn(...args);
304
+ }, []);
305
+ }
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;
325
+ }
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
+ );
572
+ }
573
+ function resetKeyboardState() {
574
+ focusedElementRef.current = null;
575
+ setKeyboardInsetHeightEnv(0);
576
+ setKeyboardState({ isVisible: false, height: 0 });
577
+ }
578
+ if (!isEnabled) {
579
+ resetKeyboardState();
580
+ return;
581
+ }
582
+ function updateKeyboardState() {
583
+ if (debounceTimer.current) {
584
+ clearTimeout(debounceTimer.current);
585
+ }
586
+ debounceTimer.current = setTimeout(() => {
587
+ const active = getActiveElement() ?? focusedElementRef.current;
588
+ const inputIsFocused = active ? willOpenKeyboard(active) : false;
589
+ if (!inputIsFocused) {
590
+ resetKeyboardState();
591
+ return;
592
+ }
593
+ focusedElementRef.current = active;
594
+ if (vk == null ? void 0 : vk.overlaysContent) {
595
+ const keyboardHeight = vk.boundingRect.height;
596
+ setKeyboardInsetHeightEnv(keyboardHeight);
597
+ setKeyboardState({ isVisible: true, height: keyboardHeight });
598
+ return;
599
+ }
600
+ if (vv) {
601
+ const heightDiff = window.innerHeight - vv.height;
602
+ if (heightDiff > visualViewportThreshold) {
603
+ setKeyboardInsetHeightEnv(heightDiff);
604
+ setKeyboardState({ isVisible: true, height: heightDiff });
605
+ return;
606
+ }
607
+ }
608
+ resetKeyboardState();
609
+ }, debounceDelay);
610
+ }
611
+ function handleFocusIn(e) {
612
+ if (e.target instanceof HTMLElement && willOpenKeyboard(e.target)) {
613
+ focusedElementRef.current = e.target;
614
+ updateKeyboardState();
615
+ }
616
+ }
617
+ function handleFocusOut() {
618
+ requestAnimationFrame(() => {
619
+ focusedElementRef.current = getActiveElement();
620
+ updateKeyboardState();
621
+ });
622
+ }
623
+ document.addEventListener("focusin", handleFocusIn);
624
+ document.addEventListener("focusout", handleFocusOut);
625
+ if (vv) {
626
+ vv.addEventListener("resize", updateKeyboardState);
627
+ vv.addEventListener("scroll", updateKeyboardState);
628
+ }
629
+ if (vk) {
630
+ if (virtualKeyboardOverlayUsers === 0) {
631
+ initialVirtualKeyboardOverlaysContent = vk.overlaysContent;
632
+ vk.overlaysContent = true;
633
+ }
634
+ virtualKeyboardOverlayUsers++;
635
+ vk.addEventListener("geometrychange", updateKeyboardState);
636
+ }
637
+ focusedElementRef.current = getActiveElement();
638
+ updateKeyboardState();
639
+ return () => {
640
+ document.removeEventListener("focusin", handleFocusIn);
641
+ document.removeEventListener("focusout", handleFocusOut);
642
+ if (vv) {
643
+ vv.removeEventListener("resize", updateKeyboardState);
644
+ vv.removeEventListener("scroll", updateKeyboardState);
645
+ }
646
+ if (vk) {
647
+ vk.removeEventListener("geometrychange", updateKeyboardState);
648
+ virtualKeyboardOverlayUsers = Math.max(
649
+ 0,
650
+ virtualKeyboardOverlayUsers - 1
651
+ );
652
+ if (virtualKeyboardOverlayUsers === 0) {
653
+ vk.overlaysContent = initialVirtualKeyboardOverlaysContent ?? false;
654
+ initialVirtualKeyboardOverlaysContent = null;
655
+ }
656
+ }
657
+ if (debounceTimer.current) {
658
+ clearTimeout(debounceTimer.current);
659
+ }
660
+ resetKeyboardState();
661
+ };
662
+ }, [debounceDelay, isEnabled, visualViewportThreshold]);
663
+ return {
664
+ keyboardHeight: state.height,
665
+ isKeyboardOpen: state.isVisible
666
+ };
667
+ }
668
+ function getVirtualKeyboardApi() {
669
+ return isHTTPS() && "virtualKeyboard" in navigator ? navigator.virtualKeyboard : null;
670
+ }
671
+ function getActiveElement() {
672
+ var _a;
673
+ let activeElement = document.activeElement;
674
+ while (activeElement instanceof HTMLElement && ((_a = activeElement.shadowRoot) == null ? void 0 : _a.activeElement)) {
675
+ activeElement = activeElement.shadowRoot.activeElement;
676
+ }
677
+ return activeElement && willOpenKeyboard(activeElement) ? activeElement : null;
678
+ }
679
+
680
+ // src/hooks/use-keyboard-avoidance.ts
681
+ function useKeyboardAvoidance({
682
+ isEnabled,
683
+ containerRef,
684
+ onWillOpenKeyboard,
685
+ onDidOpenKeyboard
686
+ }) {
687
+ const [focusedElement, setFocusedElement] = useState(
688
+ null
689
+ );
690
+ const keyboard = useVirtualKeyboard({
691
+ isEnabled,
692
+ containerRef
693
+ });
694
+ const handleFocusIn = useStableCallback(async (event) => {
695
+ var _a;
696
+ const element = event.target;
697
+ if (willOpenKeyboard(element) && ((_a = containerRef.current) == null ? void 0 : _a.contains(element))) {
698
+ await (onWillOpenKeyboard == null ? void 0 : onWillOpenKeyboard(event));
699
+ setFocusedElement(element);
700
+ }
701
+ });
702
+ const handleFocusOut = useStableCallback((event) => {
703
+ const element = event.target;
704
+ if (focusedElement === element) {
705
+ setFocusedElement(null);
706
+ }
707
+ });
708
+ useEffect(() => {
709
+ if (!isEnabled) return;
710
+ document.addEventListener("focusin", handleFocusIn);
711
+ document.addEventListener("focusout", handleFocusOut);
712
+ return () => {
713
+ document.removeEventListener("focusin", handleFocusIn);
714
+ document.removeEventListener("focusout", handleFocusOut);
715
+ };
716
+ }, [isEnabled]);
717
+ useEffect(() => {
718
+ const containerElement = containerRef.current;
719
+ if (!isEnabled || !focusedElement || !containerElement || !keyboard.isKeyboardOpen) {
720
+ return;
721
+ }
722
+ requestAnimationFrame(() => {
723
+ onDidOpenKeyboard == null ? void 0 : onDidOpenKeyboard(focusedElement);
724
+ });
725
+ }, [isEnabled, keyboard.isKeyboardOpen, focusedElement]);
726
+ return keyboard;
727
+ }
728
+ function useSafeAreaInsets() {
729
+ const [insets] = useState(() => {
730
+ const fallback = { top: 0, left: 0, right: 0, bottom: 0 };
731
+ if (IS_SSR) return fallback;
732
+ const root = document.querySelector(":root");
733
+ if (!root) return fallback;
734
+ root.style.setProperty("--rms-sat", "env(safe-area-inset-top)");
735
+ root.style.setProperty("--rms-sal", "env(safe-area-inset-left)");
736
+ root.style.setProperty("--rms-sar", "env(safe-area-inset-right)");
737
+ root.style.setProperty("--rms-sab", "env(safe-area-inset-bottom)");
738
+ const computedStyle = getComputedStyle(root);
739
+ const sat = getComputedValue(computedStyle, "--rms-sat");
740
+ const sal = getComputedValue(computedStyle, "--rms-sal");
741
+ const sar = getComputedValue(computedStyle, "--rms-sar");
742
+ const sab = getComputedValue(computedStyle, "--rms-sab");
743
+ root.style.removeProperty("--rms-sat");
744
+ root.style.removeProperty("--rms-sal");
745
+ root.style.removeProperty("--rms-sar");
746
+ root.style.removeProperty("--rms-sab");
747
+ return { top: sat, left: sal, right: sar, bottom: sab };
748
+ });
749
+ return insets;
750
+ }
751
+ function getComputedValue(computed, property) {
752
+ const strValue = computed.getPropertyValue(property).replace("px", "").trim();
753
+ return parseInt(strValue, 10) || 0;
754
+ }
755
+
756
+ // src/hooks/use-modal-effect.ts
757
+ function useModalEffect({
758
+ y,
759
+ detent,
760
+ rootId: _rootId,
761
+ sheetHeight,
762
+ snapPoints,
763
+ startThreshold
764
+ }) {
765
+ const insetTop = useSafeAreaInsets().top;
766
+ let rootId = _rootId;
767
+ if (rootId && detent === "full") {
768
+ console.warn('Using "full" detent with modal effect is not supported.');
769
+ rootId = void 0;
770
+ }
771
+ useIsomorphicLayoutEffect(() => {
772
+ return () => {
773
+ if (rootId) cleanupModalEffect(rootId);
774
+ };
775
+ }, []);
776
+ useIsomorphicLayoutEffect(() => {
777
+ 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
+ }
794
+ }
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
+ }
802
+ }
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`;
811
+ });
812
+ function onCompleted() {
813
+ if (y.get() - 5 >= sheetHeight) {
814
+ cleanupModalEffect(rootId);
815
+ }
816
+ }
817
+ const removeCompleteListener = y.on("animationComplete", onCompleted);
818
+ const removeCancelListener = y.on("animationCancel", onCompleted);
819
+ return () => {
820
+ removeStartListener();
821
+ removeChangeListener();
822
+ removeCompleteListener();
823
+ removeCancelListener();
824
+ };
825
+ }, [y, rootId, insetTop, startThreshold, sheetHeight]);
826
+ }
827
+ function setupModalEffect(rootId) {
828
+ const root = document.querySelector(`#${rootId}`);
829
+ const body = document.querySelector("body");
830
+ if (!root) return;
831
+ body.style.backgroundColor = "#000";
832
+ root.style.overflow = "hidden";
833
+ root.style.transitionTimingFunction = "cubic-bezier(0.32, 0.72, 0, 1)";
834
+ root.style.transitionProperty = "transform, border-radius";
835
+ root.style.transitionDuration = "0.5s";
836
+ root.style.transformOrigin = "center top";
837
+ }
838
+ function cleanupModalEffect(rootId) {
839
+ const root = document.querySelector(`#${rootId}`);
840
+ const body = document.querySelector("body");
841
+ if (!root) return;
842
+ body.style.removeProperty("background-color");
843
+ root.style.removeProperty("overflow");
844
+ root.style.removeProperty("transition-timing-function");
845
+ root.style.removeProperty("transition-property");
846
+ root.style.removeProperty("transition-duration");
847
+ root.style.removeProperty("transform-origin");
848
+ root.style.removeProperty("transform");
849
+ root.style.removeProperty("border-top-right-radius");
850
+ root.style.removeProperty("border-top-left-radius");
851
+ }
852
+
853
+ // src/hooks/use-prevent-scroll.ts
854
+ var KEYBOARD_BUFFER = 24;
855
+ function chain(...callbacks) {
856
+ return (...args) => {
857
+ for (const callback of callbacks) {
858
+ if (typeof callback === "function") {
859
+ callback(...args);
860
+ }
861
+ }
862
+ };
863
+ }
864
+ var visualViewport = typeof document !== "undefined" && window.visualViewport;
865
+ function isScrollable(node, checkForOverflow) {
866
+ if (!node) {
867
+ return false;
868
+ }
869
+ const style = window.getComputedStyle(node);
870
+ let scrollable = /(auto|scroll)/.test(
871
+ style.overflow + style.overflowX + style.overflowY
872
+ );
873
+ if (scrollable && checkForOverflow) {
874
+ scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
875
+ }
876
+ return scrollable;
877
+ }
878
+ function getScrollParent(node, checkForOverflow) {
879
+ let scrollableNode = node;
880
+ if (isScrollable(scrollableNode, checkForOverflow)) {
881
+ scrollableNode = scrollableNode.parentElement;
882
+ }
883
+ while (scrollableNode && !isScrollable(scrollableNode, checkForOverflow)) {
884
+ scrollableNode = scrollableNode.parentElement;
885
+ }
886
+ return scrollableNode || document.scrollingElement || document.documentElement;
887
+ }
888
+ var preventScrollCount = 0;
889
+ var restore;
890
+ function usePreventScroll(options = {}) {
891
+ const { isDisabled } = options;
892
+ useIsomorphicLayoutEffect(() => {
893
+ if (isDisabled) {
894
+ return;
895
+ }
896
+ preventScrollCount++;
897
+ if (preventScrollCount === 1) {
898
+ if (isIOS()) {
899
+ restore = preventScrollMobileSafari();
900
+ } else {
901
+ restore = preventScrollStandard();
902
+ }
903
+ }
904
+ return () => {
905
+ preventScrollCount--;
906
+ if (preventScrollCount === 0) {
907
+ restore == null ? void 0 : restore();
908
+ }
909
+ };
910
+ }, [isDisabled]);
911
+ }
912
+ function preventScrollStandard() {
913
+ return chain(
914
+ setStyle(
915
+ document.documentElement,
916
+ "paddingRight",
917
+ `${window.innerWidth - document.documentElement.clientWidth}px`
918
+ ),
919
+ setStyle(document.documentElement, "overflow", "hidden")
920
+ );
921
+ }
922
+ function preventScrollMobileSafari() {
923
+ let scrollable;
924
+ let lastY = 0;
925
+ const onTouchStart = (e) => {
926
+ var _a;
927
+ const target = (_a = e.composedPath()) == null ? void 0 : _a[0];
928
+ scrollable = getScrollParent(target, true);
929
+ if (scrollable === document.documentElement && scrollable === document.body) {
930
+ return;
931
+ }
932
+ lastY = e.changedTouches[0].pageY;
933
+ };
934
+ const onTouchMove = (e) => {
935
+ if (scrollable === void 0) {
936
+ return;
937
+ }
938
+ if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
939
+ e.preventDefault();
940
+ return;
941
+ }
942
+ const y = e.changedTouches[0].pageY;
943
+ const scrollTop = scrollable.scrollTop;
944
+ const bottom = scrollable.scrollHeight - scrollable.clientHeight;
945
+ if (bottom === 0) {
946
+ return;
947
+ }
948
+ if (scrollTop <= 0 && y > lastY || scrollTop >= bottom && y < lastY) {
949
+ e.preventDefault();
950
+ }
951
+ lastY = y;
952
+ };
953
+ const onTouchEnd = (e) => {
954
+ var _a;
955
+ const target = (_a = e.composedPath()) == null ? void 0 : _a[0];
956
+ if (willOpenKeyboard(target) && target !== document.activeElement) {
957
+ e.preventDefault();
958
+ target.style.transform = "translateY(-2000px)";
959
+ target.focus();
960
+ requestAnimationFrame(() => {
961
+ target.style.transform = "";
962
+ });
963
+ }
964
+ };
965
+ const onFocus = (e) => {
966
+ var _a;
967
+ const target = (_a = e.composedPath()) == null ? void 0 : _a[0];
968
+ if (willOpenKeyboard(target)) {
969
+ target.style.transform = "translateY(-2000px)";
970
+ requestAnimationFrame(() => {
971
+ target.style.transform = "";
972
+ if (visualViewport) {
973
+ if (visualViewport.height < window.innerHeight) {
974
+ requestAnimationFrame(() => {
975
+ scrollIntoView(target);
976
+ });
977
+ } else {
978
+ visualViewport.addEventListener(
979
+ "resize",
980
+ () => scrollIntoView(target),
981
+ { once: true }
982
+ );
983
+ }
984
+ }
985
+ });
986
+ }
987
+ };
988
+ const onWindowScroll = () => {
989
+ window.scrollTo(0, 0);
990
+ };
991
+ const scrollX = window.pageXOffset;
992
+ const scrollY = window.pageYOffset;
993
+ const restoreStyles = chain(
994
+ setStyle(
995
+ document.documentElement,
996
+ "paddingRight",
997
+ `${window.innerWidth - document.documentElement.clientWidth}px`
998
+ ),
999
+ setStyle(document.documentElement, "overflow", "hidden")
1000
+ // setStyle(document.body, 'marginTop', `-${scrollY}px`)
1001
+ );
1002
+ const removeEvents = chain(
1003
+ addEvent(document, "touchstart", onTouchStart, {
1004
+ passive: false,
1005
+ capture: true
1006
+ }),
1007
+ addEvent(document, "touchmove", onTouchMove, {
1008
+ passive: false,
1009
+ capture: true
1010
+ }),
1011
+ addEvent(document, "touchend", onTouchEnd, {
1012
+ passive: false,
1013
+ capture: true
1014
+ }),
1015
+ addEvent(document, "focus", onFocus, true),
1016
+ addEvent(window, "scroll", onWindowScroll)
1017
+ );
1018
+ return () => {
1019
+ restoreStyles();
1020
+ removeEvents();
1021
+ window.scrollTo(scrollX, scrollY);
1022
+ };
1023
+ }
1024
+ function setStyle(element, style, value) {
1025
+ const cur = element.style[style];
1026
+ element.style[style] = value;
1027
+ return () => {
1028
+ element.style[style] = cur;
1029
+ };
1030
+ }
1031
+ function addEvent(target, event, handler, options) {
1032
+ target.addEventListener(event, handler, options);
1033
+ return () => {
1034
+ target.removeEventListener(event, handler, options);
1035
+ };
1036
+ }
1037
+ function scrollIntoView(target) {
1038
+ const root = document.scrollingElement || document.documentElement;
1039
+ while (target && target !== root) {
1040
+ const scrollable = getScrollParent(target);
1041
+ if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {
1042
+ const scrollableTop = scrollable.getBoundingClientRect().top;
1043
+ const targetTop = target.getBoundingClientRect().top;
1044
+ const targetBottom = target.getBoundingClientRect().bottom;
1045
+ const keyboardHeight = scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER;
1046
+ if (targetBottom > keyboardHeight) {
1047
+ scrollable.scrollTop += targetTop - scrollableTop;
1048
+ }
1049
+ }
1050
+ target = scrollable.parentElement;
1051
+ }
1052
+ }
1053
+ function useSheetState({
1054
+ isOpen,
1055
+ onClosed: _onClosed,
1056
+ onOpening: _onOpening,
1057
+ onOpen: _onOpen,
1058
+ onClosing: _onClosing
1059
+ }) {
1060
+ const [state, setState] = useState(isOpen ? "opening" : "closed");
1061
+ const onClosed = useStableCallback(() => _onClosed == null ? void 0 : _onClosed());
1062
+ const onOpening = useStableCallback(() => _onOpening == null ? void 0 : _onOpening());
1063
+ const onOpen = useStableCallback(() => _onOpen == null ? void 0 : _onOpen());
1064
+ const onClosing = useStableCallback(() => _onClosing == null ? void 0 : _onClosing());
1065
+ useEffect(() => {
1066
+ if (isOpen && state === "closed") {
1067
+ setState("opening");
1068
+ } else if (!isOpen && (state === "open" || state === "opening")) {
1069
+ setState("closing");
1070
+ }
1071
+ }, [isOpen, state]);
1072
+ useEffect(() => {
1073
+ async function handle() {
1074
+ switch (state) {
1075
+ case "closed":
1076
+ await (onClosed == null ? void 0 : onClosed());
1077
+ break;
1078
+ case "opening":
1079
+ await (onOpening == null ? void 0 : onOpening());
1080
+ setState("open");
1081
+ break;
1082
+ case "open":
1083
+ await (onOpen == null ? void 0 : onOpen());
1084
+ break;
1085
+ case "closing":
1086
+ await (onClosing == null ? void 0 : onClosing());
1087
+ setState("closed");
1088
+ break;
1089
+ }
1090
+ }
1091
+ handle().catch((error) => {
1092
+ console.error("Internal sheet state error:", error);
1093
+ });
1094
+ }, [state]);
1095
+ return state;
1096
+ }
1097
+
1098
+ // src/snap.ts
1099
+ function computeSnapPoints({
1100
+ snapPointsProp,
1101
+ sheetHeight
1102
+ }) {
1103
+ if (snapPointsProp[0] !== 0) {
1104
+ console.error(
1105
+ `First snap point should be 0 to ensure the sheet can be fully closed. Got: [${snapPointsProp.join(", ")}]`
1106
+ );
1107
+ snapPointsProp.unshift(0);
1108
+ }
1109
+ if (snapPointsProp[snapPointsProp.length - 1] !== 1) {
1110
+ console.error(
1111
+ `Last snap point should be 1 to ensure the sheet can be fully opened. Got: [${snapPointsProp.join(", ")}]`
1112
+ );
1113
+ snapPointsProp.push(1);
1114
+ }
1115
+ if (sheetHeight <= 0) {
1116
+ console.error(
1117
+ `Sheet height is ${sheetHeight}, cannot compute snap points. Make sure the sheet is mounted and has a valid height.`
1118
+ );
1119
+ return [];
1120
+ }
1121
+ const snapPointValues = snapPointsProp.map((point) => {
1122
+ if (point > 0 && point <= 1) {
1123
+ return Math.round(point * sheetHeight);
1124
+ }
1125
+ return point < 0 ? sheetHeight + point : point;
1126
+ });
1127
+ console.assert(
1128
+ isAscendingOrder(snapPointValues),
1129
+ `Snap points need to be in ascending order got: [${snapPointsProp.join(", ")}]`
1130
+ );
1131
+ snapPointValues.forEach((snap) => {
1132
+ if (snap < 0 || snap > sheetHeight) {
1133
+ console.warn(
1134
+ `Snap point ${snap} is outside of the sheet height ${sheetHeight}. This can cause unexpected behavior. Consider adjusting your snap points.`
1135
+ );
1136
+ }
1137
+ });
1138
+ if (!snapPointValues.includes(sheetHeight)) {
1139
+ console.warn(
1140
+ "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."
1141
+ );
1142
+ snapPointValues.push(sheetHeight);
1143
+ }
1144
+ return snapPointValues.map((snap, index) => ({
1145
+ snapIndex: index,
1146
+ snapValue: snap,
1147
+ // Absolute value from the bottom of the sheet
1148
+ snapValueY: sheetHeight - snap
1149
+ // Y value is inverted as `y = 0` means sheet is at the top
1150
+ }));
1151
+ }
1152
+ function findClosestSnapPoint({
1153
+ snapPoints,
1154
+ currentY
1155
+ }) {
1156
+ return snapPoints.reduce(
1157
+ (closest, snap) => Math.abs(snap.snapValueY - currentY) < Math.abs(closest.snapValueY - currentY) ? snap : closest
1158
+ );
1159
+ }
1160
+ function findNextSnapPointInDirection({
1161
+ y,
1162
+ snapPoints,
1163
+ dragDirection
1164
+ }) {
1165
+ if (dragDirection === "down") {
1166
+ return snapPoints.slice().reverse().find((s) => s.snapValueY > y);
1167
+ } else {
1168
+ return snapPoints.find((s) => s.snapValueY < y);
1169
+ }
1170
+ }
1171
+ function handleHighVelocityDrag({
1172
+ dragDirection,
1173
+ snapPoints
1174
+ }) {
1175
+ const bottomSnapPoint = snapPoints[0];
1176
+ const topSnapPoint = snapPoints[snapPoints.length - 1];
1177
+ if (dragDirection === "down") {
1178
+ return {
1179
+ yTo: bottomSnapPoint.snapValueY,
1180
+ snapIndex: bottomSnapPoint.snapIndex
1181
+ };
1182
+ }
1183
+ return {
1184
+ yTo: topSnapPoint.snapValueY,
1185
+ snapIndex: topSnapPoint.snapIndex
1186
+ };
1187
+ }
1188
+ function handleLowVelocityDrag({
1189
+ currentSnapPoint,
1190
+ currentY,
1191
+ dragDirection,
1192
+ snapPoints,
1193
+ velocity
1194
+ }) {
1195
+ const closestSnapRelativeToCurrentY = findClosestSnapPoint({
1196
+ snapPoints,
1197
+ currentY
1198
+ });
1199
+ if (Math.abs(velocity) < 20) {
1200
+ return {
1201
+ yTo: closestSnapRelativeToCurrentY.snapValueY,
1202
+ snapIndex: closestSnapRelativeToCurrentY.snapIndex
1203
+ };
1204
+ }
1205
+ const nextSnapInDirectionRelativeToCurrentY = findNextSnapPointInDirection({
1206
+ y: currentY,
1207
+ snapPoints,
1208
+ dragDirection
1209
+ });
1210
+ if (nextSnapInDirectionRelativeToCurrentY) {
1211
+ return {
1212
+ yTo: nextSnapInDirectionRelativeToCurrentY.snapValueY,
1213
+ snapIndex: nextSnapInDirectionRelativeToCurrentY.snapIndex
1214
+ };
1215
+ }
1216
+ return {
1217
+ yTo: currentSnapPoint.snapValueY,
1218
+ snapIndex: currentSnapPoint.snapIndex
1219
+ };
1220
+ }
1221
+
1222
+ // src/sheet.tsx
1223
+ var Sheet = forwardRef(
1224
+ ({
1225
+ avoidKeyboard = true,
1226
+ children,
1227
+ className = "",
1228
+ detent = "default",
1229
+ disableDismiss = false,
1230
+ disableDrag: disableDragProp = false,
1231
+ disableScrollLocking = false,
1232
+ dragCloseThreshold = DEFAULT_DRAG_CLOSE_THRESHOLD,
1233
+ dragVelocityThreshold = DEFAULT_DRAG_VELOCITY_THRESHOLD,
1234
+ initialSnap,
1235
+ isOpen,
1236
+ modalEffectRootId,
1237
+ modalEffectThreshold,
1238
+ mountPoint,
1239
+ prefersReducedMotion = false,
1240
+ snapPoints: snapPointsProp,
1241
+ style,
1242
+ tweenConfig = DEFAULT_TWEEN_CONFIG,
1243
+ unstyled = false,
1244
+ onOpenStart,
1245
+ onOpenEnd,
1246
+ onClose,
1247
+ onCloseStart,
1248
+ onCloseEnd,
1249
+ onSnap,
1250
+ onDrag: onDragProp,
1251
+ onDragStart: onDragStartProp,
1252
+ onDragEnd: onDragEndProp,
1253
+ ...rest
1254
+ }, ref) => {
1255
+ const [sheetBoundsRef, sheetBounds] = useMeasure();
1256
+ const sheetRef = useRef(null);
1257
+ const sheetHeight = Math.round(sheetBounds.height);
1258
+ const [currentSnap, setCurrentSnap] = useState(initialSnap);
1259
+ const snapPoints = snapPointsProp && sheetHeight > 0 ? computeSnapPoints({ sheetHeight, snapPointsProp }) : [];
1260
+ const { windowHeight } = useDimensions();
1261
+ const closedY = sheetHeight > 0 ? sheetHeight : windowHeight;
1262
+ const y = useMotionValue(closedY);
1263
+ const yInverted = useTransform(y, (val) => Math.max(sheetHeight - val, 0));
1264
+ const indicatorRotation = useMotionValue(0);
1265
+ const shouldReduceMotion = useReducedMotion();
1266
+ const reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
1267
+ const animationOptions = {
1268
+ type: "tween",
1269
+ ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
1270
+ };
1271
+ const zIndex = useTransform(
1272
+ y,
1273
+ (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
1274
+ );
1275
+ const visibility = useTransform(
1276
+ y,
1277
+ (val) => val + 2 >= closedY ? "hidden" : "visible"
1278
+ );
1279
+ const updateSnap = useStableCallback((snapIndex) => {
1280
+ setCurrentSnap(snapIndex);
1281
+ onSnap == null ? void 0 : onSnap(snapIndex);
1282
+ });
1283
+ const getSnapPoint = useStableCallback((snapIndex) => {
1284
+ if (snapPointsProp && snapPoints) {
1285
+ if (snapIndex < 0 || snapIndex >= snapPoints.length) {
1286
+ console.warn(
1287
+ `Invalid snap index ${snapIndex}. Snap points are: [${snapPointsProp.join(", ")}] and their computed values are: [${snapPoints.map((point) => point.snapValue).join(", ")}]`
1288
+ );
1289
+ return null;
1290
+ }
1291
+ return snapPoints[snapIndex];
1292
+ }
1293
+ return null;
1294
+ });
1295
+ const snapTo = useStableCallback(async (snapIndex) => {
1296
+ if (!snapPointsProp) {
1297
+ console.warn("Snapping is not possible without `snapPoints` prop.");
1298
+ return;
1299
+ }
1300
+ const snapPoint = getSnapPoint(snapIndex);
1301
+ if (snapPoint === null) {
1302
+ console.warn(`Invalid snap index ${snapIndex}.`);
1303
+ return;
1304
+ }
1305
+ if (snapIndex === 0) {
1306
+ onClose();
1307
+ return;
1308
+ }
1309
+ await animate(y, snapPoint.snapValueY, {
1310
+ ...animationOptions,
1311
+ onComplete: () => updateSnap(snapIndex)
1312
+ });
1313
+ });
1314
+ const keyboard = useKeyboardAvoidance({
1315
+ isEnabled: isOpen && avoidKeyboard,
1316
+ containerRef: sheetRef,
1317
+ onWillOpenKeyboard: async () => {
1318
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
1319
+ if (lastSnapPoint && lastSnapPoint.snapIndex !== currentSnap) {
1320
+ await animate(y, lastSnapPoint.snapValueY, animationOptions);
1321
+ updateSnap(lastSnapPoint.snapIndex);
1322
+ }
1323
+ },
1324
+ onDidOpenKeyboard: (focusedElement) => {
1325
+ const sheetElement = sheetRef.current;
1326
+ if (!sheetElement) return;
1327
+ const inputRect = focusedElement.getBoundingClientRect();
1328
+ const containerRect = sheetElement.getBoundingClientRect();
1329
+ const scroller = sheetElement.querySelector(
1330
+ ".react-modal-sheet-content-scroller"
1331
+ );
1332
+ const scrollTarget = Math.max(
1333
+ inputRect.top - containerRect.top + scroller.scrollTop - inputRect.height,
1334
+ 0
1335
+ );
1336
+ requestAnimationFrame(() => {
1337
+ scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1338
+ });
1339
+ }
1340
+ });
1341
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1342
+ const blurActiveInput = useStableCallback(() => {
1343
+ var _a;
1344
+ const focusedElement = document.activeElement;
1345
+ if (focusedElement && willOpenKeyboard(focusedElement) && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
1346
+ focusedElement.blur();
1347
+ }
1348
+ });
1349
+ const onDragStart = useStableCallback((event, info) => {
1350
+ blurActiveInput();
1351
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1352
+ });
1353
+ const onDrag = useStableCallback((event, info) => {
1354
+ onDragProp == null ? void 0 : onDragProp(event, info);
1355
+ const currentY = y.get();
1356
+ const velocity = y.getVelocity();
1357
+ if (velocity > 0) indicatorRotation.set(10);
1358
+ if (velocity < 0) indicatorRotation.set(-10);
1359
+ y.set(Math.max(currentY + info.delta.y, 0));
1360
+ });
1361
+ const onDragEnd = useStableCallback((event, info) => {
1362
+ blurActiveInput();
1363
+ onDragEndProp == null ? void 0 : onDragEndProp(event, info);
1364
+ const currentY = y.get();
1365
+ let yTo = 0;
1366
+ const currentSnapPoint = currentSnap !== void 0 ? getSnapPoint(currentSnap) : null;
1367
+ if (currentSnapPoint) {
1368
+ const dragOffsetDirection = info.offset.y > 0 ? "down" : "up";
1369
+ const dragVelocityDirection = info.velocity.y > 0 ? "down" : "up";
1370
+ const isHighVelocity = Math.abs(info.velocity.y) > dragVelocityThreshold;
1371
+ let result;
1372
+ if (isHighVelocity) {
1373
+ result = handleHighVelocityDrag({
1374
+ snapPoints,
1375
+ dragDirection: dragVelocityDirection
1376
+ });
1377
+ } else {
1378
+ result = handleLowVelocityDrag({
1379
+ currentSnapPoint,
1380
+ currentY,
1381
+ dragDirection: dragOffsetDirection,
1382
+ snapPoints,
1383
+ velocity: info.velocity.y
1384
+ });
1385
+ }
1386
+ yTo = result.yTo;
1387
+ if (disableDismiss && yTo + 1 >= sheetHeight) {
1388
+ const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1389
+ if (bottomSnapPoint) {
1390
+ yTo = bottomSnapPoint.snapValueY;
1391
+ updateSnap(bottomSnapPoint.snapIndex);
1392
+ } else {
1393
+ yTo = currentY;
1394
+ }
1395
+ } else if (result.snapIndex !== void 0) {
1396
+ updateSnap(result.snapIndex);
1397
+ }
1398
+ } else if (info.velocity.y > dragVelocityThreshold || currentY > sheetHeight * dragCloseThreshold) {
1399
+ if (disableDismiss) {
1400
+ yTo = 0;
1401
+ } else {
1402
+ yTo = closedY;
1403
+ }
1404
+ }
1405
+ animate(y, yTo, animationOptions);
1406
+ if (yTo + 1 >= sheetHeight && !disableDismiss) {
1407
+ onClose();
1408
+ }
1409
+ indicatorRotation.set(0);
1410
+ });
1411
+ useImperativeHandle(ref, () => ({
1412
+ y,
1413
+ yInverted,
1414
+ height: sheetHeight,
1415
+ snapTo
1416
+ }));
1417
+ useModalEffect({
1418
+ y,
1419
+ detent,
1420
+ sheetHeight,
1421
+ snapPoints,
1422
+ rootId: modalEffectRootId,
1423
+ startThreshold: modalEffectThreshold
1424
+ });
1425
+ usePreventScroll({
1426
+ isDisabled: disableScrollLocking || !isOpen
1427
+ });
1428
+ const state = useSheetState({
1429
+ isOpen,
1430
+ onOpen: async () => {
1431
+ onOpenStart == null ? void 0 : onOpenStart();
1432
+ await waitForElement("react-modal-sheet-container");
1433
+ const initialSnapPoint = initialSnap !== void 0 ? getSnapPoint(initialSnap) : null;
1434
+ const yTo = (initialSnapPoint == null ? void 0 : initialSnapPoint.snapValueY) ?? 0;
1435
+ await animate(y, yTo, animationOptions);
1436
+ if (initialSnap !== void 0) {
1437
+ updateSnap(initialSnap);
1438
+ }
1439
+ onOpenEnd == null ? void 0 : onOpenEnd();
1440
+ },
1441
+ onClosing: async () => {
1442
+ onCloseStart == null ? void 0 : onCloseStart();
1443
+ await animate(y, closedY, animationOptions);
1444
+ onCloseEnd == null ? void 0 : onCloseEnd();
1445
+ }
1446
+ });
1447
+ const dragProps = {
1448
+ drag: "y",
1449
+ dragElastic: 0,
1450
+ dragMomentum: false,
1451
+ dragPropagation: false,
1452
+ onDrag,
1453
+ onDragStart,
1454
+ onDragEnd
1455
+ };
1456
+ const context = {
1457
+ currentSnap,
1458
+ detent,
1459
+ disableDrag,
1460
+ dragProps,
1461
+ indicatorRotation,
1462
+ avoidKeyboard,
1463
+ prefersReducedMotion,
1464
+ sheetBoundsRef,
1465
+ sheetRef,
1466
+ unstyled,
1467
+ y
1468
+ };
1469
+ const sheet = /* @__PURE__ */ React4.createElement(SheetContext.Provider, { value: context }, /* @__PURE__ */ React4.createElement(
1470
+ motion.div,
1471
+ {
1472
+ ...rest,
1473
+ ref,
1474
+ "data-sheet-state": state,
1475
+ className: `react-modal-sheet-root ${className}`,
1476
+ style: {
1477
+ ...applyStyles(styles.root, unstyled),
1478
+ zIndex,
1479
+ visibility,
1480
+ ...style
1481
+ }
1482
+ },
1483
+ state !== "closed" ? children : null
1484
+ ));
1485
+ if (IS_SSR) return sheet;
1486
+ return createPortal(sheet, mountPoint ?? document.body);
1487
+ }
1488
+ );
1489
+ Sheet.displayName = "Sheet";
1490
+
1491
+ // src/index.tsx
1492
+ var Sheet2 = Object.assign(Sheet, {
1493
+ Container: SheetContainer,
1494
+ Header: SheetHeader,
1495
+ DragIndicator: SheetDragIndicator,
1496
+ Content: SheetContent,
1497
+ Backdrop: SheetBackdrop
1498
+ });
1499
+
1500
+ export { Sheet2 as Sheet, useScrollPosition, useVirtualKeyboard };
1501
+ //# sourceMappingURL=index.mjs.map
1502
+ //# sourceMappingURL=index.mjs.map