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"> <div align="center">
<h1>dockview</h1> <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> </div>
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions - Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance). 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": [
"packages/*" "packages/*"
], ],
"version": "1.14.0", "version": "1.14.2",
"npmClient": "yarn", "npmClient": "yarn",
"command": { "command": {
"publish": { "publish": {

View File

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

View File

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
<h1>dockview</h1> <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> </div>
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions - Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance). 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", "name": "dockview-core",
"version": "1.14.0", "version": "1.14.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews", "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [ "keywords": [
"splitview", "splitview",

View File

@ -1,7 +1,34 @@
import { Overlay } from '../../dnd/overlay'; 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', () => { describe('overlay', () => {
test('toJSON', () => { test('toJSON, top left', () => {
const container = document.createElement('div'); const container = document.createElement('div');
const content = document.createElement('div'); const content = document.createElement('div');
@ -23,14 +50,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement, container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect' 'getBoundingClientRect'
).mockImplementation(() => { ).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( 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({ expect(cut.toJSON()).toEqual({
top: 70, top: 70,
left: 60, 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 container = document.createElement('div');
const content = document.createElement('div'); const content = document.createElement('div');
@ -61,14 +150,26 @@ describe('overlay', () => {
container.childNodes.item(0) as HTMLElement, container.childNodes.item(0) as HTMLElement,
'getBoundingClientRect' 'getBoundingClientRect'
).mockImplementation(() => { ).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( 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({ expect(cut.toJSON()).toEqual({
top: 70, top: 70,
left: 60, 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 container = document.createElement('div');
const content = document.createElement('div'); const content = document.createElement('div');
@ -101,11 +252,21 @@ describe('overlay', () => {
expect(element).toBeTruthy(); expect(element).toBeTruthy();
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => { 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( 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'); 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', () => { test('that the resize handles are added', () => {
const container = document.createElement('div'); const container = document.createElement('div');
const content = 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'); const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
fireEvent(container, event); fireEvent(container, event);
expect(accessor.addFloatingGroup).toHaveBeenCalledWith( expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
groupPanel, x: 100,
{ y: 60,
x: 100, inDragMode: true,
y: 60, });
},
{ inDragMode: true }
);
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1); expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1); expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);

View File

@ -10,7 +10,7 @@ import { CompositeDisposable } from '../../lifecycle';
import { Emitter } from '../../events'; import { Emitter } from '../../events';
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel'; import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel'; 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 { getPanelData } from '../../dnd/dataTransfer';
import { import {
GroupDragEvent, GroupDragEvent,
@ -5165,6 +5165,60 @@ describe('dockviewComponent', () => {
expect(panel4.api.isVisible).toBeTruthy(); 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', () => { describe('addPanel', () => {
test('that can add panel', () => { test('that can add panel', () => {
const container = document.createElement('div'); const container = document.createElement('div');
@ -5497,4 +5551,57 @@ describe('dockviewComponent', () => {
expect(api.panels.length).toBe(3); expect(api.panels.length).toBe(3);
expect(api.groups.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>(); _onDidChange = new Emitter<IViewSize | undefined>();
readonly onDidChange = this._onDidChange.event; readonly onDidChange = this._onDidChange.event;
get isActive(): boolean { isVisible: boolean = true;
return true; isActive: boolean = true;
} params: Parameters = {};
get params(): Parameters {
return {};
}
constructor( constructor(
public readonly id: string, public readonly id: string,

View File

@ -5,6 +5,7 @@ import {
IGridView, IGridView,
IViewSize, IViewSize,
SerializedGridview, SerializedGridview,
getGridLocation,
orthogonal, orthogonal,
} from '../../gridview/gridview'; } from '../../gridview/gridview';
import { Orientation, Sizing } from '../../splitview/splitview'; import { Orientation, Sizing } from '../../splitview/splitview';
@ -18,7 +19,7 @@ class MockGridview implements IGridView {
IViewSize | undefined IViewSize | undefined
>().event; >().event;
element: HTMLElement = document.createElement('div'); element: HTMLElement = document.createElement('div');
isVisible: boolean = true;
width: number = 0; width: number = 0;
height: number = 0; height: number = 0;
@ -1105,4 +1106,102 @@ describe('gridview', () => {
expect(gridview.hasMaximizedView()).toBeFalsy(); 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); expect(clamp(55, 40, 50)).toBe(50);
}); });
it('should throw an error if min > max', () => { it('if min > max return min', () => {
expect(() => clamp(55, 50, 40)).toThrow( expect(clamp(55, 50, 40)).toBe(50);
'50 > 40 is an invalid condition'
);
}); });
}); });

View File

@ -1,4 +1,5 @@
import { import {
FloatingGroupOptions,
IDockviewComponent, IDockviewComponent,
MovePanelEvent, MovePanelEvent,
SerializedDockview, SerializedDockview,
@ -43,7 +44,7 @@ import {
GroupDragEvent, GroupDragEvent,
TabDragEvent, TabDragEvent,
} from '../dockview/components/titlebar/tabsContainer'; } from '../dockview/components/titlebar/tabsContainer';
import { Box } from '../types'; import { AnchoredBox, Box } from '../types';
import { import {
DockviewDidDropEvent, DockviewDidDropEvent,
DockviewWillDropEvent, DockviewWillDropEvent,
@ -805,9 +806,9 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
*/ */
addFloatingGroup( addFloatingGroup(
item: IDockviewPanel | DockviewGroupPanel, item: IDockviewPanel | DockviewGroupPanel,
coord?: { x: number; y: number } options?: FloatingGroupOptions
): void { ): 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_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'; } from '../events';
import { CompositeDisposable, MutableDisposable } from '../lifecycle'; import { CompositeDisposable, MutableDisposable } from '../lifecycle';
import { clamp } from '../math'; import { clamp } from '../math';
import { Box } from '../types'; import { AnchoredBox } from '../types';
const bringElementToFront = (() => { const bringElementToFront = (() => {
let previous: HTMLElement | null = null; let previous: HTMLElement | null = null;
@ -40,6 +40,9 @@ export class Overlay extends CompositeDisposable {
private static MINIMUM_HEIGHT = 20; private static MINIMUM_HEIGHT = 20;
private static MINIMUM_WIDTH = 20; private static MINIMUM_WIDTH = 20;
private verticalAlignment: 'top' | 'bottom' | undefined;
private horiziontalAlignment: 'left' | 'right' | undefined;
set minimumInViewportWidth(value: number | undefined) { set minimumInViewportWidth(value: number | undefined) {
this.options.minimumInViewportWidth = value; this.options.minimumInViewportWidth = value;
} }
@ -49,7 +52,7 @@ export class Overlay extends CompositeDisposable {
} }
constructor( constructor(
private readonly options: Box & { private readonly options: AnchoredBox & {
container: HTMLElement; container: HTMLElement;
content: HTMLElement; content: HTMLElement;
minimumInViewportWidth?: number; minimumInViewportWidth?: number;
@ -78,23 +81,39 @@ export class Overlay extends CompositeDisposable {
this.setBounds({ this.setBounds({
height: this.options.height, height: this.options.height,
width: this.options.width, width: this.options.width,
top: this.options.top, ...('top' in this.options && { top: this.options.top }),
left: this.options.left, ...('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') { if (typeof bounds.height === 'number') {
this._element.style.height = `${bounds.height}px`; this._element.style.height = `${bounds.height}px`;
} }
if (typeof bounds.width === 'number') { if (typeof bounds.width === 'number') {
this._element.style.width = `${bounds.width}px`; 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.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.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(); const containerRect = this.options.container.getBoundingClientRect();
@ -106,39 +125,77 @@ export class Overlay extends CompositeDisposable {
const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width)); const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
// a minimum height of minimumViewportHeight must be inside the viewport // a minimum height of minimumViewportHeight must be inside the viewport
const yOffset = const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
typeof this.options.minimumInViewportHeight === 'number'
? Math.max(0, this.getMinimumHeight(overlayRect.height))
: 0;
const left = clamp( if (this.verticalAlignment === 'top') {
overlayRect.left - containerRect.left, const top = clamp(
-xOffset, overlayRect.top - containerRect.top,
Math.max(0, containerRect.width - overlayRect.width + xOffset) -yOffset,
); Math.max(0, containerRect.height - overlayRect.height + yOffset)
);
this._element.style.top = `${top}px`;
this._element.style.bottom = 'auto';
}
const top = clamp( if (this.verticalAlignment === 'bottom') {
overlayRect.top - containerRect.top, const bottom = clamp(
-yOffset, containerRect.bottom - overlayRect.bottom,
Math.max(0, containerRect.height - overlayRect.height + yOffset) -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`; if (this.horiziontalAlignment === 'left') {
this._element.style.top = `${top}px`; 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(); this._onDidChange.fire();
} }
toJSON(): Box { toJSON(): AnchoredBox {
const container = this.options.container.getBoundingClientRect(); const container = this.options.container.getBoundingClientRect();
const element = this._element.getBoundingClientRect(); const element = this._element.getBoundingClientRect();
return { const result: any = {};
top: element.top - container.top,
left: element.left - container.left, if (this.verticalAlignment === 'top') {
width: element.width, result.top = parseFloat(this._element.style.top);
height: element.height, } 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( setupDrag(
@ -193,18 +250,7 @@ export class Overlay extends CompositeDisposable {
); );
const yOffset = Math.max( const yOffset = Math.max(
0, 0,
this.options.minimumInViewportHeight this.getMinimumHeight(overlayRect.height)
? this.getMinimumHeight(overlayRect.height)
: 0
);
const left = clamp(
x - offset.x,
-xOffset,
Math.max(
0,
containerRect.width - overlayRect.width + xOffset
)
); );
const top = clamp( 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', () => { addDisposableWindowListener(window, 'mouseup', () => {
toggleClass( toggleClass(
@ -342,8 +434,10 @@ export class Overlay extends CompositeDisposable {
} }
let top: number | undefined = undefined; let top: number | undefined = undefined;
let bottom: number | undefined = undefined;
let height: number | undefined = undefined; let height: number | undefined = undefined;
let left: number | undefined = undefined; let left: number | undefined = undefined;
let right: number | undefined = undefined;
let width: number | undefined = undefined; let width: number | undefined = undefined;
const moveTop = () => { const moveTop = () => {
@ -363,10 +457,13 @@ export class Overlay extends CompositeDisposable {
Overlay.MINIMUM_HEIGHT Overlay.MINIMUM_HEIGHT
) )
); );
height = height =
startPosition!.originalY + startPosition!.originalY +
startPosition!.originalHeight - startPosition!.originalHeight -
top; top;
bottom = containerRect.height - top - height;
}; };
const moveBottom = () => { const moveBottom = () => {
@ -384,6 +481,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_HEIGHT, : Overlay.MINIMUM_HEIGHT,
Number.MAX_VALUE Number.MAX_VALUE
); );
bottom = containerRect.height - top - height;
}; };
const moveLeft = () => { const moveLeft = () => {
@ -406,6 +505,8 @@ export class Overlay extends CompositeDisposable {
startPosition!.originalX + startPosition!.originalX +
startPosition!.originalWidth - startPosition!.originalWidth -
left; left;
right = containerRect.width - left - width;
}; };
const moveRight = () => { const moveRight = () => {
@ -423,6 +524,8 @@ export class Overlay extends CompositeDisposable {
: Overlay.MINIMUM_WIDTH, : Overlay.MINIMUM_WIDTH,
Number.MAX_VALUE Number.MAX_VALUE
); );
right = containerRect.width - left - width;
}; };
switch (direction) { switch (direction) {
@ -456,7 +559,26 @@ export class Overlay extends CompositeDisposable {
break; 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: () => { dispose: () => {
@ -485,7 +607,7 @@ export class Overlay extends CompositeDisposable {
if (typeof this.options.minimumInViewportHeight === 'number') { if (typeof this.options.minimumInViewportHeight === 'number') {
return height - this.options.minimumInViewportHeight; return height - this.options.minimumInViewportHeight;
} }
return height; return 0;
} }
override dispose(): void { override dispose(): void {

View File

@ -1,16 +1,13 @@
import { CompositeDisposable } from '../../../lifecycle'; import { CompositeDisposable } from '../../../lifecycle';
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types'; import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
import { addDisposableListener } from '../../../events'; import { addDisposableListener } from '../../../events';
import { PanelUpdateEvent } from '../../../panel/types';
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
import { createCloseButton } from '../../../svg'; import { createCloseButton } from '../../../svg';
export class DefaultTab extends CompositeDisposable implements ITabRenderer { export class DefaultTab extends CompositeDisposable implements ITabRenderer {
private _element: HTMLElement; private _element: HTMLElement;
private _content: HTMLElement; private _content: HTMLElement;
private action: HTMLElement; private action: HTMLElement;
// private _title: string | undefined;
private params: GroupPanelPartInitParameters = {} as any;
get element(): HTMLElement { get element(): HTMLElement {
return this._element; return this._element;
@ -21,7 +18,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this._element = document.createElement('div'); this._element = document.createElement('div');
this._element.className = 'dv-default-tab'; this._element.className = 'dv-default-tab';
//
this._content = document.createElement('div'); this._content = document.createElement('div');
this._content.className = 'dv-default-tab-content'; 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.className = 'dv-default-tab-action';
this.action.appendChild(createCloseButton()); this.action.appendChild(createCloseButton());
//
this._element.appendChild(this._content); this._element.appendChild(this._content);
this._element.appendChild(this.action); this._element.appendChild(this.action);
//
this.addDisposables( this.addDisposables(
addDisposableListener(this.action, 'mousedown', (ev) => { addDisposableListener(this.action, 'mousedown', (ev) => {
ev.preventDefault(); ev.preventDefault();
@ -42,40 +38,33 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
this.render(); this.render();
} }
public update(event: PanelUpdateEvent): void { init(params: GroupPanelPartInitParameters): void {
this.params = { ...this.params, ...event.params }; 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(); 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 { private render(): void {
if (this._content.textContent !== this.params.title) { if (this._content.textContent !== this._title) {
this._content.textContent = this.params.title; this._content.textContent = this._title ?? '';
} }
} }
} }

View File

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

View File

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

View File

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

View File

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

View File

@ -253,3 +253,7 @@ export function isInDocument(element: Element): boolean {
return false; 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> = readonly onDidLayoutChange: Event<void> =
this._bufferOnDidLayoutChange.onEvent; this._bufferOnDidLayoutChange.onEvent;
private readonly _onDidViewVisibilityChangeMicroTaskQueue = new AsapEvent();
readonly onDidViewVisibilityChangeMicroTaskQueue =
this._onDidViewVisibilityChangeMicroTaskQueue.onEvent;
get id(): string { get id(): string {
return this._id; 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.layout(0, 0, true); // set some elements height/widths
this.addDisposables( this.addDisposables(
this.gridview.onDidViewVisibilityChange(() =>
this._onDidViewVisibilityChangeMicroTaskQueue.fire()
),
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
this.layout(this.width, this.height, true);
}),
Disposable.from(() => { Disposable.from(() => {
this.element.parentElement?.removeChild(this.element); 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 }> = readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
this._onDidChange.event; this._onDidChange.event;
private readonly _onDidVisibilityChange = new Emitter<boolean>(); private readonly _onDidVisibilityChange = new Emitter<{
readonly onDidVisibilityChange: Event<boolean> = visible: boolean;
this._onDidVisibilityChange.event; }>();
readonly onDidVisibilityChange: Event<{
visible: boolean;
}> = this._onDidVisibilityChange.event;
get width(): number { get width(): number {
return this.orientation === Orientation.HORIZONTAL return this.orientation === Orientation.HORIZONTAL
@ -200,10 +203,8 @@ export class BranchNode extends CompositeDisposable implements IView {
this.setupChildrenEvents(); this.setupChildrenEvents();
} }
setVisible(visible: boolean): void { setVisible(_visible: boolean): void {
for (const child of this.children) { // noop
child.setVisible(visible);
}
} }
isChildVisible(index: number): boolean { isChildVisible(index: number): boolean {
@ -224,7 +225,9 @@ export class BranchNode extends CompositeDisposable implements IView {
} }
const wereAllChildrenHidden = this.splitview.contentSize === 0; const wereAllChildrenHidden = this.splitview.contentSize === 0;
this.splitview.setViewVisible(index, visible); this.splitview.setViewVisible(index, visible);
// }
const areAllChildrenHidden = this.splitview.contentSize === 0; const areAllChildrenHidden = this.splitview.contentSize === 0;
// If all children are hidden then the parent should hide the entire splitview // 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 && wereAllChildrenHidden) ||
(!visible && areAllChildrenHidden) (!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) => { ...this.children.map((c, i) => {
if (c instanceof BranchNode) { if (c instanceof BranchNode) {
return c.onDidVisibilityChange((visible) => { return c.onDidVisibilityChange(({ visible }) => {
this.setChildVisible(i, visible); this.setChildVisible(i, visible);
}); });
} }

View File

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

View File

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

View File

@ -1,6 +1,10 @@
export const clamp = (value: number, min: number, max: number): number => { export const clamp = (value: number, min: number, max: number): number => {
if (min > max) { 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)); return Math.min(max, Math.max(value, min));
}; };

View File

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

View File

@ -8,3 +8,13 @@ export interface Box {
height: number; height: number;
width: 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"> <div align="center">
<h1>dockview</h1> <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> </div>
@ -16,6 +16,8 @@
## ##
![](packages/docs/static/img/splashscreen.gif)
Please see the website: https://dockview.dev Please see the website: https://dockview.dev
## Features ## Features
@ -34,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions - Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance). 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", "name": "dockview-react",
"version": "1.14.0", "version": "1.14.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews", "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [ "keywords": [
"splitview", "splitview",
@ -54,6 +54,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage" "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-react --coverage"
}, },
"dependencies": { "dependencies": {
"dockview": "^1.14.0" "dockview": "^1.14.2"
} }
} }

View File

@ -1,7 +1,7 @@
<div align="center"> <div align="center">
<h1>dockview</h1> <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> </div>
@ -16,6 +16,8 @@
## ##
![](packages/docs/static/img/splashscreen.gif)
Please see the website: https://dockview.dev Please see the website: https://dockview.dev
## Features ## Features
@ -34,23 +36,3 @@ Please see the website: https://dockview.dev
- Security at mind - verifed publishing and builds through GitHub Actions - Security at mind - verifed publishing and builds through GitHub Actions
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance). 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", "name": "dockview-vue",
"version": "1.14.0", "version": "1.14.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews", "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [ "keywords": [
"splitview", "splitview",
@ -52,6 +52,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage" "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage"
}, },
"dependencies": { "dependencies": {
"dockview-core": "^1.14.0" "dockview-core": "^1.14.2"
} }
} }

View File

@ -70,55 +70,61 @@ PROPERTY_KEYS.forEach((coreOptionKey) => {
onMounted(() => { onMounted(() => {
if (!el.value) { 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 = { const frameworkOptions: DockviewFrameworkOptions = {
parentElement: el.value, parentElement: el.value,
createComponent(options) { createComponent(options) {
const component = findComponent( const component = findComponent(
getCurrentInstance()!, inst,
options.name options.name
); );
return new VueRenderer(component!, getCurrentInstance()!); return new VueRenderer(component!, inst);
}, },
createTabComponent(options) { createTabComponent(options) {
let component = findComponent(getCurrentInstance()!, options.name); let component = findComponent(inst, options.name);
if (!component && props.defaultTabComponent) { if (!component && props.defaultTabComponent) {
component = findComponent( component = findComponent(
getCurrentInstance()!, inst,
props.defaultTabComponent props.defaultTabComponent
); );
} }
if (component) { if (component) {
return new VueRenderer(component, getCurrentInstance()!); return new VueRenderer(component, inst);
} }
return undefined; return undefined;
}, },
createWatermarkComponent: props.watermarkComponent createWatermarkComponent: props.watermarkComponent
? () => { ? () => {
const component = findComponent( const component = findComponent(
getCurrentInstance()!, inst,
props.watermarkComponent! props.watermarkComponent!
); );
return new VueWatermarkRenderer( return new VueWatermarkRenderer(
component!, component!,
getCurrentInstance()! inst
); );
} }
: undefined, : undefined,
createLeftHeaderActionComponent: props.leftHeaderActionsComponent createLeftHeaderActionComponent: props.leftHeaderActionsComponent
? (group) => { ? (group) => {
const component = findComponent( const component = findComponent(
getCurrentInstance()!, inst,
props.leftHeaderActionsComponent! props.leftHeaderActionsComponent!
); );
return new VueHeaderActionsRenderer( return new VueHeaderActionsRenderer(
component!, component!,
getCurrentInstance()!, inst,
group group
); );
} }
@ -126,12 +132,12 @@ onMounted(() => {
createPrefixHeaderActionComponent: props.prefixHeaderActionsComponent createPrefixHeaderActionComponent: props.prefixHeaderActionsComponent
? (group) => { ? (group) => {
const component = findComponent( const component = findComponent(
getCurrentInstance()!, inst,
props.prefixHeaderActionsComponent! props.prefixHeaderActionsComponent!
); );
return new VueHeaderActionsRenderer( return new VueHeaderActionsRenderer(
component!, component!,
getCurrentInstance()!, inst,
group group
); );
} }
@ -139,12 +145,12 @@ onMounted(() => {
createRightHeaderActionComponent: props.rightHeaderActionsComponent createRightHeaderActionComponent: props.rightHeaderActionsComponent
? (group) => { ? (group) => {
const component = findComponent( const component = findComponent(
getCurrentInstance()!, inst,
props.rightHeaderActionsComponent! props.rightHeaderActionsComponent!
); );
return new VueHeaderActionsRenderer( return new VueHeaderActionsRenderer(
component!, component!,
getCurrentInstance()!, inst,
group group
); );
} }

View File

@ -1,6 +1,6 @@
{ {
"name": "dockview", "name": "dockview",
"version": "1.14.0", "version": "1.14.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews", "description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [ "keywords": [
"splitview", "splitview",
@ -54,6 +54,6 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage" "test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
}, },
"dependencies": { "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 { DockviewDefaultTab } from '../../dockview/defaultTab';
import React from 'react'; import React from 'react';
import { fromPartial } from '@total-typescript/shoehorn'; 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', () => { describe('defaultTab', () => {
test('has close button by default', async () => { 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 containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -25,6 +30,7 @@ describe('defaultTab', () => {
test('that title is displayed', async () => { test('that title is displayed', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
title: 'test_title', title: 'test_title',
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -43,8 +49,43 @@ describe('defaultTab', () => {
).toBe('test_title'); ).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 () => { 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 containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -64,6 +105,7 @@ describe('defaultTab', () => {
test('that settings closeActionOverride skips api.close()', async () => { test('that settings closeActionOverride skips api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
close: jest.fn(), close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -85,13 +127,14 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.click(btn); fireEvent.click(btn);
expect(closeActionOverride).toBeCalledTimes(1); expect(closeActionOverride).toHaveBeenCalledTimes(1);
expect(api.close).toBeCalledTimes(0); expect(api.close).toHaveBeenCalledTimes(0);
}); });
test('that clicking close calls api.close()', async () => { test('that clicking close calls api.close()', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
close: jest.fn(), close: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -110,11 +153,13 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.click(btn); fireEvent.click(btn);
expect(api.close).toBeCalledTimes(1); expect(api.close).toHaveBeenCalledTimes(1);
}); });
test('has close button when hideClose=false', async () => { 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 containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -134,6 +179,7 @@ describe('defaultTab', () => {
test('that mouseDown on close button prevents panel becoming active', async () => { test('that mouseDown on close button prevents panel becoming active', async () => {
const api = fromPartial<DockviewPanelApi>({ const api = fromPartial<DockviewPanelApi>({
setActive: jest.fn(), setActive: jest.fn(),
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
}); });
const containerApi = fromPartial<DockviewApi>({}); const containerApi = fromPartial<DockviewApi>({});
const params = {}; const params = {};
@ -152,9 +198,9 @@ describe('defaultTab', () => {
) as HTMLElement; ) as HTMLElement;
fireEvent.mouseDown(btn); fireEvent.mouseDown(btn);
expect(api.setActive).toBeCalledTimes(0); expect(api.setActive).toHaveBeenCalledTimes(0);
fireEvent.click(element); fireEvent.click(element);
expect(api.setActive).toBeCalledTimes(1); expect(api.setActive).toHaveBeenCalledTimes(1);
}); });
}); });

View File

@ -1,6 +1,22 @@
import React from 'react'; import React from 'react';
import { CloseButton } from '../svg'; 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 & export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
React.DOMAttributes<HTMLDivElement> & { React.DOMAttributes<HTMLDivElement> & {
@ -18,6 +34,8 @@ export const DockviewDefaultTab: React.FunctionComponent<
closeActionOverride, closeActionOverride,
...rest ...rest
}) => { }) => {
const title = useTitle(api);
const onClose = React.useCallback( const onClose = React.useCallback(
(event: React.MouseEvent<HTMLSpanElement>) => { (event: React.MouseEvent<HTMLSpanElement>) => {
event.preventDefault(); event.preventDefault();
@ -57,7 +75,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
onClick={onClick} onClick={onClick}
className="dv-default-tab" className="dv-default-tab"
> >
<span className="dv-default-tab-content">{api.title}</span> <span className="dv-default-tab-content">{title}</span>
{!hideClose && ( {!hideClose && (
<div <div
className="dv-default-tab-action" 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({ api.addPanel({
id: 'panel_2', id: 'panel_2',
component: 'default', 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", "name": "dockview-docs",
"version": "1.14.0", "version": "1.14.2",
"private": true, "private": true,
"scripts": { "scripts": {
"build": "npm run build-templates && docusaurus build", "build": "npm run build-templates && docusaurus build",
@ -37,7 +37,7 @@
"ag-grid-react": "^31.0.2", "ag-grid-react": "^31.0.2",
"axios": "^1.6.3", "axios": "^1.6.3",
"clsx": "^2.1.0", "clsx": "^2.1.0",
"dockview": "^1.14.0", "dockview": "^1.14.2",
"prism-react-renderer": "^2.3.1", "prism-react-renderer": "^2.3.1",
"react-dnd": "^16.0.1", "react-dnd": "^16.0.1",
"react-laag": "^2.0.5", "react-laag": "^2.0.5",

View File

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

View File

@ -185,6 +185,19 @@ const DockviewDemo = (props: { theme?: string }) => {
const [watermark, setWatermark] = React.useState<boolean>(false); 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 ( return (
<div <div
style={{ style={{
@ -195,6 +208,7 @@ const DockviewDemo = (props: { theme?: string }) => {
padding: '8px', padding: '8px',
backgroundColor: 'rgba(0,0,50,0.25)', backgroundColor: 'rgba(0,0,50,0.25)',
borderRadius: '8px', borderRadius: '8px',
...css,
}} }}
> >
<div> <div>
@ -217,6 +231,15 @@ const DockviewDemo = (props: { theme?: string }) => {
activeGroup={activeGroup} activeGroup={activeGroup}
/> />
)} )}
{/* <div>
<button
onClick={() => {
setGapCheck(!gapCheck);
}}
>
{gapCheck ? 'Disable Gap Check' : 'Enable Gap Check'}
</button>
</div> */}
</div> </div>
<div <div
style={{ style={{

View File

@ -24,6 +24,7 @@ export const GridActions = (props: {
const onSave = () => { const onSave = () => {
if (props.api) { if (props.api) {
console.log(props.api.toJSON());
localStorage.setItem( localStorage.setItem(
'dv-demo-state', 'dv-demo-state',
JSON.stringify(props.api.toJSON()) 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'; import * as React from 'react';
const GroupAction = (props: { const GroupAction = (props: {
@ -12,14 +16,27 @@ const GroupAction = (props: {
}; };
const isActive = props.activeGroup === props.groupId; const isActive = props.activeGroup === props.groupId;
const group = React.useMemo(
() => props.api.getGroup(props.groupId), const [group, setGroup] = React.useState<DockviewGroupPanel | undefined>(
[props.api, props.groupId] 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] = const [location, setLocation] =
React.useState<DockviewGroupLocation | null>(null); React.useState<DockviewGroupLocation | null>(null);
const [isMaximized, setIsMaximized] = React.useState<boolean>(false); const [isMaximized, setIsMaximized] = React.useState<boolean>(false);
const [isVisible, setIsVisible] = React.useState<boolean>(true);
React.useEffect(() => { React.useEffect(() => {
if (!group) { if (!group) {
@ -35,12 +52,18 @@ const GroupAction = (props: {
setIsMaximized(group.api.isMaximized()); setIsMaximized(group.api.isMaximized());
}); });
const disposable3 = group.api.onDidVisibilityChange(() => {
setIsVisible(group.api.isVisible);
});
setLocation(group.api.location); setLocation(group.api.location);
setIsMaximized(group.api.isMaximized()); setIsMaximized(group.api.isMaximized());
setIsVisible(group.api.isVisible);
return () => { return () => {
disposable.dispose(); disposable.dispose();
disposable2.dispose(); disposable2.dispose();
disposable3.dispose();
}; };
}, [group]); }, [group]);
@ -65,7 +88,14 @@ const GroupAction = (props: {
} }
onClick={() => { onClick={() => {
if (group) { 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 fullscreen
</span> </span>
</button> </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 <button
className="demo-icon-button" className="demo-icon-button"
onClick={() => { onClick={() => {

View File

@ -1,4 +1,5 @@
import { DockviewApi } from 'dockview'; import { DockviewApi, IDockviewPanel } from 'dockview';
import * as React from 'react';
const PanelAction = (props: { const PanelAction = (props: {
panels: string[]; panels: string[];
@ -9,6 +10,50 @@ const PanelAction = (props: {
const onClick = () => { const onClick = () => {
props.api.getPanel(props.panelId)?.focus(); 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 ( return (
<div className="button-action"> <div className="button-action">
<div style={{ display: 'flex' }}> <div style={{ display: 'flex' }}>
@ -29,7 +74,14 @@ const PanelAction = (props: {
onClick={() => { onClick={() => {
const panel = props.api.getPanel(props.panelId); const panel = props.api.getPanel(props.panelId);
if (panel) { 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> <span className="material-symbols-outlined">close</span>
</button> </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>
</div> </div>
); );

View File

@ -84,7 +84,7 @@ function addFloatingPanel2(api: DockviewApi) {
id: (++panelCount).toString(), id: (++panelCount).toString(),
title: `Tab ${panelCount}`, title: `Tab ${panelCount}`,
component: 'default', 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(() => { React.useEffect(() => {
const disposable = props.group.api.onDidLocationChange( const disposable = props.group.api.onDidLocationChange((event) => {
(event) => { setFloating(event.location.type === 'floating');
setFloating(event.location.type === 'floating'); });
}
);
return () => { return () => {
disposable.dispose(); disposable.dispose();
@ -275,7 +273,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup(); const group = props.containerApi.addGroup();
props.group.api.moveTo({ group }); props.group.api.moveTo({ group });
} else { } 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 fs from 'fs-extra';
import * as path from 'path'; import * as path from 'path';
import { argv } from 'process'; import { argv } from 'process';
import { execSync } from 'child_process';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
@ -14,7 +15,7 @@ const { version } = JSON.parse(
const REACT_VERSION = '18.2.0'; const REACT_VERSION = '18.2.0';
const VUE_VERSION = '3.4.21'; 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 USE_LOCAL_CDN = argv.slice(2).includes('--local');
const local = 'http://localhost:1111'; 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}/dist/dockview-core.esm.js`,
'dockview-core/': `https://cdn.jsdelivr.net/npm/dockview-core@${DOCKVIEW_VERSION}/`, '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}/dist/dockview-vue.es.js`,
'dockview-vue/': `https://cdn.jsdelivr.net/npm/dockview-vue@${DOCKVIEW_VERSION}/`,
}, },
local: { local: {
'dockview-core': `${local}/dockview-core/dist/dockview-core.esm.js`, 'dockview-core': `${local}/dockview-core/dist/dockview-core.esm.js`,
'dockview-core/': `${local}/dockview-core/`, 'dockview-core/': `${local}/dockview-core/`,
'dockview-vue': `${local}/dockview-vue/dist/dockview-vue.es.js`, 'dockview-vue': `${local}/dockview-vue/dist/dockview-vue.es.js`,
'dockview-vue/': `${local}/dockview-vue/`,
}, },
}, },
typescript: { typescript: {

View File

@ -3,7 +3,7 @@ import { useActiveFramework } from '../frameworkSpecific';
import BrowserOnly from '@docusaurus/BrowserOnly'; import BrowserOnly from '@docusaurus/BrowserOnly';
const BASE_SANDBOX_URL = 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 }) => { export const _CodeRunner = (props: { id: string; height: number }) => {
const [framework] = useActiveFramework(); 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewPanelHeaderProps, IDockviewPanelHeaderProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', name: 'Panel',

View File

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

View File

@ -32,7 +32,14 @@ export const GroupActions = (props: {
onClick={() => { onClick={() => {
const panel = props.api?.getGroup(x); const panel = props.api?.getGroup(x);
if (panel) { 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={() => { onClick={() => {
const panel = props.api?.getPanel(x); const panel = props.api?.getPanel(x);
if (panel) { 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); setDisableFloatingGroups((x) => !x);
}} }}
> >
{`${ {`${disableFloatingGroups ? 'Enable' : 'Disable'
disableFloatingGroups ? 'Enable' : 'Disable' } floating groups`}
} floating groups`}
</button> </button>
</div> </div>
<div <div
@ -265,7 +264,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
const group = props.containerApi.addGroup(); const group = props.containerApi.addGroup();
props.group.api.moveTo({ group }); props.group.api.moveTo({ group });
} else { } 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewApi, DockviewApi,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
let panelCount = 0; let panelCount = 0;
@ -93,7 +93,14 @@ const RightAction = defineComponent({
const group = this.params.containerApi.addGroup(); const group = this.params.containerApi.addGroup();
this.group.api.moveTo({ group }); this.group.api.moveTo({ group });
} else { } 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
import './app.css'; import './app.css';
const LeftAction = defineComponent({ 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewApi, DockviewApi,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
let panelCount = 0; 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', name: 'Panel',

View File

@ -1,11 +1,12 @@
import 'dockview-core/dist/styles/dockview.css'; import 'dockview-vue/dist/styles/dockview.css';
import { Prop, PropType, createApp, defineComponent } from 'vue'; import { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
let panelCount = 0; 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewApi, DockviewApi,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewHeaderActionsProps, IDockviewHeaderActionsProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
let panelCount = 0; 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
import './resize.css'; import './resize.css';
const Panel = defineComponent({ 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 { createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import { DockviewVue, DockviewReadyEvent } from 'dockview-vue';
import { DockviewReadyEvent } from 'dockview-core';
const TEXT = 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.'; '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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewApi, DockviewApi,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewPanelHeaderProps, IDockviewPanelHeaderProps,
IDockviewPanelProps, IDockviewPanelProps,
} from 'dockview-core'; } from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue'; import {
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core'; DockviewVue,
DockviewReadyEvent,
IDockviewPanelProps,
} from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', 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 { PropType, createApp, defineComponent } from 'vue';
import { DockviewVue } from 'dockview-vue';
import { import {
DockviewVue,
DockviewApi, DockviewApi,
DockviewReadyEvent, DockviewReadyEvent,
IDockviewPanelProps, IDockviewPanelProps,
IWatermarkPanelProps, IWatermarkPanelProps,
Orientation, Orientation,
} from 'dockview-core'; } from 'dockview-vue';
const Panel = defineComponent({ const Panel = defineComponent({
name: 'Panel', name: 'Panel',