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,8 +4,83 @@
4
4
  * Provides a fluent API for simulating pointer events in tests,
5
5
  * making it easier to test swipe and drag gestures.
6
6
  */
7
+ import type * as React from "react";
7
8
  import { act } from "@testing-library/react";
8
9
 
10
+ /**
11
+ * Creates a complete mock React.PointerEvent with all required properties.
12
+ */
13
+ function createFullPointerEvent(
14
+ type: string,
15
+ x: number,
16
+ y: number,
17
+ pointerId: number,
18
+ pointerType: "touch" | "mouse" | "pen",
19
+ ): React.PointerEvent {
20
+ const noop = (): void => {};
21
+ const noopBool = (): boolean => false;
22
+
23
+ const element = document.createElement("div");
24
+ const nativeEvent = new PointerEvent(type, {
25
+ clientX: x,
26
+ clientY: y,
27
+ pointerId,
28
+ pointerType,
29
+ });
30
+
31
+ return {
32
+ clientX: x,
33
+ clientY: y,
34
+ pointerId,
35
+ pointerType,
36
+ isPrimary: true,
37
+ button: 0,
38
+ preventDefault: noop,
39
+ stopPropagation: noop,
40
+ // Event target
41
+ target: element,
42
+ currentTarget: element,
43
+ // Native event
44
+ nativeEvent,
45
+ // SyntheticEvent properties
46
+ bubbles: true,
47
+ cancelable: true,
48
+ defaultPrevented: false,
49
+ eventPhase: 0,
50
+ isTrusted: true,
51
+ isDefaultPrevented: noopBool,
52
+ isPropagationStopped: noopBool,
53
+ persist: noop,
54
+ timeStamp: Date.now(),
55
+ type,
56
+ // MouseEvent properties
57
+ altKey: false,
58
+ buttons: 1,
59
+ ctrlKey: false,
60
+ metaKey: false,
61
+ shiftKey: false,
62
+ getModifierState: noopBool,
63
+ movementX: 0,
64
+ movementY: 0,
65
+ pageX: x,
66
+ pageY: y,
67
+ relatedTarget: null,
68
+ screenX: x,
69
+ screenY: y,
70
+ // PointerEvent properties
71
+ height: 1,
72
+ pressure: 0.5,
73
+ tangentialPressure: 0,
74
+ tiltX: 0,
75
+ tiltY: 0,
76
+ twist: 0,
77
+ width: 1,
78
+ // UIEvent properties
79
+ detail: 0,
80
+ view: window as unknown as React.AbstractView,
81
+ };
82
+ }
83
+
9
84
  /**
10
85
  * 2D point for gesture coordinates.
11
86
  */
@@ -107,10 +182,10 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
107
182
  pointerId = 1,
108
183
  } = options;
109
184
 
110
- let isDown = false;
185
+ const pointerState = { isDown: false };
111
186
 
