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,500 @@
1
+ /**
2
+ * @file Generic state machine for drawer animations.
3
+ *
4
+ * This module provides a React-independent state machine for managing drawer
5
+ * transitions. It handles swipe gestures, button operations, and animations through
6
+ * a reducer pattern with typed actions.
7
+ *
8
+ * The position type is generic, allowing different drawer modes (reveal, overlay)
9
+ * to use the same state machine logic with mode-specific position calculations.
10
+ *
11
+ * Key design principles:
12
+ * - Pure functions: same input → same output
13
+ * - No React/DOM dependencies
14
+ * - Testable in isolation
15
+ * - Deterministic state transitions
16
+ */
17
+
18
+ import { createAction, createActionHandlerMap } from "../../utils/typedActions.js";
19
+ import type { DrawerTransformStrategy, DrawerStrategyConfig } from "./strategies/types.js";
20
+ import {
21
+ REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD,
22
+ REVEAL_DRAWER_DISMISS_RATIO,
23
+ } from "./revealDrawerConstants.js";
24
+
25
+ // ============================================================================
26
+ // Types
27
+ // ============================================================================
28
+
29
+ /**
30
+ * Phase of drawer animation lifecycle.
31
+ * - closed: Drawer is hidden
32
+ * - opening: Drawer is animating open
33
+ * - open: Drawer is fully visible
34
+ * - closing: Drawer is animating closed
35
+ */
36
+ export type DrawerPhase = "closed" | "opening" | "open" | "closing";
37
+
38
+ /**
39
+ * Direction of swipe gesture.
40
+ */
41
+ export type SwipeDirection = "opening" | "closing" | null;
42
+
43
+ /**
44
+ * Animation state tracking from/to positions.
45
+ */
46
+ export type AnimationState<TPosition> = {
47
+ /** Animation type */
48
+ type: "opening" | "closing";
49
+ /** Starting position */
50
+ from: TPosition;
51
+ /** Target position */
52
+ to: TPosition;
53
+ };
54
+
55
+ /**
56
+ * Complete state for drawer.
57
+ */
58
+ export type DrawerState<TPosition> = {
59
+ /** Current animation phase */
60
+ phase: DrawerPhase;
61
+ /** Target open state (what the drawer should be after current operation) */
62
+ targetOpen: boolean;
63
+ /** Current position */
64
+ position: TPosition;
65
+ /** Whether a swipe operation is active */
66
+ isOperating: boolean;
67
+ /** Direction of current swipe */
68
+ swipeDirection: SwipeDirection;
69
+ /** Current swipe displacement in pixels */
70
+ displacement: number;
71
+ /** Animation state if animating */
72
+ animation: AnimationState<TPosition> | null;
73
+ };
74
+
75
+ // ============================================================================
76
+ // Pure Computation Functions
77
+ // ============================================================================
78
+
79
+ /**
80
+ * Compute progress (0-1) from displacement.
81
+ *
82
+ * @param displacement - Current swipe displacement in pixels
83
+ * @param drawerSize - Size of the drawer in pixels
84
+ * @param direction - Swipe direction ("opening" or "closing")
85
+ * @returns Progress value between 0 and 1
86
+ */
87
+ export function computeProgressFromDisplacement(
88
+ displacement: number,
89
+ drawerSize: number,
90
+ direction: SwipeDirection,
91
+ ): number {
92
+ if (drawerSize <= 0 || direction === null) {
93
+ return 0;
94
+ }
95
+
96
+ const ratio = Math.min(1, Math.max(0, displacement / drawerSize));
97
+
98
+ if (direction === "opening") {
99
+ return ratio;
100
+ }
101
+ return 1 - ratio;
102
+ }
103
+
104
+ /**
105
+ * Determine if drawer should open or close after swipe ends.
106
+ *
107
+ * @param displacement - Final swipe displacement in pixels
108
+ * @param drawerSize - Size of the drawer in pixels
109
+ * @param direction - Swipe direction
110
+ * @returns true if drawer should be open, false if closed
111
+ */
112
+ export function computeSwipeEndTarget(
113
+ displacement: number,
114
+ drawerSize: number,
115
+ direction: SwipeDirection,
116
+ ): boolean {
117
+ if (direction === "opening") {
118
+ return displacement >= REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD;
119
+ }
120
+ if (direction === "closing") {
121
+ const ratio = drawerSize > 0 ? displacement / drawerSize : 0;
122
+ return ratio < REVEAL_DRAWER_DISMISS_RATIO;
123
+ }
124
+ return false;
125
+ }
126
+
127
+ // ============================================================================
128
+ // Actions
129
+ // ============================================================================
130
+
131
+ /**
132
+ * Action creators for drawer state machine.
133
+ */
134
+ export const drawerActions = {
135
+ /** Start swipe operation */
136
+ swipeStart: createAction(
137
+ "SWIPE_START",
138
+ (direction: "opening" | "closing") => ({ direction }),
139
+ ),
140
+
141
+ /** Update swipe displacement */
142
+ swipeUpdate: createAction(
143
+ "SWIPE_UPDATE",
144
+ (displacement: number) => ({ displacement }),
145
+ ),
146
+
147
+ /** End swipe operation */
148
+ swipeEnd: createAction("SWIPE_END"),
149
+
150
+ /** Animation frame update */
151
+ animationFrame: createAction(
152
+ "ANIMATION_FRAME",
153
+ (easedProgress: number) => ({ easedProgress }),
154
+ ),
155
+
156
+ /** Animation complete */
157
+ animationComplete: createAction("ANIMATION_COMPLETE"),
158
+
159
+ /** Button open */
160
+ buttonOpen: createAction("BUTTON_OPEN"),
161
+
162
+ /** Button close */
163
+ buttonClose: createAction("BUTTON_CLOSE"),
164
+
165
+ /** Sync open state from external prop */
166
+ syncOpenState: createAction(
167
+ "SYNC_OPEN_STATE",
168
+ (isOpen: boolean) => ({ isOpen }),
169
+ ),
170
+
171
+ /** Initialize state */
172
+ initialize: createAction(
173
+ "INITIALIZE",
174
+ (isOpen: boolean) => ({ isOpen }),
175
+ ),
176
+ } as const;
177
+
178
+ export type DrawerAction = ReturnType<
179
+ (typeof drawerActions)[keyof typeof drawerActions]
180
+ >;
181
+
182
+ // ============================================================================
183
+ // Initial State Factory
184
+ // ============================================================================
185
+
186
+ /**
187
+ * Get target position based on open state.
188
+ */
189
+ function getTargetPositionFromOpenState<TPosition>(
190
+ isOpen: boolean,
191
+ strategy: DrawerTransformStrategy<TPosition>,
192
+ config: DrawerStrategyConfig,
193
+ ): TPosition {
194
+ if (isOpen) {
195
+ return strategy.getOpenPosition(config);
196
+ }
197
+ return strategy.getClosedPosition(config);
198
+ }
199
+
200
+ /**
201
+ * Create initial state for drawer.
202
+ *
203
+ * @param isOpen - Initial open state
204
+ * @param strategy - Transform strategy
205
+ * @param config - Drawer configuration
206
+ * @returns Initial state
207
+ */
208
+ export function createDrawerInitialState<TPosition>(
209
+ isOpen: boolean,
210
+ strategy: DrawerTransformStrategy<TPosition>,
211
+ config: DrawerStrategyConfig,
212
+ ): DrawerState<TPosition> {
213
+ const position = getTargetPositionFromOpenState(isOpen, strategy, config);
214
+
215
+ return {
216
+ phase: isOpen ? "open" : "closed",
217
+ targetOpen: isOpen,
218
+ position,
219
+ isOperating: false,
220
+ swipeDirection: null,
221
+ displacement: 0,
222
+ animation: null,
223
+ };
224
+ }
225
+
226
+ // ============================================================================
227
+ // Reducer Factory
228
+ // ============================================================================
229
+
230
+ /**
231
+ * Create a drawer reducer for a specific strategy.
232
+ *
233
+ * @param strategy - Transform strategy to use for position calculations
234
+ * @returns Reducer function for the drawer state machine
235
+ */
236
+ export function createDrawerReducer<TPosition>(
237
+ strategy: DrawerTransformStrategy<TPosition>,
238
+ ) {
239
+ type State = DrawerState<TPosition>;
240
+ type ReducerHandler = (
241
+ state: State,
242
+ action: DrawerAction,
243
+ config: DrawerStrategyConfig,
244
+ ) => State;
245
+
246
+ const handlers = createActionHandlerMap<State, typeof drawerActions, DrawerStrategyConfig>(
247
+ drawerActions,
248
+ {
249
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires config
250
+ swipeStart: (state, action, _config) => {
251
+ if (!("payload" in action)) {return state;}
252
+ return {
253
+ ...state,
254
+ isOperating: true,
255
+ swipeDirection: action.payload.direction,
256
+ displacement: 0,
257
+ animation: null,
258
+ };
259
+ },
260
+
261
+ swipeUpdate: (state, action, config) => {
262
+ if (!("payload" in action)) {return state;}
263
+ if (!state.isOperating) {return state;}
264
+
265
+ const progress = computeProgressFromDisplacement(
266
+ action.payload.displacement,
267
+ config.drawerSize,
268
+ state.swipeDirection,
269
+ );
270
+ const position = strategy.computePositionFromProgress(progress, config);
271
+
272
+ return {
273
+ ...state,
274
+ displacement: action.payload.displacement,
275
+ position,
276
+ };
277
+ },
278
+
279
+ swipeEnd: (state, _action, config) => {
280
+ if (!state.isOperating) {return state;}
281
+
282
+ const targetOpen = computeSwipeEndTarget(
283
+ state.displacement,
284
+ config.drawerSize,
285
+ state.swipeDirection,
286
+ );
287
+ const targetPosition = getTargetPositionFromOpenState(
288
+ targetOpen,
289
+ strategy,
290
+ config
291
+ );
292
+
293
+ if (strategy.shouldAnimate(state.position, targetPosition)) {
294
+ const animType = targetOpen ? "opening" : "closing";
295
+ return {
296
+ ...state,
297
+ isOperating: false,
298
+ swipeDirection: null,
299
+ displacement: 0,
300
+ targetOpen,
301
+ phase: animType,
302
+ animation: {
303
+ type: animType,
304
+ from: state.position,
305
+ to: targetPosition,
306
+ },
307
+ };
308
+ }
309
+
310
+ return {
311
+ ...state,
312
+ isOperating: false,
313
+ swipeDirection: null,
314
+ displacement: 0,
315
+ targetOpen,
316
+ phase: targetOpen ? "open" : "closed",
317
+ position: targetPosition,
318
+ animation: null,
319
+ };
320
+ },
321
+
322
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires config
323
+ animationFrame: (state, action, _config) => {
324
+ if (!("payload" in action)) {return state;}
325
+ if (!state.animation) {return state;}
326
+
327
+ const position = strategy.interpolatePosition(
328
+ state.animation.from,
329
+ state.animation.to,
330
+ action.payload.easedProgress,
331
+ );
332
+
333
+ return {
334
+ ...state,
335
+ position,
336
+ };
337
+ },
338
+
339
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars -- callback signature requires action and config
340
+ animationComplete: (state, _action, _config) => {
341
+ if (!state.animation) {return state;}
342
+
343
+ const isOpen = state.animation.type === "opening";
344
+ return {
345
+ ...state,
346
+ phase: isOpen ? "open" : "closed",
347
+ position: state.animation.to,
348
+ animation: null,
349
+ };
350
+ },
351
+
352
+ buttonOpen: (state, _action, config) => {
353
+ if (state.phase === "open" || state.phase === "opening") {
354
+ return state;
355
+ }
356
+ if (state.isOperating) {
357
+ return state;
358
+ }
359
+
360
+ const targetPosition = strategy.getOpenPosition(config);
361
+ const fromPosition = state.position;
362
+
363
+ if (strategy.shouldAnimate(fromPosition, targetPosition)) {
364
+ return {
365
+ ...state,
366
+ targetOpen: true,
367
+ phase: "opening",
368
+ animation: {
369
+ type: "opening",
370
+ from: fromPosition,
371
+ to: targetPosition,
372
+ },
373
+ };
374
+ }
375
+
376
+ return {
377
+ ...state,
378
+ targetOpen: true,
379
+ phase: "open",
380
+ position: targetPosition,
381
+ animation: null,
382
+ };
383
+ },
384
+
385
+ buttonClose: (state, _action, config) => {
386
+ if (state.phase === "closed" || state.phase === "closing") {
387
+ return state;
388
+ }
389
+ if (state.isOperating) {
390
+ return state;
391
+ }
392
+
393
+ const targetPosition = strategy.getClosedPosition(config);
394
+ const fromPosition = state.position;
395
+
396
+ if (strategy.shouldAnimate(fromPosition, targetPosition)) {
397
+ return {
398
+ ...state,
399
+ targetOpen: false,
400
+ phase: "closing",
401
+ animation: {
402
+ type: "closing",
403
+ from: fromPosition,
404
+ to: targetPosition,
405
+ },
406
+ };
407
+ }
408
+
409
+ return {
410
+ ...state,
411
+ targetOpen: false,
412
+ phase: "closed",
413
+ position: targetPosition,
414
+ animation: null,
415
+ };
416
+ },
417
+
418
+ syncOpenState: (state, action, config) => {
419
+ if (!("payload" in action)) {return state;}
420
+
421
+ const { isOpen } = action.payload;
422
+
423
+ if (state.targetOpen === isOpen && !state.isOperating && !state.animation) {
424
+ return state;
425
+ }
426
+
427
+ if (state.isOperating) {
428
+ return {
429
+ ...state,
430
+ targetOpen: isOpen,
431
+ };
432
+ }
433
+
434
+ if (state.animation) {
435
+ const animTarget = state.animation.type === "opening";
436
+ if (animTarget === isOpen) {
437
+ return {
438
+ ...state,
439
+ targetOpen: isOpen,
440
+ };
441
+ }
442
+ }
443
+
444
+ const currentlyOpen = state.phase === "open" || state.phase === "opening";
445
+ if (currentlyOpen === isOpen) {
446
+ return {
447
+ ...state,
448
+ targetOpen: isOpen,
449
+ };
450
+ }
451
+
452
+ const targetPosition = getTargetPositionFromOpenState(
453
+ isOpen,
454
+ strategy,
455
+ config
456
+ );
457
+ const fromPosition = state.position;
458
+
459
+ if (strategy.shouldAnimate(fromPosition, targetPosition)) {
460
+ const animType = isOpen ? "opening" : "closing";
461
+ return {
462
+ ...state,
463
+ targetOpen: isOpen,
464
+ phase: animType,
465
+ animation: {
466
+ type: animType,
467
+ from: fromPosition,
468
+ to: targetPosition,
469
+ },
470
+ };
471
+ }
472
+
473
+ return {
474
+ ...state,
475
+ targetOpen: isOpen,
476
+ phase: isOpen ? "open" : "closed",
477
+ position: targetPosition,
478
+ animation: null,
479
+ };
480
+ },
481
+
482
+ initialize: (_state, action, config) => {
483
+ if (!("payload" in action)) {return _state;}
484
+ return createDrawerInitialState(action.payload.isOpen, strategy, config);
485
+ },
486
+ },
487
+ );
488
+
489
+ return function drawerReducer(
490
+ state: State,
491
+ action: DrawerAction,
492
+ config: DrawerStrategyConfig,
493
+ ): State {
494
+ const handler = handlers[action.type] as ReducerHandler | undefined;
495
+ if (handler) {
496
+ return handler(state, action, config);
497
+ }
498
+ return state;
499
+ };
500
+ }
@@ -0,0 +1,38 @@
1
+ /**
2
+ * @file Constants for reveal drawer state machine.
3
+ *
4
+ * Centralizes all threshold values used in reveal drawer behavior.
5
+ * These values control swipe gestures, animations, and state transitions.
6
+ */
7
+
8
+ /**
9
+ * Minimum swipe distance in pixels to trigger drawer open.
10
+ * Used when opening via edge swipe gesture.
11
+ * ~27% of a 375px mobile screen width.
12
+ */
13
+ export const REVEAL_DRAWER_OPEN_DISTANCE_THRESHOLD = 100;
14
+
15
+ /**
16
+ * Ratio of drawer size that must be swiped to dismiss (close) the drawer.
17
+ * Value between 0 and 1. When swiping to close, if displacement exceeds
18
+ * this ratio of the drawer size, the drawer will close.
19
+ */
20
+ export const REVEAL_DRAWER_DISMISS_RATIO = 0.3;
21
+
22
+ /**
23
+ * Percentage offset for drawer when closed.
24
+ * The drawer is positioned at -30% (or +30% for right/bottom) when closed,
25
+ * creating a "pull out" parallax effect during the reveal animation.
26
+ */
27
+ export const REVEAL_DRAWER_CLOSED_OFFSET_PERCENT = 30;
28
+
29
+ /**
30
+ * Animation duration in milliseconds for reveal transitions.
31
+ */
32
+ export const REVEAL_DRAWER_ANIMATION_DURATION = 300;
33
+
34
+ /**
35
+ * Threshold in pixels below which positions are considered equal.
36
+ * Used to determine if animation is needed between two positions.
37
+ */
38
+ export const REVEAL_DRAWER_SNAP_THRESHOLD = 1;