react-panel-layout 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (466) hide show
  1. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
  2. package/dist/FloatingPanelFrame-CEmXDvUA.cjs.map +1 -0
  3. package/dist/{FloatingPanelFrame-6W5OexYe.js → FloatingPanelFrame-SgYLc6Ud.js} +12 -15
  4. package/dist/FloatingPanelFrame-SgYLc6Ud.js.map +1 -0
  5. package/dist/FloatingWindow-BpdOpg_L.js +400 -0
  6. package/dist/FloatingWindow-BpdOpg_L.js.map +1 -0
  7. package/dist/FloatingWindow-TCDNY5gE.cjs +2 -0
  8. package/dist/FloatingWindow-TCDNY5gE.cjs.map +1 -0
  9. package/dist/GridLayout-B4VRsC0r.cjs +2 -0
  10. package/dist/GridLayout-B4VRsC0r.cjs.map +1 -0
  11. package/dist/GridLayout-BltqeCPK.js +927 -0
  12. package/dist/GridLayout-BltqeCPK.js.map +1 -0
  13. package/dist/HorizontalDivider-B5Z-KZLk.cjs +2 -0
  14. package/dist/HorizontalDivider-B5Z-KZLk.cjs.map +1 -0
  15. package/dist/HorizontalDivider-WF1k_qND.js +30 -0
  16. package/dist/HorizontalDivider-WF1k_qND.js.map +1 -0
  17. package/dist/PanelSystem-Bs8bQwQF.cjs +3 -0
  18. package/dist/PanelSystem-Bs8bQwQF.cjs.map +1 -0
  19. package/dist/PanelSystem-Dr1TBhxM.js +1946 -0
  20. package/dist/PanelSystem-Dr1TBhxM.js.map +1 -0
  21. package/dist/ResizeHandle-CScipO5l.cjs +2 -0
  22. package/dist/ResizeHandle-CScipO5l.cjs.map +1 -0
  23. package/dist/ResizeHandle-CdA_JYfN.js +120 -0
  24. package/dist/ResizeHandle-CdA_JYfN.js.map +1 -0
  25. package/dist/SwipePivotTabBar-BGO9X94m.js +407 -0
  26. package/dist/SwipePivotTabBar-BGO9X94m.js.map +1 -0
  27. package/dist/SwipePivotTabBar-BrQismcZ.cjs +2 -0
  28. package/dist/SwipePivotTabBar-BrQismcZ.cjs.map +1 -0
  29. package/dist/config.cjs +1 -1
  30. package/dist/config.cjs.map +1 -1
  31. package/dist/config.js +11 -9
  32. package/dist/config.js.map +1 -1
  33. package/dist/constants/styles.d.ts +18 -4
  34. package/dist/floating.cjs +1 -1
  35. package/dist/floating.js +1 -1
  36. package/dist/grid/index.d.ts +58 -0
  37. package/dist/grid.cjs +2 -0
  38. package/dist/grid.cjs.map +1 -0
  39. package/dist/grid.js +13 -0
  40. package/dist/grid.js.map +1 -0
  41. package/dist/hooks/gesture/presets.d.ts +33 -0
  42. package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +110 -0
  43. package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
  44. package/dist/hooks/gesture/types.d.ts +254 -0
  45. package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
  46. package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
  47. package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
  48. package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
  49. package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
  50. package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
  51. package/dist/hooks/gesture/utils.d.ts +40 -0
  52. package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
  53. package/dist/hooks/useAnimationFrame.d.ts +84 -0
  54. package/dist/hooks/useSnapAnimation.d.ts +54 -0
  55. package/dist/hooks/useSwipeContentTransform.d.ts +79 -0
  56. package/dist/index.cjs +1 -2
  57. package/dist/index.cjs.map +1 -1
  58. package/dist/index.d.ts +0 -1
  59. package/dist/index.js +25 -2006
  60. package/dist/index.js.map +1 -1
  61. package/dist/modules/pivot/PivotContent.d.ts +1 -1
  62. package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
  63. package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
  64. package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
  65. package/dist/modules/pivot/SwipePivotTabBar.d.ts +89 -0
  66. package/dist/modules/pivot/index.d.ts +3 -0
  67. package/dist/modules/pivot/scaleInputState.d.ts +37 -0
  68. package/dist/modules/pivot/types.d.ts +73 -2
  69. package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
  70. package/dist/modules/stack/StackContent.d.ts +15 -0
  71. package/dist/modules/stack/SwipeStackContent.d.ts +63 -0
  72. package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
  73. package/dist/modules/stack/computeStackContentState.d.ts +99 -0
  74. package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
  75. package/dist/modules/stack/types.d.ts +194 -0
  76. package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
  77. package/dist/modules/stack/useStackNavigation.d.ts +23 -0
  78. package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
  79. package/dist/panels/index.d.ts +67 -0
  80. package/dist/panels.cjs +2 -0
  81. package/dist/panels.cjs.map +1 -0
  82. package/dist/panels.js +28 -0
  83. package/dist/panels.js.map +1 -0
  84. package/dist/pivot/index.d.ts +3 -0
  85. package/dist/pivot.cjs +1 -1
  86. package/dist/pivot.cjs.map +1 -1
  87. package/dist/pivot.js +20 -2
  88. package/dist/pivot.js.map +1 -1
  89. package/dist/resizer/index.d.ts +57 -0
  90. package/dist/resizer.cjs +2 -0
  91. package/dist/resizer.cjs.map +1 -0
  92. package/dist/resizer.js +8 -0
  93. package/dist/resizer.js.map +1 -0
  94. package/dist/stack/index.d.ts +72 -0
  95. package/dist/stack.cjs +2 -0
  96. package/dist/stack.cjs.map +1 -0
  97. package/dist/stack.js +980 -0
  98. package/dist/stack.js.map +1 -0
  99. package/dist/sticky-header/StickyArea.d.ts +38 -0
  100. package/dist/sticky-header/index.d.ts +4 -4
  101. package/dist/sticky-header/types.d.ts +35 -22
  102. package/dist/sticky-header.cjs +1 -1
  103. package/dist/sticky-header.cjs.map +1 -1
  104. package/dist/sticky-header.js +65 -174
  105. package/dist/sticky-header.js.map +1 -1
  106. package/dist/styles-DPPuJ0sf.js +57 -0
  107. package/dist/styles-DPPuJ0sf.js.map +1 -0
  108. package/dist/styles-qf6ptVLD.cjs +2 -0
  109. package/dist/styles-qf6ptVLD.cjs.map +1 -0
  110. package/dist/types.d.ts +12 -0
  111. package/dist/useContentCache-CO3LYNmz.js +24 -0
  112. package/dist/useContentCache-CO3LYNmz.js.map +1 -0
  113. package/dist/useContentCache-DqXtLrLs.cjs +2 -0
  114. package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
  115. package/dist/useDocumentPointerEvents-CKdhGXd0.js +46 -0
  116. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +1 -0
  117. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +2 -0
  118. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +1 -0
  119. package/dist/useEffectEvent-Dp7HLCf0.js +13 -0
  120. package/dist/useEffectEvent-Dp7HLCf0.js.map +1 -0
  121. package/dist/useEffectEvent-huSsGUnl.cjs +2 -0
  122. package/dist/useEffectEvent-huSsGUnl.cjs.map +1 -0
  123. package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
  124. package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
  125. package/dist/useFloatingState-tEfA_wbc.js +74 -0
  126. package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
  127. package/dist/window/index.d.ts +61 -0
  128. package/dist/window.cjs +2 -0
  129. package/dist/window.cjs.map +1 -0
  130. package/dist/window.js +149 -0
  131. package/dist/window.js.map +1 -0
  132. package/docs/design-tokens.md +405 -0
  133. package/package.json +29 -4
  134. package/src/PanelSystemContext.tsx +88 -0
  135. package/src/components/grid/GridLayerList.tsx +172 -0
  136. package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
  137. package/src/components/grid/GridLayout.spec.tsx +743 -0
  138. package/src/components/grid/GridLayout.tsx +130 -0
  139. package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
  140. package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
  141. package/src/components/panels/DropSuggestOverlay.tsx +131 -0
  142. package/src/components/panels/PanelGroupView.tsx +112 -0
  143. package/src/components/pivot/PivotLayer.tsx +27 -0
  144. package/src/components/resizer/HorizontalDivider.tsx +52 -0
  145. package/src/components/resizer/ResizeHandle.tsx +118 -0
  146. package/src/components/tabs/TabBar.tsx +223 -0
  147. package/src/components/tabs/TabBarTab.tsx +133 -0
  148. package/src/components/tabs/TabDragOverlay.tsx +92 -0
  149. package/src/components/window/DialogOverlay.tsx +180 -0
  150. package/src/components/window/Drawer.tsx +282 -0
  151. package/src/components/window/DrawerLayers.tsx +58 -0
  152. package/src/components/window/FloatingWindow.tsx +95 -0
  153. package/src/components/window/PopupLayerPortal.tsx +218 -0
  154. package/src/config/PanelContentDeclaration.tsx +427 -0
  155. package/src/config/index.tsx +52 -0
  156. package/src/config/panelJsx.spec.tsx +54 -0
  157. package/src/config/panelJsxConfig.spec.tsx +54 -0
  158. package/src/config/panelJsxDrawer.spec.tsx +33 -0
  159. package/src/config/panelRouter.spec.ts +68 -0
  160. package/src/config/panelRouter.tsx +155 -0
  161. package/src/constants/styles.ts +261 -0
  162. package/src/demo/Layout.module.css +258 -0
  163. package/src/demo/Layout.tsx +176 -0
  164. package/src/demo/components/CodeBlock.module.css +54 -0
  165. package/src/demo/components/CodeBlock.tsx +34 -0
  166. package/src/demo/components/CodePreview.module.css +37 -0
  167. package/src/demo/components/CodePreview.tsx +31 -0
  168. package/src/demo/components/DataPreview.module.css +177 -0
  169. package/src/demo/components/DataPreview.tsx +115 -0
  170. package/src/demo/components/Story.module.css +68 -0
  171. package/src/demo/components/Story.tsx +54 -0
  172. package/src/demo/components/layout/CodePanel.module.css +183 -0
  173. package/src/demo/components/layout/CodePanel.tsx +149 -0
  174. package/src/demo/components/layout/DemoPage.module.css +60 -0
  175. package/src/demo/components/layout/DemoPage.tsx +56 -0
  176. package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
  177. package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
  178. package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
  179. package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
  180. package/src/demo/components/layout/index.ts +11 -0
  181. package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
  182. package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
  183. package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
  184. package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
  185. package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
  186. package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
  187. package/src/demo/components/tab-styles/index.ts +6 -0
  188. package/src/demo/components/ui/DemoButton.module.css +63 -0
  189. package/src/demo/components/ui/DemoButton.tsx +32 -0
  190. package/src/demo/components/ui/DemoCard.module.css +15 -0
  191. package/src/demo/components/ui/DemoCard.tsx +30 -0
  192. package/src/demo/components/ui/DemoContainer.module.css +17 -0
  193. package/src/demo/components/ui/DemoContainer.tsx +30 -0
  194. package/src/demo/components/ui/DemoPanel.module.css +23 -0
  195. package/src/demo/components/ui/DemoPanel.tsx +33 -0
  196. package/src/demo/components/ui/PanelText.module.css +18 -0
  197. package/src/demo/components/ui/PanelText.tsx +29 -0
  198. package/src/demo/components/ui/PanelTitle.module.css +18 -0
  199. package/src/demo/components/ui/PanelTitle.tsx +31 -0
  200. package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
  201. package/src/demo/demo.css +172 -0
  202. package/src/demo/hooks/useMedia.ts +41 -0
  203. package/src/demo/hooks/useShikiHighlight.ts +55 -0
  204. package/src/demo/index.tsx +293 -0
  205. package/src/demo/pages/Drawer/animations/index.tsx +22 -0
  206. package/src/demo/pages/Drawer/basics/index.tsx +17 -0
  207. package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
  208. package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
  209. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
  210. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
  211. package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
  212. package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
  213. package/src/demo/pages/Drawer/menu/index.tsx +17 -0
  214. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
  215. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
  216. package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
  217. package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
  218. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
  219. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
  220. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
  221. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
  222. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
  223. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
  224. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
  225. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
  226. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
  227. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
  228. package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
  229. package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
  230. package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
  231. package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
  232. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
  233. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
  234. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
  235. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
  236. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
  237. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
  238. package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
  239. package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
  240. package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
  241. package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
  242. package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
  243. package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
  244. package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
  245. package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
  246. package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
  247. package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
  248. package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
  249. package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
  250. package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
  251. package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
  252. package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
  253. package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
  254. package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
  255. package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
  256. package/src/demo/pages/PanelLayout/index.tsx +94 -0
  257. package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
  258. package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
  259. package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
  260. package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
  261. package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
  262. package/src/demo/pages/Pivot/basics/index.tsx +17 -0
  263. package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
  264. package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
  265. package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
  266. package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
  267. package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
  268. package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
  269. package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
  270. package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
  271. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +289 -0
  272. package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
  273. package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
  274. package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
  275. package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
  276. package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
  277. package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
  278. package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
  279. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
  280. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
  281. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
  282. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
  283. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
  284. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
  285. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
  286. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
  287. package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
  288. package/src/demo/pages/ResizeHandle/index.module.css +11 -0
  289. package/src/demo/pages/ResizeHandle/index.tsx +71 -0
  290. package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
  291. package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
  292. package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
  293. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
  294. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
  295. package/src/demo/pages/Stack/basics/index.tsx +22 -0
  296. package/src/demo/pages/Stack/components/Stack.module.css +234 -0
  297. package/src/demo/pages/Stack/components/StackBasics.tsx +217 -0
  298. package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
  299. package/src/demo/pages/Stack/components/StackTablet.tsx +401 -0
  300. package/src/demo/pages/Stack/tablet/index.tsx +22 -0
  301. package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
  302. package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
  303. package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
  304. package/src/demo/routes.tsx +193 -0
  305. package/src/demo/styles/animations.css +68 -0
  306. package/src/demo/styles/stack-themes.css +35 -0
  307. package/src/demo/utils/createPanelView.tsx +58 -0
  308. package/src/floating/index.ts +24 -0
  309. package/src/grid/index.ts +75 -0
  310. package/src/hooks/ContentCacheContext.tsx +87 -0
  311. package/src/hooks/gesture/presets.spec.ts +86 -0
  312. package/src/hooks/gesture/presets.ts +95 -0
  313. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +237 -0
  314. package/src/hooks/gesture/testing/createGestureSimulator.ts +310 -0
  315. package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
  316. package/src/hooks/gesture/thresholdValue.ts +77 -0
  317. package/src/hooks/gesture/types.ts +290 -0
  318. package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
  319. package/src/hooks/gesture/useDirectionalLock.ts +115 -0
  320. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +454 -0
  321. package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
  322. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +413 -0
  323. package/src/hooks/gesture/useNativeGestureGuard.ts +133 -0
  324. package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
  325. package/src/hooks/gesture/usePointerTracking.ts +134 -0
  326. package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
  327. package/src/hooks/gesture/useScrollBoundary.ts +113 -0
  328. package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
  329. package/src/hooks/gesture/useSwipeInput.ts +310 -0
  330. package/src/hooks/gesture/utils.spec.ts +152 -0
  331. package/src/hooks/gesture/utils.ts +87 -0
  332. package/src/hooks/useAnimatedVisibility.spec.ts +257 -0
  333. package/src/hooks/useAnimatedVisibility.ts +146 -0
  334. package/src/hooks/useAnimationFrame.ts +200 -0
  335. package/src/hooks/useCSSMatrix.spec.ts +214 -0
  336. package/src/hooks/useCSSMatrix.ts +262 -0
  337. package/src/hooks/useClonedElementPreview.ts +28 -0
  338. package/src/hooks/useContainerScroll.ts +78 -0
  339. package/src/hooks/useContentCache.spec.tsx +232 -0
  340. package/src/hooks/useContentCache.tsx +127 -0
  341. package/src/hooks/useDocumentPointerEvents.ts +137 -0
  342. package/src/hooks/useDocumentScroll.ts +41 -0
  343. package/src/hooks/useEffectEvent.ts +40 -0
  344. package/src/hooks/useElementComponentWrapper.tsx +63 -0
  345. package/src/hooks/useIntersectionObserver.tsx +125 -0
  346. package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
  347. package/src/hooks/useResizeObserver.tsx +81 -0
  348. package/src/hooks/useScrollContainer.ts +79 -0
  349. package/src/hooks/useSnapAnimation.ts +128 -0
  350. package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
  351. package/src/hooks/useSwipeContentTransform.ts +235 -0
  352. package/src/hooks/useTransitionState.ts +95 -0
  353. package/src/index.tsx +88 -0
  354. package/src/modules/grid/GridLayoutContext.tsx +57 -0
  355. package/src/modules/grid/LayerInstanceContext.tsx +56 -0
  356. package/src/modules/grid/resizeHandles.ts +157 -0
  357. package/src/modules/grid/trackUtils.ts +146 -0
  358. package/src/modules/grid/useGridPlacements.ts +143 -0
  359. package/src/modules/grid/useGridTracks.ts +156 -0
  360. package/src/modules/grid/useLayerDragHandle.ts +16 -0
  361. package/src/modules/grid/useLayerInteractions.tsx +850 -0
  362. package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
  363. package/src/modules/panels/dom/DomRegistry.tsx +94 -0
  364. package/src/modules/panels/index.ts +45 -0
  365. package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
  366. package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
  367. package/src/modules/panels/interactions/dnd.ts +28 -0
  368. package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
  369. package/src/modules/panels/layout/adapter.ts +124 -0
  370. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +304 -0
  371. package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
  372. package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
  373. package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
  374. package/src/modules/panels/rendering/RenderContext.tsx +31 -0
  375. package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
  376. package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
  377. package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
  378. package/src/modules/panels/state/StateContext.tsx +12 -0
  379. package/src/modules/panels/state/cleanup.ts +37 -0
  380. package/src/modules/panels/state/commands.ts +53 -0
  381. package/src/modules/panels/state/focus/Context.tsx +25 -0
  382. package/src/modules/panels/state/focus/logic.ts +57 -0
  383. package/src/modules/panels/state/groups/Context.tsx +25 -0
  384. package/src/modules/panels/state/groups/logic.ts +105 -0
  385. package/src/modules/panels/state/splitLimits.spec.ts +46 -0
  386. package/src/modules/panels/state/splitLimits.ts +90 -0
  387. package/src/modules/panels/state/state.spec.ts +49 -0
  388. package/src/modules/panels/state/tree/Context.tsx +24 -0
  389. package/src/modules/panels/state/tree/logic.spec.ts +34 -0
  390. package/src/modules/panels/state/tree/logic.ts +138 -0
  391. package/src/modules/panels/state/types.ts +142 -0
  392. package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
  393. package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
  394. package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
  395. package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
  396. package/src/modules/panels/system/PanelSystem.tsx +206 -0
  397. package/src/modules/pivot/PivotContent.spec.tsx +179 -0
  398. package/src/modules/pivot/PivotContent.tsx +77 -0
  399. package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
  400. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +167 -0
  401. package/src/modules/pivot/SwipePivotContent.spec.tsx +464 -0
  402. package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
  403. package/src/modules/pivot/SwipePivotContent.tsx +197 -0
  404. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +865 -0
  405. package/src/modules/pivot/SwipePivotTabBar.tsx +523 -0
  406. package/src/modules/pivot/index.ts +8 -0
  407. package/src/modules/pivot/scaleInputState.spec.ts +210 -0
  408. package/src/modules/pivot/scaleInputState.ts +66 -0
  409. package/src/modules/pivot/types.ts +139 -0
  410. package/src/modules/pivot/usePivot.spec.ts +621 -0
  411. package/src/modules/pivot/usePivot.spec.tsx +186 -0
  412. package/src/modules/pivot/usePivot.tsx +345 -0
  413. package/src/modules/pivot/usePivotSwipeInput.spec.ts +649 -0
  414. package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
  415. package/src/modules/resizer/useResizeDrag.ts +94 -0
  416. package/src/modules/stack/StackContent.spec.tsx +264 -0
  417. package/src/modules/stack/StackContent.tsx +111 -0
  418. package/src/modules/stack/SwipeStackContent.spec.tsx +1277 -0
  419. package/src/modules/stack/SwipeStackContent.tsx +356 -0
  420. package/src/modules/stack/SwipeStackOutlet.spec.tsx +252 -0
  421. package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
  422. package/src/modules/stack/computeStackContentState.spec.ts +281 -0
  423. package/src/modules/stack/computeStackContentState.ts +304 -0
  424. package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
  425. package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
  426. package/src/modules/stack/types.ts +226 -0
  427. package/src/modules/stack/useStackAnimationState.spec.ts +186 -0
  428. package/src/modules/stack/useStackAnimationState.ts +138 -0
  429. package/src/modules/stack/useStackNavigation.spec.ts +477 -0
  430. package/src/modules/stack/useStackNavigation.tsx +336 -0
  431. package/src/modules/stack/useStackSwipeInput.spec.ts +276 -0
  432. package/src/modules/stack/useStackSwipeInput.ts +139 -0
  433. package/src/modules/window/useDrawerState.ts +81 -0
  434. package/src/modules/window/useFloatingState.spec.ts +252 -0
  435. package/src/modules/window/useFloatingState.ts +141 -0
  436. package/src/panels/index.ts +119 -0
  437. package/src/pivot/index.ts +19 -0
  438. package/src/resizer/index.ts +68 -0
  439. package/src/stack/index.ts +91 -0
  440. package/src/sticky-header/StickyArea.tsx +221 -0
  441. package/src/sticky-header/index.ts +18 -0
  442. package/src/sticky-header/types.ts +68 -0
  443. package/src/types.ts +323 -0
  444. package/src/utils/CSSMatrix.ts +321 -0
  445. package/src/utils/css.ts +65 -0
  446. package/src/utils/dialogUtils.ts +43 -0
  447. package/src/utils/math.ts +18 -0
  448. package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
  449. package/src/utils/typedActions.ts +103 -0
  450. package/src/vite-env.d.ts +6 -0
  451. package/src/window/index.ts +67 -0
  452. package/dist/FloatingPanelFrame-6W5OexYe.js.map +0 -1
  453. package/dist/FloatingPanelFrame-D9Cp2al1.cjs.map +0 -1
  454. package/dist/GridLayout-BzrIDrC9.js +0 -1465
  455. package/dist/GridLayout-BzrIDrC9.js.map +0 -1
  456. package/dist/GridLayout-ZrOhoLLB.cjs +0 -2
  457. package/dist/GridLayout-ZrOhoLLB.cjs.map +0 -1
  458. package/dist/sticky-header/StickyHeader.d.ts +0 -53
  459. package/dist/styles-CA2_zLZt.js +0 -52
  460. package/dist/styles-CA2_zLZt.js.map +0 -1
  461. package/dist/styles-PsqGOEJP.cjs +0 -2
  462. package/dist/styles-PsqGOEJP.cjs.map +0 -1
  463. package/dist/usePivot-BS-DGfwd.cjs +0 -2
  464. package/dist/usePivot-BS-DGfwd.cjs.map +0 -1
  465. package/dist/usePivot-BvOGxLQQ.js +0 -124
  466. package/dist/usePivot-BvOGxLQQ.js.map +0 -1
