react-modal-sheet 1.6.3 → 1.8.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.
@@ -1,7 +1,7 @@
1
- import { useState, useEffect, useLayoutEffect, createContext, useContext, useRef, useCallback, forwardRef, useImperativeHandle, createElement, Children, cloneElement } from 'react';
1
+ import { useCallback, useEffect, useLayoutEffect, useRef, useState, createContext, useContext, forwardRef, useImperativeHandle, useMemo, createElement, Children, cloneElement } from 'react';
2
2
  import { createPortal } from 'react-dom';
3
3
  import { usePreventScroll } from '@react-aria/overlays';
4
- import { useMotionValue, animate, AnimatePresence, motion, useTransform } from 'framer-motion';
4
+ import { useMotionValue, useReducedMotion, animate, AnimatePresence, motion, useTransform } from 'framer-motion';
5
5
  import mergeRefs from 'react-merge-refs';
6
6
 
7
7
  function _extends() {
@@ -37,12 +37,21 @@ function _objectWithoutPropertiesLoose(source, excluded) {
37
37
  return target;
38
38
  }
39
39
 
40
- var isSSR = typeof window === 'undefined';
41
- var getClosest = function getClosest(nums, goal) {
40
+ var MAX_HEIGHT = 'calc(100% - env(safe-area-inset-top) - 34px)';
41
+ var IS_SSR = typeof window === 'undefined';
42
+ var DEFAULT_SPRING_CONFIG = {
43
+ stiffness: 300,
44
+ damping: 30,
45
+ mass: 0.2
46
+ };
47
+ var DRAG_CLOSE_THRESHOLD = 0.6;
48
+ var DRAG_VELOCITY_THRESHOLD = 500;
49
+
50
+ function getClosest(nums, goal) {
42
51
  return nums.reduce(function (prev, curr) {
43
52
  return Math.abs(curr - goal) < Math.abs(prev - goal) ? curr : prev;
44
53
  });
45
- };
54
+ }
46
55
  var highlightId = 'react-modal-sheet-highlight';
47
56
  function applyRootStyles(rootId) {
48
57
  var body = document.querySelector('body');
@@ -104,40 +113,25 @@ function cleanupRootStyles(rootId) {
104
113
  root.addEventListener('transitionend', onTransitionEnd);
105
114
  }
106
115
  }
107
- var useIsomorphicLayoutEffect = isSSR ? useEffect : useLayoutEffect;
108
- function useWindowHeight() {
109
- var _useState = useState(0),
110
- windowHeight = _useState[0],
111
- setWindowHeight = _useState[1];
112
-
113
- useIsomorphicLayoutEffect(function () {
114
- var updateHeight = function updateHeight() {
115
- return setWindowHeight(window.innerHeight);
116
- };
117
-
118
- window.addEventListener('resize', updateHeight);
119
- updateHeight();
120
- return function () {
121
- return window.removeEventListener('resize', updateHeight);
122
- };
123
- }, []);
124
- return windowHeight;
125
- }
126
- var inDescendingOrder = function inDescendingOrder(arr) {
116
+ function inDescendingOrder(arr) {
127
117
  for (var i = 0; i < arr.length; i++) {
128
118
  if (arr[i + 1] > arr[i]) return false;
129
119
  }
130
120
 
131
121
  return true;
132
- };
122
+ }
123
+ function validateSnapTo(_ref) {
124
+ var snapTo = _ref.snapTo,
125
+ sheetHeight = _ref.sheetHeight;
133
126
 
134
- var SheetContext = /*#__PURE__*/createContext(undefined);
135
- var useSheetContext = function useSheetContext() {
136
- var context = useContext(SheetContext);
137
- if (!context) throw Error('Sheet context error');
138
- return context;
139
- };
127
+ if (snapTo < 0) {
128
+ console.warn("Snap point is out of bounds. Sheet height is " + sheetHeight + " but snap point is " + (sheetHeight + Math.abs(snapTo)) + ".");
129
+ }
140
130
 
131
+ return Math.max(Math.round(snapTo), 0);
132
+ }
133
+
134
+ var useIsomorphicLayoutEffect = IS_SSR ? useEffect : useLayoutEffect;
141
135
  var useModalEffect = function useModalEffect(isOpen, rootId) {
142
136
  var prevOpen = usePrevious(isOpen); // Automatically apply the iOS modal effect to the body when sheet opens/closes
143
137
 
@@ -184,13 +178,54 @@ var useEventCallbacks = function useEventCallbacks(isOpen, callbacks) {
184
178
  handleAnimationComplete: handleAnimationComplete
185
179
  };
186
180
  };
181
+ function useWindowHeight() {
182
+ var _React$useState = useState(0),
183
+ windowHeight = _React$useState[0],
184
+ setWindowHeight = _React$useState[1];
187
185
 
188
- var usePrevious = function usePrevious(state) {
186
+ useIsomorphicLayoutEffect(function () {
187
+ var updateHeight = function updateHeight() {
188
+ return setWindowHeight(window.innerHeight);
189
+ };
190
+
191
+ window.addEventListener('resize', updateHeight);
192
+ updateHeight();
193
+ return function () {
194
+ return window.removeEventListener('resize', updateHeight);
195
+ };
196
+ }, []);
197
+ return windowHeight;
198
+ }
199
+ function usePrevious(state) {
189
200
  var ref = useRef();
190
201
  useEffect(function () {
191
202
  ref.current = state;
192
203
  });
193
204
  return ref.current;
205
+ } // Userland version of the `useEvent` React hook:
206
+ // RFC: https://github.com/reactjs/rfcs/blob/useevent/text/0000-useevent.md
207
+
208
+ function useEvent(handler) {
209
+ var handlerRef = useRef();
210
+ useIsomorphicLayoutEffect(function () {
211
+ handlerRef.current = handler;
212
+ });
213
+ return useCallback(function () {
214
+ var fn = handlerRef.current;
215
+
216
+ for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
217
+ args[_key] = arguments[_key];
218
+ }
219
+
220
+ return fn == null ? void 0 : fn.apply(void 0, args);
221
+ }, []);
222
+ }
223
+
224
+ var SheetContext = /*#__PURE__*/createContext(undefined);
225
+ var useSheetContext = function useSheetContext() {
226
+ var context = useContext(SheetContext);
227
+ if (!context) throw Error('Sheet context error');
228
+ return context;
194
229
  };
195
230
 
196
231
  var styles = {
@@ -253,56 +288,73 @@ var styles = {
253
288
  };
254
289
 
255
290
  var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
256
- var children = _ref.children,
257
- isOpen = _ref.isOpen,
258
- onClose = _ref.onClose,
259
- onOpenStart = _ref.onOpenStart,
291
+ var onOpenStart = _ref.onOpenStart,
260
292
  onOpenEnd = _ref.onOpenEnd,
293
+ onClose = _ref.onClose,
261
294
  onCloseStart = _ref.onCloseStart,
262
295
  onCloseEnd = _ref.onCloseEnd,
263
296
  onSnap = _ref.onSnap,
297
+ children = _ref.children,
298
+ isOpen = _ref.isOpen,
264
299
  snapPoints = _ref.snapPoints,
265
- _ref$initialSnap = _ref.initialSnap,
266
- initialSnap = _ref$initialSnap === void 0 ? 0 : _ref$initialSnap,
267
300
  rootId = _ref.rootId,
268
301
  mountPoint = _ref.mountPoint,
302
+ style = _ref.style,
303
+ _ref$detent = _ref.detent,
304
+ detent = _ref$detent === void 0 ? 'full-height' : _ref$detent,
305
+ _ref$initialSnap = _ref.initialSnap,
306
+ initialSnap = _ref$initialSnap === void 0 ? 0 : _ref$initialSnap,
269
307
  _ref$springConfig = _ref.springConfig,
270
- springConfig = _ref$springConfig === void 0 ? {
271
- stiffness: 300,
272
- damping: 30,
273
- mass: 0.2
274
- } : _ref$springConfig,
308
+ springConfig = _ref$springConfig === void 0 ? DEFAULT_SPRING_CONFIG : _ref$springConfig,
275
309
  _ref$disableDrag = _ref.disableDrag,
276
310
  disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
277
- style = _ref.style,
278
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "isOpen", "onClose", "onOpenStart", "onOpenEnd", "onCloseStart", "onCloseEnd", "onSnap", "snapPoints", "initialSnap", "rootId", "mountPoint", "springConfig", "disableDrag", "style"]);
311
+ _ref$prefersReducedMo = _ref.prefersReducedMotion,
312
+ prefersReducedMotion = _ref$prefersReducedMo === void 0 ? false : _ref$prefersReducedMo,
313
+ rest = _objectWithoutPropertiesLoose(_ref, ["onOpenStart", "onOpenEnd", "onClose", "onCloseStart", "onCloseEnd", "onSnap", "children", "isOpen", "snapPoints", "rootId", "mountPoint", "style", "detent", "initialSnap", "springConfig", "disableDrag", "prefersReducedMotion"]);
279
314
 
