Merge branch 'master' of https://github.com/mathuo/dockview into 582-expose-event-for-moving-tab-within-same-panel

This commit is contained in:
mathuo 2024-07-06 21:59:08 +01:00
commit cd59248f34
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
74 changed files with 2264 additions and 533 deletions

View File

@ -1,7 +1,7 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "dockview-angular",
"version": "1.14.0",
"version": "1.14.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.14.0"
"dockview-core": "^1.14.2"
}
}

View File

@ -1,7 +1,7 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview-core).
```
npm install --save dockview-core
```
Within your project you must import or reference the stylesheet at `dockview-core/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview-core/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

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

View File

@ -1,7 +1,34 @@
import { Overlay } from '../../dnd/overlay';
const mockGetBoundingClientRect = ({
left,
top,
height,
width,
}: {
left: number;
top: number;
height: number;
width: number;
}) => {
const result = {
left,
top,
height,
width,
right: left + width,
bottom: top + height,
x: left,
y: top,
};
return {
...result,
toJSON: () => result,
};
};
describe('overlay', () => {
test('toJSON', () => {
test('toJSON, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -23,14 +50,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
@ -39,7 +78,57 @@ describe('overlay', () => {
});
});
test('that out-of-bounds dimensions are fixed', () => {
test('toJSON, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
right: 10,
bottom: 20,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
});
test('that out-of-bounds dimensions are fixed, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -61,14 +150,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return { left: 80, top: 100, width: 40, height: 50 } as any;
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 20, top: 30, width: 100, height: 100 } as any;
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
top: 70,
left: 60,
@ -77,7 +178,57 @@ describe('overlay', () => {
});
});
test('setBounds', () => {
test('that out-of-bounds dimensions are fixed, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 200,
width: 100,
bottom: -1000,
right: -1000,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
jest.spyOn(
container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect'
).mockImplementation(() => {
return mockGetBoundingClientRect({
left: 80,
top: 100,
width: 40,
height: 50,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 20,
top: 30,
width: 100,
height: 100,
});
}
);
cut.setBounds();
expect(cut.toJSON()).toEqual({
bottom: -20,
right: 0,
width: 40,
height: 50,
});
});
test('setBounds, top left', () => {
const container = document.createElement('div');
const content = document.createElement('div');
@ -101,11 +252,21 @@ describe('overlay', () => {
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
return mockGetBoundingClientRect({
left: 300,
top: 400,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
@ -117,6 +278,56 @@ describe('overlay', () => {
expect(element.style.top).toBe('400px');
});
test('setBounds, bottom right', () => {
const container = document.createElement('div');
const content = document.createElement('div');
document.body.appendChild(container);
container.appendChild(content);
const cut = new Overlay({
height: 1000,
width: 1000,
right: 0,
bottom: 0,
minimumInViewportWidth: 0,
minimumInViewportHeight: 0,
container,
content,
});
const element: HTMLElement = container.querySelector(
'.dv-resize-container'
)!;
expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
return mockGetBoundingClientRect({
left: 500,
top: 500,
width: 200,
height: 100,
});
});
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
() => {
return mockGetBoundingClientRect({
left: 0,
top: 0,
width: 1000,
height: 1000,
});
}
);
cut.setBounds({ height: 100, width: 200, right: 300, bottom: 400 });
expect(element.style.height).toBe('100px');
expect(element.style.width).toBe('200px');
expect(element.style.right).toBe('300px');
expect(element.style.bottom).toBe('400px');
});
test('that the resize handles are added', () => {
const container = document.createElement('div');
const content = document.createElement('div');

View File

@ -0,0 +1,63 @@
import { DockviewApi } from '../../../../api/component.api';
import { DockviewPanelApi, TitleEvent } from '../../../../api/dockviewPanelApi';
import { DefaultTab } from '../../../../dockview/components/tab/defaultTab';
import { fromPartial } from '@total-typescript/shoehorn';
import { Emitter } from '../../../../events';
import { fireEvent } from '@testing-library/dom';
describe('defaultTab', () => {
test('that title updates', () => {
const cut = new DefaultTab();
let el = cut.element.querySelector('.dv-default-tab-content');
expect(el).toBeTruthy();
expect(el!.textContent).toBe('');
const onDidTitleChange = new Emitter<TitleEvent>();
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: onDidTitleChange.event,
});
const containerApi = fromPartial<DockviewApi>({});
cut.init({
api,
containerApi,
params: {},
title: 'title_abc',
});
el = cut.element.querySelector('.dv-default-tab-content');
expect(el).toBeTruthy();
expect(el!.textContent).toBe('title_abc');
onDidTitleChange.fire({ title: 'title_def' });
expect(el!.textContent).toBe('title_def');
});
test('that click closes tab', () => {
const cut = new DefaultTab();
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn(),
close: jest.fn(),
});
const containerApi = fromPartial<DockviewApi>({});
cut.init({
api,
containerApi,
params: {},
title: 'title_abc',
});
let el = cut.element.querySelector('.dv-default-tab-action');
fireEvent.mouseDown(el!);
expect(api.close).toHaveBeenCalledTimes(0);
fireEvent.click(el!);
expect(api.close).toHaveBeenCalledTimes(1);
});
});

View File

@ -490,14 +490,11 @@ describe('tabsContainer', () => {
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(
groupPanel,
{
x: 100,
y: 60,
},
{ inDragMode: true }
);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
x: 100,
y: 60,
inDragMode: true,
});
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);

View File

@ -10,7 +10,7 @@ import { CompositeDisposable } from '../../lifecycle';
import { Emitter } from '../../events';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
import { fireEvent } from '@testing-library/dom';
import { fireEvent, getByTestId, queryByTestId } from '@testing-library/dom';
import { getPanelData } from '../../dnd/dataTransfer';
import {
GroupDragEvent,
@ -5165,6 +5165,60 @@ describe('dockviewComponent', () => {
expect(panel4.api.isVisible).toBeTruthy();
});
test('setVisible #1', () => {
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel2',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
const panel3 = api.addPanel({
id: 'panel3',
component: 'default',
position: { referencePanel: panel1, direction: 'below' },
});
expect(api.groups.length).toBe(3);
panel1.group.api.setVisible(false);
panel2.group.api.setVisible(false);
panel3.group.api.setVisible(false);
expect(panel1.group.api.isVisible).toBeFalsy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
panel1.group.api.setVisible(true);
expect(panel1.group.api.isVisible).toBeTruthy();
expect(panel2.group.api.isVisible).toBeFalsy();
expect(panel3.group.api.isVisible).toBeFalsy();
});
describe('addPanel', () => {
test('that can add panel', () => {
const container = document.createElement('div');
@ -5497,4 +5551,57 @@ describe('dockviewComponent', () => {
expect(api.panels.length).toBe(3);
expect(api.groups.length).toBe(3);
});
test('that watermark appears when all views are not visible', () => {
jest.useFakeTimers();
const container = document.createElement('div');
const dockview = new DockviewComponent({
parentElement: container,
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
const api = new DockviewApi(dockview);
dockview.layout(1000, 1000);
const panel1 = api.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = api.addPanel({
id: 'panel_2',
component: 'default',
position: {
direction: 'right',
},
});
let query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel1.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
panel2.group.api.setVisible(false);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeTruthy();
panel1.group.api.setVisible(true);
jest.runAllTicks(); // visibility events check fires on microtask-queue
query = queryByTestId(container, 'watermark-component');
expect(query).toBeFalsy();
});
});

View File

@ -17,13 +17,9 @@ class TestPanel implements IGridPanelView {
_onDidChange = new Emitter<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event;
get isActive(): boolean {
return true;
}
get params(): Parameters {
return {};
}
isVisible: boolean = true;
isActive: boolean = true;
params: Parameters = {};
constructor(
public readonly id: string,

View File

@ -5,6 +5,7 @@ import {
IGridView,
IViewSize,
SerializedGridview,
getGridLocation,
orthogonal,
} from '../../gridview/gridview';
import { Orientation, Sizing } from '../../splitview/splitview';
@ -18,7 +19,7 @@ class MockGridview implements IGridView {
IViewSize | undefined
>().event;
element: HTMLElement = document.createElement('div');
isVisible: boolean = true;
width: number = 0;
height: number = 0;
@ -1105,4 +1106,102 @@ describe('gridview', () => {
expect(gridview.hasMaximizedView()).toBeFalsy();
});
test('visibility check', () => {
const gridview = new Gridview(
true,
{ separatorBorder: '' },
Orientation.HORIZONTAL
);
gridview.layout(1000, 1000);
const view1 = new MockGridview('1');
const view2 = new MockGridview('2');
const view3 = new MockGridview('3');
const view4 = new MockGridview('4');
const view5 = new MockGridview('5');
const view6 = new MockGridview('6');
gridview.addView(view1, Sizing.Distribute, [0]);
gridview.addView(view2, Sizing.Distribute, [1]);
gridview.addView(view3, Sizing.Distribute, [1, 1]);
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
/**
* _____________________________________________
* | | |
* | | 2 |
* | | |
* | 1 |_______________________|
* | | | 4 |
* | | 3 |_____________|
* | | | 5 | 6 |
* |_____________________|_________|______|______|
*/
function assertVisibility(visibility: boolean[]) {
expect(gridview.isViewVisible(getGridLocation(view1.element))).toBe(
visibility[0]
);
expect(gridview.isViewVisible(getGridLocation(view2.element))).toBe(
visibility[1]
);
expect(gridview.isViewVisible(getGridLocation(view3.element))).toBe(
visibility[2]
);
expect(gridview.isViewVisible(getGridLocation(view4.element))).toBe(
visibility[3]
);
expect(gridview.isViewVisible(getGridLocation(view5.element))).toBe(
visibility[4]
);
expect(gridview.isViewVisible(getGridLocation(view6.element))).toBe(
visibility[5]
);
}
// hide each view one by one
assertVisibility([true, true, true, true, true, true]);
gridview.setViewVisible(getGridLocation(view5.element), false);
assertVisibility([true, true, true, true, false, true]);
gridview.setViewVisible(getGridLocation(view4.element), false);
assertVisibility([true, true, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view1.element), false);
assertVisibility([false, true, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view2.element), false);
assertVisibility([false, false, true, false, false, true]);
gridview.setViewVisible(getGridLocation(view3.element), false);
assertVisibility([false, false, false, false, false, true]);
gridview.setViewVisible(getGridLocation(view6.element), false);
assertVisibility([false, false, false, false, false, false]);
// un-hide each view one by one
gridview.setViewVisible(getGridLocation(view1.element), true);
assertVisibility([true, false, false, false, false, false]);
gridview.setViewVisible(getGridLocation(view5.element), true);
assertVisibility([true, false, false, false, true, false]);
gridview.setViewVisible(getGridLocation(view6.element), true);
assertVisibility([true, false, false, false, true, true]);
gridview.setViewVisible(getGridLocation(view2.element), true);
assertVisibility([true, true, false, false, true, true]);
gridview.setViewVisible(getGridLocation(view3.element), true);
assertVisibility([true, true, true, false, true, true]);
gridview.setViewVisible(getGridLocation(view4.element), true);
assertVisibility([true, true, true, true, true, true]);
});
});

View File

@ -8,10 +8,8 @@ describe('math', () => {
expect(clamp(55, 40, 50)).toBe(50);
});
it('should throw an error if min > max', () => {
expect(() => clamp(55, 50, 40)).toThrow(
'50 > 40 is an invalid condition'
);
it('if min > max return min', () => {
expect(clamp(55, 50, 40)).toBe(50);
});
});

View File

@ -1,4 +1,5 @@
import {
FloatingGroupOptions,
IDockviewComponent,
MovePanelEvent,
SerializedDockview,
@ -43,7 +44,7 @@ import {
GroupDragEvent,
TabDragEvent,
} from '../dockview/components/titlebar/tabsContainer';
import { Box } from '../types';
import { AnchoredBox, Box } from '../types';
import {
DockviewDidDropEvent,
DockviewWillDropEvent,
@ -805,9 +806,9 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
*/
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
options?: FloatingGroupOptions
): void {
return this.component.addFloatingGroup(item, coord);
return this.component.addFloatingGroup(item, options);
}
/**

View File

@ -1,3 +1,3 @@
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100 };
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };

View File

@ -11,7 +11,7 @@ import {
} from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math';
import { Box } from '../types';
import { AnchoredBox } from '../types';
const bringElementToFront = (() => {
let previous: HTMLElement | null = null;
@ -40,6 +40,9 @@ export class Overlay extends CompositeDisposable {
private static MINIMUM_HEIGHT = 20;
private static MINIMUM_WIDTH = 20;
private verticalAlignment: 'top' | 'bottom' | undefined;
private horiziontalAlignment: 'left' | 'right' | undefined;
set minimumInViewportWidth(value: number | undefined) {
this.options.minimumInViewportWidth = value;
}
@ -49,7 +52,7 @@ export class Overlay extends CompositeDisposable {
}
constructor(
private readonly options: Box & {
private readonly options: AnchoredBox & {
container: HTMLElement;
content: HTMLElement;
minimumInViewportWidth?: number;
@ -78,23 +81,39 @@ export class Overlay extends CompositeDisposable {
this.setBounds({
height: this.options.height,
width: this.options.width,
top: this.options.top,
left: this.options.left,
...('top' in this.options && { top: this.options.top }),
...('bottom' in this.options && { bottom: this.options.bottom }),
...('left' in this.options && { left: this.options.left }),
...('right' in this.options && { right: this.options.right }),
});
}
setBounds(bounds: Partial<Box> = {}): void {
setBounds(bounds: Partial<AnchoredBox> = {}): void {
if (typeof bounds.height === 'number') {
this._element.style.height = `${bounds.height}px`;
}
if (typeof bounds.width === 'number') {
this._element.style.width = `${bounds.width}px`;
}
if (typeof bounds.top === 'number') {
if ('top' in bounds && typeof bounds.top === 'number') {
this._element.style.top = `${bounds.top}px`;
this._element.style.bottom = 'auto';
this.verticalAlignment = 'top';
}
if (typeof bounds.left === 'number') {
if ('bottom' in bounds && typeof bounds.bottom === 'number') {
this._element.style.bottom = `${bounds.bottom}px`;
this._element.style.top = 'auto';
this.verticalAlignment = 'bottom';
}
if ('left' in bounds && typeof bounds.left === 'number') {
this._element.style.left = `${bounds.left}px`;
this._element.style.right = 'auto';
this.horiziontalAlignment = 'left';
}
if ('right' in bounds && typeof bounds.right === 'number') {
this._element.style.right = `${bounds.right}px`;
this._element.style.left = 'auto';
this.horiziontalAlignment = 'right';
}
const containerRect = this.options.container.getBoundingClientRect();
@ -106,39 +125,77 @@ export class Overlay extends CompositeDisposable {
const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
// a minimum height of minimumViewportHeight must be inside the viewport
const yOffset =
typeof this.options.minimumInViewportHeight === 'number'
? Math.max(0, this.getMinimumHeight(overlayRect.height))
: 0;
const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
const left = clamp(
overlayRect.left - containerRect.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
if (this.verticalAlignment === 'top') {
const top = clamp(
overlayRect.top - containerRect.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.top = `${top}px`;
this._element.style.bottom = 'auto';
}
const top = clamp(
overlayRect.top - containerRect.top,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
if (this.verticalAlignment === 'bottom') {
const bottom = clamp(
containerRect.bottom - overlayRect.bottom,
-yOffset,
Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.bottom = `${bottom}px`;
this._element.style.top = 'auto';
}
this._element.style.left = `${left}px`;
this._element.style.top = `${top}px`;
if (this.horiziontalAlignment === 'left') {
const left = clamp(
overlayRect.left - containerRect.left,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
this._element.style.left = `${left}px`;
this._element.style.right = 'auto';
}
if (this.horiziontalAlignment === 'right') {
const right = clamp(
containerRect.right - overlayRect.right,
-xOffset,
Math.max(0, containerRect.width - overlayRect.width + xOffset)
);
this._element.style.right = `${right}px`;
this._element.style.left = 'auto';
}
this._onDidChange.fire();
}
toJSON(): Box {
toJSON(): AnchoredBox {
const container = this.options.container.getBoundingClientRect();
const element = this._element.getBoundingClientRect();
return {
top: element.top - container.top,
left: element.left - container.left,
width: element.width,
height: element.height,
};
const result: any = {};
if (this.verticalAlignment === 'top') {
result.top = parseFloat(this._element.style.top);
} else if (this.verticalAlignment === 'bottom') {
result.bottom = parseFloat(this._element.style.bottom);
} else {
result.top = element.top - container.top;
}
if (this.horiziontalAlignment === 'left') {
result.left = parseFloat(this._element.style.left);
} else if (this.horiziontalAlignment === 'right') {
result.right = parseFloat(this._element.style.right);
} else {
result.left = element.left - container.left;
}
result.width = element.width;
result.height = element.height;
return result;
}
setupDrag(
@ -193,18 +250,7 @@ export class Overlay extends CompositeDisposable {
);
const yOffset = Math.max(
0,
this.options.minimumInViewportHeight
? this.getMinimumHeight(overlayRect.height)
: 0
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
this.getMinimumHeight(overlayRect.height)
);
const top = clamp(
@ -216,7 +262,53 @@ export class Overlay extends CompositeDisposable {
)
);
this.setBounds({ top, left });
const bottom = clamp(
offset.y -
y +
containerRect.height -
overlayRect.height,
-yOffset,
Math.max(
0,
containerRect.height - overlayRect.height + yOffset
)
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const right = clamp(
offset.x - x + containerRect.width - overlayRect.width,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
);
const bounds: any = {};
// Anchor to top or to bottom depending on which one is closer
if (top <= bottom) {
bounds.top = top;
} else {
bounds.bottom = bottom;
}
// Anchor to left or to right depending on which one is closer
if (left <= right) {
bounds.left = left;
} else {
bounds.right = right;
}
this.setBounds(bounds);
}),
addDisposableWindowListener(window, 'mouseup', () => {
toggleClass(
@ -342,8 +434,10 @@ export class Overlay extends CompositeDisposable {
}
let top: number | undefined = undefined;
let bottom: number | undefined = undefined;
let height: number | undefined = undefined;
let left: number | undefined = undefined;
let right: number | undefined = undefined;
let width: number | undefined = undefined;
const moveTop = () => {
@ -363,10 +457,13 @@ export class Overlay extends CompositeDisposable {
Overlay.MINIMUM_HEIGHT
)
);
height =
startPosition!.originalY +
startPosition!.originalHeight -
top;
bottom = containerRect.height - top - height;
};
const moveBottom = () => {
@ -384,6 +481,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_HEIGHT,
Number.MAX_VALUE
);
bottom = containerRect.height - top - height;
};
const moveLeft = () => {
@ -406,6 +505,8 @@ export class Overlay extends CompositeDisposable {
startPosition!.originalX +
startPosition!.originalWidth -
left;
right = containerRect.width - left - width;
};
const moveRight = () => {
@ -423,6 +524,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_WIDTH,
Number.MAX_VALUE
);
right = containerRect.width - left - width;
};
switch (direction) {
@ -456,7 +559,26 @@ export class Overlay extends CompositeDisposable {
break;
}
this.setBounds({ height, width, top, left });
const bounds: any = {};
// Anchor to top or to bottom depending on which one is closer
if (top! <= bottom!) {
bounds.top = top;
} else {
bounds.bottom = bottom;
}
// Anchor to left or to right depending on which one is closer
if (left! <= right!) {
bounds.left = left;
} else {
bounds.right = right;
}
bounds.height = height;
bounds.width = width;
this.setBounds(bounds);
}),
{
dispose: () => {
@ -485,7 +607,7 @@ export class Overlay extends CompositeDisposable {
if (typeof this.options.minimumInViewportHeight === 'number') {
return height - this.options.minimumInViewportHeight;
}
return height;
return 0;
}
override dispose(): void {

View File

@ -1,16 +1,13 @@
import { CompositeDisposable } from '../../../lifecycle';
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
import { addDisposableListener } from '../../../events';
import { PanelUpdateEvent } from '../../../panel/types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { createCloseButton } from '../../../svg';
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
private _element: HTMLElement;
private _content: HTMLElement;
private action: HTMLElement;
//
private params: GroupPanelPartInitParameters = {} as any;
private _title: string | undefined;
get element(): HTMLElement {
return this._element;
@ -21,7 +18,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._element = document.createElement('div');
this._element.className = 'dv-default-tab';
//
this._content = document.createElement('div');
this._content.className = 'dv-default-tab-content';
@ -29,10 +26,9 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this.action.className = 'dv-default-tab-action';
this.action.appendChild(createCloseButton());
//
this._element.appendChild(this._content);
this._element.appendChild(this.action);
//
this.addDisposables(
addDisposableListener(this.action, 'mousedown', (ev) => {
ev.preventDefault();
@ -42,40 +38,33 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this.render();
}
public update(event: PanelUpdateEvent): void {
this.params = { ...this.params, ...event.params };
init(params: GroupPanelPartInitParameters): void {
this._title = params.title;
this.addDisposables(
params.api.onDidTitleChange((event) => {
this._title = event.title;
this.render();
}),
addDisposableListener(this.action, 'mousedown', (ev) => {
ev.preventDefault();
}),
addDisposableListener(this.action, 'click', (ev) => {
if (ev.defaultPrevented) {
return;
}
ev.preventDefault();
params.api.close();
})
);
this.render();
}
focus(): void {
//noop
}
public init(params: GroupPanelPartInitParameters): void {
this.params = params;
this._content.textContent = params.title;
addDisposableListener(this.action, 'click', (ev) => {
ev.preventDefault(); //
this.params.api.close();
});
}
onGroupChange(_group: DockviewGroupPanel): void {
this.render();
}
onPanelVisibleChange(_isPanelVisible: boolean): void {
this.render();
}
public layout(_width: number, _height: number): void {
// noop
}
private render(): void {
if (this._content.textContent !== this.params.title) {
this._content.textContent = this.params.title;
if (this._content.textContent !== this._title) {
this._content.textContent = this._title ?? '';
}
}
}

View File

@ -270,14 +270,11 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
this.group,
{
x: left - rootLeft + 20,
y: top - rootTop + 20,
},
{ inDragMode: true }
);
this.accessor.addFloatingGroup(this.group, {
x: left - rootLeft + 20,
y: top - rootTop + 20,
inDragMode: true,
});
}
}
),
@ -380,14 +377,11 @@ export class TabsContainer
const { top: rootTop, left: rootLeft } =
this.accessor.element.getBoundingClientRect();
this.accessor.addFloatingGroup(
panel as DockviewPanel,
{
x: left - rootLeft,
y: top - rootTop,
},
{ inDragMode: true }
);
this.accessor.addFloatingGroup(panel as DockviewPanel, {
x: left - rootLeft,
y: top - rootTop,
inDragMode: true,
});
return;
}

View File

@ -19,11 +19,15 @@
&.horizontal {
> .view-container > .view {
&:not(:last-child) {
border-right: var(--dv-group-gap-size) solid transparent;
.groupview {
border-right: var(--dv-group-gap-size) solid transparent;
}
}
&:not(:first-child) {
border-left: var(--dv-group-gap-size) solid transparent;
.groupview {
border-left: var(--dv-group-gap-size) solid transparent;
}
}
}
}
@ -31,11 +35,16 @@
&.vertical {
> .view-container > .view {
&:not(:last-child) {
border-bottom: var(--dv-group-gap-size) solid transparent;
.groupview {
border-bottom: var(--dv-group-gap-size) solid
transparent;
}
}
&:not(:first-child) {
border-top: var(--dv-group-gap-size) solid transparent;
.groupview {
border-top: var(--dv-group-gap-size) solid transparent;
}
}
}
}

View File

@ -51,13 +51,13 @@ import { DockviewPanelModel } from './dockviewPanelModel';
import { getPanelData } from '../dnd/dataTransfer';
import { Parameters } from '../panel/types';
import { Overlay } from '../dnd/overlay';
import { toggleClass, watchElementResize } from '../dom';
import { addTestId, toggleClass, watchElementResize } from '../dom';
import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
import {
GroupDragEvent,
TabDragEvent,
} from './components/titlebar/tabsContainer';
import { Box } from '../types';
import { AnchoredBox, Box } from '../types';
import {
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
DEFAULT_FLOATING_GROUP_POSITION,
@ -126,7 +126,7 @@ export interface PanelReference {
export interface SerializedFloatingGroup {
data: GroupPanelViewState;
position: Box;
position: AnchoredBox;
}
export interface SerializedPopoutGroup {
@ -170,6 +170,17 @@ type MoveGroupOrPanelOptions = {
};
};
export interface FloatingGroupOptions {
x?: number;
y?: number;
height?: number;
width?: number;
position?: AnchoredBox;
skipRemoveGroup?: boolean;
inDragMode?: boolean;
skipActiveGroup?: boolean;
}
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
readonly activePanel: IDockviewPanel | undefined;
readonly totalPanels: number;
@ -214,7 +225,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
//
addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number }
options?: FloatingGroupOptions
): void;
addPopoutGroup(
item: IDockviewPanel | DockviewGroupPanel,
@ -375,6 +386,9 @@ export class DockviewComponent
this._onDidRemoveGroup,
this._onDidActiveGroupChange,
this._onUnhandledDragOverEvent,
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.updateWatermark();
}),
this.onDidAdd((event) => {
if (!this._moving) {
this._onDidAddGroup.fire(event);
@ -754,12 +768,7 @@ export class DockviewComponent
addFloatingGroup(
item: DockviewPanel | DockviewGroupPanel,
coord?: { x?: number; y?: number; height?: number; width?: number },
options?: {
skipRemoveGroup?: boolean;
inDragMode: boolean;
skipActiveGroup?: boolean;
}
options?: FloatingGroupOptions
): void {
let group: DockviewGroupPanel;
@ -820,22 +829,62 @@ export class DockviewComponent
group.model.location = { type: 'floating' };
const overlayLeft =
typeof coord?.x === 'number'
? Math.max(coord.x, 0)
: DEFAULT_FLOATING_GROUP_POSITION.left;
const overlayTop =
typeof coord?.y === 'number'
? Math.max(coord.y, 0)
: DEFAULT_FLOATING_GROUP_POSITION.top;
function getAnchoredBox(): AnchoredBox {
if (options?.position) {
const result: any = {};
if ('left' in options.position) {
result.left = Math.max(options.position.left, 0);
} else if ('right' in options.position) {
result.right = Math.max(options.position.right, 0);
} else {
result.left = DEFAULT_FLOATING_GROUP_POSITION.left;
}
if ('top' in options.position) {
result.top = Math.max(options.position.top, 0);
} else if ('bottom' in options.position) {
result.bottom = Math.max(options.position.bottom, 0);
} else {
result.top = DEFAULT_FLOATING_GROUP_POSITION.top;
}
if ('width' in options.position) {
result.width = Math.max(options.position.width, 0);
} else {
result.width = DEFAULT_FLOATING_GROUP_POSITION.width;
}
if ('height' in options.position) {
result.height = Math.max(options.position.height, 0);
} else {
result.height = DEFAULT_FLOATING_GROUP_POSITION.height;
}
return result as AnchoredBox;
}
return {
left:
typeof options?.x === 'number'
? Math.max(options.x, 0)
: DEFAULT_FLOATING_GROUP_POSITION.left,
top:
typeof options?.y === 'number'
? Math.max(options.y, 0)
: DEFAULT_FLOATING_GROUP_POSITION.top,
width:
typeof options?.width === 'number'
? Math.max(options.width, 0)
: DEFAULT_FLOATING_GROUP_POSITION.width,
height:
typeof options?.height === 'number'
? Math.max(options.height, 0)
: DEFAULT_FLOATING_GROUP_POSITION.height,
};
}
const anchoredBox = getAnchoredBox();
const overlay = new Overlay({
container: this.gridview.element,
content: group.element,
height: coord?.height ?? 300,
width: coord?.width ?? 300,
left: overlayLeft,
top: overlayTop,
...anchoredBox,
minimumInViewportWidth:
this.options.floatingGroupBounds === 'boundedWithinViewport'
? undefined
@ -975,7 +1024,7 @@ export class DockviewComponent
this.options.floatingGroupBounds?.minimumWidthWithinViewport;
}
group.overlay.setBounds({});
group.overlay.setBounds();
}
}
@ -1198,16 +1247,11 @@ export class DockviewComponent
const group = createGroupFromSerializedState(data);
this.addFloatingGroup(
group,
{
x: position.left,
y: position.top,
height: position.height,
width: position.width,
},
{ skipRemoveGroup: true, inDragMode: false }
);
this.addFloatingGroup(group, {
position: position,
skipRemoveGroup: true,
inDragMode: false,
});
}
const serializedPopoutGroups = data.popoutGroups ?? [];
@ -1390,7 +1434,8 @@ export class DockviewComponent
? options.floating
: {};
this.addFloatingGroup(group, o, {
this.addFloatingGroup(group, {
...o,
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,
@ -1443,7 +1488,8 @@ export class DockviewComponent
? options.floating
: {};
this.addFloatingGroup(group, coordinates, {
this.addFloatingGroup(group, {
...coordinates,
inDragMode: false,
skipRemoveGroup: true,
skipActiveGroup: true,
@ -1525,6 +1571,7 @@ export class DockviewComponent
const watermarkContainer = document.createElement('div');
watermarkContainer.className = 'dv-watermark-container';
addTestId(watermarkContainer, 'watermark-component');
watermarkContainer.appendChild(this.watermark.element);
this.gridview.element.appendChild(watermarkContainer);

View File

@ -1,37 +1,22 @@
import { Overlay } from '../dnd/overlay';
import { CompositeDisposable } from '../lifecycle';
import { AnchoredBox } from '../types';
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
export interface IDockviewFloatingGroupPanel {
readonly group: IDockviewGroupPanel;
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void;
position(bounds: Partial<AnchoredBox>): void;
}
export class DockviewFloatingGroupPanel
extends CompositeDisposable
implements IDockviewFloatingGroupPanel
{
implements IDockviewFloatingGroupPanel {
constructor(readonly group: DockviewGroupPanel, readonly overlay: Overlay) {
super();
this.addDisposables(overlay);
}
position(
bounds: Partial<{
top: number;
left: number;
height: number;
width: number;
}>
): void {
position(bounds: Partial<AnchoredBox>): void {
this.overlay.setBounds(bounds);
}
}

View File

@ -14,6 +14,7 @@ import {
import { IDockviewPanel } from './dockviewPanel';
import { DockviewPanelRenderer } from '../overlayRenderContainer';
import { IGroupHeaderProps } from './framework';
import { AnchoredBox } from '../types';
export interface IHeaderActionsRenderer extends IDisposable {
readonly element: HTMLElement;
@ -37,11 +38,11 @@ export interface DockviewOptions {
singleTabMode?: 'fullwidth' | 'default';
disableFloatingGroups?: boolean;
floatingGroupBounds?:
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
| 'boundedWithinViewport'
| {
minimumHeightWithinViewport?: number;
minimumWidthWithinViewport?: number;
};
popoutUrl?: string;
defaultRenderer?: DockviewPanelRenderer;
debug?: boolean;
@ -74,7 +75,7 @@ export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent {
readonly position: Position,
readonly getData: () => PanelTransfer | undefined,
readonly group?: DockviewGroupPanel
) {}
) { }
accept(): void {
this._isAccepted = true;
@ -176,13 +177,8 @@ export function isPanelOptionsWithGroup(
type AddPanelFloatingGroupUnion = {
floating:
| {
height?: number;
width?: number;
x?: number;
y?: number;
}
| true;
| Partial<AnchoredBox>
| true;
position: never;
};

View File

@ -253,3 +253,7 @@ export function isInDocument(element: Element): boolean {
return false;
}
export function addTestId(element: HTMLElement, id: string): void {
element.setAttribute('data-testid', id);
}

View File

@ -93,6 +93,10 @@ export abstract class BaseGrid<T extends IGridPanelView>
readonly onDidLayoutChange: Event<void> =
this._bufferOnDidLayoutChange.onEvent;
private readonly _onDidViewVisibilityChangeMicroTaskQueue = new AsapEvent();
readonly onDidViewVisibilityChangeMicroTaskQueue =
this._onDidViewVisibilityChangeMicroTaskQueue.onEvent;
get id(): string {
return this._id;
}
@ -158,6 +162,12 @@ export abstract class BaseGrid<T extends IGridPanelView>
this.layout(0, 0, true); // set some elements height/widths
this.addDisposables(
this.gridview.onDidViewVisibilityChange(() =>
this._onDidViewVisibilityChangeMicroTaskQueue.fire()
),
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.layout(this.width, this.height, true);
}),
Disposable.from(() => {
this.element.parentElement?.removeChild(this.element);
}),

View File

@ -33,9 +33,12 @@ export class BranchNode extends CompositeDisposable implements IView {
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
this._onDidChange.event;
private readonly _onDidVisibilityChange = new Emitter<boolean>();
readonly onDidVisibilityChange: Event<boolean> =
this._onDidVisibilityChange.event;
private readonly _onDidVisibilityChange = new Emitter<{
visible: boolean;
}>();
readonly onDidVisibilityChange: Event<{
visible: boolean;
}> = this._onDidVisibilityChange.event;
get width(): number {
return this.orientation === Orientation.HORIZONTAL
@ -200,10 +203,8 @@ export class BranchNode extends CompositeDisposable implements IView {
this.setupChildrenEvents();
}
setVisible(visible: boolean): void {
for (const child of this.children) {
child.setVisible(visible);
}
setVisible(_visible: boolean): void {
// noop
}
isChildVisible(index: number): boolean {
@ -224,7 +225,9 @@ export class BranchNode extends CompositeDisposable implements IView {
}
const wereAllChildrenHidden = this.splitview.contentSize === 0;
this.splitview.setViewVisible(index, visible);
// }
const areAllChildrenHidden = this.splitview.contentSize === 0;
// If all children are hidden then the parent should hide the entire splitview
@ -233,7 +236,7 @@ export class BranchNode extends CompositeDisposable implements IView {
(visible && wereAllChildrenHidden) ||
(!visible && areAllChildrenHidden)
) {
this._onDidVisibilityChange.fire(visible);
this._onDidVisibilityChange.fire({ visible });
}
}
@ -335,7 +338,7 @@ export class BranchNode extends CompositeDisposable implements IView {
}),
...this.children.map((c, i) => {
if (c instanceof BranchNode) {
return c.onDidVisibilityChange((visible) => {
return c.onDidVisibilityChange(({ visible }) => {
this.setChildVisible(i, visible);
});
}

View File

@ -171,6 +171,7 @@ export interface IGridView {
readonly maximumWidth: number;
readonly minimumHeight: number;
readonly maximumHeight: number;
readonly isVisible: boolean;
priority?: LayoutPriority;
layout(width: number, height: number): void;
toJSON(): object;
@ -287,6 +288,9 @@ export class Gridview implements IDisposable {
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
this._onDidChange.event;
private readonly _onDidViewVisibilityChange = new Emitter<void>();
readonly onDidViewVisibilityChange = this._onDidViewVisibilityChange.event;
private readonly _onDidMaximizedNodeChange = new Emitter<void>();
readonly onDidMaximizedNodeChange = this._onDidMaximizedNodeChange.event;
@ -453,6 +457,8 @@ export class Gridview implements IDisposable {
this.disposable.dispose();
this._onDidChange.dispose();
this._onDidMaximizedNodeChange.dispose();
this._onDidViewVisibilityChange.dispose();
this.root.dispose();
this._maximizedNode = undefined;
this.element.remove();
@ -531,12 +537,12 @@ export class Gridview implements IDisposable {
children
);
} else {
result = new LeafNode(
deserializer.fromJSON(node),
orientation,
orthogonalSize,
node.size
);
const view = deserializer.fromJSON(node);
if (typeof node.visible === 'boolean') {
view.setVisible?.(node.visible);
}
result = new LeafNode(view, orientation, orthogonalSize, node.size);
}
return result;
@ -723,6 +729,8 @@ export class Gridview implements IDisposable {
throw new Error('Invalid from location');
}
this._onDidViewVisibilityChange.fire();
parent.setChildVisible(index, visible);
}

View File

@ -126,6 +126,10 @@ export abstract class GridviewPanel<
return this.api.isActive;
}
get isVisible(): boolean {
return this.api.isVisible;
}
constructor(
id: string,
component: string,

View File

@ -1,6 +1,10 @@
export const clamp = (value: number, min: number, max: number): number => {
if (min > max) {
throw new Error(`${min} > ${max} is an invalid condition`);
/**
* caveat: an error should be thrown here if this was a proper `clamp` function but we need to handle
* cases where `min` > `max` and in those cases return `min`.
*/
return min;
}
return Math.min(max, Math.max(value, min));
};

View File

@ -291,12 +291,8 @@ export class Splitview {
throw new Error('Index out of bounds');
}
toggleClass(this.container, 'visible', visible);
const viewItem = this.viewItems[index];
toggleClass(this.container, 'visible', visible);
viewItem.setVisible(visible, viewItem.size);
this.distributeEmptySpace(index);

View File

@ -8,3 +8,13 @@ export interface Box {
height: number;
width: number;
}
type TopLeft = { top: number; left: number };
type TopRight = { top: number; right: number };
type BottomLeft = { bottom: number; left: number };
type BottomRight = { bottom: number; right: number };
type AnchorPosition = TopLeft | TopRight | BottomLeft | BottomRight;
type Size = { width: number; height: number };
export type AnchoredBox = Size & AnchorPosition;

View File

@ -1,7 +1,7 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
@ -16,6 +16,8 @@
##
![](packages/docs/static/img/splashscreen.gif)
Please see the website: https://dockview.dev
## Features
@ -34,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -1,6 +1,6 @@
{
"name": "dockview-react",
"version": "1.14.0",
"version": "1.14.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.14.0"
"dockview": "^1.14.2"
}
}

View File

@ -1,7 +1,7 @@
<div align="center">
<h1>dockview</h1>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
</div>
@ -16,6 +16,8 @@
##
![](packages/docs/static/img/splashscreen.gif)
Please see the website: https://dockview.dev
## Features
@ -34,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
## Quick start
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
```
npm install --save dockview
```
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
```css
@import '~dockview/dist/styles/dockview.css';
```
You should also attach a dockview theme to an element containing your components. For example:
```html
<body classname="dockview-theme-dark"></body>
```

View File

@ -1,6 +1,6 @@
{
"name": "dockview-vue",
"version": "1.14.0",
"version": "1.14.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.14.0"
"dockview-core": "^1.14.2"
}
}

View File

@ -70,55 +70,61 @@ PROPERTY_KEYS.forEach((coreOptionKey) => {
onMounted(() => {
if (!el.value) {
throw new Error('element is not mounted');
throw new Error('dockview-vue: element is not mounted');
}
const inst = getCurrentInstance();
if(!inst) {
throw new Error('dockview-vue: getCurrentInstance() returned null')
}
const frameworkOptions: DockviewFrameworkOptions = {
parentElement: el.value,
createComponent(options) {
const component = findComponent(
getCurrentInstance()!,
inst,
options.name
);
return new VueRenderer(component!, getCurrentInstance()!);
return new VueRenderer(component!, inst);
},
createTabComponent(options) {
let component = findComponent(getCurrentInstance()!, options.name);
let component = findComponent(inst, options.name);
if (!component && props.defaultTabComponent) {
component = findComponent(
getCurrentInstance()!,
inst,
props.defaultTabComponent
);
}
if (component) {
return new VueRenderer(component, getCurrentInstance()!);
return new VueRenderer(component, inst);
}
return undefined;
},
createWatermarkComponent: props.watermarkComponent
? () => {
const component = findComponent(
getCurrentInstance()!,
inst,
props.watermarkComponent!
);
return new VueWatermarkRenderer(
component!,
getCurrentInstance()!
inst
);
}
: undefined,
createLeftHeaderActionComponent: props.leftHeaderActionsComponent
? (group) => {
const component = findComponent(
getCurrentInstance()!,
inst,
props.leftHeaderActionsComponent!
);
return new VueHeaderActionsRenderer(
component!,
getCurrentInstance()!,
inst,
group
);
}
@ -126,12 +132,12 @@ onMounted(() => {
createPrefixHeaderActionComponent: props.prefixHeaderActionsComponent
? (group) => {
const component = findComponent(
getCurrentInstance()!,
inst,
props.prefixHeaderActionsComponent!
);
return new VueHeaderActionsRenderer(
component!,
getCurrentInstance()!,
inst,
group
);
}
@ -139,12 +145,12 @@ onMounted(() => {
createRightHeaderActionComponent: props.rightHeaderActionsComponent
? (group) => {
const component = findComponent(
getCurrentInstance()!,
inst,
props.rightHeaderActionsComponent!
);
return new VueHeaderActionsRenderer(
component!,
getCurrentInstance()!,
inst,
group
);
}

View File

@ -1,6 +1,6 @@
{
"name": "dockview",
"version": "1.14.0",
"version": "1.14.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.14.0"
"dockview-core": "^1.14.2"
}
}

View File

@ -2,11 +2,16 @@ import { fireEvent, render, screen } from '@testing-library/react';
import { DockviewDefaultTab } from '../../dockview/defaultTab';
import React from 'react';
import { fromPartial } from '@total-typescript/shoehorn';
import { DockviewApi, DockviewPanelApi } from 'dockview-core';
import { DockviewApi, DockviewPanelApi, TitleEvent } from 'dockview-core';
import { Emitter } from 'dockview-core/dist/cjs/events';
import { act } from 'react-dom/test-utils';
import { Disposable } from 'dockview-core/dist/cjs/lifecycle';
describe('defaultTab', () => {
test('has close button by default', async () => {
const api = fromPartial<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -25,6 +30,7 @@ describe('defaultTab', () => {
test('that title is displayed', async () => {
const api = fromPartial<DockviewPanelApi>({
title: 'test_title',
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -43,8 +49,43 @@ describe('defaultTab', () => {
).toBe('test_title');
});
test('that title is updated', async () => {
const onDidTitleChange = new Emitter<TitleEvent>();
const api = fromPartial<DockviewPanelApi>({
title: 'test_title',
onDidTitleChange: onDidTitleChange.event,
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
render(
<DockviewDefaultTab
api={api}
containerApi={containerApi}
params={params}
/>
);
let element = await screen.getByTestId('dockview-dv-default-tab');
expect(
element.querySelector('.dv-default-tab-content')?.textContent
).toBe('test_title');
act(() => {
onDidTitleChange.fire({ title: 'test_title_2' });
});
element = await screen.getByTestId('dockview-dv-default-tab');
expect(
element.querySelector('.dv-default-tab-content')?.textContent
).toBe('test_title_2');
});
test('has no close button when hideClose=true', async () => {
const api = fromPartial<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -64,6 +105,7 @@ describe('defaultTab', () => {
test('that settings closeActionOverride skips api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({
close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -85,13 +127,14 @@ describe('defaultTab', () => {
) as HTMLElement;
fireEvent.click(btn);
expect(closeActionOverride).toBeCalledTimes(1);
expect(api.close).toBeCalledTimes(0);
expect(closeActionOverride).toHaveBeenCalledTimes(1);
expect(api.close).toHaveBeenCalledTimes(0);
});
test('that clicking close calls api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({
close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -110,11 +153,13 @@ describe('defaultTab', () => {
) as HTMLElement;
fireEvent.click(btn);
expect(api.close).toBeCalledTimes(1);
expect(api.close).toHaveBeenCalledTimes(1);
});
test('has close button when hideClose=false', async () => {
const api = fromPartial<DockviewPanelApi>({});
const api = fromPartial<DockviewPanelApi>({
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -134,6 +179,7 @@ describe('defaultTab', () => {
test('that mouseDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
});
const containerApi = fromPartial<DockviewApi>({});
const params = {};
@ -152,9 +198,9 @@ describe('defaultTab', () => {
) as HTMLElement;
fireEvent.mouseDown(btn);
expect(api.setActive).toBeCalledTimes(0);
expect(api.setActive).toHaveBeenCalledTimes(0);
fireEvent.click(element);
expect(api.setActive).toBeCalledTimes(1);
expect(api.setActive).toHaveBeenCalledTimes(1);
});
});

View File

@ -1,6 +1,22 @@
import React from 'react';
import { CloseButton } from '../svg';
import { IDockviewPanelHeaderProps } from 'dockview-core';
import { DockviewPanelApi, IDockviewPanelHeaderProps } from 'dockview-core';
function useTitle(api: DockviewPanelApi): string | undefined {
const [title, setTitle] = React.useState<string | undefined>(api.title);
React.useEffect(() => {
const disposable = api.onDidTitleChange((event) => {
setTitle(event.title);
});
return () => {
disposable.dispose();
};
}, [api]);
return title;
}
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement> & {
@ -18,6 +34,8 @@ export const DockviewDefaultTab: React.FunctionComponent<
closeActionOverride,
...rest
}) => {
const title = useTitle(api);
const onClose = React.useCallback(
(event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault();
@ -57,7 +75,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
onClick={onClick}
className="dv-default-tab"
>
<span className="dv-default-tab-content">{api.title}</span>
<span className="dv-default-tab-content">{title}</span>
{!hideClose && (
<div
className="dv-default-tab-action"

View File

@ -0,0 +1,13 @@
---
slug: dockview-1.14.1-release
title: Dockview 1.14.1
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🛠 Miscs
- Bug: fix CSS related to group gap sizing [#596](https://github.com/mathuo/dockview/issues/613)

View File

@ -0,0 +1,15 @@
---
slug: dockview-1.14.2-release
title: Dockview 1.14.2
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🛠 Miscs
- Bug: setTitle fixes [#623](https://github.com/mathuo/dockview/pull/623)
- Bug: Vue3 component rendering issues [#625](https://github.com/mathuo/dockview/pull/625)
- Improves docs [#617](https://github.com/mathuo/dockview/pull/617) [#620](https://github.com/mathuo/dockview/pull/620)

View File

@ -165,6 +165,6 @@ api.addPanel({
api.addPanel({
id: 'panel_2',
component: 'default',
floating: { x: 10, y: 10, width: 300, height: 300 },
floating: { left: 10, top: 10, width: 300, height: 300 },
});
```

