react-modal-sheet 1.6.2 → 1.7.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, useTransform, motion } from 'framer-motion';
4
+ import { useMotionValue, 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
+ }
130
+
131
+ return Math.max(Math.round(snapTo), 0);
132
+ }
140
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,62 @@ 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,
300
+ detent = _ref.detent,
265
301
  _ref$initialSnap = _ref.initialSnap,
266
302
  initialSnap = _ref$initialSnap === void 0 ? 0 : _ref$initialSnap,
267
303
  rootId = _ref.rootId,
268
304
  mountPoint = _ref.mountPoint,
269
305
  _ref$springConfig = _ref.springConfig,
270
- springConfig = _ref$springConfig === void 0 ? {
271
- stiffness: 300,
272
- damping: 30,
273
- mass: 0.2
274
- } : _ref$springConfig,
306
+ springConfig = _ref$springConfig === void 0 ? DEFAULT_SPRING_CONFIG : _ref$springConfig,
275
307
  _ref$disableDrag = _ref.disableDrag,
276
308
  disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
277
309
  style = _ref.style,
278
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "isOpen", "onClose", "onOpenStart", "onOpenEnd", "onCloseStart", "onCloseEnd", "onSnap", "snapPoints", "initialSnap", "rootId", "mountPoint", "springConfig", "disableDrag", "style"]);
310
+ rest = _objectWithoutPropertiesLoose(_ref, ["onOpenStart", "onOpenEnd", "onClose", "onCloseStart", "onCloseEnd", "onSnap", "children", "isOpen", "snapPoints", "detent", "initialSnap", "rootId", "mountPoint", "springConfig", "disableDrag", "style"]);
279
311
 
280
312
  var sheetRef = useRef(null);
281
- var callbacks = useRef({
282
- onOpenStart: onOpenStart,
283
- onOpenEnd: onOpenEnd,
284
- onCloseStart: onCloseStart,
285
- onCloseEnd: onCloseEnd
286
- }); // prettier-ignore
287
-
288
313
  var indicatorRotation = useMotionValue(0);
289
314
  var windowHeight = useWindowHeight(); // NOTE: the inital value for `y` doesn't matter since it is overwritten by
290
315
  // the value driven by the `AnimatePresence` component when the sheet is opened
291
316
  // and after that it is driven by the gestures and/or snapping
292
317
 
293
- var y = useMotionValue(0);
318
+ var y = useMotionValue(0); // Keep the callback fns up-to-date so that they can be accessed inside
319
+ // the effect without including them to the dependencies array
320
+
321
+ var callbacks = useRef({
322
+ onOpenStart: onOpenStart,
323
+ onOpenEnd: onOpenEnd,
324
+ onCloseStart: onCloseStart,
325
+ onCloseEnd: onCloseEnd
326
+ });
327
+ useIsomorphicLayoutEffect(function () {
328
+ callbacks.current = {
329
+ onOpenStart: onOpenStart,
330
+ onOpenEnd: onOpenEnd,
331
+ onCloseStart: onCloseStart,
332
+ onCloseEnd: onCloseEnd
333
+ };
334
+ });
294
335
 
295
336
  if (snapPoints) {
296
337
  // Convert negative / percentage snap points to absolute values
297
338
  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
-
339
+ // Percentage values e.g. between 0.0 and 1.0
340
+ if (point > 0 && point <= 1) return Math.round(point * windowHeight);
300
341
  return point < 0 ? windowHeight + point : point; // negative values
301
342
  });
302
343
  console.assert(inDescendingOrder(snapPoints) || windowHeight === 0, "Snap points need to be in descending order got: [" + snapPoints + "]");
303
344
  }
304
345
 
