react-modal-sheet 5.3.0 → 5.4.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/src/debug.ts CHANGED
@@ -4,7 +4,7 @@
4
4
  * and hide it when that element is clicked. Create the element if it doesn't exist,
5
5
  * and update its content with the payload (any JSON data).
6
6
  */
7
- export function debug(payload: any, placement: 'top' | 'bottom' = 'bottom') {
7
+ export function debug(payload: any, placement: 'top' | 'bottom' = 'top') {
8
8
  let debugElement = document.getElementById('debug-element');
9
9
 
10
10
  if (!debugElement) {
@@ -32,6 +32,7 @@ export function debug(payload: any, placement: 'top' | 'bottom' = 'bottom') {
32
32
  });
33
33
  }
34
34
 
35
- debugElement.textContent = JSON.stringify(payload, null, 2);
35
+ const id = Math.random().toString(36).substring(2, 7);
36
+ debugElement.textContent = JSON.stringify(payload, null, 2) + ` (${id})`;
36
37
  debugElement.style.display = 'block';
37
38
  }
@@ -0,0 +1,46 @@
1
+ import { type RefObject, useEffect, useState } from 'react';
2
+
3
+ export function useFocusedInput({
4
+ isEnabled,
5
+ sheetRef,
6
+ }: {
7
+ isEnabled: boolean;
8
+ sheetRef: RefObject<HTMLElement | null>;
9
+ }) {
10
+ const [focusedInput, setFocusedInput] = useState<HTMLElement | null>(null);
11
+
12
+ // Keep track of the currently focused input within the sheet
13
+ useEffect(() => {
14
+ if (!isEnabled) return;
15
+
16
+ const handleFocusIn = (event: FocusEvent) => {
17
+ const focusedElement = event.target as HTMLElement;
18
+
19
+ const isInput =
20
+ focusedElement?.tagName === 'INPUT' ||
21
+ focusedElement?.tagName === 'TEXTAREA';
22
+
23
+ if (isInput && sheetRef.current?.contains(focusedElement)) {
24
+ setFocusedInput(focusedElement);
25
+ }
26
+ };
27
+
28
+ const handleFocusOut = (event: FocusEvent) => {
29
+ const focusedElement = event.target as HTMLElement;
30
+
31
+ if (focusedElement === focusedInput) {
32
+ setFocusedInput(null);
33
+ }
34
+ };
35
+
36
+ document.addEventListener('focusin', handleFocusIn);
37
+ document.addEventListener('focusout', handleFocusOut);
38
+
39
+ return () => {
40
+ document.removeEventListener('focusin', handleFocusIn);
41
+ document.removeEventListener('focusout', handleFocusOut);
42
+ };
43
+ }, [isEnabled, focusedInput]);
44
+
45
+ return focusedInput;
46
+ }
package/src/sheet.tsx CHANGED
@@ -26,6 +26,7 @@ import {
26
26
  } from './constants';
27
27
  import { SheetContext } from './context';
28
28
  import { useDimensions } from './hooks/use-dimensions';
29
+ import { useFocusedInput } from './hooks/use-focused-input';
29
30
  import { useModalEffect } from './hooks/use-modal-effect';
30
31
  import { usePreventScroll } from './hooks/use-prevent-scroll';
31
32
  import { useSheetState } from './hooks/use-sheet-state';
@@ -277,39 +278,6 @@ export const Sheet = forwardRef<any, SheetProps>(
277
278
  snapTo,
278
279
  }));
279
280
 
280
- /**
281
- * If the keyboard is opened while we are in a snap point we need to ensure
282
- * the sheet content is not covered by the keyboard due to being at a lower
283
- * snap point. We move the sheet up enough to ensure the content is visible
284
- * and move it back down when the keyboard closes.
285
- */
286
- useEffect(() => {
287
- if (
288
- isOpen &&
289
- snapPoints.length > 0 &&
290
- avoidKeyboard &&
291
- keyboard.isKeyboardOpen
292
- ) {
293
- const visibleSheetHeight = yInverted.get() - keyboard.keyboardHeight;
294
-
295
- if (visibleSheetHeight <= 0) {
296
- const currentY = y.get();
297
- const adjustedY = Math.max(
298
- // 50 for some extra spacing between keyboard and sheet content
299
- currentY - Math.abs(visibleSheetHeight) - 50,
300
- 0
301
- );
302
-
303
- animate(y, adjustedY, animationOptions);
304
-
305
- // When keyboard closes animate back to the original snap point
306
- return () => {
307
- animate(y, currentY, animationOptions);
308
- };
309
- }
310
- }
311
- }, [isOpen, keyboard.isKeyboardOpen]);
312
-
313
281
  useModalEffect({
314
282
  y,
315
283
  detent,
@@ -327,6 +295,67 @@ export const Sheet = forwardRef<any, SheetProps>(
327
295
  isDisabled: disableScrollLocking || !isOpen,
328
296
  });
329
297
 
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
+
330
359
  const state = useSheetState({
331
360
  isOpen,
332
361
  onOpen: async () => {
@@ -378,6 +407,7 @@ export const Sheet = forwardRef<any, SheetProps>(
378
407
  dragProps,
379
408
  indicatorRotation,
380
409
  avoidKeyboard,
410
+ prefersReducedMotion,
381
411
  sheetBoundsRef,
382
412
  sheetRef,
383
413
  unstyled,
package/src/types.tsx CHANGED
@@ -111,6 +111,7 @@ export interface SheetContextType {
111
111
  dragProps?: SheetDragProps;
112
112
  indicatorRotation: MotionValue<number>;
113
113
  avoidKeyboard: boolean;
114
+ prefersReducedMotion: boolean;
114
115
  sheetBoundsRef: (node: HTMLDivElement | null) => void;
115
116
  sheetRef: RefObject<any>;
116
117
  unstyled: boolean;