mirror of
https://github.com/mathuo/dockview
synced 2025-05-07 12:08:26 +00:00
Merge branch 'master' of https://github.com/mathuo/dockview into 582-expose-event-for-moving-tab-within-same-panel
This commit is contained in:
commit
cd59248f34
22
README.md
22
README.md
@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
|
||||
|
||||
## Quick start
|
||||
|
||||
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
|
||||
|
||||
```
|
||||
npm install --save dockview
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
||||
|
@ -2,7 +2,7 @@
|
||||
"packages": [
|
||||
"packages/*"
|
||||
],
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"npmClient": "yarn",
|
||||
"command": {
|
||||
"publish": {
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-angular",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,6 +54,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.14.0"
|
||||
"dockview-core": "^1.14.2"
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews written in TypeScript</p>
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
@ -36,23 +36,3 @@ Please see the website: https://dockview.dev
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
|
||||
|
||||
## Quick start
|
||||
|
||||
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview-core).
|
||||
|
||||
```
|
||||
npm install --save dockview-core
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview-core/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview-core/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-core",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
|
@ -1,7 +1,34 @@
|
||||
import { Overlay } from '../../dnd/overlay';
|
||||
|
||||
const mockGetBoundingClientRect = ({
|
||||
left,
|
||||
top,
|
||||
height,
|
||||
width,
|
||||
}: {
|
||||
left: number;
|
||||
top: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}) => {
|
||||
const result = {
|
||||
left,
|
||||
top,
|
||||
height,
|
||||
width,
|
||||
right: left + width,
|
||||
bottom: top + height,
|
||||
x: left,
|
||||
y: top,
|
||||
};
|
||||
return {
|
||||
...result,
|
||||
toJSON: () => result,
|
||||
};
|
||||
};
|
||||
|
||||
describe('overlay', () => {
|
||||
test('toJSON', () => {
|
||||
test('toJSON, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
@ -23,14 +50,26 @@ describe('overlay', () => {
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return { left: 80, top: 100, width: 40, height: 50 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 20, top: 30, width: 100, height: 100 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
@ -39,7 +78,57 @@ describe('overlay', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('that out-of-bounds dimensions are fixed', () => {
|
||||
test('toJSON, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
right: 10,
|
||||
bottom: 20,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
bottom: -20,
|
||||
right: 0,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('that out-of-bounds dimensions are fixed, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
@ -61,14 +150,26 @@ describe('overlay', () => {
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return { left: 80, top: 100, width: 40, height: 50 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 20, top: 30, width: 100, height: 100 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
top: 70,
|
||||
left: 60,
|
||||
@ -77,7 +178,57 @@ describe('overlay', () => {
|
||||
});
|
||||
});
|
||||
|
||||
test('setBounds', () => {
|
||||
test('that out-of-bounds dimensions are fixed, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 200,
|
||||
width: 100,
|
||||
bottom: -1000,
|
||||
right: -1000,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
jest.spyOn(
|
||||
container.childNodes.item(0) as HTMLElement,
|
||||
'getBoundingClientRect'
|
||||
).mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 80,
|
||||
top: 100,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 20,
|
||||
top: 30,
|
||||
width: 100,
|
||||
height: 100,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds();
|
||||
|
||||
expect(cut.toJSON()).toEqual({
|
||||
bottom: -20,
|
||||
right: 0,
|
||||
width: 40,
|
||||
height: 50,
|
||||
});
|
||||
});
|
||||
|
||||
test('setBounds, top left', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
@ -101,11 +252,21 @@ describe('overlay', () => {
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
|
||||
return { left: 300, top: 400, width: 1000, height: 1000 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 300,
|
||||
top: 400,
|
||||
width: 200,
|
||||
height: 100,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return { left: 0, top: 0, width: 1000, height: 1000 } as any;
|
||||
return mockGetBoundingClientRect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
@ -117,6 +278,56 @@ describe('overlay', () => {
|
||||
expect(element.style.top).toBe('400px');
|
||||
});
|
||||
|
||||
test('setBounds, bottom right', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
||||
document.body.appendChild(container);
|
||||
container.appendChild(content);
|
||||
|
||||
const cut = new Overlay({
|
||||
height: 1000,
|
||||
width: 1000,
|
||||
right: 0,
|
||||
bottom: 0,
|
||||
minimumInViewportWidth: 0,
|
||||
minimumInViewportHeight: 0,
|
||||
container,
|
||||
content,
|
||||
});
|
||||
|
||||
const element: HTMLElement = container.querySelector(
|
||||
'.dv-resize-container'
|
||||
)!;
|
||||
expect(element).toBeTruthy();
|
||||
|
||||
jest.spyOn(element, 'getBoundingClientRect').mockImplementation(() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 500,
|
||||
top: 500,
|
||||
width: 200,
|
||||
height: 100,
|
||||
});
|
||||
});
|
||||
jest.spyOn(container, 'getBoundingClientRect').mockImplementation(
|
||||
() => {
|
||||
return mockGetBoundingClientRect({
|
||||
left: 0,
|
||||
top: 0,
|
||||
width: 1000,
|
||||
height: 1000,
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
cut.setBounds({ height: 100, width: 200, right: 300, bottom: 400 });
|
||||
|
||||
expect(element.style.height).toBe('100px');
|
||||
expect(element.style.width).toBe('200px');
|
||||
expect(element.style.right).toBe('300px');
|
||||
expect(element.style.bottom).toBe('400px');
|
||||
});
|
||||
|
||||
test('that the resize handles are added', () => {
|
||||
const container = document.createElement('div');
|
||||
const content = document.createElement('div');
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
@ -490,14 +490,11 @@ describe('tabsContainer', () => {
|
||||
const eventPreventDefaultSpy = jest.spyOn(event, 'preventDefault');
|
||||
fireEvent(container, event);
|
||||
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(
|
||||
groupPanel,
|
||||
{
|
||||
x: 100,
|
||||
y: 60,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledWith(groupPanel, {
|
||||
x: 100,
|
||||
y: 60,
|
||||
inDragMode: true,
|
||||
});
|
||||
expect(accessor.addFloatingGroup).toHaveBeenCalledTimes(1);
|
||||
expect(eventPreventDefaultSpy).toHaveBeenCalledTimes(1);
|
||||
|
||||
|
@ -10,7 +10,7 @@ import { CompositeDisposable } from '../../lifecycle';
|
||||
import { Emitter } from '../../events';
|
||||
import { DockviewPanel, IDockviewPanel } from '../../dockview/dockviewPanel';
|
||||
import { DockviewGroupPanel } from '../../dockview/dockviewGroupPanel';
|
||||
import { fireEvent } from '@testing-library/dom';
|
||||
import { fireEvent, getByTestId, queryByTestId } from '@testing-library/dom';
|
||||
import { getPanelData } from '../../dnd/dataTransfer';
|
||||
import {
|
||||
GroupDragEvent,
|
||||
@ -5165,6 +5165,60 @@ describe('dockviewComponent', () => {
|
||||
expect(panel4.api.isVisible).toBeTruthy();
|
||||
});
|
||||
|
||||
test('setVisible #1', () => {
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
const api = new DockviewApi(dockview);
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
const panel1 = api.addPanel({
|
||||
id: 'panel1',
|
||||
component: 'default',
|
||||
});
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel2',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1, direction: 'below' },
|
||||
});
|
||||
|
||||
const panel3 = api.addPanel({
|
||||
id: 'panel3',
|
||||
component: 'default',
|
||||
position: { referencePanel: panel1, direction: 'below' },
|
||||
});
|
||||
|
||||
expect(api.groups.length).toBe(3);
|
||||
|
||||
panel1.group.api.setVisible(false);
|
||||
panel2.group.api.setVisible(false);
|
||||
panel3.group.api.setVisible(false);
|
||||
|
||||
expect(panel1.group.api.isVisible).toBeFalsy();
|
||||
expect(panel2.group.api.isVisible).toBeFalsy();
|
||||
expect(panel3.group.api.isVisible).toBeFalsy();
|
||||
|
||||
panel1.group.api.setVisible(true);
|
||||
|
||||
expect(panel1.group.api.isVisible).toBeTruthy();
|
||||
expect(panel2.group.api.isVisible).toBeFalsy();
|
||||
expect(panel3.group.api.isVisible).toBeFalsy();
|
||||
});
|
||||
|
||||
describe('addPanel', () => {
|
||||
test('that can add panel', () => {
|
||||
const container = document.createElement('div');
|
||||
@ -5497,4 +5551,57 @@ describe('dockviewComponent', () => {
|
||||
expect(api.panels.length).toBe(3);
|
||||
expect(api.groups.length).toBe(3);
|
||||
});
|
||||
|
||||
test('that watermark appears when all views are not visible', () => {
|
||||
jest.useFakeTimers();
|
||||
const container = document.createElement('div');
|
||||
|
||||
const dockview = new DockviewComponent({
|
||||
parentElement: container,
|
||||
createComponent(options) {
|
||||
switch (options.name) {
|
||||
case 'default':
|
||||
return new PanelContentPartTest(
|
||||
options.id,
|
||||
options.name
|
||||
);
|
||||
default:
|
||||
throw new Error(`unsupported`);
|
||||
}
|
||||
},
|
||||
});
|
||||
const api = new DockviewApi(dockview);
|
||||
|
||||
dockview.layout(1000, 1000);
|
||||
|
||||
const panel1 = api.addPanel({
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
});
|
||||
const panel2 = api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
position: {
|
||||
direction: 'right',
|
||||
},
|
||||
});
|
||||
|
||||
let query = queryByTestId(container, 'watermark-component');
|
||||
expect(query).toBeFalsy();
|
||||
|
||||
panel1.group.api.setVisible(false);
|
||||
jest.runAllTicks(); // visibility events check fires on microtask-queue
|
||||
query = queryByTestId(container, 'watermark-component');
|
||||
expect(query).toBeFalsy();
|
||||
|
||||
panel2.group.api.setVisible(false);
|
||||
jest.runAllTicks(); // visibility events check fires on microtask-queue
|
||||
query = queryByTestId(container, 'watermark-component');
|
||||
expect(query).toBeTruthy();
|
||||
|
||||
panel1.group.api.setVisible(true);
|
||||
jest.runAllTicks(); // visibility events check fires on microtask-queue
|
||||
query = queryByTestId(container, 'watermark-component');
|
||||
expect(query).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
@ -17,13 +17,9 @@ class TestPanel implements IGridPanelView {
|
||||
_onDidChange = new Emitter<IViewSize | undefined>();
|
||||
readonly onDidChange = this._onDidChange.event;
|
||||
|
||||
get isActive(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
get params(): Parameters {
|
||||
return {};
|
||||
}
|
||||
isVisible: boolean = true;
|
||||
isActive: boolean = true;
|
||||
params: Parameters = {};
|
||||
|
||||
constructor(
|
||||
public readonly id: string,
|
||||
|
@ -5,6 +5,7 @@ import {
|
||||
IGridView,
|
||||
IViewSize,
|
||||
SerializedGridview,
|
||||
getGridLocation,
|
||||
orthogonal,
|
||||
} from '../../gridview/gridview';
|
||||
import { Orientation, Sizing } from '../../splitview/splitview';
|
||||
@ -18,7 +19,7 @@ class MockGridview implements IGridView {
|
||||
IViewSize | undefined
|
||||
>().event;
|
||||
element: HTMLElement = document.createElement('div');
|
||||
|
||||
isVisible: boolean = true;
|
||||
width: number = 0;
|
||||
height: number = 0;
|
||||
|
||||
@ -1105,4 +1106,102 @@ describe('gridview', () => {
|
||||
|
||||
expect(gridview.hasMaximizedView()).toBeFalsy();
|
||||
});
|
||||
|
||||
test('visibility check', () => {
|
||||
const gridview = new Gridview(
|
||||
true,
|
||||
{ separatorBorder: '' },
|
||||
Orientation.HORIZONTAL
|
||||
);
|
||||
gridview.layout(1000, 1000);
|
||||
|
||||
const view1 = new MockGridview('1');
|
||||
const view2 = new MockGridview('2');
|
||||
const view3 = new MockGridview('3');
|
||||
const view4 = new MockGridview('4');
|
||||
const view5 = new MockGridview('5');
|
||||
const view6 = new MockGridview('6');
|
||||
|
||||
gridview.addView(view1, Sizing.Distribute, [0]);
|
||||
gridview.addView(view2, Sizing.Distribute, [1]);
|
||||
gridview.addView(view3, Sizing.Distribute, [1, 1]);
|
||||
gridview.addView(view4, Sizing.Distribute, [1, 1, 0]);
|
||||
gridview.addView(view5, Sizing.Distribute, [1, 1, 0, 0]);
|
||||
gridview.addView(view6, Sizing.Distribute, [1, 1, 0, 0, 0]);
|
||||
|
||||
/**
|
||||
* _____________________________________________
|
||||
* | | |
|
||||
* | | 2 |
|
||||
* | | |
|
||||
* | 1 |_______________________|
|
||||
* | | | 4 |
|
||||
* | | 3 |_____________|
|
||||
* | | | 5 | 6 |
|
||||
* |_____________________|_________|______|______|
|
||||
*/
|
||||
|
||||
function assertVisibility(visibility: boolean[]) {
|
||||
expect(gridview.isViewVisible(getGridLocation(view1.element))).toBe(
|
||||
visibility[0]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view2.element))).toBe(
|
||||
visibility[1]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view3.element))).toBe(
|
||||
visibility[2]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view4.element))).toBe(
|
||||
visibility[3]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view5.element))).toBe(
|
||||
visibility[4]
|
||||
);
|
||||
expect(gridview.isViewVisible(getGridLocation(view6.element))).toBe(
|
||||
visibility[5]
|
||||
);
|
||||
}
|
||||
|
||||
// hide each view one by one
|
||||
|
||||
assertVisibility([true, true, true, true, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view5.element), false);
|
||||
assertVisibility([true, true, true, true, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view4.element), false);
|
||||
assertVisibility([true, true, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view1.element), false);
|
||||
assertVisibility([false, true, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view2.element), false);
|
||||
assertVisibility([false, false, true, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view3.element), false);
|
||||
assertVisibility([false, false, false, false, false, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view6.element), false);
|
||||
assertVisibility([false, false, false, false, false, false]);
|
||||
|
||||
// un-hide each view one by one
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view1.element), true);
|
||||
assertVisibility([true, false, false, false, false, false]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view5.element), true);
|
||||
assertVisibility([true, false, false, false, true, false]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view6.element), true);
|
||||
assertVisibility([true, false, false, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view2.element), true);
|
||||
assertVisibility([true, true, false, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view3.element), true);
|
||||
assertVisibility([true, true, true, false, true, true]);
|
||||
|
||||
gridview.setViewVisible(getGridLocation(view4.element), true);
|
||||
assertVisibility([true, true, true, true, true, true]);
|
||||
});
|
||||
});
|
||||
|
@ -8,10 +8,8 @@ describe('math', () => {
|
||||
expect(clamp(55, 40, 50)).toBe(50);
|
||||
});
|
||||
|
||||
it('should throw an error if min > max', () => {
|
||||
expect(() => clamp(55, 50, 40)).toThrow(
|
||||
'50 > 40 is an invalid condition'
|
||||
);
|
||||
it('if min > max return min', () => {
|
||||
expect(clamp(55, 50, 40)).toBe(50);
|
||||
});
|
||||
});
|
||||
|
||||
|
@ -1,4 +1,5 @@
|
||||
import {
|
||||
FloatingGroupOptions,
|
||||
IDockviewComponent,
|
||||
MovePanelEvent,
|
||||
SerializedDockview,
|
||||
@ -43,7 +44,7 @@ import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from '../dockview/components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
import { AnchoredBox, Box } from '../types';
|
||||
import {
|
||||
DockviewDidDropEvent,
|
||||
DockviewWillDropEvent,
|
||||
@ -805,9 +806,9 @@ export class DockviewApi implements CommonApi<SerializedDockview> {
|
||||
*/
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x: number; y: number }
|
||||
options?: FloatingGroupOptions
|
||||
): void {
|
||||
return this.component.addFloatingGroup(item, coord);
|
||||
return this.component.addFloatingGroup(item, options);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1,3 +1,3 @@
|
||||
export const DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE = 100;
|
||||
|
||||
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100 };
|
||||
export const DEFAULT_FLOATING_GROUP_POSITION = { left: 100, top: 100, width: 300, height: 300 };
|
||||
|
@ -11,7 +11,7 @@ import {
|
||||
} from '../events';
|
||||
import { CompositeDisposable, MutableDisposable } from '../lifecycle';
|
||||
import { clamp } from '../math';
|
||||
import { Box } from '../types';
|
||||
import { AnchoredBox } from '../types';
|
||||
|
||||
const bringElementToFront = (() => {
|
||||
let previous: HTMLElement | null = null;
|
||||
@ -40,6 +40,9 @@ export class Overlay extends CompositeDisposable {
|
||||
private static MINIMUM_HEIGHT = 20;
|
||||
private static MINIMUM_WIDTH = 20;
|
||||
|
||||
private verticalAlignment: 'top' | 'bottom' | undefined;
|
||||
private horiziontalAlignment: 'left' | 'right' | undefined;
|
||||
|
||||
set minimumInViewportWidth(value: number | undefined) {
|
||||
this.options.minimumInViewportWidth = value;
|
||||
}
|
||||
@ -49,7 +52,7 @@ export class Overlay extends CompositeDisposable {
|
||||
}
|
||||
|
||||
constructor(
|
||||
private readonly options: Box & {
|
||||
private readonly options: AnchoredBox & {
|
||||
container: HTMLElement;
|
||||
content: HTMLElement;
|
||||
minimumInViewportWidth?: number;
|
||||
@ -78,23 +81,39 @@ export class Overlay extends CompositeDisposable {
|
||||
this.setBounds({
|
||||
height: this.options.height,
|
||||
width: this.options.width,
|
||||
top: this.options.top,
|
||||
left: this.options.left,
|
||||
...('top' in this.options && { top: this.options.top }),
|
||||
...('bottom' in this.options && { bottom: this.options.bottom }),
|
||||
...('left' in this.options && { left: this.options.left }),
|
||||
...('right' in this.options && { right: this.options.right }),
|
||||
});
|
||||
}
|
||||
|
||||
setBounds(bounds: Partial<Box> = {}): void {
|
||||
setBounds(bounds: Partial<AnchoredBox> = {}): void {
|
||||
if (typeof bounds.height === 'number') {
|
||||
this._element.style.height = `${bounds.height}px`;
|
||||
}
|
||||
if (typeof bounds.width === 'number') {
|
||||
this._element.style.width = `${bounds.width}px`;
|
||||
}
|
||||
if (typeof bounds.top === 'number') {
|
||||
if ('top' in bounds && typeof bounds.top === 'number') {
|
||||
this._element.style.top = `${bounds.top}px`;
|
||||
this._element.style.bottom = 'auto';
|
||||
this.verticalAlignment = 'top';
|
||||
}
|
||||
if (typeof bounds.left === 'number') {
|
||||
if ('bottom' in bounds && typeof bounds.bottom === 'number') {
|
||||
this._element.style.bottom = `${bounds.bottom}px`;
|
||||
this._element.style.top = 'auto';
|
||||
this.verticalAlignment = 'bottom';
|
||||
}
|
||||
if ('left' in bounds && typeof bounds.left === 'number') {
|
||||
this._element.style.left = `${bounds.left}px`;
|
||||
this._element.style.right = 'auto';
|
||||
this.horiziontalAlignment = 'left';
|
||||
}
|
||||
if ('right' in bounds && typeof bounds.right === 'number') {
|
||||
this._element.style.right = `${bounds.right}px`;
|
||||
this._element.style.left = 'auto';
|
||||
this.horiziontalAlignment = 'right';
|
||||
}
|
||||
|
||||
const containerRect = this.options.container.getBoundingClientRect();
|
||||
@ -106,39 +125,77 @@ export class Overlay extends CompositeDisposable {
|
||||
const xOffset = Math.max(0, this.getMinimumWidth(overlayRect.width));
|
||||
|
||||
// a minimum height of minimumViewportHeight must be inside the viewport
|
||||
const yOffset =
|
||||
typeof this.options.minimumInViewportHeight === 'number'
|
||||
? Math.max(0, this.getMinimumHeight(overlayRect.height))
|
||||
: 0;
|
||||
const yOffset = Math.max(0, this.getMinimumHeight(overlayRect.height));
|
||||
|
||||
const left = clamp(
|
||||
overlayRect.left - containerRect.left,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
if (this.verticalAlignment === 'top') {
|
||||
const top = clamp(
|
||||
overlayRect.top - containerRect.top,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
this._element.style.top = `${top}px`;
|
||||
this._element.style.bottom = 'auto';
|
||||
}
|
||||
|
||||
const top = clamp(
|
||||
overlayRect.top - containerRect.top,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
if (this.verticalAlignment === 'bottom') {
|
||||
const bottom = clamp(
|
||||
containerRect.bottom - overlayRect.bottom,
|
||||
-yOffset,
|
||||
Math.max(0, containerRect.height - overlayRect.height + yOffset)
|
||||
);
|
||||
this._element.style.bottom = `${bottom}px`;
|
||||
this._element.style.top = 'auto';
|
||||
}
|
||||
|
||||
this._element.style.left = `${left}px`;
|
||||
this._element.style.top = `${top}px`;
|
||||
if (this.horiziontalAlignment === 'left') {
|
||||
const left = clamp(
|
||||
overlayRect.left - containerRect.left,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
this._element.style.left = `${left}px`;
|
||||
this._element.style.right = 'auto';
|
||||
}
|
||||
|
||||
if (this.horiziontalAlignment === 'right') {
|
||||
const right = clamp(
|
||||
containerRect.right - overlayRect.right,
|
||||
-xOffset,
|
||||
Math.max(0, containerRect.width - overlayRect.width + xOffset)
|
||||
);
|
||||
this._element.style.right = `${right}px`;
|
||||
this._element.style.left = 'auto';
|
||||
}
|
||||
|
||||
this._onDidChange.fire();
|
||||
}
|
||||
|
||||
toJSON(): Box {
|
||||
toJSON(): AnchoredBox {
|
||||
const container = this.options.container.getBoundingClientRect();
|
||||
const element = this._element.getBoundingClientRect();
|
||||
|
||||
return {
|
||||
top: element.top - container.top,
|
||||
left: element.left - container.left,
|
||||
width: element.width,
|
||||
height: element.height,
|
||||
};
|
||||
const result: any = {};
|
||||
|
||||
if (this.verticalAlignment === 'top') {
|
||||
result.top = parseFloat(this._element.style.top);
|
||||
} else if (this.verticalAlignment === 'bottom') {
|
||||
result.bottom = parseFloat(this._element.style.bottom);
|
||||
} else {
|
||||
result.top = element.top - container.top;
|
||||
}
|
||||
|
||||
if (this.horiziontalAlignment === 'left') {
|
||||
result.left = parseFloat(this._element.style.left);
|
||||
} else if (this.horiziontalAlignment === 'right') {
|
||||
result.right = parseFloat(this._element.style.right);
|
||||
} else {
|
||||
result.left = element.left - container.left;
|
||||
}
|
||||
|
||||
result.width = element.width;
|
||||
result.height = element.height;
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
setupDrag(
|
||||
@ -193,18 +250,7 @@ export class Overlay extends CompositeDisposable {
|
||||
);
|
||||
const yOffset = Math.max(
|
||||
0,
|
||||
this.options.minimumInViewportHeight
|
||||
? this.getMinimumHeight(overlayRect.height)
|
||||
: 0
|
||||
);
|
||||
|
||||
const left = clamp(
|
||||
x - offset.x,
|
||||
-xOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.width - overlayRect.width + xOffset
|
||||
)
|
||||
this.getMinimumHeight(overlayRect.height)
|
||||
);
|
||||
|
||||
const top = clamp(
|
||||
@ -216,7 +262,53 @@ export class Overlay extends CompositeDisposable {
|
||||
)
|
||||
);
|
||||
|
||||
this.setBounds({ top, left });
|
||||
const bottom = clamp(
|
||||
offset.y -
|
||||
y +
|
||||
containerRect.height -
|
||||
overlayRect.height,
|
||||
-yOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.height - overlayRect.height + yOffset
|
||||
)
|
||||
);
|
||||
|
||||
const left = clamp(
|
||||
x - offset.x,
|
||||
-xOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.width - overlayRect.width + xOffset
|
||||
)
|
||||
);
|
||||
|
||||
const right = clamp(
|
||||
offset.x - x + containerRect.width - overlayRect.width,
|
||||
-xOffset,
|
||||
Math.max(
|
||||
0,
|
||||
containerRect.width - overlayRect.width + xOffset
|
||||
)
|
||||
);
|
||||
|
||||
const bounds: any = {};
|
||||
|
||||
// Anchor to top or to bottom depending on which one is closer
|
||||
if (top <= bottom) {
|
||||
bounds.top = top;
|
||||
} else {
|
||||
bounds.bottom = bottom;
|
||||
}
|
||||
|
||||
// Anchor to left or to right depending on which one is closer
|
||||
if (left <= right) {
|
||||
bounds.left = left;
|
||||
} else {
|
||||
bounds.right = right;
|
||||
}
|
||||
|
||||
this.setBounds(bounds);
|
||||
}),
|
||||
addDisposableWindowListener(window, 'mouseup', () => {
|
||||
toggleClass(
|
||||
@ -342,8 +434,10 @@ export class Overlay extends CompositeDisposable {
|
||||
}
|
||||
|
||||
let top: number | undefined = undefined;
|
||||
let bottom: number | undefined = undefined;
|
||||
let height: number | undefined = undefined;
|
||||
let left: number | undefined = undefined;
|
||||
let right: number | undefined = undefined;
|
||||
let width: number | undefined = undefined;
|
||||
|
||||
const moveTop = () => {
|
||||
@ -363,10 +457,13 @@ export class Overlay extends CompositeDisposable {
|
||||
Overlay.MINIMUM_HEIGHT
|
||||
)
|
||||
);
|
||||
|
||||
height =
|
||||
startPosition!.originalY +
|
||||
startPosition!.originalHeight -
|
||||
top;
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveBottom = () => {
|
||||
@ -384,6 +481,8 @@ export class Overlay extends CompositeDisposable {
|
||||
: Overlay.MINIMUM_HEIGHT,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
|
||||
bottom = containerRect.height - top - height;
|
||||
};
|
||||
|
||||
const moveLeft = () => {
|
||||
@ -406,6 +505,8 @@ export class Overlay extends CompositeDisposable {
|
||||
startPosition!.originalX +
|
||||
startPosition!.originalWidth -
|
||||
left;
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
|
||||
const moveRight = () => {
|
||||
@ -423,6 +524,8 @@ export class Overlay extends CompositeDisposable {
|
||||
: Overlay.MINIMUM_WIDTH,
|
||||
Number.MAX_VALUE
|
||||
);
|
||||
|
||||
right = containerRect.width - left - width;
|
||||
};
|
||||
|
||||
switch (direction) {
|
||||
@ -456,7 +559,26 @@ export class Overlay extends CompositeDisposable {
|
||||
break;
|
||||
}
|
||||
|
||||
this.setBounds({ height, width, top, left });
|
||||
const bounds: any = {};
|
||||
|
||||
// Anchor to top or to bottom depending on which one is closer
|
||||
if (top! <= bottom!) {
|
||||
bounds.top = top;
|
||||
} else {
|
||||
bounds.bottom = bottom;
|
||||
}
|
||||
|
||||
// Anchor to left or to right depending on which one is closer
|
||||
if (left! <= right!) {
|
||||
bounds.left = left;
|
||||
} else {
|
||||
bounds.right = right;
|
||||
}
|
||||
|
||||
bounds.height = height;
|
||||
bounds.width = width;
|
||||
|
||||
this.setBounds(bounds);
|
||||
}),
|
||||
{
|
||||
dispose: () => {
|
||||
@ -485,7 +607,7 @@ export class Overlay extends CompositeDisposable {
|
||||
if (typeof this.options.minimumInViewportHeight === 'number') {
|
||||
return height - this.options.minimumInViewportHeight;
|
||||
}
|
||||
return height;
|
||||
return 0;
|
||||
}
|
||||
|
||||
override dispose(): void {
|
||||
|
@ -1,16 +1,13 @@
|
||||
import { CompositeDisposable } from '../../../lifecycle';
|
||||
import { ITabRenderer, GroupPanelPartInitParameters } from '../../types';
|
||||
import { addDisposableListener } from '../../../events';
|
||||
import { PanelUpdateEvent } from '../../../panel/types';
|
||||
import { DockviewGroupPanel } from '../../dockviewGroupPanel';
|
||||
import { createCloseButton } from '../../../svg';
|
||||
|
||||
export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
private _element: HTMLElement;
|
||||
private _content: HTMLElement;
|
||||
private action: HTMLElement;
|
||||
//
|
||||
private params: GroupPanelPartInitParameters = {} as any;
|
||||
private _title: string | undefined;
|
||||
|
||||
get element(): HTMLElement {
|
||||
return this._element;
|
||||
@ -21,7 +18,7 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
|
||||
this._element = document.createElement('div');
|
||||
this._element.className = 'dv-default-tab';
|
||||
//
|
||||
|
||||
this._content = document.createElement('div');
|
||||
this._content.className = 'dv-default-tab-content';
|
||||
|
||||
@ -29,10 +26,9 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
this.action.className = 'dv-default-tab-action';
|
||||
this.action.appendChild(createCloseButton());
|
||||
|
||||
//
|
||||
this._element.appendChild(this._content);
|
||||
this._element.appendChild(this.action);
|
||||
//
|
||||
|
||||
this.addDisposables(
|
||||
addDisposableListener(this.action, 'mousedown', (ev) => {
|
||||
ev.preventDefault();
|
||||
@ -42,40 +38,33 @@ export class DefaultTab extends CompositeDisposable implements ITabRenderer {
|
||||
this.render();
|
||||
}
|
||||
|
||||
public update(event: PanelUpdateEvent): void {
|
||||
this.params = { ...this.params, ...event.params };
|
||||
init(params: GroupPanelPartInitParameters): void {
|
||||
this._title = params.title;
|
||||
|
||||
this.addDisposables(
|
||||
params.api.onDidTitleChange((event) => {
|
||||
this._title = event.title;
|
||||
this.render();
|
||||
}),
|
||||
addDisposableListener(this.action, 'mousedown', (ev) => {
|
||||
ev.preventDefault();
|
||||
}),
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
if (ev.defaultPrevented) {
|
||||
return;
|
||||
}
|
||||
|
||||
ev.preventDefault();
|
||||
params.api.close();
|
||||
})
|
||||
);
|
||||
|
||||
this.render();
|
||||
}
|
||||
|
||||
focus(): void {
|
||||
//noop
|
||||
}
|
||||
|
||||
public init(params: GroupPanelPartInitParameters): void {
|
||||
this.params = params;
|
||||
this._content.textContent = params.title;
|
||||
|
||||
addDisposableListener(this.action, 'click', (ev) => {
|
||||
ev.preventDefault(); //
|
||||
this.params.api.close();
|
||||
});
|
||||
}
|
||||
|
||||
onGroupChange(_group: DockviewGroupPanel): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
onPanelVisibleChange(_isPanelVisible: boolean): void {
|
||||
this.render();
|
||||
}
|
||||
|
||||
public layout(_width: number, _height: number): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
private render(): void {
|
||||
if (this._content.textContent !== this.params.title) {
|
||||
this._content.textContent = this.params.title;
|
||||
if (this._content.textContent !== this._title) {
|
||||
this._content.textContent = this._title ?? '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -270,14 +270,11 @@ export class TabsContainer
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(
|
||||
this.group,
|
||||
{
|
||||
x: left - rootLeft + 20,
|
||||
y: top - rootTop + 20,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
this.accessor.addFloatingGroup(this.group, {
|
||||
x: left - rootLeft + 20,
|
||||
y: top - rootTop + 20,
|
||||
inDragMode: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
),
|
||||
@ -380,14 +377,11 @@ export class TabsContainer
|
||||
const { top: rootTop, left: rootLeft } =
|
||||
this.accessor.element.getBoundingClientRect();
|
||||
|
||||
this.accessor.addFloatingGroup(
|
||||
panel as DockviewPanel,
|
||||
{
|
||||
x: left - rootLeft,
|
||||
y: top - rootTop,
|
||||
},
|
||||
{ inDragMode: true }
|
||||
);
|
||||
this.accessor.addFloatingGroup(panel as DockviewPanel, {
|
||||
x: left - rootLeft,
|
||||
y: top - rootTop,
|
||||
inDragMode: true,
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -19,11 +19,15 @@
|
||||
&.horizontal {
|
||||
> .view-container > .view {
|
||||
&:not(:last-child) {
|
||||
border-right: var(--dv-group-gap-size) solid transparent;
|
||||
.groupview {
|
||||
border-right: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-left: var(--dv-group-gap-size) solid transparent;
|
||||
.groupview {
|
||||
border-left: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -31,11 +35,16 @@
|
||||
&.vertical {
|
||||
> .view-container > .view {
|
||||
&:not(:last-child) {
|
||||
border-bottom: var(--dv-group-gap-size) solid transparent;
|
||||
.groupview {
|
||||
border-bottom: var(--dv-group-gap-size) solid
|
||||
transparent;
|
||||
}
|
||||
}
|
||||
|
||||
&:not(:first-child) {
|
||||
border-top: var(--dv-group-gap-size) solid transparent;
|
||||
.groupview {
|
||||
border-top: var(--dv-group-gap-size) solid transparent;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -51,13 +51,13 @@ import { DockviewPanelModel } from './dockviewPanelModel';
|
||||
import { getPanelData } from '../dnd/dataTransfer';
|
||||
import { Parameters } from '../panel/types';
|
||||
import { Overlay } from '../dnd/overlay';
|
||||
import { toggleClass, watchElementResize } from '../dom';
|
||||
import { addTestId, toggleClass, watchElementResize } from '../dom';
|
||||
import { DockviewFloatingGroupPanel } from './dockviewFloatingGroupPanel';
|
||||
import {
|
||||
GroupDragEvent,
|
||||
TabDragEvent,
|
||||
} from './components/titlebar/tabsContainer';
|
||||
import { Box } from '../types';
|
||||
import { AnchoredBox, Box } from '../types';
|
||||
import {
|
||||
DEFAULT_FLOATING_GROUP_OVERFLOW_SIZE,
|
||||
DEFAULT_FLOATING_GROUP_POSITION,
|
||||
@ -126,7 +126,7 @@ export interface PanelReference {
|
||||
|
||||
export interface SerializedFloatingGroup {
|
||||
data: GroupPanelViewState;
|
||||
position: Box;
|
||||
position: AnchoredBox;
|
||||
}
|
||||
|
||||
export interface SerializedPopoutGroup {
|
||||
@ -170,6 +170,17 @@ type MoveGroupOrPanelOptions = {
|
||||
};
|
||||
};
|
||||
|
||||
export interface FloatingGroupOptions {
|
||||
x?: number;
|
||||
y?: number;
|
||||
height?: number;
|
||||
width?: number;
|
||||
position?: AnchoredBox;
|
||||
skipRemoveGroup?: boolean;
|
||||
inDragMode?: boolean;
|
||||
skipActiveGroup?: boolean;
|
||||
}
|
||||
|
||||
export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
readonly activePanel: IDockviewPanel | undefined;
|
||||
readonly totalPanels: number;
|
||||
@ -214,7 +225,7 @@ export interface IDockviewComponent extends IBaseGrid<DockviewGroupPanel> {
|
||||
//
|
||||
addFloatingGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x: number; y: number }
|
||||
options?: FloatingGroupOptions
|
||||
): void;
|
||||
addPopoutGroup(
|
||||
item: IDockviewPanel | DockviewGroupPanel,
|
||||
@ -375,6 +386,9 @@ export class DockviewComponent
|
||||
this._onDidRemoveGroup,
|
||||
this._onDidActiveGroupChange,
|
||||
this._onUnhandledDragOverEvent,
|
||||
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
|
||||
this.updateWatermark();
|
||||
}),
|
||||
this.onDidAdd((event) => {
|
||||
if (!this._moving) {
|
||||
this._onDidAddGroup.fire(event);
|
||||
@ -754,12 +768,7 @@ export class DockviewComponent
|
||||
|
||||
addFloatingGroup(
|
||||
item: DockviewPanel | DockviewGroupPanel,
|
||||
coord?: { x?: number; y?: number; height?: number; width?: number },
|
||||
options?: {
|
||||
skipRemoveGroup?: boolean;
|
||||
inDragMode: boolean;
|
||||
skipActiveGroup?: boolean;
|
||||
}
|
||||
options?: FloatingGroupOptions
|
||||
): void {
|
||||
let group: DockviewGroupPanel;
|
||||
|
||||
@ -820,22 +829,62 @@ export class DockviewComponent
|
||||
|
||||
group.model.location = { type: 'floating' };
|
||||
|
||||
const overlayLeft =
|
||||
typeof coord?.x === 'number'
|
||||
? Math.max(coord.x, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.left;
|
||||
const overlayTop =
|
||||
typeof coord?.y === 'number'
|
||||
? Math.max(coord.y, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.top;
|
||||
function getAnchoredBox(): AnchoredBox {
|
||||
if (options?.position) {
|
||||
const result: any = {};
|
||||
if ('left' in options.position) {
|
||||
result.left = Math.max(options.position.left, 0);
|
||||
} else if ('right' in options.position) {
|
||||
result.right = Math.max(options.position.right, 0);
|
||||
} else {
|
||||
result.left = DEFAULT_FLOATING_GROUP_POSITION.left;
|
||||
}
|
||||
if ('top' in options.position) {
|
||||
result.top = Math.max(options.position.top, 0);
|
||||
} else if ('bottom' in options.position) {
|
||||
result.bottom = Math.max(options.position.bottom, 0);
|
||||
} else {
|
||||
result.top = DEFAULT_FLOATING_GROUP_POSITION.top;
|
||||
}
|
||||
if ('width' in options.position) {
|
||||
result.width = Math.max(options.position.width, 0);
|
||||
} else {
|
||||
result.width = DEFAULT_FLOATING_GROUP_POSITION.width;
|
||||
}
|
||||
if ('height' in options.position) {
|
||||
result.height = Math.max(options.position.height, 0);
|
||||
} else {
|
||||
result.height = DEFAULT_FLOATING_GROUP_POSITION.height;
|
||||
}
|
||||
return result as AnchoredBox;
|
||||
}
|
||||
|
||||
return {
|
||||
left:
|
||||
typeof options?.x === 'number'
|
||||
? Math.max(options.x, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.left,
|
||||
top:
|
||||
typeof options?.y === 'number'
|
||||
? Math.max(options.y, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.top,
|
||||
width:
|
||||
typeof options?.width === 'number'
|
||||
? Math.max(options.width, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.width,
|
||||
height:
|
||||
typeof options?.height === 'number'
|
||||
? Math.max(options.height, 0)
|
||||
: DEFAULT_FLOATING_GROUP_POSITION.height,
|
||||
};
|
||||
}
|
||||
|
||||
const anchoredBox = getAnchoredBox();
|
||||
|
||||
const overlay = new Overlay({
|
||||
container: this.gridview.element,
|
||||
content: group.element,
|
||||
height: coord?.height ?? 300,
|
||||
width: coord?.width ?? 300,
|
||||
left: overlayLeft,
|
||||
top: overlayTop,
|
||||
...anchoredBox,
|
||||
minimumInViewportWidth:
|
||||
this.options.floatingGroupBounds === 'boundedWithinViewport'
|
||||
? undefined
|
||||
@ -975,7 +1024,7 @@ export class DockviewComponent
|
||||
this.options.floatingGroupBounds?.minimumWidthWithinViewport;
|
||||
}
|
||||
|
||||
group.overlay.setBounds({});
|
||||
group.overlay.setBounds();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1198,16 +1247,11 @@ export class DockviewComponent
|
||||
|
||||
const group = createGroupFromSerializedState(data);
|
||||
|
||||
this.addFloatingGroup(
|
||||
group,
|
||||
{
|
||||
x: position.left,
|
||||
y: position.top,
|
||||
height: position.height,
|
||||
width: position.width,
|
||||
},
|
||||
{ skipRemoveGroup: true, inDragMode: false }
|
||||
);
|
||||
this.addFloatingGroup(group, {
|
||||
position: position,
|
||||
skipRemoveGroup: true,
|
||||
inDragMode: false,
|
||||
});
|
||||
}
|
||||
|
||||
const serializedPopoutGroups = data.popoutGroups ?? [];
|
||||
@ -1390,7 +1434,8 @@ export class DockviewComponent
|
||||
? options.floating
|
||||
: {};
|
||||
|
||||
this.addFloatingGroup(group, o, {
|
||||
this.addFloatingGroup(group, {
|
||||
...o,
|
||||
inDragMode: false,
|
||||
skipRemoveGroup: true,
|
||||
skipActiveGroup: true,
|
||||
@ -1443,7 +1488,8 @@ export class DockviewComponent
|
||||
? options.floating
|
||||
: {};
|
||||
|
||||
this.addFloatingGroup(group, coordinates, {
|
||||
this.addFloatingGroup(group, {
|
||||
...coordinates,
|
||||
inDragMode: false,
|
||||
skipRemoveGroup: true,
|
||||
skipActiveGroup: true,
|
||||
@ -1525,6 +1571,7 @@ export class DockviewComponent
|
||||
|
||||
const watermarkContainer = document.createElement('div');
|
||||
watermarkContainer.className = 'dv-watermark-container';
|
||||
addTestId(watermarkContainer, 'watermark-component');
|
||||
watermarkContainer.appendChild(this.watermark.element);
|
||||
|
||||
this.gridview.element.appendChild(watermarkContainer);
|
||||
|
@ -1,37 +1,22 @@
|
||||
import { Overlay } from '../dnd/overlay';
|
||||
import { CompositeDisposable } from '../lifecycle';
|
||||
import { AnchoredBox } from '../types';
|
||||
import { DockviewGroupPanel, IDockviewGroupPanel } from './dockviewGroupPanel';
|
||||
|
||||
export interface IDockviewFloatingGroupPanel {
|
||||
readonly group: IDockviewGroupPanel;
|
||||
position(
|
||||
bounds: Partial<{
|
||||
top: number;
|
||||
left: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}>
|
||||
): void;
|
||||
position(bounds: Partial<AnchoredBox>): void;
|
||||
}
|
||||
|
||||
export class DockviewFloatingGroupPanel
|
||||
extends CompositeDisposable
|
||||
implements IDockviewFloatingGroupPanel
|
||||
{
|
||||
implements IDockviewFloatingGroupPanel {
|
||||
constructor(readonly group: DockviewGroupPanel, readonly overlay: Overlay) {
|
||||
super();
|
||||
|
||||
this.addDisposables(overlay);
|
||||
}
|
||||
|
||||
position(
|
||||
bounds: Partial<{
|
||||
top: number;
|
||||
left: number;
|
||||
height: number;
|
||||
width: number;
|
||||
}>
|
||||
): void {
|
||||
position(bounds: Partial<AnchoredBox>): void {
|
||||
this.overlay.setBounds(bounds);
|
||||
}
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ import {
|
||||
import { IDockviewPanel } from './dockviewPanel';
|
||||
import { DockviewPanelRenderer } from '../overlayRenderContainer';
|
||||
import { IGroupHeaderProps } from './framework';
|
||||
import { AnchoredBox } from '../types';
|
||||
|
||||
export interface IHeaderActionsRenderer extends IDisposable {
|
||||
readonly element: HTMLElement;
|
||||
@ -37,11 +38,11 @@ export interface DockviewOptions {
|
||||
singleTabMode?: 'fullwidth' | 'default';
|
||||
disableFloatingGroups?: boolean;
|
||||
floatingGroupBounds?:
|
||||
| 'boundedWithinViewport'
|
||||
| {
|
||||
minimumHeightWithinViewport?: number;
|
||||
minimumWidthWithinViewport?: number;
|
||||
};
|
||||
| 'boundedWithinViewport'
|
||||
| {
|
||||
minimumHeightWithinViewport?: number;
|
||||
minimumWidthWithinViewport?: number;
|
||||
};
|
||||
popoutUrl?: string;
|
||||
defaultRenderer?: DockviewPanelRenderer;
|
||||
debug?: boolean;
|
||||
@ -74,7 +75,7 @@ export class DockviewUnhandledDragOverEvent implements DockviewDndOverlayEvent {
|
||||
readonly position: Position,
|
||||
readonly getData: () => PanelTransfer | undefined,
|
||||
readonly group?: DockviewGroupPanel
|
||||
) {}
|
||||
) { }
|
||||
|
||||
accept(): void {
|
||||
this._isAccepted = true;
|
||||
@ -176,13 +177,8 @@ export function isPanelOptionsWithGroup(
|
||||
|
||||
type AddPanelFloatingGroupUnion = {
|
||||
floating:
|
||||
| {
|
||||
height?: number;
|
||||
width?: number;
|
||||
x?: number;
|
||||
y?: number;
|
||||
}
|
||||
| true;
|
||||
| Partial<AnchoredBox>
|
||||
| true;
|
||||
position: never;
|
||||
};
|
||||
|
||||
|
@ -253,3 +253,7 @@ export function isInDocument(element: Element): boolean {
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
export function addTestId(element: HTMLElement, id: string): void {
|
||||
element.setAttribute('data-testid', id);
|
||||
}
|
||||
|
@ -93,6 +93,10 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
readonly onDidLayoutChange: Event<void> =
|
||||
this._bufferOnDidLayoutChange.onEvent;
|
||||
|
||||
private readonly _onDidViewVisibilityChangeMicroTaskQueue = new AsapEvent();
|
||||
readonly onDidViewVisibilityChangeMicroTaskQueue =
|
||||
this._onDidViewVisibilityChangeMicroTaskQueue.onEvent;
|
||||
|
||||
get id(): string {
|
||||
return this._id;
|
||||
}
|
||||
@ -158,6 +162,12 @@ export abstract class BaseGrid<T extends IGridPanelView>
|
||||
this.layout(0, 0, true); // set some elements height/widths
|
||||
|
||||
this.addDisposables(
|
||||
this.gridview.onDidViewVisibilityChange(() =>
|
||||
this._onDidViewVisibilityChangeMicroTaskQueue.fire()
|
||||
),
|
||||
this.onDidViewVisibilityChangeMicroTaskQueue(() => {
|
||||
this.layout(this.width, this.height, true);
|
||||
}),
|
||||
Disposable.from(() => {
|
||||
this.element.parentElement?.removeChild(this.element);
|
||||
}),
|
||||
|
@ -33,9 +33,12 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||
this._onDidChange.event;
|
||||
|
||||
private readonly _onDidVisibilityChange = new Emitter<boolean>();
|
||||
readonly onDidVisibilityChange: Event<boolean> =
|
||||
this._onDidVisibilityChange.event;
|
||||
private readonly _onDidVisibilityChange = new Emitter<{
|
||||
visible: boolean;
|
||||
}>();
|
||||
readonly onDidVisibilityChange: Event<{
|
||||
visible: boolean;
|
||||
}> = this._onDidVisibilityChange.event;
|
||||
|
||||
get width(): number {
|
||||
return this.orientation === Orientation.HORIZONTAL
|
||||
@ -200,10 +203,8 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
this.setupChildrenEvents();
|
||||
}
|
||||
|
||||
setVisible(visible: boolean): void {
|
||||
for (const child of this.children) {
|
||||
child.setVisible(visible);
|
||||
}
|
||||
setVisible(_visible: boolean): void {
|
||||
// noop
|
||||
}
|
||||
|
||||
isChildVisible(index: number): boolean {
|
||||
@ -224,7 +225,9 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
}
|
||||
|
||||
const wereAllChildrenHidden = this.splitview.contentSize === 0;
|
||||
|
||||
this.splitview.setViewVisible(index, visible);
|
||||
// }
|
||||
const areAllChildrenHidden = this.splitview.contentSize === 0;
|
||||
|
||||
// If all children are hidden then the parent should hide the entire splitview
|
||||
@ -233,7 +236,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
(visible && wereAllChildrenHidden) ||
|
||||
(!visible && areAllChildrenHidden)
|
||||
) {
|
||||
this._onDidVisibilityChange.fire(visible);
|
||||
this._onDidVisibilityChange.fire({ visible });
|
||||
}
|
||||
}
|
||||
|
||||
@ -335,7 +338,7 @@ export class BranchNode extends CompositeDisposable implements IView {
|
||||
}),
|
||||
...this.children.map((c, i) => {
|
||||
if (c instanceof BranchNode) {
|
||||
return c.onDidVisibilityChange((visible) => {
|
||||
return c.onDidVisibilityChange(({ visible }) => {
|
||||
this.setChildVisible(i, visible);
|
||||
});
|
||||
}
|
||||
|
@ -171,6 +171,7 @@ export interface IGridView {
|
||||
readonly maximumWidth: number;
|
||||
readonly minimumHeight: number;
|
||||
readonly maximumHeight: number;
|
||||
readonly isVisible: boolean;
|
||||
priority?: LayoutPriority;
|
||||
layout(width: number, height: number): void;
|
||||
toJSON(): object;
|
||||
@ -287,6 +288,9 @@ export class Gridview implements IDisposable {
|
||||
readonly onDidChange: Event<{ size?: number; orthogonalSize?: number }> =
|
||||
this._onDidChange.event;
|
||||
|
||||
private readonly _onDidViewVisibilityChange = new Emitter<void>();
|
||||
readonly onDidViewVisibilityChange = this._onDidViewVisibilityChange.event;
|
||||
|
||||
private readonly _onDidMaximizedNodeChange = new Emitter<void>();
|
||||
readonly onDidMaximizedNodeChange = this._onDidMaximizedNodeChange.event;
|
||||
|
||||
@ -453,6 +457,8 @@ export class Gridview implements IDisposable {
|
||||
this.disposable.dispose();
|
||||
this._onDidChange.dispose();
|
||||
this._onDidMaximizedNodeChange.dispose();
|
||||
this._onDidViewVisibilityChange.dispose();
|
||||
|
||||
this.root.dispose();
|
||||
this._maximizedNode = undefined;
|
||||
this.element.remove();
|
||||
@ -531,12 +537,12 @@ export class Gridview implements IDisposable {
|
||||
children
|
||||
);
|
||||
} else {
|
||||
result = new LeafNode(
|
||||
deserializer.fromJSON(node),
|
||||
orientation,
|
||||
orthogonalSize,
|
||||
node.size
|
||||
);
|
||||
const view = deserializer.fromJSON(node);
|
||||
if (typeof node.visible === 'boolean') {
|
||||
view.setVisible?.(node.visible);
|
||||
}
|
||||
|
||||
result = new LeafNode(view, orientation, orthogonalSize, node.size);
|
||||
}
|
||||
|
||||
return result;
|
||||
@ -723,6 +729,8 @@ export class Gridview implements IDisposable {
|
||||
throw new Error('Invalid from location');
|
||||
}
|
||||
|
||||
this._onDidViewVisibilityChange.fire();
|
||||
|
||||
parent.setChildVisible(index, visible);
|
||||
}
|
||||
|
||||
|
@ -126,6 +126,10 @@ export abstract class GridviewPanel<
|
||||
return this.api.isActive;
|
||||
}
|
||||
|
||||
get isVisible(): boolean {
|
||||
return this.api.isVisible;
|
||||
}
|
||||
|
||||
constructor(
|
||||
id: string,
|
||||
component: string,
|
||||
|
@ -1,6 +1,10 @@
|
||||
export const clamp = (value: number, min: number, max: number): number => {
|
||||
if (min > max) {
|
||||
throw new Error(`${min} > ${max} is an invalid condition`);
|
||||
/**
|
||||
* caveat: an error should be thrown here if this was a proper `clamp` function but we need to handle
|
||||
* cases where `min` > `max` and in those cases return `min`.
|
||||
*/
|
||||
return min;
|
||||
}
|
||||
return Math.min(max, Math.max(value, min));
|
||||
};
|
||||
|
@ -291,12 +291,8 @@ export class Splitview {
|
||||
throw new Error('Index out of bounds');
|
||||
}
|
||||
|
||||
toggleClass(this.container, 'visible', visible);
|
||||
|
||||
const viewItem = this.viewItems[index];
|
||||
|
||||
toggleClass(this.container, 'visible', visible);
|
||||
|
||||
viewItem.setVisible(visible, viewItem.size);
|
||||
|
||||
this.distributeEmptySpace(index);
|
||||
|
@ -8,3 +8,13 @@ export interface Box {
|
||||
height: number;
|
||||
width: number;
|
||||
}
|
||||
|
||||
type TopLeft = { top: number; left: number };
|
||||
type TopRight = { top: number; right: number };
|
||||
type BottomLeft = { bottom: number; left: number };
|
||||
type BottomRight = { bottom: number; right: number };
|
||||
|
||||
type AnchorPosition = TopLeft | TopRight | BottomLeft | BottomRight;
|
||||
type Size = { width: number; height: number };
|
||||
|
||||
export type AnchoredBox = Size & AnchorPosition;
|
||||
|
@ -1,7 +1,7 @@
|
||||
<div align="center">
|
||||
<h1>dockview</h1>
|
||||
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews with ReactJS support written in TypeScript</p>
|
||||
<p>Zero dependency layout manager supporting tabs, groups, grids and splitviews. Supports React, Vue and Vanilla TypeScript</p>
|
||||
|
||||
</div>
|
||||
|
||||
@ -16,6 +16,8 @@
|
||||
|
||||
##
|
||||
|
||||

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

|
||||
|
||||
Please see the website: https://dockview.dev
|
||||
|
||||
## Features
|
||||
@ -34,23 +36,3 @@ Please see the website: https://dockview.dev
|
||||
- Security at mind - verifed publishing and builds through GitHub Actions
|
||||
|
||||
Want to verify our builds? Go [here](https://www.npmjs.com/package/dockview#Provenance).
|
||||
|
||||
## Quick start
|
||||
|
||||
Dockview has a peer dependency on `react >= 16.8.0` and `react-dom >= 16.8.0`. You can install dockview from [npm](https://www.npmjs.com/package/dockview).
|
||||
|
||||
```
|
||||
npm install --save dockview
|
||||
```
|
||||
|
||||
Within your project you must import or reference the stylesheet at `dockview/dist/styles/dockview.css` and attach a theme.
|
||||
|
||||
```css
|
||||
@import '~dockview/dist/styles/dockview.css';
|
||||
```
|
||||
|
||||
You should also attach a dockview theme to an element containing your components. For example:
|
||||
|
||||
```html
|
||||
<body classname="dockview-theme-dark"></body>
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-vue",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -52,6 +52,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview-vue --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.14.0"
|
||||
"dockview-core": "^1.14.2"
|
||||
}
|
||||
}
|
||||
|
@ -70,55 +70,61 @@ PROPERTY_KEYS.forEach((coreOptionKey) => {
|
||||
|
||||
onMounted(() => {
|
||||
if (!el.value) {
|
||||
throw new Error('element is not mounted');
|
||||
throw new Error('dockview-vue: element is not mounted');
|
||||
}
|
||||
|
||||
const inst = getCurrentInstance();
|
||||
|
||||
if(!inst) {
|
||||
throw new Error('dockview-vue: getCurrentInstance() returned null')
|
||||
}
|
||||
|
||||
const frameworkOptions: DockviewFrameworkOptions = {
|
||||
parentElement: el.value,
|
||||
createComponent(options) {
|
||||
const component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
options.name
|
||||
);
|
||||
return new VueRenderer(component!, getCurrentInstance()!);
|
||||
return new VueRenderer(component!, inst);
|
||||
},
|
||||
createTabComponent(options) {
|
||||
let component = findComponent(getCurrentInstance()!, options.name);
|
||||
let component = findComponent(inst, options.name);
|
||||
|
||||
if (!component && props.defaultTabComponent) {
|
||||
component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
props.defaultTabComponent
|
||||
);
|
||||
}
|
||||
|
||||
if (component) {
|
||||
return new VueRenderer(component, getCurrentInstance()!);
|
||||
return new VueRenderer(component, inst);
|
||||
}
|
||||
return undefined;
|
||||
},
|
||||
createWatermarkComponent: props.watermarkComponent
|
||||
? () => {
|
||||
const component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
props.watermarkComponent!
|
||||
);
|
||||
|
||||
return new VueWatermarkRenderer(
|
||||
component!,
|
||||
getCurrentInstance()!
|
||||
inst
|
||||
);
|
||||
}
|
||||
: undefined,
|
||||
createLeftHeaderActionComponent: props.leftHeaderActionsComponent
|
||||
? (group) => {
|
||||
const component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
props.leftHeaderActionsComponent!
|
||||
);
|
||||
return new VueHeaderActionsRenderer(
|
||||
component!,
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
group
|
||||
);
|
||||
}
|
||||
@ -126,12 +132,12 @@ onMounted(() => {
|
||||
createPrefixHeaderActionComponent: props.prefixHeaderActionsComponent
|
||||
? (group) => {
|
||||
const component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
props.prefixHeaderActionsComponent!
|
||||
);
|
||||
return new VueHeaderActionsRenderer(
|
||||
component!,
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
group
|
||||
);
|
||||
}
|
||||
@ -139,12 +145,12 @@ onMounted(() => {
|
||||
createRightHeaderActionComponent: props.rightHeaderActionsComponent
|
||||
? (group) => {
|
||||
const component = findComponent(
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
props.rightHeaderActionsComponent!
|
||||
);
|
||||
return new VueHeaderActionsRenderer(
|
||||
component!,
|
||||
getCurrentInstance()!,
|
||||
inst,
|
||||
group
|
||||
);
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
|
||||
"keywords": [
|
||||
"splitview",
|
||||
@ -54,6 +54,6 @@
|
||||
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
|
||||
},
|
||||
"dependencies": {
|
||||
"dockview-core": "^1.14.0"
|
||||
"dockview-core": "^1.14.2"
|
||||
}
|
||||
}
|
||||
|
@ -2,11 +2,16 @@ import { fireEvent, render, screen } from '@testing-library/react';
|
||||
import { DockviewDefaultTab } from '../../dockview/defaultTab';
|
||||
import React from 'react';
|
||||
import { fromPartial } from '@total-typescript/shoehorn';
|
||||
import { DockviewApi, DockviewPanelApi } from 'dockview-core';
|
||||
import { DockviewApi, DockviewPanelApi, TitleEvent } from 'dockview-core';
|
||||
import { Emitter } from 'dockview-core/dist/cjs/events';
|
||||
import { act } from 'react-dom/test-utils';
|
||||
import { Disposable } from 'dockview-core/dist/cjs/lifecycle';
|
||||
|
||||
describe('defaultTab', () => {
|
||||
test('has close button by default', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({});
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
@ -25,6 +30,7 @@ describe('defaultTab', () => {
|
||||
test('that title is displayed', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
title: 'test_title',
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
@ -43,8 +49,43 @@ describe('defaultTab', () => {
|
||||
).toBe('test_title');
|
||||
});
|
||||
|
||||
test('that title is updated', async () => {
|
||||
const onDidTitleChange = new Emitter<TitleEvent>();
|
||||
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
title: 'test_title',
|
||||
onDidTitleChange: onDidTitleChange.event,
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
render(
|
||||
<DockviewDefaultTab
|
||||
api={api}
|
||||
containerApi={containerApi}
|
||||
params={params}
|
||||
/>
|
||||
);
|
||||
|
||||
let element = await screen.getByTestId('dockview-dv-default-tab');
|
||||
expect(
|
||||
element.querySelector('.dv-default-tab-content')?.textContent
|
||||
).toBe('test_title');
|
||||
|
||||
act(() => {
|
||||
onDidTitleChange.fire({ title: 'test_title_2' });
|
||||
});
|
||||
|
||||
element = await screen.getByTestId('dockview-dv-default-tab');
|
||||
expect(
|
||||
element.querySelector('.dv-default-tab-content')?.textContent
|
||||
).toBe('test_title_2');
|
||||
});
|
||||
|
||||
test('has no close button when hideClose=true', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({});
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
@ -64,6 +105,7 @@ describe('defaultTab', () => {
|
||||
test('that settings closeActionOverride skips api.close()', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
close: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
@ -85,13 +127,14 @@ describe('defaultTab', () => {
|
||||
) as HTMLElement;
|
||||
fireEvent.click(btn);
|
||||
|
||||
expect(closeActionOverride).toBeCalledTimes(1);
|
||||
expect(api.close).toBeCalledTimes(0);
|
||||
expect(closeActionOverride).toHaveBeenCalledTimes(1);
|
||||
expect(api.close).toHaveBeenCalledTimes(0);
|
||||
});
|
||||
|
||||
test('that clicking close calls api.close()', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
close: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
@ -110,11 +153,13 @@ describe('defaultTab', () => {
|
||||
) as HTMLElement;
|
||||
fireEvent.click(btn);
|
||||
|
||||
expect(api.close).toBeCalledTimes(1);
|
||||
expect(api.close).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
|
||||
test('has close button when hideClose=false', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({});
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
|
||||
@ -134,6 +179,7 @@ describe('defaultTab', () => {
|
||||
test('that mouseDown on close button prevents panel becoming active', async () => {
|
||||
const api = fromPartial<DockviewPanelApi>({
|
||||
setActive: jest.fn(),
|
||||
onDidTitleChange: jest.fn().mockImplementation(() => Disposable.NONE),
|
||||
});
|
||||
const containerApi = fromPartial<DockviewApi>({});
|
||||
const params = {};
|
||||
@ -152,9 +198,9 @@ describe('defaultTab', () => {
|
||||
) as HTMLElement;
|
||||
|
||||
fireEvent.mouseDown(btn);
|
||||
expect(api.setActive).toBeCalledTimes(0);
|
||||
expect(api.setActive).toHaveBeenCalledTimes(0);
|
||||
|
||||
fireEvent.click(element);
|
||||
expect(api.setActive).toBeCalledTimes(1);
|
||||
expect(api.setActive).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,6 +1,22 @@
|
||||
import React from 'react';
|
||||
import { CloseButton } from '../svg';
|
||||
import { IDockviewPanelHeaderProps } from 'dockview-core';
|
||||
import { DockviewPanelApi, IDockviewPanelHeaderProps } from 'dockview-core';
|
||||
|
||||
function useTitle(api: DockviewPanelApi): string | undefined {
|
||||
const [title, setTitle] = React.useState<string | undefined>(api.title);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = api.onDidTitleChange((event) => {
|
||||
setTitle(event.title);
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [api]);
|
||||
|
||||
return title;
|
||||
}
|
||||
|
||||
export type IDockviewDefaultTabProps = IDockviewPanelHeaderProps &
|
||||
React.DOMAttributes<HTMLDivElement> & {
|
||||
@ -18,6 +34,8 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
closeActionOverride,
|
||||
...rest
|
||||
}) => {
|
||||
const title = useTitle(api);
|
||||
|
||||
const onClose = React.useCallback(
|
||||
(event: React.MouseEvent<HTMLSpanElement>) => {
|
||||
event.preventDefault();
|
||||
@ -57,7 +75,7 @@ export const DockviewDefaultTab: React.FunctionComponent<
|
||||
onClick={onClick}
|
||||
className="dv-default-tab"
|
||||
>
|
||||
<span className="dv-default-tab-content">{api.title}</span>
|
||||
<span className="dv-default-tab-content">{title}</span>
|
||||
{!hideClose && (
|
||||
<div
|
||||
className="dv-default-tab-action"
|
||||
|
13
packages/docs/blog/2024-05-28-dockview-1.14.1.md
Normal file
13
packages/docs/blog/2024-05-28-dockview-1.14.1.md
Normal 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)
|
15
packages/docs/blog/2024-06-08-dockview-1.14.2.md
Normal file
15
packages/docs/blog/2024-06-08-dockview-1.14.2.md
Normal 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)
|
@ -165,6 +165,6 @@ api.addPanel({
|
||||
api.addPanel({
|
||||
id: 'panel_2',
|
||||
component: 'default',
|
||||
floating: { x: 10, y: 10, width: 300, height: 300 },
|
||||
floating: { left: 10, top: 10, width: 300, height: 300 },
|
||||
});
|
||||
```
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "dockview-docs",
|
||||
"version": "1.14.0",
|
||||
"version": "1.14.2",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"build": "npm run build-templates && docusaurus build",
|
||||
@ -37,7 +37,7 @@
|
||||
"ag-grid-react": "^31.0.2",
|
||||
"axios": "^1.6.3",
|
||||
"clsx": "^2.1.0",
|
||||
"dockview": "^1.14.0",
|
||||
"dockview": "^1.14.2",
|
||||
"prism-react-renderer": "^2.3.1",
|
||||
"react-dnd": "^16.0.1",
|
||||
"react-laag": "^2.0.5",
|
||||
|
@ -77,6 +77,11 @@
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
|
||||
&:disabled {
|
||||
color: gray;
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
span {
|
||||
font-size: 16px;
|
||||
}
|
||||
|
@ -185,6 +185,19 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
|
||||
const [watermark, setWatermark] = React.useState<boolean>(false);
|
||||
|
||||
const [gapCheck, setGapCheck] = React.useState<boolean>(false);
|
||||
|
||||
const css = React.useMemo(() => {
|
||||
if (!gapCheck) {
|
||||
return {};
|
||||
}
|
||||
|
||||
return {
|
||||
'--dv-group-gap-size': '0.5rem',
|
||||
'--demo-border': '5px dashed purple',
|
||||
} as React.CSSProperties;
|
||||
}, [gapCheck]);
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
@ -195,6 +208,7 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
padding: '8px',
|
||||
backgroundColor: 'rgba(0,0,50,0.25)',
|
||||
borderRadius: '8px',
|
||||
...css,
|
||||
}}
|
||||
>
|
||||
<div>
|
||||
@ -217,6 +231,15 @@ const DockviewDemo = (props: { theme?: string }) => {
|
||||
activeGroup={activeGroup}
|
||||
/>
|
||||
)}
|
||||
{/* <div>
|
||||
<button
|
||||
onClick={() => {
|
||||
setGapCheck(!gapCheck);
|
||||
}}
|
||||
>
|
||||
{gapCheck ? 'Disable Gap Check' : 'Enable Gap Check'}
|
||||
</button>
|
||||
</div> */}
|
||||
</div>
|
||||
<div
|
||||
style={{
|
||||
|
@ -24,6 +24,7 @@ export const GridActions = (props: {
|
||||
|
||||
const onSave = () => {
|
||||
if (props.api) {
|
||||
console.log(props.api.toJSON());
|
||||
localStorage.setItem(
|
||||
'dv-demo-state',
|
||||
JSON.stringify(props.api.toJSON())
|
||||
|
@ -1,4 +1,8 @@
|
||||
import { DockviewApi, DockviewGroupLocation } from 'dockview';
|
||||
import {
|
||||
DockviewApi,
|
||||
DockviewGroupLocation,
|
||||
DockviewGroupPanel,
|
||||
} from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
const GroupAction = (props: {
|
||||
@ -12,14 +16,27 @@ const GroupAction = (props: {
|
||||
};
|
||||
|
||||
const isActive = props.activeGroup === props.groupId;
|
||||
const group = React.useMemo(
|
||||
() => props.api.getGroup(props.groupId),
|
||||
[props.api, props.groupId]
|
||||
|
||||
const [group, setGroup] = React.useState<DockviewGroupPanel | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.api.onDidLayoutFromJSON(() => {
|
||||
setGroup(props.api.getGroup(props.groupId));
|
||||
});
|
||||
|
||||
setGroup(props.api.getGroup(props.groupId));
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}, [props.api, props.groupId]);
|
||||
|
||||
const [location, setLocation] =
|
||||
React.useState<DockviewGroupLocation | null>(null);
|
||||
const [isMaximized, setIsMaximized] = React.useState<boolean>(false);
|
||||
const [isVisible, setIsVisible] = React.useState<boolean>(true);
|
||||
|
||||
React.useEffect(() => {
|
||||
if (!group) {
|
||||
@ -35,12 +52,18 @@ const GroupAction = (props: {
|
||||
setIsMaximized(group.api.isMaximized());
|
||||
});
|
||||
|
||||
const disposable3 = group.api.onDidVisibilityChange(() => {
|
||||
setIsVisible(group.api.isVisible);
|
||||
});
|
||||
|
||||
setLocation(group.api.location);
|
||||
setIsMaximized(group.api.isMaximized());
|
||||
setIsVisible(group.api.isVisible);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
disposable2.dispose();
|
||||
disposable3.dispose();
|
||||
};
|
||||
}, [group]);
|
||||
|
||||
@ -65,7 +88,14 @@ const GroupAction = (props: {
|
||||
}
|
||||
onClick={() => {
|
||||
if (group) {
|
||||
props.api.addFloatingGroup(group);
|
||||
props.api.addFloatingGroup(group, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
top: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -107,6 +137,23 @@ const GroupAction = (props: {
|
||||
fullscreen
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="demo-icon-button"
|
||||
onClick={() => {
|
||||
console.log(group);
|
||||
if (group) {
|
||||
if (group.api.isVisible) {
|
||||
group.api.setVisible(false);
|
||||
} else {
|
||||
group.api.setVisible(true);
|
||||
}
|
||||
}
|
||||
}}
|
||||
>
|
||||
<span className="material-symbols-outlined">
|
||||
{isVisible ? 'visibility' : 'visibility_off'}
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="demo-icon-button"
|
||||
onClick={() => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { DockviewApi } from 'dockview';
|
||||
import { DockviewApi, IDockviewPanel } from 'dockview';
|
||||
import * as React from 'react';
|
||||
|
||||
const PanelAction = (props: {
|
||||
panels: string[];
|
||||
@ -9,6 +10,50 @@ const PanelAction = (props: {
|
||||
const onClick = () => {
|
||||
props.api.getPanel(props.panelId)?.focus();
|
||||
};
|
||||
|
||||
React.useEffect(() => {
|
||||
const panel = props.api.getPanel(props.panelId);
|
||||
if (panel) {
|
||||
const disposable = panel.api.onDidVisibilityChange((event) => {
|
||||
setVisible(event.isVisible);
|
||||
});
|
||||
setVisible(panel.api.isVisible);
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
};
|
||||
}
|
||||
}, [props.api, props.panelId]);
|
||||
|
||||
const [panel, setPanel] = React.useState<IDockviewPanel | undefined>(
|
||||
undefined
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const list = [
|
||||
props.api.onDidLayoutFromJSON(() => {
|
||||
setPanel(props.api.getPanel(props.panelId));
|
||||
}),
|
||||
];
|
||||
|
||||
if (panel) {
|
||||
const disposable = panel.api.onDidVisibilityChange((event) => {
|
||||
setVisible(event.isVisible);
|
||||
});
|
||||
setVisible(panel.api.isVisible);
|
||||
|
||||
list.push(disposable);
|
||||
}
|
||||
|
||||
setPanel(props.api.getPanel(props.panelId));
|
||||
|
||||
return () => {
|
||||
list.forEach((l) => l.dispose());
|
||||
};
|
||||
}, [props.api, props.panelId]);
|
||||
|
||||
const [visible, setVisible] = React.useState<boolean>(true);
|
||||
|
||||
return (
|
||||
<div className="button-action">
|
||||
<div style={{ display: 'flex' }}>
|
||||
@ -29,7 +74,14 @@ const PanelAction = (props: {
|
||||
onClick={() => {
|
||||
const panel = props.api.getPanel(props.panelId);
|
||||
if (panel) {
|
||||
props.api.addFloatingGroup(panel);
|
||||
props.api.addFloatingGroup(panel, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
@ -57,6 +109,15 @@ const PanelAction = (props: {
|
||||
>
|
||||
<span className="material-symbols-outlined">close</span>
|
||||
</button>
|
||||
<button
|
||||
title="Panel visiblity cannot be edited manually."
|
||||
disabled={true}
|
||||
className="demo-icon-button"
|
||||
>
|
||||
<span className="material-symbols-outlined">
|
||||
{visible ? 'visibility' : 'visibility_off'}
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
@ -84,7 +84,7 @@ function addFloatingPanel2(api: DockviewApi) {
|
||||
id: (++panelCount).toString(),
|
||||
title: `Tab ${panelCount}`,
|
||||
component: 'default',
|
||||
floating: { width: 250, height: 150, x: 50, y: 50 },
|
||||
floating: { width: 250, height: 150, left: 50, top: 50 },
|
||||
});
|
||||
}
|
||||
|
||||
@ -259,11 +259,9 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const disposable = props.group.api.onDidLocationChange(
|
||||
(event) => {
|
||||
setFloating(event.location.type === 'floating');
|
||||
}
|
||||
);
|
||||
const disposable = props.group.api.onDidLocationChange((event) => {
|
||||
setFloating(event.location.type === 'floating');
|
||||
});
|
||||
|
||||
return () => {
|
||||
disposable.dispose();
|
||||
@ -275,7 +273,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const group = props.containerApi.addGroup();
|
||||
props.group.api.moveTo({ group });
|
||||
} else {
|
||||
props.containerApi.addFloatingGroup(props.group);
|
||||
props.containerApi.addFloatingGroup(props.group, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
import fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { argv } from 'process';
|
||||
import { execSync } from 'child_process';
|
||||
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
@ -14,7 +15,7 @@ const { version } = JSON.parse(
|
||||
|
||||
const REACT_VERSION = '18.2.0';
|
||||
const VUE_VERSION = '3.4.21';
|
||||
const DOCKVIEW_VERSION = 'latest'; // version;
|
||||
const DOCKVIEW_VERSION = version; //'latest';;
|
||||
const USE_LOCAL_CDN = argv.slice(2).includes('--local');
|
||||
|
||||
const local = 'http://localhost:1111';
|
||||
@ -35,11 +36,13 @@ const DOCKVIEW_CDN = {
|
||||
'dockview-core': `https://cdn.jsdelivr.net/npm/dockview-core@${DOCKVIEW_VERSION}/dist/dockview-core.esm.js`,
|
||||
'dockview-core/': `https://cdn.jsdelivr.net/npm/dockview-core@${DOCKVIEW_VERSION}/`,
|
||||
'dockview-vue': `https://cdn.jsdelivr.net/npm/dockview-vue@${DOCKVIEW_VERSION}/dist/dockview-vue.es.js`,
|
||||
'dockview-vue/': `https://cdn.jsdelivr.net/npm/dockview-vue@${DOCKVIEW_VERSION}/`,
|
||||
},
|
||||
local: {
|
||||
'dockview-core': `${local}/dockview-core/dist/dockview-core.esm.js`,
|
||||
'dockview-core/': `${local}/dockview-core/`,
|
||||
'dockview-vue': `${local}/dockview-vue/dist/dockview-vue.es.js`,
|
||||
'dockview-vue/': `${local}/dockview-vue/`,
|
||||
},
|
||||
},
|
||||
typescript: {
|
||||
|
@ -3,7 +3,7 @@ import { useActiveFramework } from '../frameworkSpecific';
|
||||
import BrowserOnly from '@docusaurus/BrowserOnly';
|
||||
|
||||
const BASE_SANDBOX_URL =
|
||||
'https://codesandbox.io/s/github/mathuo/dockview/tree/master/packages/docs';
|
||||
'https://codesandbox.io/s/github/mathuo/dockview/tree/gh-pages';
|
||||
|
||||
export const _CodeRunner = (props: { id: string; height: number }) => {
|
||||
const [framework] = useActiveFramework();
|
||||
|
File diff suppressed because it is too large
Load Diff
1
packages/docs/static/img/vue-icon.svg
vendored
Normal file
1
packages/docs/static/img/vue-icon.svg
vendored
Normal 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 |
@ -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"
|
||||
]
|
||||
}
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -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
|
||||
}
|
||||
}
|
@ -1,11 +1,11 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelHeaderProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -14,7 +14,7 @@ export function defaultConfig(api: DockviewApi) {
|
||||
id: 'panel_1',
|
||||
component: 'default',
|
||||
renderer: 'always',
|
||||
title: 'Panel 1',
|
||||
title: 'Panel 1'
|
||||
});
|
||||
|
||||
api.addPanel({
|
||||
|
@ -32,7 +32,14 @@ export const GroupActions = (props: {
|
||||
onClick={() => {
|
||||
const panel = props.api?.getGroup(x);
|
||||
if (panel) {
|
||||
props.api?.addFloatingGroup(panel);
|
||||
props.api?.addFloatingGroup(panel, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -32,7 +32,14 @@ export const PanelActions = (props: {
|
||||
onClick={() => {
|
||||
const panel = props.api?.getPanel(x);
|
||||
if (panel) {
|
||||
props.api?.addFloatingGroup(panel);
|
||||
props.api?.addFloatingGroup(panel, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
}}
|
||||
>
|
||||
|
@ -209,9 +209,8 @@ export const DockviewPersistence = (props: { theme?: string }) => {
|
||||
setDisableFloatingGroups((x) => !x);
|
||||
}}
|
||||
>
|
||||
{`${
|
||||
disableFloatingGroups ? 'Enable' : 'Disable'
|
||||
} floating groups`}
|
||||
{`${disableFloatingGroups ? 'Enable' : 'Disable'
|
||||
} floating groups`}
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
@ -265,7 +264,14 @@ const RightComponent = (props: IDockviewHeaderActionsProps) => {
|
||||
const group = props.containerApi.addGroup();
|
||||
props.group.api.moveTo({ group });
|
||||
} else {
|
||||
props.containerApi.addFloatingGroup(props.group);
|
||||
props.containerApi.addFloatingGroup(props.group, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewApi,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
let panelCount = 0;
|
||||
|
||||
@ -93,7 +93,14 @@ const RightAction = defineComponent({
|
||||
const group = this.params.containerApi.addGroup();
|
||||
this.group.api.moveTo({ group });
|
||||
} else {
|
||||
this.containerApi.addFloatingGroup(this.params.group);
|
||||
this.containerApi.addFloatingGroup(this.params.group, {
|
||||
position: {
|
||||
width: 400,
|
||||
height: 300,
|
||||
bottom: 50,
|
||||
right: 50,
|
||||
},
|
||||
});
|
||||
}
|
||||
},
|
||||
},
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
import './app.css';
|
||||
|
||||
const LeftAction = defineComponent({
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewApi,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
let panelCount = 0;
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,11 +1,12 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import { Prop, PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
let panelCount = 0;
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,12 +1,12 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewApi,
|
||||
DockviewReadyEvent,
|
||||
IDockviewHeaderActionsProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
let panelCount = 0;
|
||||
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
import './resize.css';
|
||||
|
||||
const Panel = defineComponent({
|
||||
|
@ -1,7 +1,6 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent } from 'dockview-core';
|
||||
import { DockviewVue, DockviewReadyEvent } from 'dockview-vue';
|
||||
|
||||
const TEXT =
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.';
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewApi,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,11 +1,11 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelHeaderProps,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,7 +1,10 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import { DockviewReadyEvent, IDockviewPanelProps } from 'dockview-core';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
@ -1,13 +1,13 @@
|
||||
import 'dockview-core/dist/styles/dockview.css';
|
||||
import 'dockview-vue/dist/styles/dockview.css';
|
||||
import { PropType, createApp, defineComponent } from 'vue';
|
||||
import { DockviewVue } from 'dockview-vue';
|
||||
import {
|
||||
DockviewVue,
|
||||
DockviewApi,
|
||||
DockviewReadyEvent,
|
||||
IDockviewPanelProps,
|
||||
IWatermarkPanelProps,
|
||||
Orientation,
|
||||
} from 'dockview-core';
|
||||
} from 'dockview-vue';
|
||||
|
||||
const Panel = defineComponent({
|
||||
name: 'Panel',
|
||||
|
Loading…
Reference in New Issue
Block a user