280
315
  var sheetRef = useRef(null);
316
+ var indicatorRotation = useMotionValue(0);
317
+ var windowHeight = useWindowHeight();
318
+ var shouldReduceMotion = useReducedMotion();
319
+ var reduceMotion = Boolean(prefersReducedMotion || shouldReduceMotion);
320
+ var animationOptions = reduceMotion ? {
321
+ type: 'tween',
322
+ duration: 0.01
323
+ } : _extends({
324
+ type: 'spring'
325
+ }, springConfig); // NOTE: the inital value for `y` doesn't matter since it is overwritten by
326
+ // the value driven by the `AnimatePresence` component when the sheet is opened
327
+ // and after that it is driven by the gestures and/or snapping
328
+
329
+ var y = useMotionValue(0); // Keep the callback fns up-to-date so that they can be accessed inside
330
+ // the effect without including them to the dependencies array
331
+
281
332
  var callbacks = useRef({
282
333
  onOpenStart: onOpenStart,
283
334
  onOpenEnd: onOpenEnd,
284
335
  onCloseStart: onCloseStart,
285
336
  onCloseEnd: onCloseEnd
286
- }); // prettier-ignore
287
-
288
- var indicatorRotation = useMotionValue(0);
289
- var windowHeight = useWindowHeight(); // NOTE: the inital value for `y` doesn't matter since it is overwritten by
290
- // the value driven by the `AnimatePresence` component when the sheet is opened
291
- // and after that it is driven by the gestures and/or snapping
292
-
293
- var y = useMotionValue(0);
337
+ });
338
+ useIsomorphicLayoutEffect(function () {
339
+ callbacks.current = {
340
+ onOpenStart: onOpenStart,
341
+ onOpenEnd: onOpenEnd,
342
+ onCloseStart: onCloseStart,
343
+ onCloseEnd: onCloseEnd
344
+ };
345
+ });
294
346
 
