react-panel-layout 0.6.0 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (258) hide show
  1. package/dist/{FloatingPanelFrame-SgYLc6Ud.js → FloatingPanelFrame-3eU9AwPo.js} +2 -2
  2. package/dist/{FloatingPanelFrame-SgYLc6Ud.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/FloatingWindow-Bw2djgpz.js +1542 -0
  4. package/dist/FloatingWindow-Bw2djgpz.js.map +1 -0
  5. package/dist/FloatingWindow-Cvyokf0m.cjs +2 -0
  6. package/dist/FloatingWindow-Cvyokf0m.cjs.map +1 -0
  7. package/dist/GridLayout-B4aCqSyd.js +947 -0
  8. package/dist/{GridLayout-BltqeCPK.js.map → GridLayout-B4aCqSyd.js.map} +1 -1
  9. package/dist/GridLayout-DNOClFzz.cjs +2 -0
  10. package/dist/{GridLayout-B4VRsC0r.cjs.map → GridLayout-DNOClFzz.cjs.map} +1 -1
  11. package/dist/{HorizontalDivider-WF1k_qND.js → HorizontalDivider-DdxzfV0l.js} +3 -3
  12. package/dist/{HorizontalDivider-WF1k_qND.js.map → HorizontalDivider-DdxzfV0l.js.map} +1 -1
  13. package/dist/{HorizontalDivider-B5Z-KZLk.cjs → HorizontalDivider-_pgV4Mcv.cjs} +2 -2
  14. package/dist/{HorizontalDivider-B5Z-KZLk.cjs.map → HorizontalDivider-_pgV4Mcv.cjs.map} +1 -1
  15. package/dist/PanelSystem-B8Igvnb2.cjs +3 -0
  16. package/dist/PanelSystem-B8Igvnb2.cjs.map +1 -0
  17. package/dist/{PanelSystem-Dr1TBhxM.js → PanelSystem-DDUSFjXD.js} +209 -248
  18. package/dist/PanelSystem-DDUSFjXD.js.map +1 -0
  19. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  20. package/dist/{ResizeHandle-CScipO5l.cjs.map → ResizeHandle-CBcAS918.cjs.map} +1 -1
  21. package/dist/{ResizeHandle-CdA_JYfN.js → ResizeHandle-CXjc1meV.js} +28 -29
  22. package/dist/{ResizeHandle-CdA_JYfN.js.map → ResizeHandle-CXjc1meV.js.map} +1 -1
  23. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  24. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  25. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  26. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  27. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  28. package/dist/components/window/Drawer.d.ts +4 -1
  29. package/dist/components/window/DrawerLayers.d.ts +1 -1
  30. package/dist/components/window/DrawerRevealContext.d.ts +61 -0
  31. package/dist/components/window/drawerRevealAnimationUtils.d.ts +212 -0
  32. package/dist/components/window/drawerStyles.d.ts +74 -0
  33. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  34. package/dist/components/window/useDrawerSwipeTransform.d.ts +29 -0
  35. package/dist/components/window/useDrawerTransform.d.ts +68 -0
  36. package/dist/components/window/useRevealDrawerTransform.d.ts +56 -0
  37. package/dist/config.cjs +1 -1
  38. package/dist/config.cjs.map +1 -1
  39. package/dist/config.js +9 -8
  40. package/dist/config.js.map +1 -1
  41. package/dist/constants/styles.d.ts +17 -0
  42. package/dist/dialog/index.d.ts +69 -0
  43. package/dist/floating.js +1 -1
  44. package/dist/grid.cjs +1 -1
  45. package/dist/grid.js +2 -2
  46. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +7 -0
  47. package/dist/hooks/gesture/types.d.ts +48 -5
  48. package/dist/hooks/gesture/utils.d.ts +19 -0
  49. package/dist/hooks/useAnimationFrame.d.ts +2 -0
  50. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  51. package/dist/hooks/useResizeObserver.d.ts +33 -1
  52. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  53. package/dist/hooks/useSwipeContentTransform.d.ts +9 -2
  54. package/dist/index.cjs +1 -1
  55. package/dist/index.js +7 -7
  56. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  57. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  58. package/dist/modules/dialog/Modal.d.ts +26 -0
  59. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  60. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  61. package/dist/modules/dialog/types.d.ts +183 -0
  62. package/dist/modules/dialog/useDialog.d.ts +39 -0
  63. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  64. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  65. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  66. package/dist/modules/drawer/drawerStateMachine.d.ts +168 -0
  67. package/dist/modules/drawer/revealDrawerConstants.d.ts +33 -0
  68. package/dist/modules/drawer/revealDrawerStateMachine.d.ts +146 -0
  69. package/dist/modules/drawer/strategies/index.d.ts +8 -0
  70. package/dist/modules/drawer/strategies/overlayStrategy.d.ts +12 -0
  71. package/dist/modules/drawer/strategies/revealStrategy.d.ts +12 -0
  72. package/dist/modules/drawer/strategies/types.d.ts +116 -0
  73. package/dist/modules/drawer/types.d.ts +74 -0
  74. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  75. package/dist/modules/pivot/SwipePivotTabBar.d.ts +3 -0
  76. package/dist/modules/stack/SwipeStackContent.d.ts +6 -3
  77. package/dist/modules/stack/SwipeStackOutlet.d.ts +4 -4
  78. package/dist/modules/stack/computeSwipeStackTransform.d.ts +1 -1
  79. package/dist/panels.cjs +1 -1
  80. package/dist/panels.js +1 -1
  81. package/dist/pivot.cjs +1 -1
  82. package/dist/pivot.js +1 -1
  83. package/dist/resizer.cjs +1 -1
  84. package/dist/resizer.js +2 -2
  85. package/dist/stack.cjs +1 -1
  86. package/dist/stack.cjs.map +1 -1
  87. package/dist/stack.js +480 -780
  88. package/dist/stack.js.map +1 -1
  89. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  90. package/dist/sticky-header.cjs +1 -1
  91. package/dist/sticky-header.cjs.map +1 -1
  92. package/dist/sticky-header.js +59 -51
  93. package/dist/sticky-header.js.map +1 -1
  94. package/dist/{styles-DPPuJ0sf.js → styles-NkjuMOVS.js} +13 -13
  95. package/dist/{styles-DPPuJ0sf.js.map → styles-NkjuMOVS.js.map} +1 -1
  96. package/dist/styles-qf6ptVLD.cjs.map +1 -1
  97. package/dist/types.d.ts +30 -0
  98. package/dist/useAnimationFrame-BZ6D2lMq.cjs +2 -0
  99. package/dist/useAnimationFrame-BZ6D2lMq.cjs.map +1 -0
  100. package/dist/useAnimationFrame-Bg4e-H8O.js +394 -0
  101. package/dist/useAnimationFrame-Bg4e-H8O.js.map +1 -0
  102. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  103. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  104. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  105. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  106. package/dist/window/index.d.ts +2 -0
  107. package/dist/window.cjs +1 -1
  108. package/dist/window.cjs.map +1 -1
  109. package/dist/window.js +114 -103
  110. package/dist/window.js.map +1 -1
  111. package/package.json +6 -1
  112. package/src/components/gesture/SwipeSafeZone.tsx +70 -0
  113. package/src/components/grid/GridLayout.tsx +110 -38
  114. package/src/components/window/Drawer.tsx +353 -162
  115. package/src/components/window/DrawerLayers.tsx +54 -11
  116. package/src/components/window/DrawerRevealContext.spec.ts +20 -0
  117. package/src/components/window/DrawerRevealContext.tsx +99 -0
  118. package/src/components/window/drawerRevealAnimationUtils.spec.ts +375 -0
  119. package/src/components/window/drawerRevealAnimationUtils.ts +415 -0
  120. package/src/components/window/drawerStyles.spec.ts +302 -0
  121. package/src/components/window/drawerStyles.ts +252 -0
  122. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  123. package/src/components/window/drawerSwipeConfig.ts +112 -0
  124. package/src/components/window/useDrawerSwipeTransform.ts +67 -0
  125. package/src/components/window/useDrawerTransform.ts +505 -0
  126. package/src/components/window/useRevealDrawerTransform.spec.ts +1936 -0
  127. package/src/components/window/useRevealDrawerTransform.ts +105 -0
  128. package/src/constants/styles.ts +19 -0
  129. package/src/demo/components/FullscreenDemoPage.tsx +47 -0
  130. package/src/demo/fullscreenRoutes.tsx +32 -0
  131. package/src/demo/index.tsx +5 -0
  132. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  133. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  134. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  135. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  136. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +219 -0
  137. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  138. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  139. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  140. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  141. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  142. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  143. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  144. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  145. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +6 -1
  146. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +14 -4
  147. package/src/demo/pages/Drawer/components/DrawerReveal.module.css +157 -0
  148. package/src/demo/pages/Drawer/components/DrawerReveal.tsx +128 -0
  149. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  150. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  151. package/src/demo/pages/Drawer/reveal/index.tsx +17 -0
  152. package/src/demo/pages/Drawer/reveal-fullscreen/index.tsx +135 -0
  153. package/src/demo/pages/Drawer/reveal-fullscreen/styles.module.css +233 -0
  154. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  155. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +54 -23
  156. package/src/demo/pages/Pivot/swipe-debug/index.tsx +1 -1
  157. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +156 -0
  158. package/src/demo/pages/Stack/components/StackBasics.tsx +179 -95
  159. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +110 -0
  160. package/src/demo/pages/Stack/components/StackTablet.tsx +42 -21
  161. package/src/demo/routes.tsx +24 -1
  162. package/src/dialog/index.ts +85 -0
  163. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +68 -64
  164. package/src/hooks/gesture/testing/createGestureSimulator.ts +113 -37
  165. package/src/hooks/gesture/types.ts +83 -6
  166. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +22 -14
  167. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +99 -31
  168. package/src/hooks/gesture/useNativeGestureGuard.ts +3 -1
  169. package/src/hooks/gesture/utils.ts +102 -0
  170. package/src/hooks/useAnimatedVisibility.spec.ts +44 -24
  171. package/src/hooks/useAnimatedVisibility.ts +28 -2
  172. package/src/hooks/useAnimationFrame.ts +8 -0
  173. package/src/hooks/useOperationContinuity.spec.ts +394 -0
  174. package/src/hooks/useOperationContinuity.ts +135 -0
  175. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  176. package/src/hooks/useResizeObserver.tsx +108 -39
  177. package/src/hooks/useScrollContainer.ts +4 -10
  178. package/src/hooks/useSharedElementTransition.ts +354 -0
  179. package/src/hooks/useSwipeContentTransform.spec.ts +18 -18
  180. package/src/hooks/useSwipeContentTransform.ts +166 -28
  181. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  182. package/src/modules/dialog/AlertDialog.tsx +221 -0
  183. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  184. package/src/modules/dialog/DialogContainer.tsx +188 -0
  185. package/src/modules/dialog/Modal.spec.tsx +220 -0
  186. package/src/modules/dialog/Modal.tsx +182 -0
  187. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  188. package/src/modules/dialog/dialogAnimationUtils.spec.ts +252 -0
  189. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  190. package/src/modules/dialog/types.ts +186 -0
  191. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  192. package/src/modules/dialog/useDialog.ts +214 -0
  193. package/src/modules/dialog/useDialogContainer.spec.ts +339 -0
  194. package/src/modules/dialog/useDialogContainer.ts +150 -0
  195. package/src/modules/dialog/useDialogSwipeInput.spec.ts +178 -0
  196. package/src/modules/dialog/useDialogSwipeInput.ts +350 -0
  197. package/src/modules/dialog/useDialogTransform.spec.ts +403 -0
  198. package/src/modules/dialog/useDialogTransform.ts +407 -0
  199. package/src/modules/drawer/drawerStateMachine.ts +500 -0
  200. package/src/modules/drawer/revealDrawerConstants.ts +38 -0
  201. package/src/modules/drawer/revealDrawerStateMachine.spec.ts +558 -0
  202. package/src/modules/drawer/revealDrawerStateMachine.ts +197 -0
  203. package/src/modules/drawer/strategies/index.ts +9 -0
  204. package/src/modules/drawer/strategies/overlayStrategy.ts +133 -0
  205. package/src/modules/drawer/strategies/revealStrategy.ts +111 -0
  206. package/src/modules/drawer/strategies/types.ts +160 -0
  207. package/src/modules/drawer/types.ts +102 -0
  208. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  209. package/src/modules/drawer/useDrawerSwipeInput.ts +402 -0
  210. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +21 -14
  211. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +12 -8
  212. package/src/modules/pivot/SwipePivotContent.spec.tsx +66 -25
  213. package/src/modules/pivot/SwipePivotContent.tsx +2 -2
  214. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +85 -68
  215. package/src/modules/pivot/SwipePivotTabBar.tsx +75 -15
  216. package/src/modules/pivot/scaleInputState.spec.ts +11 -2
  217. package/src/modules/pivot/usePivot.spec.ts +17 -3
  218. package/src/modules/pivot/usePivotSwipeInput.spec.ts +182 -123
  219. package/src/modules/stack/SwipeStackContent.spec.tsx +387 -100
  220. package/src/modules/stack/SwipeStackContent.tsx +43 -33
  221. package/src/modules/stack/SwipeStackOutlet.spec.tsx +14 -16
  222. package/src/modules/stack/SwipeStackOutlet.tsx +6 -6
  223. package/src/modules/stack/computeSwipeStackTransform.spec.ts +5 -5
  224. package/src/modules/stack/computeSwipeStackTransform.ts +3 -3
  225. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  226. package/src/modules/stack/useStackAnimationState.spec.ts +3 -1
  227. package/src/modules/stack/useStackAnimationState.ts +18 -13
  228. package/src/modules/stack/useStackNavigation.spec.ts +198 -3
  229. package/src/modules/stack/useStackNavigation.tsx +113 -56
  230. package/src/modules/stack/useStackSwipeInput.spec.ts +65 -32
  231. package/src/modules/stack/useStackSwipeInput.ts +1 -1
  232. package/src/sticky-header/StickyArea.tsx +29 -57
  233. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  234. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  235. package/src/types.ts +33 -0
  236. package/src/window/index.ts +2 -0
  237. package/dist/FloatingWindow-BpdOpg_L.js +0 -400
  238. package/dist/FloatingWindow-BpdOpg_L.js.map +0 -1
  239. package/dist/FloatingWindow-TCDNY5gE.cjs +0 -2
  240. package/dist/FloatingWindow-TCDNY5gE.cjs.map +0 -1
  241. package/dist/GridLayout-B4VRsC0r.cjs +0 -2
  242. package/dist/GridLayout-BltqeCPK.js +0 -927
  243. package/dist/PanelSystem-Bs8bQwQF.cjs +0 -3
  244. package/dist/PanelSystem-Bs8bQwQF.cjs.map +0 -1
  245. package/dist/PanelSystem-Dr1TBhxM.js.map +0 -1
  246. package/dist/ResizeHandle-CScipO5l.cjs +0 -2
  247. package/dist/SwipePivotTabBar-BGO9X94m.js +0 -407
  248. package/dist/SwipePivotTabBar-BGO9X94m.js.map +0 -1
  249. package/dist/SwipePivotTabBar-BrQismcZ.cjs +0 -2
  250. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +0 -1
  251. package/dist/useDocumentPointerEvents-CKdhGXd0.js +0 -46
  252. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +0 -1
  253. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +0 -2
  254. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +0 -1
  255. package/dist/useEffectEvent-Dp7HLCf0.js +0 -13
  256. package/dist/useEffectEvent-Dp7HLCf0.js.map +0 -1
  257. package/dist/useEffectEvent-huSsGUnl.cjs +0 -2
  258. package/dist/useEffectEvent-huSsGUnl.cjs.map +0 -1
@@ -1,158 +1,162 @@
1
1
  /**
2
2
  * @file Tests for gesture simulator utilities.
3
3
  */
4
- /* eslint-disable no-restricted-globals, no-restricted-properties -- test utilities need vi for timing and mocks */
5
4
  import {
6
5
  createGestureSimulator,
7
6
  createMockContainer,
8
7
  createMockContainerRef,
9
8
  } from "./createGestureSimulator.js";
10
9
 
11
- describe("createGestureSimulator", () => {
12
- beforeEach(() => {
13
- vi.useFakeTimers();
14
- });
10
+ type EventTracker = {
11
+ calls: ReadonlyArray<PointerEvent>;
12
+ handler: (event: Event) => void;
13
+ };
15
14
 
16
- afterEach(() => {
17
- vi.useRealTimers();
18
- });
15
+ const createEventTracker = (): EventTracker => {
16
+ const calls: PointerEvent[] = [];
17
+ const handler = (event: Event): void => {
18
+ calls.push(event as PointerEvent);
19
+ };
20
+ return { calls, handler };
21
+ };
19
22
 
23
+ describe("createGestureSimulator", () => {
20
24
  describe("pointerDown", () => {
21
25
  it("dispatches pointerdown event", () => {
22
26
  const simulator = createGestureSimulator();
23
- const handler = vi.fn();
24
- document.addEventListener("pointerdown", handler);
27
+ const tracker = createEventTracker();
28
+ document.addEventListener("pointerdown", tracker.handler);
25
29
 
26
30
  simulator.pointerDown(100, 200);
27
31
 
28
- expect(handler).toHaveBeenCalledTimes(1);
29
- const event = handler.mock.calls[0][0] as PointerEvent;
32
+ expect(tracker.calls).toHaveLength(1);
33
+ const event = tracker.calls[0] as PointerEvent;
30
34
  expect(event.clientX).toBe(100);
31
35
  expect(event.clientY).toBe(200);
32
36
  expect(event.pointerType).toBe("touch");
33
37
  expect(event.pointerId).toBe(1);
34
38
 
35
- document.removeEventListener("pointerdown", handler);
39
+ document.removeEventListener("pointerdown", tracker.handler);
36
40
  });
37
41
  });
38
42
 
39
43
  describe("pointerMove", () => {
40
44
  it("dispatches pointermove event after pointerdown", () => {
41
45
  const simulator = createGestureSimulator();
42
- const handler = vi.fn();
43
- document.addEventListener("pointermove", handler);
46
+ const tracker = createEventTracker();
47
+ document.addEventListener("pointermove", tracker.handler);
44
48
 
45
49
  simulator.pointerDown(100, 100);
46
50
  simulator.pointerMove(150, 120);
47
51
 
48
- expect(handler).toHaveBeenCalledTimes(1);
49
- const event = handler.mock.calls[0][0] as PointerEvent;
52
+ expect(tracker.calls).toHaveLength(1);
53
+ const event = tracker.calls[0] as PointerEvent;
50
54
  expect(event.clientX).toBe(150);
51
55
  expect(event.clientY).toBe(120);
52
56
 
53
- document.removeEventListener("pointermove", handler);
57
+ document.removeEventListener("pointermove", tracker.handler);
54
58
  });
55
59
 
56
60
  it("does not dispatch pointermove if pointer is not down", () => {
57
61
  const simulator = createGestureSimulator();
58
- const handler = vi.fn();
59
- document.addEventListener("pointermove", handler);
62
+ const tracker = createEventTracker();
63
+ document.addEventListener("pointermove", tracker.handler);
60
64
 
61
65
  simulator.pointerMove(150, 120);
62
66
 
63
- expect(handler).not.toHaveBeenCalled();
67
+ expect(tracker.calls).toHaveLength(0);
64
68
 
65
- document.removeEventListener("pointermove", handler);
69
+ document.removeEventListener("pointermove", tracker.handler);
66
70
  });
67
71
  });
68
72
 
69
73
  describe("pointerUp", () => {
70
74
  it("dispatches pointerup event", () => {
71
75
  const simulator = createGestureSimulator();
72
- const handler = vi.fn();
73
- document.addEventListener("pointerup", handler);
76
+ const tracker = createEventTracker();
77
+ document.addEventListener("pointerup", tracker.handler);
74
78
 
75
79
  simulator.pointerDown(100, 100);
76
80
  simulator.pointerUp();
77
81
 
78
- expect(handler).toHaveBeenCalledTimes(1);
82
+ expect(tracker.calls).toHaveLength(1);
79
83
 
80
- document.removeEventListener("pointerup", handler);
84
+ document.removeEventListener("pointerup", tracker.handler);
81
85
  });
82
86
 
83
87
  it("does not dispatch pointerup if pointer is not down", () => {
84
88
  const simulator = createGestureSimulator();
85
- const handler = vi.fn();
86
- document.addEventListener("pointerup", handler);
89
+ const tracker = createEventTracker();
90
+ document.addEventListener("pointerup", tracker.handler);
87
91
 
88
92
  simulator.pointerUp();
89
93
 
90
- expect(handler).not.toHaveBeenCalled();
94
+ expect(tracker.calls).toHaveLength(0);
91
95
 
92
- document.removeEventListener("pointerup", handler);
96
+ document.removeEventListener("pointerup", tracker.handler);
93
97
  });
94
98
  });
95
99
 
96
100
  describe("swipe", () => {
97
101
  it("dispatches complete swipe sequence", () => {
98
102
  const simulator = createGestureSimulator();
99
- const downHandler = vi.fn();
100
- const moveHandler = vi.fn();
101
- const upHandler = vi.fn();
103
+ const downTracker = createEventTracker();
104
+ const moveTracker = createEventTracker();
105
+ const upTracker = createEventTracker();
102
106
 
103
- document.addEventListener("pointerdown", downHandler);
104
- document.addEventListener("pointermove", moveHandler);
105
- document.addEventListener("pointerup", upHandler);
107
+ document.addEventListener("pointerdown", downTracker.handler);
108
+ document.addEventListener("pointermove", moveTracker.handler);
109
+ document.addEventListener("pointerup", upTracker.handler);
106
110
 
107
111
  simulator.swipe({ x: 100, y: 100 }, { x: 200, y: 100 }, 5);
108
112
 
109
- expect(downHandler).toHaveBeenCalledTimes(1);
110
- expect(moveHandler).toHaveBeenCalledTimes(5); // 5 steps
111
- expect(upHandler).toHaveBeenCalledTimes(1);
113
+ expect(downTracker.calls).toHaveLength(1);
114
+ expect(moveTracker.calls).toHaveLength(5); // 5 steps
115
+ expect(upTracker.calls).toHaveLength(1);
112
116
 
113
117
  // Check intermediate positions
114
- const lastMove = moveHandler.mock.calls[4][0] as PointerEvent;
118
+ const lastMove = moveTracker.calls[4] as PointerEvent;
115
119
  expect(lastMove.clientX).toBe(200);
116
120
  expect(lastMove.clientY).toBe(100);
117
121
 
118
- document.removeEventListener("pointerdown", downHandler);
119
- document.removeEventListener("pointermove", moveHandler);
120
- document.removeEventListener("pointerup", upHandler);
122
+ document.removeEventListener("pointerdown", downTracker.handler);
123
+ document.removeEventListener("pointermove", moveTracker.handler);
124
+ document.removeEventListener("pointerup", upTracker.handler);
121
125
  });
122
126
  });
123
127
 
124
128
  describe("edgeSwipe", () => {
125
129
  it("simulates left edge swipe", () => {
126
130
  const simulator = createGestureSimulator();
127
- const downHandler = vi.fn();
128
- const upHandler = vi.fn();
131
+ const downTracker = createEventTracker();
132
+ const upTracker = createEventTracker();
129
133
 
130
- document.addEventListener("pointerdown", downHandler);
131
- document.addEventListener("pointerup", upHandler);
134
+ document.addEventListener("pointerdown", downTracker.handler);
135
+ document.addEventListener("pointerup", upTracker.handler);
132
136
 
133
137
  simulator.edgeSwipe("left", 100, 300);
134
138
 
135
- expect(downHandler).toHaveBeenCalledTimes(1);
136
- const downEvent = downHandler.mock.calls[0][0] as PointerEvent;
139
+ expect(downTracker.calls).toHaveLength(1);
140
+ const downEvent = downTracker.calls[0] as PointerEvent;
137
141
  expect(downEvent.clientX).toBe(10); // Edge offset
138
- expect(upHandler).toHaveBeenCalledTimes(1);
142
+ expect(upTracker.calls).toHaveLength(1);
139
143
 
140
- document.removeEventListener("pointerdown", downHandler);
141
- document.removeEventListener("pointerup", upHandler);
144
+ document.removeEventListener("pointerdown", downTracker.handler);
145
+ document.removeEventListener("pointerup", upTracker.handler);
142
146
  });
143
147
 
144
148
  it("simulates right edge swipe", () => {
145
149
  const simulator = createGestureSimulator();
146
- const downHandler = vi.fn();
150
+ const downTracker = createEventTracker();
147
151
 
148
- document.addEventListener("pointerdown", downHandler);
152
+ document.addEventListener("pointerdown", downTracker.handler);
149
153
 
150
154
  simulator.edgeSwipe("right", 100, 300);
151
155
 
152
- const downEvent = downHandler.mock.calls[0][0] as PointerEvent;
156
+ const downEvent = downTracker.calls[0] as PointerEvent;
153
157
  expect(downEvent.clientX).toBe(290); // containerSize - edgeOffset
154
158
 
155
- document.removeEventListener("pointerdown", downHandler);
159
+ document.removeEventListener("pointerdown", downTracker.handler);
156
160
  });
157
161
  });
158
162
 
@@ -172,28 +176,28 @@ describe("createGestureSimulator", () => {
172
176
  describe("options", () => {
173
177
  it("respects custom pointer type", () => {
174
178
  const simulator = createGestureSimulator({ pointerType: "mouse" });
175
- const handler = vi.fn();
176
- document.addEventListener("pointerdown", handler);
179
+ const tracker = createEventTracker();
180
+ document.addEventListener("pointerdown", tracker.handler);
177
181
 
178
182
  simulator.pointerDown(100, 100);
179
183
 
180
- const event = handler.mock.calls[0][0] as PointerEvent;
184
+ const event = tracker.calls[0] as PointerEvent;
181
185
  expect(event.pointerType).toBe("mouse");
182
186
 
183
- document.removeEventListener("pointerdown", handler);
187
+ document.removeEventListener("pointerdown", tracker.handler);
184
188
  });
185
189
 
186
190
  it("respects custom pointer ID", () => {
187
191
  const simulator = createGestureSimulator({ pointerId: 5 });
188
- const handler = vi.fn();
189
- document.addEventListener("pointerdown", handler);
192
+ const tracker = createEventTracker();
193
+ document.addEventListener("pointerdown", tracker.handler);
190
194
 
191
195
  simulator.pointerDown(100, 100);
192
196
 
193
- const event = handler.mock.calls[0][0] as PointerEvent;
197
+ const event = tracker.calls[0] as PointerEvent;
194
198
  expect(event.pointerId).toBe(5);
195
199
 
196
- document.removeEventListener("pointerdown", handler);
200
+ document.removeEventListener("pointerdown", tracker.handler);
197
201
  });
198
202
  });
199
203
  });
@@ -4,8 +4,84 @@
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
+ // eslint-disable-next-line custom/no-as-outside-guard -- Required for React types compatibility
81
+ view: window as unknown as React.AbstractView,
82
+ };
83
+ }
84
+
9
85
  /**
10
86
  * 2D point for gesture coordinates.
11
87
  */
@@ -107,10 +183,10 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
107
183
  pointerId = 1,
108
184
  } = options;
109
185
 
110
- let isDown = false;
186
+ const pointerState = { isDown: false };
111
187
 
112
188
  const pointerDown = (x: number, y: number): void => {
113
- isDown = true;
189
+ pointerState.isDown = true;
114
190
 
115
191
  const event = new PointerEvent("pointerdown", {
116
192
  clientX: x,
@@ -129,7 +205,7 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
129
205
  };
130
206
 
131
207
  const pointerMove = (x: number, y: number): void => {
132
- if (!isDown) {
208
+ if (!pointerState.isDown) {
133
209
  return;
134
210
  }
135
211
 
@@ -149,11 +225,11 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
149
225
  };
150
226
 
151
227
  const pointerUp = (): void => {
152
- if (!isDown) {
228
+ if (!pointerState.isDown) {
153
229
  return;
154
230
  }
155
231
 
156
- isDown = false;
232
+ pointerState.isDown = false;
157
233
 
158
234
  const event = new PointerEvent("pointerup", {
159
235
  pointerId,
@@ -188,28 +264,37 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
188
264
  containerSize = 300,
189
265
  ): void => {
190
266
  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
- }
267
+ const getEdgeSwipePoints = (
268
+ direction: "left" | "right" | "top" | "bottom",
269
+ size: number,
270
+ travel: number,
271
+ offset: number,
272
+ ): { from: Point; to: Point } => {
273
+ if (direction === "left") {
274
+ return {
275
+ from: { x: offset, y: size / 2 },
276
+ to: { x: offset + travel, y: size / 2 },
277
+ };
278
+ }
279
+ if (direction === "right") {
280
+ return {
281
+ from: { x: size - offset, y: size / 2 },
282
+ to: { x: size - offset - travel, y: size / 2 },
283
+ };
284
+ }
285
+ if (direction === "top") {
286
+ return {
287
+ from: { x: size / 2, y: offset },
288
+ to: { x: size / 2, y: offset + travel },
289
+ };
290
+ }
291
+ return {
292
+ from: { x: size / 2, y: size - offset },
293
+ to: { x: size / 2, y: size - offset - travel },
294
+ };
295
+ };
296
+
297
+ const { from, to } = getEdgeSwipePoints(edge, containerSize, distance, edgeOffset);
213
298
 
214
299
  swipe(from, to);
215
300
  };
@@ -219,16 +304,7 @@ export function createGestureSimulator(options: GestureSimulatorOptions = {}): G
219
304
  x: number,
220
305
  y: number,
221
306
  ): 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;
307
+ return createFullPointerEvent(type, x, y, pointerId, pointerType);
232
308
  };
233
309
 
234
310
  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