diff --git a/packages/dockview-demo/src/layout-grid/layoutGrid.tsx b/packages/dockview-demo/src/layout-grid/layoutGrid.tsx
index 2cac597e0..5c8bc9a0c 100644
--- a/packages/dockview-demo/src/layout-grid/layoutGrid.tsx
+++ b/packages/dockview-demo/src/layout-grid/layoutGrid.tsx
@@ -25,6 +25,14 @@ import { selectedPanelAtom } from './footer';
import { ExampleFunctions } from './panels/exampleFunctions';
import { CompositeDisposable } from '../lifecycle';
+const WatermarkComponent = () => {
+ return (
+
+ Watermark component
+
+ );
+};
+
const Test = (props: IDockviewPanelProps) => {
const [counter, setCounter] = React.useState(0);
@@ -109,6 +117,7 @@ const components: PanelCollection = {
@@ -140,7 +149,9 @@ export const TestGrid = (props: IGridviewPanelProps) => {
};
const setSelectedPanel = useRecoilCallback(
- ({ set }) => (value: string) => set(selectedPanelAtom, value),
+ ({ set }) =>
+ (value: string) =>
+ set(selectedPanelAtom, value),
[]
);
@@ -192,11 +203,12 @@ export const TestGrid = (props: IGridviewPanelProps) => {
};
}, [api]);
- const [coord, setCoord] = React.useState<{
- x: number;
- y: number;
- panel: IGroupPanel;
- }>(undefined);
+ const [coord, setCoord] =
+ React.useState<{
+ x: number;
+ y: number;
+ panel: IGroupPanel;
+ }>(undefined);
const onTabContextMenu = React.useMemo(
() => (event: TabContextMenuEvent) => {
@@ -276,7 +288,6 @@ export const TestGrid = (props: IGridviewPanelProps) => {
const Watermark = (props: IWatermarkPanelProps) => {
const [groups, setGroups] = React.useState(props.containerApi.size);
React.useEffect(() => {
- console.log('mount');
const disposable = new CompositeDisposable(
props.containerApi.onDidLayoutChange(() => {
console.log(`groups2 ${props.containerApi.size}`);
@@ -285,7 +296,6 @@ const Watermark = (props: IWatermarkPanelProps) => {
);
return () => {
- console.log('unmount');
disposable.dispose();
};
}, []);
@@ -332,30 +342,7 @@ const Watermark = (props: IWatermarkPanelProps) => {
justifyContent: 'center',
}}
>
- {/* */}
+ Watermark component
);
diff --git a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts
index 30a559fe1..665a38b7d 100644
--- a/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts
+++ b/packages/dockview/src/__tests__/dockview/dockviewComponent.spec.ts
@@ -465,7 +465,7 @@ describe('dockviewComponent', () => {
await panel2.api.close();
- expect(dockview.size).toBe(0);
+ expect(dockview.size).toBe(1); // watermark
expect(dockview.totalPanels).toBe(0);
});
@@ -1178,6 +1178,89 @@ describe('dockviewComponent', () => {
expect(panel1Spy).toBeCalledTimes(1);
});
+ test('can add panel of same id if already removed', () => {
+ const container = document.createElement('div');
+
+ const dockview = new DockviewComponent(container, {
+ components: { default: PanelContentPartTest },
+ });
+
+ dockview.layout(500, 1000);
+
+ const panel1 = dockview.addPanel({
+ id: 'panel1',
+ component: 'default',
+ tabComponent: 'default',
+ });
+
+ expect(dockview.totalPanels).toBe(1);
+
+ panel1.api.close();
+
+ expect(dockview.totalPanels).toBe(0);
+
+ const panel1Again = dockview.addPanel({
+ id: 'panel1',
+ component: 'default',
+ tabComponent: 'default',
+ });
+
+ expect(dockview.totalPanels).toBe(1);
+
+ panel1Again.api.close();
+
+ expect(dockview.totalPanels).toBe(0);
+ });
+
+ test('last group is retained for watermark', () => {
+ const container = document.createElement('div');
+
+ const dockview = new DockviewComponent(container, {
+ components: { default: PanelContentPartTest },
+ });
+
+ dockview.layout(500, 1000);
+
+ const panel1 = dockview.addPanel({
+ id: 'panel1',
+ component: 'default',
+ tabComponent: 'default',
+ });
+
+ expect(dockview.size).toBe(1);
+ expect(dockview.totalPanels).toBe(1);
+
+ const group = panel1.group;
+
+ dockview.removePanel(panel1);
+
+ expect(group.model.hasWatermark).toBeTruthy();
+ expect(dockview.size).toBe(1);
+ expect(dockview.totalPanels).toBe(0);
+
+ const panel2 = dockview.addPanel({
+ id: 'panel2',
+ component: 'default',
+ tabComponent: 'default',
+ });
+
+ expect(group.model.hasWatermark).toBeFalsy();
+
+ const panel3 = dockview.addPanel({
+ id: 'panel3',
+ component: 'default',
+ tabComponent: 'default',
+ });
+
+ expect(dockview.size).toBe(1);
+ expect(dockview.totalPanels).toBe(2);
+
+ panel2.api.close();
+ expect(group.model.hasWatermark).toBeFalsy();
+ panel3.api.close();
+ expect(group.model.hasWatermark).toBeTruthy();
+ });
+
test('panel is disposed of when removed', () => {
const container = document.createElement('div');
diff --git a/packages/dockview/src/dockview/dockviewComponent.ts b/packages/dockview/src/dockview/dockviewComponent.ts
index b8fe1286e..dd7b763c5 100644
--- a/packages/dockview/src/dockview/dockviewComponent.ts
+++ b/packages/dockview/src/dockview/dockviewComponent.ts
@@ -470,7 +470,13 @@ export class DockviewComponent
panel.dispose();
- if (group.model.size === 0 && options.removeEmptyGroup) {
+ const retainGroupForWatermark = this.size === 1;
+
+ if (
+ !retainGroupForWatermark &&
+ group.model.size === 0 &&
+ options.removeEmptyGroup
+ ) {
this.removeGroup(group);
}
}
diff --git a/packages/dockview/src/groupview/groupview.ts b/packages/dockview/src/groupview/groupview.ts
index 81341ee38..e78531e2c 100644
--- a/packages/dockview/src/groupview/groupview.ts
+++ b/packages/dockview/src/groupview/groupview.ts
@@ -150,38 +150,44 @@ export class Groupview extends CompositeDisposable implements IGroupview {
this.layout(this._width, this._height);
}
- get isActive() {
+ get isActive(): boolean {
return this._isGroupActive;
}
- get panels() {
+ get panels(): IGroupPanel[] {
return this._panels;
}
- get size() {
+ get size(): number {
return this._panels.length;
}
- get isEmpty() {
+ get isEmpty(): boolean {
return this._panels.length === 0;
}
- get minimumHeight() {
+ get minimumHeight(): number {
return 100;
}
- get maximumHeight() {
+ get maximumHeight(): number {
return Number.MAX_SAFE_INTEGER;
}
- get minimumWidth() {
+ get minimumWidth(): number {
return 100;
}
- get maximumWidth() {
+ get maximumWidth(): number {
return Number.MAX_SAFE_INTEGER;
}
+ get hasWatermark(): boolean {
+ return !!(
+ this.watermark && this.container.contains(this.watermark.element)
+ );
+ }
+
constructor(
private readonly container: HTMLElement,
private accessor: IDockviewComponent,