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
@@ -4,16 +4,31 @@
4
4
  import { renderHook, act } from "@testing-library/react";
5
5
  import * as React from "react";
6
6
  import { useStackSwipeInput } from "./useStackSwipeInput.js";
7
- import type { UseStackNavigationResult, StackNavigationState } from "./types.js";
7
+ import type { UseStackNavigationResult } from "./types.js";
8
8
 
9
9
  describe("useStackSwipeInput", () => {
10
- beforeEach(() => {
11
- vi.useFakeTimers();
12
- });
10
+ type CallTracker = {
11
+ calls: ReadonlyArray<ReadonlyArray<unknown>>;
12
+ fn: (...args: ReadonlyArray<unknown>) => void;
13
+ };
13
14
 
14
- afterEach(() => {
15
- vi.useRealTimers();
16
- });
15
+ const createCallTracker = (): CallTracker => {
16
+ const calls: Array<ReadonlyArray<unknown>> = [];
17
+ const fn = (...args: ReadonlyArray<unknown>): void => {
18
+ calls.push(args);
19
+ };
20
+ return { calls, fn };
21
+ };
22
+
23
+ type MockNavigationState = {
24
+ navigation: Pick<UseStackNavigationResult, "go" | "canGo" | "revealParent" | "dismissReveal" | "state">;
25
+ calls: {
26
+ go: CallTracker;
27
+ canGo: CallTracker;
28
+ revealParent: CallTracker;
29
+ dismissReveal: CallTracker;
30
+ };
31
+ };
17
32
 
18
33
  const createRef = (width = 300): React.RefObject<HTMLDivElement> => {
19
34
  const element = document.createElement("div");
@@ -29,25 +44,42 @@ describe("useStackSwipeInput", () => {
29
44
  y: 0,
30
45
  toJSON: () => ({}),
31
46
  };
32
- vi.spyOn(element, "getBoundingClientRect").mockReturnValue(defaultRect);
47
+ Object.defineProperty(element, "getBoundingClientRect", {
48
+ value: () => defaultRect,
49
+ });
33
50
  return { current: element };
34
51
  };
35
52
 
36
- const createMockNavigation = (canGoBack = true): Pick<
37
- UseStackNavigationResult,
38
- "go" | "canGo" | "revealParent" | "dismissReveal" | "state"
39
- > => ({
40
- go: vi.fn(),
41
- canGo: vi.fn().mockReturnValue(canGoBack),
42
- revealParent: vi.fn(),
43
- dismissReveal: vi.fn(),
44
- state: {
45
- stack: ["root", "detail"],
46
- depth: 1,
47
- isRevealing: false,
48
- revealDepth: null,
49
- },
50
- });
53
+ const createMockNavigation = (canGoBack = true): MockNavigationState => {
54
+ const go = createCallTracker();
55
+ const canGo = createCallTracker();
56
+ const revealParent = createCallTracker();
57
+ const dismissReveal = createCallTracker();
58
+ const canGoFn = (direction: number): boolean => {
59
+ canGo.fn(direction);
60
+ return canGoBack;
61
+ };
62
+ return {
63
+ navigation: {
64
+ go: go.fn,
65
+ canGo: canGoFn,
66
+ revealParent: revealParent.fn,
67
+ dismissReveal: dismissReveal.fn,
68
+ state: {
69
+ stack: ["root", "detail"],
70
+ depth: 1,
71
+ isRevealing: false,
72
+ revealDepth: null,
73
+ },
74
+ },
75
+ calls: {
76
+ go,
77
+ canGo,
78
+ revealParent,
79
+ dismissReveal,
80
+ },
81
+ };
82
+ };
51
83
 
52
84
  /** Create a mock pointer event with preventDefault */
53
85
  const createMockPointerEvent = (props: {
@@ -70,7 +102,7 @@ describe("useStackSwipeInput", () => {
70
102
  describe("initialization", () => {
71
103
  it("starts with isEdgeSwiping false and progress 0", () => {
72
104
  const containerRef = createRef();
73
- const navigation = createMockNavigation();
105
+ const { navigation } = createMockNavigation();
74
106
 
75
107
  const { result } = renderHook(() =>
76
108
  useStackSwipeInput({ containerRef, navigation }),
@@ -82,7 +114,7 @@ describe("useStackSwipeInput", () => {
82
114
 
83
115
  it("provides container props with style", () => {
84
116
  const containerRef = createRef();
85
- const navigation = createMockNavigation();
117
+ const { navigation } = createMockNavigation();
86
118
 
87
119
  const { result } = renderHook(() =>
88
120
  useStackSwipeInput({ containerRef, navigation }),
@@ -95,7 +127,7 @@ describe("useStackSwipeInput", () => {
95
127
  describe("edge swipe to go back", () => {
96
128
  it("calls go(-1) when swiping from left edge", () => {
97
129
  const containerRef = createRef();
98
- const navigation = createMockNavigation();
130
+ const { navigation, calls } = createMockNavigation();
99
131
 
100
132
  const { result } = renderHook(() =>
101
133
  useStackSwipeInput({
@@ -132,12 +164,13 @@ describe("useStackSwipeInput", () => {
132
164
  document.dispatchEvent(upEvent);
133
165
  });
134
166
 
135
- expect(navigation.go).toHaveBeenCalledWith(-1);
167
+ expect(calls.go.calls).toHaveLength(1);
168
+ expect(calls.go.calls[0]?.[0]).toBe(-1);
136
169
  });
137
170
 
138
171
  it("does not activate when canGo returns false", () => {
139
172
  const containerRef = createRef();
140
- const navigation = createMockNavigation(false);
173
+ const { navigation } = createMockNavigation(false);
141
174
 
142
175
  const { result } = renderHook(() =>
143
176
  useStackSwipeInput({
@@ -162,7 +195,7 @@ describe("useStackSwipeInput", () => {
162
195
  describe("progress tracking", () => {
163
196
  it("calculates progress based on displacement", () => {
164
197
  const containerRef = createRef(300);
165
- const navigation = createMockNavigation();
198
+ const { navigation } = createMockNavigation();
166
199
 
167
200
  const { result } = renderHook(() =>
168
201
  useStackSwipeInput({
@@ -195,7 +228,7 @@ describe("useStackSwipeInput", () => {
195
228
 
196
229
  it("caps progress at 1.0", () => {
197
230
  const containerRef = createRef(300);
198
- const navigation = createMockNavigation();
231
+ const { navigation } = createMockNavigation();
199
232
 
200
233
  const { result } = renderHook(() =>
201
234
  useStackSwipeInput({
@@ -230,7 +263,7 @@ describe("useStackSwipeInput", () => {
230
263
  describe("disabled state", () => {
231
264
  it("does not track when disabled", () => {
232
265
  const containerRef = createRef();
233
- const navigation = createMockNavigation();
266
+ const { navigation } = createMockNavigation();
234
267
 
235
268
  const { result } = renderHook(() =>
236
269
  useStackSwipeInput({
@@ -253,7 +286,7 @@ describe("useStackSwipeInput", () => {
253
286
  describe("edge configuration", () => {
254
287
  it("respects custom edge width", () => {
255
288
  const containerRef = createRef();
256
- const navigation = createMockNavigation();
289
+ const { navigation } = createMockNavigation();
257
290
 
258
291
  const { result } = renderHook(() =>
259
292
  useStackSwipeInput({
@@ -71,7 +71,7 @@ export function useStackSwipeInput(options: UseStackSwipeInputOptions): UseStack
71
71
  containerRef,
72
72
  edge,
73
73
  edgeWidth,
74
- enabled: enabled && navigation.canGo(-1), // Only enable if can go back
74
+ enabled: enabled ? navigation.canGo(-1) : false, // Only enable if can go back
75
75
  onSwipeEnd: handleSwipeEnd,
76
76
  });
77
77
 
@@ -9,6 +9,7 @@
9
9
  */
10
10
  import * as React from "react";
11
11
  import { useIsomorphicLayoutEffect } from "../hooks/useIsomorphicLayoutEffect";
12
+ import { calculateStickyMetrics } from "./calculateStickyMetrics";
12
13
  import type { StickyAreaProps, StickyAreaState } from "./types";
13
14
 
14
15
  /**
@@ -127,63 +128,34 @@ export const StickyArea: React.FC<StickyAreaProps> = ({
127
128
  const liveRect = area.getBoundingClientRect();
128
129
  const viewportHeight = window.innerHeight;
129
130
 
130
- if (position === "top") {
131
- // TOP: Cover expands upward during pull-down overscroll
132
- // liveRect.top > 0 means element moved down (overscroll)
133
- // liveRect.top < 0 means element scrolled up
134
- const coverAreaHeight = Math.max(0, liveRect.height + liveRect.top);
135
-
136
- if (coverAreaHeight !== prevHeight) {
137
- coverArea.style.opacity = coverAreaHeight > 0 ? "1" : "0";
138
- coverArea.style.height = `${coverAreaHeight}px`;
139
- prevHeight = coverAreaHeight;
140
- }
141
-
142
- if (liveRect.left !== prevLeft || liveRect.width !== prevWidth) {
143
- coverArea.style.left = `${liveRect.left}px`;
144
- coverArea.style.width = `${liveRect.width}px`;
145
- prevLeft = liveRect.left;
146
- prevWidth = liveRect.width;
147
- }
148
-
149
- const isStuck = liveRect.top < 0;
150
- const scrollOffset = Math.max(0, -liveRect.top);
151
-
152
- const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;
153
- if (shouldUpdateState) {
154
- isFirstRun = false;
155
- prevIsStuck = isStuck;
156
- updateState({ isStuck, scrollOffset });
157
- }
158
- } else {
159
- // BOTTOM: Cover expands downward during pull-up overscroll
160
- // liveRect.bottom < viewportHeight means element moved up (overscroll at bottom)
161
- // liveRect.bottom > viewportHeight means element is below viewport
162
- const distanceFromBottom = viewportHeight - liveRect.bottom;
163
- const coverAreaHeight = Math.max(0, liveRect.height + distanceFromBottom);
164
-
165
- if (coverAreaHeight !== prevHeight) {
166
- coverArea.style.opacity = coverAreaHeight > 0 ? "1" : "0";
167
- coverArea.style.height = `${coverAreaHeight}px`;
168
- prevHeight = coverAreaHeight;
169
- }
170
-
171
- if (liveRect.left !== prevLeft || liveRect.width !== prevWidth) {
172
- coverArea.style.left = `${liveRect.left}px`;
173
- coverArea.style.width = `${liveRect.width}px`;
174
- prevLeft = liveRect.left;
175
- prevWidth = liveRect.width;
176
- }
177
-
178
- const isStuck = liveRect.bottom > viewportHeight;
179
- const scrollOffset = Math.max(0, liveRect.bottom - viewportHeight);
180
-
181
- const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;
182
- if (shouldUpdateState) {
183
- isFirstRun = false;
184
- prevIsStuck = isStuck;
185
- updateState({ isStuck, scrollOffset });
186
- }
131
+ // Calculate metrics using pure function
132
+ const { coverAreaHeight, isStuck, scrollOffset } = calculateStickyMetrics(
133
+ position,
134
+ liveRect,
135
+ viewportHeight
136
+ );
137
+
138
+ // Update height/opacity
139
+ if (coverAreaHeight !== prevHeight) {
140
+ coverArea.style.opacity = coverAreaHeight > 0 ? "1" : "0";
141
+ coverArea.style.height = `${coverAreaHeight}px`;
142
+ prevHeight = coverAreaHeight;
143
+ }
144
+
145
+ // Update left/width
146
+ if (liveRect.left !== prevLeft || liveRect.width !== prevWidth) {
147
+ coverArea.style.left = `${liveRect.left}px`;
148
+ coverArea.style.width = `${liveRect.width}px`;
149
+ prevLeft = liveRect.left;
150
+ prevWidth = liveRect.width;
151
+ }
152
+
153
+ // Update state
154
+ const shouldUpdateState = isFirstRun ? true : isStuck !== prevIsStuck;
155
+ if (shouldUpdateState) {
156
+ isFirstRun = false;
157
+ prevIsStuck = isStuck;
158
+ updateState({ isStuck, scrollOffset });
187
159
  }
188
160
  };
189
161
 
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file Tests for calculateStickyMetrics pure function.
3
+ *
4
+ * Verifies the calculation of coverAreaHeight, isStuck, and scrollOffset
5
+ * for both top and bottom positions under various scroll scenarios.
6
+ */
7
+ import { calculateStickyMetrics, type ElementRect } from "./calculateStickyMetrics.js";
8
+
9
+ describe("calculateStickyMetrics", () => {
10
+ describe("position: top", () => {
11
+ it("returns expanded coverAreaHeight during overscroll (pull-down)", () => {
12
+ const rect: ElementRect = { top: 50, bottom: 150, height: 100 };
13
+ const result = calculateStickyMetrics("top", rect, 800);
14
+
15
+ expect(result).toEqual({
16
+ coverAreaHeight: 150, // height + top = 100 + 50
17
+ isStuck: false,
18
+ scrollOffset: 0,
19
+ });
20
+ });
21
+
22
+ it("returns reduced coverAreaHeight during normal scroll", () => {
23
+ const rect: ElementRect = { top: -30, bottom: 70, height: 100 };
24
+ const result = calculateStickyMetrics("top", rect, 800);
25
+
26
+ expect(result).toEqual({
27
+ coverAreaHeight: 70, // height + top = 100 + (-30)
28
+ isStuck: true,
29
+ scrollOffset: 30,
30
+ });
31
+ });
32
+
33
+ it("returns exact height at boundary (top === 0)", () => {
34
+ const rect: ElementRect = { top: 0, bottom: 100, height: 100 };
35
+ const result = calculateStickyMetrics("top", rect, 800);
36
+
37
+ expect(result).toEqual({
38
+ coverAreaHeight: 100,
39
+ isStuck: false,
40
+ scrollOffset: 0,
41
+ });
42
+ });
43
+
44
+ it("clamps coverAreaHeight to 0 when fully scrolled past", () => {
45
+ const rect: ElementRect = { top: -150, bottom: -50, height: 100 };
46
+ const result = calculateStickyMetrics("top", rect, 800);
47
+
48
+ expect(result.coverAreaHeight).toBe(0);
49
+ expect(result.isStuck).toBe(true);
50
+ expect(result.scrollOffset).toBe(150);
51
+ });
52
+ });
53
+
54
+ describe("position: bottom", () => {
55
+ it("returns expanded coverAreaHeight during overscroll (pull-up)", () => {
56
+ const rect: ElementRect = { top: 600, bottom: 700, height: 100 };
57
+ const viewportHeight = 800;
58
+ const result = calculateStickyMetrics("bottom", rect, viewportHeight);
59
+
60
+ // distanceFromBottom = 800 - 700 = 100
61
+ // coverAreaHeight = 100 + 100 = 200
62
+ expect(result).toEqual({
63
+ coverAreaHeight: 200,
64
+ isStuck: false,
65
+ scrollOffset: 0,
66
+ });
67
+ });
68
+
69
+ it("returns reduced coverAreaHeight during normal scroll", () => {
70
+ const rect: ElementRect = { top: 750, bottom: 850, height: 100 };
71
+ const viewportHeight = 800;
72
+ const result = calculateStickyMetrics("bottom", rect, viewportHeight);
73
+
74
+ // distanceFromBottom = 800 - 850 = -50
75
+ // coverAreaHeight = 100 + (-50) = 50
76
+ expect(result).toEqual({
77
+ coverAreaHeight: 50,
78
+ isStuck: true,
79
+ scrollOffset: 50,
80
+ });
81
+ });
82
+
83
+ it("returns exact height at boundary (bottom === viewportHeight)", () => {
84
+ const rect: ElementRect = { top: 700, bottom: 800, height: 100 };
85
+ const viewportHeight = 800;
86
+ const result = calculateStickyMetrics("bottom", rect, viewportHeight);
87
+
88
+ expect(result).toEqual({
89
+ coverAreaHeight: 100,
90
+ isStuck: false,
91
+ scrollOffset: 0,
92
+ });
93
+ });
94
+
95
+ it("clamps coverAreaHeight to 0 when fully scrolled past", () => {
96
+ const rect: ElementRect = { top: 900, bottom: 1000, height: 100 };
97
+ const viewportHeight = 800;
98
+ const result = calculateStickyMetrics("bottom", rect, viewportHeight);
99
+
100
+ expect(result.coverAreaHeight).toBe(0);
101
+ expect(result.isStuck).toBe(true);
102
+ expect(result.scrollOffset).toBe(200);
103
+ });
104
+ });
105
+ });
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @file Pure function for calculating sticky area metrics.
3
+ *
4
+ * Extracts the position-dependent calculation logic from StickyArea component
5
+ * to enable unit testing and reduce code duplication.
6
+ */
7
+ import type { StickyAreaPosition } from "./types";
8
+
9
+ /** Output metrics for sticky area layout. */
10
+ export type StickyMetrics = {
11
+ coverAreaHeight: number;
12
+ isStuck: boolean;
13
+ scrollOffset: number;
14
+ };
15
+
16
+ /** Lightweight element bounding rect used for calculations. */
17
+ export type ElementRect = {
18
+ top: number;
19
+ bottom: number;
20
+ height: number;
21
+ };
22
+
23
+ /**
24
+ * Calculate sticky area metrics based on element position and viewport.
25
+ *
26
+ * @param position - Whether the sticky area is at "top" or "bottom"
27
+ * @param rect - Element bounding rect (top, bottom, height)
28
+ * @param viewportHeight - Current viewport height
29
+ * @returns Calculated metrics for cover area height, stuck state, and scroll offset
30
+ */
31
+ export function calculateStickyMetrics(
32
+ position: StickyAreaPosition,
33
+ rect: ElementRect,
34
+ viewportHeight: number
35
+ ): StickyMetrics {
36
+ if (position === "top") {
37
+ return {
38
+ coverAreaHeight: Math.max(0, rect.height + rect.top),
39
+ isStuck: rect.top < 0,
40
+ scrollOffset: Math.max(0, -rect.top),
41
+ };
42
+ }
43
+ // bottom
44
+ const distanceFromBottom = viewportHeight - rect.bottom;
45
+ return {
46
+ coverAreaHeight: Math.max(0, rect.height + distanceFromBottom),
47
+ isStuck: rect.bottom > viewportHeight,
48
+ scrollOffset: Math.max(0, rect.bottom - viewportHeight),
49
+ };
50
+ }
package/src/types.ts CHANGED
@@ -225,6 +225,24 @@ export type DrawerBehavior = {
225
225
  title?: string;
226
226
  showCloseButton?: boolean;
227
227
  };
228
+ /**
229
+ * Enable swipe gestures for opening/closing the drawer.
230
+ * - true: Enable both edge-swipe-to-open and swipe-to-close
231
+ * - false: Disable swipe gestures (default)
232
+ * - Object: Fine-grained control over swipe behavior
233
+ */
234
+ swipeGestures?:
235
+ | boolean
236
+ | {
237
+ /** Enable edge swipe from container to open. @default true */
238
+ edgeSwipeOpen?: boolean;
239
+ /** Enable swipe within drawer to close. @default true */
240
+ swipeClose?: boolean;
241
+ /** Width of edge detection zone in pixels. @default 20 */
242
+ edgeWidth?: number;
243
+ /** Threshold ratio (0-1) to trigger close. @default 0.3 */
244
+ dismissThreshold?: number;
245
+ };
228
246
  };
229
247
 
230
248
  // ============================================================================
@@ -57,6 +57,7 @@ export { Drawer } from "../components/window/Drawer.js";
57
57
  export { DrawerLayers } from "../components/window/DrawerLayers.js";
58
58
  export { DialogOverlay } from "../components/window/DialogOverlay.js";
59
59
  export { PopupLayerPortal } from "../components/window/PopupLayerPortal.js";
60
+ export { SwipeSafeZone } from "../components/gesture/SwipeSafeZone.js";
60
61
 
61
62
  // Hooks
62
63
  export { useFloatingState } from "../modules/window/useFloatingState.js";
@@ -65,3 +66,4 @@ export { useDrawerState } from "../modules/window/useDrawerState.js";
65
66
  // Types
66
67
  export type { FloatingWindowProps } from "../components/window/FloatingWindow.js";
67
68
  export type { DrawerProps } from "../components/window/Drawer.js";
69
+ export type { SwipeSafeZoneProps } from "../components/gesture/SwipeSafeZone.js";