Merge branch 'master' of https://github.com/mathuo/dockview into 230-explore-floating-groups

This commit is contained in:
mathuo 2023-06-04 14:36:30 +01:00
commit bd5999b0ea
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
89 changed files with 706 additions and 367 deletions

View File

@ -23,10 +23,10 @@
"/packages/docs/sandboxes/tabheight-dockview", "/packages/docs/sandboxes/tabheight-dockview",
"/packages/docs/sandboxes/updatetitle-dockview", "/packages/docs/sandboxes/updatetitle-dockview",
"/packages/docs/sandboxes/watermark-dockview", "/packages/docs/sandboxes/watermark-dockview",
"/packages/docs/sandboxes/typescript/fullwidthtab-dockview", "/packages/docs/sandboxes/javascript/fullwidthtab-dockview",
"/packages/docs/sandboxes/typescript/simple-dockview", "/packages/docs/sandboxes/javascript/simple-dockview",
"/packages/docs/sandboxes/typescript/tabheight-dockview", "/packages/docs/sandboxes/javascript/tabheight-dockview",
"/packages/docs/sandboxes/typescript/vanilla-dockview" "/packages/docs/sandboxes/javascript/vanilla-dockview"
], ],
"node": "16" "node": "16"
} }

View File

@ -3,7 +3,7 @@
"packages/*" "packages/*"
], ],
"useWorkspaces": true, "useWorkspaces": true,
"version": "1.7.2", "version": "1.7.3",
"npmClient": "yarn", "npmClient": "yarn",
"command": { "command": {
"publish": { "publish": {

View File

@ -46,6 +46,7 @@
"gulp": "^4.0.2", "gulp": "^4.0.2",
"gulp-concat": "^2.6.1", "gulp-concat": "^2.6.1",
"gulp-dart-sass": "^1.0.2", "gulp-dart-sass": "^1.0.2",
"jest": "^29.5.0",
"jest-environment-jsdom": "^29.4.3", "jest-environment-jsdom": "^29.4.3",
"jest-sonar-reporter": "^2.0.0", "jest-sonar-reporter": "^2.0.0",
"jsdom": "^21.1.0", "jsdom": "^21.1.0",
@ -58,14 +59,12 @@
"ts-jest": "^29.0.5", "ts-jest": "^29.0.5",
"ts-loader": "^9.4.2", "ts-loader": "^9.4.2",
"tslib": "^2.5.0", "tslib": "^2.5.0",
"ts-node": "^10.9.1",
"typedoc": "^0.24.7", "typedoc": "^0.24.7",
"typescript": "^4.9.5", "typescript": "^4.9.5",
"webpack": "^5.75.0", "webpack": "^5.75.0",
"webpack-cli": "^5.0.1", "webpack-cli": "^5.0.1",
"webpack-dev-server": "^4.11.1" "webpack-dev-server": "^4.11.1"
}, },
"dependencies": { "dependencies": {}
"jest": "^29.5.0", }
"ts-node": "^10.9.1"
}
}

View File

@ -1,6 +1,6 @@
{ {
"name": "dockview-core", "name": "dockview-core",
"version": "1.7.2", "version": "1.7.3",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"main": "./dist/cjs/index.js", "main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts", "types": "./dist/cjs/index.d.ts",

View File

@ -46,6 +46,7 @@ class PanelContentPartTest implements IContentRenderer {
dispose(): void { dispose(): void {
this.isDisposed = true; this.isDisposed = true;
this._onDidDispose.fire(); this._onDidDispose.fire();
this._onDidDispose.dispose();
} }
} }
@ -80,6 +81,7 @@ class PanelTabPartTest implements ITabRenderer {
dispose(): void { dispose(): void {
this.isDisposed = true; this.isDisposed = true;
this._onDidDispose.fire(); this._onDidDispose.fire();
this._onDidDispose.dispose();
} }
} }
@ -98,6 +100,68 @@ describe('dockviewComponent', () => {
}); });
}); });
test('event leakage', () => {
Emitter.setLeakageMonitorEnabled(true);
dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
});
dockview.layout(500, 1000);
dockview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel2',
component: 'default',
});
dockview.removePanel(panel2);
const panel3 = dockview.addPanel({
id: 'panel3',
component: 'default',
position: {
direction: 'right',
referencePanel: 'panel1',
},
});
const panel4 = dockview.addPanel({
id: 'panel4',
component: 'default',
position: {
direction: 'above',
},
});
dockview.moveGroupOrPanel(
panel4.group,
panel3.group.id,
panel3.id,
'center'
);
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');
}
Emitter.setLeakageMonitorEnabled(false);
});
test('duplicate panel', () => { test('duplicate panel', () => {
dockview.layout(500, 1000); dockview.layout(500, 1000);
@ -112,6 +176,8 @@ describe('dockviewComponent', () => {
component: 'default', component: 'default',
}); });
}).toThrowError('panel with id panel1 already exists'); }).toThrowError('panel with id panel1 already exists');
dockview.dispose();
}); });
test('set active panel', () => { test('set active panel', () => {
@ -1285,21 +1351,21 @@ describe('dockviewComponent', () => {
tabComponent: 'default', tabComponent: 'default',
}); });
const panel2 = dockview.addPanel({ // const panel2 = dockview.addPanel({
id: 'panel2', // id: 'panel2',
component: 'default', // component: 'default',
tabComponent: 'default', // tabComponent: 'default',
}); // });
expect(panel1.group).toEqual(panel2.group); // expect(panel1.group).toEqual(panel2.group);
const panel1Spy = jest.spyOn(panel1, 'dispose'); const panel1Spy = jest.spyOn(panel1, 'dispose');
const panel2Spy = jest.spyOn(panel2, 'dispose'); // const panel2Spy = jest.spyOn(panel2, 'dispose');
dockview.dispose(); dockview.dispose();
expect(panel1Spy).toBeCalledTimes(1); expect(panel1Spy).toBeCalledTimes(1);
expect(panel2Spy).toBeCalledTimes(1); // expect(panel2Spy).toBeCalledTimes(1);
}); });
test('panel is disposed of when from JSON is called', () => { test('panel is disposed of when from JSON is called', () => {
@ -2295,4 +2361,91 @@ describe('dockviewComponent', () => {
panels: {}, panels: {},
}); });
}); });
test('that title and params.title do not conflict', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
components: {
default: PanelContentPartTest,
},
tabComponents: {
test_tab_id: PanelTabPartTest,
},
orientation: Orientation.HORIZONTAL,
});
dockview.layout(100, 100);
dockview.addPanel({
id: 'panel1',
component: 'default',
title: 'Panel 1',
params: {
title: 'Panel 1',
},
});
dockview.addPanel({
id: 'panel2',
component: 'default',
title: 'Panel 2',
});
dockview.addPanel({
id: 'panel3',
component: 'default',
params: {
title: 'Panel 3',
},
});
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
grid: {
root: {
type: 'branch',
data: [
{
type: 'leaf',
data: {
views: ['panel1', 'panel2', 'panel3'],
activeView: 'panel3',
id: '1',
},
size: 100,
},
],
size: 100,
},
width: 100,
height: 100,
orientation: 'HORIZONTAL',
},
panels: {
panel1: {
id: 'panel1',
contentComponent: 'default',
params: {
title: 'Panel 1',
},
title: 'Panel 1',
},
panel2: {
id: 'panel2',
contentComponent: 'default',
title: 'Panel 2',
},
panel3: {
id: 'panel3',
contentComponent: 'default',
params: {
title: 'Panel 3',
},
title: 'panel3',
},
},
activeGroup: '1',
});
});
}); });

View File

@ -37,7 +37,7 @@ describe('dockviewPanel', () => {
latestTitle = event.title; latestTitle = event.title;
}); });
expect(cut.title).toBe(''); expect(cut.title).toBeUndefined();
cut.init({ title: 'new title', params: {} }); cut.init({ title: 'new title', params: {} });
expect(latestTitle).toBe('new title'); expect(latestTitle).toBe('new title');

View File

@ -585,8 +585,15 @@ describe('splitview', () => {
expect(container.childNodes.length).toBeGreaterThan(0); expect(container.childNodes.length).toBeGreaterThan(0);
splitview.dispose(); let anyEvents = false;
const listener = splitview.onDidRemoveView((e) => {
anyEvents = true; // disposing of the splitview shouldn't fire onDidRemoveView events
});
splitview.dispose();
listener.dispose();
expect(anyEvents).toBeFalsy();
expect(container.childNodes.length).toBe(0); expect(container.childNodes.length).toBe(0);
}); });
}); });

View File

