Merge branch 'master' of https://github.com/mathuo/dockview into 687-css-class-prefixing

This commit is contained in:
mathuo 2024-10-12 15:22:08 +01:00
commit c086cabd39
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
68 changed files with 1665 additions and 522 deletions

View File

@ -2,7 +2,7 @@
"packages": [
"packages/*"
],
"version": "1.16.1",
"version": "1.17.2",
"npmClient": "yarn",
"command": {
"publish": {

View File

@ -1,6 +1,6 @@
{
"name": "dockview-angular",
"version": "1.16.1",
"version": "1.17.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
@ -54,6 +54,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview-core": "^1.16.1"
"dockview-core": "^1.17.2"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "dockview-core",
"version": "1.16.1",
"version": "1.17.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",

View File

@ -54,7 +54,7 @@ describe('defaultTab', () => {
let el = cut.element.querySelector('.dv-default-tab-action');
fireEvent.mouseDown(el!);
fireEvent.pointerDown(el!);
expect(api.close).toHaveBeenCalledTimes(0);
fireEvent.click(el!);

View File

@ -459,6 +459,7 @@ describe('tabsContainer', () => {
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
@ -486,10 +487,11 @@ describe('tabsContainer', () => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
x: 100,
y: 60,
@ -498,7 +500,7 @@ describe('tabsContainer', () => {
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
@ -513,6 +515,7 @@ describe('tabsContainer', () => {
onDidRemovePanel: jest.fn(),
element: document.createElement('div'),
addFloatingGroup: jest.fn(),
doSetGroupActive: jest.fn(),
});
const groupPanelMock = jest.fn<DockviewGroupPanel, []>(() => {
@ -540,14 +543,15 @@ describe('tabsContainer', () => {
return { top: 10, left: 20, width: 0, height: 0 } as any;
});
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.doSetGroupActive).toHaveBeenCalledWith(groupPanel);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(0);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(0);
const event2 = new KeyboardEvent('mousedown', { shiftKey: false });
const event2 = new KeyboardEvent('pointerdown', { shiftKey: false });
const eventPreventDefaultSpy2 = jest.spyOn(event2, 'preventDefault');
fireEvent(container, event2);
@ -595,7 +599,7 @@ describe('tabsContainer', () => {
const el = cut.element.querySelector('.dv-tab')!;
expect(el).toBeTruthy();
const event = new KeyboardEvent('mousedown', { shiftKey: true });
const event = new KeyboardEvent('pointerdown', { shiftKey: true });
const preventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(el, event);

View File

@ -1,20 +1,16 @@
import { DockviewApi } from '../../../../api/component.api';
import { Watermark } from '../../../../dockview/components/watermark/watermark';
import { fromPartial } from '@total-typescript/shoehorn';
describe('watermark', () => {
test('that the group is closed when the close button is clicked', () => {
const cut = new Watermark();
const mockApi = jest.fn<Partial<DockviewApi>, any[]>(() => {
return {
removeGroup: jest.fn(),
};
const api = fromPartial<DockviewApi>({
removeGroup: jest.fn(),
});
const api = <DockviewApi>new mockApi();
const group = jest.fn() as any;
cut.init({ containerApi: api });
cut.updateParentGroup(group, true);
cut.init({ containerApi: api, group });
const closeEl = cut.element.querySelector('.dv-close-action')!;

View File

@ -8,9 +8,9 @@ import { PanelUpdateEvent } from '../../panel/types';
import { Orientation } from '../../splitview/splitview';
import { CompositeDisposable } from '../../lifecycle';
import { Emitter } from '../../events';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
import { IDockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fireEvent, getByTestId, queryByTestId } from '@testing-library/dom';
import { fireEvent, queryByTestId } from '@testing-library/dom';
import { getPanelData } from '../../dnd/dataTransfer';
import {
GroupDragEvent,
@ -262,6 +262,112 @@ describe('dockviewComponent', () => {
dockview.dispose();
});
describe('move group', () => {
test('horizontal', () => {
dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
dockview.layout(600, 1000);
const panel1 = dockview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel2',
component: 'default',
position: { direction: 'right' },
});
const panel3 = dockview.addPanel({
id: 'panel3',
component: 'default',
position: { direction: 'right' },
});
expect(panel1.api.width).toBe(200);
expect(panel2.api.width).toBe(200);
expect(panel3.api.width).toBe(200);
panel3.api.setSize({ width: 300 });
expect(panel1.api.width).toBe(200);
expect(panel2.api.width).toBe(100);
expect(panel3.api.width).toBe(300);
dockview.moveGroup({
from: { group: panel3.api.group },
to: { group: panel1.api.group, position: 'right' },
});
expect(panel1.api.width).toBe(200);
expect(panel2.api.width).toBe(100);
expect(panel3.api.width).toBe(300);
});
test('vertical', () => {
dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
dockview.layout(1000, 600);
const panel1 = dockview.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel2',
component: 'default',
position: { direction: 'below' },
});
const panel3 = dockview.addPanel({
id: 'panel3',
component: 'default',
position: { direction: 'below' },
});
expect(panel1.api.height).toBe(200);
expect(panel2.api.height).toBe(200);
expect(panel3.api.height).toBe(200);
panel3.api.setSize({ height: 300 });
expect(panel1.api.height).toBe(200);
expect(panel2.api.height).toBe(100);
expect(panel3.api.height).toBe(300);
dockview.moveGroup({
from: { group: panel3.api.group },
to: { group: panel1.api.group, position: 'bottom' },
});
expect(panel1.api.height).toBe(200);
expect(panel2.api.height).toBe(100);
expect(panel3.api.height).toBe(300);
});
});
test('set active panel', () => {
dockview.layout(500, 1000);
@ -626,6 +732,7 @@ describe('dockviewComponent', () => {
panel1: {
id: 'panel1',
contentComponent: 'default',
tabComponent: 'tab-default',
title: 'panel1',
},
panel2: {
@ -637,22 +744,26 @@ describe('dockviewComponent', () => {
id: 'panel3',
contentComponent: 'default',
title: 'panel3',
renderer: 'onlyWhenVisible',
},
panel4: {
id: 'panel4',
contentComponent: 'default',
title: 'panel4',
renderer: 'always',
},
panel5: {
id: 'panel5',
contentComponent: 'default',
title: 'panel5',
minimumHeight: 100,
maximumHeight: 1000,
minimumWidth: 200,
maximumWidth: 2000,
},
},
});
// dockview.layout(1000, 1000, true);
expect(JSON.parse(JSON.stringify(dockview.toJSON()))).toEqual({
activeGroup: 'group-1',
grid: {
@ -712,6 +823,7 @@ describe('dockviewComponent', () => {
panel1: {
id: 'panel1',
contentComponent: 'default',
tabComponent: 'tab-default',
title: 'panel1',
},
panel2: {
@ -723,16 +835,22 @@ describe('dockviewComponent', () => {
id: 'panel3',
contentComponent: 'default',
title: 'panel3',
renderer: 'onlyWhenVisible',
},
panel4: {
id: 'panel4',
contentComponent: 'default',
title: 'panel4',
renderer: 'always',
},
panel5: {
id: 'panel5',
contentComponent: 'default',
title: 'panel5',
minimumHeight: 100,
maximumHeight: 1000,
minimumWidth: 200,
maximumWidth: 2000,
},
},
});

View File

@ -116,10 +116,6 @@ class Watermark implements IWatermarkRenderer {
return {};
}
updateParentGroup() {
//
}
dispose() {
//
}

View File

@ -153,14 +153,14 @@ describe('events', () => {
const disposable = addDisposableWindowListener(
element as any,
'mousedown',
'pointerdown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -171,7 +171,7 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -187,13 +187,13 @@ describe('events', () => {
const disposable = addDisposableWindowListener(
element as any,
'mousedown',
'pointerdown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);
@ -204,7 +204,7 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);
@ -220,14 +220,14 @@ describe('events', () => {
const disposable = addDisposableListener(
element as any,
'mousedown',
'pointerdown',
handler,
true
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -238,7 +238,7 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
true
);
@ -254,13 +254,13 @@ describe('events', () => {
const disposable = addDisposableListener(
element as any,
'mousedown',
'pointerdown',
handler
);
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.addEventListener).toHaveBeenCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);
@ -271,7 +271,7 @@ describe('events', () => {
expect(element.addEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledTimes(1);
expect(element.removeEventListener).toBeCalledWith(
'mousedown',
'pointerdown',
handler,
undefined
);

View File

@ -68,8 +68,8 @@ class TestPanel implements IGridPanelView {
class ClassUnderTest extends BaseGrid<TestPanel> {
readonly gridview = this.gridview;
constructor(options: BaseGridOptions) {
super(options);
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
super(parentElement, options);
}
doRemoveGroup(
@ -106,8 +106,7 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
describe('baseComponentGridview', () => {
test('that .layout(...) force flag works', () => {
const cut = new ClassUnderTest({
parentElement: document.createElement('div'),
const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});
@ -131,8 +130,7 @@ describe('baseComponentGridview', () => {
});
test('can add group', () => {
const cut = new ClassUnderTest({
parentElement: document.createElement('div'),
const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});

View File

@ -369,8 +369,11 @@ describe('overlay', () => {
const overlay1 = createOverlay();
const zIndexValue = (delta: number) =>
`calc(var(--dv-overlay-z-index, 999) + ${delta})`;
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe('999');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
const overlay2 = createOverlay();
const overlay3 = createOverlay();
@ -378,38 +381,38 @@ describe('overlay', () => {
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('2');
expect(overlay1.element.style.zIndex).toBe('999');
expect(overlay2.element.style.zIndex).toBe('1001');
expect(overlay3.element.style.zIndex).toBe('1003');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(4));
overlay2.bringToFront();
expect(overlay1.element.getAttribute('aria-level')).toBe('0');
expect(overlay2.element.getAttribute('aria-level')).toBe('2');
expect(overlay3.element.getAttribute('aria-level')).toBe('1');
expect(overlay1.element.style.zIndex).toBe('999');
expect(overlay2.element.style.zIndex).toBe('1003');
expect(overlay3.element.style.zIndex).toBe('1001');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(0));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(4));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(2));
overlay1.bringToFront();
expect(overlay1.element.getAttribute('aria-level')).toBe('2');
expect(overlay2.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe('1003');
expect(overlay2.element.style.zIndex).toBe('1001');
expect(overlay3.element.style.zIndex).toBe('999');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(4));
expect(overlay2.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
overlay2.dispose();
expect(overlay1.element.getAttribute('aria-level')).toBe('1');
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay1.element.style.zIndex).toBe('1001');
expect(overlay3.element.style.zIndex).toBe('999');
expect(overlay1.element.style.zIndex).toBe(zIndexValue(2));
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
overlay1.dispose();
expect(overlay3.element.getAttribute('aria-level')).toBe('0');
expect(overlay3.element.style.zIndex).toBe('999');
expect(overlay3.element.style.zIndex).toBe(zIndexValue(0));
});
});

View File

@ -258,6 +258,8 @@ describe('overlayRenderContainer', () => {
await exhaustMicrotaskQueue();
expect(spy).toHaveBeenCalledWith('aria-level');
expect(panelContentEl.parentElement!.style.zIndex).toBe('1004');
expect(panelContentEl.parentElement!.style.zIndex).toBe(
'calc(var(--dv-overlay-z-index, 999) + 5)'
);
});
});

View File

@ -21,7 +21,7 @@ export class GroupDragHandler extends DragHandler {
this.addDisposables(
addDisposableListener(
element,
'mousedown',
'pointerdown',
(e) => {
if (e.shiftKey) {
/**

View File

@ -30,7 +30,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._element.appendChild(this.action);
this.addDisposables(
addDisposableListener(this.action, 'mousedown', (ev) => {
addDisposableListener(this.action, 'pointerdown', (ev) => {
ev.preventDefault();
})
);
@ -46,7 +46,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._title = event.title;
this.render();
}),
addDisposableListener(this.action, 'mousedown', (ev) => {
addDisposableListener(this.action, 'pointerdown', (ev) => {
ev.preventDefault();
}),
addDisposableListener(this.action, 'click', (ev) => {

View File

@ -124,7 +124,7 @@ export class Tab extends CompositeDisposable {
this._onDragStart.fire(event);
}),
dragHandler,
addDisposableListener(this._element, 'mousedown', (event) => {
addDisposableListener(this._element, 'pointerdown', (event) => {
if (event.defaultPrevented) {
return;
}

View File

@ -253,7 +253,7 @@ export class TabsContainer
}),
addDisposableListener(
this.voidContainer.element,
'mousedown',
'pointerdown',
(event) => {
const isFloatingGroupsEnabled =
!this.accessor.options.disableFloatingGroups;
@ -278,7 +278,7 @@ export class TabsContainer
}
}
),
addDisposableListener(this.tabContainer, 'mousedown', (event) => {
addDisposableListener(this.tabContainer, 'pointerdown', (event) => {
if (event.defaultPrevented) {
return;
}

View File

@ -5,8 +5,7 @@ import {
import { addDisposableListener } from '../../../events';
import { toggleClass } from '../../../dom';
import { CompositeDisposable } from '../../../lifecycle';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { PanelUpdateEvent } from '../../../panel/types';
import { IDockviewGroupPanel } from '../../dockviewGroupPanel';
import { createCloseButton } from '../../../svg';
import { DockviewApi } from '../../../api/component.api';
@ -15,7 +14,7 @@ export class Watermark
implements IWatermarkRenderer
{
private _element: HTMLElement;
private _group: DockviewGroupPanel | undefined;
private _group: IDockviewGroupPanel | undefined;
private _api: DockviewApi | undefined;
get element(): HTMLElement {
@ -52,8 +51,9 @@ export class Watermark
title.appendChild(actionsContainer);
this.addDisposables(
addDisposableListener(closeAnchor, 'click', (ev) => {
ev.preventDefault();
addDisposableListener(closeAnchor, 'click', (event: MouseEvent) => {
event.preventDefault();
if (this._group) {
this._api?.removeGroup(this._group);
}
@ -61,32 +61,12 @@ export class Watermark
);
}
update(_event: PanelUpdateEvent): void {
// noop
}
focus(): void {
// noop
}
layout(_width: number, _height: number): void {
// noop
}
init(_params: WatermarkRendererInitParameters): void {
this._api = _params.containerApi;
this._group = _params.group;
this.render();
}
updateParentGroup(group: DockviewGroupPanel, _visible: boolean): void {
this._group = group;
this.render();
}
dispose(): void {
super.dispose();
}
private render(): void {
const isOneGroup = !!(this._api && this._api.size <= 1);
toggleClass(this.element, 'dv-has-actions', isOneGroup);

View File

@ -57,6 +57,10 @@ export class DefaultDockviewDeserialzier implements IPanelDeserializer {
view,
{
renderer: panelData.renderer,
minimumWidth: panelData.minimumWidth,
minimumHeight: panelData.minimumHeight,
maximumWidth: panelData.maximumWidth,
maximumHeight: panelData.maximumHeight,
}
);

View File

@ -3,6 +3,7 @@ import {
SerializedGridObject,
getGridLocation,
ISerializedLeafNode,
orthogonal,
} from '../gridview/gridview';
import {
directionToPosition,
@ -51,7 +52,12 @@ import { DockviewPanelModel } from './dockviewPanelModel';
import { getPanelData } from '../dnd/dataTransfer';
import { Parameters } from '../panel/types';
import { Overlay } from '../overlay/overlay';
import { addTestId, toggleClass, watchElementResize } from '../dom';
import {
addTestId,
getDockviewTheme,
toggleClass,
watchElementResize,
} from '../dom';
import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
import {
GroupDragEvent,
@ -92,33 +98,6 @@ function moveGroupWithoutDestroying(options: {
});
}
function getDockviewTheme(element: HTMLElement): string | undefined {
function toClassList(element: HTMLElement) {
const list: string[] = [];
for (let i = 0; i < element.classList.length; i++) {
list.push(element.classList.item(i)!);
}
return list;
}
let theme: string | undefined = undefined;
let parent: HTMLElement | null = element;
while (parent !== null) {
theme = toClassList(parent).find((cls) =>
cls.startsWith('dockview-theme-')
);
if (typeof theme === 'string') {
break;
}
parent = parent.parentElement;
}
return theme;
}
export interface PanelReference {
update: (event: { params: { [key: string]: any } }) => void;
remove: () => void;
@ -362,23 +341,18 @@ export class DockviewComponent
}
constructor(parentElement: HTMLElement, options: DockviewComponentOptions) {
super({
super(parentElement, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
styles: options.hideBorders
? { separatorBorder: 'transparent' }
: undefined,
parentElement: parentElement,
disableAutoResizing: options.disableAutoResizing,
locked: options.locked,
margin: options.gap,
className: options.className,
});
// const gready = document.createElement('div');
// gready.className = 'dv-overlay-render-container';
// this.gridview.element.appendChild(gready);
this.overlayRenderContainer = new OverlayRenderContainer(
this.gridview.element,
this
@ -1022,19 +996,9 @@ export class DockviewComponent
override updateOptions(options: Partial<DockviewComponentOptions>): void {
super.updateOptions(options);
const changed_floatingGroupBounds =
'floatingGroupBounds' in options &&
options.floatingGroupBounds !== this.options.floatingGroupBounds;
const changed_rootOverlayOptions =
'rootOverlayModel' in options &&
options.rootOverlayModel !== this.options.rootOverlayModel;
this._options = { ...this.options, ...options };
if (changed_floatingGroupBounds) {
if ('floatingGroupBounds' in options) {
for (const group of this._floatingGroups) {
switch (this.options.floatingGroupBounds) {
switch (options.floatingGroupBounds) {
case 'boundedWithinViewport':
group.overlay.minimumInViewportHeight = undefined;
group.overlay.minimumInViewportWidth = undefined;
@ -1047,30 +1011,26 @@ export class DockviewComponent
break;
default:
group.overlay.minimumInViewportHeight =
this.options.floatingGroupBounds?.minimumHeightWithinViewport;
options.floatingGroupBounds?.minimumHeightWithinViewport;
group.overlay.minimumInViewportWidth =
this.options.floatingGroupBounds?.minimumWidthWithinViewport;
options.floatingGroupBounds?.minimumWidthWithinViewport;
}
group.overlay.setBounds();
}
}
if (changed_rootOverlayOptions) {
this._rootDropTarget.setOverlayModel(options.rootOverlayModel!);
if ('rootOverlayModel' in options) {
this._rootDropTarget.setOverlayModel(
options.rootOverlayModel ?? DEFAULT_ROOT_OVERLAY_MODEL
);
}
if (
// if explicitly set as `undefined`
'gap' in options &&
options.gap === undefined
) {
this.gridview.margin = 0;
if ('gap' in options) {
this.gridview.margin = options.gap ?? 0;
}
if (typeof options.gap === 'number') {
this.gridview.margin = options.gap;
}
this._options = { ...this.options, ...options };
this.layout(this.gridview.width, this.gridview.height, true);
}
@ -1412,6 +1372,11 @@ export class DockviewComponent
);
}
const initial = {
width: options.initialWidth,
height: options.initialHeight,
};
if (options.position) {
if (isPanelOptionsWithPanel(options.position)) {
const referencePanel =
@ -1453,6 +1418,11 @@ export class DockviewComponent
this.doSetGroupAndPanelActive(group);
}
group.api.setSize({
height: initial?.height,
width: initial?.width,
});
return panel;
}
} else {
@ -1499,6 +1469,11 @@ export class DockviewComponent
skipSetGroupActive: options.inactive,
});
referenceGroup.api.setSize({
width: initial?.width,
height: initial?.height,
});
if (!options.inactive) {
this.doSetGroupAndPanelActive(referenceGroup);
}
@ -1509,7 +1484,13 @@ export class DockviewComponent
location,
target
);
const group = this.createGroupAtLocation(relativeLocation);
const group = this.createGroupAtLocation(
relativeLocation,
this.orientationAtLocation(relativeLocation) ===
Orientation.VERTICAL
? initial?.height
: initial?.width
);
panel = this.createPanel(options, group);
group.model.openPanel(panel, {
skipSetActive: options.inactive,
@ -1543,7 +1524,12 @@ export class DockviewComponent
skipSetGroupActive: options.inactive,
});
} else {
const group = this.createGroupAtLocation();
const group = this.createGroupAtLocation(
[0],
this.gridview.orientation === Orientation.VERTICAL
? initial?.height
: initial?.width
);
panel = this.createPanel(options, group);
group.model.openPanel(panel, {
skipSetActive: options.inactive,
@ -1681,7 +1667,12 @@ export class DockviewComponent
);
const group = this.createGroup(options);
this.doAddGroup(group, relativeLocation);
const size =
this.getLocationOrientation(relativeLocation) ===
Orientation.VERTICAL
? options.initialHeight
: options.initialWidth;
this.doAddGroup(group, relativeLocation, size);
if (!options.skipSetActive) {
this.doSetGroupAndPanelActive(group);
}
@ -1695,6 +1686,13 @@ export class DockviewComponent
}
}
private getLocationOrientation(location: number[]) {
return location.length % 2 == 0 &&
this.gridview.orientation === Orientation.HORIZONTAL
? Orientation.HORIZONTAL
: Orientation.VERTICAL;
}
removeGroup(
group: DockviewGroupPanel,
options?:
@ -2109,7 +2107,24 @@ export class DockviewComponent
target
);
this.gridview.addView(from, Sizing.Distribute, dropLocation);
let size: number;
switch (this.gridview.orientation) {
case Orientation.VERTICAL:
size =
referenceLocation.length % 2 == 0
? from.api.width
: from.api.height;
break;
case Orientation.HORIZONTAL:
size =
referenceLocation.length % 2 == 0
? from.api.height
: from.api.width;
break;
}
this.gridview.addView(from, size, dropLocation);
}
from.panels.forEach((panel) => {
@ -2283,7 +2298,13 @@ export class DockviewComponent
this._api,
group,
view,
{ renderer: options.renderer }
{
renderer: options.renderer,
minimumWidth: options.minimumWidth,
minimumHeight: options.minimumHeight,
maximumWidth: options.maximumWidth,
maximumHeight: options.maximumHeight,
}
);
panel.init({
@ -2295,10 +2316,11 @@ export class DockviewComponent
}
private createGroupAtLocation(
location: number[] = [0]
location: number[],
size?: number
): DockviewGroupPanel {
const group = this.createGroup();
this.doAddGroup(group, location);
this.doAddGroup(group, location, size);
return group;
}
@ -2307,4 +2329,11 @@ export class DockviewComponent
group.value.model.containsPanel(panel)
)?.value;
}
private orientationAtLocation(location: number[]) {
const rootOrientation = this.gridview.orientation;
return location.length % 2 == 1
? rootOrientation
: orthogonal(rootOrientation);
}
}

View File

@ -34,6 +34,34 @@ export class DockviewGroupPanel
{
private readonly _model: DockviewGroupPanelModel;
get minimumWidth(): number {
const activePanelMinimumWidth = this.activePanel?.minimumWidth;
return typeof activePanelMinimumWidth === 'number'
? activePanelMinimumWidth
: MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH;
}
get minimumHeight(): number {
const activePanelMinimumHeight = this.activePanel?.minimumHeight;
return typeof activePanelMinimumHeight === 'number'
? activePanelMinimumHeight
: MINIMUM_DOCKVIEW_GROUP_PANEL_HEIGHT;
}
get maximumWidth(): number {
const activePanelMaximumWidth = this.activePanel?.maximumWidth;
return typeof activePanelMaximumWidth === 'number'
? activePanelMaximumWidth
: Number.MAX_SAFE_INTEGER;
}
get maximumHeight(): number {
const activePanelMaximumHeight = this.activePanel?.maximumHeight;
return typeof activePanelMaximumHeight === 'number'
? activePanelMaximumHeight
: Number.MAX_SAFE_INTEGER;
}
get panels(): IDockviewPanel[] {
return this._model.panels;
}
@ -71,8 +99,14 @@ export class DockviewGroupPanel
id,
'groupview_default',
{
minimumHeight: MINIMUM_DOCKVIEW_GROUP_PANEL_HEIGHT,
minimumWidth: MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH,
minimumHeight:
options.constraints?.minimumHeight ??
MINIMUM_DOCKVIEW_GROUP_PANEL_HEIGHT,
minimumWidth:
options.constraints?.maximumHeight ??
MINIMUM_DOCKVIEW_GROUP_PANEL_WIDTH,
maximumHeight: options.constraints?.maximumHeight,
maximumWidth: options.constraints?.maximumWidth,
},
new DockviewGroupPanelApiImpl(id, accessor)
);

View File

@ -38,6 +38,7 @@ import {
} from './options';
import { OverlayRenderContainer } from '../overlay/overlayRenderContainer';
import { TitleEvent } from '../api/dockviewPanelApi';
import { Contraints } from '../gridview/gridviewPanel';
interface GroupMoveEvent {
groupId: string;
@ -50,6 +51,9 @@ interface CoreGroupOptions {
locked?: DockviewGroupPanelLocked;
hideHeader?: boolean;
skipSetActive?: boolean;
constraints?: Partial<Contraints>;
initialWidth?: number;
initialHeight?: number;
}
export interface GroupOptions extends CoreGroupOptions {
@ -972,8 +976,6 @@ export class DockviewGroupPanelModel
this.tabsContainer.hide();
this.contentContainer.element.appendChild(this.watermark.element);
this.watermark.updateParentGroup(this.groupPanel, true);
}
if (!this.isEmpty && this.watermark) {
this.watermark.element.remove();

View File

@ -11,6 +11,7 @@ import { IDockviewPanelModel } from './dockviewPanelModel';
import { DockviewComponent } from './dockviewComponent';
import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
import { WillFocusEvent } from '../api/panelApi';
import { Contraints } from '../gridview/gridviewPanel';
export interface IDockviewPanel extends IDisposable, IPanel {
readonly view: IDockviewPanelModel;
@ -18,6 +19,10 @@ export interface IDockviewPanel extends IDisposable, IPanel {
readonly api: DockviewPanelApi;
readonly title: string | undefined;
readonly params: Parameters | undefined;
readonly minimumWidth?: number;
readonly minimumHeight?: number;
readonly maximumWidth?: number;
readonly maximumHeight?: number;
updateParentGroup(
group: DockviewGroupPanel,
options?: { skipSetActive?: boolean }
@ -40,6 +45,11 @@ export class DockviewPanel
private _title: string | undefined;
private _renderer: DockviewPanelRenderer | undefined;
private _minimumWidth: number | undefined;
private _minimumHeight: number | undefined;
private _maximumWidth: number | undefined;
private _maximumHeight: number | undefined;
get params(): Parameters | undefined {
return this._params;
}
@ -56,6 +66,22 @@ export class DockviewPanel
return this._renderer ?? this.accessor.renderer;
}
get minimumWidth(): number | undefined {
return this._minimumWidth;
}
get minimumHeight(): number | undefined {
return this._minimumHeight;
}
get maximumWidth(): number | undefined {
return this._maximumWidth;
}
get maximumHeight(): number | undefined {
return this._maximumHeight;
}
constructor(
public readonly id: string,
component: string,
@ -64,11 +90,15 @@ export class DockviewPanel
private readonly containerApi: DockviewApi,
group: DockviewGroupPanel,
readonly view: IDockviewPanelModel,
options: { renderer?: DockviewPanelRenderer }
options: { renderer?: DockviewPanelRenderer } & Partial<Contraints>
) {
super();
this._renderer = options.renderer;
this._group = group;
this._minimumWidth = options.minimumWidth;
this._minimumHeight = options.minimumHeight;
this._maximumWidth = options.maximumWidth;
this._maximumHeight = options.maximumHeight;
this.api = new DockviewPanelApiImpl(
this,
@ -129,6 +159,10 @@ export class DockviewPanel
: undefined,
title: this.title,
renderer: this._renderer,
minimumHeight: this._minimumHeight,
maximumHeight: this._maximumHeight,
minimumWidth: this._minimumWidth,
maximumWidth: this._maximumWidth,
};
}

View File

@ -16,6 +16,7 @@ import { DockviewPanelRenderer } from '../overlay/overlayRenderContainer';
import { IGroupHeaderProps } from './framework';
import { AnchoredBox } from '../types';
import { FloatingGroupOptions } from './dockviewComponent';
import { Contraints } from '../gridview/gridviewPanel';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;
@ -116,6 +117,17 @@ export const PROPERTY_KEYS: (keyof DockviewOptions)[] = (() => {
return Object.keys(properties) as (keyof DockviewOptions)[];
})();
export interface CreateComponentOptions {
/**
* The unqiue identifer of the component
*/
id: string;
/**
* The component name, this should determine what is rendered.
*/
name: string;
}
export interface DockviewFrameworkOptions {
defaultTabComponent?: string;
createRightHeaderActionComponent?: (
@ -127,14 +139,10 @@ export interface DockviewFrameworkOptions {
createPrefixHeaderActionComponent?: (
group: DockviewGroupPanel
) => IHeaderActionsRenderer;
createTabComponent?: (options: {
id: string;
name: string;
}) => ITabRenderer | undefined;
createComponent: (options: {
id: string;
name: string;
}) => IContentRenderer;
createTabComponent?: (
options: CreateComponentOptions
) => ITabRenderer | undefined;
createComponent: (options: CreateComponentOptions) => IContentRenderer;
createWatermarkComponent?: () => IWatermarkRenderer;
}
@ -230,7 +238,10 @@ export type AddPanelOptions<P extends object = Parameters> = {
* Defaults to `false` which forces newly added panels to become active.
*/
inactive?: boolean;
} & Partial<AddPanelOptionsUnion>;
initialWidth?: number;
initialHeight?: number;
} & Partial<AddPanelOptionsUnion> &
Partial<Contraints>;
type AddGroupOptionsWithPanel = {
referencePanel: string | IDockviewPanel;

View File

@ -32,7 +32,6 @@ export interface IWatermarkRenderer
extends Optional<Omit<IPanel, 'id' | 'init'>, RendererMethodOptionalList> {
readonly element: HTMLElement;
init: (params: WatermarkRendererInitParameters) => void;
updateParentGroup(group: DockviewGroupPanel, visible: boolean): void;
}
export interface ITabRenderer
@ -64,4 +63,8 @@ export interface GroupviewPanelState {
title?: string;
renderer?: DockviewPanelRenderer;
params?: { [key: string]: any };
minimumWidth?: number;
minimumHeight?: number;
maximumWidth?: number;
maximumHeight?: number;
}

View File

@ -280,3 +280,50 @@ export function disableIframePointEvents() {
},
};
}
export function getDockviewTheme(element: HTMLElement): string | undefined {
function toClassList(element: HTMLElement) {
const list: string[] = [];
for (let i = 0; i < element.classList.length; i++) {
list.push(element.classList.item(i)!);
}
return list;
}
let theme: string | undefined = undefined;
let parent: HTMLElement | null = element;
while (parent !== null) {
theme = toClassList(parent).find((cls) =>
cls.startsWith('dockview-theme-')
);
if (typeof theme === 'string') {
break;
}
parent = parent.parentElement;
}
return theme;
}
export class Classnames {
private _classNames: string[] = [];
constructor(private readonly element: HTMLElement) {}
setClassNames(classNames: string) {
for (const className of this._classNames) {
toggleClass(this.element, className, false);
}
this._classNames = classNames
.split(' ')
.filter((v) => v.trim().length > 0);
for (const className of this._classNames) {
toggleClass(this.element, className, true);
}
}
}

View File

@ -7,7 +7,7 @@ import { ISplitviewStyles, Orientation, Sizing } from '../splitview/splitview';
import { IPanel } from '../panel/types';
import { MovementOptions2 } from '../dockview/options';
import { Resizable } from '../resizable';
import { toggleClass } from '../dom';
import { Classnames, toggleClass } from '../dom';
const nextLayoutId = sequentialNumberGenerator();
@ -33,7 +33,6 @@ export interface BaseGridOptions {
readonly proportionalLayout: boolean;
readonly orientation: Orientation;
readonly styles?: ISplitviewStyles;
readonly parentElement: HTMLElement;
readonly disableAutoResizing?: boolean;
readonly locked?: boolean;
readonly margin?: number;
@ -100,7 +99,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
readonly onDidViewVisibilityChangeMicroTaskQueue =
this._onDidViewVisibilityChangeMicroTaskQueue.onEvent;
private classNames: string[] = [];
private readonly _classNames: Classnames;
get id(): string {
return this._id;
@ -147,18 +146,15 @@ export abstract class BaseGrid<T extends IGridPanelView>
this.gridview.locked = value;
}
constructor(options: BaseGridOptions) {
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
super(document.createElement('div'), options.disableAutoResizing);
this.element.style.height = '100%';
this.element.style.width = '100%';
this.classNames = options.className?.split(' ') ?? [];
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
options.parentElement.appendChild(this.element);
parentElement.appendChild(this.element);
this.gridview = new Gridview(
!!options.proportionalLayout,
@ -214,14 +210,26 @@ export abstract class BaseGrid<T extends IGridPanelView>
}
updateOptions(options: Partial<BaseGridOptions>) {
if (typeof options.proportionalLayout === 'boolean') {
// this.gridview.proportionalLayout = options.proportionalLayout; // not supported
}
if (options.orientation) {
this.gridview.orientation = options.orientation;
}
if ('styles' in options) {
// this.gridview.styles = options.styles; // not supported
}
if ('disableResizing' in options) {
this.disableResizing = options.disableAutoResizing ?? false;
}
if ('locked' in options) {
this.locked = options.locked ?? false;
}
if ('margin' in options) {
this.gridview.margin = options.margin ?? 0;
}
if ('className' in options) {
for (const className of this.classNames) {
toggleClass(this.element, className, false);
}
this.classNames = options.className?.split(' ') ?? [];
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
this._classNames.setClassNames(options.className ?? '');
}
}

View File

@ -115,8 +115,7 @@ export class GridviewComponent
}
constructor(parentElement: HTMLElement, options: GridviewComponentOptions) {
super({
parentElement: parentElement,
super(parentElement, {
proportionalLayout: options.proportionalLayout,
orientation: options.orientation,
styles: options.styles,

View File

@ -15,6 +15,13 @@ import { Emitter, Event } from '../events';
import { IViewSize } from './gridview';
import { BaseGrid, IGridPanelView } from './baseComponentGridview';
export interface Contraints {
minimumWidth?: number;
maximumWidth?: number;
minimumHeight?: number;
maximumHeight?: number;
}
export interface GridviewInitParameters extends PanelInitParameters {
minimumWidth?: number;
maximumWidth?: number;

View File

@ -26,8 +26,10 @@
}
.dv-resize-container {
--dv-overlay-z-index: var(--dv-overlay-z-index, 999);
position: absolute;
z-index: 997;
z-index: calc(var(--dv-overlay-z-index) - 2);
border: 1px solid var(--dv-tab-divider-color);
box-shadow: var(--dv-floating-box-shadow);
@ -41,7 +43,7 @@
width: calc(100% - 8px);
left: 4px;
top: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: ns-resize;
}
@ -51,7 +53,7 @@
width: calc(100% - 8px);
left: 4px;
bottom: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: ns-resize;
}
@ -61,7 +63,7 @@
width: 4px;
left: -2px;
top: 4px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: ew-resize;
}
@ -71,7 +73,7 @@
width: 4px;
right: -2px;
top: 4px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: ew-resize;
}
@ -81,7 +83,7 @@
width: 4px;
top: -2px;
left: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: nw-resize;
}
@ -91,7 +93,7 @@
width: 4px;
right: -2px;
top: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: ne-resize;
}
@ -101,7 +103,7 @@
width: 4px;
left: -2px;
bottom: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: sw-resize;
}
@ -111,7 +113,7 @@
width: 4px;
right: -2px;
bottom: -2px;
z-index: 999;
z-index: var(--dv-overlay-z-index);
position: absolute;
cursor: se-resize;
}

View File

@ -13,8 +13,6 @@ import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
import { AnchoredBox } from '../types';
export const DEFAULT_OVERLAY_Z_INDEX = 999;
class AriaLevelTracker {
private _orderedList: HTMLElement[] = [];
@ -37,7 +35,9 @@ class AriaLevelTracker {
private update(): void {
for (let i = 0; i < this._orderedList.length; i++) {
this._orderedList[i].setAttribute('aria-level', `${i}`);
this._orderedList[i].style.zIndex = `${DEFAULT_OVERLAY_Z_INDEX + i * 2}`;
this._orderedList[
i
].style.zIndex = `calc(var(--dv-overlay-z-index, 999) + ${i * 2})`;
}
}
}
@ -241,7 +241,7 @@ export class Overlay extends CompositeDisposable {
iframes.release();
},
},
addDisposableWindowListener(window, 'mousemove', (e) => {
addDisposableWindowListener(window, 'pointermove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const x = e.clientX - containerRect.left;
@ -327,7 +327,7 @@ export class Overlay extends CompositeDisposable {
this.setBounds(bounds);
}),
addDisposableWindowListener(window, 'mouseup', () => {
addDisposableWindowListener(window, 'pointerup', () => {
toggleClass(
this._element,
'dv-resize-container-dragging',
@ -342,7 +342,7 @@ export class Overlay extends CompositeDisposable {
this.addDisposables(
move,
addDisposableListener(dragTarget, 'mousedown', (event) => {
addDisposableListener(dragTarget, 'pointerdown', (event) => {
if (event.defaultPrevented) {
event.preventDefault();
return;
@ -358,7 +358,7 @@ export class Overlay extends CompositeDisposable {
}),
addDisposableListener(
this.options.content,
'mousedown',
'pointerdown',
(event) => {
if (event.defaultPrevented) {
return;
@ -377,7 +377,7 @@ export class Overlay extends CompositeDisposable {
),
addDisposableListener(
this.options.content,
'mousedown',
'pointerdown',
() => {
arialLevelTracker.push(this._element);
},
@ -409,7 +409,7 @@ export class Overlay extends CompositeDisposable {
this.addDisposables(
move,
addDisposableListener(resizeHandleElement, 'mousedown', (e) => {
addDisposableListener(resizeHandleElement, 'pointerdown', (e) => {
e.preventDefault();
let startPosition: {
@ -422,7 +422,7 @@ export class Overlay extends CompositeDisposable {
const iframes = disableIframePointEvents();
move.value = new CompositeDisposable(
addDisposableWindowListener(window, 'mousemove', (e) => {
addDisposableWindowListener(window, 'pointermove', (e) => {
const containerRect =
this.options.container.getBoundingClientRect();
const overlayRect =
@ -593,7 +593,7 @@ export class Overlay extends CompositeDisposable {
iframes.release();
},
},
addDisposableWindowListener(window, 'mouseup', () => {
addDisposableWindowListener(window, 'pointerup', () => {
move.dispose();
this._onDidChangeEnd.fire();
})

View File

@ -1,14 +1,12 @@
.dv-render-overlay {
--dv-overlay-z-index: var(--dv-overlay-z-index, 999);
position: absolute;
z-index: 1;
height: 100%;
&.dv-render-overlay-float {
z-index: 998;
&.dv-render-overlay-active {
// z-index: 1000;
}
z-index: calc(var(--dv-overlay-z-index) - 1);
}
}

View File

@ -9,7 +9,6 @@ import {
} from '../lifecycle';
import { IDockviewPanel } from '../dockview/dockviewPanel';
import { DockviewComponent } from '../dockview/dockviewComponent';
import { DEFAULT_OVERLAY_Z_INDEX } from './overlay';
export type DockviewPanelRenderer = 'onlyWhenVisible' | 'always';
@ -137,9 +136,9 @@ export class OverlayRenderContainer extends CompositeDisposable {
const level = Number(
element.getAttribute('aria-level')
);
focusContainer.style.zIndex = `${
DEFAULT_OVERLAY_Z_INDEX + level * 2 + 1
}`;
focusContainer.style.zIndex = `calc(var(--dv-overlay-z-index, 999) + ${
level * 2 + 1
})`;
};
const observer = new MutationObserver(() => {

View File

@ -24,7 +24,7 @@ import { sequentialNumberGenerator } from '../math';
import { PaneTransfer } from '../dnd/dataTransfer';
import { Resizable } from '../resizable';
import { Parameters } from '../panel/types';
import { toggleClass } from '../dom';
import { Classnames, toggleClass } from '../dom';
const nextLayoutId = sequentialNumberGenerator();
@ -152,7 +152,7 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
private readonly _onDidRemoveView = new Emitter<PaneviewPanel>();
readonly onDidRemoveView = this._onDidRemoveView.event;
private classNames: string[] = [];
private readonly _classNames: Classnames;
get id(): string {
return this._id;
@ -213,11 +213,8 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
this._onDidRemoveView
);
this.classNames = options.className?.split(' ') ?? [];
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
this._options = options;
@ -247,13 +244,11 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
updateOptions(options: Partial<PaneviewComponentOptions>): void {
if ('className' in options) {
for (const className of this.classNames) {
toggleClass(this.element, className, false);
}
this.classNames = options.className?.split(' ') ?? [];
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
this._classNames.setClassNames(options.className ?? '');
}
if ('disableResizing' in options) {
this.disableResizing = options.disableAutoResizing ?? false;
}
this._options = { ...this.options, ...options };

View File

@ -1,6 +1,6 @@
import { addStyles } from './dom';
import { Emitter, addDisposableWindowListener } from './events';
import { CompositeDisposable, IDisposable } from './lifecycle';
import { CompositeDisposable, Disposable, IDisposable } from './lifecycle';
import { Box } from './types';
export type PopoutWindowOptions = {
@ -99,6 +99,9 @@ export class PopoutWindow extends CompositeDisposable {
this._window = { value: externalWindow, disposable };
disposable.addDisposables(
Disposable.from(() => {
externalWindow.close();
}),
addDisposableWindowListener(window, 'beforeunload', () => {
/**
* before the main window closes we should close this popup too

View File

@ -17,7 +17,7 @@ import { Emitter, Event } from '../events';
import { SplitviewPanel, ISplitviewPanel } from './splitviewPanel';
import { createComponent } from '../panel/componentFactory';
import { Resizable } from '../resizable';
import { toggleClass } from '../dom';
import { Classnames, toggleClass } from '../dom';
export interface SerializedSplitviewPanelData {
id: string;
@ -100,7 +100,7 @@ export class SplitviewComponent
private readonly _onDidLayoutChange = new Emitter<void>();
readonly onDidLayoutChange: Event<void> = this._onDidLayoutChange.event;
private classNames: string[] = [];
private readonly _classNames: Classnames;
get panels(): SplitviewPanel[] {
return this.splitview.getViews();
@ -162,11 +162,8 @@ export class SplitviewComponent
) {
super(parentElement, options.disableAutoResizing);
this.classNames = options.className?.split(' ') ?? [];
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
this._options = options;
@ -189,25 +186,19 @@ export class SplitviewComponent
updateOptions(options: Partial<SplitviewComponentOptions>): void {
if ('className' in options) {
for (const className of this.classNames) {
toggleClass(this.element, className, false);
}
this.classNames = options.className?.split(' ') ?? [];
for (const className of this.classNames) {
toggleClass(this.element, className, true);
}
this._classNames.setClassNames(options.className ?? '');
}
const hasOrientationChanged =
typeof options.orientation === 'string' &&
this.options.orientation !== options.orientation;
if ('disableResizing' in options) {
this.disableResizing = options.disableAutoResizing ?? false;
}
this._options = { ...this.options, ...options };
if (hasOrientationChanged) {
if (typeof options.orientation === 'string') {
this.splitview.orientation = options.orientation!;
}
this._options = { ...this.options, ...options };
this.splitview.layout(
this.splitview.size,
this.splitview.orthogonalSize

View File

@ -8,6 +8,7 @@
--dv-tabs-container-scrollbar-color: #888;
--dv-icon-hover-background-color: rgba(90, 93, 94, 0.31);
--dv-floating-box-shadow: 8px 8px 8px 0px rgba(83, 89, 93, 0.5);
--dv-overlay-z-index: 999;
}
@mixin dockview-theme-dark-mixin {
@ -267,29 +268,28 @@
}
}
.dv-vertical > .dv-sash-container > .dv-sash {
&:not(.disabled) {
&::after {
content: '';
height: 4px;
width: 40px;
border-radius: 2px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--dv-separator-handle-background-color);
position: absolute;
}
&:hover {
.vertical > .sash-container > .sash {
&:not(.disabled) {
&::after {
background-color: var(
--dv-separator-handle-hover-background-color
);
content: '';
height: 4px;
width: 40px;
border-radius: 2px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--dv-separator-handle-background-color);
position: absolute;
}
&:hover {
&::after {
background-color: var(
--dv-separator-handle-hover-background-color
);
}
}
}
}
}
.dv-horizontal > .dv-sash-container > .dv-sash {
@ -308,12 +308,25 @@
&:hover {
&::after {
background-color: var(
--dv-separator-handle-hover-background-color
);
content: '';
height: 40px;
width: 4px;
border-radius: 2px;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
background-color: var(--dv-separator-handle-background-color);
position: absolute;
}
&:hover {
&::after {
background-color: var(
--dv-separator-handle-hover-background-color
);
}
}
}
}
}
}
@ -326,7 +339,7 @@
--dv-tabs-and-actions-container-background-color: #fcfcfc;
//
--dv-activegroup-visiblepanel-tab-background-color: #f0f1f2;
--dv-activegroup-hiddenpanel-tab-background-color: ##fcfcfc;
--dv-activegroup-hiddenpanel-tab-background-color: #fcfcfc;
--dv-inactivegroup-visiblepanel-tab-background-color: #f0f1f2;
--dv-inactivegroup-hiddenpanel-tab-background-color: #fcfcfc;
--dv-tab-divider-color: transparent;

View File

@ -1,6 +1,6 @@
{
"name": "dockview-react",
"version": "1.16.1",
"version": "1.17.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
@ -54,6 +54,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage"
},
"dependencies": {
"dockview": "^1.16.1"
"dockview": "^1.17.2"
}
}

View File

@ -1,6 +1,6 @@
{
"name": "dockview-vue",
"version": "1.16.1",
"version": "1.17.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
@ -52,6 +52,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage"
},
"dependencies": {
"dockview-core": "^1.16.1"
"dockview-core": "^1.17.2"
}
}

View File

@ -183,10 +183,6 @@ export class VueWatermarkRenderer
);
}
updateParentGroup(group: DockviewGroupPanel, visible: boolean): void {
// TODO: make optional on interface
}
update(event: PanelUpdateEvent<Parameters>): void {
// noop
}

View File

@ -16,6 +16,8 @@
##
![](https://github.com/mathuo/dockview/blob/HEAD/packages/docs/static/img/splashscreen.gif?raw=true)
Please see the website: https://dockview.dev
## Features

View File

@ -1,6 +1,6 @@
{
"name": "dockview",
"version": "1.16.1",
"version": "1.17.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
@ -54,6 +54,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview-core": "^1.16.1"
"dockview-core": "^1.17.2"
}
}

View File

@ -176,7 +176,7 @@ describe('defaultTab', () => {
expect(element.querySelector('.dv-default-tab-action')).toBeTruthy();
});
test('that mouseDown on close button prevents panel becoming active', async () => {
test('that pointerDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
@ -197,7 +197,7 @@ describe('defaultTab', () => {
'.dv-default-tab-action'
) as HTMLElement;
fireEvent.mouseDown(btn);
fireEvent.pointerDown(btn);
expect(api.setActive).toHaveBeenCalledTimes(0);
fireEvent.click(element);

View File

@ -49,7 +49,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
[api, closeActionOverride]
);
const onMouseDown = React.useCallback((e: React.MouseEvent) => {
const onPointerDown = React.useCallback((e: React.MouseEvent) => {
e.preventDefault();
}, []);
@ -79,7 +79,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
{!hideClose && (
<div
className="dv-default-tab-action"
onMouseDown={onMouseDown}
onPointerDown={onPointerDown}
onClick={onClose}
>
<CloseButton />

View File

@ -57,13 +57,6 @@ export class ReactWatermarkPart implements IWatermarkRenderer {
// noop - retrieval from api
}
updateParentGroup(
_group: DockviewGroupPanel,
_isPanelVisible: boolean
): void {
// noop
}
dispose(): void {
this.part?.dispose();
}

View File

@ -0,0 +1,23 @@
---
slug: dockview-1.17.0-release
title: Dockview 1.17.0
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
- Touch Support [#698](https://github.com/mathuo/dockview/pull/698)
- Initial and bounding panel sizing [#690](https://github.com/mathuo/dockview/pull/690)
- Improve group resize logic [#693](https://github.com/mathuo/dockview/pull/693)
## 🛠 Miscs
- Bug: Theme Typo [#694](https://github.com/mathuo/dockview/pull/694)
- Docs [#703](https://github.com/mathuo/dockview/pull/703)
## 🔥 Breaking changes

View File

@ -0,0 +1,23 @@
---
slug: dockview-1.17.1-release
title: Dockview 1.17.1
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
- Touch Support [#698](https://github.com/mathuo/dockview/pull/698) [#709](https://github.com/mathuo/dockview/pull/709)
- Initial and bounding panel sizing [#690](https://github.com/mathuo/dockview/pull/690)
- Improve group resize logic [#693](https://github.com/mathuo/dockview/pull/693)
## 🛠 Miscs
- Bug: Theme Typo [#694](https://github.com/mathuo/dockview/pull/694)
- Docs [#703](https://github.com/mathuo/dockview/pull/703)
## 🔥 Breaking changes

View File

@ -0,0 +1,21 @@
---
slug: dockview-1.17.2-release
title: Dockview 1.17.2
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
- Customization of z-index [#730](https://github.com/mathuo/dockview/pull/730) [#724](https://github.com/mathuo/dockview/pull/724)
## 🛠 Miscs
- Adjust README.md file [#715](https://github.com/mathuo/dockview/pull/715)
- Docs typo [#714](https://github.com/mathuo/dockview/pull/714)
## 🔥 Breaking changes

View File

@ -1,12 +0,0 @@
import { MultiFrameworkContainer } from '@site/src/components/ui/container';
import EventsDockview from '@site/sandboxes/events-dockview/src/app';
# Events
A simple example showing events fired by `dockviewz that can be interacted with.
<MultiFrameworkContainer
height={600}
sandboxId="events-dockview"
react={EventsDockview}
/>

View File

@ -9,25 +9,9 @@ import { DocRef } from '@site/src/components/ui/reference/docRef';
import LiveExample from '@site/src/components/ui/exampleFrame';
<DocRef declaration="DockviewApi"
methods={['onDidDrop']}
methods={['onDidDrop', 'onUnhandledDragOverEvent']}
/>
<FrameworkSpecific framework='React'>
<DocRef declaration="IDockviewReactProps" methods={['showDndOverlay']} />
</FrameworkSpecific>
<FrameworkSpecific framework='Vue'>
<DocRef declaration="IDockviewVueProps" methods={['showDndOverlay']} />
</FrameworkSpecific>
<FrameworkSpecific framework='JavaScript'>
<DocRef declaration="DockviewComponentOptions"
methods={['showDndOverlay']}
/>
</FrameworkSpecific>
## Intercepting Drag Events
You can intercept drag events to attach your own metadata using the `onWillDragPanel` and `onWillDragGroup` api methods.

View File

@ -8,6 +8,15 @@ This section provided a core overview.
The component takes a collection of [Options](/docs/api/dockview/options) as inputs and
once you have created a dock you can store a reference to the [API](/docs/api/dockview/overview) that is created.
<FrameworkSpecific framework='JavaScript'>
```tsx
const element: HTMLElement
const options: DockviewComponentOptions
const api: DockviewApi = createDockview(element, options);
```
</FrameworkSpecific>
<FrameworkSpecific framework='React'>
```tsx
function onReady(event: DockviewReadyEvent) {

View File

@ -176,3 +176,35 @@ api.addPanel({
}
});
```
### Minimum and Maximum
You can define both minimum and maxmium widths and heights, these are persisted with layouts.
:::info
Since panels exist within groups there are occasions where these boundaries will be ignored to prevent overflow and clipping issues within the dock.
:::
```ts
api.addPanel({
id: 'panel_1',
component: 'default',
minimumWidth: 100,
maximumWidth: 100,
minimumHeight: 200,
maximumHeight: 2000
});
```
### Initial Size
You can define an `initialWidth` and `initialHeight`. The dock will may a best attempt to obey these inputs but it may not always be possible due to the constraints of the grid.
```ts
api.addPanel({
id: 'panel_1',
component: 'default',
initialWidth: 100,
initialHeight: 100
});
```

View File

@ -20,7 +20,7 @@ You can register panels through the dock [option](/docs/api/dockview/options) `
<FrameworkSpecific framework='JavaScript'>
<DocRef declaration="DockviewComponentOptions" methods={['components']} />
<DocRef declaration="DockviewComponentOptions" methods={['createComponent']} />
</FrameworkSpecific>
@ -44,6 +44,37 @@ return <DockviewReact components={components}/>
</FrameworkSpecific>
<FrameworkSpecific framework='JavaScript'>
```tsx
class Panel implements IContentRenderer {
private readonly _element: HTMLElement;
get element(): HTMLElement {
return this._element;
}
constructor() {
this._element = document.createElement('div');
}
init(parameters: GroupPanelPartInitParameters): void {
//
}
}
const api = createDockview(parentElement, {
createComponent: (options) => {
switch (options.name) {
case 'component_1':
return new Panel();
}
},
});
```
</FrameworkSpecific>
<FrameworkSpecific framework='Vue'>
```tsx
const App = {

View File

@ -109,7 +109,7 @@ not implemented
## Accessing Custom Panel Parameters
You can provide a generic type that matches the structure of the epxected custom panels parameters
You can provide a generic type that matches the structure of the expected custom panels parameters
to provide type-hints for the panel parameters which can be accessed via the `params` option.
<FrameworkSpecific framework='React'>

View File

@ -25,7 +25,7 @@ The following properties can be set to configure the behaviours of floating grou
<FrameworkSpecific framework='JavaScript'>
<DocRef declaration="DockviewComponentOptions"
methods={['watermarkComponent', 'watermarkFrameworkComponent', 'frameworkComponentFactory']}
methods={['createWatermarkComponent']}
/>
</FrameworkSpecific>

View File

@ -18,7 +18,7 @@ npm install dockview-core
Firstly, install the `dockview` library:
```sh
npm install dockview
npm install dockview-react
```
</FrameworkSpecific>

View File

@ -19,10 +19,15 @@ Firstly, you should import `dockview.css`:
<FrameworkSpecific framework='React'>
```css
@import './node_modules/dockview/dist/styles/dockview.css';
@import './node_modules/dockview-react/dist/styles/dockview.css';
```
</FrameworkSpecific>
<FrameworkSpecific framework='Vue'>
```css
@import './node_modules/dockview-vue/dist/styles/dockview.css';
```
</FrameworkSpecific>
## Provided themes

View File

@ -1,6 +1,6 @@
{
"name": "dockview-docs",
"version": "1.16.1",
"version": "1.17.2",
"private": true,
"scripts": {
"build": "npm run build-templates && docusaurus build",
@ -38,7 +38,7 @@
"ag-grid-react": "^31.0.2",
"axios": "^1.6.3",
"clsx": "^2.1.0",
"dockview": "^1.16.1",
"dockview": "^1.17.2",
"prism-react-renderer": "^2.3.1",
"react-dnd": "^16.0.1",
"react-laag": "^2.0.5",

View File

@ -1,13 +1,33 @@
import { DockviewReact, DockviewReadyEvent } from 'dockview';
import {
DockviewReact,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview';
import * as React from 'react';
const components = {
iframeComponent: () => {
iframeComponent: (props: IDockviewPanelProps) => {
const [enabled, setEnabled] = React.useState<boolean>(
props.api.isActive
);
React.useEffect(() => {
const disposable = props.api.onDidActiveChange((event) => {
setEnabled(event.isActive);
console.log(event);
});
return () => {
disposable.dispose();
};
}, [props.api]);
return (
<iframe
style={{
width: '100%',
height: '100%',
pointerEvents: enabled ? 'inherit' : 'none',
}}
src="https://dockview.dev"
/>

View File

@ -15,8 +15,6 @@
}
}
.data-table {
table {
font-size: 11px;
@ -58,35 +56,41 @@
.selected {
background-color: #4864dc;
}
}
.demo-button {
min-width: 50px;
padding: 0px 2px;
border-radius: 0px;
display: flex;
flex-grow: 1;
align-items: center;
outline: 1px solid #4c65d4;
.button-group {
button {
margin-right: 0px;
}
}
.demo-button {
min-width: 50px;
padding: 0px 2px;
border-radius: 0px;
display: flex;
flex-grow: 1;
align-items: center;
outline: 1px solid #4c65d4;
}
.demo-icon-button {
outline: 1px solid #4c65d4;
flex-grow: 1;
display: flex;
align-items: center;
border-radius: 0px;
padding: 0px 4px;
border: none;
cursor: pointer;
&:disabled {
color: gray;
cursor: help;
}
.demo-icon-button {
outline: 1px solid #4c65d4;
flex-grow: 1;
display: flex;
align-items: center;
border-radius: 0px;
padding: 0px 4px;
border: none;
cursor: pointer;
&:disabled {
color: gray;
cursor: help;
}
span {
font-size: 16px;
}
span {
font-size: 16px;
}
}
}

View File

@ -15,8 +15,24 @@ import { GroupActions } from './groupActions';
import { LeftControls, PrefixHeaderControls, RightControls } from './controls';
import { Table, usePanelApiMetadata } from './debugPanel';
const DebugContext = React.createContext<boolean>(false);
const Option = (props: {
title: string;
onClick: () => void;
value: string;
}) => {
return (
<div>
<span>{`${props.title}: `}</span>
<button onClick={props.onClick}>{props.value}</button>
</div>
);
};
const components = {
default: (props: IDockviewPanelProps) => {
const isDebug = React.useContext(DebugContext);
const metadata = usePanelApiMetadata(props.api);
return (
@ -24,12 +40,11 @@ const components = {
style={{
height: '100%',
overflow: 'auto',
color: 'white',
position: 'relative',
// border: '5px dashed purple',
padding: 5,
border: isDebug ? '2px dashed orange' : '',
}}
>
{/* <Table data={metadata} /> */}
<span
style={{
position: 'absolute',
@ -43,12 +58,52 @@ const components = {
>
{props.api.title}
</span>
{isDebug && (
<div style={{ fontSize: '0.8em' }}>
<Option
title="Panel Rendering Mode"
value={metadata.renderer.value}
onClick={() =>
props.api.setRenderer(
props.api.renderer === 'always'
? 'onlyWhenVisible'
: 'always'
)
}
/>
<Table data={metadata} />
</div>
)}
</div>
);
},
iframe: () => {
nested: (props: IDockviewPanelProps) => {
return (
<DockviewReact
components={components}
onReady={(event: DockviewReadyEvent) => {
event.api.addPanel({ id: 'panel_1', component: 'default' });
event.api.addPanel({ id: 'panel_2', component: 'default' });
event.api.addPanel({
id: 'panel_3',
component: 'default',
floating: true,
});
}}
className={'dockview-theme-abyss'}
/>
);
},
iframe: (props: IDockviewPanelProps) => {
return (
<iframe
onMouseDown={() => {
if (!props.api.isActive) {
props.api.setActive();
}
}}
style={{
width: '100%',
height: '100%',
@ -199,6 +254,7 @@ const DockviewDemo = (props: { theme?: string }) => {
}, [gapCheck]);
const [showLogs, setShowLogs] = React.useState<boolean>(false);
const [debug, setDebug] = React.useState<boolean>(false);
return (
<div
@ -210,6 +266,7 @@ const DockviewDemo = (props: { theme?: string }) => {
padding: '8px',
backgroundColor: 'rgba(0,0,50,0.25)',
borderRadius: '8px',
position: 'relative',
...css,
}}
>
@ -252,6 +309,15 @@ const DockviewDemo = (props: { theme?: string }) => {
padding: '4px',
}}
>
<button
onClick={() => {
setDebug(!debug);
}}
>
<span className="material-symbols-outlined">
engineering
</span>
</button>
<button
onClick={() => {
setShowLogs(!showLogs);
@ -278,18 +344,20 @@ const DockviewDemo = (props: { theme?: string }) => {
display: 'flex',
}}
>
<DockviewReact
components={components}
defaultTabComponent={headerComponents.default}
rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
prefixHeaderActionsComponent={PrefixHeaderControls}
watermarkComponent={
watermark ? WatermarkComponent : undefined
}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
<DebugContext.Provider value={debug}>
<DockviewReact
components={components}
defaultTabComponent={headerComponents.default}
rightHeaderActionsComponent={RightControls}
leftHeaderActionsComponent={LeftControls}
prefixHeaderActionsComponent={PrefixHeaderControls}
watermarkComponent={
watermark ? WatermarkComponent : undefined
}
onReady={onReady}
className={props.theme || 'dockview-theme-abyss'}
/>
</DebugContext.Provider>
</div>
{showLogs && (

View File

@ -2,6 +2,93 @@ import { DockviewApi } from 'dockview';
import * as React from 'react';
import { defaultConfig, nextId } from './defaultLayout';
import { createRoot, Root } from 'react-dom/client';
import { PanelBuilder } from './panelBuilder';
let mount = document.querySelector('.popover-anchor') as HTMLElement | null;
if (!mount) {
mount = document.createElement('div');
mount.className = 'popover-anchor';
document.body.insertBefore(mount, document.body.firstChild);
}
const PopoverComponent = (props: {
close: () => void;
component: React.FC<{ close: () => void }>;
}) => {
const ref = React.useRef<HTMLDivElement>(null);
React.useEffect(() => {
const handler = (ev: MouseEvent) => {
let target = ev.target as HTMLElement;
while (target.parentElement) {
if (target === ref.current) {
return;
}
target = target.parentElement;
}
props.close();
};
window.addEventListener('mousedown', handler);
return () => {
window.removeEventListener('mousedown', handler);
};
}, []);
return (
<div
style={{
position: 'absolute',
top: 0,
left: 0,
zIndex: 9999,
height: '100%',
width: '100%',
}}
>
<div
ref={ref}
style={{
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%,-50%)',
backgroundColor: 'black',
color: 'white',
padding: 10,
}}
>
<props.component close={props.close} />
</div>
</div>
);
};
function usePopover() {
return {
open: (Component: React.FC<{ close: () => void }>) => {
const el = document.createElement('div');
mount!.appendChild(el);
const root = createRoot(el);
root.render(
<PopoverComponent
component={Component}
close={() => {
root.unmount();
el.remove();
}}
/>
);
},
};
}
export const GridActions = (props: {
api?: DockviewApi;
hasCustomWatermark: boolean;
@ -39,13 +126,21 @@ export const GridActions = (props: {
}
};
const onAddPanel = (options?: { inactive: boolean }) => {
props.api?.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${nextId()}`,
inactive: options?.inactive,
});
const popover = usePopover();
const onAddPanel = (options?: { advanced: boolean }) => {
if (options?.advanced) {
popover.open(({ close }) => {
return <PanelBuilder api={props.api!} done={close} />;
});
} else {
props.api?.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${nextId()}`,
renderer: 'always',
});
}
};
const onAddGroup = () => {
@ -60,15 +155,17 @@ export const GridActions = (props: {
return (
<div className="action-container">
<button className="text-button" onClick={() => onAddPanel()}>
Add Panel
</button>
<button
className="text-button"
onClick={() => onAddPanel({ inactive: true })}
>
Add Inactive Panel
</button>
<div className="button-group">
<button className="text-button" onClick={() => onAddPanel()}>
Add Panel
</button>
<button
className="demo-icon-button"
onClick={() => onAddPanel({ advanced: true })}
>
<span className="material-symbols-outlined">tune</span>
</button>
</div>
<button className="text-button" onClick={onAddGroup}>
Add Group
</button>

View File

@ -88,14 +88,18 @@ const GroupAction = (props: {
}
onClick={() => {
if (group) {
props.api.addFloatingGroup(group, {
width: 400,
height: 300,
x: 50,
y: 50,
position: {
width: 400,
height: 300,
top: 50,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -0,0 +1,115 @@
import { DockviewApi } from 'dockview';
import * as React from 'react';
import { nextId } from './defaultLayout';
export const PanelBuilder = (props: { api: DockviewApi; done: () => void }) => {
const [parameters, setParameters] = React.useState<{
initialWidth?: number;
initialHeight?: number;
maximumHeight?: number;
maximumWidth?: number;
minimumHeight?: number;
minimumWidth?: number;
}>({});
return (
<div>
<div
style={{
display: 'grid',
gridTemplateColumns: '1fr 1fr',
}}
>
<div>{'Initial Width'}</div>
<input
type="number"
value={parameters.initialWidth}
onChange={(event) =>
setParameters((_) => ({
..._,
initialWidth: Number(event.target.value),
}))
}
/>
<div>{'Initial Height'}</div>
<input
type="number"
value={parameters.initialHeight}
onChange={(event) =>
setParameters((_) => ({
..._,
initialHeight: Number(event.target.value),
}))
}
/>
<div>{'Maximum Width'}</div>
<input
type="number"
value={parameters.maximumWidth}
onChange={(event) =>
setParameters((_) => ({
..._,
maximumWidth: Number(event.target.value),
}))
}
/>
<div>{'Maximum Height'}</div>
<input
type="number"
value={parameters.maximumHeight}
onChange={(event) =>
setParameters((_) => ({
..._,
maximumHeight: Number(event.target.value),
}))
}
/>
<div>{'Minimum Width'}</div>
<input
type="number"
value={parameters.minimumWidth}
onChange={(event) =>
setParameters((_) => ({
..._,
minimumWidth: Number(event.target.value),
}))
}
/>
<div>{'Minimum Height'}</div>
<input
type="number"
value={parameters.minimumHeight}
onChange={(event) =>
setParameters((_) => ({
..._,
minimumHeight: Number(event.target.value),
}))
}
/>
</div>
<div>
<button
onClick={() => {
props.done();
}}
>
Cancel
</button>
<button
onClick={() => {
props.api?.addPanel({
id: `id_${Date.now().toString()}`,
component: 'default',
title: `Tab ${nextId()}`,
renderer: 'always',
...parameters,
});
props.done();
}}
>
Go
</button>
</div>
</div>
);
};

View File

@ -10,7 +10,7 @@ export interface FrameworkDescriptor {
}
const frameworks: FrameworkDescriptor[] = [
// { value: 'JavaScript', label: 'JavaScript', icon: 'img/js-icon.svg' },
{ value: 'JavaScript', label: 'JavaScript', icon: 'img/js-icon.svg' },
{ value: 'React', label: 'React', icon: 'img/react-icon.svg' },
{ value: 'Vue', label: 'Vue', icon: 'img/vue-icon.svg' },
// { value: 'Angular', label: 'Angular' },

View File

@ -9014,6 +9014,98 @@
}
}
},
{
"name": "maximumHeight",
"code": "number | undefined",
"kind": "accessor",
"value": {
"name": "maximumHeight",
"code": "number | undefined",
"kind": "getSignature",
"returnType": {
"type": "or",
"values": [
{
"type": "intrinsic",
"value": "number"
},
{
"type": "intrinsic",
"value": "undefined"
}
]
}
}
},
{
"name": "maximumWidth",
"code": "number | undefined",
"kind": "accessor",
"value": {
"name": "maximumWidth",
"code": "number | undefined",
"kind": "getSignature",
"returnType": {
"type": "or",
"values": [
{
"type": "intrinsic",
"value": "number"
},
{
"type": "intrinsic",
"value": "undefined"
}
]
}
}
},
{
"name": "minimumHeight",
"code": "number | undefined",
"kind": "accessor",
"value": {
"name": "minimumHeight",
"code": "number | undefined",
"kind": "getSignature",
"returnType": {
"type": "or",
"values": [
{
"type": "intrinsic",
"value": "number"
},
{
"type": "intrinsic",
"value": "undefined"
}
]
}
}
},
{
"name": "minimumWidth",
"code": "number | undefined",
"kind": "accessor",
"value": {
"name": "minimumWidth",
"code": "number | undefined",
"kind": "getSignature",
"returnType": {
"type": "or",
"values": [
{
"type": "intrinsic",
"value": "number"
},
{
"type": "intrinsic",
"value": "undefined"
}
]
}
}
},
{
"name": "params",
"code": "Parameters | undefined",
@ -22780,19 +22872,6 @@
"isReadonly": true
}
},
{
"name": "parentElement",
"code": "HTMLElement",
"kind": "property",
"type": {
"type": "reference",
"value": "HTMLElement",
"source": "typescript"
},
"flags": {
"isReadonly": true
}
},
{
"name": "proportionalLayout",
"code": "boolean",
@ -23032,6 +23111,104 @@
],
"extends": []
},
"Contraints": {
"kind": "interface",
"name": "Contraints",
"children": [
{
"name": "maximumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "maximumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "minimumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "minimumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
}
],
"extends": []
},
"CreateComponentOptions": {
"kind": "interface",
"name": "CreateComponentOptions",
"children": [
{
"name": "id",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {},
"comment": {
"summary": [
{
"kind": "text",
"text": "The unqiue identifer of the component"
}
]
}
},
{
"name": "name",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {},
"comment": {
"summary": [
{
"kind": "text",
"text": "The component name, this should determine what is rendered."
}
]
}
}
],
"extends": []
},
"DockviewDndOverlayEvent": {
"kind": "interface",
"name": "DockviewDndOverlayEvent",
@ -23156,13 +23333,13 @@
"children": [
{
"name": "createComponent",
"code": "(options: { id: string, name: string }): IContentRenderer",
"code": "(options: CreateComponentOptions): IContentRenderer",
"kind": "property",
"type": {
"type": "reflection",
"value": {
"name": "__type",
"code": "(options: { id: string, name: string }): IContentRenderer",
"code": "(options: CreateComponentOptions): IContentRenderer",
"kind": "typeLiteral",
"signatures": [
{
@ -23171,36 +23348,11 @@
"parameters": [
{
"name": "options",
"code": "options: { id: string, name: string }",
"code": "options: CreateComponentOptions",
"type": {
"type": "reflection",
"value": {
"name": "__type",
"code": "{ id: string, name: string }",
"kind": "typeLiteral",
"properties": [
{
"name": "id",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {}
},
{
"name": "name",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {}
}
]
}
"type": "reference",
"value": "CreateComponentOptions",
"source": "dockview-core"
},
"kind": "parameter"
}
@ -23210,7 +23362,7 @@
"value": "IContentRenderer",
"source": "dockview-core"
},
"code": "(options: { id: string, name: string }): IContentRenderer",
"code": "(options: CreateComponentOptions): IContentRenderer",
"kind": "callSignature"
}
]
@ -23343,13 +23495,13 @@
},
{
"name": "createTabComponent",
"code": "(options: { id: string, name: string }): ITabRenderer | undefined",
"code": "(options: CreateComponentOptions): ITabRenderer | undefined",
"kind": "property",
"type": {
"type": "reflection",
"value": {
"name": "__type",
"code": "(options: { id: string, name: string }): ITabRenderer | undefined",
"code": "(options: CreateComponentOptions): ITabRenderer | undefined",
"kind": "typeLiteral",
"signatures": [
{
@ -23358,36 +23510,11 @@
"parameters": [
{
"name": "options",
"code": "options: { id: string, name: string }",
"code": "options: CreateComponentOptions",
"type": {
"type": "reflection",
"value": {
"name": "__type",
"code": "{ id: string, name: string }",
"kind": "typeLiteral",
"properties": [
{
"name": "id",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {}
},
{
"name": "name",
"code": "string",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "string"
},
"flags": {}
}
]
}
"type": "reference",
"value": "CreateComponentOptions",
"source": "dockview-core"
},
"kind": "parameter"
}
@ -23406,7 +23533,7 @@
}
]
},
"code": "(options: { id: string, name: string }): ITabRenderer | undefined",
"code": "(options: CreateComponentOptions): ITabRenderer | undefined",
"kind": "callSignature"
}
]
@ -26716,6 +26843,26 @@
"isReadonly": true
}
},
{
"name": "constraints",
"code": "Partial<Contraints>",
"kind": "property",
"type": {
"type": "reference",
"value": "Partial",
"source": "typescript",
"typeArguments": [
{
"type": "reference",
"value": "Contraints",
"source": "dockview-core"
}
]
},
"flags": {
"isOptional": true
}
},
{
"name": "hideHeader",
"code": "boolean",
@ -26741,6 +26888,30 @@
"isReadonly": true
}
},
{
"name": "initialHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "initialWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "locked",
"code": "'no-drop-target' | boolean",
@ -26865,6 +27036,26 @@
"isOptional": true
}
},
{
"name": "constraints",
"code": "Partial<Contraints>",
"kind": "property",
"type": {
"type": "reference",
"value": "Partial",
"source": "typescript",
"typeArguments": [
{
"type": "reference",
"value": "Contraints",
"source": "dockview-core"
}
]
},
"flags": {
"isOptional": true
}
},
{
"name": "hideHeader",
"code": "boolean",
@ -26887,6 +27078,30 @@
},
"flags": {}
},
{
"name": "initialHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "initialWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "locked",
"code": "'no-drop-target' | boolean",
@ -26964,6 +27179,54 @@
},
"flags": {}
},
{
"name": "maximumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "maximumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "minimumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "minimumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "params",
"code": "",
@ -31160,6 +31423,58 @@
"isReadonly": true
}
},
{
"name": "maximumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true,
"isReadonly": true
}
},
{
"name": "maximumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true,
"isReadonly": true
}
},
{
"name": "minimumHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true,
"isReadonly": true
}
},
{
"name": "minimumWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true,
"isReadonly": true
}
},
{
"name": "params",
"code": "Parameters | undefined",
@ -37257,44 +37572,6 @@
"kind": "callSignature"
}
]
},
{
"name": "updateParentGroup",
"code": "(group: DockviewGroupPanel, visible: boolean): void",
"kind": "method",
"signature": [
{
"name": "updateParentGroup",
"typeParameters": [],
"parameters": [
{
"name": "group",
"code": "group: DockviewGroupPanel",
"type": {
"type": "reference",
"value": "DockviewGroupPanel",
"source": "dockview-core"
},
"kind": "parameter"
},
{
"name": "visible",
"code": "visible: boolean",
"type": {
"type": "intrinsic",
"value": "boolean"
},
"kind": "parameter"
}
],
"returnType": {
"type": "intrinsic",
"value": "void"
},
"code": "(group: DockviewGroupPanel, visible: boolean): void",
"kind": "callSignature"
}
]
}
],
"extends": [
@ -41037,11 +41314,23 @@
},
"AddPanelOptions": {
"name": "AddPanelOptions",
"code": "Partial<AddPanelOptionsUnion> & { component: string, id: string, inactive?: boolean, params?: P, renderer?: DockviewPanelRenderer, tabComponent?: string, title?: string }",
"code": "Partial<Contraints> & Partial<AddPanelOptionsUnion> & { component: string, id: string, inactive?: boolean, initialHeight?: number, initialWidth?: number, params?: P, renderer?: DockviewPanelRenderer, tabComponent?: string, title?: string }",
"typeParameters": [],
"type": {
"type": "intersection",
"values": [
{
"type": "reference",
"value": "Partial",
"source": "typescript",
"typeArguments": [
{
"type": "reference",
"value": "Contraints",
"source": "dockview-core"
}
]
},
{
"type": "reference",
"value": "Partial",
@ -41058,7 +41347,7 @@
"type": "reflection",
"value": {
"name": "__type",
"code": "{ component: string, id: string, inactive?: boolean, params?: P, renderer?: DockviewPanelRenderer, tabComponent?: string, title?: string }",
"code": "{ component: string, id: string, inactive?: boolean, initialHeight?: number, initialWidth?: number, params?: P, renderer?: DockviewPanelRenderer, tabComponent?: string, title?: string }",
"kind": "typeLiteral",
"properties": [
{
@ -41125,6 +41414,30 @@
]
}
},
{
"name": "initialHeight",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "initialWidth",
"code": "number",
"kind": "property",
"type": {
"type": "intrinsic",
"value": "number"
},
"flags": {
"isOptional": true
}
},
{
"name": "params",
"code": "P",

View File

@ -50,9 +50,14 @@ const components = {
</div>
);
},
iframe: () => {
iframe: (props: IDockviewPanelProps) => {
return (
<iframe
onMouseDown={() => {
if (!props.api.isActive) {
props.api.setActive();
}
}}
style={{
width: '100%',
height: '100%',

View File

@ -50,8 +50,7 @@ const Watermark = (props: IWatermarkPanelProps) => {
}}
>
<span>
This is a custom watermark. You can put whatever React
component you want here
This is a custom watermark. You can change this content.
</span>
<span>
<button onClick={addPanel}>Add New Panel</button>

View File

@ -0,0 +1,135 @@
import 'dockview-core/dist/styles/dockview.css';
import {
createDockview,
DockviewApi,
GroupPanelPartInitParameters,
IContentRenderer,
IDockviewGroupPanel,
IWatermarkRenderer,
WatermarkRendererInitParameters,
} from 'dockview-core';
class Panel implements IContentRenderer {
private readonly _element: HTMLElement;
get element(): HTMLElement {
return this._element;
}
constructor() {
this._element = document.createElement('div');
this._element.style.color = 'white';
}
init(parameters: GroupPanelPartInitParameters): void {
this._element.textContent = 'Hello World';
}
}
class Watermark implements IWatermarkRenderer {
private readonly _element: HTMLElement;
get element(): HTMLElement {
return this._element;
}
constructor() {
const element = document.createElement('div');
element.style.height = '100%';
element.style.display = 'flex';
element.style.justifyContent = 'center';
element.style.alignItems = 'center';
element.style.color = 'white';
this._element = element;
}
init(params: WatermarkRendererInitParameters): void {
this.build(params.containerApi, params.group);
}
private build(api: DockviewApi, group?: IDockviewGroupPanel): void {
const container = document.createElement('div');
container.style.display = 'flex';
container.style.flexDirection = 'column';
const text = document.createElement('span');
text.textContent =
'This is a custom watermark. You can change this content.';
const button = document.createElement('button');
button.textContent = 'Add New Panel';
button.addEventListener('click', () => {
api.addPanel({
id: Date.now().toString(),
component: 'default',
});
});
const button2 = document.createElement('button');
button2.textContent = 'Close Group';
button2.addEventListener('click', () => {
api.addPanel({
id: Date.now().toString(),
component: 'default',
});
});
container.append(text, button, button2);
this.element.append(container);
api.onDidLayoutChange(() => {
button2.style.display = api.groups.length > 0 ? '' : 'none';
});
button2.addEventListener('click', () => {
group?.api.close();
});
}
}
const root = document.createElement('div');
root.style.display = 'flex';
root.style.flexDirection = 'column';
root.style.height = '100%';
const button = document.createElement('button');
button.textContent = 'Add Empty Group';
const container = document.createElement('div');
container.style.flexGrow = '1';
root.append(button, container);
const app = document.getElementById('app');
app.append(root);
const api = createDockview(container, {
className: 'dockview-theme-abyss',
createComponent: (options) => {
switch (options.name) {
case 'default':
return new Panel();
}
},
createWatermarkComponent: () => {
return new Watermark();
},
});
api.addPanel({
id: 'panel_1',
component: 'default',
title: 'Panel 1',
});
api.addPanel({
id: 'panel_2',
component: 'default',
position: { referencePanel: 'panel_1', direction: 'right' },
title: 'Panel 2',
});
button.addEventListener('click', () => {
api.addGroup();
});