react-modal-sheet 5.4.0 → 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.d.mts CHANGED
@@ -139,11 +139,6 @@ type UseVirtualKeyboardOptions = {
139
139
  * @default 100
140
140
  */
141
141
  visualViewportThreshold?: number;
142
- /**
143
- * Whether to treat contenteditable elements as text inputs.
144
- * @default true
145
- */
146
- includeContentEditable?: boolean;
147
142
  /**
148
143
  * Delay in ms for debouncing viewport changes.
149
144
  * @default 100
package/dist/index.d.ts CHANGED
@@ -139,11 +139,6 @@ type UseVirtualKeyboardOptions = {
139
139
  * @default 100
140
140
  */
141
141
  visualViewportThreshold?: number;
142
- /**
143
- * Whether to treat contenteditable elements as text inputs.
144
- * @default true
145
- */
146
- includeContentEditable?: boolean;
147
142
  /**
148
143
  * Delay in ms for debouncing viewport changes.
149
144
  * @default 100
package/dist/index.js CHANGED
@@ -203,6 +203,23 @@ function waitForElement(className, interval = 50, maxAttempts = 20) {
203
203
  }, interval);
204
204
  });
205
205
  }
206
+ var nonTextInputTypes = /* @__PURE__ */ new Set([
207
+ "checkbox",
208
+ "radio",
209
+ "range",
210
+ "color",
211
+ "file",
212
+ "image",
213
+ "button",
214
+ "submit",
215
+ "reset"
216
+ ]);
217
+ function willOpenKeyboard(target) {
218
+ return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
219
+ }
220
+ function isHTTPS() {
221
+ return typeof window !== "undefined" && window.isSecureContext;
222
+ }
206
223
 
207
224
  // src/SheetBackdrop.tsx
208
225
  var isClickable = (props) => !!props.onClick || !!props.onTap;
@@ -403,7 +420,7 @@ var SheetContent = React4.forwardRef(
403
420
  };
404
421
  const scrollStyle = applyStyles(styles.scroller, isUnstyled);
405
422
  if (sheetContext.avoidKeyboard) {
406
- scrollStyle.paddingBottom = "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))";
423
+ scrollStyle.paddingBottom = isHTTPS() ? "env(keyboard-inset-height, var(--keyboard-inset-height, 0px))" : "var(--keyboard-inset-height, 0px)";
407
424
  }
408
425
  if (disableScroll) {
409
426
  scrollStyle.overflowY = "hidden";
@@ -526,35 +543,194 @@ function useDimensions() {
526
543
  }, []);
527
544
  return dimensions;
528
545
  }
