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,1564 @@
1
+ /**
2
+ * @file Tests for SwipeStackContent component.
3
+ *
4
+ * These tests ensure:
5
+ * 1. Swipe preview: active panel follows finger, behind panel reveals with parallax
6
+ * 2. Swipe commit: when swipe completes, navigation changes and panels animate to final positions
7
+ * 3. Swipe cancel: when swipe is released without threshold, panels snap back
8
+ * 4. Button navigation: when navigationDepth changes without swipe, panels should animate
9
+ */
10
+ import { render, act } from "@testing-library/react";
11
+ import { SwipeStackContent } from "./SwipeStackContent.js";
12
+ import type { ContinuousOperationState } from "../../hooks/gesture/types.js";
13
+
14
+ // Mock requestAnimationFrame for animation testing
15
+ const rafState = {
16
+ callbacks: [] as FrameRequestCallback[],
17
+ id: 0,
18
+ mockTimestamp: 0,
19
+ originalRAF: globalThis.requestAnimationFrame,
20
+ originalCAF: globalThis.cancelAnimationFrame,
21
+ };
22
+
23
+ const resetRafState = (): void => {
24
+ rafState.callbacks = [];
25
+ rafState.id = 0;
26
+ rafState.mockTimestamp = 0;
27
+ };
28
+
29
+ const mockRAF = (callback: FrameRequestCallback): number => {
30
+ rafState.callbacks = [...rafState.callbacks, callback];
31
+ rafState.id += 1;
32
+ return rafState.id;
33
+ };
34
+
35
+ const mockCAF = (): void => {};
36
+
37
+ /**
38
+ * Flush all pending RAF callbacks.
39
+ * @param advanceMs - How much to advance the mock timestamp (default: 400ms to complete 300ms animation)
40
+ */
41
+ const flushRAF = (advanceMs = 400): void => {
42
+ rafState.mockTimestamp += advanceMs;
43
+ const callbacks = rafState.callbacks;
44
+ rafState.callbacks = [];
45
+ callbacks.forEach((cb) => cb(rafState.mockTimestamp));
46
+ };
47
+
48
+ beforeEach(() => {
49
+ resetRafState();
50
+ globalThis.requestAnimationFrame = mockRAF;
51
+ globalThis.cancelAnimationFrame = mockCAF;
52
+ });
53
+
54
+ afterEach(() => {
55
+ globalThis.requestAnimationFrame = rafState.originalRAF;
56
+ globalThis.cancelAnimationFrame = rafState.originalCAF;
57
+ });
58
+
59
+ const IDLE_STATE: ContinuousOperationState = {
60
+ phase: "idle",
61
+ displacement: { x: 0, y: 0 },
62
+ velocity: { x: 0, y: 0 },
63
+ };
64
+
65
+ const createOperatingState = (displacementX: number): ContinuousOperationState => ({
66
+ phase: "operating",
67
+ displacement: { x: displacementX, y: 0 },
68
+ velocity: { x: 0.5, y: 0 },
69
+ });
70
+
71
+ describe("SwipeStackContent", () => {
72
+ describe("rendering", () => {
73
+ it("renders children correctly", () => {
74
+ const { getByText } = render(
75
+ <SwipeStackContent
76
+ id="test"
77
+ depth={0}
78
+ navigationDepth={0}
79
+ isActive={true}
80
+ operationState={IDLE_STATE}
81
+ containerSize={400}
82
+ >
83
+ <div>Test Content</div>
84
+ </SwipeStackContent>,
85
+ );
86
+
87
+ expect(getByText("Test Content")).toBeInTheDocument();
88
+ });
89
+
90
+ it("applies correct data attributes", () => {
91
+ const { container } = render(
92
+ <SwipeStackContent
93
+ id="panel-1"
94
+ depth={1}
95
+ navigationDepth={1}
96
+ isActive={true}
97
+ operationState={IDLE_STATE}
98
+ containerSize={400}
99
+ >
100
+ Content
101
+ </SwipeStackContent>,
102
+ );
103
+
104
+ const element = container.firstChild as HTMLElement;
105
+ expect(element.getAttribute("data-stack-content")).toBe("panel-1");
106
+ expect(element.getAttribute("data-depth")).toBe("1");
107
+ expect(element.getAttribute("data-active")).toBe("true");
108
+ expect(element.getAttribute("data-role")).toBe("active");
109
+ });
110
+
111
+ it("applies correct role for behind panel", () => {
112
+ const { container } = render(
113
+ <SwipeStackContent
114
+ id="panel-0"
115
+ depth={0}
116
+ navigationDepth={1}
117
+ isActive={false}
118
+ operationState={IDLE_STATE}
119
+ containerSize={400}
120
+ >
121
+ Content
122
+ </SwipeStackContent>,
123
+ );
124
+
125
+ const element = container.firstChild as HTMLElement;
126
+ expect(element.getAttribute("data-role")).toBe("behind");
127
+ });
128
+ });
129
+
130
+ describe("visibility", () => {
131
+ it("active panel is always visible", () => {
132
+ const { container } = render(
133
+ <SwipeStackContent
134
+ id="test"
135
+ depth={1}
136
+ navigationDepth={1}
137
+ isActive={true}
138
+ operationState={IDLE_STATE}
139
+ containerSize={400}
140
+ >
141
+ Content
142
+ </SwipeStackContent>,
143
+ );
144
+
145
+ const element = container.firstChild as HTMLElement;
146
+ expect(element.style.visibility).toBe("visible");
147
+ });
148
+
149
+ it("behind panel is hidden when idle", () => {
150
+ const { container } = render(
151
+ <SwipeStackContent
152
+ id="test"
153
+ depth={0}
154
+ navigationDepth={1}
155
+ isActive={false}
156
+ operationState={IDLE_STATE}
157
+ containerSize={400}
158
+ >
159
+ Content
160
+ </SwipeStackContent>,
161
+ );
162
+
163
+ const element = container.firstChild as HTMLElement;
164
+ expect(element.style.visibility).toBe("hidden");
165
+ });
166
+
167
+ it("behind panel is visible when swiping", () => {
168
+ const { container } = render(
169
+ <SwipeStackContent
170
+ id="test"
171
+ depth={0}
172
+ navigationDepth={1}
173
+ isActive={false}
174
+ operationState={createOperatingState(100)}
175
+ containerSize={400}
176
+ >
177
+ Content
178
+ </SwipeStackContent>,
179
+ );
180
+
181
+ const element = container.firstChild as HTMLElement;
182
+ expect(element.style.visibility).toBe("visible");
183
+ });
184
+ });
185
+
186
+ describe("transform during swipe", () => {
187
+ it("active panel follows displacement", () => {
188
+ const { container, rerender } = render(
189
+ <SwipeStackContent
190
+ id="test"
191
+ depth={1}
192
+ navigationDepth={1}
193
+ isActive={true}
194
+ operationState={IDLE_STATE}
195
+ containerSize={400}
196
+ >
197
+ Content
198
+ </SwipeStackContent>,
199
+ );
200
+
201
+ // Start swiping
202
+ rerender(
203
+ <SwipeStackContent
204
+ id="test"
205
+ depth={1}
206
+ navigationDepth={1}
207
+ isActive={true}
208
+ operationState={createOperatingState(150)}
209
+ containerSize={400}
210
+ >
211
+ Content
212
+ </SwipeStackContent>,
213
+ );
214
+
215
+ const element = container.firstChild as HTMLElement;
216
+ expect(element.style.transform).toBe("translateX(150px)");
217
+ });
218
+
219
+ it("behind panel uses parallax effect", () => {
220
+ const { container, rerender } = render(
221
+ <SwipeStackContent
222
+ id="test"
223
+ depth={0}
224
+ navigationDepth={1}
225
+ isActive={false}
226
+ operationState={IDLE_STATE}
227
+ containerSize={400}
228
+ >
229
+ Content
230
+ </SwipeStackContent>,
231
+ );
232
+
233
+ // Start swiping - at 50% progress (200px), behind should move from -120 to -60
234
+ rerender(
235
+ <SwipeStackContent
236
+ id="test"
237
+ depth={0}
238
+ navigationDepth={1}
239
+ isActive={false}
240
+ operationState={createOperatingState(200)}
241
+ containerSize={400}
242
+ >
243
+ Content
244
+ </SwipeStackContent>,
245
+ );
246
+
247
+ const element = container.firstChild as HTMLElement;
248
+ // Behind panel should be at -60px (halfway from -120 to 0)
249
+ expect(element.style.transform).toBe("translateX(-60px)");
250
+ });
251
+ });
252
+
253
+ describe("z-index", () => {
254
+ it("applies z-index based on depth", () => {
255
+ const { container } = render(
256
+ <SwipeStackContent
257
+ id="test"
258
+ depth={3}
259
+ navigationDepth={3}
260
+ isActive={true}
261
+ operationState={IDLE_STATE}
262
+ containerSize={400}
263
+ >
264
+ Content
265
+ </SwipeStackContent>,
266
+ );
267
+
268
+ const element = container.firstChild as HTMLElement;
269
+ expect(element.style.zIndex).toBe("3");
270
+ });
271
+ });
272
+
273
+ describe("pointer events", () => {
274
+ it("active panel has auto pointer events", () => {
275
+ const { container } = render(
276
+ <SwipeStackContent
277
+ id="test"
278
+ depth={1}
279
+ navigationDepth={1}
280
+ isActive={true}
281
+ operationState={IDLE_STATE}
282
+ containerSize={400}
283
+ >
284
+ Content
285
+ </SwipeStackContent>,
286
+ );
287
+
288
+ const element = container.firstChild as HTMLElement;
289
+ expect(element.style.pointerEvents).toBe("auto");
290
+ });
291
+
292
+ it("inactive panel has none pointer events", () => {
293
+ const { container } = render(
294
+ <SwipeStackContent
295
+ id="test"
296
+ depth={0}
297
+ navigationDepth={1}
298
+ isActive={false}
299
+ operationState={IDLE_STATE}
300
+ containerSize={400}
301
+ >
302
+ Content
303
+ </SwipeStackContent>,
304
+ );
305
+
306
+ const element = container.firstChild as HTMLElement;
307
+ expect(element.style.pointerEvents).toBe("none");
308
+ });
309
+ });
310
+
311
+ describe("swipe preview behavior", () => {
312
+ it("active panel position changes immediately with displacement during swiping", () => {
313
+ const { container, rerender } = render(
314
+ <SwipeStackContent
315
+ id="active"
316
+ depth={1}
317
+ navigationDepth={1}
318
+ isActive={true}
319
+ operationState={IDLE_STATE}
320
+ containerSize={400}
321
+ >
322
+ Content
323
+ </SwipeStackContent>,
324
+ );
325
+
326
+ const element = container.firstChild as HTMLElement;
327
+
328
+ // Initial position
329
+ expect(element.style.transform).toBe("translateX(0px)");
330
+
331
+ // Start tracking (before direction lock) - displacement is still applied
332
+ // because we want responsive feedback even before direction is confirmed
333
+ rerender(
334
+ <SwipeStackContent
335
+ id="active"
336
+ depth={1}
337
+ navigationDepth={1}
338
+ isActive={true}
339
+ operationState={createOperatingState(50)}
340
+ containerSize={400}
341
+ >
342
+ Content
343
+ </SwipeStackContent>,
344
+ );
345
+ // Tracking phase also applies displacement for responsive feedback
346
+ expect(element.style.transform).toBe("translateX(50px)");
347
+
348
+ // Swipe confirmed (direction locked)
349
+ rerender(
350
+ <SwipeStackContent
351
+ id="active"
352
+ depth={1}
353
+ navigationDepth={1}
354
+ isActive={true}
355
+ operationState={createOperatingState(100)}
356
+ containerSize={400}
357
+ >
358
+ Content
359
+ </SwipeStackContent>,
360
+ );
361
+ expect(element.style.transform).toBe("translateX(100px)");
362
+
363
+ // Continue swiping
364
+ rerender(
365
+ <SwipeStackContent
366
+ id="active"
367
+ depth={1}
368
+ navigationDepth={1}
369
+ isActive={true}
370
+ operationState={createOperatingState(250)}
371
+ containerSize={400}
372
+ >
373
+ Content
374
+ </SwipeStackContent>,
375
+ );
376
+ expect(element.style.transform).toBe("translateX(250px)");
377
+ });
378
+
379
+ it("behind panel reveals progressively during swipe with parallax effect", () => {
380
+ const { container, rerender } = render(
381
+ <SwipeStackContent
382
+ id="behind"
383
+ depth={0}
384
+ navigationDepth={1}
385
+ isActive={false}
386
+ operationState={IDLE_STATE}
387
+ containerSize={400}
388
+ >
389
+ Content
390
+ </SwipeStackContent>,
391
+ );
392
+
393
+ const element = container.firstChild as HTMLElement;
394
+
395
+ // Initial: behind panel at -30% = -120px
396
+ expect(element.style.transform).toBe("translateX(-120px)");
397
+ expect(element.style.visibility).toBe("hidden");
398
+
399
+ // Swipe 25% (100px of 400px)
400
+ rerender(
401
+ <SwipeStackContent
402
+ id="behind"
403
+ depth={0}
404
+ navigationDepth={1}
405
+ isActive={false}
406
+ operationState={createOperatingState(100)}
407
+ containerSize={400}
408
+ >
409
+ Content
410
+ </SwipeStackContent>,
411
+ );
412
+ // Behind moves 25% of 120px = 30px, so from -120 to -90
413
+ expect(element.style.transform).toBe("translateX(-90px)");
414
+ expect(element.style.visibility).toBe("visible");
415
+
416
+ // Swipe 50% (200px)
417
+ rerender(
418
+ <SwipeStackContent
419
+ id="behind"
420
+ depth={0}
421
+ navigationDepth={1}
422
+ isActive={false}
423
+ operationState={createOperatingState(200)}
424
+ containerSize={400}
425
+ >
426
+ Content
427
+ </SwipeStackContent>,
428
+ );
429
+ expect(element.style.transform).toBe("translateX(-60px)");
430
+
431
+ // Swipe 100% (400px) - behind should be at 0
432
+ rerender(
433
+ <SwipeStackContent
434
+ id="behind"
435
+ depth={0}
436
+ navigationDepth={1}
437
+ isActive={false}
438
+ operationState={createOperatingState(400)}
439
+ containerSize={400}
440
+ >
441
+ Content
442
+ </SwipeStackContent>,
443
+ );
444
+ expect(element.style.transform).toBe("translateX(0px)");
445
+ });
446
+
447
+ it("does not respond to negative displacement (swiping wrong direction)", () => {
448
+ const { container, rerender } = render(
449
+ <SwipeStackContent
450
+ id="active"
451
+ depth={1}
452
+ navigationDepth={1}
453
+ isActive={true}
454
+ operationState={IDLE_STATE}
455
+ containerSize={400}
456
+ >
457
+ Content
458
+ </SwipeStackContent>,
459
+ );
460
+
461
+ const element = container.firstChild as HTMLElement;
462
+
463
+ // Swipe left (negative) should be clamped to 0
464
+ rerender(
465
+ <SwipeStackContent
466
+ id="active"
467
+ depth={1}
468
+ navigationDepth={1}
469
+ isActive={true}
470
+ operationState={createOperatingState(-100)}
471
+ containerSize={400}
472
+ >
473
+ Content
474
+ </SwipeStackContent>,
475
+ );
476
+ expect(element.style.transform).toBe("translateX(0px)");
477
+ });
478
+ });
479
+
480
+ describe("swipe release and snap-back", () => {
481
+ it("returns to original position when swipe ends without navigation change", () => {
482
+ const { container, rerender } = render(
483
+ <SwipeStackContent
484
+ id="active"
485
+ depth={1}
486
+ navigationDepth={1}
487
+ isActive={true}
488
+ operationState={IDLE_STATE}
489
+ containerSize={400}
490
+ >
491
+ Content
492
+ </SwipeStackContent>,
493
+ );
494
+
495
+ const element = container.firstChild as HTMLElement;
496
+
497
+ // Swipe in progress
498
+ rerender(
499
+ <SwipeStackContent
500
+ id="active"
501
+ depth={1}
502
+ navigationDepth={1}
503
+ isActive={true}
504
+ operationState={createOperatingState(150)}
505
+ containerSize={400}
506
+ >
507
+ Content
508
+ </SwipeStackContent>,
509
+ );
510
+ expect(element.style.transform).toBe("translateX(150px)");
511
+
512
+ // Swipe released (back to idle) - animation starts
513
+ rerender(
514
+ <SwipeStackContent
515
+ id="active"
516
+ depth={1}
517
+ navigationDepth={1}
518
+ isActive={true}
519
+ operationState={IDLE_STATE}
520
+ containerSize={400}
521
+ >
522
+ Content
523
+ </SwipeStackContent>,
524
+ );
525
+
526
+ // Flush RAF within act() to complete the snap-back animation
527
+ act(() => {
528
+ flushRAF(0); // First flush starts the animation (sets startTime)
529
+ flushRAF(400); // Second flush completes the animation (elapsed > duration)
530
+ });
531
+
532
+ // After animation completes, should be at 0
533
+ expect(element.style.transform).toBe("translateX(0px)");
534
+ });
535
+
536
+ it("behind panel returns to hidden position when swipe is canceled", () => {
537
+ const { container, rerender } = render(
538
+ <SwipeStackContent
539
+ id="behind"
540
+ depth={0}
541
+ navigationDepth={1}
542
+ isActive={false}
543
+ operationState={IDLE_STATE}
544
+ containerSize={400}
545
+ >
546
+ Content
547
+ </SwipeStackContent>,
548
+ );
549
+
550
+ const element = container.firstChild as HTMLElement;
551
+
552
+ // Swipe in progress
553
+ rerender(
554
+ <SwipeStackContent
555
+ id="behind"
556
+ depth={0}
557
+ navigationDepth={1}
558
+ isActive={false}
559
+ operationState={createOperatingState(200)}
560
+ containerSize={400}
561
+ >
562
+ Content
563
+ </SwipeStackContent>,
564
+ );
565
+ expect(element.style.transform).toBe("translateX(-60px)");
566
+ expect(element.style.visibility).toBe("visible");
567
+
568
+ // Swipe canceled - animation starts
569
+ rerender(
570
+ <SwipeStackContent
571
+ id="behind"
572
+ depth={0}
573
+ navigationDepth={1}
574
+ isActive={false}
575
+ operationState={IDLE_STATE}
576
+ containerSize={400}
577
+ >
578
+ Content
579
+ </SwipeStackContent>,
580
+ );
581
+
582
+ // Flush RAF within act() to complete the snap-back animation
583
+ act(() => {
584
+ flushRAF(0); // First flush starts the animation (sets startTime)
585
+ flushRAF(400); // Second flush completes the animation (elapsed > duration)
586
+ });
587
+
588
+ // After animation completes, should be at -120px
589
+ expect(element.style.transform).toBe("translateX(-120px)");
590
+ });
591
+ });
592
+
593
+ describe("button navigation (without swipe)", () => {
594
+ it("animates behind panel to center when going back via button", () => {
595
+ const { container, rerender } = render(
596
+ <SwipeStackContent
597
+ id="panel"
598
+ depth={0}
599
+ navigationDepth={1}
600
+ isActive={false}
601
+ operationState={IDLE_STATE}
602
+ containerSize={400}
603
+ >
604
+ Content
605
+ </SwipeStackContent>,
606
+ );
607
+
608
+ const element = container.firstChild as HTMLElement;
609
+ // Initial: behind panel at -30% = -120px
610
+ expect(element.style.transform).toBe("translateX(-120px)");
611
+
612
+ // Button click triggers navigation change: depth 0 becomes active
613
+ rerender(
614
+ <SwipeStackContent
615
+ id="panel"
616
+ depth={0}
617
+ navigationDepth={0}
618
+ isActive={true}
619
+ operationState={IDLE_STATE}
620
+ containerSize={400}
621
+ >
622
+ Content
623
+ </SwipeStackContent>,
624
+ );
625
+
626
+ // Animation should start: still at -120px immediately
627
+ // (not snapping directly to 0)
628
+ expect(element.style.transform).toBe("translateX(-120px)");
629
+
630
+ // Flush RAF to complete animation
631
+ act(() => {
632
+ flushRAF(0);
633
+ flushRAF(400);
634
+ });
635
+
636
+ // After animation completes, should be at 0
637
+ expect(element.style.transform).toBe("translateX(0px)");
638
+ });
639
+
640
+ it("animates active panel off-screen when going back via button", () => {
641
+ const { container, rerender } = render(
642
+ <SwipeStackContent
643
+ id="panel"
644
+ depth={1}
645
+ navigationDepth={1}
646
+ isActive={true}
647
+ operationState={IDLE_STATE}
648
+ containerSize={400}
649
+ >
650
+ Content
651
+ </SwipeStackContent>,
652
+ );
653
+
654
+ const element = container.firstChild as HTMLElement;
655
+ // Initial: active panel at 0
656
+ expect(element.style.transform).toBe("translateX(0px)");
657
+
658
+ // Button click triggers navigation change: depth 1 becomes hidden
659
+ rerender(
660
+ <SwipeStackContent
661
+ id="panel"
662
+ depth={1}
663
+ navigationDepth={0}
664
+ isActive={false}
665
+ operationState={IDLE_STATE}
666
+ containerSize={400}
667
+ >
668
+ Content
669
+ </SwipeStackContent>,
670
+ );
671
+
672
+ // Animation should start: still at 0px immediately
673
+ // (not snapping directly to 400)
674
+ expect(element.style.transform).toBe("translateX(0px)");
675
+
676
+ // Flush RAF to complete animation
677
+ act(() => {
678
+ flushRAF(0);
679
+ flushRAF(400);
680
+ });
681
+
682
+ // After animation completes, should be at 400px (off-screen)
683
+ expect(element.style.transform).toBe("translateX(400px)");
684
+ });
685
+ });
686
+
687
+ describe("forward navigation (push)", () => {
688
+ it("new panel animates in from off-screen when first mounted as active with animateOnMount", () => {
689
+ // Scenario: Panel is mounted for the first time as active (push navigation)
690
+ // This is what happens when visiblePanelIds changes to include the new panel
691
+ const { container } = render(
692
+ <SwipeStackContent
693
+ id="newPanel"
694
+ depth={1}
695
+ navigationDepth={1}
696
+ isActive={true}
697
+ operationState={IDLE_STATE}
698
+ containerSize={400}
699
+ animateOnMount={true}
700
+ >
701
+ Content
702
+ </SwipeStackContent>,
703
+ );
704
+
705
+ const element = container.firstChild as HTMLElement;
706
+
707
+ // On first mount as active with animateOnMount, panel should start at off-screen position
708
+ // (not at 0, which would cause a jarring pop-in)
709
+ expect(element.style.transform).toBe("translateX(400px)");
710
+
711
+ // Flush RAF to complete the entrance animation
712
+ act(() => {
713
+ flushRAF(0);
714
+ flushRAF(400);
715
+ });
716
+
717
+ // After animation: at center (0)
718
+ expect(element.style.transform).toBe("translateX(0px)");
719
+ });
720
+
721
+ it("pre-existing hidden panel animates in when pushed", () => {
722
+ // Scenario: Panel already exists but was hidden, then becomes active
723
+ const { container, rerender } = render(
724
+ <SwipeStackContent
725
+ id="newPanel"
726
+ depth={1}
727
+ navigationDepth={0}
728
+ isActive={false}
729
+ operationState={IDLE_STATE}
730
+ containerSize={400}
731
+ >
732
+ Content
733
+ </SwipeStackContent>,
734
+ );
735
+
736
+ const element = container.firstChild as HTMLElement;
737
+ // Hidden panel should be at containerSize (off-screen right)
738
+ expect(element.style.transform).toBe("translateX(400px)");
739
+
740
+ // Push navigation: panel becomes active
741
+ rerender(
742
+ <SwipeStackContent
743
+ id="newPanel"
744
+ depth={1}
745
+ navigationDepth={1}
746
+ isActive={true}
747
+ operationState={IDLE_STATE}
748
+ containerSize={400}
749
+ >
750
+ Content
751
+ </SwipeStackContent>,
752
+ );
753
+
754
+ // Animation should start: still at 400px immediately (not snapping to 0)
755
+ expect(element.style.transform).toBe("translateX(400px)");
756
+
757
+ // Flush RAF to complete animation
758
+ act(() => {
759
+ flushRAF(0);
760
+ flushRAF(400);
761
+ });
762
+
763
+ // After animation: at center (0)
764
+ expect(element.style.transform).toBe("translateX(0px)");
765
+ });
766
+
767
+ it("behind panel animates to offset position when new panel is pushed", () => {
768
+ // First render: panel at depth 0 is active
769
+ const { container, rerender } = render(
770
+ <SwipeStackContent
771
+ id="currentPanel"
772
+ depth={0}
773
+ navigationDepth={0}
774
+ isActive={true}
775
+ operationState={IDLE_STATE}
776
+ containerSize={400}
777
+ >
778
+ Content
779
+ </SwipeStackContent>,
780
+ );
781
+
782
+ const element = container.firstChild as HTMLElement;
783
+ // Active panel at center
784
+ expect(element.style.transform).toBe("translateX(0px)");
785
+
786
+ // Push navigation: this panel becomes "behind"
787
+ rerender(
788
+ <SwipeStackContent
789
+ id="currentPanel"
790
+ depth={0}
791
+ navigationDepth={1}
792
+ isActive={false}
793
+ operationState={IDLE_STATE}
794
+ containerSize={400}
795
+ >
796
+ Content
797
+ </SwipeStackContent>,
798
+ );
799
+
800
+ // Animation should start: still at 0px immediately (not snapping to -120px)
801
+ expect(element.style.transform).toBe("translateX(0px)");
802
+
803
+ // Flush RAF to complete animation
804
+ act(() => {
805
+ flushRAF(0);
806
+ flushRAF(400);
807
+ });
808
+
809
+ // After animation: at behind offset (-30% = -120px)
810
+ expect(element.style.transform).toBe("translateX(-120px)");
811
+ });
812
+ });
813
+
814
+ describe("mount → push → back sequence", () => {
815
+ it("panel animates from correct position after push then back", () => {
816
+ // This test covers the bug where:
817
+ // 1. Mount: root panel at depth 0, active
818
+ // 2. Push: new panel at depth 1 is mounted with animateOnMount
819
+ // 3. Back: the panel at depth 1 should animate from its current position (0) to off-screen
820
+
821
+ // Step 1: Mount panel at depth 1 as active (simulating push navigation)
822
+ const { container, rerender } = render(
823
+ <SwipeStackContent
824
+ id="panel1"
825
+ depth={1}
826
+ navigationDepth={1}
827
+ isActive={true}
828
+ operationState={IDLE_STATE}
829
+ containerSize={400}
830
+ animateOnMount={true}
831
+ >
832
+ Content
833
+ </SwipeStackContent>,
834
+ );
835
+
836
+ const element = container.firstChild as HTMLElement;
837
+
838
+ // Initially at off-screen (400px) due to animateOnMount
839
+ expect(element.style.transform).toBe("translateX(400px)");
840
+
841
+ // Complete the push animation
842
+ act(() => {
843
+ flushRAF(0);
844
+ flushRAF(400);
845
+ });
846
+
847
+ // After push animation: should be at center (0)
848
+ expect(element.style.transform).toBe("translateX(0px)");
849
+
850
+ // Step 2: Go back - navigation depth changes, panel becomes hidden
851
+ rerender(
852
+ <SwipeStackContent
853
+ id="panel1"
854
+ depth={1}
855
+ navigationDepth={0}
856
+ isActive={false}
857
+ operationState={IDLE_STATE}
858
+ containerSize={400}
859
+ animateOnMount={true}
860
+ >
861
+ Content
862
+ </SwipeStackContent>,
863
+ );
864
+
865
+ // Animation should start from current position (0px), not from initialPx (400px)
866
+ // The bug is that it might jump to 400px immediately because of animateOnMount logic
867
+ expect(element.style.transform).toBe("translateX(0px)");
868
+
869
+ // Complete the back animation
870
+ act(() => {
871
+ flushRAF(0);
872
+ flushRAF(400);
873
+ });
874
+
875
+ // After back animation: should be off-screen (400px)
876
+ expect(element.style.transform).toBe("translateX(400px)");
877
+ });
878
+
879
+ it("behind panel animates correctly in mount → push → back sequence", () => {
880
+ // Track the behind panel (depth 0) through the sequence
881
+
882
+ // Step 1: Mount as active
883
+ const { container, rerender } = render(
884
+ <SwipeStackContent
885
+ id="rootPanel"
886
+ depth={0}
887
+ navigationDepth={0}
888
+ isActive={true}
889
+ operationState={IDLE_STATE}
890
+ containerSize={400}
891
+ >
892
+ Content
893
+ </SwipeStackContent>,
894
+ );
895
+
896
+ const element = container.firstChild as HTMLElement;
897
+ expect(element.style.transform).toBe("translateX(0px)");
898
+
899
+ // Step 2: Push - this panel becomes "behind"
900
+ rerender(
901
+ <SwipeStackContent
902
+ id="rootPanel"
903
+ depth={0}
904
+ navigationDepth={1}
905
+ isActive={false}
906
+ operationState={IDLE_STATE}
907
+ containerSize={400}
908
+ >
909
+ Content
910
+ </SwipeStackContent>,
911
+ );
912
+
913
+ // Animation starts from 0px
914
+ expect(element.style.transform).toBe("translateX(0px)");
915
+
916
+ act(() => {
917
+ flushRAF(0);
918
+ flushRAF(400);
919
+ });
920
+
921
+ // After animation: at behind offset (-120px)
922
+ expect(element.style.transform).toBe("translateX(-120px)");
923
+
924
+ // Step 3: Back - this panel becomes active again
925
+ rerender(
926
+ <SwipeStackContent
927
+ id="rootPanel"
928
+ depth={0}
929
+ navigationDepth={0}
930
+ isActive={true}
931
+ operationState={IDLE_STATE}
932
+ containerSize={400}
933
+ >
934
+ Content
935
+ </SwipeStackContent>,
936
+ );
937
+
938
+ // Animation should start from current position (-120px)
939
+ expect(element.style.transform).toBe("translateX(-120px)");
940
+
941
+ act(() => {
942
+ flushRAF(0);
943
+ flushRAF(400);
944
+ });
945
+
946
+ // After animation: at center (0)
947
+ expect(element.style.transform).toBe("translateX(0px)");
948
+ });
949
+ });
950
+
951
+ describe("shadow effects", () => {
952
+ it("shows shadow on active panel at depth > 0 by default", () => {
953
+ const { container } = render(
954
+ <SwipeStackContent
955
+ id="panel"
956
+ depth={1}
957
+ navigationDepth={1}
958
+ isActive={true}
959
+ operationState={IDLE_STATE}
960
+ containerSize={400}
961
+ >
962
+ Content
963
+ </SwipeStackContent>,
964
+ );
965
+
966
+ const element = container.firstChild as HTMLElement;
967
+ expect(element.style.boxShadow).toBe("-5px 0 15px rgba(0, 0, 0, 0.1)");
968
+ });
969
+
970
+ it("does not show shadow on root panel (depth 0)", () => {
971
+ const { container } = render(
972
+ <SwipeStackContent
973
+ id="panel"
974
+ depth={0}
975
+ navigationDepth={0}
976
+ isActive={true}
977
+ operationState={IDLE_STATE}
978
+ containerSize={400}
979
+ >
980
+ Content
981
+ </SwipeStackContent>,
982
+ );
983
+
984
+ const element = container.firstChild as HTMLElement;
985
+ expect(element.style.boxShadow).toBe("");
986
+ });
987
+
988
+ it("does not show shadow on behind panel", () => {
989
+ const { container } = render(
990
+ <SwipeStackContent
991
+ id="panel"
992
+ depth={0}
993
+ navigationDepth={1}
994
+ isActive={false}
995
+ operationState={IDLE_STATE}
996
+ containerSize={400}
997
+ >
998
+ Content
999
+ </SwipeStackContent>,
1000
+ );
1001
+
1002
+ const element = container.firstChild as HTMLElement;
1003
+ expect(element.style.boxShadow).toBe("");
1004
+ });
1005
+
1006
+ it("can disable shadow with showShadow=false", () => {
1007
+ const { container } = render(
1008
+ <SwipeStackContent
1009
+ id="panel"
1010
+ depth={1}
1011
+ navigationDepth={1}
1012
+ isActive={true}
1013
+ operationState={IDLE_STATE}
1014
+ containerSize={400}
1015
+ showShadow={false}
1016
+ >
1017
+ Content
1018
+ </SwipeStackContent>,
1019
+ );
1020
+
1021
+ const element = container.firstChild as HTMLElement;
1022
+ expect(element.style.boxShadow).toBe("");
1023
+ });
1024
+ });
1025
+
1026
+ describe("scale effects (stack displayMode)", () => {
1027
+ it("behind panel has scale < 1 in stack mode", () => {
1028
+ const { container } = render(
1029
+ <SwipeStackContent
1030
+ id="panel"
1031
+ depth={0}
1032
+ navigationDepth={1}
1033
+ isActive={false}
1034
+ operationState={IDLE_STATE}
1035
+ containerSize={400}
1036
+ displayMode="stack"
1037
+ >
1038
+ Content
1039
+ </SwipeStackContent>,
1040
+ );
1041
+
1042
+ const element = container.firstChild as HTMLElement;
1043
+ // Transform should include scale(0.95) for 1 level behind
1044
+ expect(element.style.transform).toContain("scale(0.95)");
1045
+ });
1046
+
1047
+ it("active panel has no scale in stack mode", () => {
1048
+ const { container } = render(
1049
+ <SwipeStackContent
1050
+ id="panel"
1051
+ depth={1}
1052
+ navigationDepth={1}
1053
+ isActive={true}
1054
+ operationState={IDLE_STATE}
1055
+ containerSize={400}
1056
+ displayMode="stack"
1057
+ >
1058
+ Content
1059
+ </SwipeStackContent>,
1060
+ );
1061
+
1062
+ const element = container.firstChild as HTMLElement;
1063
+ // Active panel should not have scale transform (or scale(1))
1064
+ expect(element.style.transform).not.toContain("scale(0.");
1065
+ });
1066
+
1067
+ it("no scale effect in overlay mode", () => {
1068
+ const { container } = render(
1069
+ <SwipeStackContent
1070
+ id="panel"
1071
+ depth={0}
1072
+ navigationDepth={1}
1073
+ isActive={false}
1074
+ operationState={IDLE_STATE}
1075
+ containerSize={400}
1076
+ displayMode="overlay"
1077
+ >
1078
+ Content
1079
+ </SwipeStackContent>,
1080
+ );
1081
+
1082
+ const element = container.firstChild as HTMLElement;
1083
+ // No scale in overlay mode
1084
+ expect(element.style.transform).not.toContain("scale");
1085
+ });
1086
+ });
1087
+
1088
+ describe("dimming overlay", () => {
1089
+ it("shows dimming overlay on behind panel by default", () => {
1090
+ const { container } = render(
1091
+ <SwipeStackContent
1092
+ id="panel"
1093
+ depth={0}
1094
+ navigationDepth={1}
1095
+ isActive={false}
1096
+ operationState={IDLE_STATE}
1097
+ containerSize={400}
1098
+ >
1099
+ Content
1100
+ </SwipeStackContent>,
1101
+ );
1102
+
1103
+ const overlay = container.querySelector("[data-dimming-overlay]");
1104
+ expect(overlay).toBeInTheDocument();
1105
+ });
1106
+
1107
+ it("no dimming overlay on active panel", () => {
1108
+ const { container } = render(
1109
+ <SwipeStackContent
1110
+ id="panel"
1111
+ depth={1}
1112
+ navigationDepth={1}
1113
+ isActive={true}
1114
+ operationState={IDLE_STATE}
1115
+ containerSize={400}
1116
+ >
1117
+ Content
1118
+ </SwipeStackContent>,
1119
+ );
1120
+
1121
+ const overlay = container.querySelector("[data-dimming-overlay]");
1122
+ expect(overlay).not.toBeInTheDocument();
1123
+ });
1124
+
1125
+ it("dimming fades during swipe", () => {
1126
+ const { container, rerender } = render(
1127
+ <SwipeStackContent
1128
+ id="panel"
1129
+ depth={0}
1130
+ navigationDepth={1}
1131
+ isActive={false}
1132
+ operationState={IDLE_STATE}
1133
+ containerSize={400}
1134
+ >
1135
+ Content
1136
+ </SwipeStackContent>,
1137
+ );
1138
+
1139
+ // At rest, should have dimming
1140
+ const initialOverlay = container.querySelector("[data-dimming-overlay]") as HTMLElement;
1141
+ expect(initialOverlay).toBeInTheDocument();
1142
+
1143
+ // During swipe at 100% progress, dimming should be gone
1144
+ rerender(
1145
+ <SwipeStackContent
1146
+ id="panel"
1147
+ depth={0}
1148
+ navigationDepth={1}
1149
+ isActive={false}
1150
+ operationState={createOperatingState(400)}
1151
+ containerSize={400}
1152
+ >
1153
+ Content
1154
+ </SwipeStackContent>,
1155
+ );
1156
+
1157
+ const overlayAfter = container.querySelector("[data-dimming-overlay]") as HTMLElement;
1158
+ // At 100% swipe, opacity should be 0 (no overlay)
1159
+ expect(overlayAfter).not.toBeInTheDocument();
1160
+ });
1161
+
1162
+ it("can disable dimming with showDimming=false", () => {
1163
+ const { container } = render(
1164
+ <SwipeStackContent
1165
+ id="panel"
1166
+ depth={0}
1167
+ navigationDepth={1}
1168
+ isActive={false}
1169
+ operationState={IDLE_STATE}
1170
+ containerSize={400}
1171
+ showDimming={false}
1172
+ >
1173
+ Content
1174
+ </SwipeStackContent>,
1175
+ );
1176
+
1177
+ const overlay = container.querySelector("[data-dimming-overlay]");
1178
+ expect(overlay).not.toBeInTheDocument();
1179
+ });
1180
+ });
1181
+
1182
+ describe("navigation during animation", () => {
1183
+ it("handles push during back animation", () => {
1184
+ // Scenario: Panel is animating back (going from active to hidden)
1185
+ // User quickly clicks to push forward again
1186
+ const { container, rerender } = render(
1187
+ <SwipeStackContent
1188
+ id="panel1"
1189
+ depth={1}
1190
+ navigationDepth={1}
1191
+ isActive={true}
1192
+ operationState={IDLE_STATE}
1193
+ containerSize={400}
1194
+ >
1195
+ Content
1196
+ </SwipeStackContent>,
1197
+ );
1198
+
1199
+ const element = container.firstChild as HTMLElement;
1200
+ expect(element.style.transform).toBe("translateX(0px)");
1201
+
1202
+ // Step 1: Go back - panel becomes hidden, starts animating to off-screen
1203
+ rerender(
1204
+ <SwipeStackContent
1205
+ id="panel1"
1206
+ depth={1}
1207
+ navigationDepth={0}
1208
+ isActive={false}
1209
+ operationState={IDLE_STATE}
1210
+ containerSize={400}
1211
+ >
1212
+ Content
1213
+ </SwipeStackContent>,
1214
+ );
1215
+
1216
+ // Start animation but don't complete it
1217
+ act(() => {
1218
+ flushRAF(0);
1219
+ flushRAF(100); // Partial animation
1220
+ });
1221
+
1222
+ // Panel should be somewhere between 0 and 400
1223
+ const midAnimationTransform = element.style.transform;
1224
+ expect(midAnimationTransform).not.toBe("translateX(0px)");
1225
+ expect(midAnimationTransform).not.toBe("translateX(400px)");
1226
+
1227
+ // Step 2: Push again - panel becomes active again DURING animation
1228
+ rerender(
1229
+ <SwipeStackContent
1230
+ id="panel1"
1231
+ depth={1}
1232
+ navigationDepth={1}
1233
+ isActive={true}
1234
+ operationState={IDLE_STATE}
1235
+ containerSize={400}
1236
+ >
1237
+ Content
1238
+ </SwipeStackContent>,
1239
+ );
1240
+
1241
+ // Complete all pending animations
1242
+ act(() => {
1243
+ flushRAF(0);
1244
+ flushRAF(400);
1245
+ });
1246
+
1247
+ // Panel should end up at center (0), visible
1248
+ expect(element.style.transform).toBe("translateX(0px)");
1249
+ expect(element.style.visibility).toBe("visible");
1250
+ });
1251
+
1252
+ it("handles back during push animation", () => {
1253
+ // Scenario: New panel is animating in from off-screen
1254
+ // User quickly clicks back before animation completes
1255
+ const { container, rerender } = render(
1256
+ <SwipeStackContent
1257
+ id="panel1"
1258
+ depth={1}
1259
+ navigationDepth={1}
1260
+ isActive={true}
1261
+ operationState={IDLE_STATE}
1262
+ containerSize={400}
1263
+ animateOnMount={true}
1264
+ >
1265
+ Content
1266
+ </SwipeStackContent>,
1267
+ );
1268
+
1269
+ const element = container.firstChild as HTMLElement;
1270
+
1271
+ // Panel starts off-screen
1272
+ expect(element.style.transform).toBe("translateX(400px)");
1273
+
1274
+ // Start animation but don't complete it
1275
+ act(() => {
1276
+ flushRAF(0);
1277
+ flushRAF(100); // Partial animation
1278
+ });
1279
+
1280
+ // Panel should be somewhere between 400 and 0
1281
+ const midAnimationTransform = element.style.transform;
1282
+ expect(midAnimationTransform).not.toBe("translateX(400px)");
1283
+ expect(midAnimationTransform).not.toBe("translateX(0px)");
1284
+
1285
+ // Go back during animation
1286
+ rerender(
1287
+ <SwipeStackContent
1288
+ id="panel1"
1289
+ depth={1}
1290
+ navigationDepth={0}
1291
+ isActive={false}
1292
+ operationState={IDLE_STATE}
1293
+ containerSize={400}
1294
+ animateOnMount={true}
1295
+ >
1296
+ Content
1297
+ </SwipeStackContent>,
1298
+ );
1299
+
1300
+ // Complete all pending animations
1301
+ act(() => {
1302
+ flushRAF(0);
1303
+ flushRAF(400);
1304
+ });
1305
+
1306
+ // Panel should end up off-screen (400), hidden
1307
+ expect(element.style.transform).toBe("translateX(400px)");
1308
+ });
1309
+
1310
+ it("handles rapid back-push-back sequence during animations", () => {
1311
+ const { container, rerender } = render(
1312
+ <SwipeStackContent
1313
+ id="panel1"
1314
+ depth={1}
1315
+ navigationDepth={1}
1316
+ isActive={true}
1317
+ operationState={IDLE_STATE}
1318
+ containerSize={400}
1319
+ >
1320
+ Content
1321
+ </SwipeStackContent>,
1322
+ );
1323
+
1324
+ const element = container.firstChild as HTMLElement;
1325
+ expect(element.style.transform).toBe("translateX(0px)");
1326
+
1327
+ // Back
1328
+ rerender(
1329
+ <SwipeStackContent
1330
+ id="panel1"
1331
+ depth={1}
1332
+ navigationDepth={0}
1333
+ isActive={false}
1334
+ operationState={IDLE_STATE}
1335
+ containerSize={400}
1336
+ >
1337
+ Content
1338
+ </SwipeStackContent>,
1339
+ );
1340
+
1341
+ act(() => {
1342
+ flushRAF(0);
1343
+ flushRAF(50);
1344
+ });
1345
+
1346
+ // Push (before back animation completes)
1347
+ rerender(
1348
+ <SwipeStackContent
1349
+ id="panel1"
1350
+ depth={1}
1351
+ navigationDepth={1}
1352
+ isActive={true}
1353
+ operationState={IDLE_STATE}
1354
+ containerSize={400}
1355
+ >
1356
+ Content
1357
+ </SwipeStackContent>,
1358
+ );
1359
+
1360
+ act(() => {
1361
+ flushRAF(0);
1362
+ flushRAF(50);
1363
+ });
1364
+
1365
+ // Back again (before push animation completes)
1366
+ rerender(
1367
+ <SwipeStackContent
1368
+ id="panel1"
1369
+ depth={1}
1370
+ navigationDepth={0}
1371
+ isActive={false}
1372
+ operationState={IDLE_STATE}
1373
+ containerSize={400}
1374
+ >
1375
+ Content
1376
+ </SwipeStackContent>,
1377
+ );
1378
+
1379
+ // Complete all animations
1380
+ act(() => {
1381
+ flushRAF(0);
1382
+ flushRAF(400);
1383
+ });
1384
+
1385
+ // Final state: should be off-screen (hidden)
1386
+ expect(element.style.transform).toBe("translateX(400px)");
1387
+ });
1388
+
1389
+ it("panel remains visible and usable after rapid navigation", () => {
1390
+ // This is the bug scenario: after rapid back-and-forth, panel becomes permanently hidden
1391
+ const { container, rerender } = render(
1392
+ <SwipeStackContent
1393
+ id="panel1"
1394
+ depth={1}
1395
+ navigationDepth={1}
1396
+ isActive={true}
1397
+ operationState={IDLE_STATE}
1398
+ containerSize={400}
1399
+ >
1400
+ Content
1401
+ </SwipeStackContent>,
1402
+ );
1403
+
1404
+ const element = container.firstChild as HTMLElement;
1405
+
1406
+ // Rapid sequence without waiting for animations
1407
+ for (let i = 0; i < 5; i++) {
1408
+ // Go back
1409
+ rerender(
1410
+ <SwipeStackContent
1411
+ id="panel1"
1412
+ depth={1}
1413
+ navigationDepth={0}
1414
+ isActive={false}
1415
+ operationState={IDLE_STATE}
1416
+ containerSize={400}
1417
+ >
1418
+ Content
1419
+ </SwipeStackContent>,
1420
+ );
1421
+
1422
+ act(() => {
1423
+ flushRAF(0);
1424
+ flushRAF(30);
1425
+ });
1426
+
1427
+ // Push forward
1428
+ rerender(
1429
+ <SwipeStackContent
1430
+ id="panel1"
1431
+ depth={1}
1432
+ navigationDepth={1}
1433
+ isActive={true}
1434
+ operationState={IDLE_STATE}
1435
+ containerSize={400}
1436
+ >
1437
+ Content
1438
+ </SwipeStackContent>,
1439
+ );
1440
+
1441
+ act(() => {
1442
+ flushRAF(0);
1443
+ flushRAF(30);
1444
+ });
1445
+ }
1446
+
1447
+ // Final state: panel should be active
1448
+ rerender(
1449
+ <SwipeStackContent
1450
+ id="panel1"
1451
+ depth={1}
1452
+ navigationDepth={1}
1453
+ isActive={true}
1454
+ operationState={IDLE_STATE}
1455
+ containerSize={400}
1456
+ >
1457
+ Content
1458
+ </SwipeStackContent>,
1459
+ );
1460
+
1461
+ // Complete all animations
1462
+ act(() => {
1463
+ flushRAF(0);
1464
+ flushRAF(400);
1465
+ });
1466
+
1467
+ // Panel should be visible at center
1468
+ expect(element.style.transform).toBe("translateX(0px)");
1469
+ expect(element.style.visibility).toBe("visible");
1470
+ });
1471
+ });
1472
+
1473
+ describe("swipe commit (navigation changes)", () => {
1474
+ it("active panel moves off-screen when navigation depth decreases", () => {
1475
+ const { container, rerender } = render(
1476
+ <SwipeStackContent
1477
+ id="panel"
1478
+ depth={1}
1479
+ navigationDepth={1}
1480
+ isActive={true}
1481
+ operationState={IDLE_STATE}
1482
+ containerSize={400}
1483
+ >
1484
+ Content
1485
+ </SwipeStackContent>,
1486
+ );
1487
+
1488
+ const element = container.firstChild as HTMLElement;
1489
+ expect(element.style.transform).toBe("translateX(0px)");
1490
+
1491
+ // Navigation changed: depth 1 is no longer active (went back to depth 0)
1492
+ // Panel should animate to off-screen position (containerSize = 400)
1493
+ rerender(
1494
+ <SwipeStackContent
1495
+ id="panel"
1496
+ depth={1}
1497
+ navigationDepth={0}
1498
+ isActive={false}
1499
+ operationState={IDLE_STATE}
1500
+ containerSize={400}
1501
+ >
1502
+ Content
1503
+ </SwipeStackContent>,
1504
+ );
1505
+
1506
+ // Role changes to "hidden"
1507
+ expect(element.getAttribute("data-role")).toBe("hidden");
1508
+
1509
+ // Flush RAF to complete animation
1510
+ act(() => {
1511
+ flushRAF(0);
1512
+ flushRAF(400);
1513
+ });
1514
+
1515
+ // After animation: off-screen at containerSize
1516
+ expect(element.style.transform).toBe("translateX(400px)");
1517
+ });
1518
+
1519
+ it("behind panel becomes active and moves to center when navigation commits", () => {
1520
+ const { container, rerender } = render(
1521
+ <SwipeStackContent
1522
+ id="panel"
1523
+ depth={0}
1524
+ navigationDepth={1}
1525
+ isActive={false}
1526
+ operationState={IDLE_STATE}
1527
+ containerSize={400}
1528
+ >
1529
+ Content
1530
+ </SwipeStackContent>,
1531
+ );
1532
+
1533
+ const element = container.firstChild as HTMLElement;
1534
+ expect(element.style.transform).toBe("translateX(-120px)");
1535
+ expect(element.getAttribute("data-role")).toBe("behind");
1536
+
1537
+ // Navigation commits: panel at depth 0 becomes active
1538
+ rerender(
1539
+ <SwipeStackContent
1540
+ id="panel"
1541
+ depth={0}
1542
+ navigationDepth={0}
1543
+ isActive={true}
1544
+ operationState={IDLE_STATE}
1545
+ containerSize={400}
1546
+ >
1547
+ Content
1548
+ </SwipeStackContent>,
1549
+ );
1550
+
1551
+ // Role changes to "active"
1552
+ expect(element.getAttribute("data-role")).toBe("active");
1553
+
1554
+ // Flush RAF to complete animation
1555
+ act(() => {
1556
+ flushRAF(0);
1557
+ flushRAF(400);
1558
+ });
1559
+
1560
+ // After animation: at center (0)
1561
+ expect(element.style.transform).toBe("translateX(0px)");
1562
+ });
1563
+ });
1564
+ });