react-panel-layout 0.6.0 → 0.6.1

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.
Files changed (216) hide show
  1. package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
  2. package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  4. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  5. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  6. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  7. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  8. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DKTg_N61.cjs.map} +1 -1
  9. package/dist/{GridLayout-BltqeCPK.js → GridLayout-UWNxXw77.js} +34 -35
  10. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-UWNxXw77.js.map} +1 -1
  11. package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
  12. package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
  13. package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
  14. package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
  15. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-BqUzNtf2.js} +5 -5
  16. package/dist/{PanelSystem-Dr1TBhxM.js.map → PanelSystem-BqUzNtf2.js.map} +1 -1
  17. package/dist/{PanelSystem-Bs8bQwQF.cjs → PanelSystem-D603LKKv.cjs} +2 -2
  18. package/dist/{PanelSystem-Bs8bQwQF.cjs.map → PanelSystem-D603LKKv.cjs.map} +1 -1
  19. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  20. package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
  21. package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
  22. package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
  23. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  24. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  25. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  26. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  27. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  28. package/dist/components/window/Drawer.d.ts +3 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/drawerStyles.d.ts +69 -0
  31. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  32. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  33. package/dist/config.cjs +1 -1
  34. package/dist/config.js +3 -3
  35. package/dist/constants/styles.d.ts +17 -0
  36. package/dist/dialog/index.d.ts +69 -0
  37. package/dist/floating.js +1 -1
  38. package/dist/grid.cjs +1 -1
  39. package/dist/grid.js +2 -2
  40. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  41. package/dist/hooks/gesture/types.d.ts +48 -5
  42. package/dist/hooks/gesture/utils.d.ts +19 -0
  43. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  44. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  45. package/dist/hooks/useResizeObserver.d.ts +33 -1
  46. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  47. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  48. package/dist/index.cjs +1 -1
  49. package/dist/index.js +7 -7
  50. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  51. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  52. package/dist/modules/dialog/Modal.d.ts +26 -0
  53. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  54. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  55. package/dist/modules/dialog/types.d.ts +183 -0
  56. package/dist/modules/dialog/useDialog.d.ts +39 -0
  57. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  58. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  59. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  60. package/dist/modules/drawer/types.d.ts +74 -0
  61. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  62. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  63. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  64. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  65. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  66. package/dist/panels.cjs +1 -1
  67. package/dist/panels.js +1 -1
  68. package/dist/pivot.cjs +1 -1
  69. package/dist/pivot.js +1 -1
  70. package/dist/resizer.cjs +1 -1
  71. package/dist/resizer.js +2 -2
  72. package/dist/stack.cjs +1 -1
  73. package/dist/stack.cjs.map +1 -1
  74. package/dist/stack.js +503 -762
  75. package/dist/stack.js.map +1 -1
  76. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  77. package/dist/sticky-header.cjs +1 -1
  78. package/dist/sticky-header.cjs.map +1 -1
  79. package/dist/sticky-header.js +59 -51
  80. package/dist/sticky-header.js.map +1 -1
  81. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  82. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  83. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  84. package/dist/types.d.ts +16 -0
  85. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  86. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  87. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  88. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  89. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  90. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  91. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  92. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  93. package/dist/window/index.d.ts +2 -0
  94. package/dist/window.cjs +1 -1
  95. package/dist/window.cjs.map +1 -1
  96. package/dist/window.js +114 -103
  97. package/dist/window.js.map +1 -1
  98. package/package.json +6 -1
  99. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  100. package/src/components/window/Drawer.tsx +249 -162
  101. package/src/components/window/DrawerLayers.tsx +13 -3
  102. package/src/components/window/drawerStyles.spec.ts +263 -0
  103. package/src/components/window/drawerStyles.ts +228 -0
  104. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  105. package/src/components/window/drawerSwipeConfig.ts +112 -0
  106. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  107. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  108. package/src/constants/styles.ts +19 -0
  109. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  110. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  111. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  112. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  113. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  114. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  115. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  116. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  117. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  118. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  119. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  120. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  121. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  122. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  123. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  124. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  125. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  126. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  127. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  128. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  129. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  130. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  131. package/src/demo/routes.tsx +22 -1
  132. package/src/dialog/index.ts +85 -0
  133. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  134. package/src/hooks/gesture/testing/createGestureSimulator.ts +112 -37
  135. package/src/hooks/gesture/types.ts +83 -6
  136. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  137. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +91 -31
  138. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  139. package/src/hooks/gesture/utils.ts +91 -0
  140. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  141. package/src/hooks/useAnimatedVisibility.ts +28 -2
  142. package/src/hooks/useAnimationFrame.ts +8 -0
  143. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  144. package/src/hooks/useOperationContinuity.ts +135 -0
  145. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  146. package/src/hooks/useResizeObserver.tsx +108 -39
  147. package/src/hooks/useScrollContainer.ts +4 -10
  148. package/src/hooks/useSharedElementTransition.ts +333 -0
  149. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  150. package/src/hooks/useSwipeContentTransform.ts +166 -28
  151. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  152. package/src/modules/dialog/AlertDialog.tsx +221 -0
  153. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  154. package/src/modules/dialog/DialogContainer.tsx +188 -0
  155. package/src/modules/dialog/Modal.spec.tsx +220 -0
  156. package/src/modules/dialog/Modal.tsx +182 -0
  157. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  158. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  159. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  160. package/src/modules/dialog/types.ts +186 -0
  161. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  162. package/src/modules/dialog/useDialog.ts +214 -0
  163. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  164. package/src/modules/dialog/useDialogContainer.ts +150 -0
  165. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  166. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  167. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  168. package/src/modules/dialog/useDialogTransform.ts +407 -0
  169. package/src/modules/drawer/types.ts +102 -0
  170. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  171. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  172. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  173. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  174. package/src/modules/pivot/SwipePivotContent.spec.tsx +55 -25
  175. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  176. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  177. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  178. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  179. package/src/modules/pivot/usePivot.spec.ts +17 -3
  180. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  181. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  182. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  183. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  184. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  185. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  186. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  187. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  188. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  189. package/src/modules/stack/useStackAnimationState.ts +18 -13
  190. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  191. package/src/modules/stack/useStackNavigation.tsx +113 -56
  192. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  193. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  194. package/src/sticky-header/StickyArea.tsx +29 -57
  195. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  196. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  197. package/src/types.ts +18 -0
  198. package/src/window/index.ts +2 -0
  199. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  200. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  201. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  202. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  203. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  204. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  205. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  206. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  207. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  208. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  209. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  210. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  211. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  212. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  213. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  214. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  215. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  216. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @file SwipeSafeZone component
