react-modal-sheet 5.2.1 → 5.2.3

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/debug.ts ADDED
@@ -0,0 +1,37 @@
1
+ /**
2
+ * This library runs on mobile devices so we can't use console.log for debugging. Instead, we can use this function to send debug information to a remote server or store it locally on the device.
3
+ * Instead display the "payload" in a fixed position element on the screen
4
+ * and hide it when that element is clicked. Create the element if it doesn't exist,
5
+ * and update its content with the payload (any JSON data).
6
+ */
7
+ export function debug(payload: any) {
8
+ let debugElement = document.getElementById('debug-element');
9
+
10
+ if (!debugElement) {
11
+ debugElement = document.createElement('div');
12
+ debugElement.id = 'debug-element';
13
+ debugElement.style.position = 'fixed';
14
+ debugElement.style.bottom = '0';
15
+ debugElement.style.left = '0';
16
+ debugElement.style.width = '100%';
17
+ debugElement.style.maxHeight = '50%';
18
+ debugElement.style.overflowY = 'auto';
19
+ debugElement.style.backgroundColor = 'rgba(0, 0, 0, 0.8)';
20
+ debugElement.style.color = 'white';
21
+ debugElement.style.padding = '10px';
22
+ debugElement.style.fontSize = '12px';
23
+ debugElement.style.zIndex = '9999';
24
+ debugElement.style.cursor = 'pointer';
25
+ document.body.appendChild(debugElement);
26
+
27
+ debugElement.addEventListener('click', () => {
28
+ if (debugElement) {
29
+ debugElement.style.display =
30
+ debugElement.style.display === 'none' ? 'block' : 'none';
31
+ }
32
+ });
33
+ }
34
+
35
+ debugElement.textContent = JSON.stringify(payload, null, 2);
36
+ debugElement.style.display = 'block';
37
+ }
@@ -1,4 +1,6 @@
1
- import { useEffect, useState } from 'react';
1
+ import { useEffect, useMemo, useRef, useState } from 'react';
2
+ import { debug } from '../debug';
3
+ import { useStableCallback } from './use-stable-callback';
2
4
 
