react-modal-sheet 5.3.1 → 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/README.md CHANGED
@@ -423,18 +423,21 @@ function KeyboardExample() {
423
423
  }
424
424
  ```
425
425
 
426
- The keyboard avoidance feature:
426
+ Virtual keyboard avoidance consists of several key features:
427
427
 
428
428
  - Uses the `env(keyboard-inset-height)` from [Virtual Keyboard API](https://developer.mozilla.org/en-US/docs/Web/API/VirtualKeyboard_API) and fallbacks to [Visual Viewport API](https://developer.mozilla.org/en-US/docs/Web/API/Visual_Viewport_API) and custom `keyboard-inset-height` CSS environment variable.
429
429
  - Works automatically on mobile devices with virtual keyboard support.
430
430
  - Applies dynamic padding to keep content visible when the keyboard opens.
431
+ - Scrolls the focused input into view if it's covered by the keyboard.
431
432
  - Disables drag gestures while the keyboard is open to prevent glitches.
432
- - When using snap points if the sheet is at a snap point that would be covered by the keyboard, **the sheet will be automatically moved up to ensure the content is visible and then moved back down when the keyboard closes.**
433
+ - When using snap points, the sheet will be automatically moved up to the last snap point to ensure that all content is reachable (even when the content is scrollable) within the sheet when the keyboard is open.
433
434
 
434
- If you need to disable keyboard avoidance, set `avoidKeyboard={false}`.
435
-
436
- > [!IMPORTANT]
437
- > If your sheet content is very long and some inputs are below the keyboard even after adding the padding, you might need to scroll the content into view manually when an input is focused.
435
+ > [!NOTE]
436
+ > Avoiding the virtual keyboard is a complex problem and the built-in avoidance may not work perfectly in all cases or on all devices. The library does its best to handle the most common scenarios but there may be edge cases where it doesn't work as expected.
437
+ >
438
+ > Mobile browsers, eg. Android Chrome, may try to automatically move the focused input into view when the keyboard opens which might interfere with the built-in keyboard avoidance behavior causing the sheet content to jump around.
439
+ >
440
+ > If you need to disable the built-in keyboard avoidance, set `avoidKeyboard={false}`.
438
441
 
439
442
  #### Managing virtual keyboard manually
440
443
 
package/dist/index.js CHANGED
@@ -218,13 +218,16 @@ var SheetBackdrop = React4.forwardRef(
218
218
  ...style,
219
219
  pointerEvents
220
220
  };
221
+ const animationProps = sheetContext.prefersReducedMotion ? {} : {
222
+ initial: { opacity: 0 },
223
+ animate: { opacity: 1 },
224
+ exit: { opacity: 0 },
225
+ transition: { duration: 1 }
226
+ };
221
227
  return /* @__PURE__ */ React4__default.default.createElement(
222
228
  Comp,
223
229
  {
224
- initial: { opacity: 0 },
225
- animate: { opacity: 1 },
226
- exit: { opacity: 0 },
227
- transition: { duration: 1 },
230
+ ...animationProps,
228
231
  ...rest,
229
232
  ref,
230
233
  className: `react-modal-sheet-backdrop ${className}`,
@@ -523,6 +526,36 @@ function useDimensions() {
523
526
  }, []);
524
527
  return dimensions;
525
528
  }
529
+ function useFocusedInput({
530
+ isEnabled,
531
+ sheetRef
532
+ }) {
533
+ const [focusedInput, setFocusedInput] = React4.useState(null);
534
+ React4.useEffect(() => {
535
+ if (!isEnabled) return;
536
+ const handleFocusIn = (event) => {
537
+ var _a;
538
+ const focusedElement = event.target;
539
+ const isInput = (focusedElement == null ? void 0 : focusedElement.tagName) === "INPUT" || (focusedElement == null ? void 0 : focusedElement.tagName) === "TEXTAREA";
540
+ if (isInput && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
541
+ setFocusedInput(focusedElement);
542
+ }
543
+ };
544
+ const handleFocusOut = (event) => {
545
+ const focusedElement = event.target;
546
+ if (focusedElement === focusedInput) {
547
+ setFocusedInput(null);
548
+ }
549
+ };
550
+ document.addEventListener("focusin", handleFocusIn);
551
+ document.addEventListener("focusout", handleFocusOut);
552
+ return () => {
553
+ document.removeEventListener("focusin", handleFocusIn);
554
+ document.removeEventListener("focusout", handleFocusOut);
555
+ };
556
+ }, [isEnabled, focusedInput]);
557
+ return focusedInput;
558
+ }
526
559
  function useSafeAreaInsets() {
527
560
  const [insets] = React4.useState(() => {
528
561
  const fallback = { top: 0, left: 0, right: 0, bottom: 0 };
@@ -1307,29 +1340,6 @@ var Sheet = React4.forwardRef(
1307
1340
  height: sheetHeight,
1308
1341
  snapTo
1309
1342
  }));
1310
- React4.useEffect(() => {
1311
- if (isOpen && snapPoints.length > 0 && avoidKeyboard && keyboard.isKeyboardOpen) {
1312
- const focusedElement = document.activeElement;
1313
- const visibleSheetHeight = yInverted.get() - keyboard.keyboardHeight;
1314
- if (visibleSheetHeight <= 0 && focusedElement) {
1315
- const currentY = y.get();
1316
- const diffY = Math.max(currentY - Math.abs(visibleSheetHeight), 0);
1317
- requestAnimationFrame(() => {
1318
- var _a;
1319
- const inputRect = focusedElement.getBoundingClientRect();
1320
- const sheetRect = (_a = sheetRef.current) == null ? void 0 : _a.getBoundingClientRect();
1321
- let inputOffset = 0;
1322
- if (sheetRect) {
1323
- inputOffset = inputRect.top - sheetRect.top + inputRect.height;
1324
- }
1325
- react.animate(y, diffY - inputOffset, animationOptions);
1326
- });
1327
- return () => {
1328
- react.animate(y, currentY, animationOptions);
1329
- };
1330
- }
1331
- }
1332
- }, [isOpen, keyboard.isKeyboardOpen]);
1333
1343
  useModalEffect({
1334
1344
  y,
1335
1345
  detent,
@@ -1341,6 +1351,41 @@ var Sheet = React4.forwardRef(
1341
1351
  usePreventScroll({
1342
1352
  isDisabled: disableScrollLocking || !isOpen
1343
1353
  });
1354
+ const focusedInput = useFocusedInput({
1355
+ isEnabled: isOpen && avoidKeyboard,
1356
+ sheetRef
1357
+ });
1358
+ React4.useEffect(() => {
1359
+ if (!isOpen || !avoidKeyboard) {
1360
+ return;
1361
+ }
1362
+ async function adjustSheetForKeyboard() {
1363
+ const sheetElement = sheetRef.current;
1364
+ if (!sheetElement || !focusedInput || !keyboard.isKeyboardOpen) {
1365
+ return;
1366
+ }
1367
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
1368
+ if (lastSnapPoint) {
1369
+ await react.animate(y, lastSnapPoint.snapValueY, animationOptions);
1370
+ updateSnap(lastSnapPoint.snapIndex);
1371
+ }
1372
+ requestAnimationFrame(() => {
1373
+ const inputRect = focusedInput.getBoundingClientRect();
1374
+ const sheetRect = sheetElement.getBoundingClientRect();
1375
+ const scroller = sheetElement.querySelector(
1376
+ ".react-modal-sheet-content-scroller"
1377
+ );
1378
+ const scrollTarget = Math.max(
1379
+ inputRect.top - sheetRect.top + scroller.scrollTop - inputRect.height,
1380
+ 0
1381
+ );
1382
+ requestAnimationFrame(() => {
1383
+ scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1384
+ });
1385
+ });
1386
+ }
1387
+ adjustSheetForKeyboard();
1388
+ }, [isOpen, avoidKeyboard, keyboard.isKeyboardOpen, focusedInput]);
1344
1389
  const state = useSheetState({
1345
1390
  isOpen,
1346
1391
  onOpen: async () => {
@@ -1376,6 +1421,7 @@ var Sheet = React4.forwardRef(
1376
1421
  dragProps,
1377
1422
  indicatorRotation,
1378
1423
  avoidKeyboard,
1424
+ prefersReducedMotion,
1379
1425
  sheetBoundsRef,
1380
1426
  sheetRef,
1381
1427
  unstyled,