react-modal-sheet 3.4.1 → 4.0.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/README.md +37 -17
- package/dist/index.d.mts +5 -3
- package/dist/index.d.ts +5 -3
- package/dist/index.js +75 -97
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +57 -79
- package/dist/index.mjs.map +1 -1
- package/package.json +38 -49
- package/src/SheetBackdrop.tsx +3 -5
- package/src/SheetContainer.tsx +3 -5
- package/src/SheetContent.tsx +3 -5
- package/src/SheetHeader.tsx +3 -5
- package/src/SheetScroller.tsx +2 -4
- package/src/context.tsx +8 -3
- package/src/hooks.tsx +10 -11
- package/src/index.tsx +24 -16
- package/src/sheet.tsx +12 -12
- package/src/styles.ts +2 -3
- package/src/types.tsx +1 -1
- package/src/use-prevent-scroll.ts +10 -4
- package/src/utils.ts +2 -6
package/src/hooks.tsx
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
type MutableRefObject,
|
|
3
2
|
type RefObject,
|
|
4
3
|
useCallback,
|
|
5
4
|
useEffect,
|
|
@@ -8,7 +7,7 @@ import {
|
|
|
8
7
|
useState,
|
|
9
8
|
} from 'react';
|
|
10
9
|
|
|
11
|
-
import { type MotionValue, type BoundingBox, transform } from '
|
|
10
|
+
import { type MotionValue, type BoundingBox, transform } from 'motion/react';
|
|
12
11
|
|
|
13
12
|
import { IS_SSR } from './constants';
|
|
14
13
|
import { type SheetEvents } from './types';
|
|
@@ -22,7 +21,7 @@ export function useModalEffect({
|
|
|
22
21
|
}: {
|
|
23
22
|
y: MotionValue<number>;
|
|
24
23
|
rootId?: string;
|
|
25
|
-
sheetRef: RefObject<HTMLDivElement>;
|
|
24
|
+
sheetRef: RefObject<HTMLDivElement | null>;
|
|
26
25
|
}) {
|
|
27
26
|
const heightRef = useRef(IS_SSR ? 0 : window.innerHeight);
|
|
28
27
|
|
|
@@ -61,7 +60,7 @@ export function useModalEffect({
|
|
|
61
60
|
return () => {
|
|
62
61
|
if (rootId) cleanup();
|
|
63
62
|
};
|
|
64
|
-
}, []);
|
|
63
|
+
}, []);
|
|
65
64
|
|
|
66
65
|
useEffect(() => {
|
|
67
66
|
const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
|
|
@@ -101,12 +100,12 @@ export function useModalEffect({
|
|
|
101
100
|
removeCompleteListener();
|
|
102
101
|
removeCancelListener();
|
|
103
102
|
};
|
|
104
|
-
}, [y, rootId]);
|
|
103
|
+
}, [y, rootId]);
|
|
105
104
|
}
|
|
106
105
|
|
|
107
106
|
export function useEventCallbacks(
|
|
108
107
|
isOpen: boolean,
|
|
109
|
-
callbacks:
|
|
108
|
+
callbacks: RefObject<SheetEvents>
|
|
110
109
|
) {
|
|
111
110
|
const prevOpen = usePrevious(isOpen);
|
|
112
111
|
const didOpen = useRef(false);
|
|
@@ -122,7 +121,7 @@ export function useEventCallbacks(
|
|
|
122
121
|
callbacks.current.onCloseEnd?.();
|
|
123
122
|
didOpen.current = false;
|
|
124
123
|
}
|
|
125
|
-
}, [isOpen, prevOpen]);
|
|
124
|
+
}, [isOpen, prevOpen]);
|
|
126
125
|
|
|
127
126
|
useEffect(() => {
|
|
128
127
|
if (!prevOpen && isOpen) {
|
|
@@ -130,7 +129,7 @@ export function useEventCallbacks(
|
|
|
130
129
|
} else if (!isOpen && prevOpen) {
|
|
131
130
|
callbacks.current.onCloseStart?.();
|
|
132
131
|
}
|
|
133
|
-
}, [isOpen, prevOpen]);
|
|
132
|
+
}, [isOpen, prevOpen]);
|
|
134
133
|
|
|
135
134
|
return { handleAnimationComplete };
|
|
136
135
|
}
|
|
@@ -162,7 +161,7 @@ export function useDimensions() {
|
|
|
162
161
|
}
|
|
163
162
|
|
|
164
163
|
export function usePrevious<T>(state: T): T | undefined {
|
|
165
|
-
const ref = useRef<T>();
|
|
164
|
+
const ref = useRef<T>(undefined);
|
|
166
165
|
|
|
167
166
|
useEffect(() => {
|
|
168
167
|
ref.current = state;
|
|
@@ -174,7 +173,7 @@ export function usePrevious<T>(state: T): T | undefined {
|
|
|
174
173
|
// Userland version of the `useEffectEvent` React hook:
|
|
175
174
|
// RFC: https://react.dev/reference/react/experimental_useEffectEvent
|
|
176
175
|
export function useEffectEvent<T extends (...args: any[]) => any>(handler: T) {
|
|
177
|
-
const handlerRef = useRef<T>();
|
|
176
|
+
const handlerRef = useRef<T>(undefined);
|
|
178
177
|
|
|
179
178
|
useIsomorphicLayoutEffect(() => {
|
|
180
179
|
handlerRef.current = handler;
|
|
@@ -186,7 +185,7 @@ export function useEffectEvent<T extends (...args: any[]) => any>(handler: T) {
|
|
|
186
185
|
}, []) as T;
|
|
187
186
|
}
|
|
188
187
|
|
|
189
|
-
// This is a hacky way to fix a bug in
|
|
188
|
+
// This is a hacky way to fix a bug in motion/react where the drag
|
|
190
189
|
// constraints are not updated when window is resized.
|
|
191
190
|
// https://github.com/framer/motion/issues/1659
|
|
192
191
|
const constraints: BoundingBox = { bottom: 0, top: 0, left: 0, right: 0 };
|
package/src/index.tsx
CHANGED
|
@@ -1,24 +1,32 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import type { MotionValue } from 'motion/react';
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
import
|
|
5
|
-
import
|
|
6
|
-
import
|
|
7
|
-
import
|
|
8
|
-
import
|
|
9
|
-
import {
|
|
3
|
+
import type { SheetCompound, SheetDraggableProps } from './types';
|
|
4
|
+
import { Sheet as SheetBase } from './sheet';
|
|
5
|
+
import { SheetContainer } from './SheetContainer';
|
|
6
|
+
import { SheetContent } from './SheetContent';
|
|
7
|
+
import { SheetHeader } from './SheetHeader';
|
|
8
|
+
import { SheetBackdrop } from './SheetBackdrop';
|
|
9
|
+
import { SheetScroller } from './SheetScroller';
|
|
10
10
|
|
|
11
11
|
export interface SheetRef {
|
|
12
12
|
y: MotionValue<number>;
|
|
13
13
|
snapTo: (index: number) => void;
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
16
|
+
export const Sheet: SheetCompound = Object.assign(SheetBase, {
|
|
17
|
+
Container: SheetContainer,
|
|
18
|
+
Header: SheetHeader,
|
|
19
|
+
Content: SheetContent,
|
|
20
|
+
Backdrop: SheetBackdrop,
|
|
21
|
+
Scroller: SheetScroller,
|
|
22
|
+
});
|
|
23
23
|
|
|
24
|
-
|
|
24
|
+
// Export types
|
|
25
|
+
export type SheetHeaderProps = SheetDraggableProps;
|
|
26
|
+
export type SheetContentProps = SheetDraggableProps;
|
|
27
|
+
export type {
|
|
28
|
+
SheetProps,
|
|
29
|
+
SheetBackdropProps,
|
|
30
|
+
SheetScrollerProps,
|
|
31
|
+
SheetContainerProps,
|
|
32
|
+
} from './types';
|
package/src/sheet.tsx
CHANGED
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
useMotionValue,
|
|
20
20
|
useReducedMotion,
|
|
21
21
|
useTransform,
|
|
22
|
-
} from '
|
|
22
|
+
} from 'motion/react';
|
|
23
23
|
|
|
24
24
|
import {
|
|
25
25
|
useModalEffect,
|
|
@@ -40,9 +40,9 @@ import { type SheetContextType, type SheetProps } from './types';
|
|
|
40
40
|
import { SheetScrollerContextProvider, SheetContext } from './context';
|
|
41
41
|
import { getClosest, inDescendingOrder, validateSnapTo } from './utils';
|
|
42
42
|
import { usePreventScroll } from './use-prevent-scroll';
|
|
43
|
-
import styles from './styles';
|
|
43
|
+
import { styles } from './styles';
|
|
44
44
|
|
|
45
|
-
const Sheet = forwardRef<any, SheetProps>(
|
|
45
|
+
export const Sheet = forwardRef<any, SheetProps>(
|
|
46
46
|
(
|
|
47
47
|
{
|
|
48
48
|
onOpenStart,
|
|
@@ -86,7 +86,7 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
86
86
|
|
|
87
87
|
// +2 for tolerance in case the animated value is slightly off
|
|
88
88
|
const zIndex = useTransform(y, (value) =>
|
|
89
|
-
value + 2 >= windowHeight ? -1 :
|
|
89
|
+
value + 2 >= windowHeight ? -1 : (style?.zIndex ?? 9999)
|
|
90
90
|
);
|
|
91
91
|
const visibility = useTransform(y, (value) =>
|
|
92
92
|
value + 2 >= windowHeight ? 'hidden' : 'visible'
|
|
@@ -160,7 +160,7 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
160
160
|
// User flicked the sheet down
|
|
161
161
|
onClose();
|
|
162
162
|
} else {
|
|
163
|
-
//
|
|
163
|
+
// biome-ignore lint/style/noNonNullAssertion: ref should be defined here
|
|
164
164
|
const sheetHeight = sheetRef.current!.getBoundingClientRect().height;
|
|
165
165
|
const currentY = y.get();
|
|
166
166
|
|
|
@@ -186,11 +186,13 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
186
186
|
snapTo = validateSnapTo({ snapTo, sheetHeight });
|
|
187
187
|
|
|
188
188
|
// Update the spring value so that the sheet is animated to the snap point
|
|
189
|
-
|
|
189
|
+
animate(y, snapTo, animationOptions);
|
|
190
190
|
|
|
191
191
|
if (snapPoints && onSnap) {
|
|
192
192
|
const snapValue = Math.abs(Math.round(snapPoints[0] - snapTo));
|
|
193
|
-
const snapIndex = snapPoints.indexOf(
|
|
193
|
+
const snapIndex = snapPoints.indexOf(
|
|
194
|
+
getClosest(snapPoints, snapValue)
|
|
195
|
+
);
|
|
194
196
|
onSnap(snapIndex);
|
|
195
197
|
}
|
|
196
198
|
|
|
@@ -209,7 +211,7 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
209
211
|
if (!snapPoints || !onSnap) return;
|
|
210
212
|
const snapIndex = isOpen ? initialSnap : snapPoints.length - 1;
|
|
211
213
|
onSnap(snapIndex);
|
|
212
|
-
}, [isOpen]);
|
|
214
|
+
}, [isOpen]);
|
|
213
215
|
|
|
214
216
|
useImperativeHandle(ref, () => ({
|
|
215
217
|
y,
|
|
@@ -224,7 +226,7 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
224
226
|
sheetHeight,
|
|
225
227
|
});
|
|
226
228
|
|
|
227
|
-
|
|
229
|
+
animate(y, snapTo, animationOptions);
|
|
228
230
|
if (onSnap) onSnap(snapIndex);
|
|
229
231
|
if (snapTo >= sheetHeight) onClose();
|
|
230
232
|
}
|
|
@@ -253,7 +255,7 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
253
255
|
};
|
|
254
256
|
|
|
255
257
|
return disableDrag ? undefined : dragProps;
|
|
256
|
-
}, [disableDrag, windowHeight]);
|
|
258
|
+
}, [disableDrag, windowHeight]);
|
|
257
259
|
|
|
258
260
|
const context: SheetContextType = {
|
|
259
261
|
y,
|
|
@@ -299,5 +301,3 @@ const Sheet = forwardRef<any, SheetProps>(
|
|
|
299
301
|
);
|
|
300
302
|
|
|
301
303
|
Sheet.displayName = 'Sheet';
|
|
302
|
-
|
|
303
|
-
export default Sheet;
|
package/src/styles.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { type Properties } from 'csstype';
|
|
2
2
|
|
|
3
|
-
const styles: Record<string, Properties> = {
|
|
3
|
+
export const styles: Record<string, Properties> = {
|
|
4
4
|
wrapper: {
|
|
5
5
|
position: 'fixed',
|
|
6
6
|
top: 0,
|
|
@@ -20,6 +20,7 @@ const styles: Record<string, Properties> = {
|
|
|
20
20
|
backgroundColor: 'rgba(0, 0, 0, 0.2)',
|
|
21
21
|
touchAction: 'none', // Disable iOS body scrolling
|
|
22
22
|
border: 'none',
|
|
23
|
+
WebkitTapHighlightColor: 'transparent',
|
|
23
24
|
},
|
|
24
25
|
container: {
|
|
25
26
|
zIndex: 2,
|
|
@@ -64,5 +65,3 @@ const styles: Record<string, Properties> = {
|
|
|
64
65
|
overflowY: 'auto',
|
|
65
66
|
},
|
|
66
67
|
};
|
|
67
|
-
|
|
68
|
-
export default styles;
|
package/src/types.tsx
CHANGED
|
@@ -14,7 +14,6 @@ function chain(...callbacks: any[]): (...args: any[]) => void {
|
|
|
14
14
|
return (...args: any[]) => {
|
|
15
15
|
for (const callback of callbacks) {
|
|
16
16
|
if (typeof callback === 'function') {
|
|
17
|
-
// eslint-disable-next-line n/no-callback-literal
|
|
18
17
|
callback(...args);
|
|
19
18
|
}
|
|
20
19
|
}
|
|
@@ -157,8 +156,12 @@ function preventScrollMobileSafari() {
|
|
|
157
156
|
let lastY = 0;
|
|
158
157
|
|
|
159
158
|
const onTouchStart = (e: TouchEvent) => {
|
|
159
|
+
// Use `composedPath` to support shadow DOM.
|
|
160
|
+
const target = e.composedPath()?.[0] as HTMLElement;
|
|
161
|
+
|
|
160
162
|
// Store the nearest scrollable parent element from the element that the user touched.
|
|
161
|
-
scrollable = getScrollParent(
|
|
163
|
+
scrollable = getScrollParent(target, true);
|
|
164
|
+
|
|
162
165
|
if (
|
|
163
166
|
scrollable === document.documentElement &&
|
|
164
167
|
scrollable === document.body
|
|
@@ -207,7 +210,8 @@ function preventScrollMobileSafari() {
|
|
|
207
210
|
};
|
|
208
211
|
|
|
209
212
|
const onTouchEnd = (e: TouchEvent) => {
|
|
210
|
-
|
|
213
|
+
// Use `composedPath` to support shadow DOM.
|
|
214
|
+
const target = e.composedPath()?.[0] as HTMLElement;
|
|
211
215
|
|
|
212
216
|
// Apply this change if we're not already focused on the target element
|
|
213
217
|
if (willOpenKeyboard(target) && target !== document.activeElement) {
|
|
@@ -225,7 +229,9 @@ function preventScrollMobileSafari() {
|
|
|
225
229
|
};
|
|
226
230
|
|
|
227
231
|
const onFocus = (e: FocusEvent) => {
|
|
228
|
-
|
|
232
|
+
// Use `composedPath` to support shadow DOM.
|
|
233
|
+
const target = e.composedPath()?.[0] as HTMLElement;
|
|
234
|
+
|
|
229
235
|
if (willOpenKeyboard(target)) {
|
|
230
236
|
// Transform also needs to be applied in the focus event in cases where focus moves
|
|
231
237
|
// other than tapping on an input directly, e.g. the next/previous buttons in the
|
package/src/utils.ts
CHANGED
|
@@ -41,9 +41,7 @@ export function validateSnapTo({
|
|
|
41
41
|
return Math.max(Math.round(snapTo), 0);
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
export function mergeRefs<T = any>(
|
|
45
|
-
refs: Array<ForwardedRef<T>>
|
|
46
|
-
): RefCallback<T> {
|
|
44
|
+
export function mergeRefs<T = any>(refs: ForwardedRef<T>[]): RefCallback<T> {
|
|
47
45
|
return (value: any) => {
|
|
48
46
|
refs.forEach((ref: any) => {
|
|
49
47
|
if (typeof ref === 'function') {
|
|
@@ -61,12 +59,10 @@ export function isTouchDevice() {
|
|
|
61
59
|
}
|
|
62
60
|
|
|
63
61
|
function testPlatform(re: RegExp) {
|
|
64
|
-
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
|
65
62
|
return typeof window !== 'undefined' && window.navigator != null
|
|
66
63
|
? re.test(
|
|
67
64
|
// @ts-expect-error
|
|
68
|
-
|
|
69
|
-
window.navigator['userAgentData']?.platform || window.navigator.platform
|
|
65
|
+
window.navigator.userAgentData?.platform || window.navigator.platform
|
|
70
66
|
)
|
|
71
67
|
: false;
|
|
72
68
|
}
|