react-modal-sheet 4.4.0 → 5.1.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/src/utils.ts CHANGED
@@ -1,161 +1,20 @@
1
- import { type ForwardedRef, type RefCallback, type RefObject } from 'react';
1
+ import { type CSSProperties, type ForwardedRef, type RefCallback } from 'react';
2
2
  import { IS_SSR } from './constants';
3
- import type { SheetDetent } from './types';
4
3
 
5
- /**
6
- * Get the rounded height of the sheet element and log a warning if the
7
- * element is not mounted yet but the height is requested.
8
- */
9
- export function getSheetHeight(sheetRef: RefObject<HTMLDivElement | null>) {
10
- const sheetEl = sheetRef.current;
11
-
12
- if (!sheetEl) {
13
- console.warn(
14
- 'Sheet height is not available because the sheet element is not mounted yet.'
15
- );
16
- return 0;
17
- }
18
-
19
- return Math.round(sheetEl.getBoundingClientRect().height);
20
- }
21
-
22
- /**
23
- * Convert negative / percentage snap points to absolute values
24
- */
25
- export function getSnapPoints({
26
- snapPointsProp,
27
- sheetHeight,
28
- }: {
29
- snapPointsProp: number[];
30
- sheetHeight: number;
31
- }) {
32
- const snapPointValues = snapPointsProp.map((point) => {
33
- // Percentage values e.g. between 0.0 and 1.0
34
- if (point > 0 && point <= 1) {
35
- return Math.round(point * sheetHeight);
36
- }
37
-
38
- return point < 0 ? sheetHeight + point : point; // negative values
39
- });
40
-
41
- console.assert(
42
- inDescendingOrder(snapPointValues) || sheetHeight === 0,
43
- `Snap points need to be in descending order got: [${snapPointsProp.join(', ')}]`
44
- );
45
-
46
- return snapPointValues;
4
+ export function applyStyles(
5
+ styles: { base: CSSProperties; decorative: CSSProperties },
6
+ unstyled: boolean
7
+ ) {
8
+ return unstyled ? styles.base : { ...styles.base, ...styles.decorative };
47
9
  }
48
10
 
49
- export function getClosestSnapPoint({
50
- snapPoints,
51
- currentY,
52
- sheetHeight,
53
- detent,
54
- }: {
55
- snapPoints: number[];
56
- currentY: number;
57
- sheetHeight: number;
58
- detent?: SheetDetent;
59
- }) {
60
- // Inverse values are the values that can be passed to `animate`
61
- const snapInverse = snapPoints.map(
62
- (p) => sheetHeight - Math.min(p, sheetHeight)
63
- );
64
-
65
- // Allow snapping to the top of the sheet if detent is set to `content-height`
66
- if (detent === 'content-height' && !snapInverse.includes(0)) {
67
- snapInverse.unshift(0);
68
- }
69
-
70
- // Get the closest snap point
71
- const snapTo = getClosest(snapInverse, currentY);
72
- const snapIndex = snapInverse.indexOf(snapTo);
73
-
74
- const snapY = validateSnapTo({
75
- snapTo: getClosest(snapInverse, currentY),
76
- sheetHeight,
77
- });
78
-
79
- return { snapY, snapIndex };
80
- }
81
-
82
- /**
83
- * Get the closest number to the goal from the array of numbers.
84
- */
85
- export function getClosest(nums: number[], goal: number) {
86
- let closest = nums[0];
87
- let minDifference = Math.abs(nums[0] - goal);
88
-
89
- for (let i = 1; i < nums.length; i++) {
90
- const difference = Math.abs(nums[i] - goal);
91
- if (difference < minDifference) {
92
- closest = nums[i];
93
- minDifference = difference;
94
- }
95
- }
96
-
97
- return closest;
98
- }
99
-
100
- /**
101
- * Check if the array is in descending order.
102
- * This is used to validate the snap points.
103
- */
104
- export function inDescendingOrder(arr: number[]) {
11
+ export function isAscendingOrder(arr: number[]) {
105
12
  for (let i = 0; i < arr.length; i++) {
106
- if (arr[i + 1] > arr[i]) return false;
13
+ if (arr[i + 1] < arr[i]) return false;
107
14
  }
108
-
109
15
  return true;
110
16
  }
111
17
 
112
- /**
113
- * Get the Y value of the given snap point. This can be passed to the `animate`
114
- * function to animate the sheet to the given snap point.
115
- */
116
- export function getSnapY({
117
- sheetHeight,
118
- snapPoints,
119
- snapIndex,
120
- }: {
121
- sheetHeight: number;
122
- snapPoints: number[];
123
- snapIndex: number;
124
- }) {
125
- const snapPoint = snapPoints[snapIndex];
126
-
127
- if (snapPoint === undefined) {
128
- console.warn(
129
- `Invalid snap index ${snapIndex}. Snap points are: [${snapPoints.join(', ')}]`
130
- );
131
- return null;
132
- }
133
-
134
- const y = validateSnapTo({ snapTo: sheetHeight - snapPoint, sheetHeight });
135
-
136
- return y;
137
- }
138
-
139
- /**
140
- * Make sure the snap point is not out of bounds.
141
- * Snap points cannot be negative - `0` means the sheet is fully open.
142
- */
143
- export function validateSnapTo({
144
- snapTo,
145
- sheetHeight,
146
- }: {
147
- snapTo: number;
148
- sheetHeight: number;
149
- }) {
150
- if (snapTo < 0) {
151
- console.warn(
152
- `Snap point is out of bounds. Sheet height is ${sheetHeight} but snap point is ${sheetHeight + Math.abs(snapTo)}.`
153
- );
154
- }
155
-
156
- return Math.max(Math.round(snapTo), 0);
157
- }
158
-
159
18
  export function mergeRefs<T = any>(refs: ForwardedRef<T>[]): RefCallback<T> {
160
19
  return (value: any) => {
161
20
  refs.forEach((ref: any) => {
@@ -1,96 +0,0 @@
1
- import React, { type TouchEvent, type UIEvent, forwardRef } from 'react';
2
- import { motion, type MotionStyle } from 'motion/react';
3
-
4
- import { useSheetContext, useSheetScrollerContext } from './context';
5
- import { type SheetScrollerProps } from './types';
6
- import { isTouchDevice } from './utils';
7
- import { styles } from './styles';
8
-
9
- export const SheetScroller = forwardRef<any, SheetScrollerProps>(
10
- (
11
- {
12
- autoPadding = true,
13
- disableScroll = false,
14
- draggableAt = 'top',
15
- children,
16
- style: styleProp,
17
- className = '',
18
- ...rest
19
- },
20
- ref
21
- ) => {
22
- const { y } = useSheetContext();
23
- const { setDragEnabled, setDragDisabled } = useSheetScrollerContext();
24
-
25
- function determineDragState(element: HTMLDivElement) {
26
- /**
27
- * If the caller is managing the scrollability of the element,
28
- * enable the dragging of the sheet content.
29
- */
30
- if (disableScroll) {
31
- setDragEnabled();
32
- return;
33
- }
34
-
35
- const { scrollTop, scrollHeight, clientHeight } = element;
36
- const isScrollable = scrollHeight > clientHeight;
37
-
38
- if (!isScrollable) return;
39
-
40
- const isAtTop = scrollTop <= 0;
41
- const isAtBottom = scrollHeight - scrollTop === clientHeight;
42
-
43
- const shouldEnable =
44
- (draggableAt === 'top' && isAtTop) ||
45
- (draggableAt === 'bottom' && isAtBottom) ||
46
- (draggableAt === 'both' && (isAtTop || isAtBottom));
47
-
48
- if (shouldEnable) {
49
- setDragEnabled();
50
- } else {
51
- setDragDisabled();
52
- }
53
- }
54
-
55
- function onScroll(event: UIEvent<HTMLDivElement>) {
56
- determineDragState(event.currentTarget);
57
- if (rest.onScroll) rest.onScroll(event);
58
- }
59
-
60
- function onTouchStart(event: TouchEvent<HTMLDivElement>) {
61
- determineDragState(event.currentTarget);
62
- if (rest.onTouchStart) rest.onTouchStart(event);
63
- }
64
-
65
- const scrollProps = isTouchDevice()
66
- ? { onScroll, onTouchStart }
67
- : undefined;
68
-
69
- const style: MotionStyle = {
70
- ...styles.scroller,
71
- ...styleProp,
72
- };
73
-
74
- if (autoPadding) {
75
- style.paddingBottom = y;
76
- }
77
-
78
- if (disableScroll) {
79
- style.overflowY = 'hidden';
80
- }
81
-
82
- return (
83
- <motion.div
84
- {...rest}
85
- ref={ref}
86
- className={`react-modal-sheet-scroller ${className}`}
87
- style={style}
88
- {...scrollProps}
89
- >
90
- {children}
91
- </motion.div>
92
- );
93
- }
94
- );
95
-
96
- SheetScroller.displayName = 'SheetScroller';
@@ -1,44 +0,0 @@
1
- import { type RefObject, useCallback, useEffect, useRef } from 'react';
2
-
3
- import { type SheetEvents } from '../types';
4
-
5
- export function useEventCallbacks(
6
- isOpen: boolean,
7
- callbacks: RefObject<SheetEvents>
8
- ) {
9
- const prevOpen = usePrevious(isOpen);
10
- const didOpen = useRef(false);
11
-
12
- // Because of AnimatePrecence we don't have access to latest isOpen value
13
- // so we need to read and write to a ref to determine if we are
14
- // opening or closing the sheet
15
- const handleAnimationComplete = useCallback(() => {
16
- if (!didOpen.current) {
17
- callbacks.current.onOpenEnd?.();
18
- didOpen.current = true;
19
- } else {
20
- callbacks.current.onCloseEnd?.();
21
- didOpen.current = false;
22
- }
23
- }, [isOpen, prevOpen]);
24
-
25
- useEffect(() => {
26
- if (!prevOpen && isOpen) {
27
- callbacks.current.onOpenStart?.();
28
- } else if (!isOpen && prevOpen) {
29
- callbacks.current.onCloseStart?.();
30
- }
31
- }, [isOpen, prevOpen]);
32
-
33
- return { handleAnimationComplete };
34
- }
35
-
36
- function usePrevious<T>(state: T): T | undefined {
37
- const ref = useRef<T>(undefined);
38
-
39
- useEffect(() => {
40
- ref.current = state;
41
- });
42
-
43
- return ref.current;
44
- }