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 +9 -6
- package/dist/index.js +73 -21
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +73 -21
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/SheetBackdrop.tsx +10 -4
- package/src/debug.ts +3 -2
- package/src/hooks/use-focused-input.ts +46 -0
- package/src/sheet.tsx +63 -33
- package/src/types.tsx +1 -0
package/README.md
CHANGED
|
@@ -423,18 +423,21 @@ function KeyboardExample() {
|
|
|
423
423
|
}
|
|
424
424
|
```
|
|
425
425
|
|
|
426
|
-
|
|
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
|
|
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
|
-
|
|
435
|
-
|
|
436
|
-
>
|
|
437
|
-
>
|
|
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
|
-
|
|
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,
|