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,304 @@
1
+ /**
2
+ * @file ContentRegistry tests - state persistence across tab switch, panel move, and split
3
+ */
4
+ /* eslint-disable no-restricted-imports, no-restricted-properties, no-restricted-syntax -- integration test */
5
+ import { render, screen, fireEvent } from "@testing-library/react";
6
+ import * as React from "react";
7
+ import { ContentRegistryProvider, useContentRegistry } from "./ContentRegistry";
8
+ import type { TabDefinition, PanelId, GroupId } from "../state/types";
9
+
10
+ // Counter component that maintains state
11
+ const CounterContent: React.FC<{ id: string }> = ({ id }) => {
12
+ const [count, setCount] = React.useState(0);
13
+ return (
14
+ <div data-testid={`content-${id}`}>
15
+ <span data-testid={`count-${id}`}>{count}</span>
16
+ <button data-testid={`increment-${id}`} onClick={() => setCount((c) => c + 1)}>
17
+ +1
18
+ </button>
19
+ </div>
20
+ );
21
+ };
22
+
23
+ // Test harness that simulates GroupContainer behavior
24
+ const GroupContent: React.FC<{ groupId: GroupId }> = ({ groupId }) => {
25
+ const { registerContentContainer } = useContentRegistry();
26
+ const ref = React.useCallback(
27
+ (el: HTMLDivElement | null) => {
28
+ registerContentContainer(groupId, el);
29
+ },
30
+ [groupId, registerContentContainer],
31
+ );
32
+ return <div ref={ref} data-testid={`container-${groupId}`} style={{ width: 100, height: 100 }} />;
33
+ };
34
+
35
+ type TestState = {
36
+ panels: Record<PanelId, TabDefinition>;
37
+ placements: Record<PanelId, { groupId: GroupId; isActive: boolean }>;
38
+ groupIds: GroupId[];
39
+ };
40
+
41
+ const TestHarness: React.FC<{ state: TestState }> = ({ state }) => {
42
+ return (
43
+ <ContentRegistryProvider panels={state.panels} placements={state.placements}>
44
+ {state.groupIds.map((gid) => (
45
+ <GroupContent key={gid} groupId={gid} />
46
+ ))}
47
+ </ContentRegistryProvider>
48
+ );
49
+ };
50
+
51
+ describe("ContentRegistry", () => {
52
+ const createPanel = (id: string): TabDefinition => ({
53
+ id,
54
+ title: `Panel ${id}`,
55
+ render: (panelId) => <CounterContent key={panelId} id={panelId} />,
56
+ });
57
+
58
+ beforeEach(() => {
59
+ // Mock ResizeObserver (polyfill provided in vitest.setup.ts)
60
+
61
+ // Mock pointer capture methods
62
+ Element.prototype.setPointerCapture = vi.fn();
63
+ Element.prototype.releasePointerCapture = vi.fn();
64
+ // Mock getBoundingClientRect
65
+ Element.prototype.getBoundingClientRect = vi.fn().mockReturnValue({
66
+ top: 0,
67
+ left: 0,
68
+ width: 100,
69
+ height: 100,
70
+ right: 100,
71
+ bottom: 100,
72
+ x: 0,
73
+ y: 0,
74
+ toJSON: () => ({}),
75
+ });
76
+ });
77
+
78
+ afterEach(() => {
79
+ // Clean up any portal containers
80
+ document.querySelectorAll("[data-panel-content-root]").forEach((el) => el.remove());
81
+ });
82
+
83
+ it("should render content inside the registered container element", () => {
84
+ const panels = {
85
+ "panel-1": createPanel("panel-1"),
86
+ };
87
+
88
+ const state: TestState = {
89
+ panels,
90
+ placements: {
91
+ "panel-1": { groupId: "group-1", isActive: true },
92
+ },
93
+ groupIds: ["group-1"],
94
+ };
95
+
96
+ render(<TestHarness state={state} />);
97
+
98
+ // The content should be rendered
99
+ const content = screen.getByTestId("content-panel-1");
100
+ expect(content).toBeInTheDocument();
101
+
102
+ // The container element should exist
103
+ const container = screen.getByTestId("container-group-1");
104
+ expect(container).toBeInTheDocument();
105
+
106
+ // Content should be inside the container (not in a separate portal overlay)
107
+ expect(container.contains(content)).toBe(true);
108
+ });
109
+
110
+ it("should preserve state when switching tabs", async () => {
111
+ const panels = {
112
+ "panel-1": createPanel("panel-1"),
113
+ "panel-2": createPanel("panel-2"),
114
+ };
115
+
116
+ const initialState: TestState = {
117
+ panels,
118
+ placements: {
119
+ "panel-1": { groupId: "group-1", isActive: true },
120
+ "panel-2": { groupId: "group-1", isActive: false },
121
+ },
122
+ groupIds: ["group-1"],
123
+ };
124
+
125
+ const { rerender } = render(<TestHarness state={initialState} />);
126
+
127
+ // Increment panel-1's counter
128
+ const incrementBtn = screen.getByTestId("increment-panel-1");
129
+ fireEvent.click(incrementBtn);
130
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("1");
131
+
132
+ // Switch to panel-2 (make it active, panel-1 inactive)
133
+ const switchedState: TestState = {
134
+ panels,
135
+ placements: {
136
+ "panel-1": { groupId: "group-1", isActive: false },
137
+ "panel-2": { groupId: "group-1", isActive: true },
138
+ },
139
+ groupIds: ["group-1"],
140
+ };
141
+ rerender(<TestHarness state={switchedState} />);
142
+
143
+ // Switch back to panel-1
144
+ rerender(<TestHarness state={initialState} />);
145
+
146
+ // panel-1's counter should still be 1
147
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("1");
148
+ });
149
+
150
+ it("should preserve state when moving panel to another group", async () => {
151
+ const panels = {
152
+ "panel-1": createPanel("panel-1"),
153
+ };
154
+
155
+ const initialState: TestState = {
156
+ panels,
157
+ placements: {
158
+ "panel-1": { groupId: "group-1", isActive: true },
159
+ },
160
+ groupIds: ["group-1"],
161
+ };
162
+
163
+ const { rerender } = render(<TestHarness state={initialState} />);
164
+
165
+ // Increment counter
166
+ fireEvent.click(screen.getByTestId("increment-panel-1"));
167
+ fireEvent.click(screen.getByTestId("increment-panel-1"));
168
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("2");
169
+
170
+ // Move panel to group-2 (new group is created at the same time)
171
+ const movedState: TestState = {
172
+ panels,
173
+ placements: {
174
+ "panel-1": { groupId: "group-2", isActive: true },
175
+ },
176
+ groupIds: ["group-1", "group-2"],
177
+ };
178
+ rerender(<TestHarness state={movedState} />);
179
+
180
+ // Counter should still be 2
181
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("2");
182
+ });
183
+
184
+ it("should preserve state when splitting panel (adding new group)", async () => {
185
+ const panels = {
186
+ "panel-1": createPanel("panel-1"),
187
+ "panel-2": createPanel("panel-2"),
188
+ };
189
+
190
+ // Initial: both panels in group-1
191
+ const initialState: TestState = {
192
+ panels,
193
+ placements: {
194
+ "panel-1": { groupId: "group-1", isActive: true },
195
+ "panel-2": { groupId: "group-1", isActive: false },
196
+ },
197
+ groupIds: ["group-1"],
198
+ };
199
+
200
+ const { rerender } = render(<TestHarness state={initialState} />);
201
+
202
+ // Increment panel-1's counter to 3
203
+ fireEvent.click(screen.getByTestId("increment-panel-1"));
204
+ fireEvent.click(screen.getByTestId("increment-panel-1"));
205
+ fireEvent.click(screen.getByTestId("increment-panel-1"));
206
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("3");
207
+
208
+ // Split: panel-2 moves to new group-2
209
+ const splitState: TestState = {
210
+ panels,
211
+ placements: {
212
+ "panel-1": { groupId: "group-1", isActive: true },
213
+ "panel-2": { groupId: "group-2", isActive: true },
214
+ },
215
+ groupIds: ["group-1", "group-2"],
216
+ };
217
+ rerender(<TestHarness state={splitState} />);
218
+
219
+ // panel-1's counter should still be 3
220
+ expect(screen.getByTestId("count-panel-1").textContent).toBe("3");
221
+ });
222
+
223
+ it("should handle multiple panels in same group without wrapper conflicts", () => {
224
+ const panels = {
225
+ "panel-1": createPanel("panel-1"),
226
+ "panel-2": createPanel("panel-2"),
227
+ "panel-3": createPanel("panel-3"),
228
+ };
229
+
230
+ const state: TestState = {
231
+ panels,
232
+ placements: {
233
+ "panel-1": { groupId: "group-1", isActive: true },
234
+ "panel-2": { groupId: "group-1", isActive: false },
235
+ "panel-3": { groupId: "group-1", isActive: false },
236
+ },
237
+ groupIds: ["group-1"],
238
+ };
239
+
240
+ render(<TestHarness state={state} />);
241
+
242
+ const container = screen.getByTestId("container-group-1");
243
+
244
+ // All panel wrappers should be inside the container
245
+ const wrappers = container.querySelectorAll("[data-panel-wrapper]");
246
+ expect(wrappers.length).toBe(3);
247
+
248
+ // Each wrapper should have unique panel id
249
+ const wrapperIds = Array.from(wrappers).map((w) => w.getAttribute("data-panel-wrapper"));
250
+ expect(wrapperIds).toContain("panel-1");
251
+ expect(wrapperIds).toContain("panel-2");
252
+ expect(wrapperIds).toContain("panel-3");
253
+
254
+ // Active panel content should be visible
255
+ expect(screen.getByTestId("content-panel-1")).toBeVisible();
256
+ });
257
+
258
+ it("should not have wrapper nesting issues when panels move between groups", () => {
259
+ const panels = {
260
+ "panel-1": createPanel("panel-1"),
261
+ "panel-2": createPanel("panel-2"),
262
+ };
263
+
264
+ const initialState: TestState = {
265
+ panels,
266
+ placements: {
267
+ "panel-1": { groupId: "group-1", isActive: true },
268
+ "panel-2": { groupId: "group-1", isActive: false },
269
+ },
270
+ groupIds: ["group-1", "group-2"],
271
+ };
272
+
273
+ const { rerender } = render(<TestHarness state={initialState} />);
274
+
275
+ // Initially both panels in group-1
276
+ const container1 = screen.getByTestId("container-group-1");
277
+ expect(container1.querySelectorAll("[data-panel-wrapper]").length).toBe(2);
278
+
279
+ // Move panel-2 to group-2
280
+ const movedState: TestState = {
281
+ panels,
282
+ placements: {
283
+ "panel-1": { groupId: "group-1", isActive: true },
284
+ "panel-2": { groupId: "group-2", isActive: true },
285
+ },
286
+ groupIds: ["group-1", "group-2"],
287
+ };
288
+ rerender(<TestHarness state={movedState} />);
289
+
290
+ // Now each group should have exactly one wrapper
291
+ const container1After = screen.getByTestId("container-group-1");
292
+ const container2 = screen.getByTestId("container-group-2");
293
+
294
+ expect(container1After.querySelectorAll("[data-panel-wrapper]").length).toBe(1);
295
+ expect(container2.querySelectorAll("[data-panel-wrapper]").length).toBe(1);
296
+
297
+ // Wrappers should not be nested inside each other
298
+ const allWrappers = document.querySelectorAll("[data-panel-wrapper]");
299
+ allWrappers.forEach((wrapper) => {
300
+ const nestedWrappers = wrapper.querySelectorAll("[data-panel-wrapper]");
301
+ expect(nestedWrappers.length).toBe(0);
302
+ });
303
+ });
304
+ });
@@ -0,0 +1,205 @@
1
+ /**
2
+ * @file Registry for panel content instances.
3
+ * Manages component lifecycle to preserve state across tab switches and panel moves.
4
+ *
5
+ * ## Architecture
6
+ *
7
+ * This module solves the problem of preserving React component state when panels
8
+ * move between different container elements (groups).
9
+ *
10
+ * ### The Problem
11
+ * React portals remount their content when the container element changes.
12
+ * This is React's intentional behavior, but it causes state loss when panels move.
13
+ *
14
+ * ### The Solution
15
+ * 1. Create a stable wrapper element per panel (outside React's tree management)
16
+ * 2. Use createPortal to render React content INTO the wrapper
17
+ * 3. Move the wrapper between containers using appendChild
18
+ *
19
+ * This works because:
20
+ * - React only manages content INSIDE the wrapper (via portal)
21
+ * - React doesn't track the wrapper's position in DOM
22
+ * - Moving the wrapper doesn't trigger React reconciliation
23
+ *
24
+ * ### DOM API Usage
25
+ * - document.createElement: Creates wrapper element (once per panel)
26
+ * - appendChild: Moves wrapper to target container
27
+ * - removeChild: Cleans up wrapper on unmount
28
+ *
29
+ * These are the minimum DOM APIs required. React features handle everything else.
30
+ */
31
+ import * as React from "react";
32
+ import { createPortal } from "react-dom";
33
+ import type { PanelId, GroupId, TabDefinition } from "../state/types";
34
+ import { useIsomorphicLayoutEffect } from "../../../hooks/useIsomorphicLayoutEffect";
35
+ import { useContentCache } from "../../../hooks/useContentCache";
36
+
37
+ type PanelPlacement = {
38
+ groupId: GroupId;
39
+ isActive: boolean;
40
+ };
41
+
42
+ type ContentRegistryContextValue = {
43
+ registerContentContainer: (groupId: GroupId, element: HTMLElement | null) => void;
44
+ };
45
+
46
+ const ContentRegistryContext = React.createContext<ContentRegistryContextValue | null>(null);
47
+
48
+ export const useContentRegistry = (): ContentRegistryContextValue => {
49
+ const ctx = React.useContext(ContentRegistryContext);
50
+ if (!ctx) {
51
+ throw new Error("useContentRegistry must be used within ContentRegistryProvider");
52
+ }
53
+ return ctx;
54
+ };
55
+
56
+ /**
57
+ * Creates a wrapper element for panel content.
58
+ * This element lives outside React's tree management so it can be moved freely.
59
+ */
60
+ const createPanelWrapper = (panelId: PanelId): HTMLDivElement => {
61
+ const wrapper = document.createElement("div");
62
+ wrapper.setAttribute("data-panel-wrapper", panelId);
63
+ wrapper.style.display = "contents";
64
+ return wrapper;
65
+ };
66
+
67
+ /**
68
+ * Hook to manage wrapper element lifecycle and positioning.
69
+ *
70
+ * Uses React.useState for lazy initialization (wrapper created once).
71
+ * Uses useIsomorphicLayoutEffect for DOM manipulation (SSR-safe).
72
+ */
73
+ const usePanelWrapper = (
74
+ panelId: PanelId,
75
+ containerElement: HTMLElement | null,
76
+ isActive: boolean,
77
+ ): HTMLDivElement => {
78
+ // Create wrapper once using React's lazy state initialization
79
+ const [wrapper] = React.useState(() => createPanelWrapper(panelId));
80
+
81
+ // Manage wrapper position and visibility
82
+ useIsomorphicLayoutEffect(() => {
83
+ wrapper.style.display = isActive ? "contents" : "none";
84
+
85
+ if (containerElement && wrapper.parentElement !== containerElement) {
86
+ containerElement.appendChild(wrapper);
87
+ }
88
+
89
+ return () => {
90
+ wrapper.parentElement?.removeChild(wrapper);
91
+ };
92
+ }, [wrapper, containerElement, isActive]);
93
+
94
+ return wrapper;
95
+ };
96
+
97
+ /**
98
+ * Host component for panel content.
99
+ * Uses createPortal to render React content into a stable wrapper element.
100
+ */
101
+ type PanelContentHostProps = {
102
+ panelId: PanelId;
103
+ content: React.ReactNode;
104
+ placement: PanelPlacement | null;
105
+ containerElement: HTMLElement | null;
106
+ };
107
+
108
+ const PanelContentHost: React.FC<PanelContentHostProps> = React.memo(
109
+ ({ panelId, content, placement, containerElement }) => {
110
+ const isActive = placement?.isActive ?? false;
111
+ const wrapper = usePanelWrapper(panelId, containerElement, isActive);
112
+
113
+ // Portal renders React content INTO the wrapper
114
+ // React manages content lifecycle, not wrapper position
115
+ return createPortal(
116
+ <React.Activity mode={isActive ? "visible" : "hidden"}>
117
+ {content}
118
+ </React.Activity>,
119
+ wrapper,
120
+ );
121
+ },
122
+ );
123
+ PanelContentHost.displayName = "PanelContentHost";
124
+
125
+ type ContentRegistryProviderProps = React.PropsWithChildren<{
126
+ panels: Record<PanelId, TabDefinition>;
127
+ placements: Record<PanelId, PanelPlacement>;
128
+ }>;
129
+
130
+ /**
131
+ * Provider that manages panel content lifecycle.
132
+ * Caches rendered content per panel to preserve React element identity.
133
+ */
134
+ export const ContentRegistryProvider: React.FC<ContentRegistryProviderProps> = ({
135
+ children,
136
+ panels,
137
+ placements,
138
+ }) => {
139
+ const [containers, setContainers] = React.useState<Map<GroupId, HTMLElement>>(new Map());
140
+
141
+ const registerContentContainer = React.useCallback((groupId: GroupId, element: HTMLElement | null): void => {
142
+ setContainers((prev) => {
143
+ const next = new Map(prev);
144
+ if (element) {
145
+ next.set(groupId, element);
146
+ } else {
147
+ next.delete(groupId);
148
+ }
149
+ return next;
150
+ });
151
+ }, []);
152
+
153
+ const value = React.useMemo<ContentRegistryContextValue>(
154
+ () => ({ registerContentContainer }),
155
+ [registerContentContainer],
156
+ );
157
+
158
+ // Store panels in ref for stable resolveContent function
159
+ const panelsRef = React.useRef(panels);
160
+ panelsRef.current = panels;
161
+
162
+ // Content resolver for useContentCache
163
+ const resolveContent = React.useCallback((panelId: PanelId): React.ReactNode | null => {
164
+ const tab = panelsRef.current[panelId];
165
+ if (!tab) {
166
+ return null;
167
+ }
168
+ return tab.render(tab.id);
169
+ }, []);
170
+
171
+ // Valid IDs for cache cleanup
172
+ const validIds = React.useMemo(() => Object.keys(panels), [panels]);
173
+
174
+ // Use shared content cache hook
175
+ const { getCachedContent } = useContentCache({
176
+ resolveContent,
177
+ validIds,
178
+ });
179
+
180
+ const panelIds = Object.keys(panels);
181
+
182
+ return (
183
+ <ContentRegistryContext.Provider value={value}>
184
+ {children}
185
+ {panelIds.map((panelId) => {
186
+ const tab = panels[panelId];
187
+ if (!tab) {
188
+ return null;
189
+ }
190
+ const placement = placements[panelId] ?? null;
191
+ const containerElement = placement ? containers.get(placement.groupId) ?? null : null;
192
+ const content = getCachedContent(panelId);
193
+ return (
194
+ <PanelContentHost
195
+ key={panelId}
196
+ panelId={panelId}
197
+ content={content}
198
+ placement={placement}
199
+ containerElement={containerElement}
200
+ />
201
+ );
202
+ })}
203
+ </ContentRegistryContext.Provider>
204
+ );
205
+ };
@@ -0,0 +1,65 @@
1
+ /**
2
+ * @file Connected group container bridging panel contexts to the presentational view.
3
+ */
4
+ import * as React from "react";
5
+ import type { GroupId, PanelGroupRenderProps, TabBarRenderProps } from "../../panels/state/types";
6
+ import { usePanelRenderContext } from "../rendering/RenderContext";
7
+ import { useDomRegistry } from "../dom/DomRegistry";
8
+ import { PanelGroupView } from "../../../components/panels/PanelGroupView";
9
+ import { TabBar } from "../../../components/tabs/TabBar";
10
+
11
+ export type ConnectedGroupContainerProps = {
12
+ id: GroupId;
13
+ TabBarComponent?: React.ComponentType<TabBarRenderProps>;
14
+ PanelGroupComponent?: React.ComponentType<PanelGroupRenderProps>;
15
+ };
16
+
17
+ export const GroupContainer: React.FC<ConnectedGroupContainerProps> = ({ id, TabBarComponent, PanelGroupComponent }) => {
18
+ const { getGroup, getGroupContent, onClickTab, onAddTab, onCloseTab, onStartTabDrag, doubleClickToAdd, registerContentContainer } = usePanelRenderContext();
19
+ const { setGroupEl, setTabbarEl, setContentEl } = useDomRegistry();
20
+ const groupRef = React.useCallback(
21
+ (el: HTMLDivElement | null) => {
22
+ setGroupEl(id, el);
23
+ },
24
+ [id, setGroupEl],
25
+ );
26
+ const contentRef = React.useCallback(
27
+ (el: HTMLDivElement | null) => {
28
+ setContentEl(id, el);
29
+ registerContentContainer(id, el);
30
+ },
31
+ [id, setContentEl, registerContentContainer],
32
+ );
33
+ const tabbarRef = React.useCallback(
34
+ (el: HTMLDivElement | null) => {
35
+ setTabbarEl(id, el);
36
+ },
37
+ [id, setTabbarEl],
38
+ );
39
+ const group = getGroup(id);
40
+ if (!group) {
41
+ return null;
42
+ }
43
+ const content = getGroupContent(id);
44
+ const TabBarImpl = TabBarComponent ?? TabBar;
45
+ const PanelGroupImpl: React.ComponentType<PanelGroupRenderProps> = PanelGroupComponent ?? ((props) => <PanelGroupView {...props} />);
46
+ return (
47
+ <PanelGroupImpl
48
+ group={group}
49
+ tabbar={
50
+ <TabBarImpl
51
+ rootRef={tabbarRef}
52
+ group={group}
53
+ onClickTab={(tabId) => onClickTab(id, tabId)}
54
+ onAddTab={onAddTab}
55
+ onCloseTab={onCloseTab}
56
+ onStartDrag={(tabId, groupId, e) => onStartTabDrag(tabId, groupId, e)}
57
+ doubleClickToAdd={doubleClickToAdd}
58
+ />
59
+ }
60
+ content={content}
61
+ groupRef={groupRef}
62
+ contentRef={contentRef}
63
+ />
64
+ );
65
+ };
@@ -0,0 +1,115 @@
1
+ /**
2
+ * @file Bridge component that binds InteractionsContext to PanelRenderContext using panel state.
3
+ */
4
+ import * as React from "react";
5
+ import { usePanelInteractions } from "../interactions/InteractionsContext";
6
+ import { PanelRenderProvider } from "./RenderContext";
7
+ import { usePanelState } from "../state/StateContext";
8
+ import { ContentRegistryProvider, useContentRegistry } from "./ContentRegistry";
9
+ import type { PanelId, GroupId } from "../state/types";
10
+
11
+ type PanelPlacement = {
12
+ groupId: GroupId;
13
+ isActive: boolean;
14
+ };
15
+
16
+ const RenderBridgeInner: React.FC<React.PropsWithChildren<{ emptyContentComponent?: React.ComponentType; doubleClickToAdd?: boolean }>> = ({
17
+ children,
18
+ emptyContentComponent,
19
+ doubleClickToAdd,
20
+ }) => {
21
+ const interactions = usePanelInteractions();
22
+ const { state, actions } = usePanelState();
23
+ const { registerContentContainer } = useContentRegistry();
24
+
25
+ const DefaultEmpty: React.FC = React.useCallback(() => {
26
+ return React.createElement("div", { style: { color: "#888", fontSize: 12, padding: 12 } }, "No tabs");
27
+ }, []);
28
+ const Empty = emptyContentComponent ?? DefaultEmpty;
29
+
30
+ const getGroup = React.useCallback(
31
+ (id: string) => {
32
+ const g = state.groups[id];
33
+ if (!g) {
34
+ return null;
35
+ }
36
+ const tabs = g.tabIds.map((tid) => state.panels[tid]).filter(Boolean);
37
+ return { ...g, tabs };
38
+ },
39
+ [state.groups, state.panels],
40
+ );
41
+
42
+ const getGroupContent = React.useCallback(
43
+ (id: string) => {
44
+ const group = state.groups[id];
45
+ if (!group || group.tabIds.length === 0) {
46
+ return <Empty />;
47
+ }
48
+ // Content is rendered via Portal from ContentRegistry
49
+ return null;
50
+ },
51
+ [state.groups, Empty],
52
+ );
53
+
54
+ const onClickTab = React.useCallback((gid: string, tabId: string) => {
55
+ actions.setActiveTab(gid, tabId);
56
+ }, [actions]);
57
+
58
+ const onAddTab = React.useCallback((gid: string) => {
59
+ actions.addNewTab({ groupId: gid, title: "New Tab", makeActive: true });
60
+ }, [actions]);
61
+
62
+ const onCloseTab = React.useCallback((gid: string, tabId: string) => {
63
+ actions.removeTab(gid, tabId);
64
+ }, [actions]);
65
+
66
+ const onStartTabDrag = React.useCallback((tabId: string, groupId: string, e: React.PointerEvent) => {
67
+ actions.setActiveTab(groupId, tabId);
68
+ interactions.onStartTabDrag(tabId, groupId, e);
69
+ }, [actions, interactions]);
70
+
71
+ const onStartContentDrag = React.useCallback((groupId: string, e: React.PointerEvent<HTMLDivElement>) => {
72
+ const g = state.groups[groupId];
73
+ if (!g || !g.activeTabId) {
74
+ return;
75
+ }
76
+ interactions.onStartContentDrag(groupId, g.activeTabId, e);
77
+ }, [state.groups, interactions]);
78
+
79
+ const value = React.useMemo(
80
+ () => ({ getGroup, getGroupContent, onClickTab, onAddTab, onCloseTab, onStartTabDrag, onStartContentDrag, doubleClickToAdd, registerContentContainer }),
81
+ [getGroup, getGroupContent, onClickTab, onAddTab, onCloseTab, onStartTabDrag, onStartContentDrag, doubleClickToAdd, registerContentContainer],
82
+ );
83
+
84
+ return <PanelRenderProvider value={value}>{children}</PanelRenderProvider>;
85
+ };
86
+
87
+ export const RenderBridge: React.FC<React.PropsWithChildren<{ emptyContentComponent?: React.ComponentType; doubleClickToAdd?: boolean }>> = ({
88
+ children,
89
+ emptyContentComponent,
90
+ doubleClickToAdd,
91
+ }) => {
92
+ const { state } = usePanelState();
93
+
94
+ // Compute placements: which group each panel belongs to and if it's active
95
+ const placements = React.useMemo(() => {
96
+ const result: Record<PanelId, PanelPlacement> = {};
97
+ for (const [groupId, group] of Object.entries(state.groups)) {
98
+ for (const tabId of group.tabIds) {
99
+ result[tabId] = {
100
+ groupId,
101
+ isActive: tabId === group.activeTabId,
102
+ };
103
+ }
104
+ }
105
+ return result;
106
+ }, [state.groups]);
107
+
108
+ return (
109
+ <ContentRegistryProvider panels={state.panels} placements={placements}>
110
+ <RenderBridgeInner emptyContentComponent={emptyContentComponent} doubleClickToAdd={doubleClickToAdd}>
111
+ {children}
112
+ </RenderBridgeInner>
113
+ </ContentRegistryProvider>
114
+ );
115
+ };