529
- function useFocusedInput({
530
- isEnabled,
531
- sheetRef
532
- }) {
533
- const [focusedInput, setFocusedInput] = React4.useState(null);
546
+ var virtualKeyboardOverlayUsers = 0;
547
+ var initialVirtualKeyboardOverlaysContent = null;
548
+ function useVirtualKeyboard(options = {}) {
549
+ const {
550
+ containerRef,
551
+ isEnabled = true,
552
+ debounceDelay = 100,
553
+ visualViewportThreshold = 100
554
+ } = options;
555
+ const [state, setState] = React4.useState({
556
+ isVisible: false,
557
+ height: 0
558
+ });
559
+ const focusedElementRef = React4.useRef(null);
560
+ const debounceTimer = React4.useRef(null);
534
561
  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);
562
+ const vv = window.visualViewport;
563
+ const vk = getVirtualKeyboardApi();
564
+ function setKeyboardInsetHeightEnv(height) {
565
+ const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
566
+ if (vk) {
567
+ element.style.setProperty(
568
+ "--keyboard-inset-height",
569
+ `env(keyboard-inset-height, ${height}px)`
570
+ );
571
+ } else {
572
+ element.style.setProperty("--keyboard-inset-height", `${height}px`);
542
573
  }
543
- };
544
- const handleFocusOut = (event) => {
545
- const focusedElement = event.target;
546
- if (focusedElement === focusedInput) {
547
- setFocusedInput(null);
574
+ }
575
+ function setKeyboardState(nextState) {
576
+ setState(
577
+ (prevState) => prevState.isVisible === nextState.isVisible && prevState.height === nextState.height ? prevState : nextState
578
+ );
579
+ }
580
+ function resetKeyboardState() {
581
+ focusedElementRef.current = null;
582
+ setKeyboardInsetHeightEnv(0);
583
+ setKeyboardState({ isVisible: false, height: 0 });
584
+ }
585
+ if (!isEnabled) {
586
+ resetKeyboardState();
587
+ return;
588
+ }
589
+ function updateKeyboardState() {
590
+ if (debounceTimer.current) {
591
+ clearTimeout(debounceTimer.current);
548
592
  }
593
+ debounceTimer.current = setTimeout(() => {
594
+ const active = getActiveElement() ?? focusedElementRef.current;
595
+ const inputIsFocused = active ? willOpenKeyboard(active) : false;
596
+ if (!inputIsFocused) {
597
+ resetKeyboardState();
598
+ return;
599
+ }
600
+ focusedElementRef.current = active;
601
+ if (vk == null ? void 0 : vk.overlaysContent) {
602
+ const keyboardHeight = vk.boundingRect.height;
603
+ setKeyboardInsetHeightEnv(keyboardHeight);
604
+ setKeyboardState({ isVisible: true, height: keyboardHeight });
605
+ return;
606
+ }
607
+ if (vv) {
608
+ const heightDiff = window.innerHeight - vv.height;
609
+ if (heightDiff > visualViewportThreshold) {
610
+ setKeyboardInsetHeightEnv(heightDiff);
611
+ setKeyboardState({ isVisible: true, height: heightDiff });
612
+ return;
613
+ }
614
+ }
615
+ resetKeyboardState();
616
+ }, debounceDelay);
617
+ }
618
+ function handleFocusIn(e) {
619
+ if (e.target instanceof HTMLElement && willOpenKeyboard(e.target)) {
620
+ focusedElementRef.current = e.target;
621
+ updateKeyboardState();
622
+ }
623
+ }
624
+ function handleFocusOut() {
625
+ requestAnimationFrame(() => {
626
+ focusedElementRef.current = getActiveElement();
627
+ updateKeyboardState();
628
+ });
629
+ }
630
+ document.addEventListener("focusin", handleFocusIn);
631
+ document.addEventListener("focusout", handleFocusOut);
632
+ if (vv) {
633
+ vv.addEventListener("resize", updateKeyboardState);
634
+ vv.addEventListener("scroll", updateKeyboardState);
635
+ }
636
+ if (vk) {
637
+ if (virtualKeyboardOverlayUsers === 0) {
638
+ initialVirtualKeyboardOverlaysContent = vk.overlaysContent;
639
+ vk.overlaysContent = true;
640
+ }
641
+ virtualKeyboardOverlayUsers++;
642
+ vk.addEventListener("geometrychange", updateKeyboardState);
643
+ }
644
+ focusedElementRef.current = getActiveElement();
645
+ updateKeyboardState();
646
+ return () => {
647
+ document.removeEventListener("focusin", handleFocusIn);
648
+ document.removeEventListener("focusout", handleFocusOut);
649
+ if (vv) {
650
+ vv.removeEventListener("resize", updateKeyboardState);
651
+ vv.removeEventListener("scroll", updateKeyboardState);
652
+ }
653
+ if (vk) {
654
+ vk.removeEventListener("geometrychange", updateKeyboardState);
655
+ virtualKeyboardOverlayUsers = Math.max(
656
+ 0,
657
+ virtualKeyboardOverlayUsers - 1
658
+ );
659
+ if (virtualKeyboardOverlayUsers === 0) {
660
+ vk.overlaysContent = initialVirtualKeyboardOverlaysContent ?? false;
661
+ initialVirtualKeyboardOverlaysContent = null;
662
+ }
663
+ }
664
+ if (debounceTimer.current) {
665
+ clearTimeout(debounceTimer.current);
666
+ }
667
+ resetKeyboardState();
549
668
  };
669
+ }, [debounceDelay, isEnabled, visualViewportThreshold]);
670
+ return {
671
+ keyboardHeight: state.height,
672
+ isKeyboardOpen: state.isVisible
673
+ };
674
+ }
675
+ function getVirtualKeyboardApi() {
676
+ return isHTTPS() && "virtualKeyboard" in navigator ? navigator.virtualKeyboard : null;
677
+ }
678
+ function getActiveElement() {
679
+ var _a;
680
+ let activeElement = document.activeElement;
681
+ while (activeElement instanceof HTMLElement && ((_a = activeElement.shadowRoot) == null ? void 0 : _a.activeElement)) {
682
+ activeElement = activeElement.shadowRoot.activeElement;
683
+ }
684
+ return activeElement && willOpenKeyboard(activeElement) ? activeElement : null;
685
+ }
686
+
687
+ // src/hooks/use-keyboard-avoidance.ts
688
+ function useKeyboardAvoidance({
689
+ isEnabled,
690
+ containerRef,
691
+ onWillOpenKeyboard,
692
+ onDidOpenKeyboard
693
+ }) {
694
+ const [focusedElement, setFocusedElement] = React4.useState(
695
+ null
696
+ );
697
+ const keyboard = useVirtualKeyboard({
698
+ isEnabled,
699
+ containerRef
700
+ });
701
+ const handleFocusIn = useStableCallback(async (event) => {
702
+ var _a;
703
+ const element = event.target;
704
+ if (willOpenKeyboard(element) && ((_a = containerRef.current) == null ? void 0 : _a.contains(element))) {
705
+ await (onWillOpenKeyboard == null ? void 0 : onWillOpenKeyboard(event));
706
+ setFocusedElement(element);
707
+ }
708
+ });
709
+ const handleFocusOut = useStableCallback((event) => {
710
+ const element = event.target;
711
+ if (focusedElement === element) {
712
+ setFocusedElement(null);
713
+ }
714
+ });
715
+ React4.useEffect(() => {
716
+ if (!isEnabled) return;
550
717
  document.addEventListener("focusin", handleFocusIn);
551
718
  document.addEventListener("focusout", handleFocusOut);
552
719
  return () => {
553
720
  document.removeEventListener("focusin", handleFocusIn);
554
721
  document.removeEventListener("focusout", handleFocusOut);
555
722
  };
556
- }, [isEnabled, focusedInput]);
557
- return focusedInput;
723
+ }, [isEnabled]);
724
+ React4.useEffect(() => {
725
+ const containerElement = containerRef.current;
726
+ if (!isEnabled || !focusedElement || !containerElement || !keyboard.isKeyboardOpen) {
727
+ return;
728
+ }
729
+ requestAnimationFrame(() => {
730
+ onDidOpenKeyboard == null ? void 0 : onDidOpenKeyboard(focusedElement);
731
+ });
732
+ }, [isEnabled, keyboard.isKeyboardOpen, focusedElement]);
733
+ return keyboard;
558
734
  }