305
- var handleDrag = useCallback(function (_, _ref2) {
346
+ var onDrag = useEvent(function (_, _ref2) {
306
347
  var delta = _ref2.delta;
307
348
  // Update drag indicator rotation based on drag velocity
308
349
  var velocity = y.getVelocity();
@@ -310,22 +351,42 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
310
351
  if (velocity < 0) indicatorRotation.set(-10); // Make sure user cannot drag beyond the top of the sheet
311
352
 
312
353
  y.set(Math.max(y.get() + delta.y, 0));
313
- }, []); // eslint-disable-line
314
-
315
- var handleDragEnd = useCallback(function (_, _ref3) {
354
+ });
355
+ var onDragEnd = useEvent(function (_, _ref3) {
316
356
  var velocity = _ref3.velocity;
317
357
 
318
- if (velocity.y > 500) {
358
+ if (velocity.y > DRAG_VELOCITY_THRESHOLD) {
319
359
  // User flicked the sheet down
320
360
  onClose();
321
361
  } else {
322
362
  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
363
+ var sheetHeight = sheetEl.getBoundingClientRect().height;
364
+ var currentY = y.get();
365
+ var snapTo = 0;
366
+
367
+ if (snapPoints) {
368
+ var snapToValues = snapPoints.map(function (p) {
369
+ return sheetHeight - p;
370
+ }).filter(function (p) {
371
+ return p >= 0;
372
+ }); // negative values can occur with `content-height` detent
373
+ // Allow snapping to the top of the sheet if detent is set to `content-height`
374
+
375
+ if (detent === 'content-height' && !snapToValues.includes(0)) {
376
+ snapToValues.unshift(0);
377
+ } // Get the closest snap point
378
+
379
+
380
+ snapTo = getClosest(snapToValues, currentY);
381
+ } else if (currentY / sheetHeight > DRAG_CLOSE_THRESHOLD) {
382
+ // Close if dragged over enough far
383
+ snapTo = sheetHeight;
384
+ }
385
+
386
+ snapTo = validateSnapTo({
387
+ snapTo: snapTo,
388
+ sheetHeight: sheetHeight
389
+ }); // Update the spring value so that the sheet is animated to the snap point
329
390
 
330
391
  animate(y, snapTo, _extends({
331
392
  type: 'spring'
@@ -338,22 +399,11 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
338
399
  onSnap(snapIndex);
339
400
  }
340
401
 
341
- if (snapTo >= contentHeight) onClose();
402
+ if (snapTo >= sheetHeight) onClose();
342
403
  } // Reset indicator rotation after dragging
343
404
 
344
405
 
345
406
  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
407
  }); // Trigger onSnap callback when sheet is opened or closed
358
408
 
359
409
  useEffect(function () {
@@ -369,13 +419,17 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
369
419
  var sheetEl = sheetRef.current;
370
420
 
371
421
  if (snapPoints && snapPoints[snapIndex] !== undefined && sheetEl !== null) {
372
- var contentHeight = sheetEl.getBoundingClientRect().height;
373
- var snapTo = contentHeight - snapPoints[snapIndex];
422
+ var sheetHeight = sheetEl.getBoundingClientRect().height;
423
+ var snapPoint = snapPoints[snapIndex];
424
+ var snapTo = validateSnapTo({
425
+ snapTo: sheetHeight - snapPoint,
426
+ sheetHeight: sheetHeight
427
+ });
374
428
  animate(y, snapTo, _extends({
375
429
  type: 'spring'
376
430
  }, springConfig));
377
431
  if (onSnap) onSnap(snapIndex);
378
- if (snapTo >= contentHeight) onClose();
432
+ if (snapTo >= sheetHeight) onClose();
379
433
  }
380
434
  }
381
435
  };
@@ -386,47 +440,48 @@ var Sheet = /*#__PURE__*/forwardRef(function (_ref, ref) {
386
440
  usePreventScroll({
387
441
  isDisabled: !isOpen
388
442
  });
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
- };
443
+ var dragProps = useMemo(function () {
444
+ var dragProps = {
445
+ drag: 'y',
446
+ dragElastic: 0,
447
+ dragConstraints: {
448
+ top: 0,
449
+ bottom: 0
450
+ },
451
+ dragMomentum: false,
452
+ onDrag: onDrag,
453
+ onDragEnd: onDragEnd
454
+ };
455
+ return disableDrag ? undefined : dragProps;
456
+ }, [disableDrag]); // eslint-disable-line
457
+
400
458
  var context = {
401
459
  y: y,
402
460
  sheetRef: sheetRef,
403
461
  isOpen: isOpen,
404
462
  initialSnap: initialSnap,
405
463
  snapPoints: snapPoints,
464
+ detent: detent,
406
465
  indicatorRotation: indicatorRotation,
407
466
  callbacks: callbacks,
408
467
  dragProps: dragProps,
409
468
  windowHeight: windowHeight,
410
469
  springConfig: springConfig
411
470
  };
412
-
413
- var wrapperProps = _extends({}, rest, {
414
- ref: ref,
415
- style: _extends({}, styles.wrapper, style)
416
- });
417
-
418
471
  var sheet = createElement(SheetContext.Provider, {
419
472
  value: context
420
- }, createElement("div", Object.assign({}, wrapperProps), createElement(AnimatePresence, null, isOpen ? Children.map(children, function (child, i) {
473
+ }, createElement("div", Object.assign({}, rest, {
474
+ ref: ref,
475
+ style: _extends({}, styles.wrapper, style)
476
+ }), createElement(AnimatePresence, null, isOpen ? Children.map(children, function (child, i) {
421
477
  return cloneElement(child, {
422
478
  key: "sheet-child-" + i
423
479
  });
424
480
  }) : null)));
425
- if (isSSR) return sheet;
481
+ if (IS_SSR) return sheet;
426
482
  return createPortal(sheet, mountPoint != null ? mountPoint : document.body);
427
483
  });
428
484
 
429
- var MAX_HEIGHT = 'calc(100% - env(safe-area-inset-top) - 34px)';
430
485
  var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
431
486
  var children = _ref.children,
432
487
  _ref$style = _ref.style,
@@ -442,24 +497,25 @@ var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
442
497
  initialSnap = _useSheetContext$init === void 0 ? 0 : _useSheetContext$init,
443
498
  sheetRef = _useSheetContext.sheetRef,
444
499
  windowHeight = _useSheetContext.windowHeight,
445
- springConfig = _useSheetContext.springConfig;
500
+ springConfig = _useSheetContext.springConfig,
501
+ _useSheetContext$dete = _useSheetContext.detent,
502
+ detent = _useSheetContext$dete === void 0 ? 'full-height' : _useSheetContext$dete;
446
503
 
447
504
  var _useEventCallbacks = useEventCallbacks(isOpen, callbacks),
448
505
  handleAnimationComplete = _useEventCallbacks.handleAnimationComplete;
449
506
 
450
507
  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;
453
- var restrictedY = useTransform(y, function (value) {
454
- return Math.max(value, 0);
455
- });
508
+ var maxSnapHeight = snapPoints ? snapPoints[0] : null;
509
+ var height = maxSnapHeight !== null ? "min(" + maxSnapHeight + "px, " + MAX_HEIGHT + ")" : MAX_HEIGHT;
456
510
  return createElement(motion.div, Object.assign({}, rest, {
457
511
  ref: mergeRefs([sheetRef, ref]),
458
512
  className: "react-modal-sheet-container",
459
- style: _extends({}, styles.container, {
460
- height: sheetHeight
461
- }, style, {
462
- y: restrictedY
513
+ style: _extends({}, styles.container, style, detent === 'full-height' && {
514
+ height: height
515
+ }, detent === 'content-height' && {
516
+ maxHeight: height
517
+ }, {
518
+ y: y
463
519
  }),
464
520
  initial: {
465
521
  y: windowHeight
@@ -479,11 +535,8 @@ var SheetContainer = /*#__PURE__*/forwardRef(function (_ref, ref) {
479
535
 
480
536
  var SheetContent = /*#__PURE__*/forwardRef(function (_ref, ref) {
481
537
  var children = _ref.children,
482
- _ref$disableDrag = _ref.disableDrag,
483
- disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
484
- _ref$style = _ref.style,
485
- style = _ref$style === void 0 ? {} : _ref$style,
486
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "disableDrag", "style"]);
538
+ style = _ref.style,
539
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "style"]);
487
540
 
488
541
  var _useSheetContext = useSheetContext(),
489
542
  dragProps = _useSheetContext.dragProps;
@@ -492,16 +545,13 @@ var SheetContent = /*#__PURE__*/forwardRef(function (_ref, ref) {
492
545
  ref: ref,
493
546
  className: "react-modal-sheet-content",
494
547
  style: _extends({}, styles.content, style)
495
- }, disableDrag ? {} : dragProps), children);
548
+ }, dragProps), children);
496
549
  });
497
550
 
498
551
  var SheetHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
499
552
  var children = _ref.children,
500
- _ref$disableDrag = _ref.disableDrag,
501
- disableDrag = _ref$disableDrag === void 0 ? false : _ref$disableDrag,
502
- _ref$style = _ref.style,
503
- style = _ref$style === void 0 ? {} : _ref$style,
504
- rest = _objectWithoutPropertiesLoose(_ref, ["children", "disableDrag", "style"]);
553
+ style = _ref.style,
554
+ rest = _objectWithoutPropertiesLoose(_ref, ["children", "style"]);
505
555
 
506
556
  var _useSheetContext = useSheetContext(),
507
557
  indicatorRotation = _useSheetContext.indicatorRotation,
@@ -516,7 +566,7 @@ var SheetHeader = /*#__PURE__*/forwardRef(function (_ref, ref) {
516
566
  return createElement(motion.div, Object.assign({}, rest, {
517
567
  ref: ref,
518
568
  style: _extends({}, styles.headerWrapper, style)
519
- }, disableDrag ? {} : dragProps), children || createElement("div", {
569
+ }, dragProps), children || createElement("div", {
520
570
  className: "react-modal-sheet-header",
521
571
  style: styles.header
522
572
  }, createElement(motion.span, {