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.d.mts +0 -5
- package/dist/index.d.ts +0 -5
- package/dist/index.js +374 -275
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +375 -276
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
- package/src/SheetContent.tsx +9 -3
- package/src/constants.ts +1 -1
- package/src/hooks/use-keyboard-avoidance.ts +75 -0
- package/src/hooks/use-prevent-scroll.ts +5 -27
- package/src/hooks/use-scroll-position.ts +0 -1
- package/src/hooks/use-virtual-keyboard.ts +127 -52
- package/src/sheet.tsx +79 -143
- package/src/snap.ts +205 -102
- package/src/types.tsx +1 -2
- package/src/utils.ts +26 -0
- package/src/hooks/use-focused-input.ts +0 -46
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 =
|
|
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
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
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
|
-
|
|
536
|
-
const
|
|
537
|
-
|
|
538
|
-
const
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
|
557
|
-
|
|
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
|
|
877
|
+
let scrollable = /(auto|scroll)/.test(
|
|
702
878
|
style.overflow + style.overflowX + style.overflowY
|
|
703
879
|
);
|
|
704
|
-
if (
|
|
705
|
-
|
|
880
|
+
if (scrollable && checkForOverflow) {
|
|
881
|
+
scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
|
|
706
882
|
}
|
|
707
|
-
return
|
|
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
|
|
1101
|
-
|
|
1102
|
-
|
|
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 -
|
|
1173
|
+
(closest, snap) => Math.abs(snap.snapValueY - y) < Math.abs(closest.snapValueY - y) ? snap : closest
|
|
1106
1174
|
);
|
|
1107
1175
|
}
|
|
1108
|
-
function
|
|
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
|
-
|
|
1209
|
+
dragVelocityThreshold,
|
|
1210
|
+
dragCloseThreshold
|
|
1112
1211
|
}) {
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
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
|
-
|
|
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:
|
|
1128
|
-
snapIndex:
|
|
1244
|
+
yTo: nearestSnap.snapValueY,
|
|
1245
|
+
snapIndex: nearestSnap.snapIndex
|
|
1129
1246
|
};
|
|
1130
1247
|
}
|
|
1131
|
-
|
|
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:
|
|
1150
|
-
snapIndex:
|
|
1250
|
+
yTo: nearestSnap.snapValueY,
|
|
1251
|
+
snapIndex: nearestSnap.snapIndex
|
|
1151
1252
|
};
|
|
1152
1253
|
}
|
|
1153
|
-
|
|
1154
|
-
|
|
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:
|
|
1161
|
-
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:
|
|
1166
|
-
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 (
|
|
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
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
|
|
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 =
|
|
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 () => {
|