react-panel-layout 0.5.2 → 0.6.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/{FloatingPanelFrame-lLg-Lpg7.js → FloatingPanelFrame-3eU9AwPo.js} +11 -11
- package/dist/{FloatingPanelFrame-lLg-Lpg7.js.map → FloatingPanelFrame-3eU9AwPo.js.map} +1 -1
- package/dist/{FloatingPanelFrame-D9Cp2al1.cjs → FloatingPanelFrame-CEmXDvUA.cjs} +2 -2
- package/dist/{FloatingPanelFrame-D9Cp2al1.cjs.map → FloatingPanelFrame-CEmXDvUA.cjs.map} +1 -1
- package/dist/FloatingWindow-CUXnEtrb.js +827 -0
- package/dist/FloatingWindow-CUXnEtrb.js.map +1 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs +2 -0
- package/dist/FloatingWindow-DMwyK0eK.cjs.map +1 -0
- package/dist/GridLayout-DKTg_N61.cjs +2 -0
- package/dist/GridLayout-DKTg_N61.cjs.map +1 -0
- package/dist/GridLayout-UWNxXw77.js +926 -0
- package/dist/GridLayout-UWNxXw77.js.map +1 -0
- package/dist/HorizontalDivider-DdxzfV0l.js +30 -0
- package/dist/HorizontalDivider-DdxzfV0l.js.map +1 -0
- package/dist/HorizontalDivider-_pgV4Mcv.cjs +2 -0
- package/dist/HorizontalDivider-_pgV4Mcv.cjs.map +1 -0
- package/dist/PanelSystem-BqUzNtf2.js +1946 -0
- package/dist/PanelSystem-BqUzNtf2.js.map +1 -0
- package/dist/PanelSystem-D603LKKv.cjs +3 -0
- package/dist/PanelSystem-D603LKKv.cjs.map +1 -0
- package/dist/ResizeHandle-CBcAS918.cjs +2 -0
- package/dist/ResizeHandle-CBcAS918.cjs.map +1 -0
- package/dist/ResizeHandle-CXjc1meV.js +119 -0
- package/dist/ResizeHandle-CXjc1meV.js.map +1 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js +411 -0
- package/dist/SwipePivotTabBar-DWrCuwEI.js.map +1 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs +2 -0
- package/dist/SwipePivotTabBar-fjjXkpj7.cjs.map +1 -0
- package/dist/components/gesture/SwipeSafeZone.d.ts +40 -0
- package/dist/components/window/Drawer.d.ts +3 -1
- package/dist/components/window/DrawerLayers.d.ts +1 -1
- package/dist/components/window/drawerStyles.d.ts +69 -0
- package/dist/components/window/drawerSwipeConfig.d.ts +29 -0
- package/dist/components/window/useDrawerSwipeTransform.d.ts +23 -0
- package/dist/config.cjs +1 -1
- package/dist/config.cjs.map +1 -1
- package/dist/config.js +11 -9
- package/dist/config.js.map +1 -1
- package/dist/constants/styles.d.ts +35 -4
- package/dist/dialog/index.d.ts +69 -0
- package/dist/floating.cjs +1 -1
- package/dist/floating.js +1 -1
- package/dist/grid/index.d.ts +58 -0
- package/dist/grid.cjs +2 -0
- package/dist/grid.cjs.map +1 -0
- package/dist/grid.js +13 -0
- package/dist/grid.js.map +1 -0
- package/dist/hooks/gesture/presets.d.ts +33 -0
- package/dist/hooks/gesture/testing/createGestureSimulator.d.ts +117 -0
- package/dist/hooks/gesture/thresholdValue.d.ts +44 -0
- package/dist/hooks/gesture/types.d.ts +297 -0
- package/dist/hooks/gesture/useDirectionalLock.d.ts +20 -0
- package/dist/hooks/gesture/useEdgeSwipeInput.d.ts +23 -0
- package/dist/hooks/gesture/useNativeGestureGuard.d.ts +23 -0
- package/dist/hooks/gesture/usePointerTracking.d.ts +22 -0
- package/dist/hooks/gesture/useScrollBoundary.d.ts +23 -0
- package/dist/hooks/gesture/useSwipeInput.d.ts +5 -0
- package/dist/hooks/gesture/utils.d.ts +59 -0
- package/dist/hooks/useAnimatedVisibility.d.ts +58 -0
- package/dist/hooks/useAnimationFrame.d.ts +86 -0
- package/dist/hooks/useOperationContinuity.d.ts +64 -0
- package/dist/hooks/useResizeObserver.d.ts +33 -1
- package/dist/hooks/useSharedElementTransition.d.ts +112 -0
- package/dist/hooks/useSnapAnimation.d.ts +54 -0
- package/dist/hooks/useSwipeContentTransform.d.ts +86 -0
- package/dist/index.cjs +1 -2
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.ts +0 -1
- package/dist/index.js +25 -2006
- package/dist/index.js.map +1 -1
- package/dist/modules/dialog/AlertDialog.d.ts +9 -0
- package/dist/modules/dialog/DialogContainer.d.ts +37 -0
- package/dist/modules/dialog/Modal.d.ts +26 -0
- package/dist/modules/dialog/SwipeDialogContainer.d.ts +16 -0
- package/dist/modules/dialog/dialogAnimationUtils.d.ts +113 -0
- package/dist/modules/dialog/types.d.ts +183 -0
- package/dist/modules/dialog/useDialog.d.ts +39 -0
- package/dist/modules/dialog/useDialogContainer.d.ts +47 -0
- package/dist/modules/dialog/useDialogSwipeInput.d.ts +70 -0
- package/dist/modules/dialog/useDialogTransform.d.ts +82 -0
- package/dist/modules/drawer/types.d.ts +74 -0
- package/dist/modules/drawer/useDrawerSwipeInput.d.ts +24 -0
- package/dist/modules/pivot/PivotContent.d.ts +1 -1
- package/dist/modules/pivot/SwipePivotContent.d.ts +39 -0
- package/dist/modules/pivot/SwipePivotContent.debug.tmp.d.ts +25 -0
- package/dist/modules/pivot/SwipePivotContent.test.d.ts +1 -0
- package/dist/modules/pivot/SwipePivotTabBar.d.ts +92 -0
- package/dist/modules/pivot/index.d.ts +3 -0
- package/dist/modules/pivot/scaleInputState.d.ts +37 -0
- package/dist/modules/pivot/types.d.ts +67 -2
- package/dist/modules/pivot/usePivotSwipeInput.d.ts +68 -0
- package/dist/modules/stack/StackContent.d.ts +15 -0
- package/dist/modules/stack/SwipeStackContent.d.ts +66 -0
- package/dist/modules/stack/SwipeStackOutlet.d.ts +80 -0
- package/dist/modules/stack/computeStackContentState.d.ts +99 -0
- package/dist/modules/stack/computeSwipeStackTransform.d.ts +76 -0
- package/dist/modules/stack/types.d.ts +194 -0
- package/dist/modules/stack/useStackAnimationState.d.ts +32 -0
- package/dist/modules/stack/useStackNavigation.d.ts +23 -0
- package/dist/modules/stack/useStackSwipeInput.d.ts +27 -0
- package/dist/panels/index.d.ts +67 -0
- package/dist/panels.cjs +2 -0
- package/dist/panels.cjs.map +1 -0
- package/dist/panels.js +28 -0
- package/dist/panels.js.map +1 -0
- package/dist/pivot/index.d.ts +3 -0
- package/dist/pivot.cjs +1 -1
- package/dist/pivot.cjs.map +1 -1
- package/dist/pivot.js +20 -2
- package/dist/pivot.js.map +1 -1
- package/dist/resizer/index.d.ts +57 -0
- package/dist/resizer.cjs +2 -0
- package/dist/resizer.cjs.map +1 -0
- package/dist/resizer.js +8 -0
- package/dist/resizer.js.map +1 -0
- package/dist/stack/index.d.ts +72 -0
- package/dist/stack.cjs +2 -0
- package/dist/stack.cjs.map +1 -0
- package/dist/stack.js +721 -0
- package/dist/stack.js.map +1 -0
- package/dist/sticky-header/StickyArea.d.ts +38 -0
- package/dist/sticky-header/calculateStickyMetrics.d.ts +28 -0
- package/dist/sticky-header/index.d.ts +4 -4
- package/dist/sticky-header/types.d.ts +35 -22
- package/dist/sticky-header.cjs +1 -1
- package/dist/sticky-header.cjs.map +1 -1
- package/dist/sticky-header.js +73 -174
- package/dist/sticky-header.js.map +1 -1
- package/dist/styles-NkjuMOVS.js +57 -0
- package/dist/styles-NkjuMOVS.js.map +1 -0
- package/dist/styles-qf6ptVLD.cjs +2 -0
- package/dist/styles-qf6ptVLD.cjs.map +1 -0
- package/dist/types.d.ts +16 -0
- package/dist/useContentCache-CO3LYNmz.js +24 -0
- package/dist/useContentCache-CO3LYNmz.js.map +1 -0
- package/dist/useContentCache-DqXtLrLs.cjs +2 -0
- package/dist/useContentCache-DqXtLrLs.cjs.map +1 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js +54 -0
- package/dist/useDocumentPointerEvents-DXxw3qWj.js.map +1 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs +2 -0
- package/dist/useDocumentPointerEvents-DxDSOtip.cjs.map +1 -0
- package/dist/useFloatingState-C4kRaW_R.cjs +2 -0
- package/dist/useFloatingState-C4kRaW_R.cjs.map +1 -0
- package/dist/useFloatingState-tEfA_wbc.js +74 -0
- package/dist/useFloatingState-tEfA_wbc.js.map +1 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs +2 -0
- package/dist/useNativeGestureGuard-C7TSqEkr.cjs.map +1 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js +347 -0
- package/dist/useNativeGestureGuard-CGYo6O0r.js.map +1 -0
- package/dist/window/index.d.ts +63 -0
- package/dist/window.cjs +2 -0
- package/dist/window.cjs.map +1 -0
- package/dist/window.js +160 -0
- package/dist/window.js.map +1 -0
- package/docs/design-tokens.md +405 -0
- package/package.json +34 -4
- package/src/PanelSystemContext.tsx +88 -0
- package/src/components/gesture/SwipeSafeZone.tsx +69 -0
- package/src/components/grid/GridLayerList.tsx +172 -0
- package/src/components/grid/GridLayerResizeHandles.tsx +145 -0
- package/src/components/grid/GridLayout.spec.tsx +743 -0
- package/src/components/grid/GridLayout.tsx +130 -0
- package/src/components/grid/GridTrackResizeHandle.tsx +87 -0
- package/src/components/paneling/FloatingPanelFrame.tsx +203 -0
- package/src/components/panels/DropSuggestOverlay.tsx +131 -0
- package/src/components/panels/PanelGroupView.tsx +112 -0
- package/src/components/pivot/PivotLayer.tsx +27 -0
- package/src/components/resizer/HorizontalDivider.tsx +52 -0
- package/src/components/resizer/ResizeHandle.tsx +118 -0
- package/src/components/tabs/TabBar.tsx +223 -0
- package/src/components/tabs/TabBarTab.tsx +133 -0
- package/src/components/tabs/TabDragOverlay.tsx +92 -0
- package/src/components/window/DialogOverlay.tsx +180 -0
- package/src/components/window/Drawer.tsx +369 -0
- package/src/components/window/DrawerLayers.tsx +68 -0
- package/src/components/window/FloatingWindow.tsx +95 -0
- package/src/components/window/PopupLayerPortal.tsx +218 -0
- package/src/components/window/drawerStyles.spec.ts +263 -0
- package/src/components/window/drawerStyles.ts +228 -0
- package/src/components/window/drawerSwipeConfig.spec.ts +131 -0
- package/src/components/window/drawerSwipeConfig.ts +112 -0
- package/src/components/window/useDrawerSwipeTransform.spec.ts +234 -0
- package/src/components/window/useDrawerSwipeTransform.ts +129 -0
- package/src/config/PanelContentDeclaration.tsx +427 -0
- package/src/config/index.tsx +52 -0
- package/src/config/panelJsx.spec.tsx +54 -0
- package/src/config/panelJsxConfig.spec.tsx +54 -0
- package/src/config/panelJsxDrawer.spec.tsx +33 -0
- package/src/config/panelRouter.spec.ts +68 -0
- package/src/config/panelRouter.tsx +155 -0
- package/src/constants/styles.ts +280 -0
- package/src/demo/Layout.module.css +258 -0
- package/src/demo/Layout.tsx +176 -0
- package/src/demo/components/CodeBlock.module.css +54 -0
- package/src/demo/components/CodeBlock.tsx +34 -0
- package/src/demo/components/CodePreview.module.css +37 -0
- package/src/demo/components/CodePreview.tsx +31 -0
- package/src/demo/components/DataPreview.module.css +177 -0
- package/src/demo/components/DataPreview.tsx +115 -0
- package/src/demo/components/Story.module.css +68 -0
- package/src/demo/components/Story.tsx +54 -0
- package/src/demo/components/layout/CodePanel.module.css +183 -0
- package/src/demo/components/layout/CodePanel.tsx +149 -0
- package/src/demo/components/layout/DemoPage.module.css +60 -0
- package/src/demo/components/layout/DemoPage.tsx +56 -0
- package/src/demo/components/layout/SingleSamplePage.module.css +11 -0
- package/src/demo/components/layout/SingleSamplePage.tsx +35 -0
- package/src/demo/components/layout/SplitDemoLayout.module.css +107 -0
- package/src/demo/components/layout/SplitDemoLayout.tsx +218 -0
- package/src/demo/components/layout/index.ts +11 -0
- package/src/demo/components/tab-styles/ChromeTabBar.module.css +75 -0
- package/src/demo/components/tab-styles/ChromeTabBar.tsx +111 -0
- package/src/demo/components/tab-styles/GitHubTabBar.module.css +81 -0
- package/src/demo/components/tab-styles/GitHubTabBar.tsx +109 -0
- package/src/demo/components/tab-styles/VSCodeTabBar.module.css +78 -0
- package/src/demo/components/tab-styles/VSCodeTabBar.tsx +109 -0
- package/src/demo/components/tab-styles/index.ts +6 -0
- package/src/demo/components/ui/DemoButton.module.css +63 -0
- package/src/demo/components/ui/DemoButton.tsx +32 -0
- package/src/demo/components/ui/DemoCard.module.css +15 -0
- package/src/demo/components/ui/DemoCard.tsx +30 -0
- package/src/demo/components/ui/DemoContainer.module.css +17 -0
- package/src/demo/components/ui/DemoContainer.tsx +30 -0
- package/src/demo/components/ui/DemoPanel.module.css +23 -0
- package/src/demo/components/ui/DemoPanel.tsx +33 -0
- package/src/demo/components/ui/PanelText.module.css +18 -0
- package/src/demo/components/ui/PanelText.tsx +29 -0
- package/src/demo/components/ui/PanelTitle.module.css +18 -0
- package/src/demo/components/ui/PanelTitle.tsx +31 -0
- package/src/demo/contexts/TabbarDemoConfig.tsx +218 -0
- package/src/demo/demo.css +172 -0
- package/src/demo/hooks/useMedia.ts +41 -0
- package/src/demo/hooks/useShikiHighlight.ts +55 -0
- package/src/demo/index.tsx +293 -0
- package/src/demo/pages/Dialog/alerts/index.tsx +22 -0
- package/src/demo/pages/Dialog/card/index.tsx +22 -0
- package/src/demo/pages/Dialog/components/AlertDialogDemo.tsx +124 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.module.css +243 -0
- package/src/demo/pages/Dialog/components/CardExpandDemo.tsx +204 -0
- package/src/demo/pages/Dialog/components/CustomAlertDialogDemo.tsx +219 -0
- package/src/demo/pages/Dialog/components/DialogDemos.module.css +77 -0
- package/src/demo/pages/Dialog/components/ModalBasics.tsx +45 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.module.css +77 -0
- package/src/demo/pages/Dialog/components/SwipeDialogDemo.tsx +181 -0
- package/src/demo/pages/Dialog/custom-alert/index.tsx +22 -0
- package/src/demo/pages/Dialog/modal/index.tsx +17 -0
- package/src/demo/pages/Dialog/swipe/index.tsx +22 -0
- package/src/demo/pages/Drawer/animations/index.tsx +22 -0
- package/src/demo/pages/Drawer/basics/index.tsx +17 -0
- package/src/demo/pages/Drawer/components/DrawerAnimations.module.css +125 -0
- package/src/demo/pages/Drawer/components/DrawerAnimations.tsx +118 -0
- package/src/demo/pages/Drawer/components/DrawerBasics.module.css +55 -0
- package/src/demo/pages/Drawer/components/DrawerBasics.tsx +76 -0
- package/src/demo/pages/Drawer/components/DrawerMenuLayout.module.css +332 -0
- package/src/demo/pages/Drawer/components/DrawerMenuLayout.tsx +199 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.module.css +316 -0
- package/src/demo/pages/Drawer/components/DrawerSwipe.tsx +178 -0
- package/src/demo/pages/Drawer/menu/index.tsx +17 -0
- package/src/demo/pages/Drawer/swipe/index.tsx +17 -0
- package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.module.css +163 -0
- package/src/demo/pages/FloatingPanelFrame/ResizableFloatingPanelsPreview.tsx +234 -0
- package/src/demo/pages/FloatingPanelFrame/basic/index.tsx +17 -0
- package/src/demo/pages/FloatingPanelFrame/complex/index.tsx +26 -0
- package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.module.css +16 -0
- package/src/demo/pages/FloatingPanelFrame/components/BasicPanel.tsx +24 -0
- package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.module.css +54 -0
- package/src/demo/pages/FloatingPanelFrame/components/ComplexPanel.tsx +67 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.module.css +21 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithControls.tsx +41 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.module.css +5 -0
- package/src/demo/pages/FloatingPanelFrame/components/PanelWithMeta.tsx +43 -0
- package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.module.css +11 -0
- package/src/demo/pages/FloatingPanelFrame/components/ScrollablePanel.tsx +42 -0
- package/src/demo/pages/FloatingPanelFrame/index.tsx +80 -0
- package/src/demo/pages/FloatingPanelFrame/scrollable/index.tsx +30 -0
- package/src/demo/pages/FloatingPanelFrame/with-controls/index.tsx +30 -0
- package/src/demo/pages/FloatingPanelFrame/with-meta/index.tsx +17 -0
- package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.module.css +112 -0
- package/src/demo/pages/HorizontalDivider/components/PanelsWithRichContent.tsx +56 -0
- package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.module.css +46 -0
- package/src/demo/pages/HorizontalDivider/components/SimpleResizablePanels.tsx +29 -0
- package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.module.css +54 -0
- package/src/demo/pages/HorizontalDivider/components/ThreePanelLayout.tsx +30 -0
- package/src/demo/pages/HorizontalDivider/index.module.css +14 -0
- package/src/demo/pages/HorizontalDivider/index.tsx +64 -0
- package/src/demo/pages/HorizontalDivider/panels-with-rich-content/index.tsx +21 -0
- package/src/demo/pages/HorizontalDivider/simple-resizable-panels/index.tsx +21 -0
- package/src/demo/pages/HorizontalDivider/three-panel-layout/index.tsx +21 -0
- package/src/demo/pages/PanelLayout/PanelLayoutDemo.module.css +174 -0
- package/src/demo/pages/PanelLayout/PanelLayoutDemo.tsx +248 -0
- package/src/demo/pages/PanelLayout/components/DashboardLayout.module.css +115 -0
- package/src/demo/pages/PanelLayout/components/DashboardLayout.tsx +124 -0
- package/src/demo/pages/PanelLayout/components/DraggableOverlays.module.css +101 -0
- package/src/demo/pages/PanelLayout/components/DraggableOverlays.tsx +122 -0
- package/src/demo/pages/PanelLayout/components/IDELayout.module.css +104 -0
- package/src/demo/pages/PanelLayout/components/IDELayout.tsx +143 -0
- package/src/demo/pages/PanelLayout/components/SimpleGrid.module.css +19 -0
- package/src/demo/pages/PanelLayout/components/SimpleGrid.tsx +62 -0
- package/src/demo/pages/PanelLayout/dashboard/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/draggable-overlays/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/ide-layout/index.tsx +22 -0
- package/src/demo/pages/PanelLayout/index.tsx +94 -0
- package/src/demo/pages/PanelLayout/simple-grid/index.tsx +22 -0
- package/src/demo/pages/PanelSystem/PanelSystemPreview.module.css +20 -0
- package/src/demo/pages/PanelSystem/PanelSystemPreview.tsx +101 -0
- package/src/demo/pages/PanelSystem/preview/index.tsx +18 -0
- package/src/demo/pages/PanelSystem/tabbar/index.tsx +129 -0
- package/src/demo/pages/Pivot/basics/index.tsx +17 -0
- package/src/demo/pages/Pivot/components/Pivot.module.css +278 -0
- package/src/demo/pages/Pivot/components/PivotBasics.tsx +103 -0
- package/src/demo/pages/Pivot/components/PivotSidebar.tsx +168 -0
- package/src/demo/pages/Pivot/components/PivotTabs.tsx +129 -0
- package/src/demo/pages/Pivot/components/PivotTransitions.tsx +120 -0
- package/src/demo/pages/Pivot/components/SwipePivot.module.css +114 -0
- package/src/demo/pages/Pivot/components/SwipePivot.tsx +193 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.module.css +203 -0
- package/src/demo/pages/Pivot/components/SwipeTabsPivot.tsx +320 -0
- package/src/demo/pages/Pivot/sidebar/index.tsx +17 -0
- package/src/demo/pages/Pivot/swipe/index.tsx +16 -0
- package/src/demo/pages/Pivot/swipe-debug/index.tsx +287 -0
- package/src/demo/pages/Pivot/swipe-tabs/index.tsx +15 -0
- package/src/demo/pages/Pivot/tabs/index.tsx +17 -0
- package/src/demo/pages/Pivot/transitions/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/both-directions/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.module.css +72 -0
- package/src/demo/pages/ResizeHandle/components/BothDirectionsDemo.tsx +41 -0
- package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.module.css +61 -0
- package/src/demo/pages/ResizeHandle/components/HorizontalResizeDemo.tsx +33 -0
- package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.module.css +83 -0
- package/src/demo/pages/ResizeHandle/components/NestedPanelsDemo.tsx +53 -0
- package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.module.css +68 -0
- package/src/demo/pages/ResizeHandle/components/VerticalResizeDemo.tsx +33 -0
- package/src/demo/pages/ResizeHandle/horizontal/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/index.module.css +11 -0
- package/src/demo/pages/ResizeHandle/index.tsx +71 -0
- package/src/demo/pages/ResizeHandle/nested-panels/index.tsx +17 -0
- package/src/demo/pages/ResizeHandle/vertical/index.tsx +17 -0
- package/src/demo/pages/ResponsiveLayout/adaptive-workspace/index.tsx +22 -0
- package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.module.css +423 -0
- package/src/demo/pages/ResponsiveLayout/components/ResponsiveWorkspace.tsx +398 -0
- package/src/demo/pages/Stack/basics/index.tsx +22 -0
- package/src/demo/pages/Stack/components/Stack.module.css +234 -0
- package/src/demo/pages/Stack/components/StackBasics.spec.tsx +152 -0
- package/src/demo/pages/Stack/components/StackBasics.tsx +301 -0
- package/src/demo/pages/Stack/components/StackTablet.module.css +299 -0
- package/src/demo/pages/Stack/components/StackTablet.spec.tsx +120 -0
- package/src/demo/pages/Stack/components/StackTablet.tsx +422 -0
- package/src/demo/pages/Stack/tablet/index.tsx +22 -0
- package/src/demo/pages/StickyHeader/basics/index.tsx +17 -0
- package/src/demo/pages/StickyHeader/components/StickyHeader.module.css +219 -0
- package/src/demo/pages/StickyHeader/components/StickyHeaderBasics.tsx +103 -0
- package/src/demo/routes.tsx +214 -0
- package/src/demo/styles/animations.css +68 -0
- package/src/demo/styles/stack-themes.css +35 -0
- package/src/demo/utils/createPanelView.tsx +58 -0
- package/src/dialog/index.ts +85 -0
- package/src/floating/index.ts +24 -0
- package/src/grid/index.ts +75 -0
- package/src/hooks/ContentCacheContext.tsx +87 -0
- package/src/hooks/gesture/presets.spec.ts +86 -0
- package/src/hooks/gesture/presets.ts +95 -0
- package/src/hooks/gesture/testing/createGestureSimulator.spec.ts +241 -0
- package/src/hooks/gesture/testing/createGestureSimulator.ts +385 -0
- package/src/hooks/gesture/thresholdValue.spec.ts +103 -0
- package/src/hooks/gesture/thresholdValue.ts +77 -0
- package/src/hooks/gesture/types.ts +367 -0
- package/src/hooks/gesture/useDirectionalLock.spec.ts +271 -0
- package/src/hooks/gesture/useDirectionalLock.ts +115 -0
- package/src/hooks/gesture/useEdgeSwipeInput.spec.ts +462 -0
- package/src/hooks/gesture/useEdgeSwipeInput.ts +131 -0
- package/src/hooks/gesture/useNativeGestureGuard.spec.ts +473 -0
- package/src/hooks/gesture/useNativeGestureGuard.ts +135 -0
- package/src/hooks/gesture/usePointerTracking.spec.ts +364 -0
- package/src/hooks/gesture/usePointerTracking.ts +134 -0
- package/src/hooks/gesture/useScrollBoundary.spec.ts +249 -0
- package/src/hooks/gesture/useScrollBoundary.ts +113 -0
- package/src/hooks/gesture/useSwipeInput.spec.ts +592 -0
- package/src/hooks/gesture/useSwipeInput.ts +310 -0
- package/src/hooks/gesture/utils.spec.ts +152 -0
- package/src/hooks/gesture/utils.ts +178 -0
- package/src/hooks/useAnimatedVisibility.spec.ts +277 -0
- package/src/hooks/useAnimatedVisibility.ts +172 -0
- package/src/hooks/useAnimationFrame.ts +208 -0
- package/src/hooks/useCSSMatrix.spec.ts +214 -0
- package/src/hooks/useCSSMatrix.ts +262 -0
- package/src/hooks/useClonedElementPreview.ts +28 -0
- package/src/hooks/useContainerScroll.ts +78 -0
- package/src/hooks/useContentCache.spec.tsx +232 -0
- package/src/hooks/useContentCache.tsx +127 -0
- package/src/hooks/useDocumentPointerEvents.ts +137 -0
- package/src/hooks/useDocumentScroll.ts +41 -0
- package/src/hooks/useEffectEvent.ts +40 -0
- package/src/hooks/useElementComponentWrapper.tsx +63 -0
- package/src/hooks/useIntersectionObserver.tsx +125 -0
- package/src/hooks/useIsomorphicLayoutEffect.ts +29 -0
- package/src/hooks/useOperationContinuity.spec.ts +387 -0
- package/src/hooks/useOperationContinuity.ts +135 -0
- package/src/hooks/useResizeObserver.spec.tsx +277 -0
- package/src/hooks/useResizeObserver.tsx +150 -0
- package/src/hooks/useScrollContainer.ts +73 -0
- package/src/hooks/useSharedElementTransition.ts +333 -0
- package/src/hooks/useSnapAnimation.ts +128 -0
- package/src/hooks/useSwipeContentTransform.spec.ts +133 -0
- package/src/hooks/useSwipeContentTransform.ts +373 -0
- package/src/hooks/useTransitionState.ts +95 -0
- package/src/index.tsx +88 -0
- package/src/modules/dialog/AlertDialog.spec.tsx +387 -0
- package/src/modules/dialog/AlertDialog.tsx +221 -0
- package/src/modules/dialog/DialogContainer.spec.tsx +228 -0
- package/src/modules/dialog/DialogContainer.tsx +188 -0
- package/src/modules/dialog/Modal.spec.tsx +220 -0
- package/src/modules/dialog/Modal.tsx +182 -0
- package/src/modules/dialog/SwipeDialogContainer.tsx +208 -0
- package/src/modules/dialog/dialogAnimationUtils.spec.ts +253 -0
- package/src/modules/dialog/dialogAnimationUtils.ts +297 -0
- package/src/modules/dialog/types.ts +186 -0
- package/src/modules/dialog/useDialog.spec.tsx +447 -0
- package/src/modules/dialog/useDialog.ts +214 -0
- package/src/modules/dialog/useDialogContainer.spec.ts +331 -0
- package/src/modules/dialog/useDialogContainer.ts +150 -0
- package/src/modules/dialog/useDialogSwipeInput.spec.ts +157 -0
- package/src/modules/dialog/useDialogSwipeInput.ts +319 -0
- package/src/modules/dialog/useDialogTransform.spec.ts +370 -0
- package/src/modules/dialog/useDialogTransform.ts +407 -0
- package/src/modules/drawer/types.ts +102 -0
- package/src/modules/drawer/useDrawerSwipeInput.spec.ts +566 -0
- package/src/modules/drawer/useDrawerSwipeInput.ts +399 -0
- package/src/modules/grid/GridLayoutContext.tsx +57 -0
- package/src/modules/grid/LayerInstanceContext.tsx +56 -0
- package/src/modules/grid/resizeHandles.ts +157 -0
- package/src/modules/grid/trackUtils.ts +146 -0
- package/src/modules/grid/useGridPlacements.ts +143 -0
- package/src/modules/grid/useGridTracks.ts +156 -0
- package/src/modules/grid/useLayerDragHandle.ts +16 -0
- package/src/modules/grid/useLayerInteractions.tsx +850 -0
- package/src/modules/keybindings/KeybindingsProvider.tsx +111 -0
- package/src/modules/panels/dom/DomRegistry.tsx +94 -0
- package/src/modules/panels/index.ts +45 -0
- package/src/modules/panels/interactions/InteractionsContext.test.tsx +330 -0
- package/src/modules/panels/interactions/InteractionsContext.tsx +394 -0
- package/src/modules/panels/interactions/dnd.ts +28 -0
- package/src/modules/panels/keybindings/KeybindingsInstaller.tsx +15 -0
- package/src/modules/panels/layout/adapter.ts +124 -0
- package/src/modules/panels/rendering/ContentRegistry.spec.tsx +311 -0
- package/src/modules/panels/rendering/ContentRegistry.tsx +205 -0
- package/src/modules/panels/rendering/GroupContainer.tsx +65 -0
- package/src/modules/panels/rendering/RenderBridge.tsx +115 -0
- package/src/modules/panels/rendering/RenderContext.tsx +31 -0
- package/src/modules/panels/state/PanelSplitHandles.tsx +147 -0
- package/src/modules/panels/state/PanelSystemContext.splitLimits.spec.tsx +50 -0
- package/src/modules/panels/state/PanelSystemContext.tsx +289 -0
- package/src/modules/panels/state/StateContext.tsx +12 -0
- package/src/modules/panels/state/cleanup.ts +37 -0
- package/src/modules/panels/state/commands.ts +53 -0
- package/src/modules/panels/state/focus/Context.tsx +25 -0
- package/src/modules/panels/state/focus/logic.ts +57 -0
- package/src/modules/panels/state/groups/Context.tsx +25 -0
- package/src/modules/panels/state/groups/logic.ts +105 -0
- package/src/modules/panels/state/splitLimits.spec.ts +46 -0
- package/src/modules/panels/state/splitLimits.ts +90 -0
- package/src/modules/panels/state/state.spec.ts +49 -0
- package/src/modules/panels/state/tree/Context.tsx +24 -0
- package/src/modules/panels/state/tree/logic.spec.ts +34 -0
- package/src/modules/panels/state/tree/logic.ts +138 -0
- package/src/modules/panels/state/types.ts +142 -0
- package/src/modules/panels/system/PanelSystem.empty-tabbar.spec.tsx +53 -0
- package/src/modules/panels/system/PanelSystem.tab-click-activates.spec.tsx +44 -0
- package/src/modules/panels/system/PanelSystem.tab-reorder.spec.tsx +64 -0
- package/src/modules/panels/system/PanelSystem.tabs-no-dup.spec.tsx +57 -0
- package/src/modules/panels/system/PanelSystem.tsx +206 -0
- package/src/modules/pivot/PivotContent.spec.tsx +179 -0
- package/src/modules/pivot/PivotContent.tsx +77 -0
- package/src/modules/pivot/SwipePivotContent.debug.tmp.tsx +237 -0
- package/src/modules/pivot/SwipePivotContent.position.spec.tsx +171 -0
- package/src/modules/pivot/SwipePivotContent.spec.tsx +494 -0
- package/src/modules/pivot/SwipePivotContent.test.tsx +502 -0
- package/src/modules/pivot/SwipePivotContent.tsx +197 -0
- package/src/modules/pivot/SwipePivotTabBar.spec.tsx +882 -0
- package/src/modules/pivot/SwipePivotTabBar.tsx +583 -0
- package/src/modules/pivot/index.ts +8 -0
- package/src/modules/pivot/scaleInputState.spec.ts +219 -0
- package/src/modules/pivot/scaleInputState.ts +66 -0
- package/src/modules/pivot/types.ts +139 -0
- package/src/modules/pivot/usePivot.spec.ts +635 -0
- package/src/modules/pivot/usePivot.spec.tsx +186 -0
- package/src/modules/pivot/usePivot.tsx +345 -0
- package/src/modules/pivot/usePivotSwipeInput.spec.ts +708 -0
- package/src/modules/pivot/usePivotSwipeInput.ts +136 -0
- package/src/modules/resizer/useResizeDrag.ts +94 -0
- package/src/modules/stack/StackContent.spec.tsx +264 -0
- package/src/modules/stack/StackContent.tsx +111 -0
- package/src/modules/stack/SwipeStackContent.spec.tsx +1564 -0
- package/src/modules/stack/SwipeStackContent.tsx +366 -0
- package/src/modules/stack/SwipeStackOutlet.spec.tsx +250 -0
- package/src/modules/stack/SwipeStackOutlet.tsx +221 -0
- package/src/modules/stack/computeStackContentState.spec.ts +281 -0
- package/src/modules/stack/computeStackContentState.ts +304 -0
- package/src/modules/stack/computeSwipeStackTransform.spec.ts +186 -0
- package/src/modules/stack/computeSwipeStackTransform.ts +145 -0
- package/src/modules/stack/swipeTransitionContinuity.spec.tsx +1133 -0
- package/src/modules/stack/types.ts +226 -0
- package/src/modules/stack/useStackAnimationState.spec.ts +188 -0
- package/src/modules/stack/useStackAnimationState.ts +143 -0
- package/src/modules/stack/useStackNavigation.spec.ts +672 -0
- package/src/modules/stack/useStackNavigation.tsx +393 -0
- package/src/modules/stack/useStackSwipeInput.spec.ts +309 -0
- package/src/modules/stack/useStackSwipeInput.ts +139 -0
- package/src/modules/window/useDrawerState.ts +81 -0
- package/src/modules/window/useFloatingState.spec.ts +252 -0
- package/src/modules/window/useFloatingState.ts +141 -0
- package/src/panels/index.ts +119 -0
- package/src/pivot/index.ts +19 -0
- package/src/resizer/index.ts +68 -0
- package/src/stack/index.ts +91 -0
- package/src/sticky-header/StickyArea.tsx +193 -0
- package/src/sticky-header/calculateStickyMetrics.spec.ts +105 -0
- package/src/sticky-header/calculateStickyMetrics.ts +50 -0
- package/src/sticky-header/index.ts +18 -0
- package/src/sticky-header/types.ts +68 -0
- package/src/types.ts +341 -0
- package/src/utils/CSSMatrix.ts +321 -0
- package/src/utils/css.ts +65 -0
- package/src/utils/dialogUtils.ts +43 -0
- package/src/utils/math.ts +18 -0
- package/src/utils/polyfills/createDialogPolyfill.ts +18 -0
- package/src/utils/typedActions.ts +103 -0
- package/src/vite-env.d.ts +6 -0
- package/src/window/index.ts +69 -0
- package/dist/GridLayout-BQQ63eA1.cjs +0 -2
- package/dist/GridLayout-BQQ63eA1.cjs.map +0 -1
- package/dist/GridLayout-CJTKq7Mp.js +0 -1465
- package/dist/GridLayout-CJTKq7Mp.js.map +0 -1
- package/dist/sticky-header/StickyHeader.d.ts +0 -53
- package/dist/styles-CA2_zLZt.js +0 -52
- package/dist/styles-CA2_zLZt.js.map +0 -1
- package/dist/styles-PsqGOEJP.cjs +0 -2
- package/dist/styles-PsqGOEJP.cjs.map +0 -1
- package/dist/usePivot-7ctin_P_.cjs +0 -2
- package/dist/usePivot-7ctin_P_.cjs.map +0 -1
- package/dist/usePivot-CgQxB8rc.js +0 -124
- package/dist/usePivot-CgQxB8rc.js.map +0 -1
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file ContentRegistry tests - state persistence across tab switch, panel move, and split
|
|
3
|
+
*/
|
|
4
|
+
import { render, screen, fireEvent } from "@testing-library/react";
|
|
5
|
+
import * as React from "react";
|
|
6
|
+
import { ContentRegistryProvider, useContentRegistry } from "./ContentRegistry";
|
|
7
|
+
import type { TabDefinition, PanelId, GroupId } from "../state/types";
|
|
8
|
+
|
|
9
|
+
// Counter component that maintains state
|
|
10
|
+
const CounterContent: React.FC<{ id: string }> = ({ id }) => {
|
|
11
|
+
const [count, setCount] = React.useState(0);
|
|
12
|
+
return (
|
|
13
|
+
<div data-testid={`content-${id}`}>
|
|
14
|
+
<span data-testid={`count-${id}`}>{count}</span>
|
|
15
|
+
<button data-testid={`increment-${id}`} onClick={() => setCount((c) => c + 1)}>
|
|
16
|
+
+1
|
|
17
|
+
</button>
|
|
18
|
+
</div>
|
|
19
|
+
);
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
// Test harness that simulates GroupContainer behavior
|
|
23
|
+
const GroupContent: React.FC<{ groupId: GroupId }> = ({ groupId }) => {
|
|
24
|
+
const { registerContentContainer } = useContentRegistry();
|
|
25
|
+
const ref = React.useCallback(
|
|
26
|
+
(el: HTMLDivElement | null) => {
|
|
27
|
+
registerContentContainer(groupId, el);
|
|
28
|
+
},
|
|
29
|
+
[groupId, registerContentContainer],
|
|
30
|
+
);
|
|
31
|
+
return <div ref={ref} data-testid={`container-${groupId}`} style={{ width: 100, height: 100 }} />;
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
type TestState = {
|
|
35
|
+
panels: Record<PanelId, TabDefinition>;
|
|
36
|
+
placements: Record<PanelId, { groupId: GroupId; isActive: boolean }>;
|
|
37
|
+
groupIds: GroupId[];
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
const TestHarness: React.FC<{ state: TestState }> = ({ state }) => {
|
|
41
|
+
return (
|
|
42
|
+
<ContentRegistryProvider panels={state.panels} placements={state.placements}>
|
|
43
|
+
{state.groupIds.map((gid) => (
|
|
44
|
+
<GroupContent key={gid} groupId={gid} />
|
|
45
|
+
))}
|
|
46
|
+
</ContentRegistryProvider>
|
|
47
|
+
);
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
describe("ContentRegistry", () => {
|
|
51
|
+
const defaultRect = {
|
|
52
|
+
top: 0,
|
|
53
|
+
left: 0,
|
|
54
|
+
width: 100,
|
|
55
|
+
height: 100,
|
|
56
|
+
right: 100,
|
|
57
|
+
bottom: 100,
|
|
58
|
+
x: 0,
|
|
59
|
+
y: 0,
|
|
60
|
+
toJSON: () => ({}),
|
|
61
|
+
} as DOMRect;
|
|
62
|
+
const originalPointerCapture = Element.prototype.setPointerCapture;
|
|
63
|
+
const originalReleasePointerCapture = Element.prototype.releasePointerCapture;
|
|
64
|
+
const originalGetBoundingClientRect = Element.prototype.getBoundingClientRect;
|
|
65
|
+
|
|
66
|
+
const createPanel = (id: string): TabDefinition => ({
|
|
67
|
+
id,
|
|
68
|
+
title: `Panel ${id}`,
|
|
69
|
+
render: (panelId) => <CounterContent key={panelId} id={panelId} />,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
beforeEach(() => {
|
|
73
|
+
// Mock ResizeObserver (polyfill provided in vitest.setup.ts)
|
|
74
|
+
|
|
75
|
+
// Mock pointer capture methods
|
|
76
|
+
Element.prototype.setPointerCapture = () => {};
|
|
77
|
+
Element.prototype.releasePointerCapture = () => {};
|
|
78
|
+
// Mock getBoundingClientRect
|
|
79
|
+
Element.prototype.getBoundingClientRect = () => defaultRect;
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
afterEach(() => {
|
|
83
|
+
// Clean up any portal containers
|
|
84
|
+
document.querySelectorAll("[data-panel-content-root]").forEach((el) => el.remove());
|
|
85
|
+
Element.prototype.setPointerCapture = originalPointerCapture;
|
|
86
|
+
Element.prototype.releasePointerCapture = originalReleasePointerCapture;
|
|
87
|
+
Element.prototype.getBoundingClientRect = originalGetBoundingClientRect;
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("should render content inside the registered container element", () => {
|
|
91
|
+
const panels = {
|
|
92
|
+
"panel-1": createPanel("panel-1"),
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const state: TestState = {
|
|
96
|
+
panels,
|
|
97
|
+
placements: {
|
|
98
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
99
|
+
},
|
|
100
|
+
groupIds: ["group-1"],
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
render(<TestHarness state={state} />);
|
|
104
|
+
|
|
105
|
+
// The content should be rendered
|
|
106
|
+
const content = screen.getByTestId("content-panel-1");
|
|
107
|
+
expect(content).toBeInTheDocument();
|
|
108
|
+
|
|
109
|
+
// The container element should exist
|
|
110
|
+
const container = screen.getByTestId("container-group-1");
|
|
111
|
+
expect(container).toBeInTheDocument();
|
|
112
|
+
|
|
113
|
+
// Content should be inside the container (not in a separate portal overlay)
|
|
114
|
+
expect(container.contains(content)).toBe(true);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("should preserve state when switching tabs", async () => {
|
|
118
|
+
const panels = {
|
|
119
|
+
"panel-1": createPanel("panel-1"),
|
|
120
|
+
"panel-2": createPanel("panel-2"),
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
const initialState: TestState = {
|
|
124
|
+
panels,
|
|
125
|
+
placements: {
|
|
126
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
127
|
+
"panel-2": { groupId: "group-1", isActive: false },
|
|
128
|
+
},
|
|
129
|
+
groupIds: ["group-1"],
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
const { rerender } = render(<TestHarness state={initialState} />);
|
|
133
|
+
|
|
134
|
+
// Increment panel-1's counter
|
|
135
|
+
const incrementBtn = screen.getByTestId("increment-panel-1");
|
|
136
|
+
fireEvent.click(incrementBtn);
|
|
137
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("1");
|
|
138
|
+
|
|
139
|
+
// Switch to panel-2 (make it active, panel-1 inactive)
|
|
140
|
+
const switchedState: TestState = {
|
|
141
|
+
panels,
|
|
142
|
+
placements: {
|
|
143
|
+
"panel-1": { groupId: "group-1", isActive: false },
|
|
144
|
+
"panel-2": { groupId: "group-1", isActive: true },
|
|
145
|
+
},
|
|
146
|
+
groupIds: ["group-1"],
|
|
147
|
+
};
|
|
148
|
+
rerender(<TestHarness state={switchedState} />);
|
|
149
|
+
|
|
150
|
+
// Switch back to panel-1
|
|
151
|
+
rerender(<TestHarness state={initialState} />);
|
|
152
|
+
|
|
153
|
+
// panel-1's counter should still be 1
|
|
154
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("1");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("should preserve state when moving panel to another group", async () => {
|
|
158
|
+
const panels = {
|
|
159
|
+
"panel-1": createPanel("panel-1"),
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const initialState: TestState = {
|
|
163
|
+
panels,
|
|
164
|
+
placements: {
|
|
165
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
166
|
+
},
|
|
167
|
+
groupIds: ["group-1"],
|
|
168
|
+
};
|
|
169
|
+
|
|
170
|
+
const { rerender } = render(<TestHarness state={initialState} />);
|
|
171
|
+
|
|
172
|
+
// Increment counter
|
|
173
|
+
fireEvent.click(screen.getByTestId("increment-panel-1"));
|
|
174
|
+
fireEvent.click(screen.getByTestId("increment-panel-1"));
|
|
175
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("2");
|
|
176
|
+
|
|
177
|
+
// Move panel to group-2 (new group is created at the same time)
|
|
178
|
+
const movedState: TestState = {
|
|
179
|
+
panels,
|
|
180
|
+
placements: {
|
|
181
|
+
"panel-1": { groupId: "group-2", isActive: true },
|
|
182
|
+
},
|
|
183
|
+
groupIds: ["group-1", "group-2"],
|
|
184
|
+
};
|
|
185
|
+
rerender(<TestHarness state={movedState} />);
|
|
186
|
+
|
|
187
|
+
// Counter should still be 2
|
|
188
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("2");
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
it("should preserve state when splitting panel (adding new group)", async () => {
|
|
192
|
+
const panels = {
|
|
193
|
+
"panel-1": createPanel("panel-1"),
|
|
194
|
+
"panel-2": createPanel("panel-2"),
|
|
195
|
+
};
|
|
196
|
+
|
|
197
|
+
// Initial: both panels in group-1
|
|
198
|
+
const initialState: TestState = {
|
|
199
|
+
panels,
|
|
200
|
+
placements: {
|
|
201
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
202
|
+
"panel-2": { groupId: "group-1", isActive: false },
|
|
203
|
+
},
|
|
204
|
+
groupIds: ["group-1"],
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
const { rerender } = render(<TestHarness state={initialState} />);
|
|
208
|
+
|
|
209
|
+
// Increment panel-1's counter to 3
|
|
210
|
+
fireEvent.click(screen.getByTestId("increment-panel-1"));
|
|
211
|
+
fireEvent.click(screen.getByTestId("increment-panel-1"));
|
|
212
|
+
fireEvent.click(screen.getByTestId("increment-panel-1"));
|
|
213
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("3");
|
|
214
|
+
|
|
215
|
+
// Split: panel-2 moves to new group-2
|
|
216
|
+
const splitState: TestState = {
|
|
217
|
+
panels,
|
|
218
|
+
placements: {
|
|
219
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
220
|
+
"panel-2": { groupId: "group-2", isActive: true },
|
|
221
|
+
},
|
|
222
|
+
groupIds: ["group-1", "group-2"],
|
|
223
|
+
};
|
|
224
|
+
rerender(<TestHarness state={splitState} />);
|
|
225
|
+
|
|
226
|
+
// panel-1's counter should still be 3
|
|
227
|
+
expect(screen.getByTestId("count-panel-1").textContent).toBe("3");
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
it("should handle multiple panels in same group without wrapper conflicts", () => {
|
|
231
|
+
const panels = {
|
|
232
|
+
"panel-1": createPanel("panel-1"),
|
|
233
|
+
"panel-2": createPanel("panel-2"),
|
|
234
|
+
"panel-3": createPanel("panel-3"),
|
|
235
|
+
};
|
|
236
|
+
|
|
237
|
+
const state: TestState = {
|
|
238
|
+
panels,
|
|
239
|
+
placements: {
|
|
240
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
241
|
+
"panel-2": { groupId: "group-1", isActive: false },
|
|
242
|
+
"panel-3": { groupId: "group-1", isActive: false },
|
|
243
|
+
},
|
|
244
|
+
groupIds: ["group-1"],
|
|
245
|
+
};
|
|
246
|
+
|
|
247
|
+
render(<TestHarness state={state} />);
|
|
248
|
+
|
|
249
|
+
const container = screen.getByTestId("container-group-1");
|
|
250
|
+
|
|
251
|
+
// All panel wrappers should be inside the container
|
|
252
|
+
const wrappers = container.querySelectorAll("[data-panel-wrapper]");
|
|
253
|
+
expect(wrappers.length).toBe(3);
|
|
254
|
+
|
|
255
|
+
// Each wrapper should have unique panel id
|
|
256
|
+
const wrapperIds = Array.from(wrappers).map((w) => w.getAttribute("data-panel-wrapper"));
|
|
257
|
+
expect(wrapperIds).toContain("panel-1");
|
|
258
|
+
expect(wrapperIds).toContain("panel-2");
|
|
259
|
+
expect(wrapperIds).toContain("panel-3");
|
|
260
|
+
|
|
261
|
+
// Active panel content should be visible
|
|
262
|
+
expect(screen.getByTestId("content-panel-1")).toBeVisible();
|
|
263
|
+
});
|
|
264
|
+
|
|
265
|
+
it("should not have wrapper nesting issues when panels move between groups", () => {
|
|
266
|
+
const panels = {
|
|
267
|
+
"panel-1": createPanel("panel-1"),
|
|
268
|
+
"panel-2": createPanel("panel-2"),
|
|
269
|
+
};
|
|
270
|
+
|
|
271
|
+
const initialState: TestState = {
|
|
272
|
+
panels,
|
|
273
|
+
placements: {
|
|
274
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
275
|
+
"panel-2": { groupId: "group-1", isActive: false },
|
|
276
|
+
},
|
|
277
|
+
groupIds: ["group-1", "group-2"],
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const { rerender } = render(<TestHarness state={initialState} />);
|
|
281
|
+
|
|
282
|
+
// Initially both panels in group-1
|
|
283
|
+
const container1 = screen.getByTestId("container-group-1");
|
|
284
|
+
expect(container1.querySelectorAll("[data-panel-wrapper]").length).toBe(2);
|
|
285
|
+
|
|
286
|
+
// Move panel-2 to group-2
|
|
287
|
+
const movedState: TestState = {
|
|
288
|
+
panels,
|
|
289
|
+
placements: {
|
|
290
|
+
"panel-1": { groupId: "group-1", isActive: true },
|
|
291
|
+
"panel-2": { groupId: "group-2", isActive: true },
|
|
292
|
+
},
|
|
293
|
+
groupIds: ["group-1", "group-2"],
|
|
294
|
+
};
|
|
295
|
+
rerender(<TestHarness state={movedState} />);
|
|
296
|
+
|
|
297
|
+
// Now each group should have exactly one wrapper
|
|
298
|
+
const container1After = screen.getByTestId("container-group-1");
|
|
299
|
+
const container2 = screen.getByTestId("container-group-2");
|
|
300
|
+
|
|
301
|
+
expect(container1After.querySelectorAll("[data-panel-wrapper]").length).toBe(1);
|
|
302
|
+
expect(container2.querySelectorAll("[data-panel-wrapper]").length).toBe(1);
|
|
303
|
+
|
|
304
|
+
// Wrappers should not be nested inside each other
|
|
305
|
+
const allWrappers = document.querySelectorAll("[data-panel-wrapper]");
|
|
306
|
+
allWrappers.forEach((wrapper) => {
|
|
307
|
+
const nestedWrappers = wrapper.querySelectorAll("[data-panel-wrapper]");
|
|
308
|
+
expect(nestedWrappers.length).toBe(0);
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
});
|
|
@@ -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
|
+
};
|