295
347
  if (snapPoints) {
296
348
  // Convert negative / percentage snap points to absolute values
297
349
  snapPoints = snapPoints.map(function (point) {
298
- if (point > 0 && point <= 1) return Math.round(point * windowHeight); // percentage values e.g. between 0.0 and 1.0
299
-
350
+ // Percentage values e.g. between 0.0 and 1.0
351
+ if (point > 0 && point <= 1) return Math.round(point * windowHeight);
300
352
  return point < 0 ? windowHeight + point : point; // negative values
301
353
  });
302
354
  console.assert(inDescendingOrder(snapPoints) || windowHeight === 0, "Snap points need to be in descending order got: [" + snapPoints + "]");
303
355
  }
304
356
 
305
- var handleDrag = useCallback(function (_, _ref2) {
357
+ var onDrag = useEvent(function (_, _ref2) {
306
358
  var delta = _ref2.delta;
307
359
  // Update drag indicator rotation based on drag velocity
308
360
  var velocity = y.getVelocity();
@@ -310,26 +362,44 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
310
362
  if (velocity < 0) indicatorRotation.set(-10); // Make sure user cannot drag beyond the top of the sheet
311
363
 
312
364
  y.set(Math.max(y.get() + delta.y, 0));
313
- }, []); // eslint-disable-line
314
-
315
- var handleDragEnd = useCallback(function (_, _ref3) {
365
+ });
366
+ var onDragEnd = useEvent(function (_, _ref3) {
316
367
  var velocity = _ref3.velocity;
317
368
 
318
- if (velocity.y > 500) {
369
+ if (velocity.y > DRAG_VELOCITY_THRESHOLD) {
319
370
  // User flicked the sheet down
320
371
  onClose();
321
372
  } else {
322
373
  var sheetEl = sheetRef.current;
323
- var contentHeight = sheetEl.getBoundingClientRect().height;
324
- var snapTo = Math.round(snapPoints ? getClosest(snapPoints.map(function (p) {
325
- return contentHeight - p;
326
- }), y.get()) // prettier-ignore
327
- : y.get() / contentHeight > 0.6 // Close if dragged over 60%
328
- ? contentHeight : 0); // Update the spring value so that the sheet is animated to the snap point
374
+ var sheetHeight = sheetEl.getBoundingClientRect().height;
375
+ var currentY = y.get();
376
+ var snapTo = 0;
377
+
378
+ if (snapPoints) {
379
+ var snapToValues = snapPoints.map(function (p) {
380
+ return sheetHeight - p;
381
+ }).filter(function (p) {
382
+ return p >= 0;
383
+ }); // negative values can occur with `content-height` detent
384
+ // Allow snapping to the top of the sheet if detent is set to `content-height`
385
+
386
+ if (detent === 'content-height' && !snapToValues.includes(0)) {
387
+ snapToValues.unshift(0);
388
+ } // Get the closest snap point
389
+
390
+
391
+ snapTo = getClosest(snapToValues, currentY);
392
+ } else if (currentY / sheetHeight > DRAG_CLOSE_THRESHOLD) {
393
+ // Close if dragged over enough far
394
+ snapTo = sheetHeight;
395
+ }
329
396
 
330
- animate(y, snapTo, _extends({
331
- type: 'spring'
332
- }, springConfig));
397
+ snapTo = validateSnapTo({
398
+ snapTo: snapTo,
399
+ sheetHeight: sheetHeight
400
+ }); // Update the spring value so that the sheet is animated to the snap point
401
+
402
+ animate(y, snapTo, animationOptions);
333
403
 
334
404
  if (snapPoints && onSnap) {
335
405
  var snapValue = Math.abs(Math.round(snapPoints[0] - snapTo));
@@ -338,22 +408,11 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
338
408
  onSnap(snapIndex);
339
409
  }
340
410
 
341
- if (snapTo >= contentHeight) onClose();
411
+ if (snapTo >= sheetHeight) onClose();
342
412
  } // Reset indicator rotation after dragging
343
413
 
344
414
 
345
415
  indicatorRotation.set(0);
346
- }, [onClose, onSnap] // eslint-disable-line
347
- ); // Keep the callback fns up-to-date so that they can be accessed inside
348
- // the effect without including them to the dependencies array
349
-
350
- useEffect(function () {
351
- callbacks.current = {
352
- onOpenStart: onOpenStart,
353
- onOpenEnd: onOpenEnd,
354
- onCloseStart: onCloseStart,
355
- onCloseEnd: onCloseEnd
356
- };
357
416
  }); // Trigger onSnap callback when sheet is opened or closed
358
417
 
359
418
  useEffect(function () {
@@ -369,13 +428,15 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
369
428
  var sheetEl = sheetRef.current;
370
429
 
371
430
  if (snapPoints && snapPoints[snapIndex] !== undefined && sheetEl !== null) {
372
- var contentHeight = sheetEl.getBoundingClientRect().height;
373
- var snapTo = contentHeight - snapPoints[snapIndex];
374
- animate(y, snapTo, _extends({
375
- type: 'spring'
376
- }, springConfig));
431
+ var sheetHeight = sheetEl.getBoundingClientRect().height;
432
+ var snapPoint = snapPoints[snapIndex];
433
+ var snapTo = validateSnapTo({
434
+ snapTo: sheetHeight - snapPoint,
435
+ sheetHeight: sheetHeight
436
+ });
437
+ animate(y, snapTo, animationOptions);
377
438
  if (onSnap) onSnap(snapIndex);
378
- if (snapTo >= contentHeight) onClose();
439
+ if (snapTo >= sheetHeight) onClose();
379
440
  }
380
441
  }
381
442
  };
@@ -386,47 +447,49 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
386
447
  usePreventScroll({
387
448
  isDisabled: !isOpen
388
449
  });
389
- var dragProps = disableDrag ? {} : {
390
- drag: 'y',
391
- dragElastic: 0,
392
- dragConstraints: {
393
- top: 0,
394
- bottom: 0
395
- },
396
- dragMomentum: false,
397
- onDrag: handleDrag,
398
- onDragEnd: handleDragEnd
399
- };
450
+ var dragProps = useMemo(function () {
451
+ var dragProps = {
452
+ drag: 'y',
453
+ dragElastic: 0,
454
+ dragConstraints: {
455
+ top: 0,
456
+ bottom: 0
457
+ },
458
+ dragMomentum: false,
459
+ onDrag: onDrag,
460
+ onDragEnd: onDragEnd
461
+ };
462
+ return disableDrag ? undefined : dragProps;
463
+ }, [disableDrag]); // eslint-disable-line
464
+
400
465
  var context = {
401
466
  y: y,
402
467
  sheetRef: sheetRef,
403
468
  isOpen: isOpen,
404
469
  initialSnap: initialSnap,
405
470
  snapPoints: snapPoints,
471
+ detent: detent,
406
472
  indicatorRotation: indicatorRotation,
407
473
  callbacks: callbacks,
408
474
  dragProps: dragProps,
409
475
  windowHeight: windowHeight,
410
- springConfig: springConfig
476
+ animationOptions: animationOptions,
477
+ reduceMotion: reduceMotion
411
478
  };
412
-
413
- var wrapperProps = _extends({}, rest, {
414
- ref: ref,
415
- style: _extends({}, styles.wrapper, style)
416
- });
417
-
418
479
  var sheet = createElement(SheetContext.Provider, {
419
480
  value: context
420
- }, createElement("div", Object.assign({}, wrapperProps), createElement(AnimatePresence, null, isOpen ? Children.map(children, function (child, i) {
481
+ }, createElement("div", Object.assign({}, rest, {
482
+ ref: ref,
483
+ style: _extends({}, styles.wrapper, style)
484
+ }), createElement(AnimatePresence, null, isOpen ? Children.map(children, function (child, i) {
421
485
  return cloneElement(child, {
422
486
  key: "sheet-child-" + i
423
487
  });
424
488
  }) : null)));
425
- if (isSSR) return sheet;
489
+ if (IS_SSR) return sheet;
426
490
  return createPortal(sheet, mountPoint != null ? mountPoint : document.body);
427
491
  });
428
492
 
429
- var MAX_HEIGHT = 'calc(100% - env(safe-area-inset-top) - 34px)';
430
493
  var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
431
494
  var children = _ref.children,
432
495
  _ref$style = _ref.style,
@@ -442,33 +505,36 @@ var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
442
505
  initialSnap = _useSheetContext$init === void 0 ? 0 : _useSheetContext$init,
443
506
  sheetRef = _useSheetContext.sheetRef,
444
507
  windowHeight = _useSheetContext.windowHeight,
445
- springConfig = _useSheetContext.springConfig;
508
+ detent = _useSheetContext.detent,
509
+ animationOptions = _useSheetContext.animationOptions,
510
+ reduceMotion = _useSheetContext.reduceMotion;
446
511
 
447
512
  var _useEventCallbacks = useEventCallbacks(isOpen, callbacks),
448
513
  handleAnimationComplete = _useEventCallbacks.handleAnimationComplete;
449
514
 
450
515
  var initialY = snapPoints ? snapPoints[0] - snapPoints[initialSnap] : 0;
451
- var h = snapPoints ? snapPoints[0] : null;
452
- var sheetHeight = h ? "min(" + h + "px, " + MAX_HEIGHT + ")" : MAX_HEIGHT;
516
+ var maxSnapHeight = snapPoints ? snapPoints[0] : null;
517
+ var height = maxSnapHeight !== null ? "min(" + maxSnapHeight + "px, " + MAX_HEIGHT + ")" : MAX_HEIGHT;
453
518
  return createElement(motion.div, Object.assign({}, rest, {
454
519
  ref: mergeRefs([sheetRef, ref]),
455
520
  className: "react-modal-sheet-container",
456
- style: _extends({}, styles.container, {
457
- height: sheetHeight
458
- }, style, {
521
+ style: _extends({}, styles.container, style, detent === 'full-height' && {
522
+ height: height
523
+ }, detent === 'content-height' && {
524
+ maxHeight: height
525
+ }, {
459
526
  y: y
460
527
  }),
461
- initial: {
528
+ initial: reduceMotion ? false : {
462
529
  y: windowHeight
463
530
  },
464
531
  animate: {
465
532
  y: initialY,
466
- transition: _extends({
467
- type: 'spring'
468
- }, springConfig)
533
+ transition: animationOptions
469
534
  },
470
535
  exit: {
471
- y: windowHeight
536
+ y: windowHeight,
537
+ transition: animationOptions
472
538
  },
473
539
  onAnimationComplete: handleAnimationComplete
474
540
  }), children);
@@ -476,11 +542,8 @@ var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
476
542
 
477
543
  var SheetContent = /*#__PURE__*/forwardRef(function (_ref, ref) {
478
544
  var children = _ref.children,
479
- _ref$disableDrag = _ref.disableDrag,
480
- disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
481
- _ref$style = _ref.style,
482
- style = _ref$style === void 0 ? {} : _ref$style,
483
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "disableDrag", "style"]);
545
+ style = _ref.style,
546
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "style"]);
484
547
 
485
548
  var _useSheetContext = useSheetContext(),
486
549
  dragProps = _useSheetContext.dragProps;
@@ -489,16 +552,13 @@ var SheetContent = /*#__PURE__*/forwardRef(function (_ref, ref) {
489
552
  ref: ref,
490
553
  className: "react-modal-sheet-content",
491
554
  style: _extends({}, styles.content, style)
492
- }, disableDrag ? {} : dragProps), children);
555
+ }, dragProps), children);
493
556
  });
494
557
 
495
558
  var SheetHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
496
559
  var children = _ref.children,
497
- _ref$disableDrag = _ref.disableDrag,
498
- disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
499
- _ref$style = _ref.style,
500
- style = _ref$style === void 0 ? {} : _ref$style,
501
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "disableDrag", "style"]);
560
+ style = _ref.style,
561
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "style"]);
502
562
 
503
563
  var _useSheetContext = useSheetContext(),
504
564
  indicatorRotation = _useSheetContext.indicatorRotation,
@@ -513,7 +573,7 @@ var SheetHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
513
573
  return createElement(motion.div, Object.assign({}, rest, {
514
574
  ref: ref,
515
575
  style: _extends({}, styles.headerWrapper, style)
516
- }, disableDrag ? {} : dragProps), children || createElement("div", {
576
+ }, dragProps), children || createElement("div", {
517
577
  className: "react-modal-sheet-header",
518
578
  style: styles.header
519
579
  }, createElement(motion.span, {