559
735
  function useSafeAreaInsets() {
560
736
  const [insets] = React4.useState(() => {
@@ -698,13 +874,13 @@ function isScrollable(node, checkForOverflow) {
698
874
  return false;
699
875
  }
700
876
  const style = window.getComputedStyle(node);
701
- let isScrollable2 = /(auto|scroll)/.test(
877
+ let scrollable = /(auto|scroll)/.test(
702
878
  style.overflow + style.overflowX + style.overflowY
703
879
  );
704
- if (isScrollable2 && checkForOverflow) {
705
- isScrollable2 = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
880
+ if (scrollable && checkForOverflow) {
881
+ scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
706
882
  }
707
- return isScrollable2;
883
+ return scrollable;
708
884
  }
709
885
  function getScrollParent(node, checkForOverflow) {
710
886
  let scrollableNode = node;
@@ -716,17 +892,6 @@ function getScrollParent(node, checkForOverflow) {
716
892
  }
717
893
  return scrollableNode || document.scrollingElement || document.documentElement;
718
894
  }
719
- var nonTextInputTypes = /* @__PURE__ */ new Set([
720
- "checkbox",
721
- "radio",
722
- "range",
723
- "color",
724
- "file",
725
- "image",
726
- "button",
727
- "submit",
728
- "reset"
729
- ]);
730
895
  var preventScrollCount = 0;
731
896
  var restore;
732
897
  function usePreventScroll(options = {}) {
@@ -893,9 +1058,6 @@ function scrollIntoView(target) {
893
1058
  target = scrollable.parentElement;
894
1059
  }
895
1060
  }
896
- function willOpenKeyboard(target) {
897
- return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
898
- }
899
1061
  function useSheetState({
900
1062
  isOpen,
901
1063
  onClosed: _onClosed,
@@ -940,108 +1102,6 @@ function useSheetState({
940
1102
  }, [state]);
941
1103
  return state;
942
1104
  }
943
- function useVirtualKeyboard(options = {}) {
944
- const {
945
- containerRef,
946
- isEnabled = true,
947
- debounceDelay = 100,
948
- includeContentEditable = true,
949
- visualViewportThreshold = 100
950
- } = options;
951
- const [state, setState] = React4.useState({
952
- isVisible: false,
953
- height: 0
954
- });
955
- const focusedElementRef = React4.useRef(null);
956
- const debounceTimer = React4.useRef(null);
957
- const isTextInput = useStableCallback((el) => {
958
- return (el == null ? void 0 : el.tagName) === "INPUT" || (el == null ? void 0 : el.tagName) === "TEXTAREA" || includeContentEditable && el instanceof HTMLElement && el.isContentEditable;
959
- });
960
- React4.useEffect(() => {
961
- if (!isEnabled) return;
962
- const vv = window.visualViewport;
963
- const vk = navigator.virtualKeyboard;
964
- function setKeyboardInsetHeightEnv(height) {
965
- const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
966
- if (window.isSecureContext) {
967
- element.style.setProperty(
968
- "--keyboard-inset-height",
969
- `env(keyboard-inset-height, ${height}px)`
970
- );
971
- } else {
972
- element.style.setProperty("--keyboard-inset-height", `${height}px`);
973
- }
974
- }
975
- function handleFocusIn(e) {
976
- if (e.target instanceof HTMLElement && isTextInput(e.target)) {
977
- focusedElementRef.current = e.target;
978
- updateKeyboardState();
979
- }
980
- }
981
- function handleFocusOut() {
982
- focusedElementRef.current = null;
983
- updateKeyboardState();
984
- }
985
- function updateKeyboardState() {
986
- if (debounceTimer.current) {
987
- clearTimeout(debounceTimer.current);
988
- }
989
- debounceTimer.current = setTimeout(() => {
990
- const active = focusedElementRef.current;
991
- const inputIsFocused = isTextInput(active);
992
- if (!inputIsFocused) {
993
- setKeyboardInsetHeightEnv(0);
994
- setState({ isVisible: false, height: 0 });
995
- return;
996
- }
997
- if (vv) {
998
- const heightDiff = window.innerHeight - vv.height;
999
- if (heightDiff > visualViewportThreshold) {
1000
- setKeyboardInsetHeightEnv(heightDiff);
1001
- setState({ isVisible: true, height: heightDiff });
1002
- } else {
1003
- setKeyboardInsetHeightEnv(0);
1004
- setState({ isVisible: false, height: 0 });
1005
- }
1006
- }
1007
- }, debounceDelay);
1008
- }
1009
- window.addEventListener("focusin", handleFocusIn);
1010
- window.addEventListener("focusout", handleFocusOut);
1011
- if (vv) {
1012
- vv.addEventListener("resize", updateKeyboardState);
1013
- vv.addEventListener("scroll", updateKeyboardState);
1014
- }
1015
- let currentOverlaysContent = false;
1016
- if (vk) {
1017
- currentOverlaysContent = vk.overlaysContent;
1018
- vk.overlaysContent = true;
1019
- }
1020
- return () => {
1021
- window.removeEventListener("focusin", handleFocusIn);
1022
- window.removeEventListener("focusout", handleFocusOut);
1023
- if (vv) {
1024
- vv.removeEventListener("resize", updateKeyboardState);
1025
- vv.removeEventListener("scroll", updateKeyboardState);
1026
- }
1027
- if (vk) {
1028
- vk.overlaysContent = currentOverlaysContent;
1029
- }
1030
- if (debounceTimer.current) {
1031
- clearTimeout(debounceTimer.current);
1032
- }
1033
- };
1034
- }, [
1035
- debounceDelay,
1036
- includeContentEditable,
1037
- isEnabled,
1038
- visualViewportThreshold
1039
- ]);
1040
- return {
1041
- keyboardHeight: state.height,
1042
- isKeyboardOpen: state.isVisible
1043
- };
1044
- }
1045
1105
 
1046
1106
  // src/snap.ts
1047
1107
  function computeSnapPoints({
@@ -1216,11 +1276,6 @@ var Sheet = React4.forwardRef(
1216
1276
  type: "tween",
1217
1277
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
1218
1278
  };
1219
- const keyboard = useVirtualKeyboard({
1220
- isEnabled: isOpen && avoidKeyboard,
1221
- containerRef: sheetRef
1222
- });
1223
- const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1224
1279
  const zIndex = react.useTransform(
1225
1280
  y,
1226
1281
  (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
@@ -1264,14 +1319,45 @@ var Sheet = React4.forwardRef(
1264
1319
  onComplete: () => updateSnap(snapIndex)
1265
1320
  });
1266
1321
  });
1322
+ const keyboard = useKeyboardAvoidance({
1323
+ isEnabled: isOpen && avoidKeyboard,
1324
+ containerRef: sheetRef,
1325
+ onWillOpenKeyboard: async () => {
1326
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
1327
+ if (lastSnapPoint && lastSnapPoint.snapIndex !== currentSnap) {
1328
+ await react.animate(y, lastSnapPoint.snapValueY, animationOptions);
1329
+ updateSnap(lastSnapPoint.snapIndex);
1330
+ }
1331
+ },
1332
+ onDidOpenKeyboard: (focusedElement) => {
1333
+ const sheetElement = sheetRef.current;
1334
+ if (!sheetElement) return;
1335
+ const inputRect = focusedElement.getBoundingClientRect();
1336
+ const containerRect = sheetElement.getBoundingClientRect();
1337
+ const scroller = sheetElement.querySelector(
1338
+ ".react-modal-sheet-content-scroller"
1339
+ );
1340
+ const scrollTarget = Math.max(
1341
+ inputRect.top - containerRect.top + scroller.scrollTop - inputRect.height,
1342
+ 0
1343
+ );
1344
+ requestAnimationFrame(() => {
1345
+ scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1346
+ });
1347
+ }
1348
+ });
1349
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1267
1350
  const blurActiveInput = useStableCallback(() => {
1351
+ var _a;
1268
1352
  const focusedElement = document.activeElement;
1269
- if (!focusedElement || !sheetRef.current) return;
1270
- const isInput = focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA";
1271
- if (isInput && sheetRef.current.contains(focusedElement)) {
1353
+ if (focusedElement && willOpenKeyboard(focusedElement) && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
1272
1354
  focusedElement.blur();
1273
1355
  }
1274
1356
  });
1357
+ const onDragStart = useStableCallback((event, info) => {
1358
+ blurActiveInput();
1359
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1360
+ });
1275
1361
  const onDrag = useStableCallback((event, info) => {
1276
1362
  onDragProp == null ? void 0 : onDragProp(event, info);
1277
1363
  const currentY = y.get();
@@ -1280,10 +1366,6 @@ var Sheet = React4.forwardRef(
1280
1366
  if (velocity < 0) indicatorRotation.set(-10);
1281
1367
  y.set(Math.max(currentY + info.delta.y, 0));
1282
1368
  });
1283
- const onDragStart = useStableCallback((event, info) => {
1284
- blurActiveInput();
1285
- onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1286
- });
1287
1369
  const onDragEnd = useStableCallback((event, info) => {
1288
1370
  blurActiveInput();
1289
1371
  onDragEndProp == null ? void 0 : onDragEndProp(event, info);
@@ -1351,41 +1433,6 @@ var Sheet = React4.forwardRef(
1351
1433
  usePreventScroll({
1352
1434
  isDisabled: disableScrollLocking || !isOpen
1353
1435
  });
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]);
1389
1436
  const state = useSheetState({
1390
1437
  isOpen,
1391
1438
  onOpen: async () => {