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.
@@ -1,18 +1,69 @@
1
- // Source: https://github.com/adobe/react-spectrum/blob/main/packages/@react-aria/overlays/src/usePreventScroll.ts
2
- import {
3
- chain,
4
- getScrollParent,
5
- isIOS,
6
- useLayoutEffect,
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
- const visualViewport =
15
- typeof window !== 'undefined' ? (window as any).visualViewport : undefined;
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: any;
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
- useLayoutEffect(() => {
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
- target.addEventListener(event, handler as any, options);
329
+ // @ts-expect-error
330
+ target.addEventListener(event, handler, options);
331
+
280
332
  return () => {
281
- target.removeEventListener(event, handler as any, options);
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
- if (targetTop > scrollableTop + target.clientHeight) {
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
- target = scrollable.parentElement as any;
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
+ });