3
5
  type UseScrollPositionOptions = {
4
6
  /**
@@ -46,68 +48,80 @@ type UseScrollPositionOptions = {
46
48
  export function useScrollPosition(options: UseScrollPositionOptions = {}) {
47
49
  const { debounceDelay = 32, isEnabled = true } = options;
48
50
 
51
+ const scrollTimeoutRef = useRef<number | null>(null);
49
52
  const [element, setElement] = useState<HTMLElement | null>(null);
50
53
  const [scrollPosition, setScrollPosition] = useState<
51
54
  'top' | 'bottom' | 'middle' | undefined
52
55
  >(undefined);
53
56
 
54
- useEffect(() => {
55
- if (!element || !isEnabled) return;
57
+ const scrollRef = useMemo(
58
+ () => (element: HTMLElement | null) => {
59
+ setElement(element);
60
+ },
61
+ []
62
+ );
63
+
64
+ const determineScrollPosition = useStableCallback((element: HTMLElement) => {
65
+ const { scrollTop, scrollHeight, clientHeight } = element;
66
+ const isScrollable = scrollHeight > clientHeight;
67
+
68
+ if (!isScrollable) {
69
+ // Reset scroll position if the content is not scrollable anymore
70
+ if (scrollPosition) setScrollPosition(undefined);
71
+ return;
72
+ }
56
73
 
57
- let scrollTimeout: number | null = null;
74
+ const isAtTop = scrollTop <= 0;
75
+ const isAtBottom =
76
+ Math.ceil(scrollHeight) - Math.ceil(scrollTop) ===
77
+ Math.ceil(clientHeight);
58
78
 
59
- function determineScrollPosition(element: HTMLElement) {
60
- const { scrollTop, scrollHeight, clientHeight } = element;
61
- const isScrollable = scrollHeight > clientHeight;
79
+ let position: 'top' | 'bottom' | 'middle';
62
80
 
63
- if (!isScrollable) {
64
- // Reset scroll position if the content is not scrollable anymore
65
- if (scrollPosition) setScrollPosition(undefined);
66
- return;
67
- }
81
+ if (isAtTop) {
82
+ position = 'top';
83
+ } else if (isAtBottom) {
84
+ position = 'bottom';
85
+ } else {
86
+ position = 'middle';
87
+ }
68
88
 
69
- const isAtTop = scrollTop <= 0;
70
- const isAtBottom =
71
- Math.ceil(scrollHeight) - Math.ceil(scrollTop) ===
72
- Math.ceil(clientHeight);
89
+ // Let browser only handle downward pan gestures (scrolling content up)
90
+ element.style.touchAction = isAtTop ? 'pan-down' : '';
73
91
 
74
- let position: 'top' | 'bottom' | 'middle';
92
+ if (position === scrollPosition) return;
93
+ setScrollPosition(position);
94
+ });
75
95
 
76
- if (isAtTop) {
77
- position = 'top';
78
- } else if (isAtBottom) {
79
- position = 'bottom';
80
- } else {
81
- position = 'middle';
82
- }
96
+ const onScroll = useStableCallback((event: Event) => {
97
+ if (event.currentTarget instanceof HTMLElement) {
98
+ const el = event.currentTarget;
83
99
 
84
- if (position === scrollPosition) return;
85
- setScrollPosition(position);
86
- }
100
+ if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
87
101
 
88
- function onScroll(event: Event) {
89
- if (event.currentTarget instanceof HTMLElement) {
90
- const el = event.currentTarget;
91
-
92
- if (scrollTimeout) clearTimeout(scrollTimeout);
93
-
94
- if (debounceDelay === 0) {
95
- determineScrollPosition(el);
96
- } else {
97
- // Debounce the scroll handler
98
- scrollTimeout = setTimeout(
99
- () => determineScrollPosition(el),
100
- debounceDelay
101
- );
102
- }
102
+ if (debounceDelay === 0) {
103
+ determineScrollPosition(el);
104
+ } else {
105
+ // Debounce the scroll handler
106
+ scrollTimeoutRef.current = setTimeout(
107
+ () => determineScrollPosition(el),
108
+ debounceDelay
109
+ );
103
110
  }
104
111
  }
105
-
106
- function onTouchStart(event: Event) {
107
- if (event.currentTarget instanceof HTMLElement) {
108
- determineScrollPosition(event.currentTarget);
109
- }
112
+ });
113
+
114
+ const onTouchStart = useStableCallback((event: Event) => {
115
+ if (event.currentTarget instanceof HTMLElement) {
116
+ const element = event.currentTarget;
117
+ requestAnimationFrame(() => {
118
+ determineScrollPosition(element);
119
+ });
110
120
  }
121
+ });
122
+
123
+ useEffect(() => {
124
+ if (!element || !isEnabled) return;
111
125
 
112
126
  // Determine initial scroll position
113
127
  determineScrollPosition(element);
@@ -116,14 +130,14 @@ export function useScrollPosition(options: UseScrollPositionOptions = {}) {
116
130
  element.addEventListener('touchstart', onTouchStart);
117
131
 
118
132
  return () => {
119
- if (scrollTimeout) clearTimeout(scrollTimeout);
133
+ if (scrollTimeoutRef.current) clearTimeout(scrollTimeoutRef.current);
120
134
  element.removeEventListener('scroll', onScroll);
121
135
  element.removeEventListener('touchstart', onTouchStart);
122
136
  };
123
137
  }, [element, isEnabled]);
124
138
 
125
139
  return {
126
- scrollRef: (element: HTMLElement | null) => setElement(element),
140
+ scrollRef,
127
141
  scrollPosition,
128
142
  };
129
143
  }
package/src/types.tsx CHANGED
@@ -23,10 +23,9 @@ type CommonProps = {
23
23
  unstyled?: boolean;
24
24
  };
25
25
 
26
- type MotionCommonProps = Omit<
27
- ComponentPropsWithoutRef<typeof motion.div>,
28
- 'initial' | 'animate' | 'exit'
29
- >;
26
+ type MotionDivProps = ComponentPropsWithoutRef<typeof motion.div>;
27
+
28
+ type MotionCommonProps = Omit<MotionDivProps, 'initial' | 'animate' | 'exit'>;
30
29
 
31
30
  export interface SheetTweenConfig {
32
31
  ease: EasingDefinition;
@@ -79,7 +78,7 @@ export type SheetContentProps = MotionCommonProps &
79
78
  scrollStyle?: MotionProps['style'];
80
79
  };
81
80
 
82
- export type SheetBackdropProps = MotionCommonProps & CommonProps;
81
+ export type SheetBackdropProps = MotionDivProps & CommonProps;
83
82
 
84
83
  export type SheetDragIndicatorProps = HTMLAttributes<HTMLDivElement> &
85
84
  CommonProps;