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