react-modal-sheet 3.3.0 → 3.4.1
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/dist/index.js +213 -110
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +209 -106
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -4
- package/src/SheetScroller.tsx +6 -0
- package/src/hooks.tsx +85 -16
- package/src/sheet.tsx +5 -1
- package/src/use-prevent-scroll.ts +80 -21
- package/src/utils.ts +41 -42
|
@@ -1,18 +1,69 @@
|
|
|
1
|
-
//
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
} from '@react-aria/utils';
|
|
1
|
+
// This code originates from https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/overlays/src/usePreventScroll.ts
|
|
2
|
+
|
|
3
|
+
import { useIsomorphicLayoutEffect } from './hooks';
|
|
4
|
+
import { isIOS } from './utils';
|
|
5
|
+
|
|
6
|
+
const KEYBOARD_BUFFER = 24;
|
|
8
7
|
|
|
9
8
|
interface PreventScrollOptions {
|
|
10
9
|
/** Whether the scroll lock is disabled. */
|
|
11
10
|
isDisabled?: boolean;
|
|
12
11
|
}
|
|
13
12
|
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
function chain(...callbacks: any[]): (...args: any[]) => void {
|
|
14
|
+
return (...args: any[]) => {
|
|
15
|
+
for (const callback of callbacks) {
|
|
16
|
+
if (typeof callback === 'function') {
|
|
17
|
+
// eslint-disable-next-line n/no-callback-literal
|
|
18
|
+
callback(...args);
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const visualViewport = typeof document !== 'undefined' && window.visualViewport;
|
|
25
|
+
|
|
26
|
+
export function isScrollable(
|
|
27
|
+
node: Element | null,
|
|
28
|
+
checkForOverflow?: boolean
|
|
29
|
+
): boolean {
|
|
30
|
+
if (!node) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const style = window.getComputedStyle(node);
|
|
35
|
+
|
|
36
|
+
let isScrollable = /(auto|scroll)/.test(
|
|
37
|
+
style.overflow + style.overflowX + style.overflowY
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
if (isScrollable && checkForOverflow) {
|
|
41
|
+
isScrollable =
|
|
42
|
+
node.scrollHeight !== node.clientHeight ||
|
|
43
|
+
node.scrollWidth !== node.clientWidth;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
return isScrollable;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function getScrollParent(
|
|
50
|
+
node: Element,
|
|
51
|
+
checkForOverflow?: boolean
|
|
52
|
+
): Element {
|
|
53
|
+
let scrollableNode: Element | null = node;
|
|
54
|
+
|
|
55
|
+
if (isScrollable(scrollableNode, checkForOverflow)) {
|
|
56
|
+
scrollableNode = scrollableNode.parentElement;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
while (scrollableNode && !isScrollable(scrollableNode, checkForOverflow)) {
|
|
60
|
+
scrollableNode = scrollableNode.parentElement;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return (
|
|
64
|
+
scrollableNode || document.scrollingElement || document.documentElement
|
|
65
|
+
);
|
|
66
|
+
}
|
|
16
67
|
|
|
17
68
|
// HTML input types that do not cause the software keyboard to appear.
|
|
18
69
|
const nonTextInputTypes = new Set([
|
|
@@ -29,7 +80,7 @@ const nonTextInputTypes = new Set([
|
|
|
29
80
|
|
|
30
81
|
// The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
|
|
31
82
|
let preventScrollCount = 0;
|
|
32
|
-
let restore:
|
|
83
|
+
let restore: () => void;
|
|
33
84
|
|
|
34
85
|
/**
|
|
35
86
|
* Prevents scrolling on the document body on mount, and
|
|
@@ -39,7 +90,7 @@ let restore: any;
|
|
|
39
90
|
export function usePreventScroll(options: PreventScrollOptions = {}) {
|
|
40
91
|
const { isDisabled } = options;
|
|
41
92
|
|
|
42
|
-
|
|
93
|
+
useIsomorphicLayoutEffect(() => {
|
|
43
94
|
if (isDisabled) {
|
|
44
95
|
return;
|
|
45
96
|
}
|
|
@@ -56,7 +107,7 @@ export function usePreventScroll(options: PreventScrollOptions = {}) {
|
|
|
56
107
|
return () => {
|
|
57
108
|
preventScrollCount--;
|
|
58
109
|
if (preventScrollCount === 0) {
|
|
59
|
-
restore();
|
|
110
|
+
restore?.();
|
|
60
111
|
}
|
|
61
112
|
};
|
|
62
113
|
}, [isDisabled]);
|
|
@@ -107,7 +158,7 @@ function preventScrollMobileSafari() {
|
|
|
107
158
|
|
|
108
159
|
const onTouchStart = (e: TouchEvent) => {
|
|
109
160
|
// Store the nearest scrollable parent element from the element that the user touched.
|
|
110
|
-
scrollable = getScrollParent(e.target as Element);
|
|
161
|
+
scrollable = getScrollParent(e.target as Element, true);
|
|
111
162
|
if (
|
|
112
163
|
scrollable === document.documentElement &&
|
|
113
164
|
scrollable === document.body
|
|
@@ -128,6 +179,7 @@ function preventScrollMobileSafari() {
|
|
|
128
179
|
|
|
129
180
|
// Prevent scrolling the window.
|
|
130
181
|
if (
|
|
182
|
+
!scrollable ||
|
|
131
183
|
scrollable === document.documentElement ||
|
|
132
184
|
scrollable === document.body
|
|
133
185
|
) {
|
|
@@ -143,7 +195,6 @@ function preventScrollMobileSafari() {
|
|
|
143
195
|
const scrollTop = scrollable.scrollTop;
|
|
144
196
|
const bottom = scrollable.scrollHeight - scrollable.clientHeight;
|
|
145
197
|
|
|
146
|
-
// Fix for: https://github.com/adobe/react-spectrum/pull/3780/files
|
|
147
198
|
if (bottom === 0) {
|
|
148
199
|
return;
|
|
149
200
|
}
|
|
@@ -198,9 +249,7 @@ function preventScrollMobileSafari() {
|
|
|
198
249
|
// measure the correct position to scroll to.
|
|
199
250
|
visualViewport.addEventListener(
|
|
200
251
|
'resize',
|
|
201
|
-
() =>
|
|
202
|
-
scrollIntoView(target);
|
|
203
|
-
},
|
|
252
|
+
() => scrollIntoView(target),
|
|
204
253
|
{ once: true }
|
|
205
254
|
);
|
|
206
255
|
}
|
|
@@ -261,6 +310,7 @@ function preventScrollMobileSafari() {
|
|
|
261
310
|
|
|
262
311
|
// Sets a CSS property on an element, and returns a function to revert it to the previous value.
|
|
263
312
|
function setStyle(element: any, style: string, value: string) {
|
|
313
|
+
// https://github.com/microsoft/TypeScript/issues/17827#issuecomment-391663310
|
|
264
314
|
const cur = element.style[style];
|
|
265
315
|
element.style[style] = value;
|
|
266
316
|
|
|
@@ -276,9 +326,12 @@ function addEvent<K extends keyof GlobalEventHandlersEventMap>(
|
|
|
276
326
|
handler: (this: Document, ev: GlobalEventHandlersEventMap[K]) => any,
|
|
277
327
|
options?: boolean | AddEventListenerOptions
|
|
278
328
|
) {
|
|
279
|
-
|
|
329
|
+
// @ts-expect-error
|
|
330
|
+
target.addEventListener(event, handler, options);
|
|
331
|
+
|
|
280
332
|
return () => {
|
|
281
|
-
|
|
333
|
+
// @ts-expect-error
|
|
334
|
+
target.removeEventListener(event, handler, options);
|
|
282
335
|
};
|
|
283
336
|
}
|
|
284
337
|
|
|
@@ -294,12 +347,18 @@ function scrollIntoView(target: Element) {
|
|
|
294
347
|
) {
|
|
295
348
|
const scrollableTop = scrollable.getBoundingClientRect().top;
|
|
296
349
|
const targetTop = target.getBoundingClientRect().top;
|
|
297
|
-
|
|
350
|
+
const targetBottom = target.getBoundingClientRect().bottom;
|
|
351
|
+
// Buffer is needed for some edge cases
|
|
352
|
+
const keyboardHeight =
|
|
353
|
+
scrollable.getBoundingClientRect().bottom + KEYBOARD_BUFFER;
|
|
354
|
+
|
|
355
|
+
if (targetBottom > keyboardHeight) {
|
|
298
356
|
scrollable.scrollTop += targetTop - scrollableTop;
|
|
299
357
|
}
|
|
300
358
|
}
|
|
301
359
|
|
|
302
|
-
|
|
360
|
+
// @ts-expect-error
|
|
361
|
+
target = scrollable.parentElement;
|
|
303
362
|
}
|
|
304
363
|
}
|
|
305
364
|
|
package/src/utils.ts
CHANGED
|
@@ -15,48 +15,6 @@ export function getClosest(nums: number[], goal: number) {
|
|
|
15
15
|
return closest;
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export function applyRootStyles(rootId: string) {
|
|
19
|
-
const body = document.querySelector('body') as HTMLBodyElement;
|
|
20
|
-
const root = document.querySelector(`#${rootId}`) as HTMLDivElement;
|
|
21
|
-
|
|
22
|
-
if (root) {
|
|
23
|
-
const p = 24;
|
|
24
|
-
const h = window.innerHeight;
|
|
25
|
-
const s = (h - p) / h;
|
|
26
|
-
body.style.backgroundColor = '#000';
|
|
27
|
-
root.style.overflow = 'hidden';
|
|
28
|
-
root.style.willChange = 'transform';
|
|
29
|
-
root.style.transition =
|
|
30
|
-
'transform 200ms ease-in-out, border-radius 200ms linear';
|
|
31
|
-
root.style.transform = `translateY(calc(env(safe-area-inset-top) + ${p / 2}px)) scale(${s})`; // prettier-ignore
|
|
32
|
-
root.style.borderTopRightRadius = '10px';
|
|
33
|
-
root.style.borderTopLeftRadius = '10px';
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export function cleanupRootStyles(rootId: string) {
|
|
38
|
-
const body = document.querySelector('body') as HTMLBodyElement;
|
|
39
|
-
const root = document.getElementById(rootId) as HTMLDivElement;
|
|
40
|
-
|
|
41
|
-
function onTransitionEnd() {
|
|
42
|
-
root.style.removeProperty('overflow');
|
|
43
|
-
root.style.removeProperty('will-change');
|
|
44
|
-
root.style.removeProperty('transition');
|
|
45
|
-
body.style.removeProperty('background-color');
|
|
46
|
-
root.removeEventListener('transitionend', onTransitionEnd);
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
if (root) {
|
|
50
|
-
// Start animating back
|
|
51
|
-
root.style.removeProperty('border-top-right-radius');
|
|
52
|
-
root.style.removeProperty('border-top-left-radius');
|
|
53
|
-
root.style.removeProperty('transform');
|
|
54
|
-
|
|
55
|
-
// Remove temp properties after animation is finished
|
|
56
|
-
root.addEventListener('transitionend', onTransitionEnd);
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
18
|
export function inDescendingOrder(arr: number[]) {
|
|
61
19
|
for (let i = 0; i < arr.length; i++) {
|
|
62
20
|
if (arr[i + 1] > arr[i]) return false;
|
|
@@ -101,3 +59,44 @@ export function isTouchDevice() {
|
|
|
101
59
|
if (typeof window === 'undefined') return false;
|
|
102
60
|
return 'ontouchstart' in window || navigator.maxTouchPoints > 0;
|
|
103
61
|
}
|
|
62
|
+
|
|
63
|
+
function testPlatform(re: RegExp) {
|
|
64
|
+
// eslint-disable-next-line @typescript-eslint/prefer-optional-chain
|
|
65
|
+
return typeof window !== 'undefined' && window.navigator != null
|
|
66
|
+
? re.test(
|
|
67
|
+
// @ts-expect-error
|
|
68
|
+
// eslint-disable-next-line @typescript-eslint/dot-notation
|
|
69
|
+
window.navigator['userAgentData']?.platform || window.navigator.platform
|
|
70
|
+
)
|
|
71
|
+
: false;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function cached(fn: () => boolean) {
|
|
75
|
+
let res: boolean | null = null;
|
|
76
|
+
return () => {
|
|
77
|
+
if (res == null) {
|
|
78
|
+
res = fn();
|
|
79
|
+
}
|
|
80
|
+
return res;
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const isMac = cached(function () {
|
|
85
|
+
return testPlatform(/^Mac/i);
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
const isIPhone = cached(function () {
|
|
89
|
+
return testPlatform(/^iPhone/i);
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
const isIPad = cached(function () {
|
|
93
|
+
return (
|
|
94
|
+
testPlatform(/^iPad/i) ||
|
|
95
|
+
// iPadOS 13 lies and says it's a Mac, but we can distinguish by detecting touch support.
|
|
96
|
+
(isMac() && navigator.maxTouchPoints > 1)
|
|
97
|
+
);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
export const isIOS = cached(function () {
|
|
101
|
+
return isIPhone() || isIPad();
|
|
102
|
+
});
|