react-panel-layout 0.5.2 → 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 (463) hide show
  1. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
  2. package/dist/{FloatingPanelFrame-D9Cp2al1.cjs.map → FloatingPanelFrame-CEmXDvUA.cjs.map} +1 -1
  3. package/dist/{FloatingPanelFrame-lLg-Lpg7.js → FloatingPanelFrame-SgYLc6Ud.js} +11 -11
  4. package/dist/{FloatingPanelFrame-lLg-Lpg7.js.map → FloatingPanelFrame-SgYLc6Ud.js.map} +1 -1
  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 +67 -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/useContentCache-CO3LYNmz.js +24 -0
  111. package/dist/useContentCache-CO3LYNmz.js.map +1 -0
  112. package/dist/useContentCache-DqXtLrLs.cjs +2 -0
  113. package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
  114. package/dist/useDocumentPointerEvents-CKdhGXd0.js +46 -0
  115. package/dist/useDocumentPointerEvents-CKdhGXd0.js.map +1 -0
  116. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs +2 -0
  117. package/dist/useDocumentPointerEvents-ChqrKXDk.cjs.map +1 -0
  118. package/dist/useEffectEvent-Dp7HLCf0.js +13 -0
  119. package/dist/useEffectEvent-Dp7HLCf0.js.map +1 -0
  120. package/dist/useEffectEvent-huSsGUnl.cjs +2 -0
  121. package/dist/useEffectEvent-huSsGUnl.cjs.map +1 -0
  122. package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
  123. package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
  124. package/dist/useFloatingState-tEfA_wbc.js +74 -0
  125. package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
  126. package/dist/window/index.d.ts +61 -0
  127. package/dist/window.cjs +2 -0
  128. package/dist/window.cjs.map +1 -0
  129. package/dist/window.js +149 -0
  130. package/dist/window.js.map +1 -0
  131. package/docs/design-tokens.md +405 -0
  132. package/package.json +29 -4
  133. package/src/PanelSystemContext.tsx +88 -0
  134. package/src/components/grid/GridLayerList.tsx +172 -0
  135. package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
  136. package/src/components/grid/GridLayout.spec.tsx +743 -0
  137. package/src/components/grid/GridLayout.tsx +130 -0
  138. package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
  139. package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
  140. package/src/components/panels/DropSuggestOverlay.tsx +131 -0
  141. package/src/components/panels/PanelGroupView.tsx +112 -0
  142. package/src/components/pivot/PivotLayer.tsx +27 -0
  143. package/src/components/resizer/HorizontalDivider.tsx +52 -0
  144. package/src/components/resizer/ResizeHandle.tsx +118 -0
  145. package/src/components/tabs/TabBar.tsx +223 -0
  146. package/src/components/tabs/TabBarTab.tsx +133 -0
  147. package/src/components/tabs/TabDragOverlay.tsx +92 -0
  148. package/src/components/window/DialogOverlay.tsx +180 -0
  149. package/src/components/window/Drawer.tsx +282 -0
  150. package/src/components/window/DrawerLayers.tsx +58 -0
  151. package/src/components/window/FloatingWindow.tsx +95 -0
  152. package/src/components/window/PopupLayerPortal.tsx +218 -0
  153. package/src/config/PanelContentDeclaration.tsx +427 -0
  154. package/src/config/index.tsx +52 -0
  155. package/src/config/panelJsx.spec.tsx +54 -0
  156. package/src/config/panelJsxConfig.spec.tsx +54 -0
  157. package/src/config/panelJsxDrawer.spec.tsx +33 -0
  158. package/src/config/panelRouter.spec.ts +68 -0
  159. package/src/config/panelRouter.tsx +155 -0
  160. package/src/constants/styles.ts +261 -0
  161. package/src/demo/Layout.module.css +258 -0
  162. package/src/demo/Layout.tsx +176 -0
  163. package/src/demo/components/CodeBlock.module.css +54 -0
  164. package/src/demo/components/CodeBlock.tsx +34 -0
  165. package/src/demo/components/CodePreview.module.css +37 -0
  166. package/src/demo/components/CodePreview.tsx +31 -0
  167. package/src/demo/components/DataPreview.module.css +177 -0
  168. package/src/demo/components/DataPreview.tsx +115 -0
  169. package/src/demo/components/Story.module.css +68 -0
  170. package/src/demo/components/Story.tsx +54 -0
  171. package/src/demo/components/layout/CodePanel.module.css +183 -0
  172. package/src/demo/components/layout/CodePanel.tsx +149 -0
  173. package/src/demo/components/layout/DemoPage.module.css +60 -0
  174. package/src/demo/components/layout/DemoPage.tsx +56 -0
  175. package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
  176. package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
  177. package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
  178. package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
  179. package/src/demo/components/layout/index.ts +11 -0
  180. package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
  181. package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
  182. package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
  183. package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
  184. package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
  185. package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
  186. package/src/demo/components/tab-styles/index.ts +6 -0
  187. package/src/demo/components/ui/DemoButton.module.css +63 -0
  188. package/src/demo/components/ui/DemoButton.tsx +32 -0
  189. package/src/demo/components/ui/DemoCard.module.css +15 -0
  190. package/src/demo/components/ui/DemoCard.tsx +30 -0
  191. package/src/demo/components/ui/DemoContainer.module.css +17 -0
  192. package/src/demo/components/ui/DemoContainer.tsx +30 -0
  193. package/src/demo/components/ui/DemoPanel.module.css +23 -0
  194. package/src/demo/components/ui/DemoPanel.tsx +33 -0
  195. package/src/demo/components/ui/PanelText.module.css +18 -0
  196. package/src/demo/components/ui/PanelText.tsx +29 -0
  197. package/src/demo/components/ui/PanelTitle.module.css +18 -0
  198. package/src/demo/components/ui/PanelTitle.tsx +31 -0
  199. package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
  200. package/src/demo/demo.css +172 -0
  201. package/src/demo/hooks/useMedia.ts +41 -0
  202. package/src/demo/hooks/useShikiHighlight.ts +55 -0
  203. package/src/demo/index.tsx +293 -0
  204. package/src/demo/pages/Drawer/animations/index.tsx +22 -0
  205. package/src/demo/pages/Drawer/basics/index.tsx +17 -0
  206. package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
  207. package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
  208. package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
  209. package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
  210. package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
  211. package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
  212. package/src/demo/pages/Drawer/menu/index.tsx +17 -0
  213. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
  214. package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
  215. package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
  216. package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
  217. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
  218. package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
  219. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
  220. package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
  221. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
  222. package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
  223. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
  224. package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
  225. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
  226. package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
  227. package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
  228. package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
  229. package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
  230. package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
  231. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
  232. package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
  233. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
  234. package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
  235. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
  236. package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
  237. package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
  238. package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
  239. package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
  240. package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
  241. package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
  242. package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
  243. package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
  244. package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
  245. package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
  246. package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
  247. package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
  248. package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
  249. package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
  250. package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
  251. package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
  252. package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
  253. package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
  254. package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
  255. package/src/demo/pages/PanelLayout/index.tsx +94 -0
  256. package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
  257. package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
  258. package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
  259. package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
  260. package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
  261. package/src/demo/pages/Pivot/basics/index.tsx +17 -0
  262. package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
  263. package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
  264. package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
  265. package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
  266. package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
  267. package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
  268. package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
  269. package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
  270. package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +289 -0
  271. package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
  272. package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
  273. package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
  274. package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
  275. package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
  276. package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
  277. package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
  278. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
  279. package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
  280. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
  281. package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
  282. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
  283. package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
  284. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
  285. package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
  286. package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
  287. package/src/demo/pages/ResizeHandle/index.module.css +11 -0
  288. package/src/demo/pages/ResizeHandle/index.tsx +71 -0
  289. package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
  290. package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
  291. package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
  292. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
  293. package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
  294. package/src/demo/pages/Stack/basics/index.tsx +22 -0
  295. package/src/demo/pages/Stack/components/Stack.module.css +234 -0
  296. package/src/demo/pages/Stack/components/StackBasics.tsx +217 -0
  297. package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
  298. package/src/demo/pages/Stack/components/StackTablet.tsx +401 -0
  299. package/src/demo/pages/Stack/tablet/index.tsx +22 -0
  300. package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
  301. package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
  302. package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
  303. package/src/demo/routes.tsx +193 -0
  304. package/src/demo/styles/animations.css +68 -0
  305. package/src/demo/styles/stack-themes.css +35 -0
  306. package/src/demo/utils/createPanelView.tsx +58 -0
  307. package/src/floating/index.ts +24 -0
  308. package/src/grid/index.ts +75 -0
  309. package/src/hooks/ContentCacheContext.tsx +87 -0
  310. package/src/hooks/gesture/presets.spec.ts +86 -0
  311. package/src/hooks/gesture/presets.ts +95 -0
  312. package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +237 -0
  313. package/src/hooks/gesture/testing/createGestureSimulator.ts +310 -0
  314. package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
  315. package/src/hooks/gesture/thresholdValue.ts +77 -0
  316. package/src/hooks/gesture/types.ts +290 -0
  317. package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
  318. package/src/hooks/gesture/useDirectionalLock.ts +115 -0
  319. package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +454 -0
  320. package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
  321. package/src/hooks/gesture/useNativeGestureGuard.spec.ts +413 -0
  322. package/src/hooks/gesture/useNativeGestureGuard.ts +133 -0
  323. package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
  324. package/src/hooks/gesture/usePointerTracking.ts +134 -0
  325. package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
  326. package/src/hooks/gesture/useScrollBoundary.ts +113 -0
  327. package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
  328. package/src/hooks/gesture/useSwipeInput.ts +310 -0
  329. package/src/hooks/gesture/utils.spec.ts +152 -0
  330. package/src/hooks/gesture/utils.ts +87 -0
  331. package/src/hooks/useAnimatedVisibility.spec.ts +257 -0
  332. package/src/hooks/useAnimatedVisibility.ts +146 -0
  333. package/src/hooks/useAnimationFrame.ts +200 -0
  334. package/src/hooks/useCSSMatrix.spec.ts +214 -0
  335. package/src/hooks/useCSSMatrix.ts +262 -0
  336. package/src/hooks/useClonedElementPreview.ts +28 -0
  337. package/src/hooks/useContainerScroll.ts +78 -0
  338. package/src/hooks/useContentCache.spec.tsx +232 -0
  339. package/src/hooks/useContentCache.tsx +127 -0
  340. package/src/hooks/useDocumentPointerEvents.ts +137 -0
  341. package/src/hooks/useDocumentScroll.ts +41 -0
  342. package/src/hooks/useEffectEvent.ts +40 -0
  343. package/src/hooks/useElementComponentWrapper.tsx +63 -0
  344. package/src/hooks/useIntersectionObserver.tsx +125 -0
  345. package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
  346. package/src/hooks/useResizeObserver.tsx +81 -0
  347. package/src/hooks/useScrollContainer.ts +79 -0
  348. package/src/hooks/useSnapAnimation.ts +128 -0
  349. package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
  350. package/src/hooks/useSwipeContentTransform.ts +235 -0
  351. package/src/hooks/useTransitionState.ts +95 -0
  352. package/src/index.tsx +88 -0
  353. package/src/modules/grid/GridLayoutContext.tsx +57 -0
  354. package/src/modules/grid/LayerInstanceContext.tsx +56 -0
  355. package/src/modules/grid/resizeHandles.ts +157 -0
  356. package/src/modules/grid/trackUtils.ts +146 -0
  357. package/src/modules/grid/useGridPlacements.ts +143 -0
  358. package/src/modules/grid/useGridTracks.ts +156 -0
  359. package/src/modules/grid/useLayerDragHandle.ts +16 -0
  360. package/src/modules/grid/useLayerInteractions.tsx +850 -0
  361. package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
  362. package/src/modules/panels/dom/DomRegistry.tsx +94 -0
  363. package/src/modules/panels/index.ts +45 -0
  364. package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
  365. package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
  366. package/src/modules/panels/interactions/dnd.ts +28 -0
  367. package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
  368. package/src/modules/panels/layout/adapter.ts +124 -0
  369. package/src/modules/panels/rendering/ContentRegistry.spec.tsx +304 -0
  370. package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
  371. package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
  372. package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
  373. package/src/modules/panels/rendering/RenderContext.tsx +31 -0
  374. package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
  375. package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
  376. package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
  377. package/src/modules/panels/state/StateContext.tsx +12 -0
  378. package/src/modules/panels/state/cleanup.ts +37 -0
  379. package/src/modules/panels/state/commands.ts +53 -0
  380. package/src/modules/panels/state/focus/Context.tsx +25 -0
  381. package/src/modules/panels/state/focus/logic.ts +57 -0
  382. package/src/modules/panels/state/groups/Context.tsx +25 -0
  383. package/src/modules/panels/state/groups/logic.ts +105 -0
  384. package/src/modules/panels/state/splitLimits.spec.ts +46 -0
  385. package/src/modules/panels/state/splitLimits.ts +90 -0
  386. package/src/modules/panels/state/state.spec.ts +49 -0
  387. package/src/modules/panels/state/tree/Context.tsx +24 -0
  388. package/src/modules/panels/state/tree/logic.spec.ts +34 -0
  389. package/src/modules/panels/state/tree/logic.ts +138 -0
  390. package/src/modules/panels/state/types.ts +142 -0
  391. package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
  392. package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
  393. package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
  394. package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
  395. package/src/modules/panels/system/PanelSystem.tsx +206 -0
  396. package/src/modules/pivot/PivotContent.spec.tsx +179 -0
  397. package/src/modules/pivot/PivotContent.tsx +77 -0
  398. package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
  399. package/src/modules/pivot/SwipePivotContent.position.spec.tsx +167 -0
  400. package/src/modules/pivot/SwipePivotContent.spec.tsx +464 -0
  401. package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
  402. package/src/modules/pivot/SwipePivotContent.tsx +197 -0
  403. package/src/modules/pivot/SwipePivotTabBar.spec.tsx +865 -0
  404. package/src/modules/pivot/SwipePivotTabBar.tsx +523 -0
  405. package/src/modules/pivot/index.ts +8 -0
  406. package/src/modules/pivot/scaleInputState.spec.ts +210 -0
  407. package/src/modules/pivot/scaleInputState.ts +66 -0
  408. package/src/modules/pivot/types.ts +139 -0
  409. package/src/modules/pivot/usePivot.spec.ts +621 -0
  410. package/src/modules/pivot/usePivot.spec.tsx +186 -0
  411. package/src/modules/pivot/usePivot.tsx +345 -0
  412. package/src/modules/pivot/usePivotSwipeInput.spec.ts +649 -0
  413. package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
  414. package/src/modules/resizer/useResizeDrag.ts +94 -0
  415. package/src/modules/stack/StackContent.spec.tsx +264 -0
  416. package/src/modules/stack/StackContent.tsx +111 -0
  417. package/src/modules/stack/SwipeStackContent.spec.tsx +1277 -0
  418. package/src/modules/stack/SwipeStackContent.tsx +356 -0
  419. package/src/modules/stack/SwipeStackOutlet.spec.tsx +252 -0
  420. package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
  421. package/src/modules/stack/computeStackContentState.spec.ts +281 -0
  422. package/src/modules/stack/computeStackContentState.ts +304 -0
  423. package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
  424. package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
  425. package/src/modules/stack/types.ts +226 -0
  426. package/src/modules/stack/useStackAnimationState.spec.ts +186 -0
  427. package/src/modules/stack/useStackAnimationState.ts +138 -0
  428. package/src/modules/stack/useStackNavigation.spec.ts +477 -0
  429. package/src/modules/stack/useStackNavigation.tsx +336 -0
  430. package/src/modules/stack/useStackSwipeInput.spec.ts +276 -0
  431. package/src/modules/stack/useStackSwipeInput.ts +139 -0
  432. package/src/modules/window/useDrawerState.ts +81 -0
  433. package/src/modules/window/useFloatingState.spec.ts +252 -0
  434. package/src/modules/window/useFloatingState.ts +141 -0
  435. package/src/panels/index.ts +119 -0
  436. package/src/pivot/index.ts +19 -0
  437. package/src/resizer/index.ts +68 -0
  438. package/src/stack/index.ts +91 -0
  439. package/src/sticky-header/StickyArea.tsx +221 -0
  440. package/src/sticky-header/index.ts +18 -0
  441. package/src/sticky-header/types.ts +68 -0
  442. package/src/types.ts +323 -0
  443. package/src/utils/CSSMatrix.ts +321 -0
  444. package/src/utils/css.ts +65 -0
  445. package/src/utils/dialogUtils.ts +43 -0
  446. package/src/utils/math.ts +18 -0
  447. package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
  448. package/src/utils/typedActions.ts +103 -0
  449. package/src/vite-env.d.ts +6 -0
  450. package/src/window/index.ts +67 -0
  451. package/dist/GridLayout-BQQ63eA1.cjs +0 -2
  452. package/dist/GridLayout-BQQ63eA1.cjs.map +0 -1
  453. package/dist/GridLayout-CJTKq7Mp.js +0 -1465
  454. package/dist/GridLayout-CJTKq7Mp.js.map +0 -1
  455. package/dist/sticky-header/StickyHeader.d.ts +0 -53
  456. package/dist/styles-CA2_zLZt.js +0 -52
  457. package/dist/styles-CA2_zLZt.js.map +0 -1
  458. package/dist/styles-PsqGOEJP.cjs +0 -2
  459. package/dist/styles-PsqGOEJP.cjs.map +0 -1
  460. package/dist/usePivot-7ctin_P_.cjs +0 -2
  461. package/dist/usePivot-7ctin_P_.cjs.map +0 -1
  462. package/dist/usePivot-CgQxB8rc.js +0 -124
  463. package/dist/usePivot-CgQxB8rc.js.map +0 -1