3
+ *
4
+ * A wrapper component that marks an area as exempt from swipe gesture detection.
5
+ * Content inside this zone will not trigger swipe-to-close or other swipe gestures.
6
+ *
7
+ * Use this for:
8
+ * - Scrollable content areas
9
+ * - Input fields and text areas
10
+ * - Interactive elements that need drag/swipe for their own purposes
11
+ */
12
+ import * as React from "react";
13
+
14
+ /**
15
+ * Data attribute used to identify swipe-safe zones.
16
+ * Swipe gesture handlers should check for this attribute on target elements.
17
+ */
18
+ export const SWIPE_SAFE_ZONE_ATTR = "data-swipe-safe-zone";
19
+
20
+ export type SwipeSafeZoneProps = {
21
+ /** Content to render inside the safe zone */
22
+ children: React.ReactNode;
23
+ /** Additional CSS class name */
24
+ className?: string;
25
+ /** Additional inline styles */
26
+ style?: React.CSSProperties;
27
+ };
28
+
29
+ /**
30
+ * SwipeSafeZone marks an area where swipe gestures should not be triggered.
31
+ *
32
+ * @example
33
+ * ```tsx
34
+ * <SwipeSafeZone>
35
+ * <ScrollableList items={items} />
36
+ * </SwipeSafeZone>
37
+ * ```
38
+ */
39
+ export const SwipeSafeZone: React.FC<SwipeSafeZoneProps> = ({
40
+ children,
41
+ className,
42
+ style,
43
+ }) => {
44
+ return (
45
+ <div
46
+ className={className}
47
+ style={style}
48
+ data-swipe-safe-zone="true"
49
+ >
50
+ {children}
51
+ </div>
52
+ );
53
+ };
54
+
55
+ /**
56
+ * Check if an element is inside a SwipeSafeZone.
57
+ */
58
+ export function isInSwipeSafeZone(element: HTMLElement, container: HTMLElement): boolean {
59
+ let current: HTMLElement | null = element;
60
+
61
+ while (current && current !== container) {
62
+ if (current.hasAttribute(SWIPE_SAFE_ZONE_ATTR)) {
63
+ return true;
64
+ }
65
+ current = current.parentElement;
66
+ }
67
+
68
+ return false;
69
+ }
@@ -2,83 +2,53 @@
2
2
  * @file Drawer component
