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.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,
|
|
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 =
|
|
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
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
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
|
-
|
|
529
|
-
const
|
|
530
|
-
|
|
531
|
-
const
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
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
|
|
550
|
-
|
|
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
|
|
870
|
+
let scrollable = /(auto|scroll)/.test(
|
|
695
871
|
style.overflow + style.overflowX + style.overflowY
|
|
696
872
|
);
|
|
697
|
-
if (
|
|
698
|
-
|
|
873
|
+
if (scrollable && checkForOverflow) {
|
|
874
|
+
scrollable = node.scrollHeight !== node.clientHeight || node.scrollWidth !== node.clientWidth;
|
|
699
875
|
}
|
|
700
|
-
return
|
|
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
|
|
1094
|
-
|
|
1095
|
-
|
|
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 -
|
|
1166
|
+
(closest, snap) => Math.abs(snap.snapValueY - y) < Math.abs(closest.snapValueY - y) ? snap : closest
|
|
1099
1167
|
);
|
|
1100
1168
|
}
|
|
1101
|
-
function
|
|
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
|
-
|
|
1202
|
+
dragVelocityThreshold,
|
|
1203
|
+
dragCloseThreshold
|
|
1105
1204
|
}) {
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
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
|
-
|
|
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:
|
|
1121
|
-
snapIndex:
|
|
1237
|
+
yTo: nearestSnap.snapValueY,
|
|
1238
|
+
snapIndex: nearestSnap.snapIndex
|
|
1122
1239
|
};
|
|
1123
1240
|
}
|
|
1124
|
-
|
|
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:
|
|
1143
|
-
snapIndex:
|
|
1243
|
+
yTo: nearestSnap.snapValueY,
|
|
1244
|
+
snapIndex: nearestSnap.snapIndex
|
|
1144
1245
|
};
|
|
1145
1246
|
}
|
|
1146
|
-
|
|
1147
|
-
|
|
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:
|
|
1154
|
-
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:
|
|
1159
|
-
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 (
|
|
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
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
|
|
1296
|
-
|
|
1297
|
-
|
|
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 =
|
|
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 () => {
|