react-panel-layout 0.5.2 → 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 (541) hide show
  1. package/dist/{FloatingPanelFrame-lLg-Lpg7.js → FloatingPanelFrame-3eU9AwPo.js} +11 -11
  2. package/dist/{FloatingPanelFrame-lLg-Lpg7.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
  3. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
  4. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs.map → FloatingPanelFrame-CEmXDvUA.cjs.map} +1 -1
  5. package/dist/FloatingWindow-CUXnEtrb.js +827 -0
  6. package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
  7. package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
  8. package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
  9. package/dist/GridLayout-DKTg_N61.cjs +2 -0
  10. package/dist/GridLayout-DKTg_N61.cjs.map +1 -0
  11. package/dist/GridLayout-UWNxXw77.js +926 -0
  12. package/dist/GridLayout-UWNxXw77.js.map +1 -0
  13. package/dist/HorizontalDivider-DdxzfV0l.js +30 -0
  14. package/dist/HorizontalDivider-DdxzfV0l.js.map +1 -0
  15. package/dist/HorizontalDivider-_pgV4Mcv.cjs +2 -0
  16. package/dist/HorizontalDivider-_pgV4Mcv.cjs.map +1 -0
  17. package/dist/PanelSystem-BqUzNtf2.js +1946 -0
  18. package/dist/PanelSystem-BqUzNtf2.js.map +1 -0
  19. package/dist/PanelSystem-D603LKKv.cjs +3 -0
  20. package/dist/PanelSystem-D603LKKv.cjs.map +1 -0
  21. package/dist/ResizeHandle-CBcAS918.cjs +2 -0
  22. package/dist/ResizeHandle-CBcAS918.cjs.map +1 -0
  23. package/dist/ResizeHandle-CXjc1meV.js +119 -0
  24. package/dist/ResizeHandle-CXjc1meV.js.map +1 -0
  25. package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
  26. package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
  27. package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
  28. package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
  29. package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
  30. package/dist/components/window/Drawer.d.ts +3 -1
  31. package/dist/components/window/DrawerLayers.d.ts +1 -1
  32. package/dist/components/window/drawerStyles.d.ts +69 -0
  33. package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
  34. package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
  35. package/dist/config.cjs +1 -1
  36. package/dist/config.cjs.map +1 -1
  37. package/dist/config.js +11 -9
  38. package/dist/config.js.map +1 -1
  39. package/dist/constants/styles.d.ts +35 -4
  40. package/dist/dialog/index.d.ts +69 -0
  41. package/dist/floating.cjs +1 -1
  42. package/dist/floating.js +1 -1
  43. package/dist/grid/index.d.ts +58 -0
  44. package/dist/grid.cjs +2 -0
  45. package/dist/grid.cjs.map +1 -0
  46. package/dist/grid.js +13 -0
  47. package/dist/grid.js.map +1 -0
  48. package/dist/hooks/gesture/presets.d.ts +33 -0
  49. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +117 -0
  50. package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
  51. package/dist/hooks/gesture/types.d.ts +297 -0
  52. package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
  53. package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
  54. package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
  55. package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
  56. package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
  57. package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
  58. package/dist/hooks/gesture/utils.d.ts +59 -0
  59. package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
  60. package/dist/hooks/useAnimationFrame.d.ts +86 -0
  61. package/dist/hooks/useOperationContinuity.d.ts +64 -0
  62. package/dist/hooks/useResizeObserver.d.ts +33 -1
  63. package/dist/hooks/useSharedElementTransition.d.ts +112 -0
  64. package/dist/hooks/useSnapAnimation.d.ts +54 -0
  65. package/dist/hooks/useSwipeContentTransform.d.ts +86 -0
  66. package/dist/index.cjs +1 -2
  67. package/dist/index.cjs.map +1 -1
  68. package/dist/index.d.ts +0 -1
  69. package/dist/index.js +25 -2006
  70. package/dist/index.js.map +1 -1
  71. package/dist/modules/dialog/AlertDialog.d.ts +9 -0
  72. package/dist/modules/dialog/DialogContainer.d.ts +37 -0
  73. package/dist/modules/dialog/Modal.d.ts +26 -0
  74. package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
  75. package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
  76. package/dist/modules/dialog/types.d.ts +183 -0
  77. package/dist/modules/dialog/useDialog.d.ts +39 -0
  78. package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
  79. package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
  80. package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
  81. package/dist/modules/drawer/types.d.ts +74 -0
  82. package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
  83. package/dist/modules/pivot/PivotContent.d.ts +1 -1
  84. package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
  85. package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
  86. package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
  87. package/dist/modules/pivot/SwipePivotTabBar.d.ts +92 -0
  88. package/dist/modules/pivot/index.d.ts +3 -0
  89. package/dist/modules/pivot/scaleInputState.d.ts +37 -0
  90. package/dist/modules/pivot/types.d.ts +67 -2
  91. package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
  92. package/dist/modules/stack/StackContent.d.ts +15 -0
  93. package/dist/modules/stack/SwipeStackContent.d.ts +66 -0
  94. package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
  95. package/dist/modules/stack/computeStackContentState.d.ts +99 -0
  96. package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
  97. package/dist/modules/stack/types.d.ts +194 -0
  98. package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
  99. package/dist/modules/stack/useStackNavigation.d.ts +23 -0
  100. package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
  101. package/dist/panels/index.d.ts +67 -0
  102. package/dist/panels.cjs +2 -0
  103. package/dist/panels.cjs.map +1 -0
  104. package/dist/panels.js +28 -0
  105. package/dist/panels.js.map +1 -0
  106. package/dist/pivot/index.d.ts +3 -0
  107. package/dist/pivot.cjs +1 -1
  108. package/dist/pivot.cjs.map +1 -1
  109. package/dist/pivot.js +20 -2
  110. package/dist/pivot.js.map +1 -1
  111. package/dist/resizer/index.d.ts +57 -0
  112. package/dist/resizer.cjs +2 -0
  113. package/dist/resizer.cjs.map +1 -0
  114. package/dist/resizer.js +8 -0
  115. package/dist/resizer.js.map +1 -0
  116. package/dist/stack/index.d.ts +72 -0
  117. package/dist/stack.cjs +2 -0
  118. package/dist/stack.cjs.map +1 -0
  119. package/dist/stack.js +721 -0
  120. package/dist/stack.js.map +1 -0
  121. package/dist/sticky-header/StickyArea.d.ts +38 -0
  122. package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
  123. package/dist/sticky-header/index.d.ts +4 -4
  124. package/dist/sticky-header/types.d.ts +35 -22
  125. package/dist/sticky-header.cjs +1 -1
  126. package/dist/sticky-header.cjs.map +1 -1
  127. package/dist/sticky-header.js +73 -174
  128. package/dist/sticky-header.js.map +1 -1
  129. package/dist/styles-NkjuMOVS.js +57 -0
  130. package/dist/styles-NkjuMOVS.js.map +1 -0
  131. package/dist/styles-qf6ptVLD.cjs +2 -0
  132. package/dist/styles-qf6ptVLD.cjs.map +1 -0
  133. package/dist/types.d.ts +16 -0
  134. package/dist/useContentCache-CO3LYNmz.js +24 -0
  135. package/dist/useContentCache-CO3LYNmz.js.map +1 -0
  136. package/dist/useContentCache-DqXtLrLs.cjs +2 -0
  137. package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
  138. package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
  139. package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
  140. package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
  141. package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
  142. package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
  143. package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
  144. package/dist/useFloatingState-tEfA_wbc.js +74 -0
  145. package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
  146. package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
  147. package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
  148. package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
  149. package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
  150. package/dist/window/index.d.ts +63 -0
  151. package/dist/window.cjs +2 -0
  152. package/dist/window.cjs.map +1 -0
  153. package/dist/window.js +160 -0
  154. package/dist/window.js.map +1 -0
  155. package/docs/design-tokens.md +405 -0
  156. package/package.json +34 -4
  157. package/src/PanelSystemContext.tsx +88 -0
  158. package/src/components/gesture/SwipeSafeZone.tsx +69 -0
  159. package/src/components/grid/GridLayerList.tsx +172 -0
  160. package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
  161. package/src/components/grid/GridLayout.spec.tsx +743 -0
  162. package/src/components/grid/GridLayout.tsx +130 -0
  163. package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
  164. package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
  165. package/src/components/panels/DropSuggestOverlay.tsx +131 -0
  166. package/src/components/panels/PanelGroupView.tsx +112 -0
  167. package/src/components/pivot/PivotLayer.tsx +27 -0
  168. package/src/components/resizer/HorizontalDivider.tsx +52 -0
  169. package/src/components/resizer/ResizeHandle.tsx +118 -0
  170. package/src/components/tabs/TabBar.tsx +223 -0
  171. package/src/components/tabs/TabBarTab.tsx +133 -0
  172. package/src/components/tabs/TabDragOverlay.tsx +92 -0
  173. package/src/components/window/DialogOverlay.tsx +180 -0
  174. package/src/components/window/Drawer.tsx +369 -0
  175. package/src/components/window/DrawerLayers.tsx +68 -0
  176. package/src/components/window/FloatingWindow.tsx +95 -0
  177. package/src/components/window/PopupLayerPortal.tsx +218 -0
  178. package/src/components/window/drawerStyles.spec.ts +263 -0
  179. package/src/components/window/drawerStyles.ts +228 -0
  180. package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
  181. package/src/components/window/drawerSwipeConfig.ts +112 -0
  182. package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
  183. package/src/components/window/useDrawerSwipeTransform.ts +129 -0
  184. package/src/config/PanelContentDeclaration.tsx +427 -0
  185. package/src/config/index.tsx +52 -0
  186. package/src/config/panelJsx.spec.tsx +54 -0
  187. package/src/config/panelJsxConfig.spec.tsx +54 -0
  188. package/src/config/panelJsxDrawer.spec.tsx +33 -0
  189. package/src/config/panelRouter.spec.ts +68 -0
  190. package/src/config/panelRouter.tsx +155 -0
  191. package/src/constants/styles.ts +280 -0
  192. package/src/demo/Layout.module.css +258 -0
  193. package/src/demo/Layout.tsx +176 -0
  194. package/src/demo/components/CodeBlock.module.css +54 -0
  195. package/src/demo/components/CodeBlock.tsx +34 -0
  196. package/src/demo/components/CodePreview.module.css +37 -0
  197. package/src/demo/components/CodePreview.tsx +31 -0
  198. package/src/demo/components/DataPreview.module.css +177 -0
  199. package/src/demo/components/DataPreview.tsx +115 -0
  200. package/src/demo/components/Story.module.css +68 -0
  201. package/src/demo/components/Story.tsx +54 -0
  202. package/src/demo/components/layout/CodePanel.module.css +183 -0
  203. package/src/demo/components/layout/CodePanel.tsx +149 -0
  204. package/src/demo/components/layout/DemoPage.module.css +60 -0
  205. package/src/demo/components/layout/DemoPage.tsx +56 -0
  206. package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
  207. package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
  208. package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
  209. package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
  210. package/src/demo/components/layout/index.ts +11 -0
  211. package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
  212. package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
  213. package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
  214. package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
  215. package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
  216. package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
  217. package/src/demo/components/tab-styles/index.ts +6 -0
  218. package/src/demo/components/ui/DemoButton.module.css +63 -0
  219. package/src/demo/components/ui/DemoButton.tsx +32 -0
  220. package/src/demo/components/ui/DemoCard.module.css +15 -0
  221. package/src/demo/components/ui/DemoCard.tsx +30 -0
  222. package/src/demo/components/ui/DemoContainer.module.css +17 -0
  223. package/src/demo/components/ui/DemoContainer.tsx +30 -0
  224. package/src/demo/components/ui/DemoPanel.module.css +23 -0
  225. package/src/demo/components/ui/DemoPanel.tsx +33 -0
  226. package/src/demo/components/ui/PanelText.module.css +18 -0
  227. package/src/demo/components/ui/PanelText.tsx +29 -0
  228. package/src/demo/components/ui/PanelTitle.module.css +18 -0
  229. package/src/demo/components/ui/PanelTitle.tsx +31 -0
  230. package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
  231. package/src/demo/demo.css +172 -0
  232. package/src/demo/hooks/useMedia.ts +41 -0
  233. package/src/demo/hooks/useShikiHighlight.ts +55 -0
  234. package/src/demo/index.tsx +293 -0
  235. package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
  236. package/src/demo/pages/Dialog/card/index.tsx +22 -0
  237. package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
  238. package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
  239. package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
  240. package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
  241. package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
  242. package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
  243. package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
  244. package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
  245. package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
  246. package/src/demo/pages/Dialog/modal/index.tsx +17 -0
  247. package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
  248. package/src/demo/pages/Drawer/animations/index.tsx +22 -0
  249. package/src/demo/pages/Drawer/basics/index.tsx +17 -0
  250. package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
  251. package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
  252. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
  253. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
  254. package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
  255. package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
  256. package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
  257. package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
  258. package/src/demo/pages/Drawer/menu/index.tsx +17 -0
  259. package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
  260. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
  261. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
  262. package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
  263. package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
  264. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
  265. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
  266. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
  267. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
  268. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
  269. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
  270. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
  271. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
  272. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
  273. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
  274. package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
  275. package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
  276. package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
  277. package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
  278. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
  279. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
  280. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
  281. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
  282. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
  283. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
  284. package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
  285. package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
  286. package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
  287. package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
  288. package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
  289. package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
  290. package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
  291. package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
  292. package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
  293. package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
  294. package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
  295. package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
  296. package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
  297. package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
  298. package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
  299. package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
  300. package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
  301. package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
  302. package/src/demo/pages/PanelLayout/index.tsx +94 -0
  303. package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
  304. package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
  305. package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
  306. package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
  307. package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
  308. package/src/demo/pages/Pivot/basics/index.tsx +17 -0
  309. package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
  310. package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
  311. package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
  312. package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
  313. package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
  314. package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
  315. package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
  316. package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
  317. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +320 -0
  318. package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
  319. package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
  320. package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
  321. package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
  322. package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
  323. package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
  324. package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
  325. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
  326. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
  327. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
  328. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
  329. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
  330. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
  331. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
  332. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
  333. package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
  334. package/src/demo/pages/ResizeHandle/index.module.css +11 -0
  335. package/src/demo/pages/ResizeHandle/index.tsx +71 -0
  336. package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
  337. package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
  338. package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
  339. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
  340. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
  341. package/src/demo/pages/Stack/basics/index.tsx +22 -0
  342. package/src/demo/pages/Stack/components/Stack.module.css +234 -0
  343. package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
  344. package/src/demo/pages/Stack/components/StackBasics.tsx +301 -0
  345. package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
  346. package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
  347. package/src/demo/pages/Stack/components/StackTablet.tsx +422 -0
  348. package/src/demo/pages/Stack/tablet/index.tsx +22 -0
  349. package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
  350. package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
  351. package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
  352. package/src/demo/routes.tsx +214 -0
  353. package/src/demo/styles/animations.css +68 -0
  354. package/src/demo/styles/stack-themes.css +35 -0
  355. package/src/demo/utils/createPanelView.tsx +58 -0
  356. package/src/dialog/index.ts +85 -0
  357. package/src/floating/index.ts +24 -0
  358. package/src/grid/index.ts +75 -0
  359. package/src/hooks/ContentCacheContext.tsx +87 -0
  360. package/src/hooks/gesture/presets.spec.ts +86 -0
  361. package/src/hooks/gesture/presets.ts +95 -0
  362. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +241 -0
  363. package/src/hooks/gesture/testing/createGestureSimulator.ts +385 -0
  364. package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
  365. package/src/hooks/gesture/thresholdValue.ts +77 -0
  366. package/src/hooks/gesture/types.ts +367 -0
  367. package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
  368. package/src/hooks/gesture/useDirectionalLock.ts +115 -0
  369. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +462 -0
  370. package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
  371. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +473 -0
  372. package/src/hooks/gesture/useNativeGestureGuard.ts +135 -0
  373. package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
  374. package/src/hooks/gesture/usePointerTracking.ts +134 -0
  375. package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
  376. package/src/hooks/gesture/useScrollBoundary.ts +113 -0
  377. package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
  378. package/src/hooks/gesture/useSwipeInput.ts +310 -0
  379. package/src/hooks/gesture/utils.spec.ts +152 -0
  380. package/src/hooks/gesture/utils.ts +178 -0
  381. package/src/hooks/useAnimatedVisibility.spec.ts +277 -0
  382. package/src/hooks/useAnimatedVisibility.ts +172 -0
  383. package/src/hooks/useAnimationFrame.ts +208 -0
  384. package/src/hooks/useCSSMatrix.spec.ts +214 -0
  385. package/src/hooks/useCSSMatrix.ts +262 -0
  386. package/src/hooks/useClonedElementPreview.ts +28 -0
  387. package/src/hooks/useContainerScroll.ts +78 -0
  388. package/src/hooks/useContentCache.spec.tsx +232 -0
  389. package/src/hooks/useContentCache.tsx +127 -0
  390. package/src/hooks/useDocumentPointerEvents.ts +137 -0
  391. package/src/hooks/useDocumentScroll.ts +41 -0
  392. package/src/hooks/useEffectEvent.ts +40 -0
  393. package/src/hooks/useElementComponentWrapper.tsx +63 -0
  394. package/src/hooks/useIntersectionObserver.tsx +125 -0
  395. package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
  396. package/src/hooks/useOperationContinuity.spec.ts +387 -0
  397. package/src/hooks/useOperationContinuity.ts +135 -0
  398. package/src/hooks/useResizeObserver.spec.tsx +277 -0
  399. package/src/hooks/useResizeObserver.tsx +150 -0
  400. package/src/hooks/useScrollContainer.ts +73 -0
  401. package/src/hooks/useSharedElementTransition.ts +333 -0
  402. package/src/hooks/useSnapAnimation.ts +128 -0
  403. package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
  404. package/src/hooks/useSwipeContentTransform.ts +373 -0
  405. package/src/hooks/useTransitionState.ts +95 -0
  406. package/src/index.tsx +88 -0
  407. package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
  408. package/src/modules/dialog/AlertDialog.tsx +221 -0
  409. package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
  410. package/src/modules/dialog/DialogContainer.tsx +188 -0
  411. package/src/modules/dialog/Modal.spec.tsx +220 -0
  412. package/src/modules/dialog/Modal.tsx +182 -0
  413. package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
  414. package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
  415. package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
  416. package/src/modules/dialog/types.ts +186 -0
  417. package/src/modules/dialog/useDialog.spec.tsx +447 -0
  418. package/src/modules/dialog/useDialog.ts +214 -0
  419. package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
  420. package/src/modules/dialog/useDialogContainer.ts +150 -0
  421. package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
  422. package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
  423. package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
  424. package/src/modules/dialog/useDialogTransform.ts +407 -0
  425. package/src/modules/drawer/types.ts +102 -0
  426. package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
  427. package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
  428. package/src/modules/grid/GridLayoutContext.tsx +57 -0
  429. package/src/modules/grid/LayerInstanceContext.tsx +56 -0
  430. package/src/modules/grid/resizeHandles.ts +157 -0
  431. package/src/modules/grid/trackUtils.ts +146 -0
  432. package/src/modules/grid/useGridPlacements.ts +143 -0
  433. package/src/modules/grid/useGridTracks.ts +156 -0
  434. package/src/modules/grid/useLayerDragHandle.ts +16 -0
  435. package/src/modules/grid/useLayerInteractions.tsx +850 -0
  436. package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
  437. package/src/modules/panels/dom/DomRegistry.tsx +94 -0
  438. package/src/modules/panels/index.ts +45 -0
  439. package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
  440. package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
  441. package/src/modules/panels/interactions/dnd.ts +28 -0
  442. package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
  443. package/src/modules/panels/layout/adapter.ts +124 -0
  444. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +311 -0
  445. package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
  446. package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
  447. package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
  448. package/src/modules/panels/rendering/RenderContext.tsx +31 -0
  449. package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
  450. package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
  451. package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
  452. package/src/modules/panels/state/StateContext.tsx +12 -0
  453. package/src/modules/panels/state/cleanup.ts +37 -0
  454. package/src/modules/panels/state/commands.ts +53 -0
  455. package/src/modules/panels/state/focus/Context.tsx +25 -0
  456. package/src/modules/panels/state/focus/logic.ts +57 -0
  457. package/src/modules/panels/state/groups/Context.tsx +25 -0
  458. package/src/modules/panels/state/groups/logic.ts +105 -0
  459. package/src/modules/panels/state/splitLimits.spec.ts +46 -0
  460. package/src/modules/panels/state/splitLimits.ts +90 -0
  461. package/src/modules/panels/state/state.spec.ts +49 -0
  462. package/src/modules/panels/state/tree/Context.tsx +24 -0
  463. package/src/modules/panels/state/tree/logic.spec.ts +34 -0
  464. package/src/modules/panels/state/tree/logic.ts +138 -0
  465. package/src/modules/panels/state/types.ts +142 -0
  466. package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
  467. package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
  468. package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
  469. package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
  470. package/src/modules/panels/system/PanelSystem.tsx +206 -0
  471. package/src/modules/pivot/PivotContent.spec.tsx +179 -0
  472. package/src/modules/pivot/PivotContent.tsx +77 -0
  473. package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
  474. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +171 -0
  475. package/src/modules/pivot/SwipePivotContent.spec.tsx +494 -0
  476. package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
  477. package/src/modules/pivot/SwipePivotContent.tsx +197 -0
  478. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +882 -0
  479. package/src/modules/pivot/SwipePivotTabBar.tsx +583 -0
  480. package/src/modules/pivot/index.ts +8 -0
  481. package/src/modules/pivot/scaleInputState.spec.ts +219 -0
  482. package/src/modules/pivot/scaleInputState.ts +66 -0
  483. package/src/modules/pivot/types.ts +139 -0
  484. package/src/modules/pivot/usePivot.spec.ts +635 -0
  485. package/src/modules/pivot/usePivot.spec.tsx +186 -0
  486. package/src/modules/pivot/usePivot.tsx +345 -0
  487. package/src/modules/pivot/usePivotSwipeInput.spec.ts +708 -0
  488. package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
  489. package/src/modules/resizer/useResizeDrag.ts +94 -0
  490. package/src/modules/stack/StackContent.spec.tsx +264 -0
  491. package/src/modules/stack/StackContent.tsx +111 -0
  492. package/src/modules/stack/SwipeStackContent.spec.tsx +1564 -0
  493. package/src/modules/stack/SwipeStackContent.tsx +366 -0
  494. package/src/modules/stack/SwipeStackOutlet.spec.tsx +250 -0
  495. package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
  496. package/src/modules/stack/computeStackContentState.spec.ts +281 -0
  497. package/src/modules/stack/computeStackContentState.ts +304 -0
  498. package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
  499. package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
  500. package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
  501. package/src/modules/stack/types.ts +226 -0
  502. package/src/modules/stack/useStackAnimationState.spec.ts +188 -0
  503. package/src/modules/stack/useStackAnimationState.ts +143 -0
  504. package/src/modules/stack/useStackNavigation.spec.ts +672 -0
  505. package/src/modules/stack/useStackNavigation.tsx +393 -0
  506. package/src/modules/stack/useStackSwipeInput.spec.ts +309 -0
  507. package/src/modules/stack/useStackSwipeInput.ts +139 -0
  508. package/src/modules/window/useDrawerState.ts +81 -0
  509. package/src/modules/window/useFloatingState.spec.ts +252 -0
  510. package/src/modules/window/useFloatingState.ts +141 -0
  511. package/src/panels/index.ts +119 -0
  512. package/src/pivot/index.ts +19 -0
  513. package/src/resizer/index.ts +68 -0
  514. package/src/stack/index.ts +91 -0
  515. package/src/sticky-header/StickyArea.tsx +193 -0
  516. package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
  517. package/src/sticky-header/calculateStickyMetrics.ts +50 -0
  518. package/src/sticky-header/index.ts +18 -0
  519. package/src/sticky-header/types.ts +68 -0
  520. package/src/types.ts +341 -0
  521. package/src/utils/CSSMatrix.ts +321 -0
  522. package/src/utils/css.ts +65 -0
  523. package/src/utils/dialogUtils.ts +43 -0
  524. package/src/utils/math.ts +18 -0
  525. package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
  526. package/src/utils/typedActions.ts +103 -0
  527. package/src/vite-env.d.ts +6 -0
  528. package/src/window/index.ts +69 -0
  529. package/dist/GridLayout-BQQ63eA1.cjs +0 -2
  530. package/dist/GridLayout-BQQ63eA1.cjs.map +0 -1
  531. package/dist/GridLayout-CJTKq7Mp.js +0 -1465
  532. package/dist/GridLayout-CJTKq7Mp.js.map +0 -1
  533. package/dist/sticky-header/StickyHeader.d.ts +0 -53
  534. package/dist/styles-CA2_zLZt.js +0 -52
  535. package/dist/styles-CA2_zLZt.js.map +0 -1
  536. package/dist/styles-PsqGOEJP.cjs +0 -2
  537. package/dist/styles-PsqGOEJP.cjs.map +0 -1
  538. package/dist/usePivot-7ctin_P_.cjs +0 -2
  539. package/dist/usePivot-7ctin_P_.cjs.map +0 -1
  540. package/dist/usePivot-CgQxB8rc.js +0 -124
  541. package/dist/usePivot-CgQxB8rc.js.map +0 -1
