@seed-design/react-drawer 1.0.7 → 1.0.9

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.
@@ -5,8 +5,8 @@ import * as DialogPrimitive from '@radix-ui/react-dialog';
5
5
  import { useCallbackRef } from '@radix-ui/react-use-callback-ref';
6
6
  import { dataAttr } from '@seed-design/dom-utils';
7
7
  import { Primitive } from '@seed-design/react-primitive';
8
- import React, { useState, useRef, useCallback, useEffect, useMemo, createContext, useContext, forwardRef } from 'react';
9
- import { useControllableState } from '@radix-ui/react-use-controllable-state';
8
+ import React, { useState, useCallback, useRef, useEffect, useMemo, createContext, useContext, forwardRef } from 'react';
9
+ import { useControllableState } from '@seed-design/react-use-controllable-state';
10
10
 
11
11
  function isMobileFirefox() {
12
12
  if (typeof window === "undefined" || typeof navigator === "undefined") return false;
@@ -28,6 +28,11 @@ function isIPad() {
28
28
  function isIOS() {
29
29
  return isIPhone() || isIPad();
30
30
  }
31
+ function isAndroid() {
32
+ if (typeof window === "undefined" || typeof navigator === "undefined") return false;
33
+ return /Android/.test(navigator.userAgent);
34
+ }
35
+ // TODO: use userAgent instead?
31
36
  function testPlatform(re) {
32
37
  return typeof window !== "undefined" && window.navigator != null ? re.test(window.navigator.platform) : undefined;
33
38
  }
@@ -357,7 +362,7 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
357
362
  snapPointsOffset,
358
363
  snapToPoint
359
364
  ]);
360
- function onRelease({ draggedDistance, closeDrawer, velocity, dismissible }) {
365
+ function onRelease({ draggedDistance, closeDrawer, velocity, dismissible, event }) {
361
366
  if (fadeFromIndex === undefined) return;
362
367
  const currentPosition = direction === "bottom" || direction === "right" ? (activeSnapPointOffset ?? 0) - draggedDistance : (activeSnapPointOffset ?? 0) + draggedDistance;
363
368
  const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
@@ -369,7 +374,10 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
369
374
  });
370
375
  }
371
376
  if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
372
- if (dismissible) closeDrawer();
377
+ if (dismissible) closeDrawer(false, {
378
+ reason: "drag",
379
+ event
380
+ });
373
381
  else snapToPoint(snapPointsOffset[0]); // snap to initial point
374
382
  return;
375
383
  }
@@ -391,7 +399,10 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
391
399
  return;
392
400
  }
393
401
  if (isFirst && dragDirection < 0 && dismissible) {
394
- closeDrawer();
402
+ closeDrawer(false, {
403
+ reason: "drag",
404
+ event
405
+ });
395
406
  }
396
407
  if (activeSnapPointIndex === null) return;
397
408
  snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