@@ -0,0 +1,57 @@
1
+ /**
2
+ * @file Focus and navigation helpers for panel-system.
3
+ */
4
+ import type { GroupId, PanelSystemState } from "../types";
5
+ import { collectGroupsInOrder } from "../tree/logic";
6
+
7
+ export const setFocusedGroup = (state: PanelSystemState, groupId: GroupId): PanelSystemState => {
8
+ if (!state.groups[groupId]) {
9
+ throw new Error(`setFocusedGroup: group ${groupId} not found.`);
10
+ }
11
+ return { ...state, focusedGroupId: groupId };
12
+ };
13
+
14
+ export const focusGroupIndex = (state: PanelSystemState, index1Based: number): PanelSystemState => {
15
+ const idx = index1Based - 1;
16
+ const id = state.groupOrder[idx];
17
+ if (!id) {
18
+ return state;
19
+ }
20
+ return setFocusedGroup(state, id);
21
+ };
22
+
23
+ export const nextGroup = (state: PanelSystemState): PanelSystemState => {
24
+ const order = state.groupOrder;
25
+ const current = state.focusedGroupId;
26
+ if (!current) {
27
+ const first = order[0];
28
+ if (!first) {
29
+ return state;
30
+ }
31
+ return setFocusedGroup(state, first);
32
+ }
33
+ const idx = order.indexOf(current);
34
+ const next = order[(idx + 1) % order.length];
35
+ return setFocusedGroup(state, next);
36
+ };
37
+
38
+ export const prevGroup = (state: PanelSystemState): PanelSystemState => {
39
+ const order = state.groupOrder;
40
+ const current = state.focusedGroupId;
41
+ if (!current) {
42
+ const last = order[order.length - 1];
43
+ if (!last) {
44
+ return state;
45
+ }
46
+ return setFocusedGroup(state, last);
47
+ }
48
+ const idx = order.indexOf(current);
49
+ const prev = order[(idx - 1 + order.length) % order.length];
50
+ return setFocusedGroup(state, prev);
51
+ };
52
+
53
+ export const refreshGroupOrder = (state: PanelSystemState): PanelSystemState => {
54
+ const groupOrder = collectGroupsInOrder(state.tree);
55
+ return { ...state, groupOrder };
56
+ };
57
+
@@ -0,0 +1,25 @@
1
+ /**
2
+ * @file GroupsContext provides group/tab management actions for PanelSystem.
3
+ * This context delegates to the root PanelSystemContext dispatch.
4
+ */
5
+ import * as React from "react";
6
+ import type { GroupId, PanelId } from "../types";
7
+
8
+ export type GroupsActions = {
9
+ setActiveTab: (groupId: GroupId, tabId: PanelId) => void;
10
+ tabDrop: (payload: { fromGroupId: GroupId; tabId: PanelId; targetGroupId: GroupId; targetIndex: number }) => void;
11
+ };
12
+
13
+ const GroupsContext = React.createContext<GroupsActions | null>(null);
14
+
15
+ export const useGroups = (): GroupsActions => {
16
+ const ctx = React.useContext(GroupsContext);
17
+ if (!ctx) {
18
+ throw new Error("useGroups must be used within GroupsProvider");
19
+ }
20
+ return ctx;
21
+ };
22
+
23
+ export const GroupsProvider: React.FC<React.PropsWithChildren<{ value: GroupsActions }>> = ({ value, children }) => {
24
+ return <GroupsContext.Provider value={value}>{children}</GroupsContext.Provider>;
25
+ };
@@ -0,0 +1,105 @@
1
+ /**
2
+ * @file Group and tab registry operations (pure, no tree logic).
3
+ */
4
+ import type { GroupId, GroupModel, PanelId, PanelSystemState, TabDefinition } from "../types";
5
+
6
+ export const createEmptyGroup = (id: GroupId): GroupModel => {
7
+ return { id, tabIds: [], tabs: [], activeTabId: null };
8
+ };
9
+
10
+ export const addTabToGroup = (state: PanelSystemState, groupId: GroupId, tab: TabDefinition, makeActive: boolean): PanelSystemState => {
11
+ const groups = { ...state.groups };
12
+ const group = groups[groupId];
13
+ if (!group) {
14
+ throw new Error(`Group ${groupId} does not exist.`);
15
+ }
16
+ const panels = { ...state.panels, [tab.id]: tab };
17
+ const tabIds = [...group.tabIds, tab.id];
18
+ const activeTabId = makeActive ? tab.id : group.activeTabId ?? tab.id;
19
+ const tabs = tabIds.map((id) => panels[id]);
20
+ const nextGroup: GroupModel = { ...group, tabIds, tabs, activeTabId };
21
+ groups[groupId] = nextGroup;
22
+ return { ...state, panels, groups };
23
+ };
24
+
25
+ export const removeTabFromGroup = (state: PanelSystemState, groupId: GroupId, tabId: PanelId): PanelSystemState => {
26
+ const groups = { ...state.groups };
27
+ const group = groups[groupId];
28
+ if (!group) {
29
+ throw new Error(`Group ${groupId} does not exist.`);
30
+ }
31
+ const tabIds = group.tabIds.filter((id) => id !== tabId);
32
+ const tabs = tabIds.map((id) => state.panels[id]);
33
+ const activeTabId = group.activeTabId === tabId ? (tabIds[0] ?? null) : group.activeTabId;
34
+ groups[groupId] = { ...group, tabIds, tabs, activeTabId };
35
+ return { ...state, groups };
36
+ };
37
+
38
+ export const moveTab = (state: PanelSystemState, fromGroupId: GroupId, toGroupId: GroupId, tabId: PanelId, makeActive: boolean): PanelSystemState => {
39
+ const from = state.groups[fromGroupId];
40
+ const to = state.groups[toGroupId];
41
+ if (!from || !to) {
42
+ throw new Error("moveTab: source or target group is missing.");
43
+ }
44
+ const groups = { ...state.groups };
45
+ const fromIds = from.tabIds.filter((id) => id !== tabId);
46
+ const toIds = [...to.tabIds.filter((id) => id !== tabId), tabId];
47
+ const fromActive = from.activeTabId === tabId ? (fromIds[0] ?? null) : from.activeTabId;
48
+ groups[fromGroupId] = { ...from, tabIds: fromIds, tabs: fromIds.map((id) => state.panels[id]), activeTabId: fromActive };
49
+ groups[toGroupId] = { ...to, tabIds: toIds, tabs: toIds.map((id) => state.panels[id]), activeTabId: makeActive ? tabId : to.activeTabId ?? tabId };
50
+ return { ...state, groups };
51
+ };
52
+
53
+ export const setActiveTab = (state: PanelSystemState, groupId: GroupId, tabId: PanelId): PanelSystemState => {
54
+ const group = state.groups[groupId];
55
+ if (!group) {
56
+ throw new Error(`setActiveTab: group ${groupId} not found.`);
57
+ }
58
+ if (!group.tabIds.some((id) => id === tabId)) {
59
+ throw new Error(`setActiveTab: tab ${tabId} not found in group ${groupId}.`);
60
+ }
61
+ const groups = { ...state.groups, [groupId]: { ...group, activeTabId: tabId } };
62
+ return { ...state, groups, focusedGroupId: groupId };
63
+ };
64
+
65
+ export const reorderTabWithinGroup = (state: PanelSystemState, groupId: GroupId, tabId: PanelId, toIndex: number): PanelSystemState => {
66
+ const group = state.groups[groupId];
67
+ if (!group) {
68
+ throw new Error(`reorderTabWithinGroup: group ${groupId} not found.`);
69
+ }
70
+ const currentIndex = group.tabIds.findIndex((id) => id === tabId);
71
+ if (currentIndex === -1) {
72
+ throw new Error(`reorderTabWithinGroup: tab ${tabId} not in group ${groupId}.`);
73
+ }
74
+ const boundedIndex = Math.max(0, Math.min(toIndex, group.tabIds.length - 1));
75
+ if (currentIndex === boundedIndex) {
76
+ return state;
77
+ }
78
+ const ids = group.tabIds.slice();
79
+ const [id] = ids.splice(currentIndex, 1);
80
+ ids.splice(boundedIndex, 0, id);
81
+ const tabs = ids.map((x) => state.panels[x]);
82
+ const groups = { ...state.groups, [groupId]: { ...group, tabIds: ids, tabs } };
83
+ return { ...state, groups };
84
+ };
85
+
86
+ export const addTabToGroupAtIndex = (
87
+ state: PanelSystemState,
88
+ groupId: GroupId,
89
+ tab: TabDefinition,
90
+ index: number,
91
+ makeActive: boolean,
92
+ ): PanelSystemState => {
93
+ const group = state.groups[groupId];
94
+ if (!group) {
95
+ throw new Error(`addTabToGroupAtIndex: group ${groupId} not found.`);
96
+ }
97
+ const panels = { ...state.panels, [tab.id]: tab };
98
+ const ids = group.tabIds.slice();
99
+ const boundedIndex = Math.max(0, Math.min(index, ids.length));
100
+ ids.splice(boundedIndex, 0, tab.id);
101
+ const tabs = ids.map((id) => panels[id]);
102
+ const activeTabId = makeActive ? tab.id : group.activeTabId ?? tab.id;
103
+ const groups = { ...state.groups, [groupId]: { ...group, tabIds: ids, tabs, activeTabId } };
104
+ return { ...state, panels, groups };
105
+ };
@@ -0,0 +1,46 @@
1
+ /**
2
+ * @file Tests for split limit helpers.
3
+ */
4
+ import type { PanelTree } from "./types";
5
+ import { measureSplitExtents, normalizeSplitLimits, canSplitDirection } from "./splitLimits";
6
+
7
+ const group = (id: string): PanelTree => ({ type: "group", groupId: id });
8
+
9
+ const horizontal = (a: PanelTree, b: PanelTree): PanelTree => ({ type: "split", direction: "horizontal", ratio: 0.5, a, b });
10
+ const vertical = (a: PanelTree, b: PanelTree): PanelTree => ({ type: "split", direction: "vertical", ratio: 0.5, a, b });
11
+
12
+ describe("split limit helpers", () => {
13
+ it("measures split extents per direction", () => {
14
+ const base = group("g1");
15
+ expect(measureSplitExtents(base)).toEqual({ horizontal: 1, vertical: 1 });
16
+
17
+ const withVertical = vertical(group("g1"), group("g2"));
18
+ expect(measureSplitExtents(withVertical)).toEqual({ horizontal: 1, vertical: 2 });
19
+
20
+ const nested = horizontal(withVertical, group("g3"));
21
+ expect(measureSplitExtents(nested)).toEqual({ horizontal: 2, vertical: 2 });
22
+
23
+ const deep = horizontal(vertical(group("g1"), group("g2")), vertical(group("g3"), group("g4")));
24
+ expect(measureSplitExtents(deep)).toEqual({ horizontal: 2, vertical: 2 });
25
+ });
26
+
27
+ it("prevents splits when limits would be exceeded", () => {
28
+ const limits = normalizeSplitLimits({ cols: 1, rows: 2 });
29
+ const base = group("g1");
30
+ expect(canSplitDirection(base, "g1", "vertical", limits)).toBe(false);
31
+ expect(canSplitDirection(base, "g1", "horizontal", limits)).toBe(true);
32
+
33
+ const withHorizontal = horizontal(group("g1"), group("g2"));
34
+ const verticalLimits = normalizeSplitLimits({ cols: 2 });
35
+ expect(canSplitDirection(withHorizontal, "g1", "horizontal", normalizeSplitLimits({ rows: 2 }))).toBe(false);
36
+ expect(canSplitDirection(withHorizontal, "g1", "vertical", verticalLimits)).toBe(true);
37
+ });
38
+
39
+ it("supports shorthand and legacy limit shapes", () => {
40
+ expect(normalizeSplitLimits(2)).toEqual({ rows: 2, cols: 2 });
41
+ expect(normalizeSplitLimits({ rows: 1 })).toEqual({ rows: 1, cols: Number.POSITIVE_INFINITY });
42
+ expect(normalizeSplitLimits({ cols: 3 })).toEqual({ rows: Number.POSITIVE_INFINITY, cols: 3 });
43
+ expect(normalizeSplitLimits({ maxHorizontal: 4 })).toEqual({ rows: 4, cols: Number.POSITIVE_INFINITY });
44
+ expect(normalizeSplitLimits({ maxVertical: 5 })).toEqual({ rows: Number.POSITIVE_INFINITY, cols: 5 });
45
+ });
46
+ });
@@ -0,0 +1,90 @@
1
+ /**
2
+ * @file Split-limit helpers for the panel system.
3
+ */
4
+ import type { GroupId, PanelSplitLimits, PanelTree, SplitDirection } from "./types";
5
+ import { splitLeaf, isGroup } from "./tree/logic";
6
+ import { clampNumber, toFiniteNumberOr } from "../../../utils/math";
7
+
8
+ export type NormalizedSplitLimits = {
9
+ rows: number;
10
+ cols: number;
11
+ };
12
+
13
+ const clampLimit = (value: number | undefined): number => {
14
+ const finite = toFiniteNumberOr(value, Number.POSITIVE_INFINITY);
15
+ return clampNumber(finite, 1);
16
+ };
17
+
18
+ const isObjectLimits = (limits: PanelSplitLimits): limits is Record<string, unknown> =>
19
+ typeof limits === "object" && limits !== null;
20
+
21
+ const isRowsColsLimits = (limits: PanelSplitLimits): limits is { rows?: number; cols?: number } => {
22
+ if (!isObjectLimits(limits)) {
23
+ return false;
24
+ }
25
+ if ("rows" in limits) {
26
+ return true;
27
+ }
28
+ return "cols" in limits;
29
+ };
30
+
31
+ export const normalizeSplitLimits = (limits?: PanelSplitLimits): NormalizedSplitLimits => {
32
+ if (!limits) {
33
+ return {
34
+ rows: Number.POSITIVE_INFINITY,
35
+ cols: Number.POSITIVE_INFINITY,
36
+ };
37
+ }
38
+ if (typeof limits === "number") {
39
+ const normalized = clampLimit(limits);
40
+ return { rows: normalized, cols: normalized };
41
+ }
42
+ if (isRowsColsLimits(limits)) {
43
+ return {
44
+ rows: clampLimit(limits.rows),
45
+ cols: clampLimit(limits.cols),
46
+ };
47
+ }
48
+ const legacy = limits as { maxHorizontal?: number; maxVertical?: number };
49
+ return {
50
+ rows: clampLimit(legacy.maxHorizontal),
51
+ cols: clampLimit(legacy.maxVertical),
52
+ };
53
+ };
54
+
55
+ export type SplitExtents = {
56
+ horizontal: number;
57
+ vertical: number;
58
+ };
59
+
60
+ export const measureSplitExtents = (tree: PanelTree): SplitExtents => {
61
+ if (isGroup(tree)) {
62
+ return { horizontal: 1, vertical: 1 };
63
+ }
64
+ const a = measureSplitExtents(tree.a);
65
+ const b = measureSplitExtents(tree.b);
66
+ if (tree.direction === "horizontal") {
67
+ return { horizontal: a.horizontal + b.horizontal, vertical: Math.max(a.vertical, b.vertical) };
68
+ }
69
+ return { horizontal: Math.max(a.horizontal, b.horizontal), vertical: a.vertical + b.vertical };
70
+ };
71
+
72
+ const previewSplitTree = (tree: PanelTree, groupId: GroupId, direction: SplitDirection): PanelTree => {
73
+ const { tree: preview } = splitLeaf(tree, groupId, direction, () => "__preview__");
74
+ return preview;
75
+ };
76
+
77
+ export const canSplitDirection = (tree: PanelTree, groupId: GroupId, direction: SplitDirection, limits: NormalizedSplitLimits): boolean => {
78
+ if (!Number.isFinite(limits.rows) && !Number.isFinite(limits.cols)) {
79
+ return true;
80
+ }
81
+ const preview = previewSplitTree(tree, groupId, direction);
82
+ const extents = measureSplitExtents(preview);
83
+ if (extents.horizontal > limits.rows) {
84
+ return false;
85
+ }
86
+ if (extents.vertical > limits.cols) {
87
+ return false;
88
+ }
89
+ return true;
90
+ };
@@ -0,0 +1,49 @@
1
+ /**
2
+ * @file Unit tests for panel-system state operations (flattened path).
3
+ */
4
+ import { buildInitialState, splitGroup, moveTab, focusGroupIndex, nextGroup, prevGroup } from "../index";
5
+ import type { TabDefinition } from "./types";
6
+
7
+ const makeTab = (id: string, title?: string): TabDefinition => ({ id, title: title ?? id, render: () => id });
8
+
9
+ describe("panel-system state", () => {
10
+ it("initializes with one group and active tab", () => {
11
+ const tabs = [makeTab("a"), makeTab("b")];
12
+ const s = buildInitialState(tabs);
13
+ expect(s.groupOrder.length).toBe(1);
14
+ const gid = s.groupOrder[0];
15
+ expect(s.groups[gid].activeTabId).toBe("a");
16
+ });
17
+
18
+ it("splits the focused group vertically", () => {
19
+ const tabs = [makeTab("a")];
20
+ const s = buildInitialState(tabs);
21
+ const gid = s.groupOrder[0];
22
+ const s2 = splitGroup(s, gid, "vertical", () => "g_2");
23
+ expect(s2.groupOrder.length).toBe(2);
24
+ expect(s2.focusedGroupId).not.toBeNull();
25
+ });
26
+
27
+ it("moves a tab between groups and activates it", () => {
28
+ const tabs = [makeTab("a"), makeTab("b")];
29
+ const s1 = buildInitialState(tabs);
30
+ const gid = s1.groupOrder[0];
31
+ const s2 = splitGroup(s1, gid, "vertical", () => "g_2");
32
+ const dest = s2.groupOrder[1];
33
+ const s3 = moveTab(s2, gid, dest, "b", true);
34
+ expect(s3.groups[dest].activeTabId).toBe("b");
35
+ expect(s3.groups[gid].tabs.some((t) => t.id === "b")).toBe(false);
36
+ });
37
+
38
+ it("focus navigation works with index and cycling", () => {
39
+ const s1 = buildInitialState([makeTab("a")]);
40
+ const gid = s1.groupOrder[0];
41
+ const s2 = splitGroup(s1, gid, "vertical", () => "g_2");
42
+ const s3 = focusGroupIndex(s2, 2);
43
+ expect(s3.focusedGroupId).toBe(s3.groupOrder[1]);
44
+ const s4 = nextGroup(s3);
45
+ expect(s4.focusedGroupId).toBe(s4.groupOrder[0]);
46
+ const s5 = prevGroup(s4);
47
+ expect(s5.focusedGroupId).toBe(s5.groupOrder[1]);
48
+ });
49
+ });
@@ -0,0 +1,24 @@
1
+ /**
2
+ * @file TreeContext provides tree/split management actions for PanelSystem.
3
+ * This context delegates to the root PanelSystemContext dispatch.
4
+ */
5
+ import * as React from "react";
6
+ import type { NodePath } from "./logic";
7
+
8
+ export type TreeActions = {
9
+ adjustSplitRatio: (payload: { path: NodePath; deltaRatio: number }) => void;
10
+ };
11
+
12
+ const TreeContext = React.createContext<TreeActions | null>(null);
13
+
14
+ export const useTree = (): TreeActions => {
15
+ const ctx = React.useContext(TreeContext);
16
+ if (!ctx) {
17
+ throw new Error("useTree must be used within TreeProvider");
18
+ }
19
+ return ctx;
20
+ };
21
+
22
+ export const TreeProvider: React.FC<React.PropsWithChildren<{ value: TreeActions }>> = ({ value, children }) => {
23
+ return <TreeContext.Provider value={value}>{children}</TreeContext.Provider>;
24
+ };
@@ -0,0 +1,34 @@
1
+ /**
2
+ * @file Unit tests for panel-system tree operations
3
+ */
4
+ import type { PanelTree } from "../types";
5
+ import { setSplitRatio } from "./logic";
6
+
7
+ describe("panel-tree setSplitRatio", () => {
8
+ it("updates ratio and clamps to bounds", () => {
9
+ const tree: PanelTree = {
10
+ type: "split",
11
+ direction: "vertical",
12
+ ratio: 0.5,
13
+ a: { type: "group", groupId: "g_1" },
14
+ b: { type: "group", groupId: "g_2" },
15
+ };
16
+ const next = setSplitRatio(tree, [], 0.2);
17
+ if (next.type !== "split") {
18
+ throw new Error("expected split node");
19
+ }
20
+ expect(next.ratio).toBeCloseTo(0.2);
21
+
22
+ const over = setSplitRatio(tree, [], -10);
23
+ if (over.type !== "split") {
24
+ throw new Error("expected split node");
25
+ }
26
+ expect(over.ratio).toBeCloseTo(0.05);
27
+
28
+ const big = setSplitRatio(tree, [], 10);
29
+ if (big.type !== "split") {
30
+ throw new Error("expected split node");
31
+ }
32
+ expect(big.ratio).toBeCloseTo(0.95);
33
+ });
34
+ });
@@ -0,0 +1,138 @@
1
+ /**
2
+ * @file Tree operations for panel-system (no registry or focus concerns).
3
+ */
4
+ import type { GroupId, PanelTree, SplitDirection } from "../types";
5
+ import { clampNumber } from "../../../../utils/math";
6
+
7
+ export type PathSegment = "a" | "b";
8
+ export type NodePath = PathSegment[];
9
+
10
+ export const isGroup = (node: PanelTree): node is Extract<PanelTree, { type: "group" }> => {
11
+ return (node as { type: string }).type === "group";
12
+ };
13
+
14
+ export const collectGroupsInOrder = (node: PanelTree, acc: GroupId[] = []): GroupId[] => {
15
+ if (isGroup(node)) {
16
+ return [...acc, node.groupId];
17
+ }
18
+ const a = collectGroupsInOrder(node.a, acc);
19
+ return collectGroupsInOrder(node.b, a);
20
+ };
21
+
22
+ export const getAtPath = (root: PanelTree, path: NodePath): PanelTree => {
23
+ return path.reduce<PanelTree>((node, key) => (isGroup(node) ? node : node[key]), root);
24
+ };
25
+
26
+ export const setAtPath = (root: PanelTree, path: NodePath, value: PanelTree): PanelTree => {
27
+ if (path.length === 0) {
28
+ return value;
29
+ }
30
+ const [head, ...rest] = path;
31
+ if (isGroup(root)) {
32
+ // Invalid set; return original root
33
+ return root;
34
+ }
35
+ const next = head === "a" ? { ...root, a: setAtPath(root.a, rest, value) } : { ...root, b: setAtPath(root.b, rest, value) };
36
+ return next;
37
+ };
38
+
39
+ /**
40
+ * Find split parent of a given group leaf. This function explicitly returns the path to the split node
41
+ * and which side ("a" | "b") the target group occupies under that split. If there is no split parent
42
+ * (i.e., the root is the leaf group), splitPath and side are null.
43
+ */
44
+ export const findLeafParent = (
45
+ node: PanelTree,
46
+ groupId: GroupId,
47
+ path: NodePath = [],
48
+ ): { splitPath: NodePath | null; side: PathSegment | null } | null => {
49
+ if (isGroup(node)) {
50
+ if (node.groupId === groupId) {
51
+ return { splitPath: null, side: null };
52
+ }
53
+ return null;
54
+ }
55
+ // Search child A; if it is the group leaf, current node is parent and side is 'a'
56
+ if (isGroup(node.a) && node.a.groupId === groupId) {
57
+ return { splitPath: path, side: "a" };
58
+ }
59
+ // Search child B
60
+ if (isGroup(node.b) && node.b.groupId === groupId) {
61
+ return { splitPath: path, side: "b" };
62
+ }
63
+ // Recurse deeper
64
+ const inA = findLeafParent(node.a, groupId, [...path, "a"]);
65
+ if (inA) {
66
+ return inA;
67
+ }
68
+ return findLeafParent(node.b, groupId, [...path, "b"]);
69
+ };
70
+
71
+ /**
72
+ * Split the leaf group referenced by groupId into a split node, keeping the original group on side 'a'
73
+ * and inserting a new group on side 'b'. Returns the new tree and the new group's id.
74
+ * This function does not touch registries; only pure tree transformation.
75
+ */
76
+ export const splitLeaf = (
77
+ root: PanelTree,
78
+ groupId: GroupId,
79
+ direction: SplitDirection,
80
+ createGroupId: () => GroupId,
81
+ ): { tree: PanelTree; newGroupId: GroupId } => {
82
+ const newGroupId = createGroupId();
83
+ const entry = findLeafParent(root, groupId);
84
+ const replacement: PanelTree = {
85
+ type: "split",
86
+ direction,
87
+ ratio: 0.5,
88
+ a: { type: "group", groupId },
89
+ b: { type: "group", groupId: newGroupId },
90
+ } as const;
91
+
92
+ if (!entry || entry.splitPath === null) {
93
+ // Root is the leaf
94
+ return { tree: replacement, newGroupId };
95
+ }
96
+ const parentPath = entry.splitPath;
97
+ const parent = getAtPath(root, parentPath);
98
+ if (isGroup(parent)) {
99
+ // Should not happen; guard for safety
100
+ return { tree: replacement, newGroupId };
101
+ }
102
+ const newParent = entry.side === "a" ? { ...parent, a: replacement } : { ...parent, b: replacement };
103
+ const newTree = setAtPath(root, parentPath, newParent);
104
+ return { tree: newTree, newGroupId };
105
+ };
106
+
107
+ /**
108
+ * Remove the leaf group by collapsing it with its sibling. Returns the new tree and the survivor group id
109
+ * (if any). If there is no split parent (single group), returns the original tree.
110
+ */
111
+ export const closeLeaf = (root: PanelTree, groupId: GroupId): { tree: PanelTree; survivorGroupId: GroupId | null } => {
112
+ const entry = findLeafParent(root, groupId);
113
+ if (!entry || entry.splitPath === null) {
114
+ // Single-group root
115
+ return { tree: root, survivorGroupId: groupId };
116
+ }
117
+ const parentPath = entry.splitPath;
118
+ const parent = getAtPath(root, parentPath);
119
+ if (isGroup(parent)) {
120
+ return { tree: root, survivorGroupId: groupId };
121
+ }
122
+ const survivor = entry.side === "a" ? parent.b : parent.a;
123
+ const newTree = setAtPath(root, parentPath, survivor);
124
+ const survivorId = isGroup(survivor) ? survivor.groupId : collectGroupsInOrder(survivor)[0] ?? null;
125
+ return { tree: newTree, survivorGroupId: survivorId };
126
+ };
127
+
128
+ /**
129
+ * Set split ratio at a specific split node path. Clamps ratio into (0.05 .. 0.95) to avoid zero-size panes.
130
+ */
131
+ export const setSplitRatio = (root: PanelTree, splitPath: NodePath, ratio: number): PanelTree => {
132
+ const node = getAtPath(root, splitPath);
133
+ if (isGroup(node)) {
134
+ return root;
135
+ }
136
+ const next = { ...node, ratio: clampNumber(ratio, 0.05, 0.95) } as PanelTree;
137
+ return setAtPath(root, splitPath, next);
138
+ };