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.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;
@@ -396,7 +413,7 @@ var SheetContent = forwardRef(
396
413
  };
397
414
  const scrollStyle = applyStyles(styles.scroller, isUnstyled);
398
415
  if (sheetContext.avoidKeyboard) {
399
- 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)";
400
417
  }
401
418
  if (disableScroll) {
402
419
  scrollStyle.overflowY = "hidden";
@@ -519,35 +536,194 @@ function useDimensions() {
519
536
  }, []);
520
537
  return dimensions;
521
538
  }
522
- function useFocusedInput({
523
- isEnabled,
524
- sheetRef
525
- }) {
526
- const [focusedInput, setFocusedInput] = useState(null);
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);
527
554
  useEffect(() => {
528
- if (!isEnabled) return;
529
- const handleFocusIn = (event) => {
530
- var _a;
531
- const focusedElement = event.target;
532
- const isInput = (focusedElement == null ? void 0 : focusedElement.tagName) === "INPUT" || (focusedElement == null ? void 0 : focusedElement.tagName) === "TEXTAREA";
533
- if (isInput && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
534
- setFocusedInput(focusedElement);
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`);
535
566
  }
536
- };
537
- const handleFocusOut = (event) => {
538
- const focusedElement = event.target;
539
- if (focusedElement === focusedInput) {
540
- setFocusedInput(null);
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);
541
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();
542
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;
543
710
  document.addEventListener("focusin", handleFocusIn);
544
711
  document.addEventListener("focusout", handleFocusOut);
545
712
  return () => {
546
713
  document.removeEventListener("focusin", handleFocusIn);
547
714
  document.removeEventListener("focusout", handleFocusOut);
548
715
  };
549
- }, [isEnabled, focusedInput]);
550
- return focusedInput;
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;
551
727
  }
552
728
  function useSafeAreaInsets() {
553
729
  const [insets] = useState(() => {
@@ -691,13 +867,13 @@ function isScrollable(node, checkForOverflow) {
691
867
  return false;
692
868
  }
693
869
  const style = window.getComputedStyle(node);
694
- let isScrollable2 = /(auto|scroll)/.test(
870
+ let scrollable = /(auto|scroll)/.test(
695
871
  style.overflow + style.overflowX + style.overflowY
696
872
  );
697
- if (isScrollable2 && checkForOverflow) {
698
- isScrollable2 = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
873
+ if (scrollable && checkForOverflow) {
874
+ scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
699
875
  }
700
- return isScrollable2;
876
+ return scrollable;
701
877
  }
702
878
  function getScrollParent(node, checkForOverflow) {
703
879
  let scrollableNode = node;
@@ -709,17 +885,6 @@ function getScrollParent(node, checkForOverflow) {
709
885
  }
710
886
  return scrollableNode || document.scrollingElement || document.documentElement;
711
887
  }
712
- var nonTextInputTypes = /* @__PURE__ */ new Set([
713
- "checkbox",
714
- "radio",
715
- "range",
716
- "color",
717
- "file",
718
- "image",
719
- "button",
720
- "submit",
721
- "reset"
722
- ]);
723
888
  var preventScrollCount = 0;
724
889
  var restore;
725
890
  function usePreventScroll(options = {}) {
@@ -886,9 +1051,6 @@ function scrollIntoView(target) {
886
1051
  target = scrollable.parentElement;
887
1052
  }
888
1053
  }
889
- function willOpenKeyboard(target) {
890
- return target instanceof HTMLInputElement && !nonTextInputTypes.has(target.type) || target instanceof HTMLTextAreaElement || target instanceof HTMLElement && target.isContentEditable;
891
- }
892
1054
  function useSheetState({
893
1055
  isOpen,
894
1056
  onClosed: _onClosed,
@@ -933,108 +1095,6 @@ function useSheetState({
933
1095
  }, [state]);
934
1096
  return state;
935
1097
  }
936
- function useVirtualKeyboard(options = {}) {
937
- const {
938
- containerRef,
939
- isEnabled = true,
940
- debounceDelay = 100,
941
- includeContentEditable = true,
942
- visualViewportThreshold = 100
943
- } = options;
944
- const [state, setState] = useState({
945
- isVisible: false,
946
- height: 0
947
- });
948
- const focusedElementRef = useRef(null);
949
- const debounceTimer = useRef(null);
950
- const isTextInput = useStableCallback((el) => {
951
- return (el == null ? void 0 : el.tagName) === "INPUT" || (el == null ? void 0 : el.tagName) === "TEXTAREA" || includeContentEditable && el instanceof HTMLElement && el.isContentEditable;
952
- });
953
- useEffect(() => {
954
- if (!isEnabled) return;
955
- const vv = window.visualViewport;
956
- const vk = navigator.virtualKeyboard;
957
- function setKeyboardInsetHeightEnv(height) {
958
- const element = (containerRef == null ? void 0 : containerRef.current) || document.documentElement;
959
- if (window.isSecureContext) {
960
- element.style.setProperty(
961
- "--keyboard-inset-height",
962
- `env(keyboard-inset-height, ${height}px)`
963
- );
964
- } else {
965
- element.style.setProperty("--keyboard-inset-height", `${height}px`);
966
- }
967
- }
968
- function handleFocusIn(e) {
969
- if (e.target instanceof HTMLElement && isTextInput(e.target)) {
970
- focusedElementRef.current = e.target;
971
- updateKeyboardState();
972
- }
973
- }
974
- function handleFocusOut() {
975
- focusedElementRef.current = null;
976
- updateKeyboardState();
977
- }
978
- function updateKeyboardState() {
979
- if (debounceTimer.current) {
980
- clearTimeout(debounceTimer.current);
981
- }
982
- debounceTimer.current = setTimeout(() => {
983
- const active = focusedElementRef.current;
984
- const inputIsFocused = isTextInput(active);
985
- if (!inputIsFocused) {
986
- setKeyboardInsetHeightEnv(0);
987
- setState({ isVisible: false, height: 0 });
988
- return;
989
- }
990
- if (vv) {
991
- const heightDiff = window.innerHeight - vv.height;
992
- if (heightDiff > visualViewportThreshold) {
993
- setKeyboardInsetHeightEnv(heightDiff);
994
- setState({ isVisible: true, height: heightDiff });
995
- } else {
996
- setKeyboardInsetHeightEnv(0);
997
- setState({ isVisible: false, height: 0 });
998
- }
999
- }
1000
- }, debounceDelay);
1001
- }
1002
- window.addEventListener("focusin", handleFocusIn);
1003
- window.addEventListener("focusout", handleFocusOut);
1004
- if (vv) {
1005
- vv.addEventListener("resize", updateKeyboardState);
1006
- vv.addEventListener("scroll", updateKeyboardState);
1007
- }
1008
- let currentOverlaysContent = false;
1009
- if (vk) {
1010
- currentOverlaysContent = vk.overlaysContent;
1011
- vk.overlaysContent = true;
1012
- }
1013
- return () => {
1014
- window.removeEventListener("focusin", handleFocusIn);
1015
- window.removeEventListener("focusout", handleFocusOut);
1016
- if (vv) {
1017
- vv.removeEventListener("resize", updateKeyboardState);
1018
- vv.removeEventListener("scroll", updateKeyboardState);
1019
- }
1020
- if (vk) {
1021
- vk.overlaysContent = currentOverlaysContent;
1022
- }
1023
- if (debounceTimer.current) {
1024
- clearTimeout(debounceTimer.current);
1025
- }
1026
- };
1027
- }, [
1028
- debounceDelay,
1029
- includeContentEditable,
1030
- isEnabled,
1031
- visualViewportThreshold
1032
- ]);
1033
- return {
1034
- keyboardHeight: state.height,
1035
- isKeyboardOpen: state.isVisible
1036
- };
1037
- }
1038
1098
 
1039
1099
  // src/snap.ts
1040
1100
  function computeSnapPoints({
@@ -1209,11 +1269,6 @@ var Sheet = forwardRef(
1209
1269
  type: "tween",
1210
1270
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
1211
1271
  };
1212
- const keyboard = useVirtualKeyboard({
1213
- isEnabled: isOpen && avoidKeyboard,
1214
- containerRef: sheetRef
1215
- });
1216
- const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1217
1272
  const zIndex = useTransform(
1218
1273
  y,
1219
1274
  (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
@@ -1257,14 +1312,45 @@ var Sheet = forwardRef(
1257
1312
  onComplete: () => updateSnap(snapIndex)
1258
1313
  });
1259
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;
1260
1343
  const blurActiveInput = useStableCallback(() => {
1344
+ var _a;
1261
1345
  const focusedElement = document.activeElement;
1262
- if (!focusedElement || !sheetRef.current) return;
1263
- const isInput = focusedElement.tagName === "INPUT" || focusedElement.tagName === "TEXTAREA";
1264
- if (isInput && sheetRef.current.contains(focusedElement)) {
1346
+ if (focusedElement && willOpenKeyboard(focusedElement) && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
1265
1347
  focusedElement.blur();
1266
1348
  }
1267
1349
  });
1350
+ const onDragStart = useStableCallback((event, info) => {
1351
+ blurActiveInput();
1352
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1353
+ });
1268
1354
  const onDrag = useStableCallback((event, info) => {
1269
1355
  onDragProp == null ? void 0 : onDragProp(event, info);
1270
1356
  const currentY = y.get();
@@ -1273,10 +1359,6 @@ var Sheet = forwardRef(
1273
1359
  if (velocity < 0) indicatorRotation.set(-10);
1274
1360
  y.set(Math.max(currentY + info.delta.y, 0));
1275
1361
  });
1276
- const onDragStart = useStableCallback((event, info) => {
1277
- blurActiveInput();
1278
- onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1279
- });
1280
1362
  const onDragEnd = useStableCallback((event, info) => {
1281
1363
  blurActiveInput();
1282
1364
  onDragEndProp == null ? void 0 : onDragEndProp(event, info);
@@ -1344,41 +1426,6 @@ var Sheet = forwardRef(
1344
1426
  usePreventScroll({
1345
1427
  isDisabled: disableScrollLocking || !isOpen
1346
1428
  });
1347
- const focusedInput = useFocusedInput({
1348
- isEnabled: isOpen && avoidKeyboard,
1349
- sheetRef
1350
- });
1351
- useEffect(() => {
1352
- if (!isOpen || !avoidKeyboard) {
1353
- return;
1354
- }
1355
- async function adjustSheetForKeyboard() {
1356
- const sheetElement = sheetRef.current;
1357
- if (!sheetElement || !focusedInput || !keyboard.isKeyboardOpen) {
1358
- return;
1359
- }
1360
- const lastSnapPoint = snapPoints[snapPoints.length - 1];
1361
- if (lastSnapPoint) {
1362
- await animate(y, lastSnapPoint.snapValueY, animationOptions);
1363
- updateSnap(lastSnapPoint.snapIndex);
1364
- }
1365
- requestAnimationFrame(() => {
1366
- const inputRect = focusedInput.getBoundingClientRect();
1367
- const sheetRect = sheetElement.getBoundingClientRect();
1368
- const scroller = sheetElement.querySelector(
1369
- ".react-modal-sheet-content-scroller"
1370
- );
1371
- const scrollTarget = Math.max(
1372
- inputRect.top - sheetRect.top + scroller.scrollTop - inputRect.height,
1373
- 0
1374
- );
1375
- requestAnimationFrame(() => {
1376
- scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1377
- });
1378
- });
1379
- }
1380
- adjustSheetForKeyboard();
1381
- }, [isOpen, avoidKeyboard, keyboard.isKeyboardOpen, focusedInput]);
1382
1429
  const state = useSheetState({
1383
1430
  isOpen,
1384
1431
  onOpen: async () => {