@@ -0,0 +1,1277 @@
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 { beforeEach, afterEach, vi } from "vitest";
12
+ import { SwipeStackContent } from "./SwipeStackContent.js";
13
+ import type { SwipeInputState } from "../../hooks/gesture/types.js";
14
+
15
+ // Mock requestAnimationFrame for animation testing
16
+ let rafCallbacks: FrameRequestCallback[] = [];
17
+ let rafId = 0;
18
+ let mockTimestamp = 0;
19
+
20
+ const mockRAF = vi.fn((callback: FrameRequestCallback) => {
21
+ rafCallbacks.push(callback);
22
+ return ++rafId;
23
+ });
24
+
25
+ const mockCAF = vi.fn(() => {
26
+ // Cancel callback if needed
27
+ });
28
+
29
+ /**
30
+ * Flush all pending RAF callbacks.
31
+ * @param advanceMs - How much to advance the mock timestamp (default: 400ms to complete 300ms animation)
32
+ */
33
+ const flushRAF = (advanceMs = 400) => {
34
+ mockTimestamp += advanceMs;
35
+ const callbacks = rafCallbacks;
36
+ rafCallbacks = [];
37
+ callbacks.forEach(cb => cb(mockTimestamp));
38
+ };
39
+
40
+ beforeEach(() => {
41
+ rafCallbacks = [];
42
+ rafId = 0;
43
+ mockTimestamp = 0;
44
+ vi.stubGlobal("requestAnimationFrame", mockRAF);
45
+ vi.stubGlobal("cancelAnimationFrame", mockCAF);
46
+ });
47
+
48
+ afterEach(() => {
49
+ vi.unstubAllGlobals();
50
+ vi.clearAllMocks();
51
+ });
52
+
53
+ const IDLE_STATE: SwipeInputState = {
54
+ phase: "idle",
55
+ displacement: { x: 0, y: 0 },
56
+ velocity: { x: 0, y: 0 },
57
+ direction: 0,
58
+ };
59
+
60
+ const createSwipingState = (displacementX: number): SwipeInputState => ({
61
+ phase: "swiping",
62
+ displacement: { x: displacementX, y: 0 },
63
+ velocity: { x: 0.5, y: 0 },
64
+ direction: displacementX > 0 ? 1 : displacementX < 0 ? -1 : 0,
65
+ });
66
+
67
+ const createTrackingState = (displacementX: number): SwipeInputState => ({
68
+ phase: "tracking",
69
+ displacement: { x: displacementX, y: 0 },
70
+ velocity: { x: 0.1, y: 0 },
71
+ direction: 0,
72
+ });
73
+
74
+ describe("SwipeStackContent", () => {
75
+ describe("rendering", () => {
76
+ it("renders children correctly", () => {
77
+ const { getByText } = render(
78
+ <SwipeStackContent
79
+ id="test"
80
+ depth={0}
81
+ navigationDepth={0}
82
+ isActive={true}
83
+ inputState={IDLE_STATE}
84
+ containerSize={400}
85
+ >
86
+ <div>Test Content</div>
87
+ </SwipeStackContent>,
88
+ );
89
+
90
+ expect(getByText("Test Content")).toBeInTheDocument();
91
+ });
92
+
93
+ it("applies correct data attributes", () => {
94
+ const { container } = render(
95
+ <SwipeStackContent
96
+ id="panel-1"
97
+ depth={1}
98
+ navigationDepth={1}
99
+ isActive={true}
100
+ inputState={IDLE_STATE}
101
+ containerSize={400}
102
+ >
103
+ Content
104
+ </SwipeStackContent>,
105
+ );
106
+
107
+ const element = container.firstChild as HTMLElement;
108
+ expect(element.getAttribute("data-stack-content")).toBe("panel-1");
109
+ expect(element.getAttribute("data-depth")).toBe("1");
110
+ expect(element.getAttribute("data-active")).toBe("true");
111
+ expect(element.getAttribute("data-role")).toBe("active");
112
+ });
113
+
114
+ it("applies correct role for behind panel", () => {
115
+ const { container } = render(
116
+ <SwipeStackContent
117
+ id="panel-0"
118
+ depth={0}
119
+ navigationDepth={1}
120
+ isActive={false}
121
+ inputState={IDLE_STATE}
122
+ containerSize={400}
123
+ >
124
+ Content
125
+ </SwipeStackContent>,
126
+ );
127
+
128
+ const element = container.firstChild as HTMLElement;
129
+ expect(element.getAttribute("data-role")).toBe("behind");
130
+ });
131
+ });
132
+
133
+ describe("visibility", () => {
134
+ it("active panel is always visible", () => {
135
+ const { container } = render(
136
+ <SwipeStackContent
137
+ id="test"
138
+ depth={1}
139
+ navigationDepth={1}
140
+ isActive={true}
141
+ inputState={IDLE_STATE}
142
+ containerSize={400}
143
+ >
144
+ Content
145
+ </SwipeStackContent>,
146
+ );
147
+
148
+ const element = container.firstChild as HTMLElement;
149
+ expect(element.style.visibility).toBe("visible");
150
+ });
151
+
152
+ it("behind panel is hidden when idle", () => {
153
+ const { container } = render(
154
+ <SwipeStackContent
155
+ id="test"
156
+ depth={0}
157
+ navigationDepth={1}
158
+ isActive={false}
159
+ inputState={IDLE_STATE}
160
+ containerSize={400}
161
+ >
162
+ Content
163
+ </SwipeStackContent>,
164
+ );
165
+
166
+ const element = container.firstChild as HTMLElement;
167
+ expect(element.style.visibility).toBe("hidden");
168
+ });
169
+
170
+ it("behind panel is visible when swiping", () => {
171
+ const { container } = render(
172
+ <SwipeStackContent
173
+ id="test"
174
+ depth={0}
175
+ navigationDepth={1}
176
+ isActive={false}
177
+ inputState={createSwipingState(100)}
178
+ containerSize={400}
179
+ >
180
+ Content
181
+ </SwipeStackContent>,
182
+ );
183
+
184
+ const element = container.firstChild as HTMLElement;
185
+ expect(element.style.visibility).toBe("visible");
186
+ });
187
+ });
188
+
189
+ describe("transform during swipe", () => {
190
+ it("active panel follows displacement", () => {
191
+ const { container, rerender } = render(
192
+ <SwipeStackContent
193
+ id="test"
194
+ depth={1}
195
+ navigationDepth={1}
196
+ isActive={true}
197
+ inputState={IDLE_STATE}
198
+ containerSize={400}
199
+ >
200
+ Content
201
+ </SwipeStackContent>,
202
+ );
203
+
204
+ // Start swiping
205
+ rerender(
206
+ <SwipeStackContent
207
+ id="test"
208
+ depth={1}
209
+ navigationDepth={1}
210
+ isActive={true}
211
+ inputState={createSwipingState(150)}
212
+ containerSize={400}
213
+ >
214
+ Content
215
+ </SwipeStackContent>,
216
+ );
217
+
218
+ const element = container.firstChild as HTMLElement;
219
+ expect(element.style.transform).toBe("translateX(150px)");
220
+ });
221
+
222
+ it("behind panel uses parallax effect", () => {
223
+ const { container, rerender } = render(
224
+ <SwipeStackContent
225
+ id="test"
226
+ depth={0}
227
+ navigationDepth={1}
228
+ isActive={false}
229
+ inputState={IDLE_STATE}
230
+ containerSize={400}
231
+ >
232
+ Content
233
+ </SwipeStackContent>,
234
+ );
235
+
236
+ // Start swiping - at 50% progress (200px), behind should move from -120 to -60
237
+ rerender(
238
+ <SwipeStackContent
239
+ id="test"
240
+ depth={0}
241
+ navigationDepth={1}
242
+ isActive={false}
243
+ inputState={createSwipingState(200)}
244
+ containerSize={400}
245
+ >
246
+ Content
247
+ </SwipeStackContent>,
248
+ );
249
+
250
+ const element = container.firstChild as HTMLElement;
251
+ // Behind panel should be at -60px (halfway from -120 to 0)
252
+ expect(element.style.transform).toBe("translateX(-60px)");
253
+ });
254
+ });
255
+
256
+ describe("z-index", () => {
257
+ it("applies z-index based on depth", () => {
258
+ const { container } = render(
259
+ <SwipeStackContent
260
+ id="test"
261
+ depth={3}
262
+ navigationDepth={3}
263
+ isActive={true}
264
+ inputState={IDLE_STATE}
265
+ containerSize={400}
266
+ >
267
+ Content
268
+ </SwipeStackContent>,
269
+ );
270
+
271
+ const element = container.firstChild as HTMLElement;
272
+ expect(element.style.zIndex).toBe("3");
273
+ });
274
+ });
275
+
276
+ describe("pointer events", () => {
277
+ it("active panel has auto pointer events", () => {
278
+ const { container } = render(
279
+ <SwipeStackContent
280
+ id="test"
281
+ depth={1}
282
+ navigationDepth={1}
283
+ isActive={true}
284
+ inputState={IDLE_STATE}
285
+ containerSize={400}
286
+ >
287
+ Content
288
+ </SwipeStackContent>,
289
+ );
290
+
291
+ const element = container.firstChild as HTMLElement;
292
+ expect(element.style.pointerEvents).toBe("auto");
293
+ });
294
+
295
+ it("inactive panel has none pointer events", () => {
296
+ const { container } = render(
297
+ <SwipeStackContent
298
+ id="test"
299
+ depth={0}
300
+ navigationDepth={1}
301
+ isActive={false}
302
+ inputState={IDLE_STATE}
303
+ containerSize={400}
304
+ >
305
+ Content
306
+ </SwipeStackContent>,
307
+ );
308
+
309
+ const element = container.firstChild as HTMLElement;
310
+ expect(element.style.pointerEvents).toBe("none");
311
+ });
312
+ });
313
+
314
+ describe("swipe preview behavior", () => {
315
+ it("active panel position changes immediately with displacement during swiping", () => {
316
+ const { container, rerender } = render(
317
+ <SwipeStackContent
318
+ id="active"
319
+ depth={1}
320
+ navigationDepth={1}
321
+ isActive={true}
322
+ inputState={IDLE_STATE}
323
+ containerSize={400}
324
+ >
325
+ Content
326
+ </SwipeStackContent>,
327
+ );
328
+
329
+ const element = container.firstChild as HTMLElement;
330
+
331
+ // Initial position
332
+ expect(element.style.transform).toBe("translateX(0px)");
333
+
334
+ // Start tracking (before direction lock) - displacement is still applied
335
+ // because we want responsive feedback even before direction is confirmed
336
+ rerender(
337
+ <SwipeStackContent
338
+ id="active"
339
+ depth={1}
340
+ navigationDepth={1}
341
+ isActive={true}
342
+ inputState={createTrackingState(50)}
343
+ containerSize={400}
344
+ >
345
+ Content
346
+ </SwipeStackContent>,
347
+ );
348
+ // Tracking phase also applies displacement for responsive feedback
349
+ expect(element.style.transform).toBe("translateX(50px)");
350
+
351
+ // Swipe confirmed (direction locked)
352
+ rerender(
353
+ <SwipeStackContent
354
+ id="active"
355
+ depth={1}
356
+ navigationDepth={1}
357
+ isActive={true}
358
+ inputState={createSwipingState(100)}
359
+ containerSize={400}
360
+ >
361
+ Content
362
+ </SwipeStackContent>,
363
+ );
364
+ expect(element.style.transform).toBe("translateX(100px)");
365
+
366
+ // Continue swiping
367
+ rerender(
368
+ <SwipeStackContent
369
+ id="active"
370
+ depth={1}
371
+ navigationDepth={1}
372
+ isActive={true}
373
+ inputState={createSwipingState(250)}
374
+ containerSize={400}
375
+ >
376
+ Content
377
+ </SwipeStackContent>,
378
+ );
379
+ expect(element.style.transform).toBe("translateX(250px)");
380
+ });
381
+
382
+ it("behind panel reveals progressively during swipe with parallax effect", () => {
383
+ const { container, rerender } = render(
384
+ <SwipeStackContent
385
+ id="behind"
386
+ depth={0}
387
+ navigationDepth={1}
388
+ isActive={false}
389
+ inputState={IDLE_STATE}
390
+ containerSize={400}
391
+ >
392
+ Content
393
+ </SwipeStackContent>,
394
+ );
395
+
396
+ const element = container.firstChild as HTMLElement;
397
+
398
+ // Initial: behind panel at -30% = -120px
399
+ expect(element.style.transform).toBe("translateX(-120px)");
400
+ expect(element.style.visibility).toBe("hidden");
401
+
402
+ // Swipe 25% (100px of 400px)
403
+ rerender(
404
+ <SwipeStackContent
405
+ id="behind"
406
+ depth={0}
407
+ navigationDepth={1}
408
+ isActive={false}
409
+ inputState={createSwipingState(100)}
410
+ containerSize={400}
411
+ >
412
+ Content
413
+ </SwipeStackContent>,
414
+ );
415
+ // Behind moves 25% of 120px = 30px, so from -120 to -90
416
+ expect(element.style.transform).toBe("translateX(-90px)");
417
+ expect(element.style.visibility).toBe("visible");
418
+
419
+ // Swipe 50% (200px)
420
+ rerender(
421
+ <SwipeStackContent
422
+ id="behind"
423
+ depth={0}
424
+ navigationDepth={1}
425
+ isActive={false}
426
+ inputState={createSwipingState(200)}
427
+ containerSize={400}
428
+ >
429
+ Content
430
+ </SwipeStackContent>,
431
+ );
432
+ expect(element.style.transform).toBe("translateX(-60px)");
433
+
434
+ // Swipe 100% (400px) - behind should be at 0
435
+ rerender(
436
+ <SwipeStackContent
437
+ id="behind"
438
+ depth={0}
439
+ navigationDepth={1}
440
+ isActive={false}
441
+ inputState={createSwipingState(400)}
442
+ containerSize={400}
443
+ >
444
+ Content
445
+ </SwipeStackContent>,
446
+ );
447
+ expect(element.style.transform).toBe("translateX(0px)");
448
+ });
449
+
450
+ it("does not respond to negative displacement (swiping wrong direction)", () => {
451
+ const { container, rerender } = render(
452
+ <SwipeStackContent
453
+ id="active"
454
+ depth={1}
455
+ navigationDepth={1}
456
+ isActive={true}
457
+ inputState={IDLE_STATE}
458
+ containerSize={400}
459
+ >
460
+ Content
461
+ </SwipeStackContent>,
462
+ );
463
+
464
+ const element = container.firstChild as HTMLElement;
465
+
466
+ // Swipe left (negative) should be clamped to 0
467
+ rerender(
468
+ <SwipeStackContent
469
+ id="active"
470
+ depth={1}
471
+ navigationDepth={1}
472
+ isActive={true}
473
+ inputState={createSwipingState(-100)}
474
+ containerSize={400}
475
+ >
476
+ Content
477
+ </SwipeStackContent>,
478
+ );
479
+ expect(element.style.transform).toBe("translateX(0px)");
480
+ });
481
+ });
482
+
483
+ describe("swipe release and snap-back", () => {
484
+ it("returns to original position when swipe ends without navigation change", () => {
485
+ const { container, rerender } = render(
486
+ <SwipeStackContent
487
+ id="active"
488
+ depth={1}
489
+ navigationDepth={1}
490
+ isActive={true}
491
+ inputState={IDLE_STATE}
492
+ containerSize={400}
493
+ >
494
+ Content
495
+ </SwipeStackContent>,
496
+ );
497
+
498
+ const element = container.firstChild as HTMLElement;
499
+
500
+ // Swipe in progress
501
+ rerender(
502
+ <SwipeStackContent
503
+ id="active"
504
+ depth={1}
505
+ navigationDepth={1}
506
+ isActive={true}
507
+ inputState={createSwipingState(150)}
508
+ containerSize={400}
509
+ >
510
+ Content
511
+ </SwipeStackContent>,
512
+ );
513
+ expect(element.style.transform).toBe("translateX(150px)");
514
+
515
+ // Swipe released (back to idle) - animation starts
516
+ rerender(
517
+ <SwipeStackContent
518
+ id="active"
519
+ depth={1}
520
+ navigationDepth={1}
521
+ isActive={true}
522
+ inputState={IDLE_STATE}
523
+ containerSize={400}
524
+ >
525
+ Content
526
+ </SwipeStackContent>,
527
+ );
528
+
529
+ // Flush RAF within act() to complete the snap-back animation
530
+ act(() => {
531
+ flushRAF(0); // First flush starts the animation (sets startTime)
532
+ flushRAF(400); // Second flush completes the animation (elapsed > duration)
533
+ });
534
+
535
+ // After animation completes, should be at 0
536
+ expect(element.style.transform).toBe("translateX(0px)");
537
+ });
538
+
539
+ it("behind panel returns to hidden position when swipe is canceled", () => {
540
+ const { container, rerender } = render(
541
+ <SwipeStackContent
542
+ id="behind"
543
+ depth={0}
544
+ navigationDepth={1}
545
+ isActive={false}
546
+ inputState={IDLE_STATE}
547
+ containerSize={400}
548
+ >
549
+ Content
550
+ </SwipeStackContent>,
551
+ );
552
+
553
+ const element = container.firstChild as HTMLElement;
554
+
555
+ // Swipe in progress
556
+ rerender(
557
+ <SwipeStackContent
558
+ id="behind"
559
+ depth={0}
560
+ navigationDepth={1}
561
+ isActive={false}
562
+ inputState={createSwipingState(200)}
563
+ containerSize={400}
564
+ >
565
+ Content
566
+ </SwipeStackContent>,
567
+ );
568
+ expect(element.style.transform).toBe("translateX(-60px)");
569
+ expect(element.style.visibility).toBe("visible");
570
+
571
+ // Swipe canceled - animation starts
572
+ rerender(
573
+ <SwipeStackContent
574
+ id="behind"
575
+ depth={0}
576
+ navigationDepth={1}
577
+ isActive={false}
578
+ inputState={IDLE_STATE}
579
+ containerSize={400}
580
+ >
581
+ Content
582
+ </SwipeStackContent>,
583
+ );
584
+
585
+ // Flush RAF within act() to complete the snap-back animation
586
+ act(() => {
587
+ flushRAF(0); // First flush starts the animation (sets startTime)
588
+ flushRAF(400); // Second flush completes the animation (elapsed > duration)
589
+ });
590
+
591
+ // After animation completes, should be at -120px
592
+ expect(element.style.transform).toBe("translateX(-120px)");
593
+ });
594
+ });
595
+
596
+ describe("button navigation (without swipe)", () => {
597
+ it("animates behind panel to center when going back via button", () => {
598
+ const { container, rerender } = render(
599
+ <SwipeStackContent
600
+ id="panel"
601
+ depth={0}
602
+ navigationDepth={1}
603
+ isActive={false}
604
+ inputState={IDLE_STATE}
605
+ containerSize={400}
606
+ >
607
+ Content
608
+ </SwipeStackContent>,
609
+ );
610
+
611
+ const element = container.firstChild as HTMLElement;
612
+ // Initial: behind panel at -30% = -120px
613
+ expect(element.style.transform).toBe("translateX(-120px)");
614
+
615
+ // Button click triggers navigation change: depth 0 becomes active
616
+ rerender(
617
+ <SwipeStackContent
618
+ id="panel"
619
+ depth={0}
620
+ navigationDepth={0}
621
+ isActive={true}
622
+ inputState={IDLE_STATE}
623
+ containerSize={400}
624
+ >
625
+ Content
626
+ </SwipeStackContent>,
627
+ );
628
+
629
+ // Animation should start: still at -120px immediately
630
+ // (not snapping directly to 0)
631
+ expect(element.style.transform).toBe("translateX(-120px)");
632
+
633
+ // Flush RAF to complete animation
634
+ act(() => {
635
+ flushRAF(0);
636
+ flushRAF(400);
637
+ });
638
+
639
+ // After animation completes, should be at 0
640
+ expect(element.style.transform).toBe("translateX(0px)");
641
+ });
642
+
643
+ it("animates active panel off-screen when going back via button", () => {
644
+ const { container, rerender } = render(
645
+ <SwipeStackContent
646
+ id="panel"
647
+ depth={1}
648
+ navigationDepth={1}
649
+ isActive={true}
650
+ inputState={IDLE_STATE}
651
+ containerSize={400}
652
+ >
653
+ Content
654
+ </SwipeStackContent>,
655
+ );
656
+
657
+ const element = container.firstChild as HTMLElement;
658
+ // Initial: active panel at 0
659
+ expect(element.style.transform).toBe("translateX(0px)");
660
+
661
+ // Button click triggers navigation change: depth 1 becomes hidden
662
+ rerender(
663
+ <SwipeStackContent
664
+ id="panel"
665
+ depth={1}
666
+ navigationDepth={0}
667
+ isActive={false}
668
+ inputState={IDLE_STATE}
669
+ containerSize={400}
670
+ >
671
+ Content
672
+ </SwipeStackContent>,
673
+ );
674
+
675
+ // Animation should start: still at 0px immediately
676
+ // (not snapping directly to 400)
677
+ expect(element.style.transform).toBe("translateX(0px)");
678
+
679
+ // Flush RAF to complete animation
680
+ act(() => {
681
+ flushRAF(0);
682
+ flushRAF(400);
683
+ });
684
+
685
+ // After animation completes, should be at 400px (off-screen)
686
+ expect(element.style.transform).toBe("translateX(400px)");
687
+ });
688
+ });
689
+
690
+ describe("forward navigation (push)", () => {
691
+ it("new panel animates in from off-screen when first mounted as active with animateOnMount", () => {
692
+ // Scenario: Panel is mounted for the first time as active (push navigation)
693
+ // This is what happens when visiblePanelIds changes to include the new panel
694
+ const { container } = render(
695
+ <SwipeStackContent
696
+ id="newPanel"
697
+ depth={1}
698
+ navigationDepth={1}
699
+ isActive={true}
700
+ inputState={IDLE_STATE}
701
+ containerSize={400}
702
+ animateOnMount={true}
703
+ >
704
+ Content
705
+ </SwipeStackContent>,
706
+ );
707
+
708
+ const element = container.firstChild as HTMLElement;
709
+
710
+ // On first mount as active with animateOnMount, panel should start at off-screen position
711
+ // (not at 0, which would cause a jarring pop-in)
712
+ expect(element.style.transform).toBe("translateX(400px)");
713
+
714
+ // Flush RAF to complete the entrance animation
715
+ act(() => {
716
+ flushRAF(0);
717
+ flushRAF(400);
718
+ });
719
+
720
+ // After animation: at center (0)
721
+ expect(element.style.transform).toBe("translateX(0px)");
722
+ });
723
+
724
+ it("pre-existing hidden panel animates in when pushed", () => {
725
+ // Scenario: Panel already exists but was hidden, then becomes active
726
+ const { container, rerender } = render(
727
+ <SwipeStackContent
728
+ id="newPanel"
729
+ depth={1}
730
+ navigationDepth={0}
731
+ isActive={false}
732
+ inputState={IDLE_STATE}
733
+ containerSize={400}
734
+ >
735
+ Content
736
+ </SwipeStackContent>,
737
+ );
738
+
739
+ const element = container.firstChild as HTMLElement;
740
+ // Hidden panel should be at containerSize (off-screen right)
741
+ expect(element.style.transform).toBe("translateX(400px)");
742
+
743
+ // Push navigation: panel becomes active
744
+ rerender(
745
+ <SwipeStackContent
746
+ id="newPanel"
747
+ depth={1}
748
+ navigationDepth={1}
749
+ isActive={true}
750
+ inputState={IDLE_STATE}
751
+ containerSize={400}
752
+ >
753
+ Content
754
+ </SwipeStackContent>,
755
+ );
756
+
757
+ // Animation should start: still at 400px immediately (not snapping to 0)
758
+ expect(element.style.transform).toBe("translateX(400px)");
759
+
760
+ // Flush RAF to complete animation
761
+ act(() => {
762
+ flushRAF(0);
763
+ flushRAF(400);
764
+ });
765
+
766
+ // After animation: at center (0)
767
+ expect(element.style.transform).toBe("translateX(0px)");
768
+ });
769
+
770
+ it("behind panel animates to offset position when new panel is pushed", () => {
771
+ // First render: panel at depth 0 is active
772
+ const { container, rerender } = render(
773
+ <SwipeStackContent
774
+ id="currentPanel"
775
+ depth={0}
776
+ navigationDepth={0}
777
+ isActive={true}
778
+ inputState={IDLE_STATE}
779
+ containerSize={400}
780
+ >
781
+ Content
782
+ </SwipeStackContent>,
783
+ );
784
+
785
+ const element = container.firstChild as HTMLElement;
786
+ // Active panel at center
787
+ expect(element.style.transform).toBe("translateX(0px)");
788
+
789
+ // Push navigation: this panel becomes "behind"
790
+ rerender(
791
+ <SwipeStackContent
792
+ id="currentPanel"
793
+ depth={0}
794
+ navigationDepth={1}
795
+ isActive={false}
796
+ inputState={IDLE_STATE}
797
+ containerSize={400}
798
+ >
799
+ Content
800
+ </SwipeStackContent>,
801
+ );
802
+
803
+ // Animation should start: still at 0px immediately (not snapping to -120px)
804
+ expect(element.style.transform).toBe("translateX(0px)");
805
+
806
+ // Flush RAF to complete animation
807
+ act(() => {
808
+ flushRAF(0);
809
+ flushRAF(400);
810
+ });
811
+
812
+ // After animation: at behind offset (-30% = -120px)
813
+ expect(element.style.transform).toBe("translateX(-120px)");
814
+ });
815
+ });
816
+
817
+ describe("mount → push → back sequence", () => {
818
+ it("panel animates from correct position after push then back", () => {
819
+ // This test covers the bug where:
820
+ // 1. Mount: root panel at depth 0, active
821
+ // 2. Push: new panel at depth 1 is mounted with animateOnMount
822
+ // 3. Back: the panel at depth 1 should animate from its current position (0) to off-screen
823
+
824
+ // Step 1: Mount panel at depth 1 as active (simulating push navigation)
825
+ const { container, rerender } = render(
826
+ <SwipeStackContent
827
+ id="panel1"
828
+ depth={1}
829
+ navigationDepth={1}
830
+ isActive={true}
831
+ inputState={IDLE_STATE}
832
+ containerSize={400}
833
+ animateOnMount={true}
834
+ >
835
+ Content
836
+ </SwipeStackContent>,
837
+ );
838
+
839
+ const element = container.firstChild as HTMLElement;
840
+
841
+ // Initially at off-screen (400px) due to animateOnMount
842
+ expect(element.style.transform).toBe("translateX(400px)");
843
+
844
+ // Complete the push animation
845
+ act(() => {
846
+ flushRAF(0);
847
+ flushRAF(400);
848
+ });
849
+
850
+ // After push animation: should be at center (0)
851
+ expect(element.style.transform).toBe("translateX(0px)");
852
+
853
+ // Step 2: Go back - navigation depth changes, panel becomes hidden
854
+ rerender(
855
+ <SwipeStackContent
856
+ id="panel1"
857
+ depth={1}
858
+ navigationDepth={0}
859
+ isActive={false}
860
+ inputState={IDLE_STATE}
861
+ containerSize={400}
862
+ animateOnMount={true}
863
+ >
864
+ Content
865
+ </SwipeStackContent>,
866
+ );
867
+
868
+ // Animation should start from current position (0px), not from initialPx (400px)
869
+ // The bug is that it might jump to 400px immediately because of animateOnMount logic
870
+ expect(element.style.transform).toBe("translateX(0px)");
871
+
872
+ // Complete the back animation
873
+ act(() => {
874
+ flushRAF(0);
875
+ flushRAF(400);
876
+ });
877
+
878
+ // After back animation: should be off-screen (400px)
879
+ expect(element.style.transform).toBe("translateX(400px)");
880
+ });
881
+
882
+ it("behind panel animates correctly in mount → push → back sequence", () => {
883
+ // Track the behind panel (depth 0) through the sequence
884
+
885
+ // Step 1: Mount as active
886
+ const { container, rerender } = render(
887
+ <SwipeStackContent
888
+ id="rootPanel"
889
+ depth={0}
890
+ navigationDepth={0}
891
+ isActive={true}
892
+ inputState={IDLE_STATE}
893
+ containerSize={400}
894
+ >
895
+ Content
896
+ </SwipeStackContent>,
897
+ );
898
+
899
+ const element = container.firstChild as HTMLElement;
900
+ expect(element.style.transform).toBe("translateX(0px)");
901
+
902
+ // Step 2: Push - this panel becomes "behind"
903
+ rerender(
904
+ <SwipeStackContent
905
+ id="rootPanel"
906
+ depth={0}
907
+ navigationDepth={1}
908
+ isActive={false}
909
+ inputState={IDLE_STATE}
910
+ containerSize={400}
911
+ >
912
+ Content
913
+ </SwipeStackContent>,
914
+ );
915
+
916
+ // Animation starts from 0px
917
+ expect(element.style.transform).toBe("translateX(0px)");
918
+
919
+ act(() => {
920
+ flushRAF(0);
921
+ flushRAF(400);
922
+ });
923
+
924
+ // After animation: at behind offset (-120px)
925
+ expect(element.style.transform).toBe("translateX(-120px)");
926
+
927
+ // Step 3: Back - this panel becomes active again
928
+ rerender(
929
+ <SwipeStackContent
930
+ id="rootPanel"
931
+ depth={0}
932
+ navigationDepth={0}
933
+ isActive={true}
934
+ inputState={IDLE_STATE}
935
+ containerSize={400}
936
+ >
937
+ Content
938
+ </SwipeStackContent>,
939
+ );
940
+
941
+ // Animation should start from current position (-120px)
942
+ expect(element.style.transform).toBe("translateX(-120px)");
943
+
944
+ act(() => {
945
+ flushRAF(0);
946
+ flushRAF(400);
947
+ });
948
+
949
+ // After animation: at center (0)
950
+ expect(element.style.transform).toBe("translateX(0px)");
951
+ });
952
+ });
953
+
954
+ describe("shadow effects", () => {
955
+ it("shows shadow on active panel at depth > 0 by default", () => {
956
+ const { container } = render(
957
+ <SwipeStackContent
958
+ id="panel"
959
+ depth={1}
960
+ navigationDepth={1}
961
+ isActive={true}
962
+ inputState={IDLE_STATE}
963
+ containerSize={400}
964
+ >
965
+ Content
966
+ </SwipeStackContent>,
967
+ );
968
+
969
+ const element = container.firstChild as HTMLElement;
970
+ expect(element.style.boxShadow).toBe("-5px 0 15px rgba(0, 0, 0, 0.1)");
971
+ });
972
+
973
+ it("does not show shadow on root panel (depth 0)", () => {
974
+ const { container } = render(
975
+ <SwipeStackContent
976
+ id="panel"
977
+ depth={0}
978
+ navigationDepth={0}
979
+ isActive={true}
980
+ inputState={IDLE_STATE}
981
+ containerSize={400}
982
+ >
983
+ Content
984
+ </SwipeStackContent>,
985
+ );
986
+
987
+ const element = container.firstChild as HTMLElement;
988
+ expect(element.style.boxShadow).toBe("");
989
+ });
990
+
991
+ it("does not show shadow on behind panel", () => {
992
+ const { container } = render(
993
+ <SwipeStackContent
994
+ id="panel"
995
+ depth={0}
996
+ navigationDepth={1}
997
+ isActive={false}
998
+ inputState={IDLE_STATE}
999
+ containerSize={400}
1000
+ >
1001
+ Content
1002
+ </SwipeStackContent>,
1003
+ );
1004
+
1005
+ const element = container.firstChild as HTMLElement;
1006
+ expect(element.style.boxShadow).toBe("");
1007
+ });
1008
+
1009
+ it("can disable shadow with showShadow=false", () => {
1010
+ const { container } = render(
1011
+ <SwipeStackContent
1012
+ id="panel"
1013
+ depth={1}
1014
+ navigationDepth={1}
1015
+ isActive={true}
1016
+ inputState={IDLE_STATE}
1017
+ containerSize={400}
1018
+ showShadow={false}
1019
+ >
1020
+ Content
1021
+ </SwipeStackContent>,
1022
+ );
1023
+
1024
+ const element = container.firstChild as HTMLElement;
1025
+ expect(element.style.boxShadow).toBe("");
1026
+ });
1027
+ });
1028
+
1029
+ describe("scale effects (stack displayMode)", () => {
1030
+ it("behind panel has scale < 1 in stack mode", () => {
1031
+ const { container } = render(
1032
+ <SwipeStackContent
1033
+ id="panel"
1034
+ depth={0}
1035
+ navigationDepth={1}
1036
+ isActive={false}
1037
+ inputState={IDLE_STATE}
1038
+ containerSize={400}
1039
+ displayMode="stack"
1040
+ >
1041
+ Content
1042
+ </SwipeStackContent>,
1043
+ );
1044
+
1045
+ const element = container.firstChild as HTMLElement;
1046
+ // Transform should include scale(0.95) for 1 level behind
1047
+ expect(element.style.transform).toContain("scale(0.95)");
1048
+ });
1049
+
1050
+ it("active panel has no scale in stack mode", () => {
1051
+ const { container } = render(
1052
+ <SwipeStackContent
1053
+ id="panel"
1054
+ depth={1}
1055
+ navigationDepth={1}
1056
+ isActive={true}
1057
+ inputState={IDLE_STATE}
1058
+ containerSize={400}
1059
+ displayMode="stack"
1060
+ >
1061
+ Content
1062
+ </SwipeStackContent>,
1063
+ );
1064
+
1065
+ const element = container.firstChild as HTMLElement;
1066
+ // Active panel should not have scale transform (or scale(1))
1067
+ expect(element.style.transform).not.toContain("scale(0.");
1068
+ });
1069
+
1070
+ it("no scale effect in overlay mode", () => {
1071
+ const { container } = render(
1072
+ <SwipeStackContent
1073
+ id="panel"
1074
+ depth={0}
1075
+ navigationDepth={1}
1076
+ isActive={false}
1077
+ inputState={IDLE_STATE}
1078
+ containerSize={400}
1079
+ displayMode="overlay"
1080
+ >
1081
+ Content
1082
+ </SwipeStackContent>,
1083
+ );
1084
+
1085
+ const element = container.firstChild as HTMLElement;
1086
+ // No scale in overlay mode
1087
+ expect(element.style.transform).not.toContain("scale");
1088
+ });
1089
+ });
1090
+
1091
+ describe("dimming overlay", () => {
1092
+ it("shows dimming overlay on behind panel by default", () => {
1093
+ const { container } = render(
1094
+ <SwipeStackContent
1095
+ id="panel"
1096
+ depth={0}
1097
+ navigationDepth={1}
1098
+ isActive={false}
1099
+ inputState={IDLE_STATE}
1100
+ containerSize={400}
1101
+ >
1102
+ Content
1103
+ </SwipeStackContent>,
1104
+ );
1105
+
1106
+ const overlay = container.querySelector("[data-dimming-overlay]");
1107
+ expect(overlay).toBeInTheDocument();
1108
+ });
1109
+
1110
+ it("no dimming overlay on active panel", () => {
1111
+ const { container } = render(
1112
+ <SwipeStackContent
1113
+ id="panel"
1114
+ depth={1}
1115
+ navigationDepth={1}
1116
+ isActive={true}
1117
+ inputState={IDLE_STATE}
1118
+ containerSize={400}
1119
+ >
1120
+ Content
1121
+ </SwipeStackContent>,
1122
+ );
1123
+
1124
+ const overlay = container.querySelector("[data-dimming-overlay]");
1125
+ expect(overlay).not.toBeInTheDocument();
1126
+ });
1127
+
1128
+ it("dimming fades during swipe", () => {
1129
+ const { container, rerender } = render(
1130
+ <SwipeStackContent
1131
+ id="panel"
1132
+ depth={0}
1133
+ navigationDepth={1}
1134
+ isActive={false}
1135
+ inputState={IDLE_STATE}
1136
+ containerSize={400}
1137
+ >
1138
+ Content
1139
+ </SwipeStackContent>,
1140
+ );
1141
+
1142
+ // At rest, should have dimming
1143
+ let overlay = container.querySelector("[data-dimming-overlay]") as HTMLElement;
1144
+ expect(overlay).toBeInTheDocument();
1145
+ const initialOpacity = overlay.style.backgroundColor;
1146
+
1147
+ // During swipe at 100% progress, dimming should be gone
1148
+ rerender(
1149
+ <SwipeStackContent
1150
+ id="panel"
1151
+ depth={0}
1152
+ navigationDepth={1}
1153
+ isActive={false}
1154
+ inputState={createSwipingState(400)}
1155
+ containerSize={400}
1156
+ >
1157
+ Content
1158
+ </SwipeStackContent>,
1159
+ );
1160
+
1161
+ overlay = container.querySelector("[data-dimming-overlay]") as HTMLElement;
1162
+ // At 100% swipe, opacity should be 0 (no overlay)
1163
+ expect(overlay).not.toBeInTheDocument();
1164
+ });
1165
+
1166
+ it("can disable dimming with showDimming=false", () => {
1167
+ const { container } = render(
1168
+ <SwipeStackContent
1169
+ id="panel"
1170
+ depth={0}
1171
+ navigationDepth={1}
1172
+ isActive={false}
1173
+ inputState={IDLE_STATE}
1174
+ containerSize={400}
1175
+ showDimming={false}
1176
+ >
1177
+ Content
1178
+ </SwipeStackContent>,
1179
+ );
1180
+
1181
+ const overlay = container.querySelector("[data-dimming-overlay]");
1182
+ expect(overlay).not.toBeInTheDocument();
1183
+ });
1184
+ });
1185
+
1186
+ describe("swipe commit (navigation changes)", () => {
1187
+ it("active panel moves off-screen when navigation depth decreases", () => {
1188
+ const { container, rerender } = render(
1189
+ <SwipeStackContent
1190
+ id="panel"
1191
+ depth={1}
1192
+ navigationDepth={1}
1193
+ isActive={true}
1194
+ inputState={IDLE_STATE}
1195
+ containerSize={400}
1196
+ >
1197
+ Content
1198
+ </SwipeStackContent>,
1199
+ );
1200
+
1201
+ const element = container.firstChild as HTMLElement;
1202
+ expect(element.style.transform).toBe("translateX(0px)");
1203
+
1204
+ // Navigation changed: depth 1 is no longer active (went back to depth 0)
1205
+ // Panel should animate to off-screen position (containerSize = 400)
1206
+ rerender(
1207
+ <SwipeStackContent
1208
+ id="panel"
1209
+ depth={1}
1210
+ navigationDepth={0}
1211
+ isActive={false}
1212
+ inputState={IDLE_STATE}
1213
+ containerSize={400}
1214
+ >
1215
+ Content
1216
+ </SwipeStackContent>,
1217
+ );
1218
+
1219
+ // Role changes to "hidden"
1220
+ expect(element.getAttribute("data-role")).toBe("hidden");
1221
+
1222
+ // Flush RAF to complete animation
1223
+ act(() => {
1224
+ flushRAF(0);
1225
+ flushRAF(400);
1226
+ });
1227
+
1228
+ // After animation: off-screen at containerSize
1229
+ expect(element.style.transform).toBe("translateX(400px)");
1230
+ });
1231
+
1232
+ it("behind panel becomes active and moves to center when navigation commits", () => {
1233
+ const { container, rerender } = render(
1234
+ <SwipeStackContent
1235
+ id="panel"
1236
+ depth={0}
1237
+ navigationDepth={1}
1238
+ isActive={false}
1239
+ inputState={IDLE_STATE}
1240
+ containerSize={400}
1241
+ >
1242
+ Content
1243
+ </SwipeStackContent>,
1244
+ );
1245
+
1246
+ const element = container.firstChild as HTMLElement;
1247
+ expect(element.style.transform).toBe("translateX(-120px)");
1248
+ expect(element.getAttribute("data-role")).toBe("behind");
1249
+
1250
+ // Navigation commits: panel at depth 0 becomes active
1251
+ rerender(
1252
+ <SwipeStackContent
1253
+ id="panel"
1254
+ depth={0}
1255
+ navigationDepth={0}
1256
+ isActive={true}
1257
+ inputState={IDLE_STATE}
1258
+ containerSize={400}
1259
+ >
1260
+ Content
1261
+ </SwipeStackContent>,
1262
+ );
1263
+
1264
+ // Role changes to "active"
1265
+ expect(element.getAttribute("data-role")).toBe("active");
1266
+
1267
+ // Flush RAF to complete animation
1268
+ act(() => {
1269
+ flushRAF(0);
1270
+ flushRAF(400);
1271
+ });
1272
+
1273
+ // After animation: at center (0)
1274
+ expect(element.style.transform).toBe("translateX(0px)");
1275
+ });
1276
+ });
1277
+ });