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,356 @@
1
+ /**
2
+ * @file SwipeStackContent component for Stack panels with direct DOM manipulation.
3
+ *
4
+ * Provides iOS-style swipe-to-go-back behavior:
5
+ * - Active panel follows the finger directly
6
+ * - Behind panel reveals from -30% with parallax effect
7
+ *
8
+ * Uses useSwipeContentTransform for immediate DOM updates.
9
+ */
10
+ import * as React from "react";
11
+ import { useSwipeContentTransform } from "../../hooks/useSwipeContentTransform.js";
12
+ import type { SwipeInputState, GestureAxis } from "../../hooks/gesture/types.js";
13
+ import type { StackDisplayMode } from "./types.js";
14
+ import {
15
+ computeActiveTargetPx,
16
+ computeBehindTargetPx,
17
+ computeSwipeVisibility,
18
+ determineSwipePanelRole,
19
+ DEFAULT_BEHIND_OFFSET,
20
+ } from "./computeSwipeStackTransform.js";
21
+
22
+ const DEFAULT_ANIMATION_DURATION = 300;
23
+
24
+ /**
25
+ * Scale factor per depth level for "stack" display mode.
26
+ * Each level behind reduces scale by this amount.
27
+ */
28
+ const STACK_SCALE_FACTOR = 0.05;
29
+
30
+ /**
31
+ * Offset percentage per depth level for "stack" display mode.
32
+ */
33
+ const STACK_OFFSET_PERCENT = 5;
34
+
35
+ /**
36
+ * Maximum dimming opacity for behind panels in iOS-style navigation.
37
+ */
38
+ const MAX_DIM_OPACITY = 0.1;
39
+
40
+ /**
41
+ * Props for SwipeStackContent component.
42
+ */
43
+ export type SwipeStackContentProps = {
44
+ /** Panel ID */
45
+ id: string;
46
+ /** Panel depth in the stack */
47
+ depth: number;
48
+ /** Current navigation depth (active panel) */
49
+ navigationDepth: number;
50
+ /** Whether this panel is currently active */
51
+ isActive: boolean;
52
+ /** Swipe input state from useStackSwipeInput */
53
+ inputState: SwipeInputState;
54
+ /** Container size in pixels (width for horizontal, height for vertical) */
55
+ containerSize: number;
56
+ /** Gesture axis. @default "horizontal" */
57
+ axis?: GestureAxis;
58
+ /** Behind panel offset ratio. @default -0.3 */
59
+ behindOffset?: number;
60
+ /** Animation duration in ms. @default 300 */
61
+ animationDuration?: number;
62
+ /**
63
+ * Whether to animate when first mounted as active.
64
+ * Set to true for push navigation animations.
65
+ * @default false
66
+ */
67
+ animateOnMount?: boolean;
68
+ /**
69
+ * Whether to show iOS-style edge shadow on active panel.
70
+ * @default true
71
+ */
72
+ showShadow?: boolean;
73
+ /**
74
+ * Display mode for visual styling.
75
+ * - "overlay": panels overlay, no scale (iOS style)
76
+ * - "slide": panels slide with parallax
77
+ * - "stack": panels scale down and offset (stacked cards style)
78
+ * @default "overlay"
79
+ */
80
+ displayMode?: StackDisplayMode;
81
+ /**
82
+ * Whether to show dimming overlay on behind panels.
83
+ * Creates iOS-style darkening effect that fades during swipe.
84
+ * @default true
85
+ */
86
+ showDimming?: boolean;
87
+ /** Content to render */
88
+ children: React.ReactNode;
89
+ };
90
+
91
+ const BASE_STYLE: React.CSSProperties = {
92
+ position: "absolute",
93
+ inset: 0,
94
+ width: "100%",
95
+ height: "100%",
96
+ };
97
+
98
+ /**
99
+ * Get displacement from input state for the given axis.
100
+ */
101
+ const getAxisDisplacement = (inputState: SwipeInputState, axis: GestureAxis): number => {
102
+ if (inputState.phase === "idle") {
103
+ return 0;
104
+ }
105
+ return axis === "horizontal" ? inputState.displacement.x : inputState.displacement.y;
106
+ };
107
+
108
+ /**
109
+ * SwipeStackContent renders a single stack panel with swipe gesture support.
110
+ *
111
+ * Key behaviors:
112
+ * - Active panel: follows finger directly (translateX = displacement)
113
+ * - Behind panel: reveals from -30% with parallax (slower movement)
114
+ * - Hidden panels: not rendered during swipe
115
+ *
116
+ * @example
117
+ * ```tsx
118
+ * <SwipeStackContent
119
+ * id="detail"
120
+ * depth={1}
121
+ * navigationDepth={1}
122
+ * isActive={true}
123
+ * inputState={swipeInput.inputState}
124
+ * containerSize={containerWidth}
125
+ * >
126
+ * <DetailPanel />
127
+ * </SwipeStackContent>
128
+ * ```
129
+ */
130
+ // iOS-style left edge shadow for active panels
131
+ const ACTIVE_PANEL_SHADOW = "-5px 0 15px rgba(0, 0, 0, 0.1)";
132
+
133
+ export const SwipeStackContent: React.FC<SwipeStackContentProps> = React.memo(
134
+ ({
135
+ id,
136
+ depth,
137
+ navigationDepth,
138
+ isActive,
139
+ inputState,
140
+ containerSize,
141
+ axis = "horizontal",
142
+ behindOffset = DEFAULT_BEHIND_OFFSET,
143
+ animationDuration = DEFAULT_ANIMATION_DURATION,
144
+ animateOnMount = false,
145
+ showShadow = true,
146
+ displayMode = "overlay",
147
+ showDimming = true,
148
+ children,
149
+ }) => {
150
+ const elementRef = React.useRef<HTMLDivElement>(null);
151
+ const isFirstMountRef = React.useRef<boolean>(true);
152
+
153
+ const displacement = getAxisDisplacement(inputState, axis);
154
+ const isSwiping = inputState.phase === "swiping" || inputState.phase === "tracking";
155
+
156
+ // Determine panel role
157
+ const role = determineSwipePanelRole(depth, navigationDepth);
158
+
159
+ // Track first mount for push animation
160
+ const isFirstMount = isFirstMountRef.current;
161
+ if (isFirstMountRef.current) {
162
+ isFirstMountRef.current = false;
163
+ }
164
+
165
+ // Compute target position based on role
166
+ const targetPx = React.useMemo(() => {
167
+ switch (role) {
168
+ case "active":
169
+ // Active panel rests at 0
170
+ return 0;
171
+ case "behind":
172
+ // Behind panel rests at offset position
173
+ return behindOffset * containerSize;
174
+ case "hidden":
175
+ // Hidden panels are off-screen
176
+ return containerSize;
177
+ }
178
+ }, [role, behindOffset, containerSize]);
179
+
180
+ // Compute displacement for this panel
181
+ const panelDisplacement = React.useMemo(() => {
182
+ if (displacement <= 0) {
183
+ return 0;
184
+ }
185
+
186
+ switch (role) {
187
+ case "active":
188
+ // Active panel follows finger directly
189
+ return computeActiveTargetPx(displacement);
190
+ case "behind": {
191
+ // Behind panel uses parallax - compute offset from base position
192
+ const currentPos = computeBehindTargetPx(displacement, containerSize, behindOffset);
193
+ const basePos = behindOffset * containerSize;
194
+ return currentPos - basePos;
195
+ }
196
+ case "hidden":
197
+ return 0;
198
+ }
199
+ }, [role, displacement, containerSize, behindOffset]);
200
+
201
+ // Compute initial position for push animation
202
+ // When animateOnMount is true and panel is first mounted as "active",
203
+ // it should animate in from off-screen
204
+ // Root panel (depth=0) should not animate on mount
205
+ const initialPx = React.useMemo(() => {
206
+ if (!isFirstMount || !animateOnMount) {
207
+ return undefined; // Only relevant on first mount with animateOnMount
208
+ }
209
+ if (role === "active" && depth > 0) {
210
+ // New active panel (not root): start from off-screen right
211
+ return containerSize;
212
+ }
213
+ // Root panel or other roles: start at their natural position
214
+ return undefined;
215
+ }, [isFirstMount, animateOnMount, role, depth, containerSize]);
216
+
217
+ // Use shared transform hook for DOM manipulation
218
+ const { isAnimating } = useSwipeContentTransform({
219
+ elementRef,
220
+ targetPx,
221
+ displacement: panelDisplacement,
222
+ isSwiping,
223
+ axis,
224
+ animationDuration,
225
+ containerSize,
226
+ // Animate when targetPx changes (button navigation)
227
+ animateOnTargetChange: true,
228
+ // For push animation: start from off-screen
229
+ initialPx,
230
+ });
231
+
232
+ // Compute visibility
233
+ const visible = computeSwipeVisibility({
234
+ depth,
235
+ navigationDepth,
236
+ isActive,
237
+ isSwiping,
238
+ isAnimating,
239
+ });
240
+
241
+ // Compute swipe progress for scale and dimming interpolation
242
+ const swipeProgress = React.useMemo(() => {
243
+ if (containerSize <= 0 || displacement <= 0) {
244
+ return 0;
245
+ }
246
+ return Math.min(displacement / containerSize, 1);
247
+ }, [displacement, containerSize]);
248
+
249
+ // Compute scale for "stack" display mode
250
+ // Behind panels are scaled down, and scale interpolates during swipe
251
+ const scale = React.useMemo(() => {
252
+ if (displayMode !== "stack") {
253
+ return 1; // No scale for overlay/slide modes
254
+ }
255
+
256
+ const depthDiff = navigationDepth - depth;
257
+
258
+ if (role === "active") {
259
+ return 1; // Active panel is always at full scale
260
+ }
261
+
262
+ if (role === "behind") {
263
+ // Base scale for behind panel
264
+ const baseScale = 1 - depthDiff * STACK_SCALE_FACTOR;
265
+ // During swipe, interpolate toward 1
266
+ return baseScale + swipeProgress * (1 - baseScale);
267
+ }
268
+
269
+ return 1;
270
+ }, [displayMode, role, depth, navigationDepth, swipeProgress]);
271
+
272
+ // Compute dimming opacity for behind panels
273
+ // Full dimming at rest, fades to 0 during swipe
274
+ const dimmingOpacity = React.useMemo(() => {
275
+ if (!showDimming || role !== "behind") {
276
+ return 0;
277
+ }
278
+ // Fade from MAX_DIM_OPACITY to 0 as swipe progresses
279
+ return MAX_DIM_OPACITY * (1 - swipeProgress);
280
+ }, [showDimming, role, swipeProgress]);
281
+
282
+ // Update visibility via direct DOM manipulation
283
+ React.useLayoutEffect(() => {
284
+ const element = elementRef.current;
285
+ if (element) {
286
+ element.style.visibility = visible ? "visible" : "hidden";
287
+ }
288
+ }, [visible]);
289
+
290
+ // Update scale via direct DOM manipulation for smooth animation
291
+ React.useLayoutEffect(() => {
292
+ const element = elementRef.current;
293
+ if (!element || displayMode !== "stack") {
294
+ return;
295
+ }
296
+
297
+ // Get current transform (translateX) and append scale
298
+ const currentTransform = element.style.transform;
299
+ if (currentTransform.includes("translateX")) {
300
+ // Extract translateX value and combine with scale
301
+ const translateMatch = currentTransform.match(/translateX\([^)]+\)/);
302
+ if (translateMatch) {
303
+ element.style.transform = `${translateMatch[0]} scale(${scale})`;
304
+ }
305
+ } else {
306
+ element.style.transform = `scale(${scale})`;
307
+ }
308
+ }, [scale, displayMode]);
309
+
310
+ // Compute shadow for active panel
311
+ // Shadow is shown on panels at depth > 0 when they're active or animating
312
+ const shouldShowShadow = showShadow && depth > 0 && role === "active";
313
+
314
+ // Static style - transform is handled entirely by useSwipeContentTransform
315
+ // to ensure smooth animations
316
+ const staticStyle = React.useMemo<React.CSSProperties>(
317
+ () => ({
318
+ ...BASE_STYLE,
319
+ pointerEvents: isActive ? "auto" : "none",
320
+ willChange: "transform",
321
+ zIndex: depth,
322
+ visibility: visible ? "visible" : "hidden",
323
+ boxShadow: shouldShowShadow ? ACTIVE_PANEL_SHADOW : undefined,
324
+ }),
325
+ [isActive, depth, visible, shouldShowShadow],
326
+ );
327
+
328
+ // Dimming overlay style for behind panels
329
+ const dimmingStyle = React.useMemo<React.CSSProperties | null>(() => {
330
+ if (dimmingOpacity <= 0) {
331
+ return null;
332
+ }
333
+ return {
334
+ position: "absolute",
335
+ inset: 0,
336
+ backgroundColor: `rgba(0, 0, 0, ${dimmingOpacity})`,
337
+ pointerEvents: "none",
338
+ zIndex: 1,
339
+ };
340
+ }, [dimmingOpacity]);
341
+
342
+ return (
343
+ <div
344
+ ref={elementRef}
345
+ data-stack-content={id}
346
+ data-depth={depth}
347
+ data-active={isActive ? "true" : "false"}
348
+ data-role={role}
349
+ style={staticStyle}
350
+ >
351
+ {children}
352
+ {dimmingStyle != null && <div style={dimmingStyle} data-dimming-overlay />}
353
+ </div>
354
+ );
355
+ },
356
+ );
@@ -0,0 +1,252 @@
1
+ /**
2
+ * @file Tests for SwipeStackOutlet component.
3
+ */
4
+ import { render } from "@testing-library/react";
5
+ import { SwipeStackOutlet } from "./SwipeStackOutlet.js";
6
+ import type { SwipeInputState } from "../../hooks/gesture/types.js";
7
+ import type { StackPanel, StackNavigationState } from "./types.js";
8
+
9
+ const IDLE_STATE: SwipeInputState = {
10
+ phase: "idle",
11
+ displacement: { x: 0, y: 0 },
12
+ velocity: { x: 0, y: 0 },
13
+ direction: 0,
14
+ };
15
+
16
+ const createSwipingState = (displacementX: number): SwipeInputState => ({
17
+ phase: "swiping",
18
+ displacement: { x: displacementX, y: 0 },
19
+ velocity: { x: 0.5, y: 0 },
20
+ direction: displacementX > 0 ? 1 : displacementX < 0 ? -1 : 0,
21
+ });
22
+
23
+ const createPanels = (): StackPanel[] => [
24
+ { id: "home", title: "Home", content: <div>Home Content</div> },
25
+ { id: "list", title: "List", content: <div>List Content</div> },
26
+ { id: "detail", title: "Detail", content: <div>Detail Content</div> },
27
+ ];
28
+
29
+ describe("SwipeStackOutlet", () => {
30
+ describe("rendering", () => {
31
+ it("renders container with correct attributes", () => {
32
+ const navigationState: StackNavigationState = {
33
+ stack: ["home"],
34
+ depth: 0,
35
+ isRevealing: false,
36
+ revealDepth: null,
37
+ };
38
+
39
+ const { container } = render(
40
+ <SwipeStackOutlet
41
+ panels={createPanels()}
42
+ navigationState={navigationState}
43
+ inputState={IDLE_STATE}
44
+ containerSize={400}
45
+ />,
46
+ );
47
+
48
+ const stackContainer = container.querySelector("[data-swipe-stack-container]");
49
+ expect(stackContainer).toBeInTheDocument();
50
+ });
51
+
52
+ it("renders only active panel at root depth", () => {
53
+ const navigationState: StackNavigationState = {
54
+ stack: ["home"],
55
+ depth: 0,
56
+ isRevealing: false,
57
+ revealDepth: null,
58
+ };
59
+
60
+ const { container, getByText } = render(
61
+ <SwipeStackOutlet
62
+ panels={createPanels()}
63
+ navigationState={navigationState}
64
+ inputState={IDLE_STATE}
65
+ containerSize={400}
66
+ />,
67
+ );
68
+
69
+ expect(getByText("Home Content")).toBeInTheDocument();
70
+
71
+ // Should only have one SwipeStackContent
72
+ const stackContents = container.querySelectorAll("[data-stack-content]");
73
+ expect(stackContents).toHaveLength(1);
74
+ });
75
+
76
+ it("renders active and behind panels when depth > 0", () => {
77
+ const navigationState: StackNavigationState = {
78
+ stack: ["home", "list"],
79
+ depth: 1,
80
+ isRevealing: false,
81
+ revealDepth: null,
82
+ };
83
+
84
+ const { container, getByText } = render(
85
+ <SwipeStackOutlet
86
+ panels={createPanels()}
87
+ navigationState={navigationState}
88
+ inputState={IDLE_STATE}
89
+ containerSize={400}
90
+ />,
91
+ );
92
+
93
+ expect(getByText("Home Content")).toBeInTheDocument();
94
+ expect(getByText("List Content")).toBeInTheDocument();
95
+
96
+ const stackContents = container.querySelectorAll("[data-stack-content]");
97
+ expect(stackContents).toHaveLength(2);
98
+ });
99
+
100
+ it("assigns correct roles to panels", () => {
101
+ const navigationState: StackNavigationState = {
102
+ stack: ["home", "list"],
103
+ depth: 1,
104
+ isRevealing: false,
105
+ revealDepth: null,
106
+ };
107
+
108
+ const { container } = render(
109
+ <SwipeStackOutlet
110
+ panels={createPanels()}
111
+ navigationState={navigationState}
112
+ inputState={IDLE_STATE}
113
+ containerSize={400}
114
+ />,
115
+ );
116
+
117
+ const homePanel = container.querySelector('[data-stack-content="home"]');
118
+ const listPanel = container.querySelector('[data-stack-content="list"]');
119
+
120
+ expect(homePanel?.getAttribute("data-role")).toBe("behind");
121
+ expect(listPanel?.getAttribute("data-role")).toBe("active");
122
+ });
123
+ });
124
+
125
+ describe("swipe behavior", () => {
126
+ it("passes input state to panels during swipe", () => {
127
+ const navigationState: StackNavigationState = {
128
+ stack: ["home", "list"],
129
+ depth: 1,
130
+ isRevealing: false,
131
+ revealDepth: null,
132
+ };
133
+
134
+ const swipeState = createSwipingState(200);
135
+
136
+ const { container, rerender } = render(
137
+ <SwipeStackOutlet
138
+ panels={createPanels()}
139
+ navigationState={navigationState}
140
+ inputState={IDLE_STATE}
141
+ containerSize={400}
142
+ />,
143
+ );
144
+
145
+ // Start swiping
146
+ rerender(
147
+ <SwipeStackOutlet
148
+ panels={createPanels()}
149
+ navigationState={navigationState}
150
+ inputState={swipeState}
151
+ containerSize={400}
152
+ />,
153
+ );
154
+
155
+ const listPanel = container.querySelector('[data-stack-content="list"]') as HTMLElement;
156
+ const homePanel = container.querySelector('[data-stack-content="home"]') as HTMLElement;
157
+
158
+ // Active panel should be at displacement position
159
+ expect(listPanel.style.transform).toBe("translateX(200px)");
160
+
161
+ // Behind panel should be at parallax position (-60px, halfway from -120 to 0)
162
+ expect(homePanel.style.transform).toBe("translateX(-60px)");
163
+ });
164
+ });
165
+
166
+ describe("cached content", () => {
167
+ it("uses getCachedContent when provided", () => {
168
+ const navigationState: StackNavigationState = {
169
+ stack: ["home"],
170
+ depth: 0,
171
+ isRevealing: false,
172
+ revealDepth: null,
173
+ };
174
+
175
+ const calledWith: string[] = [];
176
+ const getCachedContent = (panelId: string): React.ReactNode => {
177
+ calledWith.push(panelId);
178
+ if (panelId === "home") {
179
+ return <div>Cached Home Content</div>;
180
+ }
181
+ return null;
182
+ };
183
+
184
+ const { getByText, queryByText } = render(
185
+ <SwipeStackOutlet
186
+ panels={createPanels()}
187
+ navigationState={navigationState}
188
+ inputState={IDLE_STATE}
189
+ containerSize={400}
190
+ getCachedContent={getCachedContent}
191
+ />,
192
+ );
193
+
194
+ expect(getByText("Cached Home Content")).toBeInTheDocument();
195
+ expect(queryByText("Home Content")).not.toBeInTheDocument();
196
+ expect(calledWith).toContain("home");
197
+ });
198
+
199
+ it("falls back to panel content when getCachedContent returns null", () => {
200
+ const navigationState: StackNavigationState = {
201
+ stack: ["home"],
202
+ depth: 0,
203
+ isRevealing: false,
204
+ revealDepth: null,
205
+ };
206
+
207
+ const getCachedContent = (): React.ReactNode => null;
208
+
209
+ const { getByText } = render(
210
+ <SwipeStackOutlet
211
+ panels={createPanels()}
212
+ navigationState={navigationState}
213
+ inputState={IDLE_STATE}
214
+ containerSize={400}
215
+ getCachedContent={getCachedContent}
216
+ />,
217
+ );
218
+
219
+ expect(getByText("Home Content")).toBeInTheDocument();
220
+ });
221
+ });
222
+
223
+ describe("deep navigation", () => {
224
+ it("only renders active and behind panels, not deeper ones", () => {
225
+ const navigationState: StackNavigationState = {
226
+ stack: ["home", "list", "detail"],
227
+ depth: 2,
228
+ isRevealing: false,
229
+ revealDepth: null,
230
+ };
231
+
232
+ const { container, getByText, queryByText } = render(
233
+ <SwipeStackOutlet
234
+ panels={createPanels()}
235
+ navigationState={navigationState}
236
+ inputState={IDLE_STATE}
237
+ containerSize={400}
238
+ />,
239
+ );
240
+
241
+ // Should render detail (active) and list (behind)
242
+ expect(getByText("Detail Content")).toBeInTheDocument();
243
+ expect(getByText("List Content")).toBeInTheDocument();
244
+
245
+ // Should NOT render home (too deep)
246
+ expect(queryByText("Home Content")).not.toBeInTheDocument();
247
+
248
+ const stackContents = container.querySelectorAll("[data-stack-content]");
249
+ expect(stackContents).toHaveLength(2);
250
+ });
251
+ });
252
+ });