View File

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

View File

@ -77,6 +77,11 @@
border: none;
cursor: pointer;
&:disabled {
color: gray;
cursor: help;
}
span {
font-size: 16px;
}

View File

@ -185,6 +185,19 @@ const DockviewDemo = (props: { theme?: string }) => {
const [watermark, setWatermark] = React.useState<boolean>(false);
const [gapCheck, setGapCheck] = React.useState<boolean>(false);
const css = React.useMemo(() => {
if (!gapCheck) {
return {};
}
return {
'--dv-group-gap-size': '0.5rem',
'--demo-border': '5px dashed purple',
} as React.CSSProperties;
}, [gapCheck]);
return (
<div
style={{
@ -195,6 +208,7 @@ const DockviewDemo = (props: { theme?: string }) => {
padding: '8px',
backgroundColor: 'rgba(0,0,50,0.25)',
borderRadius: '8px',
...css,
}}
>
<div>
@ -217,6 +231,15 @@ const DockviewDemo = (props: { theme?: string }) => {
activeGroup={activeGroup}
/>
)}
{/* <div>
<button
onClick={() => {
setGapCheck(!gapCheck);
}}
>
{gapCheck ? 'Disable Gap Check' : 'Enable Gap Check'}
</button>
</div> */}
</div>
<div
style={{

View File

@ -24,6 +24,7 @@ export const GridActions = (props: {
const onSave = () => {
if (props.api) {
console.log(props.api.toJSON());
localStorage.setItem(
'dv-demo-state',
JSON.stringify(props.api.toJSON())

View File

@ -1,4 +1,8 @@
import { DockviewApi, DockviewGroupLocation } from 'dockview';
import {
DockviewApi,
DockviewGroupLocation,
DockviewGroupPanel,
} from 'dockview';
import * as React from 'react';
const GroupAction = (props: {
@ -12,14 +16,27 @@ const GroupAction = (props: {
};
const isActive = props.activeGroup === props.groupId;
const group = React.useMemo(
() => props.api.getGroup(props.groupId),
[props.api, props.groupId]
const [group, setGroup] = React.useState<DockviewGroupPanel | undefined>(
undefined
);
React.useEffect(() => {
const disposable = props.api.onDidLayoutFromJSON(() => {
setGroup(props.api.getGroup(props.groupId));
});
setGroup(props.api.getGroup(props.groupId));
return () => {
disposable.dispose();
};
}, [props.api, props.groupId]);
const [location, setLocation] =
React.useState<DockviewGroupLocation | null>(null);
const [isMaximized, setIsMaximized] = React.useState<boolean>(false);
const [isVisible, setIsVisible] = React.useState<boolean>(true);
React.useEffect(() => {
if (!group) {
@ -35,12 +52,18 @@ const GroupAction = (props: {
setIsMaximized(group.api.isMaximized());
});
const disposable3 = group.api.onDidVisibilityChange(() => {
setIsVisible(group.api.isVisible);
});
setLocation(group.api.location);
setIsMaximized(group.api.isMaximized());
setIsVisible(group.api.isVisible);
return () => {
disposable.dispose();
disposable2.dispose();
disposable3.dispose();
};
}, [group]);
@ -65,7 +88,14 @@ const GroupAction = (props: {
}
onClick={() => {
if (group) {
props.api.addFloatingGroup(group);
props.api.addFloatingGroup(group, {
position: {
width: 400,
height: 300,
top: 50,
right: 50,
},
});
}
}}
>
@ -107,6 +137,23 @@ const GroupAction = (props: {
fullscreen
</span>
</button>
<button
className="demo-icon-button"
onClick={() => {
console.log(group);
if (group) {
if (group.api.isVisible) {
group.api.setVisible(false);
} else {
group.api.setVisible(true);
}
}
}}
>
<span className="material-symbols-outlined">
{isVisible ? 'visibility' : 'visibility_off'}
</span>
</button>
<button
className="demo-icon-button"
onClick={() => {

View File

@ -1,4 +1,5 @@
import { DockviewApi } from 'dockview';
import { DockviewApi, IDockviewPanel } from 'dockview';
import * as React from 'react';
const PanelAction = (props: {
panels: string[];
@ -9,6 +10,50 @@ const PanelAction = (props: {
const onClick = () => {
props.api.getPanel(props.panelId)?.focus();
};
React.useEffect(() => {
const panel = props.api.getPanel(props.panelId);
if (panel) {
const disposable = panel.api.onDidVisibilityChange((event) => {
setVisible(event.isVisible);
});
setVisible(panel.api.isVisible);
return () => {
disposable.dispose();
};
}
}, [props.api, props.panelId]);
const [panel, setPanel] = React.useState<IDockviewPanel | undefined>(
undefined
);
React.useEffect(() => {
const list = [
props.api.onDidLayoutFromJSON(() => {
setPanel(props.api.getPanel(props.panelId));
}),
];
if (panel) {
const disposable = panel.api.onDidVisibilityChange((event) => {
setVisible(event.isVisible);
});
setVisible(panel.api.isVisible);
list.push(disposable);
}
setPanel(props.api.getPanel(props.panelId));
return () => {
list.forEach((l) => l.dispose());
};
}, [props.api, props.panelId]);
const [visible, setVisible] = React.useState<boolean>(true);
return (
<div className="button-action">
<div style={{ display: 'flex' }}>
@ -29,7 +74,14 @@ const PanelAction = (props: {
onClick={() => {
const panel = props.api.getPanel(props.panelId);
if (panel) {
props.api.addFloatingGroup(panel);
props.api.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>
@ -57,6 +109,15 @@ const PanelAction = (props: {
>
<span className="material-symbols-outlined">close</span>
</button>
<button
title="Panel visiblity cannot be edited manually."
disabled={true}
className="demo-icon-button"
>
<span className="material-symbols-outlined">
{visible ? 'visibility' : 'visibility_off'}
</span>
</button>
</div>
</div>
);

View File

@ -84,7 +84,7 @@ function addFloatingPanel2(api: DockviewApi) {
id: (++panelCount).toString(),
title: `Tab ${panelCount}`,
component: 'default',
floating: { width: 250, height: 150, x: 50, y: 50 },
floating: { width: 250, height: 150, left: 50, top: 50 },
});
}
@ -259,11 +259,9 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
);
React.useEffect(() => {
const disposable = props.group.api.onDidLocationChange(
(event) => {
setFloating(event.location.type === 'floating');
}
);
const disposable = props.group.api.onDidLocationChange((event) => {
setFloating(event.location.type === 'floating');
});
return () => {
disposable.dispose();
@ -275,7 +273,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
props.containerApi.addFloatingGroup(props.group);
props.containerApi.addFloatingGroup(props.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
};

View File

@ -1,6 +1,7 @@
import fs from 'fs-extra';
import * as path from 'path';
import { argv } from 'process';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url';
@ -14,7 +15,7 @@ const { version } = JSON.parse(
const REACT_VERSION = '18.2.0';
const VUE_VERSION = '3.4.21';
const DOCKVIEW_VERSION = 'latest'; // version;
const DOCKVIEW_VERSION = version; //'latest';;
const USE_LOCAL_CDN = argv.slice(2).includes('--local');
const local = 'http://localhost:1111';
@ -35,11 +36,13 @@ const DOCKVIEW_CDN = {
'dockview-core': `https://cdn.jsdelivr.net/npm/dockview-core@${DOCKVIEW_VERSION}/dist/dockview-core.esm.js`,
'dockview-core/': `https://cdn.jsdelivr.net/npm/dockview-core@${DOCKVIEW_VERSION}/`,
'dockview-vue': `https://cdn.jsdelivr.net/npm/dockview-vue@${DOCKVIEW_VERSION}/dist/dockview-vue.es.js`,
'dockview-vue/': `https://cdn.jsdelivr.net/npm/dockview-vue@${DOCKVIEW_VERSION}/`,
},
local: {
'dockview-core': `${local}/dockview-core/dist/dockview-core.esm.js`,
'dockview-core/': `${local}/dockview-core/`,
'dockview-vue': `${local}/dockview-vue/dist/dockview-vue.es.js`,
'dockview-vue/': `${local}/dockview-vue/`,
},
},
typescript: {

View File

@ -3,7 +3,7 @@ import { useActiveFramework } from '../frameworkSpecific';
import BrowserOnly from '@docusaurus/BrowserOnly';
const BASE_SANDBOX_URL =
'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs';
'https://codesandbox.io/s/github/mathuo/dockview/tree/gh-pages';
export const _CodeRunner = (props: { id: string; height: number }) => {
const [framework] = useActiveFramework();

File diff suppressed because it is too large Load Diff

1
packages/docs/static/img/vue-icon.svg vendored Normal file
View File

@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 261.76 226.69"><path d="M161.096.001l-30.225 52.351L100.647.001H-.005l130.877 226.688L261.749.001z" fill="#41b883"/><path d="M161.096.001l-30.225 52.351L100.647.001H52.346l78.526 136.01L209.398.001z" fill="#34495e"/></svg>

After

Width:  |  Height:  |  Size: 276 B

View File

@ -1,32 +0,0 @@
{
"name": "dockview.resize",
"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",
"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"
]
}

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,18 +0,0 @@
{
"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
}
}

View File

@ -1,11 +1,11 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelHeaderProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -14,7 +14,7 @@ export function defaultConfig(api: DockviewApi) {
id: 'panel_1',
component: 'default',
renderer: 'always',
title: 'Panel 1',
title: 'Panel 1'
});
api.addPanel({

View File

@ -32,7 +32,14 @@ export const GroupActions = (props: {
onClick={() => {
const panel = props.api?.getGroup(x);
if (panel) {
props.api?.addFloatingGroup(panel);
props.api?.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -32,7 +32,14 @@ export const PanelActions = (props: {
onClick={() => {
const panel = props.api?.getPanel(x);
if (panel) {
props.api?.addFloatingGroup(panel);
props.api?.addFloatingGroup(panel, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
}}
>

View File

@ -209,9 +209,8 @@ export const DockviewPersistence = (props: { theme?: string }) => {
setDisableFloatingGroups((x) => !x);
}}
>
{`${
disableFloatingGroups ? 'Enable' : 'Disable'
} floating groups`}
{`${disableFloatingGroups ? 'Enable' : 'Disable'
} floating groups`}
</button>
</div>
<div
@ -265,7 +264,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup();
props.group.api.moveTo({ group });
} else {
props.containerApi.addFloatingGroup(props.group);
props.containerApi.addFloatingGroup(props.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
}
});
}
};

View File

@ -1,12 +1,12 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewApi,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
let panelCount = 0;
@ -93,7 +93,14 @@ const RightAction = defineComponent({
const group = this.params.containerApi.addGroup();
this.group.api.moveTo({ group });
} else {
this.containerApi.addFloatingGroup(this.params.group);
this.containerApi.addFloatingGroup(this.params.group, {
position: {
width: 400,
height: 300,
bottom: 50,
right: 50,
},
});
}
},
},

View File

@ -1,11 +1,11 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
import './app.css';
const LeftAction = defineComponent({

View File

@ -1,12 +1,12 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewApi,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
let panelCount = 0;

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,11 +1,12 @@
import 'dockview-core/dist/styles/dockview.css';
import { Prop, PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
let panelCount = 0;

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,12 +1,12 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewApi,
DockviewReadyEvent,
IDockviewHeaderActionsProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
let panelCount = 0;

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
import './resize.css';
const Panel = defineComponent({

View File

@ -1,7 +1,6 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent } from 'dockview-core';
import { DockviewVue, DockviewReadyEvent } from 'dockview-vue';
const TEXT =
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';

View File

@ -1,11 +1,11 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewApi,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,11 +1,11 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelHeaderProps,
IDockviewPanelProps,
} from 'dockview-core';
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,7 +1,10 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
import {
DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',

View File

@ -1,13 +1,13 @@
import 'dockview-core/dist/styles/dockview.css';
import 'dockview-vue/dist/styles/dockview.css';
import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import {
DockviewVue,
DockviewApi,
DockviewReadyEvent,
IDockviewPanelProps,
IWatermarkPanelProps,
Orientation,
} from 'dockview-core';
} from 'dockview-vue';
const Panel = defineComponent({
name: 'Panel',