react-modal-sheet 5.4.0 → 5.5.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/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';
@@ -130,7 +130,7 @@ var REDUCED_MOTION_TWEEN_CONFIG = {
130
130
  duration: 0.01
131
131
  };
132
132
  var DEFAULT_DRAG_CLOSE_THRESHOLD = 0.6;
133
- var DEFAULT_DRAG_VELOCITY_THRESHOLD = 500;
133
+ var DEFAULT_DRAG_VELOCITY_THRESHOLD = 1200;
134
134
 
135
135
  // src/utils.ts
136
136
  function applyStyles(styles2, unstyled) {
@@ -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({
@@ -1090,73 +1150,145 @@ function computeSnapPoints({
1090
1150
  // Y value is inverted as `y = 0` means sheet is at the top
1091
1151
  }));
1092
1152
  }
1093
- function findClosestSnapPoint({
1094
- snapPoints,
1095
- currentY
1096
- }) {
1153
+ function clamp(value, min, max) {
1154
+ return Math.min(Math.max(value, min), max);
1155
+ }
1156
+ function sign(value) {
1157
+ if (value > 0) return 1;
1158
+ if (value < 0) return -1;
1159
+ return 0;
1160
+ }
1161
+ function toAscendingYSnaps(snapPoints) {
1162
+ return snapPoints.slice().sort((a, b) => a.snapValueY - b.snapValueY);
1163
+ }
1164
+ function findNearestSnapByY(snapPoints, y) {
1097
1165
  return snapPoints.reduce(
1098
- (closest, snap) => Math.abs(snap.snapValueY - currentY) < Math.abs(closest.snapValueY - currentY) ? snap : closest
1166
+ (closest, snap) => Math.abs(snap.snapValueY - y) < Math.abs(closest.snapValueY - y) ? snap : closest
1099
1167
  );
1100
1168
  }
1101
- function findNextSnapPointInDirection({
1169
+ function findNearestSnapAboveY(snapPoints, y) {
1170
+ const snaps = toAscendingYSnaps(snapPoints);
1171
+ for (let i = snaps.length - 1; i >= 0; i--) {
1172
+ if (snaps[i].snapValueY < y) {
1173
+ return snaps[i];
1174
+ }
1175
+ }
1176
+ return null;
1177
+ }
1178
+ function findNearestSnapBelowY(snapPoints, y) {
1179
+ const snaps = toAscendingYSnaps(snapPoints);
1180
+ for (let i = 0; i < snaps.length; i++) {
1181
+ if (snaps[i].snapValueY > y) {
1182
+ return snaps[i];
1183
+ }
1184
+ }
1185
+ return null;
1186
+ }
1187
+ function resolveDirection({
1188
+ offsetY,
1189
+ velocityY,
1190
+ deltaY
1191
+ }) {
1192
+ if (Math.abs(velocityY) > 50) return sign(velocityY);
1193
+ const offsetDirection = sign(offsetY);
1194
+ if (offsetDirection !== 0) return offsetDirection;
1195
+ return sign(deltaY);
1196
+ }
1197
+ function classifyDragEnd({
1102
1198
  y,
1199
+ info,
1200
+ sheetHeight,
1103
1201
  snapPoints,
1104
- dragDirection
1202
+ dragVelocityThreshold,
1203
+ dragCloseThreshold
1105
1204
  }) {
1106
- if (dragDirection === "down") {
1107
- return snapPoints.slice().reverse().find((s) => s.snapValueY > y);
1108
- } else {
1109
- return snapPoints.find((s) => s.snapValueY < y);
1205
+ const flickVelocity = dragVelocityThreshold;
1206
+ const strongFlickVelocity = Math.max(2e3, flickVelocity);
1207
+ const distanceCommitRatio = 0.35;
1208
+ const predictionTime = 0.2;
1209
+ const minDragDistance = 20;
1210
+ const snapStickiness = 24;
1211
+ const minY = 0;
1212
+ const maxY = sheetHeight;
1213
+ const offsetY = info.offset.y;
1214
+ const deltaY = info.delta.y;
1215
+ const velocityY = info.velocity.y;
1216
+ const predictedY = clamp(y + velocityY * predictionTime, minY, maxY);
1217
+ const absVelocity = Math.abs(velocityY);
1218
+ const direction = resolveDirection({ offsetY, velocityY, deltaY });
1219
+ const isStrongFlick = absVelocity > strongFlickVelocity;
1220
+ const isFlick = absVelocity > flickVelocity;
1221
+ if (snapPoints.length === 0) {
1222
+ if (isFlick) {
1223
+ return {
1224
+ yTo: direction < 0 ? minY : maxY,
1225
+ snapIndex: void 0
1226
+ };
1227
+ }
1228
+ const midpoint = minY + (maxY - minY) * dragCloseThreshold;
1229
+ return {
1230
+ yTo: predictedY < midpoint ? minY : maxY,
1231
+ snapIndex: void 0
1232
+ };
1110
1233
  }
1111
- }
1112
- function handleHighVelocityDrag({
1113
- dragDirection,
1114
- snapPoints
1115
- }) {
1116
- const bottomSnapPoint = snapPoints[0];
1117
- const topSnapPoint = snapPoints[snapPoints.length - 1];
1118
- if (dragDirection === "down") {
1234
+ const nearestSnap = findNearestSnapByY(snapPoints, y);
1235
+ if (!isFlick && Math.abs(offsetY) < minDragDistance) {
1119
1236
  return {
1120
- yTo: bottomSnapPoint.snapValueY,
1121
- snapIndex: bottomSnapPoint.snapIndex
1237
+ yTo: nearestSnap.snapValueY,
1238
+ snapIndex: nearestSnap.snapIndex
1122
1239
  };
1123
1240
  }
1124
- return {
1125
- yTo: topSnapPoint.snapValueY,
1126
- snapIndex: topSnapPoint.snapIndex
1127
- };
1128
- }
1129
- function handleLowVelocityDrag({
1130
- currentSnapPoint,
1131
- currentY,
1132
- dragDirection,
1133
- snapPoints,
1134
- velocity
1135
- }) {
1136
- const closestSnapRelativeToCurrentY = findClosestSnapPoint({
1137
- snapPoints,
1138
- currentY
1139
- });
1140
- if (Math.abs(velocity) < 20) {
1241
+ if (!isFlick && Math.abs(y - nearestSnap.snapValueY) < snapStickiness) {
1141
1242
  return {
1142
- yTo: closestSnapRelativeToCurrentY.snapValueY,
1143
- snapIndex: closestSnapRelativeToCurrentY.snapIndex
1243
+ yTo: nearestSnap.snapValueY,
1244
+ snapIndex: nearestSnap.snapIndex
1144
1245
  };
1145
1246
  }
1146
- const nextSnapInDirectionRelativeToCurrentY = findNextSnapPointInDirection({
1147
- y: currentY,
1148
- snapPoints,
1149
- dragDirection
1150
- });
1151
- if (nextSnapInDirectionRelativeToCurrentY) {
1247
+ if (isStrongFlick) {
1248
+ const target = direction < 0 ? minY : maxY;
1249
+ const targetSnap = findNearestSnapByY(snapPoints, target);
1152
1250
  return {
1153
- yTo: nextSnapInDirectionRelativeToCurrentY.snapValueY,
1154
- snapIndex: nextSnapInDirectionRelativeToCurrentY.snapIndex
1251
+ yTo: target,
1252
+ snapIndex: targetSnap.snapIndex
1155
1253
  };
1156
1254
  }
1255
+ if (isFlick) {
1256
+ const nextSnap2 = direction < 0 ? findNearestSnapAboveY(snapPoints, y) : findNearestSnapBelowY(snapPoints, y);
1257
+ const fallbackY = direction < 0 ? minY : maxY;
1258
+ const fallbackSnap = findNearestSnapByY(snapPoints, fallbackY);
1259
+ const targetSnap = nextSnap2 ?? fallbackSnap;
1260
+ return {
1261
+ yTo: targetSnap.snapValueY,
1262
+ snapIndex: targetSnap.snapIndex
1263
+ };
1264
+ }
1265
+ const firstSnap = toAscendingYSnaps(snapPoints)[0];
1266
+ const lastSnap = toAscendingYSnaps(snapPoints)[snapPoints.length - 1];
1267
+ const prevSnap = findNearestSnapAboveY(snapPoints, y) ?? firstSnap;
1268
+ const nextSnap = findNearestSnapBelowY(snapPoints, y) ?? lastSnap;
1269
+ if (prevSnap.snapIndex === nextSnap.snapIndex) {
1270
+ return {
1271
+ yTo: prevSnap.snapValueY,
1272
+ snapIndex: prevSnap.snapIndex
1273
+ };
1274
+ }
1275
+ const range = nextSnap.snapValueY - prevSnap.snapValueY;
1276
+ const progress = range > 0 ? (y - prevSnap.snapValueY) / range : 0;
1277
+ let selectedSnap = nearestSnap;
1278
+ if (direction > 0) {
1279
+ selectedSnap = progress > distanceCommitRatio ? nextSnap : prevSnap;
1280
+ } else if (direction < 0) {
1281
+ selectedSnap = progress < 1 - distanceCommitRatio ? prevSnap : nextSnap;
1282
+ }
1283
+ if (absVelocity > 200) {
1284
+ const predictedSnap = findNearestSnapByY(snapPoints, predictedY);
1285
+ if (Math.abs(predictedSnap.snapValueY - y) < Math.abs(selectedSnap.snapValueY - y)) {
1286
+ selectedSnap = predictedSnap;
1287
+ }
1288
+ }
1157
1289
  return {
1158
- yTo: currentSnapPoint.snapValueY,
1159
- snapIndex: currentSnapPoint.snapIndex
1290
+ yTo: selectedSnap.snapValueY,
1291
+ snapIndex: selectedSnap.snapIndex
1160
1292
  };
1161
1293
  }
1162
1294
 
@@ -1209,11 +1341,6 @@ var Sheet = forwardRef(
1209
1341
  type: "tween",
1210
1342
  ...reduceMotion ? REDUCED_MOTION_TWEEN_CONFIG : tweenConfig
1211
1343
  };
1212
- const keyboard = useVirtualKeyboard({
1213
- isEnabled: isOpen && avoidKeyboard,
1214
- containerRef: sheetRef
1215
- });
1216
- const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1217
1344
  const zIndex = useTransform(
1218
1345
  y,
1219
1346
  (val) => val + 2 >= closedY ? -1 : (style == null ? void 0 : style.zIndex) ?? 9999
@@ -1257,14 +1384,45 @@ var Sheet = forwardRef(
1257
1384
  onComplete: () => updateSnap(snapIndex)
1258
1385
  });
1259
1386
  });
1387
+ const keyboard = useKeyboardAvoidance({
1388
+ isEnabled: isOpen && avoidKeyboard,
1389
+ containerRef: sheetRef,
1390
+ onWillOpenKeyboard: async () => {
1391
+ const lastSnapPoint = snapPoints[snapPoints.length - 1];
1392
+ if (lastSnapPoint && lastSnapPoint.snapIndex !== currentSnap) {
1393
+ await animate(y, lastSnapPoint.snapValueY, animationOptions);
1394
+ updateSnap(lastSnapPoint.snapIndex);
1395
+ }
1396
+ },
1397
+ onDidOpenKeyboard: (focusedElement) => {
1398
+ const sheetElement = sheetRef.current;
1399
+ if (!sheetElement) return;
1400
+ const inputRect = focusedElement.getBoundingClientRect();
1401
+ const containerRect = sheetElement.getBoundingClientRect();
1402
+ const scroller = sheetElement.querySelector(
1403
+ ".react-modal-sheet-content-scroller"
1404
+ );
1405
+ const scrollTarget = Math.max(
1406
+ inputRect.top - containerRect.top + scroller.scrollTop - inputRect.height,
1407
+ 0
1408
+ );
1409
+ requestAnimationFrame(() => {
1410
+ scroller.scrollTo({ top: scrollTarget, behavior: "smooth" });
1411
+ });
1412
+ }
1413
+ });
1414
+ const disableDrag = keyboard.isKeyboardOpen || disableDragProp;
1260
1415
  const blurActiveInput = useStableCallback(() => {
1416
+ var _a;
1261
1417
  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)) {
1418
+ if (focusedElement && willOpenKeyboard(focusedElement) && ((_a = sheetRef.current) == null ? void 0 : _a.contains(focusedElement))) {
1265
1419
  focusedElement.blur();
1266
1420
  }
1267
1421
  });
1422
+ const onDragStart = useStableCallback((event, info) => {
1423
+ blurActiveInput();
1424
+ onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1425
+ });
1268
1426
  const onDrag = useStableCallback((event, info) => {
1269
1427
  onDragProp == null ? void 0 : onDragProp(event, info);
1270
1428
  const currentY = y.get();
@@ -1273,53 +1431,29 @@ var Sheet = forwardRef(
1273
1431
  if (velocity < 0) indicatorRotation.set(-10);
1274
1432
  y.set(Math.max(currentY + info.delta.y, 0));
1275
1433
  });
1276
- const onDragStart = useStableCallback((event, info) => {
1277
- blurActiveInput();
1278
- onDragStartProp == null ? void 0 : onDragStartProp(event, info);
1279
- });
1280
1434
  const onDragEnd = useStableCallback((event, info) => {
1281
1435
  blurActiveInput();
1282
1436
  onDragEndProp == null ? void 0 : onDragEndProp(event, info);
1283
1437
  const currentY = y.get();
1284
- let yTo = 0;
1285
- const currentSnapPoint = currentSnap !== void 0 ? getSnapPoint(currentSnap) : null;
1286
- if (currentSnapPoint) {
1287
- const dragOffsetDirection = info.offset.y > 0 ? "down" : "up";
1288
- const dragVelocityDirection = info.velocity.y > 0 ? "down" : "up";
1289
- const isHighVelocity = Math.abs(info.velocity.y) > dragVelocityThreshold;
1290
- let result;
1291
- if (isHighVelocity) {
1292
- result = handleHighVelocityDrag({
1293
- snapPoints,
1294
- dragDirection: dragVelocityDirection
1295
- });
1296
- } else {
1297
- result = handleLowVelocityDrag({
1298
- currentSnapPoint,
1299
- currentY,
1300
- dragDirection: dragOffsetDirection,
1301
- snapPoints,
1302
- velocity: info.velocity.y
1303
- });
1304
- }
1305
- yTo = result.yTo;
1306
- if (disableDismiss && yTo + 1 >= sheetHeight) {
1307
- const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1308
- if (bottomSnapPoint) {
1309
- yTo = bottomSnapPoint.snapValueY;
1310
- updateSnap(bottomSnapPoint.snapIndex);
1311
- } else {
1312
- yTo = currentY;
1313
- }
1314
- } else if (result.snapIndex !== void 0) {
1315
- updateSnap(result.snapIndex);
1316
- }
1317
- } else if (info.velocity.y > dragVelocityThreshold || currentY > sheetHeight * dragCloseThreshold) {
1318
- if (disableDismiss) {
1319
- yTo = 0;
1438
+ const result = classifyDragEnd({
1439
+ y: currentY,
1440
+ info,
1441
+ sheetHeight,
1442
+ dragCloseThreshold,
1443
+ snapPoints,
1444
+ dragVelocityThreshold
1445
+ });
1446
+ let yTo = result.yTo;
1447
+ if (disableDismiss && yTo + 1 >= sheetHeight) {
1448
+ const bottomSnapPoint = snapPoints.find((s) => s.snapValue > 0);
1449
+ if (bottomSnapPoint) {
1450
+ yTo = bottomSnapPoint.snapValueY;
1451
+ updateSnap(bottomSnapPoint.snapIndex);
1320
1452
  } else {
1321
- yTo = closedY;
1453
+ yTo = currentY;
1322
1454
  }
1455
+ } else if (result.snapIndex !== void 0) {
1456
+ updateSnap(result.snapIndex);
1323
1457
  }
1324
1458
  animate(y, yTo, animationOptions);
1325
1459
  if (yTo + 1 >= sheetHeight && !disableDismiss) {
@@ -1344,41 +1478,6 @@ var Sheet = forwardRef(
1344
1478
  usePreventScroll({
1345
1479
  isDisabled: disableScrollLocking || !isOpen
1346
1480
  });
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
1481
  const state = useSheetState({
1383
1482
  isOpen,
1384
1483
  onOpen: async () => {