react-modal-sheet 5.4.0 → 5.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.
@@ -6,7 +6,7 @@ import { useDragConstraints } from './hooks/use-drag-constraints';
6
6
  import { useScrollPosition } from './hooks/use-scroll-position';
7
7
  import { styles } from './styles';
8
8
  import { type SheetContentProps } from './types';
9
- import { applyStyles, mergeRefs } from './utils';
9
+ import { applyStyles, isHTTPS, mergeRefs } from './utils';
10
10
 
11
11
  export const SheetContent = forwardRef<any, SheetContentProps>(
12
12
  (
@@ -73,8 +73,14 @@ export const SheetContent = forwardRef<any, SheetContentProps>(
73
73
  const scrollStyle: MotionStyle = applyStyles(styles.scroller, isUnstyled);
74
74
 
75
75
  if (sheetContext.avoidKeyboard) {
76
- scrollStyle.paddingBottom =
77
- 'env(keyboard-inset-height, var(--keyboard-inset-height, 0px))';
76
+ /**
77
+ * Virtual Keyboard API is only available in secure context (https)
78
+ * and it will have 0px height in insecure context,
79
+ * which causes issues when developing and testing on localhost (http).
80
+ */
81
+ scrollStyle.paddingBottom = isHTTPS()
82
+ ? 'env(keyboard-inset-height, var(--keyboard-inset-height, 0px))'
83
+ : 'var(--keyboard-inset-height, 0px)';
78
84
  }
79
85
 
80
86
  if (disableScroll) {
@@ -0,0 +1,75 @@
1
+ import { type RefObject, useEffect, useState } from 'react';
2
+
3
+ import { willOpenKeyboard } from '../utils';
4
+ import { useStableCallback } from './use-stable-callback';
5
+ import { useVirtualKeyboard } from './use-virtual-keyboard';
6
+
7
+ export function useKeyboardAvoidance({
8
+ isEnabled,
9
+ containerRef,
10
+ onWillOpenKeyboard,
11
+ onDidOpenKeyboard,
12
+ }: {
13
+ isEnabled: boolean;
14
+ containerRef: RefObject<HTMLDivElement | null>;
15
+ onWillOpenKeyboard?: (event: FocusEvent) => Promise<void>;
16
+ onDidOpenKeyboard?: (focusedElement: HTMLElement) => void;
17
+ }) {
18
+ const [focusedElement, setFocusedElement] = useState<HTMLElement | null>(
19
+ null
20
+ );
21
+
22
+ const keyboard = useVirtualKeyboard({
23
+ isEnabled,
24
+ containerRef,
25
+ });
26
+
27
+ const handleFocusIn = useStableCallback(async (event: FocusEvent) => {
28
+ const element = event.target as HTMLElement;
29
+
30
+ if (willOpenKeyboard(element) && containerRef.current?.contains(element)) {
31
+ await onWillOpenKeyboard?.(event);
32
+ setFocusedElement(element);
33
+ }
34
+ });
35
+
36
+ const handleFocusOut = useStableCallback((event: FocusEvent) => {
37
+ const element = event.target as HTMLElement;
38
+
39
+ if (focusedElement === element) {
40
+ setFocusedElement(null);
41
+ }
42
+ });
43
+
44
+ // Keep track of the currently focused input within the container
45
+ useEffect(() => {
46
+ if (!isEnabled) return;
47
+
48
+ document.addEventListener('focusin', handleFocusIn);
49
+ document.addEventListener('focusout', handleFocusOut);
50
+
51
+ return () => {
52
+ document.removeEventListener('focusin', handleFocusIn);
53
+ document.removeEventListener('focusout', handleFocusOut);
54
+ };
55
+ }, [isEnabled]);
56
+
57
+ useEffect(() => {
58
+ const containerElement = containerRef.current;
59
+
60
+ if (
61
+ !isEnabled ||
62
+ !focusedElement ||
63
+ !containerElement ||
64
+ !keyboard.isKeyboardOpen
65
+ ) {
66
+ return;
67
+ }
68
+
69
+ requestAnimationFrame(() => {
70
+ onDidOpenKeyboard?.(focusedElement);
71
+ });
72
+ }, [isEnabled, keyboard.isKeyboardOpen, focusedElement]);
73
+
74
+ return keyboard;
75
+ }
@@ -1,7 +1,7 @@
1
1
  // This code originates from https://github.com/adobe/react-spectrum/blob/main/packages/%40react-aria/overlays/src/usePreventScroll.ts
2
2
 
3
+ import { isIOS, willOpenKeyboard } from '../utils';
3
4
  import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
4
- import { isIOS } from '../utils';
5
5
 
6
6
  const KEYBOARD_BUFFER = 24;
7
7
 
@@ -32,17 +32,17 @@ export function isScrollable(
32
32
 
33
33
  const style = window.getComputedStyle(node);
34
34
 
35
- let isScrollable = /(auto|scroll)/.test(
35
+ let scrollable = /(auto|scroll)/.test(
36
36
  style.overflow + style.overflowX + style.overflowY
37
37
  );
38
38
 
39
- if (isScrollable && checkForOverflow) {
40
- isScrollable =
39
+ if (scrollable && checkForOverflow) {
40
+ scrollable =
41
41
  node.scrollHeight !== node.clientHeight ||
42
42
  node.scrollWidth !== node.clientWidth;
43
43
  }
44
44
 
45
- return isScrollable;
45
+ return scrollable;
46
46
  }
47
47
 
48
48
  export function getScrollParent(
@@ -64,19 +64,6 @@ export function getScrollParent(
64
64
  );
65
65
  }
66
66
 
67
- // HTML input types that do not cause the software keyboard to appear.
68
- const nonTextInputTypes = new Set([
69
- 'checkbox',
70
- 'radio',
71
- 'range',
72
- 'color',
73
- 'file',
74
- 'image',
75
- 'button',
76
- 'submit',
77
- 'reset',
78
- ]);
79
-
80
67
  // The number of active usePreventScroll calls. Used to determine whether to revert back to the original page style/scroll position
81
68
  let preventScrollCount = 0;
82
69
  let restore: () => void;
@@ -367,12 +354,3 @@ function scrollIntoView(target: Element) {
367
354
  target = scrollable.parentElement;
368
355
  }
369
356
  }
370
-
371
- function willOpenKeyboard(target: Element) {
372
- return (
373
- (target instanceof HTMLInputElement &&
374
- !nonTextInputTypes.has(target.type)) ||
375
- target instanceof HTMLTextAreaElement ||
376
- (target instanceof HTMLElement && target.isContentEditable)
377
- );
378
- }
@@ -1,5 +1,4 @@
1
1
  import { useEffect, useMemo, useRef, useState } from 'react';
2
- import { debug } from '../debug';
3
2
  import { useStableCallback } from './use-stable-callback';
4
3
 
5
4
  type UseScrollPositionOptions = {
@@ -1,11 +1,36 @@
1
1
  import { type RefObject, useEffect, useRef, useState } from 'react';
2
- import { useStableCallback } from './use-stable-callback';
2
+ import { isHTTPS, willOpenKeyboard } from '../utils';
3
+
4
+ type VirtualKeyboardApi = {
5
+ overlaysContent: boolean;
6
+ boundingRect: {
7
+ x: number;
8
+ y: number;
9
+ height: number;
10
+ width: number;
11
+ };
12
+ addEventListener: (
13
+ type: 'geometrychange',
14
+ listener: EventListenerOrEventListenerObject
15
+ ) => void;
16
+ removeEventListener: (
17
+ type: 'geometrychange',
18
+ listener: EventListenerOrEventListenerObject
19
+ ) => void;
20
+ };
3
21
 
4
22
  type VirtualKeyboardState = {
5
23
  isVisible: boolean;
6
24
  height: number;
7
25
  };
8
26
 
27
+ /**
28
+ * Keep track of how many components are using the virtual keyboard API
29
+ * to avoid conflicts when toggling `overlaysContent` which is a global setting.
30
+ */
31
+ let virtualKeyboardOverlayUsers = 0;
32
+ let initialVirtualKeyboardOverlaysContent: boolean | null = null;
33
+
9
34
  type UseVirtualKeyboardOptions = {
10
35
  /**
11
36
  * Ref to the container element to apply `keyboard-inset-height` CSS variable updates.
@@ -22,11 +47,6 @@ type UseVirtualKeyboardOptions = {
22
47
  * @default 100
23
48
  */
24
49
  visualViewportThreshold?: number;
25
- /**
26
- * Whether to treat contenteditable elements as text inputs.
27
- * @default true
28
- */
29
- includeContentEditable?: boolean;
30
50
  /**
31
51
  * Delay in ms for debouncing viewport changes.
32
52
  * @default 100
@@ -66,7 +86,6 @@ export function useVirtualKeyboard(options: UseVirtualKeyboardOptions = {}) {
66
86
  containerRef,
67
87
  isEnabled = true,
68
88
  debounceDelay = 100,
69
- includeContentEditable = true,
70
89
  visualViewportThreshold = 100,
71
90
  } = options;
72
91
 
@@ -78,27 +97,14 @@ export function useVirtualKeyboard(options: UseVirtualKeyboardOptions = {}) {
78
97
  const focusedElementRef = useRef<HTMLElement | null>(null);
79
98
  const debounceTimer = useRef<ReturnType<typeof setTimeout> | null>(null);
80
99
 
81
- const isTextInput = useStableCallback((el: Element | null) => {
82
- return (
83
- el?.tagName === 'INPUT' ||
84
- el?.tagName === 'TEXTAREA' ||
85
- (includeContentEditable &&
86
- el instanceof HTMLElement &&
87
- el.isContentEditable)
88
- );
89
- });
90
-
91
100
  useEffect(() => {
92
- if (!isEnabled) return;
93
-
94
101
  const vv = window.visualViewport;
95
- const vk = (navigator as any).virtualKeyboard;
102
+ const vk = getVirtualKeyboardApi();
96
103
 
97
104
  function setKeyboardInsetHeightEnv(height: number) {
98
105
  const element = containerRef?.current || document.documentElement;
99
106
 
100
- // Virtual Keyboard API is only available in secure context
101
- if (window.isSecureContext) {
107
+ if (vk) {
102
108
  element.style.setProperty(
103
109
  '--keyboard-inset-height',
104
110
  `env(keyboard-inset-height, ${height}px)`
@@ -108,29 +114,46 @@ export function useVirtualKeyboard(options: UseVirtualKeyboardOptions = {}) {
108
114
  }
109
115
  }
110
116
 
111
- function handleFocusIn(e: FocusEvent) {
112
- if (e.target instanceof HTMLElement && isTextInput(e.target)) {
113
- focusedElementRef.current = e.target;
114
- updateKeyboardState();
115
- }
117
+ function setKeyboardState(nextState: VirtualKeyboardState) {
118
+ setState((prevState) =>
119
+ prevState.isVisible === nextState.isVisible &&
120
+ prevState.height === nextState.height
121
+ ? prevState
122
+ : nextState
123
+ );
116
124
  }
117
125
 
118
- function handleFocusOut() {
126
+ function resetKeyboardState() {
119
127
  focusedElementRef.current = null;
120
- updateKeyboardState();
128
+ setKeyboardInsetHeightEnv(0);
129
+ setKeyboardState({ isVisible: false, height: 0 });
130
+ }
131
+
132
+ if (!isEnabled) {
133
+ resetKeyboardState();
134
+ return;
121
135
  }
122
136
 
123
137
  function updateKeyboardState() {
124
138
  if (debounceTimer.current) {
125
139
  clearTimeout(debounceTimer.current);
126
140
  }
141
+
127
142
  debounceTimer.current = setTimeout(() => {
128
- const active = focusedElementRef.current;
129
- const inputIsFocused = isTextInput(active);
143
+ const active = getActiveElement() ?? focusedElementRef.current;
144
+ const inputIsFocused = active ? willOpenKeyboard(active) : false;
130
145
 
131
146
  if (!inputIsFocused) {
132
- setKeyboardInsetHeightEnv(0);
133
- setState({ isVisible: false, height: 0 });
147
+ resetKeyboardState();
148
+ return;
149
+ }
150
+
151
+ focusedElementRef.current = active as HTMLElement;
152
+
153
+ if (vk?.overlaysContent) {
154
+ const keyboardHeight = vk.boundingRect.height;
155
+ setKeyboardInsetHeightEnv(keyboardHeight);
156
+ setKeyboardState({ isVisible: true, height: keyboardHeight });
134
157
  return;
135
158
  }
136
159
 
@@ -139,33 +162,53 @@ export function useVirtualKeyboard(options: UseVirtualKeyboardOptions = {}) {
139
162
 
140
163
  if (heightDiff > visualViewportThreshold) {
141
164
  setKeyboardInsetHeightEnv(heightDiff);
142
- setState({ isVisible: true, height: heightDiff });
143
- } else {
144
- setKeyboardInsetHeightEnv(0);
145
- setState({ isVisible: false, height: 0 });
165
+ setKeyboardState({ isVisible: true, height: heightDiff });
166
+ return;
146
167
  }
147
168
  }
169
+
170
+ resetKeyboardState();
148
171
  }, debounceDelay);
149
172
  }
150
173
 
151
- window.addEventListener('focusin', handleFocusIn);
152
- window.addEventListener('focusout', handleFocusOut);
174
+ function handleFocusIn(e: FocusEvent) {
175
+ if (e.target instanceof HTMLElement && willOpenKeyboard(e.target)) {
176
+ focusedElementRef.current = e.target;
177
+ updateKeyboardState();
178
+ }
179
+ }
180
+
181
+ function handleFocusOut() {
182
+ requestAnimationFrame(() => {
183
+ focusedElementRef.current = getActiveElement();
184
+ updateKeyboardState();
185
+ });
186
+ }
187
+
188
+ document.addEventListener('focusin', handleFocusIn);
189
+ document.addEventListener('focusout', handleFocusOut);
153
190
 
154
191
  if (vv) {
155
192
  vv.addEventListener('resize', updateKeyboardState);
156
193
  vv.addEventListener('scroll', updateKeyboardState);
157
194
  }
158
195
 
159
- let currentOverlaysContent = false;
160
-
161
196
  if (vk) {
162
- currentOverlaysContent = vk.overlaysContent;
163
- vk.overlaysContent = true;
197
+ if (virtualKeyboardOverlayUsers === 0) {
198
+ initialVirtualKeyboardOverlaysContent = vk.overlaysContent;
199
+ vk.overlaysContent = true;
200
+ }
201
+
202
+ virtualKeyboardOverlayUsers++;
203
+ vk.addEventListener('geometrychange', updateKeyboardState);
164
204
  }
165
205
 
206
+ focusedElementRef.current = getActiveElement();
207
+ updateKeyboardState();
208
+
166
209
  return () => {
167
- window.removeEventListener('focusin', handleFocusIn);
168
- window.removeEventListener('focusout', handleFocusOut);
210
+ document.removeEventListener('focusin', handleFocusIn);
211
+ document.removeEventListener('focusout', handleFocusOut);
169
212
 
170
213
  if (vv) {
171
214
  vv.removeEventListener('resize', updateKeyboardState);
@@ -173,22 +216,54 @@ export function useVirtualKeyboard(options: UseVirtualKeyboardOptions = {}) {
173
216
  }
174
217
 
175
218
  if (vk) {
176
- vk.overlaysContent = currentOverlaysContent;
219
+ vk.removeEventListener('geometrychange', updateKeyboardState);
220
+
221
+ virtualKeyboardOverlayUsers = Math.max(
222
+ 0,
223
+ virtualKeyboardOverlayUsers - 1
224
+ );
225
+
226
+ if (virtualKeyboardOverlayUsers === 0) {
227
+ vk.overlaysContent = initialVirtualKeyboardOverlaysContent ?? false;
228
+ initialVirtualKeyboardOverlaysContent = null;
229
+ }
177
230
  }
178
231
 
179
232
  if (debounceTimer.current) {
180
233
  clearTimeout(debounceTimer.current);
181
234
  }
235
+
236
+ resetKeyboardState();
182
237
  };
183
- }, [
184
- debounceDelay,
185
- includeContentEditable,
186
- isEnabled,
187
- visualViewportThreshold,
188
- ]);
238
+ }, [debounceDelay, isEnabled, visualViewportThreshold]);
189
239
 
190
240
  return {
191
241
  keyboardHeight: state.height,
192
242
  isKeyboardOpen: state.isVisible,
193
243
  };
194
244
  }
245
+
246
+ /**
247
+ * Virtual Keyboard API is only available in secure contexts (HTTPS)
248
+ * and may not be supported in all browsers.
249
+ */
250
+ function getVirtualKeyboardApi() {
251
+ return isHTTPS() && 'virtualKeyboard' in navigator
252
+ ? (navigator.virtualKeyboard as VirtualKeyboardApi)
253
+ : null;
254
+ }
255
+
256
+ function getActiveElement() {
257
+ let activeElement: Element | null = document.activeElement;
258
+
259
+ while (
260
+ activeElement instanceof HTMLElement &&
261
+ activeElement.shadowRoot?.activeElement
262
+ ) {
263
+ activeElement = activeElement.shadowRoot.activeElement;
264
+ }
265
+
266
+ return activeElement && willOpenKeyboard(activeElement)
267
+ ? (activeElement as HTMLElement)
268
+ : null;
269
+ }
package/src/sheet.tsx CHANGED
@@ -9,7 +9,6 @@ import {
9
9
  } from 'motion/react';
10
10
  import React, {
11
11
  forwardRef,
12
- useEffect,
13
12
  useImperativeHandle,
14
13
  useRef,
15
14
  useState,
@@ -26,12 +25,11 @@ import {
26
25
  } from './constants';
27
26
  import { SheetContext } from './context';
28
27
  import { useDimensions } from './hooks/use-dimensions';
29
- import { useFocusedInput } from './hooks/use-focused-input';
28
+ import { useKeyboardAvoidance } from './hooks/use-keyboard-avoidance';
30
29
  import { useModalEffect } from './hooks/use-modal-effect';
31
30
  import { usePreventScroll } from './hooks/use-prevent-scroll';
32
31
  import { useSheetState } from './hooks/use-sheet-state';
33
32
  import { useStableCallback } from './hooks/use-stable-callback';
34
- import { useVirtualKeyboard } from './hooks/use-virtual-keyboard';
35
33
  import {
36
34
  computeSnapPoints,
37
35
  handleHighVelocityDrag,
@@ -39,7 +37,7 @@ import {
39
37
  } from './snap';
40
38
  import { styles } from './styles';
41
39
  import { type SheetContextType, type SheetProps } from './types';
42
- import { applyStyles, waitForElement } from './utils';
40
+ import { applyStyles, waitForElement, willOpenKeyboard } from './utils';
43
41
 
44
42
  export const Sheet = forwardRef<any, SheetProps>(
45
43
  (
@@ -98,14 +96,6 @@ export const Sheet = forwardRef<any, SheetProps>(
98
96
  ...(reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig),
99
97
  };
100
98
 
101
- const keyboard = useVirtualKeyboard({
102
- isEnabled: isOpen && avoidKeyboard,
103
- containerRef: sheetRef,
104
- });
105
-
106
- // Disable drag if the keyboard is open to avoid weird behavior
107
- const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
108
-
109
99
  // +2 for tolerance in case the animated value is slightly off
110
100
  const zIndex = useTransform(y, (val) =>
111
101
  val + 2 >= closedY ? -1 : (style?.zIndex ?? 9999)
@@ -158,22 +148,70 @@ export const Sheet = forwardRef<any, SheetProps>(
158
148
  });
159
149
  });
160
150
 
151
+ const keyboard = useKeyboardAvoidance({
152
+ isEnabled: isOpen && avoidKeyboard,
153
+ containerRef: sheetRef,
154
+ onWillOpenKeyboard: async () => {
155
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
156
+
157
+ /**
158
+ * If there are snap points and the sheet is not already at the last snap point,
159
+ * move it there to make sure the focused input is not covered by the keyboard
160
+ */
161
+ if (lastSnapPoint && lastSnapPoint.snapIndex !== currentSnap) {
162
+ await animate(y, lastSnapPoint.snapValueY, animationOptions);
163
+ updateSnap(lastSnapPoint.snapIndex);
164
+ }
165
+ },
166
+ onDidOpenKeyboard: (focusedElement) => {
167
+ const sheetElement = sheetRef.current;
168
+ if (!sheetElement) return;
169
+
170
+ const inputRect = focusedElement.getBoundingClientRect();
171
+ const containerRect = sheetElement.getBoundingClientRect();
172
+ const scroller = sheetElement.querySelector(
173
+ '.react-modal-sheet-content-scroller'
174
+ ) as HTMLElement;
175
+
176
+ const scrollTarget = Math.max(
177
+ inputRect.top -
178
+ containerRect.top +
179
+ scroller.scrollTop -
180
+ inputRect.height,
181
+ 0
182
+ );
183
+
184
+ requestAnimationFrame(() => {
185
+ scroller.scrollTo({ top: scrollTarget, behavior: 'smooth' });
186
+ });
187
+ },
188
+ });
189
+
190
+ // Disable drag if the keyboard is open to avoid weird behavior
191
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
192
+
161
193
  const blurActiveInput = useStableCallback(() => {
162
- // Find focused input inside the sheet and blur it when dragging starts
163
- // to prevent a weird ghost caret "bug" on mobile
194
+ /**
195
+ * Find focused input inside the sheet and blur it when dragging starts
196
+ * to prevent a weird ghost caret "bug" on mobile
197
+ */
164
198
  const focusedElement = document.activeElement as HTMLElement | null;
165
- if (!focusedElement || !sheetRef.current) return;
166
-
167
- const isInput =
168
- focusedElement.tagName === 'INPUT' ||
169
- focusedElement.tagName === 'TEXTAREA';
170
199
 
171
200
  // Only blur the focused element if it's inside the sheet
172
- if (isInput && sheetRef.current.contains(focusedElement)) {
201
+ if (
202
+ focusedElement &&
203
+ willOpenKeyboard(focusedElement) &&
204
+ sheetRef.current?.contains(focusedElement)
205
+ ) {
173
206
  focusedElement.blur();
174
207
  }
175
208
  });
176
209
 
210
+ const onDragStart = useStableCallback<DragHandler>((event, info) => {
211
+ blurActiveInput();
212
+ onDragStartProp?.(event, info);
213
+ });
214
+
177
215
  const onDrag = useStableCallback<DragHandler>((event, info) => {
178
216
  onDragProp?.(event, info);
179
217
 
@@ -188,11 +226,6 @@ export const Sheet = forwardRef<any, SheetProps>(
188
226
  y.set(Math.max(currentY + info.delta.y, 0));
189
227
  });
190
228
 
191
- const onDragStart = useStableCallback<DragHandler>((event, info) => {
192
- blurActiveInput();
193
- onDragStartProp?.(event, info);
194
- });
195
-
196
229
  const onDragEnd = useStableCallback<DragHandler>((event, info) => {
197
230
  blurActiveInput();
198
231
  onDragEndProp?.(event, info);
@@ -295,67 +328,6 @@ export const Sheet = forwardRef<any, SheetProps>(
295
328
  isDisabled: disableScrollLocking || !isOpen,
296
329
  });
297
330
 
298
- const focusedInput = useFocusedInput({
299
- isEnabled: isOpen && avoidKeyboard,
300
- sheetRef,
301
- });
302
-
303
- /**
304
- * Keyboard avoidance.
305
- *
306
- * Move the sheet up when the keyboard opens if the sheet has snap points,
307
- * and then scroll the focused input into view to make sure it's not covered
308
- * by the keyboard.
309
- */
310
- useEffect(() => {
311
- if (!isOpen || !avoidKeyboard) {
312
- return;
313
- }
314
-
315
- async function adjustSheetForKeyboard() {
316
- const sheetElement = sheetRef.current;
317
-
318
- if (!sheetElement || !focusedInput || !keyboard.isKeyboardOpen) {
319
- return;
320
- }
321
-
322
- /**
323
- * If we have snap points and the sheet is currently snapped to a point
324
- * that is being covered by the keyboard, we want to move the sheet up
325
- * to reveal it.
326
- */
327
- const lastSnapPoint = snapPoints[snapPoints.length - 1];
328
-
329
- if (lastSnapPoint) {
330
- await animate(y, lastSnapPoint.snapValueY, animationOptions);
331
- updateSnap(lastSnapPoint.snapIndex);
332
- }
333
-
334
- // Then scroll the focused input into view if it's covered by the keyboard
335
- requestAnimationFrame(() => {
336
- const inputRect = focusedInput.getBoundingClientRect();
337
- const sheetRect = sheetElement.getBoundingClientRect();
338
- const scroller = sheetElement.querySelector(
339
- '.react-modal-sheet-content-scroller'
340
- ) as HTMLElement;
341
-
342
- const scrollTarget = Math.max(
343
- inputRect.top -
344
- sheetRect.top +
345
- scroller.scrollTop -
346
- inputRect.height,
347
- 0
348
- );
349
-
350
- requestAnimationFrame(() => {
351
- scroller.scrollTo({ top: scrollTarget, behavior: 'smooth' });
352
- });
353
- });
354
- }
355
-
356
- adjustSheetForKeyboard();
357
- }, [isOpen, avoidKeyboard, keyboard.isKeyboardOpen, focusedInput]);
358
-
359
331
  const state = useSheetState({
360
332
  isOpen,
361
333
  onOpen: async () => {
package/src/types.tsx CHANGED
@@ -7,7 +7,6 @@ import {
7
7
  } from 'motion/react';
8
8
  import {
9
9
  type ComponentPropsWithoutRef,
10
- type CSSProperties,
11
10
  type ForwardRefExoticComponent,
12
11
  type FunctionComponent,
13
12
  type HTMLAttributes,
@@ -108,7 +107,7 @@ export interface SheetContextType {
108
107
  currentSnap?: number;
109
108
  detent: SheetDetent;
110
109
  disableDrag: boolean;
111
- dragProps?: SheetDragProps;
110
+ dragProps: SheetDragProps;
112
111
  indicatorRotation: MotionValue<number>;
113
112
  avoidKeyboard: boolean;
114
113
  prefersReducedMotion: boolean;
package/src/utils.ts CHANGED
@@ -88,3 +88,29 @@ export function waitForElement(
88
88
  }, interval);
89
89
  });
90
90
  }
91
+
92
+ // HTML input types that do not cause the software keyboard to appear.
93
+ const nonTextInputTypes = new Set([
94
+ 'checkbox',
95
+ 'radio',
96
+ 'range',
97
+ 'color',
98
+ 'file',
99
+ 'image',
100
+ 'button',
101
+ 'submit',
102
+ 'reset',
103
+ ]);
104
+
105
+ export function willOpenKeyboard(target: Element) {
106
+ return (
107
+ (target instanceof HTMLInputElement &&
108
+ !nonTextInputTypes.has(target.type)) ||
109
+ target instanceof HTMLTextAreaElement ||
110
+ (target instanceof HTMLElement && target.isContentEditable)
111
+ );
112
+ }
113
+
114
+ export function isHTTPS() {
115
+ return typeof window !== 'undefined' && window.isSecureContext;
116
+ }