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