3
3
  *
4
4
  * Mobile-friendly slide-in panel with backdrop support.
5
+ * Supports swipe gestures for opening/closing.
5
6
  */
6
7
  import * as React from "react";
7
- import type { DrawerBehavior, WindowPosition } from "../../types";
8
+ import type { DrawerBehavior, WindowPosition } from "../../types.js";
8
9
  import {
9
10
  FloatingPanelCloseButton,
10
11
  FloatingPanelContent,
11
12
  FloatingPanelFrame,
12
13
  FloatingPanelHeader,
13
14
  FloatingPanelTitle,
14
- } from "../paneling/FloatingPanelFrame";
15
+ } from "../paneling/FloatingPanelFrame.js";
15
16
  import {
16
17
  DRAWER_HEADER_PADDING_Y,
17
18
  DRAWER_HEADER_PADDING_X,
18
19
  DRAWER_HEADER_GAP,
19
20
  DRAWER_CONTENT_PADDING,
20
- COLOR_DRAWER_BACKDROP,
21
- DRAWER_TRANSITION_DURATION,
22
- DRAWER_TRANSITION_EASING,
23
- } from "../../constants/styles";
24
-
25
- const drawerBackdropStyle: React.CSSProperties = {
26
- position: "fixed",
27
- inset: 0,
28
- background: COLOR_DRAWER_BACKDROP,
29
- };
30
-
31
- const drawerBaseStyle: React.CSSProperties = {
32
- willChange: "transform",
33
- };
34
-
35
- const drawerPlacementStyles: Record<string, React.CSSProperties> = {
36
- left: {
37
- top: 0,
38
- bottom: 0,
39
- left: 0,
40
- transform: "translateX(-100%)",
41
- },
42
- right: {
43
- top: 0,
44
- bottom: 0,
45
- right: 0,
46
- transform: "translateX(100%)",
47
- },
48
- top: {
49
- top: 0,
50
- left: 0,
51
- right: 0,
52
- transform: "translateY(-100%)",
53
- },
54
- bottom: {
55
- bottom: 0,
56
- left: 0,
57
- right: 0,
58
- transform: "translateY(100%)",
59
- },
60
- };
61
-
62
- const computeTransitionValue = (
63
- mode: DrawerBehavior["transitionMode"] | undefined,
64
- duration: DrawerBehavior["transitionDuration"],
65
- easing: DrawerBehavior["transitionEasing"],
66
- ): string | undefined => {
67
- if (mode === "none") {
68
- return undefined;
69
- }
70
-
71
- const durationValue = duration ?? DRAWER_TRANSITION_DURATION;
72
- const easingValue = easing ?? DRAWER_TRANSITION_EASING;
21
+ } from "../../constants/styles.js";
22
+ import { useDrawerSwipeInput } from "../../modules/drawer/useDrawerSwipeInput.js";
23
+ import type { DrawerSwipeDirection } from "../../modules/drawer/types.js";
24
+ import {
25
+ DRAWER_BACKDROP_BASE_STYLE,
26
+ DRAWER_PANEL_BASE_STYLE,
27
+ getPlacementStyle,
28
+ getOpenTransform,
29
+ computeTransitionValue,
30
+ computeBackdropTransition,
31
+ formatDimension,
32
+ computeEdgeZoneStyle,
33
+ } from "./drawerStyles.js";
34
+ import type { DrawerPlacement } from "./drawerStyles.js";
35
+ import {
36
+ parseSwipeGesturesConfig,
37
+ resolvePlacement,
38
+ shouldShowEdgeZone,
39
+ } from "./drawerSwipeConfig.js";
40
+ import { useDrawerSwipeTransform } from "./useDrawerSwipeTransform.js";
73
41
 
