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,112 @@
1
+ /**
2
+ * @file Drawer swipe gesture configuration parsing.
3
+ *
4
+ * Normalizes swipeGestures config from DrawerBehavior into a consistent shape.
5
+ */
6
+ import type { DrawerBehavior, WindowPosition } from "../../types.js";
7
+ import type { DrawerPlacement } from "./drawerStyles.js";
8
+
9
+ /**
10
+ * Normalized swipe gesture configuration.
11
+ */
12
+ export type NormalizedSwipeConfig = {
13
+ enabled: boolean;
14
+ edgeSwipeOpen: boolean;
15
+ swipeClose: boolean;
16
+ edgeWidth: number;
17
+ dismissThreshold: number;
18
+ };
19
+
20
+ const DEFAULT_EDGE_WIDTH = 20;
21
+ const DEFAULT_DISMISS_THRESHOLD = 0.3;
22
+
23
+ const DISABLED_CONFIG: NormalizedSwipeConfig = {
24
+ enabled: false,
25
+ edgeSwipeOpen: false,
26
+ swipeClose: false,
27
+ edgeWidth: DEFAULT_EDGE_WIDTH,
28
+ dismissThreshold: DEFAULT_DISMISS_THRESHOLD,
29
+ };
30
+
31
+ const ENABLED_DEFAULT_CONFIG: NormalizedSwipeConfig = {
32
+ enabled: true,
33
+ edgeSwipeOpen: true,
34
+ swipeClose: true,
35
+ edgeWidth: DEFAULT_EDGE_WIDTH,
36
+ dismissThreshold: DEFAULT_DISMISS_THRESHOLD,
37
+ };
38
+
39
+ /**
40
+ * Parse swipeGestures config into normalized options.
41
+ */
42
+ export function parseSwipeGesturesConfig(
43
+ swipeGestures: DrawerBehavior["swipeGestures"],
44
+ ): NormalizedSwipeConfig {
45
+ if (swipeGestures === true) {
46
+ return ENABLED_DEFAULT_CONFIG;
47
+ }
48
+
49
+ if (swipeGestures === false || swipeGestures === undefined) {
50
+ return DISABLED_CONFIG;
51
+ }
52
+
53
+ return {
54
+ enabled: true,
55
+ edgeSwipeOpen: swipeGestures.edgeSwipeOpen ?? true,
56
+ swipeClose: swipeGestures.swipeClose ?? true,
57
+ edgeWidth: swipeGestures.edgeWidth ?? DEFAULT_EDGE_WIDTH,
58
+ dismissThreshold: swipeGestures.dismissThreshold ?? DEFAULT_DISMISS_THRESHOLD,
59
+ };
60
+ }
61
+
62
+ /**
63
+ * Resolve drawer placement from anchor and position.
64
+ */
65
+ export function resolvePlacement(
66
+ anchor: DrawerBehavior["anchor"],
67
+ position: WindowPosition | undefined,
68
+ ): DrawerPlacement {
69
+ if (anchor) {
70
+ return anchor;
71
+ }
72
+
73
+ if (!position) {
74
+ return "right";
75
+ }
76
+
77
+ if (position.left !== undefined) {
78
+ return "left";
79
+ }
80
+ if (position.right !== undefined) {
81
+ return "right";
82
+ }
83
+ if (position.top !== undefined) {
84
+ return "top";
85
+ }
86
+ if (position.bottom !== undefined) {
87
+ return "bottom";
88
+ }
89
+
90
+ return "right";
91
+ }
92
+
93
+ /**
94
+ * Determine if edge zone should be visible.
95
+ */
96
+ export function shouldShowEdgeZone(
97
+ config: NormalizedSwipeConfig,
98
+ isOpen: boolean,
99
+ isOpening: boolean,
100
+ ): boolean {
101
+ if (!config.enabled) {
102
+ return false;
103
+ }
104
+ if (!config.edgeSwipeOpen) {
105
+ return false;
106
+ }
107
+ // Show when closed or actively opening
108
+ if (!isOpen) {
109
+ return true;
110
+ }
111
+ return isOpening;
112
+ }
@@ -0,0 +1,234 @@
1
+ /**
2
+ * @file Tests for useDrawerSwipeTransform hook.
3
+ */
4
+ import { renderHook } from "@testing-library/react";
5
+ import * as React from "react";
6
+ import { useDrawerSwipeTransform } from "./useDrawerSwipeTransform.js";
7
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
8
+
9
+ describe("useDrawerSwipeTransform", () => {
10
+ const createDivRef = (width = 300, height = 500): React.RefObject<HTMLDivElement> => {
11
+ const element = document.createElement("div");
12
+ Object.defineProperty(element, "clientWidth", { value: width });
13
+ Object.defineProperty(element, "clientHeight", { value: height });
14
+ return { current: element };
15
+ };
16
+
17
+ const idleState: ContinuousOperationState = {
18
+ phase: "idle",
19
+ displacement: { x: 0, y: 0 },
20
+ velocity: { x: 0, y: 0 },
21
+ };
22
+
23
+ const operatingState: ContinuousOperationState = {
24
+ phase: "operating",
25
+ displacement: { x: 50, y: 0 },
26
+ velocity: { x: 1, y: 0 },
27
+ };
28
+
29
+ const endedState: ContinuousOperationState = {
30
+ phase: "ended",
31
+ displacement: { x: 0, y: 0 },
32
+ velocity: { x: 0, y: 0 },
33
+ };
34
+
35
+ describe("when disabled", () => {
36
+ it("does not apply transforms", () => {
37
+ const drawerRef = createDivRef();
38
+ const backdropRef = createDivRef();
39
+
40
+ renderHook(() =>
41
+ useDrawerSwipeTransform({
42
+ drawerRef,
43
+ backdropRef,
44
+ placement: "left",
45
+ swipeState: operatingState,
46
+ displacement: 50,
47
+ isOpening: true,
48
+ isClosing: false,
49
+ enabled: false,
50
+ }),
51
+ );
52
+
53
+ expect(drawerRef.current?.style.transform).toBe("");
54
+ });
55
+ });
56
+
57
+ describe("when closing", () => {
58
+ it("applies translateX for left drawer", () => {
59
+ const drawerRef = createDivRef();
60
+ const backdropRef = createDivRef();
61
+
62
+ renderHook(() =>
63
+ useDrawerSwipeTransform({
64
+ drawerRef,
65
+ backdropRef,
66
+ placement: "left",
67
+ swipeState: operatingState,
68
+ displacement: 50,
69
+ isOpening: false,
70
+ isClosing: true,
71
+ enabled: true,
72
+ }),
73
+ );
74
+
75
+ // Left drawer closes with negative translateX
76
+ expect(drawerRef.current?.style.transform).toBe("translateX(-50px)");
77
+ });
78
+
79
+ it("applies translateX for right drawer", () => {
80
+ const drawerRef = createDivRef();
81
+ const backdropRef = createDivRef();
82
+
83
+ renderHook(() =>
84
+ useDrawerSwipeTransform({
85
+ drawerRef,
86
+ backdropRef,
87
+ placement: "right",
88
+ swipeState: operatingState,
89
+ displacement: 50,
90
+ isOpening: false,
91
+ isClosing: true,
92
+ enabled: true,
93
+ }),
94
+ );
95
+
96
+ // Right drawer closes with positive translateX
97
+ expect(drawerRef.current?.style.transform).toBe("translateX(50px)");
98
+ });
99
+
100
+ it("applies translateY for bottom drawer", () => {
101
+ const drawerRef = createDivRef();
102
+ const backdropRef = createDivRef();
103
+
104
+ renderHook(() =>
105
+ useDrawerSwipeTransform({
106
+ drawerRef,
107
+ backdropRef,
108
+ placement: "bottom",
109
+ swipeState: operatingState,
110
+ displacement: 50,
111
+ isOpening: false,
112
+ isClosing: true,
113
+ enabled: true,
114
+ }),
115
+ );
116
+
117
+ // Bottom drawer closes with positive translateY
118
+ expect(drawerRef.current?.style.transform).toBe("translateY(50px)");
119
+ });
120
+
121
+ it("updates backdrop opacity based on progress", () => {
122
+ const drawerRef = createDivRef();
123
+ const backdropRef = createDivRef();
124
+
125
+ renderHook(() =>
126
+ useDrawerSwipeTransform({
127
+ drawerRef,
128
+ backdropRef,
129
+ placement: "left",
130
+ swipeState: operatingState,
131
+ displacement: 150, // 50% of 300px width
132
+ isOpening: false,
133
+ isClosing: true,
134
+ enabled: true,
135
+ }),
136
+ );
137
+
138
+ // 150px / 300px = 50% progress, so opacity = 1 - 0.5 = 0.5
139
+ expect(backdropRef.current?.style.opacity).toBe("0.5");
140
+ });
141
+ });
142
+
143
+ describe("when opening", () => {
144
+ it("applies opening transform for left drawer", () => {
145
+ const drawerRef = createDivRef();
146
+ const backdropRef = createDivRef();
147
+
148
+ renderHook(() =>
149
+ useDrawerSwipeTransform({
150
+ drawerRef,
151
+ backdropRef,
152
+ placement: "left",
153
+ swipeState: operatingState,
154
+ displacement: 100,
155
+ isOpening: true,
156
+ isClosing: false,
157
+ enabled: true,
158
+ }),
159
+ );
160
+
161
+ // Left drawer starts at -300px (width), opening 100px means -200px
162
+ expect(drawerRef.current?.style.transform).toBe("translateX(-200px)");
163
+ });
164
+
165
+ it("updates backdrop opacity during opening", () => {
166
+ const drawerRef = createDivRef();
167
+ const backdropRef = createDivRef();
168
+
169
+ renderHook(() =>
170
+ useDrawerSwipeTransform({
171
+ drawerRef,
172
+ backdropRef,
173
+ placement: "left",
174
+ swipeState: operatingState,
175
+ displacement: 150,
176
+ isOpening: true,
177
+ isClosing: false,
178
+ enabled: true,
179
+ }),
180
+ );
181
+
182
+ expect(backdropRef.current?.style.opacity).toBe("0.5");
183
+ });
184
+ });
185
+
186
+ describe("reset on swipe end", () => {
187
+ it("clears transform when swipe ends", () => {
188
+ const drawerRef = createDivRef();
189
+ const backdropRef = createDivRef();
190
+
191
+ // First apply a transform
192
+ drawerRef.current!.style.transform = "translateX(-50px)";
193
+ backdropRef.current!.style.opacity = "0.5";
194
+
195
+ renderHook(() =>
196
+ useDrawerSwipeTransform({
197
+ drawerRef,
198
+ backdropRef,
199
+ placement: "left",
200
+ swipeState: endedState,
201
+ displacement: 0,
202
+ isOpening: false,
203
+ isClosing: false,
204
+ enabled: true,
205
+ }),
206
+ );
207
+
208
+ expect(drawerRef.current?.style.transform).toBe("");
209
+ expect(backdropRef.current?.style.opacity).toBe("");
210
+ });
211
+ });
212
+
213
+ describe("idle state", () => {
214
+ it("does not apply transforms in idle state", () => {
215
+ const drawerRef = createDivRef();
216
+ const backdropRef = createDivRef();
217
+
218
+ renderHook(() =>
219
+ useDrawerSwipeTransform({
220
+ drawerRef,
221
+ backdropRef,
222
+ placement: "left",
223
+ swipeState: idleState,
224
+ displacement: 0,
225
+ isOpening: false,
226
+ isClosing: false,
227
+ enabled: true,
228
+ }),
229
+ );
230
+
231
+ expect(drawerRef.current?.style.transform).toBe("");
232
+ });
233
+ });
234
+ });
@@ -0,0 +1,129 @@
1
+ /**
2
+ * @file Hook for applying real-time transform during drawer swipe gestures.
3
+ *
4
+ * Handles DOM manipulation for smooth swipe animations.
5
+ */
6
+ import * as React from "react";
7
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
8
+ import { getDrawerAnimationAxis, getDrawerCloseSwipeSign } from "../../modules/drawer/types.js";
9
+ import type { DrawerSwipeDirection } from "../../modules/drawer/types.js";
10
+
11
+ type UseDrawerSwipeTransformOptions = {
12
+ drawerRef: React.RefObject<HTMLDivElement | null>;
13
+ backdropRef: React.RefObject<HTMLDivElement | null>;
14
+ placement: DrawerSwipeDirection;
15
+ swipeState: ContinuousOperationState;
16
+ displacement: number;
17
+ isOpening: boolean;
18
+ isClosing: boolean;
19
+ enabled: boolean;
20
+ };
21
+
22
+ /**
23
+ * Apply real-time transform to drawer and backdrop during swipe.
24
+ */
25
+ export function useDrawerSwipeTransform(options: UseDrawerSwipeTransformOptions): void {
26
+ const {
27
+ drawerRef,
28
+ backdropRef,
29
+ placement,
30
+ swipeState,
31
+ displacement,
32
+ isOpening,
33
+ isClosing,
34
+ enabled,
35
+ } = options;
36
+
37
+ const isOperating = swipeState.phase === "operating";
38
+
39
+ // Apply real-time transform during swipe
40
+ React.useLayoutEffect(() => {
41
+ if (!enabled || !isOperating) {
42
+ return;
43
+ }
44
+
45
+ const drawer = drawerRef.current;
46
+ const backdrop = backdropRef.current;
47
+
48
+ if (!drawer) {
49
+ return;
50
+ }
51
+
52
+ const axis = getDrawerAnimationAxis(placement);
53
+ const closeSign = getDrawerCloseSwipeSign(placement);
54
+ const drawerSize = axis === "x" ? drawer.clientWidth : drawer.clientHeight;
55
+
56
+ if (drawerSize <= 0) {
57
+ return;
58
+ }
59
+
60
+ const translateFn = axis === "x" ? "translateX" : "translateY";
61
+
62
+ if (isClosing) {
63
+ applyClosingTransform(drawer, backdrop, translateFn, closeSign, displacement, drawerSize);
64
+ } else if (isOpening) {
65
+ applyOpeningTransform(drawer, backdrop, translateFn, closeSign, displacement, drawerSize);
66
+ }
67
+ }, [enabled, isOperating, isClosing, isOpening, displacement, placement, drawerRef, backdropRef]);
68
+
69
+ // Reset transform after swipe ends
70
+ React.useLayoutEffect(() => {
71
+ if (!enabled || swipeState.phase !== "ended") {
72
+ return;
73
+ }
74
+
75
+ const drawer = drawerRef.current;
76
+ const backdrop = backdropRef.current;
77
+
78
+ if (drawer) {
79
+ drawer.style.transform = "";
80
+ }
81
+ if (backdrop) {
82
+ backdrop.style.opacity = "";
83
+ }
84
+ }, [enabled, swipeState.phase, drawerRef, backdropRef]);
85
+ }
86
+
87
+ function applyClosingTransform(
88
+ drawer: HTMLDivElement,
89
+ backdrop: HTMLDivElement | null,
90
+ translateFn: "translateX" | "translateY",
91
+ closeSign: 1 | -1,
92
+ displacement: number,
93
+ drawerSize: number,
94
+ ): void {
95
+ const translate = closeSign * displacement;
96
+ drawer.style.transform = `${translateFn}(${translate}px)`;
97
+
98
+ const progress = Math.min(displacement / drawerSize, 1);
99
+ if (backdrop) {
100
+ backdrop.style.opacity = String(1 - progress);
101
+ }
102
+ }
103
+
104
+ function applyOpeningTransform(
105
+ drawer: HTMLDivElement,
106
+ backdrop: HTMLDivElement | null,
107
+ translateFn: "translateX" | "translateY",
108
+ closeSign: 1 | -1,
109
+ displacement: number,
110
+ drawerSize: number,
111
+ ): void {
112
+ const closedPosition = closeSign * drawerSize;
113
+ const translate = closedPosition + closeSign * -1 * displacement;
114
+ const clampedTranslate = clampTranslate(translate, closeSign);
115
+ drawer.style.transform = `${translateFn}(${clampedTranslate}px)`;
116
+
117
+ const progress = Math.min(displacement / drawerSize, 1);
118
+ if (backdrop) {
119
+ backdrop.style.opacity = String(progress);
120
+ backdrop.style.pointerEvents = "auto";
121
+ }
122
+ }
123
+
124
+ function clampTranslate(translate: number, closeSign: 1 | -1): number {
125
+ if (closeSign > 0) {
126
+ return Math.max(0, translate);
127
+ }
128
+ return Math.min(0, translate);
129
+ }
@@ -259,3 +259,22 @@ export const SPLIT_HANDLE_THICKNESS = SIZE_SPLIT_HANDLE_THICKNESS;
259
259
  */