@ -1,4 +1,5 @@
import { PanelDimensionChangeEvent } from '../../api/panelApi'; import { PanelDimensionChangeEvent } from '../../api/panelApi';
import { Emitter } from '../../events';
import { CompositeDisposable } from '../../lifecycle'; import { CompositeDisposable } from '../../lifecycle';
import { Orientation } from '../../splitview/splitview'; import { Orientation } from '../../splitview/splitview';
import { SplitviewComponent } from '../../splitview/splitviewComponent'; import { SplitviewComponent } from '../../splitview/splitviewComponent';
@ -25,6 +26,45 @@ describe('componentSplitview', () => {
container.className = 'container'; container.className = 'container';
}); });
test('event leakage', () => {
Emitter.setLeakageMonitorEnabled(true);
const splitview = new SplitviewComponent({
parentElement: container,
orientation: Orientation.VERTICAL,
components: {
testPanel: TestPanel,
},
});
splitview.layout(600, 400);
const panel1 = splitview.addPanel({
id: 'panel1',
component: 'testPanel',
});
const panel2 = splitview.addPanel({
id: 'panel2',
component: 'testPanel',
});
splitview.movePanel(0, 1);
splitview.removePanel(panel1);
splitview.dispose();
if (Emitter.MEMORY_LEAK_WATCHER.size > 0) {
for (const entry of Array.from(
Emitter.MEMORY_LEAK_WATCHER.events
)) {
console.log(entry[1]);
}
throw new Error('not all listeners disposed');
}
Emitter.setLeakageMonitorEnabled(false);
});
test('remove panel', () => { test('remove panel', () => {
const splitview = new SplitviewComponent({ const splitview = new SplitviewComponent({
parentElement: container, parentElement: container,

View File

@ -19,7 +19,7 @@ export interface DockviewPanelApi
> { > {
readonly group: DockviewGroupPanel; readonly group: DockviewGroupPanel;
readonly isGroupActive: boolean; readonly isGroupActive: boolean;
readonly title: string; readonly title: string | undefined;
readonly onDidActiveGroupChange: Event<void>; readonly onDidActiveGroupChange: Event<void>;
readonly onDidGroupChange: Event<void>; readonly onDidGroupChange: Event<void>;
close(): void; close(): void;
@ -43,7 +43,7 @@ export class DockviewPanelApiImpl
private readonly disposable = new MutableDisposable(); private readonly disposable = new MutableDisposable();
get title(): string { get title(): string | undefined {
return this.panel.title; return this.panel.title;
} }

View File

@ -126,15 +126,6 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
super(); super();
this.addDisposables( this.addDisposables(
this.panelUpdatesDisposable,
this._onDidDimensionChange,
this._onDidChangeFocus,
this._onDidVisibilityChange,
this._onDidActiveChange,
this._onFocusEvent,
this._onActiveChange,
this._onVisibilityChange,
this._onUpdateParameters,
this.onDidFocusChange((event) => { this.onDidFocusChange((event) => {
this._isFocused = event.isFocused; this._isFocused = event.isFocused;
}), }),
@ -147,7 +138,16 @@ export class PanelApiImpl extends CompositeDisposable implements PanelApi {
this.onDidDimensionsChange((event) => { this.onDidDimensionsChange((event) => {
this._width = event.width; this._width = event.width;
this._height = event.height; this._height = event.height;
}) }),
this.panelUpdatesDisposable,
this._onDidDimensionChange,
this._onDidChangeFocus,
this._onDidVisibilityChange,
this._onDidActiveChange,
this._onFocusEvent,
this._onActiveChange,
this._onVisibilityChange,
this._onUpdateParameters
); );
} }

View File

@ -16,6 +16,9 @@ export abstract class DragHandler extends CompositeDisposable {
constructor(protected readonly el: HTMLElement) { constructor(protected readonly el: HTMLElement) {
super(); super();
this.addDisposables(this._onDragStart);
this.configure(); this.configure();
} }

View File

@ -182,6 +182,7 @@ export class Droptarget extends CompositeDisposable {
public dispose(): void { public dispose(): void {
this.removeDropTarget(); this.removeDropTarget();
super.dispose();
} }
private toggleClasses( private toggleClasses(

View File

@ -60,8 +60,4 @@ export class GroupDragHandler extends DragHandler {
}, },
}; };
} }
public dispose(): void {
//
}
} }

View File

@ -77,11 +77,12 @@ export class ContentContainer
const _onDidFocus = this.panel.view.content.onDidFocus; const _onDidFocus = this.panel.view.content.onDidFocus;
const _onDidBlur = this.panel.view.content.onDidBlur; const _onDidBlur = this.panel.view.content.onDidBlur;
const { onDidFocus, onDidBlur } = trackFocus(this._element); const focusTracker = trackFocus(this._element);
disposable.addDisposables( disposable.addDisposables(
onDidFocus(() => this._onDidFocus.fire()), focusTracker,
onDidBlur(() => this._onDidBlur.fire()) focusTracker.onDidFocus(() => this._onDidFocus.fire()),
focusTracker.onDidBlur(() => this._onDidBlur.fire())
); );
if (_onDidFocus) { if (_onDidFocus) {

View File

@ -13,7 +13,7 @@ import { DroptargetEvent, Droptarget } from '../../../dnd/droptarget';
import { DragHandler } from '../../../dnd/abstractDragHandler'; import { DragHandler } from '../../../dnd/abstractDragHandler';
import { DockviewPanel } from '../../dockviewPanel'; import { DockviewPanel } from '../../dockviewPanel';
export interface ITab { export interface ITab extends IDisposable {
readonly panelId: string; readonly panelId: string;
readonly element: HTMLElement; readonly element: HTMLElement;
setContent: (element: ITabRenderer) => void; setContent: (element: ITabRenderer) => void;
@ -44,8 +44,6 @@ export class Tab extends CompositeDisposable implements ITab {
) { ) {
super(); super();
this.addDisposables(this._onChanged, this._onDropped);
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'tab'; this._element.className = 'tab';
this._element.tabIndex = 0; this._element.tabIndex = 0;
@ -54,6 +52,8 @@ export class Tab extends CompositeDisposable implements ITab {
toggleClass(this.element, 'inactive-tab', true); toggleClass(this.element, 'inactive-tab', true);
this.addDisposables( this.addDisposables(
this._onChanged,
this._onDropped,
new (class Handler extends DragHandler { new (class Handler extends DragHandler {
private readonly panelTransfer = private readonly panelTransfer =
LocalSelectionTransfer.getInstance<PanelTransfer>(); LocalSelectionTransfer.getInstance<PanelTransfer>();
@ -72,10 +72,6 @@ export class Tab extends CompositeDisposable implements ITab {
}, },
}; };
} }
public dispose(): void {
//
}
})(this._element) })(this._element)
); );
@ -141,7 +137,8 @@ export class Tab extends CompositeDisposable implements ITab {
this.addDisposables( this.addDisposables(
this.droptarget.onDrop((event) => { this.droptarget.onDrop((event) => {
this._onDropped.fire(event); this._onDropped.fire(event);
}) }),
this.droptarget
); );
} }
@ -160,6 +157,5 @@ export class Tab extends CompositeDisposable implements ITab {
public dispose(): void { public dispose(): void {
super.dispose(); super.dispose();
this.droptarget.dispose();
} }
} }

View File

@ -216,6 +216,7 @@ export class TabsContainer
const { value, disposable } = tabToRemove; const { value, disposable } = tabToRemove;
disposable.dispose(); disposable.dispose();
value.dispose();
value.element.remove(); value.element.remove();
} }
@ -275,9 +276,11 @@ export class TabsContainer
public dispose(): void { public dispose(): void {
super.dispose(); super.dispose();
this.tabs.forEach((tab) => { for (const { value, disposable } of this.tabs) {
tab.disposable.dispose(); disposable.dispose();
}); value.dispose();
}
this.tabs = []; this.tabs = [];
} }
} }

View File

@ -262,7 +262,6 @@ export class DockviewComponent
}); });
this.addDisposables( this.addDisposables(
dropTarget,
dropTarget.onDrop((event) => { dropTarget.onDrop((event) => {
const data = getPanelData(); const data = getPanelData();
@ -281,7 +280,8 @@ export class DockviewComponent
getData: getPanelData, getData: getPanelData,
}); });
} }
}) }),
dropTarget
); );
this._api = new DockviewApi(this); this._api = new DockviewApi(this);
@ -827,43 +827,49 @@ export class DockviewComponent
} }
moveGroupOrPanel( moveGroupOrPanel(
referenceGroup: DockviewGroupPanel, destinationGroup: DockviewGroupPanel,
groupId: string, sourceGroupId: string,
itemId: string | undefined, sourceItemId: string | undefined,
target: Position, destinationTarget: Position,
index?: number destinationIndex?: number
): void { ): void {
const sourceGroup = groupId const sourceGroup = sourceGroupId
? this._groups.get(groupId)?.value ? this._groups.get(sourceGroupId)?.value
: undefined; : undefined;
if (itemId === undefined) { if (sourceItemId === undefined) {
if (sourceGroup) { if (sourceGroup) {
this.moveGroup(sourceGroup, referenceGroup, target); this.moveGroup(
sourceGroup,
destinationGroup,
destinationTarget
);
} }
return; return;
} }
if (!target || target === 'center') { if (!destinationTarget || destinationTarget === 'center') {
const groupItem: IDockviewPanel | undefined = const groupItem: IDockviewPanel | undefined =
sourceGroup?.model.removePanel(itemId) || sourceGroup?.model.removePanel(sourceItemId) ||
this.panels.find((panel) => panel.id === itemId); this.panels.find((panel) => panel.id === sourceItemId);
if (!groupItem) { if (!groupItem) {
throw new Error(`No panel with id ${itemId}`); throw new Error(`No panel with id ${sourceItemId}`);
} }
if (sourceGroup?.model.size === 0) { if (sourceGroup?.model.size === 0) {
this.doRemoveGroup(sourceGroup); this.doRemoveGroup(sourceGroup);
} }
referenceGroup.model.openPanel(groupItem, { index }); destinationGroup.model.openPanel(groupItem, {
index: destinationIndex,
});
} else { } else {
const referenceLocation = getGridLocation(referenceGroup.element); const referenceLocation = getGridLocation(destinationGroup.element);
const targetLocation = getRelativeLocation( const targetLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
target destinationTarget
); );
if (sourceGroup && sourceGroup.size < 2) { if (sourceGroup && sourceGroup.size < 2) {
@ -898,27 +904,27 @@ export class DockviewComponent
// after deleting the group we need to re-evaulate the ref location // after deleting the group we need to re-evaulate the ref location
const updatedReferenceLocation = getGridLocation( const updatedReferenceLocation = getGridLocation(
referenceGroup.element destinationGroup.element
); );
const location = getRelativeLocation( const location = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
updatedReferenceLocation, updatedReferenceLocation,
target destinationTarget
); );
this.doAddGroup(targetGroup, location); this.doAddGroup(targetGroup, location);
} else { } else {
const groupItem: IDockviewPanel | undefined = const groupItem: IDockviewPanel | undefined =
sourceGroup?.model.removePanel(itemId) || sourceGroup?.model.removePanel(sourceItemId) ||
this.panels.find((panel) => panel.id === itemId); this.panels.find((panel) => panel.id === sourceItemId);
if (!groupItem) { if (!groupItem) {
throw new Error(`No panel with id ${itemId}`); throw new Error(`No panel with id ${sourceItemId}`);
} }
const dropLocation = getRelativeLocation( const dropLocation = getRelativeLocation(
this.gridview.orientation, this.gridview.orientation,
referenceLocation, referenceLocation,
target destinationTarget
); );
const group = this.createGroupAtLocation(dropLocation); const group = this.createGroupAtLocation(dropLocation);
@ -1094,11 +1100,11 @@ export class DockviewComponent
} }
public dispose(): void { public dispose(): void {
super.dispose();
this._onDidActivePanelChange.dispose(); this._onDidActivePanelChange.dispose();
this._onDidAddPanel.dispose(); this._onDidAddPanel.dispose();
this._onDidRemovePanel.dispose(); this._onDidRemovePanel.dispose();
this._onDidLayoutFromJSON.dispose(); this._onDidLayoutFromJSON.dispose();
super.dispose();
} }
} }