@@ -0,0 +1,1133 @@
1
+ /**
2
+ * @file Tests for swipe-to-navigation transition continuity.
3
+ *
4
+ * These tests verify that when a swipe gesture completes and triggers navigation,
5
+ * the behind panel (which becomes active) should NOT restart its animation from
6
+ * the original "behind" position, but should continue smoothly from its current position.
7
+ *
8
+ * Issue: When swiping back strongly, the panel that was "behind" becomes "active",
9
+ * and its animation appears to restart from the beginning rather than continuing
10
+ * from where the swipe left it.
11
+ */
12
+ import { render, act } from "@testing-library/react";
13
+ import { SwipeStackContent } from "./SwipeStackContent.js";
14
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
15
+
16
+ // Mock requestAnimationFrame for animation testing
17
+ const rafState = {
18
+ callbacks: [] as FrameRequestCallback[],
19
+ id: 0,
20
+ mockTimestamp: 0,
21
+ originalRAF: globalThis.requestAnimationFrame,
22
+ originalCAF: globalThis.cancelAnimationFrame,
23
+ };
24
+
25
+ const resetRafState = (): void => {
26
+ rafState.callbacks = [];
27
+ rafState.id = 0;
28
+ rafState.mockTimestamp = 0;
29
+ };
30
+
31
+ const mockRAF = (callback: FrameRequestCallback): number => {
32
+ rafState.callbacks = [...rafState.callbacks, callback];
33
+ rafState.id += 1;
34
+ return rafState.id;
35
+ };
36
+
37
+ const mockCAF = (): void => {};
38
+
39
+ const flushRAF = (advanceMs = 400): void => {
40
+ rafState.mockTimestamp += advanceMs;
41
+ const callbacks = rafState.callbacks;
42
+ rafState.callbacks = [];
43
+ callbacks.forEach((cb) => cb(rafState.mockTimestamp));
44
+ };
45
+
46
+ beforeEach(() => {
47
+ resetRafState();
48
+ globalThis.requestAnimationFrame = mockRAF;
49
+ globalThis.cancelAnimationFrame = mockCAF;
50
+ });
51
+
52
+ afterEach(() => {
53
+ globalThis.requestAnimationFrame = rafState.originalRAF;
54
+ globalThis.cancelAnimationFrame = rafState.originalCAF;
55
+ });
56
+
57
+ const IDLE_STATE: ContinuousOperationState = {
58
+ phase: "idle",
59
+ displacement: { x: 0, y: 0 },
60
+ velocity: { x: 0, y: 0 },
61
+ };
62
+
63
+ const createOperatingState = (displacementX: number): ContinuousOperationState => ({
64
+ phase: "operating",
65
+ displacement: { x: displacementX, y: 0 },
66
+ velocity: { x: 0.5, y: 0 },
67
+ });
68
+
69
+ describe("Swipe transition continuity", () => {
70
+ describe("behind panel becoming active after full swipe", () => {
71
+ /**
72
+ * Scenario: User swipes 100% to go back.
73
+ *
74
+ * Before swipe:
75
+ * - Panel at depth 0: role=behind, targetPx=-120, visible=hidden
76
+ * - Panel at depth 1: role=active, targetPx=0, visible=visible
77
+ *
78
+ * During 100% swipe:
79
+ * - Panel at depth 0: role=behind, at 0px (fully revealed via parallax)
80
+ * - Panel at depth 1: role=active, at 400px (fully off-screen)
81
+ *
82
+ * After swipe (navigation changed, depth 1→0):
83
+ * - Panel at depth 0: role=active, should stay at 0px (NO animation needed)
84
+ * - Panel at depth 1: role=hidden, should stay at 400px
85
+ *
86
+ * Bug: The panel at depth 0 incorrectly animates from -120px to 0px,
87
+ * even though it's already at 0px.
88
+ */
89
+ it("behind panel should NOT animate after completing 100% swipe back", () => {
90
+ const containerSize = 400;
91
+ const behindOffset = -0.3; // -120px
92
+
93
+ // Step 1: Render behind panel at depth 0, navigationDepth 1
94
+ const { container, rerender } = render(
95
+ <SwipeStackContent
96
+ id="behind"
97
+ depth={0}
98
+ navigationDepth={1}
99
+ isActive={false}
100
+ operationState={IDLE_STATE}
101
+ containerSize={containerSize}
102
+ >
103
+ Content
104
+ </SwipeStackContent>,
105
+ );
106
+
107
+ const element = container.firstChild as HTMLElement;
108
+
109
+ // Initial: behind position at -120px
110
+ expect(element.style.transform).toBe("translateX(-120px)");
111
+
112
+ // Step 2: Swipe 100% (400px displacement)
113
+ // Behind panel should be at 0px (fully revealed)
114
+ rerender(
115
+ <SwipeStackContent
116
+ id="behind"
117
+ depth={0}
118
+ navigationDepth={1}
119
+ isActive={false}
120
+ operationState={createOperatingState(400)}
121
+ containerSize={containerSize}
122
+ >
123
+ Content
124
+ </SwipeStackContent>,
125
+ );
126
+
127
+ // Behind panel at 0px (parallax completed)
128
+ expect(element.style.transform).toBe("translateX(0px)");
129
+
130
+ // Step 3: Swipe ends AND navigation changes simultaneously
131
+ // - operationState becomes IDLE (displacement = 0)
132
+ // - navigationDepth changes to 0 (this panel becomes active)
133
+ // - role changes from "behind" to "active"
134
+ // - targetPx changes from -120 to 0
135
+ //
136
+ // CRITICAL: Since the panel is ALREADY at 0px, it should NOT animate.
137
+ rerender(
138
+ <SwipeStackContent
139
+ id="behind"
140
+ depth={0}
141
+ navigationDepth={0}
142
+ isActive={true}
143
+ operationState={IDLE_STATE}
144
+ containerSize={containerSize}
145
+ >
146
+ Content
147
+ </SwipeStackContent>,
148
+ );
149
+
150
+ // The panel should stay at 0px immediately (no jump to -120px)
151
+ // This is the bug: it might incorrectly start an animation from -120px
152
+ expect(element.style.transform).toBe("translateX(0px)");
153
+
154
+ // Flush any pending animations
155
+ act(() => {
156
+ flushRAF(0);
157
+ flushRAF(400);
158
+ });
159
+
160
+ // After potential animation, should still be at 0px
161
+ expect(element.style.transform).toBe("translateX(0px)");
162
+ });
163
+
164
+ /**
165
+ * Scenario: User swipes 80% and releases (meets threshold).
166
+ *
167
+ * The behind panel at depth 0 should animate from its current position
168
+ * (-24px at 80% swipe) to 0px, NOT from -120px to 0px.
169
+ */
170
+ it("behind panel should animate from current position after 80% swipe", () => {
171
+ const containerSize = 400;
172
+
173
+ const { container, rerender } = render(
174
+ <SwipeStackContent
175
+ id="behind"
176
+ depth={0}
177
+ navigationDepth={1}
178
+ isActive={false}
179
+ operationState={IDLE_STATE}
180
+ containerSize={containerSize}
181
+ >
182
+ Content
183
+ </SwipeStackContent>,
184
+ );
185
+
186
+ const element = container.firstChild as HTMLElement;
187
+
188
+ // Swipe 80% (320px)
189
+ // Behind panel: -120 + 0.8 * 120 = -120 + 96 = -24px
190
+ rerender(
191
+ <SwipeStackContent
192
+ id="behind"
193
+ depth={0}
194
+ navigationDepth={1}
195
+ isActive={false}
196
+ operationState={createOperatingState(320)}
197
+ containerSize={containerSize}
198
+ >
199
+ Content
200
+ </SwipeStackContent>,
201
+ );
202
+
203
+ // At 80% swipe, behind panel should be at -24px
204
+ expect(element.style.transform).toBe("translateX(-24px)");
205
+
206
+ // Swipe ends, navigation changes
207
+ rerender(
208
+ <SwipeStackContent
209
+ id="behind"
210
+ depth={0}
211
+ navigationDepth={0}
212
+ isActive={true}
213
+ operationState={IDLE_STATE}
214
+ containerSize={containerSize}
215
+ >
216
+ Content
217
+ </SwipeStackContent>,
218
+ );
219
+
220
+ // Animation should start from -24px (current position), not -120px
221
+ // Immediately after render, should still be at -24px (animation hasn't run yet)
222
+ expect(element.style.transform).toBe("translateX(-24px)");
223
+
224
+ // Flush animations
225
+ act(() => {
226
+ flushRAF(0);
227
+ flushRAF(400);
228
+ });
229
+
230
+ // After animation, should be at 0px
231
+ expect(element.style.transform).toBe("translateX(0px)");
232
+ });
233
+ });
234
+
235
+ describe("swipe continuation beyond threshold", () => {
236
+ /**
237
+ * Scenario: User swipes past 100% and continues moving.
238
+ *
239
+ * This tests the "strong swipe" behavior where the user swipes
240
+ * past the container width.
241
+ */
242
+ it("handles swipe beyond 100% without animation glitches", () => {
243
+ const containerSize = 400;
244
+
245
+ const { container, rerender } = render(
246
+ <SwipeStackContent
247
+ id="behind"
248
+ depth={0}
249
+ navigationDepth={1}
250
+ isActive={false}
251
+ operationState={IDLE_STATE}
252
+ containerSize={containerSize}
253
+ >
254
+ Content
255
+ </SwipeStackContent>,
256
+ );
257
+
258
+ const element = container.firstChild as HTMLElement;
259
+
260
+ // Swipe beyond 100% (500px, 125%)
261
+ rerender(
262
+ <SwipeStackContent
263
+ id="behind"
264
+ depth={0}
265
+ navigationDepth={1}
266
+ isActive={false}
267
+ operationState={createOperatingState(500)}
268
+ containerSize={containerSize}
269
+ >
270
+ Content
271
+ </SwipeStackContent>,
272
+ );
273
+
274
+ // Behind panel should be at 0px (capped at full reveal)
275
+ expect(element.style.transform).toBe("translateX(0px)");
276
+
277
+ // Swipe ends, navigation changes
278
+ rerender(
279
+ <SwipeStackContent
280
+ id="behind"
281
+ depth={0}
282
+ navigationDepth={0}
283
+ isActive={true}
284
+ operationState={IDLE_STATE}
285
+ containerSize={containerSize}
286
+ >
287
+ Content
288
+ </SwipeStackContent>,
289
+ );
290
+
291
+ // Should stay at 0px, no animation needed
292
+ expect(element.style.transform).toBe("translateX(0px)");
293
+
294
+ act(() => {
295
+ flushRAF(0);
296
+ flushRAF(400);
297
+ });
298
+
299
+ expect(element.style.transform).toBe("translateX(0px)");
300
+ });
301
+ });
302
+
303
+ describe("role change during operation", () => {
304
+ /**
305
+ * Edge case: What happens if role changes while still operating?
306
+ *
307
+ * This could happen when navigation changes before the swipe gesture fully ends.
308
+ * The panel should maintain its current position to avoid visual jumps,
309
+ * then transition to the new role's behavior when the swipe ends.
310
+ */
311
+ it("maintains position continuity when role changes during swipe", () => {
312
+ const containerSize = 400;
313
+
314
+ const { container, rerender } = render(
315
+ <SwipeStackContent
316
+ id="behind"
317
+ depth={0}
318
+ navigationDepth={1}
319
+ isActive={false}
320
+ operationState={createOperatingState(200)}
321
+ containerSize={containerSize}
322
+ >
323
+ Content
324
+ </SwipeStackContent>,
325
+ );
326
+
327
+ const element = container.firstChild as HTMLElement;
328
+
329
+ // At 50% swipe, behind panel at -60px (parallax effect)
330
+ expect(element.style.transform).toBe("translateX(-60px)");
331
+
332
+ // Role changes while still operating (navigation happened before swipe ended)
333
+ rerender(
334
+ <SwipeStackContent
335
+ id="behind"
336
+ depth={0}
337
+ navigationDepth={0}
338
+ isActive={true}
339
+ operationState={createOperatingState(200)}
340
+ containerSize={containerSize}
341
+ >
342
+ Content
343
+ </SwipeStackContent>,
344
+ );
345
+
346
+ // KEY BEHAVIOR: Position should be maintained to avoid visual jump
347
+ // Panel continues using previous role's calculation during swipe
348
+ expect(element.style.transform).toBe("translateX(-60px)");
349
+
350
+ // When swipe ends, panel transitions to new role
351
+ rerender(
352
+ <SwipeStackContent
353
+ id="behind"
354
+ depth={0}
355
+ navigationDepth={0}
356
+ isActive={true}
357
+ operationState={IDLE_STATE}
358
+ containerSize={containerSize}
359
+ >
360
+ Content
361
+ </SwipeStackContent>,
362
+ );
363
+
364
+ // Now at rest position for active panel
365
+ act(() => {
366
+ flushRAF(0);
367
+ flushRAF(400);
368
+ });
369
+
370
+ expect(element.style.transform).toBe("translateX(0px)");
371
+ });
372
+ });
373
+
374
+ describe("consecutive operations", () => {
375
+ /**
376
+ * Scenario: Complete a swipe back, then immediately start another swipe.
377
+ *
378
+ * This tests the "strong swipe" followed by another operation scenario.
379
+ */
380
+ it("handles immediate second swipe after first swipe completes", () => {
381
+ const containerSize = 400;
382
+
383
+ // Start as behind panel
384
+ const { container, rerender } = render(
385
+ <SwipeStackContent
386
+ id="panel"
387
+ depth={0}
388
+ navigationDepth={1}
389
+ isActive={false}
390
+ operationState={IDLE_STATE}
391
+ containerSize={containerSize}
392
+ >
393
+ Content
394
+ </SwipeStackContent>,
395
+ );
396
+
397
+ const element = container.firstChild as HTMLElement;
398
+ expect(element.style.transform).toBe("translateX(-120px)");
399
+
400
+ // First swipe: 100%
401
+ rerender(
402
+ <SwipeStackContent
403
+ id="panel"
404
+ depth={0}
405
+ navigationDepth={1}
406
+ isActive={false}
407
+ operationState={createOperatingState(400)}
408
+ containerSize={containerSize}
409
+ >
410
+ Content
411
+ </SwipeStackContent>,
412
+ );
413
+ expect(element.style.transform).toBe("translateX(0px)");
414
+
415
+ // First swipe ends, navigation changes
416
+ rerender(
417
+ <SwipeStackContent
418
+ id="panel"
419
+ depth={0}
420
+ navigationDepth={0}
421
+ isActive={true}
422
+ operationState={IDLE_STATE}
423
+ containerSize={containerSize}
424
+ >
425
+ Content
426
+ </SwipeStackContent>,
427
+ );
428
+ expect(element.style.transform).toBe("translateX(0px)");
429
+
430
+ // Immediately start a second swipe (even though we can't go back further)
431
+ // This simulates the user continuing to swipe after navigation completed
432
+ rerender(
433
+ <SwipeStackContent
434
+ id="panel"
435
+ depth={0}
436
+ navigationDepth={0}
437
+ isActive={true}
438
+ operationState={createOperatingState(50)}
439
+ containerSize={containerSize}
440
+ >
441
+ Content
442
+ </SwipeStackContent>,
443
+ );
444
+
445
+ // Panel should respond to the new displacement
446
+ expect(element.style.transform).toBe("translateX(50px)");
447
+
448
+ // Release the second swipe
449
+ rerender(
450
+ <SwipeStackContent
451
+ id="panel"
452
+ depth={0}
453
+ navigationDepth={0}
454
+ isActive={true}
455
+ operationState={IDLE_STATE}
456
+ containerSize={containerSize}
457
+ >
458
+ Content
459
+ </SwipeStackContent>,
460
+ );
461
+
462
+ // Should snap back to 0
463
+ act(() => {
464
+ flushRAF(0);
465
+ flushRAF(400);
466
+ });
467
+
468
+ expect(element.style.transform).toBe("translateX(0px)");
469
+ });
470
+
471
+ /**
472
+ * Scenario: Swipe ends with animation in progress, then new operation starts.
473
+ *
474
+ * This tests interrupting an animation with a new swipe.
475
+ */
476
+ it("interrupts snap-back animation with new swipe", () => {
477
+ const containerSize = 400;
478
+
479
+ const { container, rerender } = render(
480
+ <SwipeStackContent
481
+ id="panel"
482
+ depth={0}
483
+ navigationDepth={0}
484
+ isActive={true}
485
+ operationState={IDLE_STATE}
486
+ containerSize={containerSize}
487
+ >
488
+ Content
489
+ </SwipeStackContent>,
490
+ );
491
+
492
+ const element = container.firstChild as HTMLElement;
493
+
494
+ // Start a swipe
495
+ rerender(
496
+ <SwipeStackContent
497
+ id="panel"
498
+ depth={0}
499
+ navigationDepth={0}
500
+ isActive={true}
501
+ operationState={createOperatingState(200)}
502
+ containerSize={containerSize}
503
+ >
504
+ Content
505
+ </SwipeStackContent>,
506
+ );
507
+ expect(element.style.transform).toBe("translateX(200px)");
508
+
509
+ // Release (starts snap-back animation)
510
+ rerender(
511
+ <SwipeStackContent
512
+ id="panel"
513
+ depth={0}
514
+ navigationDepth={0}
515
+ isActive={true}
516
+ operationState={IDLE_STATE}
517
+ containerSize={containerSize}
518
+ >
519
+ Content
520
+ </SwipeStackContent>,
521
+ );
522
+
523
+ // Animation starts but we don't complete it
524
+ act(() => {
525
+ flushRAF(0); // Start animation
526
+ flushRAF(100); // Partial progress
527
+ });
528
+
529
+ // Position should be somewhere between 200 and 0
530
+ const currentTransform = element.style.transform;
531
+ const matchResult = currentTransform.match(/translateX\(([^)]+)px\)/);
532
+ const currentPx = matchResult ? parseFloat(matchResult[1]) : NaN;
533
+
534
+ // Should be between 0 and 200 (animation in progress)
535
+ expect(currentPx).toBeGreaterThanOrEqual(0);
536
+ expect(currentPx).toBeLessThanOrEqual(200);
537
+
538
+ // Start a new swipe (interrupts animation)
539
+ rerender(
540
+ <SwipeStackContent
541
+ id="panel"
542
+ depth={0}
543
+ navigationDepth={0}
544
+ isActive={true}
545
+ operationState={createOperatingState(150)}
546
+ containerSize={containerSize}
547
+ >
548
+ Content
549
+ </SwipeStackContent>,
550
+ );
551
+
552
+ // Should now be at the new displacement position
553
+ expect(element.style.transform).toBe("translateX(150px)");
554
+ });
555
+ });
556
+
557
+ describe("stack display mode (scale effect)", () => {
558
+ /**
559
+ * Scenario: Stack display mode adds scale transform.
560
+ *
561
+ * In stack mode, behind panels have a scale < 1.
562
+ * When the panel becomes active, the scale should animate to 1.
563
+ * This should not interfere with the translate animation.
564
+ */
565
+ it("behind panel in stack mode transitions smoothly when becoming active", () => {
566
+ const containerSize = 400;
567
+
568
+ const { container, rerender } = render(
569
+ <SwipeStackContent
570
+ id="panel"
571
+ depth={0}
572
+ navigationDepth={1}
573
+ isActive={false}
574
+ operationState={IDLE_STATE}
575
+ containerSize={containerSize}
576
+ displayMode="stack"
577
+ >
578
+ Content
579
+ </SwipeStackContent>,
580
+ );
581
+
582
+ const element = container.firstChild as HTMLElement;
583
+
584
+ // Behind panel should have scale(0.95)
585
+ expect(element.style.transform).toContain("scale(0.95)");
586
+ expect(element.style.transform).toContain("translateX(-120px)");
587
+
588
+ // 100% swipe
589
+ rerender(
590
+ <SwipeStackContent
591
+ id="panel"
592
+ depth={0}
593
+ navigationDepth={1}
594
+ isActive={false}
595
+ operationState={createOperatingState(400)}
596
+ containerSize={containerSize}
597
+ displayMode="stack"
598
+ >
599
+ Content
600
+ </SwipeStackContent>,
601
+ );
602
+
603
+ // At 100% swipe, scale should be 1 and position 0
604
+ expect(element.style.transform).toContain("translateX(0px)");
605
+ expect(element.style.transform).toContain("scale(1)");
606
+
607
+ // Swipe ends, becomes active
608
+ rerender(
609
+ <SwipeStackContent
610
+ id="panel"
611
+ depth={0}
612
+ navigationDepth={0}
613
+ isActive={true}
614
+ operationState={IDLE_STATE}
615
+ containerSize={containerSize}
616
+ displayMode="stack"
617
+ >
618
+ Content
619
+ </SwipeStackContent>,
620
+ );
621
+
622
+ // Should stay at 0 with scale 1 (no animation restart)
623
+ expect(element.style.transform).toContain("translateX(0px)");
624
+
625
+ act(() => {
626
+ flushRAF(0);
627
+ flushRAF(400);
628
+ });
629
+
630
+ expect(element.style.transform).toContain("translateX(0px)");
631
+ });
632
+
633
+ /**
634
+ * Scenario: Partial swipe in stack mode.
635
+ *
636
+ * After 80% swipe, behind panel should animate from -24px to 0px.
637
+ * Scale should also animate from ~0.99 to 1.
638
+ */
639
+ it("partial swipe in stack mode animates from correct position", () => {
640
+ const containerSize = 400;
641
+
642
+ const { container, rerender } = render(
643
+ <SwipeStackContent
644
+ id="panel"
645
+ depth={0}
646
+ navigationDepth={1}
647
+ isActive={false}
648
+ operationState={IDLE_STATE}
649
+ containerSize={containerSize}
650
+ displayMode="stack"
651
+ >
652
+ Content
653
+ </SwipeStackContent>,
654
+ );
655
+
656
+ const element = container.firstChild as HTMLElement;
657
+
658
+ // 80% swipe
659
+ rerender(
660
+ <SwipeStackContent
661
+ id="panel"
662
+ depth={0}
663
+ navigationDepth={1}
664
+ isActive={false}
665
+ operationState={createOperatingState(320)}
666
+ containerSize={containerSize}
667
+ displayMode="stack"
668
+ >
669
+ Content
670
+ </SwipeStackContent>,
671
+ );
672
+
673
+ expect(element.style.transform).toContain("translateX(-24px)");
674
+
675
+ // Becomes active
676
+ rerender(
677
+ <SwipeStackContent
678
+ id="panel"
679
+ depth={0}
680
+ navigationDepth={0}
681
+ isActive={true}
682
+ operationState={IDLE_STATE}
683
+ containerSize={containerSize}
684
+ displayMode="stack"
685
+ >
686
+ Content
687
+ </SwipeStackContent>,
688
+ );
689
+
690
+ // Should start animation from -24px
691
+ expect(element.style.transform).toContain("translateX(-24px)");
692
+
693
+ act(() => {
694
+ flushRAF(0);
695
+ flushRAF(400);
696
+ });
697
+
698
+ expect(element.style.transform).toContain("translateX(0px)");
699
+ });
700
+ });
701
+
702
+ describe("over-swipe behavior (beyond 100%)", () => {
703
+ /**
704
+ * Scenario: User swipes beyond 100% (e.g., 500px when container is 400px).
705
+ *
706
+ * This is the "strong swipe" case:
707
+ * - behind panel should stay at 0px (clamped)
708
+ * - active panel should be at 500px (beyond container)
709
+ * - On release, exiting panel should animate from 500px to 400px
710
+ * - behind (now active) panel should stay at 0px (no animation needed)
711
+ */
712
+ it("behind panel stays at 0px when over-swiped 125%", () => {
713
+ const containerSize = 400;
714
+
715
+ const { container, rerender } = render(
716
+ <SwipeStackContent
717
+ id="behind"
718
+ depth={0}
719
+ navigationDepth={1}
720
+ isActive={false}
721
+ operationState={IDLE_STATE}
722
+ containerSize={containerSize}
723
+ >
724
+ Content
725
+ </SwipeStackContent>,
726
+ );
727
+
728
+ const element = container.firstChild as HTMLElement;
729
+ expect(element.style.transform).toBe("translateX(-120px)");
730
+
731
+ // Over-swipe: 125% (500px)
732
+ rerender(
733
+ <SwipeStackContent
734
+ id="behind"
735
+ depth={0}
736
+ navigationDepth={1}
737
+ isActive={false}
738
+ operationState={createOperatingState(500)}
739
+ containerSize={containerSize}
740
+ >
741
+ Content
742
+ </SwipeStackContent>,
743
+ );
744
+
745
+ // Behind panel should be clamped at 0px (not going positive)
746
+ expect(element.style.transform).toBe("translateX(0px)");
747
+
748
+ // Release and navigation changes
749
+ rerender(
750
+ <SwipeStackContent
751
+ id="behind"
752
+ depth={0}
753
+ navigationDepth={0}
754
+ isActive={true}
755
+ operationState={IDLE_STATE}
756
+ containerSize={containerSize}
757
+ >
758
+ Content
759
+ </SwipeStackContent>,
760
+ );
761
+
762
+ // Should stay at 0px immediately (no jump)
763
+ expect(element.style.transform).toBe("translateX(0px)");
764
+
765
+ act(() => {
766
+ flushRAF(0);
767
+ flushRAF(400);
768
+ });
769
+
770
+ // After any animation, still at 0px
771
+ expect(element.style.transform).toBe("translateX(0px)");
772
+ });
773
+
774
+ it("exiting panel at 125% swipe snaps to target (no backward animation)", () => {
775
+ const containerSize = 400;
776
+
777
+ const { container, rerender } = render(
778
+ <SwipeStackContent
779
+ id="exiting"
780
+ depth={1}
781
+ navigationDepth={1}
782
+ isActive={true}
783
+ operationState={IDLE_STATE}
784
+ containerSize={containerSize}
785
+ >
786
+ Content
787
+ </SwipeStackContent>,
788
+ );
789
+
790
+ const element = container.firstChild as HTMLElement;
791
+ expect(element.style.transform).toBe("translateX(0px)");
792
+
793
+ // Over-swipe: 125%
794
+ rerender(
795
+ <SwipeStackContent
796
+ id="exiting"
797
+ depth={1}
798
+ navigationDepth={1}
799
+ isActive={true}
800
+ operationState={createOperatingState(500)}
801
+ containerSize={containerSize}
802
+ >
803
+ Content
804
+ </SwipeStackContent>,
805
+ );
806
+
807
+ // Active panel follows displacement exactly (no clamp)
808
+ expect(element.style.transform).toBe("translateX(500px)");
809
+
810
+ // Over-swipe triggers navigation change while STILL SWIPING
811
+ // This is the key: navigation changes before the gesture ends
812
+ rerender(
813
+ <SwipeStackContent
814
+ id="exiting"
815
+ depth={1}
816
+ navigationDepth={0}
817
+ isActive={false}
818
+ operationState={createOperatingState(500)}
819
+ containerSize={containerSize}
820
+ >
821
+ Content
822
+ </SwipeStackContent>,
823
+ );
824
+
825
+ // Panel maintains position (useOperationContinuity keeps using old role)
826
+ expect(element.style.transform).toBe("translateX(500px)");
827
+
828
+ // Now release - gesture ends
829
+ rerender(
830
+ <SwipeStackContent
831
+ id="exiting"
832
+ depth={1}
833
+ navigationDepth={0}
834
+ isActive={false}
835
+ operationState={IDLE_STATE}
836
+ containerSize={containerSize}
837
+ >
838
+ Content
839
+ </SwipeStackContent>,
840
+ );
841
+
842
+ // Should snap to target position (400px) - no backward ANIMATION
843
+ // The key is we snap (instant) rather than animate backward
844
+ // Since the panel is off-screen, snapping to 400px is visually acceptable
845
+ expect(element.style.transform).toBe("translateX(400px)");
846
+
847
+ act(() => {
848
+ flushRAF(0);
849
+ flushRAF(400);
850
+ });
851
+
852
+ // After frames, still at target position (no animation occurred)
853
+ expect(element.style.transform).toBe("translateX(400px)");
854
+ });
855
+
856
+ /**
857
+ * This tests both panels together during over-swipe.
858
+ * The exiting panel should stay at 500px (no backward animation).
859
+ */
860
+ it("both panels handle 125% over-swipe transition smoothly", () => {
861
+ const containerSize = 400;
862
+
863
+ // Render behind panel
864
+ const behindResult = render(
865
+ <SwipeStackContent
866
+ id="behind"
867
+ depth={0}
868
+ navigationDepth={1}
869
+ isActive={false}
870
+ operationState={IDLE_STATE}
871
+ containerSize={containerSize}
872
+ >
873
+ Behind
874
+ </SwipeStackContent>,
875
+ );
876
+
877
+ // Render active panel
878
+ const activeResult = render(
879
+ <SwipeStackContent
880
+ id="active"
881
+ depth={1}
882
+ navigationDepth={1}
883
+ isActive={true}
884
+ operationState={IDLE_STATE}
885
+ containerSize={containerSize}
886
+ >
887
+ Active
888
+ </SwipeStackContent>,
889
+ );
890
+
891
+ const behindEl = behindResult.container.firstChild as HTMLElement;
892
+ const activeEl = activeResult.container.firstChild as HTMLElement;
893
+
894
+ // Over-swipe 125%
895
+ const overSwipeState = createOperatingState(500);
896
+
897
+ behindResult.rerender(
898
+ <SwipeStackContent
899
+ id="behind"
900
+ depth={0}
901
+ navigationDepth={1}
902
+ isActive={false}
903
+ operationState={overSwipeState}
904
+ containerSize={containerSize}
905
+ >
906
+ Behind
907
+ </SwipeStackContent>,
908
+ );
909
+
910
+ activeResult.rerender(
911
+ <SwipeStackContent
912
+ id="active"
913
+ depth={1}
914
+ navigationDepth={1}
915
+ isActive={true}
916
+ operationState={overSwipeState}
917
+ containerSize={containerSize}
918
+ >
919
+ Active
920
+ </SwipeStackContent>,
921
+ );
922
+
923
+ // Positions during over-swipe
924
+ expect(behindEl.style.transform).toBe("translateX(0px)");
925
+ expect(activeEl.style.transform).toBe("translateX(500px)");
926
+
927
+ // Over-swipe triggers navigation change while STILL SWIPING
928
+ behindResult.rerender(
929
+ <SwipeStackContent
930
+ id="behind"
931
+ depth={0}
932
+ navigationDepth={0}
933
+ isActive={true}
934
+ operationState={overSwipeState}
935
+ containerSize={containerSize}
936
+ >
937
+ Behind
938
+ </SwipeStackContent>,
939
+ );
940
+
941
+ activeResult.rerender(
942
+ <SwipeStackContent
943
+ id="active"
944
+ depth={1}
945
+ navigationDepth={0}
946
+ isActive={false}
947
+ operationState={overSwipeState}
948
+ containerSize={containerSize}
949
+ >
950
+ Active
951
+ </SwipeStackContent>,
952
+ );
953
+
954
+ // Panels maintain positions (useOperationContinuity keeps using old roles)
955
+ expect(behindEl.style.transform).toBe("translateX(0px)");
956
+ expect(activeEl.style.transform).toBe("translateX(500px)");
957
+
958
+ // Now release - gesture ends
959
+ behindResult.rerender(
960
+ <SwipeStackContent
961
+ id="behind"
962
+ depth={0}
963
+ navigationDepth={0}
964
+ isActive={true}
965
+ operationState={IDLE_STATE}
966
+ containerSize={containerSize}
967
+ >
968
+ Behind
969
+ </SwipeStackContent>,
970
+ );
971
+
972
+ activeResult.rerender(
973
+ <SwipeStackContent
974
+ id="active"
975
+ depth={1}
976
+ navigationDepth={0}
977
+ isActive={false}
978
+ operationState={IDLE_STATE}
979
+ containerSize={containerSize}
980
+ >
981
+ Active
982
+ </SwipeStackContent>,
983
+ );
984
+
985
+ // Behind (now active) should stay at 0
986
+ expect(behindEl.style.transform).toBe("translateX(0px)");
987
+ // Active (now exiting) snaps to target (400px) - no backward ANIMATION
988
+ expect(activeEl.style.transform).toBe("translateX(400px)");
989
+
990
+ act(() => {
991
+ flushRAF(0);
992
+ flushRAF(400);
993
+ });
994
+
995
+ // Final positions - both at their target positions, no animation occurred
996
+ expect(behindEl.style.transform).toBe("translateX(0px)");
997
+ expect(activeEl.style.transform).toBe("translateX(400px)");
998
+ });
999
+ });
1000
+
1001
+ describe("exiting panel behavior", () => {
1002
+ /**
1003
+ * Scenario: Panel exits (becomes hidden) after swipe back.
1004
+ *
1005
+ * When a panel at depth 1 becomes hidden (navigationDepth changes to 0),
1006
+ * it should animate off-screen from its current position.
1007
+ */
1008
+ it("exiting panel animates from current position", () => {
1009
+ const containerSize = 400;
1010
+
1011
+ // Panel at depth 1, currently active
1012
+ const { container, rerender } = render(
1013
+ <SwipeStackContent
1014
+ id="exiting"
1015
+ depth={1}
1016
+ navigationDepth={1}
1017
+ isActive={true}
1018
+ operationState={IDLE_STATE}
1019
+ containerSize={containerSize}
1020
+ >
1021
+ Content
1022
+ </SwipeStackContent>,
1023
+ );
1024
+
1025
+ const element = container.firstChild as HTMLElement;
1026
+ expect(element.style.transform).toBe("translateX(0px)");
1027
+
1028
+ // Swipe 100% to go back
1029
+ rerender(
1030
+ <SwipeStackContent
1031
+ id="exiting"
1032
+ depth={1}
1033
+ navigationDepth={1}
1034
+ isActive={true}
1035
+ operationState={createOperatingState(400)}
1036
+ containerSize={containerSize}
1037
+ >
1038
+ Content
1039
+ </SwipeStackContent>,
1040
+ );
1041
+ expect(element.style.transform).toBe("translateX(400px)");
1042
+
1043
+ // Navigation changes - panel becomes hidden
1044
+ rerender(
1045
+ <SwipeStackContent
1046
+ id="exiting"
1047
+ depth={1}
1048
+ navigationDepth={0}
1049
+ isActive={false}
1050
+ operationState={IDLE_STATE}
1051
+ containerSize={containerSize}
1052
+ >
1053
+ Content
1054
+ </SwipeStackContent>,
1055
+ );
1056
+
1057
+ // Panel is already at 400px (off-screen), should stay there
1058
+ expect(element.style.transform).toBe("translateX(400px)");
1059
+
1060
+ act(() => {
1061
+ flushRAF(0);
1062
+ flushRAF(400);
1063
+ });
1064
+
1065
+ // After animation, should be at containerSize (off-screen)
1066
+ expect(element.style.transform).toBe("translateX(400px)");
1067
+ });
1068
+
1069
+ /**
1070
+ * Scenario: Panel exits after 80% swipe.
1071
+ *
1072
+ * The panel should animate from ~320px to 400px, not from 0px.
1073
+ */
1074
+ it("exiting panel at 80% swipe animates from 320px to 400px", () => {
1075
+ const containerSize = 400;
1076
+
1077
+ const { container, rerender } = render(
1078
+ <SwipeStackContent
1079
+ id="exiting"
1080
+ depth={1}
1081
+ navigationDepth={1}
1082
+ isActive={true}
1083
+ operationState={IDLE_STATE}
1084
+ containerSize={containerSize}
1085
+ >
1086
+ Content
1087
+ </SwipeStackContent>,
1088
+ );
1089
+
1090
+ const element = container.firstChild as HTMLElement;
1091
+
1092
+ // Swipe 80%
1093
+ rerender(
1094
+ <SwipeStackContent
1095
+ id="exiting"
1096
+ depth={1}
1097
+ navigationDepth={1}
1098
+ isActive={true}
1099
+ operationState={createOperatingState(320)}
1100
+ containerSize={containerSize}
1101
+ >
1102
+ Content
1103
+ </SwipeStackContent>,
1104
+ );
1105
+ expect(element.style.transform).toBe("translateX(320px)");
1106
+
1107
+ // Navigation changes
1108
+ rerender(
1109
+ <SwipeStackContent
1110
+ id="exiting"
1111
+ depth={1}
1112
+ navigationDepth={0}
1113
+ isActive={false}
1114
+ operationState={IDLE_STATE}
1115
+ containerSize={containerSize}
1116
+ >
1117
+ Content
1118
+ </SwipeStackContent>,
1119
+ );
1120
+
1121
+ // Should be at 320px (animation starting point)
1122
+ expect(element.style.transform).toBe("translateX(320px)");
1123
+
1124
+ act(() => {
1125
+ flushRAF(0);
1126
+ flushRAF(400);
1127
+ });
1128
+
1129
+ // After animation, at containerSize
1130
+ expect(element.style.transform).toBe("translateX(400px)");
1131
+ });
1132
+ });
1133
+ });