react-panel-layout 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) 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-Bw2djgpz.js +1542 -0
  4. package/dist/FloatingWindow-Bw2djgpz.js.map +1 -0
  5. package/dist/FloatingWindow-Cvyokf0m.cjs +2 -0
  6. package/dist/FloatingWindow-Cvyokf0m.cjs.map +1 -0
  7. package/dist/GridLayout-B4aCqSyd.js +947 -0
  8. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-B4aCqSyd.js.map} +1 -1
  9. package/dist/GridLayout-DNOClFzz.cjs +2 -0
  10. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DNOClFzz.cjs.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-B8Igvnb2.cjs +3 -0
  16. package/dist/PanelSystem-B8Igvnb2.cjs.map +1 -0
  17. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-DDUSFjXD.js} +209 -248
  18. package/dist/PanelSystem-DDUSFjXD.js.map +1 -0
  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 +4 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/DrawerRevealContext.d.ts +61 -0
  31. package/dist/components/window/drawerRevealAnimationUtils.d.ts +212 -0
  32. package/dist/components/window/drawerStyles.d.ts +74 -0
  33. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  34. package/dist/components/window/useDrawerSwipeTransform.d.ts +29 -0
  35. package/dist/components/window/useDrawerTransform.d.ts +68 -0
  36. package/dist/components/window/useRevealDrawerTransform.d.ts +56 -0
  37. package/dist/config.cjs +1 -1
  38. package/dist/config.cjs.map +1 -1
  39. package/dist/config.js +9 -8
  40. package/dist/config.js.map +1 -1
  41. package/dist/constants/styles.d.ts +17 -0
  42. package/dist/dialog/index.d.ts +69 -0
  43. package/dist/floating.js +1 -1
  44. package/dist/grid.cjs +1 -1
  45. package/dist/grid.js +2 -2
  46. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  47. package/dist/hooks/gesture/types.d.ts +48 -5
  48. package/dist/hooks/gesture/utils.d.ts +19 -0
  49. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  50. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  51. package/dist/hooks/useResizeObserver.d.ts +33 -1
  52. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  53. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  54. package/dist/index.cjs +1 -1
  55. package/dist/index.js +7 -7
  56. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  57. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  58. package/dist/modules/dialog/Modal.d.ts +26 -0
  59. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  60. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  61. package/dist/modules/dialog/types.d.ts +183 -0
  62. package/dist/modules/dialog/useDialog.d.ts +39 -0
  63. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  64. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  65. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  66. package/dist/modules/drawer/drawerStateMachine.d.ts +168 -0
  67. package/dist/modules/drawer/revealDrawerConstants.d.ts +33 -0
  68. package/dist/modules/drawer/revealDrawerStateMachine.d.ts +146 -0
  69. package/dist/modules/drawer/strategies/index.d.ts +8 -0
  70. package/dist/modules/drawer/strategies/overlayStrategy.d.ts +12 -0
  71. package/dist/modules/drawer/strategies/revealStrategy.d.ts +12 -0
  72. package/dist/modules/drawer/strategies/types.d.ts +116 -0
  73. package/dist/modules/drawer/types.d.ts +74 -0
  74. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  75. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  76. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  77. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  78. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  79. package/dist/panels.cjs +1 -1
  80. package/dist/panels.js +1 -1
  81. package/dist/pivot.cjs +1 -1
  82. package/dist/pivot.js +1 -1
  83. package/dist/resizer.cjs +1 -1
  84. package/dist/resizer.js +2 -2
  85. package/dist/stack.cjs +1 -1
  86. package/dist/stack.cjs.map +1 -1
  87. package/dist/stack.js +480 -780
  88. package/dist/stack.js.map +1 -1
  89. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  90. package/dist/sticky-header.cjs +1 -1
  91. package/dist/sticky-header.cjs.map +1 -1
  92. package/dist/sticky-header.js +59 -51
  93. package/dist/sticky-header.js.map +1 -1
  94. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  95. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  96. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  97. package/dist/types.d.ts +30 -0
  98. package/dist/useAnimationFrame-BZ6D2lMq.cjs +2 -0
  99. package/dist/useAnimationFrame-BZ6D2lMq.cjs.map +1 -0
  100. package/dist/useAnimationFrame-Bg4e-H8O.js +394 -0
  101. package/dist/useAnimationFrame-Bg4e-H8O.js.map +1 -0
  102. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  103. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  104. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  105. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  106. package/dist/window/index.d.ts +2 -0
  107. package/dist/window.cjs +1 -1
  108. package/dist/window.cjs.map +1 -1
  109. package/dist/window.js +114 -103
  110. package/dist/window.js.map +1 -1
  111. package/package.json +6 -1
  112. package/src/components/gesture/SwipeSafeZone.tsx +70 -0
  113. package/src/components/grid/GridLayout.tsx +110 -38
  114. package/src/components/window/Drawer.tsx +353 -162
  115. package/src/components/window/DrawerLayers.tsx +54 -11
  116. package/src/components/window/DrawerRevealContext.spec.ts +20 -0
  117. package/src/components/window/DrawerRevealContext.tsx +99 -0
  118. package/src/components/window/drawerRevealAnimationUtils.spec.ts +375 -0
  119. package/src/components/window/drawerRevealAnimationUtils.ts +415 -0
  120. package/src/components/window/drawerStyles.spec.ts +302 -0
  121. package/src/components/window/drawerStyles.ts +252 -0
  122. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  123. package/src/components/window/drawerSwipeConfig.ts +112 -0
  124. package/src/components/window/useDrawerSwipeTransform.ts +67 -0
  125. package/src/components/window/useDrawerTransform.ts +505 -0
  126. package/src/components/window/useRevealDrawerTransform.spec.ts +1936 -0
  127. package/src/components/window/useRevealDrawerTransform.ts +105 -0
  128. package/src/constants/styles.ts +19 -0
  129. package/src/demo/components/FullscreenDemoPage.tsx +47 -0
  130. package/src/demo/fullscreenRoutes.tsx +32 -0
  131. package/src/demo/index.tsx +5 -0
  132. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  133. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  134. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  135. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  136. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +219 -0
  137. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  138. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  139. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  140. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  141. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  142. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  143. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  144. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  145. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +6 -1
  146. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +14 -4
  147. package/src/demo/pages/Drawer/components/DrawerReveal.module.css +157 -0
  148. package/src/demo/pages/Drawer/components/DrawerReveal.tsx +128 -0
  149. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  150. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  151. package/src/demo/pages/Drawer/reveal/index.tsx +17 -0
  152. package/src/demo/pages/Drawer/reveal-fullscreen/index.tsx +135 -0
  153. package/src/demo/pages/Drawer/reveal-fullscreen/styles.module.css +233 -0
  154. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  155. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  156. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  157. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +156 -0
  158. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  159. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +110 -0
  160. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  161. package/src/demo/routes.tsx +24 -1
  162. package/src/dialog/index.ts +85 -0
  163. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  164. package/src/hooks/gesture/testing/createGestureSimulator.ts +113 -37
  165. package/src/hooks/gesture/types.ts +83 -6
  166. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  167. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +99 -31
  168. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  169. package/src/hooks/gesture/utils.ts +102 -0
  170. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  171. package/src/hooks/useAnimatedVisibility.ts +28 -2
  172. package/src/hooks/useAnimationFrame.ts +8 -0
  173. package/src/hooks/useOperationContinuity.spec.ts +394 -0
  174. package/src/hooks/useOperationContinuity.ts +135 -0
  175. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  176. package/src/hooks/useResizeObserver.tsx +108 -39
  177. package/src/hooks/useScrollContainer.ts +4 -10
  178. package/src/hooks/useSharedElementTransition.ts +354 -0
  179. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  180. package/src/hooks/useSwipeContentTransform.ts +166 -28
  181. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  182. package/src/modules/dialog/AlertDialog.tsx +221 -0
  183. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  184. package/src/modules/dialog/DialogContainer.tsx +188 -0
  185. package/src/modules/dialog/Modal.spec.tsx +220 -0
  186. package/src/modules/dialog/Modal.tsx +182 -0
  187. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  188. package/src/modules/dialog/dialogAnimationUtils.spec.ts +252 -0
  189. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  190. package/src/modules/dialog/types.ts +186 -0
  191. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  192. package/src/modules/dialog/useDialog.ts +214 -0
  193. package/src/modules/dialog/useDialogContainer.spec.ts +339 -0
  194. package/src/modules/dialog/useDialogContainer.ts +150 -0
  195. package/src/modules/dialog/useDialogSwipeInput.spec.ts +178 -0
  196. package/src/modules/dialog/useDialogSwipeInput.ts +350 -0
  197. package/src/modules/dialog/useDialogTransform.spec.ts +403 -0
  198. package/src/modules/dialog/useDialogTransform.ts +407 -0
  199. package/src/modules/drawer/drawerStateMachine.ts +500 -0
  200. package/src/modules/drawer/revealDrawerConstants.ts +38 -0
  201. package/src/modules/drawer/revealDrawerStateMachine.spec.ts +558 -0
  202. package/src/modules/drawer/revealDrawerStateMachine.ts +197 -0
  203. package/src/modules/drawer/strategies/index.ts +9 -0
  204. package/src/modules/drawer/strategies/overlayStrategy.ts +133 -0
  205. package/src/modules/drawer/strategies/revealStrategy.ts +111 -0
  206. package/src/modules/drawer/strategies/types.ts +160 -0
  207. package/src/modules/drawer/types.ts +102 -0
  208. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  209. package/src/modules/drawer/useDrawerSwipeInput.ts +402 -0
  210. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  211. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  212. package/src/modules/pivot/SwipePivotContent.spec.tsx +66 -25
  213. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  214. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  215. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  216. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  217. package/src/modules/pivot/usePivot.spec.ts +17 -3
  218. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  219. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  220. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  221. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  222. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  223. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  224. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  225. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  226. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  227. package/src/modules/stack/useStackAnimationState.ts +18 -13
  228. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  229. package/src/modules/stack/useStackNavigation.tsx +113 -56
  230. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  231. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  232. package/src/sticky-header/StickyArea.tsx +29 -57
  233. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  234. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  235. package/src/types.ts +33 -0
  236. package/src/window/index.ts +2 -0
  237. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  238. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  239. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  240. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  241. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  242. package/dist/GridLayout-BltqeCPK.js +0 -927
  243. package/dist/PanelSystem-Bs8bQwQF.cjs +0 -3
  244. package/dist/PanelSystem-Bs8bQwQF.cjs.map +0 -1
  245. package/dist/PanelSystem-Dr1TBhxM.js.map +0 -1
  246. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  247. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  248. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  249. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  250. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  251. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  252. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  253. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  254. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  255. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  256. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  257. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  258. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -0,0 +1,505 @@
