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