74
- return `transform ${durationValue} ${easingValue}`;
75
- };
42
+ // ============================================================================
43
+ // Types
44
+ // ============================================================================
76
45
 
77
46
  export type DrawerProps = {
78
47
  id: string;
79
48
  config: DrawerBehavior;
80
49
  isOpen: boolean;
81
50
  onClose: () => void;
51
+ onOpen?: () => void;
82
52
  children: React.ReactNode;
83
53
  zIndex?: number;
84
54
  width?: string | number;
@@ -86,12 +56,9 @@ export type DrawerProps = {
86
56
  position?: WindowPosition;
87
57
  };
88
58
 
89
- const shouldShowCloseButton = (dismissible: boolean, showClose: boolean): boolean => {
90
- if (!dismissible) {
91
- return false;
92
- }
93
- return showClose;
94
- };
59
+ // ============================================================================
60
+ // Sub-components
61
+ // ============================================================================
95
62
 
96
63
  type DrawerContentProps = {
97
64
  chrome: boolean;
@@ -102,7 +69,14 @@ type DrawerContentProps = {
102
69
  children: React.ReactNode;
103
70
  };
104
71
 
105
- const DrawerContent: React.FC<DrawerContentProps> = ({ chrome, frameStyle, header, dismissible, onClose, children }) => {
72
+ const DrawerContent: React.FC<DrawerContentProps> = ({
73
+ chrome,
74
+ frameStyle,
75
+ header,
76
+ dismissible,
77
+ onClose,
78
+ children,
79
+ }) => {
106
80
  if (!chrome) {
107
81
  return <>{children}</>;
108
82
  }
@@ -118,43 +92,152 @@ const DrawerContent: React.FC<DrawerContentProps> = ({ chrome, frameStyle, heade
118
92
  );
119
93
  };
120
94
 
121
- const DrawerHeaderView: React.FC<{
95
+ type DrawerHeaderViewProps = {
122
96
  header?: DrawerBehavior["header"];
123
97
  dismissible: boolean;
124
98
  onClose: () => void;
125
- }> = ({ header, dismissible, onClose }) => {
99
+ };
100
+
101
+ function shouldShowCloseButton(dismissible: boolean, showCloseButton: boolean): boolean {
102
+ if (!dismissible) {
103
+ return false;
104
+ }
105
+ return showCloseButton;
106
+ }
107
+
108
+ function renderCloseButton(
109
+ shouldShow: boolean,
110
+ onClose: () => void,
111
+ ): React.ReactNode {
112
+ if (!shouldShow) {
113
+ return null;
114
+ }
115
+ return (
116
+ <FloatingPanelCloseButton
117
+ onClick={onClose}
118
+ aria-label="Close drawer"
119
+ style={{ marginLeft: "auto" }}
120
+ />
121
+ );
122
+ }
123
+
124
+ function renderEdgeZone(
125
+ showEdgeZone: boolean,
126
+ edgeZoneRef: React.RefObject<HTMLDivElement | null>,
127
+ edgeZoneStyle: React.CSSProperties,
128
+ onPointerDown: ((e: React.PointerEvent<HTMLElement>) => void) | undefined,
129
+ placement: string,
130
+ ): React.ReactNode {
131
+ if (!showEdgeZone) {
132
+ return null;
133
+ }
134
+ return (
135
+ <div
136
+ ref={edgeZoneRef}
137
+ style={edgeZoneStyle}
138
+ onPointerDown={onPointerDown}
139
+ data-drawer-edge-zone={placement}
140
+ />
141
+ );
142
+ }
143
+
144
+ const DrawerHeaderView: React.FC<DrawerHeaderViewProps> = ({ header, dismissible, onClose }) => {
126
145
  if (!header) {
127
146
  return null;
128
147
  }
129
148
 
130
149
  const showCloseButton = header.showCloseButton ?? true;
131
- const shouldShowClose = shouldShowCloseButton(dismissible, showCloseButton);
150
+ const shouldShow = shouldShowCloseButton(dismissible, showCloseButton);
132
151
 
133
152
  return (
134
- <React.Activity mode={header ? "visible" : "hidden"}>
135
- <FloatingPanelHeader
136
- style={{ padding: `${DRAWER_HEADER_PADDING_Y} ${DRAWER_HEADER_PADDING_X}`, gap: DRAWER_HEADER_GAP }}
137
- >
138
- <React.Activity mode={header ? "visible" : "hidden"}>
139
- <FloatingPanelTitle>{header.title}</FloatingPanelTitle>
140
- </React.Activity>
141
- <React.Activity mode={shouldShowClose ? "visible" : "hidden"}>
142
- <FloatingPanelCloseButton
143
- onClick={onClose}
144
- aria-label="Close drawer"
145
- style={{ marginLeft: "auto" }}
146
- />
147
- </React.Activity>
148
- </FloatingPanelHeader>
149
- </React.Activity>
153
+ <FloatingPanelHeader
154
+ style={{ padding: `${DRAWER_HEADER_PADDING_Y} ${DRAWER_HEADER_PADDING_X}`, gap: DRAWER_HEADER_GAP }}
155
+ >
156
+ <FloatingPanelTitle>{header.title}</FloatingPanelTitle>
157
+ {renderCloseButton(shouldShow, onClose)}
158
+ </FloatingPanelHeader>
150
159
  );
151
160
  };
152
161
 
162
+ // ============================================================================
163
+ // Style computation hooks
164
+ // ============================================================================
165
+
166
+ function useDrawerPanelStyle(
167
+ placement: DrawerPlacement,
168
+ isOpen: boolean,
169
+ isSwipeOperating: boolean,
170
+ config: DrawerBehavior,
171
+ dimensions: { width?: string | number; height?: string | number; zIndex?: number },
172
+ ): React.CSSProperties {
173
+ return React.useMemo((): React.CSSProperties => {
174
+ const effectiveMode = isSwipeOperating ? "none" : config.transitionMode;
175
+ const transitionValue = computeTransitionValue(
176
+ effectiveMode,
177
+ config.transitionDuration,
178
+ config.transitionEasing,
179
+ );
180
+
181
+ const placementStyle = getPlacementStyle(placement);
182
+ const transform = isOpen ? getOpenTransform(placement) : placementStyle.transform;
183
+ const position = config.inline ? "absolute" : "fixed";
184
+
185
+ return {
186
+ ...DRAWER_PANEL_BASE_STYLE,
187
+ position,
188
+ ...placementStyle,
189
+ transform,
190
+ transition: transitionValue,
191
+ zIndex: dimensions.zIndex,
192
+ width: formatDimension(dimensions.width),
193
+ height: formatDimension(dimensions.height),
194
+ };
195
+ }, [placement, isOpen, isSwipeOperating, config, dimensions]);
196
+ }
197
+
198
+ function useBackdropStyle(
199
+ placement: DrawerPlacement,
200
+ isOpen: boolean,
201
+ isSwipeOperating: boolean,
202
+ config: DrawerBehavior,
203
+ zIndex: number | undefined,
204
+ ): React.CSSProperties {
205
+ return React.useMemo((): React.CSSProperties => {
206
+ const effectiveMode = isSwipeOperating ? "none" : config.transitionMode;
207
+ const transitionValue = computeBackdropTransition(effectiveMode, config.transitionDuration);
208
+ const position = config.inline ? "absolute" as const : "fixed" as const;
209
+
210
+ return {
211
+ ...DRAWER_BACKDROP_BASE_STYLE,
212
+ position,
213
+ opacity: isOpen ? 1 : 0,
214
+ pointerEvents: isOpen ? "auto" : "none",
215
+ transition: transitionValue,
216
+ zIndex: zIndex !== undefined ? zIndex - 1 : undefined,
217
+ };
218
+ }, [isOpen, isSwipeOperating, config, zIndex]);
219
+ }
220
+
221
+ function useFrameStyle(placement: DrawerPlacement): React.CSSProperties {
222
+ return React.useMemo((): React.CSSProperties => {
223
+ const style: React.CSSProperties = { borderRadius: 0 };
224
+ if (placement === "left" || placement === "right") {
225
+ style.height = "100%";
226
+ }
227
+ return style;
228
+ }, [placement]);
229
+ }
230
+
231
+ // ============================================================================
232
+ // Main Component
233
+ // ============================================================================
234
+
153
235
  export const Drawer: React.FC<DrawerProps> = ({
154
236
  id,
155
237
  config,
156
238
  isOpen,
157
239
  onClose,
240
+ onOpen,
158
241
  children,
159
242
  zIndex,
160
243
  width,
@@ -166,106 +249,110 @@ export const Drawer: React.FC<DrawerProps> = ({
166
249
  header,
167
250
  chrome = true,
168
251
  inline = false,
169
- transitionMode = "css",
170
- transitionDuration,
171
- transitionEasing,
172
252
  } = config;
173
253
 
174
- const resolvePlacement = React.useCallback(
175
- (anchor?: DrawerBehavior["anchor"], pos?: WindowPosition): "left" | "right" | "top" | "bottom" => {
176
- // Prefer explicit anchor from config
177
- if (anchor) {
178
- return anchor;
179
- }
180
- // Fall back to inferring from position
181
- if (!pos) {
182
- return "right";
183
- }
184
- if (pos.left !== undefined) {
185
- return "left";
186
- }
187
- if (pos.right !== undefined) {
188
- return "right";
189
- }
190
- if (pos.top !== undefined) {
191
- return "top";
192
- }
193
- if (pos.bottom !== undefined) {
194
- return "bottom";
195
- }
196
- return "right";
197
- },
198
- [],
199
- );
200
-
254
+ const swipeConfig = parseSwipeGesturesConfig(config.swipeGestures);
201
255
  const placement = resolvePlacement(config.anchor, position);
202
256
 
203
- const openTransforms: Record<string, string> = {
204
- left: "translateX(0)",
205
- right: "translateX(0)",
206
- top: "translateY(0)",
207
- bottom: "translateY(0)",
208
- };
209
-
210
- const drawerStyle = React.useMemo((): React.CSSProperties => {
211
- const transitionValue = computeTransitionValue(transitionMode, transitionDuration, transitionEasing);
212
-
213
- const style: React.CSSProperties = {
214
- ...drawerBaseStyle,
215
- ...(inline ? { position: "absolute" } : { position: "fixed" }),
216
- ...drawerPlacementStyles[placement],
217
- transform: isOpen ? openTransforms[placement] : drawerPlacementStyles[placement].transform,
218
- transition: transitionValue,
219
- };
257
+ // Refs
258
+ const drawerRef = React.useRef<HTMLDivElement>(null);
259
+ const backdropRef = React.useRef<HTMLDivElement>(null);
260
+ const edgeZoneRef = React.useRef<HTMLDivElement>(null);
220
261
 
221
- if (zIndex !== undefined) {
222
- style.zIndex = zIndex;
223
- }
262
+ // Swipe callbacks
263
+ const handleSwipeOpen = React.useCallback(() => {
264
+ onOpen?.();
265
+ config.onStateChange?.(true);
266
+ }, [onOpen, config]);
224
267
 
225
- if (width !== undefined) {
226
- style.width = typeof width === "number" ? `${width}px` : width;
227
- }
228
- if (height !== undefined) {
229
- style.height = typeof height === "number" ? `${height}px` : height;
230
- }
268
+ const handleSwipeClose = React.useCallback(() => {
269
+ onClose();
270
+ }, [onClose]);
231
271
 
232
- return style;
233
- }, [height, inline, isOpen, placement, transitionDuration, transitionEasing, transitionMode, width, zIndex]);
272
+ // Swipe input handling
273
+ const {
274
+ state: swipeState,
275
+ displacement,
276
+ edgeContainerProps,
277
+ drawerContentProps,
278
+ isOpening,
279
+ isClosing,
280
+ } = useDrawerSwipeInput({
281
+ edgeContainerRef: edgeZoneRef,
282
+ drawerContentRef: drawerRef,
283
+ direction: placement as DrawerSwipeDirection,
284
+ isOpen,
285
+ onSwipeOpen: handleSwipeOpen,
286
+ onSwipeClose: handleSwipeClose,
287
+ enableEdgeSwipeOpen: swipeConfig.edgeSwipeOpen,
288
+ enableSwipeClose: swipeConfig.swipeClose,
289
+ edgeWidth: swipeConfig.edgeWidth,
290
+ dismissThreshold: swipeConfig.dismissThreshold,
291
+ });
234
292
 
235
- const ariaLabel = header?.title ?? config.ariaLabel ?? "Drawer";
293
+ const isSwipeOperating = swipeState.phase === "operating";
236
294
 
237
- const backdropStyle = React.useMemo((): React.CSSProperties => {
238
- const base = inline ? { ...drawerBackdropStyle, position: "absolute" as const } : drawerBackdropStyle;
239
- const transitionValue = transitionMode === "none" ? undefined : `opacity ${transitionDuration ?? "220ms"} ease`;
240
- return {
241
- ...base,
242
- opacity: isOpen ? 1 : 0,
243
- pointerEvents: isOpen ? "auto" : "none",
244
- transition: transitionValue,
245
- zIndex: zIndex !== undefined ? zIndex - 1 : undefined,
246
- };
247
- }, [inline, isOpen, transitionDuration, transitionMode, zIndex]);
295
+ // Apply swipe transform
296
+ useDrawerSwipeTransform({
297
+ drawerRef,
298
+ backdropRef,
299
+ placement: placement as DrawerSwipeDirection,
300
+ swipeState,
301
+ displacement,
302
+ isOpening,
303
+ isClosing,
304
+ enabled: swipeConfig.enabled,
305
+ });
248
306
 
249
- const frameStyle = React.useMemo((): React.CSSProperties => {
250
- const isVertical = placement === "left" || placement === "right";
251
- const style: React.CSSProperties = { borderRadius: 0 };
252
- if (isVertical) {
253
- style.height = "100%";
307
+ // Computed styles
308
+ const drawerStyle = useDrawerPanelStyle(placement, isOpen, isSwipeOperating, config, { width, height, zIndex });
309
+ const backdropStyle = useBackdropStyle(placement, isOpen, isSwipeOperating, config, zIndex);
310
+ const frameStyle = useFrameStyle(placement);
311
+
312
+ // Edge zone style: merge positioning style with gesture handlers' style
313
+ const edgeZoneStyle = React.useMemo((): React.CSSProperties => {
314
+ const positioningStyle = computeEdgeZoneStyle({
315
+ placement,
316
+ inline,
317
+ edgeWidth: swipeConfig.edgeWidth,
318
+ zIndex,
319
+ });
320
+ // Merge with gesture container styles (touchAction, etc.)
321
+ return { ...positioningStyle, ...edgeContainerProps.style };
322
+ }, [placement, inline, swipeConfig.edgeWidth, zIndex, edgeContainerProps.style]);
323
+
324
+ // Merged drawer style with swipe props
325
+ const mergedDrawerStyle = React.useMemo((): React.CSSProperties => {
326
+ if (!swipeConfig.enabled) {
327
+ return drawerStyle;
254
328
  }
255
- return style;
256
- }, [placement]);
329
+ return { ...drawerStyle, ...drawerContentProps.style };
330
+ }, [swipeConfig.enabled, drawerStyle, drawerContentProps.style]);
331
+
332
+ // Visibility flags
333
+ const showEdgeZone = shouldShowEdgeZone(swipeConfig, isOpen, isOpening);
334
+ const ariaLabel = header?.title ?? config.ariaLabel ?? "Drawer";
335
+
336
+ const edgeZoneElement = renderEdgeZone(showEdgeZone, edgeZoneRef, edgeZoneStyle, edgeContainerProps.onPointerDown, placement);
257
337
 
258
338
  return (
259
339
  <>
260
- <div style={backdropStyle} onClick={dismissible ? onClose : undefined} />
340
+ {edgeZoneElement}
341
+ <div
342
+ ref={backdropRef}
343
+ style={backdropStyle}
344
+ onClick={dismissible ? onClose : undefined}
345
+ />
261
346
  <div
347
+ ref={drawerRef}
262
348
  data-layer-id={id}
263
349
  data-placement={placement}
264
- style={drawerStyle}
350
+ style={mergedDrawerStyle}
265
351
  role="dialog"
266
352
  aria-modal={dismissible ? true : undefined}
267
353
  aria-hidden={isOpen ? undefined : true}
268
354
  aria-label={ariaLabel}
355
+ onPointerDown={swipeConfig.enabled ? drawerContentProps.onPointerDown : undefined}
269
356
  >
270
357
  <DrawerContent
271
358
  chrome={chrome}
@@ -2,9 +2,9 @@
2
2
  * @file DrawerLayers component
3
3
  */
4
4
  import * as React from "react";
5
- import type { LayerDefinition } from "../../types";
6
- import { Drawer } from "./Drawer";
7
- import { useDrawerState } from "../../modules/window/useDrawerState";
5
+ import type { LayerDefinition } from "../../types.js";
6
+ import { Drawer } from "./Drawer.js";
7
+ import { useDrawerState } from "../../modules/window/useDrawerState.js";
8
8
 
9
9
  export type DrawerLayersProps = {
10
10
  layers: LayerDefinition[];
@@ -23,6 +23,14 @@ export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
23
23
  return handlers;
24
24
  }, [drawerLayers, drawer.close]);
25
25
 
26
+ const openHandlers = React.useMemo(() => {
27
+ const handlers = new Map<string, () => void>();
28
+ drawerLayers.forEach((layer) => {
29
+ handlers.set(layer.id, () => drawer.open(layer.id));
30
+ });
31
+ return handlers;
32
+ }, [drawerLayers, drawer.open]);
33
+
26
34
  return (
27
35
  <>
28
36
  {drawerLayers.map((layer) => {
@@ -32,6 +40,7 @@ export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
32
40
 
33
41
  const isOpen = drawer.state(layer.id);
34
42
  const onClose = closeHandlers.get(layer.id);
43
+ const onOpen = openHandlers.get(layer.id);
35
44
 
36
45
  if (!onClose) {
37
46
  return null;
@@ -44,6 +53,7 @@ export const DrawerLayers: React.FC<DrawerLayersProps> = ({ layers }) => {
44
53
  config={layer.drawer}
45
54
  isOpen={isOpen}
46
55
  onClose={onClose}
56
+ onOpen={onOpen}
47
57
  zIndex={layer.zIndex}
48
58
  width={layer.width}
49
59
  height={layer.height}