View File

@ -297,12 +297,6 @@ export class DockviewGroupPanelModel
this.locked = !!options.locked; this.locked = !!options.locked;
this.addDisposables( this.addDisposables(
this._onMove,
this._onDidChange,
this._onDidDrop,
this._onDidAddPanel,
this._onDidRemovePanel,
this._onDidActivePanelChange,
this.tabsContainer.onDrop((event) => { this.tabsContainer.onDrop((event) => {
this.handleDropEvent(event.event, 'center', event.index); this.handleDropEvent(event.event, 'center', event.index);
}), }),
@ -314,7 +308,13 @@ export class DockviewGroupPanelModel
}), }),
this.dropTarget.onDrop((event) => { this.dropTarget.onDrop((event) => {
this.handleDropEvent(event.nativeEvent, event.position); this.handleDropEvent(event.nativeEvent, event.position);
}) }),
this._onMove,
this._onDidChange,
this._onDidDrop,
this._onDidAddPanel,
this._onDidRemovePanel,
this._onDidActivePanelChange
); );
} }

View File

@ -18,7 +18,7 @@ export interface IDockviewPanel extends IDisposable, IPanel {
readonly view: IDockviewPanelModel; readonly view: IDockviewPanelModel;
readonly group: DockviewGroupPanel; readonly group: DockviewGroupPanel;
readonly api: DockviewPanelApi; readonly api: DockviewPanelApi;
readonly title: string; readonly title: string | undefined;
readonly params: Record<string, any> | undefined; readonly params: Record<string, any> | undefined;
updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void; updateParentGroup(group: DockviewGroupPanel, isGroupActive: boolean): void;
init(params: IGroupPanelInitParameters): void; init(params: IGroupPanelInitParameters): void;
@ -34,13 +34,13 @@ export class DockviewPanel
private _group: DockviewGroupPanel; private _group: DockviewGroupPanel;
private _params?: Parameters; private _params?: Parameters;
private _title: string; private _title: string | undefined;
get params(): Parameters | undefined { get params(): Parameters | undefined {
return this._params; return this._params;
} }
get title(): string { get title(): string | undefined {
return this._title; return this._title;
} }
@ -56,7 +56,6 @@ export class DockviewPanel
readonly view: IDockviewPanelModel readonly view: IDockviewPanelModel
) { ) {
super(); super();
this._title = '';
this._group = group; this._group = group;
this.api = new DockviewPanelApiImpl(this, this._group); this.api = new DockviewPanelApiImpl(this, this._group);
@ -76,13 +75,13 @@ export class DockviewPanel
public init(params: IGroupPanelInitParameters): void { public init(params: IGroupPanelInitParameters): void {
this._params = params.params; this._params = params.params;
this.setTitle(params.title);
this.view.init({ this.view.init({
...params, ...params,
api: this.api, api: this.api,
containerApi: this.containerApi, containerApi: this.containerApi,
}); });
this.setTitle(params.title);
} }
focus(): void { focus(): void {
@ -103,12 +102,12 @@ export class DockviewPanel
} }
setTitle(title: string): void { setTitle(title: string): void {
const didTitleChange = title !== this._params?.title; const didTitleChange = title !== this.title;
if (didTitleChange) { if (didTitleChange) {
this._title = title; this._title = title;
this.view?.update({ this.view.update({
params: { params: {
params: this._params, params: this._params,
title: this.title, title: this.title,
@ -128,10 +127,10 @@ export class DockviewPanel
if (params.title !== this.title) { if (params.title !== this.title) {
this._title = params.title; this._title = params.title;
this.api._onDidTitleChange.fire({ title: this.title }); this.api._onDidTitleChange.fire({ title: params.title });
} }
this.view?.update({ this.view.update({
params: { params: {
params: this._params, params: this._params,
title: this.title, title: this.title,

View File

@ -111,6 +111,8 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
constructor(element: HTMLElement | Window) { constructor(element: HTMLElement | Window) {
super(); super();
this.addDisposables(this._onDidFocus, this._onDidBlur);
let hasFocus = isAncestor(document.activeElement, <HTMLElement>element); let hasFocus = isAncestor(document.activeElement, <HTMLElement>element);
let loosingFocus = false; let loosingFocus = false;
@ -169,11 +171,4 @@ class FocusTracker extends CompositeDisposable implements IFocusTracker {
refreshState(): void { refreshState(): void {
this._refreshStateHandler(); this._refreshStateHandler();
} }
public dispose(): void {
super.dispose();
this._onDidBlur.dispose();
this._onDidFocus.dispose();
}
} }

View File

@ -24,24 +24,76 @@ export namespace Event {
}; };
} }
// dumb event emitter with better typings than nodes event emitter class LeakageMonitor {
// https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts readonly events = new Map<Event<any>, Stacktrace>();
get size(): number {
return this.events.size;
}
add<T>(event: Event<T>, stacktrace: Stacktrace): void {
this.events.set(event, stacktrace);
}
delete<T>(event: Event<T>): void {
this.events.delete(event);
}
clear(): void {
this.events.clear();
}
}
class Stacktrace {
static create(): Stacktrace {
return new Stacktrace(new Error().stack ?? '');
}
private constructor(readonly value: string) {}
print(): void {
console.warn(this.value);
}
}
class Listener<T> {
constructor(
readonly callback: (t: T) => void,
readonly stacktrace: Stacktrace | undefined
) {}
}
// relatively simple event emitter taken from https://github.com/microsoft/vscode/blob/master/src/vs/base/common/event.ts
export class Emitter<T> implements IDisposable { export class Emitter<T> implements IDisposable {
private _event?: Event<T>; private _event?: Event<T>;
private _last?: T; private _last?: T;
private _listeners: Array<(e: T) => any> = []; private _listeners: Listener<any>[] = [];
private _disposed = false; private _disposed = false;
static ENABLE_TRACKING = false;
static readonly MEMORY_LEAK_WATCHER = new LeakageMonitor();
static setLeakageMonitorEnabled(isEnabled: boolean) {
if (isEnabled !== Emitter.ENABLE_TRACKING) {
Emitter.MEMORY_LEAK_WATCHER.clear();
}
Emitter.ENABLE_TRACKING = isEnabled;
}
constructor(private readonly options?: EmitterOptions) {} constructor(private readonly options?: EmitterOptions) {}
get event(): Event<T> { get event(): Event<T> {
if (!this._event) { if (!this._event) {
this._event = (listener: (e: T) => void): IDisposable => { this._event = (callback: (e: T) => void): IDisposable => {
if (this.options?.replay && this._last !== undefined) { if (this.options?.replay && this._last !== undefined) {
listener(this._last); callback(this._last);
} }
const listener = new Listener(
callback,
Emitter.ENABLE_TRACKING ? Stacktrace.create() : undefined
);
this._listeners.push(listener); this._listeners.push(listener);
return { return {
@ -49,10 +101,22 @@ export class Emitter<T> implements IDisposable {
const index = this._listeners.indexOf(listener); const index = this._listeners.indexOf(listener);
if (index > -1) { if (index > -1) {
this._listeners.splice(index, 1); this._listeners.splice(index, 1);
} else if (Emitter.ENABLE_TRACKING) {
console.warn(
`Listener already disposed`,
Stacktrace.create().print()
);
} }
}, },
}; };
}; };
if (Emitter.ENABLE_TRACKING) {
Emitter.MEMORY_LEAK_WATCHER.add(
this._event,
Stacktrace.create()
);
}
} }
return this._event; return this._event;
} }
@ -60,13 +124,31 @@ export class Emitter<T> implements IDisposable {
public fire(e: T): void { public fire(e: T): void {
this._last = e; this._last = e;
for (const listener of this._listeners) { for (const listener of this._listeners) {
listener(e); listener.callback(e);
} }
} }
public dispose(): void { public dispose(): void {
this._listeners = []; if (!this._disposed) {
this._disposed = true; this._disposed = true;
if (this._listeners.length > 0) {
if (Emitter.ENABLE_TRACKING) {
queueMicrotask(() => {
// don't check until stack of execution is completed to allow for out-of-order disposals within the same execution block
for (const listener of this._listeners) {
console.warn(listener.stacktrace?.print());
}
});
}
this._listeners = [];
}
if (Emitter.ENABLE_TRACKING && this._event) {
Emitter.MEMORY_LEAK_WATCHER.delete(this._event);
}
}
} }
} }

View File

@ -143,10 +143,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
this.addDisposables( this.addDisposables(
this.gridview.onDidChange(() => { this.gridview.onDidChange(() => {
this._bufferOnDidLayoutChange.fire(); this._bufferOnDidLayoutChange.fire();
}) }),
);
this.addDisposables(
Event.any( Event.any(
this.onDidAddGroup, this.onDidAddGroup,
this.onDidRemoveGroup, this.onDidRemoveGroup,
@ -297,8 +294,6 @@ export abstract class BaseGrid<T extends IGridPanelView>
} }
public dispose(): void { public dispose(): void {
super.dispose();
this._onDidActiveGroupChange.dispose(); this._onDidActiveGroupChange.dispose();
this._onDidAddGroup.dispose(); this._onDidAddGroup.dispose();
this._onDidRemoveGroup.dispose(); this._onDidRemoveGroup.dispose();
@ -309,5 +304,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
} }
this.gridview.dispose(); this.gridview.dispose();
super.dispose();
} }
} }

View File

@ -68,16 +68,17 @@ export abstract class BasePanelView<T extends PanelApiImpl>
this._element.style.width = '100%'; this._element.style.width = '100%';
this._element.style.overflow = 'hidden'; this._element.style.overflow = 'hidden';
const { onDidFocus, onDidBlur } = trackFocus(this._element); const focusTracker = trackFocus(this._element);
this.addDisposables( this.addDisposables(
this.api, this.api,
onDidFocus(() => { focusTracker.onDidFocus(() => {
this.api._onDidChangeFocus.fire({ isFocused: true }); this.api._onDidChangeFocus.fire({ isFocused: true });
}), }),
onDidBlur(() => { focusTracker.onDidBlur(() => {
this.api._onDidChangeFocus.fire({ isFocused: false }); this.api._onDidChangeFocus.fire({ isFocused: false });
}) }),
focusTracker
); );
} }
@ -124,9 +125,9 @@ export abstract class BasePanelView<T extends PanelApiImpl>
} }
dispose(): void { dispose(): void {
super.dispose();
this.api.dispose(); this.api.dispose();
this.part?.dispose(); this.part?.dispose();
super.dispose();
} }
} }

View File

@ -260,13 +260,13 @@ export class BranchNode extends CompositeDisposable implements IView {
return this.splitview.getViewCachedVisibleSize(index); return this.splitview.getViewCachedVisibleSize(index);
} }
public removeChild(index: number, sizing?: Sizing): void { public removeChild(index: number, sizing?: Sizing): Node {
if (index < 0 || index >= this.children.length) { if (index < 0 || index >= this.children.length) {
throw new Error('Invalid index'); throw new Error('Invalid index');
} }
this.splitview.removeView(index, sizing); this.splitview.removeView(index, sizing);
this._removeChild(index); return this._removeChild(index);
} }
private _addChild(node: Node, index: number): void { private _addChild(node: Node, index: number): void {
@ -296,9 +296,10 @@ export class BranchNode extends CompositeDisposable implements IView {
} }
public dispose(): void { public dispose(): void {
super.dispose();
this._childrenDisposable.dispose(); this._childrenDisposable.dispose();
this.children.forEach((child) => child.dispose());
this.splitview.dispose(); this.splitview.dispose();
this.children.forEach((child) => child.dispose());
super.dispose();
} }
} }

View File

@ -462,7 +462,8 @@ export class Gridview implements IDisposable {
if (oldRoot.children.length === 1) { if (oldRoot.children.length === 1) {
// can remove one level of redundant branching if there is only a single child // can remove one level of redundant branching if there is only a single child
const childReference = oldRoot.children[0]; const childReference = oldRoot.children[0];
oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root const child = oldRoot.removeChild(0); // remove to prevent disposal when disposing of unwanted root
child.dispose();
oldRoot.dispose(); oldRoot.dispose();
this._root.addChild( this._root.addChild(
@ -632,7 +633,8 @@ export class Gridview implements IDisposable {
newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize); newSiblingSize = Sizing.Invisible(newSiblingCachedVisibleSize);
} }
grandParent.removeChild(parentIndex); const child = grandParent.removeChild(parentIndex);
child.dispose();
const newParent = new BranchNode( const newParent = new BranchNode(
parent.orientation, parent.orientation,
@ -682,14 +684,18 @@ export class Gridview implements IDisposable {
throw new Error('Invalid location'); throw new Error('Invalid location');
} }
parent.removeChild(index, sizing); const view = node.view;
node.dispose(); // dispose of node
const child = parent.removeChild(index, sizing);
child.dispose();
if (parent.children.length === 0) { if (parent.children.length === 0) {
return node.view; return view;
} }
if (parent.children.length > 1) { if (parent.children.length > 1) {
return node.view; return view;
} }
const sibling = parent.children[0]; const sibling = parent.children[0];
@ -698,25 +704,28 @@ export class Gridview implements IDisposable {
// parent is root // parent is root
if (sibling instanceof LeafNode) { if (sibling instanceof LeafNode) {
return node.view; return view;
} }
// we must promote sibling to be the new root // we must promote sibling to be the new root
parent.removeChild(0, sizing); const child = parent.removeChild(0, sizing);
child.dispose();
this.root = sibling; this.root = sibling;
return node.view; return view;
} }
const [grandParent, ..._] = [...pathToParent].reverse(); const [grandParent, ..._] = [...pathToParent].reverse();
const [parentIndex, ...__] = [...rest].reverse(); const [parentIndex, ...__] = [...rest].reverse();
const isSiblingVisible = parent.isChildVisible(0); const isSiblingVisible = parent.isChildVisible(0);
parent.removeChild(0, sizing); const childNode = parent.removeChild(0, sizing);
childNode.dispose();
const sizes = grandParent.children.map((_size, i) => const sizes = grandParent.children.map((_size, i) =>
grandParent.getChildSize(i) grandParent.getChildSize(i)
); );
grandParent.removeChild(parentIndex, sizing); const parentNode = grandParent.removeChild(parentIndex, sizing);
parentNode.dispose();
if (sibling instanceof BranchNode) { if (sibling instanceof BranchNode) {
sizes.splice( sizes.splice(
@ -745,7 +754,7 @@ export class Gridview implements IDisposable {
grandParent.resizeChild(i, sizes[i]); grandParent.resizeChild(i, sizes[i]);
} }
return node.view; return view;
} }
public layout(width: number, height: number): void { public layout(width: number, height: number): void {

View File

@ -154,7 +154,6 @@ export abstract class GridviewPanel
this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement this.api.initialize(this); // TODO: required to by-pass 'super before this' requirement
this.addDisposables( this.addDisposables(
this._onDidChange,
this.api.onVisibilityChange((event) => { this.api.onVisibilityChange((event) => {
const { isVisible } = event; const { isVisible } = event;
const { accessor } = this._params as GridviewInitParameters; const { accessor } = this._params as GridviewInitParameters;
@ -195,7 +194,8 @@ export abstract class GridviewPanel
height: event.height, height: event.height,
width: event.width, width: event.width,
}); });
}) }),
this._onDidChange
); );
} }

View File

@ -16,7 +16,7 @@ export namespace Disposable {
} }
export class CompositeDisposable { export class CompositeDisposable {
private readonly disposables: IDisposable[]; private readonly _disposables: IDisposable[];
private _isDisposed = false; private _isDisposed = false;
protected get isDisposed(): boolean { protected get isDisposed(): boolean {
@ -28,15 +28,15 @@ export class CompositeDisposable {
} }
constructor(...args: IDisposable[]) { constructor(...args: IDisposable[]) {
this.disposables = args; this._disposables = args;
} }
public addDisposables(...args: IDisposable[]): void { public addDisposables(...args: IDisposable[]): void {
args.forEach((arg) => this.disposables.push(arg)); args.forEach((arg) => this._disposables.push(arg));
} }
public dispose(): void { public dispose(): void {
this.disposables.forEach((arg) => arg.dispose()); this._disposables.forEach((arg) => arg.dispose());
this._isDisposed = true; this._isDisposed = true;
} }

View File

@ -13,6 +13,7 @@ import { Event, Emitter } from '../events';
import { pushToStart, pushToEnd, firstIndex } from '../array'; import { pushToStart, pushToEnd, firstIndex } from '../array';
import { range, clamp } from '../math'; import { range, clamp } from '../math';
import { ViewItem } from './viewItem'; import { ViewItem } from './viewItem';
import { IDisposable } from '../lifecycle';
export enum Orientation { export enum Orientation {
HORIZONTAL = 'HORIZONTAL', HORIZONTAL = 'HORIZONTAL',
@ -42,7 +43,7 @@ export enum LayoutPriority {
Normal = 'normal', Normal = 'normal',
} }
export interface IBaseView { export interface IBaseView extends IDisposable {
minimumSize: number; minimumSize: number;
maximumSize: number; maximumSize: number;
snap?: boolean; snap?: boolean;
@ -97,7 +98,7 @@ export class Splitview {
private element: HTMLElement; private element: HTMLElement;
private viewContainer: HTMLElement; private viewContainer: HTMLElement;
private sashContainer: HTMLElement; private sashContainer: HTMLElement;
private views: ViewItem[] = []; private viewItems: ViewItem[] = [];
private sashes: ISashItem[] = []; private sashes: ISashItem[] = [];
private _orientation: Orientation; private _orientation: Orientation;
private _size = 0; private _size = 0;
@ -132,7 +133,7 @@ export class Splitview {
} }
public get length(): number { public get length(): number {
return this.views.length; return this.viewItems.length;
} }
public get proportions(): number[] | undefined { public get proportions(): number[] | undefined {
@ -159,13 +160,13 @@ export class Splitview {
} }
get minimumSize(): number { get minimumSize(): number {
return this.views.reduce((r, item) => r + item.minimumSize, 0); return this.viewItems.reduce((r, item) => r + item.minimumSize, 0);
} }
get maximumSize(): number { get maximumSize(): number {
return this.length === 0 return this.length === 0
? Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY
: this.views.reduce((r, item) => r + item.maximumSize, 0); : this.viewItems.reduce((r, item) => r + item.maximumSize, 0);
} }
get startSnappingEnabled(): boolean { get startSnappingEnabled(): boolean {
@ -240,7 +241,7 @@ export class Splitview {
}); });
// Initialize content size and proportions for first layout // Initialize content size and proportions for first layout
this.contentSize = this.views.reduce((r, i) => r + i.size, 0); this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.saveProportions(); this.saveProportions();
} }
} }
@ -261,22 +262,22 @@ export class Splitview {
} }
isViewVisible(index: number): boolean { isViewVisible(index: number): boolean {
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds'); throw new Error('Index out of bounds');
} }
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
return viewItem.visible; return viewItem.visible;
} }
setViewVisible(index: number, visible: boolean): void { setViewVisible(index: number, visible: boolean): void {
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds'); throw new Error('Index out of bounds');
} }
toggleClass(this.container, 'visible', visible); toggleClass(this.container, 'visible', visible);
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
toggleClass(this.container, 'visible', visible); toggleClass(this.container, 'visible', visible);
@ -288,30 +289,30 @@ export class Splitview {
} }
getViewSize(index: number): number { getViewSize(index: number): number {
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
return -1; return -1;
} }
return this.views[index].size; return this.viewItems[index].size;
} }
resizeView(index: number, size: number): void { resizeView(index: number, size: number): void {
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
return; return;
} }
const indexes = range(this.views.length).filter((i) => i !== index); const indexes = range(this.viewItems.length).filter((i) => i !== index);
const lowPriorityIndexes = [ const lowPriorityIndexes = [
...indexes.filter( ...indexes.filter(
(i) => this.views[i].priority === LayoutPriority.Low (i) => this.viewItems[i].priority === LayoutPriority.Low
), ),
index, index,
]; ];
const highPriorityIndexes = indexes.filter( const highPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.High (i) => this.viewItems[i].priority === LayoutPriority.High
); );
const item = this.views[index]; const item = this.viewItems[index];
size = Math.round(size); size = Math.round(size);
size = clamp( size = clamp(
size, size,
@ -324,13 +325,13 @@ export class Splitview {
} }
public getViews<T extends IView>(): T[] { public getViews<T extends IView>(): T[] {
return this.views.map((x) => x.view as T); return this.viewItems.map((x) => x.view as T);
} }
private onDidChange(item: ViewItem, size: number | undefined): void { private onDidChange(item: ViewItem, size: number | undefined): void {
const index = this.views.indexOf(item); const index = this.viewItems.indexOf(item);
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
return; return;
} }
@ -345,7 +346,7 @@ export class Splitview {
public addView( public addView(
view: IView, view: IView,
size: number | Sizing = { type: 'distribute' }, size: number | Sizing = { type: 'distribute' },
index: number = this.views.length, index: number = this.viewItems.length,
skipLayout?: boolean skipLayout?: boolean
): void { ): void {
const container = document.createElement('div'); const container = document.createElement('div');
@ -369,14 +370,14 @@ export class Splitview {
this.onDidChange(viewItem, newSize.size) this.onDidChange(viewItem, newSize.size)
); );
const dispose = () => { const viewItem = new ViewItem(container, view, viewSize, {
disposable?.dispose(); dispose: () => {
this.viewContainer.removeChild(container); disposable.dispose();
}; this.viewContainer.removeChild(container);
},
});
const viewItem = new ViewItem(container, view, viewSize, { dispose }); if (index === this.viewItems.length) {
if (index === this.views.length) {
this.viewContainer.appendChild(container); this.viewContainer.appendChild(container);
} else { } else {
this.viewContainer.insertBefore( this.viewContainer.insertBefore(
@ -385,15 +386,15 @@ export class Splitview {
); );
} }
this.views.splice(index, 0, viewItem); this.viewItems.splice(index, 0, viewItem);
if (this.views.length > 1) { if (this.viewItems.length > 1) {
//add sash //add sash
const sash = document.createElement('div'); const sash = document.createElement('div');
sash.className = 'sash'; sash.className = 'sash';
const onStart = (event: MouseEvent) => { const onStart = (event: MouseEvent) => {
for (const item of this.views) { for (const item of this.viewItems) {
item.enabled = false; item.enabled = false;
} }
@ -417,19 +418,20 @@ export class Splitview {
); );
// //
const sizes = this.views.map((x) => x.size); const sizes = this.viewItems.map((x) => x.size);
// //
let snapBefore: ISashDragSnapState | undefined; let snapBefore: ISashDragSnapState | undefined;
let snapAfter: ISashDragSnapState | undefined; let snapAfter: ISashDragSnapState | undefined;
const upIndexes = range(sashIndex, -1); const upIndexes = range(sashIndex, -1);
const downIndexes = range(sashIndex + 1, this.views.length); const downIndexes = range(sashIndex + 1, this.viewItems.length);
const minDeltaUp = upIndexes.reduce( const minDeltaUp = upIndexes.reduce(
(r, i) => r + (this.views[i].minimumSize - sizes[i]), (r, i) => r + (this.viewItems[i].minimumSize - sizes[i]),
0 0
); );
const maxDeltaUp = upIndexes.reduce( const maxDeltaUp = upIndexes.reduce(
(r, i) => r + (this.views[i].viewMaximumSize - sizes[i]), (r, i) =>
r + (this.viewItems[i].viewMaximumSize - sizes[i]),
0 0
); );
const maxDeltaDown = const maxDeltaDown =
@ -437,7 +439,8 @@ export class Splitview {
? Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY
: downIndexes.reduce( : downIndexes.reduce(
(r, i) => (r, i) =>
r + (sizes[i] - this.views[i].minimumSize), r +
(sizes[i] - this.viewItems[i].minimumSize),
0 0
); );
const minDeltaDown = const minDeltaDown =
@ -446,7 +449,8 @@ export class Splitview {
: downIndexes.reduce( : downIndexes.reduce(
(r, i) => (r, i) =>
r + r +
(sizes[i] - this.views[i].viewMaximumSize), (sizes[i] -
this.viewItems[i].viewMaximumSize),
0 0
); );
const minDelta = Math.max(minDeltaUp, minDeltaDown); const minDelta = Math.max(minDeltaUp, minDeltaDown);
@ -454,7 +458,7 @@ export class Splitview {
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
const snapAfterIndex = this.findFirstSnapIndex(downIndexes); const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
if (typeof snapBeforeIndex === 'number') { if (typeof snapBeforeIndex === 'number') {
const snappedViewItem = this.views[snapBeforeIndex]; const snappedViewItem = this.viewItems[snapBeforeIndex];
const halfSize = Math.floor( const halfSize = Math.floor(
snappedViewItem.viewMinimumSize / 2 snappedViewItem.viewMinimumSize / 2
); );
@ -469,7 +473,7 @@ export class Splitview {
} }
if (typeof snapAfterIndex === 'number') { if (typeof snapAfterIndex === 'number') {
const snappedViewItem = this.views[snapAfterIndex]; const snappedViewItem = this.viewItems[snapAfterIndex];
const halfSize = Math.floor( const halfSize = Math.floor(
snappedViewItem.viewMinimumSize / 2 snappedViewItem.viewMinimumSize / 2
); );
@ -507,7 +511,7 @@ export class Splitview {
}; };
const end = () => { const end = () => {
for (const item of this.views) { for (const item of this.viewItems) {
item.enabled = true; item.enabled = true;
} }
@ -562,7 +566,7 @@ export class Splitview {
const flexibleViewItems: ViewItem[] = []; const flexibleViewItems: ViewItem[] = [];
let flexibleSize = 0; let flexibleSize = 0;
for (const item of this.views) { for (const item of this.viewItems) {
if (item.maximumSize - item.minimumSize > 0) { if (item.maximumSize - item.minimumSize > 0) {
flexibleViewItems.push(item); flexibleViewItems.push(item);
flexibleSize += item.size; flexibleSize += item.size;
@ -575,12 +579,12 @@ export class Splitview {
item.size = clamp(size, item.minimumSize, item.maximumSize); item.size = clamp(size, item.minimumSize, item.maximumSize);
} }
const indexes = range(this.views.length); const indexes = range(this.viewItems.length);
const lowPriorityIndexes = indexes.filter( const lowPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.Low (i) => this.viewItems[i].priority === LayoutPriority.Low
); );
const highPriorityIndexes = indexes.filter( const highPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.High (i) => this.viewItems[i].priority === LayoutPriority.High
); );
this.relayout(lowPriorityIndexes, highPriorityIndexes); this.relayout(lowPriorityIndexes, highPriorityIndexes);
@ -592,11 +596,11 @@ export class Splitview {
skipLayout = false skipLayout = false
): IView { ): IView {
// Remove view // Remove view
const viewItem = this.views.splice(index, 1)[0]; const viewItem = this.viewItems.splice(index, 1)[0];
viewItem.dispose(); viewItem.dispose();
// Remove sash // Remove sash
if (this.views.length >= 1) { if (this.viewItems.length >= 1) {
const sashIndex = Math.max(index - 1, 0); const sashIndex = Math.max(index - 1, 0);
const sashItem = this.sashes.splice(sashIndex, 1)[0]; const sashItem = this.sashes.splice(sashIndex, 1)[0];
sashItem.disposable(); sashItem.disposable();
@ -616,11 +620,11 @@ export class Splitview {
} }
getViewCachedVisibleSize(index: number): number | undefined { getViewCachedVisibleSize(index: number): number | undefined {
if (index < 0 || index >= this.views.length) { if (index < 0 || index >= this.viewItems.length) {
throw new Error('Index out of bounds'); throw new Error('Index out of bounds');
} }
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
return viewItem.cachedVisibleSize; return viewItem.cachedVisibleSize;
} }
@ -640,24 +644,24 @@ export class Splitview {
this.orthogonalSize = orthogonalSize; this.orthogonalSize = orthogonalSize;
if (!this.proportions) { if (!this.proportions) {
const indexes = range(this.views.length); const indexes = range(this.viewItems.length);
const lowPriorityIndexes = indexes.filter( const lowPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.Low (i) => this.viewItems[i].priority === LayoutPriority.Low
); );
const highPriorityIndexes = indexes.filter( const highPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.High (i) => this.viewItems[i].priority === LayoutPriority.High
); );
this.resize( this.resize(
this.views.length - 1, this.viewItems.length - 1,
size - previousSize, size - previousSize,
undefined, undefined,
lowPriorityIndexes, lowPriorityIndexes,
highPriorityIndexes highPriorityIndexes
); );
} else { } else {
for (let i = 0; i < this.views.length; i++) { for (let i = 0; i < this.viewItems.length; i++) {
const item = this.views[i]; const item = this.viewItems[i];
item.size = clamp( item.size = clamp(
Math.round(this.proportions[i] * size), Math.round(this.proportions[i] * size),
@ -675,10 +679,10 @@ export class Splitview {
lowPriorityIndexes?: number[], lowPriorityIndexes?: number[],
highPriorityIndexes?: number[] highPriorityIndexes?: number[]
): void { ): void {
const contentSize = this.views.reduce((r, i) => r + i.size, 0); const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
this.resize( this.resize(
this.views.length - 1, this.viewItems.length - 1,
this._size - contentSize, this._size - contentSize,
undefined, undefined,
lowPriorityIndexes, lowPriorityIndexes,
@ -690,15 +694,15 @@ export class Splitview {
} }
private distributeEmptySpace(lowPriorityIndex?: number): void { private distributeEmptySpace(lowPriorityIndex?: number): void {
const contentSize = this.views.reduce((r, i) => r + i.size, 0); const contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let emptyDelta = this.size - contentSize; let emptyDelta = this.size - contentSize;
const indexes = range(this.views.length - 1, -1); const indexes = range(this.viewItems.length - 1, -1);
const lowPriorityIndexes = indexes.filter( const lowPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.Low (i) => this.viewItems[i].priority === LayoutPriority.Low
); );
const highPriorityIndexes = indexes.filter( const highPriorityIndexes = indexes.filter(
(i) => this.views[i].priority === LayoutPriority.High (i) => this.viewItems[i].priority === LayoutPriority.High
); );
for (const index of highPriorityIndexes) { for (const index of highPriorityIndexes) {
@ -714,7 +718,7 @@ export class Splitview {
} }
for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) { for (let i = 0; emptyDelta !== 0 && i < indexes.length; i++) {
const item = this.views[indexes[i]]; const item = this.viewItems[indexes[i]];
const size = clamp( const size = clamp(
item.size + emptyDelta, item.size + emptyDelta,
item.minimumSize, item.minimumSize,
@ -729,21 +733,21 @@ export class Splitview {
private saveProportions(): void { private saveProportions(): void {
if (this.proportionalLayout && this.contentSize > 0) { if (this.proportionalLayout && this.contentSize > 0) {
this._proportions = this.views.map( this._proportions = this.viewItems.map(
(i) => i.size / this.contentSize (i) => i.size / this.contentSize
); );
} }
} }
private layoutViews(): void { private layoutViews(): void {
this.contentSize = this.views.reduce((r, i) => r + i.size, 0); this.contentSize = this.viewItems.reduce((r, i) => r + i.size, 0);
let sum = 0; let sum = 0;
const x: number[] = []; const x: number[] = [];
this.updateSashEnablement(); this.updateSashEnablement();
for (let i = 0; i < this.views.length - 1; i++) { for (let i = 0; i < this.viewItems.length - 1; i++) {
sum += this.views[i].size; sum += this.viewItems[i].size;
x.push(sum); x.push(sum);
const offset = Math.min(Math.max(0, sum - 2), this.size - 4); const offset = Math.min(Math.max(0, sum - 2), this.size - 4);
@ -757,7 +761,7 @@ export class Splitview {
this.sashes[i].container.style.top = `${offset}px`; this.sashes[i].container.style.top = `${offset}px`;
} }
} }
this.views.forEach((view, i) => { this.viewItems.forEach((view, i) => {
if (this._orientation === Orientation.HORIZONTAL) { if (this._orientation === Orientation.HORIZONTAL) {
view.container.style.width = `${view.size}px`; view.container.style.width = `${view.size}px`;
view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`; view.container.style.left = i == 0 ? '0px' : `${x[i - 1]}px`;
@ -778,7 +782,7 @@ export class Splitview {
private findFirstSnapIndex(indexes: number[]): number | undefined { private findFirstSnapIndex(indexes: number[]): number | undefined {
// visible views first // visible views first
for (const index of indexes) { for (const index of indexes) {
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
if (!viewItem.visible) { if (!viewItem.visible) {
continue; continue;
@ -791,7 +795,7 @@ export class Splitview {
// then, hidden views // then, hidden views
for (const index of indexes) { for (const index of indexes) {
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
if ( if (
viewItem.visible && viewItem.visible &&
@ -810,16 +814,16 @@ export class Splitview {
private updateSashEnablement(): void { private updateSashEnablement(): void {
let previous = false; let previous = false;
const collapsesDown = this.views.map( const collapsesDown = this.viewItems.map(
(i) => (previous = i.size - i.minimumSize > 0 || previous) (i) => (previous = i.size - i.minimumSize > 0 || previous)
); );
previous = false; previous = false;
const expandsDown = this.views.map( const expandsDown = this.viewItems.map(
(i) => (previous = i.maximumSize - i.size > 0 || previous) (i) => (previous = i.maximumSize - i.size > 0 || previous)
); );
const reverseViews = [...this.views].reverse(); const reverseViews = [...this.viewItems].reverse();
previous = false; previous = false;
const collapsesUp = reverseViews const collapsesUp = reverseViews
.map((i) => (previous = i.size - i.minimumSize > 0 || previous)) .map((i) => (previous = i.size - i.minimumSize > 0 || previous))
@ -833,7 +837,7 @@ export class Splitview {
let position = 0; let position = 0;
for (let index = 0; index < this.sashes.length; index++) { for (let index = 0; index < this.sashes.length; index++) {
const sash = this.sashes[index]; const sash = this.sashes[index];
const viewItem = this.views[index]; const viewItem = this.viewItems[index];
position += viewItem.size; position += viewItem.size;
const min = !(collapsesDown[index] && expandsUp[index + 1]); const min = !(collapsesDown[index] && expandsUp[index + 1]);
@ -841,16 +845,16 @@ export class Splitview {
if (min && max) { if (min && max) {
const upIndexes = range(index, -1); const upIndexes = range(index, -1);
const downIndexes = range(index + 1, this.views.length); const downIndexes = range(index + 1, this.viewItems.length);
const snapBeforeIndex = this.findFirstSnapIndex(upIndexes); const snapBeforeIndex = this.findFirstSnapIndex(upIndexes);
const snapAfterIndex = this.findFirstSnapIndex(downIndexes); const snapAfterIndex = this.findFirstSnapIndex(downIndexes);
const snappedBefore = const snappedBefore =
typeof snapBeforeIndex === 'number' && typeof snapBeforeIndex === 'number' &&
!this.views[snapBeforeIndex].visible; !this.viewItems[snapBeforeIndex].visible;
const snappedAfter = const snappedAfter =
typeof snapAfterIndex === 'number' && typeof snapAfterIndex === 'number' &&
!this.views[snapAfterIndex].visible; !this.viewItems[snapAfterIndex].visible;
if ( if (
snappedBefore && snappedBefore &&
@ -887,7 +891,7 @@ export class Splitview {
private resize = ( private resize = (
index: number, index: number,
delta: number, delta: number,
sizes: number[] = this.views.map((x) => x.size), sizes: number[] = this.viewItems.map((x) => x.size),
lowPriorityIndexes?: number[], lowPriorityIndexes?: number[],
highPriorityIndexes?: number[], highPriorityIndexes?: number[],
overloadMinDelta: number = Number.NEGATIVE_INFINITY, overloadMinDelta: number = Number.NEGATIVE_INFINITY,
@ -895,12 +899,12 @@ export class Splitview {
snapBefore?: ISashDragSnapState, snapBefore?: ISashDragSnapState,
snapAfter?: ISashDragSnapState snapAfter?: ISashDragSnapState
): number => { ): number => {
if (index < 0 || index > this.views.length) { if (index < 0 || index > this.viewItems.length) {
return 0; return 0;
} }
const upIndexes = range(index, -1); const upIndexes = range(index, -1);
const downIndexes = range(index + 1, this.views.length); const downIndexes = range(index + 1, this.viewItems.length);
// //
if (highPriorityIndexes) { if (highPriorityIndexes) {
for (const i of highPriorityIndexes) { for (const i of highPriorityIndexes) {
@ -916,18 +920,18 @@ export class Splitview {
} }
} }
// //
const upItems = upIndexes.map((i) => this.views[i]); const upItems = upIndexes.map((i) => this.viewItems[i]);
const upSizes = upIndexes.map((i) => sizes[i]); const upSizes = upIndexes.map((i) => sizes[i]);
// //
const downItems = downIndexes.map((i) => this.views[i]); const downItems = downIndexes.map((i) => this.viewItems[i]);
const downSizes = downIndexes.map((i) => sizes[i]); const downSizes = downIndexes.map((i) => sizes[i]);
// //
const minDeltaUp = upIndexes.reduce( const minDeltaUp = upIndexes.reduce(
(_, i) => _ + this.views[i].minimumSize - sizes[i], (_, i) => _ + this.viewItems[i].minimumSize - sizes[i],
0 0
); );
const maxDeltaUp = upIndexes.reduce( const maxDeltaUp = upIndexes.reduce(
(_, i) => _ + this.views[i].maximumSize - sizes[i], (_, i) => _ + this.viewItems[i].maximumSize - sizes[i],
0 0
); );
// //
@ -935,7 +939,7 @@ export class Splitview {
downIndexes.length === 0 downIndexes.length === 0
? Number.POSITIVE_INFINITY ? Number.POSITIVE_INFINITY
: downIndexes.reduce( : downIndexes.reduce(
(_, i) => _ + sizes[i] - this.views[i].minimumSize, (_, i) => _ + sizes[i] - this.viewItems[i].minimumSize,
0 0
); );
@ -943,7 +947,7 @@ export class Splitview {
downIndexes.length === 0 downIndexes.length === 0
? Number.NEGATIVE_INFINITY ? Number.NEGATIVE_INFINITY
: downIndexes.reduce( : downIndexes.reduce(
(_, i) => _ + sizes[i] - this.views[i].maximumSize, (_, i) => _ + sizes[i] - this.viewItems[i].maximumSize,
0 0
); );
// //
@ -952,14 +956,14 @@ export class Splitview {
// //
let snapped = false; let snapped = false;
if (snapBefore) { if (snapBefore) {
const snapView = this.views[snapBefore.index]; const snapView = this.viewItems[snapBefore.index];
const visible = delta >= snapBefore.limitDelta; const visible = delta >= snapBefore.limitDelta;
snapped = visible !== snapView.visible; snapped = visible !== snapView.visible;
snapView.setVisible(visible, snapBefore.size); snapView.setVisible(visible, snapBefore.size);
} }
if (!snapped && snapAfter) { if (!snapped && snapAfter) {
const snapView = this.views[snapAfter.index]; const snapView = this.viewItems[snapAfter.index];
const visible = delta < snapAfter.limitDelta; const visible = delta < snapAfter.limitDelta;
snapped = visible !== snapView.visible; snapped = visible !== snapView.visible;
snapView.setVisible(visible, snapAfter.size); snapView.setVisible(visible, snapAfter.size);
@ -1047,6 +1051,10 @@ export class Splitview {
} }
} }
for (const viewItem of this.viewItems) {
viewItem.dispose();
}
this.element.remove(); this.element.remove();
} }
} }

View File

@ -1,7 +1,6 @@
import { import {
CompositeDisposable, CompositeDisposable,
IDisposable, IDisposable,
IValueDisposable,
MutableDisposable, MutableDisposable,
} from '../lifecycle'; } from '../lifecycle';
import { import {
@ -83,10 +82,10 @@ export class SplitviewComponent
extends Resizable extends Resizable
implements ISplitviewComponent implements ISplitviewComponent
{ {
private _disposable = new MutableDisposable(); private _splitviewChangeDisposable = new MutableDisposable();
private _splitview!: Splitview; private _splitview!: Splitview;
private _activePanel: SplitviewPanel | undefined; private _activePanel: SplitviewPanel | undefined;
private _panels = new Map<string, IValueDisposable<SplitviewPanel>>(); private _panels = new Map<string, IDisposable>();
private _options: SplitviewComponentOptions; private _options: SplitviewComponentOptions;
private readonly _onDidLayoutfromJSON = new Emitter<void>(); private readonly _onDidLayoutfromJSON = new Emitter<void>();
@ -124,7 +123,7 @@ export class SplitviewComponent
set splitview(value: Splitview) { set splitview(value: Splitview) {
this._splitview = value; this._splitview = value;
this._disposable.value = new CompositeDisposable( this._splitviewChangeDisposable.value = new CompositeDisposable(
this._splitview.onDidSashEnd(() => { this._splitview.onDidSashEnd(() => {
this._onDidLayoutChange.fire(undefined); this._onDidLayoutChange.fire(undefined);
}), }),
@ -170,7 +169,6 @@ export class SplitviewComponent
this.splitview = new Splitview(this.element, options); this.splitview = new Splitview(this.element, options);
this.addDisposables( this.addDisposables(
this._disposable,
this._onDidAddView, this._onDidAddView,
this._onDidLayoutfromJSON, this._onDidLayoutfromJSON,
this._onDidRemoveView, this._onDidRemoveView,
@ -226,19 +224,19 @@ export class SplitviewComponent
} }
removePanel(panel: SplitviewPanel, sizing?: Sizing): void { removePanel(panel: SplitviewPanel, sizing?: Sizing): void {
const disposable = this._panels.get(panel.id); const item = this._panels.get(panel.id);
if (!disposable) { if (!item) {
throw new Error(`unknown splitview panel ${panel.id}`); throw new Error(`unknown splitview panel ${panel.id}`);
} }
disposable.disposable.dispose(); item.dispose();
disposable.value.dispose();
this._panels.delete(panel.id); this._panels.delete(panel.id);
const index = this.panels.findIndex((_) => _ === panel); const index = this.panels.findIndex((_) => _ === panel);
this.splitview.removeView(index, sizing); const removedView = this.splitview.removeView(index, sizing);
removedView.dispose();
const panels = this.panels; const panels = this.panels;
if (panels.length > 0) { if (panels.length > 0) {
@ -250,7 +248,7 @@ export class SplitviewComponent
return this.panels.find((view) => view.id === id); return this.panels.find((view) => view.id === id);
} }
addPanel(options: AddSplitviewComponentOptions): ISplitviewPanel { addPanel(options: AddSplitviewComponentOptions): SplitviewPanel {
if (this._panels.has(options.id)) { if (this._panels.has(options.id)) {
throw new Error(`panel ${options.id} already exists`); throw new Error(`panel ${options.id} already exists`);
} }
@ -308,7 +306,7 @@ export class SplitviewComponent
this.setActive(view, true); this.setActive(view, true);
}); });
this._panels.set(view.id, { disposable, value: view }); this._panels.set(view.id, disposable);
} }
toJSON(): SerializedSplitview { toJSON(): SerializedSplitview {
@ -404,23 +402,34 @@ export class SplitviewComponent
} }
clear(): void { clear(): void {
for (const [_, value] of this._panels.entries()) { for (const disposable of this._panels.values()) {
value.disposable.dispose(); disposable.dispose();
value.value.dispose();
} }
this._panels.clear(); this._panels.clear();
this.splitview.dispose();
while (this.splitview.length > 0) {
const view = this.splitview.removeView(0, Sizing.Distribute, true);
view.dispose();
}
} }
dispose(): void { dispose(): void {
for (const [_, value] of this._panels.entries()) { for (const disposable of this._panels.values()) {
value.disposable.dispose(); disposable.dispose();
value.value.dispose();
} }
this._panels.clear(); this._panels.clear();
const views = this.splitview.getViews();
this._splitviewChangeDisposable.dispose();
this.splitview.dispose(); this.splitview.dispose();
for (const view of views) {
view.dispose();
}
super.dispose(); super.dispose();
} }
} }

View File

@ -7,6 +7,7 @@ import { SplitviewPanelApiImpl } from '../api/splitviewPanelApi';
import { LayoutPriority, Orientation } from './splitview'; import { LayoutPriority, Orientation } from './splitview';
import { FunctionOrValue } from '../types'; import { FunctionOrValue } from '../types';
import { Emitter, Event } from '../events'; import { Emitter, Event } from '../events';
import { CompositeDisposable } from '../lifecycle';
export interface ISplitviewPanel export interface ISplitviewPanel
extends BasePanelViewExported<SplitviewPanelApiImpl> { extends BasePanelViewExported<SplitviewPanelApiImpl> {

View File

@ -1,6 +1,6 @@
{ {
"name": "dockview", "name": "dockview",
"version": "1.7.2", "version": "1.7.3",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support", "description": "Zero dependency layout manager supporting tabs, grids and splitviews with ReactJS support",
"main": "./dist/cjs/index.js", "main": "./dist/cjs/index.js",
"types": "./dist/cjs/index.d.ts", "types": "./dist/cjs/index.d.ts",
@ -56,7 +56,7 @@
"author": "https://github.com/mathuo", "author": "https://github.com/mathuo",
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
"dockview-core": "^1.7.2" "dockview-core": "^1.7.3"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^15.0.1", "@rollup/plugin-node-resolve": "^15.0.1",

View File

@ -0,0 +1,17 @@
---
slug: dockview-1.7.3-release
title: Dockview 1.7.3
tags: [release]
---
# Release Notes
Please reference to docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
## 🛠 Miscs
- Fix bug custom params named 'title' conflicting with built-in tab 'title' object [#258](https://github.com/mathuo/dockview/issues/258)
## 🔥 Breaking changes

View File

@ -1,6 +1,6 @@
{ {
"name": "dockview-docs", "name": "dockview-docs",
"version": "1.7.2", "version": "1.7.3",
"private": true, "private": true,
"scripts": { "scripts": {
"docusaurus": "docusaurus", "docusaurus": "docusaurus",
@ -22,7 +22,7 @@
"@minoru/react-dnd-treeview": "^3.4.3", "@minoru/react-dnd-treeview": "^3.4.3",
"axios": "^1.3.3", "axios": "^1.3.3",
"clsx": "^1.2.1", "clsx": "^1.2.1",
"dockview": "^1.7.2", "dockview": "^1.7.3",
"prism-react-renderer": "^1.3.5", "prism-react-renderer": "^1.3.5",
"react": "^18.2.0", "react": "^18.2.0",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -16,7 +16,8 @@
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"@types/uuid": "^9.0.0", "@types/uuid": "^9.0.0",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -16,7 +16,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -10,7 +10,8 @@
"dockview-core": "*" "dockview-core": "*"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -10,7 +10,8 @@
"dockview-core": "*" "dockview-core": "*"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -10,7 +10,8 @@
"dockview-core": "*" "dockview-core": "*"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -1,5 +1,5 @@
{ {
"name": "vanilla-dockview", "name": "javascript-vanilla-dockview",
"description": "", "description": "",
"keywords": [ "keywords": [
"dockview" "dockview"
@ -10,9 +10,15 @@
"dockview-core": "*" "dockview-core": "*"
}, },
"devDependencies": { "devDependencies": {
"typescript": "^4.9.5" "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"
}, },
"scripts": {},
"browserslist": [ "browserslist": [
">0.2%", ">0.2%",
"not dead", "not dead",

View File

@ -1,19 +1,18 @@
{ {
"compilerOptions": { "compilerOptions": {
"outDir": "build/dist", "outDir": "build/dist",
"module": "esnext", "module": "esnext",
"target": "es5", "target": "es5",
"lib": ["es6", "dom"], "lib": ["es6", "dom"],
"sourceMap": true, "sourceMap": true,
"allowJs": true, "allowJs": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"moduleResolution": "node", "moduleResolution": "node",
"rootDir": "src", "rootDir": "src",
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true }
}
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",
@ -28,4 +29,4 @@
"not ie <= 11", "not ie <= 11",
"not op_mini all" "not op_mini all"
] ]
} }

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -14,7 +14,8 @@
"devDependencies": { "devDependencies": {
"@types/react": "^18.0.28", "@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11", "@types/react-dom": "^18.0.11",
"typescript": "^4.9.5" "typescript": "^4.9.5",
"react-scripts": "*"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "react-scripts start",

View File

@ -13,7 +13,6 @@
"noImplicitReturns": true, "noImplicitReturns": true,
"noImplicitThis": true, "noImplicitThis": true,
"noImplicitAny": true, "noImplicitAny": true,
"strictNullChecks": true, "strictNullChecks": true
"noUnusedLocals": true
} }
} }

View File

@ -8,7 +8,6 @@ import { SimpleSplitview2 } from '@site/src/components/simpleSplitview2';
# Basics # Basics
asd
This section will take you through a number of concepts that can be applied to all dockview components. This section will take you through a number of concepts that can be applied to all dockview components.
## Panels ## Panels

View File

@ -2,7 +2,10 @@
description: Dockview Documentation description: Dockview Documentation
--- ---
import { Container } from '@site/src/components/ui/container'; import {
Container,
MultiFrameworkContainer,
} from '@site/src/components/ui/container';
import Link from '@docusaurus/Link'; import Link from '@docusaurus/Link';
import useBaseUrl from '@docusaurus/useBaseUrl'; import useBaseUrl from '@docusaurus/useBaseUrl';
@ -24,7 +27,11 @@ import RenderingDockview from '@site/sandboxes/rendering-dockview/src/app';
import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app'; import DockviewExternalDnd from '@site/sandboxes/externaldnd-dockview/src/app';
import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app'; import DockviewResizeContainer from '@site/sandboxes/resizecontainer-dockview/src/app';
import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app'; import DockviewTabheight from '@site/sandboxes/tabheight-dockview/src/app';
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app'; import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
import { attach as attachTabHeightDockview } from '@site/sandboxes/javascript/tabheight-dockview/src/app';
import { attach as attachNativeDockview } from '@site/sandboxes/javascript/fullwidthtab-dockview/src/app';
# Dockview # Dockview
@ -32,12 +39,16 @@ import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vani
Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels. Dockview is an abstraction built on top of [Gridviews](./gridview) where each view is a container of many tabbed panels.
<Container sandboxId="simple-dockview"> <MultiFrameworkContainer
<SimpleDockview /> sandboxId="simple-dockview"
</Container> react={SimpleDockview}
typescript={attachSimpleDockview}
/>
You can access the panels associated group through the `panel.group` variable. <br />
The group will always be defined and will change if a panel is moved into another group.
> You can access the panels associated group through the `panel.group` variable.
> The group will always be defined and will change if a panel is moved into another group.
## DockviewReact Component ## DockviewReact Component
@ -340,7 +351,9 @@ return (
### Third Party Dnd Libraries ### Third Party Dnd Libraries
To be completed... This shows a simple example of a third-party library used inside a panel that relies on drag
and drop functionalities. This examples serves to show that `dockview` doesn't interfer with
any drag and drop logic for other controls.
<Container> <Container>
<DockviewExternalDnd /> <DockviewExternalDnd />
@ -606,15 +619,21 @@ to the entire width of the group. For example:
<DockviewReactComponent singleTabMode="fullwidth" {...otherProps} /> <DockviewReactComponent singleTabMode="fullwidth" {...otherProps} />
``` ```
<Container sandboxId="fullwidthtab-dockview"> <MultiFrameworkContainer
<DockviewNative /> sandboxId="fullwidthtab-dockview"
</Container> react={DockviewNative}
typescript={attachNativeDockview}
/>
### Tab Height ### Tab Height
<Container sandboxId="tabheight-dockview"> Tab height can be controlled through CSS.
<DockviewTabheight />
</Container> <MultiFrameworkContainer
sandboxId="tabheight-dockview"
react={DockviewTabheight}
typescript={attachTabHeightDockview}
/>
## Groups ## Groups
@ -705,19 +724,11 @@ If you wish to interact with the drop event from one dockview instance in anothe
<NestedDockview /> <NestedDockview />
</Container> </Container>
### Example ### Window-like mananger with tabs
hello
<DockviewNative2 /> <DockviewNative2 />
hello 2 ## Vanilla JS
<div style={{ height: '400px', width: '100%' }}>
<App />
</div>
## VanillaJS
> Note: This section is experimental and support for Vanilla JS is a work in progress. > Note: This section is experimental and support for Vanilla JS is a work in progress.
@ -728,6 +739,6 @@ The core library is published as an independant package under the name `dockview
> `dockview-core` is a dependency of `dockview` and automatically installed during the installation process of `dockview` via `npm install dockview`. > `dockview-core` is a dependency of `dockview` and automatically installed during the installation process of `dockview` via `npm install dockview`.
<Container <Container
sandboxId="javascript/vanilla-dockview" sandboxId="typescript/vanilla-dockview"
injectVanillaJS={attachDockviewVanilla} injectVanillaJS={attachDockviewVanilla}
/> />

View File

@ -1,3 +1,3 @@
[ [
"1.7.2" "1.7.3"
] ]