react-modal-sheet 5.3.1 → 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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { motion, useMotionValue, useTransform, useReducedMotion, animate } from 'motion/react';
2
- import React4, { createContext, forwardRef, useRef, useState, useImperativeHandle, useEffect, useContext, useCallback, useMemo, useLayoutEffect } from 'react';
2
+ import React4, { createContext, forwardRef, useRef, useState, useImperativeHandle, useContext, useCallback, useMemo, useEffect, useLayoutEffect } from 'react';
3
3
  import { createPortal } from 'react-dom';
4
4
  import useMeasure from 'react-use-measure';
5
5
  import { transform } from 'motion';
@@ -196,6 +196,23 @@ function waitForElement(className, interval = 50, maxAttempts = 20) {
196
196
  }, interval);
197
197
  });
198
198
  }
199
+ var nonTextInputTypes = /* @__PURE__ */ new Set([
200
+ "checkbox",
201
+ "radio",
202
+ "range",
203
+ "color",
204
+ "file",
205
+ "image",
206
+ "button",
207
+ "submit",
208
+ "reset"
209
+ ]);
210
+ function willOpenKeyboard(target) {
211
+ return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
212
+ }
213
+ function isHTTPS() {
214
+ return typeof window !== "undefined" && window.isSecureContext;
215
+ }
199
216
 
200
217
  // src/SheetBackdrop.tsx
201
218
  var isClickable = (props) => !!props.onClick || !!props.onTap;
@@ -211,13 +228,16 @@ var SheetBackdrop = forwardRef(
211
228
  ...style,
212
229
  pointerEvents
213
230
  };
