@howells/stacksheet 1.1.1 → 1.1.3
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.ts +5 -27
- package/dist/index.js +290 -147
- package/dist/index.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
// src/create.tsx
|
|
2
|
+
import { Portal } from "@radix-ui/react-portal";
|
|
3
|
+
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
4
|
+
import { useStore as useStore2 } from "zustand";
|
|
5
|
+
import { useShallow } from "zustand/react/shallow";
|
|
6
|
+
|
|
1
7
|
// src/springs.ts
|
|
2
8
|
var springs = {
|
|
3
9
|
subtle: { stiffness: 300, damping: 30, mass: 1 },
|
|
@@ -60,16 +66,17 @@ function resolveConfig(config = {}) {
|
|
|
60
66
|
};
|
|
61
67
|
}
|
|
62
68
|
|
|
63
|
-
// src/create.tsx
|
|
64
|
-
import { Portal } from "@radix-ui/react-portal";
|
|
65
|
-
import { createContext as createContext2, useContext as useContext2, useMemo as useMemo2 } from "react";
|
|
66
|
-
import { useStore as useStore2 } from "zustand";
|
|
67
|
-
import { useShallow } from "zustand/react/shallow";
|
|
68
|
-
|
|
69
69
|
// src/renderer.tsx
|
|
70
70
|
import FocusTrap from "focus-trap-react";
|
|
71
|
-
import { AnimatePresence, motion as m } from "motion/react";
|
|
72
71
|
import {
|
|
72
|
+
AnimatePresence,
|
|
73
|
+
motion as m,
|
|
74
|
+
useMotionValue,
|
|
75
|
+
useReducedMotion,
|
|
76
|
+
useTransform
|
|
77
|
+
} from "motion/react";
|
|
78
|
+
import {
|
|
79
|
+
memo,
|
|
73
80
|
useCallback as useCallback2,
|
|
74
81
|
useEffect as useEffect3,
|
|
75
82
|
useMemo,
|
|
@@ -166,8 +173,12 @@ function resolveSnapPointPx(point, viewportHeight) {
|
|
|
166
173
|
case "px":
|
|
167
174
|
return value;
|
|
168
175
|
case "rem":
|
|
169
|
-
case "em":
|
|
170
|
-
|
|
176
|
+
case "em": {
|
|
177
|
+
const fontSize = typeof document !== "undefined" ? Number.parseFloat(
|
|
178
|
+
getComputedStyle(document.documentElement).fontSize
|
|
179
|
+
) : 16;
|
|
180
|
+
return value * fontSize;
|
|
181
|
+
}
|
|
171
182
|
case "vh":
|
|
172
183
|
case "%":
|
|
173
184
|
return value / 100 * viewportHeight;
|
|
@@ -307,7 +318,7 @@ function getTransformOrigin(side) {
|
|
|
307
318
|
}
|
|
308
319
|
return "center top";
|
|
309
320
|
}
|
|
310
|
-
function getPanelStyles(side, config,
|
|
321
|
+
function getPanelStyles(side, config, index) {
|
|
311
322
|
const { width, maxWidth, zIndex } = config;
|
|
312
323
|
const base = {
|
|
313
324
|
position: "fixed",
|
|
@@ -323,7 +334,9 @@ function getPanelStyles(side, config, _depth, index) {
|
|
|
323
334
|
left: 0,
|
|
324
335
|
right: 0,
|
|
325
336
|
bottom: 0,
|
|
326
|
-
|
|
337
|
+
// dvh tracks the dynamic viewport on iOS Safari (accounts for browser chrome).
|
|
338
|
+
// The vh fallback covers browsers that don't support dvh yet.
|
|
339
|
+
maxHeight: "85dvh"
|
|
327
340
|
// borderRadius is animated via Motion's animate prop for scale correction
|
|
328
341
|
};
|
|
329
342
|
}
|
|
@@ -416,16 +429,27 @@ function classifyGesture(dx, dy, axis, sign) {
|
|
|
416
429
|
}
|
|
417
430
|
return "drag";
|
|
418
431
|
}
|
|
432
|
+
function commitGesture(dx, dy, axis, sign, scrollEl) {
|
|
433
|
+
const gesture = classifyGesture(dx, dy, axis, sign);
|
|
434
|
+
if (gesture === "none") {
|
|
435
|
+
return "none";
|
|
436
|
+
}
|
|
437
|
+
if (scrollEl && !isAtScrollEdge(scrollEl, axis, sign)) {
|
|
438
|
+
return "none";
|
|
439
|
+
}
|
|
440
|
+
return "drag";
|
|
441
|
+
}
|
|
419
442
|
function getPanelDimension(panel, axis) {
|
|
420
443
|
if (!panel) {
|
|
421
444
|
return 300;
|
|
422
445
|
}
|
|
423
446
|
return axis === "x" ? panel.offsetWidth : panel.offsetHeight;
|
|
424
447
|
}
|
|
425
|
-
function useDrag(panelRef, config,
|
|
448
|
+
function useDrag(panelRef, config, callbacks) {
|
|
426
449
|
const startRef = useRef(null);
|
|
427
450
|
const committedRef = useRef(null);
|
|
428
451
|
const offsetRef = useRef(0);
|
|
452
|
+
const isDraggingRef = useRef(false);
|
|
429
453
|
const scrollTargetRef = useRef(null);
|
|
430
454
|
const { axis, sign } = getDismissAxis(config.side);
|
|
431
455
|
const handlePointerDown = useCallback(
|
|
@@ -464,19 +488,17 @@ function useDrag(panelRef, config, onDragUpdate) {
|
|
|
464
488
|
return;
|
|
465
489
|
}
|
|
466
490
|
if (committedRef.current === null) {
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
if (
|
|
475
|
-
committedRef.current = "none";
|
|
491
|
+
committedRef.current = commitGesture(
|
|
492
|
+
dx,
|
|
493
|
+
dy,
|
|
494
|
+
axis,
|
|
495
|
+
sign,
|
|
496
|
+
scrollTargetRef.current
|
|
497
|
+
);
|
|
498
|
+
if (committedRef.current !== "drag") {
|
|
476
499
|
startRef.current = null;
|
|
477
500
|
return;
|
|
478
501
|
}
|
|
479
|
-
committedRef.current = "drag";
|
|
480
502
|
}
|
|
481
503
|
if (committedRef.current !== "drag") {
|
|
482
504
|
return;
|
|
@@ -485,10 +507,14 @@ function useDrag(panelRef, config, onDragUpdate) {
|
|
|
485
507
|
const directional = rawOffset * sign;
|
|
486
508
|
const clampedOffset = directional >= 0 ? directional : -Math.sqrt(Math.abs(directional)) * RUBBER_BAND_FACTOR;
|
|
487
509
|
offsetRef.current = clampedOffset;
|
|
488
|
-
|
|
510
|
+
callbacks.dragOffset.set(clampedOffset);
|
|
511
|
+
if (!isDraggingRef.current) {
|
|
512
|
+
isDraggingRef.current = true;
|
|
513
|
+
callbacks.onDragStart();
|
|
514
|
+
}
|
|
489
515
|
e.preventDefault();
|
|
490
516
|
},
|
|
491
|
-
[axis, sign,
|
|
517
|
+
[axis, sign, callbacks]
|
|
492
518
|
);
|
|
493
519
|
const dismiss = useCallback(() => {
|
|
494
520
|
if (config.isNested) {
|
|
@@ -496,8 +522,10 @@ function useDrag(panelRef, config, onDragUpdate) {
|
|
|
496
522
|
} else {
|
|
497
523
|
config.onClose();
|
|
498
524
|
}
|
|
499
|
-
|
|
500
|
-
|
|
525
|
+
callbacks.dragOffset.set(0);
|
|
526
|
+
isDraggingRef.current = false;
|
|
527
|
+
callbacks.onDragEnd();
|
|
528
|
+
}, [config, callbacks]);
|
|
501
529
|
const handlePointerUp = useCallback(
|
|
502
530
|
(_e) => {
|
|
503
531
|
if (!startRef.current || committedRef.current !== "drag") {
|
|
@@ -527,7 +555,9 @@ function useDrag(panelRef, config, onDragUpdate) {
|
|
|
527
555
|
dismiss();
|
|
528
556
|
} else {
|
|
529
557
|
config.onSnap(targetIndex);
|
|
530
|
-
|
|
558
|
+
callbacks.dragOffset.set(0);
|
|
559
|
+
isDraggingRef.current = false;
|
|
560
|
+
callbacks.onDragEnd();
|
|
531
561
|
}
|
|
532
562
|
return;
|
|
533
563
|
}
|
|
@@ -536,18 +566,24 @@ function useDrag(panelRef, config, onDragUpdate) {
|
|
|
536
566
|
if (pastThreshold || fastEnough) {
|
|
537
567
|
dismiss();
|
|
538
568
|
} else {
|
|
539
|
-
|
|
569
|
+
callbacks.dragOffset.set(0);
|
|
570
|
+
isDraggingRef.current = false;
|
|
571
|
+
callbacks.onDragEnd();
|
|
540
572
|
}
|
|
541
573
|
},
|
|
542
|
-
[panelRef, axis, config,
|
|
574
|
+
[panelRef, axis, config, callbacks, dismiss]
|
|
543
575
|
);
|
|
544
576
|
const handlePointerCancel = useCallback(() => {
|
|
545
577
|
startRef.current = null;
|
|
546
578
|
committedRef.current = null;
|
|
547
579
|
offsetRef.current = 0;
|
|
548
580
|
scrollTargetRef.current = null;
|
|
549
|
-
|
|
550
|
-
|
|
581
|
+
callbacks.dragOffset.set(0);
|
|
582
|
+
if (isDraggingRef.current) {
|
|
583
|
+
isDraggingRef.current = false;
|
|
584
|
+
callbacks.onDragEnd();
|
|
585
|
+
}
|
|
586
|
+
}, [callbacks]);
|
|
551
587
|
useEffect2(() => {
|
|
552
588
|
const el = panelRef.current;
|
|
553
589
|
if (!(el && config.enabled)) {
|
|
@@ -625,10 +661,7 @@ function resolveClassNames(cn) {
|
|
|
625
661
|
header: cn.header ?? ""
|
|
626
662
|
};
|
|
627
663
|
}
|
|
628
|
-
function
|
|
629
|
-
return isTop ? spring : stackSpring;
|
|
630
|
-
}
|
|
631
|
-
function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId) {
|
|
664
|
+
function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId, hasDescription) {
|
|
632
665
|
if (!isTop) {
|
|
633
666
|
return {};
|
|
634
667
|
}
|
|
@@ -638,27 +671,19 @@ function buildAriaProps(isTop, isModal, isComposable, ariaLabel, panelId) {
|
|
|
638
671
|
}
|
|
639
672
|
if (isComposable) {
|
|
640
673
|
props["aria-labelledby"] = `${panelId}-title`;
|
|
641
|
-
|
|
674
|
+
if (hasDescription) {
|
|
675
|
+
props["aria-describedby"] = `${panelId}-desc`;
|
|
676
|
+
}
|
|
642
677
|
} else {
|
|
643
678
|
props["aria-label"] = ariaLabel;
|
|
644
679
|
}
|
|
645
680
|
return props;
|
|
646
681
|
}
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
case "right":
|
|
653
|
-
return { x: offset };
|
|
654
|
-
case "left":
|
|
655
|
-
return { x: -offset };
|
|
656
|
-
case "bottom":
|
|
657
|
-
return { y: offset };
|
|
658
|
-
default:
|
|
659
|
-
return {};
|
|
660
|
-
}
|
|
661
|
-
}
|
|
682
|
+
var VISUAL_TWEEN = {
|
|
683
|
+
type: "tween",
|
|
684
|
+
duration: 0.25,
|
|
685
|
+
ease: "easeOut"
|
|
686
|
+
};
|
|
662
687
|
function usePanelHeight(panelRef, hasSnapPoints) {
|
|
663
688
|
const [height, setHeight] = useState2(0);
|
|
664
689
|
useEffect3(() => {
|
|
@@ -690,16 +715,11 @@ function buildPanelStyle(panelStyles, isTop, hasPanelClass, isDragging) {
|
|
|
690
715
|
};
|
|
691
716
|
}
|
|
692
717
|
function buildPanelTransition(isDragging, isTop, spring, stackSpring) {
|
|
693
|
-
const visualTween = {
|
|
694
|
-
type: "tween",
|
|
695
|
-
duration: 0.25,
|
|
696
|
-
ease: "easeOut"
|
|
697
|
-
};
|
|
698
718
|
if (isDragging) {
|
|
699
719
|
return { type: "tween", duration: 0 };
|
|
700
720
|
}
|
|
701
|
-
const base =
|
|
702
|
-
return { ...base, borderRadius:
|
|
721
|
+
const base = isTop ? spring : stackSpring;
|
|
722
|
+
return { ...base, borderRadius: VISUAL_TWEEN, boxShadow: VISUAL_TWEEN };
|
|
703
723
|
}
|
|
704
724
|
function computeSnapYOffset(side, snapHeights, activeSnapIndex, measuredHeight) {
|
|
705
725
|
if (side !== "bottom" || snapHeights.length === 0 || measuredHeight <= 0) {
|
|
@@ -707,6 +727,38 @@ function computeSnapYOffset(side, snapHeights, activeSnapIndex, measuredHeight)
|
|
|
707
727
|
}
|
|
708
728
|
return getSnapOffset(activeSnapIndex, snapHeights, measuredHeight);
|
|
709
729
|
}
|
|
730
|
+
function ModalFocusTrap({
|
|
731
|
+
enabled,
|
|
732
|
+
active,
|
|
733
|
+
fallbackRef,
|
|
734
|
+
children
|
|
735
|
+
}) {
|
|
736
|
+
if (!enabled) {
|
|
737
|
+
return children;
|
|
738
|
+
}
|
|
739
|
+
return /* @__PURE__ */ jsx2(
|
|
740
|
+
FocusTrap,
|
|
741
|
+
{
|
|
742
|
+
active,
|
|
743
|
+
focusTrapOptions: {
|
|
744
|
+
initialFocus: false,
|
|
745
|
+
returnFocusOnDeactivate: true,
|
|
746
|
+
escapeDeactivates: false,
|
|
747
|
+
allowOutsideClick: true,
|
|
748
|
+
checkCanFocusTrap: () => new Promise(
|
|
749
|
+
(resolve) => requestAnimationFrame(() => resolve())
|
|
750
|
+
),
|
|
751
|
+
fallbackFocus: () => {
|
|
752
|
+
if (fallbackRef.current) {
|
|
753
|
+
return fallbackRef.current;
|
|
754
|
+
}
|
|
755
|
+
return document.body;
|
|
756
|
+
}
|
|
757
|
+
},
|
|
758
|
+
children
|
|
759
|
+
}
|
|
760
|
+
);
|
|
761
|
+
}
|
|
710
762
|
function PanelInnerContent({
|
|
711
763
|
isComposable,
|
|
712
764
|
shouldRender,
|
|
@@ -731,31 +783,54 @@ function PanelInnerContent({
|
|
|
731
783
|
)
|
|
732
784
|
] });
|
|
733
785
|
}
|
|
734
|
-
function BottomHandle() {
|
|
786
|
+
function BottomHandle({ onDismiss }) {
|
|
735
787
|
return /* @__PURE__ */ jsx2(
|
|
736
|
-
"
|
|
788
|
+
"button",
|
|
737
789
|
{
|
|
738
|
-
|
|
790
|
+
"aria-label": "Dismiss",
|
|
791
|
+
className: "flex w-full shrink-0 cursor-grab touch-none items-center justify-center border-none bg-transparent pt-4 pb-1",
|
|
739
792
|
"data-stacksheet-handle": "",
|
|
740
|
-
|
|
793
|
+
onClick: onDismiss,
|
|
794
|
+
type: "button",
|
|
795
|
+
children: /* @__PURE__ */ jsx2("div", { "aria-hidden": "true", className: "h-1 w-9 rounded-sm bg-current/25" })
|
|
741
796
|
}
|
|
742
797
|
);
|
|
743
798
|
}
|
|
744
|
-
function SideHandle({
|
|
799
|
+
function SideHandle({
|
|
800
|
+
side,
|
|
801
|
+
isHovered,
|
|
802
|
+
onDismiss
|
|
803
|
+
}) {
|
|
745
804
|
const position = side === "right" ? { right: "100%" } : { left: "100%" };
|
|
746
805
|
return /* @__PURE__ */ jsx2(
|
|
747
806
|
m.div,
|
|
748
807
|
{
|
|
749
808
|
animate: { opacity: isHovered ? 1 : 0 },
|
|
809
|
+
"aria-label": "Dismiss",
|
|
750
810
|
className: "absolute top-0 bottom-0 flex w-6 cursor-grab touch-none items-center justify-center",
|
|
751
811
|
"data-stacksheet-handle": "",
|
|
812
|
+
onClick: onDismiss,
|
|
813
|
+
onKeyDown: (e) => {
|
|
814
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
815
|
+
e.preventDefault();
|
|
816
|
+
onDismiss?.();
|
|
817
|
+
}
|
|
818
|
+
},
|
|
819
|
+
role: "button",
|
|
752
820
|
style: position,
|
|
821
|
+
tabIndex: 0,
|
|
753
822
|
transition: { duration: isHovered ? 0.15 : 0.4, ease: "easeOut" },
|
|
754
|
-
children: /* @__PURE__ */ jsx2(
|
|
823
|
+
children: /* @__PURE__ */ jsx2(
|
|
824
|
+
"div",
|
|
825
|
+
{
|
|
826
|
+
"aria-hidden": "true",
|
|
827
|
+
className: "h-10 w-[5px] rounded-sm bg-current/35 shadow-sm"
|
|
828
|
+
}
|
|
829
|
+
)
|
|
755
830
|
}
|
|
756
831
|
);
|
|
757
832
|
}
|
|
758
|
-
|
|
833
|
+
var SheetPanel = memo(function SheetPanel2({
|
|
759
834
|
item,
|
|
760
835
|
index,
|
|
761
836
|
depth,
|
|
@@ -778,18 +853,25 @@ function SheetPanel({
|
|
|
778
853
|
slideTarget,
|
|
779
854
|
spring,
|
|
780
855
|
stackSpring,
|
|
781
|
-
exitSpring
|
|
856
|
+
exitSpring,
|
|
857
|
+
prefersReducedMotion
|
|
782
858
|
}) {
|
|
783
859
|
const panelRef = useRef2(null);
|
|
784
860
|
const hasEnteredRef = useRef2(false);
|
|
785
|
-
const
|
|
786
|
-
|
|
787
|
-
isDragging: false
|
|
788
|
-
});
|
|
861
|
+
const dragOffset = useMotionValue(0);
|
|
862
|
+
const [isDragging, setIsDragging] = useState2(false);
|
|
789
863
|
const [isHovered, setIsHovered] = useState2(false);
|
|
864
|
+
const dragCallbacks = useMemo(
|
|
865
|
+
() => ({
|
|
866
|
+
dragOffset,
|
|
867
|
+
onDragStart: () => setIsDragging(true),
|
|
868
|
+
onDragEnd: () => setIsDragging(false)
|
|
869
|
+
}),
|
|
870
|
+
[dragOffset]
|
|
871
|
+
);
|
|
790
872
|
const measuredHeight = usePanelHeight(panelRef, snapHeights.length > 0);
|
|
791
873
|
const transform = getStackTransform(depth, config.stacking);
|
|
792
|
-
const panelStyles = getPanelStyles(side, config,
|
|
874
|
+
const panelStyles = getPanelStyles(side, config, index);
|
|
793
875
|
useEffect3(() => {
|
|
794
876
|
if (!isTop) {
|
|
795
877
|
hasEnteredRef.current = false;
|
|
@@ -804,7 +886,7 @@ function SheetPanel({
|
|
|
804
886
|
useDrag(
|
|
805
887
|
panelRef,
|
|
806
888
|
{
|
|
807
|
-
enabled: isTop && config.drag && config.dismissible,
|
|
889
|
+
enabled: isTop && config.drag && config.dismissible && !prefersReducedMotion,
|
|
808
890
|
closeThreshold: config.closeThreshold,
|
|
809
891
|
velocityThreshold: config.velocityThreshold,
|
|
810
892
|
side,
|
|
@@ -816,22 +898,44 @@ function SheetPanel({
|
|
|
816
898
|
onSnap,
|
|
817
899
|
sequential: config.snapToSequentialPoints
|
|
818
900
|
},
|
|
819
|
-
|
|
901
|
+
dragCallbacks
|
|
820
902
|
);
|
|
821
903
|
const ariaLabel = (typeof item.data?.__ariaLabel === "string" ? item.data.__ariaLabel : void 0) ?? config.ariaLabel;
|
|
822
904
|
const panelId = `stacksheet-${item.id}`;
|
|
905
|
+
const [hasDescription, setHasDescription] = useState2(false);
|
|
906
|
+
const registerDescription = useCallback2(() => {
|
|
907
|
+
setHasDescription(true);
|
|
908
|
+
return () => setHasDescription(false);
|
|
909
|
+
}, []);
|
|
823
910
|
const panelContext = useMemo(
|
|
824
|
-
() => ({
|
|
825
|
-
|
|
911
|
+
() => ({
|
|
912
|
+
close,
|
|
913
|
+
back: pop,
|
|
914
|
+
isNested,
|
|
915
|
+
isTop,
|
|
916
|
+
panelId,
|
|
917
|
+
side,
|
|
918
|
+
hasDescription,
|
|
919
|
+
registerDescription
|
|
920
|
+
}),
|
|
921
|
+
[
|
|
922
|
+
close,
|
|
923
|
+
pop,
|
|
924
|
+
isNested,
|
|
925
|
+
isTop,
|
|
926
|
+
panelId,
|
|
927
|
+
side,
|
|
928
|
+
hasDescription,
|
|
929
|
+
registerDescription
|
|
930
|
+
]
|
|
826
931
|
);
|
|
827
932
|
const isComposable = renderHeader === false;
|
|
828
933
|
const hasPanelClass = classNames.panel !== "";
|
|
829
|
-
const dragOffset = getDragTransform(side, dragState.offset);
|
|
830
934
|
const panelStyle = buildPanelStyle(
|
|
831
935
|
panelStyles,
|
|
832
936
|
isTop,
|
|
833
937
|
hasPanelClass,
|
|
834
|
-
|
|
938
|
+
isDragging
|
|
835
939
|
);
|
|
836
940
|
const headerProps = {
|
|
837
941
|
isNested,
|
|
@@ -844,10 +948,11 @@ function SheetPanel({
|
|
|
844
948
|
config.modal,
|
|
845
949
|
isComposable,
|
|
846
950
|
ariaLabel,
|
|
847
|
-
panelId
|
|
951
|
+
panelId,
|
|
952
|
+
hasDescription
|
|
848
953
|
);
|
|
849
954
|
const transition = buildPanelTransition(
|
|
850
|
-
|
|
955
|
+
isDragging,
|
|
851
956
|
isTop,
|
|
852
957
|
spring,
|
|
853
958
|
stackSpring
|
|
@@ -863,22 +968,19 @@ function SheetPanel({
|
|
|
863
968
|
const animateTarget = {
|
|
864
969
|
...slideTarget,
|
|
865
970
|
...stackOffset,
|
|
866
|
-
...dragOffset,
|
|
867
971
|
scale: transform.scale,
|
|
868
972
|
opacity: transform.opacity,
|
|
869
973
|
...animatedRadius,
|
|
870
|
-
boxShadow: getShadow(
|
|
974
|
+
boxShadow: getShadow(!isTop),
|
|
871
975
|
transition,
|
|
872
|
-
...snapYOffset > 0 ? { y:
|
|
976
|
+
...snapYOffset > 0 ? { y: snapYOffset } : {}
|
|
873
977
|
};
|
|
978
|
+
const dragSign = side === "left" ? -1 : 1;
|
|
979
|
+
const dragTranslate = useTransform(dragOffset, (v) => v * dragSign);
|
|
980
|
+
const dragStyle = side === "bottom" ? { y: dragTranslate } : { x: dragTranslate };
|
|
874
981
|
const initialRadius = getInitialRadius(side);
|
|
875
982
|
const showSideHandle = isTop && side !== "bottom";
|
|
876
983
|
const showBottomHandle = isTop && side === "bottom";
|
|
877
|
-
const exitTween = {
|
|
878
|
-
type: "tween",
|
|
879
|
-
duration: 0.25,
|
|
880
|
-
ease: "easeOut"
|
|
881
|
-
};
|
|
882
984
|
const panelContent = /* @__PURE__ */ jsxs(
|
|
883
985
|
m.div,
|
|
884
986
|
{
|
|
@@ -887,26 +989,36 @@ function SheetPanel({
|
|
|
887
989
|
exit: {
|
|
888
990
|
...slideFrom,
|
|
889
991
|
opacity: 0.6,
|
|
890
|
-
boxShadow: getShadow(
|
|
891
|
-
transition: { ...exitSpring, boxShadow:
|
|
992
|
+
boxShadow: getShadow(false),
|
|
993
|
+
transition: { ...exitSpring, boxShadow: VISUAL_TWEEN }
|
|
892
994
|
},
|
|
893
995
|
initial: {
|
|
894
996
|
...slideFrom,
|
|
895
997
|
opacity: 0.8,
|
|
896
998
|
...initialRadius,
|
|
897
|
-
boxShadow: getShadow(
|
|
999
|
+
boxShadow: getShadow(false)
|
|
898
1000
|
},
|
|
899
1001
|
onAnimationComplete: handleAnimationComplete,
|
|
1002
|
+
onBlur: showSideHandle ? () => setIsHovered(false) : void 0,
|
|
1003
|
+
onFocus: showSideHandle ? () => setIsHovered(true) : void 0,
|
|
900
1004
|
onMouseEnter: showSideHandle ? () => setIsHovered(true) : void 0,
|
|
901
1005
|
onMouseLeave: showSideHandle ? () => setIsHovered(false) : void 0,
|
|
902
1006
|
ref: panelRef,
|
|
903
|
-
style: panelStyle,
|
|
1007
|
+
style: { ...panelStyle, ...dragStyle },
|
|
904
1008
|
tabIndex: isTop ? -1 : void 0,
|
|
1009
|
+
...isTop ? {} : { "aria-hidden": "true", inert: true },
|
|
905
1010
|
...ariaProps,
|
|
906
1011
|
children: [
|
|
907
|
-
showSideHandle && /* @__PURE__ */ jsx2(
|
|
1012
|
+
showSideHandle && /* @__PURE__ */ jsx2(
|
|
1013
|
+
SideHandle,
|
|
1014
|
+
{
|
|
1015
|
+
isHovered,
|
|
1016
|
+
onDismiss: isNested ? pop : close,
|
|
1017
|
+
side
|
|
1018
|
+
}
|
|
1019
|
+
),
|
|
908
1020
|
/* @__PURE__ */ jsxs("div", { className: "flex min-h-0 flex-1 flex-col overflow-hidden rounded-[inherit]", children: [
|
|
909
|
-
showBottomHandle && /* @__PURE__ */ jsx2(BottomHandle, {}),
|
|
1021
|
+
showBottomHandle && /* @__PURE__ */ jsx2(BottomHandle, { onDismiss: isNested ? pop : close }),
|
|
910
1022
|
/* @__PURE__ */ jsx2(
|
|
911
1023
|
PanelInnerContent,
|
|
912
1024
|
{
|
|
@@ -924,35 +1036,19 @@ function SheetPanel({
|
|
|
924
1036
|
},
|
|
925
1037
|
item.id
|
|
926
1038
|
);
|
|
927
|
-
if (!config.modal) {
|
|
928
|
-
return /* @__PURE__ */ jsx2(SheetPanelContext.Provider, { value: panelContext, children: panelContent });
|
|
929
|
-
}
|
|
930
1039
|
return /* @__PURE__ */ jsx2(SheetPanelContext.Provider, { value: panelContext, children: /* @__PURE__ */ jsx2(
|
|
931
|
-
|
|
1040
|
+
ModalFocusTrap,
|
|
932
1041
|
{
|
|
933
1042
|
active: isTop,
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
returnFocusOnDeactivate: true,
|
|
937
|
-
escapeDeactivates: false,
|
|
938
|
-
allowOutsideClick: true,
|
|
939
|
-
checkCanFocusTrap: () => new Promise(
|
|
940
|
-
(resolve) => requestAnimationFrame(() => resolve())
|
|
941
|
-
),
|
|
942
|
-
fallbackFocus: () => {
|
|
943
|
-
if (panelRef.current) {
|
|
944
|
-
return panelRef.current;
|
|
945
|
-
}
|
|
946
|
-
return document.body;
|
|
947
|
-
}
|
|
948
|
-
},
|
|
1043
|
+
enabled: config.modal,
|
|
1044
|
+
fallbackRef: panelRef,
|
|
949
1045
|
children: panelContent
|
|
950
1046
|
}
|
|
951
1047
|
) });
|
|
952
|
-
}
|
|
953
|
-
function useBodyScale(config, isOpen) {
|
|
1048
|
+
});
|
|
1049
|
+
function useBodyScale(config, isOpen, prefersReducedMotion) {
|
|
954
1050
|
useEffect3(() => {
|
|
955
|
-
if (!config.shouldScaleBackground) {
|
|
1051
|
+
if (!config.shouldScaleBackground || prefersReducedMotion) {
|
|
956
1052
|
return;
|
|
957
1053
|
}
|
|
958
1054
|
const wrapper = document.querySelector("[data-stacksheet-wrapper]");
|
|
@@ -977,7 +1073,12 @@ function useBodyScale(config, isOpen) {
|
|
|
977
1073
|
};
|
|
978
1074
|
wrapper.addEventListener("transitionend", handleEnd, { once: true });
|
|
979
1075
|
return () => wrapper.removeEventListener("transitionend", handleEnd);
|
|
980
|
-
}, [
|
|
1076
|
+
}, [
|
|
1077
|
+
isOpen,
|
|
1078
|
+
config.shouldScaleBackground,
|
|
1079
|
+
config.scaleBackgroundAmount,
|
|
1080
|
+
prefersReducedMotion
|
|
1081
|
+
]);
|
|
981
1082
|
}
|
|
982
1083
|
function SheetRenderer({
|
|
983
1084
|
store,
|
|
@@ -992,7 +1093,11 @@ function SheetRenderer({
|
|
|
992
1093
|
const rawClose = useStore(store, (s) => s.close);
|
|
993
1094
|
const rawPop = useStore(store, (s) => s.pop);
|
|
994
1095
|
const side = useResolvedSide(config);
|
|
995
|
-
const
|
|
1096
|
+
const prefersReducedMotion = useReducedMotion() ?? false;
|
|
1097
|
+
const classNames = useMemo(
|
|
1098
|
+
() => resolveClassNames(classNamesProp),
|
|
1099
|
+
[classNamesProp]
|
|
1100
|
+
);
|
|
996
1101
|
const snapHeights = useMemo(
|
|
997
1102
|
() => side === "bottom" && config.snapPoints.length > 0 ? resolveSnapPoints(
|
|
998
1103
|
config.snapPoints,
|
|
@@ -1034,7 +1139,7 @@ function SheetRenderer({
|
|
|
1034
1139
|
);
|
|
1035
1140
|
const close = useCallback2(() => closeWith("programmatic"), [closeWith]);
|
|
1036
1141
|
const pop = useCallback2(() => popWith("programmatic"), [popWith]);
|
|
1037
|
-
useBodyScale(config, isOpen);
|
|
1142
|
+
useBodyScale(config, isOpen, prefersReducedMotion);
|
|
1038
1143
|
const triggerRef = useRef2(null);
|
|
1039
1144
|
const wasOpenRef = useRef2(false);
|
|
1040
1145
|
useEffect3(() => {
|
|
@@ -1042,13 +1147,17 @@ function SheetRenderer({
|
|
|
1042
1147
|
triggerRef.current = document.activeElement;
|
|
1043
1148
|
} else if (!isOpen && wasOpenRef.current) {
|
|
1044
1149
|
const el = triggerRef.current;
|
|
1045
|
-
if (el && el instanceof HTMLElement) {
|
|
1150
|
+
if (el && el instanceof HTMLElement && el !== document.body && el.tagName !== "BODY") {
|
|
1046
1151
|
el.focus();
|
|
1047
1152
|
}
|
|
1048
1153
|
triggerRef.current = null;
|
|
1049
1154
|
}
|
|
1050
1155
|
wasOpenRef.current = isOpen;
|
|
1051
1156
|
}, [isOpen]);
|
|
1157
|
+
const stackLengthRef = useRef2(stack.length);
|
|
1158
|
+
useEffect3(() => {
|
|
1159
|
+
stackLengthRef.current = stack.length;
|
|
1160
|
+
}, [stack.length]);
|
|
1052
1161
|
useEffect3(() => {
|
|
1053
1162
|
if (!(isOpen && config.closeOnEscape && config.dismissible)) {
|
|
1054
1163
|
return;
|
|
@@ -1056,7 +1165,7 @@ function SheetRenderer({
|
|
|
1056
1165
|
function handleKeyDown(e) {
|
|
1057
1166
|
if (e.key === "Escape") {
|
|
1058
1167
|
e.preventDefault();
|
|
1059
|
-
if (
|
|
1168
|
+
if (stackLengthRef.current > 1) {
|
|
1060
1169
|
popWith("escape");
|
|
1061
1170
|
} else {
|
|
1062
1171
|
closeWith("escape");
|
|
@@ -1065,36 +1174,37 @@ function SheetRenderer({
|
|
|
1065
1174
|
}
|
|
1066
1175
|
document.addEventListener("keydown", handleKeyDown);
|
|
1067
1176
|
return () => document.removeEventListener("keydown", handleKeyDown);
|
|
1068
|
-
}, [
|
|
1069
|
-
isOpen,
|
|
1070
|
-
config.closeOnEscape,
|
|
1071
|
-
config.dismissible,
|
|
1072
|
-
stack.length,
|
|
1073
|
-
popWith,
|
|
1074
|
-
closeWith
|
|
1075
|
-
]);
|
|
1177
|
+
}, [isOpen, config.closeOnEscape, config.dismissible, popWith, closeWith]);
|
|
1076
1178
|
useEffect3(() => {
|
|
1077
1179
|
if (!(isOpen && config.dismissible) || typeof globalThis.CloseWatcher === "undefined") {
|
|
1078
1180
|
return;
|
|
1079
1181
|
}
|
|
1080
1182
|
const watcher = new globalThis.CloseWatcher();
|
|
1081
1183
|
watcher.onclose = () => {
|
|
1082
|
-
if (
|
|
1184
|
+
if (stackLengthRef.current > 1) {
|
|
1083
1185
|
popWith("escape");
|
|
1084
1186
|
} else {
|
|
1085
1187
|
closeWith("escape");
|
|
1086
1188
|
}
|
|
1087
1189
|
};
|
|
1088
1190
|
return () => watcher.destroy();
|
|
1089
|
-
}, [isOpen, config.dismissible,
|
|
1090
|
-
const slideFrom = getSlideFrom(side);
|
|
1091
|
-
const slideTarget = getSlideTarget();
|
|
1092
|
-
const spring =
|
|
1093
|
-
type: "
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1191
|
+
}, [isOpen, config.dismissible, popWith, closeWith]);
|
|
1192
|
+
const slideFrom = useMemo(() => getSlideFrom(side), [side]);
|
|
1193
|
+
const slideTarget = useMemo(() => getSlideTarget(), []);
|
|
1194
|
+
const spring = useMemo(
|
|
1195
|
+
() => prefersReducedMotion ? { type: "tween", duration: 0 } : {
|
|
1196
|
+
type: "spring",
|
|
1197
|
+
damping: config.spring.damping,
|
|
1198
|
+
stiffness: config.spring.stiffness,
|
|
1199
|
+
mass: config.spring.mass
|
|
1200
|
+
},
|
|
1201
|
+
[
|
|
1202
|
+
prefersReducedMotion,
|
|
1203
|
+
config.spring.damping,
|
|
1204
|
+
config.spring.stiffness,
|
|
1205
|
+
config.spring.mass
|
|
1206
|
+
]
|
|
1207
|
+
);
|
|
1098
1208
|
const stackSpring = spring;
|
|
1099
1209
|
const exitSpring = spring;
|
|
1100
1210
|
const isModal = config.modal;
|
|
@@ -1102,6 +1212,7 @@ function SheetRenderer({
|
|
|
1102
1212
|
const hasBackdropClass = classNames.backdrop !== "";
|
|
1103
1213
|
const backdropStyle = {
|
|
1104
1214
|
zIndex: config.zIndex,
|
|
1215
|
+
willChange: "opacity",
|
|
1105
1216
|
cursor: config.closeOnBackdrop && config.dismissible ? "pointer" : void 0,
|
|
1106
1217
|
...hasBackdropClass ? {} : { background: "var(--overlay, rgba(0, 0, 0, 0.2))" }
|
|
1107
1218
|
};
|
|
@@ -1110,11 +1221,16 @@ function SheetRenderer({
|
|
|
1110
1221
|
config.onCloseComplete?.(closeReasonRef.current);
|
|
1111
1222
|
}
|
|
1112
1223
|
}, [stack.length, config]);
|
|
1224
|
+
const handleBackdropExitComplete = useCallback2(() => {
|
|
1225
|
+
requestAnimationFrame(() => {
|
|
1226
|
+
void document.body.offsetHeight;
|
|
1227
|
+
});
|
|
1228
|
+
}, []);
|
|
1113
1229
|
const swipeClose = useCallback2(() => closeWith("swipe"), [closeWith]);
|
|
1114
1230
|
const swipePop = useCallback2(() => popWith("swipe"), [popWith]);
|
|
1115
1231
|
const shouldLockScroll = isOpen && isModal && config.lockScroll;
|
|
1116
1232
|
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
1117
|
-
showOverlay && /* @__PURE__ */ jsx2(AnimatePresence, { children: isOpen && /* @__PURE__ */ jsx2(
|
|
1233
|
+
showOverlay && /* @__PURE__ */ jsx2(AnimatePresence, { onExitComplete: handleBackdropExitComplete, children: isOpen && /* @__PURE__ */ jsx2(
|
|
1118
1234
|
m.div,
|
|
1119
1235
|
{
|
|
1120
1236
|
animate: { opacity: 1 },
|
|
@@ -1154,6 +1270,7 @@ function SheetRenderer({
|
|
|
1154
1270
|
item,
|
|
1155
1271
|
onSnap: handleSnap,
|
|
1156
1272
|
pop,
|
|
1273
|
+
prefersReducedMotion,
|
|
1157
1274
|
renderHeader,
|
|
1158
1275
|
shouldRender,
|
|
1159
1276
|
side,
|
|
@@ -1185,7 +1302,7 @@ function getInitialRadius(side) {
|
|
|
1185
1302
|
}
|
|
1186
1303
|
var SHADOW_SM = "0px 2px 5px 0px rgba(0,0,0,0.11), 0px 9px 9px 0px rgba(0,0,0,0.1), 0px 21px 13px 0px rgba(0,0,0,0.06)";
|
|
1187
1304
|
var SHADOW_LG = "0px 23px 52px 0px rgba(0,0,0,0.08), 0px 94px 94px 0px rgba(0,0,0,0.07), 0px 211px 127px 0px rgba(0,0,0,0.04)";
|
|
1188
|
-
function getShadow(
|
|
1305
|
+
function getShadow(isNested) {
|
|
1189
1306
|
return isNested ? SHADOW_SM : SHADOW_LG;
|
|
1190
1307
|
}
|
|
1191
1308
|
|
|
@@ -1261,6 +1378,15 @@ function createSheetStore(config) {
|
|
|
1261
1378
|
third
|
|
1262
1379
|
);
|
|
1263
1380
|
}
|
|
1381
|
+
function pruneRegistry(remainingStack) {
|
|
1382
|
+
const usedTypes = new Set(remainingStack.map((item) => item.type));
|
|
1383
|
+
for (const [component, typeKey] of componentRegistry) {
|
|
1384
|
+
if (!usedTypes.has(typeKey)) {
|
|
1385
|
+
componentRegistry.delete(component);
|
|
1386
|
+
componentMap.delete(typeKey);
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1264
1390
|
const store = createStore()((set, get) => {
|
|
1265
1391
|
function _openResolved({ type, id, data }) {
|
|
1266
1392
|
set({
|
|
@@ -1372,6 +1498,7 @@ function createSheetStore(config) {
|
|
|
1372
1498
|
if (next.length === state.stack.length) {
|
|
1373
1499
|
return state;
|
|
1374
1500
|
}
|
|
1501
|
+
pruneRegistry(next);
|
|
1375
1502
|
if (next.length === 0) {
|
|
1376
1503
|
return { stack: [], isOpen: false };
|
|
1377
1504
|
}
|
|
@@ -1381,12 +1508,16 @@ function createSheetStore(config) {
|
|
|
1381
1508
|
pop() {
|
|
1382
1509
|
set((state) => {
|
|
1383
1510
|
if (state.stack.length <= 1) {
|
|
1511
|
+
pruneRegistry([]);
|
|
1384
1512
|
return { stack: [], isOpen: false };
|
|
1385
1513
|
}
|
|
1386
|
-
|
|
1514
|
+
const next = state.stack.slice(0, -1);
|
|
1515
|
+
pruneRegistry(next);
|
|
1516
|
+
return { stack: next, isOpen: true };
|
|
1387
1517
|
});
|
|
1388
1518
|
},
|
|
1389
1519
|
close() {
|
|
1520
|
+
pruneRegistry([]);
|
|
1390
1521
|
set({ stack: [], isOpen: false });
|
|
1391
1522
|
}
|
|
1392
1523
|
};
|
|
@@ -1470,6 +1601,9 @@ import {
|
|
|
1470
1601
|
Viewport as ScrollAreaViewport
|
|
1471
1602
|
} from "@radix-ui/react-scroll-area";
|
|
1472
1603
|
import { Slot } from "@radix-ui/react-slot";
|
|
1604
|
+
import {
|
|
1605
|
+
useEffect as useEffect4
|
|
1606
|
+
} from "react";
|
|
1473
1607
|
import { jsx as jsx4, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
1474
1608
|
function SheetHandle({
|
|
1475
1609
|
asChild,
|
|
@@ -1477,14 +1611,26 @@ function SheetHandle({
|
|
|
1477
1611
|
style,
|
|
1478
1612
|
children
|
|
1479
1613
|
}) {
|
|
1614
|
+
const { close, back, isNested } = useSheetPanel();
|
|
1615
|
+
const dismiss = isNested ? back : close;
|
|
1480
1616
|
const Comp = asChild ? Slot : "div";
|
|
1481
1617
|
return /* @__PURE__ */ jsx4(
|
|
1482
1618
|
Comp,
|
|
1483
1619
|
{
|
|
1620
|
+
"aria-label": "Dismiss",
|
|
1484
1621
|
className: `flex shrink-0 cursor-grab touch-none items-center justify-center pt-4 pb-1 ${className ?? ""}`,
|
|
1485
1622
|
"data-stacksheet-handle": "",
|
|
1623
|
+
onClick: dismiss,
|
|
1624
|
+
onKeyDown: (e) => {
|
|
1625
|
+
if (e.key === "Enter" || e.key === " ") {
|
|
1626
|
+
e.preventDefault();
|
|
1627
|
+
dismiss();
|
|
1628
|
+
}
|
|
1629
|
+
},
|
|
1630
|
+
role: "button",
|
|
1486
1631
|
style,
|
|
1487
|
-
|
|
1632
|
+
tabIndex: 0,
|
|
1633
|
+
children: children ?? /* @__PURE__ */ jsx4("div", { "aria-hidden": "true", className: "h-1 w-9 rounded-sm bg-current/25" })
|
|
1488
1634
|
}
|
|
1489
1635
|
);
|
|
1490
1636
|
}
|
|
@@ -1523,7 +1669,8 @@ function SheetDescription({
|
|
|
1523
1669
|
style,
|
|
1524
1670
|
children
|
|
1525
1671
|
}) {
|
|
1526
|
-
const { panelId } = useSheetPanel();
|
|
1672
|
+
const { panelId, registerDescription } = useSheetPanel();
|
|
1673
|
+
useEffect4(() => registerDescription(), [registerDescription]);
|
|
1527
1674
|
const Comp = asChild ? Slot : "p";
|
|
1528
1675
|
return /* @__PURE__ */ jsx4(Comp, { className, id: `${panelId}-desc`, style, children });
|
|
1529
1676
|
}
|
|
@@ -1621,10 +1768,6 @@ var Sheet = {
|
|
|
1621
1768
|
export {
|
|
1622
1769
|
Sheet,
|
|
1623
1770
|
createStacksheet,
|
|
1624
|
-
getPanelStyles,
|
|
1625
|
-
getSlideFrom,
|
|
1626
|
-
getStackTransform,
|
|
1627
|
-
resolveConfig,
|
|
1628
1771
|
springs,
|
|
1629
1772
|
useIsMobile,
|
|
1630
1773
|
useResolvedSide,
|