react-modal-sheet 4.4.0 → 5.1.0

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