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