react-modal-sheet 3.2.0 → 3.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,7 +1,6 @@
1
1
  import React2, { createContext, forwardRef, useRef, useEffect, useImperativeHandle, useMemo, Children, cloneElement, useLayoutEffect, useCallback, useState, useContext } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
- import { useMotionValue, useReducedMotion, useTransform, animate, motion, AnimatePresence } from 'framer-motion';
4
- import { useLayoutEffect as useLayoutEffect$1, isIOS, chain, getScrollParent } from '@react-aria/utils';
3
+ import { useMotionValue, useReducedMotion, useTransform, animate, motion, AnimatePresence, transform } from 'framer-motion';
5
4
 
6
5
  // src/sheet.tsx
7
6
 
@@ -16,107 +15,88 @@ var REDUCED_MOTION_TWEEN_CONFIG = {
16
15
  ease: "linear",
17
16
  duration: 0.01
18
17
  };
19
- var DRAG_CLOSE_THRESHOLD = 0.6;
20
- var DRAG_VELOCITY_THRESHOLD = 500;
18
+ var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
19
+ var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
21
20
 
22
- // src/utils.ts
23
- function getClosest(nums, goal) {
24
- let closest = nums[0];
25
- let minDifference = Math.abs(nums[0] - goal);
26
- for (let i = 1; i < nums.length; i++) {
27
- const difference = Math.abs(nums[i] - goal);
28
- if (difference < minDifference) {
29
- closest = nums[i];
30
- minDifference = difference;
31
- }
32
- }
33
- return closest;
34
- }
35
- function applyRootStyles(rootId) {
36
- const body = document.querySelector("body");
37
- const root = document.querySelector(`#${rootId}`);
38
- if (root) {
39
- const p = 24;
40
- const h = window.innerHeight;
41
- const s = (h - p) / h;
21
+ // src/hooks.tsx
22
+ var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
23
+ function useModalEffect({
24
+ y,
25
+ rootId,
26
+ sheetRef
27
+ }) {
28
+ const heightRef = useRef(IS_SSR ? 0 : window.innerHeight);
29
+ function setup() {
30
+ const root = document.querySelector(`#${rootId}`);
31
+ const body = document.querySelector("body");
32
+ if (!root)
33
+ return;
42
34
  body.style.backgroundColor = "#000";
43
35
  root.style.overflow = "hidden";
44
- root.style.willChange = "transform";
45
- root.style.transition = "transform 200ms ease-in-out, border-radius 200ms linear";
46
- root.style.transform = `translateY(calc(env(safe-area-inset-top) + ${p / 2}px)) scale(${s})`;
47
- root.style.borderTopRightRadius = "10px";
48
- root.style.borderTopLeftRadius = "10px";
36
+ root.style.transitionTimingFunction = "cubic-bezier(0.32, 0.72, 0, 1)";
37
+ root.style.transitionProperty = "transform, border-radius";
38
+ root.style.transitionDuration = "0.5s";
39
+ root.style.transformOrigin = "center top";
49
40
  }
50
- }
51
- function cleanupRootStyles(rootId) {
52
- const body = document.querySelector("body");
53
- const root = document.getElementById(rootId);
54
- function onTransitionEnd() {
55
- root.style.removeProperty("overflow");
56
- root.style.removeProperty("will-change");
57
- root.style.removeProperty("transition");
58
- body.style.removeProperty("background-color");
59
- root.removeEventListener("transitionend", onTransitionEnd);
60
- }
61
- if (root) {
62
- root.style.removeProperty("border-top-right-radius");
63
- root.style.removeProperty("border-top-left-radius");
64
- root.style.removeProperty("transform");
65
- root.addEventListener("transitionend", onTransitionEnd);
66
- }
67
- }
68
- function inDescendingOrder(arr) {
69
- for (let i = 0; i < arr.length; i++) {
70
- if (arr[i + 1] > arr[i])
71
- return false;
72
- }
73
- return true;
74
- }
75
- function validateSnapTo({
76
- snapTo,
77
- sheetHeight
78
- }) {
79
- if (snapTo < 0) {
80
- console.warn(
81
- `Snap point is out of bounds. Sheet height is ${sheetHeight} but snap point is ${sheetHeight + Math.abs(snapTo)}.`
82
- );
41
+ function cleanup() {
42
+ const root = document.querySelector(`#${rootId}`);
43
+ const body = document.querySelector("body");
44
+ if (!root)
45
+ return;
46
+ setTimeout(() => {
47
+ body.style.removeProperty("background-color");
48
+ root.style.removeProperty("overflow");
49
+ root.style.removeProperty("transition-timing-function");
50
+ root.style.removeProperty("transition-property");
51
+ root.style.removeProperty("transition-duration");
52
+ root.style.removeProperty("transform-origin");
53
+ root.style.removeProperty("transform");
54
+ root.style.removeProperty("border-top-right-radius");
55
+ root.style.removeProperty("border-top-left-radius");
56
+ }, 100);
83
57
  }
84
- return Math.max(Math.round(snapTo), 0);
85
- }
86
- function mergeRefs(refs) {
87
- return (value) => {
88
- refs.forEach((ref) => {
89
- if (typeof ref === "function") {
90
- ref(value);
91
- } else if (ref) {
92
- ref.current = value;
93
- }
94
- });
95
- };
96
- }
97
- function isTouchDevice() {
98
- if (typeof window === "undefined")
99
- return false;
100
- return "ontouchstart" in window || navigator.maxTouchPoints > 0;
101
- }
102
-
103
- // src/hooks.tsx
104
- var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
105
- function useModalEffect(isOpen, rootId) {
106
- const prevOpen = usePrevious(isOpen);
58
+ useIsomorphicLayoutEffect(() => {
59
+ return () => {
60
+ if (rootId)
61
+ cleanup();
62
+ };
63
+ }, []);
107
64
  useEffect(() => {
108
- if (rootId && !prevOpen && isOpen) {
109
- applyRootStyles(rootId);
110
- } else if (rootId && !isOpen && prevOpen) {
111
- cleanupRootStyles(rootId);
65
+ const root = document.querySelector(`#${rootId}`);
66
+ if (!root)
67
+ return;
68
+ function onCompleted() {
69
+ if (y.get() - 10 >= heightRef.current)
70
+ cleanup();
112
71
  }
113
- }, [isOpen, prevOpen]);
114
- useEffect(() => {
72
+ const removeStartListener = y.on("animationStart", () => {
73
+ var _a;
74
+ heightRef.current = ((_a = sheetRef.current) == null ? void 0 : _a.offsetHeight) || window.innerHeight;
75
+ setup();
76
+ });
77
+ const removeChangeListener = y.on("change", (value) => {
78
+ if (root) {
79
+ const progress = Math.max(0, 1 - value / heightRef.current);
80
+ const pageWidth = window.innerWidth;
81
+ const scale = (pageWidth - 16) / pageWidth;
82
+ const ty = transform(progress, [0, 1], [0, 24]);
83
+ const s = transform(progress, [0, 1], [1, scale]);
84
+ const borderRadius = transform(progress, [0, 1], [0, 10]);
85
+ const inset = "env(safe-area-inset-top)";
86
+ root.style.transform = `scale(${s}) translate3d(0, calc(${inset} + ${ty}px), 0)`;
87
+ root.style.borderTopRightRadius = `${borderRadius}px`;
88
+ root.style.borderTopLeftRadius = `${borderRadius}px`;
89
+ }
90
+ });
91
+ const removeCompleteListener = y.on("animationComplete", onCompleted);
92
+ const removeCancelListener = y.on("animationCancel", onCompleted);
115
93
  return () => {
116
- if (rootId && isOpen)
117
- cleanupRootStyles(rootId);
94
+ removeStartListener();
95
+ removeChangeListener();
96
+ removeCompleteListener();
97
+ removeCancelListener();
118
98
  };
119
- }, [isOpen]);
99
+ }, [y, rootId]);
120
100
  }
121
101
  function useEventCallbacks(isOpen, callbacks) {
122
102
  const prevOpen = usePrevious(isOpen);
@@ -142,7 +122,10 @@ function useEventCallbacks(isOpen, callbacks) {
142
122
  return { handleAnimationComplete };
143
123
  }
144
124
  function useDimensions() {
145
- const [dimensions, setDimensions] = useState({ height: 0, width: 0 });
125
+ const [dimensions, setDimensions] = useState({
126
+ height: !IS_SSR ? window.innerHeight : 0,
127
+ width: !IS_SSR ? window.innerWidth : 0
128
+ });
146
129
  useIsomorphicLayoutEffect(() => {
147
130
  function handler() {
148
131
  setDimensions({
@@ -218,7 +201,120 @@ var useSheetScrollerContext = () => {
218
201
  throw Error("Sheet scroller context error");
219
202
  return context;
220
203
  };
221
- var visualViewport = typeof window !== "undefined" ? window.visualViewport : void 0;
204
+
205
+ // src/utils.ts
206
+ function getClosest(nums, goal) {
207
+ let closest = nums[0];
208
+ let minDifference = Math.abs(nums[0] - goal);
209
+ for (let i = 1; i < nums.length; i++) {
210
+ const difference = Math.abs(nums[i] - goal);
211
+ if (difference < minDifference) {
212
+ closest = nums[i];
213
+ minDifference = difference;
214
+ }
215
+ }
216
+ return closest;
217
+ }
218
+ function inDescendingOrder(arr) {
219
+ for (let i = 0; i < arr.length; i++) {
220
+ if (arr[i + 1] > arr[i])
221
+ return false;
222
+ }
223
+ return true;
224
+ }
225
+ function validateSnapTo({
226
+ snapTo,
227
+ sheetHeight
228
+ }) {
229
+ if (snapTo < 0) {
230
+ console.warn(
231
+ `Snap point is out of bounds. Sheet height is ${sheetHeight} but snap point is ${sheetHeight + Math.abs(snapTo)}.`
232
+ );
233
+ }
234
+ return Math.max(Math.round(snapTo), 0);
235
+ }
236
+ function mergeRefs(refs) {
237
+ return (value) => {
238
+ refs.forEach((ref) => {
239
+ if (typeof ref === "function") {
240
+ ref(value);
241
+ } else if (ref) {
242
+ ref.current = value;
243
+ }
244
+ });
245
+ };
246
+ }
247
+ function isTouchDevice() {
248
+ if (typeof window === "undefined")
249
+ return false;
250
+ return "ontouchstart" in window || navigator.maxTouchPoints > 0;
251
+ }
252
+ function testPlatform(re) {
253
+ var _a;
254
+ return typeof window !== "undefined" && window.navigator != null ? re.test(
255
+ // @ts-expect-error
256
+ // eslint-disable-next-line @typescript-eslint/dot-notation
257
+ ((_a = window.navigator["userAgentData"]) == null ? void 0 : _a.platform) || window.navigator.platform
258
+ ) : false;
259
+ }
260
+ function cached(fn) {
261
+ let res = null;
262
+ return () => {
263
+ if (res == null) {
264
+ res = fn();
265
+ }
266
+ return res;
267
+ };
268
+ }
269
+ var isMac = cached(function() {
270
+ return testPlatform(/^Mac/i);
271
+ });
272
+ var isIPhone = cached(function() {
273
+ return testPlatform(/^iPhone/i);
274
+ });
275
+ var isIPad = cached(function() {
276
+ return testPlatform(/^iPad/i) || // iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
277
+ isMac() && navigator.maxTouchPoints > 1;
278
+ });
279
+ var isIOS = cached(function() {
280
+ return isIPhone() || isIPad();
281
+ });
282
+
283
+ // src/use-prevent-scroll.ts
284
+ var KEYBOARD_BUFFER = 24;
285
+ function chain(...callbacks) {
286
+ return (...args) => {
287
+ for (const callback of callbacks) {
288
+ if (typeof callback === "function") {
289
+ callback(...args);
290
+ }
291
+ }
292
+ };
293
+ }
294
+ var visualViewport = typeof document !== "undefined" && window.visualViewport;
295
+ function isScrollable(node, checkForOverflow) {
296
+ if (!node) {
297
+ return false;
298
+ }
299
+ const style = window.getComputedStyle(node);
300
+ let isScrollable2 = /(auto|scroll)/.test(
301
+ style.overflow + style.overflowX + style.overflowY
302
+ );
303
+ if (isScrollable2 && checkForOverflow) {
304
+ isScrollable2 = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
305
+ }
306
+ return isScrollable2;
307
+ }
308
+ function getScrollParent(node, checkForOverflow) {
309
+ let scrollableNode = node;
310
+ if (isScrollable(scrollableNode, checkForOverflow)) {
311
+ scrollableNode = scrollableNode.parentElement;
312
+ }
313
+ while (scrollableNode && !isScrollable(scrollableNode, checkForOverflow)) {
314
+ scrollableNode = scrollableNode.parentElement;
315
+ }
316
+ return scrollableNode || document.scrollingElement || document.documentElement;
317
+ }
222
318
  var nonTextInputTypes = /* @__PURE__ */ new Set([
223
319
  "checkbox",
224
320
  "radio",
@@ -234,7 +330,7 @@ var preventScrollCount = 0;
234
330
  var restore;
235
331
  function usePreventScroll(options = {}) {
236
332
  const { isDisabled } = options;
237
- useLayoutEffect$1(() => {
333
+ useIsomorphicLayoutEffect(() => {
238
334
  if (isDisabled) {
239
335
  return;
240
336
  }
@@ -249,7 +345,7 @@ function usePreventScroll(options = {}) {
249
345
  return () => {
250
346
  preventScrollCount--;
251
347
  if (preventScrollCount === 0) {
252
- restore();
348
+ restore == null ? void 0 : restore();
253
349
  }
254
350
  };
255
351
  }, [isDisabled]);
@@ -268,7 +364,7 @@ function preventScrollMobileSafari() {
268
364
  let scrollable;
269
365
  let lastY = 0;
270
366
  const onTouchStart = (e) => {
271
- scrollable = getScrollParent(e.target);
367
+ scrollable = getScrollParent(e.target, true);
272
368
  if (scrollable === document.documentElement && scrollable === document.body) {
273
369
  return;
274
370
  }
@@ -278,7 +374,7 @@ function preventScrollMobileSafari() {
278
374
  if (scrollable === void 0) {
279
375
  return;
280
376
  }
281
- if (scrollable === document.documentElement || scrollable === document.body) {
377
+ if (!scrollable || scrollable === document.documentElement || scrollable === document.body) {
282
378
  e.preventDefault();
283
379
  return;
284
380
  }
@@ -318,9 +414,7 @@ function preventScrollMobileSafari() {
318
414
  } else {
319
415
  visualViewport.addEventListener(
320
416
  "resize",
321
- () => {
322
- scrollIntoView(target);
323
- },
417
+ () => scrollIntoView(target),
324
418
  { once: true }
325
419
  );
326
420
  }
@@ -385,7 +479,9 @@ function scrollIntoView(target) {
385
479
  if (scrollable !== document.documentElement && scrollable !== document.body && scrollable !== target) {
386
480
  const scrollableTop = scrollable.getBoundingClientRect().top;
387
481
  const targetTop = target.getBoundingClientRect().top;
388
- if (targetTop > scrollableTop + target.clientHeight) {
482
+ const targetBottom = target.getBoundingClientRect().bottom;
483
+ const keyboardHeight = scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER;
484
+ if (targetBottom > keyboardHeight) {
389
485
  scrollable.scrollTop += targetTop - scrollableTop;
390
486
  }
391
487
  }
@@ -476,7 +572,7 @@ var Sheet = forwardRef(
476
572
  children,
477
573
  disableScrollLocking = false,
478
574
  isOpen,
479
- snapPoints,
575
+ snapPoints: snapPointsProp,
480
576
  rootId,
481
577
  mountPoint,
482
578
  style,
@@ -485,6 +581,8 @@ var Sheet = forwardRef(
485
581
  disableDrag = false,
486
582
  prefersReducedMotion = false,
487
583
  tweenConfig = DEFAULT_TWEEN_CONFIG,
584
+ dragVelocityThreshold = DEFAULT_DRAG_VELOCITY_THRESHOLD,
585
+ dragCloseThreshold = DEFAULT_DRAG_CLOSE_THRESHOLD,
488
586
  ...rest
489
587
  }, ref) => {
490
588
  const sheetRef = useRef(null);
@@ -496,7 +594,7 @@ var Sheet = forwardRef(
496
594
  type: "tween",
497
595
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
498
596
  };
499
- const y = useMotionValue(0);
597
+ const y = useMotionValue(windowHeight);
500
598
  const zIndex = useTransform(
501
599
  y,
502
600
  (value) => value + 2 >= windowHeight ? -1 : 9999999
@@ -519,6 +617,7 @@ var Sheet = forwardRef(
519
617
  onCloseEnd
520
618
  };
521
619
  });
620
+ let snapPoints = snapPointsProp;
522
621
  if (snapPoints) {
523
622
  snapPoints = snapPoints.map((point) => {
524
623
  if (point > 0 && point <= 1)
@@ -550,7 +649,7 @@ var Sheet = forwardRef(
550
649
  }
551
650
  });
552
651
  const onDragEnd = useEffectEvent((_, { velocity }) => {
553
- if (velocity.y > DRAG_VELOCITY_THRESHOLD) {
652
+ if (velocity.y > dragVelocityThreshold) {
554
653
  onClose();
555
654
  } else {
556
655
  const sheetHeight = sheetRef.current.getBoundingClientRect().height;
@@ -564,7 +663,7 @@ var Sheet = forwardRef(
564
663
  snapToValues.unshift(0);
565
664
  }
566
665
  snapTo = getClosest(snapToValues, currentY);
567
- } else if (currentY / sheetHeight > DRAG_CLOSE_THRESHOLD) {
666
+ } else if (currentY / sheetHeight > dragCloseThreshold) {
568
667
  snapTo = sheetHeight;
569
668
  }
570
669
  snapTo = validateSnapTo({ snapTo, sheetHeight });
@@ -606,7 +705,11 @@ var Sheet = forwardRef(
606
705
  }
607
706
  }
608
707
  }));
609
- useModalEffect(isOpen, rootId);
708
+ useModalEffect({
709
+ y,
710
+ rootId,
711
+ sheetRef
712
+ });
610
713
  usePreventScroll({ isDisabled: disableScrollLocking || !isOpen });
611
714
  const dragProps = useMemo(() => {
612
715
  const dragProps2 = {
@@ -786,8 +889,8 @@ var SheetScroller = forwardRef(
786
889
  const sheetScrollerContext = useSheetScrollerContext();
787
890
  function determineDragState(element) {
788
891
  const { scrollTop, scrollHeight, clientHeight } = element;
789
- const isScrollable = scrollHeight > clientHeight;
790
- if (!isScrollable)
892
+ const isScrollable2 = scrollHeight > clientHeight;
893
+ if (!isScrollable2)
791
894
  return;
792
895
  const isAtTop = scrollTop <= 0;
793
896
  const isAtBottom = scrollHeight - scrollTop === clientHeight;