@@ -461,8 +472,8 @@ function useDrawer(props) {
461
472
  const [isOpen = false, setIsOpen] = useControllableState({
462
473
  defaultProp: defaultOpen,
463
474
  prop: openProp,
464
- onChange: (o)=>{
465
- onOpenChange?.(o);
475
+ onChange: (o, details)=>{
476
+ onOpenChange?.(o, details);
466
477
  if (!o && !nested) {
467
478
  restorePositionSetting();
468
479
  }
@@ -482,8 +493,13 @@ function useDrawer(props) {
482
493
  }
483
494
  });
484
495
  const [hasBeenOpened, setHasBeenOpened] = useState(false);
496
+ const [hasAnimationDone, setHasAnimationDone] = useState(false);
485
497
  const [isDragging, setIsDragging] = useState(false);
486
498
  const [shouldOverlayAnimate, setShouldOverlayAnimate] = useState(false);
499
+ const [isCloseButtonRendered, setIsCloseButtonRendered] = useState(false);
500
+ const closeButtonRef = useCallback((node)=>{
501
+ setIsCloseButtonRendered(!!node);
502
+ }, []);
487
503
  const overlayRef = useRef(null);
488
504
  const openTime = useRef(null);
489
505
  const dragStartTime = useRef(null);
@@ -652,11 +668,11 @@ function useDrawer(props) {
652
668
  }, [
653
669
  isDragging
654
670
  ]);
655
- const closeDrawer = useCallback((fromWithin)=>{
671
+ const closeDrawer = useCallback((fromWithin, details)=>{
656
672
  cancelDrag();
657
673
  onClose?.();
658
674
  if (!fromWithin) {
659
- setIsOpen(false);
675
+ setIsOpen(false, details);
660
676
  }
661
677
  if (fadeFromIndex !== undefined && fadeFromIndex > 0 && activeSnapPointIndex === 0) {
662
678
  set(overlayRef.current, {
@@ -706,7 +722,8 @@ function useDrawer(props) {
706
722
  draggedDistance: distMoved * directionMultiplier,
707
723
  closeDrawer,
708
724
  velocity,
709
- dismissible
725
+ dismissible,
726
+ event: event.nativeEvent
710
727
  });
711
728
  onReleaseProp?.(event, true);
712
729
  return;
@@ -717,7 +734,10 @@ function useDrawer(props) {
717
734
  return;
718
735
  }
719
736
  if (velocity > VELOCITY_THRESHOLD) {
720
- closeDrawer();
737
+ closeDrawer(false, {
738
+ reason: "drag",
739
+ event: event.nativeEvent
740
+ });
721
741
  onReleaseProp?.(event, false);
722
742
  return;
723
743
  }
@@ -725,7 +745,10 @@ function useDrawer(props) {
725
745
  const visibleDrawerWidth = Math.min(drawerRef.current.getBoundingClientRect().width ?? 0, window.innerWidth);
726
746
  const isHorizontalSwipe = direction === "left" || direction === "right";
727
747
  if (Math.abs(swipeAmount) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold) {
728
- closeDrawer();
748
+ closeDrawer(false, {
749
+ reason: "drag",
750
+ event: event.nativeEvent
751
+ });
729
752
  onReleaseProp?.(event, false);
730
753
  return;
731
754
  }
@@ -778,7 +801,7 @@ function useDrawer(props) {
778
801
  } else {
779
802
  drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
780
803
  }
781
- } else if (!isMobileFirefox()) {
804
+ } else if (!isMobileFirefox() && !isAndroid()) {
782
805
  drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
783
806
  }
784
807
  if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) {
@@ -806,6 +829,7 @@ function useDrawer(props) {
806
829
  }, [
807
830
  modal
808
831
  ]);
832
+ // Effect 1: Track drawer open state
809
833
  useEffect(()=>{
810
834
  if (isOpen) {
811
835
  setHasBeenOpened(true);
@@ -813,6 +837,24 @@ function useDrawer(props) {
813
837
  }, [
814
838
  isOpen
815
839
  ]);
840
+ // Effect 2: Handle animation state and timer
841
+ useEffect(()=>{
842
+ if (isOpen) {
843
+ // Only reset animation state if this is the first open
844
+ if (!hasBeenOpened) {
845
+ setHasAnimationDone(false);
846
+ }
847
+ const timeoutId = setTimeout(()=>{
848
+ setHasAnimationDone(true);
849
+ }, TRANSITIONS.ENTER_DURATION * 1000);
850
+ return ()=>clearTimeout(timeoutId);
851
+ }
852
+ // Reset animation state when drawer closes
853
+ setHasAnimationDone(false);
854
+ }, [
855
+ isOpen,
856
+ hasBeenOpened
857
+ ]);
816
858
  useEffect(()=>{
817
859
  if (isOpen && snapPoints && fadeFromIndex === 0) {
818
860
  setShouldOverlayAnimate(true);
@@ -855,7 +897,10 @@ function useDrawer(props) {
855
897
  setHasBeenOpened,
856
898
  setIsOpen,
857
899
  closeOnInteractOutside,
858
- closeOnEscape
900
+ closeOnEscape,
901
+ hasAnimationDone,
902
+ closeButtonRef,
903
+ isCloseButtonRendered
859
904
  }), [
860
905
  activeSnapPoint,
861
906
  snapPoints,
@@ -880,7 +925,10 @@ function useDrawer(props) {
880
925
  closeOnEscape,
881
926
  onRelease,
882
927
  onDrag,
883
- onPress
928
+ onPress,
929
+ hasAnimationDone,
930
+ closeButtonRef,
931
+ isCloseButtonRendered
884
932
  ]);
885
933
  }
886
934
 
@@ -930,11 +978,10 @@ const DrawerPositioner = /*#__PURE__*/ forwardRef((props, ref)=>{
930
978
  });
931
979
  DrawerPositioner.displayName = "DrawerPositioner";
932
980
  const DrawerBackdrop = /*#__PURE__*/ forwardRef((props, ref)=>{
933
- const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate } = useDrawerContext();
981
+ const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone } = useDrawerContext();
934
982
  const composedRef = useComposedRefs(ref, overlayRef);
935
983
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
936
984
  const onMouseUp = useCallbackRef((event)=>onRelease(event));
937
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
938
985
  if (!modal) {
939
986
  return null;
940
987
  }
@@ -945,13 +992,14 @@ const DrawerBackdrop = /*#__PURE__*/ forwardRef((props, ref)=>{
945
992
  "data-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
946
993
  "data-should-overlay-animate": shouldOverlayAnimate ? "true" : "false",
947
994
  "data-open": dataAttr(isOpen),
995
+ "data-animation-done": hasAnimationDone ? "true" : "false",
948
996
  ...props
949
997
  });
950
998
  });
951
999
  DrawerBackdrop.displayName = "DrawerBackdrop";
952
1000
  const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
953
1001
  const { onPointerDownOutside, style, onOpenAutoFocus, ...restProps } = props;
954
- const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible } = useDrawerContext();
1002
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone } = useDrawerContext();
955
1003
  // Needed to use transition instead of animations
956
1004
  const [delayedSnapPoints, setDelayedSnapPoints] = useState(false);
957
1005
  const composedRef = useComposedRefs(ref, drawerRef);
@@ -998,6 +1046,7 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
998
1046
  "data-delayed-snap-points": delayedSnapPoints ? "true" : "false",
999
1047
  "data-drawer-direction": direction,
1000
1048
  "data-open": dataAttr(isOpen),
1049
+ "data-animation-done": hasAnimationDone ? "true" : "false",
1001
1050
  "data-drawer": "",
1002
1051
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1003
1052
  "data-custom-container": container ? "true" : "false",
@@ -1075,13 +1124,19 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1075
1124
  onInteractOutside: (e)=>{
1076
1125
  // Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
1077
1126
  if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
1078
- closeDrawer();
1127
+ closeDrawer(false, {
1128
+ reason: "interactOutside",
1129
+ event: e.detail.originalEvent
1130
+ });
1079
1131
  }
1080
1132
  props.onInteractOutside?.(e);
1081
1133
  },
1082
1134
  onEscapeKeyDown: (e)=>{
1083
1135
  if (dismissible && closeOnEscape) {
1084
- closeDrawer();
1136
+ closeDrawer(false, {
1137
+ reason: "escapeKeyDown",
1138
+ event: e
1139
+ });
1085
1140
  }
1086
1141
  props.onEscapeKeyDown?.(e);
1087
1142
  }
@@ -1090,15 +1145,28 @@ const DrawerContent = /*#__PURE__*/ forwardRef((props, ref)=>{
1090
1145
  DrawerContent.displayName = "DrawerContent";
1091
1146
  const DrawerTitle = DialogPrimitive.Title;
1092
1147
  const DrawerDescription = DialogPrimitive.Description;
1148
+ const DrawerHeader = /*#__PURE__*/ forwardRef((props, ref)=>{
1149
+ const { isCloseButtonRendered } = useDrawerContext();
1150
+ return /*#__PURE__*/ jsx(Primitive.div, {
1151
+ ref: ref,
1152
+ "data-show-close-button": dataAttr(isCloseButtonRendered),
1153
+ ...props
1154
+ });
1155
+ });
1156
+ DrawerHeader.displayName = "DrawerHeader";
1093
1157
  const DrawerCloseButton = /*#__PURE__*/ forwardRef((props, ref)=>{
1094
- const api = useDrawerContext();
1158
+ const { closeButtonRef, setIsOpen } = useDrawerContext();
1159
+ const composedRef = useComposedRefs(ref, closeButtonRef);
1095
1160
  return /*#__PURE__*/ jsx(Primitive.button, {
1096
- ref: ref,
1161
+ ref: composedRef,
1097
1162
  ...props,
1098
1163
  onClick: (e)=>{
1099
1164
  props.onClick?.(e);
1100
1165
  if (e.defaultPrevented) return;
1101
- api.setIsOpen(false);
1166
+ setIsOpen(false, {
1167
+ reason: "closeButton",
1168
+ event: e.nativeEvent
1169
+ });
1102
1170
  }
1103
1171
  });
1104
1172
  });
@@ -1109,17 +1177,17 @@ const DrawerHandle = /*#__PURE__*/ forwardRef((props, ref)=>{
1109
1177
  const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag, onRelease } = useDrawerContext();
1110
1178
  const closeTimeoutIdRef = useRef(null);
1111
1179
  const shouldCancelInteractionRef = useRef(false);
1112
- function handleStartCycle() {
1180
+ function handleStartCycle(event) {
1113
1181
  // Stop if this is the second click of a double click
1114
1182
  if (shouldCancelInteractionRef.current) {
1115
1183
  handleCancelInteraction();
1116
1184
  return;
1117
1185
  }
1118
1186
  window.setTimeout(()=>{
1119
- handleCycleSnapPoints();
1187
+ handleCycleSnapPoints(event);
1120
1188
  }, DOUBLE_TAP_TIMEOUT);
1121
1189
  }
1122
- function handleCycleSnapPoints() {
1190
+ function handleCycleSnapPoints(event) {
1123
1191
  // Prevent accidental taps while resizing drawer
1124
1192
  if (isDragging || preventCycle || shouldCancelInteractionRef.current) {
1125
1193
  handleCancelInteraction();
@@ -1129,13 +1197,19 @@ const DrawerHandle = /*#__PURE__*/ forwardRef((props, ref)=>{
1129
1197
  handleCancelInteraction();
1130
1198
  if (!snapPoints || snapPoints.length === 0) {
1131
1199
  if (!dismissible) {
1132
- closeDrawer();
1200
+ closeDrawer(false, {
1201
+ reason: "handleClickOnLastSnapPoint",
1202
+ event: event.nativeEvent
1203
+ });
1133
1204
  }
1134
1205
  return;
1135
1206
  }
1136
1207
  const isLastSnapPoint = activeSnapPoint === snapPoints[snapPoints.length - 1];
1137
1208
  if (isLastSnapPoint && dismissible) {
1138
- closeDrawer();
1209
+ closeDrawer(false, {
1210
+ reason: "handleClickOnLastSnapPoint",
1211
+ event: event.nativeEvent
1212
+ });
1139
1213
  return;
1140
1214
  }
1141
1215
  const currentSnapIndex = snapPoints.findIndex((point)=>point === activeSnapPoint);
@@ -1179,4 +1253,4 @@ const DrawerHandle = /*#__PURE__*/ forwardRef((props, ref)=>{
1179
1253
  });
1180
1254
  DrawerHandle.displayName = "DrawerHandle";
1181
1255
 
1182
- export { DrawerBackdrop as D, DrawerCloseButton as a, DrawerContent as b, DrawerDescription as c, DrawerHandle as d, DrawerPositioner as e, DrawerRoot as f, DrawerTitle as g, DrawerTrigger as h, useDrawerContext as i, useDrawer as u };
1256
+ export { DrawerBackdrop as D, DrawerCloseButton as a, DrawerContent as b, DrawerDescription as c, DrawerHandle as d, DrawerHeader as e, DrawerPositioner as f, DrawerRoot as g, DrawerTitle as h, DrawerTrigger as i, useDrawerContext as j, useDrawer as u };
@@ -6,7 +6,7 @@ var reactUseCallbackRef = require('@radix-ui/react-use-callback-ref');
6
6
  var domUtils = require('@seed-design/dom-utils');
7
7
  var reactPrimitive = require('@seed-design/react-primitive');
8
8
  var React = require('react');
9
- var reactUseControllableState = require('@radix-ui/react-use-controllable-state');
9
+ var reactUseControllableState = require('@seed-design/react-use-controllable-state');
10
10
 
11
11
  function _interopDefault (e) { return e && e.__esModule ? e : { default: e }; }
12
12
 
@@ -51,6 +51,11 @@ function isIPad() {
51
51
  function isIOS() {
52
52
  return isIPhone() || isIPad();
53
53
  }
54
+ function isAndroid() {
55
+ if (typeof window === "undefined" || typeof navigator === "undefined") return false;
56
+ return /Android/.test(navigator.userAgent);
57
+ }
58
+ // TODO: use userAgent instead?
54
59
  function testPlatform(re) {
55
60
  return typeof window !== "undefined" && window.navigator != null ? re.test(window.navigator.platform) : undefined;
56
61
  }
@@ -380,7 +385,7 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
380
385
  snapPointsOffset,
381
386
  snapToPoint
382
387
  ]);
383
- function onRelease({ draggedDistance, closeDrawer, velocity, dismissible }) {
388
+ function onRelease({ draggedDistance, closeDrawer, velocity, dismissible, event }) {
384
389
  if (fadeFromIndex === undefined) return;
385
390
  const currentPosition = direction === "bottom" || direction === "right" ? (activeSnapPointOffset ?? 0) - draggedDistance : (activeSnapPointOffset ?? 0) + draggedDistance;
386
391
  const isOverlaySnapPoint = activeSnapPointIndex === fadeFromIndex - 1;
@@ -392,7 +397,10 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
392
397
  });
393
398
  }
394
399
  if (!snapToSequentialPoint && velocity > 2 && !hasDraggedUp) {
395
- if (dismissible) closeDrawer();
400
+ if (dismissible) closeDrawer(false, {
401
+ reason: "drag",
402
+ event
403
+ });
396
404
  else snapToPoint(snapPointsOffset[0]); // snap to initial point
397
405
  return;
398
406
  }
@@ -414,7 +422,10 @@ function useSnapPoints({ activeSnapPointProp, setActiveSnapPointProp, snapPoints
414
422
  return;
415
423
  }
416
424
  if (isFirst && dragDirection < 0 && dismissible) {
417
- closeDrawer();
425
+ closeDrawer(false, {
426
+ reason: "drag",
427
+ event
428
+ });
418
429
  }
419
430
  if (activeSnapPointIndex === null) return;
420
431
  snapToPoint(snapPointsOffset[activeSnapPointIndex + dragDirection]);
@@ -484,8 +495,8 @@ function useDrawer(props) {
484
495
  const [isOpen = false, setIsOpen] = reactUseControllableState.useControllableState({
485
496
  defaultProp: defaultOpen,
486
497
  prop: openProp,
487
- onChange: (o)=>{
488
- onOpenChange?.(o);
498
+ onChange: (o, details)=>{
499
+ onOpenChange?.(o, details);
489
500
  if (!o && !nested) {
490
501
  restorePositionSetting();
491
502
  }
@@ -505,8 +516,13 @@ function useDrawer(props) {
505
516
  }
506
517
  });
507
518
  const [hasBeenOpened, setHasBeenOpened] = React.useState(false);
519
+ const [hasAnimationDone, setHasAnimationDone] = React.useState(false);
508
520
  const [isDragging, setIsDragging] = React.useState(false);
509
521
  const [shouldOverlayAnimate, setShouldOverlayAnimate] = React.useState(false);
522
+ const [isCloseButtonRendered, setIsCloseButtonRendered] = React.useState(false);
523
+ const closeButtonRef = React.useCallback((node)=>{
524
+ setIsCloseButtonRendered(!!node);
525
+ }, []);
510
526
  const overlayRef = React.useRef(null);
511
527
  const openTime = React.useRef(null);
512
528
  const dragStartTime = React.useRef(null);
@@ -675,11 +691,11 @@ function useDrawer(props) {
675
691
  }, [
676
692
  isDragging
677
693
  ]);
678
- const closeDrawer = React.useCallback((fromWithin)=>{
694
+ const closeDrawer = React.useCallback((fromWithin, details)=>{
679
695
  cancelDrag();
680
696
  onClose?.();
681
697
  if (!fromWithin) {
682
- setIsOpen(false);
698
+ setIsOpen(false, details);
683
699
  }
684
700
  if (fadeFromIndex !== undefined && fadeFromIndex > 0 && activeSnapPointIndex === 0) {
685
701
  set(overlayRef.current, {
@@ -729,7 +745,8 @@ function useDrawer(props) {
729
745
  draggedDistance: distMoved * directionMultiplier,
730
746
  closeDrawer,
731
747
  velocity,
732
- dismissible
748
+ dismissible,
749
+ event: event.nativeEvent
733
750
  });
734
751
  onReleaseProp?.(event, true);
735
752
  return;
@@ -740,7 +757,10 @@ function useDrawer(props) {
740
757
  return;
741
758
  }
742
759
  if (velocity > VELOCITY_THRESHOLD) {
743
- closeDrawer();
760
+ closeDrawer(false, {
761
+ reason: "drag",
762
+ event: event.nativeEvent
763
+ });
744
764
  onReleaseProp?.(event, false);
745
765
  return;
746
766
  }
@@ -748,7 +768,10 @@ function useDrawer(props) {
748
768
  const visibleDrawerWidth = Math.min(drawerRef.current.getBoundingClientRect().width ?? 0, window.innerWidth);
749
769
  const isHorizontalSwipe = direction === "left" || direction === "right";
750
770
  if (Math.abs(swipeAmount) >= (isHorizontalSwipe ? visibleDrawerWidth : visibleDrawerHeight) * closeThreshold) {
751
- closeDrawer();
771
+ closeDrawer(false, {
772
+ reason: "drag",
773
+ event: event.nativeEvent
774
+ });
752
775
  onReleaseProp?.(event, false);
753
776
  return;
754
777
  }
@@ -801,7 +824,7 @@ function useDrawer(props) {
801
824
  } else {
802
825
  drawerRef.current.style.height = `${Math.max(newDrawerHeight, visualViewportHeight - offsetFromTop)}px`;
803
826
  }
804
- } else if (!isMobileFirefox()) {
827
+ } else if (!isMobileFirefox() && !isAndroid()) {
805
828
  drawerRef.current.style.height = `${initialDrawerHeight.current}px`;
806
829
  }
807
830
  if (snapPoints && snapPoints.length > 0 && !keyboardIsOpen.current) {
@@ -829,6 +852,7 @@ function useDrawer(props) {
829
852
  }, [
830
853
  modal
831
854
  ]);
855
+ // Effect 1: Track drawer open state
832
856
  React.useEffect(()=>{
833
857
  if (isOpen) {
834
858
  setHasBeenOpened(true);
@@ -836,6 +860,24 @@ function useDrawer(props) {
836
860
  }, [
837
861
  isOpen
838
862
  ]);
863
+ // Effect 2: Handle animation state and timer
864
+ React.useEffect(()=>{
865
+ if (isOpen) {
866
+ // Only reset animation state if this is the first open
867
+ if (!hasBeenOpened) {
868
+ setHasAnimationDone(false);
869
+ }
870
+ const timeoutId = setTimeout(()=>{
871
+ setHasAnimationDone(true);
872
+ }, TRANSITIONS.ENTER_DURATION * 1000);
873
+ return ()=>clearTimeout(timeoutId);
874
+ }
875
+ // Reset animation state when drawer closes
876
+ setHasAnimationDone(false);
877
+ }, [
878
+ isOpen,
879
+ hasBeenOpened
880
+ ]);
839
881
  React.useEffect(()=>{
840
882
  if (isOpen && snapPoints && fadeFromIndex === 0) {
841
883
  setShouldOverlayAnimate(true);
@@ -878,7 +920,10 @@ function useDrawer(props) {
878
920
  setHasBeenOpened,
879
921
  setIsOpen,
880
922
  closeOnInteractOutside,
881
- closeOnEscape
923
+ closeOnEscape,
924
+ hasAnimationDone,
925
+ closeButtonRef,
926
+ isCloseButtonRendered
882
927
  }), [
883
928
  activeSnapPoint,
884
929
  snapPoints,
@@ -903,7 +948,10 @@ function useDrawer(props) {
903
948
  closeOnEscape,
904
949
  onRelease,
905
950
  onDrag,
906
- onPress
951
+ onPress,
952
+ hasAnimationDone,
953
+ closeButtonRef,
954
+ isCloseButtonRendered
907
955
  ]);
908
956
  }
909
957
 
@@ -953,11 +1001,10 @@ const DrawerPositioner = /*#__PURE__*/ React.forwardRef((props, ref)=>{
953
1001
  });
954
1002
  DrawerPositioner.displayName = "DrawerPositioner";
955
1003
  const DrawerBackdrop = /*#__PURE__*/ React.forwardRef((props, ref)=>{
956
- const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate } = useDrawerContext();
1004
+ const { overlayRef, onRelease, modal, snapPoints, isOpen, shouldFade, shouldOverlayAnimate, hasAnimationDone } = useDrawerContext();
957
1005
  const composedRef = reactComposeRefs.useComposedRefs(ref, overlayRef);
958
1006
  const hasSnapPoints = snapPoints && snapPoints.length > 0;
959
1007
  const onMouseUp = reactUseCallbackRef.useCallbackRef((event)=>onRelease(event));
960
- // Overlay is the component that is locking scroll, removing it will unlock the scroll without having to dig into Radix's Dialog library
961
1008
  if (!modal) {
962
1009
  return null;
963
1010
  }
@@ -968,13 +1015,14 @@ const DrawerBackdrop = /*#__PURE__*/ React.forwardRef((props, ref)=>{
968
1015
  "data-snap-points-overlay": isOpen && shouldFade ? "true" : "false",
969
1016
  "data-should-overlay-animate": shouldOverlayAnimate ? "true" : "false",
970
1017
  "data-open": domUtils.dataAttr(isOpen),
1018
+ "data-animation-done": hasAnimationDone ? "true" : "false",
971
1019
  ...props
972
1020
  });
973
1021
  });
974
1022
  DrawerBackdrop.displayName = "DrawerBackdrop";
975
1023
  const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
976
1024
  const { onPointerDownOutside, style, onOpenAutoFocus, ...restProps } = props;
977
- const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible } = useDrawerContext();
1025
+ const { drawerRef, onPress, onRelease, onDrag, keyboardIsOpen, snapPointsOffset, activeSnapPointIndex, modal, isOpen, direction, snapPoints, container, handleOnly, autoFocus, closeDrawer, closeOnInteractOutside, closeOnEscape, dismissible, hasAnimationDone } = useDrawerContext();
978
1026
  // Needed to use transition instead of animations
979
1027
  const [delayedSnapPoints, setDelayedSnapPoints] = React.useState(false);
980
1028
  const composedRef = reactComposeRefs.useComposedRefs(ref, drawerRef);
@@ -1021,6 +1069,7 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1021
1069
  "data-delayed-snap-points": delayedSnapPoints ? "true" : "false",
1022
1070
  "data-drawer-direction": direction,
1023
1071
  "data-open": domUtils.dataAttr(isOpen),
1072
+ "data-animation-done": hasAnimationDone ? "true" : "false",
1024
1073
  "data-drawer": "",
1025
1074
  "data-snap-points": isOpen && hasSnapPoints ? "true" : "false",
1026
1075
  "data-custom-container": container ? "true" : "false",
@@ -1098,13 +1147,19 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1098
1147
  onInteractOutside: (e)=>{
1099
1148
  // Only close if event is not prevented (e.g., by onFocusOutside or onPointerDownOutside)
1100
1149
  if (dismissible && closeOnInteractOutside && !e.defaultPrevented) {
1101
- closeDrawer();
1150
+ closeDrawer(false, {
1151
+ reason: "interactOutside",
1152
+ event: e.detail.originalEvent
1153
+ });
1102
1154
  }
1103
1155
  props.onInteractOutside?.(e);
1104
1156
  },
1105
1157
  onEscapeKeyDown: (e)=>{
1106
1158
  if (dismissible && closeOnEscape) {
1107
- closeDrawer();
1159
+ closeDrawer(false, {
1160
+ reason: "escapeKeyDown",
1161
+ event: e
1162
+ });
1108
1163
  }
1109
1164
  props.onEscapeKeyDown?.(e);
1110
1165
  }
@@ -1113,15 +1168,28 @@ const DrawerContent = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1113
1168
  DrawerContent.displayName = "DrawerContent";
1114
1169
  const DrawerTitle = DialogPrimitive__namespace.Title;
1115
1170
  const DrawerDescription = DialogPrimitive__namespace.Description;
1171
+ const DrawerHeader = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1172
+ const { isCloseButtonRendered } = useDrawerContext();
1173
+ return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.div, {
1174
+ ref: ref,
1175
+ "data-show-close-button": domUtils.dataAttr(isCloseButtonRendered),
1176
+ ...props
1177
+ });
1178
+ });
1179
+ DrawerHeader.displayName = "DrawerHeader";
1116
1180
  const DrawerCloseButton = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1117
- const api = useDrawerContext();
1181
+ const { closeButtonRef, setIsOpen } = useDrawerContext();
1182
+ const composedRef = reactComposeRefs.useComposedRefs(ref, closeButtonRef);
1118
1183
  return /*#__PURE__*/ jsxRuntime.jsx(reactPrimitive.Primitive.button, {
1119
- ref: ref,
1184
+ ref: composedRef,
1120
1185
  ...props,
1121
1186
  onClick: (e)=>{
1122
1187
  props.onClick?.(e);
1123
1188
  if (e.defaultPrevented) return;
1124
- api.setIsOpen(false);
1189
+ setIsOpen(false, {
1190
+ reason: "closeButton",
1191
+ event: e.nativeEvent
1192
+ });
1125
1193
  }
1126
1194
  });
1127
1195
  });
@@ -1132,17 +1200,17 @@ const DrawerHandle = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1132
1200
  const { closeDrawer, isDragging, snapPoints, activeSnapPoint, setActiveSnapPoint, dismissible, handleOnly, isOpen, onPress, onDrag, onRelease } = useDrawerContext();
1133
1201
  const closeTimeoutIdRef = React.useRef(null);
1134
1202
  const shouldCancelInteractionRef = React.useRef(false);
1135
- function handleStartCycle() {
1203
+ function handleStartCycle(event) {
1136
1204
  // Stop if this is the second click of a double click
1137
1205
  if (shouldCancelInteractionRef.current) {
1138
1206
  handleCancelInteraction();
1139
1207
  return;
1140
1208
  }
1141
1209
  window.setTimeout(()=>{
1142
- handleCycleSnapPoints();
1210
+ handleCycleSnapPoints(event);
1143
1211
  }, DOUBLE_TAP_TIMEOUT);
1144
1212
  }
1145
- function handleCycleSnapPoints() {
1213
+ function handleCycleSnapPoints(event) {
1146
1214
  // Prevent accidental taps while resizing drawer
1147
1215
  if (isDragging || preventCycle || shouldCancelInteractionRef.current) {
1148
1216
  handleCancelInteraction();
@@ -1152,13 +1220,19 @@ const DrawerHandle = /*#__PURE__*/ React.forwardRef((props, ref)=>{
1152
1220
  handleCancelInteraction();
1153
1221
  if (!snapPoints || snapPoints.length === 0) {
1154
1222
  if (!dismissible) {
1155
- closeDrawer();
1223
+ closeDrawer(false, {
1224
+ reason: "handleClickOnLastSnapPoint",
1225
+ event: event.nativeEvent
1226
+ });
1156
1227
  }
1157
1228
  return;
1158
1229
  }
1159
1230
  const isLastSnapPoint = activeSnapPoint === snapPoints[snapPoints.length - 1];
1160
1231
  if (isLastSnapPoint && dismissible) {
1161
- closeDrawer();
1232
+ closeDrawer(false, {
1233
+ reason: "handleClickOnLastSnapPoint",
1234
+ event: event.nativeEvent
1235
+ });
1162
1236
  return;
1163
1237
  }
1164
1238
  const currentSnapIndex = snapPoints.findIndex((point)=>point === activeSnapPoint);
@@ -1207,6 +1281,7 @@ exports.DrawerCloseButton = DrawerCloseButton;
1207
1281
  exports.DrawerContent = DrawerContent;
1208
1282
  exports.DrawerDescription = DrawerDescription;
1209
1283
  exports.DrawerHandle = DrawerHandle;
1284
+ exports.DrawerHeader = DrawerHeader;
1210
1285
  exports.DrawerPositioner = DrawerPositioner;
1211
1286
  exports.DrawerRoot = DrawerRoot;
1212
1287
  exports.DrawerTitle = DrawerTitle;
package/lib/index.cjs CHANGED
@@ -1,4 +1,4 @@
1
- var Drawer12s = require('./Drawer-12s-L4KZ1h_g.cjs');
1
+ var Drawer12s = require('./Drawer-12s-B35ZJcKm.cjs');
2
2
 
3
3
  var Drawer_namespace = {
4
4
  __proto__: null,
@@ -7,6 +7,7 @@ var Drawer_namespace = {
7
7
  Content: Drawer12s.DrawerContent,
8
8
  Description: Drawer12s.DrawerDescription,
9
9
  Handle: Drawer12s.DrawerHandle,
10
+ Header: Drawer12s.DrawerHeader,
10
11
  Positioner: Drawer12s.DrawerPositioner,
11
12
  Root: Drawer12s.DrawerRoot,
12
13
  Title: Drawer12s.DrawerTitle,
@@ -18,6 +19,7 @@ exports.DrawerCloseButton = Drawer12s.DrawerCloseButton;
18
19
  exports.DrawerContent = Drawer12s.DrawerContent;
19
20
  exports.DrawerDescription = Drawer12s.DrawerDescription;
20
21
  exports.DrawerHandle = Drawer12s.DrawerHandle;
22
+ exports.DrawerHeader = Drawer12s.DrawerHeader;
21
23
  exports.DrawerPositioner = Drawer12s.DrawerPositioner;
22
24
  exports.DrawerRoot = Drawer12s.DrawerRoot;
23
25
  exports.DrawerTitle = Drawer12s.DrawerTitle;