260
260
  export const HORIZONTAL_DIVIDER_WIDTH = "var(--rpl-size-horizontal-divider-width, 4px)";
261
261
  export const HORIZONTAL_DIVIDER_HIT_AREA_OFFSET = "var(--rpl-space-horizontal-divider-hit-area-offset, 4px)";
262
+
263
+ /**
264
+ * Modal
265
+ */
266
+ export const COLOR_MODAL_BACKDROP = "var(--rpl-color-modal-backdrop, rgba(0, 0, 0, 0.5))";
267
+ export const MODAL_TRANSITION_DURATION = "var(--rpl-modal-transition-duration, 200ms)";
268
+ export const MODAL_TRANSITION_EASING = "var(--rpl-modal-transition-easing, ease-out)";
269
+ export const MODAL_MIN_WIDTH = "var(--rpl-modal-min-width, 280px)";
270
+ export const MODAL_MAX_WIDTH = "var(--rpl-modal-max-width, 90vw)";
271
+ export const MODAL_MAX_HEIGHT = "var(--rpl-modal-max-height, 85vh)";
272
+
273
+ /**
274
+ * Alert Dialog
275
+ */
276
+ export const ALERT_DIALOG_WIDTH = "var(--rpl-alert-dialog-width, 320px)";
277
+ export const ALERT_DIALOG_BUTTON_GAP = "var(--rpl-alert-dialog-button-gap, 8px)";
278
+ export const ALERT_DIALOG_ACTIONS_PADDING = "var(--rpl-alert-dialog-actions-padding, 12px)";
279
+ export const ALERT_DIALOG_MESSAGE_PADDING = "var(--rpl-alert-dialog-message-padding, 16px)";
280
+ export const ALERT_DIALOG_INPUT_MARGIN_TOP = "var(--rpl-alert-dialog-input-margin-top, 12px)";
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @file Dialog - Alerts page
3
+ */
4
+ import * as React from "react";
5
+ import { AlertDialogDemo } from "../components/AlertDialogDemo";
6
+ import AlertDialogDemoSource from "../components/AlertDialogDemo.tsx?raw";
7
+ import { SingleSamplePage } from "../../../components/layout";
8
+
9
+ const Page: React.FC = () => {
10
+ return (
11
+ <SingleSamplePage
12
+ title="Dialog / Alert, Confirm, Prompt"
13
+ code={AlertDialogDemoSource}
14
+ codeTitle="AlertDialogDemo.tsx"
15
+ previewHeight={520}
16
+ >
17
+ <AlertDialogDemo />
18
+ </SingleSamplePage>
19
+ );
20
+ };
21
+
22
+ export default Page;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * @file Dialog - Card Expansion page (Apple Music style)
3
+ */
4
+ import * as React from "react";
5
+ import { CardExpandDemo } from "../components/CardExpandDemo.js";
6
+ import CardExpandDemoSource from "../components/CardExpandDemo.tsx?raw";
7
+ import { SingleSamplePage } from "../../../components/layout/index.js";
8
+
9
+ const Page: React.FC = () => {
10
+ return (
11
+ <SingleSamplePage
12
+ title="Dialog / Card Expansion"
13
+ code={CardExpandDemoSource}
14
+ codeTitle="CardExpandDemo.tsx"
15
+ previewHeight={600}
16
+ >
17
+ <CardExpandDemo />
18
+ </SingleSamplePage>
19
+ );
20
+ };
21
+
22
+ export default Page;
@@ -0,0 +1,124 @@
1
+ /**
2
+ * @file Alert, Confirm, Prompt dialog demo
3
+ */
4
+ import * as React from "react";
5
+ import { useDialog } from "../../../../modules/dialog/useDialog";
6
+ import { DemoButton } from "../../../components/ui/DemoButton";
7
+ import styles from "./DialogDemos.module.css";
8
+
9
+ export const AlertDialogDemo: React.FC = () => {
10
+ const { alert, confirm, prompt, Outlet } = useDialog();
11
+ const [lastResult, setLastResult] = React.useState<string>("");
12
+
13
+ const handleAlert = async () => {
14
+ await alert({
15
+ title: "Information",
16
+ message: "This is an alert dialog. It displays information and waits for acknowledgment.",
17
+ okLabel: "Got it",
18
+ });
19
+ setLastResult("alert: acknowledged");
20
+ };
21
+
22
+ const handleConfirm = async () => {
23
+ const result = await confirm({
24
+ title: "Confirm Action",
25
+ message: "Are you sure you want to proceed? This action cannot be undone.",
26
+ confirmLabel: "Proceed",
27
+ cancelLabel: "Cancel",
28
+ });
29
+ setLastResult(`confirm: ${result ? "confirmed" : "cancelled"}`);
30
+ };
31
+
32
+ const handlePrompt = async () => {
33
+ const result = await prompt({
34
+ title: "Enter Name",
35
+ message: "Please enter your name:",
36
+ placeholder: "John Doe",
37
+ defaultValue: "",
38
+ });
39
+ setLastResult(`prompt: ${result === null ? "cancelled" : `"${result}"`}`);
40
+ };
41
+
42
+ const handlePasswordPrompt = async () => {
43
+ const result = await prompt({
44
+ title: "Enter Password",
45
+ message: "Please enter your password:",
46
+ inputType: "password",
47
+ confirmLabel: "Submit",
48
+ });
49
+ setLastResult(`password prompt: ${result === null ? "cancelled" : "(password entered)"}`);
50
+ };
51
+
52
+ const handleSequence = async () => {
53
+ await alert("First, let me explain...");
54
+
55
+ const proceed = await confirm({
56
+ message: "Would you like to continue with the setup?",
57
+ confirmLabel: "Yes",
58
+ cancelLabel: "No",
59
+ });
60
+
61
+ if (!proceed) {
62
+ setLastResult("sequence: cancelled at step 2");
63
+ return;
64
+ }
65
+
66
+ const name = await prompt({
67
+ message: "What is your name?",
68
+ defaultValue: "Anonymous",
69
+ });
70
+
71
+ if (name === null) {
72
+ setLastResult("sequence: cancelled at step 3");
73
+ return;
74
+ }
75
+
76
+ await alert(`Hello, ${name}! Setup complete.`);
77
+ setLastResult(`sequence: completed with name "${name}"`);
78
+ };
79
+
80
+ return (
81
+ <div className={styles.container}>
82
+ <div className={styles.section}>
83
+ <h3>Alert Dialog</h3>
84
+ <p>Simple information dialog that waits for acknowledgment.</p>
85
+ <DemoButton variant="primary" size="md" onClick={handleAlert}>
86
+ Show Alert
87
+ </DemoButton>
88
+ </div>
89
+
90
+ <div className={styles.section}>
91
+ <h3>Confirm Dialog</h3>
92
+ <p>Yes/No dialog that returns a boolean result.</p>
93
+ <DemoButton variant="primary" size="md" onClick={handleConfirm}>
94
+ Show Confirm
95
+ </DemoButton>
96
+ </div>
97
+
98
+ <div className={styles.section}>
99
+ <h3>Prompt Dialog</h3>
100
+ <p>Input dialog that returns the entered value or null if cancelled.</p>
101
+ <div className={styles.buttonGroup}>
102
+ <DemoButton variant="primary" size="md" onClick={handlePrompt}>
103
+ Show Prompt
104
+ </DemoButton>
105
+ <DemoButton variant="secondary" size="md" onClick={handlePasswordPrompt}>
106
+ Password Prompt
107
+ </DemoButton>
108
+ </div>
109
+ </div>
110
+
111
+ <div className={styles.section}>
112
+ <h3>Dialog Sequence</h3>
113
+ <p>Chain multiple dialogs together using async/await.</p>
114
+ <DemoButton variant="primary" size="md" onClick={handleSequence}>
115
+ Start Sequence
116
+ </DemoButton>
117
+ </div>
118
+
119
+ {lastResult ? <div className={styles.resultBox}>Last result: {lastResult}</div> : null}
120
+
121
+ <Outlet />
122
+ </div>
123
+ );
124
+ };