231
+ const animationProps = sheetContext.prefersReducedMotion ? {} : {
232
+ initial: { opacity: 0 },
233
+ animate: { opacity: 1 },
234
+ exit: { opacity: 0 },
235
+ transition: { duration: 1 }
236
+ };
214
237
  return /* @__PURE__ */ React4.createElement(
215
238
  Comp,
216
239
  {
217
- initial: { opacity: 0 },
218
- animate: { opacity: 1 },
219
- exit: { opacity: 0 },
220
- transition: { duration: 1 },
240
+ ...animationProps,
221
241
  ...rest,
222
242
  ref,
223
243
  className: `react-modal-sheet-backdrop ${className}`,
@@ -393,7 +413,7 @@ var SheetContent = forwardRef(
393
413
  };
394
414
  const scrollStyle = applyStyles(styles.scroller, isUnstyled);
395
415
  if (sheetContext.avoidKeyboard) {
396
- scrollStyle.paddingBottom = "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))";
416
+ scrollStyle.paddingBottom = isHTTPS() ? "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))" : "var(--keyboard-inset-height, 0px)";
397
417
  }
398
418
  if (disableScroll) {
399
419
  scrollStyle.overflowY = "hidden";
@@ -516,6 +536,195 @@ function useDimensions() {
516
536
  }, []);
517
537
  return dimensions;
518
538
  }
539
+ var virtualKeyboardOverlayUsers = 0;
540
+ var initialVirtualKeyboardOverlaysContent = null;
541
+ function useVirtualKeyboard(options = {}) {
542
+ const {
543
+ containerRef,
544
+ isEnabled = true,
545
+ debounceDelay = 100,
546
+ visualViewportThreshold = 100
547
+ } = options;
548
+ const [state, setState] = useState({
549
+ isVisible: false,
550
+ height: 0
551
+ });
552
+ const focusedElementRef = useRef(null);
553
+ const debounceTimer = useRef(null);
554
+ useEffect(() => {
555
+ const vv = window.visualViewport;
556
+ const vk = getVirtualKeyboardApi();
557
+ function setKeyboardInsetHeightEnv(height) {
558
+ const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
559
+ if (vk) {
560
+ element.style.setProperty(
561
+ "--keyboard-inset-height",
562
+ `env(keyboard-inset-height, ${height}px)`
563
+ );
564
+ } else {
565
+ element.style.setProperty("--keyboard-inset-height", `${height}px`);
566
+ }
567
+ }
568
+ function setKeyboardState(nextState) {
569
+ setState(
570
+ (prevState) => prevState.isVisible === nextState.isVisible && prevState.height === nextState.height ? prevState : nextState
571
+ );
572
+ }
573
+ function resetKeyboardState() {
574
+ focusedElementRef.current = null;
575
+ setKeyboardInsetHeightEnv(0);
576
+ setKeyboardState({ isVisible: false, height: 0 });
577
+ }
578
+ if (!isEnabled) {
579
+ resetKeyboardState();
580
+ return;
581
+ }
582
+ function updateKeyboardState() {
583
+ if (debounceTimer.current) {
584
+ clearTimeout(debounceTimer.current);
585
+ }
586
+ debounceTimer.current = setTimeout(() => {
587
+ const active = getActiveElement() ?? focusedElementRef.current;
588
+ const inputIsFocused = active ? willOpenKeyboard(active) : false;
589
+ if (!inputIsFocused) {
590
+ resetKeyboardState();
591
+ return;
592
+ }
593
+ focusedElementRef.current = active;
594
+ if (vk == null ? void 0 : vk.overlaysContent) {
595
+ const keyboardHeight = vk.boundingRect.height;
596
+ setKeyboardInsetHeightEnv(keyboardHeight);
597
+ setKeyboardState({ isVisible: true, height: keyboardHeight });
598
+ return;
599
+ }
600
+ if (vv) {
601
+ const heightDiff = window.innerHeight - vv.height;
602
+ if (heightDiff > visualViewportThreshold) {
603
+ setKeyboardInsetHeightEnv(heightDiff);
604
+ setKeyboardState({ isVisible: true, height: heightDiff });
605
+ return;
606
+ }
607
+ }
608
+ resetKeyboardState();
609
+ }, debounceDelay);
610
+ }
611
+ function handleFocusIn(e) {
612
+ if (e.target instanceof HTMLElement && willOpenKeyboard(e.target)) {
613
+ focusedElementRef.current = e.target;
614
+ updateKeyboardState();
615
+ }
616
+ }
617
+ function handleFocusOut() {
618
+ requestAnimationFrame(() => {
619
+ focusedElementRef.current = getActiveElement();
620
+ updateKeyboardState();
621
+ });
622
+ }
623
+ document.addEventListener("focusin", handleFocusIn);
624
+ document.addEventListener("focusout", handleFocusOut);
625
+ if (vv) {
626
+ vv.addEventListener("resize", updateKeyboardState);
627
+ vv.addEventListener("scroll", updateKeyboardState);
628
+ }
629
+ if (vk) {
630
+ if (virtualKeyboardOverlayUsers === 0) {
631
+ initialVirtualKeyboardOverlaysContent = vk.overlaysContent;
632
+ vk.overlaysContent = true;
633
+ }
634
+ virtualKeyboardOverlayUsers++;
635
+ vk.addEventListener("geometrychange", updateKeyboardState);
636
+ }
637
+ focusedElementRef.current = getActiveElement();
638
+ updateKeyboardState();
639
+ return () => {
640
+ document.removeEventListener("focusin", handleFocusIn);
641
+ document.removeEventListener("focusout", handleFocusOut);
642
+ if (vv) {
643
+ vv.removeEventListener("resize", updateKeyboardState);
644
+ vv.removeEventListener("scroll", updateKeyboardState);
645
+ }
646
+ if (vk) {
647
+ vk.removeEventListener("geometrychange", updateKeyboardState);
648
+ virtualKeyboardOverlayUsers = Math.max(
649
+ 0,
650
+ virtualKeyboardOverlayUsers - 1
651
+ );
652
+ if (virtualKeyboardOverlayUsers === 0) {
653
+ vk.overlaysContent = initialVirtualKeyboardOverlaysContent ?? false;
654
+ initialVirtualKeyboardOverlaysContent = null;
655
+ }
656
+ }
657
+ if (debounceTimer.current) {
658
+ clearTimeout(debounceTimer.current);
659
+ }
660
+ resetKeyboardState();
661
+ };
662
+ }, [debounceDelay, isEnabled, visualViewportThreshold]);
663
+ return {
664
+ keyboardHeight: state.height,
665
+ isKeyboardOpen: state.isVisible
666
+ };
667
+ }
668
+ function getVirtualKeyboardApi() {
669
+ return isHTTPS() && "virtualKeyboard" in navigator ? navigator.virtualKeyboard : null;
670
+ }
671
+ function getActiveElement() {
672
+ var _a;
673
+ let activeElement = document.activeElement;
674
+ while (activeElement instanceof HTMLElement && ((_a = activeElement.shadowRoot) == null ? void 0 : _a.activeElement)) {
675
+ activeElement = activeElement.shadowRoot.activeElement;
676
+ }
677
+ return activeElement && willOpenKeyboard(activeElement) ? activeElement : null;
678
+ }
679
+
680
+ // src/hooks/use-keyboard-avoidance.ts
681
+ function useKeyboardAvoidance({
682
+ isEnabled,
683
+ containerRef,
684
+ onWillOpenKeyboard,
685
+ onDidOpenKeyboard
686
+ }) {
687
+ const [focusedElement, setFocusedElement] = useState(
688
+ null
689
+ );
690
+ const keyboard = useVirtualKeyboard({
691
+ isEnabled,
692
+ containerRef
693
+ });
694
+ const handleFocusIn = useStableCallback(async (event) => {
695
+ var _a;
696
+ const element = event.target;
697
+ if (willOpenKeyboard(element) && ((_a = containerRef.current) == null ? void 0 : _a.contains(element))) {
698
+ await (onWillOpenKeyboard == null ? void 0 : onWillOpenKeyboard(event));
699
+ setFocusedElement(element);
700
+ }
701
+ });
702
+ const handleFocusOut = useStableCallback((event) => {
703
+ const element = event.target;
704
+ if (focusedElement === element) {
705
+ setFocusedElement(null);
706
+ }
707
+ });
708
+ useEffect(() => {
709
+ if (!isEnabled) return;
710
+ document.addEventListener("focusin", handleFocusIn);
711
+ document.addEventListener("focusout", handleFocusOut);
712
+ return () => {
713
+ document.removeEventListener("focusin", handleFocusIn);
714
+ document.removeEventListener("focusout", handleFocusOut);
715
+ };
716
+ }, [isEnabled]);
717
+ useEffect(() => {
718
+ const containerElement = containerRef.current;
719
+ if (!isEnabled || !focusedElement || !containerElement || !keyboard.isKeyboardOpen) {
720
+ return;
721
+ }
722
+ requestAnimationFrame(() => {
723
+ onDidOpenKeyboard == null ? void 0 : onDidOpenKeyboard(focusedElement);
724
+ });
725
+ }, [isEnabled, keyboard.isKeyboardOpen, focusedElement]);
726
+ return keyboard;
727
+ }
519
728
  function useSafeAreaInsets() {
520
729
  const [insets] = useState(() => {
521
730
  const fallback = { top: 0, left: 0, right: 0, bottom: 0 };
@@ -658,13 +867,13 @@ function isScrollable(node, checkForOverflow) {
658
867
  return false;
659
868
  }
660
869
  const style = window.getComputedStyle(node);
661
- let isScrollable2 = /(auto|scroll)/.test(
870
+ let scrollable = /(auto|scroll)/.test(
662
871
  style.overflow + style.overflowX + style.overflowY
663
872
  );
664
- if (isScrollable2 && checkForOverflow) {
665
- isScrollable2 = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
873
+ if (scrollable && checkForOverflow) {
874
+ scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
666
875
  }
667
- return isScrollable2;
876
+ return scrollable;
668
877
  }
669
878
  function getScrollParent(node, checkForOverflow) {
670
879
  let scrollableNode = node;
@@ -676,17 +885,6 @@ function getScrollParent(node, checkForOverflow) {
676
885
  }
677
886
  return scrollableNode || document.scrollingElement || document.documentElement;
678
887
  }
679
- var nonTextInputTypes = /* @__PURE__ */ new Set([
680
- "checkbox",
681
- "radio",
682
- "range",
683
- "color",
684
- "file",
685
- "image",
686
- "button",
687
- "submit",
688
- "reset"
689
- ]);
690
888
  var preventScrollCount = 0;
691
889
  var restore;
692
890
  function usePreventScroll(options = {}) {
@@ -853,9 +1051,6 @@ function scrollIntoView(target) {
853
1051
  target = scrollable.parentElement;
854
1052
  }
855
1053
  }
856
- function willOpenKeyboard(target) {
857
- return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
858
- }
859
1054
  function useSheetState({
860
1055
  isOpen,
861
1056
  onClosed: _onClosed,
@@ -900,108 +1095,6 @@ function useSheetState({
900
1095
  }, [state]);
901
1096
  return state;
902
1097
  }
903
- function useVirtualKeyboard(options = {}) {
904
- const {
905
- containerRef,
906
- isEnabled = true,
907
- debounceDelay = 100,
908
- includeContentEditable = true,
909
- visualViewportThreshold = 100
910
- } = options;
911
- const [state, setState] = useState({
912
- isVisible: false,
913
- height: 0
914
- });
915
- const focusedElementRef = useRef(null);
916
- const debounceTimer = useRef(null);
917
- const isTextInput = useStableCallback((el) => {
918
- return (el == null ? void 0 : el.tagName) === "INPUT" || (el == null ? void 0 : el.tagName) === "TEXTAREA" || includeContentEditable && el instanceof HTMLElement && el.isContentEditable;
919
- });
920
- useEffect(() => {
921
- if (!isEnabled) return;
922
- const vv = window.visualViewport;
923
- const vk = navigator.virtualKeyboard;
924
- function setKeyboardInsetHeightEnv(height) {
925
- const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
926
- if (window.isSecureContext) {
927
- element.style.setProperty(
928
- "--keyboard-inset-height",
929
- `env(keyboard-inset-height, ${height}px)`
930
- );
931
- } else {
932
- element.style.setProperty("--keyboard-inset-height", `${height}px`);
933
- }
934
- }
935
- function handleFocusIn(e) {
936
- if (e.target instanceof HTMLElement && isTextInput(e.target)) {
937
- focusedElementRef.current = e.target;
938
- updateKeyboardState();
939
- }
940
- }
941
- function handleFocusOut() {
942
- focusedElementRef.current = null;
943
- updateKeyboardState();
944
- }
945
- function updateKeyboardState() {
946
- if (debounceTimer.current) {
947
- clearTimeout(debounceTimer.current);
948
- }
949
- debounceTimer.current = setTimeout(() => {
950
- const active = focusedElementRef.current;
951
- const inputIsFocused = isTextInput(active);
952
- if (!inputIsFocused) {
953
- setKeyboardInsetHeightEnv(0);
954
- setState({ isVisible: false, height: 0 });
955
- return;
956
- }
957
- if (vv) {
958
- const heightDiff = window.innerHeight - vv.height;
959
- if (heightDiff > visualViewportThreshold) {
960
- setKeyboardInsetHeightEnv(heightDiff);
961
- setState({ isVisible: true, height: heightDiff });
962
- } else {
963
- setKeyboardInsetHeightEnv(0);
964
- setState({ isVisible: false, height: 0 });
965
- }
966
- }
967
- }, debounceDelay);
968
- }
969
- window.addEventListener("focusin", handleFocusIn);
970
- window.addEventListener("focusout", handleFocusOut);
971
- if (vv) {
972
- vv.addEventListener("resize", updateKeyboardState);
973
- vv.addEventListener("scroll", updateKeyboardState);
974
- }
975
- let currentOverlaysContent = false;
976
- if (vk) {
977
- currentOverlaysContent = vk.overlaysContent;
978
- vk.overlaysContent = true;
979
- }
980
- return () => {
981
- window.removeEventListener("focusin", handleFocusIn);
982
- window.removeEventListener("focusout", handleFocusOut);
983
- if (vv) {
984
- vv.removeEventListener("resize", updateKeyboardState);
985
- vv.removeEventListener("scroll", updateKeyboardState);
986
- }
987
- if (vk) {
988
- vk.overlaysContent = currentOverlaysContent;
989
- }
990
- if (debounceTimer.current) {
991
- clearTimeout(debounceTimer.current);
992
- }
993
- };
994
- }, [
995
- debounceDelay,
996
- includeContentEditable,
997
- isEnabled,
998
- visualViewportThreshold
999
- ]);
1000
- return {
1001
- keyboardHeight: state.height,
1002
- isKeyboardOpen: state.isVisible
1003
- };
1004
- }
1005
1098
 
1006
1099
  // src/snap.ts
1007
1100
  function computeSnapPoints({
@@ -1176,11 +1269,6 @@ var Sheet = forwardRef(
1176
1269
  type: "tween",
1177
1270
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
1178
1271
  };
1179
- const keyboard = useVirtualKeyboard({
1180
- isEnabled: isOpen && avoidKeyboard,
1181
- containerRef: sheetRef
1182
- });
1183
- const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1184
1272
  const zIndex = useTransform(
1185
1273
  y,
1186
1274
  (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
@@ -1224,14 +1312,45 @@ var Sheet = forwardRef(
1224
1312
  onComplete: () => updateSnap(snapIndex)
1225
1313
  });
1226
1314
  });
1315
+ const keyboard = useKeyboardAvoidance({
1316
+ isEnabled: isOpen && avoidKeyboard,
1317
+ containerRef: sheetRef,
1318
+ onWillOpenKeyboard: async () => {
1319
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
1320
+ if (lastSnapPoint && lastSnapPoint.snapIndex !== currentSnap) {
1321
+ await animate(y, lastSnapPoint.snapValueY, animationOptions);
1322
+ updateSnap(lastSnapPoint.snapIndex);
1323
+ }
1324
+ },
1325
+ onDidOpenKeyboard: (focusedElement) => {
1326
+ const sheetElement = sheetRef.current;
1327
+ if (!sheetElement) return;
1328
+ const inputRect = focusedElement.getBoundingClientRect();
1329
+ const containerRect = sheetElement.getBoundingClientRect();
1330
+ const scroller = sheetElement.querySelector(
1331
+ ".react-modal-sheet-content-scroller"
1332
+ );
1333
+ const scrollTarget = Math.max(
1334
+ inputRect.top - containerRect.top + scroller.scrollTop - inputRect.height,
1335
+ 0
1336
+ );
1337
+ requestAnimationFrame(() => {
1338
+ scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1339
+ });
1340
+ }
1341
+ });
1342
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1227
1343
  const blurActiveInput = useStableCallback(() => {
1344
+ var _a;
1228
1345
  const focusedElement = document.activeElement;
1229
- if (!focusedElement || !sheetRef.current) return;
1230
- const isInput = focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA";
1231
- if (isInput && sheetRef.current.contains(focusedElement)) {
1346
+ if (focusedElement && willOpenKeyboard(focusedElement) && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
1232
1347
  focusedElement.blur();
1233
1348
  }
1234
1349
  });
1350
+ const onDragStart = useStableCallback((event, info) => {
1351
+ blurActiveInput();
1352
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1353
+ });
1235
1354
  const onDrag = useStableCallback((event, info) => {
1236
1355
  onDragProp == null ? void 0 : onDragProp(event, info);
1237
1356
  const currentY = y.get();
@@ -1240,10 +1359,6 @@ var Sheet = forwardRef(
1240
1359
  if (velocity < 0) indicatorRotation.set(-10);
1241
1360
  y.set(Math.max(currentY + info.delta.y, 0));
1242
1361
  });
1243
- const onDragStart = useStableCallback((event, info) => {
1244
- blurActiveInput();
1245
- onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1246
- });
1247
1362
  const onDragEnd = useStableCallback((event, info) => {
1248
1363
  blurActiveInput();
1249
1364
  onDragEndProp == null ? void 0 : onDragEndProp(event, info);
@@ -1300,29 +1415,6 @@ var Sheet = forwardRef(
1300
1415
  height: sheetHeight,
1301
1416
  snapTo
1302
1417
  }));
1303
- useEffect(() => {
1304
- if (isOpen && snapPoints.length > 0 && avoidKeyboard && keyboard.isKeyboardOpen) {
1305
- const focusedElement = document.activeElement;
1306
- const visibleSheetHeight = yInverted.get() - keyboard.keyboardHeight;
1307
- if (visibleSheetHeight <= 0 && focusedElement) {
1308
- const currentY = y.get();
1309
- const diffY = Math.max(currentY - Math.abs(visibleSheetHeight), 0);
1310
- requestAnimationFrame(() => {
1311
- var _a;
1312
- const inputRect = focusedElement.getBoundingClientRect();
1313
- const sheetRect = (_a = sheetRef.current) == null ? void 0 : _a.getBoundingClientRect();
1314
- let inputOffset = 0;
1315
- if (sheetRect) {
1316
- inputOffset = inputRect.top - sheetRect.top + inputRect.height;
1317
- }
1318
- animate(y, diffY - inputOffset, animationOptions);
1319
- });
1320
- return () => {
1321
- animate(y, currentY, animationOptions);
1322
- };
1323
- }
1324
- }
1325
- }, [isOpen, keyboard.isKeyboardOpen]);
1326
1418
  useModalEffect({
1327
1419
  y,
1328
1420
  detent,
@@ -1369,6 +1461,7 @@ var Sheet = forwardRef(
1369
1461
  dragProps,
1370
1462
  indicatorRotation,
1371
1463
  avoidKeyboard,
1464
+ prefersReducedMotion,
1372
1465
  sheetBoundsRef,
1373
1466
  sheetRef,
1374
1467
  unstyled,