diff --git a/.codesandbox/ci.json b/.codesandbox/ci.json index b8d35457a..d4a057c08 100644 --- a/.codesandbox/ci.json +++ b/.codesandbox/ci.json @@ -13,6 +13,8 @@ "/packages/docs/sandboxes/externaldnd-dockview", "/packages/docs/sandboxes/floatinggroup-dockview", "/packages/docs/sandboxes/fullwidthtab-dockview", + "/packages/docs/sandboxes/headeractions-dockview", + "/packages/docs/sandboxes/ide-example", "/packages/docs/sandboxes/groupcontol-dockview", "/packages/docs/sandboxes/iframe-dockview", "/packages/docs/sandboxes/keyboard-dockview", diff --git a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts index ec7654393..ed02acf4c 100644 --- a/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts +++ b/packages/dockview-core/src/__tests__/splitview/splitview.spec.ts @@ -676,4 +676,100 @@ describe('splitview', () => { expect(addEventListenerSpy).toBeCalledTimes(3); expect(removeEventListenerSpy).toBeCalledTimes(3); }); + + test('setViewVisible', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(900, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000); + const view3 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + splitview.addView(view3); + + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + + splitview.setViewVisible(0, false); + expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]); + + splitview.setViewVisible(0, true); + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + }); + + test('setViewVisible with one view having high layout priority', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(900, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000, LayoutPriority.High); + const view3 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + splitview.addView(view3); + + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + + splitview.setViewVisible(0, false); + expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]); + + splitview.setViewVisible(0, true); + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + }); + + test('set view size', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(900, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000); + const view3 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + splitview.addView(view3); + + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + + view1.fireChangeEvent({ size: 0 }); + expect([view1.size, view2.size, view3.size]).toEqual([0, 300, 600]); + + view1.fireChangeEvent({ size: 300 }); + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + }); + + test('set view size with one view having high layout priority', () => { + const splitview = new Splitview(container, { + orientation: Orientation.HORIZONTAL, + proportionalLayout: false, + }); + splitview.layout(900, 500); + + const view1 = new Testview(0, 1000); + const view2 = new Testview(0, 1000, LayoutPriority.High); + const view3 = new Testview(0, 1000); + + splitview.addView(view1); + splitview.addView(view2); + splitview.addView(view3); + + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + + view1.fireChangeEvent({ size: 0 }); + expect([view1.size, view2.size, view3.size]).toEqual([0, 600, 300]); + + view1.fireChangeEvent({ size: 300 }); + expect([view1.size, view2.size, view3.size]).toEqual([300, 300, 300]); + }); }); diff --git a/packages/dockview-core/src/splitview/splitview.ts b/packages/dockview-core/src/splitview/splitview.ts index 007a8e60a..0d56cce24 100644 --- a/packages/dockview-core/src/splitview/splitview.ts +++ b/packages/dockview-core/src/splitview/splitview.ts @@ -37,10 +37,11 @@ export interface SplitViewOptions { readonly proportionalLayout?: boolean; readonly styles?: ISplitviewStyles; } + export enum LayoutPriority { - Low = 'low', - High = 'high', - Normal = 'normal', + Low = 'low', // view is offered space last + High = 'high', // view is offered space first + Normal = 'normal', // view is offered space in view order } export interface IBaseView extends IDisposable { @@ -340,7 +341,22 @@ export class Splitview { item.size = size; - this.relayout([index]); + const indexes = range(this.viewItems.length).filter((i) => i !== index); + const lowPriorityIndexes = [ + ...indexes.filter( + (i) => this.viewItems[i].priority === LayoutPriority.Low + ), + index, + ]; + const highPriorityIndexes = indexes.filter( + (i) => this.viewItems[i].priority === LayoutPriority.High + ); + + /** + * add this view we are changing to the low-index list since we have determined the size + * here and don't want it changed + */ + this.relayout([...lowPriorityIndexes, index], highPriorityIndexes); } public addView( diff --git a/packages/docs/docs/components/dockview.mdx b/packages/docs/docs/components/dockview.mdx index 648185d7e..b8e6748e6 100644 --- a/packages/docs/docs/components/dockview.mdx +++ b/packages/docs/docs/components/dockview.mdx @@ -27,6 +27,7 @@ import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app'; import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app'; import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app'; +import IDEExample from '@site/sandboxes/ide-example/src/app'; import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; @@ -911,6 +912,14 @@ Keyboard shortcuts react={DockviewKeyboard} /> +## Application with sidebars + + + ### Nested Dockviews You can safely create multiple dockview instances within one page and nest dockviews within other dockviews. diff --git a/packages/docs/sandboxes/ide-example/package.json b/packages/docs/sandboxes/ide-example/package.json new file mode 100644 index 000000000..5de7b1222 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/package.json @@ -0,0 +1,32 @@ +{ + "name": "ide-example", + "description": "", + "keywords": [ + "dockview" + ], + "version": "1.0.0", + "main": "src/index.tsx", + "dependencies": { + "dockview": "*", + "react": "^18.2.0", + "react-dom": "^18.2.0" + }, + "devDependencies": { + "@types/react": "^18.0.28", + "@types/react-dom": "^18.0.11", + "typescript": "^4.9.5", + "react-scripts": "*" + }, + "scripts": { + "start": "react-scripts start", + "build": "react-scripts build", + "test": "react-scripts test --env=jsdom", + "eject": "react-scripts eject" + }, + "browserslist": [ + ">0.2%", + "not dead", + "not ie <= 11", + "not op_mini all" + ] +} diff --git a/packages/docs/sandboxes/ide-example/public/index.html b/packages/docs/sandboxes/ide-example/public/index.html new file mode 100644 index 000000000..1f8a52426 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/public/index.html @@ -0,0 +1,44 @@ + + + + + + + + + + + + + React App + + + + +
+ + + + diff --git a/packages/docs/sandboxes/ide-example/src/app.tsx b/packages/docs/sandboxes/ide-example/src/app.tsx new file mode 100644 index 000000000..d63b035f8 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/src/app.tsx @@ -0,0 +1,154 @@ +import { + GridviewReact, + GridviewReadyEvent, + IGridviewPanelProps, + GridviewComponent, + Orientation, + GridviewApi, + LayoutPriority, +} from 'dockview'; +import * as React from 'react'; + +const components = { + 'left-sidebar': (props: IGridviewPanelProps<{ title: string }>) => { + return ( +
+ {props.params.title} +
+ ); + }, + 'middle-content': (props: IGridviewPanelProps<{ title: string }>) => { + return ( +
+ {props.params.title} +
+ ); + }, + 'right-sidebar': (props: IGridviewPanelProps<{ title: string }>) => { + return ( +
+ {props.params.title} +
+ ); + }, +}; + +const App = (props: { theme?: string }) => { + const [api, setApi] = React.useState(); + + const onReady = (event: GridviewReadyEvent) => { + event.api.fromJSON({ + grid: { + height: 1000, + width: 1000, + orientation: Orientation.HORIZONTAL, + root: { + type: 'branch', + data: [ + { + type: 'leaf', + data: { + id: 'left-sidebar-id', + component: 'left-sidebar', + snap: true, + minimumWidth: 100, + }, + size: 200, + }, + { + type: 'leaf', + data: { + id: 'middle-content-id', + component: 'middle-content', + priority: LayoutPriority.High, + minimumWidth: 100, + }, + size: 600, + }, + { + type: 'leaf', + data: { + id: 'right-sidebar-id', + component: 'right-sidebar', + snap: true, + minimumWidth: 100, + }, + size: 200, + }, + ], + }, + }, + }); + + setApi(event.api); + }; + + const onKeyPress = (event: React.KeyboardEvent) => { + if (!api) { + return; + } + + if (event.ctrlKey) { + if (event.code === 'ArrowLeft') { + const leftSidebarPanel = api.getPanel('left-sidebar-id'); + + if (leftSidebarPanel) { + leftSidebarPanel.api.setVisible(false); + } + } + } + + if (event.code === 'ArrowRight') { + const leftSidebarPanel = api.getPanel('left-sidebar-id'); + + if (leftSidebarPanel) { + leftSidebarPanel.api.setVisible(true); + } + } + }; + + return ( +
+
+ {'Use '} + {'Ctrl+ArrowLeft'} + {' and '} + {'Ctrl+ArrowRight'} + { + ' to show and hide the left sidebar. The right sidebar can be hidden by dragging it to the right.' + } +
+
+ +
+
+ ); +}; + +export default App; diff --git a/packages/docs/sandboxes/ide-example/src/index.tsx b/packages/docs/sandboxes/ide-example/src/index.tsx new file mode 100644 index 000000000..2fe1be232 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/src/index.tsx @@ -0,0 +1,20 @@ +import { StrictMode } from 'react'; +import * as ReactDOMClient from 'react-dom/client'; +import './styles.css'; +import 'dockview/dist/styles/dockview.css'; + +import App from './app'; + +const rootElement = document.getElementById('root'); + +if (rootElement) { + const root = ReactDOMClient.createRoot(rootElement); + + root.render( + +
+ +
+
+ ); +} diff --git a/packages/docs/sandboxes/ide-example/src/styles.css b/packages/docs/sandboxes/ide-example/src/styles.css new file mode 100644 index 000000000..2198f8a37 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/src/styles.css @@ -0,0 +1,15 @@ +body { + margin: 0px; + font-family: sans-serif; + text-align: center; +} + +#root { + height: 100vh; + width: 100vw; +} + +.app { + height: 100%; + +} diff --git a/packages/docs/sandboxes/ide-example/tsconfig.json b/packages/docs/sandboxes/ide-example/tsconfig.json new file mode 100644 index 000000000..cdc4fb5f5 --- /dev/null +++ b/packages/docs/sandboxes/ide-example/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "build/dist", + "module": "esnext", + "target": "es5", + "lib": ["es6", "dom"], + "sourceMap": true, + "allowJs": true, + "jsx": "react-jsx", + "moduleResolution": "node", + "rootDir": "src", + "forceConsistentCasingInFileNames": true, + "noImplicitReturns": true, + "noImplicitThis": true, + "noImplicitAny": true, + "strictNullChecks": true + } +}