mirror of
https://github.com/mathuo/dockview
synced 2025-01-23 01:45:58 +00:00
feat: gready render mode
This commit is contained in:
parent
b17aa24637
commit
a2a4e68166
@ -23,6 +23,7 @@
|
||||
"/packages/docs/sandboxes/nativeapp-dockview",
|
||||
"/packages/docs/sandboxes/nested-dockview",
|
||||
"/packages/docs/sandboxes/rendering-dockview",
|
||||
"/packages/docs/sandboxes/rendermode-dockview",
|
||||
"/packages/docs/sandboxes/resize-dockview",
|
||||
"/packages/docs/sandboxes/resizecontainer-dockview",
|
||||
"/packages/docs/sandboxes/simple-dockview",
|
||||
|
@ -36,7 +36,7 @@ describe('groupPanelApi', () => {
|
||||
});
|
||||
|
||||
test('updateParameters', () => {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
const groupPanel: Partial<DockviewPanel> = {
|
||||
id: 'test_id',
|
||||
update: jest.fn(),
|
||||
};
|
||||
@ -53,7 +53,7 @@ describe('groupPanelApi', () => {
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<IDockviewPanel>groupPanel,
|
||||
<DockviewPanel>groupPanel,
|
||||
<DockviewGroupPanel>groupViewPanel,
|
||||
<DockviewComponent>accessor
|
||||
);
|
||||
@ -67,7 +67,7 @@ describe('groupPanelApi', () => {
|
||||
});
|
||||
|
||||
test('onDidGroupChange', () => {
|
||||
const groupPanel: Partial<IDockviewPanel> = {
|
||||
const groupPanel: Partial<DockviewPanel> = {
|
||||
id: 'test_id',
|
||||
};
|
||||
|
||||
@ -83,7 +83,7 @@ describe('groupPanelApi', () => {
|
||||
);
|
||||
|
||||
const cut = new DockviewPanelApiImpl(
|
||||
<IDockviewPanel>groupPanel,
|
||||
<DockviewPanel>groupPanel,
|
||||
<DockviewGroupPanel>groupViewPanel,
|
||||
<DockviewComponent>accessor
|
||||
);
|
||||
|
@ -9,6 +9,7 @@ import { CompositeDisposable } from '../../../../lifecycle';
|
||||
import { PanelUpdateEvent } from '../../../../panel/types';
|
||||
import { IDockviewPanel } from '../../../../dockview/dockviewPanel';
|
||||
import { IDockviewPanelModel } from '../../../../dockview/dockviewPanelModel';
|
||||
import { DockviewComponent } from '../../../../dockview/dockviewComponent';
|
||||
|
||||
class TestContentRenderer
|
||||
extends CompositeDisposable
|
||||
@ -56,7 +57,14 @@ describe('contentContainer', () => {
|
||||
let blur = 0;
|
||||
|
||||
const disposable = new CompositeDisposable();
|
||||
const cut = new ContentContainer();
|
||||
|
||||
const dockviewComponent = jest.fn<DockviewComponent, []>(() => {
|
||||
return {
|
||||
renderer: 'destructive',
|
||||
} as DockviewComponent;
|
||||
});
|
||||
|
||||
const cut = new ContentContainer(dockviewComponent(), jest.fn() as any);
|
||||
|
||||
disposable.addDisposables(
|
||||
cut.onDidFocus(() => {
|
||||
@ -73,6 +81,7 @@ describe('contentContainer', () => {
|
||||
view: {
|
||||
content: contentRenderer,
|
||||
} as Partial<IDockviewPanelModel>,
|
||||
api: { renderer: 'destructive' },
|
||||
} as Partial<IDockviewPanel>;
|
||||
|
||||
cut.openPanel(panel as IDockviewPanel);
|
||||
@ -107,6 +116,7 @@ describe('contentContainer', () => {
|
||||
view: {
|
||||
content: contentRenderer2,
|
||||
} as Partial<IDockviewPanelModel>,
|
||||
api: { renderer: 'destructive' },
|
||||
} as Partial<IDockviewPanel>;
|
||||
|
||||
cut.openPanel(panel2 as IDockviewPanel);
|
||||
|
@ -257,9 +257,15 @@ describe('groupview', () => {
|
||||
});
|
||||
|
||||
test('panel events are captured during de-serialization', () => {
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
const panel1 = new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel2 = new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel3 = new TestPanel('panel3', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
const groupview2 = new DockviewGroupPanel(dockview, 'groupview-2', {
|
||||
panels: [panel1, panel2, panel3],
|
||||
@ -343,9 +349,15 @@ describe('groupview', () => {
|
||||
})
|
||||
);
|
||||
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
const panel1 = new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel2 = new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel3 = new TestPanel('panel3', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
expect(events.length).toBe(0);
|
||||
|
||||
@ -423,9 +435,15 @@ describe('groupview', () => {
|
||||
});
|
||||
|
||||
test('moveToPrevious and moveToNext', () => {
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
const panel1 = new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel2 = new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel3 = new TestPanel('panel3', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
groupview.model.openPanel(panel1);
|
||||
groupview.model.openPanel(panel2);
|
||||
@ -469,9 +487,15 @@ describe('groupview', () => {
|
||||
});
|
||||
|
||||
test('closeAllPanels with panels', () => {
|
||||
const panel1 = new TestPanel('panel1', jest.fn() as any);
|
||||
const panel2 = new TestPanel('panel2', jest.fn() as any);
|
||||
const panel3 = new TestPanel('panel3', jest.fn() as any);
|
||||
const panel1 = new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel2 = new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
const panel3 = new TestPanel('panel3', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
groupview.model.openPanel(panel1);
|
||||
groupview.model.openPanel(panel2);
|
||||
@ -576,19 +600,25 @@ describe('groupview', () => {
|
||||
.getElementsByClassName('content-container')
|
||||
.item(0)!.childNodes;
|
||||
|
||||
const panel1 = new TestPanel('id_1', null as any);
|
||||
const panel1 = new TestPanel('id_1', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
cut.openPanel(panel1);
|
||||
expect(contentContainer.length).toBe(1);
|
||||
expect(contentContainer.item(0)).toBe(panel1.view.content.element);
|
||||
|
||||
const panel2 = new TestPanel('id_2', null as any);
|
||||
const panel2 = new TestPanel('id_2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
cut.openPanel(panel2);
|
||||
expect(contentContainer.length).toBe(1);
|
||||
expect(contentContainer.item(0)).toBe(panel2.view.content.element);
|
||||
|
||||
const panel3 = new TestPanel('id_2', null as any);
|
||||
const panel3 = new TestPanel('id_2', {
|
||||
renderer: 'destructive',
|
||||
} as any);
|
||||
|
||||
cut.openPanel(panel3, { skipSetPanelActive: true });
|
||||
expect(contentContainer.length).toBe(1);
|
||||
@ -790,7 +820,11 @@ describe('groupview', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('content-container')
|
||||
@ -856,8 +890,16 @@ describe('groupview', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
cut.openPanel(
|
||||
new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('content-container')
|
||||
@ -923,8 +965,16 @@ describe('groupview', () => {
|
||||
new groupPanelMock() as DockviewGroupPanel
|
||||
);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
cut.openPanel(
|
||||
new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
const element = container
|
||||
.getElementsByClassName('content-container')
|
||||
@ -1025,7 +1075,11 @@ describe('groupview', () => {
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
@ -1035,7 +1089,11 @@ describe('groupview', () => {
|
||||
.length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel2', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel2', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
@ -1053,7 +1111,11 @@ describe('groupview', () => {
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
).toBe(1);
|
||||
|
||||
cut.openPanel(new TestPanel('panel1', jest.fn() as any));
|
||||
cut.openPanel(
|
||||
new TestPanel('panel1', {
|
||||
renderer: 'destructive',
|
||||
} as any)
|
||||
);
|
||||
|
||||
expect(
|
||||
container.getElementsByClassName('watermark-test-container').length
|
||||
|
@ -29,7 +29,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
let latestTitle: string | undefined = undefined;
|
||||
|
||||
@ -74,7 +76,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
cut.init({ title: 'myTitle', params: {} });
|
||||
expect(cut.title).toBe('myTitle');
|
||||
@ -109,7 +113,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
cut.init({ params: {}, title: 'title' });
|
||||
|
||||
@ -141,7 +147,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
expect(cut.params).toEqual(undefined);
|
||||
|
||||
@ -177,7 +185,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
cut.api.setSize({ height: 123, width: 456 });
|
||||
|
||||
@ -208,7 +218,9 @@ describe('dockviewPanel', () => {
|
||||
const group = new groupMock();
|
||||
const model = <IDockviewPanelModel>new panelModelMock();
|
||||
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model);
|
||||
const cut = new DockviewPanel('fake-id', accessor, api, group, model, {
|
||||
renderer: 'destructive',
|
||||
});
|
||||
|
||||
cut.init({ params: { a: '1', b: '2' }, title: 'A title' });
|
||||
expect(cut.params).toEqual({ a: '1', b: '2' });
|
||||
|
@ -2,14 +2,19 @@ import { Emitter, Event } from '../events';
|
||||
import { GridviewPanelApiImpl, GridviewPanelApi } from './gridviewPanelApi';
|
||||
import { DockviewGroupPanel } from '../dockview/dockviewGroupPanel';
|
||||
import { MutableDisposable } from '../lifecycle';
|
||||
import { IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { DockviewPanel, IDockviewPanel } from '../dockview/dockviewPanel';
|
||||
import { DockviewComponent } from '../dockview/dockviewComponent';
|
||||
import { Position } from '../dnd/droptarget';
|
||||
import { DockviewPanelRenderer } from '../dockview/components/greadyRenderContainer';
|
||||
|
||||
export interface TitleEvent {
|
||||
readonly title: string;
|
||||
}
|
||||
|
||||
export interface RendererChangedEvent {
|
||||
renderer: DockviewPanelRenderer;
|
||||
}
|
||||
|
||||
/*
|
||||
* omit visibility modifiers since the visibility of a single group doesn't make sense
|
||||
* because it belongs to a groupview
|
||||
@ -21,11 +26,14 @@ export interface DockviewPanelApi
|
||||
> {
|
||||
readonly group: DockviewGroupPanel;
|
||||
readonly isGroupActive: boolean;
|
||||
readonly renderer: DockviewPanelRenderer;
|
||||
readonly title: string | undefined;
|
||||
readonly onDidActiveGroupChange: Event<void>;
|
||||
readonly onDidGroupChange: Event<void>;
|
||||
readonly onDidRendererChange: Event<RendererChangedEvent>;
|
||||
close(): void;
|
||||
setTitle(title: string): void;
|
||||
setRenderer(renderer: DockviewPanelRenderer): void;
|
||||
moveTo(options: {
|
||||
group: DockviewGroupPanel;
|
||||
position?: Position;
|
||||
@ -48,6 +56,9 @@ export class DockviewPanelApiImpl
|
||||
private readonly _onDidGroupChange = new Emitter<void>();
|
||||
readonly onDidGroupChange = this._onDidGroupChange.event;
|
||||
|
||||
readonly _onDidRendererChange = new Emitter<RendererChangedEvent>();
|
||||
readonly onDidRendererChange = this._onDidRendererChange.event;
|
||||
|
||||
private readonly disposable = new MutableDisposable();
|
||||
|
||||
get title(): string | undefined {
|
||||
@ -58,6 +69,10 @@ export class DockviewPanelApiImpl
|
||||
return !!this.group?.isActive;
|
||||
}
|
||||
|
||||
get renderer(): DockviewPanelRenderer {
|
||||
return this.panel.renderer;
|
||||
}
|
||||
|
||||
set group(value: DockviewGroupPanel) {
|
||||
const isOldGroupActive = this.isGroupActive;
|
||||
|
||||
@ -81,7 +96,7 @@ export class DockviewPanelApiImpl
|
||||
}
|
||||
|
||||
constructor(
|
||||
private panel: IDockviewPanel,
|
||||
private panel: DockviewPanel,
|
||||
group: DockviewGroupPanel,
|
||||
private readonly accessor: DockviewComponent
|
||||
) {
|
||||
@ -93,6 +108,7 @@ export class DockviewPanelApiImpl
|
||||
|
||||
this.addDisposables(
|
||||
this.disposable,
|
||||
this._onDidRendererChange,
|
||||
this._onDidTitleChange,
|
||||
this._onDidGroupChange,
|
||||
this._onDidActiveGroupChange
|
||||
@ -117,6 +133,10 @@ export class DockviewPanelApiImpl
|
||||
this.panel.setTitle(title);
|
||||
}
|
||||
|
||||
setRenderer(renderer: DockviewPanelRenderer): void {
|
||||
this.panel.setRenderer(renderer);
|
||||
}
|
||||
|
||||
close(): void {
|
||||
this.group.model.closePanel(this.panel);
|
||||
}
|
||||
|
@ -21,14 +21,43 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
this.registerListeners();
|
||||
}
|
||||
|
||||
onDragEnter(e: DragEvent): void {
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
}
|
||||
|
||||
onDragOver(e: DragEvent): void {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
}
|
||||
|
||||
onDragLeave(e: DragEvent): void {
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
}
|
||||
|
||||
onDragEnd(e: DragEvent): void {
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
}
|
||||
|
||||
onDrop(e: DragEvent): void {
|
||||
this.callbacks.onDrop(e);
|
||||
}
|
||||
|
||||
private registerListeners(): void {
|
||||
this.addDisposables(
|
||||
addDisposableListener(
|
||||
this.element,
|
||||
'dragenter',
|
||||
(e: DragEvent) => {
|
||||
this.target = e.target;
|
||||
this.callbacks.onDragEnter(e);
|
||||
this.onDragEnter(e);
|
||||
},
|
||||
true
|
||||
)
|
||||
@ -39,11 +68,7 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
this.element,
|
||||
'dragover',
|
||||
(e: DragEvent) => {
|
||||
e.preventDefault(); // needed so that the drop event fires (https://stackoverflow.com/questions/21339924/drop-event-not-firing-in-chrome)
|
||||
|
||||
if (this.callbacks.onDragOver) {
|
||||
this.callbacks.onDragOver(e);
|
||||
}
|
||||
this.onDragOver(e);
|
||||
},
|
||||
true
|
||||
)
|
||||
@ -51,24 +76,19 @@ export class DragAndDropObserver extends CompositeDisposable {
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragleave', (e: DragEvent) => {
|
||||
if (this.target === e.target) {
|
||||
this.target = null;
|
||||
|
||||
this.callbacks.onDragLeave(e);
|
||||
}
|
||||
this.onDragLeave(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'dragend', (e: DragEvent) => {
|
||||
this.target = null;
|
||||
this.callbacks.onDragEnd(e);
|
||||
this.onDragEnd(e);
|
||||
})
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.element, 'drop', (e: DragEvent) => {
|
||||
this.callbacks.onDrop(e);
|
||||
this.onDrop(e);
|
||||
})
|
||||
);
|
||||
}
|
||||
|
@ -63,6 +63,8 @@ export class Droptarget extends CompositeDisposable {
|
||||
private readonly _onDrop = new Emitter<DroptargetEvent>();
|
||||
readonly onDrop: Event<DroptargetEvent> = this._onDrop.event;
|
||||
|
||||
readonly dnd: DragAndDropObserver;
|
||||
|
||||
private static USED_EVENT_ID = '__dockview_droptarget_event_is_used__';
|
||||
|
||||
get state(): Position | undefined {
|
||||
@ -90,9 +92,7 @@ export class Droptarget extends CompositeDisposable {
|
||||
this.options.acceptedTargetZones
|
||||
);
|
||||
|
||||
this.addDisposables(
|
||||
this._onDrop,
|
||||
new DragAndDropObserver(this.element, {
|
||||
this.dnd = new DragAndDropObserver(this.element, {
|
||||
onDragEnter: () => undefined,
|
||||
onDragOver: (e) => {
|
||||
if (this._acceptedTargetZonesSet.size === 0) {
|
||||
@ -180,8 +180,9 @@ export class Droptarget extends CompositeDisposable {
|
||||
this._onDrop.fire({ position: state, nativeEvent: e });
|
||||
}
|
||||
},
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
this.addDisposables(this._onDrop, this.dnd);
|
||||
}
|
||||
|
||||
setTargetZones(acceptedTargetZones: Position[]): void {
|
||||
|
@ -0,0 +1,16 @@
|
||||
.dv-render-overlay {
|
||||
position: absolute;
|
||||
z-index: 1;
|
||||
height: 100%;
|
||||
|
||||
&.dv-render-overlay-float {
|
||||
z-index: 999;
|
||||
}
|
||||
}
|
||||
|
||||
.dv-debug {
|
||||
.dv-render-overlay {
|
||||
outline: 1px solid red;
|
||||
outline-offset: -1;
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
import { DragAndDropObserver } from '../../dnd/dnd';
|
||||
import { Droptarget } from '../../dnd/droptarget';
|
||||
import { getDomNodePagePosition, toggleClass } from '../../dom';
|
||||
import { CompositeDisposable, Disposable, IDisposable } from '../../lifecycle';
|
||||
import { IDockviewPanel } from '../dockviewPanel';
|
||||
|
||||
export type DockviewPanelRenderer = 'destructive' | 'gready';
|
||||
|
||||
export interface IRenderable {
|
||||
readonly element: HTMLElement;
|
||||
readonly dropTarget: Droptarget;
|
||||
}
|
||||
|
||||
function createFocusableElement(): HTMLDivElement {
|
||||
const element = document.createElement('div');
|
||||
element.tabIndex = -1;
|
||||
return element;
|
||||
}
|
||||
|
||||
export class GreadyRenderContainer extends CompositeDisposable {
|
||||
private readonly map: Record<
|
||||
string,
|
||||
{ disposable: IDisposable; element: HTMLElement }
|
||||
> = {};
|
||||
|
||||
get allIds(): string[] {
|
||||
return Object.keys(this.map);
|
||||
}
|
||||
|
||||
constructor(private readonly element: HTMLElement) {
|
||||
super();
|
||||
|
||||
this.addDisposables({
|
||||
dispose: () => {
|
||||
for (const value of Object.values(this.map)) {
|
||||
value.disposable.dispose();
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
remove(panel: IDockviewPanel): boolean {
|
||||
if (this.map[panel.api.id]) {
|
||||
this.map[panel.api.id].disposable.dispose();
|
||||
delete this.map[panel.api.id];
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
setReferenceContentContainer(
|
||||
panel: IDockviewPanel,
|
||||
referenceContainer: IRenderable
|
||||
): HTMLElement {
|
||||
if (!this.map[panel.api.id]) {
|
||||
const element = createFocusableElement();
|
||||
element.className = 'dv-render-overlay';
|
||||
|
||||
this.map[panel.api.id] = {
|
||||
disposable: Disposable.NONE,
|
||||
element,
|
||||
};
|
||||
}
|
||||
|
||||
this.map[panel.api.id]?.disposable.dispose();
|
||||
const focusContainer = this.map[panel.api.id].element;
|
||||
|
||||
if (panel.view.content.element.parentElement !== focusContainer) {
|
||||
focusContainer.appendChild(panel.view.content.element);
|
||||
}
|
||||
|
||||
if (focusContainer.parentElement !== this.element) {
|
||||
this.element.appendChild(focusContainer);
|
||||
}
|
||||
|
||||
const resize = () => {
|
||||
// TODO propagate position to avoid getDomNodePagePosition calls
|
||||
const box = getDomNodePagePosition(referenceContainer.element);
|
||||
const box2 = getDomNodePagePosition(this.element);
|
||||
focusContainer.style.left = `${box.left - box2.left}px`;
|
||||
focusContainer.style.top = `${box.top - box2.top}px`;
|
||||
focusContainer.style.width = `${box.width}px`;
|
||||
focusContainer.style.height = `${box.height}px`;
|
||||
|
||||
toggleClass(
|
||||
focusContainer,
|
||||
'dv-render-overlay-float',
|
||||
panel.group.api.isFloating
|
||||
);
|
||||
};
|
||||
|
||||
const disposable = new CompositeDisposable(
|
||||
/**
|
||||
* since container is positioned absoutely we must explicitly forward
|
||||
* the dnd events for the expect behaviours to continue to occur in terms of dnd
|
||||
*/
|
||||
new DragAndDropObserver(focusContainer, {
|
||||
onDragEnd: (e) => {
|
||||
referenceContainer.dropTarget.dnd.onDragEnd(e);
|
||||
},
|
||||
onDragEnter: (e) => {
|
||||
referenceContainer.dropTarget.dnd.onDragEnter(e);
|
||||
},
|
||||
onDragLeave: (e) => {
|
||||
referenceContainer.dropTarget.dnd.onDragLeave(e);
|
||||
},
|
||||
onDrop: (e) => {
|
||||
referenceContainer.dropTarget.dnd.onDrop(e);
|
||||
},
|
||||
onDragOver: (e) => {
|
||||
referenceContainer.dropTarget.dnd.onDragOver(e);
|
||||
},
|
||||
}),
|
||||
panel.api.onDidVisibilityChange((event) => {
|
||||
focusContainer.style.display = event.isVisible ? '' : 'none';
|
||||
}),
|
||||
panel.api.onDidDimensionsChange((event) => {
|
||||
resize();
|
||||
}),
|
||||
{
|
||||
dispose: () => {
|
||||
focusContainer.removeChild(panel.view.content.element);
|
||||
this.element.removeChild(focusContainer);
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
queueMicrotask(() => {
|
||||
/**
|
||||
* wait until everything has finished in the current stack-frame call before
|
||||
* calling the first resize as other size-altering events may still occur before
|
||||
* the end of the stack-frame.
|
||||
*/
|
||||
resize();
|
||||
});
|
||||
|
||||
this.map[panel.api.id].disposable = disposable;
|
||||
|
||||
return focusContainer;
|
||||
}
|
||||
}
|
@ -1,13 +1,21 @@
|
||||
import {
|
||||
CompositeDisposable,
|
||||
Disposable,
|
||||
IDisposable,
|
||||
MutableDisposable,
|
||||
} from '../../../lifecycle';
|
||||
import { Emitter, Event } from '../../../events';
|
||||
import { trackFocus } from '../../../dom';
|
||||
import { IDockviewPanel } from '../../dockviewPanel';
|
||||
import { DockviewComponent } from '../../dockviewComponent';
|
||||
import { DragAndDropObserver } from '../../../dnd/dnd';
|
||||
import { Droptarget } from '../../../dnd/droptarget';
|
||||
import { DockviewGroupPanelModel } from '../../dockviewGroupPanelModel';
|
||||
import { getPanelData } from '../../../dnd/dataTransfer';
|
||||
import { DockviewDropTargets } from '../../types';
|
||||
|
||||
export interface IContentContainer extends IDisposable {
|
||||
readonly dropTarget: Droptarget;
|
||||
onDidFocus: Event<void>;
|
||||
onDidBlur: Event<void>;
|
||||
element: HTMLElement;
|
||||
@ -16,6 +24,7 @@ export interface IContentContainer extends IDisposable {
|
||||
closePanel: () => void;
|
||||
show(): void;
|
||||
hide(): void;
|
||||
renderPanel(panel: IDockviewPanel): void;
|
||||
}
|
||||
|
||||
export class ContentContainer
|
||||
@ -36,7 +45,12 @@ export class ContentContainer
|
||||
return this._element;
|
||||
}
|
||||
|
||||
constructor() {
|
||||
readonly dropTarget: Droptarget;
|
||||
|
||||
constructor(
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly group: DockviewGroupPanelModel
|
||||
) {
|
||||
super();
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'content-container';
|
||||
@ -49,6 +63,51 @@ export class ContentContainer
|
||||
// 2) register window dragStart events to disable pointer events
|
||||
// 3) register dragEnd events
|
||||
// 4) register mouseMove events (if no buttons are present we take this as a dragEnd event)
|
||||
|
||||
this.dropTarget = new Droptarget(this.element, {
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (
|
||||
this.group.locked === 'no-drop-target' ||
|
||||
(this.group.locked && position === 'center')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (!data && event.shiftKey && !this.group.isFloating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.group.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this.group.panels.length === 1 &&
|
||||
data.groupId === this.group.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
}
|
||||
|
||||
return this.group.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
this.addDisposables(this.dropTarget);
|
||||
}
|
||||
|
||||
show(): void {
|
||||
@ -59,26 +118,44 @@ export class ContentContainer
|
||||
this.element.style.display = 'none';
|
||||
}
|
||||
|
||||
public openPanel(panel: IDockviewPanel): void {
|
||||
if (this.panel === panel) {
|
||||
return;
|
||||
}
|
||||
renderPanel(panel: IDockviewPanel): void {
|
||||
const isActive = panel === this.group.activePanel;
|
||||
|
||||
let container: HTMLElement;
|
||||
|
||||
switch (panel.api.renderer) {
|
||||
case 'destructive':
|
||||
this.accessor.greadyRenderContainer.remove(panel);
|
||||
if (isActive) {
|
||||
if (this.panel) {
|
||||
if (this.panel.view?.content) {
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
this._element.appendChild(
|
||||
this.panel.view.content.element
|
||||
);
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
this.panel = panel;
|
||||
container = this._element;
|
||||
break;
|
||||
case 'gready':
|
||||
if (
|
||||
panel.view.content.element.parentElement === this._element
|
||||
) {
|
||||
this._element.removeChild(panel.view.content.element);
|
||||
}
|
||||
container =
|
||||
this.accessor.greadyRenderContainer.setReferenceContentContainer(
|
||||
panel,
|
||||
this
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
if (isActive) {
|
||||
const _onDidFocus = panel.view.content.onDidFocus;
|
||||
const _onDidBlur = panel.view.content.onDidBlur;
|
||||
|
||||
const focusTracker = trackFocus(container);
|
||||
const disposable = new CompositeDisposable();
|
||||
|
||||
if (this.panel.view) {
|
||||
const _onDidFocus = this.panel.view.content.onDidFocus;
|
||||
const _onDidBlur = this.panel.view.content.onDidBlur;
|
||||
|
||||
const focusTracker = trackFocus(this._element);
|
||||
|
||||
disposable.addDisposables(
|
||||
focusTracker,
|
||||
focusTracker.onDidFocus(() => this._onDidFocus.fire()),
|
||||
@ -96,7 +173,64 @@ export class ContentContainer
|
||||
);
|
||||
}
|
||||
|
||||
this.disposable.value = disposable;
|
||||
}
|
||||
}
|
||||
|
||||
public openPanel(panel: IDockviewPanel): void {
|
||||
if (this.panel === panel) {
|
||||
return;
|
||||
}
|
||||
|
||||
const renderer = panel.api.renderer;
|
||||
|
||||
if (
|
||||
this.panel &&
|
||||
this.panel.view.content.element.parentElement === this._element
|
||||
) {
|
||||
/**
|
||||
* If the currently attached panel is mounted directly to the content then remove it
|
||||
*/
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
}
|
||||
|
||||
this.panel = panel;
|
||||
|
||||
let container: HTMLElement;
|
||||
|
||||
switch (renderer) {
|
||||
case 'gready':
|
||||
container =
|
||||
this.accessor.greadyRenderContainer.setReferenceContentContainer(
|
||||
panel,
|
||||
this
|
||||
);
|
||||
break;
|
||||
case 'destructive':
|
||||
this._element.appendChild(this.panel.view.content.element);
|
||||
container = this._element;
|
||||
break;
|
||||
}
|
||||
|
||||
const _onDidFocus = this.panel.view.content.onDidFocus;
|
||||
const _onDidBlur = this.panel.view.content.onDidBlur;
|
||||
|
||||
const disposable = new CompositeDisposable();
|
||||
const focusTracker = trackFocus(container);
|
||||
|
||||
disposable.addDisposables(
|
||||
focusTracker,
|
||||
focusTracker.onDidFocus(() => this._onDidFocus.fire()),
|
||||
focusTracker.onDidBlur(() => this._onDidBlur.fire())
|
||||
);
|
||||
|
||||
if (_onDidFocus) {
|
||||
disposable.addDisposables(
|
||||
_onDidFocus(() => this._onDidFocus.fire())
|
||||
);
|
||||
}
|
||||
if (_onDidBlur) {
|
||||
disposable.addDisposables(_onDidBlur(() => this._onDidBlur.fire()));
|
||||
}
|
||||
|
||||
this.disposable.value = disposable;
|
||||
@ -107,8 +241,10 @@ export class ContentContainer
|
||||
}
|
||||
|
||||
public closePanel(): void {
|
||||
if (this.panel?.view?.content?.element) {
|
||||
if (this.panel) {
|
||||
if (this.accessor.options.defaultRenderer === 'destructive') {
|
||||
this._element.removeChild(this.panel.view.content.element);
|
||||
}
|
||||
this.panel = undefined;
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ interface LegacyState extends GroupviewPanelState {
|
||||
}
|
||||
|
||||
export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
constructor(private readonly layout: DockviewComponent) {}
|
||||
constructor(private readonly accessor: DockviewComponent) {}
|
||||
|
||||
public fromJSON(
|
||||
panelData: GroupviewPanelState,
|
||||
@ -41,7 +41,7 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
: panelData.tabComponent;
|
||||
|
||||
const view = new DockviewPanelModel(
|
||||
this.layout,
|
||||
this.accessor,
|
||||
panelId,
|
||||
contentComponent,
|
||||
tabComponent
|
||||
@ -49,10 +49,13 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
|
||||
|
||||
const panel = new DockviewPanel(
|
||||
panelId,
|
||||
this.layout,
|
||||
new DockviewApi(this.layout),
|
||||
this.accessor,
|
||||
new DockviewApi(this.accessor),
|
||||
group,
|
||||
view
|
||||
view,
|
||||
{
|
||||
renderer: panelData.renderer,
|
||||
}
|
||||
);
|
||||
|
||||
panel.init({
|
||||
|
@ -10,6 +10,10 @@
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dv-gready-render-container {
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
|
||||
.groupview {
|
||||
|
@ -55,6 +55,10 @@ import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from './components/titlebar/tabsContainer';
|
||||
import {
|
||||
GreadyRenderContainer,
|
||||
DockviewPanelRenderer,
|
||||
} from './components/greadyRenderContainer';
|
||||
|
||||
const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
||||
|
||||
@ -245,6 +249,8 @@ export class DockviewComponent
|
||||
private _options: Exclude<DockviewComponentOptions, 'orientation'>;
|
||||
private watermark: IWatermarkRenderer | null = null;
|
||||
|
||||
readonly greadyRenderContainer: GreadyRenderContainer;
|
||||
|
||||
private readonly _onWillDragPanel = new Emitter<TabDragEvent>();
|
||||
readonly onWillDragPanel: Event<TabDragEvent> = this._onWillDragPanel.event;
|
||||
|
||||
@ -299,6 +305,10 @@ export class DockviewComponent
|
||||
return activeGroup.activePanel;
|
||||
}
|
||||
|
||||
get renderer(): DockviewPanelRenderer {
|
||||
return this.options.defaultRenderer ?? 'destructive';
|
||||
}
|
||||
|
||||
constructor(options: DockviewComponentOptions) {
|
||||
super({
|
||||
proportionalLayout: true,
|
||||
@ -308,9 +318,17 @@ export class DockviewComponent
|
||||
disableAutoResizing: options.disableAutoResizing,
|
||||
});
|
||||
|
||||
const gready = document.createElement('div');
|
||||
gready.className = 'dv-gready-render-container';
|
||||
this.gridview.element.appendChild(gready);
|
||||
|
||||
this.greadyRenderContainer = new GreadyRenderContainer(gready);
|
||||
|
||||
toggleClass(this.gridview.element, 'dv-dockview', true);
|
||||
toggleClass(this.element, 'dv-debug', !!options.debug);
|
||||
|
||||
this.addDisposables(
|
||||
this.greadyRenderContainer,
|
||||
this._onWillDragPanel,
|
||||
this._onWillDragGroup,
|
||||
this._onDidActivePanelChange,
|
||||
@ -1041,6 +1059,7 @@ export class DockviewComponent
|
||||
group.model.removePanel(panel);
|
||||
|
||||
if (!options.skipDispose) {
|
||||
this.greadyRenderContainer.remove(panel);
|
||||
panel.dispose();
|
||||
}
|
||||
|
||||
@ -1463,8 +1482,10 @@ export class DockviewComponent
|
||||
this,
|
||||
this._api,
|
||||
group,
|
||||
view
|
||||
view,
|
||||
{ renderer: options.renderer }
|
||||
);
|
||||
|
||||
panel.init({
|
||||
title: options.title ?? options.id,
|
||||
params: options?.params ?? {},
|
||||
|
@ -136,7 +136,7 @@ export class DockviewGroupPanelModel
|
||||
{
|
||||
private readonly tabsContainer: ITabsContainer;
|
||||
private readonly contentContainer: IContentContainer;
|
||||
private readonly dropTarget: Droptarget;
|
||||
// private readonly dropTarget: Droptarget;
|
||||
private _activePanel: IDockviewPanel | undefined;
|
||||
private watermark?: IWatermarkRenderer;
|
||||
private _isGroupActive = false;
|
||||
@ -248,7 +248,7 @@ export class DockviewGroupPanelModel
|
||||
set isFloating(value: boolean) {
|
||||
this._isFloating = value;
|
||||
|
||||
this.dropTarget.setTargetZones(
|
||||
this.contentContainer.dropTarget.setTargetZones(
|
||||
value ? ['center'] : ['top', 'bottom', 'left', 'right', 'center']
|
||||
);
|
||||
|
||||
@ -272,49 +272,7 @@ export class DockviewGroupPanelModel
|
||||
|
||||
this.tabsContainer = new TabsContainer(this.accessor, this.groupPanel);
|
||||
|
||||
this.contentContainer = new ContentContainer();
|
||||
|
||||
this.dropTarget = new Droptarget(this.contentContainer.element, {
|
||||
acceptedTargetZones: ['top', 'bottom', 'left', 'right', 'center'],
|
||||
canDisplayOverlay: (event, position) => {
|
||||
if (
|
||||
this.locked === 'no-drop-target' ||
|
||||
(this.locked && position === 'center')
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const data = getPanelData();
|
||||
|
||||
if (!data && event.shiftKey && !this.isFloating) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (data && data.viewId === this.accessor.id) {
|
||||
if (data.groupId === this.id) {
|
||||
if (position === 'center') {
|
||||
// don't allow to drop on self for center position
|
||||
return false;
|
||||
}
|
||||
if (data.panelId === null) {
|
||||
// don't allow group move to drop anywhere on self
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const groupHasOnePanelAndIsActiveDragElement =
|
||||
this._panels.length === 1 && data.groupId === this.id;
|
||||
|
||||
return !groupHasOnePanelAndIsActiveDragElement;
|
||||
}
|
||||
|
||||
return this.canDisplayOverlay(
|
||||
event,
|
||||
position,
|
||||
DockviewDropTargets.Panel
|
||||
);
|
||||
},
|
||||
});
|
||||
this.contentContainer = new ContentContainer(this.accessor, this);
|
||||
|
||||
container.append(
|
||||
this.tabsContainer.element,
|
||||
@ -342,7 +300,7 @@ export class DockviewGroupPanelModel
|
||||
this.contentContainer.onDidBlur(() => {
|
||||
// noop
|
||||
}),
|
||||
this.dropTarget.onDrop((event) => {
|
||||
this.contentContainer.dropTarget.onDrop((event) => {
|
||||
this.handleDropEvent(event.nativeEvent, event.position);
|
||||
}),
|
||||
this._onMove,
|
||||
@ -416,6 +374,10 @@ export class DockviewGroupPanelModel
|
||||
}
|
||||
}
|
||||
|
||||
rerender(panel: IDockviewPanel): void {
|
||||
this.contentContainer.renderPanel(panel);
|
||||
}
|
||||
|
||||
public indexOf(panel: IDockviewPanel): number {
|
||||
return this.tabsContainer.indexOf(panel.id);
|
||||
}
|
||||
@ -687,15 +649,15 @@ export class DockviewGroupPanelModel
|
||||
const existingPanel = this._panels.indexOf(panel);
|
||||
const hasExistingPanel = existingPanel > -1;
|
||||
|
||||
this.tabsContainer.show();
|
||||
this.contentContainer.show();
|
||||
|
||||
this.tabsContainer.openPanel(panel, index);
|
||||
|
||||
if (!skipSetActive) {
|
||||
this.contentContainer.openPanel(panel);
|
||||
}
|
||||
|
||||
this.tabsContainer.show();
|
||||
this.contentContainer.show();
|
||||
|
||||
if (hasExistingPanel) {
|
||||
// TODO - need to ensure ordering hasn't changed and if it has need to re-order this.panels
|
||||
return;
|
||||
@ -849,7 +811,7 @@ export class DockviewGroupPanelModel
|
||||
panel.dispose();
|
||||
}
|
||||
|
||||
this.dropTarget.dispose();
|
||||
// this.dropTarget.dispose();
|
||||
this.tabsContainer.dispose();
|
||||
this.contentContainer.dispose();
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import { CompositeDisposable, IDisposable } from '../lifecycle';
|
||||
import { IPanel, PanelUpdateEvent, Parameters } from '../panel/types';
|
||||
import { IDockviewPanelModel } from './dockviewPanelModel';
|
||||
import { DockviewComponent } from './dockviewComponent';
|
||||
import { DockviewPanelRenderer } from './components/greadyRenderContainer';
|
||||
|
||||
export interface IDockviewPanel extends IDisposable, IPanel {
|
||||
readonly view: IDockviewPanelModel;
|
||||
@ -28,10 +29,11 @@ export class DockviewPanel
|
||||
implements IDockviewPanel
|
||||
{
|
||||
readonly api: DockviewPanelApiImpl;
|
||||
|
||||
private _group: DockviewGroupPanel;
|
||||
private _params?: Parameters;
|
||||
|
||||
private _title: string | undefined;
|
||||
private _renderer: DockviewPanelRenderer | undefined;
|
||||
|
||||
get params(): Parameters | undefined {
|
||||
return this._params;
|
||||
@ -45,14 +47,20 @@ export class DockviewPanel
|
||||
return this._group;
|
||||
}
|
||||
|
||||
get renderer(): DockviewPanelRenderer {
|
||||
return this._renderer ?? this.accessor.renderer;
|
||||
}
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
accessor: DockviewComponent,
|
||||
private readonly accessor: DockviewComponent,
|
||||
private readonly containerApi: DockviewApi,
|
||||
group: DockviewGroupPanel,
|
||||
readonly view: IDockviewPanelModel
|
||||
readonly view: IDockviewPanelModel,
|
||||
options: { renderer?: DockviewPanelRenderer }
|
||||
) {
|
||||
super();
|
||||
this._renderer = options.renderer;
|
||||
this._group = group;
|
||||
|
||||
this.api = new DockviewPanelApiImpl(this, this._group, accessor);
|
||||
@ -65,6 +73,9 @@ export class DockviewPanel
|
||||
// forward the resize event to the group since if you want to resize a panel
|
||||
// you are actually just resizing the panels parent which is the group
|
||||
this.group.api.setSize(event);
|
||||
}),
|
||||
this.api.onDidRendererChange((event) => {
|
||||
this.group.model.rerender(this);
|
||||
})
|
||||
);
|
||||
}
|
||||
@ -95,6 +106,7 @@ export class DockviewPanel
|
||||
? this._params
|
||||
: undefined,
|
||||
title: this.title,
|
||||
renderer: this._renderer,
|
||||
};
|
||||
}
|
||||
|
||||
@ -114,6 +126,17 @@ export class DockviewPanel
|
||||
}
|
||||
}
|
||||
|
||||
setRenderer(renderer: DockviewPanelRenderer): void {
|
||||
const didChange = renderer !== this.renderer;
|
||||
|
||||
if (didChange) {
|
||||
this._renderer = renderer;
|
||||
this.api._onDidRendererChange.fire({
|
||||
renderer: renderer,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public update(event: PanelUpdateEvent): void {
|
||||
// merge the new parameters with the existing parameters
|
||||
this._params = {
|
||||
|
@ -20,6 +20,7 @@ import {
|
||||
FrameworkFactory,
|
||||
} from '../panel/componentFactory';
|
||||
import { DockviewGroupPanelApi } from '../api/dockviewGroupPanelApi';
|
||||
import { DockviewPanelRenderer } from './components/greadyRenderContainer';
|
||||
|
||||
export interface IHeaderActionsRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
@ -96,6 +97,8 @@ export interface DockviewComponentOptions extends DockviewRenderFunctions {
|
||||
minimumHeightWithinViewport?: number;
|
||||
minimumWidthWithinViewport?: number;
|
||||
};
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
debug?: boolean;
|
||||
}
|
||||
|
||||
export interface PanelOptions<P extends object = Parameters> {
|
||||
@ -168,6 +171,7 @@ export type AddPanelOptions<P extends object = Parameters> = Omit<
|
||||
> & {
|
||||
component: string;
|
||||
tabComponent?: string;
|
||||
renderer?: DockviewPanelRenderer;
|
||||
} & Partial<AddPanelOptionsUnion>;
|
||||
|
||||
type AddGroupOptionsWithPanel = {
|
||||
|
@ -5,6 +5,7 @@ import { DockviewApi } from '../api/component.api';
|
||||
import { Event } from '../events';
|
||||
import { Optional } from '../types';
|
||||
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||
import { DockviewPanelRenderer } from './components/greadyRenderContainer';
|
||||
|
||||
export enum DockviewDropTargets {
|
||||
Tab,
|
||||
@ -91,5 +92,6 @@ export interface GroupviewPanelState {
|
||||
contentComponent?: string;
|
||||
tabComponent?: string;
|
||||
title?: string;
|
||||
renderer?: DockviewPanelRenderer;
|
||||
params?: { [key: string]: any };
|
||||
}
|
||||
|
@ -185,3 +185,18 @@ export function quasiPreventDefault(event: Event): void {
|
||||
export function quasiDefaultPrevented(event: Event): boolean {
|
||||
return (event as any)[QUASI_PREVENT_DEFAULT_KEY];
|
||||
}
|
||||
|
||||
export function getDomNodePagePosition(domNode: Element): {
|
||||
left: number;
|
||||
top: number;
|
||||
width: number;
|
||||
height: number;
|
||||
} {
|
||||
const { left, top, width, height } = domNode.getBoundingClientRect();
|
||||
return {
|
||||
left: left + window.scrollX,
|
||||
top: top + window.scrollY,
|
||||
width: width,
|
||||
height: height,
|
||||
};
|
||||
}
|
||||
|
@ -49,6 +49,8 @@ export * from './splitview/splitviewPanel';
|
||||
export * from './paneview/paneviewPanel';
|
||||
export * from './dockview/types';
|
||||
|
||||
export { DockviewPanelRenderer } from './dockview/components/greadyRenderContainer';
|
||||
|
||||
export {
|
||||
Position,
|
||||
positionToDirection,
|
||||
@ -66,7 +68,11 @@ export {
|
||||
GridviewPanelApi,
|
||||
GridConstraintChangeEvent,
|
||||
} from './api/gridviewPanelApi';
|
||||
export { TitleEvent, DockviewPanelApi } from './api/dockviewPanelApi';
|
||||
export {
|
||||
TitleEvent,
|
||||
RendererChangedEvent,
|
||||
DockviewPanelApi,
|
||||
} from './api/dockviewPanelApi';
|
||||
export {
|
||||
PanelSizeEvent,
|
||||
PanelConstraintChangeEvent,
|
||||
|
@ -57,7 +57,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
onClick={onClick}
|
||||
className="dockview-react-tab"
|
||||
>
|
||||
<span className="dockview-react-tab-title">{api.title}</span>
|
||||
<span className="dockview-react-tab-title">{api.renderer}</span>
|
||||
{!hideClose && (
|
||||
<div
|
||||
className="dv-react-tab-close-btn"
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
ITabRenderer,
|
||||
DockviewGroupPanel,
|
||||
IHeaderActionsRenderer,
|
||||
DockviewPanelRenderer,
|
||||
} from 'dockview-core';
|
||||
import { ReactPanelContentPart } from './reactContentPart';
|
||||
import { ReactPanelHeaderPart } from './reactHeaderPart';
|
||||
@ -76,6 +77,8 @@ export interface IDockviewReactProps {
|
||||
minimumHeightWithinViewport?: number;
|
||||
minimumWidthWithinViewport?: number;
|
||||
};
|
||||
debug?: boolean;
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
}
|
||||
|
||||
const DEFAULT_REACT_TAB = 'props.defaultTabComponent';
|
||||
@ -175,6 +178,8 @@ export const DockviewReact = React.forwardRef(
|
||||
singleTabMode: props.singleTabMode,
|
||||
disableFloatingGroups: props.disableFloatingGroups,
|
||||
floatingGroupBounds: props.floatingGroupBounds,
|
||||
defaultRenderer: props.defaultRenderer,
|
||||
debug: props.debug,
|
||||
});
|
||||
|
||||
const { clientWidth, clientHeight } = domRef.current;
|
||||
|
@ -28,8 +28,9 @@ import DockviewWithIFrames from '@site/sandboxes/iframe-dockview/src/app';
|
||||
import DockviewFloating from '@site/sandboxes/floatinggroup-dockview/src/app';
|
||||
import DockviewLockedGroup from '@site/sandboxes/lockedgroup-dockview/src/app';
|
||||
import DockviewKeyboard from '@site/sandboxes/keyboard-dockview/src/app';
|
||||
import DockviewRenderMode from '@site/sandboxes/rendermode-dockview/src/app';
|
||||
|
||||
import { DocRef, Markdown } from '@site/src/components/ui/reference/docRef';
|
||||
import { DocRef } from '@site/src/components/ui/reference/docRef';
|
||||
|
||||
import { attach as attachDockviewVanilla } from '@site/sandboxes/javascript/vanilla-dockview/src/app';
|
||||
import { attach as attachSimpleDockview } from '@site/sandboxes/javascript/simple-dockview/src/app';
|
||||
@ -816,6 +817,24 @@ api.group.api.setConstraints(...)
|
||||
react={DockviewConstraints}
|
||||
/>
|
||||
|
||||
## Render Mode
|
||||
|
||||
Dockview has two rendering modes `destructive` (default) and `gready`. A rendering mode can be defined through the `renderer` prop to `DockviewReact` or at an individual panel level when added where
|
||||
the panel declaration takes precedence if both are defined. Rendering modes defined at the panel level are persisted, those defined at the `DockviewReact` level are not persisted.
|
||||
|
||||
destructive
|
||||
- Destructive mode is the default mode. In this mode when a panel is no longer visible through either it's visiblity being hidden or it not being the active panel within a group the panels HTMLElement is removed
|
||||
from the DOM and any DOM state such as scrollbar positions will be lost. If you are using any ResizeObservers to measure size this will result both zero height and width as the HTMLElement no longer belongs to the DOM.
|
||||
This design allows for maximum performance at some cost.
|
||||
- Gready mode. In this mode when panels become hidden the HTMLElement is not destroyed so all DOM state such as scrollbar positions will be maintained. This is implemented by rendering each panel as an absolutely positioned
|
||||
HTMLElement and hidden the HTMLElement with `display: none` when it should be hidden.
|
||||
|
||||
<MultiFrameworkContainer
|
||||
height={500}
|
||||
sandboxId="rendermode-dockview"
|
||||
react={DockviewRenderMode}
|
||||
/>
|
||||
|
||||
## iFrames
|
||||
|
||||
iFrames required special attention because of a particular behaviour in how iFrames render:
|
||||
|
@ -5,15 +5,71 @@ import {
|
||||
IDockviewPanelHeaderProps,
|
||||
IDockviewPanelProps,
|
||||
IDockviewHeaderActionsProps,
|
||||
DockviewPanelApi,
|
||||
DockviewPanelRenderer,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import * as ReactDOM from 'react-dom';
|
||||
import { v4 } from 'uuid';
|
||||
import './app.scss';
|
||||
|
||||
const useRenderer = (
|
||||
api: DockviewPanelApi
|
||||
): [DockviewPanelRenderer, (value: DockviewPanelRenderer) => void] => {
|
||||
const [mode, setMode] = React.useState<DockviewPanelRenderer>(api.renderer);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = api.onDidRendererChange((event) => {
|
||||
setMode(event.renderer);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const _setMode = React.useCallback(
|
||||
(mode: DockviewPanelRenderer) => {
|
||||
api.setRenderer(mode);
|
||||
},
|
||||
[api]
|
||||
);
|
||||
|
||||
return [mode, _setMode];
|
||||
};
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
return <div style={{ padding: '20px' }}>{props.params.title}</div>;
|
||||
const [mode, setMode] = useRenderer(props.api);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', overflow: 'auto', color: 'white' }}>
|
||||
<div
|
||||
style={{
|
||||
height: '1000px',
|
||||
padding: '20px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<div>{props.api.title}</div>
|
||||
<input />
|
||||
<div>
|
||||
{mode}
|
||||
<button
|
||||
onClick={() => {
|
||||
setMode(
|
||||
mode === 'destructive'
|
||||
? 'gready'
|
||||
: 'destructive'
|
||||
);
|
||||
}}
|
||||
>
|
||||
Toggle render mode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
@ -233,18 +289,18 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
title: 'Panel 4',
|
||||
position: { referencePanel: 'panel_3', direction: 'right' },
|
||||
});
|
||||
event.api.addPanel({
|
||||
id: 'panel_5',
|
||||
component: 'default',
|
||||
title: 'Panel 5',
|
||||
position: { referencePanel: 'panel_3', direction: 'below' },
|
||||
});
|
||||
event.api.addPanel({
|
||||
id: 'panel_6',
|
||||
component: 'default',
|
||||
title: 'Panel 6',
|
||||
position: { referencePanel: 'panel_3', direction: 'right' },
|
||||
});
|
||||
// event.api.addPanel({
|
||||
// id: 'panel_5',
|
||||
// component: 'default',
|
||||
// title: 'Panel 5',
|
||||
// position: { referencePanel: 'panel_3', direction: 'below' },
|
||||
// });
|
||||
// event.api.addPanel({
|
||||
// id: 'panel_6',
|
||||
// component: 'default',
|
||||
// title: 'Panel 6',
|
||||
// position: { referencePanel: 'panel_3', direction: 'right' },
|
||||
// });
|
||||
|
||||
event.api.getPanel('panel_1')!.api.setActive();
|
||||
|
||||
@ -260,6 +316,7 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
prefixHeaderActionsComponent={PrefixHeaderControls}
|
||||
onReady={onReady}
|
||||
className={props.theme || 'dockview-theme-abyss'}
|
||||
// debug={true}
|
||||
/>
|
||||
);
|
||||
};
|
||||
|
33
packages/docs/sandboxes/rendermode-dockview/package.json
Normal file
33
packages/docs/sandboxes/rendermode-dockview/package.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"name": "rendermode-dockview",
|
||||
"description": "",
|
||||
"keywords": [
|
||||
"dockview"
|
||||
],
|
||||
"version": "1.0.0",
|
||||
"main": "src/index.tsx",
|
||||
"dependencies": {
|
||||
"dockview": "*",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/react": "^18.0.28",
|
||||
"@types/react-dom": "^18.0.11",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"typescript": "^4.9.5",
|
||||
"react-scripts": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"start": "react-scripts start",
|
||||
"build": "react-scripts build",
|
||||
"test": "react-scripts test --env=jsdom",
|
||||
"eject": "react-scripts eject"
|
||||
},
|
||||
"browserslist": [
|
||||
">0.2%",
|
||||
"not dead",
|
||||
"not ie <= 11",
|
||||
"not op_mini all"
|
||||
]
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta name="theme-color" content="#000000">
|
||||
<!--
|
||||
manifest.json provides metadata used when your web app is added to the
|
||||
homescreen on Android. See https://developers.google.com/web/fundamentals/engage-and-retain/web-app-manifest/
|
||||
-->
|
||||
<link rel="manifest" href="%PUBLIC_URL%/manifest.json">
|
||||
<link rel="shortcut icon" href="%PUBLIC_URL%/favicon.ico">
|
||||
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" />
|
||||
|
||||
<!--
|
||||
Notice the use of %PUBLIC_URL% in the tags above.
|
||||
It will be replaced with the URL of the `public` folder during the build.
|
||||
Only files inside the `public` folder can be referenced from the HTML.
|
||||
|
||||
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
|
||||
work correctly both with client-side routing and a non-root public URL.
|
||||
Learn how to configure a non-root public URL by running `npm run build`.
|
||||
-->
|
||||
<title>React App</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<noscript>
|
||||
You need to enable JavaScript to run this app.
|
||||
</noscript>
|
||||
<div id="root"></div>
|
||||
<!--
|
||||
This HTML file is a template.
|
||||
If you open it directly in the browser, you will see an empty page.
|
||||
|
||||
You can add webfonts, meta tags, or analytics to this file.
|
||||
The build step will place the bundled scripts into the <body> tag.
|
||||
|
||||
To begin the development, run `npm start` or `yarn start`.
|
||||
To create a production bundle, use `npm run build` or `yarn build`.
|
||||
-->
|
||||
</body>
|
||||
|
||||
</html>
|
160
packages/docs/sandboxes/rendermode-dockview/src/app.tsx
Normal file
160
packages/docs/sandboxes/rendermode-dockview/src/app.tsx
Normal file
@ -0,0 +1,160 @@
|
||||
import {
|
||||
DockviewReact,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
DockviewPanelApi,
|
||||
DockviewPanelRenderer,
|
||||
DockviewApi,
|
||||
SerializedDockview,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
import './app.scss';
|
||||
|
||||
const useRenderer = (
|
||||
api: DockviewPanelApi
|
||||
): [DockviewPanelRenderer, (value: DockviewPanelRenderer) => void] => {
|
||||
const [mode, setMode] = React.useState<DockviewPanelRenderer>(api.renderer);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = api.onDidRendererChange((event) => {
|
||||
setMode(event.renderer);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, []);
|
||||
|
||||
const _setMode = React.useCallback(
|
||||
(mode: DockviewPanelRenderer) => {
|
||||
api.setRenderer(mode);
|
||||
},
|
||||
[api]
|
||||
);
|
||||
|
||||
return [mode, _setMode];
|
||||
};
|
||||
|
||||
const components = {
|
||||
default: (props: IDockviewPanelProps<{ title: string }>) => {
|
||||
const [mode, setMode] = useRenderer(props.api);
|
||||
|
||||
return (
|
||||
<div style={{ height: '100%', overflow: 'auto', color: 'white' }}>
|
||||
<div
|
||||
style={{
|
||||
height: '1000px',
|
||||
padding: '20px',
|
||||
overflow: 'auto',
|
||||
}}
|
||||
>
|
||||
<div>{props.api.title}</div>
|
||||
<input />
|
||||
<div>
|
||||
{mode}
|
||||
<button
|
||||
onClick={() => {
|
||||
setMode(
|
||||
mode === 'destructive'
|
||||
? 'gready'
|
||||
: 'destructive'
|
||||
);
|
||||
}}
|
||||
>
|
||||
Toggle render mode
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
const DockviewDemo = (props: { theme?: string }) => {
|
||||
const [value, setValue] = React.useState<string>('100');
|
||||
const [api, setApi] = React.useState<DockviewApi | null>(null);
|
||||
|
||||
const onReady = (event: DockviewReadyEvent) => {
|
||||
event.api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
title: 'Panel 1',
|
||||
});
|
||||
event.api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
title: 'Panel 2',
|
||||
position: { referencePanel: 'panel_1', direction: 'within' },
|
||||
});
|
||||
event.api.addPanel({
|
||||
id: 'panel_3',
|
||||
component: 'default',
|
||||
title: 'Panel 3',
|
||||
});
|
||||
|
||||
event.api.addPanel({
|
||||
id: 'panel_4',
|
||||
component: 'default',
|
||||
title: 'Panel 4',
|
||||
position: { referencePanel: 'panel_3', direction: 'below' },
|
||||
});
|
||||
|
||||
setApi(event.api);
|
||||
};
|
||||
|
||||
const onSave = () => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
localStorage.setItem(
|
||||
'dv_rendermode_state',
|
||||
JSON.stringify({ size: value, state: api.toJSON() })
|
||||
);
|
||||
};
|
||||
|
||||
const onLoad = () => {
|
||||
if (!api) {
|
||||
return;
|
||||
}
|
||||
|
||||
const state = localStorage.getItem('dv_rendermode_state');
|
||||
if (typeof state !== 'string') {
|
||||
return;
|
||||
}
|
||||
|
||||
const json = JSON.parse(state) as {
|
||||
size: string;
|
||||
state: SerializedDockview;
|
||||
};
|
||||
setValue(json.size);
|
||||
api.fromJSON(json.state);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{ height: '100%', display: 'flex', flexDirection: 'column' }}
|
||||
>
|
||||
<div>
|
||||
<button onClick={onSave}>Save</button>
|
||||
<button onClick={onLoad}>Load</button>
|
||||
<input
|
||||
onChange={(event) => setValue(event.target.value)}
|
||||
type="range"
|
||||
min="1"
|
||||
max="100"
|
||||
value={value}
|
||||
/>
|
||||
</div>
|
||||
<div style={{ height: `${value}%`, width: `${value}%` }}>
|
||||
<DockviewReact
|
||||
components={components}
|
||||
onReady={onReady}
|
||||
className={props.theme || 'dockview-theme-abyss'}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default DockviewDemo;
|
20
packages/docs/sandboxes/rendermode-dockview/src/index.tsx
Normal file
20
packages/docs/sandboxes/rendermode-dockview/src/index.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { StrictMode } from 'react';
|
||||
import * as ReactDOMClient from 'react-dom/client';
|
||||
import './styles.css';
|
||||
import 'dockview/dist/styles/dockview.css';
|
||||
|
||||
import App from './app';
|
||||
|
||||
const rootElement = document.getElementById('root');
|
||||
|
||||
if (rootElement) {
|
||||
const root = ReactDOMClient.createRoot(rootElement);
|
||||
|
||||
root.render(
|
||||
<StrictMode>
|
||||
<div className="app">
|
||||
<App />
|
||||
</div>
|
||||
</StrictMode>
|
||||
);
|
||||
}
|
16
packages/docs/sandboxes/rendermode-dockview/src/styles.css
Normal file
16
packages/docs/sandboxes/rendermode-dockview/src/styles.css
Normal file
@ -0,0 +1,16 @@
|
||||
body {
|
||||
margin: 0px;
|
||||
color: white;
|
||||
font-family: sans-serif;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
#root {
|
||||
height: 100vh;
|
||||
width: 100vw;
|
||||
}
|
||||
|
||||
.app {
|
||||
height: 100%;
|
||||
|
||||
}
|
18
packages/docs/sandboxes/rendermode-dockview/tsconfig.json
Normal file
18
packages/docs/sandboxes/rendermode-dockview/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"outDir": "build/dist",
|
||||
"module": "esnext",
|
||||
"target": "es5",
|
||||
"lib": ["es6", "dom"],
|
||||
"sourceMap": true,
|
||||
"allowJs": true,
|
||||
"jsx": "react-jsx",
|
||||
"moduleResolution": "node",
|
||||
"rootDir": "src",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitThis": true,
|
||||
"noImplicitAny": true,
|
||||
"strictNullChecks": true
|
||||
}
|
||||
}
|
@ -112,8 +112,6 @@ const Container = () => {
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
||||
return <App />;
|
||||
};
|
||||
|
||||
export default Container;
|
||||
|
Loading…
Reference in New Issue
Block a user