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/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,23 +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 visibleSheetHeight = yInverted.get() - keyboard.keyboardHeight;
1313
- if (visibleSheetHeight <= 0) {
1314
- const currentY = y.get();
1315
- const adjustedY = Math.max(
1316
- // 50 for some extra spacing between keyboard and sheet content
1317
- currentY - Math.abs(visibleSheetHeight) - 50,
1318
- 0
1319
- );
1320
- react.animate(y, adjustedY, animationOptions);
1321
- return () => {
1322
- react.animate(y, currentY, animationOptions);
1323
- };
1324
- }
1325
- }
1326
- }, [isOpen, keyboard.isKeyboardOpen]);
1327
1343
  useModalEffect({
1328
1344
  y,
1329
1345
  detent,
@@ -1335,6 +1351,41 @@ var Sheet = React4.forwardRef(
1335
1351
  usePreventScroll({
1336
1352
  isDisabled: disableScrollLocking || !isOpen
1337
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]);
1338
1389
  const state = useSheetState({
1339
1390
  isOpen,
1340
1391
  onOpen: async () => {
@@ -1370,6 +1421,7 @@ var Sheet = React4.forwardRef(
1370
1421
  dragProps,
1371
1422
  indicatorRotation,
1372
1423
  avoidKeyboard,
1424
+ prefersReducedMotion,
1373
1425
  sheetBoundsRef,
1374
1426
  sheetRef,
1375
1427
  unstyled,