Merge branch 'master' of https://github.com/mathuo/dockview into 610-feature-request-dropdown-menu-to-handle-overflow-tabs-4

This commit is contained in:
mathuo 2025-01-14 21:24:37 +00:00
commit 723272d6de
No known key found for this signature in database
GPG Key ID: C6EEDEFD6CA07281
29 changed files with 319 additions and 192 deletions

View File

@ -7,12 +7,12 @@
---
[![npm version](https://badge.fury.io/js/dockview.svg)](https://www.npmjs.com/package/dockview)
[![npm](https://img.shields.io/npm/dm/dockview)](https://www.npmjs.com/package/dockview)
[![npm version](https://badge.fury.io/js/dockview-core.svg)](https://www.npmjs.com/package/dockview-core)
[![npm](https://img.shields.io/npm/dm/dockview-core)](https://www.npmjs.com/package/dockview-core)
[![CI Build](https://github.com/mathuo/dockview/workflows/CI/badge.svg)](https://github.com/mathuo/dockview/actions?query=workflow%3ACI)
[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=coverage)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=mathuo_dockview&metric=alert_status)](https://sonarcloud.io/summary/overall?id=mathuo_dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview)](https://bundlephobia.com/result?p=dockview)
[![Bundle Phobia](https://badgen.net/bundlephobia/minzip/dockview-core)](https://bundlephobia.com/result?p=dockview-core)
##

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "dockview-angular",
"version": "3.0.0",
"version": "3.0.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": "^3.0.0"
"dockview-core": "^3.0.2"
}
}

View File

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

View File

@ -5021,6 +5021,78 @@ describe('dockviewComponent', () => {
expect(panel3.api.location.type).toBe('grid');
});
test('grid -> floating -> popout -> floating', async () => {
const container = document.createElement('div');
window.open = () => setupMockWindow();
const dockview = new DockviewComponent(container, {
createComponent(options) {
switch (options.name) {
case 'default':
return new PanelContentPartTest(
options.id,
options.name
);
default:
throw new Error(`unsupported`);
}
},
});
dockview.layout(1000, 500);
const panel1 = dockview.addPanel({
id: 'panel_1',
component: 'default',
});
const panel2 = dockview.addPanel({
id: 'panel_2',
component: 'default',
});
const panel3 = dockview.addPanel({
id: 'panel_3',
component: 'default',
position: { direction: 'right' },
});
expect(panel1.api.location.type).toBe('grid');
expect(panel2.api.location.type).toBe('grid');
expect(panel3.api.location.type).toBe('grid');
dockview.addFloatingGroup(panel2.group);
expect(panel1.api.location.type).toBe('floating');
expect(panel2.api.location.type).toBe('floating');
expect(panel3.api.location.type).toBe('grid');
await dockview.addPopoutGroup(panel2.group);
expect(panel1.api.location.type).toBe('popout');
expect(panel2.api.location.type).toBe('popout');
expect(panel3.api.location.type).toBe('grid');
dockview.addFloatingGroup(panel2.group);
expect(panel1.api.location.type).toBe('floating');
expect(panel2.api.location.type).toBe('floating');
expect(panel3.api.location.type).toBe('grid');
await dockview.addPopoutGroup(panel2.group);
expect(panel1.api.location.type).toBe('popout');
expect(panel2.api.location.type).toBe('popout');
expect(panel3.api.location.type).toBe('grid');
panel2.group.api.moveTo({ group: panel3.group });
expect(panel1.api.location.type).toBe('grid');
expect(panel2.api.location.type).toBe('grid');
expect(panel3.api.location.type).toBe('grid');
});
test('that panel is rendered when moving from popout to new group', async () => {
const container = document.createElement('div');

View File

@ -105,6 +105,21 @@ class ClassUnderTest extends BaseGrid<TestPanel> {
}
describe('baseComponentGridview', () => {
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const cut = new ClassUnderTest(container, {
orientation: Orientation.HORIZONTAL,
proportionalLayout: true,
});
cut.dispose();
expect(container.parentElement).toBe(root);
});
test('that .layout(...) force flag works', () => {
const cut = new ClassUnderTest(document.createElement('div'), {
orientation: Orientation.HORIZONTAL,

View File

@ -67,6 +67,27 @@ describe('componentPaneview', () => {
container.className = 'container';
});
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.dispose();
expect(container.parentElement).toBe(root);
});
test('vertical panels', () => {
const disposables = new CompositeDisposable();
@ -293,40 +314,6 @@ describe('componentPaneview', () => {
disposable.dispose();
});
test('dispose of paneviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
paneview.layout(1000, 1000);
paneview.addPanel({
id: 'panel1',
component: 'default',
title: 'Panel 1',
});
paneview.addPanel({
id: 'panel2',
component: 'default',
title: 'Panel 2',
});
expect(container.childNodes.length).toBeGreaterThan(0);
paneview.dispose();
expect(container.childNodes.length).toBe(0);
});
test('panel is disposed of when component is disposed', () => {
const paneview = new PaneviewComponent(container, {
createComponent: (options) => {
@ -606,10 +593,10 @@ describe('componentPaneview', () => {
className: 'test-a test-b',
});
expect(paneview.element.className).toBe('container test-a test-b');
expect(paneview.element.className).toBe('test-a test-b');
paneview.updateOptions({ className: 'test-b test-c' });
expect(paneview.element.className).toBe('container test-b test-c');
expect(paneview.element.className).toBe('test-b test-c');
});
});

View File

@ -26,6 +26,28 @@ describe('componentSplitview', () => {
container.className = 'container';
});
test('that the container is not removed when grid is disposed', () => {
const root = document.createElement('div');
const container = document.createElement('div');
root.appendChild(container);
const splitview = new SplitviewComponent(container, {
orientation: Orientation.VERTICAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.dispose();
expect(container.parentElement).toBe(root);
});
test('event leakage', () => {
Emitter.setLeakageMonitorEnabled(true);
@ -451,39 +473,6 @@ describe('componentSplitview', () => {
disposable.dispose();
});
test('dispose of splitviewComponent', () => {
expect(container.childNodes.length).toBe(0);
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
createComponent: (options) => {
switch (options.name) {
case 'default':
return new TestPanel(options.id, options.name);
default:
throw new Error('unsupported');
}
},
});
splitview.layout(1000, 1000);
splitview.addPanel({
id: 'panel1',
component: 'default',
});
splitview.addPanel({
id: 'panel2',
component: 'default',
});
expect(container.childNodes.length).toBeGreaterThan(0);
splitview.dispose();
expect(container.childNodes.length).toBe(0);
});
test('panel is disposed of when component is disposed', () => {
const splitview = new SplitviewComponent(container, {
orientation: Orientation.HORIZONTAL,
@ -736,10 +725,10 @@ describe('componentSplitview', () => {
className: 'test-a test-b',
});
expect(splitview.element.className).toBe('container test-a test-b');
expect(splitview.element.className).toBe('test-a test-b');
splitview.updateOptions({ className: 'test-b test-c' });
expect(splitview.element.className).toBe('container test-b test-c');
expect(splitview.element.className).toBe('test-b test-c');
});
});

View File

@ -370,8 +370,8 @@ export class DockviewComponent
return this._floatingGroups;
}
constructor(parentElement: HTMLElement, options: DockviewComponentOptions) {
super(parentElement, {
constructor(container: HTMLElement, options: DockviewComponentOptions) {
super(container, {
proportionalLayout: true,
orientation: Orientation.HORIZONTAL,
styles: options.hideBorders
@ -850,6 +850,20 @@ export class DockviewComponent
this.overlayRenderContainer;
returnedGroup = group;
const alreadyRemoved = !this._popoutGroups.find(
(p) => p.popoutGroup === group
);
if (alreadyRemoved) {
/**
* If this popout group was explicitly removed then we shouldn't run the additional
* steps. To tell if the running of this disposable is the result of this popout group
* being explicitly removed we can check if this popout group is still referenced in
* the `this._popoutGroups` list.
*/
return;
}
if (floatingBox) {
this.addFloatingGroup(group, {
height: floatingBox.height,

View File

@ -155,7 +155,7 @@ export abstract class BaseGrid<T extends IGridPanelView>
this.gridview.locked = value;
}
constructor(parentElement: HTMLElement, options: BaseGridOptions) {
constructor(container: HTMLElement, options: BaseGridOptions) {
super(document.createElement('div'), options.disableAutoResizing);
this.element.style.height = '100%';
this.element.style.width = '100%';
@ -163,7 +163,8 @@ export abstract class BaseGrid<T extends IGridPanelView>
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
parentElement.appendChild(this.element);
// the container is owned by the third-party, do not modify/delete it
container.appendChild(this.element);
this.gridview = new Gridview(
!!options.proportionalLayout,

View File

@ -113,8 +113,8 @@ export class GridviewComponent
this._deserializer = value;
}
constructor(parentElement: HTMLElement, options: GridviewComponentOptions) {
super(parentElement, {
constructor(container: HTMLElement, options: GridviewComponentOptions) {
super(container, {
proportionalLayout: options.proportionalLayout ?? true,
orientation: options.orientation,
styles: options.hideBorders

View File

@ -195,8 +195,10 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
return this._options;
}
constructor(parentElement: HTMLElement, options: PaneviewComponentOptions) {
super(parentElement, options.disableAutoResizing);
constructor(container: HTMLElement, options: PaneviewComponentOptions) {
super(document.createElement('div'), options.disableAutoResizing);
this.element.style.height = '100%';
this.element.style.width = '100%';
this.addDisposables(
this._onDidLayoutChange,
@ -210,6 +212,9 @@ export class PaneviewComponent extends Resizable implements IPaneviewComponent {
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
// the container is owned by the third-party, do not modify/delete it
container.appendChild(this.element);
this._options = options;
this.paneview = new Paneview(this.element, {

View File

@ -32,7 +32,7 @@ export interface ISplitviewStyles {
}
export interface SplitViewOptions {
orientation: Orientation;
orientation?: Orientation;
descriptor?: ISplitViewDescriptor;
proportionalLayout?: boolean;
styles?: ISplitviewStyles;
@ -225,7 +225,7 @@ export class Splitview {
private readonly container: HTMLElement,
options: SplitViewOptions
) {
this._orientation = options.orientation;
this._orientation = options.orientation ?? Orientation.VERTICAL;
this.element = this.createContainer();
this.margin = options.margin ?? 0;

View File

@ -155,15 +155,17 @@ export class SplitviewComponent
: this.splitview.orthogonalSize;
}
constructor(
parentElement: HTMLElement,
options: SplitviewComponentOptions
) {
super(parentElement, options.disableAutoResizing);
constructor(container: HTMLElement, options: SplitviewComponentOptions) {
super(document.createElement('div'), options.disableAutoResizing);
this.element.style.height = '100%';
this.element.style.width = '100%';
this._classNames = new Classnames(this.element);
this._classNames.setClassNames(options.className ?? '');
// the container is owned by the third-party, do not modify/delete it
container.appendChild(this.element);
this._options = options;
this.splitview = new Splitview(this.element, options);

View File

@ -1,6 +1,6 @@
{
"name": "dockview-react",
"version": "3.0.0",
"version": "3.0.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": "^3.0.0"
"dockview": "^3.0.2"
}
}

View File

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

View File

@ -1,6 +1,6 @@
{
"name": "dockview",
"version": "3.0.0",
"version": "3.0.2",
"description": "Zero dependency layout manager supporting tabs, grids and splitviews",
"keywords": [
"splitview",
@ -54,7 +54,7 @@
"test:cov": "cross-env ../../node_modules/.bin/jest --selectProjects dockview --coverage"
},
"dependencies": {
"dockview-core": "^3.0.0"
"dockview-core": "^3.0.2"
},
"peerDependencies": {
"react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0"

View File

@ -318,11 +318,7 @@ export const DockviewReact = React.forwardRef(
}, [props.prefixHeaderActionsComponent]);
return (
<div
className={props.className}
style={{ height: '100%', width: '100%' }}
ref={domRef}
>
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
{portals}
</div>
);

View File

@ -126,11 +126,7 @@ export const GridviewReact = React.forwardRef(
}, [props.components]);
return (
<div
className={props.className}
style={{ height: '100%', width: '100%' }}
ref={domRef}
>
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
{portals}
</div>
);

View File

@ -176,11 +176,7 @@ export const PaneviewReact = React.forwardRef(
}, [props.onDidDrop]);
return (
<div
className={props.className}
style={{ height: '100%', width: '100%' }}
ref={domRef}
>
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
{portals}
</div>
);

View File

@ -126,11 +126,7 @@ export const SplitviewReact = React.forwardRef(
}, [props.components]);
return (
<div
className={props.className}
style={{ height: '100%', width: '100%' }}
ref={domRef}
>
<div style={{ height: '100%', width: '100%' }} ref={domRef}>
{portals}
</div>
);

View File

@ -0,0 +1,17 @@
---
slug: dockview-3.0.1-release
title: Dockview 3.0.1
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
## 🛠 Miscs
## 🔥 Breaking changes
- Fix duplicate HTML element [#810](https://github.com/mathuo/dockview/issues/818)

View File

@ -0,0 +1,18 @@
---
slug: dockview-3.0.2-release
title: Dockview 3.0.2
tags: [release]
---
# Release Notes
Please reference docs @ [dockview.dev](https://dockview.dev).
## 🚀 Features
## 🛠 Miscs
## 🔥 Breaking changes
- Fix issue when transitioning panel from floating to popout to floating [#810](https://github.com/mathuo/dockview/issues/824)
- Fix duplicate HTML containers [#825](https://github.com/mathuo/dockview/pull/825)

View File

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

View File

@ -176,75 +176,92 @@ const DockviewDemo = (props: { theme?: string }) => {
setPending([]);
}, [pending]);
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
event.api.onDidAddPanel((event) => {
setPanels((_) => [..._, event.id]);
addLogLine(`Panel Added ${event.id}`);
});
event.api.onDidActivePanelChange((event) => {
setActivePanel(event?.id);
addLogLine(`Panel Activated ${event?.id}`);
});
event.api.onDidRemovePanel((event) => {
setPanels((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
addLogLine(`Panel Removed ${event.id}`);
});
event.api.onDidAddGroup((event) => {
setGroups((_) => [..._, event.id]);
addLogLine(`Group Added ${event.id}`);
});
event.api.onDidMovePanel((event) => {
addLogLine(`Panel Moved ${event.panel.id}`);
});
event.api.onDidMaximizedGroupChange((event) => {
addLogLine(
`Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]`
);
});
event.api.onDidRemoveGroup((event) => {
setGroups((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
addLogLine(`Group Removed ${event.id}`);
});
event.api.onDidActiveGroupChange((event) => {
setActiveGroup(event?.id);
addLogLine(`Group Activated ${event?.id}`);
});
const state = localStorage.getItem('dv-demo-state');
if (state) {
try {
event.api.fromJSON(JSON.parse(state));
return;
} catch {
localStorage.removeItem('dv-demo-state');
}
React.useEffect(() => {
if (!api) {
return;
}
defaultConfig(event.api);
const disposables = [
api.onDidAddPanel((event) => {
setPanels((_) => [..._, event.id]);
addLogLine(`Panel Added ${event.id}`);
}),
api.onDidActivePanelChange((event) => {
setActivePanel(event?.id);
addLogLine(`Panel Activated ${event?.id}`);
}),
api.onDidRemovePanel((event) => {
setPanels((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
addLogLine(`Panel Removed ${event.id}`);
}),
api.onDidAddGroup((event) => {
setGroups((_) => [..._, event.id]);
addLogLine(`Group Added ${event.id}`);
}),
api.onDidMovePanel((event) => {
addLogLine(`Panel Moved ${event.panel.id}`);
}),
api.onDidMaximizedGroupChange((event) => {
addLogLine(
`Group Maximized Changed ${event.group.api.id} [${event.isMaximized}]`
);
}),
api.onDidRemoveGroup((event) => {
setGroups((_) => {
const next = [..._];
next.splice(
next.findIndex((x) => x === event.id),
1
);
return next;
});
addLogLine(`Group Removed ${event.id}`);
}),
api.onDidActiveGroupChange((event) => {
setActiveGroup(event?.id);
addLogLine(`Group Activated ${event?.id}`);
}),
];
const loadLayout = () => {
const state = localStorage.getItem('dv-demo-state');
if (state) {
try {
api.fromJSON(JSON.parse(state));
return;
} catch {
localStorage.removeItem('dv-demo-state');
}
return;
}
defaultConfig(api);
};
loadLayout();
return () => {
disposables.forEach((disposable) => disposable.dispose());
};
}, [api]);
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
};
const [watermark, setWatermark] = React.useState<boolean>(false);
@ -359,7 +376,6 @@ const DockviewDemo = (props: { theme?: string }) => {
style={{
flexGrow: 1,
overflow: 'hidden',
height: '100%',
display: 'flex',
}}
>

View File

@ -88,7 +88,6 @@ const GroupAction = (props: {
}
onClick={() => {
if (group) {
props.api.addFloatingGroup(group, {
width: 400,
height: 300,
@ -99,7 +98,6 @@ const GroupAction = (props: {
right: 50,
},
});
}
}}
>

View File

@ -27,7 +27,7 @@ const ThemeToggle: React.FC = () => {
value={theme}
>
{themeConfig.map((theme) => {
return <option>{theme.id}</option>;
return <option key={theme.id}>{theme.id}</option>;
})}
</select>
</div>

View File

@ -3,5 +3,9 @@ import { RecoilRoot } from 'recoil';
// Default implementation, that you can customize
export default function Root({ children }) {
return <RecoilRoot>{children}</RecoilRoot>;
return (
<React.StrictMode>
<RecoilRoot>{children}</RecoilRoot>
</React.StrictMode>
);
}

View File

@ -124,6 +124,11 @@ const DockviewDemo = (props: { theme?: string }) => {
const onReady = (event: DockviewReadyEvent) => {
setApi(event.api);
setPanels([]);
setGroups([]);
setActivePanel(undefined);
setActiveGroup(undefined);
addLogLine(`Dockview Is Ready`);
};
React.useEffect(() => {