112
187
  const pointerDown = (x: number, y: number): void => {
113
- isDown = true;
188
+ pointerState.isDown = true;
114
189
 
115
190
  const event = new PointerEvent("pointerdown", {
116
191
  clientX: x,
@@ -129,7 +204,7 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
129
204
  };
130
205
 
131
206
  const pointerMove = (x: number, y: number): void => {
132
- if (!isDown) {
207
+ if (!pointerState.isDown) {
133
208
  return;
134
209
  }
135
210
 
@@ -149,11 +224,11 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
149
224
  };
150
225
 
151
226
  const pointerUp = (): void => {
152
- if (!isDown) {
227
+ if (!pointerState.isDown) {
153
228
  return;
154
229
  }
155
230
 
156
- isDown = false;
231
+ pointerState.isDown = false;
157
232
 
158
233
  const event = new PointerEvent("pointerup", {
159
234
  pointerId,
@@ -188,28 +263,37 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
188
263
  containerSize = 300,
189
264
  ): void => {
190
265
  const edgeOffset = 10; // Start 10px from edge
191
-
192
- let from: Point;
193
- let to: Point;
194
-
195
- switch (edge) {
196
- case "left":
197
- from = { x: edgeOffset, y: containerSize / 2 };
198
- to = { x: edgeOffset + distance, y: containerSize / 2 };
199
- break;
200
- case "right":
201
- from = { x: containerSize - edgeOffset, y: containerSize / 2 };
202
- to = { x: containerSize - edgeOffset - distance, y: containerSize / 2 };
203
- break;
204
- case "top":
205
- from = { x: containerSize / 2, y: edgeOffset };
206
- to = { x: containerSize / 2, y: edgeOffset + distance };
207
- break;
208
- case "bottom":
209
- from = { x: containerSize / 2, y: containerSize - edgeOffset };
210
- to = { x: containerSize / 2, y: containerSize - edgeOffset - distance };
211
- break;
212
- }
266
+ const getEdgeSwipePoints = (
267
+ direction: "left" | "right" | "top" | "bottom",
268
+ size: number,
269
+ travel: number,
270
+ offset: number,
271
+ ): { from: Point; to: Point } => {
272
+ if (direction === "left") {
273
+ return {
274
+ from: { x: offset, y: size / 2 },
275
+ to: { x: offset + travel, y: size / 2 },
276
+ };
277
+ }
278
+ if (direction === "right") {
279
+ return {
280
+ from: { x: size - offset, y: size / 2 },
281
+ to: { x: size - offset - travel, y: size / 2 },
282
+ };
283
+ }
284
+ if (direction === "top") {
285
+ return {
286
+ from: { x: size / 2, y: offset },
287
+ to: { x: size / 2, y: offset + travel },
288
+ };
289
+ }
290
+ return {
291
+ from: { x: size / 2, y: size - offset },
292
+ to: { x: size / 2, y: size - offset - travel },
293
+ };
294
+ };
295
+
296
+ const { from, to } = getEdgeSwipePoints(edge, containerSize, distance, edgeOffset);
213
297
 
214
298
  swipe(from, to);
215
299
  };
@@ -219,16 +303,7 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
219
303
  x: number,
220
304
  y: number,
221
305
  ): React.PointerEvent => {
222
- return {
223
- clientX: x,
224
- clientY: y,
225
- pointerId,
226
- pointerType,
227
- isPrimary: true,
228
- button: 0,
229
- preventDefault: () => {},
230
- stopPropagation: () => {},
231
- } as unknown as React.PointerEvent;
306
+ return createFullPointerEvent(type, x, y, pointerId, pointerType);
232
307
  };
233
308
 
234
309
  return {
@@ -6,7 +6,9 @@
6
6
  * - Input: how to command (swipe, click, keyboard)
7
7
  * - Presentation: how to show (animation, transition)
8
8
  *
9
- * This file defines types for the Input layer.
9
+ * This file defines types for the Input layer, including the abstract
10
+ * ContinuousOperationState that represents any continuous state transition
11
+ * (whether controlled by human gesture or system animation).
10
12
  */
11
13
  import type * as React from "react";
12
14
 
@@ -15,11 +17,6 @@ import type * as React from "react";
15
17
  */
16
18
  export type GestureAxis = "horizontal" | "vertical";
17
19
 
18
- /**
19
- * Phase of swipe input lifecycle.
20
- */
21
- export type SwipeInputPhase = "idle" | "tracking" | "swiping" | "ended";
22
-
23
20
  /**
24
21
  * 2D vector for displacement and velocity.
25
22
  */
@@ -28,6 +25,86 @@ export type Vector2 = {
28
25
  y: number;
29
26
  };
30
27
 
28
+ // ============================================================================
29
+ // Continuous Operation State
30
+ // ============================================================================
31
+ // A continuous operation is any state transition that occurs over time,
32
+ // where progress can be observed incrementally. The controlling agent
33
+ // may be human (gesture) or system (animation).
34
+
35
+ /**
36
+ * Phase of a continuous operation lifecycle.
37
+ * - "idle": No operation in progress
38
+ * - "operating": Operation is in progress (human or system controlled)
39
+ * - "ended": Operation has completed
40
+ */
41
+ export type ContinuousOperationPhase = "idle" | "operating" | "ended";
42
+
43
+ /**
44
+ * State of a continuous operation.
45
+ *
46
+ * This is the abstract representation of any operation that occurs over time,
47
+ * whether controlled by human gesture or system animation. Components that
48
+ * accept this state can respond to both input types uniformly.
49
+ */
50
+ export type ContinuousOperationState = {
51
+ /** Current phase of the operation */
52
+ phase: ContinuousOperationPhase;
53
+ /** Displacement from start position in pixels */
54
+ displacement: Vector2;
55
+ /** Current velocity in pixels per millisecond */
56
+ velocity: Vector2;
57
+ };
58
+
59
+ /**
60
+ * Initial idle state for ContinuousOperationState.
61
+ */
62
+ export const IDLE_CONTINUOUS_OPERATION_STATE: ContinuousOperationState = {
63
+ phase: "idle",
64
+ displacement: { x: 0, y: 0 },
65
+ velocity: { x: 0, y: 0 },
66
+ };
67
+
68
+ /**
69
+ * Convert SwipeInputPhase to ContinuousOperationPhase.
70
+ * - "idle" → "idle"
71
+ * - "tracking" | "swiping" → "operating"
72
+ * - "ended" → "ended"
73
+ */
74
+ export function toContinuousPhase(phase: SwipeInputPhase): ContinuousOperationPhase {
75
+ if (phase === "idle") {
76
+ return "idle";
77
+ }
78
+ if (phase === "ended") {
79
+ return "ended";
80
+ }
81
+ return "operating";
82
+ }
83
+
84
+ /**
85
+ * Convert SwipeInputState to ContinuousOperationState.
86
+ */
87
+ export function toContinuousOperationState(state: SwipeInputState): ContinuousOperationState {
88
+ return {
89
+ phase: toContinuousPhase(state.phase),
90
+ displacement: state.displacement,
91
+ velocity: state.velocity,
92
+ };
93
+ }
94
+
95
+ // ============================================================================
96
+ // Swipe Input (concrete implementation of continuous operation)
97
+ // ============================================================================
98
+
99
+ /**
100
+ * Phase of swipe input lifecycle.
101
+ * - "idle": No swipe in progress
102
+ * - "tracking": Pointer down, tracking movement (direction not yet locked)
103
+ * - "swiping": Direction locked, actively swiping
104
+ * - "ended": Swipe gesture completed
105
+ */
106
+ export type SwipeInputPhase = "idle" | "tracking" | "swiping" | "ended";
107
+
31
108
  /**
32
109
  * Point with timestamp for velocity calculation.
33
110
  */
@@ -1,19 +1,23 @@
1
1
  /**
2
2
  * @file Tests for useEdgeSwipeInput hook.
3
3
  */
4
- /* eslint-disable no-restricted-globals, no-restricted-properties -- test requires vi for timing control */
5
4
  import { renderHook, act } from "@testing-library/react";
6
5
  import * as React from "react";
7
6
  import { useEdgeSwipeInput } from "./useEdgeSwipeInput.js";
8
7
 
9
8
  describe("useEdgeSwipeInput", () => {
10
- beforeEach(() => {
11
- vi.useFakeTimers();
12
- });
9
+ type CallTracker = {
10
+ calls: ReadonlyArray<ReadonlyArray<unknown>>;
11
+ fn: (...args: ReadonlyArray<unknown>) => void;
12
+ };
13
13
 
14
- afterEach(() => {
15
- vi.useRealTimers();
16
- });
14
+ const createCallTracker = (): CallTracker => {
15
+ const calls: Array<ReadonlyArray<unknown>> = [];
16
+ const fn = (...args: ReadonlyArray<unknown>): void => {
17
+ calls.push(args);
18
+ };
19
+ return { calls, fn };
20
+ };
17
21
 
18
22
  const createRef = (rect: Partial<DOMRect> = {}): React.RefObject<HTMLDivElement> => {
19
23
  const element = document.createElement("div");
@@ -28,7 +32,10 @@ describe("useEdgeSwipeInput", () => {
28
32
  y: 0,
29
33
  toJSON: () => ({}),
30
34
  };
31
- vi.spyOn(element, "getBoundingClientRect").mockReturnValue({ ...defaultRect, ...rect });
35
+ const mergedRect = Object.assign({}, defaultRect, rect);
36
+ Object.defineProperty(element, "getBoundingClientRect", {
37
+ value: () => mergedRect,
38
+ });
32
39
  return { current: element };
33
40
  };
34
41
 
@@ -283,7 +290,7 @@ describe("useEdgeSwipeInput", () => {
283
290
  describe("swipe completion callback", () => {
284
291
  it("calls onSwipeEnd when edge swipe threshold is met", () => {
285
292
  const containerRef = createRef({ left: 0, right: 300 });
286
- const onSwipeEnd = vi.fn();
293
+ const onSwipeEnd = createCallTracker();
287
294
 
288
295
  const { result } = renderHook(() =>
289
296
  useEdgeSwipeInput({
@@ -291,7 +298,7 @@ describe("useEdgeSwipeInput", () => {
291
298
  edge: "left",
292
299
  edgeWidth: 20,
293
300
  thresholds: { distanceThreshold: 50, velocityThreshold: 0.3, lockThreshold: 10 },
294
- onSwipeEnd,
301
+ onSwipeEnd: onSwipeEnd.fn,
295
302
  }),
296
303
  );
297
304
 
@@ -327,7 +334,8 @@ describe("useEdgeSwipeInput", () => {
327
334
  document.dispatchEvent(upEvent);
328
335
  });
329
336
 
330
- expect(onSwipeEnd).toHaveBeenCalledWith(
337
+ expect(onSwipeEnd.calls).toHaveLength(1);
338
+ expect(onSwipeEnd.calls[0]?.[0]).toEqual(
331
339
  expect.objectContaining({
332
340
  phase: "ended",
333
341
  direction: 1,
@@ -337,7 +345,7 @@ describe("useEdgeSwipeInput", () => {
337
345
 
338
346
  it("does not call onSwipeEnd when gesture starts outside edge", () => {
339
347
  const containerRef = createRef({ left: 0, right: 300 });
340
- const onSwipeEnd = vi.fn();
348
+ const onSwipeEnd = createCallTracker();
341
349
 
342
350
  const { result } = renderHook(() =>
343
351
  useEdgeSwipeInput({
@@ -345,7 +353,7 @@ describe("useEdgeSwipeInput", () => {
345
353
  edge: "left",
346
354
  edgeWidth: 20,
347
355
  thresholds: { distanceThreshold: 50, velocityThreshold: 0.3, lockThreshold: 10 },
348
- onSwipeEnd,
356
+ onSwipeEnd: onSwipeEnd.fn,
349
357
  }),
350
358
  );
351
359
 
@@ -365,7 +373,7 @@ describe("useEdgeSwipeInput", () => {
365
373
 
366
374
  // The gesture should not be tracked, so no callback should be fired
367
375
  expect(result.current.isEdgeGesture).toBe(false);
368
- expect(onSwipeEnd).not.toHaveBeenCalled();
376
+ expect(onSwipeEnd.calls).toHaveLength(0);
369
377
  });
370
378
  });
371
379
 
@@ -1,11 +1,91 @@
1
1
  /**
2
2
  * @file Tests for useNativeGestureGuard hook.
3
3
  */
4
- /* eslint-disable custom/no-as-outside-guard -- test requires overrides */
5
4
  import { renderHook, act } from "@testing-library/react";
6
5
  import * as React from "react";
7
6
  import { useNativeGestureGuard } from "./useNativeGestureGuard.js";
8
7
 
8
+ /**
9
+ * Extended PointerEvent type with test utility method.
10
+ */
11
+ type TestPointerEvent = React.PointerEvent<HTMLElement> & {
12
+ wasDefaultPrevented: () => boolean;
13
+ };
14
+
15
+ /**
16
+ * Creates a mock PointerEvent that satisfies the React.PointerEvent interface.
17
+ */
18
+ function createMockPointerEvent(props: {
19
+ clientX: number;
20
+ clientY: number;
21
+ pointerType: string;
22
+ }): TestPointerEvent {
23
+ const noop = (): void => {};
24
+ const noopBool = (): boolean => false;
25
+ const state = { preventDefaultCalled: false };
26
+
27
+ const element = document.createElement("div");
28
+ const nativeEvent = new PointerEvent("pointerdown", {
29
+ clientX: props.clientX,
30
+ clientY: props.clientY,
31
+ pointerType: props.pointerType,
32
+ });
33
+
34
+ return {
35
+ ...props,
36
+ // Test utility
37
+ wasDefaultPrevented: () => state.preventDefaultCalled,
38
+ // Event target
39
+ target: element,
40
+ currentTarget: element,
41
+ // Native event
42
+ nativeEvent,
43
+ // SyntheticEvent properties
44
+ bubbles: true,
45
+ cancelable: true,
46
+ defaultPrevented: false,
47
+ eventPhase: 0,
48
+ isTrusted: true,
49
+ preventDefault: () => {
50
+ state.preventDefaultCalled = true;
51
+ },
52
+ isDefaultPrevented: () => state.preventDefaultCalled,
53
+ stopPropagation: noop,
54
+ isPropagationStopped: noopBool,
55
+ persist: noop,
56
+ timeStamp: Date.now(),
57
+ type: "pointerdown",
58
+ // MouseEvent properties
59
+ altKey: false,
60
+ button: 0,
61
+ buttons: 1,
62
+ ctrlKey: false,
63
+ metaKey: false,
64
+ shiftKey: false,
65
+ getModifierState: noopBool,
66
+ movementX: 0,
67
+ movementY: 0,
68
+ pageX: props.clientX,
69
+ pageY: props.clientY,
70
+ relatedTarget: null,
71
+ screenX: props.clientX,
72
+ screenY: props.clientY,
73
+ // PointerEvent properties
74
+ height: 1,
75
+ isPrimary: true,
76
+ pointerId: 1,
77
+ pressure: 0.5,
78
+ tangentialPressure: 0,
79
+ tiltX: 0,
80
+ tiltY: 0,
81
+ twist: 0,
82
+ width: 1,
83
+ // UIEvent properties
84
+ detail: 0,
85
+ view: window,
86
+ };
87
+ }
88
+
9
89
  describe("useNativeGestureGuard", () => {
10
90
  const createRef = (rect: Partial<DOMRect> = {}): React.RefObject<HTMLDivElement> => {
11
91
  const element = document.createElement("div");
@@ -25,26 +105,6 @@ describe("useNativeGestureGuard", () => {
25
105
  return { current: element };
26
106
  };
27
107
 
28
- /**
29
- * Creates a fake pointer event with preventDefault tracking.
30
- * Uses an object to track state instead of let variable.
31
- */
32
- const createFakePointerEvent = (props: {
33
- clientX: number;
34
- clientY: number;
35
- pointerType: string;
36
- }) => {
37
- const state = { preventDefaultCalled: false };
38
- const event = {
39
- ...props,
40
- preventDefault: () => {
41
- state.preventDefaultCalled = true;
42
- },
43
- wasDefaultPrevented: () => state.preventDefaultCalled,
44
- };
45
- return event as unknown as React.PointerEvent<HTMLElement> & { wasDefaultPrevented: () => boolean };
46
- };
47
-
48
108
  describe("overscroll behavior", () => {
49
109
  it("applies overscroll-behavior: contain when active and preventOverscroll is true", () => {
50
110
  const containerRef = createRef();
@@ -99,7 +159,7 @@ describe("useNativeGestureGuard", () => {
99
159
  }),
100
160
  );
101
161
 
102
- const mockEvent = createFakePointerEvent({
162
+ const mockEvent = createMockPointerEvent({
103
163
  clientX: 10, // Within 20px edge zone
104
164
  clientY: 100,
105
165
  pointerType: "touch",
@@ -123,7 +183,7 @@ describe("useNativeGestureGuard", () => {
123
183
  }),
124
184
  );
125
185
 
126
- const mockEvent = createFakePointerEvent({
186
+ const mockEvent = createMockPointerEvent({
127
187
  clientX: 50, // Outside 20px edge zone
128
188
  clientY: 100,
129
189
  pointerType: "touch",
@@ -147,7 +207,7 @@ describe("useNativeGestureGuard", () => {
147
207
  }),
148
208
  );
149
209
 
150
- const mockEvent = createFakePointerEvent({
210
+ const mockEvent = createMockPointerEvent({
151
211
  clientX: 10, // Within edge zone
152
212
  clientY: 100,
153
213
  pointerType: "mouse", // Not touch
@@ -173,7 +233,7 @@ describe("useNativeGestureGuard", () => {
173
233
  }),
174
234
  );
175
235
 
176
- const mockEvent = createFakePointerEvent({
236
+ const mockEvent = createMockPointerEvent({
177
237
  clientX: 10,
178
238
  clientY: 100,
179
239
  pointerType: "touch",
@@ -227,7 +287,7 @@ describe("useNativeGestureGuard", () => {
227
287
  }),
228
288
  );
229
289
 
230
- const mockEventInCustomEdge = createFakePointerEvent({
290
+ const mockEventInCustomEdge = createMockPointerEvent({
231
291
  clientX: 40, // Within 50px edge, but outside default 20px
232
292
  clientY: 100,
233
293
  pointerType: "touch",
@@ -266,7 +326,7 @@ describe("useNativeGestureGuard", () => {
266
326
  }),
267
327
  );
268
328
 
269
- const mockEvent = createFakePointerEvent({
329
+ const mockEvent = createMockPointerEvent({
270
330
  clientX: 10,
271
331
  clientY: 100,
272
332
  pointerType: "touch",
@@ -306,7 +366,7 @@ describe("useNativeGestureGuard", () => {
306
366
 
307
367
  expect(document.documentElement.style.overscrollBehavior).toBe("");
308
368
 
309
- const mockEvent = createFakePointerEvent({
369
+ const mockEvent = createMockPointerEvent({
310
370
  clientX: 10,
311
371
  clientY: 100,
312
372
  pointerType: "touch",
@@ -335,7 +395,7 @@ describe("useNativeGestureGuard", () => {
335
395
  );
336
396
 
337
397
  // Simulate pointerdown to apply style
338
- const mockEvent = createFakePointerEvent({
398
+ const mockEvent = createMockPointerEvent({
339
399
  clientX: 10,
340
400
  clientY: 100,
341
401
  pointerType: "touch",
@@ -369,7 +429,7 @@ describe("useNativeGestureGuard", () => {
369
429
  );
370
430
 
371
431
  // Simulate pointerdown to apply style
372
- const mockEvent = createFakePointerEvent({
432
+ const mockEvent = createMockPointerEvent({
373
433
  clientX: 10,
374
434
  clientY: 100,
375
435
  pointerType: "touch",
@@ -397,7 +457,7 @@ describe("useNativeGestureGuard", () => {
397
457
  }),
398
458
  );
399
459
 
400
- const mockEvent = createFakePointerEvent({
460
+ const mockEvent = createMockPointerEvent({
401
461
  clientX: 10,
402
462
  clientY: 100,
403
463
  pointerType: "touch",
@@ -57,7 +57,9 @@ export function useNativeGestureGuard(options: UseNativeGestureGuardOptions): Us
57
57
 
58
58
  // Apply overscroll-behavior to html synchronously (called from onPointerDown)
59
59
  const applyHtmlOverscroll = React.useCallback(() => {
60
- if (!preventOverscroll) return;
60
+ if (!preventOverscroll) {
61
+ return;
62
+ }
61
63
 
62
64
  const html = document.documentElement;
63
65
  if (previousHtmlOverscrollRef.current === null) {