1
+ /**
2
+ * @file Unified hook for drawer transform animations.
3
+ *
4
+ * This hook provides a single interface for both reveal and overlay drawer modes.
5
+ * It uses a strategy pattern internally to handle mode-specific position calculations
6
+ * and DOM operations while sharing the common state machine logic.
7
+ */
8
+ import * as React from "react";
9
+ import { useAnimationFrame, easings } from "../../hooks/useAnimationFrame.js";
10
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
11
+ import type { DrawerPlacement } from "./drawerStyles.js";
12
+ import {
13
+ createDrawerReducer,
14
+ createDrawerInitialState,
15
+ drawerActions,
16
+ type DrawerPhase,
17
+ type DrawerAction,
18
+ } from "../../modules/drawer/drawerStateMachine.js";
19
+ import {
20
+ revealStrategy,
21
+ overlayStrategy,
22
+ type DrawerStrategyConfig,
23
+ type DrawerElements,
24
+ type DrawerTransformStrategy,
25
+ type RevealPosition,
26
+ type OverlayPosition,
27
+ } from "../../modules/drawer/strategies/index.js";
28
+
29
+ /**
30
+ * Union type for drawer positions (reveal or overlay mode).
31
+ */
32
+ type DrawerPosition = RevealPosition | OverlayPosition;
33
+
34
+ /**
35
+ * Strategy type that works with either position type.
36
+ */
37
+ type AnyDrawerStrategy = DrawerTransformStrategy<DrawerPosition>;
38
+ import { REVEAL_DRAWER_ANIMATION_DURATION } from "../../modules/drawer/revealDrawerConstants.js";
39
+ import {
40
+ applyOverflowHidden,
41
+ clearOverflowHidden,
42
+ getContentElement,
43
+ clearDrawerVisibility,
44
+ } from "./drawerRevealAnimationUtils.js";
45
+
46
+ // ============================================================================
47
+ // Types
48
+ // ============================================================================
49
+
50
+ /**
51
+ * Drawer animation mode.
52
+ */
53
+ export type DrawerMode = "reveal" | "overlay";
54
+
55
+ /**
56
+ * Phase of drawer animation lifecycle.
57
+ */
58
+ export type DrawerAnimationPhase = "idle" | "opening" | "open" | "closing" | "closed";
59
+
60
+ /**
61
+ * Options for useDrawerTransform hook.
62
+ */
63
+ export type UseDrawerTransformOptions = {
64
+ /** Animation mode */
65
+ mode: DrawerMode;
66
+ /** Ref to the drawer element */
67
+ drawerRef: React.RefObject<HTMLElement | null>;
68
+ /** Ref to the content element (reveal mode) */
69
+ contentRef?: React.RefObject<HTMLElement | null>;
70
+ /** Ref to the backdrop element (overlay mode) */
71
+ backdropRef?: React.RefObject<HTMLElement | null>;
72
+ /** Drawer placement */
73
+ placement: DrawerPlacement;
74
+ /** Drawer size in pixels */
75
+ drawerSize: number;
76
+ /** Whether the drawer is open */
77
+ isOpen: boolean;
78
+ /** Current swipe state */
79
+ swipeState: ContinuousOperationState;
80
+ /** Current swipe displacement */
81
+ displacement: number;
82
+ /** Whether opening via swipe */
83
+ isOpening: boolean;
84
+ /** Whether closing via swipe */
85
+ isClosing: boolean;
86
+ /** Whether this hook is enabled */
87
+ enabled: boolean;
88
+ /** Whether drawer is inline (affects content element for reveal mode) */
89
+ inline?: boolean;
90
+ /** Grid container ref for inline mode (reveal mode) */
91
+ gridRef?: React.RefObject<HTMLElement | null>;
92
+ /** Content background color (reveal mode) */
93
+ contentBackground?: string;
94
+ };
95
+
96
+ /**
97
+ * Result from useDrawerTransform hook.
98
+ */
99
+ export type UseDrawerTransformResult = {
100
+ /** Current animation phase */
101
+ phase: DrawerAnimationPhase;
102
+ /** Whether any animation is running */
103
+ isAnimating: boolean;
104
+ };
105
+
106
+ // ============================================================================
107
+ // Implementation
108
+ // ============================================================================
109
+
110
+ /**
111
+ * Select strategy based on drawer mode.
112
+ */
113
+ function selectStrategy(mode: DrawerMode): AnyDrawerStrategy {
114
+ if (mode === "reveal") {
115
+ return revealStrategy as AnyDrawerStrategy;
116
+ }
117
+ return overlayStrategy as AnyDrawerStrategy;
118
+ }
119
+
120
+ /**
121
+ * Convert state machine phase to animation phase.
122
+ */
123
+ function toAnimationPhase(phase: DrawerPhase): DrawerAnimationPhase {
124
+ return phase;
125
+ }
126
+
127
+ /**
128
+ * Unified hook for drawer transform animations.
129
+ *
130
+ * Supports both reveal and overlay modes through a strategy pattern.
131
+ */
132
+ export function useDrawerTransform(
133
+ options: UseDrawerTransformOptions,
134
+ ): UseDrawerTransformResult {
135
+ const {
136
+ mode,
137
+ drawerRef,
138
+ contentRef,
139
+ backdropRef,
140
+ placement,
141
+ drawerSize,
142
+ isOpen,
143
+ swipeState,
144
+ displacement,
145
+ isOpening,
146
+ isClosing,
147
+ enabled,
148
+ inline = false,
149
+ gridRef,
150
+ contentBackground = "#fff",
151
+ } = options;
152
+
153
+ const isOperating = swipeState.phase === "operating";
154
+
155
+ // Select strategy based on mode (cast to AnyDrawerStrategy for generic state machine)
156
+ const strategy: AnyDrawerStrategy = selectStrategy(mode);
157
+
158
+ // Config for strategy
159
+ const config: DrawerStrategyConfig = React.useMemo(
160
+ () => ({ placement, drawerSize, contentBackground }),
161
+ [placement, drawerSize, contentBackground],
162
+ );
163
+
164
+ // Create reducer for the selected strategy
165
+ const reducer = React.useMemo(
166
+ () => createDrawerReducer(strategy),
167
+ [strategy],
168
+ );
169
+
170
+ // State machine state
171
+ const stateRef = React.useRef(
172
+ createDrawerInitialState(isOpen, strategy, config),
173
+ );
174
+
175
+ // React state for phase (for external API)
176
+ const [phase, setPhase] = React.useState<DrawerAnimationPhase>(
177
+ isOpen ? "open" : "closed",
178
+ );
179
+
180
+ // Track previous values for detecting transitions
181
+ // Note: enabled starts as false to ensure initialization runs on first mount.
182
+ // This is critical for React Strict Mode compatibility where effects run twice.
183
+ const prevRef = React.useRef({
184
+ isOperating: false,
185
+ isOpening: false,
186
+ isClosing: false,
187
+ isOpen,
188
+ enabled: false,
189
+ });
190
+
191
+ // Resolve elements based on mode
192
+ const resolveContentElement = (): HTMLElement | null => {
193
+ if (mode !== "reveal") {
194
+ return null;
195
+ }
196
+ return contentRef?.current ?? getContentElement(inline, gridRef);
197
+ };
198
+
199
+ const resolveBackdropElement = (): HTMLElement | null => {
200
+ if (mode !== "overlay") {
201
+ return null;
202
+ }
203
+ return backdropRef?.current ?? null;
204
+ };
205
+
206
+ const getElements = React.useCallback((): DrawerElements => {
207
+ return {
208
+ drawer: drawerRef.current,
209
+ content: resolveContentElement(),
210
+ backdrop: resolveBackdropElement(),
211
+ };
212
+ }, [mode, drawerRef, contentRef, backdropRef, inline, gridRef]);
213
+
214
+ // Dispatch helper
215
+ const dispatch = React.useCallback(
216
+ (action: DrawerAction) => {
217
+ const newState = reducer(stateRef.current, action, config);
218
+ stateRef.current = newState;
219
+ setPhase(toAnimationPhase(newState.phase));
220
+ return newState;
221
+ },
222
+ [reducer, config],
223
+ );
224
+
225
+ // Animation frame handler
226
+ const handleFrame = React.useCallback(
227
+ ({ easedProgress }: { easedProgress: number }) => {
228
+ const state = stateRef.current;
229
+ if (!state.animation) {
230
+ return;
231
+ }
232
+
233
+ const position = strategy.interpolatePosition(
234
+ state.animation.from,
235
+ state.animation.to,
236
+ easedProgress,
237
+ );
238
+
239
+ stateRef.current = { ...state, position };
240
+
241
+ const elements = getElements();
242
+ strategy.applyToDOM(position, elements, config);
243
+ },
244
+ [strategy, getElements, config],
245
+ );
246
+
247
+ // Animation complete handler
248
+ const handleComplete = React.useCallback(() => {
249
+ const newState = dispatch(drawerActions.animationComplete());
250
+ const elements = getElements();
251
+
252
+ if (newState.phase === "closed") {
253
+ strategy.onClosingComplete?.(elements, config);
254
+ }
255
+ }, [dispatch, strategy, getElements, config]);
256
+
257
+ const { isAnimating, start, cancel } = useAnimationFrame({
258
+ duration: REVEAL_DRAWER_ANIMATION_DURATION,
259
+ easing: easings.easeOutExpo,
260
+ onFrame: handleFrame,
261
+ onComplete: handleComplete,
262
+ });
263
+
264
+ // Initialize drawer state on mount/enable
265
+ React.useLayoutEffect(() => {
266
+ if (!enabled) {
267
+ prevRef.current.enabled = false;
268
+ return;
269
+ }
270
+
271
+ if (prevRef.current.enabled) {
272
+ return;
273
+ }
274
+ prevRef.current.enabled = true;
275
+
276
+ // Re-initialize state machine
277
+ stateRef.current = createDrawerInitialState(isOpen, strategy, config);
278
+ setPhase(isOpen ? "open" : "closed");
279
+
280
+ const elements = getElements();
281
+
282
+ if (isOpen) {
283
+ strategy.onOpeningStart?.(elements, config);
284
+ strategy.applyToDOM(stateRef.current.position, elements, config);
285
+ } else {
286
+ strategy.onClosingComplete?.(elements, config);
287
+ }
288
+ }, [enabled, isOpen, strategy, config, getElements]);
289
+
290
+ // Handle swipe start/end
291
+ React.useLayoutEffect(() => {
292
+ if (!enabled) {
293
+ return;
294
+ }
295
+
296
+ const prev = prevRef.current;
297
+ const wasOperating = prev.isOperating;
298
+ const wasOpening = prev.isOpening;
299
+ const wasClosing = prev.isClosing;
300
+
301
+ prev.isOperating = isOperating;
302
+ prev.isOpening = isOpening;
303
+ prev.isClosing = isClosing;
304
+
305
+ // Swipe start
306
+ if (!wasOperating && isOperating) {
307
+ cancel();
308
+ const direction = isOpening ? "opening" : isClosing ? "closing" : null;
309
+ if (direction) {
310
+ dispatch(drawerActions.swipeStart(direction));
311
+ }
312
+ return;
313
+ }
314
+
315
+ // Swipe end
316
+ if (wasOperating && !isOperating) {
317
+ const newState = dispatch(drawerActions.swipeEnd());
318
+ const elements = getElements();
319
+
320
+ if (newState.animation) {
321
+ strategy.onOpeningStart?.(elements, config);
322
+ start();
323
+ } else {
324
+ if (newState.phase === "open") {
325
+ strategy.onOpeningStart?.(elements, config);
326
+ strategy.applyToDOM(newState.position, elements, config);
327
+ } else if (newState.phase === "closed") {
328
+ strategy.onClosingComplete?.(elements, config);
329
+ }
330
+ }
331
+ return;
332
+ }
333
+
334
+ // Direction change during operation
335
+ if (isOperating) {
336
+ const currentDirection = isOpening ? "opening" : isClosing ? "closing" : null;
337
+ const prevDirection = wasOpening ? "opening" : wasClosing ? "closing" : null;
338
+
339
+ if (currentDirection !== prevDirection && currentDirection) {
340
+ dispatch(drawerActions.swipeStart(currentDirection));
341
+ }
342
+ }
343
+ }, [
344
+ enabled,
345
+ isOperating,
346
+ isOpening,
347
+ isClosing,
348
+ dispatch,
349
+ cancel,
350
+ start,
351
+ strategy,
352
+ config,
353
+ getElements,
354
+ ]);
355
+
356
+ // Handle displacement updates during swipe
357
+ React.useLayoutEffect(() => {
358
+ if (!enabled) {
359
+ return;
360
+ }
361
+ if (!isOperating) {
362
+ return;
363
+ }
364
+ if (!isOpening && !isClosing) {
365
+ return;
366
+ }
367
+
368
+ const newState = dispatch(drawerActions.swipeUpdate(displacement));
369
+ const elements = getElements();
370
+
371
+ strategy.onOpeningStart?.(elements, config);
372
+ strategy.applyToDOM(newState.position, elements, config);
373
+ }, [
374
+ enabled,
375
+ isOperating,
376
+ isOpening,
377
+ isClosing,
378
+ displacement,
379
+ dispatch,
380
+ strategy,
381
+ config,
382
+ getElements,
383
+ ]);
384
+
385
+ // Handle external isOpen prop changes
386
+ React.useLayoutEffect(() => {
387
+ if (!enabled) {
388
+ return;
389
+ }
390
+
391
+ const prev = prevRef.current;
392
+ if (prev.isOpen === isOpen) {
393
+ return;
394
+ }
395
+ prev.isOpen = isOpen;
396
+
397
+ if (isOperating) {
398
+ dispatch(drawerActions.syncOpenState(isOpen));
399
+ return;
400
+ }
401
+
402
+ if (isAnimating) {
403
+ const currentTarget = stateRef.current.animation?.type === "opening";
404
+ if (currentTarget === isOpen) {
405
+ return;
406
+ }
407
+ }
408
+
409
+ const newState = dispatch(drawerActions.syncOpenState(isOpen));
410
+ const elements = getElements();
411
+
412
+ if (newState.animation) {
413
+ strategy.onOpeningStart?.(elements, config);
414
+ strategy.applyToDOM(newState.animation.from, elements, config);
415
+ start();
416
+ } else if (!isAnimating) {
417
+ if (newState.phase === "open") {
418
+ strategy.onOpeningStart?.(elements, config);
419
+ strategy.applyToDOM(newState.position, elements, config);
420
+ } else if (newState.phase === "closed") {
421
+ strategy.onClosingComplete?.(elements, config);
422
+ }
423
+ }
424
+ }, [
425
+ enabled,
426
+ isOpen,
427
+ isOperating,
428
+ isAnimating,
429
+ dispatch,
430
+ start,
431
+ strategy,
432
+ config,
433
+ getElements,
434
+ ]);
435
+
436
+ // Cleanup on disable
437
+ React.useLayoutEffect(() => {
438
+ if (enabled) {
439
+ return;
440
+ }
441
+ if (!prevRef.current.enabled) {
442
+ return;
443
+ }
444
+
445
+ const elements = getElements();
446
+ strategy.clearFromDOM(elements, config);
447
+
448
+ if (elements.drawer) {
449
+ clearDrawerVisibility(elements.drawer);
450
+ }
451
+ }, [enabled, strategy, config, getElements]);
452
+
453
+ // Effect A: Manage body overflow based on drawer state (reveal mode only)
454
+ //
455
+ // This effect runs frequently as isOpen/isOperating/isAnimating change.
456
+ // Cleanup only clears overflow - safe to call repeatedly.
457
+ React.useLayoutEffect(() => {
458
+ if (!enabled || mode !== "reveal") {
459
+ if (mode === "reveal") {
460
+ clearOverflowHidden();
461
+ }
462
+ return;
463
+ }
464
+
465
+ const isActive = isOpen ? true : isOperating ? true : isAnimating;
466
+
467
+ if (isActive) {
468
+ applyOverflowHidden();
469
+ } else {
470
+ clearOverflowHidden();
471
+ }
472
+
473
+ return () => {
474
+ clearOverflowHidden();
475
+ };
476
+ }, [enabled, mode, isOpen, isOperating, isAnimating]);
477
+
478
+ // Effect B: Clear content element styles on unmount (reveal mode only)
479
+ //
480
+ // This effect has minimal dependencies (enabled, mode) so cleanup only runs:
481
+ // - When enabled changes to false (handled by "Cleanup on disable" effect above)
482
+ // - When mode changes (rare)
483
+ // - On unmount (the main case we care about)
484
+ //
485
+ // Content styles are applied by strategy.applyToDOM() during swipe/animation.
486
+ // Content styles are cleared by strategy.onClosingComplete() during NORMAL close.
487
+ // This effect handles UNMOUNT where onClosingComplete never runs.
488
+ React.useLayoutEffect(() => {
489
+ if (!enabled || mode !== "reveal") {
490
+ return;
491
+ }
492
+
493
+ return () => {
494
+ const elements = getElements();
495
+ if (elements.content) {
496
+ strategy.clearFromDOM({ drawer: null, content: elements.content, backdrop: null }, config);
497
+ }
498
+ };
499
+ }, [enabled, mode, strategy, config, getElements]);
500
+
501
+ return {
502
+ phase,
503
+ isAnimating,
504
+ };
505
+ }