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,11 +1,99 @@
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
+ * Create a mock view for test events.
17
+ */
18
+ function createMockView(): React.PointerEvent<HTMLElement>["view"] {
19
+ // eslint-disable-next-line custom/no-as-outside-guard -- test helper for view casting
20
+ return window as unknown as React.PointerEvent<HTMLElement>["view"];
21
+ }
22
+
23
+ /**
24
+ * Creates a mock PointerEvent that satisfies the React.PointerEvent interface.
25
+ */
26
+ function createMockPointerEvent(props: {
27
+ clientX: number;
28
+ clientY: number;
29
+ pointerType: "mouse" | "touch" | "pen";
30
+ }): TestPointerEvent {
31
+ const noop = (): void => {};
32
+ const noopBool = (): boolean => false;
33
+ const state = { preventDefaultCalled: false };
34
+
35
+ const element = document.createElement("div");
36
+ const nativeEvent = new PointerEvent("pointerdown", {
37
+ clientX: props.clientX,
38
+ clientY: props.clientY,
39
+ pointerType: props.pointerType,
40
+ });
41
+
42
+ return {
43
+ ...props,
44
+ // Test utility
45
+ wasDefaultPrevented: () => state.preventDefaultCalled,
46
+ // Event target
47
+ target: element,
48
+ currentTarget: element,
49
+ // Native event
50
+ nativeEvent,
51
+ // SyntheticEvent properties
52
+ bubbles: true,
53
+ cancelable: true,
54
+ defaultPrevented: false,
55
+ eventPhase: 0,
56
+ isTrusted: true,
57
+ preventDefault: () => {
58
+ state.preventDefaultCalled = true;
59
+ },
60
+ isDefaultPrevented: () => state.preventDefaultCalled,
61
+ stopPropagation: noop,
62
+ isPropagationStopped: noopBool,
63
+ persist: noop,
64
+ timeStamp: Date.now(),
65
+ type: "pointerdown",
66
+ // MouseEvent properties
67
+ altKey: false,
68
+ button: 0,
69
+ buttons: 1,
70
+ ctrlKey: false,
71
+ metaKey: false,
72
+ shiftKey: false,
73
+ getModifierState: noopBool,
74
+ movementX: 0,
75
+ movementY: 0,
76
+ pageX: props.clientX,
77
+ pageY: props.clientY,
78
+ relatedTarget: null,
79
+ screenX: props.clientX,
80
+ screenY: props.clientY,
81
+ // PointerEvent properties
82
+ height: 1,
83
+ isPrimary: true,
84
+ pointerId: 1,
85
+ pressure: 0.5,
86
+ tangentialPressure: 0,
87
+ tiltX: 0,
88
+ tiltY: 0,
89
+ twist: 0,
90
+ width: 1,
91
+ // UIEvent properties
92
+ detail: 0,
93
+ view: createMockView(),
94
+ };
95
+ }
96
+
9
97
  describe("useNativeGestureGuard", () => {
10
98
  const createRef = (rect: Partial<DOMRect> = {}): React.RefObject<HTMLDivElement> => {
11
99
  const element = document.createElement("div");
@@ -25,26 +113,6 @@ describe("useNativeGestureGuard", () => {
25
113
  return { current: element };
26
114
  };
27
115
 
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
116
  describe("overscroll behavior", () => {
49
117
  it("applies overscroll-behavior: contain when active and preventOverscroll is true", () => {
50
118
  const containerRef = createRef();
@@ -99,7 +167,7 @@ describe("useNativeGestureGuard", () => {
99
167
  }),
100
168
  );
101
169
 
102
- const mockEvent = createFakePointerEvent({
170
+ const mockEvent = createMockPointerEvent({
103
171
  clientX: 10, // Within 20px edge zone
104
172
  clientY: 100,
105
173
  pointerType: "touch",
@@ -123,7 +191,7 @@ describe("useNativeGestureGuard", () => {
123
191
  }),
124
192
  );
125
193
 
126
- const mockEvent = createFakePointerEvent({
194
+ const mockEvent = createMockPointerEvent({
127
195
  clientX: 50, // Outside 20px edge zone
128
196
  clientY: 100,
129
197
  pointerType: "touch",
@@ -147,7 +215,7 @@ describe("useNativeGestureGuard", () => {
147
215
  }),
148
216
  );
149
217
 
150
- const mockEvent = createFakePointerEvent({
218
+ const mockEvent = createMockPointerEvent({
151
219
  clientX: 10, // Within edge zone
152
220
  clientY: 100,
153
221
  pointerType: "mouse", // Not touch
@@ -173,7 +241,7 @@ describe("useNativeGestureGuard", () => {
173
241
  }),
174
242
  );
175
243
 
176
- const mockEvent = createFakePointerEvent({
244
+ const mockEvent = createMockPointerEvent({
177
245
  clientX: 10,
178
246
  clientY: 100,
179
247
  pointerType: "touch",
@@ -227,7 +295,7 @@ describe("useNativeGestureGuard", () => {
227
295
  }),
228
296
  );
229
297
 
230
- const mockEventInCustomEdge = createFakePointerEvent({
298
+ const mockEventInCustomEdge = createMockPointerEvent({
231
299
  clientX: 40, // Within 50px edge, but outside default 20px
232
300
  clientY: 100,
233
301
  pointerType: "touch",
@@ -266,7 +334,7 @@ describe("useNativeGestureGuard", () => {
266
334
  }),
267
335
  );
268
336
 
269
- const mockEvent = createFakePointerEvent({
337
+ const mockEvent = createMockPointerEvent({
270
338
  clientX: 10,
271
339
  clientY: 100,
272
340
  pointerType: "touch",
@@ -306,7 +374,7 @@ describe("useNativeGestureGuard", () => {
306
374
 
307
375
  expect(document.documentElement.style.overscrollBehavior).toBe("");
308
376
 
309
- const mockEvent = createFakePointerEvent({
377
+ const mockEvent = createMockPointerEvent({
310
378
  clientX: 10,
311
379
  clientY: 100,
312
380
  pointerType: "touch",
@@ -335,7 +403,7 @@ describe("useNativeGestureGuard", () => {
335
403
  );
336
404
 
337
405
  // Simulate pointerdown to apply style
338
- const mockEvent = createFakePointerEvent({
406
+ const mockEvent = createMockPointerEvent({
339
407
  clientX: 10,
340
408
  clientY: 100,
341
409
  pointerType: "touch",
@@ -369,7 +437,7 @@ describe("useNativeGestureGuard", () => {
369
437
  );
370
438
 
371
439
  // Simulate pointerdown to apply style
372
- const mockEvent = createFakePointerEvent({
440
+ const mockEvent = createMockPointerEvent({
373
441
  clientX: 10,
374
442
  clientY: 100,
375
443
  pointerType: "touch",
@@ -397,7 +465,7 @@ describe("useNativeGestureGuard", () => {
397
465
  }),
398
466
  );
399
467
 
400
- const mockEvent = createFakePointerEvent({
468
+ const mockEvent = createMockPointerEvent({
401
469
  clientX: 10,
402
470
  clientY: 100,
403
471
  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) {
@@ -85,3 +85,105 @@ export const mergeGestureContainerProps = (
85
85
  style: mergedStyle,
86
86
  };
87
87
  };
88
+
89
+ // ============================================================================
90
+ // Scroll Detection Utilities
91
+ // ============================================================================
92
+
93
+ /**
94
+ * Compute scroll size for an element based on axis.
95
+ */
96
+ function computeScrollSize(element: HTMLElement, isHorizontal: boolean): number {
97
+ if (isHorizontal) {
98
+ return element.scrollWidth - element.clientWidth;
99
+ }
100
+ return element.scrollHeight - element.clientHeight;
101
+ }
102
+
103
+ /**
104
+ * Check if an element is scrollable in any direction.
105
+ */
106
+ export function isScrollableElement(element: HTMLElement): boolean {
107
+ const style = getComputedStyle(element);
108
+
109
+ const isScrollableX =
110
+ (style.overflowX === "scroll" || style.overflowX === "auto") &&
111
+ element.scrollWidth > element.clientWidth;
112
+
113
+ const isScrollableY =
114
+ (style.overflowY === "scroll" || style.overflowY === "auto") &&
115
+ element.scrollHeight > element.clientHeight;
116
+
117
+ if (isScrollableX) {
118
+ return true;
119
+ }
120
+ return isScrollableY;
121
+ }
122
+
123
+ /**
124
+ * Check if we should start drag based on scroll state.
125
+ * Returns false if the target is inside a scrollable element.
126
+ */
127
+ export function shouldStartDrag(
128
+ event: React.PointerEvent,
129
+ container: HTMLElement,
130
+ ): boolean {
131
+ // eslint-disable-next-line no-restricted-syntax -- loop variable requires let
132
+ let current = event.target as HTMLElement | null;
133
+
134
+ while (current !== null && current !== container) {
135
+ if (isScrollableElement(current)) {
136
+ return false;
137
+ }
138
+ current = current.parentElement;
139
+ }
140
+
141
+ return true;
142
+ }
143
+
144
+ /**
145
+ * Check if an element or its ancestors are scrollable in the specified direction.
146
+ * Returns true if scrolling is possible and would block the swipe gesture.
147
+ *
148
+ * @param element - The target element to check
149
+ * @param container - The container boundary
150
+ * @param axis - The axis to check ("x" or "y")
151
+ * @param direction - The swipe direction (1 = right/down, -1 = left/up)
152
+ */
153
+ export function isScrollableInDirection(
154
+ element: HTMLElement,
155
+ container: HTMLElement,
156
+ axis: "x" | "y",
157
+ direction: 1 | -1,
158
+ ): boolean {
159
+ // eslint-disable-next-line no-restricted-syntax -- loop variable requires let
160
+ let current: HTMLElement | null = element;
161
+
162
+ while (current !== null && current !== container) {
163
+ const style = getComputedStyle(current);
164
+ const isHorizontal = axis === "x";
165
+
166
+ const overflow = isHorizontal ? style.overflowX : style.overflowY;
167
+ const isScrollable = overflow === "scroll" || overflow === "auto";
168
+
169
+ if (isScrollable) {
170
+ const scrollSize = computeScrollSize(current, isHorizontal);
171
+
172
+ if (scrollSize > 0) {
173
+ const scrollPos = isHorizontal ? current.scrollLeft : current.scrollTop;
174
+
175
+ // If swiping in close direction and not at boundary, block swipe
176
+ if (direction === -1 && scrollPos > 1) {
177
+ return true; // Can scroll left/up, block swipe
178
+ }
179
+ if (direction === 1 && scrollPos < scrollSize - 1) {
180
+ return true; // Can scroll right/down, block swipe
181
+ }
182
+ }
183
+ }
184
+
185
+ current = current.parentElement;
186
+ }
187
+
188
+ return false;
189
+ }
@@ -5,9 +5,46 @@
5
5
  * 1. アニメーションなし → 即座にdisplay:none
6
6
  * 2. アニメーションあり → 完了待ってdisplay:none
7
7
  */
8
+ import type * as React from "react";
8
9
  import { renderHook, act } from "@testing-library/react";
9
10
  import { useAnimatedVisibility } from "./useAnimatedVisibility.js";
10
11
 
12
+ /**
13
+ * Create a mock AnimationEvent for testing.
14
+ */
15
+ function createMockAnimationEvent(
16
+ target: Element,
17
+ currentTarget: Element,
18
+ ): React.AnimationEvent<Element> {
19
+ const noop = (): void => {};
20
+ const noopBool = (): boolean => false;
21
+ const nativeEvent = {
22
+ animationName: "test",
23
+ elapsedTime: 0,
24
+ pseudoElement: "",
25
+ } as AnimationEvent;
26
+ return {
27
+ target,
28
+ currentTarget,
29
+ nativeEvent,
30
+ bubbles: true,
31
+ cancelable: false,
32
+ defaultPrevented: false,
33
+ eventPhase: 0,
34
+ isTrusted: true,
35
+ preventDefault: noop,
36
+ isDefaultPrevented: noopBool,
37
+ stopPropagation: noop,
38
+ isPropagationStopped: noopBool,
39
+ persist: noop,
40
+ timeStamp: Date.now(),
41
+ type: "animationend",
42
+ animationName: "test",
43
+ elapsedTime: 0,
44
+ pseudoElement: "",
45
+ };
46
+ }
47
+
11
48
  describe("useAnimatedVisibility", () => {
12
49
  describe("initial state", () => {
13
50
  it("displays when initially visible", () => {
@@ -120,10 +157,7 @@ describe("useAnimatedVisibility", () => {
120
157
 
121
158
  // Simulate animationend event
122
159
  const sharedElement = document.createElement("div");
123
- const mockEvent = {
124
- target: sharedElement,
125
- currentTarget: sharedElement,
126
- } as unknown as React.AnimationEvent;
160
+ const mockEvent = createMockAnimationEvent(sharedElement, sharedElement);
127
161
 
128
162
  act(() => {
129
163
  result.current.props.onAnimationEnd(mockEvent);
@@ -149,10 +183,7 @@ describe("useAnimatedVisibility", () => {
149
183
  // Simulate animationend from a child element (target !== currentTarget)
150
184
  const parent = document.createElement("div");
151
185
  const child = document.createElement("div");
152
- const mockEvent = {
153
- target: child,
154
- currentTarget: parent,
155
- } as unknown as React.AnimationEvent;
186
+ const mockEvent = createMockAnimationEvent(child, parent);
156
187
 
157
188
  act(() => {
158
189
  result.current.props.onAnimationEnd(mockEvent);
@@ -188,14 +219,12 @@ describe("useAnimatedVisibility", () => {
188
219
 
189
220
  describe("timeout fallback", () => {
190
221
  it("hides after timeout if animationEnd never fires", async () => {
191
- vi.useFakeTimers();
192
-
193
222
  const { result, rerender } = renderHook(
194
223
  ({ isVisible }) =>
195
224
  useAnimatedVisibility({
196
225
  isVisible,
197
226
  leaveAnimation: "fadeOut 200ms ease-out",
198
- animationTimeout: 500,
227
+ animationTimeout: 10,
199
228
  }),
200
229
  { initialProps: { isVisible: true } },
201
230
  );
@@ -206,25 +235,21 @@ describe("useAnimatedVisibility", () => {
206
235
 
207
236
  // Advance time past timeout
208
237
  await act(async () => {
209
- vi.advanceTimersByTime(600);
238
+ await new Promise((resolve) => setTimeout(resolve, 20));
210
239
  });
211
240
 
212
241
  // Should be hidden now (fallback triggered)
213
242
  expect(result.current.style.display).toBe("none");
214
243
  expect(result.current.state.isAnimatingOut).toBe(false);
215
-
216
- vi.useRealTimers();
217
244
  });
218
245
 
219
246
  it("clears timeout when animationEnd fires before timeout", async () => {
220
- vi.useFakeTimers();
221
-
222
247
  const { result, rerender } = renderHook(
223
248
  ({ isVisible }) =>
224
249
  useAnimatedVisibility({
225
250
  isVisible,
226
251
  leaveAnimation: "fadeOut 200ms ease-out",
227
- animationTimeout: 500,
252
+ animationTimeout: 10,
228
253
  }),
229
254
  { initialProps: { isVisible: true } },
230
255
  );
@@ -233,10 +258,7 @@ describe("useAnimatedVisibility", () => {
233
258
 
234
259
  // Fire animationEnd before timeout
235
260
  const sharedElement = document.createElement("div");
236
- const mockEvent = {
237
- target: sharedElement,
238
- currentTarget: sharedElement,
239
- } as unknown as React.AnimationEvent;
261
+ const mockEvent = createMockAnimationEvent(sharedElement, sharedElement);
240
262
 
241
263
  act(() => {
242
264
  result.current.props.onAnimationEnd(mockEvent);
@@ -246,12 +268,10 @@ describe("useAnimatedVisibility", () => {
246
268
 
247
269
  // Advance past timeout - should not affect state
248
270
  await act(async () => {
249
- vi.advanceTimersByTime(600);
271
+ await new Promise((resolve) => setTimeout(resolve, 20));
250
272
  });
251
273
 
252
274
  expect(result.current.style.display).toBe("none");
253
-
254
- vi.useRealTimers();
255
275
  });
256
276
  });
257
277
  });
@@ -72,6 +72,32 @@ export function useAnimatedVisibility({
72
72
  const timeoutRef = React.useRef<ReturnType<typeof setTimeout> | null>(null);
73
73
 
74
74
  // Clear timeout on unmount
75
+ const shouldSkipLeaveAnimation = (
76
+ isSkipped: boolean,
77
+ animation: string | undefined,
78
+ ): boolean => {
79
+ if (isSkipped) {
80
+ return true;
81
+ }
82
+ if (!animation) {
83
+ return true;
84
+ }
85
+ if (animation === "none") {
86
+ return true;
87
+ }
88
+ return false;
89
+ };
90
+
91
+ const getShouldDisplay = (visible: boolean, animatingOut: boolean): boolean => {
92
+ if (visible) {
93
+ return true;
94
+ }
95
+ if (animatingOut) {
96
+ return true;
97
+ }
98
+ return false;
99
+ };
100
+
75
101
  React.useEffect(() => {
76
102
  return () => {
77
103
  if (timeoutRef.current) {
@@ -92,7 +118,7 @@ export function useAnimatedVisibility({
92
118
 
93
119
  if (wasVisible && !isVisible) {
94
120
  // Transitioning from visible to hidden
95
- if (skipAnimation || !leaveAnimation || leaveAnimation === "none") {
121
+ if (shouldSkipLeaveAnimation(skipAnimation, leaveAnimation)) {
96
122
  // No animation, hide immediately
97
123
  setIsAnimatingOut(false);
98
124
  } else {
@@ -129,7 +155,7 @@ export function useAnimatedVisibility({
129
155
  // Element should be displayed if:
130
156
  // - It's visible, OR
131
157
  // - It's animating out (leave animation in progress)
132
- const shouldDisplay = isVisible || isAnimatingOut;
158
+ const shouldDisplay = getShouldDisplay(isVisible, isAnimatingOut);
133
159
 
134
160
  return {
135
161
  state: {
@@ -40,6 +40,14 @@ export const easings = {
40
40
  }
41
41
  return 1 - Math.pow(-2 * t + 2, 3) / 2;
42
42
  },
43
+
44
+ /** Ease in expo (accelerating, for "suck in" effect) */
45
+ easeInExpo: (t: number): number => {
46
+ if (t === 0) {
47
+ return 0;
48
+ }
49
+ return Math.pow(2, 10 * t - 10);
50
+ },
43
51
  } as const;
44
52
 
45
53
  /**