diff --git a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts index 67f317886..ae3659753 100644 --- a/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts +++ b/packages/dockview-core/src/__tests__/dockview/dockviewComponent.spec.ts @@ -100,67 +100,67 @@ describe('dockviewComponent', () => { }); }); - // test('event leakage', () => { - // Emitter.setLeakageMonitorEnabled(true); + test('event leakage', () => { + Emitter.setLeakageMonitorEnabled(true); - // dockview = new DockviewComponent({ - // parentElement: container, - // components: { - // default: PanelContentPartTest, - // }, - // }); + dockview = new DockviewComponent({ + parentElement: container, + components: { + default: PanelContentPartTest, + }, + }); - // dockview.layout(500, 1000); + dockview.layout(500, 1000); - // dockview.addPanel({ - // id: 'panel1', - // component: 'default', - // }); + dockview.addPanel({ + id: 'panel1', + component: 'default', + }); - // const panel2 = dockview.addPanel({ - // id: 'panel2', - // component: 'default', - // }); + const panel2 = dockview.addPanel({ + id: 'panel2', + component: 'default', + }); - // dockview.removePanel(panel2); + dockview.removePanel(panel2); - // const panel3 = dockview.addPanel({ - // id: 'panel3', - // component: 'default', - // position: { - // direction: 'right', - // referencePanel: 'panel1', - // }, - // }); + const panel3 = dockview.addPanel({ + id: 'panel3', + component: 'default', + position: { + direction: 'right', + referencePanel: 'panel1', + }, + }); - // const panel4 = dockview.addPanel({ - // id: 'panel4', - // component: 'default', - // position: { - // direction: 'above', - // }, - // }); + const panel4 = dockview.addPanel({ + id: 'panel4', + component: 'default', + position: { + direction: 'above', + }, + }); - // dockview.moveGroupOrPanel( - // panel4.group, - // panel3.group.id, - // panel3.id, - // 'center' - // ); + dockview.moveGroupOrPanel( + panel4.group, + panel3.group.id, + panel3.id, + 'center' + ); - // dockview.dispose(); + dockview.dispose(); - // if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { - // for (const entry of Array.from( - // Emitter.MEMORY_LEAK_WATCHER.events - // )) { - // console.log('disposal', entry[1]); - // } - // throw new Error('not all listeners disposed'); - // } + if (Emitter.MEMORY_LEAK_WATCHER.size > 0) { + for (const entry of Array.from( + Emitter.MEMORY_LEAK_WATCHER.events + )) { + console.log('disposal', entry[1]); + } + throw new Error('not all listeners disposed'); + } - // Emitter.setLeakageMonitorEnabled(false); - // }); + Emitter.setLeakageMonitorEnabled(false); + }); test('duplicate panel', () => { dockview.layout(500, 1000); @@ -2449,7 +2449,7 @@ describe('dockviewComponent', () => { }); }); - test('check', () => { + test('check dockview component is rendering to the DOM as expected', () => { const container = document.createElement('div'); const dockview = new DockviewComponent({ @@ -2470,16 +2470,22 @@ describe('dockviewComponent', () => { component: 'default', }); + expect(dockview.element.querySelectorAll('.view').length).toBe(1); + const panel2 = dockview.addPanel({ id: 'panel2', component: 'default', }); + expect(dockview.element.querySelectorAll('.view').length).toBe(1); + const panel3 = dockview.addPanel({ id: 'panel3', component: 'default', }); + expect(dockview.element.querySelectorAll('.view').length).toBe(1); + dockview.moveGroupOrPanel( panel3.group, panel3.group.id, @@ -2488,6 +2494,7 @@ describe('dockviewComponent', () => { ); expect(dockview.groups.length).toBe(2); + expect(dockview.element.querySelectorAll('.view').length).toBe(2); dockview.moveGroupOrPanel( panel3.group, @@ -2497,6 +2504,7 @@ describe('dockviewComponent', () => { ); expect(dockview.groups.length).toBe(3); + expect(dockview.element.querySelectorAll('.view').length).toBe(4); dockview.moveGroupOrPanel( panel2.group, @@ -2507,7 +2515,6 @@ describe('dockviewComponent', () => { expect(dockview.groups.length).toBe(2); - const viewQuery = container.querySelectorAll('.split-view-container'); - expect(viewQuery).toBeTruthy(); + expect(dockview.element.querySelectorAll('.view').length).toBe(2); }); }); diff --git a/packages/dockview-core/src/gridview/gridview.ts b/packages/dockview-core/src/gridview/gridview.ts index ee9d8f923..62c2ca9fb 100644 --- a/packages/dockview-core/src/gridview/gridview.ts +++ b/packages/dockview-core/src/gridview/gridview.ts @@ -678,60 +678,82 @@ export class Gridview implements IDisposable { throw new Error('Invalid location'); } - const node = parent.children[index]; + const nodeToRemove = parent.children[index]; - if (!(node instanceof LeafNode)) { + if (!(nodeToRemove instanceof LeafNode)) { throw new Error('Invalid location'); } parent.removeChild(index, sizing); + nodeToRemove.dispose(); - if (parent.children.length === 0) { - return node.view; + if (parent.children.length !== 1) { + return nodeToRemove.view; } - if (parent.children.length > 1) { - return node.view; - } + // if the parent has only one child and we know the parent is a BranchNode we can make the tree + // more efficiently spaced by replacing the parent BranchNode with the child. + // if that child is a LeafNode then we simply replace the BranchNode with the child otherwise if the child + // is a BranchNode too we should spread it's children into the grandparent. + // refer to the remaining child as the sibling const sibling = parent.children[0]; if (pathToParent.length === 0) { - // parent is root + // if the parent is root if (sibling instanceof LeafNode) { - return node.view; + // if the sibling is a leaf node no action is required + return nodeToRemove.view; } - // we must promote sibling to be the new root + // otherwise the sibling is a branch node. since the parent is the root and the root has only one child + // which is a branch node we can just set this branch node to be the new root node + + // for good housekeeping we'll removing the sibling from it's existing tree parent.removeChild(0, sizing); + + // and set that sibling node to be root this.root = sibling; - return node.view; + + return nodeToRemove.view; } + // otherwise the parent is apart of a large sub-tree + const [grandParent, ..._] = [...pathToParent].reverse(); const [parentIndex, ...__] = [...rest].reverse(); const isSiblingVisible = parent.isChildVisible(0); + + // either way we need to remove the sibling from it's existing tree parent.removeChild(0, sizing); + // note the sizes of all of the grandparents children const sizes = grandParent.children.map((_size, i) => grandParent.getChildSize(i) ); - grandParent.removeChild(parentIndex, sizing); + + // remove the parent from the grandparent since we are moving the sibling to take the parents place + // this parent is no longer used and can be disposed of + grandParent.removeChild(parentIndex, sizing).dispose(); if (sibling instanceof BranchNode) { + // replace the parent with the siblings children sizes.splice( parentIndex, 1, ...sibling.children.map((c) => c.size) ); + // and add those siblings to the grandparent for (let i = 0; i < sibling.children.length; i++) { const child = sibling.children[i]; grandParent.addChild(child, child.size, parentIndex + i); } } else { + // otherwise create a new leaf node and add that to the grandparent + const newSibling = new LeafNode( sibling.view, orthogonal(sibling.orientation), @@ -740,14 +762,19 @@ export class Gridview implements IDisposable { const siblingSizing = isSiblingVisible ? sibling.orthogonalSize : Sizing.Invisible(sibling.orthogonalSize); + grandParent.addChild(newSibling, siblingSizing, parentIndex); } + // the containing node of the sibling is no longer required and can be disposed of + sibling.dispose(); + + // resize everything for (let i = 0; i < sizes.length; i++) { grandParent.resizeChild(i, sizes[i]); } - return node.view; + return nodeToRemove.view; } public layout(width: number, height: number): void {